Skip to content

Commit 3f43701

Browse files
committed
get closures working
1 parent 94fedf1 commit 3f43701

File tree

11 files changed

+145
-32
lines changed

11 files changed

+145
-32
lines changed

Sources/slips/Closure.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// Closure.swift
3+
// Slips
4+
//
5+
// Created by Pat Nakajima on 7/24/24.
6+
//
7+
8+
public class Closure {
9+
let funcExpr: FuncExpr
10+
let environment: Scope
11+
12+
init(funcExpr: FuncExpr, environment: Scope) {
13+
self.funcExpr = funcExpr
14+
self.environment = environment
15+
}
16+
}

Sources/slips/Exprs/FuncExpr.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
public struct FuncExpr: Expr {
99
public let params: ParamsExpr
10-
public let body: [any Expr]
10+
public let body: any Expr
1111

1212
public func accept<V>(_ visitor: V, _ scope: Scope) -> V.Value where V: Visitor {
1313
visitor.visit(self, scope)

Sources/slips/Lexer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public struct Token {
1515
case int, float, string, identifier
1616

1717
// Keywords
18-
case def, `true`, `false`, `if`, `in`
18+
case def, `true`, `false`, `if`, `in`, call
1919

2020
case eof
2121
case error
@@ -89,6 +89,7 @@ public struct Lexer {
8989
case "false": make(.false)
9090
case "if": make(.if)
9191
case "in": make(.in)
92+
case "call": make(.call)
9293
default:
9394
make(.identifier)
9495
}

Sources/slips/Parser.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public struct Parser {
7575
return defExpr()
7676
}
7777

78+
if match(.call) {
79+
return callExpr()
80+
}
81+
7882
if match(.identifier) {
7983
return callOrFuncExpr()
8084
}
@@ -117,15 +121,10 @@ public struct Parser {
117121
}
118122

119123
mutating func funcExpr(parameters: [Token]) -> Expr {
120-
var exprs: [Expr] = []
121-
122-
while !check(.rightParen), !check(.eof) {
123-
exprs.append(expr())
124-
}
125-
124+
let body = expr()
126125
_ = consume(.rightParen)
127126

128-
return FuncExpr(params: ParamsExpr(names: parameters.map(\.lexeme)), body: exprs)
127+
return FuncExpr(params: ParamsExpr(names: parameters.map(\.lexeme)), body: body)
129128
}
130129

131130
mutating func addExpr() -> Expr {

Sources/slips/Scope.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
public class Scope {
9-
let parent: Scope?
9+
var parent: Scope?
1010
var locals: [String: Value] = [:]
1111

1212
init(parent: Scope? = nil) {
@@ -19,6 +19,6 @@ public class Scope {
1919
}
2020

2121
func lookup(_ name: String) -> Value {
22-
return locals[name] ?? .none
22+
return locals[name] ?? parent?.lookup(name) ?? .none
2323
}
2424
}

Sources/slips/Value.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
public enum Value: Equatable {
9-
case int(Int), string(String), bool(Bool), none, error(String), fn(FuncExpr)
9+
case int(Int), string(String), bool(Bool), none, error(String), fn(Closure)
1010

1111
public static func == (lhs: Value, rhs: Value) -> Bool {
1212
switch lhs {
@@ -32,12 +32,12 @@ public enum Value: Equatable {
3232
return false
3333
case .error:
3434
return false
35-
case let .fn(fn):
35+
case let .fn(closure):
3636
guard case let .fn(rhs) = rhs else {
3737
return false
3838
}
3939

40-
return fn.body.map { $0.accept(Formatter(), Scope()) } == rhs.body.map { $0.accept(Formatter(), Scope()) }
40+
return closure.funcExpr.body.accept(Formatter(), Scope()) == rhs.funcExpr.body.accept(Formatter(), Scope())
4141
}
4242
}
4343

Sources/slips/Visitors/Compiler.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// Compiler.swift
3+
//
4+
//
5+
// Created by Pat Nakajima on 7/24/24.
6+
//
7+
8+
public struct Compiler {
9+
let source: String
10+
11+
public init(_ source: String) {
12+
self.source = source
13+
}
14+
15+
public func run() -> Value {
16+
.none
17+
}
18+
}

Sources/slips/Visitors/Formatter.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public struct Formatter: Visitor {
2020

2121
public func visit(_ expr: LiteralExpr, _ scope: Scope) -> String {
2222
switch expr.value {
23-
case let .fn(fn):
24-
"fn: \(fn.body.map { $0.accept(self, scope) }.joined(separator: " "))"
23+
case let .fn(closure):
24+
"fn: \(closure.funcExpr.body.accept(self, scope))"
2525
case let .bool(bool):
2626
"\(bool)"
2727
case let .int(int):
@@ -48,7 +48,7 @@ public struct Formatter: Visitor {
4848
}
4949

5050
public func visit(_ expr: FuncExpr, _ scope: Scope) -> String {
51-
"(\(visit(expr.params, scope)) in \(expr.body.map { $0.accept(self, scope) }.joined(separator: " ")))"
51+
"(\(visit(expr.params, scope)) in \(expr.body.accept(self, scope)))"
5252
}
5353

5454
public func visit(_ expr: ParamsExpr, _: Scope) -> String {

Sources/slips/Visitors/Interpreter.swift

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,16 @@ public struct Interpreter: Visitor {
5151
}
5252

5353
public func visit(_ expr: CallExpr, _ scope: Scope) -> Value {
54-
if case let .fn(fn) = scope.lookup(expr.op.lexeme) {
55-
let innerScope = Scope(parent: scope)
56-
57-
for (i, argument) in expr.args.enumerated() {
58-
_ = innerScope.define(fn.params.names[i], argument.accept(self, innerScope))
59-
}
60-
61-
var lastReturn: Value = .none
62-
for expr in fn.body {
63-
lastReturn = expr.accept(self, innerScope)
54+
if expr.op.kind == .call {
55+
if case let .fn(closure) = expr.args[0].accept(self, scope) {
56+
return call(closure, args: Array(expr.args[1 ..< expr.args.count]), scope)
57+
} else {
58+
fatalError("\(expr.args[0].accept(self, scope)) is not callable")
6459
}
60+
}
6561

66-
return lastReturn
62+
if case let .fn(closure) = scope.lookup(expr.op.lexeme) {
63+
return call(closure, args: expr.args, scope)
6764
} else {
6865
fatalError("\(expr.op.lexeme) not callable")
6966
}
@@ -82,7 +79,7 @@ public struct Interpreter: Visitor {
8279
}
8380

8481
public func visit(_ expr: VarExpr, _ scope: Scope) -> Value {
85-
scope.locals[expr.name] ?? .none
82+
scope.lookup(expr.name)
8683
}
8784

8885
public func visit(_ expr: IfExpr, _ scope: Scope) -> Value {
@@ -95,8 +92,8 @@ public struct Interpreter: Visitor {
9592
}
9693
}
9794

98-
public func visit(_ expr: FuncExpr, _: Scope) -> Value {
99-
.fn(expr)
95+
public func visit(_ expr: FuncExpr, _ scope: Scope) -> Value {
96+
.fn(Closure(funcExpr: expr, environment: scope))
10097
}
10198

10299
public func visit(_: ParamsExpr, _: Scope) -> Value {
@@ -105,6 +102,20 @@ public struct Interpreter: Visitor {
105102

106103
private
107104

105+
func call(_ closure: Closure, args: [any Expr], _ scope: Scope) -> Value {
106+
let innerScope = Scope(parent: scope)
107+
108+
for (name, value) in closure.environment.locals {
109+
_ = innerScope.define(name, value)
110+
}
111+
112+
for (i, argument) in args.enumerated() {
113+
_ = innerScope.define(closure.funcExpr.params.names[i], argument.accept(self, innerScope))
114+
}
115+
116+
return closure.funcExpr.body.accept(self, innerScope)
117+
}
118+
108119
func runtimeError(_ err: String) {
109120
fatalError(err)
110121
}

Tests/SlipsTests/CompilerTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// CompilerTests.swift
3+
//
4+
//
5+
// Created by Pat Nakajima on 7/22/24.
6+
//
7+
8+
import Slips
9+
import Testing
10+
11+
struct CompilerTests {
12+
@Test("Compiles literals") func literals() {
13+
#expect(Compiler("1").run() == .int(1))
14+
#expect(Compiler("(2)").run() == .int(2))
15+
}
16+
17+
@Test("Compiles strings") func strings() {
18+
#expect(Compiler("'sup'").run() == .string("sup"))
19+
#expect(Compiler("('sup')").run() == .string("sup"))
20+
}
21+
22+
@Test("Compiles add") func add() {
23+
#expect(Compiler("(+ 1 2)").run() == .int(3))
24+
#expect(Compiler("(+ 'fizz' 'buzz')").run() == .string("fizzbuzz"))
25+
}
26+
27+
@Test("Compiles multiple") func multiple() {
28+
#expect(Compiler("""
29+
(def a 1)
30+
(def b 2)
31+
(+ a b)
32+
""").run() == .int(3))
33+
}
34+
35+
@Test("Compiles if") func ifEval() {
36+
#expect(Compiler("""
37+
(if true (def a 1) (def b 2))
38+
a
39+
""").run() == .int(1))
40+
}
41+
42+
@Test("Compiles functions") func functions() {
43+
#expect(Compiler("""
44+
(def addtwo (x in (+ x 2)))
45+
(addtwo 2)
46+
""").run() == .int(4))
47+
}
48+
}

0 commit comments

Comments
 (0)