567 lines
16 KiB
Plaintext
567 lines
16 KiB
Plaintext
(import "stdlib.phi" (
|
|
slice contains
|
|
list_push list_pop list_contains
|
|
map map_has map_get map_set
|
|
))
|
|
(import "compiler/parse.phi" (Parser tokenize))
|
|
(import "compiler/syms.phi" (Syms))
|
|
|
|
(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")
|
|
))
|
|
|
|
(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)
|
|
))
|
|
|
|
(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) (_ _ idents)) s)
|
|
|
|
(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 "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 "runtime.setFile(\"%\");\n" 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 Counter () (do
|
|
(let counter 0)
|
|
|
|
(fn count () (do
|
|
(return counter)
|
|
))
|
|
|
|
(fn increment () (do
|
|
(+= counter 1)
|
|
))
|
|
|
|
(return (list count increment))
|
|
))
|
|
|
|
(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)
|
|
))
|