Comparing version 0.4.0 to 0.5.0
@@ -1,16 +0,33 @@ | ||
import { visitExpr } from "./ast.js"; | ||
import { | ||
visitExpr, | ||
visitStmt | ||
} from "./ast.js"; | ||
const astPrinter = { | ||
assign: (stmt) => parenthesize("assign", stmt.name.lexeme, stmt.value), | ||
binary: (expr) => parenthesize(expr.operator.lexeme, expr.left, expr.right), | ||
block: (block) => parenthesizeText( | ||
"block", | ||
...block.statements.map((stmt) => visitStmt(stmt, astPrinter)) | ||
), | ||
expr: (stmt) => visitExpr(stmt.expression, astPrinter), | ||
grouping: (expr) => parenthesize("group", expr.expr), | ||
literal: (expr) => expr.value === null ? "nil" : String(expr.value), | ||
unary: (expr) => parenthesize(expr.operator.lexeme, expr.right) | ||
print: (stmt) => parenthesize("print", stmt.expression), | ||
unary: (expr) => parenthesize(expr.operator.lexeme, expr.right), | ||
"var-expr": (expr) => String(expr.name.literal), | ||
"var-stmt": (stmt) => parenthesizeText( | ||
"var", | ||
String(stmt.name.literal), | ||
...stmt.initializer ? [visitExpr(stmt.initializer, astPrinter)] : [] | ||
) | ||
}; | ||
function parenthesizeText(...parts) { | ||
return "(" + parts.join(" ") + ")"; | ||
} | ||
function parenthesize(name, ...exprs) { | ||
const parts = ["(", name]; | ||
const parts = [name]; | ||
for (const expr of exprs) { | ||
parts.push(" "); | ||
parts.push(visitExpr(expr, astPrinter)); | ||
parts.push(typeof expr == "string" ? expr : visitExpr(expr, astPrinter)); | ||
} | ||
parts.push(")"); | ||
return parts.join(""); | ||
return parenthesizeText(...parts); | ||
} | ||
@@ -17,0 +34,0 @@ export { |
function visitExpr(expr, visitor) { | ||
return visitor[expr.kind](expr); | ||
} | ||
function visitStmt(stmt, visitor) { | ||
return visitor[stmt.kind](stmt); | ||
} | ||
export { | ||
visitExpr | ||
visitExpr, | ||
visitStmt | ||
}; | ||
//# sourceMappingURL=ast.js.map |
import { | ||
visitExpr | ||
visitExpr, | ||
visitStmt | ||
} from "./ast.js"; | ||
import { Environment } from "./environment.js"; | ||
import { runtimeError } from "./main.js"; | ||
class Interpreter { | ||
#environment = new Environment(); | ||
"var-expr"(expr) { | ||
return this.#environment.get(expr.name); | ||
} | ||
"var-stmt"(stmt) { | ||
let value = null; | ||
if (stmt.initializer) { | ||
value = this.evaluate(stmt.initializer); | ||
} | ||
this.#environment.define(stmt.name.lexeme, value); | ||
return null; | ||
} | ||
assign(expr) { | ||
const value = this.evaluate(expr.value); | ||
this.#environment.assign(expr.name, value); | ||
return value; | ||
} | ||
binary(expr) { | ||
@@ -56,12 +75,33 @@ const left = this.evaluate(expr.left); | ||
} | ||
block(block) { | ||
this.executeBlock(block.statements, new Environment(this.#environment)); | ||
} | ||
evaluate(expr) { | ||
return visitExpr(expr, this); | ||
} | ||
execute(stmt) { | ||
visitStmt(stmt, this); | ||
} | ||
executeBlock(stmts, environment) { | ||
const prev = this.#environment; | ||
try { | ||
this.#environment = environment; | ||
for (const stmt of stmts) { | ||
this.execute(stmt); | ||
} | ||
} finally { | ||
this.#environment = prev; | ||
} | ||
} | ||
expr(stmt) { | ||
this.evaluate(stmt.expression); | ||
} | ||
grouping(expr) { | ||
return this.evaluate(expr.expr); | ||
} | ||
interpret(expr) { | ||
interpret(statements) { | ||
try { | ||
const value = this.evaluate(expr); | ||
console.log(stringify(value)); | ||
for (const statement of statements) { | ||
this.execute(statement); | ||
} | ||
} catch (e) { | ||
@@ -76,2 +116,6 @@ if (e instanceof RuntimeError) { | ||
} | ||
print(stmt) { | ||
const value = this.evaluate(stmt.expression); | ||
console.log(stringify(value)); | ||
} | ||
unary(expr) { | ||
@@ -78,0 +122,0 @@ const right = this.evaluate(expr.right); |
@@ -6,5 +6,2 @@ import * as fs from "node:fs/promises"; | ||
import { Scanner } from "./scanner.js"; | ||
function add(a, b) { | ||
return a + b; | ||
} | ||
async function runFile(interpreter, path) { | ||
@@ -20,2 +17,6 @@ const contents = await fs.readFile(path, "utf-8"); | ||
} | ||
function resetErrors() { | ||
hadError = false; | ||
hadRuntimeError = false; | ||
} | ||
async function runPrompt(interpreter) { | ||
@@ -25,3 +26,3 @@ process.stdout.write("> "); | ||
run(interpreter, line); | ||
hadError = false; | ||
resetErrors(); | ||
process.stdout.write("> "); | ||
@@ -49,2 +50,3 @@ } | ||
console.error(`[line ${line}] Error${where}: ${message}`); | ||
hadError = true; | ||
} | ||
@@ -54,7 +56,7 @@ function run(interpreter, contents) { | ||
const tokens = scanner.scanTokens(); | ||
const expr = parse(tokens); | ||
if (hadError || !expr) { | ||
const statements = parse(tokens); | ||
if (hadError || !statements) { | ||
return; | ||
} | ||
interpreter.interpret(expr); | ||
interpreter.interpret(statements); | ||
} | ||
@@ -64,3 +66,3 @@ async function main() { | ||
if (args.length > 1) { | ||
console.error("Usage:", args[1], "[script]"); | ||
console.error("Usage:", process.argv[1], "[script]"); | ||
process.exit(64); | ||
@@ -76,6 +78,6 @@ } | ||
export { | ||
add, | ||
error, | ||
errorOnToken, | ||
main, | ||
resetErrors, | ||
runFile, | ||
@@ -82,0 +84,0 @@ runPrompt, |
@@ -57,3 +57,68 @@ import { errorOnToken } from "./main.js"; | ||
}; | ||
const expression = () => equality(); | ||
const declaration = () => { | ||
try { | ||
if (match("var")) { | ||
return varDeclaration(); | ||
} | ||
return statement(); | ||
} catch (e) { | ||
if (e instanceof ParseError) { | ||
synchronize(); | ||
} else { | ||
throw e; | ||
} | ||
} | ||
}; | ||
const varDeclaration = () => { | ||
const name = consume("identifier", "Expect variable name."); | ||
let initializer = null; | ||
if (match("=")) { | ||
initializer = expression(); | ||
} | ||
consume(";", "Expect ';' after variable declaration."); | ||
return { initializer, kind: "var-stmt", name }; | ||
}; | ||
const statement = () => { | ||
if (match("print")) { | ||
return printStatement(); | ||
} else if (match("{")) { | ||
return { kind: "block", statements: block() }; | ||
} | ||
return expressionStatement(); | ||
}; | ||
const expressionStatement = () => { | ||
const expr = expression(); | ||
consume(";", "Expect ';' after expression."); | ||
return { expression: expr, kind: "expr" }; | ||
}; | ||
const printStatement = () => { | ||
const expr = expression(); | ||
consume(";", "Expect ';' after expression."); | ||
return { expression: expr, kind: "print" }; | ||
}; | ||
const block = () => { | ||
const statements = []; | ||
while (!check("}") && !isAtEnd()) { | ||
const d = declaration(); | ||
if (d) { | ||
statements.push(d); | ||
} | ||
} | ||
consume("}", "Expect '}' after block."); | ||
return statements; | ||
}; | ||
const expression = () => assignment(); | ||
const assignment = () => { | ||
const expr = equality(); | ||
if (match("=")) { | ||
const equals = previous(); | ||
const value = assignment(); | ||
if (expr.kind == "var-expr") { | ||
const name = expr.name; | ||
return { kind: "assign", name, value }; | ||
} | ||
error(equals, "Invalid assignment target."); | ||
} | ||
return expr; | ||
}; | ||
const unary = () => { | ||
@@ -80,2 +145,4 @@ if (match("!", "-")) { | ||
return { expr, kind: "grouping" }; | ||
} else if (match("identifier")) { | ||
return { kind: "var-expr", name: previous() }; | ||
} | ||
@@ -100,3 +167,10 @@ throw error(peek(), "Expect expression."); | ||
try { | ||
return expression(); | ||
const statements = []; | ||
while (!isAtEnd()) { | ||
const decl = declaration(); | ||
if (decl) { | ||
statements.push(decl); | ||
} | ||
} | ||
return statements; | ||
} catch (e) { | ||
@@ -103,0 +177,0 @@ if (e instanceof ParseError) { |
{ | ||
"name": "gravlax", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "A Lox interpreter with tasty TypeScript seasoning", | ||
@@ -31,2 +31,3 @@ "repository": { | ||
"lint:spelling": "cspell \"**\" \".github/**/*\"", | ||
"pre-push": "pnpm run '/^lint(?!:packages).*$/'", | ||
"prepare": "husky install", | ||
@@ -33,0 +34,0 @@ "repl": "pnpm run:ts src/index.ts", |
@@ -18,12 +18,17 @@ <h1 align="center">Gravlax</h1> | ||
This is my implementation of an interpreter for the Lox language from Robert Nystrom's _[Crafting Interpreters]_. | ||
I'm building this as part of my Winter 2024 batch at the [Recurse Center]. | ||
## Usage | ||
```shell | ||
npm i gravlax | ||
npx gravlax [file.lox] | ||
``` | ||
```ts | ||
import { greet } from "gravlax"; | ||
## Development | ||
greet("Hello, world! ๐"); | ||
```shell | ||
pnpm install | ||
pnpm test | ||
pnpm repl | ||
``` | ||
@@ -55,1 +60,4 @@ | ||
> ๐ This package was templated with [`create-typescript-app`](https://github.com/JoshuaKGoldberg/create-typescript-app). | ||
[Crafting Interpreters]: https://craftinginterpreters.com/contents.html | ||
[Recurse Center]: https://www.recurse.com/ |
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
65367
719
62
25