(import "../stdlib.phi" ( slice contains list_push list_pop list_contains map map_has map_get map_set )) (import "./parse.phi" (Parser tokenize)) (import "./syms.phi" (Syms)) (import "./counter.phi" (Counter)) (fn JsEmitter (ast initial_filename) (do (let output (list)) (let filename initial_filename) (let syms (Syms)) (let (sym_id_count sym_id_increment) (Counter)) (let (let_node_reg_count let_node_reg_increment) (Counter)) (let import_stack (list filename)) (let imported_files (map)) (let builtin_syms (list (list "format" "builtinFormat") (list "print" "builtinPrint") (list "println" "builtinPrintln") (list "panic" "builtinPanic") (list "read_text_file" "builtinReadTextFile") (list "write_text_file" "builtinWriteTextFile") (list "push" "builtinPush") (list "at" "builtinAt") (list "set" "builtinSet") (list "len" "builtinLen") (list "string_to_int" "builtinStringToInt") (list "char_code" "builtinCharCode") (list "strings_join" "builtinStringsJoin") (list "get_args" "builtinGetArgs") (list "fs_basename" "builtinFsBasename") (list "fs_dirname" "builtinFsDirname") (list "fs_cwd" "builtinFsCwd") (list "fs_resolve" "builtinFsResolve") )) (fn generate () (do (emit "#!/usr/bin/env node\n") (emit "import { Runtime } from \"./runtime.js\";\n") (emit (format "const runtime = new Runtime(\"%\");\n" filename)) (for (ident builtin_id) builtin_syms (do (define_builtin ident builtin_id) )) (emit (format "// === emitting file % ===\n" filename)) (enter_scope) (discover_syms ast) (emit_exprs ast) (return (strings_join output)) )) (fn emit_exprs (exprs) (do (for expr exprs (do (emit_expr expr) (emit ";\n") )) )) (fn discover_syms (exprs) (do (for expr exprs (do (let (_ ty line) expr) (if (!= ty "list") (return)) (let (_ _ _ s) expr) (if (== (len s) 0) (return)) (let ((_ _ _ id)) s) (if (== id "fn") (do (let (_ (_ _ _ ident) (_ _ _ params) body) s) (define_fn ident line) )) )) )) (fn emit_expr (expr) (do (let (_ ty line) expr) (if (== ty "list") (do (emit_list expr) ) (if (== ty "int") (do (let (_ _ _ value) expr) (emit (format "({ type: \"int\", value: % })" value)) ) (if (== ty "string") (do (let (_ _ _ value) expr) (emit (format "({ type: \"string\", value: \"%\" })" (string_escape value))) ) (if (== ty "ident") (do (let (_ _ _ value) expr) (if (== value "null") (do (emit "({ type: \"null\" })") (return) ) (if (== value "false") (do (emit "({ type: \"bool\", value: false })") (return) ) (if (== value "true") (do (emit "({ type: \"bool\", value: true })") (return) )))) (let sym (get_sym value)) (if (== sym null) (do (panic "undefined symbol '%' on line %" value line) )) (let (sym_id sym_ty) sym) (if (== sym_ty "builtin") (do (let (_ _ id) sym) (emit (format "((...args) => runtime.%(...args))" id)) ) (if (== sym_ty "fn") (do (emit (format "_%%" value sym_id)) ) (if (== sym_ty "param") (do (emit (format "_%%" value sym_id)) ) (if (== sym_ty "let") (do (emit (format "_%%" value sym_id)) ) (if (== sym_ty "imported") (do // this might give problems in the future. // the solution is to feed the imported symbol // back into this code, so that stuff gets // handled appropriately. (emit (format "_%%" value sym_id)) ) (do (panic "not implemented '%'" sym_ty) )))))) ) (do (panic "unknown expr type '%' on line %" ty line) ))))) )) (fn emit_list (expr) (do (let (_ ty line s) expr) (if (== (len s) 0) (do (panic "illegal function on line %" line) )) (let ((_ id_ty _ id)) s) (if (!= id_ty "ident") (do (panic "illegal function on line %" line) )) (if (== id "import") (do (let (_ (_ _ _ inner_filename_rel) (_ _ _ idents)) s) (let inner_filename (fs_resolve (fs_dirname filename) inner_filename_rel)) (if (list_contains import_stack inner_filename) (do (panic "circular dependendy: '%' imports '%'" filename inner_filename) )) (list_push import_stack inner_filename) (let sym_map null) (if (map_has imported_files inner_filename) (do (= sym_map (map_get imported_files inner_filename)) ) (do (println "compiling '%'" inner_filename) (let outer_filename filename) (= filename inner_filename) (let text (read_text_file filename)) (let tokens (tokenize text)) (let parser (Parser tokens)) (let (parse) parser) (let ast (parse)) (emit (format "// === emitting file % ===\n" filename)) (emit (format "runtime.setFile(\"%\");\n" filename)) (let outer_syms syms) (= syms (Syms)) (for (ident builtin_id) builtin_syms (do (define_builtin ident builtin_id) )) (enter_scope) (discover_syms ast) (emit_exprs ast) (= sym_map (get_current_map)) (map_set imported_files filename sym_map) (= syms outer_syms) (= filename outer_filename) (emit (format "\n// === resuming file % ===\n" outer_filename)) (emit (format "runtime.setFile(\"%\")" outer_filename)) )) (list_pop import_stack) (for (_ _ _ ident) idents (do (let sym null) (for (sym_ident found_sym) sym_map (do (if (== sym_ident ident) (do (= sym found_sym) (break) )) )) (if (== sym null) (do (panic "no symbol '%' from imported '%'" ident inner_filename) )) (let (_ sym_type) sym) (if (== sym_type "imported") (do (define_sym ident sym) ) (do (let (sym_id) sym) (define_sym ident (list sym_id "imported" sym)) )) )) ) (if (== id "fn") (do (let (_ (_ _ _ ident) (_ _ _ params) body) s) (let sym (get_sym ident)) (let (sym_id) sym) (emit (format "function _%%(" ident sym_id)) (enter_scope) (let first true) (for (_ _ _ ident) params (do (if (not first) (do (emit ", ") )) (= first false) (let sym_id (define_param ident line)) (emit (format "_%%" ident sym_id)) )) (emit ") {\n") (emit (format "runtime.pushCall(\"%\", \"%\");\n" ident filename)) (emit_expr body) (emit ";\nruntime.popCall();\nreturn { type: \"null\" };\n}") (leave_scope) ) (if (== id "let") (do (let (_ pat expr) s) (let reg (let_node_reg_count)) (let_node_reg_increment) (emit (format "const r_% = " reg)) (emit_expr expr) (emit_let_node pat reg) ) (if (== id "do") (do (enter_scope) (discover_syms (slice s 1)) (emit_exprs (slice s 1)) (leave_scope) ) (if (== id "for") (do (let (_ pat expr body) s) (let reg (let_node_reg_count)) (let_node_reg_increment) (emit (format "for (const r_% of " reg)) (emit_expr expr) (emit ".values) {") (enter_scope) (emit_let_node pat reg) (enter_scope) (emit ";\n") (emit_expr body) (emit "}") (leave_scope) (leave_scope) ) (if (== id "loop") (do (let (_ body) s) (emit "while (true) {\n") (emit_expr body) (emit "}") ) (if (== id "if") (do (let (_ cond truthy falsy) s) (emit "if (runtime.truthy(") (emit_expr cond) (emit ")) {\n") (emit_expr truthy) (emit "}") (if (!= falsy null) (do (emit " else {\n") (emit_expr falsy) (emit "}") )) ) (if (== id "return") (do (let (_ value) s) (emit "runtime.popCall();\n") (emit "return ") (if (!= value null) (do (emit_expr value) ) (do (emit "{ type: \"null\" }") )) ) (if (== id "break") (do (let (_ value) s) (emit "break") (if (!= value null) (do (panic "not implemented") )) ) (if (== id "call") (do (let (_ callee) s) (let args (slice s 2)) (emit (format "(%, " (rt_info line))) (emit_expr callee) (emit "(") (let first true) (for arg args (do (if (not first) (do (emit ", ") )) (= first false) (emit_expr arg) )) (emit "))") ) (if (== id "list") (do (emit_list_literal (slice s 1)) ) (if (== id "=") (do (emit_assign_expr s line "=") ) (if (== id "+=") (do (emit_assign_expr s line "+") ) (if (== id "-=") (do (emit_assign_expr s line "-") ) (if (== id "or") (do (let (_ left right) s) (emit (format "(%" (rt_info line) line)) (emit ", { type: \"bool\", value: runtime.truthy(") (emit_expr left) (emit ") || runtime.truthy(") (emit_expr right) (emit ") })") ) (if (== id "and") (do (let (_ left right) s) (emit (format "(%" (rt_info line) line)) (emit ", { type: \"bool\", value: runtime.truthy(") (emit_expr left) (emit ") && runtime.truthy(") (emit_expr right) (emit ") })") ) (if (== id "==") (do (emit_binary_op s "opEq") ) (if (== id "!=") (do (emit_binary_op s "opNe") ) (if (== id "<") (do (emit_binary_op s "opLt") ) (if (== id ">") (do (emit_binary_op s "opGt") ) (if (== id "<=") (do (emit_binary_op s "opLte") ) (if (== id ">=") (do (emit_binary_op s "opGte") ) (if (== id "+") (do (emit_binary_op s "opAdd") ) (if (== id "-") (do (emit_binary_op s "opSub") ) (if (== id "not") (do (let (_ expr) s) (emit "runtime.opNot(") (emit_expr expr) (emit ")") ) (do (let (callee) s) (let args (slice s 1)) (emit (format "(%, " (rt_info line) line)) (emit_expr callee) (emit "(") (let first true) (for arg args (do (if (not first) (do (emit ", ") )) (= first false) (emit_expr arg) )) (emit "))") )))))))))))))))))))))))))) )) (fn emit_list_literal (s) (do (emit "({ type: \"list\", values: [") (let first true) (for e s (do (if (not first) (do (emit ", ") )) (= first false) (emit_expr e) )) (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 (define_let ident line)) (emit (format ";\nlet _%% = r_%" ident sym_id base_reg)) ) (if (== pat_ty "list") (do (let (_ _ _ pats) pat) (let i 0) (for pat pats (do (let reg (let_node_reg_count)) (let_node_reg_increment) (emit (format ";\nconst r_% = r_%.values[%] ?? { type: \"null\"}" reg base_reg i )) (emit_let_node pat reg) (+= i 1) )) ) (do (panic "malformed pattern on line %" line) ))) )) (fn emit_binary_op (s id) (do (let ((_ _ line _) left right) s) (emit (format "(%, runtime.%(" (rt_info line) id)) (emit_expr left) (emit ", ") (emit_expr right) (emit "))") )) (fn rt_info (line) (do (return (format "runtime.info(\"%\", %)" filename line)) )) (fn emit_assign_expr (s line id) (do (let (_ (_ target_type) expr) s) (if (!= target_type "ident") (do (panic "cannot assign to expression on line %" line) )) (let (_ (_ _ _ ident)) s) (let sym (get_sym ident)) (if (== sym null) (do (panic "could not find symbol '%' on line %" ident line) )) (let (sym_id sym_type sym_ident _) sym) (if (== sym_type "let") (do (emit (format "(_%% = " sym_ident sym_id)) (if (== id "=") (do (emit_expr expr) ) (if (== id "+") (do (emit (format "runtime.opAdd(_%%, " sym_ident sym_id)) (emit_expr expr) (emit ")") ) (if (== id "-") (do (emit (format "runtime.opSub(_%%, " sym_ident sym_id)) (emit_expr expr) (emit ")") ) (do (panic "not implemented") )))) (emit ")") ) (do (panic "cannot assign to symbol '%' on line %" sym_ident line) )) )) (fn emit (str) (do (push output str) )) (fn define_builtin (ident builtin_id) (do (let sym_id (sym_id_count)) (sym_id_increment) (define_sym ident (list sym_id "builtin" builtin_id)) (return sym_id) )) (fn define_fn (ident line) (do (let sym_id (sym_id_count)) (sym_id_increment) (define_sym ident (list sym_id "fn" ident line)) (return sym_id) )) (fn define_param (ident line) (do (let sym_id (sym_id_count)) (sym_id_increment) (define_sym ident (list sym_id "param" ident line)) (return sym_id) )) (fn define_let (ident line) (do (let sym_id (sym_id_count)) (sym_id_increment) (define_sym ident (list sym_id "let" ident line)) (return sym_id) )) // indirection for syms (fn enter_scope () (do (let (enter_scope) syms) (enter_scope) )) (fn leave_scope () (do (let (_ leave_scope) syms) (leave_scope) )) (fn define_sym (ident sym) (do (let (_ _ define_sym) syms) (return (define_sym ident sym)) )) (fn get_sym (ident) (do (let (_ _ _ get_sym) syms) (return (get_sym ident)) )) (fn get_current_map () (do (let (_ _ _ _ get_current_map) syms) (return (get_current_map)) )) (fn print_syms () (do (let (_ _ _ _ _ print_syms) syms) (print_syms) )) (return (list generate)) )) (fn string_escape (str) (do (let str_len (len str)) (let i 0) (let result "") (loop (do (if (>= i str_len) (break)) (let ch (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) ))