Comparing version 0.7.0 to 0.8.0
@@ -12,3 +12,9 @@ import { | ||
), | ||
call: (expr) => parenthesize(expr.callee, ...expr.args), | ||
expr: (stmt) => visitExpr(stmt.expression, astPrinter), | ||
func: (stmt) => parenthesizeText( | ||
"func", | ||
parenthesizeText(stmt.name.lexeme, ...stmt.params.map((p) => p.lexeme)), | ||
visitStmt({ kind: "block", statements: stmt.body }, astPrinter) | ||
), | ||
grouping: (expr) => parenthesize("group", expr.expr), | ||
@@ -24,2 +30,3 @@ if: (stmt) => parenthesizeText( | ||
print: (stmt) => parenthesize("print", stmt.expression), | ||
return: (stmt) => parenthesize("return", ...stmt.value ? [stmt.value] : []), | ||
unary: (expr) => parenthesize(expr.operator.lexeme, expr.right), | ||
@@ -41,4 +48,4 @@ "var-expr": (expr) => String(expr.name.literal), | ||
} | ||
function parenthesize(name, ...exprs) { | ||
const parts = [name]; | ||
function parenthesize(...exprs) { | ||
const parts = []; | ||
for (const expr of exprs) { | ||
@@ -45,0 +52,0 @@ parts.push(typeof expr == "string" ? expr : visitExpr(expr, astPrinter)); |
@@ -5,6 +5,34 @@ import { | ||
} from "./ast.js"; | ||
import { LoxCallable } from "./callable.js"; | ||
import { Environment } from "./environment.js"; | ||
import { LoxFunction } from "./lox-function.js"; | ||
import { runtimeError } from "./main.js"; | ||
class ClockFn extends LoxCallable { | ||
arity() { | ||
return 0; | ||
} | ||
call() { | ||
return Date.now(); | ||
} | ||
toString() { | ||
return "<native fn>"; | ||
} | ||
} | ||
class ReturnCall extends Error { | ||
value; | ||
constructor(value) { | ||
super(); | ||
this.value = value; | ||
} | ||
} | ||
class Interpreter { | ||
#environment = new Environment(); | ||
globals = new Environment(); | ||
#environment = this.globals; | ||
constructor() { | ||
this.globals.define("clock", new ClockFn()); | ||
} | ||
return(stmt) { | ||
const value = stmt.value && this.evaluate(stmt.value); | ||
throw new ReturnCall(value); | ||
} | ||
"var-expr"(expr) { | ||
@@ -79,2 +107,19 @@ return this.#environment.get(expr.name); | ||
} | ||
call(expr) { | ||
const callee = this.evaluate(expr.callee); | ||
const args = expr.args.map((arg) => this.evaluate(arg)); | ||
if (!(callee instanceof LoxCallable)) { | ||
throw new RuntimeError( | ||
expr.paren, | ||
"Can only call functions and classes." | ||
); | ||
} | ||
if (args.length != callee.arity()) { | ||
throw new RuntimeError( | ||
expr.paren, | ||
`Expected ${callee.arity()} arguments but got ${args.length}.` | ||
); | ||
} | ||
return callee.call(this, args); | ||
} | ||
evaluate(expr) { | ||
@@ -100,2 +145,6 @@ return visitExpr(expr, this); | ||
} | ||
func(stmt) { | ||
const func = new LoxFunction(stmt, this.#environment); | ||
this.#environment.define(stmt.name.lexeme, func); | ||
} | ||
grouping(expr) { | ||
@@ -186,2 +235,4 @@ return this.evaluate(expr.expr); | ||
return "nil"; | ||
} else if (val === void 0) { | ||
throw new Error(`undefined is not a valid Lox value`); | ||
} | ||
@@ -192,2 +243,3 @@ return String(val); | ||
Interpreter, | ||
ReturnCall, | ||
RuntimeError, | ||
@@ -194,0 +246,0 @@ isEqual, |
@@ -59,3 +59,5 @@ import { errorOnToken } from "./main.js"; | ||
try { | ||
if (match("var")) { | ||
if (match("fun")) { | ||
return func("function"); | ||
} else if (match("var")) { | ||
return varDeclaration(); | ||
@@ -72,2 +74,19 @@ } | ||
}; | ||
const func = (kind) => { | ||
const name = consume("identifier", `Expect ${kind} name.`); | ||
consume("(", "Expect '(' after ${kind} name."); | ||
const params = []; | ||
if (!check(")")) { | ||
do { | ||
if (params.length >= 255) { | ||
error(peek(), "Can't have more than 255 parameters."); | ||
} | ||
params.push(consume("identifier", "Expect parameter name.")); | ||
} while (match(",")); | ||
} | ||
consume(")", "Expect ')' after parameters."); | ||
consume("{", `Expect '{' before ${kind} body.`); | ||
const body = block(); | ||
return { body, kind: "func", name, params }; | ||
}; | ||
const varDeclaration = () => { | ||
@@ -89,2 +108,4 @@ const name = consume("identifier", "Expect variable name."); | ||
return printStatement(); | ||
} else if (match("return")) { | ||
return returnStatement(); | ||
} else if (match("while")) { | ||
@@ -150,2 +171,11 @@ return whileStatement(); | ||
}; | ||
const returnStatement = () => { | ||
const keyword = previous(); | ||
let value = null; | ||
if (!check(";")) { | ||
value = expression(); | ||
} | ||
consume(";", "Expect ';' after return value."); | ||
return { keyword, kind: "return", value }; | ||
}; | ||
const block = () => { | ||
@@ -200,4 +230,28 @@ const statements = []; | ||
} | ||
return primary(); | ||
return call(); | ||
}; | ||
const call = () => { | ||
let expr = primary(); | ||
while (true) { | ||
if (match("(")) { | ||
expr = finishCall(expr); | ||
} else { | ||
break; | ||
} | ||
} | ||
return expr; | ||
}; | ||
const finishCall = (callee) => { | ||
const args = []; | ||
if (!check(")")) { | ||
do { | ||
if (args.length >= 255) { | ||
error(peek(), "Can't have more than 255 arguments."); | ||
} | ||
args.push(expression()); | ||
} while (match(",")); | ||
} | ||
const paren = consume(")", "Expect ')' after arguments."); | ||
return { args, callee, kind: "call", paren }; | ||
}; | ||
const primary = () => { | ||
@@ -204,0 +258,0 @@ if (match("false")) { |
@@ -64,3 +64,4 @@ import { error } from "./main.js"; | ||
const isCurrency = this.source[this.start] === "$"; | ||
while (isDigit(this.#peek()) || this.#peek() === ",") { | ||
while (isDigit(this.#peek()) || // a trailing comma might be part of an argument list. | ||
this.#peek() === "," && isDigit(this.#peekNext())) { | ||
this.#advance(); | ||
@@ -67,0 +68,0 @@ } |
{ | ||
"name": "gravlax", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "A Lox interpreter with tasty TypeScript seasoning", | ||
@@ -31,3 +31,3 @@ "repository": { | ||
"lint:spelling": "cspell \"**\" \".github/**/*\"", | ||
"pre-push": "pnpm run '/^lint(?!:packages).*$/'", | ||
"pre-push": "pnpm run '/^(tsc|lint(?!:packages).*)$/'", | ||
"prepare": "husky install", | ||
@@ -34,0 +34,0 @@ "repl": "pnpm run:ts src/index.ts", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
91920
29
1015