2018-11-17 13:01:37 -06:00
+++
title = "UCG Expressions"
weight = 3
sort_by = "weight"
in_search_index = true
+++
2018-11-18 14:23:35 -06:00
Ucg expressions can reference a bound name, do math, concatenate lists or
strings, copy and modify a struct, or format a string.
2018-11-17 13:01:37 -06:00
Symbols
-------
2019-01-05 09:56:07 -06:00
Many UCG expressions or statements use a symbol. A symbol might be used as
2018-11-18 14:23:35 -06:00
either a name for a binding or a name for a field. Symbols must start with an
ascii letter and can contain any ascii letter, number, `_` , or `-` characters.
2018-11-17 13:01:37 -06:00
2019-01-05 09:05:02 -06:00
### The environment symbol
2018-11-17 13:01:37 -06:00
2019-01-05 09:56:07 -06:00
There is a special symbol in UCG for obtaining a value from the environment.
2019-01-05 09:05:02 -06:00
The `env` symbol references the environment variables in environment at the
time of the build. You reference an environment variable just like it was in a
tuple. By default, attempting to reference a variable that doesn't exist will
be a compile error. You can turn this behavior off with the `--nostrict`
argument to the compiler. When in nostrict mode nonexistent variables will
result in a warning and be set to the NULL empty value.
```
let env_name = env.DEPLOY_ENV;
```
Binary Operators
----------
UCG has a number of binary infix operators. Some work only on numeric values and others
work on more than one type.
### Selector operators
The UCG selector operator `.` selects a field or index from tuples or lists.
2019-01-05 09:56:07 -06:00
They can descend arbitrarily deep into data structures.
2018-11-17 13:01:37 -06:00
2019-01-05 09:56:07 -06:00
You can reference a field in a tuple by putting the field name after a dot. You
can index into a list by referencing the index after the `.` . Lists are always
0 indexed.
2018-11-17 13:01:37 -06:00
```
let tuple = {
inner = {
field = "value",
},
list = [1, 2, 3],
2018-11-25 12:44:04 -06:00
"quoted field" = "quoted value",
2018-11-17 13:01:37 -06:00
};
// reference the field in the inner tuple in our tuple defined above.
tuple.inner.field;
// reference the field in the list contained in our tuple defined above.
tuple.list.0;
```
2019-01-05 09:56:07 -06:00
Selectors can quote fields if there are quoted fields with spaces in the tuple.
2018-11-25 12:44:04 -06:00
```
tuple."quoted field";
```
2018-11-17 13:01:37 -06:00
### Numeric Operators
2019-01-05 09:56:07 -06:00
UCG supports the following numeric operators, `+` , `-` , `*` , `/` Each one is type safe
2018-11-17 13:01:37 -06:00
and infers the types from the values they operate on. The operators expect both the
left and right operands to be of the same type.
```
1 + 1;
1.0 - 1.0;
```
### Concatenation
The `+` operator can also do concatenation on strings and lists. As with the numeric
version both sides must be the same type, either string or list.
```
"Hello " + "World"; // "Hello World"
[1, 2] + [3]; // [1, 2, 3]
```
### Comparison Operators
2019-01-04 10:34:57 -06:00
UCG supports the comparison operators `==` , `!=` , `>=` , `<=` , `<` , `>` , and `in` .
2018-11-18 14:23:35 -06:00
They all expect both sides to be of the same type.
2018-11-17 13:01:37 -06:00
2018-11-18 14:23:35 -06:00
The `>` , `<` , `>=` , and `>=` operators are only supported on numeric types
(i.e. int, and float).
2018-11-17 13:01:37 -06:00
```
1 > 2; // result is false
2 < 3 ; / / result is true
10 > "9"; // This is a compile error.
(1+2) == 3;
```
2018-11-18 14:23:35 -06:00
The equality operators `==` and `!=` are supported for all types and will
perform deep equal comparisons on complex types.
2018-11-17 13:01:37 -06:00
```
let tpl1 = {
foo = "bar",
one = 1
};
let tpl2 = {
foo = "bar",
one = 1
};
tpl1 == tpl2; // returns true
let tpl2 = {
foo = "bar",
one = 1
duck = "quack",
};
tpl1 == tpl3; // returns false
```
2018-11-18 14:23:35 -06:00
Because tuples are an ordered set both tuples in a comparison must have their
fields in the same order to compare as equal.
2018-11-17 13:01:37 -06:00
2019-01-04 10:34:57 -06:00
The `in` operator tests for the existence of a field in a tuple or an element in a
list.
```
let tpl = { foo = "bar" };
foo in tpl; // evaluates to true
"foo" in tpl; // also evaluates to true.
```
Lists do a deep equal comparison when testing for the existence of an element.
```
let lst = [1, "two", {three = 3}];
1 in lst; // evaluates to true;
{three = 3} in lst; // evaluates to true
{three = "3"} in lst; // evaluates to false
{three = 3, two = 2} in lst // evaluates to false
```
2018-11-17 13:01:37 -06:00
#### Operator Precedence
2018-11-18 14:23:35 -06:00
UCG binary operators follow the typical operator precedence for math. `*` and
`/` are higher precendence than `+` and `-` which are higher precedence than
any of the comparison operators.
2018-11-17 13:01:37 -06:00
Copy Expressions
----------------
2018-11-18 14:23:35 -06:00
UCG expressions have a special copy expression for tuples. These faciliate a
form of data reuse as well as a way to get a modified version of a tuple. Copy
expressions start with a selector referencing a tuple followed by braces `{}`
with `name = value` pairs separated by commas. Trailing commas are allowed.
2018-11-17 13:01:37 -06:00
2018-11-18 14:23:35 -06:00
Copied expressions can change base fields in the copied tuple or add new
fields. If you are changing the value of a base field in the copy then the new
value must be of the same type as the base field's value. This allows you to
define a base "type" of sorts and ensure that any modified fields stay the
same.
2018-11-17 13:01:37 -06:00
```
let base = {
field1 = "value1",
field2 = 100,
field3 = 5.6,
};
let overridden = base{
field1 = "new value"
};
let expanded = base{
field2 = 200,
field3 = "look ma a new field",
};
let bad = base{
field1 = 300, // Error!!! must be a string.
};
```
2018-11-18 14:23:35 -06:00
There is a special selector that can be used in a copy expression to refer to
the base tuple in a copy called `self` . `self` can only be used in the body of
the copy.
2018-11-17 13:01:37 -06:00
```
let nestedtpl = {
field1 = "value1",
inner = {
field2 = 2
inner = {
field3 = "three",
},
},
};
let copiedtpl = nestedtpl{
inner = self.inner{
inner = self.inner{
field4 = 4,
},
},
};
```
Format Expressions
----------
2018-11-18 14:23:35 -06:00
UCG has a format expression that has a limited form of string templating. A
format expression starts with a string followed by the `%` operator and a list
of arguments in parentheses separated by commas. Trailing commas are allowed.
The format string should have `@` characters in each location where a value
should be placed. Any primitive value can be used as an argument.
2018-11-17 13:01:37 -06:00
```
"https://@:@/ " % (host, port)
```
2019-01-06 16:17:00 -06:00
Functional processing expressions
---------------------------------
UCG has a few functional processing expressions called `map` and `filter` . Both of
them can process a list or tuple.
Their syntax starts with either map or filter followed by a symbol that
references a valid macro and the outfield for the tuple the macro produces. and
finally an expression that resolves to either a list of tuple.
### Map expressions
Map macros should produce in the result field a value or [field, value] that
will replace the element or field it is curently processing.
**For Lists**
When mapping a macro across a list the result field can be any valid value. The
macro is expected to take a single argument.
```
let list1 = [1, 2, 3, 4];
let mapper = macro(item) => { result = item + 1 };
map mapper.result list1 == [2, 3, 4, 5];
```
**For Tuples**
Macros for mapping across a tuple are expected to take two arguments. The first
argument is the name of the field. The second argument is the value in that
field. The result field should be a two item list with the first item being the
new field name and the second item being the new value.
```
let test_tpl = {
foo = "bar",
quux = "baz",
};
let tpl_mapper = macro(name, val) => {
result = select name, [name, val], {
"foo" = ["foo", "barbar"],
quux = ["cute", "pygmy"],
},
};
map tpl_mapper.result test_tpl == {foo = "barbar", cute = "pygmy"};
```
### Filter expressions
Filter expressions should return a result field with false or NULL for items to
filter out of the list or tuple. Any other value in the return field results in
the item or field staying in the resulting list or tuple.
**Lists**
```
let list2 = ["foo", "bar", "foo", "bar"];
let filtrator = macro(item) => {
result = select item, NULL, {
foo = item,
},
};
filter filtrator.result list2 == ["foo", "foo"];
```
**Tuples**
```
let test_tpl = {
foo = "bar",
quux = "baz",
};
let tpl_filter = macro(name, val) => {
result = name != "foo",
};
filter tpl_filter.result test_tpl == { quux = "baz" };
```
2019-01-04 10:34:57 -06:00
Include expressions
-------------------
UCG can include the contents of other files as an expression. Currently we only
support strings but we plan to support yaml, and json and possibly base64 encoding
in the future. include expressions start with the `include` keyword a type (currently only `str` ), and a path. Relative paths are calculated relative to the including file.
```
let script = include str "./script.sh";
```
2018-11-17 13:01:37 -06:00
Conditionals
----------
2018-11-18 14:23:35 -06:00
UCG supports a limited conditional expression called a select. A select
expression starts with the `select` keyword and is followed by a an expression
2018-11-26 23:03:11 -06:00
resolving to a string or boolean naming the field to select, an expression
resolving to the default value, and finally a tuple literal to select the field
from. If the field selected is not in the tuple then the default value will be
used.
2018-11-17 13:01:37 -06:00
```
let want = "baz";
// field default
select want, "quux", {
baz = "foo",
fuzz = "bang",
}; // result will be "foo"
// field default
select "quack", "quux", {
baz = "foo",
fuzz = "bang",
}; // result will be "quux"
2018-11-26 23:03:11 -06:00
let ifresult = select true, NULL, {
true = "true result",
false = "false result",
}; // result will be "true result"
2018-11-17 13:01:37 -06:00
```
Macros
-----
2018-11-18 14:23:35 -06:00
Macros look like functions but they are resolved at compile time and
configurations don't execute so they never appear in output. Macros do not
close over their environment so they can only reference values defined in their
arguments. They can't refer to bindings or other macros defined elsewhere. They
are useful for constructing tuples of a certain shape or otherwise promoting
data reuse. You define a macro with the `macro` keyword followed by the
arguments in parentheses, a `=>` , and then a tuple literal.
2018-11-17 13:01:37 -06:00
```
let mymacro = macro (arg1, arg2) => {
host = arg1,
port = arg2,
connstr = "couchdb://@: @" % (arg1, arg2),
}
let my_dbconf = mymacro("couchdb.example.org", "9090");
let my_dbhost = dbconf.host;
```
2018-11-23 13:12:30 -06:00
Modules
-------
UCG has another form of reusable execution that is a little more composable than macros
are. Modules allow you to parameterize a set of statements and build the statements
later. Modules are an expression. They can be bound to a value and then reused later.
2019-01-05 09:56:07 -06:00
Modules do not close over their environment by they can import other UCG files into
2018-11-23 13:12:30 -06:00
the module using import statements.
Module expressions start with the module keyword followed by a tuple representing their
parameters with any associated default values. The body of the module is separated from
the parameter tuple by the `=>` symbol and is delimited by `{` and `}` respectively.
2019-01-05 09:56:07 -06:00
The body of the module can contain any valid UCG statement.
2018-11-23 13:12:30 -06:00
```
let top_mod = module {
deep_value = "None",
} => {
2019-01-05 09:56:07 -06:00
import "shared.UCG" as shared_macros;
2018-11-23 13:12:30 -06:00
let embedded_def = module {
deep_value = "None",
} => {
let value = mod.deep_value;
};
let embedded = embedded_def{deep_value = mod.deep_value};
};
```
You instantiate a module via the copy expression. The resulting module instance can
reference the bindings in the module similarly to selecting a tuple field or a binding
from an imported file.
```
let embedded_default_params = top_mod{};
embedded_default_params.embedded.value == "None";
let embedded_with_params = embedded_mod{deep_value = "Some"};
embedded_with_params.embedded.value == "Some";
```
2018-11-17 22:28:25 -06:00
Next: < a href = "/reference/statements" > Statements< / a >