From f6ffdd6ae6779698b1a84ab902d2771e9752ad15 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 1 Apr 2025 22:31:42 +0200 Subject: [PATCH] mondo: move +- to ops + add "raw" to parsed pairs + implement match + if + add more sicp examples --- packages/mondo/mondo.mjs | 57 +++++++++++++++++++++++++++--- packages/mondo/test/mondo.test.mjs | 43 ++++++++++++++++++---- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/packages/mondo/mondo.mjs b/packages/mondo/mondo.mjs index 2553f74fb..cdbaf964a 100644 --- a/packages/mondo/mondo.mjs +++ b/packages/mondo/mondo.mjs @@ -19,12 +19,12 @@ export class MondoParser { open_curly: /^\{/, close_curly: /^\}/, number: /^-?[0-9]*\.?[0-9]+/, // before pipe! - op: /^[*/:!@%?]|^\.{2}/, // * / : ! @ % ? .. + op: /^[*/:!@%?+-]|^\.{2}/, // * / : ! @ % ? .. // dollar: /^\$/, pipe: /^\./, stack: /^[,$]/, or: /^[|]/, - plain: /^[a-zA-Z0-9-~_^#+-]+/, + plain: /^[a-zA-Z0-9-~_^#]+/, }; // matches next token next_token(code, offset = 0) { @@ -230,7 +230,10 @@ export class MondoParser { const end = this.tokens[0].loc?.[1]; this.consume(close_type); const node = { type: 'list', children }; - begin !== undefined && (node.loc = [begin, end]); + if (begin !== undefined) { + node.loc = [begin, end]; + node.raw = this.code.slice(begin, end); + } return node; } desugar(children, type) { @@ -338,6 +341,43 @@ export class MondoRunner { scope[name] = body; // def with fall through } + evaluate_match(ast, scope) { + // (match (p1 e1) (p2 e2) ... (pn en)) + // = cond in lisp + if (ast.children.length < 2) { + return; + } + const [_, ...body] = ast.children; + for (let i = 0; i < body.length; ++i) { + const [predicate, exp] = body[i].children; + if (predicate.value === 'else') { + return this.evaluate(exp, scope); + } + const outcome = this.evaluate(predicate, scope); + if (outcome) { + return this.evaluate(exp, scope); + } + } + return undefined; // nothing was matched + } + evaluate_if(ast, scope) { + // if is a special case of match + if (ast.children.length !== 4) { + return; + } + // (if predicate consequent alternative) + const [_, predicate, consequent, alternative] = ast.children; + // (match (predicate consequent) (else alternative)) + const matcher = { + type: 'list', + children: [ + { type: 'plain', value: 'match' }, + { type: 'list', children: [predicate, consequent] }, + { type: 'list', children: [{ type: 'plain', value: 'else' }, alternative] }, + ], + }; + return this.evaluate_match(matcher, scope); + } evaluate_lambda(ast, scope) { // (fn (_) (ply 2 _) // ^args ^ body @@ -369,10 +409,17 @@ export class MondoRunner { if (ast.type !== 'list') { return this.evaluate_leaf(ast, scope); } - if (ast.children[0]?.value === 'fn') { + const name = ast.children[0]?.value; + if (name === 'fn') { return this.evaluate_lambda(ast, scope); } - if (ast.children[0]?.value === 'def') { + if (name === 'match') { + return this.evaluate_match(ast, scope); + } + if (name === 'if') { + return this.evaluate_if(ast, scope); + } + if (name === 'def') { this.evaluate_def(ast, scope); } return this.evaluate_list(ast, scope); diff --git a/packages/mondo/test/mondo.test.mjs b/packages/mondo/test/mondo.test.mjs index c9ec8aa02..15cb42218 100644 --- a/packages/mondo/test/mondo.test.mjs +++ b/packages/mondo/test/mondo.test.mjs @@ -141,6 +141,12 @@ describe('mondo arithmetic', () => { '-': multi((a, b) => a - b), '*': multi((a, b) => a * b), '/': multi((a, b) => a / b), + eq: (a, b) => a === b, + lt: (a, b) => a < b, + gt: (a, b) => a > b, + and: (a, b) => a && b, + or: (a, b) => a || b, + not: (a) => !a, run: (...args) => args[args.length - 1], def: () => 0, PI: Math.PI, @@ -204,12 +210,37 @@ describe('mondo arithmetic', () => { it('sicp 17.1', () => expect(evaluate('(square 21)', scope)).toEqual(441)); it('sicp 17.2', () => expect(evaluate('(square (+ 2 5))', scope)).toEqual(49)); it('sicp 17.3', () => expect(evaluate('(square (square 3))', scope)).toEqual(81)); - it('sicp 17.4', () => - expect(evaluate(`(def sum-of-squares (fn (x y) (+ (square x) (square y))))`, scope)).toEqual(0)); - it('sicp 17.5', () => expect(evaluate(`(sum-of-squares 3 4)`, scope)).toEqual(25)); - it('sicp 17.6', () => - expect(evaluate(`(def f (fn (a) (sum-of-squares (+ a 1) (* a 2)))) (f 5)`, scope)).toEqual(136)); - it('sicp 21.1', () => expect(evaluate(`(sum-of-squares (+ 5 1) (* 5 2))`, scope)).toEqual(136)); + it('sicp 17.4', () => expect(evaluate(`(def sumofsquares (fn (x y) (+ (square x) (square y))))`, scope)).toEqual(0)); + it('sicp 17.5', () => expect(evaluate(`(sumofsquares 3 4)`, scope)).toEqual(25)); + it('sicp 17.6', () => expect(evaluate(`(def f (fn (a) (sumofsquares (+ a 1) (* a 2)))) (f 5)`, scope)).toEqual(136)); + it('sicp 21.1', () => expect(evaluate(`(sumofsquares (+ 5 1) (* 5 2))`, scope)).toEqual(136)); + + it('sicp 22.1', () => + expect( + evaluate( + `(def abs (fn (x) + (match + ((gt x 0) x) + ((eq x 0) 0) + ((lt x 0) (- 0 x)) + )))`, // sicp was doing (- x), which doesnt work with our - + scope, + ), + ).toEqual(0)); + + it('sicp gt1', () => expect(evaluate(`(gt -12 0)`, scope)).toEqual(false)); + it('sicp gt2', () => expect(evaluate(`(gt 0 -12)`, scope)).toEqual(true)); + it('sicp lt1', () => expect(evaluate(`(lt -12 0)`, scope)).toEqual(true)); + it('sicp lt2', () => expect(evaluate(`(lt 0 -12)`, scope)).toEqual(false)); + + it('sicp 24.1', () => expect(evaluate(`(abs (- 3))`, scope)).toEqual(3)); + it('sicp 24.2', () => expect(evaluate(`(abs (+ 3))`, scope)).toEqual(3)); + it('sicp 24.3', () => expect(evaluate(`(abs -12)`, scope)).toEqual(12)); + + it('sicp 24.4', () => expect(evaluate(`(def abs (fn (x) (if (lt x 0) (- 0 x) x)))`, scope)).toEqual(0)); + it('sicp 24.5', () => expect(evaluate(`(abs -13)`, scope)).toEqual(13)); + it('sicp 25.1', () => expect(evaluate(`(and (gt 6 5) (lt 6 10))`, scope)).toEqual(true)); + it('sicp 25.2', () => expect(evaluate(`(and (gt 4 5) (lt 6 10))`, scope)).toEqual(false)); /* it('sicp 11.1', () => expect(evaluate('(* 5 size)', { size: 3 })).toEqual(15)); it('sicp 11.1', () => expect(evaluate('(def b 3) (* a b)', scope)).toEqual(12));