shithub: femtolisp

Download patch

ref: 15c8cb327d542607b6faaa90498cdef29a321110
parent: adb702cdf82a2ac6ccadd8f786234086e7c2743a
author: JeffBezanson <jeff.bezanson@gmail.com>
date: Sun Aug 2 00:06:07 EDT 2009

finishing initial implementation of keyword arguments
fixing up interpreter so it can be used for bootstrapping again
removing let/copyenv optimization because it really didn't seem to help much


--- a/femtolisp/compiler.lsp
+++ b/femtolisp/compiler.lsp
@@ -22,11 +22,11 @@
 	  setg setg.l
 	  seta seta.l setc setc.l
 	  
-	  closure argc vargc trycatch copyenv let for tapply
+	  closure argc vargc trycatch for tapply
 	  add2 sub2 neg largc lvargc
 	  loada0 loada1 loadc00 loadc01 call.l tcall.l
 	  brne brne.l cadr brnn brnn.l brn brn.l
-	  optargs brbound
+	  optargs brbound keyargs
 	  
 	  dummy_t dummy_f dummy_nil]))
     (for 0 (1- (length keys))
@@ -101,15 +101,18 @@
 	(let ((lasti (if (pair? (aref e 0))
 			 (car (aref e 0)) ()))
 	      (bc (aref e 0)))
-	  (cond ((and (eq? inst 'brf) (eq? lasti 'not)
-		      (eq? (cadr bc) 'null?))
-		 (aset! e 0 (cons (car args) (cons 'brn (cddr bc)))))
-		((and (eq? inst 'brf) (eq? lasti 'not))
-		 (aset! e 0 (cons (car args) (cons 'brt (cdr bc)))))
-		((and (eq? inst 'brf) (eq? lasti 'eq?))
-		 (aset! e 0 (cons (car args) (cons 'brne (cdr bc)))))
-		((and (eq? inst 'brf) (eq? lasti 'null?))
-		 (aset! e 0 (cons (car args) (cons 'brnn (cdr bc)))))
+	  (cond ((and
+		  (eq? inst 'brf)
+		  (cond ((and (eq? lasti 'not)
+			      (eq? (cadr bc) 'null?))
+			 (aset! e 0 (cons (car args) (cons 'brn (cddr bc)))))
+			((eq? lasti 'not)
+			 (aset! e 0 (cons (car args) (cons 'brt (cdr bc)))))
+			((eq? lasti 'eq?)
+			 (aset! e 0 (cons (car args) (cons 'brne (cdr bc)))))
+			((eq? lasti 'null?)
+			 (aset! e 0 (cons (car args) (cons 'brnn (cdr bc)))))
+			(else #f))))
 		((and (eq? inst 'brt) (eq? lasti 'null?))
 		 (aset! e 0 (cons (car args) (cons 'brn (cdr bc)))))
 		(else
@@ -182,11 +185,14 @@
 			  (io.write bcode (uint8 (aref v i)))
 			  (set! i (+ i 1)))
 			 
-			 ((loadc.l setc.l optargs)  ; 2 int32 args
+			 ((loadc.l setc.l optargs keyargs)  ; 2 int32 args
 			  (io.write bcode (int32 nxt))
 			  (set! i (+ i 1))
 			  (io.write bcode (int32 (aref v i)))
-			  (set! i (+ i 1)))
+			  (set! i (+ i 1))
+			  (if (eq? vi 'keyargs)
+			      (begin (io.write bcode (int32 (aref v i)))
+				     (set! i (+ i 1)))))
 			 
 			 (else
 			  ; other number arguments are always uint8
@@ -343,27 +349,8 @@
 	     " arguments.")))
 
 (define (compile-app g env tail? x)
-  (let ((head (car x)))
-    (if (and (pair? head)
-	     (eq? (car head) 'lambda)
-	     (list? (cadr head))
-	     (every symbol? (cadr head))
-	     (not (length> (cadr head) 255)))
-	(compile-let  g env tail? x)
-	(compile-call g env tail? x))))
+  (compile-call g env tail? x))
 
-(define (compile-let g env tail? x)
-  (let ((head (car x))
-	(args (cdr x)))
-    (unless (length= args (length (cadr head)))
-	    (error "apply: incorrect number of arguments to " head))
-    (receive (the-f dept) (compile-f- env head #t)
-      (emit g 'loadv the-f)
-      (bcode:cdepth g dept))
-    (let ((nargs (compile-arglist g env args)))
-      (emit g 'copyenv)
-      (emit g (if tail? 'tcall 'call) (+ 1 nargs)))))
-
 (define builtin->instruction
   (let ((b2i (table number? 'number?  cons 'cons
 		    fixnum? 'fixnum?  equal? 'equal?
@@ -485,9 +472,9 @@
 		     (emit g 'trycatch))
 	   (else   (compile-app g env tail? x))))))
 
-(define (compile-f env f . let?)
+(define (compile-f env f)
   (receive (ff ignore)
-	   (apply compile-f- env f let?)
+	   (compile-f- env f)
 	   ff))
 
 (define get-defined-vars
@@ -507,6 +494,13 @@
 		    (else ())))))
     (lambda (expr) (delete-duplicates (get-defined-vars- expr)))))
 
+(define (keyword-arg? x) (and (pair? x) (keyword? (car x))))
+(define (keyword->symbol k)
+  (if (keyword? k)
+      (symbol (let ((s (string k)))
+		(string.sub s 0 (string.dec s (length s)))))
+      k))
+
 (define (lambda-vars l)
   (define (check-formals l o)
     (or
@@ -517,7 +511,12 @@
 	  (and (pair? (car l))
 	       (or (every pair? (cdr l))
 		   (error "compile error: invalid argument list "
-			  o ". optional arguments must come last.")))
+			  o ". optional arguments must come after required."))
+	       (if (keyword? (caar l))
+		   (or (every keyword-arg? (cdr l))
+		       (error "compile error: invalid argument list "
+			      o ". keyword arguments must come last."))
+		   #t))
 	  (error "compile error: invalid formal argument " (car l)
 		 " in list " o))
       (check-formals (cdr l) o))
@@ -525,8 +524,8 @@
 	 (error "compile error: invalid argument list " o)
 	 (error "compile error: invalid formal argument " l " in list " o))))
   (check-formals l l)
-  (map (lambda (s) (if (pair? s) (car s) s))
-       (to-proper l)))
+  (map! (lambda (s) (if (pair? s) (keyword->symbol (car s)) s))
+	(to-proper l)))
 
 (define (emit-optional-arg-inits g env opta vars i)
   ; i is the lexical var index of the opt arg to process next
@@ -547,7 +546,7 @@
      (lambda (expr)
        (compile `(lambda () ,expr . ,*defines-processed-token*))))
 
-    (lambda (env f . let?)
+    (lambda (env f)
       ; convert lambda to one body expression and process internal defines
       (define (lambda-body e)
 	(let ((B (if (pair? (cddr e))
@@ -570,15 +569,25 @@
 		      'lambda
 		      (lastcdr f))))
 	(let* ((nargs (if (atom? args) 0 (length args)))
-	       (nreq  (- nargs (length opta))))
+	       (nreq  (- nargs (length opta)))
+	       (kwa   (filter keyword-arg? opta)))
 
 	  ; emit argument checking prologue
 	  (if (not (null? opta))
-	      (begin (emit g 'optargs nreq (if (null? atail) nargs (- nargs)))
-		     (emit-optional-arg-inits g env opta vars nreq)))
+	      (begin
+		(if (null? kwa)
+		    (emit g 'optargs nreq
+			  (if (null? atail) nargs (- nargs)))
+		    (begin
+		      (bcode:indexfor g (make-perfect-hash-table
+					 (map cons
+					      (map car kwa)
+					      (iota (length kwa)))))
+		      (emit g 'keyargs nreq (length kwa)
+			    (if (null? atail) nargs (- nargs)))))
+		(emit-optional-arg-inits g env opta vars nreq)))
 
-	  (cond ((not (null? let?))      (emit g 'let))
-		((> nargs 255)           (emit g (if (null? atail)
+	  (cond ((> nargs 255)           (emit g (if (null? atail)
 						     'largc 'lvargc)
 					       nargs))
 		((not (null? atail))     (emit g 'vargc nargs))
@@ -661,11 +670,16 @@
 		  (princ (number->string (aref code i)))
 		  (set! i (+ i 1)))
 		 
-		 ((loadc.l setc.l optargs)
+		 ((loadc.l setc.l optargs keyargs)
 		  (princ (number->string (ref-int32-LE code i)) " ")
 		  (set! i (+ i 4))
 		  (princ (number->string (ref-int32-LE code i)))
-		  (set! i (+ i 4)))
+		  (set! i (+ i 4))
+		  (if (eq? inst 'keyargs)
+		      (begin 
+			(princ " ")
+			(princ (number->string (ref-int32-LE code i)) " ")
+			(set! i (+ i 4)))))
 		 
 		 ((brbound)
 		  (princ (number->string (ref-int32-LE code i)) " ")
@@ -682,5 +696,32 @@
 		  (set! i (+ i 4)))
 		 
 		 (else #f)))))))
+
+; From SRFI 89 by Marc Feeley (http://srfi.schemers.org/srfi-89/srfi-89.html)
+; Copyright (C) Marc Feeley 2006. All Rights Reserved.
+;
+; "alist" is a list of pairs of the form "(keyword . value)"
+; The result is a perfect hash-table represented as a vector of
+; length 2*N, where N is the hash modulus.  If the keyword K is in
+; the hash-table it is at index
+;
+;   X = (* 2 ($hash-keyword K N))
+;
+; and the associated value is at index X+1.
+(define (make-perfect-hash-table alist)
+  (define ($hash-keyword key n) (mod0 (abs (hash key)) n))
+  (let loop1 ((n (length alist)))
+    (let ((v (vector.alloc (* 2 n) #f)))
+      (let loop2 ((lst alist))
+        (if (pair? lst)
+            (let ((key (caar lst)))
+              (let ((x (* 2 ($hash-keyword key n))))
+                (if (aref v x)
+                    (loop1 (+ n 1))
+                    (begin
+                      (aset! v x key)
+                      (aset! v (+ x 1) (cdar lst))
+                      (loop2 (cdr lst))))))
+            v)))))
 
 #t
--- a/femtolisp/flisp.boot
+++ b/femtolisp/flisp.boot
@@ -1,1 +1,1 @@
-(*banner* ";  _\n; |_ _ _ |_ _ |  . _ _\n; | (-||||_(_)|__|_)|_)\n;-------------------|----------------------------------------------------------\n\n" *syntax-environment* #table(assert #function("<000r1c0~]c1c2c3~L2L2L2L4;" [if raise quote assert-failed])  letrec #function("?000s1e0e0c1L1e2c3~32L1e2c4~32e5\x7f3134L1e2c6~3242;" [nconc lambda map #.car #function("9000r1e0c1L1e2~3142;" [nconc set! copy-list]) copy-list #function("6000r1^;" [])])  backquote #function("7000r1e0~41;" [bq-process])  label #function(":000r2c0~L1c1~\x7fL3L3^L2;" [lambda set!])  do #function("A000s2c0e130\x7fMe2c3~32e2e4~32e2c5~32u46;" [#function("B000vc0~c1g2c2\x7fe3c4L1e5\x81N3132e3c4L1e5i0231e3~L1g432L133L4L3L2L1e3~L1g332L3;" [letrec lambda if nconc begin copy-list]) gensym map #.car cadr #function("7000r1e0~31F680e1~41;~M;" [cddr caddr])])  when #function("<000s1c0~c1\x7fK^L4;" [if begin])  unwind-protect #function("9000r2c0e130e130u43;" [#function("@000vc0\x7fc1_\x81L3L2L1c2c3\x80c1~L1c4\x7fL1c5~L2L3L3L3\x7fL1L3L3;" [let lambda prog1 trycatch begin raise]) gensym])  dotimes #function("<000s1c0~M~\x86u43;" [#function("=000vc0`c1\x7faL3e2c3L1~L1L1e4\x813133L4;" [for - nconc lambda copy-list])])  define-macro #function("?000s1c0c1~ML2e2c3L1~NL1e4\x7f3133L3;" [set-syntax! quote nconc lambda copy-list])  receive #function("@000s2c0c1_\x7fL3e2c1L1~L1e3g23133L3;" [call-with-values lambda nconc copy-list])  unless #function("=000s1c0~^c1\x7fKL4;" [if begin])  let #function(";000s1c0^u42;" [#function("<000v\x80C6D0\x80m02\x81Mo002\x81No01530^2c0e1c2L1e3c4\x8032L1e5\x813133e3c6\x8032u43;" [#function("8000v\x806;0c0\x80~L3530~\x7fK;" [label]) nconc lambda map #function("6000r1~F650~M;~;" []) copy-list #function("6000r1~F650~\x86;^;" [])])])  cond #function(":000s0c0^u42;" [#function("7000vc0qm02~\x8041;" [#function("8000r1~?640^;c0~Mu42;" [#function(";000v~Mc0<17702~M]<6@0~N\x8750~M;c1~NK;~N\x87@0c2~Mi10\x80N31L3;c3~Mc1~NKi10\x80N31L4;" [else begin or if])] cond-clauses->if)])])  throw #function(":000r2c0c1c2c3L2~\x7fL4L2;" [raise list quote thrown-value])  time #function("8000r1c0e130u42;" [#function(">000vc0~c1L1L2L1c2\x80c3c4c5c1L1~L3c6L4L3L3;" [let time.now prog1 princ "Elapsed time: " - " seconds\n"]) gensym])  let* #function("A000s1~?6E0e0c1L1_L1e2\x7f3133L1;e0c1L1e3~31L1L1e2~NF6H0e0c4L1~NL1e2\x7f3133L1530\x7f3133e5~31L2;" [nconc lambda copy-list caar let* cadar])  case #function(";000s1c0^u42;" [#function("8000vc0m02c1e230u42;" [#function(";000r2\x7fc0\x8450c0;\x7f\x8740^;\x7fC6=0c1~e2\x7f31L3;\x7f?6=0c3~e2\x7f31L3;\x7fN\x87>0c3~e2\x7fM31L3;e4c5\x7f326=0c6~c7\x7fL2L3;c8~c7\x7fL2L3;" [else eq? quote-value eqv? every #.symbol? memq quote memv] vals->cond) #function("<000vc0~i10L2L1e1c2L1e3c4qi113232L3;" [let nconc cond map #function("8000r1i10\x80~M32~NK;" [])]) gensym])])  catch #function("8000r2c0e130u42;" [#function("@000vc0\x81c1~L1c2c3c4~L2c5c6~L2c7c8L2L3c5c9~L2\x80L3L4c:~L2c;~L2L4L3L3;" [trycatch lambda if and pair? eq car quote thrown-value cadr caddr raise]) gensym])) *whitespace* "\t\n\v\f\r \u0085  ᠎           \u2028\u2029   " /= #function("7000r2~\x7fW@;" [] /=) 1+ #function("7000r1~ay;" [] 1+) 1- #function("7000r1~az;" [] 1-) 1arg-lambda? #function("8000r1~F16T02~Mc0<16J02~NF16B02~\x86F16:02e1~\x86a42;" [lambda length=] 1arg-lambda?) <= #function("7000r2~\x7fX17602~\x7fW;" [] <=) > #function("7000r2\x7f~X;" [] >) >= #function("7000r2\x7f~X17602~\x7fW;" [] >=) Instructions #table(not 16  vargc 67  load1 49  = 39  setc.l 64  sub2 74  brne.l 85  largc 76  brnn 87  loadc.l 58  loadi8 50  < 40  nop 0  set-cdr! 32  loada 55  bound? 21  / 37  neg 75  brn.l 90  lvargc 77  brt 7  trycatch 68  null? 17  load0 48  jmp.l 8  loadv 51  seta 61  * 36  function? 26  builtin? 23  aref 43  optargs 91  vector? 24  loadt 45  brf 6  symbol? 19  cdr 30  for 71  loadc00 80  pop 2  pair? 22  cadr 86  closure 65  loadf 46  compare 41  loadv.l 52  setg.l 60  brn 89  eqv? 13  aset! 44  eq? 12  atom? 15  boolean? 18  brt.l 10  tapply 72  dummy_nil 95  loada0 78  brbound 92  list 28  dup 1  apply 33  loadc 57  loadc01 81  dummy_t 
\ No newline at end of file
+(*banner* ";  _\n; |_ _ _ |_ _ |  . _ _\n; | (-||||_(_)|__|_)|_)\n;-------------------|----------------------------------------------------------\n\n" *syntax-environment* #table(assert #function("<000r1c0|]c1c2c3|L2L2L2L4;" [if raise quote assert-failed])  letrec #function("?000s1e0e0c1L1e2c3|32L1e2c4|32e5}3134L1e2c6|3242;" [nconc lambda map #.car #function("9000r1e0c1L1e2|3142;" [nconc set! copy-list]) copy-list #function("6000r1^;" [])])  backquote #function("7000r1e0|41;" [bq-process])  label #function(":000r2c0|L1c1|}L3L3^L2;" [lambda set!])  do #function("A000s2c0qe130}Me2c3|32e2e4|32e2c5|3245;" [#function("B000r5c0|c1g2c2}e3c4L1e5\x7fN3132e3c4L1e5i0231e3|L1g432L133L4L3L2L1e3|L1g332L3;" [letrec lambda if nconc begin copy-list]) gensym map #.car cadr #function("7000r1e0|31F680e1|41;|M;" [cddr caddr])])  when #function("<000s1c0|c1}K^L4;" [if begin])  unwind-protect #function("8000r2c0qe130e13042;" [#function("@000r2c0}c1_\x7fL3L2L1c2c3~c1|L1c4}L1c5|L2L3L3L3}L1L3L3;" [let lambda prog1 trycatch begin raise]) gensym])  dotimes #function(";000s1c0q|M|\x8442;" [#function("=000r2c0`c1}aL3e2c3L1|L1L1e4\x7f3133L4;" [for - nconc lambda copy-list])])  define-macro #function("?000s1c0c1|ML2e2c3L1|NL1e4}3133L3;" [set-syntax! quote nconc lambda copy-list])  receive #function("@000s2c0c1_}L3e2c1L1|L1e3g23133L3;" [call-with-values lambda nconc copy-list])  unless #function("=000s1c0|^c1}KL4;" [if begin])  let #function(":000s1c0q^41;" [#function("<000r1~C6D0~m02\x7fMo002\x7fNo01530^2c0qe1c2L1e3c4~32L1e5\x7f3133e3c6~3242;" [#function("8000r2~6;0c0~|L3530|}K;" [label]) nconc lambda map #function("6000r1|F650|M;|;" []) copy-list #function("6000r1|F650|\x84;^;" [])])])  cond #function("9000s0c0q^41;" [#function("7000r1c0qm02|~41;" [#function("7000r1|?640^;c0q|M41;" [#function(";000r1|Mc0<17702|M]<6@0|N\x8550|M;c1|NK;|N\x85@0c2|Mi10~N31L3;c3|Mc1|NKi10~N31L4;" [else begin or if])] cond-clauses->if)])])  throw #function(":000r2c0c1c2c3L2|}L4L2;" [raise list quote thrown-value])  time #function("7000r1c0qe13041;" [#function(">000r1c0|c1L1L2L1c2~c3c4c5c1L1|L3c6L4L3L3;" [let time.now prog1 princ "Elapsed time: " - " seconds\n"]) gensym])  let* #function("A000s1|?6E0e0c1L1_L1e2}3133L1;e0c1L1e3|31L1L1e2|NF6H0e0c4L1|NL1e2}3133L1530}3133e5|31L2;" [nconc lambda copy-list caar let* cadar])  case #function(":000s1c0q^41;" [#function("7000r1c0m02c1qe23041;" [#function(";000r2}c0\x8250c0;}\x8540^;}C6=0c1|e2}31L3;}?6=0c3|e2}31L3;}N\x85>0c3|e2}M31L3;e4c5}326=0c6|c7}L2L3;c8|c7}L2L3;" [else eq? quote-value eqv? every #.symbol? memq quote memv] vals->cond) #function("<000r1c0|i10L2L1e1c2L1e3c4qi113232L3;" [let nconc cond map #function("8000r1i10~|M32|NK;" [])]) gensym])])  catch #function("7000r2c0qe13041;" [#function("@000r1c0\x7fc1|L1c2c3c4|L2c5c6|L2c7c8L2L3c5c9|L2~L3L4c:|L2c;|L2L4L3L3;" [trycatch lambda if and pair? eq car quote thrown-value cadr caddr raise]) gensym])) *whitespace* "\t\n\v\f\r \u0085  ᠎           \u2028\u2029   " /= #function("7000r2|}W@;" [] /=) 1+ #function("7000r1|aw;" [] 1+) 1- #function("7000r1|ax;" [] 1-) 1arg-lambda? #function("8000r1|F16T02|Mc0<16J02|NF16B02|\x84F16:02e1|\x84a42;" [lambda length=] 1arg-lambda?) <= #function("7000r2|}X17602|}W;" [] <=) > #function("7000r2}|X;" [] >) >= #function("7000r2}|X17602|}W;" [] >=) Instructions #table(not 16  vargc 67  load1 49  = 39  setc.l 64  sub2 72  brne.l 83  largc 74  brnn 85  loadc.l 58  loadi8 50  < 40  nop 0  set-cdr! 32  loada 55  bound? 21  / 37  neg 73  brn.l 88  lvargc 75  brt 7  trycatch 68  null? 17  load0 48  jmp.l 8  loadv 51  seta 61  keyargs 91  * 36  function? 26  builtin? 23  aref 43  optargs 89  vector? 24  loadt 45  brf 6  symbol? 19  cdr 30  for 69  loadc00 78  pop 2  pair? 22  cadr 84  closure 65  loadf 46  compare 41  loadv.l 52  setg.l 60  brn 87  eqv? 13  aset! 44  eq? 12  atom? 15  boolean? 18  brt.l 10  tapply 70  dummy_nil 94  loada0 76  brbound 90  list 28  dup 1  apply 33  loadc 57  loadc01 79  dummy_t 92  setg 59  loada1 77  tcall.l 81  jmp 5  fixnum? 25  cons 27  loadg.l 54  tcall 4  call 3  - 35  brf.l 9  + 34  dumm
\ No newline at end of file
--- a/femtolisp/flisp.c
+++ b/femtolisp/flisp.c
@@ -391,7 +391,7 @@
     GCHandleStack[N_GCHND++] = pv;
 }
 
-void fl_free_gc_handles(int n)
+void fl_free_gc_handles(uint32_t n)
 {
     assert(N_GCHND >= n);
     N_GCHND -= n;
@@ -826,11 +826,11 @@
             lerrorf(ArgError, "keyword %s requires an argument",
                     symbol_name(v));
         value_t hv = fixnum(((symbol_t*)ptr(v))->hash);
-        uint32_t x = 2*(numval(hv) % n);
+        uint32_t x = 2*(abs(numval(hv)) % n);
         if (vector_elt(kwtable, x) == v) {
             uint32_t idx = numval(vector_elt(kwtable, x+1));
             assert(idx < nkw);
-            idx += (nreq+nopt);
+            idx += nopt;
             if (args[idx] == UNBOUND) {
                 // if duplicate key, keep first value
                 args[idx] = Stack[bp+i];
@@ -995,40 +995,6 @@
         OP(OP_LVARGC)
             i = GET_INT32(ip); ip+=4;
             goto do_vargc;
-        OP(OP_LET)
-            // last arg is closure environment to use
-            nargs--;
-            Stack[SP-5] = Stack[SP-4];
-            Stack[SP-4] = nargs;
-            POPN(1);
-            Stack[SP-1] = 0;
-            curr_frame = SP;
-            NEXT_OP;
-        OP(OP_OPTARGS)
-            i = GET_INT32(ip); ip+=4;
-            n = GET_INT32(ip); ip+=4;
-            if (nargs < i)
-                lerror(ArgError, "apply: too few arguments");
-            if ((int32_t)n > 0) {
-                if (nargs > n)
-                    lerror(ArgError, "apply: too many arguments");
-            }
-            else n = -n;
-            if (n > nargs) {
-                n -= nargs;
-                SP += n;
-                Stack[SP-1] = Stack[SP-n-1];
-                Stack[SP-2] = Stack[SP-n-2];
-                Stack[SP-3] = nargs+n;
-                Stack[SP-4] = Stack[SP-n-4];
-                Stack[SP-5] = Stack[SP-n-5];
-                curr_frame = SP;
-                for(i=0; i < n; i++) {
-                    Stack[bp+nargs+i] = UNBOUND;
-                }
-                nargs += n;
-            }
-            NEXT_OP;
         OP(OP_BRBOUND)
             i = GET_INT32(ip); ip+=4;
             if (captured)
@@ -1038,7 +1004,6 @@
             if (v != UNBOUND) ip += (ptrint_t)GET_INT32(ip);
             else ip += 4;
             NEXT_OP;
-        OP(OP_NOP) NEXT_OP;
         OP(OP_DUP) SP++; Stack[SP-1] = Stack[SP-2]; NEXT_OP;
         OP(OP_POP) POPN(1); NEXT_OP;
         OP(OP_TCALL)
@@ -1716,7 +1681,6 @@
             NEXT_OP;
 
         OP(OP_CLOSURE)
-        OP(OP_COPYENV)
             // build a closure (lambda args body . env)
             if (nargs > 0 && !captured) {
                 // save temporary environment to the heap
@@ -1737,17 +1701,15 @@
             else {
                 PUSH(Stack[bp]); // env has already been captured; share
             }
-            if (ip[-1] == OP_CLOSURE) {
-                pv = alloc_words(4);
-                e = Stack[SP-2];  // closure to copy
-                assert(isfunction(e));
-                pv[0] = ((value_t*)ptr(e))[0];
-                pv[1] = ((value_t*)ptr(e))[1];
-                pv[2] = Stack[SP-1];  // env
-                pv[3] = ((value_t*)ptr(e))[3];
-                POPN(1);
-                Stack[SP-1] = tagptr(pv, TAG_FUNCTION);
-            }
+            pv = alloc_words(4);
+            e = Stack[SP-2];  // closure to copy
+            assert(isfunction(e));
+            pv[0] = ((value_t*)ptr(e))[0];
+            pv[1] = ((value_t*)ptr(e))[1];
+            pv[2] = Stack[SP-1];  // env
+            pv[3] = ((value_t*)ptr(e))[3];
+            POPN(1);
+            Stack[SP-1] = tagptr(pv, TAG_FUNCTION);
             NEXT_OP;
 
         OP(OP_TRYCATCH)
@@ -1756,6 +1718,40 @@
             Stack[SP-1] = v;
             NEXT_OP;
 
+        OP(OP_OPTARGS)
+            i = GET_INT32(ip); ip+=4;
+            n = GET_INT32(ip); ip+=4;
+            if (nargs < i)
+                lerror(ArgError, "apply: too few arguments");
+            if ((int32_t)n > 0) {
+                if (nargs > n)
+                    lerror(ArgError, "apply: too many arguments");
+            }
+            else n = -n;
+            if (n > nargs) {
+                n -= nargs;
+                SP += n;
+                Stack[SP-1] = Stack[SP-n-1];
+                Stack[SP-2] = Stack[SP-n-2];
+                Stack[SP-3] = nargs+n;
+                Stack[SP-4] = Stack[SP-n-4];
+                Stack[SP-5] = Stack[SP-n-5];
+                curr_frame = SP;
+                for(i=0; i < n; i++) {
+                    Stack[bp+nargs+i] = UNBOUND;
+                }
+                nargs += n;
+            }
+            NEXT_OP;
+        OP(OP_KEYARGS)
+            v = fn_vals(Stack[bp-1]);
+            v = vector_elt(v, 0);
+            i = GET_INT32(ip); ip+=4;
+            n = GET_INT32(ip); ip+=4;
+            s = GET_INT32(ip); ip+=4;
+            nargs = process_keys(v, i, n, abs(s)-(i+n), bp, nargs, s<0);
+            NEXT_OP;
+
 #ifndef USE_COMPUTED_GOTO
         default:
             goto dispatch;
@@ -1794,10 +1790,15 @@
             n = GET_INT32(ip); ip+=4;
             sp += (n+2);
             break;
-        case OP_LET: break;
         case OP_OPTARGS:
-            i = abs(GET_INT32(ip)); ip+=4;
+            i = GET_INT32(ip); ip+=4;
+            n = abs(GET_INT32(ip)); ip+=4;
+            sp += (n-i);
+            break;
+        case OP_KEYARGS:
+            i = GET_INT32(ip); ip+=4;
             n = GET_INT32(ip); ip+=4;
+            n = abs(GET_INT32(ip)); ip+=4;
             sp += (n-i);
             break;
         case OP_BRBOUND:
@@ -1854,7 +1855,7 @@
 
         case OP_LOADT: case OP_LOADF: case OP_LOADNIL: case OP_LOAD0:
         case OP_LOAD1: case OP_LOADA0: case OP_LOADA1: case OP_LOADC00:
-        case OP_LOADC01: case OP_COPYENV: case OP_DUP:
+        case OP_LOADC01: case OP_DUP:
             sp++;
             break;
 
--- a/femtolisp/flisp.h
+++ b/femtolisp/flisp.h
@@ -101,7 +101,7 @@
 #define iscbuiltin(x) (iscvalue(x) && (cv_class((cvalue_t*)ptr(x))==builtintype))
 
 void fl_gc_handle(value_t *pv);
-void fl_free_gc_handles(int n);
+void fl_free_gc_handles(uint32_t n);
 
 #include "opcodes.h"
 
--- a/femtolisp/mkboot0.lsp
+++ b/femtolisp/mkboot0.lsp
@@ -1,7 +1,7 @@
 ; -*- scheme -*-
 
-;(if (not (bound? 'top-level-value)) (set! top-level-value %eval))
-;(if (not (bound? 'set-top-level-value!)) (set! set-top-level-value! set))
+(if (not (bound? 'top-level-value)) (set! top-level-value %eval))
+(if (not (bound? 'set-top-level-value!)) (set! set-top-level-value! set))
 
 ;(load "compiler.lsp")
 
--- a/femtolisp/opcodes.h
+++ b/femtolisp/opcodes.h
@@ -23,11 +23,11 @@
     OP_SETG, OP_SETGL,
     OP_SETA, OP_SETAL, OP_SETC, OP_SETCL,
 
-    OP_CLOSURE, OP_ARGC, OP_VARGC, OP_TRYCATCH, OP_COPYENV, OP_LET, OP_FOR,
+    OP_CLOSURE, OP_ARGC, OP_VARGC, OP_TRYCATCH, OP_FOR,
     OP_TAPPLY, OP_ADD2, OP_SUB2, OP_NEG, OP_LARGC, OP_LVARGC,
     OP_LOADA0, OP_LOADA1, OP_LOADC00, OP_LOADC01, OP_CALLL, OP_TCALLL,
     OP_BRNE, OP_BRNEL, OP_CADR, OP_BRNN, OP_BRNNL, OP_BRN, OP_BRNL,
-    OP_OPTARGS, OP_BRBOUND,
+    OP_OPTARGS, OP_BRBOUND, OP_KEYARGS,
 
     OP_BOOL_CONST_T, OP_BOOL_CONST_F, OP_THE_EMPTY_LIST,
 
@@ -37,7 +37,7 @@
 #ifdef USE_COMPUTED_GOTO
 #define VM_LABELS                                                       \
     static void *vm_labels[] = {                                        \
-&&L_OP_NOP, &&L_OP_DUP, &&L_OP_POP, &&L_OP_CALL, &&L_OP_TCALL, &&L_OP_JMP, \
+NULL, &&L_OP_DUP, &&L_OP_POP, &&L_OP_CALL, &&L_OP_TCALL, &&L_OP_JMP, \
     &&L_OP_BRF, &&L_OP_BRT,                                             \
     &&L_OP_JMPL, &&L_OP_BRFL, &&L_OP_BRTL, &&L_OP_RET,                  \
                                                                         \
@@ -64,19 +64,18 @@
     &&L_OP_SETA, &&L_OP_SETAL, &&L_OP_SETC, &&L_OP_SETCL,               \
                                                                         \
     &&L_OP_CLOSURE, &&L_OP_ARGC, &&L_OP_VARGC, &&L_OP_TRYCATCH,         \
-    &&L_OP_COPYENV,                                                     \
-    &&L_OP_LET, &&L_OP_FOR,                                             \
+    &&L_OP_FOR,                                                         \
     &&L_OP_TAPPLY, &&L_OP_ADD2, &&L_OP_SUB2, &&L_OP_NEG, &&L_OP_LARGC,  \
     &&L_OP_LVARGC,                                                      \
     &&L_OP_LOADA0, &&L_OP_LOADA1, &&L_OP_LOADC00, &&L_OP_LOADC01,       \
     &&L_OP_CALLL, &&L_OP_TCALLL, &&L_OP_BRNE, &&L_OP_BRNEL, &&L_OP_CADR,\
     &&L_OP_BRNN, &&L_OP_BRNNL, &&L_OP_BRN, &&L_OP_BRNL,                 \
-    &&L_OP_OPTARGS, &&L_OP_BRBOUND                                      \
+    &&L_OP_OPTARGS, &&L_OP_BRBOUND, &&L_OP_KEYARGS                      \
     }
 
 #define VM_APPLY_LABELS                                                 \
     static void *vm_apply_labels[] = {                                  \
-&&L_OP_NOP, &&L_OP_DUP, &&L_OP_POP, &&L_OP_CALL, &&L_OP_TCALL, &&L_OP_JMP, \
+NULL, &&L_OP_DUP, &&L_OP_POP, &&L_OP_CALL, &&L_OP_TCALL, &&L_OP_JMP, \
     &&L_OP_BRF, &&L_OP_BRT,                                             \
     &&L_OP_JMPL, &&L_OP_BRFL, &&L_OP_BRTL, &&L_OP_RET,                  \
                                                                         \
--- a/femtolisp/unittest.lsp
+++ b/femtolisp/unittest.lsp
@@ -126,6 +126,17 @@
 (assert (equal? ((lambda ((x 0) . r) (list x r))) '(0 ())))
 (assert (equal? ((lambda ((x 0) . r) (list x r)) 1 2 3) '(1 (2 3))))
 
+; keyword arguments
+(assert (equal? ((lambda (x (a 2) (b: a) . r) (list x a b r)) 1 0 8 4 5)
+		'(1 0 0 (8 4 5))))
+(assert (equal? ((lambda (x (a 2) (b: a) . r) (list x a b r)) 0 b: 3 1)
+		'(0 2 3 (1))))
+(define (keys4 (a: 8) (b: 3) (c: 7) (d: 6)) (list a b c d))
+(assert (equal? (keys4 a: 10) '(10 3 7 6)))
+(assert (equal? (keys4 b: 10) '(8 10 7 6)))
+(assert (equal? (keys4 c: 10) '(8 3 10 6)))
+(assert (equal? (keys4 d: 10) '(8 3 7 10)))
+
 ; ok, a couple end-to-end tests as well
 (define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
 (assert (equal? (fib 20) 6765))