(hidden fmt-internal) (defndynamic fmt-internal [s args] (let [idx (String.index-of s \%) len (String.length s)] (if (= idx -1) (if (= (length args) 0) s ; no more splits found, just return string (macro-error (str "error in format string: too many arguments to format string (missing directive for '" (car args) "')"))) (if (= len 1) (macro-error "error in format string: expected expression after last %") (if (= \% (String.char-at s (inc idx))) ; this is an escaped % `(ref (String.append "%" %(fmt-internal (String.slice s (+ idx 2) len) args))) (if (= 0 (length args)) ; we need to insert something, but have nothing (macro-error (str "error in format string: not enough arguments to format string (missing argument for '%" (String.slice s (inc idx) (inc (inc idx))) "')")) ; okay, this is the meat: ; get the next % after our escaper (let [next (String.index-of (String.slice s (inc idx) len) \%)] (if (= -1 next) (if (< 1 (length args)) (macro-error (str "error in format string: too many arguments to format string (missing directive for '" (cadr args) "')")) `(ref (format %s %(car args)))) (let [slice (String.slice s 0 (+ (inc idx) next))] `(ref (String.append (ref (format %slice %(car args))) %(fmt-internal (String.slice s (+ (inc idx) next) len) (cdr args))))))))))))) (doc fmt "formats a string. It supports all of the string interpolations defined in format of the type that should be interpolated (e.g. %d and %x on integers).") (defmacro fmt [s :rest args] (list 'copy (fmt-internal s args))) (hidden f-parse-expr-string) (defndynamic f-parse-expr-string [s r] (if (= (String.length s) 0) -1 (let [h (String.head s)] (if (= h "\"") (inc r) (let [i (if (= h "\\") 2 1)] (f-parse-expr-string (String.suffix s i) (+ r i))))))) (hidden f-parse-expr) (defndynamic f-parse-expr [s idx r] (cond (= idx 0) r (= (String.length s) 0) -1 (let [h (String.head s) t (String.tail s)] (if (= h "\"") (let [l (f-parse-expr-string t 0)] (if (= l -1) -1 (f-parse-expr (String.suffix s (inc l)) idx (+ r (inc l))))) (f-parse-expr t (cond (= h "{") (inc idx) (= h "}") (dec idx) idx) (inc r)))))) (hidden f-internal) (defndynamic f-internal [s] (let [idx (String.index-of s \{) len (String.length s)] (cond (= idx -1) [(list 'copy s)] (= len 1) (macro-error "error in format string: expected expression after last {") (= \{ (String.char-at s (inc idx))) (append [(list 'copy (String.prefix s (inc idx)))] (f-internal (String.suffix s (+ 2 idx)))) (let [ss (String.suffix s (inc idx)) endx (f-parse-expr ss 1 0)] (if (= endx -1) (macro-error "error in format string: unclosed open bracket") (append [(list 'copy (String.prefix s idx)) (list 'str (parse (String.prefix ss (dec endx))))] (f-internal (String.suffix ss endx)))))))) (doc fstr "formats a string. It allows arbitrary expression to be intercalated. Example: ``` (def x 1) (def y \"hi\") (fstr \"this is x: {x}, and this is the first letter of y: {(head y)}\") ``") (defmacro fstr [s] (list 'String.concat (list 'ref (f-internal s))))