Skip to content

Commit 67343b1

Browse files
committed
Implement SRFI 227 natively.
1 parent da0df0a commit 67343b1

File tree

12 files changed

+328
-84
lines changed

12 files changed

+328
-84
lines changed

LispKit.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@
433433
CCBDF72C2749B0DA00635B5C /* 229.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCBDF72A2749AFAC00635B5C /* 229.sld */; };
434434
CCBDF72E2749B23400635B5C /* SRFI-229.scm in Copy tests */ = {isa = PBXBuildFile; fileRef = CCBDF72D2749B17600635B5C /* SRFI-229.scm */; };
435435
CCBDF72F2749B24900635B5C /* SRFI-229.scm in Copy tests */ = {isa = PBXBuildFile; fileRef = CCBDF72D2749B17600635B5C /* SRFI-229.scm */; };
436+
CCBDF731274ADF7000635B5C /* 227.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCBDF730274ADA6300635B5C /* 227.sld */; };
437+
CCBDF732274ADF8B00635B5C /* 227.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCBDF730274ADA6300635B5C /* 227.sld */; };
438+
CCBDF734274AEB0800635B5C /* SRFI-227.scm in Copy tests */ = {isa = PBXBuildFile; fileRef = CCBDF733274AE78E00635B5C /* SRFI-227.scm */; };
439+
CCBDF735274AEB2900635B5C /* SRFI-227.scm in Copy tests */ = {isa = PBXBuildFile; fileRef = CCBDF733274AE78E00635B5C /* SRFI-227.scm */; };
436440
CCC072461F9AB3110063974E /* 64.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCC072451F9AA8B70063974E /* 64.sld */; };
437441
CCC072481F9C09D30063974E /* 128.sld in Copy pre-installed SRFI libraries */ = {isa = PBXBuildFile; fileRef = CCC072471F9C047D0063974E /* 128.sld */; };
438442
CCC0724A1F9C11FA0063974E /* Sysctl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC072491F9C11FA0063974E /* Sysctl.swift */; };
@@ -1014,6 +1018,7 @@
10141018
dstPath = Root/LispKit/Tests;
10151019
dstSubfolderSpec = 7;
10161020
files = (
1021+
CCBDF734274AEB0800635B5C /* SRFI-227.scm in Copy tests */,
10171022
CCBDF72E2749B23400635B5C /* SRFI-229.scm in Copy tests */,
10181023
CCD4A6F826EDFC6500BEE906 /* SRFI-217.scm in Copy tests */,
10191024
CCD4A6F226ED4CE700BEE906 /* SRFI-224.scm in Copy tests */,
@@ -1533,6 +1538,7 @@
15331538
dstPath = Root/LispKit/Libraries/srfi;
15341539
dstSubfolderSpec = 7;
15351540
files = (
1541+
CCBDF731274ADF7000635B5C /* 227.sld in Copy pre-installed SRFI libraries */,
15361542
CCBDF72B2749B0C100635B5C /* 229.sld in Copy pre-installed SRFI libraries */,
15371543
CCD4A6F726EDFC5800BEE906 /* 217.sld in Copy pre-installed SRFI libraries */,
15381544
CCD4A6F126ED4CBD00BEE906 /* 224.sld in Copy pre-installed SRFI libraries */,
@@ -1634,6 +1640,7 @@
16341640
dstPath = Root/LispKit/Libraries/srfi;
16351641
dstSubfolderSpec = 7;
16361642
files = (
1643+
CCBDF732274ADF8B00635B5C /* 227.sld in Copy pre-installed SRFI libraries */,
16371644
CCBDF72C2749B0DA00635B5C /* 229.sld in Copy pre-installed SRFI libraries */,
16381645
CCD4A6F926EDFC7200BEE906 /* 217.sld in Copy pre-installed SRFI libraries */,
16391646
CCD4A6F326ED4CF800BEE906 /* 224.sld in Copy pre-installed SRFI libraries */,
@@ -1977,6 +1984,7 @@
19771984
dstPath = Root/LispKit/Tests;
19781985
dstSubfolderSpec = 7;
19791986
files = (
1987+
CCBDF735274AEB2900635B5C /* SRFI-227.scm in Copy tests */,
19801988
CCBDF72F2749B24900635B5C /* SRFI-229.scm in Copy tests */,
19811989
CCD4A6FA26EDFC8300BEE906 /* SRFI-217.scm in Copy tests */,
19821990
CCD4A6F426ED4D0D00BEE906 /* SRFI-224.scm in Copy tests */,
@@ -2600,6 +2608,8 @@
26002608
CCBDB73A1EDB4AB3001606E2 /* Prolog.scm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Prolog.scm; sourceTree = "<group>"; };
26012609
CCBDF72A2749AFAC00635B5C /* 229.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 229.sld; sourceTree = "<group>"; };
26022610
CCBDF72D2749B17600635B5C /* SRFI-229.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = "SRFI-229.scm"; sourceTree = "<group>"; };
2611+
CCBDF730274ADA6300635B5C /* 227.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 227.sld; sourceTree = "<group>"; };
2612+
CCBDF733274AE78E00635B5C /* SRFI-227.scm */ = {isa = PBXFileReference; lastKnownFileType = text; path = "SRFI-227.scm"; sourceTree = "<group>"; };
26032613
CCC072451F9AA8B70063974E /* 64.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 64.sld; sourceTree = "<group>"; };
26042614
CCC072471F9C047D0063974E /* 128.sld */ = {isa = PBXFileReference; lastKnownFileType = text; path = 128.sld; sourceTree = "<group>"; };
26052615
CCC072491F9C11FA0063974E /* Sysctl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sysctl.swift; sourceTree = "<group>"; };
@@ -2818,6 +2828,7 @@
28182828
CC1DDB6326A9B9D30049E99D /* SRFI-222.scm */,
28192829
CC0E57C3268FAA7800693DD2 /* SRFI-223.scm */,
28202830
CCD4A6F026ED4C9200BEE906 /* SRFI-224.scm */,
2831+
CCBDF733274AE78E00635B5C /* SRFI-227.scm */,
28212832
CCBDF72D2749B17600635B5C /* SRFI-229.scm */,
28222833
);
28232834
path = Tests;
@@ -3148,6 +3159,7 @@
31483159
CC1DDB6426A9BA2F0049E99D /* 222.sld */,
31493160
CC0E57C2268FAA4200693DD2 /* 223.sld */,
31503161
CCD4A6EF26ED4C7000BEE906 /* 224.sld */,
3162+
CCBDF730274ADA6300635B5C /* 227.sld */,
31513163
CCBDF72A2749AFAC00635B5C /* 229.sld */,
31523164
);
31533165
path = srfi;

Sources/LispKit/Compiler/Compiler.swift

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,17 @@ public final class Compiler {
152152
return (arguments, next)
153153
}
154154

155-
/// Compiles the given body of a function (or expression, if this compiler is not used to
156-
/// compile a function).
157-
private func compileBody(_ expr: Expr, localDefine: Bool = false) throws {
155+
/// Compiles the given body of a function `expr` (or expression, if this compiler is not
156+
/// used to compile a function).
157+
/// When an `optionals` formal list of parameters is provided, the formals are matched
158+
/// against a list on the stack. The matching happens in the context of `optenv`. If
159+
/// `optenv` is not provided, the matching happens in the context of the environment
160+
/// constructed on top of `self.env`.
161+
/// When `localDefine` is set to true, local definitions are allowed.
162+
private func compileBody(_ expr: Expr,
163+
optionals: Expr? = nil,
164+
optenv: Env? = nil,
165+
localDefine: Bool = false) throws {
158166
if expr.isNull {
159167
self.emit(.pushVoid)
160168
self.emit(.return)
@@ -170,12 +178,31 @@ public final class Compiler {
170178
}
171179
}
172180
// Compile body
173-
if !(try compileSeq(expr, in: self.env, inTailPos: true, localDefine: localDefine)) {
174-
self.emit(.return)
181+
if let optionals = optionals {
182+
let initialLocals = self.numLocals
183+
let group = try self.compileOptionalBindings(optionals,
184+
in: self.env,
185+
optenv: optenv)
186+
let res = try self.compileSeq(expr,
187+
in: Env(group),
188+
inTailPos: true,
189+
localDefine: localDefine)
190+
if !self.finalizeBindings(group, exit: res, initialLocals: initialLocals) {
191+
self.emit(.return)
192+
}
193+
} else {
194+
if !(try compileSeq(expr, in: self.env, inTailPos: true, localDefine: localDefine)) {
195+
self.emit(.return)
196+
}
175197
}
176198
// Insert instruction to reserve local variables
177199
if self.maxLocals > self.arguments?.count ?? 0 {
178-
self.patch(.alloc(self.maxLocals - (self.arguments?.count ?? 0)), at: reserveLocalIp)
200+
let n = self.maxLocals - (self.arguments?.count ?? 0)
201+
if optionals != nil {
202+
self.patch(.allocBelow(n), at: reserveLocalIp)
203+
} else {
204+
self.patch(.alloc(n), at: reserveLocalIp)
205+
}
179206
}
180207
}
181208
// Checkpoint argument mutability
@@ -1021,6 +1048,70 @@ public final class Compiler {
10211048
return group
10221049
}
10231050

1051+
func compileOptionalBindings(_ bindingList: Expr,
1052+
in lenv: Env,
1053+
optenv: Env?) throws -> BindingGroup {
1054+
let group = BindingGroup(owner: self, parent: lenv)
1055+
let env = optenv ?? .local(group)
1056+
var bindings = bindingList
1057+
var prevIndex = -1
1058+
while case .pair(.symbol(let sym), let rest) = bindings {
1059+
self.emit(.decons)
1060+
let binding = group.allocBindingFor(sym)
1061+
guard binding.index > prevIndex else {
1062+
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
1063+
}
1064+
if binding.isValue {
1065+
self.emit(.setLocal(binding.index))
1066+
} else {
1067+
self.emit(.makeLocalVariable(binding.index))
1068+
}
1069+
prevIndex = binding.index
1070+
bindings = rest
1071+
}
1072+
while case .pair(let binding, let rest) = bindings {
1073+
guard case .pair(.symbol(let sym), .pair(let expr, .null)) = binding else {
1074+
throw RuntimeError.eval(.malformedBinding, binding, bindingList)
1075+
}
1076+
self.emit(.dup)
1077+
self.emit(.isNull)
1078+
let branchIfIp = self.emitPlaceholder()
1079+
self.emit(.decons)
1080+
let branchIp = self.emitPlaceholder()
1081+
self.patch(.branchIf(self.offsetToNext(branchIfIp)), at: branchIfIp)
1082+
try self.compile(expr, in: env, inTailPos: false)
1083+
self.patch(.branch(self.offsetToNext(branchIp)), at: branchIp)
1084+
let binding = group.allocBindingFor(sym)
1085+
guard binding.index > prevIndex else {
1086+
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
1087+
}
1088+
if binding.isValue {
1089+
self.emit(.setLocal(binding.index))
1090+
} else {
1091+
self.emit(.makeLocalVariable(binding.index))
1092+
}
1093+
prevIndex = binding.index
1094+
bindings = rest
1095+
}
1096+
switch bindings {
1097+
case .null:
1098+
self.emit(.failIfNotNull)
1099+
case .symbol(let sym):
1100+
let binding = group.allocBindingFor(sym)
1101+
guard binding.index > prevIndex else {
1102+
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
1103+
}
1104+
if binding.isValue {
1105+
self.emit(.setLocal(binding.index))
1106+
} else {
1107+
self.emit(.makeLocalVariable(binding.index))
1108+
}
1109+
default:
1110+
throw RuntimeError.eval(.malformedBindings, bindingList)
1111+
}
1112+
return group
1113+
}
1114+
10241115
/// This function should be used for finalizing the compilation of blocks with local
10251116
/// bindings. It finalizes the binding group and resets the local bindings so that the
10261117
/// garbage collector can deallocate the objects that are not used anymore.
@@ -1071,10 +1162,17 @@ public final class Compiler {
10711162
/// Compiles a closure consisting of a list of formal arguments `arglist`, a list of
10721163
/// expressions `body`, and a local environment `env`. It puts the closure on top of the
10731164
/// stack.
1165+
/// When `optionals` is set to true, optional parameters are supported.
1166+
/// When `atomic` is set to true, defaults of optional parameters are evaluated in `env`.
1167+
/// When `tagged` is set to true, it is assumed a tag is on the stack and this tag is stored
1168+
/// in the resulting closure.
1169+
/// When `continuation` is set to true, the resulting closure is represented as a continuation.
10741170
public func compileLambda(_ nameIdx: Int?,
10751171
_ arglist: Expr,
10761172
_ body: Expr,
10771173
_ env: Env,
1174+
optionals: Bool = false,
1175+
atomic: Bool = true,
10781176
tagged: Bool = false,
10791177
continuation: Bool = false) throws {
10801178
// Create closure compiler as child of the current compiler
@@ -1085,20 +1183,39 @@ public final class Compiler {
10851183
let (arguments, next) = closureCompiler.collectArguments(arglist)
10861184
switch next {
10871185
case .null:
1186+
// Handle arguments
10881187
closureCompiler.emit(.assertArgCount(arguments.count))
1188+
closureCompiler.arguments = arguments
1189+
closureCompiler.env = .local(arguments)
1190+
// Compile body
1191+
try closureCompiler.compileBody(body, localDefine: true)
10891192
case .symbol(let sym):
1193+
// Handle arguments
10901194
if arguments.count > 0 {
10911195
closureCompiler.emit(.assertMinArgCount(arguments.count))
10921196
}
10931197
closureCompiler.emit(.collectRest(arguments.count))
10941198
arguments.allocBindingFor(sym)
1199+
closureCompiler.arguments = arguments
1200+
closureCompiler.env = .local(arguments)
1201+
// Compile body
1202+
try closureCompiler.compileBody(body, localDefine: true)
1203+
case .pair(.pair(.symbol(_), _), _):
1204+
// Handle arguments
1205+
if arguments.count > 0 {
1206+
closureCompiler.emit(.assertMinArgCount(arguments.count))
1207+
}
1208+
closureCompiler.emit(.collectRest(arguments.count))
1209+
closureCompiler.arguments = arguments
1210+
closureCompiler.env = .local(arguments)
1211+
// Compile body
1212+
try closureCompiler.compileBody(body,
1213+
optionals: next,
1214+
optenv: atomic ? env : nil,
1215+
localDefine: true)
10951216
default:
10961217
throw RuntimeError.eval(.malformedArgumentList, arglist)
10971218
}
1098-
closureCompiler.arguments = arguments
1099-
closureCompiler.env = .local(arguments)
1100-
// Compile body
1101-
try closureCompiler.compileBody(body, localDefine: true)
11021219
// Link compiled closure in the current compiler
11031220
let codeIndex = self.fragments.count
11041221
let code = closureCompiler.bundle()

Sources/LispKit/Compiler/EvalError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public enum EvalError: Int, Hashable {
176176
case .malformedCaseClause:
177177
return "malformed clause in case form: $0"
178178
case .listTooLong:
179-
return "list too long; expected empty list, but received $0"
179+
return "argument list too long; overhead: $0"
180180
case .duplicateBinding:
181181
return "symbol $0 bound multiple times in $1"
182182
case .notBoundInEnvironment:

Sources/LispKit/Primitives/ControlFlowLibrary.swift

Lines changed: 3 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ public final class ControlFlowLibrary: NativeLibrary {
263263
inTailPos: tail)
264264
case .pair(_, _):
265265
try compiler.compile(optlist, in: env, inTailPos: false)
266-
let group = try self.compileOptionalBindings(compiler, first, in: env, atomic: true)
266+
let group = try compiler.compileOptionalBindings(first, in: env, optenv: env)
267267
let res = try compiler.compileSeq(body,
268268
in: Env(group),
269269
inTailPos: tail)
@@ -288,7 +288,7 @@ public final class ControlFlowLibrary: NativeLibrary {
288288
inTailPos: tail)
289289
case .pair(_, _):
290290
try compiler.compile(optlist, in: env, inTailPos: false)
291-
let group = try self.compileOptionalBindings(compiler, first, in: env, atomic: false)
291+
let group = try compiler.compileOptionalBindings(first, in: env, optenv: nil)
292292
let res = try compiler.compileSeq(body,
293293
in: Env(group),
294294
inTailPos: tail)
@@ -297,72 +297,7 @@ public final class ControlFlowLibrary: NativeLibrary {
297297
throw RuntimeError.type(first, expected: [.listType])
298298
}
299299
}
300-
301-
private func compileOptionalBindings(_ compiler: Compiler,
302-
_ bindingList: Expr,
303-
in lenv: Env,
304-
atomic: Bool) throws -> BindingGroup {
305-
let group = BindingGroup(owner: compiler, parent: lenv)
306-
let env = atomic ? lenv : .local(group)
307-
var bindings = bindingList
308-
var prevIndex = -1
309-
while case .pair(.symbol(let sym), let rest) = bindings {
310-
compiler.emit(.decons)
311-
let binding = group.allocBindingFor(sym)
312-
guard binding.index > prevIndex else {
313-
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
314-
}
315-
if binding.isValue {
316-
compiler.emit(.setLocal(binding.index))
317-
} else {
318-
compiler.emit(.makeLocalVariable(binding.index))
319-
}
320-
prevIndex = binding.index
321-
bindings = rest
322-
}
323-
while case .pair(let binding, let rest) = bindings {
324-
guard case .pair(.symbol(let sym), .pair(let expr, .null)) = binding else {
325-
throw RuntimeError.eval(.malformedBinding, binding, bindingList)
326-
}
327-
compiler.emit(.dup)
328-
compiler.emit(.isNull)
329-
let branchIfIp = compiler.emitPlaceholder()
330-
compiler.emit(.decons)
331-
let branchIp = compiler.emitPlaceholder()
332-
compiler.patch(.branchIf(compiler.offsetToNext(branchIfIp)), at: branchIfIp)
333-
try compiler.compile(expr, in: env, inTailPos: false)
334-
compiler.patch(.branch(compiler.offsetToNext(branchIp)), at: branchIp)
335-
let binding = group.allocBindingFor(sym)
336-
guard binding.index > prevIndex else {
337-
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
338-
}
339-
if binding.isValue {
340-
compiler.emit(.setLocal(binding.index))
341-
} else {
342-
compiler.emit(.makeLocalVariable(binding.index))
343-
}
344-
prevIndex = binding.index
345-
bindings = rest
346-
}
347-
switch bindings {
348-
case .null:
349-
compiler.emit(.failIfNotNull)
350-
case .symbol(let sym):
351-
let binding = group.allocBindingFor(sym)
352-
guard binding.index > prevIndex else {
353-
throw RuntimeError.eval(.duplicateBinding, .symbol(sym), bindingList)
354-
}
355-
if binding.isValue {
356-
compiler.emit(.setLocal(binding.index))
357-
} else {
358-
compiler.emit(.makeLocalVariable(binding.index))
359-
}
360-
default:
361-
throw RuntimeError.eval(.malformedBindings, bindingList)
362-
}
363-
return group
364-
}
365-
300+
366301
private func compileLetKeywords(_ compiler: Compiler,
367302
expr: Expr,
368303
env: Env,

0 commit comments

Comments
 (0)