001 (ns beowulf.core
002 "Essentially, the `-main` function and the bootstrap read-eval-print loop."
003 (:require [beowulf.bootstrap :refer [EVAL]]
004 [beowulf.io :refer [default-sysout SYSIN]]
005 [beowulf.oblist :refer [*options* NIL]]
006 [beowulf.read :refer [READ read-from-console]]
007 [clojure.java.io :as io]
008 [clojure.pprint :refer [pprint]]
009 [clojure.string :refer [trim]]
010 [clojure.tools.cli :refer [parse-opts]])
011 (:gen-class))
012
013 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
014 ;;;
015 ;;; Copyright (C) 2022-2023 Simon Brooke
016 ;;;
017 ;;; This program is free software; you can redistribute it and/or
018 ;;; modify it under the terms of the GNU General Public License
019 ;;; as published by the Free Software Foundation; either version 2
020 ;;; of the License, or (at your option) any later version.
021 ;;;
022 ;;; This program is distributed in the hope that it will be useful,
023 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
024 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 ;;; GNU General Public License for more details.
026 ;;;
027 ;;; You should have received a copy of the GNU General Public License
028 ;;; along with this program; if not, write to the Free Software
029 ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
030 ;;;
031 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
032
033 (def stop-word
034 "The word which, if submitted an an input line, will cause Beowulf to quit.
035 Question: should this be `forlǣte`?"
036 "STOP")
037
038 (def cli-options
039 [["-f FILEPATH" "--file-path FILEPATH"
040 "Set the path to the directory for reading and writing Lisp files."
041 :validate [#(and (.exists (io/file %))
042 (.isDirectory (io/file %))
043 (.canRead (io/file %))
044 (.canWrite (io/file %)))
045 "File path must exist and must be a directory."]]
046 ["-h" "--help"]
047 ["-p PROMPT" "--prompt PROMPT" "Set the REPL prompt to PROMPT"
048 :default "Sprecan::"]
049 ["-r SYSOUTFILE" "--read SYSOUTFILE" "Read Lisp system from file SYSOUTFILE"
050 :default default-sysout
051 :validate [#(and
052 (.exists (io/file %))
053 (.canRead (io/file %)))
054 "Could not find sysout file"]]
055 ["-s" "--strict" "Strictly interpret the Lisp 1.5 language, without extensions."]
056 ["-t" "--time" "Time evaluations."]
057 ["-x" "--testing" "Disable the jline reader - useful when piping input."]])
058
059 (defn- re
060 "Like REPL, but it isn't a loop and doesn't print."
061 [input]
062 (EVAL (READ input) NIL 0))
063
064 (defn repl
065 "Read/eval/print loop."
066 [prompt]
067 (loop []
068 (flush)
069 (try
070 (if-let [input (trim (read-from-console prompt))]
071 (if (= input stop-word)
072 (throw (ex-info "\nFærwell!" {:cause :quit}))
073 (println
074 (str "> "
075 (print-str (if (:time *options*)
076 (time (re input))
077 (re input))))))
078 (println))
079 (catch
080 Exception
081 e
082 (let [data (ex-data e)]
083 (println (.getMessage e))
084 (when
085 data
086 (case (:cause data)
087 :parse-failure (println (:failure data))
088 :strict nil ;; the message, which has already been printed, is enough.
089 :quit (throw e)
090 ;; default
091 (pprint data))))))
092 (recur)))
093
094 (defn -main
095 "Parse options, print the banner, read the init file if any, and enter the
096 read/eval/print loop."
097 [& opts]
098 (let [args (parse-opts opts cli-options)]
099 (println
100 (str
101 "\nHider wilcuman. Béowulf is mín nama.\n"
102 (when
103 (System/getProperty "beowulf.version")
104 (str "Síðe " (System/getProperty "beowulf.version") "\n"))
105 (when
106 (:help (:options args))
107 (:summary args))
108 (when (:errors args)
109 (apply str (interpose "; " (:errors args))))
110 "\nSprecan '" stop-word "' tó laéfan\n"))
111
112 (binding [*options* (:options args)]
113 ;; (pprint *options*)
114 (when (:read *options*)
115 (try (SYSIN (:read *options*))
116 (catch Throwable any
117 (println any))))
118 (try
119 (repl (:prompt (:options args)))
120 (catch
121 Exception
122 e
123 (let [data (ex-data e)]
124 (if
125 data
126 (case (:cause data)
127 :quit nil
128 ;; default
129 (do
130 (println "STÆFLEAHTER: " (.getMessage e))
131 (pprint data)))
132 (println e))))))))