001  (ns beowulf.read
                
                002    "This provides the reader required for boostrapping. It's not a bad
                
                003    reader - it provides feedback on errors found in the input - but it isn't
                
                004    the real Lisp reader.
                
                005  
                
                006    Intended deviations from the behaviour of the real Lisp reader are as follows:
                
                007  
                
                008    1. It reads the meta-expression language `MEXPR` in addition to the
                
                009        symbolic expression language `SEXPR`, which I do not believe the Lisp 1.5
                
                010        reader ever did;
                
                011    2. It treats everything between a double semi-colon and an end of line as a comment,
                
                012        as most modern Lisps do; but I do not believe Lisp 1.5 had this feature.
                
                013  
                
                014    Both these extensions can be disabled by using the `--strict` command line
                
                015    switch."
                
                016    (:require ;; [beowulf.reader.char-reader :refer [read-chars]]
                
                017              [beowulf.reader.generate :refer [generate]]
                
                018              [beowulf.reader.parser :refer [parse]]
                
                019              [beowulf.reader.simplify :refer [simplify]]
                
                020              [clojure.string :refer [join split starts-with? trim]])
                
                021    (:import [java.io InputStream]
                
                022             [instaparse.gll Failure]))
                
                023  
                
                024  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                025  ;;;
                
                026  ;;; This file provides the reader required for boostrapping. It's not a bad
                
                027  ;;; reader - it provides feedback on errors found in the input - but it isn't
                
                028  ;;; the real Lisp reader.
                
                029  ;;;
                
                030  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                031  ;;;
                
                032  ;;; Copyright (C) 2022-2023 Simon Brooke
                
                033  ;;;
                
                034  ;;; This program is free software; you can redistribute it and/or
                
                035  ;;; modify it under the terms of the GNU General Public License
                
                036  ;;; as published by the Free Software Foundation; either version 2
                
                037  ;;; of the License, or (at your option) any later version.
                
                038  ;;; 
                
                039  ;;; This program is distributed in the hope that it will be useful,
                
                040  ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
                
                041  ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                
                042  ;;; GNU General Public License for more details.
                
                043  ;;; 
                
                044  ;;; You should have received a copy of the GNU General Public License
                
                045  ;;; along with this program; if not, write to the Free Software
                
                046  ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
                
                047  ;;;
                
                048  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                
                049  
                
                050  (defn strip-line-comments
                
                051    "Strip blank lines and comment lines from this string `s`, expected to
                
                052     be Lisp source."
                
                053    [^String s]
                
                054    (join "\n"
                
                055          (remove
                
                056           #(or (empty? %)
                
                057                (starts-with? (trim %) ";;"))
                
                058           (split s #"\n"))))
                
                059  
                
                060  (defn number-lines
                
                061    ([^String s]
                
                062     (number-lines s nil))
                
                063    ([^String s ^Failure e]
                
                064     (let [l (-> e :line)
                
                065           c (-> e :column)]
                
                066       (join "\n"
                
                067             (map #(str (format "%5d %s" (inc %1) %2)
                
                068                        (when (= l (inc %1))
                
                069                          (str "\n" (apply str (repeat c " ")) "^")))
                
                070                  (range)
                
                071                  (split s #"\n"))))))
                
                072  
                
                073  (defn gsp
                
                074    "Shortcut macro - the internals of read; or, if you like, read-string.
                
                075    Argument `s` should be a string representation of a valid Lisp
                
                076    expression."
                
                077    [s]
                
                078    (let [source (strip-line-comments s)
                
                079          parse-tree (parse source)]
                
                080      (if (instance? Failure parse-tree)
                
                081        (doall (println (number-lines source parse-tree))
                
                082               (throw (ex-info "Ne can forstande " (assoc parse-tree :source source))))
                
                083        (generate (simplify parse-tree)))))
                
                084  
                
                085  (defn read-from-console
                
                086    "Attempt to read a complete lisp expression from the console. NOTE that this
                
                087     will only really work for S-Expressions, not M-Expressions."
                
                088    []
                
                089    (loop [r (read-line)]
                
                090      (if (and (= (count (re-seq #"\(" r))
                
                091             (count (re-seq #"\)" r)))
                
                092               (= (count (re-seq #"\[" r))
                
                093                  (count (re-seq #"\]" r))))
                
                094        r
                
                095        (recur (str r "\n" (read-line))))))
                
                096  
                
                097  (defn READ
                
                098    "An implementation of a Lisp reader sufficient for bootstrapping; not necessarily
                
                099    the final Lisp reader. `input` should be either a string representation of a LISP
                
                100    expression, or else an input stream. A single form will be read."
                
                101    ([]
                
                102     (gsp (read-from-console)))
                
                103    ([input]
                
                104     (cond
                
                105       (empty? input) (READ)
                
                106       (string? input) (gsp input)
                
                107       (instance? InputStream input) (READ (slurp input))
                
                108       :else    (throw (ex-info "READ: `input` should be a string or an input stream" {})))))