在C++编译时实现类C语言编译器
一款C语言子集的编译器已被开发出来,在C++的constexpr上下文中实现。源代码被转换为自定义字节码,在运行时于虚拟机中执行。这种方法确保了零解释器开销和完全的类型安全。
优势包括二进制文件中无编译器代码、无需运行时编译,以及防止函数签名不匹配。缺点:缺乏LLVM级别的优化,无法热重载。
绕过constexpr限制
C++20通过动态内存分配和对std::vector的支持扩展了constexpr,但结果无法直接传递到运行时。需要模板包装器。
通过模板传递字符串
字符串字面量通过const_string模板类传递:
template<std::size_t N>
struct const_string {
constexpr const_string() = default;
constexpr const_string(const char (&str)[N]) {
std::copy_n(str, N, value);
}
constexpr operator std::string_view() const {
return {value, value + N - 1};
}
char value[N]{};
const std::size_t length = N;
};
// 用法:
template<const_string str>
auto very_smart_function(...) { /* ... */ }
从constexpr提取数组
将std::vector转换为std::array需要通过lambda函数在编译时固定大小:
template<auto data_getter>
constexpr auto to_array() {
using value_type = typename decltype(data_getter())::value_type;
constexpr static std::size_t size = data_getter().size();
std::array<value_type, size> out;
auto in = data_getter();
for (std::size_t i = 0; i < size; ++i) {
out[i] = in[i];
}
return out;
}
template<const_string str>
constexpr auto lex() {
constexpr static auto data_getter = [] constexpr {
return lexer{static_cast<std::string_view>(str)}.lex();
};
return to_array<data_getter>();
}
处理编译错误
在C++26之前,static_assert需要字符串字面量。解决方案是使用ErrorMessage模板,错误文本在参数中:
template<const_string Msg>
struct ErrorMessage {
static_assert(false, "检查模板参数以获取详细信息");
};
template<auto err_getter>
consteval auto report_error() -> void {
#ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT
static_assert(false, to_string(err_getter()));
#else
constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>();
std::ignore = ErrorMessage<msg>{};
#endif
}
C++26中的输出:词法分析器错误:第12行未终止的字符串。
映射函数签名
为了匹配字符串名称与签名类型,使用了哈希和重载:
template<auto function_info_getter, std::size_t... Is>
struct signature_mapper<function_info_getter, std::index_sequence<Is...>> {
consteval static auto hash(auto &&v) -> std::size_t {
return frozen::elsa<std::string_view>{}(v, 0);
}
constexpr static auto _overloaded = overloaded{
([] (unique_type<hash(function_info_getter(Is).name)>)
-> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * {
return nullptr;
})...
};
template<const_string name>
using get_signature_t = std::remove_pointer_t<decltype(_overloaded(unique_type<hash(name)>{}))>;
};
提取:compile_result.function<"fib">();。
原生函数绑定
通过wrapped_function将C++函数与脚本语言链接:
consteval auto wrap(std::string_view name) {
return wrapped_function<std::decay_t<decltype(func)>>{
binding_wrapper<func>,
name
};
}
constexpr auto bindings = korka::make_bindings(
korka::wrap<fib>("cpp_fib"),
korka::wrap<print_n>("print_n")
);
binding_wrapper生成代码以从虚拟机提取参数并返回结果。
编译器架构
分为三个阶段:
- 词法分析器:在字符循环中进行标记化
- 解析器:AST构建
- 字节码生成器:带有语义分析的发射
示例词法分析器:
constexpr auto scan_token() -> std::optional<std::expected<lex_token, error_t>> {
char c = advance();
switch (c) {
case '{': return make_token(lex_kind::kOpenBrace);
case '}': return make_token(lex_kind::kCloseBrace);
case '(': return make_token(lex_kind::kOpenParenthesis);
case ')': /* ... */
case ' ': case '\r': case '\t': return std::nullopt;
}
}
关键要点
- 编译器完全为constexpr,字节码嵌入二进制文件中
- 通过const_string和lambda包装器绕过C++20限制
- 使用名称哈希进行类型安全的函数提取
- 带有自动虚拟机代码生成的C++函数绑定
- 无优化的基本词法分析器/解析器
— Editorial Team
暂无评论。