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))))))))