Skip to content
Paolo Tranquilli edited this page May 16, 2012 · 22 revisions

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.

Selecting

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 the i+2th li 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.

The commands

def

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

[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

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

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

pass

Do nothing. To be used for empty blocks, if you ever need them.

copy

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.

assignment

[_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

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

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

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 or keep tells whether the copied node should remain or not; the default is drop.

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)

evaluation

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.

other

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.

Clone this wiki locally