TGViewer
C++ - Reddit C++ - Reddit @r_cpp · 229 subscribers
Post #25691 15
while (...) ...
case lex_kind::kReturn: return parse_return_statement(); // return ...;
default: return parse_expression_stmt(); /// ...;
}
}

constexpr auto parse_return_statement() -> parse_result {
if (!match(lex_kind::kReturn)) return make_error("Expected 'return'");

index_t expr_idx = empty_node; // empty_node = -1
if (auto next = peek(); next && next->kind != lex_kind::kSemicolon) {
auto expr = parse_expression(); // another recursive call
if (!expr) return std::unexpected{expr.error()};
expr_idx = *expr;
}

if (!match(lex_kind::kSemicolon)) return make_error("Expected ';' after return");
return m_pool.add(stmt_return{expr_idx});
}
```
parse_return_statement goes into parse_expression, that goes into parse_assigment, that goes into parse_logical_or, that goes... Well, you got it. That's how operator priority works here.

### Their Majesty Compiler (and analyser)
I may have cheated here a bit.

Before we even write a compiler, we must know for what architecture we do it. x86, ARM or even JVM.
Initially when I was working on a similar project, I planned to generate raw assembly for x86 (last versions of Clang and GCC support passing `constexpr std::string_view` into `asm(...)` statement), but honestly writing a compiler for a zoo of x86 instructions is the right way to madhouse.

And even so, if we downgrade our compiler we won't have nice constexpr asm anymore. And we can't also generate raw machine instructions because of DEP (data execution prevention). We'll have to call non-crossplatform `mmap` or `VirtualAlloc` to allocate some memory, copy the code there... Good riddance cross platform build compler, hello Windows Defender that will kill our app for such tricks with memory.

So where have I cheated? I made my own architecture that will execute inside a VM. A stack VM.
Why stack it? It turned out to be incredibly easy to generate the bytecode for. If you are doing a register architecture (as in processors or Lua), then you will have to write register allocation algorithms (it is difficult). And in the stack everything is much simpler.

If we need to sum A and B we just do this:
1. Put A into the stack.
2. Put B into the stack.
3. Execute sum instruction. It takes these two values and puts back their sum.

So I had this set of instructions at the end:
```cpp
enum class op_code : char {
// Loads/saves locals to/from the stack (variables).
lload, lsave,

i64_const, // Puts a constant onto the stack

// Math
i64_add, i64_sub, i64_mul, i64_div,

// Puts 1 if values are equal (i made <, <= later)
i64_cmp,

jmp, // jumps by offset
jmpz, // conditional jump by offset, only when 0 on the stack

call, // calls a function
ret, // returns from the function

trap, // calls a native C++ function
};
```

### So what about the analysis?
In classical compilers phases are strictly splitted: lexers builds tokens, parser builds tree, semantic analyser checks the types and variables, then optimisations, then codegen and then optimisations again.

As you can remember, I'm pretty lazy. And keep in mind that `constexpr` ops are not infinite. I didn't want to make a separate pipeline phase. So my compiler combines these two functions: semantic analysis and code generation.

They usually call it Single-Pass Compilation, but it's not really the case here, since it's only about these two phases. My compiler is a bit hybrid.

My compiler recursively walks the tree and does two things:
1. Checks the semantics: "was this variable declared and what's its type" before we even try to multiply something. Do function param types match? Does this function even exist? Etc.
2. Generates the byte-code. If semantics is ok, then we just write corresponding instructions immediately into the `std::vector<std::byte>` (the one we're going to elegantly extract via `to_array`).
And in the result we receive a ready, semantically-correct and absolute safe (let's pretend that I wrote the compiler bug-free, huh) byte-code that we feed to the
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 →