Archived
5c05ce9b99
Today's milestone: dist/platform/elc compiles itself byte-for-byte to
itself (stage1 == stage2 == stage3 verified). The compiler is now a
real binary in the world.
What landed
- Spec rewrite (language.md) to truth — every feature marked
implemented / planned / not-in-this-language with no fiction.
- C runtime extension: 51 new builtins. JSON parser + accessors,
time, UUID, env, in-process state K/V, float formatting + math,
string ops (index_of, split, char_at, char_code, pad_left/right,
format), list ops (push, push_front, join, range), bool_to_str.
Runtime grew 631 → 1611 lines, header 171 → 247.
- Codegen fix: transform_implicit_return lifts a function's bare
trailing expression into an explicit return. Without it, lex(),
parse(), and every other implicit-return function returned 0/nil
and the whole pipeline produced empty C output.
- Codegen fix: index expressions dispatch on AST kind. obj["literal"]
→ el_get_field (map), arr[i] → el_list_get (list). Same Index node
in the parser, two different runtime calls.
- Codegen fix: skip emitting fn main() (collides with C main()) and
honor parsed return-type annotations so Void functions don't get
return-wrapped (return println(x) is a C type error).
- Parser: capture return-type identifier from -> Ret annotations.
- Lexer: + vessel keyword, + % operator, + \r escape.
- Runtime fix: el_list_append now allocates a fresh list rather than
realloc'ing the input. Realloc moved blocks made caller pointers
dangle, which was inserting garbage values into declared lists and
causing strcmp segfaults. Persistent allocation eliminates the
whole class of use-after-free at modest memory cost.
Bootstrap path
- One-shot Python helper translated elc-combined.el to C and
produced stage1. Helper is disposable; not committed.
- stage1 compiles elc-combined.el → stage2.c which cc compiles to
stage2; stage2 compiles elc-combined.el → stage3.c. stage2.c and
stage3.c are byte-identical. Closure proven.
- New elc installed at dist/platform/elc; old broken binary
preserved as dist/platform/elc.legacy.
- dist/platform/elc.c is the canonical generated source.
- elvm and the bytecode pipeline are no longer on the critical path.
Known gap
- The `+` operator's heuristic dispatch still picks string concat
when both operands are Idents with no literal anchor. Self-hosting
works because the compiler source is careful, but `fn add(a:Int,
b:Int) { a + b }` will not do arithmetic until codegen reads the
parsed type annotations to dispatch. Fix is wiring; not done here.
Tested
- tiny / lextest / whiletest / map+field / array build all run.
- cgi-studio (1037 lines real El) compiles to C cleanly. Link fails
only because runtime is missing fs_list, json_encode, llm_*; those
are scheduled batches.
- Three-stage closure (stage1 vs stage2 vs stage3) byte-identical.
2082 lines
78 KiB
EmacsLisp
2082 lines
78 KiB
EmacsLisp
// elc-combined.el — El self-hosting compiler, single-file bootstrap edition
|
|
// Inlines lexer + parser + codegen — CLI entry at end.
|
|
|
|
// lexer.el — el self-hosting lexer
|
|
//
|
|
// Tokenises an el source string into a list of token maps.
|
|
// Each token is a Map<String, Any> with keys:
|
|
// "kind" -> String (e.g. "Int", "Ident", "Plus")
|
|
// "value" -> String (the raw text of the token)
|
|
//
|
|
// Entry point: fn lex(source: String) -> [Map<String, Any>]
|
|
//
|
|
// Uses native_string_chars to split the source into a chars list,
|
|
// then indexes it with native_list_get — avoids O(N²) string cloning.
|
|
|
|
// ── Character helpers ─────────────────────────────────────────────────────────
|
|
|
|
fn is_digit(ch: String) -> Bool {
|
|
if ch == "0" { return true }
|
|
if ch == "1" { return true }
|
|
if ch == "2" { return true }
|
|
if ch == "3" { return true }
|
|
if ch == "4" { return true }
|
|
if ch == "5" { return true }
|
|
if ch == "6" { return true }
|
|
if ch == "7" { return true }
|
|
if ch == "8" { return true }
|
|
if ch == "9" { return true }
|
|
false
|
|
}
|
|
|
|
fn is_alpha(ch: String) -> Bool {
|
|
if ch == "a" { return true }
|
|
if ch == "b" { return true }
|
|
if ch == "c" { return true }
|
|
if ch == "d" { return true }
|
|
if ch == "e" { return true }
|
|
if ch == "f" { return true }
|
|
if ch == "g" { return true }
|
|
if ch == "h" { return true }
|
|
if ch == "i" { return true }
|
|
if ch == "j" { return true }
|
|
if ch == "k" { return true }
|
|
if ch == "l" { return true }
|
|
if ch == "m" { return true }
|
|
if ch == "n" { return true }
|
|
if ch == "o" { return true }
|
|
if ch == "p" { return true }
|
|
if ch == "q" { return true }
|
|
if ch == "r" { return true }
|
|
if ch == "s" { return true }
|
|
if ch == "t" { return true }
|
|
if ch == "u" { return true }
|
|
if ch == "v" { return true }
|
|
if ch == "w" { return true }
|
|
if ch == "x" { return true }
|
|
if ch == "y" { return true }
|
|
if ch == "z" { return true }
|
|
if ch == "A" { return true }
|
|
if ch == "B" { return true }
|
|
if ch == "C" { return true }
|
|
if ch == "D" { return true }
|
|
if ch == "E" { return true }
|
|
if ch == "F" { return true }
|
|
if ch == "G" { return true }
|
|
if ch == "H" { return true }
|
|
if ch == "I" { return true }
|
|
if ch == "J" { return true }
|
|
if ch == "K" { return true }
|
|
if ch == "L" { return true }
|
|
if ch == "M" { return true }
|
|
if ch == "N" { return true }
|
|
if ch == "O" { return true }
|
|
if ch == "P" { return true }
|
|
if ch == "Q" { return true }
|
|
if ch == "R" { return true }
|
|
if ch == "S" { return true }
|
|
if ch == "T" { return true }
|
|
if ch == "U" { return true }
|
|
if ch == "V" { return true }
|
|
if ch == "W" { return true }
|
|
if ch == "X" { return true }
|
|
if ch == "Y" { return true }
|
|
if ch == "Z" { return true }
|
|
false
|
|
}
|
|
|
|
fn is_alnum_or_underscore(ch: String) -> Bool {
|
|
if is_digit(ch) { return true }
|
|
if is_alpha(ch) { return true }
|
|
if ch == "_" { return true }
|
|
false
|
|
}
|
|
|
|
fn is_whitespace(ch: String) -> Bool {
|
|
if ch == " " { return true }
|
|
if ch == "\t" { return true }
|
|
if ch == "\n" { return true }
|
|
if ch == "\r" { return true }
|
|
false
|
|
}
|
|
|
|
fn make_tok(kind: String, value: String) -> Map<String, Any> {
|
|
{ "kind": kind, "value": value }
|
|
}
|
|
|
|
// ── Keyword lookup ────────────────────────────────────────────────────────────
|
|
|
|
fn keyword_kind(word: String) -> String {
|
|
if word == "let" { return "Let" }
|
|
if word == "fn" { return "Fn" }
|
|
if word == "type" { return "Type" }
|
|
if word == "enum" { return "Enum" }
|
|
if word == "match" { return "Match" }
|
|
if word == "return" { return "Return" }
|
|
if word == "if" { return "If" }
|
|
if word == "else" { return "Else" }
|
|
if word == "for" { return "For" }
|
|
if word == "in" { return "In" }
|
|
if word == "while" { return "While" }
|
|
if word == "import" { return "Import" }
|
|
if word == "from" { return "From" }
|
|
if word == "as" { return "As" }
|
|
if word == "with" { return "With" }
|
|
if word == "sealed" { return "Sealed" }
|
|
if word == "activate" { return "Activate" }
|
|
if word == "where" { return "Where" }
|
|
if word == "test" { return "Test" }
|
|
if word == "seed" { return "Seed" }
|
|
if word == "assert" { return "Assert" }
|
|
if word == "protocol" { return "Protocol" }
|
|
if word == "impl" { return "Impl" }
|
|
if word == "retry" { return "Retry" }
|
|
if word == "times" { return "Times" }
|
|
if word == "fallback" { return "Fallback" }
|
|
if word == "reason" { return "Reason" }
|
|
if word == "parallel" { return "Parallel" }
|
|
if word == "trace" { return "Trace" }
|
|
if word == "requires" { return "Requires" }
|
|
if word == "deploy" { return "Deploy" }
|
|
if word == "to" { return "To" }
|
|
if word == "via" { return "Via" }
|
|
if word == "target" { return "Target" }
|
|
if word == "true" { return "Bool" }
|
|
if word == "false" { return "Bool" }
|
|
if word == "cgi" { return "Cgi" }
|
|
if word == "manager" { return "Manager" }
|
|
if word == "engine" { return "Engine" }
|
|
if word == "accessor" { return "Accessor" }
|
|
if word == "vessel" { return "Vessel" }
|
|
""
|
|
}
|
|
|
|
// ── Scan helpers ──────────────────────────────────────────────────────────────
|
|
// All scan helpers receive the chars list and total length.
|
|
|
|
// scan_digits — advance i while chars[i] is a digit
|
|
// Returns { "text": ..., "pos": i }
|
|
fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
|
|
let i = start
|
|
let text = ""
|
|
let running = true
|
|
while running {
|
|
if i >= total {
|
|
let running = false
|
|
} else {
|
|
let ch: String = native_list_get(chars, i)
|
|
if is_digit(ch) {
|
|
let text = text + ch
|
|
let i = i + 1
|
|
} else {
|
|
let running = false
|
|
}
|
|
}
|
|
}
|
|
{ "text": text, "pos": i }
|
|
}
|
|
|
|
// scan_ident — advance i while chars[i] is alphanumeric or underscore
|
|
fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
|
|
let i = start
|
|
let text = ""
|
|
let running = true
|
|
while running {
|
|
if i >= total {
|
|
let running = false
|
|
} else {
|
|
let ch: String = native_list_get(chars, i)
|
|
if is_alnum_or_underscore(ch) {
|
|
let text = text + ch
|
|
let i = i + 1
|
|
} else {
|
|
let running = false
|
|
}
|
|
}
|
|
}
|
|
{ "text": text, "pos": i }
|
|
}
|
|
|
|
// scan_string — scan a quoted string literal, handling \" escapes.
|
|
// Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close }
|
|
fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
|
|
let i = start
|
|
let text = ""
|
|
let running = true
|
|
while running {
|
|
if i >= total {
|
|
let running = false
|
|
} else {
|
|
let ch: String = native_list_get(chars, i)
|
|
if ch == "\\" {
|
|
// escape: peek next char
|
|
let next_i = i + 1
|
|
if next_i < total {
|
|
let next_ch: String = native_list_get(chars, next_i)
|
|
if next_ch == "\"" {
|
|
let text = text + "\""
|
|
let i = next_i + 1
|
|
} else {
|
|
if next_ch == "n" {
|
|
let text = text + "\n"
|
|
let i = next_i + 1
|
|
} else {
|
|
if next_ch == "t" {
|
|
let text = text + "\t"
|
|
let i = next_i + 1
|
|
} else {
|
|
if next_ch == "r" {
|
|
let text = text + "\r"
|
|
let i = next_i + 1
|
|
} else {
|
|
if next_ch == "\\" {
|
|
let text = text + "\\"
|
|
let i = next_i + 1
|
|
} else {
|
|
let text = text + next_ch
|
|
let i = next_i + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == "\"" {
|
|
let i = i + 1
|
|
let running = false
|
|
} else {
|
|
let text = text + ch
|
|
let i = i + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{ "text": text, "pos": i }
|
|
}
|
|
|
|
// ── Main lexer ────────────────────────────────────────────────────────────────
|
|
|
|
fn lex(source: String) -> [Map<String, Any>] {
|
|
let chars: [String] = native_string_chars(source)
|
|
let total: Int = native_list_len(chars)
|
|
let tokens: [Map<String, Any>] = native_list_empty()
|
|
let i: Int = 0
|
|
|
|
while i < total {
|
|
let ch: String = native_list_get(chars, i)
|
|
|
|
// Skip whitespace
|
|
if is_whitespace(ch) {
|
|
let i = i + 1
|
|
} else {
|
|
// Line comments: //
|
|
if ch == "/" {
|
|
let next_i = i + 1
|
|
if next_i < total {
|
|
let next_ch: String = native_list_get(chars, next_i)
|
|
if next_ch == "/" {
|
|
// skip to end of line
|
|
let i = i + 2
|
|
let running2 = true
|
|
while running2 {
|
|
if i >= total {
|
|
let running2 = false
|
|
} else {
|
|
let lch: String = native_list_get(chars, i)
|
|
if lch == "\n" {
|
|
let running2 = false
|
|
} else {
|
|
let i = i + 1
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Slash", "/"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Slash", "/"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
// String literal
|
|
if ch == "\"" {
|
|
let result = scan_string(chars, i + 1, total)
|
|
let str_text: String = result["text"]
|
|
let new_pos: Int = result["pos"]
|
|
let tokens = native_list_append(tokens, make_tok("Str", str_text))
|
|
let i = new_pos
|
|
} else {
|
|
// Number literal
|
|
if is_digit(ch) {
|
|
let result = scan_digits(chars, i, total)
|
|
let num_text: String = result["text"]
|
|
let new_pos: Int = result["pos"]
|
|
// check for float (dot followed by digit)
|
|
if new_pos < total {
|
|
let dot_ch: String = native_list_get(chars, new_pos)
|
|
if dot_ch == "." {
|
|
let after_dot = new_pos + 1
|
|
if after_dot < total {
|
|
let after_dot_ch: String = native_list_get(chars, after_dot)
|
|
if is_digit(after_dot_ch) {
|
|
let frac_result = scan_digits(chars, after_dot, total)
|
|
let frac_text: String = frac_result["text"]
|
|
let frac_pos: Int = frac_result["pos"]
|
|
let tokens = native_list_append(tokens, make_tok("Float", num_text + "." + frac_text))
|
|
let i = frac_pos
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Int", num_text))
|
|
let i = new_pos
|
|
}
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Int", num_text))
|
|
let i = new_pos
|
|
}
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Int", num_text))
|
|
let i = new_pos
|
|
}
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Int", num_text))
|
|
let i = new_pos
|
|
}
|
|
} else {
|
|
// Identifier or keyword
|
|
if is_alpha(ch) || ch == "_" {
|
|
let result = scan_ident(chars, i, total)
|
|
let word: String = result["text"]
|
|
let new_pos: Int = result["pos"]
|
|
let kw = keyword_kind(word)
|
|
if kw == "" {
|
|
let tokens = native_list_append(tokens, make_tok("Ident", word))
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok(kw, word))
|
|
}
|
|
let i = new_pos
|
|
} else {
|
|
// Multi-char and single-char operators/delimiters
|
|
let peek_i = i + 1
|
|
let peek_ch = ""
|
|
if peek_i < total {
|
|
let peek_ch: String = native_list_get(chars, peek_i)
|
|
}
|
|
|
|
if ch == "=" {
|
|
if peek_ch == "=" {
|
|
let tokens = native_list_append(tokens, make_tok("EqEq", "=="))
|
|
let i = i + 2
|
|
} else {
|
|
if peek_ch == ">" {
|
|
let tokens = native_list_append(tokens, make_tok("FatArrow", "=>"))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Eq", "="))
|
|
let i = i + 1
|
|
}
|
|
}
|
|
} else {
|
|
if ch == "!" {
|
|
if peek_ch == "=" {
|
|
let tokens = native_list_append(tokens, make_tok("NotEq", "!="))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Not", "!"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == "<" {
|
|
if peek_ch == "=" {
|
|
let tokens = native_list_append(tokens, make_tok("LtEq", "<="))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Lt", "<"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == ">" {
|
|
if peek_ch == "=" {
|
|
let tokens = native_list_append(tokens, make_tok("GtEq", ">="))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Gt", ">"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == "&" {
|
|
if peek_ch == "&" {
|
|
let tokens = native_list_append(tokens, make_tok("And", "&&"))
|
|
let i = i + 2
|
|
} else {
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == "|" {
|
|
if peek_ch == "|" {
|
|
let tokens = native_list_append(tokens, make_tok("Or", "||"))
|
|
let i = i + 2
|
|
} else {
|
|
if peek_ch == ">" {
|
|
let tokens = native_list_append(tokens, make_tok("PipeOp", "|>"))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Pipe", "|"))
|
|
let i = i + 1
|
|
}
|
|
}
|
|
} else {
|
|
if ch == "-" {
|
|
if peek_ch == ">" {
|
|
let tokens = native_list_append(tokens, make_tok("Arrow", "->"))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Minus", "-"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == ":" {
|
|
if peek_ch == ":" {
|
|
let tokens = native_list_append(tokens, make_tok("ColonColon", "::"))
|
|
let i = i + 2
|
|
} else {
|
|
let tokens = native_list_append(tokens, make_tok("Colon", ":"))
|
|
let i = i + 1
|
|
}
|
|
} else {
|
|
if ch == "+" {
|
|
let tokens = native_list_append(tokens, make_tok("Plus", "+"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "*" {
|
|
let tokens = native_list_append(tokens, make_tok("Star", "*"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "%" {
|
|
let tokens = native_list_append(tokens, make_tok("Percent", "%"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "(" {
|
|
let tokens = native_list_append(tokens, make_tok("LParen", "("))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == ")" {
|
|
let tokens = native_list_append(tokens, make_tok("RParen", ")"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "{" {
|
|
let tokens = native_list_append(tokens, make_tok("LBrace", "{"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "}" {
|
|
let tokens = native_list_append(tokens, make_tok("RBrace", "}"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "[" {
|
|
let tokens = native_list_append(tokens, make_tok("LBracket", "["))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "]" {
|
|
let tokens = native_list_append(tokens, make_tok("RBracket", "]"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "," {
|
|
let tokens = native_list_append(tokens, make_tok("Comma", ","))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "." {
|
|
let tokens = native_list_append(tokens, make_tok("Dot", "."))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == ";" {
|
|
let tokens = native_list_append(tokens, make_tok("Semicolon", ";"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "@" {
|
|
let tokens = native_list_append(tokens, make_tok("At", "@"))
|
|
let i = i + 1
|
|
} else {
|
|
if ch == "?" {
|
|
let tokens = native_list_append(tokens, make_tok("QuestionMark", "?"))
|
|
let i = i + 1
|
|
} else {
|
|
// unknown char — skip
|
|
let i = i + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let tokens = native_list_append(tokens, make_tok("Eof", ""))
|
|
tokens
|
|
}
|
|
|
|
// parser.el — el self-hosting recursive descent parser
|
|
//
|
|
// Consumes the token list produced by lexer.el and builds a list of AST
|
|
// statement maps. Each statement and expression is a Map<String, Any>.
|
|
//
|
|
// The cursor (integer position into the token list) is threaded through every
|
|
// parse function. Functions return { "node": <map>, "pos": <int> }.
|
|
//
|
|
// The token list is passed as a parameter to all parse functions.
|
|
// native_list_get is used to index into it without cloning.
|
|
//
|
|
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
|
|
|
|
// ── Token access helpers ──────────────────────────────────────────────────────
|
|
|
|
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
native_list_get(tokens, pos)
|
|
}
|
|
|
|
fn tok_kind(tokens: [Map<String, Any>], pos: Int) -> String {
|
|
let t = native_list_get(tokens, pos)
|
|
t["kind"]
|
|
}
|
|
|
|
fn tok_value(tokens: [Map<String, Any>], pos: Int) -> String {
|
|
let t = native_list_get(tokens, pos)
|
|
t["value"]
|
|
}
|
|
|
|
fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
|
|
let k = tok_kind(tokens, pos)
|
|
if k == kind {
|
|
return pos + 1
|
|
}
|
|
// On mismatch just advance; error recovery is best-effort
|
|
pos + 1
|
|
}
|
|
|
|
// ── Result helpers ────────────────────────────────────────────────────────────
|
|
|
|
fn make_result(node: Map<String, Any>, pos: Int) -> Map<String, Any> {
|
|
{ "node": node, "pos": pos }
|
|
}
|
|
|
|
// ── Type annotation parser ────────────────────────────────────────────────────
|
|
// Skips over a type annotation, returning the new position.
|
|
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
|
|
|
|
fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
|
|
let k = tok_kind(tokens, pos)
|
|
// Array type: [Type]
|
|
if k == "LBracket" {
|
|
let p = pos + 1
|
|
let p = skip_type(tokens, p)
|
|
let p = expect(tokens, p, "RBracket")
|
|
return p
|
|
}
|
|
// Named type (possibly generic)
|
|
if k == "Ident" {
|
|
let p = pos + 1
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Lt" {
|
|
// Generic params: skip until matching >
|
|
let p = p + 1
|
|
let depth = 1
|
|
let running = true
|
|
while running {
|
|
let kk = tok_kind(tokens, p)
|
|
if kk == "Eof" {
|
|
let running = false
|
|
} else {
|
|
if kk == "Lt" {
|
|
let depth = depth + 1
|
|
let p = p + 1
|
|
} else {
|
|
if kk == "Gt" {
|
|
let depth = depth - 1
|
|
let p = p + 1
|
|
if depth <= 0 {
|
|
let running = false
|
|
}
|
|
} else {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "QuestionMark" {
|
|
let p = p + 1
|
|
}
|
|
return p
|
|
}
|
|
// Optional marker
|
|
if k2 == "QuestionMark" {
|
|
return p + 1
|
|
}
|
|
return p
|
|
}
|
|
pos + 1
|
|
}
|
|
|
|
// ── Parameter list ────────────────────────────────────────────────────────────
|
|
// Parses (name: Type, name: Type, ...) — returns { "params": [...], "pos": ... }
|
|
|
|
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let p = expect(tokens, pos, "LParen")
|
|
let params: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k = tok_kind(tokens, p)
|
|
if k == "RParen" {
|
|
let running = false
|
|
} else {
|
|
if k == "Eof" {
|
|
let running = false
|
|
} else {
|
|
// param name
|
|
let pname = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "Colon")
|
|
let p = skip_type(tokens, p)
|
|
let param = { "name": pname }
|
|
let params = native_list_append(params, param)
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RParen")
|
|
{ "params": params, "pos": p }
|
|
}
|
|
|
|
// ── Expression parsing ────────────────────────────────────────────────────────
|
|
|
|
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let k = tok_kind(tokens, pos)
|
|
let v = tok_value(tokens, pos)
|
|
|
|
// Integer literal
|
|
if k == "Int" {
|
|
return make_result({ "expr": "Int", "value": v }, pos + 1)
|
|
}
|
|
|
|
// Float literal
|
|
if k == "Float" {
|
|
return make_result({ "expr": "Float", "value": v }, pos + 1)
|
|
}
|
|
|
|
// String literal
|
|
if k == "Str" {
|
|
return make_result({ "expr": "Str", "value": v }, pos + 1)
|
|
}
|
|
|
|
// Bool literal
|
|
if k == "Bool" {
|
|
return make_result({ "expr": "Bool", "value": v }, pos + 1)
|
|
}
|
|
|
|
// Identifier
|
|
if k == "Ident" {
|
|
return make_result({ "expr": "Ident", "name": v }, pos + 1)
|
|
}
|
|
|
|
// Grouped expression
|
|
if k == "LParen" {
|
|
let r = parse_expr(tokens, pos + 1)
|
|
let node = r["node"]
|
|
let p = r["pos"]
|
|
let p = expect(tokens, p, "RParen")
|
|
return make_result(node, p)
|
|
}
|
|
|
|
// Array literal: [e1, e2, ...]
|
|
if k == "LBracket" {
|
|
let p = pos + 1
|
|
let elems: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RBracket" {
|
|
let running = false
|
|
} else {
|
|
if k2 == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let r = parse_expr(tokens, p)
|
|
let elem = r["node"]
|
|
let p = r["pos"]
|
|
let elems = native_list_append(elems, elem)
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBracket")
|
|
return make_result({ "expr": "Array", "elems": elems }, p)
|
|
}
|
|
|
|
// Map literal: { "key": val, ... }
|
|
if k == "LBrace" {
|
|
let p = pos + 1
|
|
let pairs: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k2 == "Eof" {
|
|
let running = false
|
|
} else {
|
|
// key: Str token
|
|
let key = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "Colon")
|
|
let r = parse_expr(tokens, p)
|
|
let val_node = r["node"]
|
|
let p = r["pos"]
|
|
let pair = { "key": key, "value": val_node }
|
|
let pairs = native_list_append(pairs, pair)
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
|
}
|
|
|
|
// if expression
|
|
if k == "If" {
|
|
let r = parse_if(tokens, pos)
|
|
return r
|
|
}
|
|
|
|
// match expression
|
|
if k == "Match" {
|
|
let r = parse_match(tokens, pos)
|
|
return r
|
|
}
|
|
|
|
// for expression (used as statement)
|
|
if k == "For" {
|
|
let r = parse_for_expr(tokens, pos)
|
|
return r
|
|
}
|
|
|
|
// Unary not
|
|
if k == "Not" {
|
|
let r = parse_primary(tokens, pos + 1)
|
|
let inner = r["node"]
|
|
let p = r["pos"]
|
|
return make_result({ "expr": "Not", "inner": inner }, p)
|
|
}
|
|
|
|
// Unary minus
|
|
if k == "Minus" {
|
|
let r = parse_primary(tokens, pos + 1)
|
|
let inner = r["node"]
|
|
let p = r["pos"]
|
|
return make_result({ "expr": "Neg", "inner": inner }, p)
|
|
}
|
|
|
|
// Fallback: skip unknown token
|
|
make_result({ "expr": "Nil" }, pos + 1)
|
|
}
|
|
|
|
fn parse_if(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let p = expect(tokens, pos, "If")
|
|
let r = parse_expr(tokens, p)
|
|
let cond = r["node"]
|
|
let p = r["pos"]
|
|
let r2 = parse_block(tokens, p)
|
|
let then_stmts = r2["stmts"]
|
|
let p = r2["pos"]
|
|
let has_else = false
|
|
let else_stmts: [Map<String, Any>] = native_list_empty()
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Else" {
|
|
let p = p + 1
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "If" {
|
|
// else-if chain: parse as nested if
|
|
let r3 = parse_if(tokens, p)
|
|
let nested = r3["node"]
|
|
let p = r3["pos"]
|
|
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
|
|
let has_else = true
|
|
} else {
|
|
let r3 = parse_block(tokens, p)
|
|
let else_stmts = r3["stmts"]
|
|
let p = r3["pos"]
|
|
let has_else = true
|
|
}
|
|
}
|
|
make_result({ "expr": "If", "cond": cond, "then": then_stmts, "else": else_stmts, "has_else": has_else }, p)
|
|
}
|
|
|
|
fn parse_match(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let p = expect(tokens, pos, "Match")
|
|
let r = parse_expr(tokens, p)
|
|
let subject = r["node"]
|
|
let p = r["pos"]
|
|
let p = expect(tokens, p, "LBrace")
|
|
let arms: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k = tok_kind(tokens, p)
|
|
if k == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k == "Eof" {
|
|
let running = false
|
|
} else {
|
|
// parse pattern => body
|
|
let r2 = parse_pattern(tokens, p)
|
|
let pattern = r2["node"]
|
|
let p = r2["pos"]
|
|
let p = expect(tokens, p, "FatArrow")
|
|
let r3 = parse_expr(tokens, p)
|
|
let body = r3["node"]
|
|
let p = r3["pos"]
|
|
let arm = { "pattern": pattern, "body": body }
|
|
let arms = native_list_append(arms, arm)
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
|
|
}
|
|
|
|
fn parse_pattern(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let k = tok_kind(tokens, pos)
|
|
if k == "Ident" {
|
|
let v = tok_value(tokens, pos)
|
|
if v == "_" {
|
|
return make_result({ "pattern": "Wildcard" }, pos + 1)
|
|
}
|
|
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
|
|
}
|
|
if k == "Int" {
|
|
return make_result({ "pattern": "LitInt", "value": tok_value(tokens, pos) }, pos + 1)
|
|
}
|
|
if k == "Str" {
|
|
return make_result({ "pattern": "LitStr", "value": tok_value(tokens, pos) }, pos + 1)
|
|
}
|
|
if k == "Bool" {
|
|
return make_result({ "pattern": "LitBool", "value": tok_value(tokens, pos) }, pos + 1)
|
|
}
|
|
// Wildcard _
|
|
make_result({ "pattern": "Wildcard" }, pos + 1)
|
|
}
|
|
|
|
fn parse_for_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let p = expect(tokens, pos, "For")
|
|
let item_name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "In")
|
|
let r = parse_expr(tokens, p)
|
|
let list_expr = r["node"]
|
|
let p = r["pos"]
|
|
let r2 = parse_block(tokens, p)
|
|
let body = r2["stmts"]
|
|
let p = r2["pos"]
|
|
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
|
}
|
|
|
|
fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let p = expect(tokens, pos, "LBrace")
|
|
let stmts: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k = tok_kind(tokens, p)
|
|
if k == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let r = parse_stmt(tokens, p)
|
|
let stmt = r["node"]
|
|
let p = r["pos"]
|
|
let stmts = native_list_append(stmts, stmt)
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
{ "stmts": stmts, "pos": p }
|
|
}
|
|
|
|
// ── Postfix expressions (calls, field access, index) ─────────────────────────
|
|
|
|
fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let r = parse_primary(tokens, pos)
|
|
let node = r["node"]
|
|
let p = r["pos"]
|
|
let running = true
|
|
while running {
|
|
let k = tok_kind(tokens, p)
|
|
if k == "LParen" {
|
|
// function call
|
|
let p = p + 1
|
|
let args: [Map<String, Any>] = native_list_empty()
|
|
let run2 = true
|
|
while run2 {
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RParen" {
|
|
let run2 = false
|
|
} else {
|
|
if k2 == "Eof" {
|
|
let run2 = false
|
|
} else {
|
|
let r2 = parse_expr(tokens, p)
|
|
let arg = r2["node"]
|
|
let p = r2["pos"]
|
|
let args = native_list_append(args, arg)
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RParen")
|
|
let node = { "expr": "Call", "func": node, "args": args }
|
|
} else {
|
|
if k == "Dot" {
|
|
let field = tok_value(tokens, p + 1)
|
|
let p = p + 2
|
|
let node = { "expr": "Field", "object": node, "field": field }
|
|
} else {
|
|
if k == "LBracket" {
|
|
let r2 = parse_expr(tokens, p + 1)
|
|
let idx = r2["node"]
|
|
let p = r2["pos"]
|
|
let p = expect(tokens, p, "RBracket")
|
|
let node = { "expr": "Index", "object": node, "index": idx }
|
|
} else {
|
|
if k == "QuestionMark" {
|
|
let p = p + 1
|
|
let node = { "expr": "Try", "inner": node }
|
|
} else {
|
|
let running = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
make_result(node, p)
|
|
}
|
|
|
|
// ── Binary expression precedence climbing ────────────────────────────────────
|
|
|
|
fn op_precedence(kind: String) -> Int {
|
|
if kind == "Or" { return 1 }
|
|
if kind == "And" { return 2 }
|
|
if kind == "EqEq" { return 3 }
|
|
if kind == "NotEq" { return 3 }
|
|
if kind == "Lt" { return 4 }
|
|
if kind == "Gt" { return 4 }
|
|
if kind == "LtEq" { return 4 }
|
|
if kind == "GtEq" { return 4 }
|
|
if kind == "Plus" { return 5 }
|
|
if kind == "Minus" { return 5 }
|
|
if kind == "Star" { return 6 }
|
|
if kind == "Slash" { return 6 }
|
|
0
|
|
}
|
|
|
|
fn is_binop(kind: String) -> Bool {
|
|
if kind == "Or" { return true }
|
|
if kind == "And" { return true }
|
|
if kind == "EqEq" { return true }
|
|
if kind == "NotEq" { return true }
|
|
if kind == "Lt" { return true }
|
|
if kind == "Gt" { return true }
|
|
if kind == "LtEq" { return true }
|
|
if kind == "GtEq" { return true }
|
|
if kind == "Plus" { return true }
|
|
if kind == "Minus" { return true }
|
|
if kind == "Star" { return true }
|
|
if kind == "Slash" { return true }
|
|
false
|
|
}
|
|
|
|
fn parse_binop(tokens: [Map<String, Any>], pos: Int, min_prec: Int) -> Map<String, Any> {
|
|
let r = parse_postfix(tokens, pos)
|
|
let left = r["node"]
|
|
let p = r["pos"]
|
|
let running = true
|
|
while running {
|
|
let k = tok_kind(tokens, p)
|
|
let prec = op_precedence(k)
|
|
if is_binop(k) {
|
|
if prec >= min_prec {
|
|
let op = k
|
|
let r2 = parse_binop(tokens, p + 1, prec + 1)
|
|
let right = r2["node"]
|
|
let p = r2["pos"]
|
|
let left = { "expr": "BinOp", "op": op, "left": left, "right": right }
|
|
} else {
|
|
let running = false
|
|
}
|
|
} else {
|
|
let running = false
|
|
}
|
|
}
|
|
make_result(left, p)
|
|
}
|
|
|
|
fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
parse_binop(tokens, pos, 1)
|
|
}
|
|
|
|
// ── Statement parsing ─────────────────────────────────────────────────────────
|
|
|
|
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
|
|
let k = tok_kind(tokens, pos)
|
|
|
|
// let binding
|
|
if k == "Let" {
|
|
let p = pos + 1
|
|
let name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let k2 = tok_kind(tokens, p)
|
|
// optional type annotation: name: Type
|
|
if k2 == "Colon" {
|
|
let p = p + 1
|
|
let p = skip_type(tokens, p)
|
|
}
|
|
let p = expect(tokens, p, "Eq")
|
|
let r = parse_expr(tokens, p)
|
|
let val = r["node"]
|
|
let p = r["pos"]
|
|
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
|
|
}
|
|
|
|
// return statement
|
|
if k == "Return" {
|
|
let p = pos + 1
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RBrace" {
|
|
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
|
}
|
|
if k2 == "Eof" {
|
|
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
|
|
}
|
|
let r = parse_expr(tokens, p)
|
|
let val = r["node"]
|
|
let p = r["pos"]
|
|
return make_result({ "stmt": "Return", "value": val }, p)
|
|
}
|
|
|
|
// fn definition
|
|
if k == "Fn" {
|
|
let p = pos + 1
|
|
let name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let r = parse_params(tokens, p)
|
|
let params = r["params"]
|
|
let p = r["pos"]
|
|
// return type annotation: -> Type. Capture the leading identifier
|
|
// so codegen can distinguish Void-returning functions from value-
|
|
// returning ones. Anything not "Void" is treated as a value type.
|
|
let ret_type = ""
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Arrow" {
|
|
let p = p + 1
|
|
let kt = tok_kind(tokens, p)
|
|
if kt == "Ident" {
|
|
let ret_type = tok_value(tokens, p)
|
|
}
|
|
let p = skip_type(tokens, p)
|
|
}
|
|
let r2 = parse_block(tokens, p)
|
|
let body = r2["stmts"]
|
|
let p = r2["pos"]
|
|
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body, "ret_type": ret_type }, p)
|
|
}
|
|
|
|
// type definition
|
|
if k == "Type" {
|
|
let p = pos + 1
|
|
let name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "LBrace")
|
|
let fields: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k2 == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let fname = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "Colon")
|
|
let p = skip_type(tokens, p)
|
|
let fields = native_list_append(fields, { "name": fname })
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
return make_result({ "stmt": "TypeDef", "name": name, "fields": fields }, p)
|
|
}
|
|
|
|
// enum definition
|
|
if k == "Enum" {
|
|
let p = pos + 1
|
|
let name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "LBrace")
|
|
let variants: [Map<String, Any>] = native_list_empty()
|
|
let running = true
|
|
while running {
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k2 == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let vname = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let variants = native_list_append(variants, { "name": vname })
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
return make_result({ "stmt": "EnumDef", "name": name, "variants": variants }, p)
|
|
}
|
|
|
|
// import statement
|
|
if k == "Import" {
|
|
let p = pos + 1
|
|
let path = tok_value(tokens, p)
|
|
let p = p + 1
|
|
return make_result({ "stmt": "Import", "path": path }, p)
|
|
}
|
|
|
|
// from ... import { ... }
|
|
if k == "From" {
|
|
let p = pos + 1
|
|
let module_name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
// skip "import" keyword
|
|
let k2 = tok_kind(tokens, p)
|
|
if k2 == "Import" {
|
|
let p = p + 1
|
|
}
|
|
// skip { Name, ... }
|
|
let k3 = tok_kind(tokens, p)
|
|
if k3 == "LBrace" {
|
|
let p = p + 1
|
|
let running = true
|
|
while running {
|
|
let k4 = tok_kind(tokens, p)
|
|
if k4 == "RBrace" {
|
|
let running = false
|
|
} else {
|
|
if k4 == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let p = p + 1
|
|
let k5 = tok_kind(tokens, p)
|
|
if k5 == "Comma" {
|
|
let p = p + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let p = expect(tokens, p, "RBrace")
|
|
}
|
|
return make_result({ "stmt": "Import", "path": module_name }, p)
|
|
}
|
|
|
|
// while loop
|
|
if k == "While" {
|
|
let p = pos + 1
|
|
let r = parse_expr(tokens, p)
|
|
let cond = r["node"]
|
|
let p = r["pos"]
|
|
let r2 = parse_block(tokens, p)
|
|
let body = r2["stmts"]
|
|
let p = r2["pos"]
|
|
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
|
|
}
|
|
|
|
// for loop
|
|
if k == "For" {
|
|
let p = pos + 1
|
|
let item_name = tok_value(tokens, p)
|
|
let p = p + 1
|
|
let p = expect(tokens, p, "In")
|
|
let r = parse_expr(tokens, p)
|
|
let list_expr = r["node"]
|
|
let p = r["pos"]
|
|
let r2 = parse_block(tokens, p)
|
|
let body = r2["stmts"]
|
|
let p = r2["pos"]
|
|
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
|
|
}
|
|
|
|
// @decorator — skip and parse next stmt
|
|
if k == "At" {
|
|
let p = pos + 1
|
|
// skip decorator name
|
|
let p = p + 1
|
|
return parse_stmt(tokens, p)
|
|
}
|
|
|
|
// bare expression or if/match statement
|
|
let r = parse_expr(tokens, pos)
|
|
let val = r["node"]
|
|
let p = r["pos"]
|
|
make_result({ "stmt": "Expr", "value": val }, p)
|
|
}
|
|
|
|
// ── Top-level parse ────────────────────────────────────────────────────────────
|
|
|
|
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
|
|
let total: Int = native_list_len(tokens)
|
|
let stmts: [Map<String, Any>] = native_list_empty()
|
|
let pos: Int = 0
|
|
let running = true
|
|
while running {
|
|
if pos >= total {
|
|
let running = false
|
|
} else {
|
|
let k = tok_kind(tokens, pos)
|
|
if k == "Eof" {
|
|
let running = false
|
|
} else {
|
|
let r = parse_stmt(tokens, pos)
|
|
let stmt = r["node"]
|
|
let new_pos: Int = r["pos"]
|
|
let stmts = native_list_append(stmts, stmt)
|
|
// Guard against infinite loops — if pos didn't advance, force it
|
|
if new_pos <= pos {
|
|
let pos = pos + 1
|
|
} else {
|
|
let pos = new_pos
|
|
}
|
|
}
|
|
}
|
|
}
|
|
stmts
|
|
}
|
|
|
|
// codegen.el — El compiler C source code generator
|
|
//
|
|
// Input: list of AST statement maps (from parser.el)
|
|
// Output: C source printed to stdout (streamed, one line at a time)
|
|
//
|
|
// Each El program compiles to a single .c file that #includes el_runtime.h.
|
|
// Functions map directly to C functions; top-level statements become main().
|
|
//
|
|
// Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String
|
|
// Returns "" — output goes to stdout via println().
|
|
//
|
|
// Streaming output avoids O(n²) string concatenation: each emitted line is
|
|
// printed immediately rather than appended to a growing string.
|
|
|
|
// ── String helpers ────────────────────────────────────────────────────────────
|
|
|
|
// Escape a C string literal (double-quotes and backslashes).
|
|
fn c_escape(s: String) -> String {
|
|
let chars: [String] = native_string_chars(s)
|
|
let total: Int = native_list_len(chars)
|
|
let out = ""
|
|
let i = 0
|
|
while i < total {
|
|
let ch: String = native_list_get(chars, i)
|
|
if ch == "\"" {
|
|
let out = out + "\\\""
|
|
} else {
|
|
if ch == "\\" {
|
|
let out = out + "\\\\"
|
|
} else {
|
|
if ch == "\n" {
|
|
let out = out + "\\n"
|
|
} else {
|
|
if ch == "\r" {
|
|
let out = out + "\\r"
|
|
} else {
|
|
if ch == "\t" {
|
|
let out = out + "\\t"
|
|
} else {
|
|
let out = out + ch
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
out
|
|
}
|
|
|
|
fn c_str_lit(s: String) -> String {
|
|
"\"" + c_escape(s) + "\""
|
|
}
|
|
|
|
// ── Type mapping ──────────────────────────────────────────────────────────────
|
|
|
|
fn el_type_to_c(type_str: String) -> String {
|
|
if type_str == "String" { return "const char*" }
|
|
if type_str == "Int" { return "int64_t" }
|
|
if type_str == "Bool" { return "int" }
|
|
if type_str == "Float" { return "double" }
|
|
if type_str == "Void" { return "void" }
|
|
if type_str == "void" { return "void" }
|
|
"void*"
|
|
}
|
|
|
|
// ── Code emission ─────────────────────────────────────────────────────────────
|
|
//
|
|
// emit_line/emit_blank stream output directly via println.
|
|
// This avoids building a large string in memory.
|
|
|
|
fn emit_line(line: String) -> Void {
|
|
println(line)
|
|
}
|
|
|
|
fn emit_blank() -> Void {
|
|
println("")
|
|
}
|
|
|
|
// ── Operator helpers ──────────────────────────────────────────────────────────
|
|
|
|
fn binop_to_c(op: String) -> String {
|
|
if op == "Plus" { return "+" }
|
|
if op == "Minus" { return "-" }
|
|
if op == "Star" { return "*" }
|
|
if op == "Slash" { return "/" }
|
|
if op == "EqEq" { return "==" }
|
|
if op == "NotEq" { return "!=" }
|
|
if op == "Lt" { return "<" }
|
|
if op == "Gt" { return ">" }
|
|
if op == "LtEq" { return "<=" }
|
|
if op == "GtEq" { return ">=" }
|
|
if op == "And" { return "&&" }
|
|
if op == "Or" { return "||" }
|
|
op
|
|
}
|
|
|
|
// ── Expression codegen ────────────────────────────────────────────────────────
|
|
//
|
|
// cg_expr returns a C expression string (not a statement).
|
|
|
|
fn cg_expr(expr: Map<String, Any>) -> String {
|
|
let kind: String = expr["expr"]
|
|
|
|
if kind == "Int" {
|
|
let v: String = expr["value"]
|
|
return v
|
|
}
|
|
|
|
if kind == "Float" {
|
|
let v: String = expr["value"]
|
|
return v
|
|
}
|
|
|
|
if kind == "Str" {
|
|
let v: String = expr["value"]
|
|
return "EL_STR(" + c_str_lit(v) + ")"
|
|
}
|
|
|
|
if kind == "Bool" {
|
|
let v: String = expr["value"]
|
|
if v == "true" { return "1" }
|
|
return "0"
|
|
}
|
|
|
|
if kind == "Nil" {
|
|
return "EL_NULL"
|
|
}
|
|
|
|
if kind == "Ident" {
|
|
let name: String = expr["name"]
|
|
return name
|
|
}
|
|
|
|
if kind == "Not" {
|
|
let inner = expr["inner"]
|
|
let inner_c: String = cg_expr(inner)
|
|
return "!" + inner_c
|
|
}
|
|
|
|
if kind == "Neg" {
|
|
let inner = expr["inner"]
|
|
let inner_c: String = cg_expr(inner)
|
|
return "(-" + inner_c + ")"
|
|
}
|
|
|
|
if kind == "BinOp" {
|
|
let op: String = expr["op"]
|
|
let left = expr["left"]
|
|
let right = expr["right"]
|
|
let left_c: String = cg_expr(left)
|
|
let right_c: String = cg_expr(right)
|
|
let left_kind: String = left["expr"]
|
|
let right_kind: String = right["expr"]
|
|
|
|
if op == "Plus" {
|
|
// If either side is a string literal, always concat
|
|
if left_kind == "Str" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Str" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
// If either side is an integer literal, this is arithmetic (not string concat)
|
|
if left_kind == "Int" {
|
|
let op_c: String = binop_to_c(op)
|
|
return "(" + left_c + " " + op_c + " " + right_c + ")"
|
|
}
|
|
if right_kind == "Int" {
|
|
let op_c: String = binop_to_c(op)
|
|
return "(" + left_c + " " + op_c + " " + right_c + ")"
|
|
}
|
|
if left_kind == "Call" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Call" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if left_kind == "BinOp" {
|
|
let left_op: String = left["op"]
|
|
if left_op == "Plus" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
}
|
|
if right_kind == "BinOp" {
|
|
let right_op: String = right["op"]
|
|
if right_op == "Plus" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
}
|
|
// Ident + Ident or Ident + unknown — assume string concat
|
|
// (This is the ambiguous case: El uses + for both string and integer ops)
|
|
if left_kind == "Ident" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Ident" {
|
|
return "el_str_concat(" + left_c + ", " + right_c + ")"
|
|
}
|
|
}
|
|
|
|
// String equality: use str_eq() when either side is a string literal or ident.
|
|
// Use plain == when comparing integer literals.
|
|
if op == "EqEq" {
|
|
// Integer literal on either side → arithmetic comparison
|
|
if left_kind == "Int" {
|
|
return "(" + left_c + " == " + right_c + ")"
|
|
}
|
|
if right_kind == "Int" {
|
|
return "(" + left_c + " == " + right_c + ")"
|
|
}
|
|
if left_kind == "Bool" {
|
|
return "(" + left_c + " == " + right_c + ")"
|
|
}
|
|
if right_kind == "Bool" {
|
|
return "(" + left_c + " == " + right_c + ")"
|
|
}
|
|
if left_kind == "Str" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Str" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if left_kind == "Ident" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Ident" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if left_kind == "Call" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Call" {
|
|
return "str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
}
|
|
|
|
if op == "NotEq" {
|
|
if left_kind == "Int" {
|
|
return "(" + left_c + " != " + right_c + ")"
|
|
}
|
|
if right_kind == "Int" {
|
|
return "(" + left_c + " != " + right_c + ")"
|
|
}
|
|
if left_kind == "Bool" {
|
|
return "(" + left_c + " != " + right_c + ")"
|
|
}
|
|
if right_kind == "Bool" {
|
|
return "(" + left_c + " != " + right_c + ")"
|
|
}
|
|
if left_kind == "Str" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Str" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if left_kind == "Ident" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Ident" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if left_kind == "Call" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
if right_kind == "Call" {
|
|
return "!str_eq(" + left_c + ", " + right_c + ")"
|
|
}
|
|
}
|
|
|
|
let op_c: String = binop_to_c(op)
|
|
return "(" + left_c + " " + op_c + " " + right_c + ")"
|
|
}
|
|
|
|
if kind == "Call" {
|
|
let func = expr["func"]
|
|
let args = expr["args"]
|
|
let arity: Int = native_list_len(args)
|
|
let func_kind: String = func["expr"]
|
|
|
|
let args_c = ""
|
|
let i = 0
|
|
while i < arity {
|
|
let arg = native_list_get(args, i)
|
|
let arg_c: String = cg_expr(arg)
|
|
if i > 0 {
|
|
let args_c = args_c + ", "
|
|
}
|
|
let args_c = args_c + arg_c
|
|
let i = i + 1
|
|
}
|
|
|
|
if func_kind == "Ident" {
|
|
let fn_name: String = func["name"]
|
|
return fn_name + "(" + args_c + ")"
|
|
}
|
|
|
|
if func_kind == "Field" {
|
|
let obj = func["object"]
|
|
let field: String = func["field"]
|
|
let obj_c: String = cg_expr(obj)
|
|
if arity > 0 {
|
|
return field + "(" + obj_c + ", " + args_c + ")"
|
|
}
|
|
return field + "(" + obj_c + ")"
|
|
}
|
|
|
|
let fn_c: String = cg_expr(func)
|
|
return fn_c + "(" + args_c + ")"
|
|
}
|
|
|
|
if kind == "Field" {
|
|
let obj = expr["object"]
|
|
let field: String = expr["field"]
|
|
let obj_c: String = cg_expr(obj)
|
|
return "el_get_field(" + obj_c + ", " + c_str_lit(field) + ")"
|
|
}
|
|
|
|
if kind == "Index" {
|
|
// El programs use `t["field"]` for map access and `arr[i]` for
|
|
// list access. The parser emits the same Index node for both.
|
|
// Dispatch at codegen time on the index expression kind: string-
|
|
// literal index → map field access (`el_get_field`); anything
|
|
// else → list element access (`el_list_get`).
|
|
let obj = expr["object"]
|
|
let idx = expr["index"]
|
|
let obj_c: String = cg_expr(obj)
|
|
let idx_c: String = cg_expr(idx)
|
|
let idx_kind: String = idx["expr"]
|
|
if str_eq(idx_kind, "Str") {
|
|
return "el_get_field(" + obj_c + ", " + idx_c + ")"
|
|
}
|
|
return "el_list_get(" + obj_c + ", " + idx_c + ")"
|
|
}
|
|
|
|
if kind == "Array" {
|
|
let elems = expr["elems"]
|
|
let n: Int = native_list_len(elems)
|
|
let items = ""
|
|
let i = 0
|
|
while i < n {
|
|
let elem = native_list_get(elems, i)
|
|
let elem_c: String = cg_expr(elem)
|
|
if i > 0 {
|
|
let items = items + ", "
|
|
}
|
|
let items = items + elem_c
|
|
let i = i + 1
|
|
}
|
|
return "el_list_new(" + native_int_to_str(n) + ", " + items + ")"
|
|
}
|
|
|
|
if kind == "Map" {
|
|
let pairs = expr["pairs"]
|
|
let n: Int = native_list_len(pairs)
|
|
let items = ""
|
|
let i = 0
|
|
while i < n {
|
|
let pair = native_list_get(pairs, i)
|
|
let key: String = pair["key"]
|
|
let val = pair["value"]
|
|
let val_c: String = cg_expr(val)
|
|
if i > 0 {
|
|
let items = items + ", "
|
|
}
|
|
let items = items + c_str_lit(key) + ", " + val_c
|
|
let i = i + 1
|
|
}
|
|
return "el_map_new(" + native_int_to_str(n) + ", " + items + ")"
|
|
}
|
|
|
|
if kind == "Try" {
|
|
let inner = expr["inner"]
|
|
return cg_expr(inner)
|
|
}
|
|
|
|
if kind == "If" {
|
|
let cond = expr["cond"]
|
|
let cond_c: String = cg_expr(cond)
|
|
return "/* if-expr */ ((" + cond_c + ") ? (el_val_t)1 : (el_val_t)0)"
|
|
}
|
|
|
|
"EL_NULL"
|
|
}
|
|
|
|
// ── Variable scope tracking ───────────────────────────────────────────────────
|
|
//
|
|
// El allows `let x = expr` to both declare and reassign x in the same scope.
|
|
// C doesn't allow redeclaring the same name in the same block.
|
|
// We track declared names in a list and emit `x = expr` (no type prefix)
|
|
// when x is already declared. The declared list is passed through all
|
|
// statement emitters.
|
|
|
|
fn list_contains(lst: [String], s: String) -> Bool {
|
|
let n: Int = native_list_len(lst)
|
|
let i = 0
|
|
while i < n {
|
|
let item: String = native_list_get(lst, i)
|
|
if item == s { return true }
|
|
let i = i + 1
|
|
}
|
|
false
|
|
}
|
|
|
|
// ── Statement codegen ─────────────────────────────────────────────────────────
|
|
//
|
|
// cg_stmt emits C lines via println. declared is a list of already-declared
|
|
// variable names in the current C scope; returns updated declared list.
|
|
|
|
fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [String] {
|
|
let kind: String = stmt["stmt"]
|
|
|
|
if kind == "Let" {
|
|
let name: String = stmt["name"]
|
|
let val = stmt["value"]
|
|
let val_c: String = cg_expr(val)
|
|
if list_contains(declared, name) {
|
|
emit_line(indent + name + " = " + val_c + ";")
|
|
return declared
|
|
} else {
|
|
emit_line(indent + "el_val_t " + name + " = " + val_c + ";")
|
|
return native_list_append(declared, name)
|
|
}
|
|
}
|
|
|
|
if kind == "Return" {
|
|
let val = stmt["value"]
|
|
let val_kind: String = val["expr"]
|
|
if val_kind == "Nil" {
|
|
emit_line(indent + "return 0;")
|
|
} else {
|
|
let val_c: String = cg_expr(val)
|
|
emit_line(indent + "return " + val_c + ";")
|
|
}
|
|
return declared
|
|
}
|
|
|
|
if kind == "Expr" {
|
|
let val = stmt["value"]
|
|
let val_kind: String = val["expr"]
|
|
if val_kind == "If" {
|
|
cg_if_stmt(val, indent, declared)
|
|
return declared
|
|
}
|
|
if val_kind == "For" {
|
|
cg_for_stmt(val, indent, declared)
|
|
return declared
|
|
}
|
|
let val_c: String = cg_expr(val)
|
|
emit_line(indent + val_c + ";")
|
|
return declared
|
|
}
|
|
|
|
if kind == "While" {
|
|
let cond = stmt["cond"]
|
|
let body = stmt["body"]
|
|
let cond_c: String = cg_expr(cond)
|
|
let cond_c = strip_outer_parens(cond_c)
|
|
emit_line(indent + "while (" + cond_c + ") {")
|
|
cg_stmts(body, indent + " ", declared)
|
|
emit_line(indent + "}")
|
|
return declared
|
|
}
|
|
|
|
if kind == "For" {
|
|
let item: String = stmt["item"]
|
|
let list_expr = stmt["list"]
|
|
let body = stmt["body"]
|
|
cg_for_body(item, list_expr, body, indent, declared)
|
|
return declared
|
|
}
|
|
|
|
if kind == "FnDef" { return declared }
|
|
if kind == "TypeDef" { return declared }
|
|
if kind == "EnumDef" { return declared }
|
|
if kind == "Import" { return declared }
|
|
declared
|
|
}
|
|
|
|
// Strip a single layer of surrounding parentheses from a C expression string.
|
|
fn strip_outer_parens(s: String) -> String {
|
|
let chars: [String] = native_string_chars(s)
|
|
let n: Int = native_list_len(chars)
|
|
if n < 2 { return s }
|
|
let first: String = native_list_get(chars, 0)
|
|
let last: String = native_list_get(chars, n - 1)
|
|
if first == "(" {
|
|
if last == ")" {
|
|
let depth = 1
|
|
let i = 1
|
|
let balanced = true
|
|
while i < n - 1 {
|
|
let ch: String = native_list_get(chars, i)
|
|
if ch == "(" {
|
|
let depth = depth + 1
|
|
}
|
|
if ch == ")" {
|
|
let depth = depth - 1
|
|
if depth == 0 {
|
|
let balanced = false
|
|
let i = n
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
if balanced {
|
|
let inner = ""
|
|
let j = 1
|
|
while j < n - 1 {
|
|
let ch: String = native_list_get(chars, j)
|
|
let inner = inner + ch
|
|
let j = j + 1
|
|
}
|
|
return inner
|
|
}
|
|
}
|
|
}
|
|
s
|
|
}
|
|
|
|
fn cg_if_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Void {
|
|
let cond = expr["cond"]
|
|
let then_stmts = expr["then"]
|
|
let else_stmts = expr["else"]
|
|
let has_else: Bool = expr["has_else"]
|
|
let cond_c: String = cg_expr(cond)
|
|
let cond_c = strip_outer_parens(cond_c)
|
|
emit_line(indent + "if (" + cond_c + ") {")
|
|
cg_stmts(then_stmts, indent + " ", declared)
|
|
if has_else {
|
|
emit_line(indent + "} else {")
|
|
cg_stmts(else_stmts, indent + " ", declared)
|
|
}
|
|
emit_line(indent + "}")
|
|
}
|
|
|
|
fn cg_for_body(item: String, list_expr: Map<String, Any>, body: [Map<String, Any>], indent: String, declared: [String]) -> Void {
|
|
let list_c: String = cg_expr(list_expr)
|
|
let idx = "_el_i"
|
|
let list_tmp = "_el_lst"
|
|
let len_tmp = "_el_len"
|
|
emit_line(indent + "{")
|
|
emit_line(indent + " el_val_t " + list_tmp + " = " + list_c + ";")
|
|
emit_line(indent + " el_val_t " + len_tmp + " = el_list_len(" + list_tmp + ");")
|
|
emit_line(indent + " for (el_val_t " + idx + " = 0; " + idx + " < " + len_tmp + "; " + idx + "++) {")
|
|
emit_line(indent + " el_val_t " + item + " = el_list_get(" + list_tmp + ", " + idx + ");")
|
|
cg_stmts(body, indent + " ", declared)
|
|
emit_line(indent + " }")
|
|
emit_line(indent + "}")
|
|
}
|
|
|
|
fn cg_for_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Void {
|
|
let item: String = expr["item"]
|
|
let list_expr = expr["list"]
|
|
let body = expr["body"]
|
|
cg_for_body(item, list_expr, body, indent, declared)
|
|
}
|
|
|
|
fn cg_stmts(stmts: [Map<String, Any>], indent: String, declared: [String]) -> [String] {
|
|
let n: Int = native_list_len(stmts)
|
|
let i = 0
|
|
let decl = declared
|
|
while i < n {
|
|
let stmt = native_list_get(stmts, i)
|
|
let decl = cg_stmt(stmt, indent, decl)
|
|
let i = i + 1
|
|
}
|
|
decl
|
|
}
|
|
|
|
// ── Function declaration codegen ───────────────────────────────────────────────
|
|
|
|
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
|
|
let name: String = param["name"]
|
|
"el_val_t " + name
|
|
}
|
|
|
|
fn params_to_c(params: [Map<String, Any>]) -> String {
|
|
let n: Int = native_list_len(params)
|
|
if n == 0 { return "void" }
|
|
let out = ""
|
|
let i = 0
|
|
while i < n {
|
|
let param = native_list_get(params, i)
|
|
let decl: String = param_decl(param, i)
|
|
if i > 0 {
|
|
let out = out + ", "
|
|
}
|
|
let out = out + decl
|
|
let i = i + 1
|
|
}
|
|
out
|
|
}
|
|
|
|
// Transform a function body so that an implicit-return final expression
|
|
// becomes an explicit Return. El allows the last expression in a function
|
|
// body to be the return value (e.g. `fn lex(s) { ... tokens }` returns
|
|
// `tokens`). Without this transform, the codegen emits the bare expression
|
|
// and falls through to the trailing `return 0;`, losing the value.
|
|
//
|
|
// Rules: a body ending in a bare Expr whose inner expr is NOT a control-
|
|
// flow construct (If/For) is rewritten so that final Expr becomes a
|
|
// Return statement carrying the same value. Bodies whose final statement
|
|
// is already a Return, While, For, or a non-value-producing form pass
|
|
// through unchanged.
|
|
fn transform_implicit_return(body: [Map<String, Any>]) -> [Map<String, Any>] {
|
|
let n: Int = native_list_len(body)
|
|
if n == 0 { return body }
|
|
let last: Map<String, Any> = native_list_get(body, n - 1)
|
|
let last_kind: String = last["stmt"]
|
|
if last_kind == "Expr" {
|
|
let val = last["value"]
|
|
let val_kind: String = val["expr"]
|
|
// Skip control-flow expressions used as statements
|
|
if val_kind == "If" { return body }
|
|
if val_kind == "For" { return body }
|
|
// Replace the last bare Expr with a Return carrying the same value
|
|
let new_body: [Map<String, Any>] = native_list_empty()
|
|
let i = 0
|
|
while i < n - 1 {
|
|
let new_body = native_list_append(new_body, native_list_get(body, i))
|
|
let i = i + 1
|
|
}
|
|
let return_stmt: Map<String, Any> = { "stmt": "Return", "value": val }
|
|
let new_body = native_list_append(new_body, return_stmt)
|
|
return new_body
|
|
}
|
|
body
|
|
}
|
|
|
|
fn cg_fn(stmt: Map<String, Any>) -> Void {
|
|
let fn_name: String = stmt["name"]
|
|
// Skip El's `fn main()` — C provides its own main() for top-level stmts
|
|
// and a duplicate `el_val_t main(void)` would collide with it.
|
|
if fn_name == "main" { return }
|
|
let params = stmt["params"]
|
|
let body = stmt["body"]
|
|
let ret_type: String = stmt["ret_type"]
|
|
let params_c: String = params_to_c(params)
|
|
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
|
// Seed declared with parameter names so reassignment works
|
|
let decl = native_list_empty()
|
|
let np: Int = native_list_len(params)
|
|
let pi = 0
|
|
while pi < np {
|
|
let param = native_list_get(params, pi)
|
|
let pname: String = param["name"]
|
|
let decl = native_list_append(decl, pname)
|
|
let pi = pi + 1
|
|
}
|
|
// Lift the final bare expression into an explicit return so implicit
|
|
// returns ("fn lex(s) { ... tokens }") actually return their value.
|
|
// Void-returning functions skip this — wrapping `println(x)` in
|
|
// `return …` is a C type error.
|
|
let body_xformed = body
|
|
if !str_eq(ret_type, "Void") {
|
|
let body_xformed = transform_implicit_return(body)
|
|
}
|
|
cg_stmts(body_xformed, " ", decl)
|
|
emit_line(" return 0;")
|
|
emit_line("}")
|
|
emit_blank()
|
|
}
|
|
|
|
// ── Top-level codegen ─────────────────────────────────────────────────────────
|
|
|
|
fn is_fndef(stmt: Map<String, Any>) -> Bool {
|
|
let kind: String = stmt["stmt"]
|
|
if kind == "FnDef" { return true }
|
|
false
|
|
}
|
|
|
|
fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
|
|
let kind: String = stmt["stmt"]
|
|
if kind == "TypeDef" { return true }
|
|
if kind == "EnumDef" { return true }
|
|
if kind == "Import" { return true }
|
|
false
|
|
}
|
|
|
|
// ── Entry point ────────────────────────────────────────────────────────────────
|
|
|
|
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|
// Preamble
|
|
emit_line("#include <stdint.h>")
|
|
emit_line("#include <stdlib.h>")
|
|
emit_line("#include \"el_runtime.h\"")
|
|
emit_blank()
|
|
|
|
// Forward declarations (skip `main` — C provides its own)
|
|
let n: Int = native_list_len(stmts)
|
|
let i = 0
|
|
while i < n {
|
|
let stmt = native_list_get(stmts, i)
|
|
let kind: String = stmt["stmt"]
|
|
if kind == "FnDef" {
|
|
let fn_name: String = stmt["name"]
|
|
if !str_eq(fn_name, "main") {
|
|
let params = stmt["params"]
|
|
let params_c: String = params_to_c(params)
|
|
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
emit_blank()
|
|
|
|
// Function definitions
|
|
let i = 0
|
|
while i < n {
|
|
let stmt = native_list_get(stmts, i)
|
|
if is_fndef(stmt) {
|
|
cg_fn(stmt)
|
|
}
|
|
let i = i + 1
|
|
}
|
|
|
|
// main()
|
|
emit_line("int main(int argc, char** argv) {")
|
|
emit_line(" el_runtime_init_args(argc, argv);")
|
|
let main_decl = native_list_empty()
|
|
let i = 0
|
|
while i < n {
|
|
let stmt = native_list_get(stmts, i)
|
|
if is_fndef(stmt) {
|
|
// skip
|
|
} else {
|
|
if is_top_level_decl(stmt) {
|
|
// skip
|
|
} else {
|
|
let main_decl = cg_stmt(stmt, " ", main_decl)
|
|
}
|
|
}
|
|
let i = i + 1
|
|
}
|
|
emit_line(" return 0;")
|
|
emit_line("}")
|
|
emit_blank()
|
|
|
|
// Return empty string — output was streamed via println
|
|
""
|
|
}
|
|
|
|
// compiler.el — el self-hosting compiler pipeline
|
|
//
|
|
// Wires lexer -> parser -> codegen into a single compile() function.
|
|
// This is the bootstrap entry point: compiled once by the Rust el-compiler,
|
|
// then self-hosted from that point forward.
|
|
//
|
|
// The returned string is C source code. Compile the output with:
|
|
// cc -o <prog> <prog>.c el_runtime.c
|
|
|
|
|
|
// compile — full pipeline: source string -> C source string
|
|
fn compile(source: String) -> String {
|
|
let tokens: [Map<String, Any>] = lex(source)
|
|
let stmts: [Map<String, Any>] = parse(tokens)
|
|
codegen(stmts, source)
|
|
}
|
|
|
|
// main — CLI entry point for self-hosted compilation.
|
|
//
|
|
// Called by: elc <source.el> <output.c>
|
|
//
|
|
// Reads El source from args()[0], compiles it to C source, and writes the
|
|
// result to args()[1]. Then run:
|
|
// cc -o <prog> <output.c> el_runtime.c
|
|
|
|
// CLI driver — equivalent to: elc <source.el>
|
|
let _argv: [String] = args()
|
|
let _src_path: String = native_list_get(_argv, 0)
|
|
let _source: String = fs_read(_src_path)
|
|
compile(_source)
|