diff --git a/app.js b/app.js index 54a1fb5..3cc46a7 100644 --- a/app.js +++ b/app.js @@ -90,28 +90,115 @@ "<=": (a, b) => a <= b, }; + function* markStarted(node) { + node.started = true; + yield node; + } + + function* markDone(node, result) { + node.result = result; + node.started = false; + node.done = true; + yield node; + } + function* evaluate(x, env) { if (x instanceof String) { const res = env[x.valueOf()]; if (typeof res === "undefined") { throw Error("Variable '" + x + "' not found"); } - return env[x]; + yield* markStarted(x); + var fromEnv = env[x]; + if (!Array.isArray(fromEnv) && typeof fromEnv !== "function") { + yield* markDone(x); + } + return fromEnv; } else if (x instanceof Number) { + yield* markDone(x); return x.valueOf(); + } else if (x[0].valueOf() === "if") { + if (x.length != 4) { + throw Error("Wrong number of arguments for if: " + + (x.length - 1) + " != 3"); + } + const [_, test, conseq, alt] = x; + const exp = (yield* evaluate(test, env)) ? conseq : alt; + var last_evald = yield* evaluate(exp, env); + return last_evald; + } else if (x[0].valueOf() === "define") { + if (x.length != 3) { + throw Error("Wrong number of arguments for define: " + + (x.length - 1) + " != 2"); + } + const [_, name, exp] = x; + if (!(name instanceof String)){ + throw Error("Name of a definition is not a string: " + + typeof name); + } else if ("begin define if lambda".split(" ").includes(name)) { + throw Error("Invalid name of a definition: " + name); + } + yield* markStarted(_); + yield* markStarted(name); + env[name.valueOf()] = yield* evaluate(exp, env); + yield* markDone(name); + yield* markDone(_); } else if (x[0].valueOf() === "begin") { if (x.length < 2) { throw Error("At least one expression required in begin block."); } const [_, ...exps] = x; - return exps.map(exp => yield * evaluate(exp, env)).slice(-1)[0]; + + // because we're yield'ing map is not allowed... + // + // return exps.map(exp => evaluate(exp, env)).slice(-1)[0]; + // + var last_evald; + for (var i=0; i"; - const [func, ...args] = x.map(exp => yield * evaluate(exp, env)); + + // because we're yield'ing map is not allowed... + // + // const [func, ...args] = x.map(exp => yield * evaluate(exp, env)); + // + var evald = [] + var firstToken, lastToken; + for (var i=0; i el.id == result.id); + Vue.set(this.tokens, token_idx, result); + if (!done) { + return result; + } + + // we're done stepping: reset eval_gen to null + this.eval_gen = null; + this.processResult(result); + }, + debuggerPlay: function() { + var {value: result, done} = this.eval_gen.next(); + + // tell vue to update token list + let token_idx = this.tokens.findIndex(el => el.id == result.id); + Vue.set(this.tokens, token_idx, result); + if (!done) { + setTimeout(this.debuggerPlay, 80); + return result; + } + + // we're done stepping: reset eval_gen to null + this.eval_gen = null; + this.processResult(result); + }, + debuggerContinue: function() { + while (!done) { + var {value: result, done} = this.eval_gen.next(); + + } + + // we're done stepping: reset eval_gen to null + this.eval_gen = null; + this.processResult(result); + }, + processResult: function(result) { + // return final result + if (result instanceof Array) { + const pprint = tree => tree instanceof Array ? + "(" + tree.map(pprint).join(" ") + ")" : tree; + this.result = pprint(result.slice(0, -1)); + } else if (typeof result === "function") { + this.result = "native function: " + result.name; + } else { + this.result = result; + } + } + }, watch: { input: function(val) { this.ast = []; @@ -199,20 +348,7 @@ this.ast = parse(this.tokens.slice()); this.env = Object.create(this.global_env); - let evaluate_gen = evaluate(this.ast, this.env); - while (!done) { - var {value: result, done} = evaluate_gen.next(); - } - - if (result instanceof Array) { - const pprint = tree => tree instanceof Array ? - "(" + tree.map(pprint).join(" ") + ")" : tree; - this.result = pprint(result.slice(0, -1)); - } else if (typeof result === "function") { - this.result = "native function: " + result.name; - } else { - this.result = result; - } + this.eval_gen = evaluate(this.ast, this.env); } catch (error) { this.error = error; } @@ -221,9 +357,21 @@ }); // Example input: - vm.input = `(+ 2 2)`; + vm.input_ = `(define avg (lambda (a b) (/ (+ a b) 2) )) +(avg 6 12)`; + + vm.input_ = `(if (< 2 3) 2 3)`; + + vm.input = `(define x 4) +(define avg (lambda (a b) (/ (+ a b) 2) )) +(if (< 2 3) (avg x (avg 5 10)) 3)`; + + vm.input_ = `(define x 3) +(+ (+ 2 2) x)`; + + vm.input_ = `(+ (+ 2 2) 5)`; - vm.input_ = `(define abs (lambda (a) (if (> a 0) a (- 0 a)))) + vm.input = `(define abs (lambda (a) (if (> a 0) a (- 0 a)))) (define avg (lambda (a b) (/ (+ a b) 2) )) (define sqrt (lambda (x) (begin (define start_guess 1) diff --git a/index.html b/index.html index 94f3043..4d78982 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,8 @@ mini-scheme.js + +

mini-scheme.js

@@ -17,16 +19,20 @@

Source

)

{{error.name}}: {{error.message}}

Tokens

diff --git a/recu.js b/recu.js new file mode 100644 index 0000000..893a436 --- /dev/null +++ b/recu.js @@ -0,0 +1,16 @@ + +// this is a small function to play with and demonstrate the use of generator functions + + +function* recu(level, currval) { + if (currval < 3) { + console.log('hello: ' + currval + ' level: ' + level); + currval += 1; + final = yield* recu(level+1, currval); + } else { + console.log('returning from level: ' + level + ' with currval: ' + currval); + return currval; + } + debugger; + return final; +} \ No newline at end of file diff --git a/styles.css b/styles.css index 20ab5d8..ce79225 100644 --- a/styles.css +++ b/styles.css @@ -76,6 +76,14 @@ span.tokendone { line-height: 2.2; } +span.tokenstarted { + border: 2px darkblue solid; + background-color: #B2E6AD; + padding: 2px 5px; + border-radius: 4px; + line-height: 2.2; +} + ul.env { display: inline-block; list-style: none;