aboutsummaryrefslogtreecommitdiff
path: root/cljcc-compiler
diff options
context:
space:
mode:
authorYour Name <agrawalshagun07@gmail.com>2025-03-16 14:53:45 +0530
committerYour Name <agrawalshagun07@gmail.com>2025-03-16 14:53:45 +0530
commit277319fa392f5ee9f21eedf2c4d224739f045690 (patch)
treef2e3a89de7946647d7560db242e7ce22fda25a5c /cljcc-compiler
parent39b6930e14cfda58fd066805f5da447c685ab67f (diff)
Add common functions for handling cljcc compiler
Diffstat (limited to 'cljcc-compiler')
-rw-r--r--cljcc-compiler/deps.edn4
-rw-r--r--cljcc-compiler/src/cljcc/cljcc.cljc89
-rw-r--r--cljcc-compiler/src/cljcc/core/exception.cljc34
-rw-r--r--cljcc-compiler/src/cljcc/core/format.cljc10
-rw-r--r--cljcc-compiler/src/cljcc/core/log.cljc28
-rw-r--r--cljcc-compiler/src/cljcc/emit.cljc11
-rw-r--r--cljcc-compiler/src/cljcc/lexer.cljc3
-rw-r--r--cljcc-compiler/src/cljcc/parser.cljc6
-rw-r--r--cljcc-compiler/src/cljcc/util.cljc2
9 files changed, 120 insertions, 67 deletions
diff --git a/cljcc-compiler/deps.edn b/cljcc-compiler/deps.edn
new file mode 100644
index 0000000..52fd6d7
--- /dev/null
+++ b/cljcc-compiler/deps.edn
@@ -0,0 +1,4 @@
+{:paths ["src"]
+ :deps {org.clojure/clojure {:mvn/version "1.11.1"}
+ org.clojure/core.match {:mvn/version "1.1.0"}
+ metosin/malli {:mvn/version "0.16.4"}}}
diff --git a/cljcc-compiler/src/cljcc/cljcc.cljc b/cljcc-compiler/src/cljcc/cljcc.cljc
index c067b75..742c1c2 100644
--- a/cljcc-compiler/src/cljcc/cljcc.cljc
+++ b/cljcc-compiler/src/cljcc/cljcc.cljc
@@ -1,11 +1,18 @@
(ns cljcc.cljcc
(:require
- [clojure.tools.cli :refer [parse-opts]]
- [clojure.string :as string]
- [cljcc.util :refer [exit]]
- [cljcc.driver :as d])
+ [cljcc.lexer :as lexer]
+ [cljcc.parser :as parser]
+ [cljcc.tacky :as tacky]
+ [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})
+
(defn run
"Compiles source input using specified compiler options.
@@ -13,54 +20,30 @@
source - Source C file.
options - Map of compiler configuration options.
- Returns generated AST for specified stage."
- [source & {:keys [config] :or {config {}}}]
- (let [default-config {:target {:os :linux}}]))
-
-(set! *warn-on-reflection* true)
-
-(defn usage [options-summary]
- (->>
- ["Usage: ./cljcc path/to/file.c [options]"
- ""
- "Options:"
- options-summary]
- (string/join \newline)))
+ Options map:
+ :target - Map of target platform settings
+ :os - Target OS #{:linux :mac}
+ :stage - Which stage to generate #{:lex :parse :validate :tacky :codegen :emit}
-(def cli-options
- [[nil "--lex" "Runs lexer. Does not emit any files."]
- [nil "--parse" "Runs parser. Does not emit any files."]
- [nil "--validate" "Runs semantic analyzer. Does not emit any files."]
- [nil "--tacky" "Runs tacky generation. Does not emit any files."]
- [nil "--codegen" "Runs compiler. Does not emit any files."]
- ["-c" nil "Generate object file."
- :id :generate-object-file]
- ["-h" "--help"]])
-
-(defn validate-args [args]
- (let [{:keys [options arguments summary]} (parse-opts args cli-options)]
- (cond
- (:help options) {:exit-message (usage summary) :ok? true}
- (= 1 (count arguments)) {:file-path (first arguments)
- :options options}
- :else {:exit-message (usage summary)})))
-
-(defn -main
- "Main entrypoint for cljcc compiler."
- [& args]
- (let [{:keys [file-path exit-message ok? options]} (validate-args args)
- libs (filterv (fn [v] (and
- (string? v)
- (re-matches #"-l.+" v)))
- args)]
- (if exit-message
- (exit (if ok? 0 1) exit-message)
- (try
- (d/run file-path (assoc options :libs libs))
- (exit 0 "Successfully executed.")
- (catch Exception e
- (exit 1 (ex-message e) e))))))
-
-(comment
+ Returns generated AST for specified stage."
+ [source & {:keys [options] :or {options {}}}]
+ (let [default-options {:target {:os :linux}
+ :stage :emit}
+ merged-options (merge default-options options)
+ stage (:stage merged-options)
+ target-os (:os (:target merged-options))
+ _ (assert (stage valid-stages) "Invalid stage for compilation.")
+ _ (assert (target-os valid-os-targets) "Invalid operating system.")
+ stages [lexer/lex parser/parse
+ analyzer/validate tacky/tacky-generate
+ codegen/assembly emit/emit]
+ stage-idx (condp = stage
+ :lex 1
+ :parse 2
+ :validate 3
+ :tacky 4
+ :codegen 5
+ :emit 6)
+ stages-to-run (vec (take stage-idx stages))]
+ (reduce (fn [acc f] (f acc)) source stages-to-run)))
- ())
diff --git a/cljcc-compiler/src/cljcc/core/exception.cljc b/cljcc-compiler/src/cljcc/core/exception.cljc
new file mode 100644
index 0000000..19245aa
--- /dev/null
+++ b/cljcc-compiler/src/cljcc/core/exception.cljc
@@ -0,0 +1,34 @@
+(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.cljc b/cljcc-compiler/src/cljcc/core/format.cljc
new file mode 100644
index 0000000..851b6a1
--- /dev/null
+++ b/cljcc-compiler/src/cljcc/core/format.cljc
@@ -0,0 +1,10 @@
+(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.cljc
new file mode 100644
index 0000000..bb92b02
--- /dev/null
+++ b/cljcc-compiler/src/cljcc/core/log.cljc
@@ -0,0 +1,28 @@
+(ns cljcc.core.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/emit.cljc b/cljcc-compiler/src/cljcc/emit.cljc
index 0686b31..b4fdc13 100644
--- a/cljcc-compiler/src/cljcc/emit.cljc
+++ b/cljcc-compiler/src/cljcc/emit.cljc
@@ -2,8 +2,9 @@
(:require
[cljcc.util :refer [get-os]]
[cljcc.compiler :as c]
+ [cljcc.core.format :refer [safe-format]]
[clojure.string :as str]
- [cljcc.exception :as exc]))
+ [cljcc.core.exception :as exc]))
(defn- handle-label [identifier]
(condp = (get-os)
@@ -26,13 +27,13 @@
;;;; Operand Emit
(defn- imm-opernad-emit [operand _opts]
- (format "$%d" (:value operand)))
+ (safe-format "$%d" (:value operand)))
(defn- stack-operand-emit [operand _opts]
- (format "%d(%%rbp)" (:value operand)))
+ (safe-format "%d(%%rbp)" (:value operand)))
(defn- data-operand-emit [operand _opts]
- (format "%s(%%rip)" (handle-symbol-name (:identifier operand))))
+ (safe-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"
@@ -95,7 +96,7 @@
([operand opts]
(if-let [[_ operand-emit-fn] (find operand-emitters (:operand operand))]
(operand-emit-fn operand opts)
- (throw (AssertionError. (str "Invalid operand: " operand))))))
+ (exc/emit-error "Invalid operand" {:operand operand}))))
;;;; Instruction Emit
diff --git a/cljcc-compiler/src/cljcc/lexer.cljc b/cljcc-compiler/src/cljcc/lexer.cljc
index ef4235f..b2854cf 100644
--- a/cljcc-compiler/src/cljcc/lexer.cljc
+++ b/cljcc-compiler/src/cljcc/lexer.cljc
@@ -9,8 +9,6 @@
:line 1
:col 1})
-(set! *warn-on-reflection* true)
-
(defn lex
([source]
(lex source (lexer-ctx)))
@@ -75,7 +73,6 @@
}
")
-
(lex
"
int main(void) {
diff --git a/cljcc-compiler/src/cljcc/parser.cljc b/cljcc-compiler/src/cljcc/parser.cljc
index f8d039d..e505aa3 100644
--- a/cljcc-compiler/src/cljcc/parser.cljc
+++ b/cljcc-compiler/src/cljcc/parser.cljc
@@ -6,13 +6,11 @@
[clojure.set :refer [union]]
[malli.dev.pretty :as pretty]
[cljcc.schema :as s]
- [cljcc.exception :as exc]
+ [cljcc.core.exception :as exc]
[cljcc.util :as u]))
(declare parse parse-exp parse-statement parse-block expect parse-declaration parse-variable-declaration)
-(set! *warn-on-reflection* true)
-
(def valid-declaration-starts
(union t/type-specifier-keywords t/storage-specifier-keywords))
@@ -538,7 +536,7 @@
(def file-path "./test-programs/example.c")
- (slurp "./test-programs/example.c")
+ #?(:clj (slurp "./test-programs/example.c"))
(-> file-path
slurp
diff --git a/cljcc-compiler/src/cljcc/util.cljc b/cljcc-compiler/src/cljcc/util.cljc
index 4c56ab9..00f11dc 100644
--- a/cljcc-compiler/src/cljcc/util.cljc
+++ b/cljcc-compiler/src/cljcc/util.cljc
@@ -6,8 +6,6 @@
(def ^:private counter "Global integer counter for generating unique identifier names." (atom 0))
-(set! *warn-on-reflection* true)
-
(defn create-identifier!
"Returns a unique identifier. Used for generating unique identifier.