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
|
(fn tokenize (text) (do
|
||||||
(let text_len (call len text))
|
(let text_len (call len text))
|
||||||
@ -15,14 +207,13 @@
|
|||||||
(let ch (call at text i))
|
(let ch (call at text i))
|
||||||
|
|
||||||
(if (call contains " \t\r\n" ch) (do
|
(if (call contains " \t\r\n" ch) (do
|
||||||
(call println "line = %, ch = '%'" line ch)
|
|
||||||
(if (== ch "\n") (do
|
(if (== ch "\n") (do
|
||||||
(+= line 1)
|
(+= line 1)
|
||||||
))
|
))
|
||||||
(+= i 1)
|
(+= i 1)
|
||||||
) (if (call slice_eq text i "//") (do
|
) (if (call slice_eq text i "//") (do
|
||||||
(loop (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)
|
(break)
|
||||||
))
|
))
|
||||||
(+= i 1)
|
(+= i 1)
|
||||||
@ -31,8 +222,52 @@
|
|||||||
(call push tokens (ch line))
|
(call push tokens (ch line))
|
||||||
(+= i 1)
|
(+= i 1)
|
||||||
) (if (== ch "\"") (do
|
) (if (== ch "\"") (do
|
||||||
|
(let value "")
|
||||||
(+= i 1)
|
(+= 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
|
) (if (call contains identChars ch) (do
|
||||||
(let value "")
|
(let value "")
|
||||||
(loop (do
|
(loop (do
|
||||||
@ -40,19 +275,22 @@
|
|||||||
(if (or (>= i text_len) (not (call contains identChars ch))) (do
|
(if (or (>= i text_len) (not (call contains identChars ch))) (do
|
||||||
(break)
|
(break)
|
||||||
))
|
))
|
||||||
(call push value ch)
|
(+= value ch)
|
||||||
(+= i 1)
|
(+= i 1)
|
||||||
))
|
))
|
||||||
(call push tokens ("ident" line value))
|
(call push tokens ("ident" line value))
|
||||||
) (do
|
) (do
|
||||||
(call println "illegal char '%'" ch)
|
(call println "illegal char '%'" ch)
|
||||||
(+= i 1)
|
(+= i 1)
|
||||||
))))))
|
)))))))
|
||||||
|
|
||||||
))
|
))
|
||||||
(return tokens)
|
(return tokens)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(let identChars (+ "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-*/%&|=?!<>'_"))
|
||||||
|
|
||||||
(fn contains (text ch) (do
|
(fn contains (text ch) (do
|
||||||
(let text_len (call len text))
|
(let text_len (call len text))
|
||||||
(let i 0)
|
(let i 0)
|
||||||
@ -69,9 +307,11 @@
|
|||||||
(fn slice_eq (str slice_idx substr) (do
|
(fn slice_eq (str slice_idx substr) (do
|
||||||
(let str_len (call len str))
|
(let str_len (call len str))
|
||||||
(let substr_len (call len substr))
|
(let substr_len (call len substr))
|
||||||
(let i slice_idx)
|
(let i 0)
|
||||||
(loop (do
|
(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))
|
(return false))
|
||||||
(if (!= (call at str (+ slice_idx i)) (call at substr i))
|
(if (!= (call at str (+ slice_idx i)) (call at substr i))
|
||||||
(return false))
|
(return false))
|
||||||
@ -80,14 +320,76 @@
|
|||||||
(return true)
|
(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 text (call read_text_file "program.phi"))
|
||||||
|
|
||||||
(let tokens (call tokenize text))
|
(call println "reading file...")
|
||||||
|
|
||||||
(call println "=== text ===")
|
(call println "=== text ===")
|
||||||
(call println text)
|
(call println text)
|
||||||
|
|
||||||
|
(call println "tokenizing...")
|
||||||
|
(let tokens (call tokenize text))
|
||||||
|
|
||||||
(call println "=== tokens ===")
|
(call println "=== tokens ===")
|
||||||
(call println tokens)
|
(for (tok line value) tokens (do
|
||||||
(call println (+ 1 2))
|
(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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
474
phi.js
474
phi.js
@ -4,13 +4,14 @@ import * as fs from "node:fs";
|
|||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
||||||
function main() {
|
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();
|
const ast = new Parser(text).parse();
|
||||||
|
|
||||||
let lastValue = null;
|
let lastValue = null;
|
||||||
|
|
||||||
const evaluator = new Evaluator();
|
const evaluator = new Evaluator(filename);
|
||||||
for (const expr of ast) {
|
for (const expr of ast) {
|
||||||
const result = evaluator.eval(expr);
|
const result = evaluator.eval(expr);
|
||||||
if (result.type !== "value") {
|
if (result.type !== "value") {
|
||||||
@ -24,14 +25,22 @@ function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Evaluator {
|
class Evaluator {
|
||||||
constructor() {
|
constructor(filename) {
|
||||||
this.syms = { parent: undefined, map: new Map(builtins) };
|
this.syms = { parent: undefined, map: new Map(this.builtins) };
|
||||||
|
this.currentLine = 0;
|
||||||
|
this.callStack = [{ name: "<file>", line: 0 }];
|
||||||
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Expr} expr
|
* @param {Expr} expr
|
||||||
*/
|
*/
|
||||||
eval(expr) {
|
eval(expr) {
|
||||||
|
if (!expr) {
|
||||||
|
console.log(this.callStack)
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
this.currentLine = expr.line;
|
||||||
if (expr.type === "list") {
|
if (expr.type === "list") {
|
||||||
return this.evalList(expr, expr.line);
|
return this.evalList(expr, expr.line);
|
||||||
} else if (expr.type === "int") {
|
} else if (expr.type === "int") {
|
||||||
@ -48,7 +57,11 @@ class Evaluator {
|
|||||||
`could not find symbol '${expr.value}' on line ${expr.line}`,
|
`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 {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`unknown expr type '${expr.type}' on line ${expr.line}`,
|
`unknown expr type '${expr.type}' on line ${expr.line}`,
|
||||||
@ -68,97 +81,29 @@ class Evaluator {
|
|||||||
const s = expr.values;
|
const s = expr.values;
|
||||||
const id = s[0]?.type === "ident" ? 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;
|
return this.evalFn(expr);
|
||||||
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" } };
|
|
||||||
} else if (id === "return") {
|
} else if (id === "return") {
|
||||||
return {
|
return {
|
||||||
type: "return",
|
type: "return",
|
||||||
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
||||||
};
|
};
|
||||||
} else if (id === "let") {
|
} else if (id === "let") {
|
||||||
const value = this.evalToValue(s[2]);
|
return this.evalLet(expr);
|
||||||
this.syms.map.set(s[1].value, value);
|
|
||||||
return { type: "value", value: { type: "null" } };
|
|
||||||
} else if (id === "if") {
|
} else if (id === "if") {
|
||||||
const cond = this.evalToValue(s[1]);
|
return this.evalIf(expr);
|
||||||
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") {
|
} else if (id === "loop") {
|
||||||
while (true) {
|
return this.evalLoop(expr);
|
||||||
const result = this.eval(s[1]);
|
} else if (id === "for") {
|
||||||
if (result.type === "break") {
|
return this.evalFor(expr);
|
||||||
return { type: "value", value: result.value };
|
|
||||||
} else if (result.type !== "value") {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (id === "break") {
|
} else if (id === "break") {
|
||||||
return {
|
return {
|
||||||
type: "break",
|
type: "break",
|
||||||
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
value: s[1] ? this.evalToValue(s[1]) : { type: "null" },
|
||||||
};
|
};
|
||||||
} else if (id === "do") {
|
} else if (id === "do") {
|
||||||
let lastValue = { type: "null" };
|
return this.evalDo(expr);
|
||||||
|
|
||||||
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));
|
return this.evalCall(expr);
|
||||||
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 };
|
|
||||||
} else if (id === "not") {
|
} else if (id === "not") {
|
||||||
const value = this.evalToValue(s[1]);
|
const value = this.evalToValue(s[1]);
|
||||||
return {
|
return {
|
||||||
@ -181,44 +126,31 @@ class Evaluator {
|
|||||||
} else {
|
} else {
|
||||||
return { type: "value", value: left };
|
return { type: "value", value: left };
|
||||||
}
|
}
|
||||||
} else if (id in 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") {
|
||||||
|
return { type: "value", value: { type: "string", value: left.value + right.value } };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: "value",
|
type: "value",
|
||||||
value: {
|
value: {
|
||||||
type: "int",
|
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 left = this.evalToValue(s[1]);
|
||||||
const right = this.evalToValue(s[2]);
|
const right = this.evalToValue(s[2]);
|
||||||
return {
|
return {
|
||||||
type: "value",
|
type: "value",
|
||||||
value: {
|
value: {
|
||||||
type: "bool",
|
type: "bool",
|
||||||
value: comparisonOps[id](left.value, right.value),
|
value: this.comparisonOps[id](left.value, right.value),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if (id in assignOps) {
|
} else if (id in this.assignOps) {
|
||||||
if (s[1].type === "ident") {
|
return this.evalAssign(expr);
|
||||||
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 {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: "value",
|
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) {
|
findSym(ident, syms = this.syms) {
|
||||||
if (syms.map.has(ident)) {
|
if (syms.map.has(ident)) {
|
||||||
return syms.map.get(ident);
|
return syms.map.get(ident);
|
||||||
@ -239,28 +361,30 @@ class Evaluator {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const artithmeticOps = {
|
panic(msg) {
|
||||||
"+": (left, right) => right + left,
|
console.error(`\x1b[1;91mpanic\x1b[1;97m: ${msg}\x1b[0m"`);
|
||||||
"-": (left, right) => right - left,
|
this.printCallStack();
|
||||||
};
|
}
|
||||||
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 = {
|
printCallStack() {
|
||||||
println(msg, ...args) {
|
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);
|
let text = valueToPrint(msg);
|
||||||
|
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
@ -269,44 +393,108 @@ const builtinFns = {
|
|||||||
|
|
||||||
console.log(text);
|
console.log(text);
|
||||||
return { type: "null" };
|
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();
|
const text = fs.readFileSync(path.value).toString();
|
||||||
return { type: "string", value: text };
|
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") {
|
if (list.type === "string") {
|
||||||
list.value += value.value;
|
list.value += value.value;
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
list.values.push(value);
|
list.values.push(value);
|
||||||
return list;
|
return list;
|
||||||
},
|
}
|
||||||
at(value, index) {
|
builtinAt(value, index) {
|
||||||
if (value.type === "string") {
|
if (value.type === "string") {
|
||||||
return { type: "string", value: value.value[index.value] };
|
return { type: "string", value: value.value[index.value] };
|
||||||
}
|
}
|
||||||
return value.values[index.value];
|
return value.values[index.value];
|
||||||
},
|
}
|
||||||
len(value) {
|
builtinLen(value) {
|
||||||
if (value.type === "string") {
|
if (value.type === "string") {
|
||||||
return { type: "int", value: value.value.length };
|
return { type: "int", value: value.value.length };
|
||||||
}
|
}
|
||||||
return { type: "int", value: value.values.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("")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const consts = {
|
artithmeticOps = {
|
||||||
"null": { type: "null" },
|
"+": (left, right) => right + left,
|
||||||
"false": { type: "bool", value: false },
|
"-": (left, right) => right - left,
|
||||||
"true": { type: "bool", value: true },
|
};
|
||||||
};
|
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 builtins = [
|
builtinFns = {
|
||||||
...Object.entries(builtinFns)
|
"format": (...args) => this.builtinFormat(...args),
|
||||||
.map(([key, fn]) => [key, { type: "builtin", fn }]),
|
"println": (...args) => this.builtinPrintln(...args),
|
||||||
...Object.entries(consts),
|
"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 },
|
||||||
|
};
|
||||||
|
|
||||||
|
builtins = [
|
||||||
|
...Object.entries(this.builtinFns)
|
||||||
|
.map(([key, fn]) => [key, { type: "builtin", fn }]),
|
||||||
|
...Object.entries(this.consts),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function valueToPrint(value) {
|
function valueToPrint(value) {
|
||||||
if (value.type === "null") {
|
if (value.type === "null") {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
(fn hello () (do
|
(fn hello () (do
|
||||||
(call println "hello world")
|
(call println "hello world")
|
||||||
(call println "hello world")
|
(call println "hello world")
|
||||||
|
@ -8,7 +8,7 @@ if exists("b:current_syntax")
|
|||||||
endif
|
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 Operator and or not
|
||||||
syn keyword Special null
|
syn keyword Special null
|
||||||
syn keyword Boolean true false
|
syn keyword Boolean true false
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
set -xe
|
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]}")
|
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||||
CODE_PATH=$1
|
CODE_PATH=$1
|
||||||
|
|
||||||
|
@ -2,28 +2,32 @@
|
|||||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
"name": "phi",
|
"name": "phi",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{ "include": "#comments" },
|
||||||
"include": "#keywords"
|
{ "include": "#keywords" },
|
||||||
},
|
{ "include": "#operators" },
|
||||||
{
|
{ "include": "#numbers" },
|
||||||
"include": "#operators"
|
{ "include": "#strings" },
|
||||||
},
|
{ "include": "#idents" }
|
||||||
{
|
|
||||||
"include": "#numbers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#idents"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
|
"comments": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "comment.line.phi",
|
||||||
|
"begin": "//",
|
||||||
|
"end": "\\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "keyword.control.phi",
|
"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",
|
"name": "keyword.operator.phi",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user