X-Git-Url: http://repo.macrolet.net/gitweb/?a=blobdiff_plain;f=ecmalisp.lisp;h=56a70c670070e4d6451c1b2eebeac7726833e750;hb=ed1428b0d61942100e810e5560259f2ec8dcc53f;hp=cd831bff6e6cae38369a7b1dc7d726a8bc29805a;hpb=204c7064cb1d792fc7aaf3d7819244bc3fc052c3;p=jscl.git diff --git a/ecmalisp.lisp b/ecmalisp.lisp index cd831bf..56a70c6 100644 --- a/ecmalisp.lisp +++ b/ecmalisp.lisp @@ -24,8 +24,6 @@ #+ecmalisp (progn - - 'defmacro (eval-when-compile (%compile-defmacro 'defmacro '(lambda (name args &rest body) @@ -40,28 +38,42 @@ ,@body))) ',name)))) + (setq nil 'nil) + (setq t 't) + + (defmacro when (condition &body body) + `(if ,condition (progn ,@body) nil)) + + (defmacro unless (condition &body body) + `(if ,condition nil (progn ,@body))) + (defmacro defvar (name value) `(progn + (unless (boundp ',name) + (setq ,name ,value)) + ',name)) + + (defmacro defparameter (name value) + `(progn (setq ,name ,value) ',name)) - (defmacro named-lambda (name args &body body) + (defmacro named-lambda (name args &rest body) (let ((x (gensym "FN"))) `(let ((,x (lambda ,args ,@body))) (oset ,x "fname" ,name) ,x))) - (defmacro defun (name args &body body) + (defmacro defun (name args &rest body) `(progn - (fset ',name (named-lambda ,(symbol-name name) ,args - (block ,name ,@body))) + (fset ',name + (named-lambda ,(symbol-name name) + ,args + (block ,name ,@body))) ',name)) (defvar *package* (new)) - (defvar nil 'nil) - (defvar t 't) - (defun null (x) (eq x nil)) @@ -87,6 +99,9 @@ (setq *gensym-counter* (+ *gensym-counter* 1)) (make-symbol (concat-two prefix (integer-to-string *gensym-counter*)))) + (defun boundp (x) + (boundp x)) + ;; Basic functions (defun = (x y) (= x y)) (defun + (x y) (+ x y)) @@ -133,12 +148,6 @@ (defmacro push (x place) `(setq ,place (cons ,x ,place))) - (defmacro when (condition &body body) - `(if ,condition (progn ,@body) nil)) - - (defmacro unless (condition &body body) - `(if ,condition nil (progn ,@body))) - (defmacro dolist (iter &body body) (let ((var (first iter)) (g!list (gensym))) @@ -416,7 +425,20 @@ (car alist)) (defun string= (s1 s2) - (equal s1 s2))) + (equal s1 s2)) + + (defun fdefinition (x) + (cond + ((functionp x) + x) + ((symbolp x) + (symbol-function x)) + (t + (error "Invalid function")))) + + (defun disassemble (function) + (write-line (lambda-code (fdefinition function))) + nil)) ;;; The compiler offers some primitives and special forms which are @@ -742,14 +764,10 @@ (defun push-to-lexenv (binding lexenv namespace) (ecase namespace - (variable - (setcar lexenv (cons binding (car lexenv)))) - (function - (setcar (cdr lexenv) (cons binding (cadr lexenv)))) - (block - (setcar (cddr lexenv) (cons binding (caddr lexenv)))) - (gotag - (setcar (cdddr lexenv) (cons binding (cadddr lexenv)))))) + (variable (setcar lexenv (cons binding (car lexenv)))) + (function (setcar (cdr lexenv) (cons binding (cadr lexenv)))) + (block (setcar (cddr lexenv) (cons binding (caddr lexenv)))) + (gotag (setcar (cdddr lexenv) (cons binding (cadddr lexenv)))))) (defun extend-lexenv (bindings lexenv namespace) (let ((env (copy-lexenv lexenv))) @@ -763,6 +781,7 @@ (block (third lexenv)) (gotag (fourth lexenv))))) +(defvar *global-environment* (make-lexenv)) (defvar *environment* (make-lexenv)) (defun clear-undeclared-global-bindings () @@ -776,11 +795,11 @@ (defun gvarname (symbol) (concat "v" (integer-to-string (incf *variable-counter*)))) -(defun translate-variable (symbol env) - (binding-translation (lookup-in-lexenv symbol env 'variable))) +(defun translate-variable (symbol) + (binding-translation (lookup-in-lexenv symbol *environment* 'variable))) -(defun extend-local-env (args env) - (let ((new (copy-lexenv env))) +(defun extend-local-env (args) + (let ((new (copy-lexenv *environment*))) (dolist (symbol args new) (let ((b (make-binding symbol 'lexical-variable (gvarname symbol) t))) (push-to-lexenv b new 'variable))))) @@ -798,33 +817,29 @@ (reverse (remove-if #'null-or-empty-p *toplevel-compilations*))) (defun %compile-defmacro (name lambda) + (toplevel-compilation (ls-compile `',name)) (push-to-lexenv (make-binding name 'macro lambda t) *environment* 'function)) (defvar *compilations* nil) -(defun ls-compile-block (sexps env) +(defun ls-compile-block (sexps) (join-trailing - (remove-if #'null-or-empty-p - (mapcar (lambda (x) (ls-compile x env)) sexps)) + (remove-if #'null-or-empty-p (mapcar #'ls-compile sexps)) (concat ";" *newline*))) (defmacro define-compilation (name args &body body) ;; Creates a new primitive `name' with parameters args and ;; @body. The body can access to the local environment through the - ;; variable ENV. - `(push (list ',name (lambda (env ,@args) (block ,name ,@body))) + ;; variable *ENVIRONMENT*. + `(push (list ',name (lambda ,args (block ,name ,@body))) *compilations*)) (define-compilation if (condition true false) - (concat "(" - (ls-compile condition env) " !== " (ls-compile nil) - " ? " - (ls-compile true env) - " : " - (ls-compile false env) + (concat "(" (ls-compile condition) " !== " (ls-compile nil) + " ? " (ls-compile true) + " : " (ls-compile false) ")")) - (defvar *lambda-list-keywords* '(&optional &rest)) (defun list-until-keyword (list) @@ -853,14 +868,12 @@ (rest-argument (lambda-list-rest-argument lambda-list))) (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))) + (*environment* (extend-local-env + (append (ensure-list rest-argument) + required-arguments + optional-arguments)))) (concat "(function (" - (join (mapcar (lambda (x) - (translate-variable x new-env)) + (join (mapcar #'translate-variable (append required-arguments optional-arguments)) ",") "){" *newline* @@ -887,44 +900,44 @@ (let ((arg (nth idx optional-and-defaults))) (push (concat "case " (integer-to-string (+ idx n-required-arguments)) ":" *newline* - (translate-variable (car arg) new-env) + (translate-variable (car arg)) "=" - (ls-compile (cadr arg) new-env) + (ls-compile (cadr arg)) ";" *newline*) cases) (incf idx))) - (push (concat "default: break;" *newline*) cases) - (join (reverse cases)))) + (push (concat "default: break;" *newline*) cases) + (join (reverse cases)))) "}" *newline*) "") ;; &rest/&body argument (if rest-argument - (let ((js!rest (translate-variable rest-argument new-env))) + (let ((js!rest (translate-variable rest-argument))) (concat "var " js!rest "= " (ls-compile nil) ";" *newline* "for (var i = arguments.length-1; i>=" (integer-to-string (+ n-required-arguments n-optional-arguments)) "; i--)" *newline* (indent js!rest " = " "{car: arguments[i], cdr: ") js!rest "};" - *newline*)) + *newline*)) "") ;; Body - (concat (ls-compile-block (butlast body) new-env) - "return " (ls-compile (car (last body)) new-env) ";")) *newline* + (concat (ls-compile-block (butlast body)) + "return " (ls-compile (car (last body))) ";")) *newline* "})")))) (define-compilation setq (var val) - (let ((b (lookup-in-lexenv var env 'variable))) + (let ((b (lookup-in-lexenv var *environment* 'variable))) (if (eq (binding-type b) 'lexical-variable) - (concat (binding-translation b) " = " (ls-compile val env)) - (ls-compile `(set ',var ,val) env)))) + (concat (binding-translation b) " = " (ls-compile val)) + (ls-compile `(set ',var ,val))))) ;;; FFI Variable accessors (define-compilation js-vref (var) var) (define-compilation js-vset (var val) - (concat "(" var " = " (ls-compile val env) ")")) + (concat "(" var " = " (ls-compile val) ")")) ;;; Literals @@ -955,18 +968,13 @@ ((integerp sexp) (integer-to-string sexp)) ((stringp sexp) (concat "\"" (escape-string sexp) "\"")) ((symbolp sexp) - #+common-lisp (or (cdr (assoc sexp *literal-symbols*)) (let ((v (genlit)) - (s (concat "{name: \"" (escape-string (symbol-name sexp)) "\"}"))) + (s #+common-lisp (concat "{name: \"" (escape-string (symbol-name sexp)) "\"}") + #+ecmalisp (ls-compile `(intern ,(symbol-name sexp))))) (push (cons sexp v) *literal-symbols*) (toplevel-compilation (concat "var " v " = " s)) - v)) - #+ecmalisp - (let ((v (genlit)) - (s (ls-compile `(intern ,(symbol-name sexp))))) - (toplevel-compilation (concat "var " v " = " s)) - v)) + v))) ((consp sexp) (let ((c (concat "{car: " (literal (car sexp) t) ", " "cdr: " (literal (cdr sexp) t) "}"))) @@ -979,23 +987,19 @@ (define-compilation quote (sexp) (literal sexp)) - (define-compilation %while (pred &rest body) (js!selfcall - "while(" (ls-compile pred env) " !== " (ls-compile nil) "){" *newline* - (indent (ls-compile-block body env)) + "while(" (ls-compile pred) " !== " (ls-compile nil) "){" *newline* + (indent (ls-compile-block body)) "}" "return " (ls-compile nil) ";" *newline*)) (define-compilation function (x) (cond ((and (listp x) (eq (car x) 'lambda)) - (ls-compile x env)) + (ls-compile x)) ((symbolp x) - (ls-compile `(symbol-function ',x)) - ;; TODO: Add lexical functions - ;;(lookup-function-translation x env) - ))) + (ls-compile `(symbol-function ',x))))) (define-compilation eval-when-compile (&rest body) (eval (cons 'progn body)) @@ -1003,31 +1007,64 @@ (defmacro define-transformation (name args form) `(define-compilation ,name ,args - (ls-compile ,form env))) + (ls-compile ,form))) (define-compilation progn (&rest body) (js!selfcall - (ls-compile-block (butlast body) env) - "return " (ls-compile (car (last body)) env) ";" *newline*)) + (ls-compile-block (butlast body)) + "return " (ls-compile (car (last body))) ";" *newline*)) + + +(defun dynamic-binding-wrapper (bindings body) + (if (null bindings) + body + (concat + "try {" *newline* + (indent + "var tmp;" *newline* + (join + (mapcar (lambda (b) + (let ((s (ls-compile `(quote ,(car b))))) + (concat "tmp = " s ".value;" *newline* + s ".value = " (cdr b) ";" *newline* + (cdr b) " = tmp;" *newline*))) + bindings)) + body) + "}" *newline* + "finally {" *newline* + (indent + (join-trailing + (mapcar (lambda (b) + (let ((s (ls-compile `(quote ,(car b))))) + (concat s ".value" " = " (cdr b)))) + bindings) + (concat ";" *newline*))) + "}" *newline*))) + (define-compilation let (bindings &rest body) (let ((bindings (mapcar #'ensure-list bindings))) (let ((variables (mapcar #'first bindings)) (values (mapcar #'second bindings))) - (let ((new-env (extend-local-env variables env))) + (let ((cvalues (mapcar #'ls-compile values)) + (*environment* (extend-local-env (remove-if #'boundp variables))) + (dynamic-bindings)) (concat "(function(" (join (mapcar (lambda (x) - (translate-variable x new-env)) + (if (boundp x) + (let ((v (gvarname x))) + (push (cons x v) dynamic-bindings) + v) + (translate-variable x))) variables) ",") "){" *newline* - (indent (ls-compile-block (butlast body) new-env) - "return " (ls-compile (car (last body)) new-env) - ";" *newline*) - "})(" (join (mapcar (lambda (x) (ls-compile x env)) - values) - ",") - ")"))))) + (let ((body + (concat (ls-compile-block (butlast body)) + "return " (ls-compile (car (last body))) + ";" *newline*))) + (indent (dynamic-binding-wrapper dynamic-bindings body))) + "})(" (join cvalues ",") ")"))))) (defvar *block-counter* 0) @@ -1037,9 +1074,8 @@ (let ((b (make-binding name 'block tr t))) (js!selfcall "try {" *newline* - (indent "return " (ls-compile `(progn ,@body) - (extend-lexenv (list b) env 'block)) - ";" *newline*) + (let ((*environment* (extend-lexenv (list b) *environment* 'block))) + (indent "return " (ls-compile `(progn ,@body)) ";" *newline*)) "}" *newline* "catch (cf){" *newline* " if (cf.type == 'block' && cf.id == " tr ")" *newline* @@ -1049,13 +1085,13 @@ "}" *newline*)))) (define-compilation return-from (name &optional value) - (let ((b (lookup-in-lexenv name env 'block))) + (let ((b (lookup-in-lexenv name *environment* 'block))) (if b (js!selfcall "throw ({" "type: 'block', " "id: " (binding-translation b) ", " - "value: " (ls-compile value env) ", " + "value: " (ls-compile value) ", " "message: 'Return from unknown block " (symbol-name name) ".'" "})") (error (concat "Unknown block `" (symbol-name name) "'."))))) @@ -1063,7 +1099,7 @@ (define-compilation catch (id &rest body) (js!selfcall - "var id = " (ls-compile id env) ";" *newline* + "var id = " (ls-compile id) ";" *newline* "try {" *newline* (indent "return " (ls-compile `(progn ,@body)) ";" *newline*) @@ -1079,8 +1115,8 @@ (js!selfcall "throw ({" "type: 'catch', " - "id: " (ls-compile id env) ", " - "value: " (ls-compile value env) ", " + "id: " (ls-compile id) ", " + "value: " (ls-compile value) ", " "message: 'Throw uncatched.'" "})")) @@ -1091,28 +1127,28 @@ (defun go-tag-p (x) (or (integerp x) (symbolp x))) -(defun declare-tagbody-tags (env tbidx body) +(defun declare-tagbody-tags (tbidx body) (let ((bindings (mapcar (lambda (label) (let ((tagidx (integer-to-string (incf *go-tag-counter*)))) (make-binding label 'gotag (list tbidx tagidx) t))) (remove-if-not #'go-tag-p body)))) - (extend-lexenv bindings env 'gotag))) + (extend-lexenv bindings *environment* 'gotag))) (define-compilation tagbody (&rest body) ;; Ignore the tagbody if it does not contain any go-tag. We do this ;; because 1) it is easy and 2) many built-in forms expand to a ;; implicit tagbody, so we save some space. (unless (some #'go-tag-p body) - (return-from tagbody (ls-compile `(progn ,@body nil) env))) + (return-from tagbody (ls-compile `(progn ,@body nil)))) ;; The translation assumes the first form in BODY is a label (unless (go-tag-p (car body)) (push (gensym "START") body)) ;; Tagbody compilation (let ((tbidx (integer-to-string *tagbody-counter*))) - (let ((env (declare-tagbody-tags env tbidx body)) + (let ((*environment* (declare-tagbody-tags tbidx body)) initag) - (let ((b (lookup-in-lexenv (first body) env 'gotag))) + (let ((b (lookup-in-lexenv (first body) *environment* 'gotag))) (setq initag (second (binding-translation b)))) (js!selfcall "var tagbody_" tbidx " = " initag ";" *newline* @@ -1125,8 +1161,8 @@ (dolist (form (cdr body) content) (concatf content (if (not (go-tag-p form)) - (indent (ls-compile form env) ";" *newline*) - (let ((b (lookup-in-lexenv form env 'gotag))) + (indent (ls-compile form) ";" *newline*) + (let ((b (lookup-in-lexenv form *environment* 'gotag))) (concat "case " (second (binding-translation b)) ":" *newline*))))) "default:" *newline* " break tbloop;" *newline* @@ -1142,7 +1178,7 @@ "return " (ls-compile nil) ";" *newline*)))) (define-compilation go (label) - (let ((b (lookup-in-lexenv label env 'gotag)) + (let ((b (lookup-in-lexenv label *environment* 'gotag)) (n (cond ((symbolp label) (symbol-name label)) ((integerp label) (integer-to-string label))))) @@ -1161,9 +1197,9 @@ (js!selfcall "var ret = " (ls-compile nil) ";" *newline* "try {" *newline* - (indent "ret = " (ls-compile form env) ";" *newline*) + (indent "ret = " (ls-compile form) ";" *newline*) "} finally {" *newline* - (indent (ls-compile-block clean-up env)) + (indent (ls-compile-block clean-up)) "}" *newline* "return ret;" *newline*)) @@ -1206,9 +1242,10 @@ ;;; Primitives (defmacro define-builtin (name args &body body) - `(define-compilation ,name ,args - (let ,(mapcar (lambda (arg) `(,arg (ls-compile ,arg env))) args) - ,@body))) + `(progn + (define-compilation ,name ,args + (let ,(mapcar (lambda (arg) `(,arg (ls-compile ,arg))) args) + ,@body)))) ;;; DECLS is a list of (JSVARNAME TYPE LISPFORM) declarations. (defmacro type-check (decls &body body) @@ -1296,10 +1333,13 @@ (concat "(" x ").name")) (define-builtin set (symbol value) - (concat "(" symbol ").value =" value)) + (concat "(" symbol ").value = " value)) (define-builtin fset (symbol value) - (concat "(" symbol ").function =" value)) + (concat "(" symbol ").function = " value)) + +(define-builtin boundp (x) + (js!bool (concat "(" x ".value !== undefined)"))) (define-builtin symbol-value (x) (js!selfcall @@ -1315,6 +1355,13 @@ "if (func === undefined) throw \"Function `\" + symbol.name + \"' is undefined.\";" *newline* "return func;" *newline*)) +(define-builtin symbol-plist (x) + (concat "((" x ").plist || " (ls-compile nil) ")")) + +(define-builtin lambda-code (x) + (concat "(" x ").toString()")) + + (define-builtin eq (x y) (js!bool (concat "(" x " === " y ")"))) (define-builtin equal (x y) (js!bool (concat "(" x " == " y ")"))) @@ -1335,11 +1382,11 @@ (define-compilation slice (string a &optional b) (js!selfcall - "var str = " (ls-compile string env) ";" *newline* - "var a = " (ls-compile a env) ";" *newline* + "var str = " (ls-compile string) ";" *newline* + "var a = " (ls-compile a) ";" *newline* "var b;" *newline* (if b - (concat "b = " (ls-compile b env) ";" *newline*) + (concat "b = " (ls-compile b) ";" *newline*) "") "return str.slice(a,b);" *newline*)) @@ -1354,26 +1401,22 @@ "string1.concat(string2)")) (define-compilation funcall (func &rest args) - (concat "(" (ls-compile func env) ")(" - (join (mapcar (lambda (x) - (ls-compile x env)) - args) + (concat "(" (ls-compile func) ")(" + (join (mapcar #'ls-compile args) ", ") ")")) (define-compilation apply (func &rest args) (if (null args) - (concat "(" (ls-compile func env) ")()") + (concat "(" (ls-compile func) ")()") (let ((args (butlast args)) (last (car (last args)))) (js!selfcall - "var f = " (ls-compile func env) ";" *newline* - "var args = [" (join (mapcar (lambda (x) - (ls-compile x env)) - args) + "var f = " (ls-compile func) ";" *newline* + "var args = [" (join (mapcar #'ls-compile args) ", ") "];" *newline* - "var tail = (" (ls-compile last env) ");" *newline* + "var tail = (" (ls-compile last) ");" *newline* "while (tail != " (ls-compile nil) "){" *newline* " args.push(tail.car);" *newline* " tail = tail.cdr;" *newline* @@ -1410,8 +1453,8 @@ (defun macro (x) (and (symbolp x) (let ((b (lookup-in-lexenv x *environment* 'function))) - (eq (binding-type b) 'macro) - b))) + (and (eq (binding-type b) 'macro) + b)))) (defun ls-macroexpand-1 (form) (let ((macro-binding (macro (car form)))) @@ -1419,52 +1462,42 @@ (apply (eval (binding-translation macro-binding)) (cdr form)) form))) -(defun compile-funcall (function args env) - (cond - ((symbolp function) - (concat (ls-compile `(quote ,function)) ".function(" - (join (mapcar (lambda (x) (ls-compile x env)) args) - ", ") - ")")) - ((and (listp function) (eq (car function) 'lambda)) - (concat "(" (ls-compile function env) ")(" - (join (mapcar (lambda (x) (ls-compile x env)) args) - ", ") - ")")) - (t - (error (concat "Invalid function designator " (symbol-name function)))))) +(defun compile-funcall (function args) + (concat (ls-compile `#',function) "(" + (join (mapcar #'ls-compile args) + ", ") + ")")) -(defun ls-compile (sexp &optional (env (make-lexenv))) +(defun ls-compile (sexp) (cond ((symbolp sexp) - (let ((b (lookup-in-lexenv sexp env 'variable))) + (let ((b (lookup-in-lexenv sexp *environment* 'variable))) (if (eq (binding-type b) 'lexical-variable) (binding-translation b) - (ls-compile `(symbol-value ',sexp) env)))) + (ls-compile `(symbol-value ',sexp))))) ((integerp sexp) (integer-to-string sexp)) ((stringp sexp) (concat "\"" (escape-string sexp) "\"")) ((listp sexp) (if (assoc (car sexp) *compilations*) (let ((comp (second (assoc (car sexp) *compilations*)))) - (apply comp env (cdr sexp))) + (apply comp (cdr sexp))) (if (macro (car sexp)) - (ls-compile (ls-macroexpand-1 sexp) env) - (compile-funcall (car sexp) (cdr sexp) env)))))) + (ls-compile (ls-macroexpand-1 sexp)) + (compile-funcall (car sexp) (cdr sexp))))))) (defun ls-compile-toplevel (sexp) - (setq *toplevel-compilations* nil) - (cond - ((and (consp sexp) (eq (car sexp) 'progn)) - (let ((subs (mapcar #'ls-compile-toplevel (cdr sexp)))) - (join (remove-if #'null-or-empty-p subs)))) - (t - (let ((code (ls-compile sexp))) - (prog1 - (concat (join-trailing (get-toplevel-compilations) (concat ";" *newline*)) - (if code - (concat code ";" *newline*) - "")) - (setq *toplevel-compilations* nil)))))) + (let ((*toplevel-compilations* nil)) + (cond + ((and (consp sexp) (eq (car sexp) 'progn)) + (let ((subs (mapcar #'ls-compile-toplevel (cdr sexp)))) + (join (remove-if #'null-or-empty-p subs)))) + (t + (let ((code (ls-compile sexp))) + (concat (join-trailing (get-toplevel-compilations) + (concat ";" *newline*)) + (if code + (concat code ";" *newline*) + ""))))))) ;;; Once we have the compiler, we define the runtime environment and @@ -1507,6 +1540,7 @@ `(oset *package* ,(symbol-name (car s)) (js-vref ,(cdr s)))) *literal-symbols*) + (setq *literal-symbols* ',*literal-symbols*) (setq *environment* ',*environment*) (setq *variable-counter* ,*variable-counter*) (setq *gensym-counter* ,*gensym-counter*) @@ -1514,7 +1548,8 @@ (eval-when-compile (toplevel-compilation - (ls-compile `(setq *literal-counter* ,*literal-counter*))))) + (ls-compile + `(setq *literal-counter* ,*literal-counter*))))) ;;; Finally, we provide a couple of functions to easily bootstrap