mondo: move +- to ops

+ add "raw" to parsed pairs
+ implement match + if
+ add more sicp examples
This commit is contained in:
Felix Roos
2025-04-01 22:31:42 +02:00
parent 077aac1888
commit f6ffdd6ae6
2 changed files with 89 additions and 11 deletions
+52 -5
View File
@@ -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);
+37 -6
View File
@@ -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));