format-string-builder
2017-01-24
A DSL wrapping cl:format's syntax with something more lispy.
A simple package implementing a DSL for generating format strings.
Introduction
Hello World
CL-USER> (define-message hello (name) "Hello " :str) CL-USER> (hello "world") Hello world NIL
(make-format-string '(:str)) #| ==> "~a" |# (make-format-string '((:map () :str))) #| ==> "~{~a~}" |# (define-message print-comma-separated (values) (:map () :str))
Api Reference
(make-format-string spec) #| function |#
Takes a format string specification and turns it into a string.
(format* stream spec &rest args) #| macro |#
Use like CL:FORMAT, except translate a format specification to a string at macroexpansion time.
(define-message (stream-symbol &rest format-args) &body spec) #| macro |#
Defines a function that takes a stream and the arguments to be formatted and then formats the arguments to the stream. The spec is compiled to a string at macroexpansion time, so this should be reasonably efficient.
TODO: document the API for defining directives.
DSL Reference
A spec consists of operators and literals. Literals are either
strings, characters or integers and they are formatted as-is via
princ. There are two kinds of operators: simple operators and compound
ones. Simple operators correspond to format control directives and
represented in the spec by keywords such as ~A
or by lists
(keyword . modifiers)
and they expand to the corresponding
directives. Compound operators correspond to format directives that
can contain other directives such as ~{~}
. In a spec, these are
formatted like flet function definitions:
(keyword (&rest modifiers) &body spec)
Compound operators are further divided into sectioned operators and
non-sectioned ones. The difference is that, in non sectioned
operators, the body is treated just as a normal spec. In sectioned
ones, the body is treated as a list of items to be divided with ~;
.
See CLHS 22.3 for a full guide to the modifiers for the various format
directives.
Simple Format Operations
- :str --- Translates to ~a, format a lisp value for humans
- :repr --- Translates to ~s, format a lisp value in a way that can be read by the reader (?)
- :float --- Translates to ~f, format a float.
- :dec --- Translates to ~d, format a number as a base 10 number.
- :decimal --- Translates to ~d, format a number as a base 10 number.
- :hex --- Translates to ~x, format a number as a base 16 number.
- :hexadecimal --- Translates to ~x, format a number as a base 16 number.
- :oct --- Translates to ~o, format a number as a base 8 number.
- :octal --- Translates to ~o, format a number as a base 8 number.
- :currency --- Translates to ~$, format a number in a manner suitable for currency
- :exit --- Translates to ~^, leaves a iteration construct
- :end-section --- Translates to ~;, divides sections of a construct (TODO: maybe this will go away?)
- :goto --- Translates to ~*, moves within the list of arguments
- :fresh-line --- Translates to ~&, ensures we're at the beginning of a line and, possibly adds Modifier-1 linebreaks
- :ensure-line --- Translates to ~&, alias for :fresh-line
- :new-line --- Adds a linebreak
Compound Format Operations
Iteration
- :map --- Translates to
{}, iterate over a list passed in - :rest --- Translates to
@{}, iterate over the rest of the arguments - :ap --- Translates to
:{}, apply a list to the corresponding enclosed format directives - :apply --- Alias for :ap
- :aprest --- Translates to
:@{}, apply the rest of the arguments to the corresponding enclosed format directives - :apply-rest --- Translates to
:@{}, apply the rest of the arguments to the corresponding enclosed format directives
Conditional Output (Sectioned Operators)
- :y-or-n --- Translates to
:[], if the argument is nil, print first spec otherwise print second - TODO: add others here...
Case Control
- :lowercase --- Translates to
(), lowercase all alphabetic characters. - :downcase --- Translates to
(), alias for :lowercase - :uppercase --- Translates to
:@(), uppercase all alphabetic characters. - :upcase --- Translates to
:@(), alias for :uppercase - :titlecase --- Translates to
:(), titlecase all alphabetic characters. - :capitalize --- Translates to
:(), alias for :titlecase - :initialcap --- Translates to
@(), uppercase first character
Justification
TODO: finish documenting this and switch them to sectioned operators.
- :spread --- Translates to
<> - :ljust --- Translates to
@<> - :left --- Translates to
@<> - :rjust --- Translates to
:<> - :right --- Translates to
:<> - :cjust --- Translates to
:@<> - :center --- Translates to
:@<>
Miscellaneous
These are not part of Format, but are defined just to be helpful
- :own-line --- Translates to
&%, ensure that the included text is on its own line, without unnecessary gaps.