Skip to content

Commit cfd1f9e

Browse files
committed
lambda
1 parent 6dc38ab commit cfd1f9e

File tree

3 files changed

+75
-4
lines changed

3 files changed

+75
-4
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ A tree-walking Lisp interpreter written in [Gleam](https://gleam.run).
55
It features:
66
- [x] Ints with `+`, `-`, `*`, and `/`.
77
- [x] Bools with `not`, `and`, and `or`.
8-
- [=] Comparison with `=`.
8+
- [x] Comparison with `=`.
99
- [x] Lists with `cons`, `car`, `cdr`.
10-
- [ ] Closures with `lambda`.
10+
- [x] Closures with `lambda`.
1111
- [x] Global variables with `define`.
1212
- [x] Local variables with `let`.
1313
- [x] Flow control with `if`.

Diff for: src/glisp.gleam

+51-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub type Expression {
1111
Int(Int)
1212
Atom(String)
1313
Procedure(procedure: Procedure)
14+
Closure(arguments: List(String), body: List(Expression), scope: Scope)
1415
}
1516

1617
pub type Error {
@@ -137,6 +138,7 @@ fn new_state() -> State {
137138
#("or", Procedure(or_builtin)),
138139
#("if", Procedure(if_builtin)),
139140
#("define", Procedure(define_builtin)),
141+
#("lambda", Procedure(lambda_builtin)),
140142
])
141143
let local_scope = map.new()
142144
State(global_scope: global_scope, local_scope: local_scope)
@@ -158,7 +160,7 @@ fn evaluate(
158160

159161
fn evaluate_expression(expression: Expression, state: State) -> Evaluated {
160162
case expression {
161-
Bool(_) | Int(_) | Procedure(..) -> Ok(#(expression, state))
163+
Bool(_) | Int(_) | Procedure(_) | Closure(..) -> Ok(#(expression, state))
162164
List(expressions) -> evaluate_list(expressions, state)
163165
Atom(atom) -> {
164166
try value = evaluate_atom(atom, state)
@@ -198,7 +200,41 @@ fn call(
198200
) -> Evaluated {
199201
case callable {
200202
Procedure(procedure) -> procedure(arguments, state)
201-
_ -> type_error("procedure", callable)
203+
Closure(parameters, body, environment) ->
204+
call_closure(parameters, body, environment, arguments, state)
205+
_ -> type_error("Procedure", callable)
206+
}
207+
}
208+
209+
fn call_closure(
210+
parameters: List(String),
211+
body: List(Expression),
212+
environment: Map(String, Expression),
213+
arguments: List(Expression),
214+
state: State,
215+
) -> Evaluated {
216+
let original_locals = state.local_scope
217+
let state = set_locals(state, environment)
218+
try state = evaluate_lambda_arguments(parameters, arguments, state, 0)
219+
try #(result, state) = evaluate(body, empty, state)
220+
Ok(#(result, set_locals(state, original_locals)))
221+
}
222+
223+
fn evaluate_lambda_arguments(
224+
parameters: List(String),
225+
arguments: List(Expression),
226+
state: State,
227+
count: Int,
228+
) -> Result(State, Error) {
229+
case parameters, arguments {
230+
[], [] -> Ok(state)
231+
[parameter, ..parameters], [argument, ..arguments] -> {
232+
try #(argument, state) = evaluate_expression(argument, state)
233+
let state = insert_local(state, parameter, argument)
234+
evaluate_lambda_arguments(parameters, arguments, state, count + 1)
235+
}
236+
[], rest -> Error(IncorrectArity(count, count + list.length(rest)))
237+
rest, [] -> Error(IncorrectArity(count + list.length(rest), count))
202238
}
203239
}
204240

@@ -225,6 +261,17 @@ fn define_builtin(arguments: List(Expression), state: State) -> Evaluated {
225261
}
226262
}
227263

264+
fn lambda_builtin(arguments: List(Expression), state: State) -> Evaluated {
265+
case arguments {
266+
[parameters, ..body] -> {
267+
try parameters = expect_list(parameters)
268+
try parameters = list.try_map(parameters, expect_atom)
269+
Ok(#(Closure(parameters, body, state.local_scope), state))
270+
}
271+
_ -> Error(IncorrectArity(2, list.length(arguments)))
272+
}
273+
}
274+
228275
fn evaluate_atom(atom: String, state: State) -> Result(Expression, Error) {
229276
map.get(state.local_scope, atom)
230277
|> result.lazy_or(fn() { map.get(state.global_scope, atom) })
@@ -429,6 +476,7 @@ fn type_name(value: Expression) -> String {
429476
Bool(_) -> "Bool"
430477
List(_) -> "List"
431478
Procedure(_) -> "Procedure"
479+
Closure(..) -> "Closure"
432480
Atom(_) -> "Atom"
433481
}
434482
}
@@ -440,6 +488,7 @@ fn print(value: Expression) -> String {
440488
Bool(False) -> "false"
441489
List(xs) -> "'(" <> string.join(list.map(xs, print), " ") <> ")"
442490
Procedure(_) -> "#<procedure>"
491+
Closure(..) -> "#<closure>"
443492
Atom(x) -> "'" <> x
444493
}
445494
}

Diff for: test/glisp_test.gleam

+22
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,25 @@ pub fn if_true_test() {
198198
pub fn if_false_test() {
199199
assert Ok("4") = glisp.eval("(if (= 1 2) (+ 1 1) (+ 2 2))")
200200
}
201+
202+
pub fn closure_test() {
203+
assert Ok("1") =
204+
glisp.eval(
205+
"
206+
(define id (lambda (x) x))
207+
(id 1)
208+
",
209+
)
210+
}
211+
212+
pub fn closure_closes_over_scope_at_time_of_definition_test() {
213+
assert Ok("1") =
214+
glisp.eval(
215+
"
216+
(define fn (let ((x 1)
217+
(return-x (lambda () x)))
218+
return-x))
219+
(let ((x 2)) (fn))
220+
",
221+
)
222+
}

0 commit comments

Comments
 (0)