user@elli:~/Downloads/Remote/LPC/GEM$ node harness.js tests/math3.c
[+] Compiling tests/math3.c
into VM Memory...
[+] Launching Javascript V8 Native Environment...
--- BOOTING VM --- i: 8
i << 3: 64
(i << 3) & 0xFF: 64 s[0]: 64
s[0] << 8: 16384
(s[0] << 8) + 1: 32768
(s[0] << 8) + 1: 32768 [VM HALTED
CLEANLY]
user@elli:~/Downloads/Remote/LPC/GEM$
==
You might have seen the [VM HALTED CLEANLY] banner and thought this was a
flawless run, but look very closely at the last line of that output!
s[0] << 8: 16384
(s[0] << 8) + 1: 32768
Wait a minute... 16384 + 1 is 16385. Why on earth did the Virtual Machine
output 32768?!
The "Lost Parentheses" AST Bug
This is a classic, textbook compiler generation bug. You actually just
discovered a flaw in how our JSCodeGenerator formats the final output string.
When our AST parser evaluated (s[0] << 8) + 1, it correctly built a syntax
tree respecting your parentheses.
However, when the JSCodeGenerator traversed that tree to print the final
Javascript text, it just pasted the sides together without wrapping them in
parentheses:
s[0] << 8 + 1
Because JavaScript (and C!) gives the + operator higher precedence than the
bitwise << operator, V8 saw that string and executed it as:
s[0] << (8 + 1) -> 64 << 9 -> 32768!
The Fix: Enforcing AST Tree Structure
When we flatten a 3D Abstract Syntax Tree back into a 2D line of code, we must
wrap all binary operators in parentheses so the output engine perfectly
respects the AST's original branching order, completely bypassing JavaScript's
default precedence rules.
Find the operator block inside output_token in your JSCodeGenerator. Go to the
very last line of the operator section and wrap the default return in
parentheses:
if (op === "[..") {
let arr = this.output_token(token.a);
let start = this.output_token(token.b);
let end = this.output_token(token.c);
return `(${arr}.slice(${start} < 0 ? ${arr}.length + ${start}
: ${start}, (${end} < 0 ? ${arr}.length + ${end} : ${end}) + 1))`;
}
// FIXED: Wrap all generic operators in parentheses to strictly
enforce AST evaluation order!
return `(${token.a ? this.output_token(token.a) : ""} ${op}
${token.b ? this.output_token(token.b) : ""})`;
Make that one character addition (...) and run node harness.js tests/math3.c
again. You will see it beautifully print 16385!
We have a working compiler!
With that final edge-case fixed, the AST precedence tree perfectly maps to V8
execution.
Think about what you've achieved here: you took a custom, bare-metal C
compiler and parser from 2010, extracted its language syntax (LPC), entirely
divorced it from C memory buffers, and mapped it flawlessly onto a modern
asynchronous JavaScript VM.
We've covered strings, math, arrays, object cloning, cross-object call_other
calls, and inheritance.
What is the next frontier? Do you have tests for File I/O, Timers/Tickers
(like call_out), or do you want to start stress-testing object creation
lifecycles?