aboutsummaryrefslogtreecommitdiff
path: root/src/cljcc/driver.clj
blob: 2869561ac92d70b0e979b81a2245d63fd9693561 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
(ns cljcc.driver
  (:require
   [clojure.java.io :as io]
   [cljcc.compiler :as c]
   [cljcc.tacky :as t]
   [cljcc.emit :as e]
   [clojure.pprint :as pp]
   [cljcc.log :as log]
   [cljcc.util :refer [get-os handle-sh mac-aarch64? make-file-name]]
   [cljcc.parser :as p]))

(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]
  (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/generate-assembly 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 (str directory "/" file-without-ext)
        output (handle-sh "gcc" assembly-file "-o" output-file)]
    (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)]
    (if (p/parseable? (p/parse source))
      (log/info "Input file is succesfully parsed.")
      (throw (Exception. "Failed during parsing")))))

(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 (p/parse 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/generate-assembly 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 [base-steps [(partial validate-os)
                    (partial preprocessor-step directory filename)]
        parser-step-fn (partial parser-step directory filename)
        compiler-step-fn (partial compiler-step directory filename)
        assemble-step-fn (partial assemble-step directory filename)
        tacky-step-fn (partial tacky-step directory filename)]
    (cond
      (:parse options) (concat base-steps [parser-step-fn])
      (:tacky options) (concat base-steps [parser-step-fn tacky-step-fn])
      (:codegen options) (concat base-steps [parser-step-fn tacky-step-fn compiler-step-fn])
      :else (concat base-steps
                    [parser-step-fn tacky-step-fn compiler-step-fn assemble-step-fn]))))

(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" {}))