001  (ns beowulf.reader.generate
002    "Generating S-Expressions from parse trees. 
003     
004     ## From Lisp 1.5 Programmers Manual, page 10
005     *Note that I've retyped much of this, since copy/pasting out of PDF is less
006     than reliable. Any typos are mine.*
007     
008     *Quote starts:*
009  
010     We are now in a position to define the universal LISP function
011     `evalquote[fn;args]`, When evalquote is given a function and a list of arguments
012     for that function, it computes the value of the function applied to the arguments.
013     LISP functions have S-expressions as arguments. In particular, the argument `fn`
014     of the function evalquote must be an S-expression. Since we have been
015     writing functions as M-expressions, it is necessary to translate them into
016     S-expressions.
017  
018     The following rules define a method of translating functions written in the
019     meta-language into S-expressions.
020     1. If the function is represented by its name, it is translated by changing
021        all of the letters to upper case, making it an atomic symbol. Thus `car` is 
022        translated to `CAR`.
023     2. If the function uses the lambda notation, then the expression
024        `λ[[x ..;xn]; ε]` is translated into `(LAMBDA (X1 ...XN) ε*)`, where ε* is the translation
025        of ε.
026     3. If the function begins with label, then the translation of
027        `label[α;ε]` is `(LABEL α* ε*)`.
028  
029     Forms are translated as follows:
030     1. A variable, like a function name, is translated by using uppercase letters.
031        Thus the translation of `var1` is `VAR1`.
032     2. The obvious translation of letting a constant translate into itself will not
033        work. Since the translation of `x` is `X`, the translation of `X` must be something
034        else to avoid ambiguity. The solution is to quote it. Thus `X` is translated
035        into `(QUOTE X)`.
036     3. The form `fn[argl;. ..;argn]` is translated into `(fn* argl* ...argn*)`
037     4. The conditional expression `[pl-el;...;pn-en]` is translated into
038        `(COND (p1* e1*)...(pn* en*))`
039  
040     ## Examples
041     ```
042       M-expressions                                  S-expressions             
043    
044       x                                              X                         
045       car                                            CAR                       
046       car[x]                                         (CAR X)                   
047       T                                              (QUOTE T)                 
048       ff[car [x]]                                    (FF (CAR X))              
049       [atom[x]->x; T->ff[car[x]]]                    (COND ((ATOM X) X) 
050                                                          ((QUOTE T)(FF (CAR X))))
051       label[ff;λ[[x];[atom[x]->x;                    (LABEL FF (LAMBDA (X) 
052            T->ff[car[x]]]]]                              (COND ((ATOM X) X) 
053                                                              ((QUOTE T)(FF (CAR X))))))
054     ```
055  
056     *quote ends*
057  "
058    (:require [beowulf.cons-cell :refer [make-beowulf-list make-cons-cell]]
059              [beowulf.reader.macros :refer [expand-macros]]
060              [beowulf.oblist :refer [NIL]]
061              [clojure.math.numeric-tower :refer [expt]]
062              [clojure.string :refer [upper-case]]
063              [clojure.tools.trace :refer [deftrace]]))
064  
065  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
066  ;;;
067  ;;; Copyright (C) 2022-2023 Simon Brooke
068  ;;;
069  ;;; This program is free software; you can redistribute it and/or
070  ;;; modify it under the terms of the GNU General Public License
071  ;;; as published by the Free Software Foundation; either version 2
072  ;;; of the License, or (at your option) any later version.
073  ;;; 
074  ;;; This program is distributed in the hope that it will be useful,
075  ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
076  ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
077  ;;; GNU General Public License for more details.
078  ;;; 
079  ;;; You should have received a copy of the GNU General Public License
080  ;;; along with this program; if not, write to the Free Software
081  ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
082  ;;;
083  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
084  
085  (declare generate)
086  
087  (defn gen-cond-clause
088    "Generate a cond clause from this simplified parse tree fragment `p`;
089    returns `nil` if `p` does not represent a cond clause."
090    [p context]
091    (when
092     (and (coll? p) (= :cond-clause (first p)))
093      (make-beowulf-list
094       (list (if (= (nth p 1) [:quoted-expr [:atom "T"]])
095               'T
096               (generate (nth p 1) context))
097             (generate (nth p 2) context)))))
098  
099  (defn gen-cond
100    "Generate a cond statement from this simplified parse tree fragment `p`;
101    returns `nil` if `p` does not represent a (MEXPR) cond statement."
102    [p context]
103    (when
104     (and (coll? p) (= :cond (first p)))
105      (make-beowulf-list
106       (cons
107        'COND
108        (map
109         #(generate % (if (= context :mexpr) :cond-mexpr context))
110         (rest p))))))
111  
112  (defn gen-fn-call
113    "Generate a function call from this simplified parse tree fragment `p`;
114    returns `nil` if `p` does not represent a (MEXPR) function call."
115    [p context]
116    (when
117     (and (coll? p) (= :fncall (first p)) (= :mvar (first (second p))))
118      (make-cons-cell
119       (generate (second p) context)
120       (generate (nth p 2) context))))
121  
122  
123  (defn gen-dot-terminated-list
124    "Generate a list, which may be dot-terminated, from this partial parse tree
125    'p'. Note that the function acts recursively and progressively decapitates
126    its argument, so that the argument will not always be a valid parse tree."
127    [p]
128    (cond
129      (empty? p)
130      NIL
131      (and (coll? (first p)) (= :dot-terminal (first (first p))))
132      (let [dt (first p)]
133        (make-cons-cell
134         (generate (nth dt 1))
135         (generate (nth dt 2))))
136      :else
137      (make-cons-cell
138       (generate (first p))
139       (gen-dot-terminated-list (rest p)))))
140  
141  ;; null[x] = [x = NIL -> T; T -> F]
142  ;; [:defn 
143  ;;  [:mexpr [:fncall [:mvar "null"] [:bindings [:args [:mexpr [:mvar "x"]]]]]] 
144  ;;  "=" 
145  ;;  [:mexpr [:cond 
146  ;;           [:cond-clause [:mexpr [:iexpr [:lhs [:mexpr [:mvar "x"]]] [:iop "="] [:rhs [:mexpr [:mconst "NIL"]]]]] [:mexpr [:mconst "T"]]] 
147  ;;           [:cond-clause [:mexpr [:mconst "T"]] [:mexpr [:mconst "F"]]]]]]
148  
149  (defn generate-defn
150    [tree context]
151    (if (= :mexpr (first tree))
152      (generate-defn (second tree) context)
153      (make-beowulf-list
154       (list 'PUT
155             (list 'QUOTE (generate (-> tree second second second) context))
156             (list 'QUOTE 'EXPR)
157             (list 'QUOTE
158                   (cons 'LAMBDA
159                         (list (generate (nth (-> tree second second) 2) context)
160                               (generate (nth tree 3) context))))))))
161  
162  (defn gen-iexpr
163    [tree context]
164    (let [bundle (reduce #(assoc %1 (first %2) %2)
165                         {}
166                         (rest tree))]
167      (list (generate (:iop bundle) context)
168            (generate (:lhs bundle) context)
169            (generate (:rhs bundle) context))))
170  
171  (defn generate-set
172    "Actually not sure what the mexpr representation of set looks like"
173    [tree context]
174    (throw (ex-info "Not Yet Implemented" {:feature "generate-set"})))
175  
176  (defn generate-assign
177    "Generate an assignment statement based on this `tree`. If the thing 
178     being assigned to is a function signature, then we have to do something 
179     different to if it's an atom."
180    [tree context]
181    (case (first (second tree))
182      :fncall (generate-defn tree context)
183      :mexpr (map #(generate % context) (rest (second tree)))
184      (:mvar :atom) (generate-set tree context)))
185  
186  (defn strip-leading-zeros
187    "`read-string` interprets strings with leading zeros as octal; strip
188    any from this string `s`. If what's left is empty (i.e. there were
189    only zeros, return `\"0\"`."
190    ([s]
191     (strip-leading-zeros s ""))
192    ([s prefix]
193     (if
194      (empty? s) "0"
195      (case (first s)
196        (\+ \-) (strip-leading-zeros (subs s 1) (str (first s) prefix))
197        "0" (strip-leading-zeros (subs s 1) prefix)
198        (str prefix s)))))
199  
200  (defn generate
201    "Generate lisp structure from this parse tree `p`. It is assumed that
202    `p` has been simplified."
203    ([p]
204     (generate p :expr))
205    ([p context]
206     (try
207       (expand-macros
208        (if
209         (coll? p)
210          (case (first p)
211            :λ "LAMBDA"
212            :λexpr (make-cons-cell
213                    (generate (nth p 1) context)
214                    (make-cons-cell (generate (nth p 2) context)
215                                    (generate (nth p 3) context)))
216            :args (make-beowulf-list (map #(generate % context) (rest p)))
217            :atom (symbol (second p))
218            :bindings (generate (second p) context)
219            :body (make-beowulf-list (map #(generate % context) (rest p)))
220            (:coefficient :exponent) (generate (second p) context)
221            :cond (gen-cond p (if (= context :mexpr) :cond-mexpr context))
222            :cond-clause (gen-cond-clause p context)
223            :decimal (read-string (apply str (map second (rest p))))
224            :defn (generate-defn p context)
225            :dotted-pair (make-cons-cell
226                          (generate (nth p 1) context)
227                          (generate (nth p 2) context))
228            :fncall (gen-fn-call p context)
229            :iexpr (gen-iexpr p context)
230            :integer (read-string (strip-leading-zeros (second p)))
231            :iop (case (second p)
232                   "/" 'DIFFERENCE
233                   "=" 'EQUAL
234                   ">" 'GREATERP
235                   "<" 'LESSP
236                   "+" 'PLUS
237                   "*" 'TIMES
238                  ;; else
239                   (throw (ex-info "Unrecognised infix operator symbol"
240                                   {:phase :generate
241                                    :fragment p})))
242            :list (gen-dot-terminated-list (rest p))
243            (:lhs :rhs) (generate (second p) context)
244            :mexpr (generate (second p) (if (= context :cond-mexpr) context :mexpr))
245            :mconst (if (= context :cond-mexpr)
246                      (case (second p)
247                        ("T" "F" "NIL") (symbol (second p))
248                        ;; else
249                        (list 'QUOTE (symbol (second p))))
250                      ;; else
251                      (list 'QUOTE (symbol (second p))))
252            :mvar (symbol (upper-case (second p)))
253            :number (generate (second p) context)
254            :octal (let [n (read-string (strip-leading-zeros (second p) "0"))
255                         scale (generate (nth p 3) context)]
256                     (* n (expt 8 scale)))
257  
258        ;; the quote read macro (which probably didn't exist in Lisp 1.5, but...)
259            :quoted-expr (make-beowulf-list (list 'QUOTE (generate (second p) context)))
260            :scale-factor (if
261                           (empty? (second p)) 0
262                           (read-string (strip-leading-zeros (second p))))
263            :scientific (let [n (generate (second p) context)
264                              exponent (generate (nth p 3) context)]
265                          (* n (expt 10 exponent)))
266            :sexpr (generate (second p) :sexpr)
267            :subr (symbol (second p))
268  
269        ;; default
270            (throw (ex-info (str "Unrecognised head: " (first p))
271                            {:generating p})))
272          p))
273       (catch Throwable any
274         (throw (ex-info "Could not generate"
275                         {:generating p}
276                         any))))))