Skip to content

Commit 9be1508

Browse files
committed
Let there be let
Introduced the (let[rec] ((name expr) ...) body) forms, which evaluate body in an environment where all the name(s) ... are bound to the result of evaluating the corresponding expr(s) ..., either in the surrounding environment (let) or in the body's environment as it's being built (letrec).
1 parent e944916 commit 9be1508

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

tinylisp.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
};
3939
};
4040

41+
var let = function(input, ctx, rec) {
42+
var letCtx = new Ctx({}, ctx);
43+
input[1].forEach(function(binding) {
44+
var name = binding[0].value;
45+
var init = binding[1];
46+
letCtx.scope[name] = interpret(init, rec ? letCtx : ctx);
47+
});
48+
return interpret(input[2], letCtx);
49+
}
50+
4151
var fn = function(input, ctx) {
4252
return {
4353
type: "function",
@@ -51,9 +61,11 @@
5161
if (ctx === undefined) {
5262
return interpret(input, new Ctx(library));
5363
} else if (input instanceof Array) {
54-
if (input[0].value === "lambda") {
55-
return lambda(input, ctx);
56-
} else {
64+
switch (input[0].value) {
65+
case "lambda": return lambda(input, ctx);
66+
case "letrec": return let(input, ctx, true);
67+
case "let": return let(input, ctx, false);
68+
default:
5769
var list = input.map(function(x) { return interpret(x, ctx); });
5870
if (list[0].type === "function") {
5971
return list[0].value(list.slice(1));

tinylisp.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,35 @@ describe('tinyLisp', function() {
127127
.toEqual(1);
128128
});
129129
});
130+
131+
describe('let', function() {
132+
it('should eval inner expression w names bound', function() {
133+
expect(t.interpret(t.parse("(let ((x 1) (y 2)) (x y))"))).toEqual([1, 2]);
134+
});
135+
136+
it('should not expose parallel bindings to each other', function() {
137+
// Expecting undefined for y to be consistent with normal
138+
// identifier resolution in tinyLisp.
139+
expect(t.interpret(t.parse("(let ((x 1) (y x)) (x y))"))).toEqual([1, undefined]);
140+
});
141+
142+
it('should accept empty binding list', function() {
143+
expect(t.interpret(t.parse("(let () 42)"))).toEqual(42);
144+
});
145+
});
146+
147+
describe('letrec', function() {
148+
it('should expose previous bindings to later ones', function() {
149+
expect(t.interpret(t.parse("(letrec ((x 42) (y x)) y)"))).toEqual(42);
150+
});
151+
152+
it('should not expose later bindings to previous ones', function() {
153+
expect(t.interpret(t.parse("(letrec ((x y) (y 42)) x)"))).toEqual(undefined);
154+
});
155+
156+
it('should accept empty binding list', function() {
157+
expect(t.interpret(t.parse("(letrec () 42)"))).toEqual(42);
158+
});
159+
});
130160
});
131161
});

0 commit comments

Comments
 (0)