remove bootstrap

This commit is contained in:
sfja 2025-09-23 01:10:25 +02:00
parent ab660ae7a8
commit 31d2f7336b
4 changed files with 0 additions and 1877 deletions

View File

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

View File

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

639
phi.js
View File

@ -1,639 +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}`,
);
}
for (let i = 0; i < value.values.length; ++i) {
this.enterScope();
this.assignPattern(s[1], value.values[i]);
this.enterScope();
const result = this.eval(s[3]);
this.leaveScope();
this.leaveScope();
if (result.type === "break") {
return { type: "value", value: result.value };
} else if (result.type !== "value") {
return result;
}
}
return { type: "value", value: { type: "null" } };
}
evalDo(expr) {
const s = expr.values;
this.enterScope();
let lastValue = { type: "null" };
for (const expr of s.slice(1)) {
const result = this.eval(expr);
if (!result) {
console.log({ expr, result });
}
if (result.type !== "value") {
return result;
}
lastValue = result.value;
}
this.leaveScope();
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]);
}
this.enterScope();
let returnValue = { type: "null" };
const result = this.eval(fnValue.body);
this.leaveScope();
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;
}
if (this.syms.map.has(pattern.value)) {
throw new Error(
`redeclaration of local symbol '${pattern.value}' on line ${pattern.line}`,
);
}
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 on line ${pattern.line}`);
}
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);
}
enterScope() {
this.syms = { parent: this.syms, map: new Map() };
}
leaveScope() {
this.syms = this.syms.parent;
}
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 },
"is_phi_compiler": { type: "bool", value: false },
};
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();

1228
stage1.js

File diff suppressed because it is too large Load Diff