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

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

View File

@ -1,4 +1,3 @@
(fn hello () (do
(call println "hello world")
(call println "hello world")

View File

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

View File

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

View File

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