-
Notifications
You must be signed in to change notification settings - Fork 0
Syntax
Marcus files, with extension .ipsum
, contain one command per line,
with blocks marked by indentation (the off-side
rule).
Marcus code is organized in functions introduced by def
commands. Each function is a guided traversal of a DOM tree. Within a
function there is a single variable (the first parameter of the
function, or root
by default) which always marks the currently
selected node in the tree. Other variables are pos
, which hold the
iteration number in the current loop and old
, which holds the
current inner content of a node or of one of its attributes when we
are about to change it. These variables can be accessed by the
encapsulated code.
Instructions can have modifiers, which are introduced by |
. In
each instruction we will explain what modifiers are accepted. If
contradicting modifiers are attached to an instruction, the last takes
precedence.
Many instructions use selectors, whose syntax we thus go into detail next.
Selectors are (usually) just plain CSS selectors, with some templating features. Quick examples:
-
#sidebar li
: does not really need explanations; -
#sidebar li{updating ? ".vanilla" : ""}
: brackets are used to inject native code inside the selector (not just variables); -
#sidebar li:nth-child({i+2})
: take thei+2
thli
child (counting from 1).
Selection always happens inside the current DOM element marked by the root variable.
The compiler does not make any analysis on selectors and instead passes them verbatim (with the injected code) to the underlying selector engine, so that selectors need not be strictly CSS compliant if the underlying engine uses extensions.
All instructions that accept selectors can have a selecting modifier, which is of the form:
all|_n_[?]|{_expr_}[?]|?
The all
modifier selects all nodes matching the selector and process
them sequentially, with pos
indicating the position in the list.
The n modifier takes the node at position n in the results of the
query. After such selection, pos
will hold n. In case {expr} is
given, the expression expr is passed as index. With a ?
, selection
does not happen if there is no element at the provided position
(otherwise, you can expect errors on misses, as the root variable will
be undefined; this may be what desired). ?
alone is a shorthand for
0?
, and is the default.
So for example drop|all li:not(.vanilla)}
will mark for removal all
li
elements not having the class vanilla
.
When a selecting command has been executed (together with its
blocks) the root turns back to what it was before it. Use
select
blocks to execute several commands on the same node. The
same happens for pos
, which is set back to its previous value each
time a marcus statement changing it is done executing.
def _name_ (_args_)
_block_
Functions are introduced by def statements. A directive will be
compiled to a function with the same name inside the object defined as
the main container (window
by default in javascript, so that the
name is global). The first parameter in args holds the special root
variable, and will set its name for the rest of the definition. All
other are free for the taking.
As a function is compiled to one with the same name, recursive calls to marcus functions are possible. Just be sure to include the correct container, if needed.
[select|$][_modifiers_] _selector_
_block_
Select nodes matching selector (as explained above) and execute
block, holding the current node in the root variable and the current
position in pos
. select
and $
are synonims. Modifiers are only
those allowed for selecting commands.
drop[_modifiers_] [_selector_]
Mark the nodes corresponding to selector (or the root if none) for
removal, which will happen only when exiting the function. Using the
modifier now
(i.e. writing drop|now
) will erase the node
immediately.
keep[_modifiers_] [_selector_]
Undoes dropping, either one explicitly issued by a drop
statement or
an implicit one due to a repeat
one. No other modifiers than the
selecting one exist.
pass
Do nothing. To be used for empty blocks, if you ever need them.
copy[_modifiers_] [_selector_]
_block_
Duplicate the nodes marked by selector (or the root) and execute
block on the copies (i.e. with the root variable holding the
copy). Additional modifiers tell where the old, copied node will be
after the copy: up
of ↑
means the old node is above the new one,
down
or ↓
(the default) the contrary. The fact that the direction
modifier tells where the old node is and not the new one, which is a
bit counterintuitive, is for consistency with the repeat
command.
[_modifiers][_selector_] [/ [_attribute_]] := _expr_
Change nodes corresponding to selector (or the root if selector is
absent). If attribute is present, then the corresponding attribute
of the node is the one to be changed. If no attribute is specified,
then it is the inner content of the node to be affected. Attribute
names can use the same templating that is in place for
selectors. expr can use the variable old
to reference previous
contents.
if _expr_
_block_
[else
_block_]
expr will be passed verbatim to the compiled directive and will fork execution as expected. Truthyness and falsyness of expression is the same as in the underlying language.
for [_key_ :] [_el_] in _expr_
_block_
[else
_block_]
Yeah, you got it, for cycles through the elements in an expression. By default (when key is not present) expr is treated as an array, and the word el (if it is there) will hold the elements of the array in turn.
If key is there, then expr is treated as an associative array/map, key will cycle through the keys in it and el (if there) through the values.
In both cases pos
will count the iterations, but remember that in
case of cycling through an associative array you cannot really rely on
the order of key/value pairs.
The else
block, if present, will be executed if no iteration was
executed, i.e. if expr is empty.
In any case, this form of for
loop does not change the current root.
repeat[_modifiers_] [_selector_] for [[_key_ :] _el_] in _expr_
_block_
[else
_block_]
This is a for loop that repeats a DOM node. A peculiarity is that the
original node is copied for every iteration of the loop and then
marked for removal. Erasure of the node can be avoided either by
adding |keep
to the command, or by calling the keep
command later
on the node. In this way a vanilla element can be saved and just
hidden for future changes. The block inside the for
loop has the
current copy as root, while the one in the else
clause has the
copied one.
Apart from the selecting modifiers, you can use:
-
up
,↑
,down
,↓
make the repetition go upwards (with the copied node staying on top) or downwards (the default, with the copied node staying at the bottom); -
drop
orkeep
tells whether the copied node should remain or not; the default isdrop
.
In fact, the repeat
statement regarded as a shortcut for:
select[_selecting_mod_] [_selector_]
[drop]
for [[_key_ :] _el_] in _expr_
copy[_direction_mod_]
_block_
[else
_block_]
(almost, as dropping is done just before the else
, but it does not
change much)
eval _expr_
Evaluate expr. As for the moment the only supported langauge is
javascript, no language selection syntax is available, but it will be
in the future. When compiling with the -w
or --weak-parsing
option, the eval
may be dropped: all duly indented lines that are
not recognized will be interpreted as evaluation lines. This will
still issue a warning, so that unwanted errors might still be spotted.
Comments can be inserted with a double hash (##
) and must occupy a
whole line. They do not need to follow indentation. If the comment
starts with a -
sign, the comment is treated as a compilation
directive, which are the same as command-line options
of the compiler (though some may make no sense, and no quoting is
necessary for multi-word arguments). Compilation directives can be
anywhere in the code, but bear in mind that they are executed at the
lexing stage and before parsing and compilation.
Lines can be continued on the following one by ending them with
\
. However the marcus part of the line must already be complete
(i.e. you can break only the last selector or expr part of a
command). This can be used to correctly parse tricky lines. Take for
example
repeat li for x in f("function containing for and in in it")
As marcus makes no effort in parsing non-marcus stuff, the above line will be parsed with
-
selector:
li for x in f("function containing
-
el:
and
-
expr :
in it")
You can resolve this unpleasant case by writing
repeat li for x in \
f("function containing for and in in it")
which will yield the correct interpretation.