;;; boot.lisp --- First forms to be cross compiled ;; Copyright (C) 2012, 2013 David Vazquez ;; Copyright (C) 2012 Raimon Grau ;; JSCL is free software: you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation, either version 3 of the ;; License, or (at your option) any later version. ;; ;; JSCL is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with JSCL. If not, see . ;;; This code is executed when JSCL compiles this file itself. The ;;; compiler provides compilation of some special forms, as well as ;;; funcalls and macroexpansion, but no functions. So, we define the ;;; Lisp world from scratch. This code has to define enough language ;;; to the compiler to be able to run. (/debug "loading boot.lisp!") (eval-when-compile (let ((defmacro-macroexpander '#'(lambda (form) (destructuring-bind (name args &body body) form (let ((whole (gensym))) `(eval-when-compile (%compile-defmacro ',name '#'(lambda (,whole) (destructuring-bind ,args ,whole ,@body))))))))) (%compile-defmacro 'defmacro defmacro-macroexpander))) (defmacro declaim (&rest decls) `(eval-when-compile ,@(mapcar (lambda (decl) `(!proclaim ',decl)) decls))) (defmacro defconstant (name value &optional docstring) `(progn (declaim (special ,name)) (declaim (constant ,name)) (setq ,name ,value) ,@(when (stringp docstring) `((oset ,docstring ',name "vardoc"))) ',name)) (defconstant t 't) (defconstant nil 'nil) (%js-vset "nil" nil) (defmacro lambda (args &body body) `(function (lambda ,args ,@body))) (defmacro when (condition &body body) `(if ,condition (progn ,@body) nil)) (defmacro unless (condition &body body) `(if ,condition nil (progn ,@body))) (defmacro defvar (name &optional (value nil value-p) docstring) `(progn (declaim (special ,name)) ,@(when value-p `((unless (boundp ',name) (setq ,name ,value)))) ,@(when (stringp docstring) `((oset ,docstring ',name "vardoc"))) ',name)) (defmacro defparameter (name value &optional docstring) `(progn (setq ,name ,value) ,@(when (stringp docstring) `((oset ,docstring ',name "vardoc"))) ',name)) (defmacro defun (name args &rest body) `(progn (fset ',name #'(named-lambda ,name ,args ,@body)) ',name)) (defmacro return (&optional value) `(return-from nil ,value)) (defmacro while (condition &body body) `(block nil (%while ,condition ,@body))) (defvar *gensym-counter* 0) (defun gensym (&optional (prefix "G")) (setq *gensym-counter* (+ *gensym-counter* 1)) (make-symbol (concat prefix (integer-to-string *gensym-counter*)))) (defun boundp (x) (boundp x)) (defun fboundp (x) (fboundp x)) (defun eq (x y) (eq x y)) (defun eql (x y) (eq x y)) (defun not (x) (if x nil t)) ;; Basic macros (defmacro incf (place &optional (delta 1)) (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion place) (let ((d (gensym))) `(let* (,@(mapcar #'list dummies vals) (,d ,delta) (,(car newval) (+ ,getter ,d)) ,@(cdr newval)) ,setter)))) (defmacro decf (place &optional (delta 1)) (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion place) (let ((d (gensym))) `(let* (,@(mapcar #'list dummies vals) (,d ,delta) (,(car newval) (- ,getter ,d)) ,@(cdr newval)) ,setter)))) (defmacro push (x place) (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion place) (let ((g (gensym))) `(let* ((,g ,x) ,@(mapcar #'list dummies vals) (,(car newval) (cons ,g ,getter)) ,@(cdr newval)) ,setter)))) (defmacro pushnew (x place &rest keys &key key test test-not) (declare (ignore key test test-not)) (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion place) (let ((g (gensym)) (v (gensym))) `(let* ((,g ,x) ,@(mapcar #'list dummies vals) ,@(cdr newval) (,v ,getter)) (if (member ,g ,v ,@keys) ,v (let ((,(car newval) (cons ,g ,getter))) ,setter)))))) (defmacro dolist ((var list &optional result) &body body) (let ((g!list (gensym))) (unless (symbolp var) (error "`~S' is not a symbol." var)) `(block nil (let ((,g!list ,list) (,var nil)) (%while ,g!list (setq ,var (car ,g!list)) (tagbody ,@body) (setq ,g!list (cdr ,g!list))) ,result)))) (defmacro dotimes ((var count &optional result) &body body) (let ((g!count (gensym))) (unless (symbolp var) (error "`~S' is not a symbol." var)) `(block nil (let ((,var 0) (,g!count ,count)) (%while (< ,var ,g!count) (tagbody ,@body) (incf ,var)) ,result)))) (defmacro cond (&rest clausules) (unless (null clausules) (destructuring-bind (condition &body body) (first clausules) (cond ((eq condition t) `(progn ,@body)) ((null body) (let ((test-symbol (gensym))) `(let ((,test-symbol ,condition)) (if ,test-symbol ,test-symbol (cond ,@(rest clausules)))))) (t `(if ,condition (progn ,@body) (cond ,@(rest clausules)))))))) (defmacro case (form &rest clausules) (let ((!form (gensym))) `(let ((,!form ,form)) (cond ,@(mapcar (lambda (clausule) (destructuring-bind (keys &body body) clausule (if (or (eq keys 't) (eq keys 'otherwise)) `(t nil ,@body) (let ((keys (if (listp keys) keys (list keys)))) `((or ,@(mapcar (lambda (key) `(eql ,!form ',key)) keys)) nil ,@body))))) clausules))))) (defmacro ecase (form &rest clausules) (let ((g!form (gensym))) `(let ((,g!form ,form)) (case ,g!form ,@(append clausules `((t (error "ECASE expression failed for the object `~S'." ,g!form)))))))) (defmacro and (&rest forms) (cond ((null forms) t) ((null (cdr forms)) (car forms)) (t `(if ,(car forms) (and ,@(cdr forms)) nil)))) (defmacro or (&rest forms) (cond ((null forms) nil) ((null (cdr forms)) (car forms)) (t (let ((g (gensym))) `(let ((,g ,(car forms))) (if ,g ,g (or ,@(cdr forms)))))))) (defmacro prog1 (form &body body) (let ((value (gensym))) `(let ((,value ,form)) ,@body ,value))) (defmacro prog2 (form1 result &body body) `(prog1 (progn ,form1 ,result) ,@body)) (defmacro prog (inits &rest body ) (multiple-value-bind (forms decls docstring) (parse-body body) `(block nil (let ,inits ,@decls (tagbody ,@forms))))) ;;; Go on growing the Lisp language in Ecmalisp, with more high level ;;; utilities as well as correct versions of other constructions. (defun append-two (list1 list2) (if (null list1) list2 (cons (car list1) (append (cdr list1) list2)))) (defun append (&rest lists) (!reduce #'append-two lists nil)) (defun revappend (list1 list2) (while list1 (push (car list1) list2) (setq list1 (cdr list1))) list2) (defun reverse (list) (revappend list '())) (defmacro psetq (&rest pairs) (let (;; For each pair, we store here a list of the form ;; (VARIABLE GENSYM VALUE). (assignments '())) (while t (cond ((null pairs) (return)) ((null (cdr pairs)) (error "Odd paris in PSETQ")) (t (let ((variable (car pairs)) (value (cadr pairs))) (push `(,variable ,(gensym) ,value) assignments) (setq pairs (cddr pairs)))))) (setq assignments (reverse assignments)) ;; `(let ,(mapcar #'cdr assignments) (setq ,@(!reduce #'append (mapcar #'butlast assignments) nil))))) (defmacro do (varlist endlist &body body) `(block nil (let ,(mapcar (lambda (x) (if (symbolp x) (list x nil) (list (first x) (second x)))) varlist) (while t (when ,(car endlist) (return (progn ,@(cdr endlist)))) (tagbody ,@body) (psetq ,@(apply #'append (mapcar (lambda (v) (and (listp v) (consp (cddr v)) (list (first v) (third v)))) varlist))))))) (defmacro do* (varlist endlist &body body) `(block nil (let* ,(mapcar (lambda (x1) (if (symbolp x1) (list x1 nil) (list (first x1) (second x1)))) varlist) (while t (when ,(car endlist) (return (progn ,@(cdr endlist)))) (tagbody ,@body) (setq ,@(apply #'append (mapcar (lambda (v) (and (listp v) (consp (cddr v)) (list (first v) (third v)))) varlist))))))) (defun list-length (list) (let ((l 0)) (while (not (null list)) (incf l) (setq list (cdr list))) l)) (defun length (seq) (cond ((stringp seq) (string-length seq)) ((arrayp seq) (oget seq "length")) ((listp seq) (list-length seq)))) (defmacro with-collect (&body body) (let ((head (gensym)) (tail (gensym))) `(let* ((,head (cons 'sentinel nil)) (,tail ,head)) (flet ((collect (x) (rplacd ,tail (cons x nil)) (setq ,tail (cdr ,tail)) x)) ,@body) (cdr ,head)))) (defmacro loop (&body body) `(while t ,@body)) (defun identity (x) x) (defun complement (x) (lambda (&rest args) (not (apply x args)))) (defun constantly (x) (lambda (&rest args) x)) (defun code-char (x) (code-char x)) (defun char-code (x) (char-code x)) (defun char= (x y) (eql x y)) (defun char< (x y) (< (char-code x) (char-code y))) (defun atom (x) (not (consp x))) (defun alpha-char-p (x) (or (<= (char-code #\a) (char-code x) (char-code #\z)) (<= (char-code #\A) (char-code x) (char-code #\Z)))) (defun digit-char-p (x) (if (and (<= (char-code #\0) (char-code x) (char-code #\9))) (- (char-code x) (char-code #\0)) nil)) (defun digit-char (weight) (and (<= 0 weight 9) (char "0123456789" weight))) (defun equal (x y) (cond ((eql x y) t) ((consp x) (and (consp y) (equal (car x) (car y)) (equal (cdr x) (cdr y)))) ((stringp x) (and (stringp y) (string= x y))) (t nil))) (defun fdefinition (x) (cond ((functionp x) x) ((symbolp x) (symbol-function x)) (t (error "Invalid function `~S'." x)))) (defun disassemble (function) (write-line (lambda-code (fdefinition function))) nil) (defmacro multiple-value-bind (variables value-from &body body) `(multiple-value-call (lambda (&optional ,@variables &rest ,(gensym)) ,@body) ,value-from)) (defmacro multiple-value-list (value-from) `(multiple-value-call #'list ,value-from)) ;;; Generalized references (SETF) (defvar *setf-expanders* nil) (defun get-setf-expansion (place) (if (symbolp place) (let ((value (gensym))) (values nil nil `(,value) `(setq ,place ,value) place)) (let ((place (!macroexpand-1 place))) (let* ((access-fn (car place)) (expander (cdr (assoc access-fn *setf-expanders*)))) (when (null expander) (error "Unknown generalized reference.")) (apply expander (cdr place)))))) (defmacro define-setf-expander (access-fn lambda-list &body body) (unless (symbolp access-fn) (error "ACCESS-FN `~S' must be a symbol." access-fn)) `(progn (push (cons ',access-fn (lambda ,lambda-list ,@body)) *setf-expanders*) ',access-fn)) (defmacro setf (&rest pairs) (cond ((null pairs) nil) ((null (cdr pairs)) (error "Odd number of arguments to setf.")) ((null (cddr pairs)) (let ((place (!macroexpand-1 (first pairs))) (value (second pairs))) (multiple-value-bind (vars vals store-vars writer-form reader-form) (get-setf-expansion place) ;; TODO: Optimize the expansion a little bit to avoid let* ;; or multiple-value-bind when unnecesary. `(let* ,(mapcar #'list vars vals) (multiple-value-bind ,store-vars ,value ,writer-form ,reader-form))))) (t `(progn ,@(do ((pairs pairs (cddr pairs)) (result '() (cons `(setf ,(car pairs) ,(cadr pairs)) result))) ((null pairs) (reverse result))))))) ;; Incorrect typecase, but used in NCONC. (defmacro typecase (x &rest clausules) (let ((value (gensym))) `(let ((,value ,x)) (cond ,@(mapcar (lambda (c) (if (eq (car c) t) `(t ,@(rest c)) `((,(ecase (car c) (integer 'integerp) (cons 'consp) (list 'listp) (sequence 'sequencep) (symbol 'symbolp) (function 'functionp) (float 'floatp) (array 'arrayp) (string 'stringp) (atom 'atom) (null 'null)) ,value) ,@(or (rest c) (list nil))))) clausules))))) (defmacro etypecase (x &rest clausules) (let ((g!x (gensym))) `(let ((,g!x ,x)) (typecase ,g!x ,@clausules (t (error "~S fell through etypecase expression." ,g!x)))))) (defun notany (fn seq) (not (some fn seq))) (defconstant internal-time-units-per-second 1000) (defun get-internal-real-time () (get-internal-real-time)) (defun get-unix-time () (truncate (/ (get-internal-real-time) 1000))) (defun get-universal-time () (+ (get-unix-time) 2208988800)) (defun values-list (list) (values-array (list-to-vector list))) (defun values (&rest args) (values-list args)) (defun error (fmt &rest args) (%throw (apply #'format nil fmt args))) (defmacro nth-value (n form) `(multiple-value-call (lambda (&rest values) (nth ,n values)) ,form))