Number of argument checking and &optional parameters
authorDavid Vazquez <davazp@gmail.com>
Wed, 26 Dec 2012 17:25:42 +0000 (17:25 +0000)
committerDavid Vazquez <davazp@gmail.com>
Wed, 26 Dec 2012 17:25:42 +0000 (17:25 +0000)
lispstrack.lisp

index 1f80270..12b3a69 100644 (file)
   (defun listp (x)
     (or (consp x) (null x)))
 
+  (defun nth (n list)
+    (cond
+      ((null list) list)
+      ((zerop n) (car list))
+      (t (nth (1- n) (cdr list)))))
+
   (defun integerp (x)
     (and (numberp x) (= (floor x) x)))
 
           (ls-compile false env fenv)
           ")"))
 
-;;; Return the required args of a lambda list
-(defun lambda-list-required-argument (lambda-list)
-  (if (or (null lambda-list) (eq (car lambda-list) '&rest))
+
+(defvar *lambda-list-keywords* '(&optional &rest))
+
+(defun list-until-keyword (list)
+  (if (or (null list) (member (car list) *lambda-list-keywords*))
       nil
-      (cons (car lambda-list) (lambda-list-required-argument (cdr lambda-list)))))
+      (cons (car list) (list-until-keyword (cdr list)))))
+
+(defun lambda-list-required-arguments (lambda-list)
+  (list-until-keyword lambda-list))
+
+(defun lambda-list-optional-arguments-with-default (lambda-list)
+  (mapcar #'ensure-list (list-until-keyword (cdr (member '&optional lambda-list)))))
+
+(defun lambda-list-optional-arguments (lambda-list)
+  (mapcar #'car (lambda-list-optional-arguments-with-default lambda-list)))
 
 (defun lambda-list-rest-argument (lambda-list)
-  (second (member '&rest lambda-list)))
+  (let ((rest (list-until-keyword (cdr (member '&rest lambda-list)))))
+    (when (cdr rest)
+      (error "Bad lambda-list"))
+    (car rest)))
 
 (define-compilation lambda (lambda-list &rest body)
-  (let ((required-arguments (lambda-list-required-argument lambda-list))
+  (let ((required-arguments (lambda-list-required-arguments lambda-list))
+        (optional-arguments (lambda-list-optional-arguments lambda-list))
         (rest-argument (lambda-list-rest-argument lambda-list)))
-    (let ((new-env (extend-local-env
-                    (append (and rest-argument (list rest-argument))
-                            required-arguments)
+    (let ((n-required-arguments (length required-arguments))
+          (n-optional-arguments (length optional-arguments))
+          (new-env (extend-local-env
+                    (append (ensure-list rest-argument)
+                            required-arguments
+                            optional-arguments)
                     env)))
       (concat "(function ("
               (join (mapcar (lambda (x)
                               (lookup-variable-translation x new-env))
-                            required-arguments)
+                            (append required-arguments optional-arguments))
                     ",")
-              "){"
-              *newline*
+              "){" *newline*
+              ;; Check number of arguments
+              (if required-arguments
+                  (concat "if (arguments.length < " (integer-to-string n-required-arguments)
+                          ") throw 'too few arguments';" *newline*)
+                  "")
+              (if (not rest-argument)
+                  (concat "if (arguments.length > "
+                          (integer-to-string (+ n-required-arguments n-optional-arguments))
+                          ") throw 'too many arguments';" *newline*)
+                  "")
+              ;; Optional arguments
+              (if optional-arguments
+                  (concat "switch(arguments.length){" *newline*
+                          (let ((optional-and-defaults
+                                 (lambda-list-optional-arguments-with-default lambda-list))
+                                (cases nil)
+                                (idx 0))
+                            (progn (while (< idx n-optional-arguments)
+                                     (let ((arg (nth idx optional-and-defaults)))
+                                       (push (concat "case "
+                                                     (integer-to-string (+ idx n-required-arguments)) ":" *newline*
+                                                     (lookup-variable-translation (car arg) new-env)
+                                                     "="
+                                                     (ls-compile (cdr arg) new-env fenv)
+                                                     ";" *newline*)
+                                             cases)
+                                       (incf idx)))
+                                   (push (concat "default: break;" *newline*) cases)
+                                   (join (reverse cases) "")))
+                          "}" *newline*)
+                  "")
+              ;; &rest argument
               (if rest-argument
                   (let ((js!rest (lookup-variable-translation rest-argument new-env)))
                     (concat "var " js!rest "= " (ls-compile nil env fenv) ";" *newline*
                             "for (var i = arguments.length-1; i>="
-                            (integer-to-string (length required-arguments))
+                            (integer-to-string (+ n-required-arguments n-optional-arguments))
                             "; i--)" *newline*
                             js!rest " = "
                             "{car: arguments[i], cdr: " js!rest "};"
                             *newline*))
                   "")
+              ;; Body
               (concat (ls-compile-block (butlast body) new-env fenv)
                       "return " (ls-compile (car (last body)) new-env fenv) ";")
-              *newline*
-              "})"))))
+              *newline* "})"))))
 
 (define-compilation fsetq (var val)
   (concat (lookup-function-translation var fenv)
   `(define-compilation ,name ,args
      (ls-compile ,form env fenv)))
 
-(define-transformation progn (&rest body)
-  `((lambda () ,@body)))
+(define-compilation progn (&rest body)
+  (concat "(function(){" *newline*
+          (ls-compile-block (butlast body) env fenv)
+          "return " (ls-compile (car (last body)) env fenv) ";"
+          "})()" *newline*))
 
 (define-transformation let (bindings &rest body)
   (let ((bindings (mapcar #'ensure-list bindings)))
  (defun eval (x)
    (let ((code
           (with-compilation-unit
-              (ls-compile-toplevel x nil nil))))
+              (ls-compile-toplevel x))))
      (js-eval code)))
 
  ;; Set the initial global environment to be equal to the host global