001  (ns beowulf.bootstrap
                
                002    "Lisp as defined in Chapter 1 (pages 1-14) of the
                
                003    `Lisp 1.5 Programmer's Manual`; that is to say, a very simple Lisp language,
                
                004    which should, I believe, be sufficient in conjunction with the functions
                
                005    provided by `beowulf.host`, be sufficient to bootstrap the full Lisp 1.5
                
                006    interpreter..
                
                007  
                
                008    The convention is adopted that functions in this file with names in
                
                009    ALLUPPERCASE are Lisp 1.5 functions (although written in Clojure) and that
                
                010    therefore all arguments must be numbers, symbols or `beowulf.cons_cell.ConsCell`
                
                011    objects."
                
                012    (:require [beowulf.cons-cell :refer [F make-beowulf-list make-cons-cell
                
                013                                         pretty-print T]]
                
                014              [beowulf.host :refer [ASSOC ATOM CAAR CADAR CADDR CADR CAR CDR GET
                
                015                                    LIST NUMBERP PAIRLIS traced?]]
                
                016              [beowulf.oblist :refer [*options* NIL oblist]])
                
                017    (:import [beowulf.cons_cell ConsCell]
                
                018             [clojure.lang Symbol]))
                
                019  
                
                020  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                021  ;;;
                
                022  ;;; Copyright (C) 2022-2023 Simon Brooke
                
                023  ;;;
                
                024  ;;; This program is free software; you can redistribute it and/or
                
                025  ;;; modify it under the terms of the GNU General Public License
                
                026  ;;; as published by the Free Software Foundation; either version 2
                
                027  ;;; of the License, or (at your option) any later version.
                
                028  ;;; 
                
                029  ;;; This program is distributed in the hope that it will be useful,
                
                030  ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
                
                031  ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                
                032  ;;; GNU General Public License for more details.
                
                033  ;;; 
                
                034  ;;; You should have received a copy of the GNU General Public License
                
                035  ;;; along with this program; if not, write to the Free Software
                
                036  ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
                
                037  ;;;
                
                038  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                039  
                
                040  (declare APPLY EVAL prog-eval)
                
                041  
                
                042  ;;;; The PROGram feature ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                043  
                
                044  (def find-target
                
                045    (memoize
                
                046     (fn [target body]
                
                047       (loop [body' body]
                
                048         (cond
                
                049           (= body' NIL) (throw (ex-info (str "Mislar GO miercels: `" target "`")
                
                050                                         {:phase :lisp
                
                051                                          :function 'PROG
                
                052                                          :type :lisp
                
                053                                          :code :A6
                
                054                                          :target target}))
                
                055           (= (.getCar body') target) body'
                
                056           :else (recur (.getCdr body')))))))
                
                057  
                
                058  (defn- prog-cond
                
                059    "Like `EVCON`, q.v. except using `prog-eval` instead of `EVAL` and not
                
                060     throwing an error if no clause matches."
                
                061    [clauses vars env depth]
                
                062    (loop [clauses' clauses]
                
                063      (if-not (= clauses' NIL)
                
                064        (let [test (prog-eval (CAAR clauses') vars env depth)]
                
                065          (if (not (#{NIL F} test))
                
                066            (prog-eval (CADAR clauses') vars env depth)
                
                067            (recur (.getCdr clauses'))))
                
                068        NIL)))
                
                069  
                
                070  (defn- merge-vars [vars env]
                
                071    (reduce
                
                072     #(make-cons-cell
                
                073       (make-cons-cell %2 (@vars %2))
                
                074       env)
                
                075     env
                
                076     (keys @vars)))
                
                077  
                
                078  (defn prog-eval
                
                079    "Like `EVAL`, q.v., except handling symbols, and expressions starting
                
                080     `GO`, `RETURN`, `SET` and `SETQ` specially."
                
                081    [expr vars env depth]
                
                082    (cond
                
                083      (number? expr) expr
                
                084      (symbol? expr) (@vars expr)
                
                085      (instance? ConsCell expr) (case (.getCar expr)
                
                086                                  COND (prog-cond (.getCdr expr)
                
                087                                                  vars env depth)
                
                088                                  GO (make-cons-cell
                
                089                                      '*PROGGO* (.getCar (.getCdr expr)))
                
                090                                  RETURN (make-cons-cell
                
                091                                          '*PROGRETURN*
                
                092                                          (prog-eval (.getCar (.getCdr expr))
                
                093                                                     vars env depth))
                
                094                                  SET (let [v (CADDR expr)]
                
                095                                        (swap! vars
                
                096                                               assoc
                
                097                                               (prog-eval (CADR expr)
                
                098                                                          vars env depth)
                
                099                                               (prog-eval (CADDR expr)
                
                100                                                          vars env depth))
                
                101                                        v)
                
                102                                  SETQ (let [v (CADDR expr)]
                
                103                                         (swap! vars
                
                104                                                assoc
                
                105                                                (CADR expr)
                
                106                                                (prog-eval v
                
                107                                                           vars env depth))
                
                108                                         v)
                
                109                                   ;; else
                
                110                                  (beowulf.bootstrap/EVAL expr
                
                111                                                          (merge-vars vars env)
                
                112                                                          depth))))
                
                113  
                
                114  (defn PROG
                
                115    "The accursed `PROG` feature. See page 71 of the manual.
                
                116     
                
                117     Lisp 1.5 introduced `PROG`, and most Lisps have been stuck with it ever 
                
                118     since. It introduces imperative programming into what should be a pure 
                
                119     functional language, and consequently it's going to be a pig to implement.
                
                120     
                
                121     Broadly, `PROG` is a variadic pseudo function called as a `FEXPR` (or 
                
                122     possibly an `FSUBR`, although I'm not presently sure that would even work.)
                
                123  
                
                124     The arguments, which are unevaluated, are a list of forms, the first of 
                
                125     which is expected to be a list of symbols which will be treated as names 
                
                126     of variables within the program, and the rest of which (the 'program body')
                
                127     are either lists or symbols. Lists are treated as Lisp expressions which
                
                128     may be evaulated in turn. Symbols are treated as targets for the `GO` 
                
                129     statement. 
                
                130        
                
                131     **GO:** 
                
                132     A `GO` statement takes the form of `(GO target)`, where 
                
                133     `target` should be one of the symbols which occur at top level among that
                
                134     particular invocation of `PROG`s arguments. A `GO` statement may occur at 
                
                135     top level in a PROG, or in a clause of a `COND` statement in a `PROG`, but
                
                136     not in a function called from the `PROG` statement. When a `GO` statement
                
                137     is evaluated, execution should transfer immediately to the expression which
                
                138     is the argument list immediately following the symbol which is its target.
                
                139  
                
                140     If the target is not found, an error with the code `A6` should be thrown.
                
                141  
                
                142     **RETURN:** 
                
                143     A `RETURN` statement takes the form `(RETURN value)`, where 
                
                144     `value` is any value. Following the evaluation of a `RETURN` statement, 
                
                145     the `PROG` should immediately exit without executing any further 
                
                146     expressions, returning the  value.
                
                147  
                
                148     **SET and SETQ:**
                
                149     In addition to the above, if a `SET` or `SETQ` expression is encountered
                
                150     in any expression within the `PROG` body, it should affect not the global
                
                151     object list but instead only the local variables of the program.
                
                152  
                
                153     **COND:**
                
                154     In **strict** mode, when in normal execution, a `COND` statement none of 
                
                155     whose clauses match should not return `NIL` but should throw an error with
                
                156     the code `A3`... *except* that inside a `PROG` body, it should not do so.
                
                157     *sigh*.
                
                158  
                
                159     **Flow of control:**
                
                160     Apart from the exceptions specified above, expressions in the program body
                
                161     are evaluated sequentially. If execution reaches the end of the program 
                
                162     body, `NIL` is returned.
                
                163  
                
                164     Got all that?
                
                165  
                
                166     Good."
                
                167    [program env depth]
                
                168    (let [trace (traced? 'PROG)
                
                169          vars (atom (reduce merge (map #(assoc {} % NIL) (.getCar program))))
                
                170          body (.getCdr program)
                
                171          targets (set (filter symbol? body))]
                
                172      (when trace (do
                
                173                    (println "Program:")
                
                174                    (pretty-print program))) ;; for debugging
                
                175      (loop [cursor body]
                
                176        (let [step (.getCar cursor)]
                
                177          (when trace (do (println "Executing step: " step)
                
                178                          (println "  with vars: " @vars)))
                
                179          (cond (= cursor NIL) NIL
                
                180                (symbol? step) (recur (.getCdr cursor))
                
                181                :else (let [v (prog-eval (.getCar cursor) vars env depth)]
                
                182                        (when trace (println "  --> " v))
                
                183                        (if (instance? ConsCell v)
                
                184                          (case (.getCar v)
                
                185                            *PROGGO* (let [target (.getCdr v)]
                
                186                                       (if (targets target)
                
                187                                         (recur (find-target target body))
                
                188                                         (throw (ex-info (str "Uncynlic GO miercels `"
                
                189                                                              target "`")
                
                190                                                         {:phase :lisp
                
                191                                                          :function 'PROG
                
                192                                                          :args program
                
                193                                                          :type :lisp
                
                194                                                          :code :A6
                
                195                                                          :target target
                
                196                                                          :targets targets}))))
                
                197                            *PROGRETURN* (.getCdr v)
                
                198                          ;; else
                
                199                            (recur (.getCdr cursor)))
                
                200                          (recur (.getCdr cursor)))))))))
                
                201  
                
                202  ;;;; Tracing execution ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                203  
                
                204  (defn- trace-call
                
                205    "Show a trace of a call to the function named by this `function-symbol` 
                
                206    with these `args` at this depth."
                
                207    [function-symbol args depth]
                
                208    (when (traced? function-symbol)
                
                209      (let [indent (apply str (repeat depth "-"))]
                
                210        (println (str indent "> " function-symbol " " args)))))
                
                211  
                
                212  (defn- trace-response
                
                213    "Show a trace of this `response` from the function named by this
                
                214     `function-symbol` at this depth."
                
                215    [function-symbol response depth]
                
                216    (when (traced? function-symbol)
                
                217      (let [indent (apply str (repeat depth "-"))]
                
                218        (println (str "<" indent " " function-symbol " " response))))
                
                219    response)
                
                220  
                
                221  (defn- value
                
                222    "Seek a value for this symbol `s` by checking each of these indicators in
                
                223     turn."
                
                224    ([s]
                
                225     (value s (list 'APVAL 'EXPR 'FEXPR 'SUBR 'FSUBR)))
                
                226    ([s indicators]
                
                227     (when (symbol? s)
                
                228       (first (remove #(= % NIL) (map #(GET s %)
                
                229                                      indicators))))))
                
                230  
                
                231  ;;;; APPLY ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                232  
                
                233  (defn try-resolve-subroutine
                
                234    "Attempt to resolve this `subr` with these `args`."
                
                235    [subr args]
                
                236    (when (and subr (not= subr NIL))
                
                237      (try @(resolve subr)
                
                238           (catch Throwable any
                
                239             (throw (ex-info "þegnung (SUBR) ne āfand"
                
                240                             {:phase :apply
                
                241                              :function subr
                
                242                              :args args
                
                243                              :type :beowulf}
                
                244                             any))))))
                
                245  
                
                246  (defn- apply-symbolic
                
                247    "Apply this `funtion-symbol` to these `args` in this `environment` and 
                
                248     return the result."
                
                249    [^Symbol function-symbol args ^ConsCell environment depth]
                
                250    (trace-call function-symbol args depth)
                
                251    (let [lisp-fn (value function-symbol '(EXPR FEXPR))
                
                252          args' (cond (= NIL args) args
                
                253                      (empty? args) NIL
                
                254                      (instance? ConsCell args) args
                
                255                      :else (make-beowulf-list args))
                
                256          subr (value function-symbol '(SUBR FSUBR))
                
                257          host-fn (try-resolve-subroutine subr args')
                
                258          result (cond (and lisp-fn
                
                259                            (not= lisp-fn NIL)) (APPLY lisp-fn args' environment depth)
                
                260                       host-fn (try
                
                261                                 (apply host-fn (when (instance? ConsCell args') args'))
                
                262                                 (catch Exception any
                
                263                                   (throw (ex-info (str "Uncynlic þegnung: "
                
                264                                                        (.getMessage any))
                
                265                                                   {:phase :apply
                
                266                                                    :function function-symbol
                
                267                                                    :args args
                
                268                                                    :type :beowulf}
                
                269                                                   any))))
                
                270                       :else (ex-info "þegnung ne āfand"
                
                271                                      {:phase :apply
                
                272                                       :function function-symbol
                
                273                                       :args args
                
                274                                       :type :beowulf}))]
                
                275      (trace-response function-symbol result depth)
                
                276      result))
                
                277  
                
                278  (defn APPLY
                
                279    "Apply this `function` to these `arguments` in this `environment` and return
                
                280     the result.
                
                281     
                
                282     For bootstrapping, at least, a version of APPLY written in Clojure.
                
                283     All args are assumed to be symbols or `beowulf.cons-cell/ConsCell` objects.
                
                284     See page 13 of the Lisp 1.5 Programmers Manual."
                
                285    [function args environment depth]
                
                286    (trace-call 'APPLY (list function args environment) depth)
                
                287    (let [result (cond
                
                288                   (= NIL function) (if (:strict *options*)
                
                289                                      NIL
                
                290                                      (throw (ex-info "NIL sí ne þegnung"
                
                291                                                      {:phase :apply
                
                292                                                       :function "NIL"
                
                293                                                       :args args
                
                294                                                       :type :beowulf})))
                
                295                   (= (ATOM function) T) (apply-symbolic function args environment (inc depth))
                
                296                   :else (case (first function)
                
                297                           LABEL (APPLY
                
                298                                  (CADDR function)
                
                299                                  args
                
                300                                  (make-cons-cell
                
                301                                   (make-cons-cell
                
                302                                    (CADR function)
                
                303                                    (CADDR function))
                
                304                                   environment)
                
                305                                  depth)
                
                306                           FUNARG (APPLY (CADR function) args (CADDR function) depth)
                
                307                           LAMBDA (EVAL
                
                308                                   (CADDR function)
                
                309                                   (PAIRLIS (CADR function) args environment) depth)
                
                310                           (throw (ex-info "Ungecnáwen wyrþan sí þegnung-weard"
                
                311                                           {:phase :apply
                
                312                                            :function function
                
                313                                            :args args
                
                314                                            :type :beowulf}))))]
                
                315      (trace-response 'APPLY result depth)
                
                316      result))
                
                317  
                
                318  ;;;; EVAL ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                319  
                
                320  (defn- EVCON
                
                321    "Inner guts of primitive COND. All `clauses` are assumed to be
                
                322    `beowulf.cons-cell/ConsCell` objects. Note that tests in Lisp 1.5
                
                323     often return `F`, not `NIL`, on failure. If no clause matches,
                
                324     then, strictly, we throw an error with code `:A3`.
                
                325  
                
                326     See pages 13 and 71 of the Lisp 1.5 Programmers Manual."
                
                327    [clauses env depth]
                
                328    (loop [clauses' clauses]
                
                329      (if-not (= clauses' NIL)
                
                330        (let [test (EVAL (CAAR clauses') env depth)]
                
                331          (if (not (#{NIL F} test))
                
                332           ;; (and (not= test NIL) (not= test F))
                
                333            (EVAL (CADAR clauses') env depth)
                
                334            (recur (.getCdr clauses'))))
                
                335        (if (:strict *options*)
                
                336          (throw (ex-info "Ne ġefōg dǣl in COND"
                
                337                          {:phase :eval
                
                338                           :function 'COND
                
                339                           :args (list clauses)
                
                340                           :type :lisp
                
                341                           :code :A3}))
                
                342          NIL))))
                
                343  
                
                344  (defn- EVLIS
                
                345    "Map `EVAL` across this list of `args` in the context of this
                
                346    `env`ironment.All args are assumed to be `beowulf.cons-cell/ConsCell` objects.
                
                347    See page 13 of the Lisp 1.5 Programmers Manual."
                
                348    [args env depth]
                
                349    (cond
                
                350      (= NIL args) NIL
                
                351      :else
                
                352      (make-cons-cell
                
                353       (EVAL (CAR args) env depth)
                
                354       (EVLIS (CDR args) env depth))))
                
                355  
                
                356  (defn- eval-symbolic
                
                357    [expr env depth]
                
                358    (let [v (ASSOC expr env)
                
                359          indent (apply str (repeat depth "-"))]
                
                360      (when (traced? 'EVAL)
                
                361        (println (str indent ": EVAL: sceald bindele: " (or v "nil"))))
                
                362      (if (instance? ConsCell v)
                
                363        (.getCdr v)
                
                364        (let [v' (value expr (list 'APVAL))]
                
                365          (when (traced? 'EVAL)
                
                366            (println (str indent ": EVAL: deóp bindele: (" expr " . " (or v' "nil") ")")))
                
                367          (if v'
                
                368            v'
                
                369            (throw (ex-info "Ne tácen-bindele āfand"
                
                370                            {:phase :eval
                
                371                             :function 'EVAL
                
                372                             :args (list expr env depth)
                
                373                             :type :lisp
                
                374                             :code :A8})))))))
                
                375  
                
                376  (defn EVAL
                
                377    "Evaluate this `expr` and return the result. If `environment` is not passed,
                
                378     it defaults to the current value of the global object list. The `depth`
                
                379     argument is part of the tracing system and should not be set by user code.
                
                380  
                
                381     All args are assumed to be numbers, symbols or `beowulf.cons-cell/ConsCell` 
                
                382     objects. However, if called with just a single arg, `expr`, I'll assume it's
                
                383     being called from the Clojure REPL and will coerce the `expr` to `ConsCell`."
                
                384    ([expr]
                
                385     (let [expr' (if (and (coll? expr) (not (instance? ConsCell expr)))
                
                386                   (make-beowulf-list expr)
                
                387                   expr)]
                
                388       (EVAL expr' NIL 0)))
                
                389    ([expr env depth]
                
                390     (trace-call 'EVAL (list expr env depth) depth)
                
                391     (let [result (cond
                
                392                    (= NIL expr) NIL ;; it was probably a mistake to make Lisp 
                
                393                                     ;; NIL distinct from Clojure nil
                
                394                    (= (NUMBERP expr) T) expr
                
                395                    (symbol? expr) (eval-symbolic expr env depth)
                
                396                    (string? expr) (if (:strict *options*)
                
                397                                     (throw
                
                398                                      (ex-info
                
                399                                       (str "EVAL: strings not allowed in strict mode: \"" expr "\"")
                
                400                                       {:phase  :eval
                
                401                                        :detail :strict
                
                402                                        :expr   expr}))
                
                403                                     (symbol expr))
                
                404                    (= (ATOM (CAR expr)) T) (case (CAR expr)
                
                405                                              COND (EVCON (CDR expr) env depth)
                
                406                                              FUNCTION (LIST 'FUNARG (CADR expr))
                
                407                                              PROG (PROG (CDR expr) env depth)
                
                408                                              QUOTE (CADR expr)
                
                409             ;; else 
                
                410                                              (APPLY
                
                411                                               (CAR expr)
                
                412                                               (EVLIS (CDR expr) env depth)
                
                413                                               env
                
                414                                               depth))
                
                415                    :else (APPLY
                
                416                           (CAR expr)
                
                417                           (EVLIS (CDR expr) env depth)
                
                418                           env
                
                419                           depth))]
                
                420       (trace-response 'EVAL result depth)
                
                421       result)))
                
                422