This repository has been archived by the owner on Dec 5, 2019. It is now read-only.
/
codegen.clj
188 lines (140 loc) · 5.28 KB
/
codegen.clj
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
(ns objclj.codegen
(:use [clojure.core.match :only [match defpred]])
(:require [clojure.string :as s])
(:require [objclj.reader :as reader]))
;;;
;;; Objective-C ASTs and generation
;;;
(defmulti objc
"Translates an Objective-C element into a string of Objective-C code."
#(first %))
(defn escape
"Escapes a single character, to create part of a valid Objective-C identifier. Returns a string."
[c]
(str "S" (int c)))
(defn sel-parts
"Splits a selector into its constituent parts, keeping any colons. Returns a sequence of strings."
[sel]
(re-seq #"[a-zA-Z0-9_]+\:?" sel))
(defn method-part
"Given remaining selector parts and arguments, returns a string representing the rest of an Objective-C message send. selparts and args should both be sequences of strings."
[selparts args]
(str
(cond (empty? selparts) (str ", " (s/join ", " args))
(empty? args) (str " " (first selparts))
:else (str " " (first selparts) (first args)))
; If we had both a selector part and an argument this time,
(if (and (and (seq selparts) (seq args))
; ... and we have at least one more of either
(or (next selparts) (next args)))
; ... recur
(method-part (next selparts) (next args)))))
;; Expressions
(derive ::void-expr ::expr)
(derive ::nil-literal ::expr)
(derive ::null-literal ::expr)
(derive ::bool-literal ::expr)
(derive ::number-literal ::expr)
(derive ::character-literal ::expr)
(derive ::nsstring-literal ::expr)
(derive ::selector-literal ::expr)
(derive ::identifier ::expr)
(derive ::message-expr ::expr)
(derive ::nsarray-literal ::expr)
(defmethod objc :void-expr [_]
"((void)0)")
(defmethod objc :nil-literal [_]
"((id)nil)")
(defmethod objc :null-literal [_]
"NULL")
(defmethod objc :bool-literal [[_ b]]
(if b "@YES" "@NO"))
(defmethod objc :number-literal [[_ n]]
(str "@" n))
(defmethod objc :character-literal [[_ c]]
(str "@'" c "'"))
(defmethod objc :nsstring-literal [[_ s]]
(str "@\"" s \"""))
(defmethod objc :selector-literal [[_ s]]
(str "@selector(" s ")"))
(defmethod objc :identifier [[_ id]]
(s/replace id #"[^a-zA-Z0-9_]" (comp escape char)))
(defmethod objc :message-expr [[_ obj sel args]]
(str "["
(objc obj)
(method-part (sel-parts sel)
(map objc args))
"]"))
(defmethod objc :nsarray-literal [[_ items]]
(objc [:message-expr [:identifier "NSArray"] "arrayWithObjects:" (concat items (list [:nil-literal]))]))
(defmethod objc :default [_] nil)
;;;
;;; Translating forms to Objective-C
;;;
;; Predicates for core.match
(defpred number? number?)
(defpred symbol? symbol?)
(defpred keyword? keyword?)
(defpred char? char?)
(defpred string? string?)
(defn gen-form
"Generates and returns an Objective-C element from a Clojure form. The returned value is suitable for later being passed to the objc function."
[form]
(match form
[:reader/literal true] [:bool-literal true]
[:reader/literal false] [:bool-literal false]
[:reader/literal (n :when number?)] [:number-literal n]
[:reader/literal (c :when char?)] [:character-literal c]
[:reader/literal (s :when string?)] [:nsstring-literal s]
; TODO: emit EXTNil
;[:reader/literal nil]
[:reader/symbol sym] [:identifier sym]
; TODO: intern a selector
;[:reader/keyword sym]
;; TODO: it's probable that some of these need to be taken care of before hitting the backend
; TODO
;[:reader/list [[:reader/symbol "def"] [:reader/symbol sym] & init?]]
; TODO
;[:reader/list [[:reader/symbol "if"] test then & else?]]
; TODO
;[:reader/list [[:reader/symbol "do"] & exprs]]
; TODO
;[:reader/list [[:reader/symbol "let"] [:reader/vector bindings] & exprs]]
; TODO
;[:reader/list [[:reader/symbol "quote"] form]]
; TODO
;[:reader/list [[:reader/symbol "var"] [:reader/symbol sym]]]
; TODO
;[:reader/list [[:reader/symbol "fn"] [:reader/vector params] & exprs]]
;[:reader/list [[:reader/symbol "fn"] & overloads]]
; TODO
;[:reader/list [[:reader/symbol "loop"] [:reader/vector bindings] & exprs]]
; TODO
;[:reader/list [[:reader/symbol "recur"] & exprs]]
; TODO
;[:reader/list [[:reader/symbol "throw"] expr]]
; TODO
;[:reader/list [[:reader/symbol "try"] & exprs]]
; TODO
;[:reader/list [[:reader/symbol "monitor-enter"] x]]
; TODO
;[:reader/list [[:reader/symbol "monitor-exit"] x]]
[:reader/list [[:reader/symbol "."] obj & args]]
(let [[seltype sel] (first args)
args (next args)]
(if (= :reader/keyword seltype)
; TODO: support non-literal selectors
(concat [:message-expr (gen-form obj) sel] [(map gen-form args)])))
; TODO
;[:reader/list & exprs]
[:reader/vector items] [:nsarray-literal (map gen-form items)]
; TODO: emit NSDictionary literal
;[:reader/map keys values]
_ nil))
;;;
;;; API
;;;
(defn codegen
"Generates a string of Objective-C code from a sequence of Clojure forms."
[forms]
(s/join "\n" (map #(objc (gen-form %)) forms)))