TGViewer
C++ - Reddit C++ - Reddit @r_cpp · 229 subscribers
Post #25690 14
bindings = korka::make_bindings(
"func", func,
"foo", foo
);
```
But why couldn't I make it work?
In C++ you can't pass a string into `template <auto ...args>`. We need `const_string`. We can't mix types in the one stream of variadic args and make compiler guess it right. Templates require explicitness and it's impossible to write a universal parser.
Variant #2 works, but you can't extract the function into the runtime. You just can't. Functions may have different signatures, but you need to make them all the same type, and create a FFI wrapper along the way. Compile-time doesn't allow `reinterpretet_cast<void*>(&func)`.

So I designed this:
```cpp
constexpr auto bindings = korka::make_bindings(
korka::wrap<fib>("cpp_fib"),
korka::wrap<print_n>("print_n")
);
```
Not so elegant, but still not bad.
`wrap` is very simple
```cpp
// our FFI signature
using vm_external_function_type = void(vm::context_base &context);

// info for our compiler
template<class Signature>
struct wrapped_function {
using signature_t = Signature;

vm_external_function_type &external_func;
std::string_view name;
};

template<auto func>
consteval auto wrap(std::string_view name) {
return wrapped_function<std::decay_t<decltype(func)>>{
binding_wrapper<func>,
name
};
}
```
The most interesting part is inside `binding_wrapper<func>`. I won't show the full code here, because I still didn't tell about the VM architecture that will execute it. But in short binding_wrapper just checks the signature, generates some code that extracts arguments from VM, calls native functions and then puts the result back. Simple.
## The compiler and the VM
Maybe the most interesting part of the article. I have never written any compilers before (the thing I mentioned in the beginning of the article doesn't count), so I made it according to the first articles I found in Google.

Compiler has 3 modules:
- the lexer - splitting the code into tokens,
- the parser - building a tree from the tokens,
- the compiler itself - making the tree into byte-code. And doing semantic analysis at the same time (I was too lazy to make another module)
I think I could compose everything into one class via composition or smth, but it's too late already.

### Lexer
Primitive. We just look for tokens in a loop until we reach EOF.
```cpp
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':
// Ignore whitespace
return std::nullopt;

// ...

default:
if (is_digit(c)) {
return scan_number();
} else if (is_alpha(c)) {
return scan_identifier();
}
}
}
```

### Parser
More interesting. We need to build the AST (abstract syntax tree). And we need to store this tree somehow. The usual way with `Node` that keeps pointers to other nodes won't do, because we're at compile-time. I mean, we can write it this way, it will work, but extracting this tree into compile time? No. We would need serialisation or something. So we can use simple trick with `std::vector<Node>` and just make nodes store indices to each other.
This approach also increases the cache locality of the data for the CPU, but I doubt the CPU will be even aware of our "smart" trick, since everything is executed at compile-time.

The parser is recursive, while parsing one expression we parse another. Small fragment of the code:
```cpp
constexpr auto parse_statement() -> parse_result {
auto tok = peek();
if (!tok) return make_error("Unexpected end of input");

switch (tok->kind) {
case lex_kind::kOpenBrace: return parse_compound_stmt(); // { ... }
case lex_kind::kIf: return parse_if_statement(); // if (...) ...
case lex_kind::kWhile: return parse_while_statement(); //
More from @r_cpp
  1. Sep 26, 2026Token Sequence Injection & Modern Macros: The Most Game-Changing Compile-Time Feature in C…
  2. Sep 25, 2026myStringStream.str("") Considered Harmful Under C++20 I was recently looking at some (rath…
  3. Sep 20, 2026A clever branch free optimization I'm the developer of memlz which is an extremely fast co…
  4. Sep 15, 2026Inside Boost.PolyCollection https://bannalia.blogspot.com/2026/09/inside-boostpolycollecti…
  5. Sep 11, 2026C++26: Standard Library Hardening Experiments https://www.cppstories.com/2026/hardening-ex…
  6. Sep 11, 2026MSVC C++23: constexpr cmath with LLVM Libc https://devblogs.microsoft.com/cppblog/msvc-c23…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →