Comparing version 0.9.0 to 0.10.0
@@ -5,2 +5,3 @@ import { | ||
} from "./ast.js"; | ||
import { stringify } from "./interpreter.js"; | ||
const astPrinter = { | ||
@@ -27,3 +28,3 @@ assign: (stmt) => parenthesize("assign", stmt.name.lexeme, stmt.value), | ||
), | ||
literal: (expr) => expr.value === null ? "nil" : String(expr.value), | ||
literal: (expr) => stringify(expr.value), | ||
logical: (expr) => parenthesize(expr.operator.lexeme, expr.left, expr.right), | ||
@@ -30,0 +31,0 @@ print: (stmt) => parenthesize("print", stmt.expression), |
@@ -35,4 +35,5 @@ import { RuntimeError } from "./interpreter.js"; | ||
const { lexeme } = name; | ||
if (this.#values.has(lexeme)) { | ||
return this.#values.get(lexeme); | ||
const value = this.#values.get(lexeme); | ||
if (value !== void 0) { | ||
return value; | ||
} | ||
@@ -42,3 +43,7 @@ throw new RuntimeError(name, `Undefined variable '${lexeme}'.`); | ||
getAt(distance, name) { | ||
return this.ancestor(distance).#values.get(name); | ||
const value = this.ancestor(distance).#values.get(name); | ||
if (value !== void 0) { | ||
return value; | ||
} | ||
throw new Error(`Resolution pass failed for ${name}`); | ||
} | ||
@@ -45,0 +50,0 @@ } |
@@ -8,2 +8,3 @@ import { | ||
import { LoxFunction } from "./lox-function.js"; | ||
import { isCurrency } from "./lox-value.js"; | ||
import { runtimeError } from "./main.js"; | ||
@@ -28,2 +29,19 @@ class ClockFn extends LoxCallable { | ||
} | ||
function applyOperatorToPair(pair, op) { | ||
if (typeof pair.left === "number") { | ||
return op(pair.left, pair.right); | ||
} else { | ||
const value = op(pair.left.value, pair.right.value); | ||
if (typeof value === "number") { | ||
return { currency: pair.left.currency, value }; | ||
} | ||
return value; | ||
} | ||
} | ||
function applyToNumOrCurrency(val, fn) { | ||
if (isCurrency(val)) { | ||
return { currency: val.currency, value: fn(val.value) }; | ||
} | ||
return fn(val); | ||
} | ||
class Interpreter { | ||
@@ -59,3 +77,2 @@ globals = new Environment(); | ||
this.#environment.define(stmt.name.lexeme, value); | ||
return null; | ||
} | ||
@@ -76,41 +93,44 @@ assign(expr) { | ||
const { operator } = expr; | ||
const pair = { left, right }; | ||
switch (operator.type) { | ||
case "-": | ||
checkNumberOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left - right; | ||
checkSameNumberOperands(operator, pair); | ||
return applyOperatorToPair(pair, (a, b) => a - b); | ||
case "/": | ||
checkNumberOperand(operator, left); | ||
checkNumberOrCurrencyOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left / right; | ||
return applyToNumOrCurrency(left, (v) => v / right); | ||
case "*": | ||
checkNumberOperand(operator, left); | ||
checkNumberOrCurrencyOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left * right; | ||
return applyToNumOrCurrency(left, (v) => v * right); | ||
case "+": | ||
if (typeof left === "number" && typeof right === "number") { | ||
if (typeof left === "string" && typeof right === "string") { | ||
return left + right; | ||
} else if (typeof left === "string" && typeof right === "string") { | ||
return left + right; | ||
} else { | ||
try { | ||
checkSameNumberOperands(operator, pair); | ||
} catch (e) { | ||
if (e instanceof MixedCurrencyError) { | ||
throw e; | ||
} | ||
throw new RuntimeError( | ||
operator, | ||
`Operands must be two numbers/currencies or two strings.` | ||
); | ||
} | ||
return applyOperatorToPair(pair, (a, b) => a + b); | ||
} | ||
throw new RuntimeError( | ||
operator, | ||
"Operands must be two numbers or two strings." | ||
); | ||
case ">": | ||
checkNumberOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left > right; | ||
checkSameNumberOperands(operator, pair); | ||
return applyOperatorToPair(pair, (a, b) => a > b); | ||
case ">=": | ||
checkNumberOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left >= right; | ||
checkSameNumberOperands(operator, pair); | ||
return applyOperatorToPair(pair, (a, b) => a >= b); | ||
case "<": | ||
checkNumberOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left < right; | ||
checkSameNumberOperands(operator, pair); | ||
return applyOperatorToPair(pair, (a, b) => a < b); | ||
case "<=": | ||
checkNumberOperand(operator, left); | ||
checkNumberOperand(operator, right); | ||
return left <= right; | ||
checkSameNumberOperands(operator, pair); | ||
return applyOperatorToPair(pair, (a, b) => a <= b); | ||
case "==": | ||
@@ -212,7 +232,8 @@ return isEqual(left, right); | ||
case "-": | ||
checkNumberOperand(expr.operator, right); | ||
return -right; | ||
checkNumberOrCurrencyOperand(expr.operator, right); | ||
return applyToNumOrCurrency(right, (v) => -v); | ||
case "!": | ||
return !isTruthy(right); | ||
} | ||
throw new Error(`Unknown unary type ${expr.operator.type}`); | ||
} | ||
@@ -232,2 +253,4 @@ while(stmt) { | ||
} | ||
class MixedCurrencyError extends RuntimeError { | ||
} | ||
function checkNumberOperand(operator, operand) { | ||
@@ -239,2 +262,26 @@ if (typeof operand === "number") { | ||
} | ||
function checkNumberOrCurrencyOperand(operator, operand) { | ||
if (typeof operand === "number" || isCurrency(operand)) { | ||
return; | ||
} | ||
throw new RuntimeError(operator, "Operand must be a number or currency."); | ||
} | ||
function checkSameNumberOperands(operator, pair) { | ||
const { left, right } = pair; | ||
if (typeof left === "number" && typeof right === "number") { | ||
return; | ||
} else if (isCurrency(left) && isCurrency(right)) { | ||
if (left.currency == right.currency) { | ||
return; | ||
} | ||
throw new MixedCurrencyError( | ||
operator, | ||
"Operands must be the same currency." | ||
); | ||
} | ||
throw new RuntimeError( | ||
operator, | ||
"Operands must both be numbers or currencies." | ||
); | ||
} | ||
function isTruthy(val) { | ||
@@ -250,3 +297,3 @@ if (val === null) { | ||
function isEqual(a, b) { | ||
return a === b; | ||
return a === b || isCurrency(a) && isCurrency(b) && a.value === b.value && a.currency === b.currency; | ||
} | ||
@@ -256,5 +303,9 @@ function stringify(val) { | ||
return "nil"; | ||
} else if (val === void 0) { | ||
} | ||
if (val === void 0) { | ||
throw new Error(`undefined is not a valid Lox value`); | ||
} | ||
if (isCurrency(val)) { | ||
return `${val.currency}${val.value.toLocaleString()}`; | ||
} | ||
return String(val); | ||
@@ -264,2 +315,3 @@ } | ||
Interpreter, | ||
MixedCurrencyError, | ||
ReturnCall, | ||
@@ -266,0 +318,0 @@ RuntimeError, |
@@ -15,2 +15,3 @@ import { LoxCallable } from "./callable.js"; | ||
} | ||
// Why, oh why, do I need type annotations here? | ||
call(interpreter, args) { | ||
@@ -17,0 +18,0 @@ const env = new Environment(this.closure); |
@@ -26,3 +26,3 @@ import { error } from "./main.js"; | ||
} | ||
#addToken(type, literal, isCurrency) { | ||
#addToken(type, literal) { | ||
const lexeme = this.source.slice(this.start, this.current); | ||
@@ -33,4 +33,3 @@ this.tokens.push({ | ||
literal, | ||
type, | ||
...isCurrency && { isCurrency } | ||
type | ||
}); | ||
@@ -65,3 +64,4 @@ } | ||
#number() { | ||
const isCurrency = this.source[this.start] === "$"; | ||
const char = this.source[this.start]; | ||
const currency = char === "$" || char === "\u20AC" ? char : null; | ||
while (isDigit(this.#peek()) || // a trailing comma might be part of an argument list. | ||
@@ -77,4 +77,5 @@ this.#peek() === "," && isDigit(this.#peekNext())) { | ||
} | ||
const numText = this.source.slice(this.start, this.current).replace(/[$,]/g, ""); | ||
this.#addToken("number", Number(numText), isCurrency); | ||
const numText = this.source.slice(this.start, this.current).replace(/[$€,]/g, ""); | ||
const value = Number(numText); | ||
this.#addToken("number", currency ? { currency, value } : value); | ||
} | ||
@@ -134,3 +135,3 @@ #peek() { | ||
default: | ||
if (isDigit(c) || c === "$") { | ||
if (isDigit(c) || c === "$" || c === "\u20AC") { | ||
this.#number(); | ||
@@ -137,0 +138,0 @@ } else if (isAlpha(c)) { |
@@ -0,3 +1,6 @@ | ||
import { isCurrency } from "./lox-value.js"; | ||
function tokenToString(token) { | ||
return `'${token.lexeme}': ${token.type}` + (token.literal !== null ? `: ${token.isCurrency ? "$" : ""}${token.literal}` : ""); | ||
const { lexeme, literal, type } = token; | ||
const str = isCurrency(literal) ? `${literal.currency}${literal.value}` : String(literal); | ||
return `'${lexeme}': ${type}` + (literal !== null ? `: ${str}` : ""); | ||
} | ||
@@ -4,0 +7,0 @@ export { |
{ | ||
"name": "gravlax", | ||
"version": "0.9.0", | ||
"version": "0.10.0", | ||
"description": "A Lox interpreter with tasty TypeScript seasoning", | ||
@@ -5,0 +5,0 @@ "repository": { |
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
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
117863
35
1312