From 3a3b0ac238a50fd36b4f5e4f05bc88545433fca9 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 11 Jul 2012 23:34:32 -0700 Subject: [PATCH 1/3] Fix position of docstrings (supposed to come before any parameter list) --- src/objclj/codegen.clj | 15 ++++++---- src/objclj/reader.clj | 64 +++++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/objclj/codegen.clj b/src/objclj/codegen.clj index ca0c602..ee867d7 100644 --- a/src/objclj/codegen.clj +++ b/src/objclj/codegen.clj @@ -11,12 +11,14 @@ "Translates an Objective-C AST into a string of Objective-C code." #(first %)) -(defn escape [c] +(defn escape "Escapes a single character, to create part of a valid Objective-C identifier." + [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)) ;; Expressions @@ -60,8 +62,9 @@ ; TODO: do we need to escape initial digits? (s/replace id #"[^a-zA-Z0-9_]" (comp escape char))) -(defn method-part [selparts args] +(defn method-part "Given remaining selector parts and arguments, generates the rest of an Objective-C message send." + [selparts args] (str (cond (empty? selparts) (str ", " (s/join ", " args)) (empty? args) (str " " (first selparts)) @@ -97,8 +100,9 @@ (defpred char? char?) (defpred string? string?) -(defn gen-form [form] +(defn gen-form "Generates an Objective-C AST from a Clojure form" + [form] (match form [:reader/literal true] [:bool-literal true] [:reader/literal false] [:bool-literal false] @@ -179,7 +183,8 @@ ;;; API ;;; -(defn codegen [forms] +(defn codegen "Generates a string of Objective-C code from a sequence of Clojure forms" + [forms] (doall (map #(objc (gen-form %)) forms))) diff --git a/src/objclj/reader.clj b/src/objclj/reader.clj index d0deb66..d8ed534 100644 --- a/src/objclj/reader.clj +++ b/src/objclj/reader.clj @@ -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) ])) @@ -48,6 +54,7 @@ ;;; (defn whitespace? [c] + "Tests whether a character is considered whitespace in Clojure." (or (= c \,) (Character/isWhitespace #^java.lang.Character c))) ;;; @@ -56,47 +63,56 @@ (declare skip-whitespaces) -(defn oneOf [s] +(defn oneOf "Parser that matches any one character in the given string." + [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] +(defmacro always-fn "Parser that does not consume any input, and always returns the result of fn." + [fn & more] `(always (~fn ~@more))) -(defn surrounded-by [p l r] +(defn surrounded-by "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] +(defn parens "Matches parentheses around parser p. Returns the result of parser p." + [p] (surrounded-by p \( \))) -(defn brackets [p] +(defn brackets "Matches square brackets around parser p. Returns the result of parser p." + [p] (surrounded-by p \[ \])) -(defn braces [p] +(defn braces "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)))))) @@ -168,8 +184,9 @@ (<$> #(literal-form (s/join %)) (around (char \") (many char-in-string)))) -(defn special-char-literal [ch name] +(defn special-char-literal "Parser that matches the name of a special character literal. Returns ch." + [ch name] (<* (always-fn literal-form ch) (string name))) @@ -203,6 +220,7 @@ (lst) (vector-literal) (map-literal) kwd sym]))) -(defn parse [str] +(defn parse "Parses a string of Clojure code into an AST" + [str] (-> (parse-once (many form) str) :result)) From 58a6658fa54e48aa8c24e84e5ef2f2830bd959fa Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 11 Jul 2012 23:43:26 -0700 Subject: [PATCH 2/3] Fleshed out reader documentation --- src/objclj/reader.clj | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/objclj/reader.clj b/src/objclj/reader.clj index d8ed534..b1b84ef 100644 --- a/src/objclj/reader.clj +++ b/src/objclj/reader.clj @@ -64,7 +64,7 @@ (declare skip-whitespaces) (defn oneOf - "Parser that matches any one character in the given string." + "Parser that matches any one character in the given string. Returns the matched character." [s] (char (set s))) @@ -74,29 +74,29 @@ `(take-while1 #(re-matches pat %))) (defmacro always-fn - "Parser that does not consume any input, and always returns the result of 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 - "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." + "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 - "Matches parentheses around parser p. Returns the result of parser p." + "Parser that matches parentheses around parser p. Returns the result of parser p." [p] (surrounded-by p \( \))) (defn brackets - "Matches square brackets around parser p. Returns the result of parser p." + "Parser that matches square brackets around parser p. Returns the result of parser p." [p] (surrounded-by p \[ \])) (defn braces - "Matches curly braces around parser p. Returns the result of parser p." + "Parser that matches curly braces around parser p. Returns the result of parser p." [p] (surrounded-by p \{ \})) @@ -130,67 +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 - "Parser that matches the name of a special character literal. Returns ch." + "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") @@ -200,14 +214,17 @@ (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))))) @@ -215,12 +232,13 @@ ;; ' @ ^ #{} #"" #' #() #_ ` ~ ~@ (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 - "Parses a string of Clojure code into an AST" + "Parses a string of Clojure code into an AST. Returns a sequence of forms." [str] (-> (parse-once (many form) str) :result)) From 6ef08f6c25ec79d2e8f019903a4d70de0f1ad6cf Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 11 Jul 2012 23:49:36 -0700 Subject: [PATCH 3/3] Fleshed out codegen documentation --- src/objclj/codegen.clj | 46 ++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/objclj/codegen.clj b/src/objclj/codegen.clj index ee867d7..ee19aa3 100644 --- a/src/objclj/codegen.clj +++ b/src/objclj/codegen.clj @@ -8,11 +8,11 @@ ;;; (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 - "Escapes a single character, to create part of a valid Objective-C identifier." + "Escapes a single character, to create part of a valid Objective-C identifier. Returns a string." [c] (str "S" (int c))) @@ -21,6 +21,21 @@ [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) @@ -59,24 +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 - "Given remaining selector parts and arguments, generates the rest of an Objective-C message send." - [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))))) - (defmethod objc :message-expr [[_ obj sel args]] (str "[" (objc obj) @@ -87,13 +86,13 @@ (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?) @@ -101,7 +100,7 @@ (defpred string? string?) (defn gen-form - "Generates an Objective-C AST from a Clojure 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] @@ -184,7 +183,6 @@ ;;; (defn codegen - "Generates a string of Objective-C code from a sequence of Clojure forms" + "Generates a string of Objective-C code from a sequence of Clojure forms." [forms] - (doall - (map #(objc (gen-form %)) forms))) + (s/join "\n" (map #(objc (gen-form %)) forms)))