Login
7 branches 0 tags
Ben (X13/Arch) Fixed some bugs 7e9a151 1 year ago 1236 Commits
nujel / stdlib / compiler / backend_bytecode / compiler.nuj
;;; Nujel - Copyright (C) 2020-2021 - Benjamin Vincent Schulenburg
;;; This project uses the MIT license, a copy should be included under /LICENSE
;;;
;;; Contains the Bytecode compiler that takes macroexpanded/lowered  Nujel code
;;; and emits bytecode assembly

(def bytecompile* (let*
                    (defn bytecompile/literal (source)
                          (case (:type-name source)
                                (:int      ($push/int source))
                                (:nil      ($push/nil))
                                (:bool     (if source
                                               ($push/true)
                                               ($push/false)))
                                (:symbol   (list ($get/val source)))
                                ((:native-function :lambda) (bytecompile/literal (closure/name source)))
                                (otherwise ($push/val source))))

                    (defn bytecompile/quote (source)
                          (case (:type-name source)
                                (:nil      ($push/nil))
                                (:int      ($push/int source))
                                (otherwise ($push/val source))))

                    (defn bytecompile/do/form (source env)
                          (when source (cons (cons (bytecompile* (car source) env)
                                                   (if (last? source) #nil
                                                       (cons ($drop) #nil)))
                                             (bytecompile/do/form (cdr source) env))))

                    (defn bytecompile/do (source env)
                          (list (bytecompile/do/form (cdr source) env)))

                    (defn bytecompile/def (source env)
                          (when (or (not (symbol? (cadr source)))
                                    (not (cddr source)))
                            (throw (list :type-error
                                         "(def) needs a symbol name and a value as arguments" #nil env)))
                          (list (bytecompile* (caddr source) env)
                                ($def/val (cadr source))))

                    (defn bytecompile/set! (source env)
                          (case (:length (cdr source))
                                (2 (when (not (symbol? (cadr source)))
                                     (throw (list :type-error
                                                  "(set!) needs a symbol name and a value as arguments" #nil env)))
                                   (list (bytecompile* (caddr source) env)
                                         ($set/val (cadr source))))
                                (3 (list (bytecompile* (cadr source) env)
                                         (bytecompile* (caddr source) env)
                                         (bytecompile* (cadddr source) env)
                                         ($set/gen)))
                                (otherwise (throw (list :arity-error "(set!) needs 2 or 3 arguments" #nil env)))))

                    (defn bytecompile/if (source env)
                          (let ((sym-else (gensym))
                                (sym-after (gensym)))
                               (list (bytecompile* (cadr source) env)
                                     ($jf sym-else)
                                     (bytecompile* (caddr source) env)
                                     ($jmp sym-after)
                                     (list :label sym-else)
                                     (bytecompile* (cadddr source) env)
                                     (list :label sym-after))))

                    (defn bytecompile/while (source env)
                          (def sym-start (gensym))
                          (def sym-end (gensym))
                          (list ($push/nil) ;; Return value if predicate is #f from the beginning
                                ($jmp sym-end)
                                (list :label sym-start)
                                ($drop)
                                (bytecompile/do/form (cddr source) env)
                                (list :label sym-end)
                                (bytecompile* (cadr source) env)
                                ($jt sym-start)))

                    (defn bytecompile/procedure/arg (source env)
                          (if (last? source)
                              (bytecompile* (car source) env)
                              (cons (bytecompile* (car source) env)
                                    (bytecompile/procedure/arg (cdr source) env))))

                    (defn bytecompile/procedure/inline? (op)
                          (:meta op :inline))

                    (defn bytecompile/procedure/inline (op args env)
                          (def arg-count (:length args))
                          (when (> arg-count 1)
                            (throw (list :compiler-error "For now only monadic functions can be inlined" op env)))
                          (def form (macroexpand* (cons 'do (:meta op :source)) env))
                          (def arg-name (car (:arguments op)))
                          (if args
                              (bytecompile* (list/replace form arg-name (car args)) env)
                              (bytecompile* form env)))

                    (defn bytecompile/procedure (op args env op-raw)
                          (if (bytecompile/procedure/inline? op)
                              (bytecompile/procedure/inline op args env)
                              (bytecompile/procedure/default op args env op-raw)))

                    (defn bytecompile/procedure/default (op args env op-raw)
                          (when (and (not (procedure? op))
                                     (not (symbol? op))
                                     (not (pair? op))
                                     (not (keyword? op)))
                            (exception :type-error "Can't apply to that" op))
                          (def arg-count (:length args))
                          (def optimized ($apply/optimized arg-count op))
                          (if optimized
                              (list (when args (bytecompile/procedure/arg args))
                                    optimized)
                              (list (bytecompile* op-raw env)
                                    (when args (bytecompile/procedure/arg args))
                                    ($apply arg-count))))

                    (defn bytecompile/and/rec (source env label-end)
                          (list (bytecompile* (car source) env)
                                (when (cdr source)
                                  (list ($dup)
                                        ($jf label-end)
                                        ($drop)
                                        (bytecompile/and/rec (cdr source) env label-end)))))

                    (defn bytecompile/and (source env)
                          (def label-end (gensym))
                          (list (bytecompile/and/rec (cdr source) env label-end)
                                (list :label label-end)))

                    (defn bytecompile/or/rec (source env label-end)
                          (when source(list (bytecompile* (car source) env)
                                            ($dup)
                                            ($jt label-end)
                                            ($drop)
                                            (bytecompile/or/rec (cdr source) env label-end))))

                    (defn bytecompile/or (source env)
                          (def label-end (gensym))
                          (list (bytecompile/or/rec (cdr source) env label-end)
                                ($push/val #f)
                                (list :label label-end)))

                    (defn bytecompile/fn* (source env)
                          (def arg-count (:length (cdr source)))
                          (when (not= arg-count 5) (exception :arity-error "(fn*) needs exactly 4 arguments" source))
                          (cdr! source)
                          (list (bytecompile* (car source) env)
                                (bytecompile* (cadr source) env)
                                (bytecompile* (caddr source) env)
                                ($fn/new)))

                    (defn bytecompile/macro* (source env)
                          (def arg-count (:length (cdr source)))
                          (when (not= arg-count 5) (exception :arity-error "(macro*) needs exactly 4 arguments" source))
                          (cdr! source)
                          (list (bytecompile* (car source) env)
                                (bytecompile* (cadr source) env)
                                (bytecompile* (caddr source) env)
                                ($macro/new)))

                    (defn bytecompile/environment*  (source env)
                          (list ($let)
                                (bytecompile/do/form (cdr source) env)
                                ($drop)
                                ($closure/push)
                                ($closure/pop)))

                    (defn bytecompile/let*  (source env)
                          (list ($let)
                                (bytecompile/do (cadr source) env)
                                ($closure/pop)))

                    (defn bytecompile/return (source env)
                          (list (bytecompile* (cadr source) env)
                                ($ret)))

                    (defn bytecompile/try  (source env)
                          (def end-sym (gensym))
                          (list (bytecompile* (cadr source) env)
                                ($try end-sym)
                                (bytecompile/do/form (cddr source) env)
                                ($closure/pop)
                                (list :label end-sym)))

                    (defn bytecompile/apply (source env)
                          (def arg-count (:length (cdr source)))
                          (cond ((= arg-count 1)
                                 (list (bytecompile* (cadr source) env)
                                       ($list 0)
                                       ($apply/collection)))
                                ((= arg-count 2)
                                 (list (bytecompile* (cadr source) env)
                                       (bytecompile* (caddr source) env)
                                       ($apply/collection)))))

                    (defn bytecompile* (source env)
                          "Compile the forms in source"
                          :internal
                          (def op (if (resolves? (car source) env)
                                      (ref (or env (current-closure)) (car source))
                                      (car source)))
                          (case (:type-name op)
                                ((:lambda :native-function :pair :symbol :keyword)
                                 (case op
                                       (do           (bytecompile/do           source env))
                                       (let*         (bytecompile/let*         source env))
                                       (def          (bytecompile/def          source env))
                                       (set!         (bytecompile/set!         source env))
                                       (if           (bytecompile/if           source env))
                                       (while        (bytecompile/while        source env))
                                       (and          (bytecompile/and          source env))
                                       (or           (bytecompile/or           source env))
                                       (fn*          (bytecompile/fn*          source env))
                                       (macro*       (bytecompile/macro*       source env))
                                       (environment* (bytecompile/environment* source env))
                                       (try          (bytecompile/try          source env))
                                       (return       (bytecompile/return       source env))
                                       (apply        (bytecompile/apply        source env))
                                       (quote        (bytecompile/quote        (cadr source)))
                                       (otherwise    (bytecompile/procedure    op (cdr source) env (car source)))))
                                (otherwise (if (pair? source)
                                               (exception :type-error "Can't evaluate that" source)
                                               (bytecompile/literal source)))))))

(defn bytecompile (form environment)
      :internal
      (list (bytecompile* form environment)
            ($ret)))