aboutsummaryrefslogtreecommitdiff
path: root/cljcc-compiler
diff options
context:
space:
mode:
authorYour Name <agrawalshagun07@gmail.com>2025-03-16 18:03:26 +0530
committerYour Name <agrawalshagun07@gmail.com>2025-03-16 18:03:26 +0530
commit32499638cef3c49ff686b19b5708d6b08712c526 (patch)
tree31f09287602d243b22660fe455e5174bdcc280b4 /cljcc-compiler
parent277319fa392f5ee9f21eedf2c4d224739f045690 (diff)
Refactor into cli and cljcc-compiler folders
Pass all tests. Fix babashka tasks and build setup. Repl is behaving in unexpected ways, otherwise working as expected.
Diffstat (limited to 'cljcc-compiler')
-rw-r--r--cljcc-compiler/src/cljcc/analyze/core.clj (renamed from cljcc-compiler/src/cljcc/analyze/core.cljc)0
-rw-r--r--cljcc-compiler/src/cljcc/analyze/label_loops.clj (renamed from cljcc-compiler/src/cljcc/analyze/label_loops.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/analyze/resolve.clj (renamed from cljcc-compiler/src/cljcc/analyze/resolve.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/analyze/typecheck.clj (renamed from cljcc-compiler/src/cljcc/analyze/typecheck.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/cljcc.clj (renamed from cljcc-compiler/src/cljcc/cljcc.cljc)4
-rw-r--r--cljcc-compiler/src/cljcc/compiler.clj (renamed from cljcc-compiler/src/cljcc/compiler.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/config.clj9
-rw-r--r--cljcc-compiler/src/cljcc/core/exception.clj (renamed from cljcc-compiler/src/cljcc/exception.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/core/exception.cljc34
-rw-r--r--cljcc-compiler/src/cljcc/core/format.clj1
-rw-r--r--cljcc-compiler/src/cljcc/core/format.cljc10
-rw-r--r--cljcc-compiler/src/cljcc/core/log.clj (renamed from cljcc-compiler/src/cljcc/core/log.cljc)0
-rw-r--r--cljcc-compiler/src/cljcc/driver.cljc139
-rw-r--r--cljcc-compiler/src/cljcc/emit.clj (renamed from cljcc-compiler/src/cljcc/emit.cljc)17
-rw-r--r--cljcc-compiler/src/cljcc/lexer.clj (renamed from cljcc-compiler/src/cljcc/lexer.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/log.cljc28
-rw-r--r--cljcc-compiler/src/cljcc/parser.clj (renamed from cljcc-compiler/src/cljcc/parser.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/schema.clj (renamed from cljcc-compiler/src/cljcc/schema.cljc)0
-rw-r--r--cljcc-compiler/src/cljcc/symbol.clj (renamed from cljcc-compiler/src/cljcc/symbol.cljc)0
-rw-r--r--cljcc-compiler/src/cljcc/tacky.clj (renamed from cljcc-compiler/src/cljcc/tacky.cljc)2
-rw-r--r--cljcc-compiler/src/cljcc/token.clj (renamed from cljcc-compiler/src/cljcc/token.cljc)0
-rw-r--r--cljcc-compiler/src/cljcc/util.clj (renamed from cljcc-compiler/src/cljcc/util.cljc)41
22 files changed, 30 insertions, 269 deletions
diff --git a/cljcc-compiler/src/cljcc/analyze/core.cljc b/cljcc-compiler/src/cljcc/analyze/core.clj
index 793b667..793b667 100644
--- a/cljcc-compiler/src/cljcc/analyze/core.cljc
+++ b/cljcc-compiler/src/cljcc/analyze/core.clj
diff --git a/cljcc-compiler/src/cljcc/analyze/label_loops.cljc b/cljcc-compiler/src/cljcc/analyze/label_loops.clj
index 56fffc9..152e696 100644
--- a/cljcc-compiler/src/cljcc/analyze/label_loops.cljc
+++ b/cljcc-compiler/src/cljcc/analyze/label_loops.clj
@@ -1,6 +1,6 @@
(ns cljcc.analyze.label-loops
(:require [cljcc.parser :as p]
- [cljcc.exception :as exc]
+ [cljcc.core.exception :as exc]
[cljcc.analyze.resolve :as r]
[cljcc.schema :as s]
[cljcc.util :as util]
diff --git a/cljcc-compiler/src/cljcc/analyze/resolve.cljc b/cljcc-compiler/src/cljcc/analyze/resolve.clj
index 9f09333..7db16b6 100644
--- a/cljcc-compiler/src/cljcc/analyze/resolve.cljc
+++ b/cljcc-compiler/src/cljcc/analyze/resolve.clj
@@ -1,5 +1,5 @@
(ns cljcc.analyze.resolve
- (:require [cljcc.exception :as exc]
+ (:require [cljcc.core.exception :as exc]
[cljcc.parser :as p]
[malli.dev.pretty :as pretty]
[cljcc.schema :as s]
diff --git a/cljcc-compiler/src/cljcc/analyze/typecheck.cljc b/cljcc-compiler/src/cljcc/analyze/typecheck.clj
index d1e79dc..4d6843f 100644
--- a/cljcc-compiler/src/cljcc/analyze/typecheck.cljc
+++ b/cljcc-compiler/src/cljcc/analyze/typecheck.clj
@@ -8,7 +8,7 @@
[clojure.core.match :refer [match]]
[cljcc.analyze.resolve :as r]
[cljcc.analyze.label-loops :as l]
- [cljcc.exception :as exc]
+ [cljcc.core.exception :as exc]
[cljcc.util :as u]))
(declare typecheck-block typecheck-declaration to-static-init)
diff --git a/cljcc-compiler/src/cljcc/cljcc.cljc b/cljcc-compiler/src/cljcc/cljcc.clj
index 742c1c2..fc088fc 100644
--- a/cljcc-compiler/src/cljcc/cljcc.cljc
+++ b/cljcc-compiler/src/cljcc/cljcc.clj
@@ -3,13 +3,12 @@
[cljcc.lexer :as lexer]
[cljcc.parser :as parser]
[cljcc.tacky :as tacky]
+ [cljcc.config :as config]
[cljcc.analyze.core :as analyzer]
[cljcc.compiler :as codegen]
[cljcc.emit :as emit])
(:gen-class))
-#?(:clj (set! *warn-on-reflection* true))
-
(def valid-os-targets #{:mac :linux})
(def valid-stages #{:lex :parse :validate :tacky :codegen :emit})
@@ -34,6 +33,7 @@
target-os (:os (:target merged-options))
_ (assert (stage valid-stages) "Invalid stage for compilation.")
_ (assert (target-os valid-os-targets) "Invalid operating system.")
+ _ (config/set-os target-os)
stages [lexer/lex parser/parse
analyzer/validate tacky/tacky-generate
codegen/assembly emit/emit]
diff --git a/cljcc-compiler/src/cljcc/compiler.cljc b/cljcc-compiler/src/cljcc/compiler.clj
index 39b3506..19e1780 100644
--- a/cljcc-compiler/src/cljcc/compiler.cljc
+++ b/cljcc-compiler/src/cljcc/compiler.clj
@@ -8,7 +8,7 @@
[malli.core :as m]
[malli.dev.pretty :as pretty]
[cljcc.util :as util]
- [cljcc.exception :as exc]))
+ [cljcc.core.exception :as exc]))
(def registers #{:ax :dx :di :si :r8 :r9 :r10 :r11 :cx :cl :sp})
diff --git a/cljcc-compiler/src/cljcc/config.clj b/cljcc-compiler/src/cljcc/config.clj
new file mode 100644
index 0000000..d5a6dc2
--- /dev/null
+++ b/cljcc-compiler/src/cljcc/config.clj
@@ -0,0 +1,9 @@
+(ns cljcc.config)
+
+(def config (atom {}))
+
+(defn set-os [os]
+ (swap! config assoc :os os))
+
+(defn get-os []
+ (or (get @config :os) :linux))
diff --git a/cljcc-compiler/src/cljcc/exception.cljc b/cljcc-compiler/src/cljcc/core/exception.clj
index 40ea930..412dfff 100644
--- a/cljcc-compiler/src/cljcc/exception.cljc
+++ b/cljcc-compiler/src/cljcc/core/exception.clj
@@ -1,4 +1,4 @@
-(ns cljcc.exception)
+(ns cljcc.core.exception)
(defn lex-error [{line :line col :col :as data}]
(throw (ex-info
diff --git a/cljcc-compiler/src/cljcc/core/exception.cljc b/cljcc-compiler/src/cljcc/core/exception.cljc
deleted file mode 100644
index 19245aa..0000000
--- a/cljcc-compiler/src/cljcc/core/exception.cljc
+++ /dev/null
@@ -1,34 +0,0 @@
-(ns cljcc.core.exception
- (:require [cljcc.core.format :refer [safe-format]]))
-
-(defn try-catch-ex
- ([f]
- (try
- (f)
- (catch #?(:clj Throwable :cljs :default) e
- [:error e])))
- ([f default]
- (try
- (f)
- (catch #?(:clj Throwable :cljs :default) e
- default))))
-
-(defn lex-error [{line :line col :col :as data}]
- (throw (ex-info
- (safe-format "Invalid token at line: %s, col: %s." line col)
- (merge {:error/type :lexer} data))))
-
-(defn parser-error [msg data]
- (throw (ex-info msg (merge {:error/type :parser} data))))
-
-(defn analyzer-error [msg data]
- (throw (ex-info msg (merge {:error/type :analyzer} data))))
-
-(defn tacky-error [msg data]
- (throw (ex-info msg (merge {:error/type :tacky} data))))
-
-(defn compiler-error [msg data]
- (throw (ex-info msg (merge {:error/type :compiler} data))))
-
-(defn emit-error [msg data]
- (throw (ex-info msg (merge {:error/type :emit} data))))
diff --git a/cljcc-compiler/src/cljcc/core/format.clj b/cljcc-compiler/src/cljcc/core/format.clj
new file mode 100644
index 0000000..7b3fc48
--- /dev/null
+++ b/cljcc-compiler/src/cljcc/core/format.clj
@@ -0,0 +1 @@
+(ns cljcc.core.format)
diff --git a/cljcc-compiler/src/cljcc/core/format.cljc b/cljcc-compiler/src/cljcc/core/format.cljc
deleted file mode 100644
index 851b6a1..0000000
--- a/cljcc-compiler/src/cljcc/core/format.cljc
+++ /dev/null
@@ -1,10 +0,0 @@
-(ns cljcc.core.format
- #?(:clj (:require [clojure.core :refer [format]])
- :cljs (:require [goog.string :as gstring]
- [goog.string.format])))
-
-(defn safe-format
- "Cross platform format."
- [fmt & args]
- #?(:clj (apply format fmt args)
- :cljs (apply gstring/format fmt args)))
diff --git a/cljcc-compiler/src/cljcc/core/log.cljc b/cljcc-compiler/src/cljcc/core/log.clj
index bb92b02..bb92b02 100644
--- a/cljcc-compiler/src/cljcc/core/log.cljc
+++ b/cljcc-compiler/src/cljcc/core/log.clj
diff --git a/cljcc-compiler/src/cljcc/driver.cljc b/cljcc-compiler/src/cljcc/driver.cljc
deleted file mode 100644
index 20d2d22..0000000
--- a/cljcc-compiler/src/cljcc/driver.cljc
+++ /dev/null
@@ -1,139 +0,0 @@
-(ns cljcc.driver
- (:require
- [clojure.java.io :as io]
- [cljcc.compiler :as c]
- [cljcc.tacky :as t]
- [cljcc.lexer :as l]
- [cljcc.emit :as e]
- [cljcc.analyze.core :as a]
- [clojure.pprint :as pp]
- [cljcc.log :as log]
- [cljcc.util :refer [get-os handle-sh mac-aarch64? make-file-name]]
- [cljcc.parser :as p]
- [clojure.string :as str]))
-
-(defn- validate-os []
- (let [os (get-os)]
- (condp = os
- :linux (log/info "Running on Linux.")
- :mac (if (mac-aarch64?)
- (log/info "Running on Mac ARM64.")
- (log/info "Running on Mac x86_64."))
- :unsupported (throw (Exception. (str os " is not currently supported."))))))
-
-(defn- remove-extension [^String filename]
- (if (.contains filename ".")
- (.substring filename 0 (.lastIndexOf filename "."))
- filename))
-
-(defn- preprocessor-step [directory filename]
- (let [input-file-path (make-file-name directory (remove-extension filename) "c")
- preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- output (handle-sh "gcc" "-E" "-P" input-file-path "-o" preprocessed-file-path)]
- (if (= 1 (:exit output))
- (throw (Exception. ^String (:err output)))
- (log/info (str "Successfully preprocessed file: " preprocessed-file-path)))))
-
-(defn- assemble-step [directory filename options]
- (let [file-without-ext (remove-extension filename)
- assembly-file (make-file-name directory file-without-ext "s")
- preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- assembly-ast (c/assembly-from-src source)
- assembly-output (e/emit assembly-ast)
- assembly-out-file-path (make-file-name directory (remove-extension filename) "s")
- _ (spit assembly-out-file-path assembly-output)
- output-file (if (:generate-object-file options)
- (str directory "/" (str file-without-ext ".o"))
- (str directory "/" file-without-ext))
- libs (str/join " " (:libs options))
- output (if (:generate-object-file options)
- (handle-sh "gcc" "-c" assembly-file "-o" output-file libs)
- (handle-sh "gcc" assembly-file "-o" output-file libs))]
- (if (= 1 (:exit output))
- (throw (Exception. ^String (:err output)))
- (log/info (str "Successfully created executable at: " output-file)))))
-
-(defn- parser-step [directory filename]
- (let [preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- ast (p/parse (l/lex source))]
- (log/info "Input file is succesfully parsed.")
- (pp/pprint ast)))
-
-(defn- semantic-analyzer-step [directory filename]
- (let [preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- ast (a/validate (p/parse (l/lex source)))]
- (log/info "Input file is succesfully validated.")
- (pp/pprint ast)))
-
-(defn- lexer-step [directory filename]
- (let [preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- output (l/lex source)]
- (log/info "Input file is succesfully lexed.")
- (pp/pprint output)))
-
-(defn- tacky-step [directory filename]
- (let [preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- output (t/tacky-generate (a/validate (p/parse (l/lex source))))]
- (log/info (str
- "Successfully generated Tacky IR.\n"
- (with-out-str (pp/pprint output))))))
-
-(defn- compiler-step [directory filename]
- (let [preprocessed-file-path (make-file-name directory (remove-extension filename) "i")
- file (io/file preprocessed-file-path)
- source (slurp file)
- assembly-ast (c/assembly-from-src source)]
- (log/info (str "Succesfully generated assembly ast.\n" assembly-ast))))
-
-(defn- cleanup-step [directory filename]
- (let [file-without-ext (remove-extension filename)]
- (io/delete-file (make-file-name directory file-without-ext "i") true)
- (io/delete-file (make-file-name directory file-without-ext "s") true)))
-
-(defn- create-steps [options directory filename]
- (let [steps [(partial validate-os)
- (partial preprocessor-step directory filename)
- (partial lexer-step directory filename)
- (partial parser-step directory filename)
- (partial semantic-analyzer-step directory filename)
- (partial tacky-step directory filename)
- (partial compiler-step directory filename)
- (partial assemble-step directory filename options)]]
- (cond
- (:lex options) (subvec steps 0 3)
- (:parse options) (subvec steps 0 4)
- (:validate options) (subvec steps 0 5)
- (:tacky options) (subvec steps 0 6)
- (:codegen options) (subvec steps 0 7)
- :else steps)))
-
-(defn- run-steps [options directory filename]
- (let [steps (create-steps options directory filename)]
- (run! #(apply % []) steps)))
-
-(defn run
- "Runs the compiler driver with the given input source file."
- [^String file-path options]
- (let [file (io/file ^String file-path)
- filename (.getName file)
- directory (.getParent file)]
- (try
- (run-steps options directory filename)
- (finally
- (cleanup-step directory filename)))))
-
-(comment
-
- (run "./test-programs/ex1.c" {})
-
- ())
diff --git a/cljcc-compiler/src/cljcc/emit.cljc b/cljcc-compiler/src/cljcc/emit.clj
index b4fdc13..6c1566e 100644
--- a/cljcc-compiler/src/cljcc/emit.cljc
+++ b/cljcc-compiler/src/cljcc/emit.clj
@@ -1,24 +1,23 @@
(ns cljcc.emit
(:require
- [cljcc.util :refer [get-os]]
[cljcc.compiler :as c]
- [cljcc.core.format :refer [safe-format]]
+ [cljcc.config :as config]
[clojure.string :as str]
[cljcc.core.exception :as exc]))
(defn- handle-label [identifier]
- (condp = (get-os)
+ (condp = (config/get-os)
:mac (str "L" identifier)
:linux (str ".L" identifier)
(throw (ex-info "Error in generating label." {}))))
(defn- handle-symbol-name [name]
- (if (= :mac (get-os))
+ (if (= :mac (config/get-os))
(str "_" name)
name))
(defn- handle-current-translation-unit [name ident->asm-entry]
- (if (= :mac (get-os))
+ (if (= :mac (config/get-os))
(handle-symbol-name name)
(if (get-in ident->asm-entry [name :defined?])
name
@@ -27,13 +26,13 @@
;;;; Operand Emit
(defn- imm-opernad-emit [operand _opts]
- (safe-format "$%d" (:value operand)))
+ (format "$%d" (:value operand)))
(defn- stack-operand-emit [operand _opts]
- (safe-format "%d(%%rbp)" (:value operand)))
+ (format "%d(%%rbp)" (:value operand)))
(defn- data-operand-emit [operand _opts]
- (safe-format "%s(%%rip)" (handle-symbol-name (:identifier operand))))
+ (format "%s(%%rip)" (handle-symbol-name (:identifier operand))))
(defn- register-operand [{:keys [register] :as operand} {register-width :register-width :or {register-width :4-byte}}]
(let [register->width->output {:ax {:8-byte "%rax"
@@ -290,7 +289,7 @@
(defn emit [{:keys [program backend-symbol-table]}]
(let [handle-os (fn [ast]
- (if (= :linux (get-os))
+ (if (= :linux (config/get-os))
(conj (conj (conj (vec ast) linux-assembly-end) "\n"))
ast))]
(->> program
diff --git a/cljcc-compiler/src/cljcc/lexer.cljc b/cljcc-compiler/src/cljcc/lexer.clj
index b2854cf..0bfa12f 100644
--- a/cljcc-compiler/src/cljcc/lexer.cljc
+++ b/cljcc-compiler/src/cljcc/lexer.clj
@@ -1,7 +1,7 @@
(ns cljcc.lexer
(:require
[cljcc.util :refer [newline? whitespace? read-number digit? letter-digit? letter? letter-digit-period?]]
- [cljcc.exception :as exc]
+ [cljcc.core.exception :as exc]
[cljcc.token :as t]))
(defn- lexer-ctx []
diff --git a/cljcc-compiler/src/cljcc/log.cljc b/cljcc-compiler/src/cljcc/log.cljc
deleted file mode 100644
index 3dbc4fb..0000000
--- a/cljcc-compiler/src/cljcc/log.cljc
+++ /dev/null
@@ -1,28 +0,0 @@
-(ns cljcc.log
- (:require [clojure.string :as str]))
-
-(def ^:private log-colors
- {:debug "\u001b[36m" ; Cyan
- :info "\u001b[32m" ; Green
- :warn "\u001b[33m" ; Yellow
- :error "\u001b[31m" ; Red
- :reset "\u001b[0m"}) ; Reset color
-
-(def reset-color (get log-colors :reset))
-
-(defn- log-message [level message]
- (let [color (get log-colors level)
- formatted-message (str color "[" (str/upper-case (name level)) "] " message reset-color)]
- (println formatted-message)))
-
-(defn debug [msg]
- (log-message :debug msg))
-
-(defn info [msg]
- (log-message :info msg))
-
-(defn warn [msg]
- (log-message :warn msg))
-
-(defn error [msg]
- (log-message :error msg))
diff --git a/cljcc-compiler/src/cljcc/parser.cljc b/cljcc-compiler/src/cljcc/parser.clj
index e505aa3..a2067ed 100644
--- a/cljcc-compiler/src/cljcc/parser.cljc
+++ b/cljcc-compiler/src/cljcc/parser.clj
@@ -536,8 +536,6 @@
(def file-path "./test-programs/example.c")
- #?(:clj (slurp "./test-programs/example.c"))
-
(-> file-path
slurp
parse-from-src)
diff --git a/cljcc-compiler/src/cljcc/schema.cljc b/cljcc-compiler/src/cljcc/schema.clj
index bf216f9..bf216f9 100644
--- a/cljcc-compiler/src/cljcc/schema.cljc
+++ b/cljcc-compiler/src/cljcc/schema.clj
diff --git a/cljcc-compiler/src/cljcc/symbol.cljc b/cljcc-compiler/src/cljcc/symbol.clj
index c410dac..c410dac 100644
--- a/cljcc-compiler/src/cljcc/symbol.cljc
+++ b/cljcc-compiler/src/cljcc/symbol.clj
diff --git a/cljcc-compiler/src/cljcc/tacky.cljc b/cljcc-compiler/src/cljcc/tacky.clj
index be60841..a771117 100644
--- a/cljcc-compiler/src/cljcc/tacky.cljc
+++ b/cljcc-compiler/src/cljcc/tacky.clj
@@ -3,7 +3,7 @@
[cljcc.lexer :as l]
[cljcc.util :as u]
[cljcc.parser :as p]
- [cljcc.exception :as exc]
+ [cljcc.core.exception :as exc]
[cljcc.symbol :as sym]
[malli.core :as m]
[malli.dev.pretty :as pretty]
diff --git a/cljcc-compiler/src/cljcc/token.cljc b/cljcc-compiler/src/cljcc/token.clj
index 213588c..213588c 100644
--- a/cljcc-compiler/src/cljcc/token.cljc
+++ b/cljcc-compiler/src/cljcc/token.clj
diff --git a/cljcc-compiler/src/cljcc/util.cljc b/cljcc-compiler/src/cljcc/util.clj
index 00f11dc..dee99ad 100644
--- a/cljcc-compiler/src/cljcc/util.cljc
+++ b/cljcc-compiler/src/cljcc/util.clj
@@ -1,8 +1,7 @@
(ns cljcc.util
- (:require [clojure.java.shell :refer [sh]]
- [clojure.string :as str]
- [cljcc.log :as log]
- [cljcc.exception :as exc]))
+ (:require
+ [clojure.string :as str]
+ [cljcc.core.exception :as exc]))
(def ^:private counter "Global integer counter for generating unique identifier names." (atom 0))
@@ -24,40 +23,6 @@
(defn reset-counter! []
(reset! counter 0))
-(defn make-file-name
- ([^String filename ^String ext]
- (str filename "." ext))
- ([directory filename ext]
- (str directory "/" filename "." ext)))
-
-(defn get-os []
- (let [os-name (.toLowerCase (System/getProperty "os.name"))]
- (cond
- (.contains os-name "mac") :mac
- (.contains os-name "linux") :linux
- :else :unsupported)))
-
-(defn mac-aarch64? []
- (and (= :mac (get-os)) (= (System/getProperty "os.arch") "aarch64")))
-
-(defn handle-sh
- "Preprends arch -x86_64 if running under Mac M chips."
- [command & args]
- (let [args (filterv (comp not empty?) args)]
- (if (mac-aarch64?)
- (apply sh "arch" "-x86_64" command args)
- (apply sh command args))))
-
-(defn exit
- ([status msg]
- (if (= status 0)
- (log/info msg)
- (log/error msg))
- (System/exit status))
- ([status msg e]
- (log/error (ex-data e))
- (exit status msg)))
-
(defn letter? [^Character ch]
(or (= \_ ch)
(Character/isLetter ch)))