phi-lang/compiler/emit_js.phi
2025-09-25 19:31:24 +02:00

569 lines
17 KiB
Plaintext

(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)
))