Archived
951b8d574b
Batches 2/3/4 of the runtime extension. The runtime grew from 1620
to 3112 lines (.c) and 247 to 286 lines (.h) — adding 27 new or
real-implementation builtins and replacing every batch-1 stub.
Batch 2 — HTTP / fs (8 builtins)
- http_get, http_post: replaced stubs with real libcurl client.
Network errors return JSON {"error":"..."} so callers can detect.
- http_post_json: sets Content-Type: application/json.
- http_get_with_headers, http_post_with_headers: ElMap → headers.
- http_post_form_auth: form-urlencoded + Authorization header
(Stripe-style API calls).
- http_serve: replaced stub with real POSIX-socket server, threaded,
capped at 64 concurrent connections. Auto-detects content type
(HTML / JSON / plain). Handler dispatch via named registry.
- fs_list: directory listing via opendir/readdir.
Batch 3 — In-process graph store (14 builtins)
- engram_node, engram_node_full: create node, returns UUID.
- engram_get_node, engram_forget, engram_node_count.
- engram_strengthen: Hebbian potentiation (+0.05, clamp 1.0,
bumps last_activated).
- engram_search, engram_scan_nodes: text search, paginated scan.
- engram_connect, engram_edge_between, engram_neighbors,
engram_neighbors_filtered, engram_edge_count.
- engram_activate: real spreading-activation algorithm.
BFS to depth, max-activation merge across paths, decay 0.7/hop,
multiplied by node confidence, filtered by epistemic_confidence
≥ 0.2 (refresh threshold), sorted desc.
- engram_save, engram_load: JSON snapshot persistence.
Batch 4 — LLM (5 builtins)
- llm_call, llm_call_system: Anthropic /v1/messages via libcurl.
ANTHROPIC_API_KEY from env. Default model claude-sonnet-4-5.
- llm_vision: adds image content block. URL / base64 / file path
detected by prefix.
- llm_models: returns the available model list.
- llm_call_agentic: stubbed with TODO (single-turn fallback to
llm_call_system); full tool_use loop is the next iteration.
Codegen fix: emit Float literals as `el_from_float(<v>)`. Without
the wrapper, C implicit conversion truncates 0.8 to 0 when passed to
a builtin expecting el_val_t. Float helpers moved to el_runtime.h
so generated programs can call them.
Compile-time
- cc -std=c11 -Wall -Wextra -c el_runtime.c → no errors, no warnings.
- Link requires -lcurl -lpthread (documented in header comment).
Verified end-to-end
- engram_node × 2, engram_connect, engram_activate("Hebbian", 2)
returns 2 activated nodes with correct epistemic confidence.
- http_get("https://httpbin.org/get") returns 259-byte JSON live.
- Self-host closure: stage1 vs stage2 byte-identical against the
new runtime.
- engram_save → engram_load round-trip preserves graph.
dist/platform/elc rebuilt against the new runtime (147 KB, up from
94 KB due to libcurl link). .prev2 preserves the prior binary.
2162 lines
82 KiB
EmacsLisp
2162 lines
82 KiB
EmacsLisp
// elc-combined.el — El self-hosting compiler, single-file bootstrap edition
|
|
|
|
// 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")
|
|
// Capture the leading type identifier so codegen can dispatch
|
|
// arithmetic vs string-concat on `+` based on declared types.
|
|
let ptype = ""
|
|
let kt = tok_kind(tokens, p)
|
|
if kt == "Ident" {
|
|
let ptype = tok_value(tokens, p)
|
|
}
|
|
let p = skip_type(tokens, p)
|
|
let param = { "name": pname, "type": ptype }
|
|
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 ltype = ""
|
|
let k2 = tok_kind(tokens, p)
|
|
// optional type annotation: name: Type — capture the leading
|
|
// identifier so codegen can dispatch arithmetic vs concat on
|
|
// `+` between two typed Idents.
|
|
if k2 == "Colon" {
|
|
let p = p + 1
|
|
let kt = tok_kind(tokens, p)
|
|
if kt == "Ident" {
|
|
let ltype = tok_value(tokens, p)
|
|
}
|
|
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, "type": ltype }, 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" {
|
|
// Wrap Float literals in el_from_float() so the bit pattern is
|
|
// preserved through the el_val_t (int64) slot. Without this,
|
|
// implicit double→int64 conversion in C truncates `0.8` to `0`
|
|
// when passed to a builtin that expects el_val_t.
|
|
let v: String = expr["value"]
|
|
return "el_from_float(" + 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 + ")"
|
|
}
|
|
// Type-driven dispatch: if both sides are Idents declared
|
|
// with type Int (parameters annotated `: Int` or let bindings
|
|
// annotated `: Int`), this is arithmetic, not concat. The
|
|
// current-function int-name set is maintained by cg_fn /
|
|
// cg_stmt via state_set("__int_names", csv).
|
|
if left_kind == "Ident" {
|
|
if right_kind == "Ident" {
|
|
let lname: String = left["name"]
|
|
let rname: String = right["name"]
|
|
if is_int_name(lname) {
|
|
if is_int_name(rname) {
|
|
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 without int-typed evidence —
|
|
// fall back to string concat (the historical heuristic).
|
|
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 the binding is annotated `: Int` and val is an Int literal,
|
|
// register `name` in the per-function int-name set so that later
|
|
// `name + ...` dispatches to arithmetic, not concat.
|
|
let ltype: String = stmt["type"]
|
|
if str_eq(ltype, "Int") {
|
|
add_int_name(name)
|
|
}
|
|
let vk: String = val["expr"]
|
|
if str_eq(vk, "Int") {
|
|
add_int_name(name)
|
|
}
|
|
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
|
|
}
|
|
|
|
// Test whether `name` is currently registered as an Int-typed identifier
|
|
// for the function being codegened. The set is maintained as a comma-
|
|
// bounded CSV in process state; cg_fn seeds it from typed parameters,
|
|
// cg_stmt extends it from typed `let` bindings.
|
|
fn is_int_name(name: String) -> Bool {
|
|
let csv: String = state_get("__int_names")
|
|
if str_eq(csv, "") { return false }
|
|
return str_contains(csv, "," + name + ",")
|
|
}
|
|
|
|
fn add_int_name(name: String) -> Bool {
|
|
let csv: String = state_get("__int_names")
|
|
if str_eq(csv, "") { csv = "," }
|
|
let key: String = "," + name + ","
|
|
if str_contains(csv, key) { return true }
|
|
state_set("__int_names", csv + name + ",")
|
|
return true
|
|
}
|
|
|
|
fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
|
state_set("__int_names", ",")
|
|
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 ptype: String = param["type"]
|
|
if str_eq(ptype, "Int") {
|
|
add_int_name(pname)
|
|
}
|
|
let pi = pi + 1
|
|
}
|
|
return true
|
|
}
|
|
|
|
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)
|
|
// Seed the per-function int-name set so the `+` codegen can dispatch
|
|
// arithmetic vs concat on type-annotated identifiers.
|
|
build_int_names_for_params(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
|
|
let _argv: [String] = args()
|
|
let _src_path: String = native_list_get(_argv, 0)
|
|
let _source: String = fs_read(_src_path)
|
|
compile(_source)
|