Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 170 additions & 22 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<exps.length; i++) {
last_evald = yield* evaluate(exps[i], env);
}
return last_evald
} else if (x[0].valueOf() === "lambda") {
if (x.length != 3) {
throw Error("Wrong number of arguments for lambda: " +
(x.length - 1) + " != 2");
}
const [_, arg_names, body] = x;
if (!(arg_names instanceof Array)) {
throw Error("Function arguments must be a list");
}
// Do nothing for now, except store the current environment
// together with the function definition.
yield* markStarted(x[0]);
for (var i=0; i<arg_names.length; i++) {
yield* markDone(arg_names[i]);
}
yield* markDone(x[0]);
return ["lambda", arg_names, body, env];
} else {
// Function call (no special form)
const func_name = typeof x[0] === "string" ? x[0] : "<anon>";
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<x.length; i++) {
let oneeval = yield* evaluate(x[i], env);
evald.push(oneeval);
firstToken = x[0];
lastToken = x[x.length-1];
}
const [func, ...args] = evald;

if (typeof func === "function") {
// Native JavaScript function call
return func(...args);
let func_result = func(...args);
yield* markDone(firstToken, func_result);

return func_result;
} else if (func instanceof Array) {
// MiniScheme function call
const [_, arg_names, body, definition_env] = func;
Expand All @@ -130,7 +217,15 @@
}, Object.create(definition_env));

// Evaluate the function body with the newly created environment
return evaluate(body, call_env);
var last_evald = yield* evaluate(body, call_env);
yield* markDone(firstToken);
return last_evald;
} else {
if (typeof func === "undefined") {
throw Error("No function supplied.");
} else {
throw Error("Invalid function: " + func_name);
}
}
}
}
Expand Down Expand Up @@ -181,14 +276,68 @@
global_env: global_env,
result: undefined,
error: false,
debug: true
debug: true,
eval_gen: undefined,
},
computed: {
parenBalance: function() {
return this.input.split("(").length -
this.input.split(")").length;
}
},
methods: {
debuggerStep: 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) {
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 = [];
Expand All @@ -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;
}
Expand All @@ -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)
Expand Down
16 changes: 11 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<meta charset="utf-8">
<title>mini-scheme.js</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.logrocket.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('cf4wag/mini-scheme');</script>
</head>
<body>
<h1>mini-scheme.js</h1>
Expand All @@ -17,16 +19,20 @@ <h4>Source</h4>
<code class="chrome">)</code>
<p v-if="error" class="error"><strong>{{error.name}}: </strong>{{error.message}}</p>
<template v-else>
<h4>Step</h4>
<p class="code"><button>Step</button></p>
<h4>Result</h4>
<p class="code">{{result}}</p>
<h4 v-if="eval_gen">Debugger</h4>
<p v-if="eval_gen" class="code">
<button v-on:click="debuggerStep">Step</button>
<button v-on:click="debuggerContinue">Continue</button>
<button v-on:click="debuggerPlay">Play</button>
</p>
<h4 v-if="!eval_gen">Result</h4>
<p v-if="!eval_gen" class="code">{{result}}</p>
</template>
<div class="tokens">
<h4>Tokens</h4>
<p>
<template v-for="token in tokens">
<span v-bind:id="token.id" class="token tooltip" v-bind:class="{tokendone: token.doing}" :key="token.valueOf()">
<span v-bind:id="token.id" class="token tooltip" v-bind:class="{tokendone: token.done, tokenstarted: token.started}" :key="token.valueOf()">
{{token.valueOf()}}<span class="tooltiptext">Line: {{token.line}}:{{token.col}}</span></span>&nbsp;
</template>
</p>
Expand Down
16 changes: 16 additions & 0 deletions recu.js
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down