compile test program
This commit is contained in:
parent
d084959281
commit
b9b479fd63
110
compile.phi
110
compile.phi
@ -1,9 +1,15 @@
|
|||||||
|
|
||||||
(fn Emitter (ast) (do
|
(fn Emitter (ast filename) (do
|
||||||
(let output ())
|
(let output ())
|
||||||
|
|
||||||
|
(let (enter_scope leave_scope define_sym get_sym) (call Syms))
|
||||||
|
|
||||||
(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 "const runtime = new Runtime(\"")
|
||||||
|
(call emit filename)
|
||||||
|
(call emit "\")\n")
|
||||||
(call emit_exprs ast)
|
(call emit_exprs ast)
|
||||||
(return (call strings_join output))
|
(return (call strings_join output))
|
||||||
))
|
))
|
||||||
@ -11,7 +17,7 @@
|
|||||||
(fn emit_exprs (exprs) (do
|
(fn emit_exprs (exprs) (do
|
||||||
(for expr exprs (do
|
(for expr exprs (do
|
||||||
(call emit_expr expr)
|
(call emit_expr expr)
|
||||||
(call emit "\n")
|
(call emit ";\n")
|
||||||
))
|
))
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -21,13 +27,33 @@
|
|||||||
(call emit_list expr)
|
(call emit_list expr)
|
||||||
) (if (== ty "int") (
|
) (if (== ty "int") (
|
||||||
(let (_ _ value) expr)
|
(let (_ _ value) expr)
|
||||||
(call emit (call format "%" value))
|
(call emit (call format "{ type: \"int\", value: % }" value))
|
||||||
) (if (== ty "string") (
|
) (if (== ty "string") (
|
||||||
(let (_ _ value) expr)
|
(let (_ _ value) expr)
|
||||||
(call emit (call format "\"%\"" (call string_escape value)))
|
(call emit (call format "{ type: \"string\", value: \"%\" }" (call string_escape value)))
|
||||||
) (if (== ty "ident") (
|
) (if (== ty "ident") (
|
||||||
(let (_ _ value) expr)
|
(let (_ _ value) expr)
|
||||||
(call emit (call format "_%" value))
|
|
||||||
|
(let sym (call get_sym value))
|
||||||
|
(if (== sym null) (do
|
||||||
|
(call panic "undefined symbol '%'" value)
|
||||||
|
))
|
||||||
|
|
||||||
|
(let (sym_ty) sym)
|
||||||
|
(if (== sym_ty "builtin") (do
|
||||||
|
(let (_ id) sym)
|
||||||
|
(if (== id "println") (do
|
||||||
|
(call emit (call format
|
||||||
|
"((...args) => (runtime.setLine(%), runtime.builtinPrintln(...args)))"
|
||||||
|
line))
|
||||||
|
) (do
|
||||||
|
(call panic "unknown builtin '%'" id)
|
||||||
|
))
|
||||||
|
) (if (== sym_ty "fn") (do
|
||||||
|
(call emit (call format "_%" value))
|
||||||
|
) (do
|
||||||
|
(call panic "not implemented '%'" sym_ty)
|
||||||
|
)))
|
||||||
) (do
|
) (do
|
||||||
(call panic "unknown expr type '%' on line %" ty line)
|
(call panic "unknown expr type '%' on line %" ty line)
|
||||||
)))))
|
)))))
|
||||||
@ -36,7 +62,7 @@
|
|||||||
(fn emit_list (expr) (do
|
(fn emit_list (expr) (do
|
||||||
(let (ty line s) expr)
|
(let (ty line s) expr)
|
||||||
(if (== (call len s) 0) (do
|
(if (== (call len s) 0) (do
|
||||||
(call emit "[]")
|
(call emit "{ type: \"list\", values: [] }")
|
||||||
(return)
|
(return)
|
||||||
))
|
))
|
||||||
(let ((_ _ id)) s)
|
(let ((_ _ id)) s)
|
||||||
@ -53,6 +79,9 @@
|
|||||||
|
|
||||||
(call emit (call format "_%" name))
|
(call emit (call format "_%" name))
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(call define_sym name ("fn" name line))
|
||||||
|
|
||||||
(call emit (call format ") {\n" name))
|
(call emit (call format ") {\n" name))
|
||||||
(call emit_expr body)
|
(call emit_expr body)
|
||||||
(call emit "}")
|
(call emit "}")
|
||||||
@ -85,7 +114,7 @@
|
|||||||
|
|
||||||
(call emit ")")
|
(call emit ")")
|
||||||
) (do
|
) (do
|
||||||
(call emit "[")
|
(call emit "{ type: \"list\", values: [")
|
||||||
(let first true)
|
(let first true)
|
||||||
(for e s (do
|
(for e s (do
|
||||||
(if (not first) (do
|
(if (not first) (do
|
||||||
@ -95,7 +124,7 @@
|
|||||||
|
|
||||||
(call emit_expr e)
|
(call emit_expr e)
|
||||||
))
|
))
|
||||||
(call emit "]")
|
(call emit "] }")
|
||||||
)))))
|
)))))
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -106,6 +135,65 @@
|
|||||||
(return (generate))
|
(return (generate))
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(fn Syms () (do
|
||||||
|
(let syms (null (
|
||||||
|
("println" ("builtin" "println"))
|
||||||
|
)))
|
||||||
|
|
||||||
|
(fn enter_scope () (do
|
||||||
|
(= syms (syms ()))
|
||||||
|
))
|
||||||
|
|
||||||
|
(fn leave_scope () (do
|
||||||
|
(let (parent _) syms)
|
||||||
|
(= syms parent)
|
||||||
|
))
|
||||||
|
|
||||||
|
(fn define (ident sym) (do
|
||||||
|
(let (_ syms) syms)
|
||||||
|
(let i 0)
|
||||||
|
(loop (do
|
||||||
|
(if (>= i (call len syms)) (break))
|
||||||
|
(let (s_ident s_sym) (call at syms i))
|
||||||
|
(if (== ident s_ident) (do
|
||||||
|
(call set s_syms i sym)
|
||||||
|
(return)
|
||||||
|
))
|
||||||
|
(+= i 1)
|
||||||
|
))
|
||||||
|
(call push syms (ident sym))
|
||||||
|
))
|
||||||
|
|
||||||
|
(fn find_sym (syms ident) (do
|
||||||
|
(let (parent map) syms)
|
||||||
|
(let i 0)
|
||||||
|
(loop (do
|
||||||
|
(if (>= i (call len map)) (break))
|
||||||
|
(let (s_ident s_sym) (call at map i))
|
||||||
|
(if (== ident s_ident) (do
|
||||||
|
(return s_sym)
|
||||||
|
))
|
||||||
|
(+= i 1)
|
||||||
|
))
|
||||||
|
(if (!= parent null) (do
|
||||||
|
(return (call find_syms parent ident))
|
||||||
|
))
|
||||||
|
(return null)
|
||||||
|
))
|
||||||
|
|
||||||
|
(fn get (ident) (do
|
||||||
|
(return (call find_sym syms ident))
|
||||||
|
))
|
||||||
|
|
||||||
|
(return (
|
||||||
|
enter_scope
|
||||||
|
leave_scope
|
||||||
|
define
|
||||||
|
get
|
||||||
|
))
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
(fn string_escape (str) (do
|
(fn string_escape (str) (do
|
||||||
(let str_len (call len str))
|
(let str_len (call len str))
|
||||||
(let i 0)
|
(let i 0)
|
||||||
@ -357,8 +445,10 @@
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
(let input_filename "program.phi")
|
||||||
|
|
||||||
(call println "reading file...")
|
(call println "reading file...")
|
||||||
(let text (call read_text_file "program.phi"))
|
(let text (call read_text_file input_filename))
|
||||||
|
|
||||||
//(call println "=== text ===")
|
//(call println "=== text ===")
|
||||||
//(call println text)
|
//(call println text)
|
||||||
@ -382,7 +472,7 @@
|
|||||||
//))
|
//))
|
||||||
|
|
||||||
(call println "emitting...")
|
(call println "emitting...")
|
||||||
(let emitter (call Emitter ast))
|
(let emitter (call Emitter ast input_filename))
|
||||||
(let (emit) emitter)
|
(let (emit) emitter)
|
||||||
(let js_code (call emit))
|
(let js_code (call emit))
|
||||||
|
|
||||||
|
|||||||
236
phi.js
236
phi.js
@ -1,7 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import * as fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
import { Runtime } from "./runtime.js";
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const filename = process.argv[2];
|
const filename = process.argv[2];
|
||||||
@ -20,7 +21,7 @@ function main() {
|
|||||||
lastValue = result.value;
|
lastValue = result.value;
|
||||||
}
|
}
|
||||||
if (lastValue !== null) {
|
if (lastValue !== null) {
|
||||||
console.log(valueToJs(lastValue));
|
console.log(Runtime.valueToJs(lastValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ class Evaluator {
|
|||||||
constructor(filename) {
|
constructor(filename) {
|
||||||
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
||||||
this.currentLine = 0;
|
this.currentLine = 0;
|
||||||
this.callStack = [{ name: "<file>", line: 0 }];
|
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
|
this.runtime = new Runtime(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,9 +38,13 @@ class Evaluator {
|
|||||||
*/
|
*/
|
||||||
eval(expr) {
|
eval(expr) {
|
||||||
if (!expr) {
|
if (!expr) {
|
||||||
console.log(this.callStack)
|
this.runtime.printPanic(
|
||||||
throw new Error()
|
"expression could not be evaluated",
|
||||||
|
this.currentLine,
|
||||||
|
);
|
||||||
|
throw new Error("expression could not be evaluated");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentLine = expr.line;
|
this.currentLine = expr.line;
|
||||||
if (expr.type === "list") {
|
if (expr.type === "list") {
|
||||||
return this.evalList(expr, expr.line);
|
return this.evalList(expr, expr.line);
|
||||||
@ -129,8 +134,13 @@ class Evaluator {
|
|||||||
} else if (id in this.artithmeticOps) {
|
} else if (id in this.artithmeticOps) {
|
||||||
const left = this.evalToValue(s[1]);
|
const left = this.evalToValue(s[1]);
|
||||||
const right = this.evalToValue(s[2]);
|
const right = this.evalToValue(s[2]);
|
||||||
if (id === "+" && left.type === "string" && right.type === "string") {
|
if (
|
||||||
return { type: "value", value: { type: "string", value: left.value + right.value } };
|
id === "+" && left.type === "string" && right.type === "string"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: "value",
|
||||||
|
value: { type: "string", value: left.value + right.value },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "value",
|
type: "value",
|
||||||
@ -139,6 +149,26 @@ class Evaluator {
|
|||||||
value: this.artithmeticOps[id](left.value, right.value),
|
value: this.artithmeticOps[id](left.value, right.value),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
} else if (id === "==") {
|
||||||
|
const left = this.evalToValue(s[1]);
|
||||||
|
const right = this.evalToValue(s[2]);
|
||||||
|
return {
|
||||||
|
type: "value",
|
||||||
|
value: {
|
||||||
|
type: "bool",
|
||||||
|
value: this.runtime.eq(left, right),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (id === "!=") {
|
||||||
|
const left = this.evalToValue(s[1]);
|
||||||
|
const right = this.evalToValue(s[2]);
|
||||||
|
return {
|
||||||
|
type: "value",
|
||||||
|
value: {
|
||||||
|
type: "bool",
|
||||||
|
value: !this.runtime.eq(left, right),
|
||||||
|
},
|
||||||
|
};
|
||||||
} else if (id in this.comparisonOps) {
|
} else if (id in this.comparisonOps) {
|
||||||
const left = this.evalToValue(s[1]);
|
const left = this.evalToValue(s[1]);
|
||||||
const right = this.evalToValue(s[2]);
|
const right = this.evalToValue(s[2]);
|
||||||
@ -235,7 +265,7 @@ class Evaluator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.syms = outerSyms;
|
this.syms = outerSyms;
|
||||||
return { type: "value", value: { type: "null" } }
|
return { type: "value", value: { type: "null" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
evalDo(expr) {
|
evalDo(expr) {
|
||||||
@ -268,21 +298,15 @@ class Evaluator {
|
|||||||
throw new Error("cannot call non-function");
|
throw new Error("cannot call non-function");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.callStack.length > 100) {
|
this.runtime.pushCall(fnValue.name, expr.line);
|
||||||
this.panic("stack overflow")
|
|
||||||
this.printCallStack()
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callStack.push({ name: fnValue.name, line: expr.line });
|
|
||||||
const callerSyms = this.syms;
|
const callerSyms = this.syms;
|
||||||
this.syms = {
|
this.syms = {
|
||||||
parent: fnValue.syms,
|
parent: fnValue.syms,
|
||||||
map: new Map(),
|
map: new Map(),
|
||||||
};
|
};
|
||||||
if (fnValue.params.length !== args.length) {
|
if (fnValue.params.length !== args.length) {
|
||||||
throw new Error(
|
this.panic(
|
||||||
`incorrect amount of arguments for function '${fnValue.name}' on line ${expr.line}`,
|
`incorrect amount of arguments for function '${fnValue.name}'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < fnValue.params.length; ++i) {
|
for (let i = 0; i < fnValue.params.length; ++i) {
|
||||||
@ -299,7 +323,7 @@ class Evaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.syms = callerSyms;
|
this.syms = callerSyms;
|
||||||
this.callStack.pop()
|
this.runtime.popCall();
|
||||||
return { type: "value", value: returnValue };
|
return { type: "value", value: returnValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,10 +366,10 @@ class Evaluator {
|
|||||||
});
|
});
|
||||||
} else if (pattern.type === "list") {
|
} else if (pattern.type === "list") {
|
||||||
if (value.type !== "list") {
|
if (value.type !== "list") {
|
||||||
throw new Error(`expected list on line ${pattern.line}`);
|
this.panic(`expected list`);
|
||||||
}
|
}
|
||||||
for (const [i, p] of pattern.values.entries()) {
|
for (const [i, p] of pattern.values.entries()) {
|
||||||
this.assignPattern(p, value.values[i] ?? { type: "null" })
|
this.assignPattern(p, value.values[i] ?? { type: "null" });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`cannot assign to pattern on line ${pattern.line}`);
|
throw new Error(`cannot assign to pattern on line ${pattern.line}`);
|
||||||
@ -363,89 +387,8 @@ class Evaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
panic(msg) {
|
panic(msg) {
|
||||||
console.error(`\x1b[1;91mpanic\x1b[1;97m: ${msg}\x1b[0m"`);
|
this.runtime.setLine(this.currentLine);
|
||||||
this.printCallStack();
|
this.runtime.panic(msg);
|
||||||
}
|
|
||||||
|
|
||||||
printCallStack() {
|
|
||||||
const last = this.callStack[this.callStack.length - 1];
|
|
||||||
console.error(` \x1b[90mat \x1b[37m${last.name} \x1b[90m(${this.filename}:${this.currentLine})\x1b[0m`);
|
|
||||||
for (let i = this.callStack.length - 2; i >= 0; --i) {
|
|
||||||
const name = this.callStack[i].name;
|
|
||||||
const line = this.callStack[i + 1].line;
|
|
||||||
console.error(` \x1b[90mat \x1b[37m${name} \x1b[90m(${this.filename}:${line})\x1b[0m`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builtinFormat(msg, ...args) {
|
|
||||||
let value = valueToPrint(msg);
|
|
||||||
for (const arg of args) {
|
|
||||||
value = value.replace("%", valueToPrint(arg));
|
|
||||||
}
|
|
||||||
return { type: "string", value };
|
|
||||||
}
|
|
||||||
builtinPrintln(msg, ...args) {
|
|
||||||
let text = valueToPrint(msg);
|
|
||||||
|
|
||||||
for (const arg of args) {
|
|
||||||
text = text.replace("%", valueToPrint(arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(text);
|
|
||||||
return { type: "null" };
|
|
||||||
}
|
|
||||||
builtinPanic(msg, ...args) {
|
|
||||||
let text = valueToPrint(msg);
|
|
||||||
|
|
||||||
for (const arg of args) {
|
|
||||||
text = text.replace("%", valueToPrint(arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.panic(text);
|
|
||||||
process.exit(1);
|
|
||||||
return { type: "null" };
|
|
||||||
}
|
|
||||||
builtinReadTextFile(path) {
|
|
||||||
const text = fs.readFileSync(path.value).toString();
|
|
||||||
return { type: "string", value: text };
|
|
||||||
}
|
|
||||||
builtinWriteTextFile(path, text) {
|
|
||||||
fs.writeFileSync(path.value, text.value);
|
|
||||||
return { type: "null" };
|
|
||||||
}
|
|
||||||
builtinPush(list, value) {
|
|
||||||
if (list.type === "string") {
|
|
||||||
list.value += value.value;
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
list.values.push(value);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
builtinAt(value, index) {
|
|
||||||
if (value.type === "string") {
|
|
||||||
return { type: "string", value: value.value[index.value] };
|
|
||||||
}
|
|
||||||
return value.values[index.value];
|
|
||||||
}
|
|
||||||
builtinLen(value) {
|
|
||||||
if (value.type === "string") {
|
|
||||||
return { type: "int", value: value.value.length };
|
|
||||||
}
|
|
||||||
return { type: "int", value: value.values.length };
|
|
||||||
}
|
|
||||||
builtinStringToInt(value) {
|
|
||||||
return { type: "int", value: Number(value.value) };
|
|
||||||
}
|
|
||||||
builtinCharCode(value) {
|
|
||||||
return { type: "int", value: value.value.charCodeAt(0) };
|
|
||||||
}
|
|
||||||
builtinStringsJoin(value) {
|
|
||||||
return {
|
|
||||||
type: "string",
|
|
||||||
value: value.values
|
|
||||||
.map(value => value.value)
|
|
||||||
.join("")
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
artithmeticOps = {
|
artithmeticOps = {
|
||||||
@ -453,8 +396,6 @@ class Evaluator {
|
|||||||
"-": (left, right) => right - left,
|
"-": (left, right) => right - left,
|
||||||
};
|
};
|
||||||
comparisonOps = {
|
comparisonOps = {
|
||||||
"==": (left, right) => left === right,
|
|
||||||
"!=": (left, right) => left !== right,
|
|
||||||
"<": (left, right) => left < right,
|
"<": (left, right) => left < right,
|
||||||
">": (left, right) => left > right,
|
">": (left, right) => left > right,
|
||||||
"<=": (left, right) => left <= right,
|
"<=": (left, right) => left <= right,
|
||||||
@ -463,24 +404,33 @@ class Evaluator {
|
|||||||
assignOps = {
|
assignOps = {
|
||||||
"=": (_, right) => right,
|
"=": (_, right) => right,
|
||||||
"+=": (left, right) => ({
|
"+=": (left, right) => ({
|
||||||
type: left.type === "string" && left.type === right.type ? "string" : "int",
|
type: left.type === "string" && left.type === right.type
|
||||||
value: left.value + right.value
|
? "string"
|
||||||
|
: "int",
|
||||||
|
value: left.value + right.value,
|
||||||
|
}),
|
||||||
|
"-=": (left, right) => ({
|
||||||
|
type: "int",
|
||||||
|
value: left.value - right.value,
|
||||||
}),
|
}),
|
||||||
"-=": (left, right) => ({ type: "int", value: left.value - right.value }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
builtinFns = {
|
builtinFns = {
|
||||||
"format": (...args) => this.builtinFormat(...args),
|
"format": (...args) => this.runtime.builtinFormat(...args),
|
||||||
"println": (...args) => this.builtinPrintln(...args),
|
"print": (...args) => this.runtime.builtinPrint(...args),
|
||||||
"panic": (...args) => this.builtinPanic(...args),
|
"println": (...args) => this.runtime.builtinPrintln(...args),
|
||||||
"read_text_file": (...args) => this.builtinReadTextFile(...args),
|
"panic": (...args) =>
|
||||||
"write_text_file": (...args) => this.builtinWriteTextFile(...args),
|
this.runtime.builtinPanic(this.currentLine, ...args),
|
||||||
"push": (...args) => this.builtinPush(...args),
|
"read_text_file": (...args) =>
|
||||||
"at": (...args) => this.builtinAt(...args),
|
this.runtime.builtinReadTextFile(...args),
|
||||||
"len": (...args) => this.builtinLen(...args),
|
"write_text_file": (...args) =>
|
||||||
"string_to_int": (...args) => this.builtinStringToInt(...args),
|
this.runtime.builtinWriteTextFile(...args),
|
||||||
"char_code": (...args) => this.builtinCharCode(...args),
|
"push": (...args) => this.runtime.builtinPush(...args),
|
||||||
"strings_join": (...args) => this.builtinStringsJoin(...args),
|
"at": (...args) => this.runtime.builtinAt(...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),
|
||||||
};
|
};
|
||||||
|
|
||||||
consts = {
|
consts = {
|
||||||
@ -496,54 +446,6 @@ class Evaluator {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function valueToPrint(value) {
|
|
||||||
if (value.type === "null") {
|
|
||||||
return "null";
|
|
||||||
} else if (value.type === "bool") {
|
|
||||||
return `${value.value}`;
|
|
||||||
} else if (value.type === "int") {
|
|
||||||
return `${value.value}`;
|
|
||||||
} else if (value.type === "string") {
|
|
||||||
return `${value.value}`;
|
|
||||||
} else if (value.type === "list") {
|
|
||||||
return `(${value.values.map((v) => valueToString(v)).join(" ")})`;
|
|
||||||
} else {
|
|
||||||
throw new Error(`unknown value type ${value.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function valueToString(value) {
|
|
||||||
if (value.type === "null") {
|
|
||||||
return "null";
|
|
||||||
} else if (value.type === "bool") {
|
|
||||||
return `${value.value}`;
|
|
||||||
} else if (value.type === "int") {
|
|
||||||
return `${value.value}`;
|
|
||||||
} else if (value.type === "string") {
|
|
||||||
return `"${value.value}"`;
|
|
||||||
} else if (value.type === "list") {
|
|
||||||
return `(${value.values.map((v) => valueToString(v)).join(" ")})`;
|
|
||||||
} else {
|
|
||||||
throw new Error(`unknown value type ${value.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function valueToJs(value) {
|
|
||||||
if (value.type === "null") {
|
|
||||||
return null;
|
|
||||||
} else if (value.type === "bool") {
|
|
||||||
return value.value;
|
|
||||||
} else if (value.type === "int") {
|
|
||||||
return value.value;
|
|
||||||
} else if (value.type === "string") {
|
|
||||||
return value.value;
|
|
||||||
} else if (value.type === "list") {
|
|
||||||
return value.values.map((v) => valueToJs(v));
|
|
||||||
} else {
|
|
||||||
throw new Error(`unknown value type ${value.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Expr} expr
|
* @param {Expr} expr
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
|||||||
210
runtime.js
Normal file
210
runtime.js
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import process from "node:process";
|
||||||
|
|
||||||
|
export class Runtime {
|
||||||
|
constructor(filename) {
|
||||||
|
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
||||||
|
this.callStack = [{ name: "<entry>", line: 0 }];
|
||||||
|
this.filename = filename;
|
||||||
|
this.currentLine = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLine(line) {
|
||||||
|
this.currentLine = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushCall(name, line) {
|
||||||
|
this.callStack.push({ name, line });
|
||||||
|
}
|
||||||
|
|
||||||
|
popCall() {
|
||||||
|
this.callStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(msg) {
|
||||||
|
this.printPanic(msg);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printPanic(msg) {
|
||||||
|
console.error(`\x1b[1;91mpanic\x1b[1;97m: ${msg}\x1b[0m`);
|
||||||
|
this.printCallStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
printCallStack() {
|
||||||
|
const last = this.callStack[this.callStack.length - 1];
|
||||||
|
console.error(
|
||||||
|
` \x1b[90mat \x1b[37m${last.name} \x1b[90m(${this.filename}:${this.currentLine})\x1b[0m`,
|
||||||
|
);
|
||||||
|
for (let i = this.callStack.length - 2; i >= 0; --i) {
|
||||||
|
const name = this.callStack[i].name;
|
||||||
|
const line = this.callStack[i + 1].line;
|
||||||
|
console.error(
|
||||||
|
` \x1b[90mat \x1b[37m${name} \x1b[90m(${this.filename}:${line})\x1b[0m`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
format(msg, ...args) {
|
||||||
|
let value = Runtime.valueToPrint(msg);
|
||||||
|
for (const arg of args) {
|
||||||
|
value = value.replace("%", Runtime.valueToPrint(arg));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
eq(left, right) {
|
||||||
|
if (left.type === "null") {
|
||||||
|
return right.type === "null";
|
||||||
|
}
|
||||||
|
if (right.type === "null") {
|
||||||
|
return left.type === "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.type !== right.type) {
|
||||||
|
this.panic(`cannot compare ${left.type} with ${right.type}`);
|
||||||
|
}
|
||||||
|
const type = left.type;
|
||||||
|
if (["bool", "int", "string"].includes(type)) {
|
||||||
|
return left.value === right.value;
|
||||||
|
} else if (type === "list") {
|
||||||
|
if (left.values.length !== right.values.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < left.values.length; ++i) {
|
||||||
|
if (!this.eq(left.values[i], right.values[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.panic(`equality not implemented for ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinFormat(msg, ...args) {
|
||||||
|
return { type: "string", value: this.format(msg, ...args) };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinPrint(msg, ...args) {
|
||||||
|
process.stdout.write(this.format(msg, ...args));
|
||||||
|
return { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinPrintln(msg, ...args) {
|
||||||
|
console.log(this.format(msg, ...args));
|
||||||
|
return { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinPanic(currentLine, msg, ...args) {
|
||||||
|
this.panic(this.format(msg, ...args), currentLine);
|
||||||
|
return { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinReadTextFile(path) {
|
||||||
|
const text = fs.readFileSync(path.value).toString();
|
||||||
|
return { type: "string", value: text };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinWriteTextFile(path, text) {
|
||||||
|
fs.writeFileSync(path.value, text.value);
|
||||||
|
return { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinPush(list, value) {
|
||||||
|
if (list.type === "string") {
|
||||||
|
list.value += value.value;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
list.values.push(value);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinAt(value, index) {
|
||||||
|
if (value.type === "string") {
|
||||||
|
return { type: "string", value: value.value[index.value] };
|
||||||
|
}
|
||||||
|
return value.values[index.value] ?? { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinSet(subject, index, value) {
|
||||||
|
subject.values[index.value] = value;
|
||||||
|
return { type: "null" };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinLen(value) {
|
||||||
|
if (value.type === "string") {
|
||||||
|
return { type: "int", value: value.value.length };
|
||||||
|
}
|
||||||
|
return { type: "int", value: value.values.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinStringToInt(value) {
|
||||||
|
return { type: "int", value: Number(value.value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinCharCode(value) {
|
||||||
|
return { type: "int", value: value.value.charCodeAt(0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
builtinStringsJoin(value) {
|
||||||
|
return {
|
||||||
|
type: "string",
|
||||||
|
value: value.values
|
||||||
|
.map((value) => value.value)
|
||||||
|
.join(""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static valueToPrint(value) {
|
||||||
|
if (value.type === "null") {
|
||||||
|
return "null";
|
||||||
|
} else if (value.type === "bool") {
|
||||||
|
return `${value.value}`;
|
||||||
|
} else if (value.type === "int") {
|
||||||
|
return `${value.value}`;
|
||||||
|
} else if (value.type === "string") {
|
||||||
|
return `${value.value}`;
|
||||||
|
} else if (value.type === "list") {
|
||||||
|
return `(${
|
||||||
|
value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||||
|
})`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown value type ${value.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static valueToString(value) {
|
||||||
|
if (value.type === "null") {
|
||||||
|
return "null";
|
||||||
|
} else if (value.type === "bool") {
|
||||||
|
return `${value.value}`;
|
||||||
|
} else if (value.type === "int") {
|
||||||
|
return `${value.value}`;
|
||||||
|
} else if (value.type === "string") {
|
||||||
|
return `"${value.value}"`;
|
||||||
|
} else if (value.type === "list") {
|
||||||
|
return `(${
|
||||||
|
value.values.map((v) => Runtime.valueToString(v)).join(" ")
|
||||||
|
})`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown value type ${value.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static valueToJs(value) {
|
||||||
|
if (value.type === "null") {
|
||||||
|
return null;
|
||||||
|
} else if (value.type === "bool") {
|
||||||
|
return value.value;
|
||||||
|
} else if (value.type === "int") {
|
||||||
|
return value.value;
|
||||||
|
} else if (value.type === "string") {
|
||||||
|
return value.value;
|
||||||
|
} else if (value.type === "list") {
|
||||||
|
return value.values.map((v) => Runtime.valueToJs(v));
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown value type ${value.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user