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.oblist :refer [*options*]]
017 [beowulf.reader.char-reader :refer [read-chars]]
018 [beowulf.reader.generate :refer [generate]]
019 [beowulf.reader.parser :refer [parse]]
020 [beowulf.reader.simplify :refer [simplify]]
021 [clojure.string :refer [join split starts-with? trim]])
022 (:import [instaparse.gll Failure]
023 [java.io InputStream]))
024
025 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
026 ;;;
027 ;;; This file provides the reader required for boostrapping. It's not a bad
028 ;;; reader - it provides feedback on errors found in the input - but it isn't
029 ;;; the real Lisp reader.
030 ;;;
031 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
032 ;;;
033 ;;; Copyright (C) 2022-2023 Simon Brooke
034 ;;;
035 ;;; This program is free software; you can redistribute it and/or
036 ;;; modify it under the terms of the GNU General Public License
037 ;;; as published by the Free Software Foundation; either version 2
038 ;;; of the License, or (at your option) any later version.
039 ;;;
040 ;;; This program is distributed in the hope that it will be useful,
041 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
042 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
043 ;;; GNU General Public License for more details.
044 ;;;
045 ;;; You should have received a copy of the GNU General Public License
046 ;;; along with this program; if not, write to the Free Software
047 ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
048 ;;;
049 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
050
051 (defn strip-line-comments
052 "Strip blank lines and comment lines from this string `s`, expected to
053 be Lisp source."
054 [^String s]
055 (join "\n"
056 (remove
057 #(or (empty? %)
058 (starts-with? (trim %) ";;"))
059 (split s #"\n"))))
060
061 (defn number-lines
062 ([^String s]
063 (number-lines s nil))
064 ([^String s ^Failure e]
065 (let [l (-> e :line)
066 c (-> e :column)]
067 (join "\n"
068 (map #(str (format "%5d %s" (inc %1) %2)
069 (when (= l (inc %1))
070 (str "\n" (apply str (repeat c " ")) "^")))
071 (range)
072 (split s #"\n"))))))
073
074 (defn gsp
075 "Shortcut macro - the internals of read; or, if you like, read-string.
076 Argument `s` should be a string representation of a valid Lisp
077 expression."
078 [s]
079 (let [source (strip-line-comments s)
080 parse-tree (parse source)]
081 (if (instance? Failure parse-tree)
082 (doall (println (number-lines source parse-tree))
083 (throw (ex-info "Ne can forstande " (assoc parse-tree :source source))))
084 (generate (simplify parse-tree)))))
085
086 (defn- dummy-read-chars [prompt]
087 (loop [r "" p prompt]
088 (if (and (seq r)
089 (= (count (re-seq #"\(" r))
090 (count (re-seq #"\)" r)))
091 (= (count (re-seq #"\[" r))
092 (count (re-seq #"\]" r))))
093 r
094 (do
095 (print (str p " "))
096 (flush)
097 (recur (str r "\n" (read-line)) "::")))))
098
099 (defn read-from-console
100 "Attempt to read a complete lisp expression from the console.
101
102 There's a major problem here that the read-chars reader messes up testing.
103 We need to be able to disable it while testing!"
104 [prompt]
105 (if (:testing *options*)
106 (dummy-read-chars prompt)
107 (read-chars prompt)))
108
109 (defn READ
110 "An implementation of a Lisp reader sufficient for bootstrapping; not necessarily
111 the final Lisp reader. `input` should be either a string representation of a LISP
112 expression, or else an input stream. A single form will be read."
113 ([]
114 (gsp (read-from-console (:prompt *options*))))
115 ([input]
116 (cond
117 (empty? input) (READ)
118 (string? input) (gsp input)
119 (instance? InputStream input) (READ (slurp input))
120 :else (throw (ex-info "READ: `input` should be a string or an input stream" {})))))