@@ -37,6 +37,8 @@ public final class ControlFlowLibrary: NativeLibrary {
3737 self . define ( SpecialForm ( " let*-values " , self . compileLetStarValues) )
3838 self . define ( SpecialForm ( " let-optionals " , self . compileLetOptionals) )
3939 self . define ( SpecialForm ( " let*-optionals " , self . compileLetStarOptionals) )
40+ self . define ( SpecialForm ( " let-keywords " , self . compileLetKeywords) )
41+ self . define ( SpecialForm ( " let*-keywords " , self . compileLetStarKeywords) )
4042 self . define ( SpecialForm ( " let-syntax " , self . compileLetSyntax) )
4143 self . define ( SpecialForm ( " letrec-syntax " , self . compileLetRecSyntax) )
4244 self . define ( SpecialForm ( " do " , self . compileDo) )
@@ -343,6 +345,129 @@ public final class ControlFlowLibrary: NativeLibrary {
343345 return group
344346 }
345347
348+ private func compileLetKeywords( _ compiler: Compiler ,
349+ expr: Expr ,
350+ env: Env ,
351+ tail: Bool ) throws -> Bool {
352+ guard case . pair( _, . pair( let optlist, . pair( let first, let body) ) ) = expr else {
353+ throw RuntimeError . argumentCount ( of: " let-keywords " , min: 2 , expr: expr)
354+ }
355+ let initialLocals = compiler. numLocals
356+ switch first {
357+ case . null:
358+ return try compiler. compileSeq ( . pair( optlist, body) ,
359+ in: env,
360+ inTailPos: tail)
361+ case . pair( _, _) :
362+ try compiler. compile ( optlist, in: env, inTailPos: false )
363+ let group = try self . compileKeywordBindings ( compiler, first, in: env, atomic: true )
364+ compiler. emit ( . pop)
365+ let res = try compiler. compileSeq ( body,
366+ in: Env ( group) ,
367+ inTailPos: tail)
368+ return compiler. finalizeBindings ( group, exit: res, initialLocals: initialLocals)
369+ default :
370+ throw RuntimeError . type ( first, expected: [ . listType] )
371+ }
372+ }
373+
374+ private func compileLetStarKeywords( _ compiler: Compiler ,
375+ expr: Expr ,
376+ env: Env ,
377+ tail: Bool ) throws -> Bool {
378+ guard case . pair( _, . pair( let optlist, . pair( let first, let body) ) ) = expr else {
379+ throw RuntimeError . argumentCount ( of: " let*-keywords " , min: 2 , expr: expr)
380+ }
381+ let initialLocals = compiler. numLocals
382+ switch first {
383+ case . null:
384+ return try compiler. compileSeq ( . pair( optlist, body) ,
385+ in: env,
386+ inTailPos: tail)
387+ case . pair( _, _) :
388+ try compiler. compile ( optlist, in: env, inTailPos: false )
389+ let group = try self . compileKeywordBindings ( compiler, first, in: env, atomic: false )
390+ compiler. emit ( . pop)
391+ let res = try compiler. compileSeq ( body,
392+ in: Env ( group) ,
393+ inTailPos: tail)
394+ return compiler. finalizeBindings ( group, exit: res, initialLocals: initialLocals)
395+ default :
396+ throw RuntimeError . type ( first, expected: [ . listType] )
397+ }
398+ }
399+
400+ private func compileKeywordBindings( _ compiler: Compiler ,
401+ _ bindingList: Expr ,
402+ in lenv: Env ,
403+ atomic: Bool ) throws -> BindingGroup {
404+ let group = BindingGroup ( owner: compiler, parent: lenv)
405+ let env = atomic ? lenv : . local( group)
406+ var bindings = bindingList
407+ var prevIndex = - 1
408+ let initialIp = compiler. emitPlaceholder ( )
409+ let backfillIp = compiler. offsetToNext ( 0 )
410+ // Backfill keyword bindings with defaults
411+ while case . pair( let binding, let rest) = bindings {
412+ guard case . pair( . symbol( let sym) , . pair( let expr, . null) ) = binding else {
413+ throw RuntimeError . eval ( . malformedBinding, binding, bindingList)
414+ }
415+ compiler. emit ( . pushUndef)
416+ let pushValueIp = compiler. emitPlaceholder ( )
417+ compiler. emit ( . eq)
418+ let alreadySetIp = compiler. emitPlaceholder ( )
419+ try compiler. compile ( expr, in: env, inTailPos: false )
420+ let binding = group. allocBindingFor ( sym)
421+ guard binding. index > prevIndex else {
422+ throw RuntimeError . eval ( . duplicateBinding, . symbol( sym) , bindingList)
423+ }
424+ compiler. emit ( binding. isValue ? . setLocal( binding. index) : . setLocalValue( binding. index) )
425+ compiler. patch ( binding. isValue ? . pushLocal( binding. index) : . pushLocalValue( binding. index) ,
426+ at: pushValueIp)
427+ compiler. patch ( . branchIfNot( compiler. offsetToNext ( alreadySetIp) ) , at: alreadySetIp)
428+ prevIndex = binding. index
429+ bindings = rest
430+ }
431+ guard bindings. isNull else {
432+ throw RuntimeError . eval ( . malformedBindings, bindingList)
433+ }
434+ let finalIp = compiler. emitPlaceholder ( )
435+ compiler. patch ( . branch( compiler. offsetToNext ( initialIp) ) , at: initialIp)
436+ // Allocate space for all the bindings
437+ bindings = bindingList
438+ while case . pair( . pair( . symbol( let sym) , _) , let rest) = bindings {
439+ let binding = group. allocBindingFor ( sym)
440+ compiler. emit ( . pushUndef)
441+ compiler. emit ( binding. isValue ? . setLocal( binding. index) : . makeLocalVariable( binding. index) )
442+ bindings = rest
443+ }
444+ // Process keyword list
445+ bindings = bindingList
446+ let loopIp = compiler. emit ( . dup)
447+ compiler. emit ( . isNull)
448+ let listEmptyIp = compiler. emitPlaceholder ( )
449+ compiler. emit ( . deconsKeyword)
450+ while case . pair( . pair( . symbol( let sym) , _) , let rest) = bindings {
451+ let binding = group. allocBindingFor ( sym)
452+ compiler. emit ( . dup)
453+ compiler. pushConstant ( . symbol( compiler. context. symbols. intern ( sym. identifier + " : " ) ) )
454+ compiler. emit ( . eq)
455+ let keywordCompIp = compiler. emitPlaceholder ( )
456+ compiler. emit ( . pop)
457+ compiler. emit ( binding. isValue ? . setLocal( binding. index) : . makeLocalVariable( binding. index) )
458+ compiler. emit ( . branch( - compiler. offsetToNext ( loopIp) ) )
459+ compiler. patch ( . branchIfNot( compiler. offsetToNext ( keywordCompIp) ) , at: keywordCompIp)
460+ bindings = rest
461+ }
462+ compiler. emit ( . raiseError( EvalError . unknownKeyword. rawValue, 2 ) )
463+ compiler. patch ( . branchIf( compiler. offsetToNext ( listEmptyIp) ) , at: listEmptyIp)
464+ // Jumop to the default backfill
465+ compiler. emit ( . branch( - compiler. offsetToNext ( backfillIp) ) )
466+ // Patch instructions jumping to the end
467+ compiler. patch ( . branch( compiler. offsetToNext ( finalIp) ) , at: finalIp)
468+ return group
469+ }
470+
346471 private func compileLetSyntax( _ compiler: Compiler ,
347472 expr: Expr ,
348473 env: Env , tail: Bool ) throws -> Bool {
0 commit comments