Compare commits
2 Commits
4289d04ec3
...
c4e199c868
| Author | SHA1 | Date | |
|---|---|---|---|
| c4e199c868 | |||
| ab660ae7a8 |
288
compile.phi
288
compile.phi
@ -1,17 +1,52 @@
|
||||
|
||||
(fn dbg (msg) (do
|
||||
(if is_phi_compiler (do
|
||||
(call println "dbg: %" msg)
|
||||
))
|
||||
))
|
||||
|
||||
(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(\"")
|
||||
(call emit "import { Runtime } from \"./runtime.js\";\n")
|
||||
(call emit "const runtime = new Runtime({ filename: \"")
|
||||
(call emit filename)
|
||||
(call emit "\")\n")
|
||||
(call emit "\" });\n")
|
||||
|
||||
(for (ident builtin_id) builtin_syms (do
|
||||
(call define_builtin ident builtin_id)
|
||||
))
|
||||
|
||||
(let is_phi_compiler_sym_id (call define_let "is_phi_compiler" 0))
|
||||
(call emit (call format
|
||||
"let _is_phi_compiler% = { type: \"bool\", value: true };\n"
|
||||
is_phi_compiler_sym_id
|
||||
))
|
||||
|
||||
(call discover_syms ast)
|
||||
(call emit_exprs ast)
|
||||
(return (call strings_join output))
|
||||
@ -32,8 +67,8 @@
|
||||
(if (== (call len s) 0) (return))
|
||||
(let ((_ _ id)) s)
|
||||
(if (== id "fn") (do
|
||||
(let (_ (_ _ name) (_ _ params) body) s)
|
||||
(call define_sym name ("fn" name line))
|
||||
(let (_ (_ _ ident) (_ _ params) body) s)
|
||||
(call define_fn ident line)
|
||||
))
|
||||
))
|
||||
))
|
||||
@ -42,10 +77,10 @@
|
||||
(let (ty line) expr)
|
||||
(if (== ty "list") (do
|
||||
(call emit_list expr)
|
||||
) (if (== ty "int") (
|
||||
) (if (== ty "int") (do
|
||||
(let (_ _ value) expr)
|
||||
(call emit (call format "({ type: \"int\", value: % })" value))
|
||||
) (if (== ty "string") (
|
||||
) (if (== ty "string") (do
|
||||
(let (_ _ value) expr)
|
||||
(call emit (call format "({ type: \"string\", value: \"%\" })" (call string_escape value)))
|
||||
) (if (== ty "ident") (do
|
||||
@ -68,16 +103,16 @@
|
||||
(call panic "undefined symbol '%' on line %" value line)
|
||||
))
|
||||
|
||||
(let (sym_ty) sym)
|
||||
(let (sym_id sym_ty) sym)
|
||||
(if (== sym_ty "builtin") (do
|
||||
(let (_ id) sym)
|
||||
(let (_ _ id) sym)
|
||||
(call emit (call format "((...args) => runtime.%(...args))" id))
|
||||
) (if (== sym_ty "fn") (do
|
||||
(call emit (call format "_%" value))
|
||||
) (if (== sym_ty "param") (do
|
||||
(call emit (call format "_%" value))
|
||||
) (if (== sym_ty "let") (do
|
||||
(call emit (call format "_%" value))
|
||||
(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)
|
||||
)))))
|
||||
@ -94,30 +129,33 @@
|
||||
))
|
||||
(let ((id_ty _ id)) s)
|
||||
(if (!= id_ty "ident") (do
|
||||
(call emit "({ type: \"list\", values: [] })")
|
||||
(call emit_list_literal s)
|
||||
(return)
|
||||
))
|
||||
(if (== id "fn") (do
|
||||
(let (_ (_ _ name) (_ _ params) body) s)
|
||||
(call emit (call format "function _%(" name))
|
||||
(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 (_ _ name) params (do
|
||||
(for (_ _ ident) params (do
|
||||
(if (not first) (do
|
||||
(call emit ", ")
|
||||
))
|
||||
(= first false)
|
||||
|
||||
(call emit (call format "_%" name))
|
||||
|
||||
(call define_sym name ("param" name line))
|
||||
(let sym_id (call define_param ident line))
|
||||
(call emit (call format "_%%" ident sym_id))
|
||||
))
|
||||
|
||||
|
||||
(call emit (call format ") {\n" name))
|
||||
(call emit (call format "runtime.pushCall(\"%\");\n" name))
|
||||
(call emit ") {\n")
|
||||
(call emit (call format "runtime.pushCall(\"%\");\n" ident))
|
||||
|
||||
(call emit_expr body)
|
||||
(call emit ";\nruntime.popCall();\nreturn { type: \"null\" };\n}")
|
||||
@ -127,7 +165,7 @@
|
||||
(let (_ pat expr) s)
|
||||
(let reg (call let_node_reg_count))
|
||||
(call let_node_reg_increment)
|
||||
(call emit (call format "let r_% = " reg))
|
||||
(call emit (call format "const r_% = " reg))
|
||||
(call emit_expr expr)
|
||||
(call emit_let_node pat reg)
|
||||
) (if (== id "do") (do
|
||||
@ -137,14 +175,22 @@
|
||||
(call leave_scope)
|
||||
) (if (== id "for") (do
|
||||
(let (_ pat expr body) s)
|
||||
(call enter_scope)
|
||||
(call emit "for (let ")
|
||||
(call emit_pat pat)
|
||||
(call emit " of ")
|
||||
|
||||
(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) {\n")
|
||||
(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)
|
||||
@ -153,9 +199,9 @@
|
||||
(call emit "}")
|
||||
) (if (== id "if") (do
|
||||
(let (_ cond truthy falsy) s)
|
||||
(call emit "if (")
|
||||
(call emit "if (runtime.truthy(")
|
||||
(call emit_expr cond)
|
||||
(call emit ") {\n")
|
||||
(call emit ")) {\n")
|
||||
(call emit_expr truthy)
|
||||
(call emit "}")
|
||||
(if (!= falsy null) (do
|
||||
@ -205,17 +251,17 @@
|
||||
) (if (== id "or") (do
|
||||
(let (_ left right) s)
|
||||
(call emit (call format "(runtime.setLine(%)" line))
|
||||
(call emit ", { type: \"bool\", value: this.runtime.truthy(")
|
||||
(call emit ", { type: \"bool\", value: runtime.truthy(")
|
||||
(call emit_expr left)
|
||||
(call emit ") || this.runtime.falsy(")
|
||||
(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: this.runtime.truthy(")
|
||||
(call emit ", { type: \"bool\", value: runtime.truthy(")
|
||||
(call emit_expr left)
|
||||
(call emit ") && this.runtime.falsy(")
|
||||
(call emit ") && runtime.truthy(")
|
||||
(call emit_expr right)
|
||||
(call emit ") })")
|
||||
) (if (== id "==") (do
|
||||
@ -240,20 +286,24 @@
|
||||
(call emit_expr expr)
|
||||
(call emit ")")
|
||||
) (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 "] })")
|
||||
(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
|
||||
@ -261,16 +311,35 @@
|
||||
|
||||
(if (== ident "_") (return))
|
||||
|
||||
(call emit (call format ";\nlet _% = r_%" ident base_reg))
|
||||
(call define_sym ident ("let" line))
|
||||
(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 ";\nlet r_% = r_%.values[%]" reg base_reg i))
|
||||
(call emit (call format
|
||||
";\nconst r_% = r_%.values[%] ?? { type: \"null\"}"
|
||||
reg base_reg i
|
||||
))
|
||||
(call emit_let_node pat reg)
|
||||
(+= i 1)
|
||||
))
|
||||
@ -298,59 +367,64 @@
|
||||
(if (== sym null) (do
|
||||
(call panic "could not find symbol '%' on line %" ident line)
|
||||
))
|
||||
(let (sym_type sym_ident _) sym)
|
||||
(let (sym_id sym_type sym_ident _) sym)
|
||||
(if (== sym_type "let") (do
|
||||
(call emit (call format "(_% = runtime.assignValue(" sym_ident))
|
||||
(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))
|
||||
(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))
|
||||
(call emit (call format "runtime.opSub(_%%, " sym_ident sym_id))
|
||||
(call emit_expr expr)
|
||||
(call emit ")")
|
||||
) (do
|
||||
(call panic "not implemented")
|
||||
))))
|
||||
(call emit "))")
|
||||
(call emit ")")
|
||||
) (do
|
||||
(call panic "cannot assign to symbol '%' on line %" sym_ident line)
|
||||
))
|
||||
))
|
||||
|
||||
(fn emit_pat (pat) (do
|
||||
(let (ty) pat)
|
||||
(if (== ty "ident") (do
|
||||
(let (_ line name) pat)
|
||||
(if (== name "_") (do
|
||||
(return)
|
||||
))
|
||||
(call emit (call format "_%" name))
|
||||
(call define_sym name ("let" name line))
|
||||
) (if (== ty "list") (do
|
||||
(let (_ _ pats) pat)
|
||||
(call emit "[")
|
||||
(let first true)
|
||||
(for pat pats (do
|
||||
(if (not first) (do
|
||||
(call emit ", ")
|
||||
))
|
||||
(= first false)
|
||||
|
||||
(call emit_pat pat)
|
||||
))
|
||||
(call emit "]")
|
||||
) (do
|
||||
(call panic "cannot assign to '%'" pat)
|
||||
)))
|
||||
))
|
||||
|
||||
(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))
|
||||
))
|
||||
|
||||
@ -369,22 +443,7 @@
|
||||
))
|
||||
|
||||
(fn Syms () (do
|
||||
(let syms (null (
|
||||
("format" ("builtin" "builtinFormat"))
|
||||
("print" ("builtin" "builtinPrint"))
|
||||
("println" ("builtin" "builtinPrintln"))
|
||||
("panic" ("builtin" "builtinPanic"))
|
||||
("read_text_file" ("builtin" "builtinReadTextFile"))
|
||||
("write_text_file" ("builtin" "builtinWriteTextFile"))
|
||||
("push" ("builtin" "builtinPush"))
|
||||
("at" ("builtin" "builtinAt"))
|
||||
("set" ("builtin" "builtinSet"))
|
||||
("len" ("builtin" "builtinLen"))
|
||||
("string_to_int" ("builtin" "builtinStringToInt"))
|
||||
("char_code" ("builtin" "builtinCharCode"))
|
||||
("strings_join" ("builtin" "builtinStringsJoin"))
|
||||
("get_args" ("builtin" "builtinGetArgs"))
|
||||
)))
|
||||
(let syms (null ()))
|
||||
|
||||
(fn enter_scope () (do
|
||||
(= syms (syms ()))
|
||||
@ -396,18 +455,18 @@
|
||||
))
|
||||
|
||||
(fn define (ident sym) (do
|
||||
(let (_ syms) syms)
|
||||
(let (_ map) syms)
|
||||
(let i 0)
|
||||
(loop (do
|
||||
(if (>= i (call len syms)) (break))
|
||||
(let (s_ident _) (call at syms i))
|
||||
(if (>= i (call len map)) (break))
|
||||
(let (s_ident _) (call at map i))
|
||||
(if (== ident s_ident) (do
|
||||
(call set syms i (ident sym))
|
||||
(call set map i (ident sym))
|
||||
(return)
|
||||
))
|
||||
(+= i 1)
|
||||
))
|
||||
(call push syms (ident sym))
|
||||
(call push map (ident sym))
|
||||
))
|
||||
|
||||
(fn find_sym (syms ident) (do
|
||||
@ -706,25 +765,30 @@
|
||||
(return elems)
|
||||
))
|
||||
|
||||
(let silent true)
|
||||
(let silent false)
|
||||
|
||||
(let (input_filename output_filename) (call get_args))
|
||||
|
||||
(if (not silent) (call println "reading file..."))
|
||||
(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 text)
|
||||
(call println (call len text))
|
||||
//(call println (call len text))
|
||||
|
||||
(if (not silent) (call println "tokenizing..."))
|
||||
(let tokens (call tokenize text))
|
||||
|
||||
(call println "=== tokens ===")
|
||||
// (for (tok line value) tokens (do
|
||||
// (call println "%\t%\t%" line tok (if (!= value null) value ""))
|
||||
// ))
|
||||
(call println (call len tokens))
|
||||
//(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))
|
||||
@ -744,7 +808,7 @@
|
||||
// (call println "=== js ===")
|
||||
// (call println js_code)
|
||||
|
||||
(if (not silent) (call println "writing file..."))
|
||||
(if (not silent) (call println "writing file '%'..." output_filename))
|
||||
(call write_text_file output_filename js_code)
|
||||
|
||||
|
||||
|
||||
0
make_stage1.sh
Normal file → Executable file
0
make_stage1.sh
Normal file → Executable file
@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
|
||||
node stage1.js compile.phi stage2.js
|
||||
603
phi.js
603
phi.js
@ -1,603 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import fs from "node:fs";
|
||||
import process from "node:process";
|
||||
import { Runtime } from "./runtime.js";
|
||||
|
||||
function main() {
|
||||
const filename = process.argv[2];
|
||||
const text = fs.readFileSync(filename).toString();
|
||||
|
||||
const ast = new Parser(text).parse();
|
||||
|
||||
let lastValue = null;
|
||||
|
||||
const evaluator = new Evaluator(filename);
|
||||
for (const expr of ast) {
|
||||
const result = evaluator.eval(expr);
|
||||
if (result.type !== "value") {
|
||||
break;
|
||||
}
|
||||
lastValue = result.value;
|
||||
}
|
||||
if (lastValue !== null) {
|
||||
// console.log(Runtime.valueToJs(lastValue));
|
||||
}
|
||||
}
|
||||
|
||||
class Evaluator {
|
||||
constructor(filename) {
|
||||
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
||||
this.currentLine = 0;
|
||||
this.filename = filename;
|
||||
this.runtime = new Runtime({ filename, args: process.argv.slice(3) });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expr} expr
|
||||
*/
|
||||
eval(expr) {
|
||||
if (!expr) {
|
||||
this.runtime.printPanic(
|
||||
"expression could not be evaluated",
|
||||
this.currentLine,
|
||||
);
|
||||
throw new Error("expression could not be evaluated");
|
||||
}
|
||||
|
||||
this.currentLine = expr.line;
|
||||
this.runtime.setLine(expr.line)
|
||||
if (expr.type === "list") {
|
||||
return this.evalList(expr, expr.line);
|
||||
} else if (expr.type === "int") {
|
||||
return { type: "value", value: { type: "int", value: expr.value } };
|
||||
} else if (expr.type === "string") {
|
||||
return {
|
||||
type: "value",
|
||||
value: { type: "string", value: expr.value },
|
||||
};
|
||||
} else if (expr.type === "ident") {
|
||||
const sym = this.findSym(expr.value);
|
||||
if (!sym) {
|
||||
this.panic(
|
||||
`undefined symbol '${expr.value}'`,
|
||||
);
|
||||
}
|
||||
if (sym.type === "local") {
|
||||
return { type: "value", value: { ...sym.value } };
|
||||
} else {
|
||||
return { type: "value", value: { ...sym } };
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`unknown expr type '${expr.type}' on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
evalToValue(expr) {
|
||||
const result = this.eval(expr);
|
||||
if (result.type !== "value") {
|
||||
throw new Error(`expected value on line ${expr.line}`);
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
|
||||
evalList(expr) {
|
||||
const s = expr.values;
|
||||
const id = s[0]?.type === "ident" ? s[0].value : undefined;
|
||||
if (id === "fn") {
|
||||
return this.evalFn(expr);
|
||||
} else if (id === "return") {
|
||||
return {
|
||||
type: "return",
|
||||
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
||||
};
|
||||
} else if (id === "let") {
|
||||
return this.evalLet(expr);
|
||||
} else if (id === "if") {
|
||||
return this.evalIf(expr);
|
||||
} else if (id === "loop") {
|
||||
return this.evalLoop(expr);
|
||||
} else if (id === "for") {
|
||||
return this.evalFor(expr);
|
||||
} else if (id === "break") {
|
||||
return {
|
||||
type: "break",
|
||||
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
||||
};
|
||||
} else if (id === "do") {
|
||||
return this.evalDo(expr);
|
||||
} else if (id === "call") {
|
||||
return this.evalCall(expr);
|
||||
} else if (id === "not") {
|
||||
const value = this.evalToValue(s[1]);
|
||||
return {
|
||||
type: "value",
|
||||
value: { type: "bool", value: !value.value },
|
||||
};
|
||||
} else if (id === "or") {
|
||||
const left = this.evalToValue(s[1]);
|
||||
if (this.runtime.truthy(left)) {
|
||||
return { type: "value", value: { type: "bool", value: true } };
|
||||
} else {
|
||||
const right = this.evalToValue(s[2]);
|
||||
return { type: "value", value: { type: "bool", value: this.runtime.truthy(right) } };
|
||||
}
|
||||
} else if (id === "and") {
|
||||
const left = this.evalToValue(s[1]);
|
||||
if (this.runtime.truthy(left)) {
|
||||
const right = this.evalToValue(s[2]);
|
||||
return { type: "value", value: { type: "bool", value: this.runtime.truthy(right) } };
|
||||
} else {
|
||||
return { type: "value", value: { type: "bool", value: false } };
|
||||
}
|
||||
} else if (id in this.artithmeticOps) {
|
||||
const left = this.evalToValue(s[1]);
|
||||
const right = this.evalToValue(s[2]);
|
||||
return { type: "value", value: this.artithmeticOps[id](left, right) };
|
||||
} else if (id === "==") {
|
||||
const left = this.evalToValue(s[1]);
|
||||
const right = this.evalToValue(s[2]);
|
||||
return {
|
||||
type: "value",
|
||||
value: this.runtime.opEq(left, right),
|
||||
};
|
||||
} else if (id === "!=") {
|
||||
const left = this.evalToValue(s[1]);
|
||||
const right = this.evalToValue(s[2]);
|
||||
return {
|
||||
type: "value",
|
||||
value: this.runtime.opNe(left, right),
|
||||
};
|
||||
} else if (id in this.comparisonOps) {
|
||||
const left = this.evalToValue(s[1]);
|
||||
const right = this.evalToValue(s[2]);
|
||||
return {
|
||||
type: "value",
|
||||
value: this.runtime.comparisonOperation(left, right, this.comparisonOps[id]),
|
||||
};
|
||||
} else if (id in this.assignOps) {
|
||||
return this.evalAssign(expr);
|
||||
} else {
|
||||
return {
|
||||
type: "value",
|
||||
value: {
|
||||
type: "list",
|
||||
values: s.map((expr) => this.evalToValue(expr)),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
evalFn(expr) {
|
||||
const s = expr.values;
|
||||
const name = s[1].value;
|
||||
this.syms.map.set(name, {
|
||||
type: "fn",
|
||||
line: expr.line,
|
||||
name,
|
||||
params: s[2].values.map((ident) => ident.value),
|
||||
body: s[3],
|
||||
syms: this.syms,
|
||||
});
|
||||
return { type: "value", value: { type: "null" } };
|
||||
}
|
||||
|
||||
evalLet(expr) {
|
||||
const s = expr.values;
|
||||
const value = this.evalToValue(s[2]);
|
||||
this.assignPattern(s[1], value);
|
||||
return { type: "value", value: { type: "null" } };
|
||||
}
|
||||
|
||||
evalIf(expr) {
|
||||
const s = expr.values;
|
||||
const cond = this.evalToValue(s[1]);
|
||||
if (cond.type !== "bool") {
|
||||
throw new Error(
|
||||
`expected bool on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
if (cond.value) {
|
||||
return this.eval(s[2]);
|
||||
} else if (s[3]) {
|
||||
return this.eval(s[3]);
|
||||
} else {
|
||||
return { type: "value", value: "null" };
|
||||
}
|
||||
}
|
||||
|
||||
evalLoop(expr) {
|
||||
const s = expr.values;
|
||||
while (true) {
|
||||
const result = this.eval(s[1]);
|
||||
if (result.type === "break") {
|
||||
return { type: "value", value: result.value };
|
||||
} else if (result.type !== "value") {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evalFor(expr) {
|
||||
const s = expr.values;
|
||||
|
||||
const value = this.evalToValue(s[2]);
|
||||
if (value.type !== "list") {
|
||||
throw new Error(
|
||||
`expected list on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
|
||||
const outerSyms = this.syms;
|
||||
this.syms = { parent: outerSyms, map: new Map() };
|
||||
|
||||
for (let i = 0; i < value.values.length; ++i) {
|
||||
this.assignPattern(s[1], value.values[i]);
|
||||
const result = this.eval(s[3]);
|
||||
if (result.type === "break") {
|
||||
return { type: "value", value: result.value };
|
||||
} else if (result.type !== "value") {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
this.syms = outerSyms;
|
||||
return { type: "value", value: { type: "null" } };
|
||||
}
|
||||
|
||||
evalDo(expr) {
|
||||
const s = expr.values;
|
||||
const outerSyms = this.syms;
|
||||
this.syms = { parent: outerSyms, map: new Map() };
|
||||
|
||||
let lastValue = { type: "null" };
|
||||
|
||||
for (const expr of s.slice(1)) {
|
||||
const result = this.eval(expr);
|
||||
if (result.type !== "value") {
|
||||
return result;
|
||||
}
|
||||
lastValue = result.value;
|
||||
}
|
||||
|
||||
this.syms = outerSyms;
|
||||
return { type: "value", value: lastValue };
|
||||
}
|
||||
|
||||
evalCall(expr) {
|
||||
const s = expr.values;
|
||||
const args = s.slice(2).map((arg) => this.evalToValue(arg));
|
||||
const fnValue = this.evalToValue(s[1]);
|
||||
|
||||
if (fnValue.type === "builtin") {
|
||||
return { type: "value", value: fnValue.fn(...args) };
|
||||
} else if (fnValue.type !== "fn") {
|
||||
throw new Error("cannot call non-function");
|
||||
}
|
||||
|
||||
this.runtime.pushCall(fnValue.name, expr.line);
|
||||
const callerSyms = this.syms;
|
||||
this.syms = {
|
||||
parent: fnValue.syms,
|
||||
map: new Map(),
|
||||
};
|
||||
if (fnValue.params.length !== args.length) {
|
||||
this.panic(
|
||||
`incorrect amount of arguments for function '${fnValue.name}'`,
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < fnValue.params.length; ++i) {
|
||||
this.syms.map.set(fnValue.params[i], args[i]);
|
||||
}
|
||||
|
||||
let returnValue = { type: "null" };
|
||||
const result = this.eval(fnValue.body);
|
||||
|
||||
if (result.type === "value" || result.type === "return") {
|
||||
returnValue = result.value;
|
||||
} else {
|
||||
throw new Error(`illegal ${result.type} across boundry`);
|
||||
}
|
||||
|
||||
this.syms = callerSyms;
|
||||
this.runtime.popCall();
|
||||
return { type: "value", value: returnValue };
|
||||
}
|
||||
|
||||
evalAssign(expr) {
|
||||
const s = expr.values;
|
||||
const id = s[0].value;
|
||||
if (s[1].type === "ident") {
|
||||
const sym = this.findSym(s[1].value);
|
||||
if (!sym) {
|
||||
throw new Error(
|
||||
`could not find symbol '${expr.value}' on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
const value = this.evalToValue(s[2]);
|
||||
if (sym.type === "local") {
|
||||
sym.value = this.assignOps[id](sym.value, value);
|
||||
} else {
|
||||
throw new Error(
|
||||
`cannot assign to symbol on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`cannot assign to expression on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
return { type: "value", value: { type: "null" } };
|
||||
}
|
||||
|
||||
/** @param {Expr} pattern */
|
||||
assignPattern(pattern, value) {
|
||||
if (pattern.type === "ident") {
|
||||
if (pattern.value === "_") {
|
||||
return;
|
||||
}
|
||||
this.syms.map.set(pattern.value, {
|
||||
type: "local",
|
||||
line: pattern.line,
|
||||
value,
|
||||
});
|
||||
} else if (pattern.type === "list") {
|
||||
if (value.type !== "list") {
|
||||
this.panic(`expected list`);
|
||||
}
|
||||
for (const [i, p] of pattern.values.entries()) {
|
||||
this.assignPattern(p, value.values[i] ?? { type: "null" });
|
||||
}
|
||||
} else {
|
||||
throw new Error(`cannot assign to pattern on line ${pattern.line}`);
|
||||
}
|
||||
}
|
||||
|
||||
findSym(ident, syms = this.syms) {
|
||||
if (syms.map.has(ident)) {
|
||||
return syms.map.get(ident);
|
||||
} else if (syms.parent) {
|
||||
return this.findSym(ident, syms.parent);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
panic(msg) {
|
||||
this.runtime.setLine(this.currentLine);
|
||||
this.runtime.panic(msg);
|
||||
}
|
||||
|
||||
artithmeticOps = {
|
||||
"+": (left, right) => this.runtime.opAdd(left, right),
|
||||
"-": (left, right) => this.runtime.opSub(left, right),
|
||||
};
|
||||
comparisonOps = {
|
||||
"<": (left, right) => left < right,
|
||||
">": (left, right) => left > right,
|
||||
"<=": (left, right) => left <= right,
|
||||
">=": (left, right) => left >= right,
|
||||
};
|
||||
assignOps = {
|
||||
"=": (_, right) => right,
|
||||
"+=": (left, right) => ({
|
||||
type: left.type === "string" && left.type === right.type
|
||||
? "string"
|
||||
: "int",
|
||||
value: left.value + right.value,
|
||||
}),
|
||||
"-=": (left, right) => ({
|
||||
type: "int",
|
||||
value: left.value - right.value,
|
||||
}),
|
||||
};
|
||||
|
||||
builtinFns = {
|
||||
"format": (...args) => this.runtime.builtinFormat(...args),
|
||||
"print": (...args) => this.runtime.builtinPrint(...args),
|
||||
"println": (...args) => this.runtime.builtinPrintln(...args),
|
||||
"panic": (...args) => this.runtime.builtinPanic(...args),
|
||||
"read_text_file": (...args) => this.runtime.builtinReadTextFile(...args),
|
||||
"write_text_file": (...args) => this.runtime.builtinWriteTextFile(...args),
|
||||
"push": (...args) => this.runtime.builtinPush(...args),
|
||||
"at": (...args) => this.runtime.builtinAt(...args),
|
||||
"set": (...args) => this.runtime.builtinSet(...args),
|
||||
"len": (...args) => this.runtime.builtinLen(...args),
|
||||
"string_to_int": (...args) => this.runtime.builtinStringToInt(...args),
|
||||
"char_code": (...args) => this.runtime.builtinCharCode(...args),
|
||||
"strings_join": (...args) => this.runtime.builtinStringsJoin(...args),
|
||||
"get_args": (...args) => this.runtime.builtinGetArgs(...args),
|
||||
};
|
||||
|
||||
consts = {
|
||||
"null": { type: "null" },
|
||||
"false": { type: "bool", value: false },
|
||||
"true": { type: "bool", value: true },
|
||||
};
|
||||
|
||||
builtins = [
|
||||
...Object.entries(this.builtinFns)
|
||||
.map(([key, fn]) => [key, { type: "builtin", fn }]),
|
||||
...Object.entries(this.consts),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expr} expr
|
||||
* @returns {string}
|
||||
*/
|
||||
function exprToString(expr) {
|
||||
if (expr.type === "ident") {
|
||||
return expr.value;
|
||||
} else if (expr.type === "int") {
|
||||
return `${expr.value}`;
|
||||
} else if (expr.type === "string") {
|
||||
return `"${expr.value}"`;
|
||||
} else if (expr.type === "list") {
|
||||
return `(${expr.values.map((v) => exprToString(v)).join(" ")})`;
|
||||
} else {
|
||||
throw new Error(`unknown value type ${expr.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ type: string, line: number, value: any, values?: Expr } } Expr
|
||||
*/
|
||||
|
||||
class Parser {
|
||||
constructor(text) {
|
||||
const stringExtractor = new StringExtractor(text);
|
||||
stringExtractor.extract();
|
||||
this.strings = stringExtractor.getStrings();
|
||||
this.text = stringExtractor.getOutputText();
|
||||
this.tokens = this.text
|
||||
.replace(/\/\/.*?$/mg, "")
|
||||
.replace(/([\(\)\n])/g, " $1 ")
|
||||
.split(/[ \t\r]/)
|
||||
.filter((tok) => tok !== "");
|
||||
this.idx = 0;
|
||||
this.line = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Expr[]}
|
||||
*/
|
||||
parse() {
|
||||
if (this.curr === "\n") {
|
||||
this.step();
|
||||
}
|
||||
|
||||
const exprs = [];
|
||||
while (!this.done) {
|
||||
exprs.push(this.parseExpr());
|
||||
}
|
||||
return exprs;
|
||||
}
|
||||
|
||||
parseExpr() {
|
||||
const line = this.line;
|
||||
if (this.eat("(")) {
|
||||
const values = [];
|
||||
while (!this.test(")")) {
|
||||
values.push(this.parseExpr());
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
throw new Error(`expected ')'`);
|
||||
}
|
||||
this.step();
|
||||
return { type: "list", line, values };
|
||||
} else if (this.test(/STRING_\d+/)) {
|
||||
const id = Number(this.curr.match(/STRING_(\d+)/)[1]);
|
||||
this.step();
|
||||
return { type: "string", line, value: this.strings[id] };
|
||||
} else if (this.test(/0|(:?[1-9][0-9]*)/)) {
|
||||
const value = Number(this.curr);
|
||||
this.step();
|
||||
return { type: "int", line, value };
|
||||
} else if (this.test(/[a-zA-Z0-9\+\-\*/%&\|=\?\!<>'_]+/)) {
|
||||
const value = this.curr;
|
||||
this.step();
|
||||
return { type: "ident", line, value };
|
||||
} else {
|
||||
throw new Error(
|
||||
`expected expression, got ${this.curr} on line ${this.line}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
eat(tok) {
|
||||
if (!this.test(tok)) {
|
||||
return false;
|
||||
}
|
||||
this.step();
|
||||
return true;
|
||||
}
|
||||
test(tok) {
|
||||
if (this.done) {
|
||||
return false;
|
||||
}
|
||||
if (typeof tok === "string") {
|
||||
return this.curr === tok;
|
||||
} else if (tok instanceof RegExp) {
|
||||
return new RegExp(`^${tok.source}$`)
|
||||
.test(this.curr);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
step() {
|
||||
do {
|
||||
if (!this.done && this.curr === "\n") {
|
||||
this.line += 1;
|
||||
}
|
||||
this.idx += 1;
|
||||
} while (!this.done && this.curr === "\n");
|
||||
}
|
||||
|
||||
get done() {
|
||||
return this.idx >= this.tokens.length;
|
||||
}
|
||||
get curr() {
|
||||
return this.tokens[this.idx];
|
||||
}
|
||||
}
|
||||
|
||||
class StringExtractor {
|
||||
constructor(text) {
|
||||
this.text = text;
|
||||
this.idx = 0;
|
||||
this.outputText = "";
|
||||
this.strings = [];
|
||||
}
|
||||
|
||||
extract() {
|
||||
while (this.idx < this.text.length) {
|
||||
if (this.text[this.idx] == '"') {
|
||||
this.extractString();
|
||||
} else {
|
||||
this.outputText += this.text[this.idx];
|
||||
this.idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractString() {
|
||||
this.idx += 1;
|
||||
let value = "";
|
||||
while (this.idx < this.text.length && this.text[this.idx] != '"') {
|
||||
if (this.text[this.idx] == "\\") {
|
||||
this.idx += 1;
|
||||
if (this.idx > this.text.length) {
|
||||
break;
|
||||
}
|
||||
const ch = this.text[this.idx];
|
||||
value += {
|
||||
"0": "\0",
|
||||
"t": "\t",
|
||||
"r": "\r",
|
||||
"n": "\n",
|
||||
}[ch] ?? ch;
|
||||
} else {
|
||||
value += this.text[this.idx];
|
||||
}
|
||||
this.idx += 1;
|
||||
}
|
||||
if (this.idx >= this.text.length || this.text[this.idx] != '"') {
|
||||
throw new Error("expected '\"'");
|
||||
}
|
||||
this.idx += 1;
|
||||
const id = this.strings.length;
|
||||
this.strings.push(value);
|
||||
this.outputText += `STRING_${id}`;
|
||||
}
|
||||
|
||||
getStrings() {
|
||||
return this.strings;
|
||||
}
|
||||
getOutputText() {
|
||||
return this.outputText;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
36
runtime.js
36
runtime.js
@ -84,11 +84,11 @@ export class Runtime {
|
||||
}
|
||||
|
||||
opEq(left, right) {
|
||||
return { type: "bool", value: this.equalityOperation(left, right) }
|
||||
return { type: "bool", value: this.equalityOperation(left, right) };
|
||||
}
|
||||
|
||||
opNe(left, right) {
|
||||
return { type: "bool", value: !this.equalityOperation(left, right) }
|
||||
return { type: "bool", value: !this.equalityOperation(left, right) };
|
||||
}
|
||||
|
||||
opNot(expr) {
|
||||
@ -105,23 +105,31 @@ export class Runtime {
|
||||
return { type: "bool", value: action(left.value, right.value) };
|
||||
}
|
||||
|
||||
opLt(left, right) { return this.comparisonOperation(right, left, (l, r) => l < r); }
|
||||
opGt(left, right) { return this.comparisonOperation(right, left, (l, r) => l > r); }
|
||||
opLte(left, right) { return this.comparisonOperation(right, left, (l, r) => l <= r); }
|
||||
opGte(left, right) { return this.comparisonOperation(right, left, (l, r) => l >= r); }
|
||||
opLt(left, right) {
|
||||
return this.comparisonOperation(left, right, (l, r) => l < r);
|
||||
}
|
||||
opGt(left, right) {
|
||||
return this.comparisonOperation(left, right, (l, r) => l > r);
|
||||
}
|
||||
opLte(left, right) {
|
||||
return this.comparisonOperation(left, right, (l, r) => l <= r);
|
||||
}
|
||||
opGte(left, right) {
|
||||
return this.comparisonOperation(left, right, (l, r) => l >= r);
|
||||
}
|
||||
|
||||
opAdd(left, right) {
|
||||
if (left.type === "int" && right.type === "int") {
|
||||
return { type: "int", value: left.value + right.value }
|
||||
return { type: "int", value: left.value + right.value };
|
||||
} else if (left.type === "string" && right.type === "string") {
|
||||
return { type: "string", value: left.value + right.value }
|
||||
return { type: "string", value: left.value + right.value };
|
||||
} else {
|
||||
this.panic(`cannot apply '+' on ${left.type} and ${right.type}`);
|
||||
}
|
||||
}
|
||||
opSub(left, right) {
|
||||
if (left.type === "int" && right.type === "int") {
|
||||
return { type: "int", value: left.value + right.value }
|
||||
return { type: "int", value: left.value - right.value };
|
||||
} else {
|
||||
this.panic(`cannot apply '-' on ${left.type} and ${right.type}`);
|
||||
}
|
||||
@ -226,8 +234,9 @@ export class Runtime {
|
||||
} else if (value.type === "string") {
|
||||
return `${value.value}`;
|
||||
} else if (value.type === "list") {
|
||||
return `(${value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||
})`;
|
||||
return `(${
|
||||
value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||
})`;
|
||||
} else {
|
||||
throw new Error(`unknown value type ${value.type}`);
|
||||
}
|
||||
@ -243,8 +252,9 @@ export class Runtime {
|
||||
} else if (value.type === "string") {
|
||||
return `"${value.value}"`;
|
||||
} else if (value.type === "list") {
|
||||
return `(${value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||
})`;
|
||||
return `(${
|
||||
value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||
})`;
|
||||
} else {
|
||||
throw new Error(`unknown value type ${value.type}`);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user