compiler stuff

This commit is contained in:
sfja 2025-09-10 02:04:13 +02:00
parent f3b064394e
commit 8b38e5e883
4 changed files with 352 additions and 100 deletions

View File

@ -1,7 +1,93 @@
(let text (call read_text_file "program.phi")) (let identChars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/%&|=?!<>'_")
(fn a (b c) (do
(fn tokenize (text) (do
(let text_len (call len text))
(let tokens ())
(let i 0)
(let line 1)
(loop (do
(if (>= i text_len) (break))
(let ch (call at text i))
(if (call contains " \t\r\n" ch) (do
(call println "line = %, ch = '%'" line ch)
(if (== ch "\n") (do
(+= line 1)
))
(+= i 1)
) (if (call slice_eq text i "//") (do
(loop (do
if (or (>= i text_len) (== (call at text i) "\n") (do
(break)
))
(+= i 1)
))
) (if (call contains "()" ch) (do
(call push tokens (ch line))
(+= i 1)
) (if (== ch "\"") (do
(+= i 1)
) (if (call contains identChars ch) (do
(let value "")
(loop (do
(= ch (call at text i))
(if (or (>= i text_len) (not (call contains identChars ch))) (do
(break)
))
(call push value ch)
(+= i 1)
))
(call push tokens ("ident" line value))
) (do
(call println "illegal char '%'" ch)
(+= i 1)
))))))
))
(return tokens)
)) ))
(fn contains (text ch) (do
(let text_len (call len text))
(let i 0)
(loop (do
(if (>= i text_len) (break))
(if (== (call at text i) ch) (do
(return true)
))
(+= i 1)
))
(return false)
))
(fn slice_eq (str slice_idx substr) (do
(let str_len (call len str))
(let substr_len (call len substr))
(let i slice_idx)
(loop (do
(if (or (>= (+ slice_idx i) str_len) (>= i substr_len))
(return false))
(if (!= (call at str (+ slice_idx i)) (call at substr i))
(return false))
(+= i 1)
))
(return true)
))
(let text (call read_text_file "program.phi"))
(let tokens (call tokenize text))
(call println "=== text ===")
(call println text) (call println text)
(call println "=== tokens ===")
(call println tokens)
(call println (+ 1 2))

View File

@ -1,4 +1,5 @@
{ {
"name": "phi-lang", "name": "phi-lang",
"version": "1.0.0", "version": "1.0.0",
"type": "module"
} }

343
phi.js
View File

@ -1,6 +1,7 @@
"use strict"; "use strict";
import * as fs from "fs"; import * as fs from "node:fs";
import process from "node:process";
function main() { function main() {
const text = fs.readFileSync(process.argv[2]).toString(); const text = fs.readFileSync(process.argv[2]).toString();
@ -28,7 +29,7 @@ class Evaluator {
} }
/** /**
* @param {Expr} expr * @param {Expr} expr
*/ */
eval(expr) { eval(expr) {
if (expr.type === "list") { if (expr.type === "list") {
@ -36,22 +37,22 @@ class Evaluator {
} else if (expr.type === "int") { } else if (expr.type === "int") {
return { type: "value", value: { type: "int", value: expr.value } }; return { type: "value", value: { type: "int", value: expr.value } };
} else if (expr.type === "string") { } else if (expr.type === "string") {
return { type: "value", value: { type: "string", value: expr.value } }; return {
type: "value",
value: { type: "string", value: expr.value },
};
} else if (expr.type === "ident") { } else if (expr.type === "ident") {
const findInTree = (syms, ident) => { const sym = this.findSym(expr.value);
if (syms.map.has(ident)) if (!sym) {
return syms.map.get(ident); throw new Error(
else if (syms.parent) `could not find symbol '${expr.value}' on line ${expr.line}`,
return findInTree(syms.parent, ident); );
else
return undefined;
} }
const sym = findInTree(this.syms, expr.value);
if (!sym)
throw new Error(`could not find symbol '${expr.value}' on line ${expr.line}`);
return { type: "value", value: sym }; return { type: "value", value: sym };
} else { } else {
throw new Error(`unknown expr type '${expr.type}' on line ${expr.line}`); throw new Error(
`unknown expr type '${expr.type}' on line ${expr.line}`,
);
} }
} }
@ -65,19 +66,67 @@ class Evaluator {
evalList(expr) { evalList(expr) {
const s = expr.values; const s = expr.values;
const id = s[0]?.value ?? undefined; const id = s[0]?.type === "ident" ? s[0].value : undefined;
if (id === "fn") { if (id === "fn") {
const name = s[1].value; const name = s[1].value;
this.syms.map.set(name, { this.syms.map.set(name, {
type: "fn", type: "fn",
name, name,
params: s[2].values.map(ident => ident.value), params: s[2].values.map((ident) => ident.value),
body: s[3], body: s[3],
syms: this.syms, syms: this.syms,
}); });
return { type: "value", value: { type: "null" } }; return { type: "value", value: { type: "null" } };
} else if (id === "return") {
return {
type: "return",
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
};
} else if (id === "let") {
const value = this.evalToValue(s[2]);
this.syms.map.set(s[1].value, value);
return { type: "value", value: { type: "null" } };
} else if (id === "if") {
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" };
}
} else if (id === "loop") {
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;
}
}
} else if (id === "break") {
return {
type: "break",
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
};
} else if (id === "do") {
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;
}
return { type: "value", value: lastValue };
} else if (id === "call") { } else if (id === "call") {
const args = s.slice(2).map(arg => this.evalToValue(arg)); const args = s.slice(2).map((arg) => this.evalToValue(arg));
const fnValue = this.evalToValue(s[1]); const fnValue = this.evalToValue(s[1]);
if (fnValue.type === "builtin") { if (fnValue.type === "builtin") {
@ -91,7 +140,9 @@ class Evaluator {
map: new Map(), map: new Map(),
}; };
if (fnValue.params.length !== args.length) { if (fnValue.params.length !== args.length) {
throw new Error(`incorrect amount of arguments on line ${line}`); throw new Error(
`incorrect amount of arguments on line ${line}`,
);
} }
for (let i = 0; i < fnValue.params.length; ++i) { for (let i = 0; i < fnValue.params.length; ++i) {
this.syms.map.set(fnValue.params[i], args[i]); this.syms.map.set(fnValue.params[i], args[i]);
@ -103,70 +154,159 @@ class Evaluator {
if (result.type === "value" || result.type === "return") { if (result.type === "value" || result.type === "return") {
returnValue = result.value; returnValue = result.value;
} else { } else {
throw new Error(`illegal ${result.type} across boundry`) throw new Error(`illegal ${result.type} across boundry`);
} }
this.syms = callerSyms; this.syms = callerSyms;
return { type: "value", value: returnValue }; return { type: "value", value: returnValue };
} else if (id === "return") { } else if (id === "not") {
return { type: "return", value: s[1] ? this.evalToValue(s[1]) : { type: "null" } }; const value = this.evalToValue(s[1]);
} else if (id === "let") { return {
const value = this.evalToValue(s[2]); type: "value",
this.syms.map.set(s[1].value, value); value: { type: "bool", value: !value.value },
return { type: "value", value: { type: "null" } }; };
} else if (id === "do") { } else if (id === "or") {
let lastValue = { type: "null" }; const left = this.evalToValue(s[1]);
if (left.value) {
for (const expr of s.slice(1)) { return { type: "value", value: left };
const result = this.eval(expr);
if (result.type !== "value") {
break;
}
lastValue = result.value;
}
return { type: "value", value: lastValue };
} else if (s[0] === "if") {
const cond = this.evalToValue(s[1]);
if (cond.type !== "bool") {
throw new Error(`expected bool on line ${line}`);
}
if (cond.value) {
return this.eval(s[2]);
} else if (s[3]) {
return this.eval(s[3]);
} else { } else {
return { type: "value", value: "null" }; const right = this.evalToValue(s[2]);
return { type: "value", value: right };
} }
} else if (s[0] === "loop") { } else if (id === "and") {
while (true) { const left = this.evalToValue(s[1]);
const result = this.eval(s[1]); if (left.value) {
if (result.type === "break") { const right = this.evalToValue(s[2]);
return { type: "value", value: result.value }; return { type: "value", value: right };
} else if (result.type !== "value") { } else {
return result; return { type: "value", value: left };
}
} else if (id in artithmeticOps) {
const left = this.evalToValue(s[1]);
const right = this.evalToValue(s[2]);
return {
type: "value",
value: {
type: "int",
value: artithmeticOps[id](left.value, right.value),
},
};
} else if (id in comparisonOps) {
const left = this.evalToValue(s[1]);
const right = this.evalToValue(s[2]);
return {
type: "value",
value: {
type: "bool",
value: comparisonOps[id](left.value, right.value),
},
};
} else if (id in assignOps) {
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 right = this.evalToValue(s[2]);
const newValue = assignOps[id](sym, right);
sym.type = newValue.type;
sym.value = newValue.value;
} else {
throw new Error(
`cannot assign to expression on line ${expr.line}`,
);
} }
} else if (s[0] === "break") { return { type: "value", value: { type: "null" } };
return { type: "break", value: s[1] ? this.evalToValue(s[1]) : { type: "null" } };
} else { } else {
return { type: "value", value: { type: "list", values: s.map(expr => this.evalToValue(expr)) } }; return {
type: "value",
value: {
type: "list",
values: s.map((expr) => this.evalToValue(expr)),
},
};
}
}
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;
} }
} }
} }
const artithmeticOps = {
"+": (left, right) => right + left,
"-": (left, right) => right - left,
};
const 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,
};
const assignOps = {
"=": (_, right) => right,
"+=": (left, right) => ({ type: "int", value: left.value + right.value }),
"-=": (left, right) => ({ type: "int", value: left.value - right.value }),
};
const builtinFns = { const builtinFns = {
println(msg) { println(msg, ...args) {
console.log(valueToPrint(msg)); let text = valueToPrint(msg);
for (const arg of args) {
text = text.replace("%", valueToPrint(arg));
}
console.log(text);
return { type: "null" }; return { type: "null" };
}, },
read_text_file(path) { read_text_file(path) {
const text = fs.readFileSync(path.value); const text = fs.readFileSync(path.value).toString();
return { type: "string", value: text }; return { type: "string", value: text };
} },
push(list, value) {
if (list.type === "string") {
list.value += value.value;
return list;
}
list.values.push(value);
return list;
},
at(value, index) {
if (value.type === "string") {
return { type: "string", value: value.value[index.value] };
}
return value.values[index.value];
},
len(value) {
if (value.type === "string") {
return { type: "int", value: value.value.length };
}
return { type: "int", value: value.values.length };
},
}; };
const builtins = Object.entries(builtinFns) const consts = {
.map(([key, fn]) => [key, { type: "builtin", fn }]); "null": { type: "null" },
"false": { type: "bool", value: false },
"true": { type: "bool", value: true },
};
const builtins = [
...Object.entries(builtinFns)
.map(([key, fn]) => [key, { type: "builtin", fn }]),
...Object.entries(consts),
];
function valueToPrint(value) { function valueToPrint(value) {
if (value.type === "null") { if (value.type === "null") {
@ -178,7 +318,7 @@ function valueToPrint(value) {
} 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 `(${value.values.map(v => valueToString(v)).join(" ")})`; return `(${value.values.map((v) => valueToString(v)).join(" ")})`;
} else { } else {
throw new Error(`unknown value type ${value.type}`); throw new Error(`unknown value type ${value.type}`);
} }
@ -194,7 +334,7 @@ function valueToString(value) {
} 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 `(${value.values.map(v => valueToString(v)).join(" ")})`; return `(${value.values.map((v) => valueToString(v)).join(" ")})`;
} else { } else {
throw new Error(`unknown value type ${value.type}`); throw new Error(`unknown value type ${value.type}`);
} }
@ -210,14 +350,14 @@ function valueToJs(value) {
} 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 value.values.map(v => valueToJs(v)); return value.values.map((v) => valueToJs(v));
} else { } else {
throw new Error(`unknown value type ${value.type}`); throw new Error(`unknown value type ${value.type}`);
} }
} }
/** /**
* @param {Expr} expr * @param {Expr} expr
* @returns {string} * @returns {string}
*/ */
function exprToString(expr) { function exprToString(expr) {
@ -228,7 +368,7 @@ function exprToString(expr) {
} else if (expr.type === "string") { } else if (expr.type === "string") {
return `"${expr.value}"`; return `"${expr.value}"`;
} else if (expr.type === "list") { } else if (expr.type === "list") {
return `(${expr.values.map(v => exprToString(v)).join(" ")})`; return `(${expr.values.map((v) => exprToString(v)).join(" ")})`;
} else { } else {
throw new Error(`unknown value type ${expr.type}`); throw new Error(`unknown value type ${expr.type}`);
} }
@ -248,18 +388,18 @@ class Parser {
.replace(/\/\/.*?$/mg, "") .replace(/\/\/.*?$/mg, "")
.replace(/([\(\)\n])/g, " $1 ") .replace(/([\(\)\n])/g, " $1 ")
.split(/[ \t\r]/) .split(/[ \t\r]/)
.filter(tok => tok !== ""); .filter((tok) => tok !== "");
this.idx = 0; this.idx = 0;
this.line = 1; this.line = 1;
} }
/** /**
*
* @returns {Expr[]} * @returns {Expr[]}
*/ */
parse() { parse() {
if (this.curr === "\n") if (this.curr === "\n") {
this.step(); this.step();
}
const exprs = []; const exprs = [];
while (!this.done) { while (!this.done) {
@ -276,62 +416,66 @@ class Parser {
values.push(this.parseExpr()); values.push(this.parseExpr());
} }
if (!this.test(")")) { if (!this.test(")")) {
throw new Error(`expected ')'`) throw new Error(`expected ')'`);
} }
this.step(); this.step();
return { type: "list", line, values }; return { type: "list", line, values };
} } else if (this.test(/STRING_\d+/)) {
else if (this.test(/STRING_\d+/)) {
const id = Number(this.curr.match(/STRING_(\d+)/)[1]); const id = Number(this.curr.match(/STRING_(\d+)/)[1]);
this.step(); this.step();
return { type: "string", line, value: this.strings[id] }; return { type: "string", line, value: this.strings[id] };
} } else if (this.test(/0|(:?[1-9][0-9]*)/)) {
else if (this.test(/0|(:?[1-9][0-9]*)/)) {
const value = Number(this.curr); const value = Number(this.curr);
this.step(); this.step();
return { type: "int", line, value }; return { type: "int", line, value };
} } else if (this.test(/[a-zA-Z0-9\+\-\*/%&\|=\?\!<>'_]+/)) {
else if (this.test(/[a-zA-Z0-9\+\-\*/%&\|=\?\!<>'_]+/)) {
const value = this.curr; const value = this.curr;
this.step(); this.step();
return { type: "ident", line, value }; return { type: "ident", line, value };
} } else {
else { throw new Error(
throw new Error(`expected expression, got ${this.curr}`) `expected expression, got ${this.curr} on line ${this.line}`,
);
} }
} }
eat(tok) { eat(tok) {
if (!this.test(tok)) if (!this.test(tok)) {
return false; return false;
this.step() }
this.step();
return true; return true;
} }
test(tok) { test(tok) {
if (this.done) if (this.done) {
return false; return false;
if (typeof tok === "string") }
if (typeof tok === "string") {
return this.curr === tok; return this.curr === tok;
else if (tok instanceof RegExp) } else if (tok instanceof RegExp) {
return new RegExp(`^${tok.source}$`) return new RegExp(`^${tok.source}$`)
.test(this.curr); .test(this.curr);
else } else {
throw new Error() throw new Error();
}
} }
step() { step() {
do { do {
this.idx += 1;
if (!this.done && this.curr === "\n") { if (!this.done && this.curr === "\n") {
this.line += 1; this.line += 1;
} }
this.idx += 1;
} while (!this.done && this.curr === "\n"); } while (!this.done && this.curr === "\n");
} }
get done() { return this.idx >= this.tokens.length; } get done() {
get curr() { return this.tokens[this.idx]; } return this.idx >= this.tokens.length;
}
get curr() {
return this.tokens[this.idx];
}
} }
class StringExtractor { class StringExtractor {
constructor(text) { constructor(text) {
this.text = text; this.text = text;
@ -342,8 +486,8 @@ class StringExtractor {
extract() { extract() {
while (this.idx < this.text.length) { while (this.idx < this.text.length) {
if (this.text[this.idx] == '\"') { if (this.text[this.idx] == '"') {
this.extractString() this.extractString();
} else { } else {
this.outputText += this.text[this.idx]; this.outputText += this.text[this.idx];
this.idx += 1; this.idx += 1;
@ -355,10 +499,11 @@ class StringExtractor {
this.idx += 1; this.idx += 1;
let value = ""; let value = "";
while (this.idx < this.text.length && this.text[this.idx] != '"') { while (this.idx < this.text.length && this.text[this.idx] != '"') {
if (this.text[this.idx] == '\\') { if (this.text[this.idx] == "\\") {
this.idx += 1; this.idx += 1;
if (this.idx > this.text.length) if (this.idx > this.text.length) {
break; break;
}
const ch = this.text[this.idx]; const ch = this.text[this.idx];
value += { value += {
"0": "\0", "0": "\0",
@ -380,8 +525,12 @@ class StringExtractor {
this.outputText += `STRING_${id}`; this.outputText += `STRING_${id}`;
} }
getStrings() { return this.strings; } getStrings() {
getOutputText() { return this.outputText; } return this.strings;
}
getOutputText() {
return this.outputText;
}
} }
main(); main();

View File

@ -9,9 +9,25 @@ endif
syn keyword Keyword fn call return loop break if let do syn keyword Keyword fn call return loop break if let do
syn keyword Operator and or not
syn keyword Special null syn keyword Special null
syn keyword Boolean true false syn keyword Boolean true false
syn match Operator '+'
syn match Operator '-'
syn match Operator '\*'
syn match Operator '/'
syn match Operator '='
syn match Operator '+='
syn match Operator '-='
syn match Operator '=='
syn match Operator '!='
syn match Operator '<'
syn match Operator '>'
syn match Operator '<='
syn match Operator '>='
syn match Number '0' syn match Number '0'
syn match Number '[1-9][0-9]*' syn match Number '[1-9][0-9]*'