format.lisp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. (import core/prelude ())
  2. (import core/base (type#))
  3. (defun display (x) :hidden
  4. (cond
  5. [(= (type# x) "string") x]
  6. [(and (= (type# x) "table")
  7. (= (.> x :tag) "string"))
  8. (.> x :value)]
  9. [else (pretty x)]))
  10. (defun format-output! (out buf) :hidden
  11. (cond
  12. [(= out nil) buf]
  13. [(= out true) (print! buf)]
  14. [(number? out) (error! buf out)]
  15. [(function? out) (out buf)]
  16. [else ((.> out :write) out buf)]))
  17. (defun str->sym (x) :hidden { :tag "symbol" :contents x })
  18. (defun name-terminator-char? (c) :hidden
  19. (or (= c ":")
  20. (= c "%")))
  21. (defun parse-format-reference (str last-pos-ref) :hidden
  22. "Parse the formatting reference STR."
  23. (let* [((val end)
  24. (cond
  25. [(= (string/char-at str 1) "$")
  26. (let* [((start end match)
  27. (string/find str "%$(%d+)"))]
  28. (values-list (list 'positional (tonumber match)) end))]
  29. [(string/find (string/char-at str 1) "[a-zA-Z]")
  30. (let* [((start end match)
  31. (string/find str "([a-zA-Z][^:%%@]*)"))]
  32. (values-list (list 'named match) end))]
  33. [(string/find (string/char-at str 1) "#")
  34. (let* [((start end match)
  35. (string/find str "#([a-zA-Z][^:%%@]*)"))]
  36. (values-list (list 'implicitly-named match) end))]
  37. [(string/find (string/char-at str 1) "[:%%@]") ; anonymous, but with a formatter
  38. (let* [(pos (.> last-pos-ref 1))]
  39. (.<! last-pos-ref 1 (+ pos 1))
  40. (values-list (list 'positional pos) 0))]
  41. [else
  42. (let* [(pos (.> last-pos-ref 1))]
  43. (.<! last-pos-ref 1 (+ pos 1))
  44. (values-list (list 'positional pos) 1))]))]
  45. (values-list val (string/sub str (+ 1 end)))))
  46. (defun display-with-sep (sep frag) :hidden
  47. (let* [(arg (gensym))
  48. (li (gensym))
  49. (bd (compile-format-fragment frag (lambda (x) arg)))]
  50. `(lambda (,li)
  51. (string/concat (map (lambda (,arg) ,bd) ,li) ,sep))))
  52. (defun parse-format-formatter (spec) :hidden
  53. "Parse the formatter specifier in SPEC, returning a continuation that,
  54. when applied, returns a formatting fragment."
  55. (cond
  56. [(string/find spec "^:")
  57. (lambda (ref) (list 'urn-format ref (str->sym (string/sub spec 2))))]
  58. [(string/find spec "^%%")
  59. (lambda (ref) (list 'printf-format ref spec))]
  60. [(string/find spec "^@")
  61. (let* [((start end sep fmtr)
  62. (string/find spec "^@(%b())(.*)$"))
  63. (k (parse-format-formatter fmtr))]
  64. (lambda (ref)
  65. (list 'urn-format ref (display-with-sep (string/sub sep 2 (- (n sep) 1)) (k 'ignored)))))]
  66. [(= spec "") (lambda (ref) (list 'urn-format ref `display))]))
  67. (defun handle-formatting-specifier (spec last-pos) :hidden
  68. "Parse the entirety of the format specifier SPEC."
  69. (let* [((ref spec) (parse-format-reference spec last-pos))
  70. ((formatter spec) (parse-format-formatter spec))]
  71. (formatter ref)))
  72. (defun parse-format (str) :hidden
  73. "Parse the format string STR into a list of fragments."
  74. (let* [(cur "") ; the current character (for convenience)
  75. (buf "") ; a buffer
  76. ; we don't have pointers, so we keep it in a table.
  77. (last-positional { 1 1 }) ; for anonymous positions
  78. (frags '())]
  79. (loop [(i 1)]
  80. [(> i (n str))
  81. (when (/= buf "") ; it isn't empty, so we flush it again
  82. (push! frags (list 'literal buf)))]
  83. (set! cur (string/char-at str i))
  84. (case cur
  85. ["{" ; a formatting fragment (splice)
  86. (push! frags (list 'literal buf)) ; flush the buffer
  87. (set! buf "")
  88. (loop [(parsed "")
  89. (j (+ 1 i))]
  90. [(or (>= j (n str))
  91. (= (string/char-at str j) "}"))
  92. (set! i j)
  93. (let* [(frag (handle-formatting-specifier parsed last-positional))]
  94. (push! frags frag))]
  95. (recur (.. parsed (string/char-at str j)) (+ 1 j)))]
  96. [else => (set! buf (.. buf it))])
  97. (recur (+ 1 i)))
  98. frags))
  99. (defun compile-format-fragment (frag resolve-spec) :hidden
  100. (case frag
  101. [(literal ?x) x]
  102. [(urn-format ?spec ?fmtr)
  103. `(,fmtr ,(resolve-spec spec))]
  104. [(printf-format ?spec ?fmt)
  105. `(string/format ,fmt ,(resolve-spec spec))]))
  106. (defmacro format (out str &args)
  107. "Output the string STR formatted against ARGS to the stream OUT. In
  108. the case OUT is nil, a string in returned; If OUT is true, the result
  109. is printed to standard output.
  110. ### Formatting specifiers
  111. Formatting specifiers take the form `{...}`, where `...` includes
  112. both a _reference_ (what's to be output) and a _formatter_ (how to
  113. output it).
  114. - If the reference starts with `#`, it is an implicit named symbol
  115. (something in scope, and not passed explicitly).
  116. - If the reference starts with an alphabetic character, it is
  117. _named_: something given to the [[format]] macro explicitly, as a
  118. keyword argument.
  119. - If the reference starts with `$`, it is a positional argument.
  120. The formatter can either start with `:`, in which case it references
  121. an Urn symbol, or start with `%`, in which case it is a string.format
  122. format sequence.
  123. ### Examples
  124. ```cl
  125. > (format nil \"{#pretty:pretty} is {what}\" :what 'pretty)
  126. out = \"«method: (pretty x)» is pretty\"
  127. > (format nil \"0x{foo%x}\" :foo 123)
  128. out = \"0x7b\"
  129. ```"
  130. (let* [(str (const-val str))
  131. (fragments (parse-format str))
  132. (last-positional
  133. (apply math/max 0
  134. (filter-map (function
  135. [((_ (positional ?d) _)) d]
  136. [else nil])
  137. fragments)))
  138. (named-map (gensym))
  139. ((positionals nameds)
  140. (loop [(pos '())
  141. (nam {}) (expecting-nam nil)
  142. (togo args)]
  143. [(empty? togo)
  144. (when expecting-nam
  145. (error! (string/format "(format %q): expecting value for named argument %s"
  146. (.> expecting-nam :value))))
  147. (values-list pos nam)]
  148. (cond
  149. [(key? (car togo))
  150. (recur pos nam (.> (car togo) :contents) (cdr togo))]
  151. [else
  152. (if expecting-nam
  153. (.<! nam expecting-nam (car togo))
  154. (push! pos (car togo)))
  155. (recur pos nam nil (cdr togo))])))
  156. (named-alist (let* [(arg '())]
  157. (for-pairs (k v) nameds
  158. (push! arg k)
  159. (push! arg v))
  160. arg))
  161. (interpret-spec
  162. (function
  163. [((positional ?k)) (or (nth positionals k)
  164. (error! (string/format "(format %q): not given positional argument %d"
  165. str k)))]
  166. [((implicitly-named ?k)) (str->sym k)]
  167. [((named ?k)) (if (.> nameds (.. ":" k))
  168. `(.> ,named-map ,(.. ":" k))
  169. (error! (string/format "(format %q): not given value for named argument %s"
  170. str k)))]))]
  171. (when (> last-positional (n args))
  172. (error! (string/format "(format %q): not given enough positional arguments (expected %d, got %d)"
  173. str last-positional (n args))))
  174. (with (parts (dolist [(frag fragments)]
  175. (compile-format-fragment frag interpret-spec)))
  176. `(let* [(,named-map { ,@named-alist })]
  177. (format-output! ,out
  178. ,(if (= (n parts) 1)
  179. (car parts)
  180. (cons `.. parts)))))))