format-string-builder

2017-01-24

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.
Author
fiddlerwoaroof
License
MIT