Compare commits

..

No commits in common. "c4e199c8686df44db21bf03e9e7e24c987769567" and "4289d04ec3f17800973196b396abefaca764dcec" have entirely different histories.

7 changed files with 1884 additions and 1425 deletions

View File

@ -1,52 +1,17 @@
(fn dbg (msg) (do
(if is_phi_compiler (do
(call println "dbg: %" msg)
))
))
(fn Emitter (ast filename) (do (fn Emitter (ast filename) (do
(let output ()) (let output ())
(let (enter_scope leave_scope define_sym get_sym print_syms) (call Syms)) (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 (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 (fn generate () (do
(call emit "#!/usr/bin/env node\n") (call emit "#!/usr/bin/env node\n")
(call emit "import { Runtime } from \"./runtime.js\";\n") (call emit "import { Runtime } from \"./runtime.js\"\n")
(call emit "const runtime = new Runtime({ filename: \"") (call emit "const runtime = new Runtime(\"")
(call emit 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 discover_syms ast)
(call emit_exprs ast) (call emit_exprs ast)
(return (call strings_join output)) (return (call strings_join output))
@ -67,8 +32,8 @@
(if (== (call len s) 0) (return)) (if (== (call len s) 0) (return))
(let ((_ _ id)) s) (let ((_ _ id)) s)
(if (== id "fn") (do (if (== id "fn") (do
(let (_ (_ _ ident) (_ _ params) body) s) (let (_ (_ _ name) (_ _ params) body) s)
(call define_fn ident line) (call define_sym name ("fn" name line))
)) ))
)) ))
)) ))
@ -77,10 +42,10 @@
(let (ty line) expr) (let (ty line) expr)
(if (== ty "list") (do (if (== ty "list") (do
(call emit_list expr) (call emit_list expr)
) (if (== ty "int") (do ) (if (== ty "int") (
(let (_ _ value) expr) (let (_ _ value) expr)
(call emit (call format "({ type: \"int\", value: % })" value)) (call emit (call format "({ type: \"int\", value: % })" value))
) (if (== ty "string") (do ) (if (== ty "string") (
(let (_ _ value) expr) (let (_ _ value) expr)
(call emit (call format "({ type: \"string\", value: \"%\" })" (call string_escape value))) (call emit (call format "({ type: \"string\", value: \"%\" })" (call string_escape value)))
) (if (== ty "ident") (do ) (if (== ty "ident") (do
@ -103,16 +68,16 @@
(call panic "undefined symbol '%' on line %" value line) (call panic "undefined symbol '%' on line %" value line)
)) ))
(let (sym_id sym_ty) sym) (let (sym_ty) sym)
(if (== sym_ty "builtin") (do (if (== sym_ty "builtin") (do
(let (_ _ id) sym) (let (_ id) sym)
(call emit (call format "((...args) => runtime.%(...args))" id)) (call emit (call format "((...args) => runtime.%(...args))" id))
) (if (== sym_ty "fn") (do ) (if (== sym_ty "fn") (do
(call emit (call format "_%%" value sym_id)) (call emit (call format "_%" value))
) (if (== sym_ty "param") (do ) (if (== sym_ty "param") (do
(call emit (call format "_%%" value sym_id)) (call emit (call format "_%" value))
) (if (== sym_ty "let") (do ) (if (== sym_ty "let") (do
(call emit (call format "_%%" value sym_id)) (call emit (call format "_%" value))
) (do ) (do
(call panic "not implemented '%'" sym_ty) (call panic "not implemented '%'" sym_ty)
))))) )))))
@ -129,33 +94,30 @@
)) ))
(let ((id_ty _ id)) s) (let ((id_ty _ id)) s)
(if (!= id_ty "ident") (do (if (!= id_ty "ident") (do
(call emit_list_literal s) (call emit "({ type: \"list\", values: [] })")
(return) (return)
)) ))
(if (== id "fn") (do (if (== id "fn") (do
(let (_ (_ _ ident) (_ _ params) body) s) (let (_ (_ _ name) (_ _ params) body) s)
(call emit (call format "function _%(" name))
(let sym (call get_sym ident))
(let (sym_id) sym)
(call emit (call format "function _%%(" ident sym_id))
(call enter_scope) (call enter_scope)
(let first true) (let first true)
(for (_ _ ident) params (do (for (_ _ name) params (do
(if (not first) (do (if (not first) (do
(call emit ", ") (call emit ", ")
)) ))
(= first false) (= first false)
(let sym_id (call define_param ident line)) (call emit (call format "_%" name))
(call emit (call format "_%%" ident sym_id))
(call define_sym name ("param" name line))
)) ))
(call emit ") {\n") (call emit (call format ") {\n" name))
(call emit (call format "runtime.pushCall(\"%\");\n" ident)) (call emit (call format "runtime.pushCall(\"%\");\n" name))
(call emit_expr body) (call emit_expr body)
(call emit ";\nruntime.popCall();\nreturn { type: \"null\" };\n}") (call emit ";\nruntime.popCall();\nreturn { type: \"null\" };\n}")
@ -165,7 +127,7 @@
(let (_ pat expr) s) (let (_ pat expr) s)
(let reg (call let_node_reg_count)) (let reg (call let_node_reg_count))
(call let_node_reg_increment) (call let_node_reg_increment)
(call emit (call format "const r_% = " reg)) (call emit (call format "let r_% = " reg))
(call emit_expr expr) (call emit_expr expr)
(call emit_let_node pat reg) (call emit_let_node pat reg)
) (if (== id "do") (do ) (if (== id "do") (do
@ -175,22 +137,14 @@
(call leave_scope) (call leave_scope)
) (if (== id "for") (do ) (if (== id "for") (do
(let (_ pat expr body) s) (let (_ pat expr body) s)
(call enter_scope)
(let reg (call let_node_reg_count)) (call emit "for (let ")
(call let_node_reg_increment) (call emit_pat pat)
(call emit (call format "for (const r_% of " reg)) (call emit " of ")
(call emit_expr expr) (call emit_expr expr)
(call emit ".values) {") (call emit ".values) {\n")
(call enter_scope)
(call emit_let_node pat reg)
(call enter_scope)
(call emit ";\n")
(call emit_expr body) (call emit_expr body)
(call emit "}") (call emit "}")
(call leave_scope)
(call leave_scope) (call leave_scope)
) (if (== id "loop") (do ) (if (== id "loop") (do
(let (_ body) s) (let (_ body) s)
@ -199,9 +153,9 @@
(call emit "}") (call emit "}")
) (if (== id "if") (do ) (if (== id "if") (do
(let (_ cond truthy falsy) s) (let (_ cond truthy falsy) s)
(call emit "if (runtime.truthy(") (call emit "if (")
(call emit_expr cond) (call emit_expr cond)
(call emit ")) {\n") (call emit ") {\n")
(call emit_expr truthy) (call emit_expr truthy)
(call emit "}") (call emit "}")
(if (!= falsy null) (do (if (!= falsy null) (do
@ -251,17 +205,17 @@
) (if (== id "or") (do ) (if (== id "or") (do
(let (_ left right) s) (let (_ left right) s)
(call emit (call format "(runtime.setLine(%)" line)) (call emit (call format "(runtime.setLine(%)" line))
(call emit ", { type: \"bool\", value: runtime.truthy(") (call emit ", { type: \"bool\", value: this.runtime.truthy(")
(call emit_expr left) (call emit_expr left)
(call emit ") || runtime.truthy(") (call emit ") || this.runtime.falsy(")
(call emit_expr right) (call emit_expr right)
(call emit ") })") (call emit ") })")
) (if (== id "and") (do ) (if (== id "and") (do
(let (_ left right) s) (let (_ left right) s)
(call emit (call format "(runtime.setLine(%)" line)) (call emit (call format "(runtime.setLine(%)" line))
(call emit ", { type: \"bool\", value: runtime.truthy(") (call emit ", { type: \"bool\", value: this.runtime.truthy(")
(call emit_expr left) (call emit_expr left)
(call emit ") && runtime.truthy(") (call emit ") && this.runtime.falsy(")
(call emit_expr right) (call emit_expr right)
(call emit ") })") (call emit ") })")
) (if (== id "==") (do ) (if (== id "==") (do
@ -286,11 +240,6 @@
(call emit_expr expr) (call emit_expr expr)
(call emit ")") (call emit ")")
) (do ) (do
(call emit_list_literal s)
))))))))))))))))))))))))
))
(fn emit_list_literal (s) (do
(call emit "({ type: \"list\", values: [") (call emit "({ type: \"list\", values: [")
(let first true) (let first true)
(for e s (do (for e s (do
@ -302,6 +251,7 @@
(call emit_expr e) (call emit_expr e)
)) ))
(call emit "] })") (call emit "] })")
))))))))))))))))))))))))
)) ))
(fn emit_let_node (pat base_reg) (do (fn emit_let_node (pat base_reg) (do
@ -311,35 +261,16 @@
(if (== ident "_") (return)) (if (== ident "_") (return))
(let sym_id (call define_let ident line)) (call emit (call format ";\nlet _% = r_%" ident base_reg))
(call emit (call format ";\nlet _%% = r_%" ident sym_id base_reg)) (call define_sym ident ("let" line))
) (if (== pat_ty "list") (do ) (if (== pat_ty "list") (do
(let (_ _ pats) pat) (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) (let i 0)
(for pat pats (do (for pat pats (do
(let reg (call let_node_reg_count)) (let reg (call let_node_reg_count))
(call let_node_reg_increment) (call let_node_reg_increment)
(call emit (call format (call emit (call format ";\nlet r_% = r_%.values[%]" reg base_reg i))
";\nconst r_% = r_%.values[%] ?? { type: \"null\"}"
reg base_reg i
))
(call emit_let_node pat reg) (call emit_let_node pat reg)
(+= i 1) (+= i 1)
)) ))
@ -367,64 +298,59 @@
(if (== sym null) (do (if (== sym null) (do
(call panic "could not find symbol '%' on line %" ident line) (call panic "could not find symbol '%' on line %" ident line)
)) ))
(let (sym_id sym_type sym_ident _) sym) (let (sym_type sym_ident _) sym)
(if (== sym_type "let") (do (if (== sym_type "let") (do
(call emit (call format "(_%% = " sym_ident sym_id)) (call emit (call format "(_% = runtime.assignValue(" sym_ident))
(if (== id "=") (do (if (== id "=") (do
(call emit_expr expr) (call emit_expr expr)
) (if (== id "+") (do ) (if (== id "+") (do
(call emit (call format "runtime.opAdd(_%%, " sym_ident sym_id)) (call emit (call format "runtime.opAdd(_%, " sym_ident))
(call emit_expr expr) (call emit_expr expr)
(call emit ")") (call emit ")")
) (if (== id "-") (do ) (if (== id "-") (do
(call emit (call format "runtime.opSub(_%%, " sym_ident sym_id)) (call emit (call format "runtime.opSub(_%, " sym_ident))
(call emit_expr expr) (call emit_expr expr)
(call emit ")") (call emit ")")
) (do ) (do
(call panic "not implemented") (call panic "not implemented")
)))) ))))
(call emit ")") (call emit "))")
) (do ) (do
(call panic "cannot assign to symbol '%' on line %" sym_ident line) (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 (fn emit (str) (do
(call push output str) (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)) (return (generate))
)) ))
@ -443,7 +369,22 @@
)) ))
(fn Syms () (do (fn Syms () (do
(let syms (null ())) (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"))
)))
(fn enter_scope () (do (fn enter_scope () (do
(= syms (syms ())) (= syms (syms ()))
@ -455,18 +396,18 @@
)) ))
(fn define (ident sym) (do (fn define (ident sym) (do
(let (_ map) syms) (let (_ syms) syms)
(let i 0) (let i 0)
(loop (do (loop (do
(if (>= i (call len map)) (break)) (if (>= i (call len syms)) (break))
(let (s_ident _) (call at map i)) (let (s_ident _) (call at syms i))
(if (== ident s_ident) (do (if (== ident s_ident) (do
(call set map i (ident sym)) (call set syms i (ident sym))
(return) (return)
)) ))
(+= i 1) (+= i 1)
)) ))
(call push map (ident sym)) (call push syms (ident sym))
)) ))
(fn find_sym (syms ident) (do (fn find_sym (syms ident) (do
@ -765,30 +706,25 @@
(return elems) (return elems)
)) ))
(let silent false) (let silent true)
(let (input_filename output_filename) (call get_args)) (let (input_filename output_filename) (call get_args))
(if (not silent) (call println "reading file '%'..." input_filename)) (if (not silent) (call println "reading file..."))
(let text (call read_text_file input_filename)) (let text (call read_text_file input_filename))
//(call println "=== text ===") (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...")) (if (not silent) (call println "tokenizing..."))
(let tokens (call tokenize text)) (let tokens (call tokenize text))
//(call println "=== tokens ===") (call println "=== tokens ===")
//(for elem tokens (do // (for (tok line value) tokens (do
// (let (tok line value) elem) // (call println "%\t%\t%" line tok (if (!= value null) value ""))
// (if (!= value null) (do
// (call println "%\t%\t%" line tok value)
// ) (do
// (call println "%\t%" line tok)
// )) // ))
//)) (call println (call len tokens))
//(call println (call len tokens))
(if (not silent) (call println "parsing...")) (if (not silent) (call println "parsing..."))
(let parser (call Parser tokens)) (let parser (call Parser tokens))
@ -808,7 +744,7 @@
// (call println "=== js ===") // (call println "=== js ===")
// (call println js_code) // (call println js_code)
(if (not silent) (call println "writing file '%'..." output_filename)) (if (not silent) (call println "writing file..."))
(call write_text_file output_filename js_code) (call write_text_file output_filename js_code)

0
make_stage1.sh Executable file → Normal file
View File

5
make_stage2.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -xe
node stage1.js compile.phi stage2.js

603
phi.js Normal file
View File

@ -0,0 +1,603 @@
"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();

View File

@ -84,11 +84,11 @@ export class Runtime {
} }
opEq(left, right) { opEq(left, right) {
return { type: "bool", value: this.equalityOperation(left, right) }; return { type: "bool", value: this.equalityOperation(left, right) }
} }
opNe(left, right) { opNe(left, right) {
return { type: "bool", value: !this.equalityOperation(left, right) }; return { type: "bool", value: !this.equalityOperation(left, right) }
} }
opNot(expr) { opNot(expr) {
@ -105,31 +105,23 @@ export class Runtime {
return { type: "bool", value: action(left.value, right.value) }; return { type: "bool", value: action(left.value, right.value) };
} }
opLt(left, right) { opLt(left, right) { return this.comparisonOperation(right, left, (l, r) => l < r); }
return this.comparisonOperation(left, right, (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); }
opGt(left, right) { opGte(left, right) { return this.comparisonOperation(right, left, (l, r) => l >= r); }
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) { opAdd(left, right) {
if (left.type === "int" && right.type === "int") { 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") { } 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 { } else {
this.panic(`cannot apply '+' on ${left.type} and ${right.type}`); this.panic(`cannot apply '+' on ${left.type} and ${right.type}`);
} }
} }
opSub(left, right) { opSub(left, right) {
if (left.type === "int" && right.type === "int") { if (left.type === "int" && right.type === "int") {
return { type: "int", value: left.value - right.value }; return { type: "int", value: left.value + right.value }
} else { } else {
this.panic(`cannot apply '-' on ${left.type} and ${right.type}`); this.panic(`cannot apply '-' on ${left.type} and ${right.type}`);
} }
@ -234,8 +226,7 @@ export class Runtime {
} else if (value.type === "string") { } else if (value.type === "string") {
return `${value.value}`; return `${value.value}`;
} else if (value.type === "list") { } else if (value.type === "list") {
return `(${ return `(${value.values.map((v) => Runtime.valueToString(v)).join(" ")
value.values.map((v) => Runtime.valueToString(v)).join(" ")
})`; })`;
} else { } else {
throw new Error(`unknown value type ${value.type}`); throw new Error(`unknown value type ${value.type}`);
@ -252,8 +243,7 @@ export class Runtime {
} else if (value.type === "string") { } else if (value.type === "string") {
return `"${value.value}"`; return `"${value.value}"`;
} else if (value.type === "list") { } else if (value.type === "list") {
return `(${ return `(${value.values.map((v) => Runtime.valueToString(v)).join(" ")
value.values.map((v) => Runtime.valueToString(v)).join(" ")
})`; })`;
} else { } else {
throw new Error(`unknown value type ${value.type}`); throw new Error(`unknown value type ${value.type}`);

1150
stage1.js Normal file

File diff suppressed because it is too large Load Diff

1229
stage2.js

File diff suppressed because it is too large Load Diff