Carp/core/Format.carp
2022-01-24 13:05:27 +01:00

108 lines
3.8 KiB
Plaintext

(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
%(String.slice s 0 (inc idx))
%(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))))