Up till this point we haven’t given much thought as to how our source
code is organized. Instead we’ve crammed all our code in a Tridash
code tag inside a single ui.html
file. This strategy obviously wont
scale for larger applications. Ideally we’d like to have separation
of concerns with our application divided into multiple source files,
each containing the code implementing a separate component of the
application. The ui.html
should ideally contain a minimal amount of
Tridash code, limited only to inline node references and bindings
directly related to the presentation logic. The core application logic
should be in a separate source file.
In this tutorial we’ll take the Adding Numbers application, as it is at the end of Section 9, “Contexts”, and reorganize its source code.
The simplest form of code organization is splitting up the application
into multiple source files. Source files containing only Tridash code
are given the extension trd
.
Let’s extract the validation logic in a separate validation.trd
file. This includes the meta-nodes validate
, valid-int
and the
Negative-Number
failure type.
validation.trd
.
/import(core) Negative-Number <- &(Negative-Number) Negative-Number! <- fail(Negative-Number) validate(x) : case( x >= 0 : x, Negative-Number! ) valid-int(value) : { x <- int(value) validate(x) } /attribute(valid-int, target-node, valid-int)
We’ve kept the |
The application logic simply consists of the definition of the
error-message
meta-node, which is responsible for choosing an
appropriate error message, the computation of the sum, and the setting
of the initial values for a
and b
.
Let’s place this in a separate source file called app.trd
app.trd
.
/import(core) error-message(value) : { value !- "" -> @(self) "Not a valid number!" -> self @ when(Invalid-Integer) "Number must be greater than or equal to 0!" -> self @ when(Negative-Number) } sum <- a + b 0 -> a 0 -> b
We’ve kept the |
We’ve bound the sum of |
All that’s left in the ui.html
file is the user interface elements
and the inline bindings to the nodes a
, b
, sum
and the
error-message
instances, which display the error messages.
ui.html
.
<!doctype html> <html> <head> <title>Adding Numbers</title> </head> <body> <h1>Adding Numbers</h1> <div> <label>A: <input id="a" value="<?@ valid-int(a) ?>"/></label> <?@ error-message(a) ?> </div> <div> <label>B: <input id="b" value="<?@ valid-int(b) ?>"/></label> <?@ error-message(b) ?> </div> <hr/> <div><strong>A + B = <?@ sum ?></strong></div> </body> </html>
To build an application consisting of multiple source files, the
source files are simply listed, under the sources
entry, of the
build configuration file. Modify the build.yml
file to the
following:
build.yml
.
sources: - validation.trd - app.trd - path: ui.html node-name: ui output: path: app.html type: html main-ui: ui
The source files are processed in the order they are listed. |
Since there are no processing options for validation.trd
and
app.trd
, we’ve listed the paths to the files directly rather than in
the path
entry of a dictionary.
To build entirely from the command-line, simply list the files, in the
order they are to be processed, after The following is equivalent to the build configuration file tridashc validation.trd app.trd path : node-name=ui.html \ -o path.html -p type=html -p main-ui=ui |
Whilst separating the application into multiple source files, grouping
related components in a single file, is a significant cleanup, it
would be even better if the nodes could be grouped into different
namespaces based on their purpose, for example utility, application
logic and ui. This would allow us to use only the nodes from the
utility namespace, without having the rest of the utility nodes
clash (i.e. have the same identifiers) with the application logic
nodes. Whilst in this application there are no clashes, keeping the
nodes in separate namespaces allows us to use the same
validation.trd
file in another application without the fear that
some node is going to clash with other nodes in the application.
Modules are a means of separating nodes into different namespaces,
where each module is a namespace in which nodes are contained. A node
with identifier x
in module mod1
is a distinct node from the node
x
in mod2
, even though the two nodes share the same identifier.
We’ve already made use of the |
Modules are created with the /module
operator which has the
following syntax:
/module
Operator Syntax.
/module(module-name)
This indicates that all node references, in the declarations following
the /module
declaration, will occur in the module with identifier
module-name
. Remember that nodes are created the first time they are
referenced, thus if a node is referenced which is not in the module,
it is created and added to the module.
Example.
/module(mod1) x -> node1 /module(mod2) x -> node2
The first reference to the node x
occurs in module mod1
thus a
node x
is added to mod1
. The second referenced occurs in module
mod2
thus the node is added to mod2
. The two nodes are distinct
even though they share the same identifier.
If no module is specified the node references occur in a nameless init module. The current module is reset to the init module prior to processing each source file.
Module identifiers are distinct from node identifiers, thus
a node |
This is great but it is of little use if you can’t reference a node that is declared in a different module from the current module.
The /use
operator allows nodes in a module to be referenced as
subnodes of a node with same identifier as the module identifier.
/use
Operator Syntax.
/use(mod1)
This adds a node mod1
to the module in which the declaration
occurs. Then you can reference a node x
in mod1
as a subnode of
mod1
, mod1.x
. mod1
is, however, a pseudo-node as its value
cannot be referenced nor can bindings involving it be established. It
only serves as a means to reference nodes in the module mod1
.
To use a meta-node |
This greatly increases the functionality of modules however sometimes
it may become annoying to have to type out the full name of the module
over and over again, for each node. You can try keeping the module
names short however then you run the risk of module name
collisions. The /use-as
operator allows you to control the
identifier of the pseudo-node, that is created in the current module,
with which nodes in the module can be referenced.
/use-as
Operator Syntax.
/use-as(mod1, m)
This creates a pseudo-node with identifier m
, with which nodes in
mod1
can be referenced. Nodes in mod1
can then be referenced as
subnodes of m
.
Both |
Sometimes you would like to explicitly add a node in another module to
the current module, so that you don’t have to reference it as a
subnode of the module. The /import
operator allows you to do this.
It has two forms:
Example.
# Short form: Import all nodes exported from mod1 /import(mod1) # Long form: Only import nodes x, y, z /import(mod1, x, y, z)
The short form only imports those nodes which are exported from the module not all nodes. |
The long form allows you to choose which nodes are imported into the current module. You can list any node in the module, not just an exported node. |
/import
also has a side-effect in that if an imported node, whether
imported by the long or short form, is registered as an infix
operator, its entry in the operator table is copied over to the
operator table of the current module so that it can also be written in
infix position in the current module.
Example.
# @ is a meta-node that is registered as an infix operator /import(mod1, @) # It can be also be placed in infix position in the current module x @ y
You cannot place a node in infix position if it is referenced as a subnode of the module.
/use(mod1) # The following will not compile as you cannot place a subnode in # infix position. x mod1.@ y # Instead you have to write it in prefix notation: mod1.@(x, y)
We mentioned that the short form of the /import
operator imports all
nodes which are exported from the module. Nodes are exported from the
current module using the /export
operator.
/module(mod1) # Exports nodes x, y and z from the current module /export(x, y, z)
Importing mod1
by the short form, /import(mod1)
, will import nodes
x
, y
and z
into the module.
/export
can take any number of arguments and multiple /export
declarations will result in the nodes listed in each declaration being
exported.
The /in
operator references a node in another module, for which a
symbol has not been created in the current module using /use
or
/use-as
.
/in
Operator Syntax.
/in(module, node)
where module
is the name of the module
, as declared by the
/module
operator, and node
is the node expression which is
processed in module
.
This operator is most useful when writing macros, however there is
rarely a need for it when writing code directly as whatever can be
achieved with /in
can be achieved in a more readable manner with
/use
, /use-as
or /import
.
Let’s group the nodes declared in the validation.trd
file, i.e. the
nodes related to the input validation logic into a module called
validation
. To do that simply add the declaration
/module(validation)
to the top of the file.
We mainly require the valid-int
meta-node and Negative-Number
failure type node to be referenced outside the validation
module,
thus we’ll export those nodes using the declaration
/export(valid-int, Negative-Number)
.
We haven’t exported |
validation.trd
.
/module(validation) /import(core) ... /export(valid-int, Negative-Number)
We’ll group the main application logic into a sum-app
module, by
adding the declaration /module(sum-app)
to the top of the app.trd
file. The nodes exported from the validation
module have to be
imported in this module, by adding an import declaration for the
module after the import declaration for the core
module. We’ll
contain the user interface in this module, thus we wont be needing to
export any nodes from it.
app.trd
.
/module(sum-app) /import(core) /import(validation) ...
Finally, we have to add a Tridash code tag to the top of the ui.html
file, which changes the module to sum-app
, since the sum
and
error-message
nodes are contained in it.
ui.html
.
<? /module(sum-app) ?> <!doctype html> <html> <head> <title>Adding Numbers</title> </head> <body> <h1>Adding Numbers</h1> <div> <label>A: <input id="a" value="<?@ valid-int(a) ?>"/></label> <?@ error-message(a) ?> </div> <div> <label>B: <input id="b" value="<?@ valid-int(b) ?>"/></label> <?@ error-message(b) ?> </div> <hr/> <div><strong>A + B = <?@ sum ?></strong></div> </body> </html>
We’ve successfully divided our source code into multiple modules,
based on the purpose of the nodes defined in it. However, the node
which references the contents of the ui.html
file is still added to
the init module. Whilst in this application it doesn’t pose a
problem as there is no other node with identifier ui
in the init
module, it may become a problem if there is a need to reference HTML
elements from within the app.trd
file. Remember HTML elements can
only be referenced as subnodes of self
, within the HTML file
itself. Outside the file they have to be referenced as subnodes of the
node which references the contents of the HTML file.
In order for the ui
node to be created in a module other than
init, the node-name
option has to be of the following form
module.name
, where module
is the module in which the node should
be created and name
is the name of the node. The same syntax applies
in the main-ui
output option.
Change the build.yml
file to the following:
build.yml
.
sources: - util.trd - app.trd - path: ui.html node-name: sum-app.ui output: path: app.html type: html main-ui: sum-app.ui
With this you can now reference HTML elements, from app.trd
, as
subnodes of the ui
node.
Build and run the application. You wont see any new features however we’ve significantly improved the organization of the code and thereby improved the maintainability of the application.
You wouldn’t typically divide an application this small into multiple modules, however we’ve done so here in order to demonstrate how it is done. |
We have referred to some meta-nodes as being registered as infix operators. This allows them to be placed in infix position instead of prefix position. It turns out you can do the same for your own nodes. This section describes how.
Each module has an operator table, which contains the identifiers of all nodes which can be placed in infix position as well as their precedence and associativity. The precedence is a number which controls the priority with which operands are grouped with infix operators, in an expression containing multiple different infix operators. Higher numbers indicate greater precedence.
The multiplication *
operator has a greater precedence (200), than
the addition +
operator (100) thus arguments will be grouped with
the multiplication operator first and then the addition operator.
The following infix expression:
x + y * z
is parsed to the following expression in prefix notation:
+(x, *(y, z))
Notice that the *
operator is grouped with the operands y
and z
first, and then x
and *(y, z)
are grouped with the +
operator. This is due to *
having a greater precedence than +
.
To achieve the following grouping:
*(+(x, y), z)
enclose x + y
in parenthesis:
(x + y) * z
Associativity controls the grouping of operands in an expression
containing multiple instances of the same infix operator. The +
operator has left associativity.
Thus the following infix expression:
x + y + z
is parsed to the following expression in prefix notation:
+(+(x, y), z)
i.e. it is equivalent to
(x + y) + z
If the +
operator were to have right associativity, the expression
would be parsed to the following:
+(x, +(y, z))
Below is a table showing the precedence and associativity of some of the builtin operators.
Operator | Precedence | Associativity |
---|---|---|
| 10 | right |
| 10 | left |
| 15 | right |
| 20 | left |
| 25 | left |
| 100 | left |
| 100 | left |
| 200 | left |
| 200 | left |
Visit the |
Node identifiers can be registered as infix operators with the special
/operator
declaration.
/operator
Syntax.
/operator(id, precedence, [left | right])
The first argument is the identifier, the second argument is the
operator precedence as a number and the final argument is the symbol
left
or right
for left or right associativity. If the third
argument is omitted it defaults to left
.
This declaration adds an infix operator to the operator table of the
current module. In the declarations, following the /operator
declaration, id
can be placed in infix position.
|
It is not checked whether id
actually names an existing
node, however using it in infix position only makes sense if
id
names a meta-node.
If the node with identifier id
is imported into another module, its
entry in the operator table, of the module from which it is imported,
is copied into the operator table of the module into which it is
imported.
The precedence and associativity of existing operators can be changed
by an /operator
declaration, however only the operator table of the
current module is changed even if the operator is an imported node.
To demonstrate how you would go about registering a meta-node as an
infix operator, we’ll write an add
meta-node which computes the sum
of two numbers, register it as an infix operator and replace a + b
with a add b
in the Adding Numbers Application.
There is no value in writing a new meta-node which is simply a wrapper over a builtin meta-node, and registering it as an infix operator. The purpose of this tutorial is simply to demonstrate how you would register your own meta-node as an infix operator. |
We’ll first define add
and then register it as an infix operator
with precedence 100 and left associativity which is the same as the
builtin +
operator.
Add the following somewhere near the top in app.trd
:
add(a, b) : a + b /operator(add, 100, left)
Now change the binding declaration to sum
with the following:
sum <- a add b
That’s it you’ve now added your own infix operator to the language.