Layout handlers
A layout handler reads an indented block into a form. The reader resolves
the block’s head as layout/<head> in the module namespace, then in the
builtins, and hands the handler the header and the body lines. A head
with no handler takes the default, which splices the body lines after
the header as arguments. The general rules for blocks, lines, the
structural colon, and peer blocks are in
Reader and layout syntax; the motivation is in
Koskari and s-expressions.
Handlers
Head |
Peers |
Reads to |
|---|---|---|
|
||
|
||
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
||
|
||
|
||
the block of the word after |
||
a |
||
a |
||
a |
||
a |
||
a |
||
a |
||
|
Peers are blocks at the same indent as the primary header whose head is in the handler’s peer set; the reader gathers them and expands the whole spine as one form. Blank and comment-only lines between a body and its peer do not break the spine.
Blocks as data
An indented block is first read as a #block: cons, the header’s
elements as one list and then one entry per body line:
(#block: (HEAD ARG ...) ENTRY ...)
ENTRY ::= (#line: ELEMENT ...) | (#block: ...)
LayoutReader.read() expands those conses through apply_layout for a
single block and apply_layouts for a primary block with its peers,
invoking the handler as read(reader, source, peers). The handler calls
reader.fold_line or reader.fold_lines where it wants forms and reads
the elements directly where it does not, which is how the default block
handler turns a keyword line into a keyword argument.
Quoted and quasiquoted blocks keep the #block: structure as data,
through layout/quote and layout/quasiquote, or layout/quote-syntax
and layout/quasiquote-syntax for the #'head: and #`head:
prefixes that keep hygiene marks, and a macro template
that contains a block is expanded through apply_layout before the
captured material is unboxed, so that expansion matches what the
reader would have produced.
Writing a handler
A block that only needs its body spliced after the header needs no handler; the default applies.
A block whose body should be one begin form binds
var layout/<head> layout/block-begin in the module that uses it. A block
that only breaks a long line binds layout/inline-block the same way.
Anything else is a LayoutHandler bound to layout/<head>. The handler wraps a
function of (reader, source, peers): source is the block cons’s cdr,
((HEAD . ARGS) . ENTRIES); peers holds the gathered peer blocks; the return
value is the form the block reads to. LayoutHandler takes the head symbol,
the function, and optional peers: and raw: arguments. Only a
LayoutHandler is recognised – a bare function bound to layout/<head> is
ignored.
>>> import-from koskari.reader.layouts LayoutHandler
>>> def report-read [reader source peers]:
... (cons (symbol "#list") (reader.fold_lines source.cdr))
>>> var layout/report (LayoutHandler (symbol "layout/report") (demand report-read))
>>> var vals:
... report:
... 1
... (+ 1 1)
... 3
>>> vals
#[1 2 3]
The handler runs at read time, so it must already be bound when the block is read – in an imported module, or earlier in the same file.