Skip to content
This repository has been archived by the owner on Dec 5, 2019. It is now read-only.

Commit

Permalink
Merge branch 'Documentation'
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers committed Jul 12, 2012
2 parents caaa942 + 6ef08f6 commit c230ff4
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 60 deletions.
57 changes: 30 additions & 27 deletions src/objclj/codegen.clj
Expand Up @@ -8,17 +8,34 @@
;;;

(defmulti objc
"Translates an Objective-C AST into a string of Objective-C code."
"Translates an Objective-C element into a string of Objective-C code."
#(first %))

(defn escape [c]
"Escapes a single character, to create part of a valid Objective-C identifier."
(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 [sel]
(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)
Expand Down Expand Up @@ -57,23 +74,8 @@
(str "@selector(" s ")"))

(defmethod objc :identifier [[_ id]]
; TODO: do we need to escape initial digits?
(s/replace id #"[^a-zA-Z0-9_]" (comp escape char)))

(defn method-part [selparts args]
"Given remaining selector parts and arguments, generates the rest of an Objective-C message send."
(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)))))

(defmethod objc :message-expr [[_ obj sel args]]
(str "["
(objc obj)
Expand All @@ -84,21 +86,22 @@
(defmethod objc :nsarray-literal [[_ items]]
(objc [:message-expr [:identifier "NSArray"] "arrayWithObjects:" (concat items (list [:nil-literal]))]))

(defmethod objc nil [_]
"")
(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 [form]
"Generates an Objective-C AST from a Clojure form"
(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]
Expand Down Expand Up @@ -179,7 +182,7 @@
;;; API
;;;

(defn codegen [forms]
"Generates a string of Objective-C code from a sequence of Clojure forms"
(doall
(map #(objc (gen-form %)) forms)))
(defn codegen
"Generates a string of Objective-C code from a sequence of Clojure forms."
[forms]
(s/join "\n" (map #(objc (gen-form %)) forms)))
102 changes: 69 additions & 33 deletions src/objclj/reader.clj
Expand Up @@ -16,29 +16,35 @@
nil)

; TODO: resolve namespaced symbols
(defn symbol-form [sym]
"Returns a vector representing a symbol. sym should be a string."
(defn symbol-form
"Returns a vector representing a Clojure symbol. sym should be a string."
[sym]
[ :reader/symbol sym ])

; TODO: resolve double-colon keywords
(defn keyword-form [kwd]
"Returns a vector representing a keyword. kwd should be a string and should not include an initial colon."
(defn keyword-form
"Returns a vector representing a Clojure keyword. kwd should be a string and should not include an initial colon."
[kwd]
[ :reader/keyword kwd ])

(defn literal-form [x]
"Returns a vector representing a literal value. x may be a string, number, character, boolean, or nil. (Use keyword-form for keywords.)"
(defn literal-form
"Returns a vector representing a Clojure literal value. x may be a string, number, character, boolean, or nil. Use keyword-form for keywords."
[x]
[ :reader/literal x ])

(defn vector-form [items]
"Returns a vector representing a vector. items may be any kind of sequence."
(defn vector-form
"Returns a vector representing a Clojure vector. items may be any kind of sequence."
[items]
[ :reader/vector (vec items) ])

(defn list-form [items]
"Returns a vector representing a list. items may be any kind of sequence."
(defn list-form
"Returns a vector representing a Clojure list. items may be any kind of sequence."
[items]
[ :reader/list (vec items) ])

(defn map-form [pairs]
"Returns a vector representing a map. pairs may be any kind of sequence."
(defn map-form
"Returns a vector representing a Clojure map. pairs should be a sequence of two-item sequences."
[pairs]
(let [keys (map #(nth % 0) pairs)
vals (map #(nth % 1) pairs)]
[ :reader/map (vec keys) (vec vals) ]))
Expand All @@ -48,6 +54,7 @@
;;;

(defn whitespace? [c]
"Tests whether a character is considered whitespace in Clojure."
(or (= c \,) (Character/isWhitespace #^java.lang.Character c)))

;;;
Expand All @@ -56,47 +63,56 @@

(declare skip-whitespaces)

(defn oneOf [s]
"Parser that matches any one character in the given string."
(defn oneOf
"Parser that matches any one character in the given string. Returns the matched character."
[s]
(char (set s)))

(defmacro regex [pat]
(defmacro regex
"Parser that matches a regular expression. Returns the matched string."
[pat]
`(take-while1 #(re-matches pat %)))

(defmacro always-fn [fn & more]
"Parser that does not consume any input, and always returns the result of fn."
(defmacro always-fn
"Parser that does not consume any input, and always returns the result of invoking fn with the given arguments."
[fn & more]
`(always (~fn ~@more)))

(defn surrounded-by [p l r]
"Matches character l on the left side of p, and character r on the right side. Returns the result of parser p. Automatically skips spaces within the delimiters."
(defn surrounded-by
"Parser that matches character l on the left side of p, and character r on the right side. Returns the result of parser p. Automatically skips spaces within the delimiters."
[p l r]
(*> (char l)
(<* (>> skip-whitespaces p)
(char r))))

(defn parens [p]
"Matches parentheses around parser p. Returns the result of parser p."
(defn parens
"Parser that matches parentheses around parser p. Returns the result of parser p."
[p]
(surrounded-by p \( \)))

(defn brackets [p]
"Matches square brackets around parser p. Returns the result of parser p."
(defn brackets
"Parser that matches square brackets around parser p. Returns the result of parser p."
[p]
(surrounded-by p \[ \]))

(defn braces [p]
"Matches curly braces around parser p. Returns the result of parser p."
(defn braces
"Parser that matches curly braces around parser p. Returns the result of parser p."
[p]
(surrounded-by p \{ \}))

;;;
;;; Clojure-specific parsers
;;;

(defmacro match-escape-seq [seq rep]
(defmacro match-escape-seq
"Parser that matches a backslash followed by seq. Returns rep."
[seq rep]
`(<* (always ~rep)
(string (str \\ ~seq))))

(defn match-escape-seqs [seqmap]
(defn match-escape-seqs
"Parser that matches a backslash followed by any key in seqmap. Returns the value associated with the matched key, or the literal escape sequence if no match was found."
[seqmap]
(*> (char \\)
(<$> #(get seqmap % (str \\ %))
(oneOf (str (keys seqmap))))))
Expand All @@ -114,66 +130,81 @@
(<$> str (not-char \"))))

(def line-comment
"Parser that matches a line comment. Returns nil."
"Parser that matches a line comment. Returns empty-form."
(<* (always empty-form)
(char \;)
(many-till any-token
(<|> end-of-input eol))))

(def whitespace
(<|> (satisfy? whitespace?) line-comment))
"Parser that matches whitespace and comments. Returns empty-form."
(<* (always empty-form)
(<|> (satisfy? whitespace?) line-comment)))

(def skip-whitespaces
"Skips whitespace and comments."
(skip-many whitespace))

(def sym-special-char
"Parser that matches any non-alphanumeric character that is allowed in a symbol. Returns the matched character."
(oneOf "*+!-_?/.%&"))

(def sym-start
"Parser that matches any character that is allowed to begin a symbol. Returns the matched character."
(<|> letter sym-special-char))

(def sym-char
"Parser that matches any character that is allowed within (but not necessarily at the beginning of) a symbol. Returns the matched character."
(choice [sym-start
digit
(char \:)]))

(def sym
"Parser that matches a symbol. Returns a symbol-form."
(<$> #(symbol-form (str %1 %2))
sym-start
(<$> s/join (many sym-char))))

(def kwd
"Parser that matches a keyword. Returns a keyword-form."
(*> (char \:)
(<$> #(keyword-form (s/join %))
(many sym-char))))

(def nil-literal
"Parser that matches literal nil. Returns a literal-form containing nil."
(<* (always-fn literal-form nil)
(string "nil")))

(def true-literal
"Parser that matches literal true. Returns a literal-form containing true."
(<* (always-fn literal-form true)
(string "true")))

(def false-literal
"Parser that matches literal false. Returns a literal-form containing false."
(<* (always-fn literal-form false)
(string "false")))

(def number-literal
"Parser that matches a literal number. Returns a literal-form containing the number."
; TODO: BigDecimals
; TODO: ratios
(<$> literal-form number))

(def string-literal
"Parser that matches a literal string. Returns a literal-form containing the string."
(<$> #(literal-form (s/join %))
(around (char \") (many char-in-string))))

(defn special-char-literal [ch name]
"Parser that matches the name of a special character literal. Returns ch."
(defn special-char-literal
"Parser that matches reserved character literal name. Returns ch."
[ch name]
(<* (always-fn literal-form ch)
(string name)))

(def char-literal
"Parser that matches a literal character. Returns a literal-form containing the character."
(*> (char \\)
(choice [(special-char-literal \tab "tab")
(special-char-literal \space "space")
Expand All @@ -183,26 +214,31 @@
(declare form)

(defn lst []
"Parser that matches a list. Returns a list-form."
(<$> list-form
(parens (many form))))

(defn vector-literal []
"Parser that matches a vector. Returns a vector-form."
(<$> vector-form
(brackets (many form))))

(defn map-literal []
"Parser that matches a map. Returns a map-form."
(<$> map-form
(braces (many (replicate 2 form)))))

;; TODO: implement reader macros:
;; ' @ ^ #{} #"" #' #() #_ ` ~ ~@

(def form
"Parser that matches any Clojure form."
(>> skip-whitespaces
(choice [nil-literal true-literal false-literal number-literal string-literal char-literal
(lst) (vector-literal) (map-literal)
kwd sym])))

(defn parse [str]
"Parses a string of Clojure code into an AST"
(defn parse
"Parses a string of Clojure code into an AST. Returns a sequence of forms."
[str]
(-> (parse-once (many form) str) :result))

0 comments on commit c230ff4

Please sign in to comment.