js gen + syntax

This commit is contained in:
sfj 2025-09-10 15:43:53 +02:00
parent 896e9a5e45
commit 623964374a
7 changed files with 674 additions and 173 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
out.js

View File

@ -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
View File

@ -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") {

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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",