js gen + syntax
This commit is contained in:
parent
896e9a5e45
commit
623964374a
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
out.js
|
326
compile.phi
326
compile.phi
@ -1,6 +1,198 @@
|
||||
|
||||
(let identChars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/%&|=?!<>'_")
|
||||
(fn Emitter (ast) (do
|
||||
(let output ())
|
||||
|
||||
(fn generate () (do
|
||||
(call emit "#!/usr/bin/env node\n")
|
||||
(call emit_exprs ast)
|
||||
(return (call strings_join output))
|
||||
))
|
||||
|
||||
(fn emit_exprs (exprs) (do
|
||||
(for expr exprs (do
|
||||
(call emit_expr expr)
|
||||
(call emit "\n")
|
||||
))
|
||||
))
|
||||
|
||||
(fn emit_expr (expr) (do
|
||||
(let (ty line) expr)
|
||||
(if (== ty "list") (do
|
||||
(call emit_list expr)
|
||||
) (if (== ty "int") (
|
||||
(let (_ _ value) expr)
|
||||
(call emit (call format "%" value))
|
||||
) (if (== ty "string") (
|
||||
(let (_ _ value) expr)
|
||||
(call emit (call format "\"%\"" (call string_escape value)))
|
||||
) (if (== ty "ident") (
|
||||
(let (_ _ value) expr)
|
||||
(call emit (call format "_%" value))
|
||||
) (do
|
||||
(call panic "unknown expr type '%' on line %" ty line)
|
||||
)))))
|
||||
))
|
||||
|
||||
(fn emit_list (expr) (do
|
||||
(let (ty line s) expr)
|
||||
(if (== (call len s) 0) (do
|
||||
(call emit "[]")
|
||||
(return)
|
||||
))
|
||||
(let ((_ _ id)) s)
|
||||
(if (== id "fn") (do
|
||||
(let (_ (_ _ name) (_ _ params) body) s)
|
||||
(call emit (call format "function _%(" name))
|
||||
|
||||
(let first true)
|
||||
(for (_ _ name) params (do
|
||||
(if (not first) (do
|
||||
(call emit ", ")
|
||||
))
|
||||
(= first false)
|
||||
|
||||
(call emit (call format "_%" name))
|
||||
))
|
||||
(call emit (call format ") {\n" name))
|
||||
(call emit_expr body)
|
||||
(call emit "}")
|
||||
) (if (== id "do") (do
|
||||
(call emit_exprs (call slice s 1))
|
||||
) (if (== id "return") (do
|
||||
(let (_ value) s)
|
||||
(call emit "return ")
|
||||
(if (!= value null) (do
|
||||
(call emit_expr value)
|
||||
) (do
|
||||
(call emit "null")
|
||||
))
|
||||
(call emit ";")
|
||||
) (if (== id "call") (do
|
||||
(let (_ callee) s)
|
||||
(let args (call slice s 2))
|
||||
(call emit_expr callee)
|
||||
(call emit "(")
|
||||
|
||||
(let first true)
|
||||
(for arg args (do
|
||||
(if (not first) (do
|
||||
(call emit ", ")
|
||||
))
|
||||
(= first false)
|
||||
|
||||
(call emit_expr arg)
|
||||
))
|
||||
|
||||
(call emit ")")
|
||||
) (do
|
||||
(call emit "[")
|
||||
(let first true)
|
||||
(for e s (do
|
||||
(if (not first) (do
|
||||
(call emit ", ")
|
||||
))
|
||||
(= first false)
|
||||
|
||||
(call emit_expr e)
|
||||
))
|
||||
(call emit "]")
|
||||
)))))
|
||||
))
|
||||
|
||||
(fn emit (str) (do
|
||||
(call push output str)
|
||||
))
|
||||
|
||||
(return (generate))
|
||||
))
|
||||
|
||||
(fn string_escape (str) (do
|
||||
(let str_len (call len str))
|
||||
(let i 0)
|
||||
(let result "")
|
||||
(loop (do
|
||||
(if (>= i str_len) (break))
|
||||
(let ch (call at str i))
|
||||
(if (== ch "\"") (do
|
||||
(+= result "\\\"")
|
||||
) (if (== ch "\t") (do
|
||||
(+= result "\\t")
|
||||
) (if (== ch "\r") (do
|
||||
(+= result "\\r")
|
||||
) (if (== ch "\n") (do
|
||||
(+= result "\\n")
|
||||
) (if (== ch "\0") (do
|
||||
(+= result "\\0")
|
||||
) (do
|
||||
(+= result ch)
|
||||
))))))
|
||||
(+= i 1)
|
||||
))
|
||||
(return result)
|
||||
))
|
||||
|
||||
(fn Parser (tokens) (do
|
||||
(let i 0)
|
||||
(let tok (call at tokens i))
|
||||
|
||||
(fn parse () (do
|
||||
(let exprs ())
|
||||
(loop (do
|
||||
(if (call done) (break))
|
||||
(call push exprs (call parse_expr))
|
||||
))
|
||||
(return exprs)
|
||||
))
|
||||
|
||||
(fn parse_expr () (do
|
||||
(let (ty line value) tok)
|
||||
(if (call eat "(") (do
|
||||
(let values ())
|
||||
(loop (do
|
||||
(if (call test ")") (break))
|
||||
(call push values (call parse_expr))
|
||||
))
|
||||
(if (not (call eat ")")) (do
|
||||
(call panic "expected ')' on line %" (call at tok 1))
|
||||
))
|
||||
(return ("list" line values))
|
||||
) (if (call eat "string") (do
|
||||
(return ("string" line value))
|
||||
) (if (call eat "int") (do
|
||||
(return ("int" line (call string_to_int value)))
|
||||
) (if (call eat "ident") (do
|
||||
(return ("ident" line value))
|
||||
) (do
|
||||
(call panic "expected expression, got '%' on line %" ty line)
|
||||
)))))
|
||||
))
|
||||
|
||||
(fn eat (pat) (do
|
||||
(if (not (call test pat)) (return false))
|
||||
(call step)
|
||||
(return true)
|
||||
))
|
||||
|
||||
(fn step () (do
|
||||
(+= i 1)
|
||||
(if (not (call done)) (do
|
||||
(let new_tok (call at tokens i))
|
||||
(= tok new_tok)
|
||||
))
|
||||
))
|
||||
|
||||
(fn test (pat) (do
|
||||
(if (call done) (return false))
|
||||
(let (ty) tok)
|
||||
(return (== pat ty))
|
||||
))
|
||||
|
||||
(fn done () (do
|
||||
(return (>= i (call len tokens)))
|
||||
))
|
||||
|
||||
(return (parse))
|
||||
))
|
||||
|
||||
(fn tokenize (text) (do
|
||||
(let text_len (call len text))
|
||||
@ -15,14 +207,13 @@
|
||||
(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
|
||||
(if (or (>= i text_len) (== (call at text i) "\n")) (do
|
||||
(break)
|
||||
))
|
||||
(+= i 1)
|
||||
@ -31,8 +222,52 @@
|
||||
(call push tokens (ch line))
|
||||
(+= i 1)
|
||||
) (if (== ch "\"") (do
|
||||
|
||||
(let value "")
|
||||
(+= i 1)
|
||||
(= ch (call at text i))
|
||||
(loop (do
|
||||
(if (or (>= i text_len) (== ch "\"")) (do
|
||||
(break)
|
||||
))
|
||||
(if (== ch "\\") (do
|
||||
(+= i 1)
|
||||
(if (>= i text_len) (do
|
||||
(break)
|
||||
))
|
||||
(= ch (call at text i))
|
||||
(if (== ch "t") (do
|
||||
(+= value "\t")
|
||||
) (if (== ch "r") (do
|
||||
(+= value "\r")
|
||||
) (if (== ch "n") (do
|
||||
(+= value "\n")
|
||||
) (if (== ch "0") (do
|
||||
(+= value "\n")
|
||||
) (do
|
||||
(+= value ch)
|
||||
)))))
|
||||
) (do
|
||||
(+= value ch)
|
||||
))
|
||||
(+= i 1)
|
||||
(= ch (call at text i))
|
||||
))
|
||||
(if (or (>= i text_len) (!= ch "\"") (do
|
||||
(call panic "expected '\"' on line %" line)
|
||||
)))
|
||||
(+= i 1)
|
||||
(call push tokens ("string" line value))
|
||||
) (if (call contains "0123456789" ch) (do
|
||||
(let value "")
|
||||
(loop (do
|
||||
(= ch (call at text i))
|
||||
(if (or (>= i text_len) (not (call contains "0123456789" ch))) (do
|
||||
(break)
|
||||
))
|
||||
(+= value ch)
|
||||
(+= i 1)
|
||||
))
|
||||
(call push tokens ("int" line value))
|
||||
) (if (call contains identChars ch) (do
|
||||
(let value "")
|
||||
(loop (do
|
||||
@ -40,19 +275,22 @@
|
||||
(if (or (>= i text_len) (not (call contains identChars ch))) (do
|
||||
(break)
|
||||
))
|
||||
(call push value ch)
|
||||
(+= value ch)
|
||||
(+= i 1)
|
||||
))
|
||||
(call push tokens ("ident" line value))
|
||||
) (do
|
||||
(call println "illegal char '%'" ch)
|
||||
(+= i 1)
|
||||
))))))
|
||||
)))))))
|
||||
|
||||
))
|
||||
(return tokens)
|
||||
))
|
||||
|
||||
(let identChars (+ "abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/%&|=?!<>'_"))
|
||||
|
||||
(fn contains (text ch) (do
|
||||
(let text_len (call len text))
|
||||
(let i 0)
|
||||
@ -69,9 +307,11 @@
|
||||
(fn slice_eq (str slice_idx substr) (do
|
||||
(let str_len (call len str))
|
||||
(let substr_len (call len substr))
|
||||
(let i slice_idx)
|
||||
(let i 0)
|
||||
(loop (do
|
||||
(if (or (>= (+ slice_idx i) str_len) (>= i substr_len))
|
||||
(if (>= i substr_len)
|
||||
(return true))
|
||||
(if (>= (+ slice_idx i) str_len)
|
||||
(return false))
|
||||
(if (!= (call at str (+ slice_idx i)) (call at substr i))
|
||||
(return false))
|
||||
@ -80,14 +320,76 @@
|
||||
(return true)
|
||||
))
|
||||
|
||||
(fn print_expr (expr depth) (do
|
||||
(let (ty line value) expr)
|
||||
(if (== ty "list") (do
|
||||
(call println "%(% %" (call indent depth) ty line)
|
||||
(for e value (do
|
||||
(call print_expr e (+ depth 1))
|
||||
))
|
||||
(call println "%)" (call indent depth))
|
||||
) (do
|
||||
(call println "%%" (call indent depth) expr)
|
||||
))
|
||||
))
|
||||
|
||||
(fn indent (depth) (do
|
||||
(let space "")
|
||||
(let i 0)
|
||||
(loop (do
|
||||
(if (>= i depth) (break))
|
||||
(+= space " ")
|
||||
(+= i 1)
|
||||
))
|
||||
(return space)
|
||||
))
|
||||
|
||||
(fn slice (list idx) (do
|
||||
(let list_len (call len list))
|
||||
(let elems ())
|
||||
(let i idx)
|
||||
(loop (do
|
||||
(if (>= i list_len) (break))
|
||||
(call push elems (call at list i))
|
||||
(+= i 1)
|
||||
))
|
||||
(return elems)
|
||||
))
|
||||
|
||||
(let text (call read_text_file "program.phi"))
|
||||
|
||||
(let tokens (call tokenize text))
|
||||
|
||||
(call println "reading file...")
|
||||
(call println "=== text ===")
|
||||
(call println text)
|
||||
|
||||
(call println "tokenizing...")
|
||||
(let tokens (call tokenize text))
|
||||
|
||||
(call println "=== tokens ===")
|
||||
(call println tokens)
|
||||
(call println (+ 1 2))
|
||||
(for (tok line value) tokens (do
|
||||
(call println "%\t%\t%" line tok (if (!= value null) value ""))
|
||||
))
|
||||
|
||||
(call println "parsing...")
|
||||
(let parser (call Parser tokens))
|
||||
(let (parse) parser)
|
||||
(let ast (call parse))
|
||||
|
||||
(call println "=== ast ===")
|
||||
(for expr ast (do
|
||||
(call print_expr expr 0)
|
||||
))
|
||||
|
||||
(call println "emitting...")
|
||||
(let emitter (call Emitter ast))
|
||||
(let (emit) emitter)
|
||||
(let js_code (call emit))
|
||||
|
||||
(call println "=== js ===")
|
||||
(call println js_code)
|
||||
|
||||
(call println "writing file...")
|
||||
(call write_text_file "out.js" js_code)
|
||||
|
||||
|
||||
|
||||
|
458
phi.js
458
phi.js
@ -4,13 +4,14 @@ import * as fs from "node:fs";
|
||||
import process from "node:process";
|
||||
|
||||
function main() {
|
||||
const text = fs.readFileSync(process.argv[2]).toString();
|
||||
const filename = process.argv[2];
|
||||
const text = fs.readFileSync(filename).toString();
|
||||
|
||||
const ast = new Parser(text).parse();
|
||||
|
||||
let lastValue = null;
|
||||
|
||||
const evaluator = new Evaluator();
|
||||
const evaluator = new Evaluator(filename);
|
||||
for (const expr of ast) {
|
||||
const result = evaluator.eval(expr);
|
||||
if (result.type !== "value") {
|
||||
@ -24,14 +25,22 @@ function main() {
|
||||
}
|
||||
|
||||
class Evaluator {
|
||||
constructor() {
|
||||
this.syms = { parent: undefined, map: new Map(builtins) };
|
||||
constructor(filename) {
|
||||
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
||||
this.currentLine = 0;
|
||||
this.callStack = [{ name: "<file>", line: 0 }];
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expr} expr
|
||||
*/
|
||||
eval(expr) {
|
||||
if (!expr) {
|
||||
console.log(this.callStack)
|
||||
throw new Error()
|
||||
}
|
||||
this.currentLine = expr.line;
|
||||
if (expr.type === "list") {
|
||||
return this.evalList(expr, expr.line);
|
||||
} else if (expr.type === "int") {
|
||||
@ -48,7 +57,11 @@ class Evaluator {
|
||||
`could not find symbol '${expr.value}' on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
return { type: "value", value: sym };
|
||||
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}`,
|
||||
@ -68,97 +81,29 @@ class Evaluator {
|
||||
const s = expr.values;
|
||||
const id = s[0]?.type === "ident" ? s[0].value : undefined;
|
||||
if (id === "fn") {
|
||||
const name = s[1].value;
|
||||
this.syms.map.set(name, {
|
||||
type: "fn",
|
||||
name,
|
||||
params: s[2].values.map((ident) => ident.value),
|
||||
body: s[3],
|
||||
syms: this.syms,
|
||||
});
|
||||
return { type: "value", value: { type: "null" } };
|
||||
return this.evalFn(expr);
|
||||
} 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" } };
|
||||
return this.evalLet(expr);
|
||||
} 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" };
|
||||
}
|
||||
return this.evalIf(expr);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
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") {
|
||||
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 };
|
||||
return this.evalDo(expr);
|
||||
} else if (id === "call") {
|
||||
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");
|
||||
}
|
||||
const callerSyms = this.syms;
|
||||
this.syms = {
|
||||
parent: fnValue.syms,
|
||||
map: new Map(),
|
||||
};
|
||||
if (fnValue.params.length !== args.length) {
|
||||
throw new Error(
|
||||
`incorrect amount of arguments on line ${line}`,
|
||||
);
|
||||
}
|
||||
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;
|
||||
return { type: "value", value: returnValue };
|
||||
return this.evalCall(expr);
|
||||
} else if (id === "not") {
|
||||
const value = this.evalToValue(s[1]);
|
||||
return {
|
||||
@ -181,44 +126,31 @@ class Evaluator {
|
||||
} else {
|
||||
return { type: "value", value: left };
|
||||
}
|
||||
} else if (id in artithmeticOps) {
|
||||
} else if (id in this.artithmeticOps) {
|
||||
const left = this.evalToValue(s[1]);
|
||||
const right = this.evalToValue(s[2]);
|
||||
if (id === "+" && left.type === "string" && right.type === "string") {
|
||||
return { type: "value", value: { type: "string", value: left.value + right.value } };
|
||||
}
|
||||
return {
|
||||
type: "value",
|
||||
value: {
|
||||
type: "int",
|
||||
value: artithmeticOps[id](left.value, right.value),
|
||||
value: this.artithmeticOps[id](left.value, right.value),
|
||||
},
|
||||
};
|
||||
} else if (id in comparisonOps) {
|
||||
} else if (id in this.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),
|
||||
value: this.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}`,
|
||||
);
|
||||
}
|
||||
return { type: "value", value: { type: "null" } };
|
||||
} else if (id in this.assignOps) {
|
||||
return this.evalAssign(expr);
|
||||
} else {
|
||||
return {
|
||||
type: "value",
|
||||
@ -230,6 +162,196 @@ class Evaluator {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (this.callStack.length > 100) {
|
||||
this.panic("stack overflow")
|
||||
this.printCallStack()
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
this.callStack.push({ name: fnValue.name, line: expr.line });
|
||||
const callerSyms = this.syms;
|
||||
this.syms = {
|
||||
parent: fnValue.syms,
|
||||
map: new Map(),
|
||||
};
|
||||
if (fnValue.params.length !== args.length) {
|
||||
throw new Error(
|
||||
`incorrect amount of arguments for function '${fnValue.name}' on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
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.callStack.pop()
|
||||
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") {
|
||||
throw new Error(`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);
|
||||
@ -239,28 +361,30 @@ class Evaluator {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
panic(msg) {
|
||||
console.error(`\x1b[1;91mpanic\x1b[1;97m: ${msg}\x1b[0m"`);
|
||||
this.printCallStack();
|
||||
}
|
||||
|
||||
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 }),
|
||||
};
|
||||
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`);
|
||||
}
|
||||
}
|
||||
|
||||
const builtinFns = {
|
||||
println(msg, ...args) {
|
||||
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) {
|
||||
@ -269,44 +393,108 @@ const builtinFns = {
|
||||
|
||||
console.log(text);
|
||||
return { type: "null" };
|
||||
},
|
||||
read_text_file(path) {
|
||||
}
|
||||
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 };
|
||||
},
|
||||
push(list, value) {
|
||||
}
|
||||
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;
|
||||
},
|
||||
at(value, index) {
|
||||
}
|
||||
builtinAt(value, index) {
|
||||
if (value.type === "string") {
|
||||
return { type: "string", value: value.value[index.value] };
|
||||
}
|
||||
return value.values[index.value];
|
||||
},
|
||||
len(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 = {
|
||||
"+": (left, right) => right + left,
|
||||
"-": (left, right) => right - left,
|
||||
};
|
||||
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,
|
||||
};
|
||||
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 }),
|
||||
};
|
||||
|
||||
const consts = {
|
||||
builtinFns = {
|
||||
"format": (...args) => this.builtinFormat(...args),
|
||||
"println": (...args) => this.builtinPrintln(...args),
|
||||
"panic": (...args) => this.builtinPanic(...args),
|
||||
"read_text_file": (...args) => this.builtinReadTextFile(...args),
|
||||
"write_text_file": (...args) => this.builtinWriteTextFile(...args),
|
||||
"push": (...args) => this.builtinPush(...args),
|
||||
"at": (...args) => this.builtinAt(...args),
|
||||
"len": (...args) => this.builtinLen(...args),
|
||||
"string_to_int": (...args) => this.builtinStringToInt(...args),
|
||||
"char_code": (...args) => this.builtinCharCode(...args),
|
||||
"strings_join": (...args) => this.builtinStringsJoin(...args),
|
||||
};
|
||||
|
||||
consts = {
|
||||
"null": { type: "null" },
|
||||
"false": { type: "bool", value: false },
|
||||
"true": { type: "bool", value: true },
|
||||
};
|
||||
|
||||
const builtins = [
|
||||
...Object.entries(builtinFns)
|
||||
builtins = [
|
||||
...Object.entries(this.builtinFns)
|
||||
.map(([key, fn]) => [key, { type: "builtin", fn }]),
|
||||
...Object.entries(consts),
|
||||
...Object.entries(this.consts),
|
||||
];
|
||||
}
|
||||
|
||||
function valueToPrint(value) {
|
||||
if (value.type === "null") {
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
(fn hello () (do
|
||||
(call println "hello world")
|
||||
(call println "hello world")
|
||||
|
@ -8,7 +8,7 @@ if exists("b:current_syntax")
|
||||
endif
|
||||
|
||||
|
||||
syn keyword Keyword fn call return loop break if let do
|
||||
syn keyword Keyword fn call return loop for break if let do
|
||||
syn keyword Operator and or not
|
||||
syn keyword Special null
|
||||
syn keyword Boolean true false
|
||||
|
@ -2,6 +2,13 @@
|
||||
|
||||
set -xe
|
||||
|
||||
if [ $# -eq 0 ]
|
||||
then
|
||||
echo "No VS Code location specified."
|
||||
echo "USAGE: ./install.sh <vscode location>"
|
||||
echo "EXAMPLE: ./install.sh ~/.vscode"
|
||||
fi
|
||||
|
||||
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
CODE_PATH=$1
|
||||
|
||||
|
@ -2,28 +2,32 @@
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "phi",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"include": "#operators"
|
||||
},
|
||||
{
|
||||
"include": "#numbers"
|
||||
},
|
||||
{
|
||||
"include": "#strings"
|
||||
},
|
||||
{
|
||||
"include": "#idents"
|
||||
}
|
||||
{ "include": "#comments" },
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#operators" },
|
||||
{ "include": "#numbers" },
|
||||
{ "include": "#strings" },
|
||||
{ "include": "#idents" }
|
||||
],
|
||||
"repository": {
|
||||
"comments": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "comment.line.phi",
|
||||
"begin": "//",
|
||||
"end": "\\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.phi",
|
||||
"match": "\\b(fn|call|return|loop|break|if|let|do)\\b"
|
||||
"match": "\\b(fn|return|loop|for|break|if|do|call)\\b"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.phi",
|
||||
"match": "\\b(let)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.phi",
|
||||
|
Loading…
x
Reference in New Issue
Block a user