diff --git a/lib/expressions.js b/lib/expressions.js index 24243a4..53c4271 100644 --- a/lib/expressions.js +++ b/lib/expressions.js @@ -2,7 +2,31 @@ const OPERATORS = require('./operators.js'); +const BOOL = { + t: true, + nil: false, + true: true, + false: false, +}; + +class BooleanExpression { + type = 'boolean'; + + constructor(value) { + if (!(value in BOOL)) { + throw new Error(`Unknown boolean value: ${value}`); + } + this.value = BOOL[value]; + } + + interpret() { + return this.value; + } +} + class NumberExpression { + type = 'number'; + constructor(value) { this.value = parseFloat(value); } @@ -13,6 +37,8 @@ class NumberExpression { } class VariableExpression { + type = 'variable'; + constructor(name) { this.name = name; } @@ -26,6 +52,8 @@ class VariableExpression { } class OperationExpression { + type = 'operation'; + constructor(operator, operands) { this.operator = operator; this.operands = operands; @@ -41,4 +69,34 @@ class OperationExpression { } } -module.exports = { NumberExpression, VariableExpression, OperationExpression }; +const expressions = { + number: NumberExpression, + variable: VariableExpression, + boolean: BooleanExpression, + operation: OperationExpression, +}; + +const expressionType = { + number: 'number', + variable: 'variable', + operation: 'operation', + boolean: 'boolean', +}; + +const getExpressionType = (token) => { + if (Array.isArray(token)) return expressionType.operation; + if (!isNaN(token)) return expressionType.number; + if (token in BOOL) return expressionType.boolean; + return expressionType.variable; +}; + +module.exports = { + NumberExpression, + VariableExpression, + OperationExpression, + BooleanExpression, + BOOL, + expressions, + expressionType, + getExpressionType, +}; diff --git a/lib/parser.js b/lib/parser.js index 782b1e2..90d9fc3 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,9 +1,9 @@ 'use strict'; const { - NumberExpression, - VariableExpression, - OperationExpression, + expressions, + expressionType, + getExpressionType, } = require('./expressions.js'); const tokenize = (source) => { @@ -33,15 +33,15 @@ const tokenize = (source) => { }; const parse = (tokens) => { - if (!Array.isArray(tokens)) { - const isVar = isNaN(tokens); - const Expression = isVar ? VariableExpression : NumberExpression; + const type = getExpressionType(tokens); + const Expression = expressions[type]; + if (type !== expressionType.operation) { return new Expression(tokens); } const operator = tokens[0]; const operands = tokens.slice(1); const operandExpressions = operands.map((x) => parse(x)); - return new OperationExpression(operator, operandExpressions); + return new Expression(operator, operandExpressions); }; const evaluate = (input, context = {}) => { diff --git a/metalisp.d.ts b/metalisp.d.ts index 09173f1..2150d50 100644 --- a/metalisp.d.ts +++ b/metalisp.d.ts @@ -1,5 +1,5 @@ export type List = Array; -export type Value = number | string; +export type Value = number | string | boolean; export type Result = List | Value; export type Operator = (...list: List) => Result; export type LispContext = Record; @@ -14,6 +14,11 @@ export interface Expression { interpret(context: LispContext): Value; } +export class BooleanExpression implements Expression { + constructor(value: string); + interpret(context: LispContext): boolean; +} + export class NumberExpression implements Expression { constructor(value: number | string); interpret(context: LispContext): number; diff --git a/test/cases.js b/test/cases.js index b7ee02d..69958ca 100644 --- a/test/cases.js +++ b/test/cases.js @@ -46,3 +46,17 @@ test('Evaluate cdr (tail of list)', () => { const expected = [3, 1]; assert.deepStrictEqual(result, expected, 'cdr (tail of list) failed'); }); + +test('Evaluate equality with t', () => { + const program = '(eq (eq 3 3) t)'; + const result = evaluate(program, {}); + const expected = true; + assert.strictEqual(result, expected, 'Equality with t failed'); +}); + +test('Evaluate equality with nil', () => { + const program = '(eq (eq 3 4) nil)'; + const result = evaluate(program, {}); + const expected = true; + assert.strictEqual(result, expected, 'Equality with nil failed'); +});