mirror of
https://github.com/AdaCore/uwrap.git
synced 2026-02-12 13:06:34 -08:00
429 lines
13 KiB
ReStructuredText
429 lines
13 KiB
ReStructuredText
Templates
|
|
=========
|
|
|
|
Overall Structure
|
|
-----------------
|
|
|
|
A template is a structure that can be created through the wrapping process. It
|
|
contains a set of fields that can be valued during template instantiation
|
|
process. It structure is:
|
|
|
|
.. code-block:: text
|
|
|
|
template <some name> <command>
|
|
|
|
or if the template is inheriting from a parent:
|
|
|
|
.. code-block:: text
|
|
|
|
template <some name> extends <some parent> <command>
|
|
|
|
Field Types
|
|
-----------
|
|
|
|
TODO: Not all of the below fields types are implemented yet
|
|
|
|
Fields can be declared of the following types:
|
|
|
|
- string: this field is a simple string.
|
|
- text: this field is a text reference. It can be combined with other text and
|
|
strings to crate dynamic text structure (more on that on a dedicated section
|
|
below).
|
|
- regexp: this field is a regular expression reference
|
|
- object: this field is a reference to an object, for example a node.
|
|
- set (string|object): this field contains a set indexed by string.
|
|
- map (string, [string|text|object|map|set]): this field is a map indexed by
|
|
strings
|
|
|
|
Template Commands and Variables
|
|
-------------------------------
|
|
|
|
Template can contain arbitrary complex commands, in particular commands that
|
|
contains themselves other commands and variables. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template T do
|
|
var v1: text => "A";
|
|
var v2: text;
|
|
|
|
do
|
|
var v3: text;
|
|
|
|
pick origin wrap T2 (v1 & v2 & v3);
|
|
end;
|
|
end;
|
|
|
|
This command will be executed only once, at template instanciation. Through
|
|
this execution, any variable declared will be stored as field in the template,
|
|
in the order of evaluation. In particular, in the above case, the template will
|
|
have a variable v1 and v2.
|
|
|
|
Upon template instanciation, these values can be modified, using either
|
|
positional or named notation. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
match some_node ()
|
|
wrap T (v1 => "X", v3 => "Y");
|
|
|
|
match some_node ()
|
|
wrap T ("X", "Y");
|
|
|
|
The left reference `@` is set during these parameter expressions, so that
|
|
the previous value of the variable can be refered to.
|
|
|
|
Parameters expressions are computed in the calling context. If the wrapper
|
|
is named upon instanciation, this name can be used to refer to the template
|
|
already created variables. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
match some_node ()
|
|
wrap w: T (v1 => @ & "X", v3 => w.v1 & "Y");
|
|
|
|
The specific creation of these variables will go as follow:
|
|
|
|
- All variables for a given block are created
|
|
- All default values for these variables are evaluated
|
|
- If parameters have been provided for the variables
|
|
|
|
In the above example, this will translate in:
|
|
|
|
- create v1 and v2 (root block)
|
|
- initialize v1 to "A"
|
|
- apply v1 parameter expression, v1 now is "AX"
|
|
- create v3 (nested block)
|
|
- apply v3 parameter expression, v3 is now "AXY"
|
|
|
|
Once a template has been instantiated (through a ``wrap`` or a ``weave`` clause),
|
|
further calls will not execute the command, but instead update the variables,
|
|
e.g.:
|
|
|
|
.. code-block:: text
|
|
|
|
match some_node () do
|
|
wrap T (v1 => @ & "X1", v3 => v1 & "Y1");
|
|
then
|
|
weave T (v1 => @ & "X2", v3 => v1 & "Y2");
|
|
end;
|
|
|
|
Template Predicates
|
|
-------------------
|
|
|
|
Templates offer three kind of predicates:
|
|
- type predicates, testing on the template type
|
|
- field predicates, tested on the template fields
|
|
- origin, which refers to the node that is wrapped by this template
|
|
- peer, which allows to browse other templates (TODO: to implement)
|
|
- wrapper, which allows to browse the wrappers of a given node
|
|
- kind, which provides a text-based version of the predicate type
|
|
|
|
Type predicates for templates work like other type perdicates. They are
|
|
is-predicates and can check if a template is of a given type. They allow for
|
|
nested match expressions. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A_Template do
|
|
|
|
end;
|
|
|
|
match A_Template ()
|
|
|
|
TODO: for now, templates convert to empty string, work on that.
|
|
|
|
Field predicate allow to match against specific fields of a template. They
|
|
behave like other field predicates, as is-predicates without parenthesis and
|
|
has-predicate with parenthesis. There is no specific notation for the fields,
|
|
as opposed to the nodes coming from langkit. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A_Template do
|
|
var d1 : text;
|
|
var d2 : text;
|
|
end;
|
|
|
|
match A_Template (d1 ("something)) and d2 ("something else");
|
|
|
|
match d1 ("something");
|
|
|
|
``origin`` is a reference to the object that the template is wrapping. It can
|
|
be used like fields or other references, for example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A_Template do
|
|
|
|
end;
|
|
|
|
match DefiningName ()
|
|
wrap A_Template ();
|
|
|
|
match A_Template (origin (DefiningName ()));
|
|
|
|
``wrapper`` works on any node, and allows to browsed wrappers that have
|
|
been used to wrap this node so far. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A_Template do
|
|
|
|
end;
|
|
|
|
match BaseDecl ()
|
|
wrap A_Template ();
|
|
|
|
match parent (wrapper (A_Template));
|
|
|
|
TODO: peer needs to be implemented
|
|
|
|
``peer`` iterates over the templates of the same origin. It is a shortcut
|
|
to origin (template ()).
|
|
|
|
Inheritance
|
|
-----------
|
|
|
|
A template inheriting from another template will call its parent command before
|
|
its own. It will however be a distinct type - in particular a given node can be
|
|
wrapped with both a parent and its child templates, as well as different
|
|
siblings. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A do
|
|
V : text;
|
|
end;
|
|
|
|
template B extends A do
|
|
V2 : text;
|
|
end;
|
|
|
|
match some_condition do
|
|
wrap A;
|
|
wrap B;
|
|
end;
|
|
|
|
When matching on template predicate will match if a node is of the type or a
|
|
child of the type of the predicate. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
match A () # will match for instances of A and B
|
|
|
|
Building Text Templates
|
|
-----------------------
|
|
|
|
It is common when writing complex templates to need to describe how various
|
|
variables are combined. To that extend, when evaluating values for a given
|
|
template variables, other can referred to. E.g:
|
|
|
|
.. code-block:: text
|
|
|
|
template A do
|
|
V1 : text;
|
|
V2 : text;
|
|
V3 : text;
|
|
end;
|
|
|
|
match some_other_predicate
|
|
wrap a: A (
|
|
V1 => "A",
|
|
V2 => "B",
|
|
V3 => a.V1 & "-" & a.V2);
|
|
|
|
Note that the previous example works because V1 and V2 are evaluated before
|
|
V3. However, it may be the case that these values can be overridden and
|
|
evolve over time. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
.. code-block:: text
|
|
|
|
match A ()
|
|
weave (
|
|
V1 => @ & "_Weaved",
|
|
V2 => @ & "_Weaved");
|
|
|
|
In this case, as V3 is already computed, it will not change. A typical way
|
|
to work around this issue is to defer the computation of the expression as late
|
|
as possible, typically whenever needed to be converted to a string. This can
|
|
be done with a ``defer`` expression:
|
|
|
|
.. code-block:: text
|
|
|
|
match some_other_predicate
|
|
wrap a: A (
|
|
V1 => "A",
|
|
V2 => "B",
|
|
V3 => defer (a.V1 & "-" & a.V2));
|
|
|
|
With this wrapper, V3 actual value will evolve as V1 and V2 will evolve, and
|
|
the combined result of the last two pieces of code will value V3 to
|
|
"A_Weaved-B_Weaved" as opposed to "A-B" at the end of the program.
|
|
|
|
The value "A-B" could have been computed on the matcher, and then modified
|
|
later on:
|
|
|
|
.. code-block:: text
|
|
|
|
match V3 ("A-B")
|
|
weave (
|
|
V1 => @ & "_Weaved",
|
|
V2 => @ & "_Weaved");
|
|
|
|
Matching against V3 value doesn't fix its value, it just evaluates its current
|
|
value which can be modified later one.
|
|
|
|
Note that modifying V1 and V2 after themselves in the expression above doesn't
|
|
create an infinite recursion. The reference to V1 is replaced upon
|
|
``V1 => @ & "_Weaved" by a new reference that concatenates the old reference
|
|
and the string "_Weaved". Consequently, that old reference may itself still
|
|
evolve over time if it was build after references to other text fields.
|
|
|
|
Note that standard templates ``out`` and ``file`` evaluate at the very end of
|
|
the program execution.
|
|
|
|
This capability is fundamental to the creation of complex wrapping texts, where
|
|
the warious wrapping and weavings steps are building a text structure from
|
|
various places without a constraint order, and get resolved at the end of the
|
|
process.
|
|
|
|
Creation through new Functions
|
|
------------------------------
|
|
|
|
In some cases, node creation through the wrapping is not enough, and allocation
|
|
needs to be performed outside of the wrapped / wrapping system. This can be
|
|
created through the new () function.
|
|
|
|
new () can be invoked in match and pick clauses. In a match clauses, it's always
|
|
return a reference to the object evaluated (and is therefore evaluated to true).
|
|
In a pick clause, the value returned by new will then be the target of the
|
|
controlled actions.
|
|
|
|
In its simplest form, a new () operator contains a template instantiation
|
|
expression similar to the one of a wrap and weave clause, which value can be
|
|
captured. The only difference is that no origin is set, and therefore no peer
|
|
exist. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
template A do
|
|
var V : text;
|
|
end;
|
|
|
|
match new (A ("some text"));
|
|
|
|
pick new (A ("some text));
|
|
|
|
# TODO: only child is implemented for the model below. Implement others.
|
|
|
|
Templates can be created and inserted in an existing tree structure. To do
|
|
so, they need to be created within a tree browsing match nested expression.
|
|
For example:
|
|
|
|
.. code-block:: text
|
|
|
|
# will create a new child of type A
|
|
match child (new (A ("some text")));
|
|
|
|
# will insert a new sibling right after the current one
|
|
pick next (new (A ("some text")));
|
|
|
|
# will insert a parent between the current node and the current parent
|
|
pick parent (new (A ("some text")));
|
|
|
|
Allocations can also happen in the context of boolean expressions. In this
|
|
case, they will only be evaluated if needed to obtain a true result for the
|
|
expression. If the boolean expression is a tree browsing match nested
|
|
expression, then the new operator will only be evaluated if no other node
|
|
matches the expression. For example:
|
|
|
|
.. code-block:: text
|
|
|
|
# creates a new template instance A if the current node doesn't match A
|
|
match "A" or new A ("A");
|
|
|
|
# creates a new child "A" if no child matches "A".
|
|
match child ("A" or new ("A"));
|
|
|
|
Allocator new also allows to create an tree structure at once, with curly
|
|
brackets. Comma separated elements belong to the same level, each of them
|
|
being optionally followed by a curly bracked pair to describe its children.
|
|
For example:
|
|
|
|
#TODO: on the first example, which value is being picked? all 3 root ones
|
|
presumably? In a match, which value is returned? the last one?
|
|
|
|
.. code-block:: text
|
|
|
|
# creates three siblings "A", "B" and "C"
|
|
pick new ({A ("A"), A ("B"), A ("C")}).
|
|
|
|
# creates a root "A" and two children "CHILD A" and "CHILD B", with
|
|
# "CHILD B" captured on x.
|
|
pick new (A ("A") {A ("CHILD A"), x: A ("CHILD B")})
|
|
|
|
Tree Browsing Predicates for Templates
|
|
--------------------------------------
|
|
|
|
While entities wrapping a given node belong to two structure: their own
|
|
structure, in particular if they have been created by allocators, as well as
|
|
the structure of their origin. The tree browsing predicates parent, child, next,
|
|
prev and sbling will iterate over these two dimentions.
|
|
|
|
The rule is that the relation between two templates is the same as the one of
|
|
the nodes they are wrapping. In other words, if A1 and B1 wrap N1, A2 and B2
|
|
wrap N2, and if N1 is the parent of N2, then A1 and B1 are also the parents of
|
|
A2 and B2.
|
|
|
|
Tree browsing predicates always browse the wrapping structure. Going to the
|
|
wrapped nodes can only be done through a reference with ``origin``.
|
|
|
|
For example, let's consider a situation in Ada where a PackageDecl is a parent
|
|
of a SubpDecl, which we need to wrap respectively by w_PackageDecl and
|
|
w_SubpDecl:
|
|
|
|
.. code-block:: text
|
|
|
|
template w_PackageDecl;
|
|
template w_SubpDecl;
|
|
|
|
match PackageDecl
|
|
wrap w_PackageDecl ();
|
|
|
|
match SubpDecl ()
|
|
wrap w_SubpDecl ();
|
|
|
|
There is a parent / child relationship between the w_PackageDecl and w_SubpDecl
|
|
which can be retreived by the regular tree browsing predicates. For example,
|
|
if I want to capture the w_PackageDecl parent of a w_SubpDecl, I can write:
|
|
|
|
.. code-block:: text
|
|
|
|
match w_SubpDecl () and parent (p: w_PackageDecl ())
|
|
|
|
Template Registries
|
|
-------------------
|
|
|
|
Every created template will eventually be itself browed by the main program.
|
|
However, it's sometimes convenient to access to all templates of a given type
|
|
that have been created. Templates profile a find predicate that allows to
|
|
iterate over its instances:
|
|
|
|
.. code-block:: text
|
|
|
|
template A do
|
|
var V : text;
|
|
end;
|
|
|
|
match something and x: A.find (V ("A"))
|
|
|
|
In the above example, the first instance of A that has a text "A" for V will
|
|
be returned. This can also be used with an extension suffix:
|
|
|
|
.. code-block:: text
|
|
|
|
pick A.all (); #TODO to implement, right now the syntax needs to go through a filter, e.g. A.filter().all()
|