(fn Emitter (ast filename) (do (let output ()) (let (enter_scope leave_scope define_sym get_sym print_syms) (call Syms)) (let (let_node_reg_count let_node_reg_increment) (call Counter)) (let (sym_id_count sym_id_increment) (call Counter)) (let builtin_syms ( ("format" "builtinFormat") ("print" "builtinPrint") ("println" "builtinPrintln") ("panic" "builtinPanic") ("read_text_file" "builtinReadTextFile") ("write_text_file" "builtinWriteTextFile") ("push" "builtinPush") ("at" "builtinAt") ("set" "builtinSet") ("len" "builtinLen") ("string_to_int" "builtinStringToInt") ("char_code" "builtinCharCode") ("strings_join" "builtinStringsJoin") ("get_args" "builtinGetArgs") )) (fn generate () (do (call emit "#!/usr/bin/env node\n") (call emit "import { Runtime } from \"./runtime.js\";\n") (call emit "const runtime = new Runtime({ filename: \"") (call emit filename) (call emit "\" });\n") (for (ident builtin_id) builtin_syms (do (call define_builtin ident builtin_id) )) (call discover_syms ast) (call emit_exprs ast) (return (call strings_join output)) )) (fn emit_exprs (exprs) (do (for expr exprs (do (call emit_expr expr) (call emit ";\n") )) )) (fn discover_syms (exprs) (do (for expr exprs (do (let (ty line) expr) (if (!= ty "list") (return)) (let (_ _ s) expr) (if (== (call len s) 0) (return)) (let ((_ _ id)) s) (if (== id "fn") (do (let (_ (_ _ ident) (_ _ params) body) s) (call define_fn ident line) )) )) )) (fn emit_expr (expr) (do (let (ty line) expr) (if (== ty "list") (do (call emit_list expr) ) (if (== ty "int") (do (let (_ _ value) expr) (call emit (call format "({ type: \"int\", value: % })" value)) ) (if (== ty "string") (do (let (_ _ value) expr) (call emit (call format "({ type: \"string\", value: \"%\" })" (call string_escape value))) ) (if (== ty "ident") (do (let (_ _ value) expr) (if (== value "null") (do (call emit "({ type: \"null\" })") (return) ) (if (== value "false") (do (call emit "({ type: \"bool\", value: false })") (return) ) (if (== value "true") (do (call emit "({ type: \"bool\", value: true })") (return) )))) (let sym (call get_sym value)) (if (== sym null) (do (call panic "undefined symbol '%' on line %" value line) )) (let (sym_id sym_ty) sym) (if (== sym_ty "builtin") (do (let (_ _ id) sym) (call emit (call format "((...args) => runtime.%(...args))" id)) ) (if (== sym_ty "fn") (do (call emit (call format "_%%" value sym_id)) ) (if (== sym_ty "param") (do (call emit (call format "_%%" value sym_id)) ) (if (== sym_ty "let") (do (call emit (call format "_%%" value sym_id)) ) (do (call panic "not implemented '%'" sym_ty) ))))) ) (do (call panic "unknown expr type '%' on line %" ty line) ))))) )) (fn emit_list (expr) (do (let (ty line s) expr) (if (== (call len s) 0) (do (call emit "({ type: \"list\", values: [] })") (return) )) (let ((id_ty _ id)) s) (if (!= id_ty "ident") (do (call emit_list_literal s) (return) )) (if (== id "fn") (do (let (_ (_ _ ident) (_ _ params) body) s) (let sym (call get_sym ident)) (let (sym_id) sym) (call emit (call format "function _%%(" ident sym_id)) (call enter_scope) (let first true) (for (_ _ ident) params (do (if (not first) (do (call emit ", ") )) (= first false) (let sym_id (call define_param ident line)) (call emit (call format "_%%" ident sym_id)) )) (call emit ") {\n") (call emit (call format "runtime.pushCall(\"%\");\n" ident)) (call emit_expr body) (call emit ";\nruntime.popCall();\nreturn { type: \"null\" };\n}") (call leave_scope) ) (if (== id "let") (do (let (_ pat expr) s) (let reg (call let_node_reg_count)) (call let_node_reg_increment) (call emit (call format "const r_% = " reg)) (call emit_expr expr) (call emit_let_node pat reg) ) (if (== id "do") (do (call enter_scope) (call discover_syms (call slice s 1)) (call emit_exprs (call slice s 1)) (call leave_scope) ) (if (== id "for") (do (let (_ pat expr body) s) (let reg (call let_node_reg_count)) (call let_node_reg_increment) (call emit (call format "for (const r_% of " reg)) (call emit_expr expr) (call emit ".values) {") (call enter_scope) (call emit_let_node pat reg) (call enter_scope) (call emit ";\n") (call emit_expr body) (call emit "}") (call leave_scope) (call leave_scope) ) (if (== id "loop") (do (let (_ body) s) (call emit "while (true) {\n") (call emit_expr body) (call emit "}") ) (if (== id "if") (do (let (_ cond truthy falsy) s) (call emit "if (runtime.truthy(") (call emit_expr cond) (call emit ")) {\n") (call emit_expr truthy) (call emit "}") (if (!= falsy null) (do (call emit " else {\n") (call emit_expr falsy) (call emit "}") )) ) (if (== id "return") (do (let (_ value) s) (call emit "runtime.popCall();\n") (call emit "return ") (if (!= value null) (do (call emit_expr value) ) (do (call emit "{ type: \"null\" }") )) ) (if (== id "break") (do (let (_ value) s) (call emit "break") (if (!= value null) (do (call panic "not implemented") )) ) (if (== id "call") (do (let (_ callee) s) (let args (call slice s 2)) (call emit (call format "(runtime.setLine(%), " line)) (call emit_expr callee) (call emit "(") (let first true) (for arg args (do (if (not first) (do (call emit ", ") )) (= first false) (call emit_expr arg) )) (call emit "))") ) (if (== id "list") (do (call emit_list_literal (call slice s 1)) ) (if (== id "=") (do (call emit_assign_expr s line "=") ) (if (== id "+=") (do (call emit_assign_expr s line "+") ) (if (== id "-=") (do (call emit_assign_expr s line "-") ) (if (== id "or") (do (let (_ left right) s) (call emit (call format "(runtime.setLine(%)" line)) (call emit ", { type: \"bool\", value: runtime.truthy(") (call emit_expr left) (call emit ") || runtime.truthy(") (call emit_expr right) (call emit ") })") ) (if (== id "and") (do (let (_ left right) s) (call emit (call format "(runtime.setLine(%)" line)) (call emit ", { type: \"bool\", value: runtime.truthy(") (call emit_expr left) (call emit ") && runtime.truthy(") (call emit_expr right) (call emit ") })") ) (if (== id "==") (do (call emit_binary_op s "opEq") ) (if (== id "!=") (do (call emit_binary_op s "opNe") ) (if (== id "<") (do (call emit_binary_op s "opLt") ) (if (== id ">") (do (call emit_binary_op s "opGt") ) (if (== id "<=") (do (call emit_binary_op s "opLte") ) (if (== id ">=") (do (call emit_binary_op s "opGte") ) (if (== id "+") (do (call emit_binary_op s "opAdd") ) (if (== id "-") (do (call emit_binary_op s "opSub") ) (if (== id "not") (do (let (_ expr) s) (call emit "runtime.opNot(") (call emit_expr expr) (call emit ")") ) (do (call emit_list_literal s) ))))))))))))))))))))))))) )) (fn emit_list_literal (s) (do (call emit "({ type: \"list\", values: [") (let first true) (for e s (do (if (not first) (do (call emit ", ") )) (= first false) (call emit_expr e) )) (call emit "] })") )) (fn emit_let_node (pat base_reg) (do (let (pat_ty line) pat) (if (== pat_ty "ident") (do (let (_ _ ident) pat) (if (== ident "_") (return)) (let sym_id (call define_let ident line)) (call emit (call format ";\nlet _%% = r_%" ident sym_id base_reg)) ) (if (== pat_ty "list") (do (let (_ _ pats) pat) //(call emit (call format // (+ ";\nif (r_%.type !== \"list\") {\nruntime.setLine(%);" // "\nruntime.panic(\"expected list\");\n}\n") // base_reg // line //)) //(call emit (call format // (+ ";\nif (% > r_%.values.length) {\nruntime.setLine(%);\nruntime.panic" // "(`expected % elements, got ${r_%.values.length}`);\n}\n") // (call len pats) // base_reg // line // (call len pats) // base_reg //)) (let i 0) (for pat pats (do (let reg (call let_node_reg_count)) (call let_node_reg_increment) (call emit (call format ";\nconst r_% = r_%.values[%] ?? { type: \"null\"}" reg base_reg i )) (call emit_let_node pat reg) (+= i 1) )) ) (do (call panic "malformed pattern on line %" line) ))) )) (fn emit_binary_op (s id) (do (let (_ left right) s) (call emit (call format "runtime.%(" id)) (call emit_expr left) (call emit ", ") (call emit_expr right) (call emit ")") )) (fn emit_assign_expr (s line id) (do (let (_ (target_type) expr) s) (if (!= target_type "ident") (do (call panic "cannot assign to expression on line %" line) )) (let (_ (_ _ ident)) s) (let sym (call get_sym ident)) (if (== sym null) (do (call panic "could not find symbol '%' on line %" ident line) )) (let (sym_id sym_type sym_ident _) sym) (if (== sym_type "let") (do (call emit (call format "(_%% = " sym_ident sym_id)) (if (== id "=") (do (call emit_expr expr) ) (if (== id "+") (do (call emit (call format "runtime.opAdd(_%%, " sym_ident sym_id)) (call emit_expr expr) (call emit ")") ) (if (== id "-") (do (call emit (call format "runtime.opSub(_%%, " sym_ident sym_id)) (call emit_expr expr) (call emit ")") ) (do (call panic "not implemented") )))) (call emit ")") ) (do (call panic "cannot assign to symbol '%' on line %" sym_ident line) )) )) (fn emit (str) (do (call push output str) )) (fn define_builtin (ident builtin_id) (do (let sym_id (call sym_id_count)) (call sym_id_increment) (call define_sym ident (sym_id "builtin" builtin_id)) (return sym_id) )) (fn define_fn (ident line) (do (let sym_id (call sym_id_count)) (call sym_id_increment) (call define_sym ident (sym_id "fn" ident line)) (return sym_id) )) (fn define_param (ident line) (do (let sym_id (call sym_id_count)) (call sym_id_increment) (call define_sym ident (sym_id "param" ident line)) (return sym_id) )) (fn define_let (ident line) (do (let sym_id (call sym_id_count)) (call sym_id_increment) (call define_sym ident (sym_id "let" ident line)) (return sym_id) )) (return (generate)) )) (fn Counter () (do (let counter 0) (fn count () (do (return counter) )) (fn increment () (do (+= counter 1) )) (return (count increment)) )) (fn Syms () (do (let syms (null ())) (fn enter_scope () (do (= syms (syms ())) )) (fn leave_scope () (do (let (parent _) syms) (= syms parent) )) (fn define (ident sym) (do (let (_ map) syms) (let i 0) (loop (do (if (>= i (call len map)) (break)) (let (s_ident _) (call at map i)) (if (== ident s_ident) (do (call set map i (ident sym)) (return) )) (+= i 1) )) (call push map (ident sym)) )) (fn find_sym (syms ident) (do (let (parent map) syms) (let i 0) (loop (do (if (>= i (call len map)) (break)) (let (s_ident s_sym) (call at map i)) (if (== ident s_ident) (do (return s_sym) )) (+= i 1) )) (if (!= parent null) (do (return (call find_sym parent ident)) )) (return null) )) (fn get (ident) (do (return (call find_sym syms ident)) )) (fn print_syms_node (syms depth) (do (let (parent map) syms) (for (ident sym) map (do (call println "%- %: %" (call indent depth) ident sym) )) (if (!= parent null) (do (call print_syms_node parent (+ depth 1)) )) )) (fn print_syms () (do (call print_syms_node syms 0) )) (return ( enter_scope leave_scope define get print_syms )) )) (fn string_escape (str) (do (let str_len (call len str)) (let i 0) (let result "") (loop (do (if (>= i str_len) (break)) (let ch (call at str i)) (if (== ch "\\") (do (+= result "\\\\") ) (if (== ch "\"") (do (+= result "\\\"") ) (if (== ch "\t") (do (+= result "\\t") ) (if (== ch "\r") (do (+= result "\\r") ) (if (== ch "\n") (do (+= result "\\n") ) (if (== ch "\0") (do (+= result "\\0") ) (do (+= result ch) ))))))) (+= i 1) )) (return result) )) (fn Parser (tokens) (do (let i 0) (let tok (call at tokens i)) (fn parse () (do (let exprs ()) (loop (do (if (call done) (break)) (call push exprs (call parse_expr)) )) (return exprs) )) (fn parse_expr () (do (let (ty line value) tok) (if (call eat "(") (do (let values ()) (loop (do (if (call test ")") (break)) (call push values (call parse_expr)) )) (if (not (call eat ")")) (do (call panic "expected ')' on line %" (call at tok 1)) )) (return ("list" line values)) ) (if (call eat "string") (do (return ("string" line value)) ) (if (call eat "int") (do (return ("int" line (call string_to_int value))) ) (if (call eat "ident") (do (return ("ident" line value)) ) (do (call panic "expected expression, got '%' on line %" ty line) ))))) )) (fn eat (pat) (do (if (not (call test pat)) (return false)) (call step) (return true) )) (fn step () (do (+= i 1) (if (not (call done)) (do (let new_tok (call at tokens i)) (= tok new_tok) )) )) (fn test (pat) (do (if (call done) (return false)) (let (ty) tok) (return (== pat ty)) )) (fn done () (do (return (>= i (call len tokens))) )) (return (parse)) )) (fn tokenize (text) (do (let text_len (call len text)) (let tokens ()) (let i 0) (let line 1) (let ident_chars (+ "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/%&|=?!<>'_")) (loop (do (if (>= i text_len) (break)) (let ch (call at text i)) (if (call contains " \t\r\n" ch) (do (if (== ch "\n") (do (+= line 1) )) (+= i 1) ) (if (call slice_eq text i "//") (do (loop (do (if (or (>= i text_len) (== (call at text i) "\n")) (do (break) )) (+= i 1) )) ) (if (call contains "()" ch) (do (call push tokens (ch line)) (+= i 1) ) (if (== ch "\"") (do (let value "") (+= i 1) (= ch (call at text i)) (loop (do (if (or (>= i text_len) (== ch "\"")) (do (break) )) (if (== ch "\\") (do (+= i 1) (if (>= i text_len) (do (break) )) (= ch (call at text i)) (if (== ch "t") (do (+= value "\t") ) (if (== ch "r") (do (+= value "\r") ) (if (== ch "n") (do (+= value "\n") ) (if (== ch "0") (do (+= value "\n") ) (do (+= value ch) ))))) ) (do (+= value ch) )) (+= i 1) (= ch (call at text i)) )) (if (or (>= i text_len) (!= ch "\"")) (do (call panic "expected '\"' on line %" line) )) (+= i 1) (call push tokens ("string" line value)) ) (if (call contains "0123456789" ch) (do (let value "") (loop (do (= ch (call at text i)) (if (or (>= i text_len) (not (call contains "0123456789" ch))) (do (break) )) (+= value ch) (+= i 1) )) (call push tokens ("int" line value)) ) (if (call contains ident_chars ch) (do (let value "") (loop (do (= ch (call at text i)) (if (or (>= i text_len) (not (call contains ident_chars ch))) (do (break) )) (+= value ch) (+= i 1) )) (call push tokens ("ident" line value)) ) (do (call println "illegal char '%'" ch) (+= i 1) ))))))) )) (return tokens) )) (fn contains (text ch) (do (let text_len (call len text)) (let i 0) (loop (do (if (>= i text_len) (break)) (if (== (call at text i) ch) (do (return true) )) (+= i 1) )) (return false) )) (fn slice_eq (str slice_idx substr) (do (let str_len (call len str)) (let substr_len (call len substr)) (let i 0) (loop (do (if (>= i substr_len) (return true)) (if (>= (+ slice_idx i) str_len) (return false)) (if (!= (call at str (+ slice_idx i)) (call at substr i)) (return false)) (+= i 1) )) (return true) )) (fn print_expr (expr depth) (do (let (ty line value) expr) (if (== ty "list") (do (call println "%(% %" (call indent depth) ty line) (for e value (do (call print_expr e (+ depth 1)) )) (call println "%)" (call indent depth)) ) (do (call println "%%" (call indent depth) expr) )) )) (fn indent (depth) (do (let space "") (let i 0) (loop (do (if (>= i depth) (break)) (+= space " ") (+= i 1) )) (return space) )) (fn slice (list idx) (do (let list_len (call len list)) (let elems ()) (let i idx) (loop (do (if (>= i list_len) (break)) (call push elems (call at list i)) (+= i 1) )) (return elems) )) (let silent false) (let (input_filename output_filename) (call get_args)) (if (not silent) (call println "reading file '%'..." input_filename)) (let text (call read_text_file input_filename)) //(call println "=== text ===") // (call println text) //(call println (call len text)) (if (not silent) (call println "tokenizing...")) (let tokens (call tokenize text)) //(call println "=== tokens ===") //(for elem tokens (do // (let (tok line value) elem) // (if (!= value null) (do // (call println "%\t%\t%" line tok value) // ) (do // (call println "%\t%" line tok) // )) //)) //(call println (call len tokens)) (if (not silent) (call println "parsing...")) (let parser (call Parser tokens)) (let (parse) parser) (let ast (call parse)) // (call println "=== ast ===") // (for expr ast (do // (call print_expr expr 0) // )) (if (not silent) (call println "emitting...")) (let emitter (call Emitter ast input_filename)) (let (emit) emitter) (let js_code (call emit)) (call println "=== js ===") (call println js_code) (if (not silent) (call println "writing file '%'..." output_filename)) (call write_text_file output_filename js_code)