summaryrefslogtreecommitdiffhomepage
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md856
1 files changed, 856 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3b8fcf0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,856 @@
+## ABOUT
+
+An utpl template consists of arbitrary plain text which is outputted as-is
+while control flow or expression logic is embedded in blocks that may appear
+anywhere throughout the template.
+
+
+## BLOCKS
+
+There are three kinds of blocks; expression blocks, statement blocks and
+comment blocks. The former two embed code logic using a C-like syntax while
+the latter comment block type is simply discarded during processing.
+
+
+### 1. STATEMENT BLOCKS
+
+Statement blocks are enclosed in an opening `{%` and a closing `%}` tag and
+may contain any number of script code statements, even entire programs.
+
+It is allowed to omit the closing `%}` of a statement block to parse the
+entire remaining source text after the opening tag as utpl script.
+
+By default, statement blocks produce no output and the entire block is
+reduced to an empty string during template evaluation but contained script
+code might invoke functions such as `print()` to explicitly output contents.
+
+For example the following template would result in "The epoch is odd" or
+"The epoch is even", depending on the current epoch value:
+
+`The epoch is {% if (time() % 2): %}odd{% else %}even{% endif %}!`
+
+
+### 2. EXPRESSION BLOCKS
+
+Expression blocks are enclosed in an opening `{{` and a closing `}}` tag and
+may only contain a single expression statement (multiple expressions may be
+chained with comma). The implicit result of the rightmost evaluated expression
+is used as output when processing the block.
+
+For example the template `Hello world, {{ getenv("USER") }}!` would result in
+the output "Hello world, user!" where `user` would correspond to the name of
+the current user executing the utpl interpreter.
+
+
+### 3. COMMENT BLOCKS
+
+Comment blocks, which are denoted with an opening '{#' and a closing '#}' tag
+may contain arbitrary text except the closing '#}' tag itself. Comments blocks
+are completely stripped during processing and are replaced with an empty string.
+
+The following example template would result in the output "Hello world":
+
+`Hello {# mad #}word`
+
+
+### WHITESPACE
+
+Each block start tag may be suffixed with a dash to strip any whitespace
+before the block and likewise any block end tag may be prefixed with a dash
+to strip any whitespace following the block.
+
+Without using whitespace stripping, the following example:
+
+```
+This is a first line
+{% for (x in [1, 2, 3]): %}
+This is item {{ x }}.
+{% endfor %}
+This is the last line
+```
+
+Would result in the following output:
+
+```
+This is a first line
+
+This is item 1.
+This is item 2.
+This is item 3.
+
+This is the last line
+```
+
+By adding a trailing dash to apply whitespace stripping after the block, the
+empty lines can be eliminated:
+
+```
+This is a first line
+{% for (x in [1, 2, 3]): -%}
+This is item {{ x }}.
+{% endfor -%}
+This is the last line
+```
+
+Output:
+
+```
+This is a first line
+This is item 1.
+This is item 2.
+This is item 3.
+This is the last line
+```
+
+By applying whitespace stripping before the block, all lines can be joined
+into a single output line:
+
+```
+This is a first line
+{%- for (x in [1, 2, 3]): -%}
+This is item {{ x }}.
+{%- endfor -%}
+This is the last line
+```
+
+Output:
+
+```
+This is a first lineThis is item 1.This is item 2.This is item 3.This is the last line
+```
+
+## SCRIPT LANGUAGE
+
+The language used within statement and expression blocks uses untyped variables
+and employs a simplified JavaScript like syntax.
+
+Utpl script implements function scoping and differentiates between local and global variables. Each function has its own private scope while executing and
+local variables declared inside a function are not accessible in the outer
+calling scope.
+
+### 1. Data types
+
+Utpl supports seven different basic types as well as an additional special
+function type. The supported types are:
+
+ - Boolean values (`true` or `false`)
+ - Integer values (`-9223372036854775808` to `+9223372036854775807`)
+ - Double values (`-1.7e308` to `+1.7e308`)
+ - String values (e.g. `'Hello world!'` or `"Sunshine \u2600!"`)
+ - Array values (e.g. `[1, false, "foo"]`)
+ - Object values (e.g. `{ foo: true, "bar": 123 }`)
+ - Null value (`null`)
+
+Utpl utilizes reference counting to manage memory used for variables and values
+and frees data automatically as soon as values go out of scope.
+
+Numeric values are either stored as signed 64bit integers or as IEEE 756 double
+value. Conversion between integer and double values can happen implicitly, e.g.
+through numeric operations, or explicitely, e.g. by invoking functions such as
+`int()`.
+
+### 2. Variables
+
+Variable names must start with a letter or an underscore and may only contain
+the characters `A`..`Z`, `a`..`z`, `0`..`9` or `_`. By prefixing a variable
+name with the keyword `local`, it is declared in the local function scope only
+and not visible outside anymore.
+
+```javascript
+{%
+
+ a = 1; // global variable assignment
+
+ function test() {
+ local b = 2; // declare `b` as local variable
+ a = 2; // overwrite global a
+ }
+
+ test();
+
+ print(a, "\n"); // outputs "2"
+ print(b, "\n"); // outputs nothing
+
+%}
+```
+
+### 3. Control statements
+
+Similar to JavaScript, utpl supports `if`, `for` and `while` statements to
+control execution flow.
+
+#### 3.1. Conditional statement
+
+If/else blocks can be used execute statements depending on a condition.
+
+```javascript
+{%
+
+ user = getenv("USER");
+
+ if (user == "alice") {
+ print("Hello Alice!\n");
+ }
+ else if (user == "bob") {
+ print("Hello Bob!\n");
+ }
+ else {
+ print("Hello guest!\n");
+ }
+
+%}
+```
+
+If only a single statement is wrapped by an if or else branch, the enclosing
+curly braces may be omitted:
+
+```javascript
+{%
+
+ if (rand() == 3)
+ print("This is quite unlikely\n");
+
+%}
+```
+
+#### 3.2. Loop statements
+
+Utpl script supports three different flavors of loop control statements; a
+`while` loop that executes enclosed statements as long as the loop condition is
+fulfilled, a `for in` loop that iterates keys of objects or items of arrays and
+a counting `for` loop that is a variation of the `while` loop.
+
+```javascript
+{%
+
+ i = 0;
+ arr = [1, 2, 3];
+ obj = { Alice: 32, Bob: 54 };
+
+ // execute as long as condition is true
+ while (i < length(arr)) {
+ print(arr[i], "\n");
+ i++;
+ }
+
+ // execute for each item in arr
+ for (n in arr) {
+ print(n, "\n");
+ }
+
+ // execute for each key in obj
+ for (person in obj) {
+ print(person, " is ", obj[person], " years old.\n");
+ }
+
+ // execute initialization statement (j = 0) once
+ // execute as long as condition (j < length(arr)) is true
+ // execute step statement (j++) after each iteration
+ for (j = 0; j < length(arr); j++) {
+ print(arr[j], "\n");
+ }
+
+%}
+```
+
+#### 3.3. Alternative syntax
+
+Since conditional statements and loops are often used for template formatting
+purposes, e.g. to repeat a specific markup for each item of a list, utpl
+supports an alternative syntax that does not require curly braces to group
+statements but that uses explicit end keywords to denote the end of the control
+statement body for better readability instead.
+
+The following two examples first illustrate the normal syntax, followed by the
+alternative syntax that is more suitable for statement blocks:
+
+```
+Printing a list:
+{% for (n in [1, 2, 3]) { -%}
+ - Item #{{ n }}
+{% } %}
+```
+
+The alternative syntax replaces the opening curly brace (`{`) with a colon
+(`:`) and the closing curly brace (`}`) with and explicit `endfor` keyword:
+
+```
+Printing a list:
+{% for (n in [1, 2, 3]): -%}
+ - Item #{{ n }}
+{% endfor %}
+```
+
+For each control statement type, a corresponding alternative end keyword is defined:
+
+ - `if (...): ... endif`
+ - `for (...): ... endfor`
+ - `while (...): ... endwhile`
+
+
+### 4. Functions
+
+Utpl scripts may define functions to group repeating operations into reusable
+operations. Functions can be both declared with a name, in which case they're
+automatically registered in the current scope, or anonymously which allows
+assigning the resulting value to a variable, e.g. to build arrays or objects of
+functions:
+
+```javascript
+{%
+
+ function duplicate(n) {
+ return n * 2;
+ }
+
+ local utilities = {
+ concat: function(a, b) {
+ return "" + a + b;
+ },
+ greeting: function() {
+ return "Hello, " + getenv("USER") + "!";
+ }
+ };
+
+-%}
+
+The duplicate of 2 is {{ duplicate(2) }}.
+The concatenation of 'abc' and 123 is {{ utilities.concat("abc", 123) }}.
+Your personal greeting is: {{ utilities.greeting() }}.
+```
+
+#### 4.1. Alternative syntax
+
+Function declarations support the same kind of alternative syntax as defined
+for control statements (3.3.)
+
+The alternative syntax replaces the opening curly brace (`{`) with a colon
+(`:`) and the closing curly brace (`}`) with and explicit `endfunction`
+keyword:
+
+```
+{% function printgreeting(name): -%}
+ Hallo {{ name }}, nice to meet you.
+{% endfunction -%}
+
+<h1>{{ printgreeting("Alice") }}</h1>
+```
+
+
+### 5. Operators
+
+Similar to JavaScript and C, utpl scripts support a range of different
+operators to manipulate values and variables.
+
+#### 5.1. Arithmetic operations
+
+The operators `+`, `-`, `*`, `/`, `%`, `++` and `--` allow to perform
+additions, substractions, multiplications, divisions, modulo increment or
+decrement operations respectively where the result depends on the type of
+involved values.
+
+The `++` and `--` operators are unary, means that they only apply to one
+operand. The `+` and `-` operators may be used in unary context to either
+convert a given value to a numeric value or to negate a given value.
+
+If either operand of the `+` operator is a string, the other one is converted
+to a string value as well and a concatenated string is returned.
+
+All other arithmetic operators coerce their operands to numeric values.
+Fractional values are converted to doubles, other numeric values to integers.
+
+If either operand is a double, the other one is converted to a double value as
+well and a double result is returned.
+
+Divisions by zero result in the special double value `Infinity`. If an operand
+cannot be converted to a numeric value, the result of the operation is the
+special double value `NaN`.
+
+```javascript
+{%
+ a = 2;
+ b = 5.2;
+ s1 = "125";
+ s2 = "Hello world";
+
+ print(+s1); // 125
+ print(+s2); // NaN
+ print(-s1); // -125
+ print(-s2); // NaN
+ print(-a); // -2
+
+ print(a++); // 2 (Return value of a, then increment by 1)
+ print(++a); // 4 (Increment by 1, then return value of a)
+
+ print(b--); // 5.2 (Return value of b, then decrement by 1)
+ print(--b); // 3.2 (Decrement by 1, then return value of b)
+
+ print(4 + 8); // 12
+ print(7 - 4); // 3
+ print(3 * 3); // 9
+
+ print(10 / 4); // 2 (Integer division)
+ print(10 / 4.0); // 2.5 (Double division)
+ print(10 / 0); // Infinity
+
+ print(10 % 7); // 3
+ print(10 % 7.0); // NaN (Modulo is undefined for non-integers)
+%}
+```
+
+#### 5.2. Bitwise operations
+
+The operators `&`, `|`, `^`, `<<`, `>>` and `~` allow to perform bitwise and,
+or, xor, left shift, right shift and complement operations respectively.
+
+The `~` operator is unary, means that is only applies to one operand.
+
+```javascript
+{%
+ print(0 & 0, 0 & 1, 1 & 1); // 001
+ print(0 | 0, 0 | 1, 1 | 1); // 011
+ print(0 ^ 0, 0 ^ 1, 1 ^ 1); // 010
+ print(10 << 2); // 40
+ print(10 >> 2); // 2
+ print(~15); // -16 (0xFFFFFFFFFFFFFFF0)
+%}
+```
+
+An important property of bitwise operators is that they're coercing their
+operand values to whole integers:
+
+```javascript
+{%
+ print(12.34 >> 0); // 12
+ print(~(~12.34)); // 12
+%}
+```
+
+#### 5.3. Relational operations
+
+The operators `==`, `!=`, `<`, `<=`, `>` and `>=` test whether their operands
+are equal, inequal, lower than, lower than/equal to, higher than or higher
+than/equal to each other respectively.
+
+If both operands are strings, their respective byte values are compared, if
+both are objects or array, their underlying memory addresses are compared.
+
+In all other cases, both operands are compared to numeric values and the
+compared with each other.
+
+This means that comparing values of different types will coerce them both to
+numbers.
+
+The result of the relational operation is a boolean indicating trueness.
+
+```javascript
+{%
+ print(123 == 123); // true
+ print(123 == "123"); // true!
+ print(123 < 456); // true
+ print(123 > 456); // false
+ print(123 != 456); // true
+ print(123 != "123"); // false!
+ print({} == {}); // false (two different anonymous objects)
+ a = {}; print(a == a); // true (same object)
+%}
+```
+
+#### 5.4. Logical operations
+
+The operators `&&`, `||` and `!` test whether their operands are both true,
+partially true or false respectively.
+
+In the case of `&&` the rightmost value is returned while `||` results in the
+first trueish value.
+
+The unary `!` operator will result in `true` if the operand is not trueish,
+otherwise it will result in `false`.
+
+Operands are evaluated from left to right while testing trueness, which means
+that expressions with side effects, such as function calls, are only executed
+if the preceeding condition was satisifed.
+
+```javascript
+{%
+ print(1 && 2 && 3); // 3
+ print(1 || 2 || 3); // 1
+ print(2 > 1 && 3 < 4); // true
+ print(!false); // true
+ print(!true); // false
+
+ res = test1() && test2(); // test2() is only called if test1() returns true
+%}
+```
+
+#### 5.5. Assignment operations
+
+Besides the basic assignment operator `=`, most other operators have a
+corresponding shortcut assignment operator which reads the specified variable,
+applies the operation and operand to it, and writes it back.
+
+The result of assignment expressions is the assigned value.
+
+```javascript
+{%
+ a = 1; // assign 1 to variable a
+ a += 2; // a = a + 2;
+ a -= 3; // a = a - 3;
+ a *= 4; // a = a * 4;
+ a /= 5; // a = a / 5;
+ a %= 6; // a = a % 6;
+ a &= 7; // a = a & 7;
+ a |= 8; // a = a | 8;
+ a ^= 9; // a = a ^ 9;
+ a <<= 10; // a = a << 10;
+ a >>= 11; // a = a >> 11;
+%}
+```
+
+### 6. Functions
+
+Utpl scripts may call a number of builtin functions to manipulate values or
+to output information.
+
+#### 6.1. `abs(x)`
+
+Returns the absolute value of the given operand. Results in `NaN` if operand is
+not convertible to number.
+
+```javascript
+abs(1); // 1
+abs(-2); // 2
+abs(-3.5); // 3.5
+abs("0x123"); // 291
+abs("-0x123"); // NaN
+abs([]); // NaN
+```
+
+#### 6.2. `atan2(x, y)`
+
+Calculates the principal value of the arc tangent of x/y, using the signs of
+the two arguments to determine the quadrant of the result.
+
+#### 6.3. `chr(n1, ...)`
+
+Converts each given numeric value to a byte and return the resulting string.
+Invalid numeric values or values < 0 resul in `\0` bytes, values larger than
+255 are truncated to 255.
+
+```javascript
+chr(65, 98, 99); // "Abc"
+chr(-1, 300); // string consisting of "\0" and "\377" bytes
+```
+
+#### 6.4. `cos(x)`
+
+Return the cosine of x, where x is given in radians.
+
+#### 6.5. `delete(obj, key1, ...)`
+
+Delete the given key(s) from the object passed as first argument. Returns the
+corresponding value of the last removed key, if any.
+
+#### 6.6. `die(msg)`
+
+Raise an exception with the given message and abort execution.
+
+#### 6.7. `exists(obj, key)`
+
+Return `true` if the given key is present within the object passed as first
+argument, otherwise `false`.
+
+#### 6.8. `exit(n)`
+
+Terminate the interpreter with the given exit code.
+
+#### 6.9. `exp(n)`
+
+Return the value of e (the base of natural logarithms) raised to the power
+of n.
+
+#### 6.10. `filter(arr, fn)`
+
+Filter the array passed as first argument by invoking the function specified
+in the second argument for each array item.
+
+If the invoked function returns a trueish result, the item is retained,
+otherwise it is dropped. The filter function is invoked with three arguments:
+
+ 1. The array value
+ 2. The current index
+ 3. The array being filtered
+
+Returns the filtered array.
+
+```javascript
+// filter out any empty string:
+a = filter(["foo", "", "bar", "", "baz"], length)
+// a = ["foo", "bar", "baz"]
+
+// filter out any non-number type:
+a = filter(["foo", 1, true, null, 2.2], function(v) {
+ return (type(v) == "int" || type(v) == "double");
+});
+// a = [1, 2.2]
+```
+
+#### 6.11. `getenv(name)`
+
+Return the value of the given environment variable.
+
+#### 6.12. `hex(x)`
+
+Convert the given hexadecimal string into a number.
+
+#### 6.13. `index(arr_or_str, needle)`
+
+Find the given value passed as second argument within the array or string
+specified in the first argument.
+
+Returns the first matching array index or first matching string offset or `-1`
+if the value was not found.
+
+Returns `null` if the first argument was neither an array, nor a string.
+
+#### 6.14. `int(x)`
+
+Convert the given value to an integer. Returns `NaN` if the value is not
+convertible.
+
+#### 6.15. `join(sep, arr)`
+
+Join the array passed as 2nd argument into a string, using the separator passed
+in the first argument as glue. Returns `null` if the second argument is not an
+array.
+
+#### 6.16. `keys(obj)`
+
+Return an array of all key names present in the passed object. Returns `null`
+if the given argument is no object.
+
+#### 6.17. `lc(s)`
+
+Convert the given string to lowercase and return the resulting string.
+Returns `null` if the given argument could not be converted to a string.
+
+#### 6.18. `length(arr_or_str)`
+
+Return the length of the given array or string. Returns `null` if the given
+argument is neither an array, nor a string.
+
+#### 6.19. `log(x)`
+
+Return the natural logarithm of x.
+
+#### 6.20. `ltrim(s, c)`
+
+Trim any of the specified characters in `c` from the start of `str`.
+If the second argument is omitted, trims the characters, `' '` (space), `\t`,
+`\r` and `\n`.
+
+```javascript
+ltrim(" foo \n") // "foo \n"
+ltrim("--bar--", "-") // "bar--"
+```
+
+#### 6.21. `map(arr, fn)`
+
+Transform the array passed as first argument by invoking the function specified
+in the second argument for each array item.
+
+The result of the invoked function is put into the resulting array.
+The map function is invoked with three arguments:
+
+ 1. The array value
+ 2. The current index
+ 3. The array being filtered
+
+Returns the transformed array.
+
+```javascript
+// turn into array of string lengths:
+a = map(["Apple", "Banana", "Bean"], length)
+// a = [5, 6, 4]
+
+// map to type names:
+a = map(["foo", 1, true, null, 2.2], type);
+// a = ["string", "int", "bool", null, "double"]
+```
+
+#### 6.22. `ord(s)`
+
+Returns the byte value of the first character in the given string.
+
+```javascript
+ord("Abc"); // 65
+```
+
+#### 6.23. `pop(arr)`
+
+Pops the last item from the given array and returns it. Returns `null` if the
+array was empty or if a non-array argument was passed.
+
+#### 6.24. `print(x, ...)`
+
+Print any of the given values to stdout. Arrays and objects are converted to
+their JSON representation.
+
+Returns the amount of bytes printed.
+
+#### 6.25. `push(arr, v1, ...)`
+
+Push the given argument(s) to the given array. Returns the last pushed value.
+
+#### 6.26. `rand()`
+
+Returns a random number. If `srand()` has not been called already, it is
+automatically invoked passing the current time as seed.
+
+#### 6.27. `reverse(arr_or_str)`
+
+If an array is passed, returns the array in reverse order. If a string is
+passed, returns the string with the sequence of the characters reversed.
+
+Returns `null` if neither an array nor a string were passed.
+
+#### 6.28. `rindex(arr_or_str, needle)`
+
+Find the given value passed as second argument within the array or string
+specified in the first argument.
+
+Returns the last matching array index or last matching string offset or `-1`
+if the value was not found.
+
+Returns `null` if the first argument was neither an array, nor a string.
+
+#### 6.29. `rtrim(str, c)`
+
+Trim any of the specified characters in `c` from the end of `str`.
+If the second argument is omitted, trims the characters, `' '` (space), `\t`,
+`\r` and `\n`.
+
+```javascript
+rtrim(" foo \n") // " foo"
+rtrim("--bar--", "-") // "--bar"
+```
+
+#### 6.30. `shift(arr)`
+
+Pops the first item from the given array and returns it. Returns `null` if the
+array was empty or if a non-array argument was passed.
+
+#### 6.31. `sin(x)`
+
+Return the sine of x, where x is given in radians.
+
+#### 6.32. `sort(arr, fn)`
+
+Sort the given array according to the given sort function. If no sort
+function is provided, a default ascending sort order is applied.
+
+```javascript
+sort([8, 1, 5, 9]) // [1, 5, 8, 9]
+sort(["Bean", "Orange", "Apple"], function(a, b) {
+ return length(a) < length(b);
+}) // ["Bean", "Apple", "Orange"]
+```
+
+#### 6.33. `splice(arr, off, len, ...)`
+
+Removes the elements designated by `off` and `len` from the given an array,
+and replaces them with the additional arguments passed, if any. Returns the
+last element removed, or `null` if no elements are removed. The array grows or shrinks as necessary.
+
+If `off` is negative then it starts that far from the end of the array. If
+`len` is omitted, removes everything from `off` onward. If `len` is negative,
+removes the elements from `off` onward except for `-len` elements at the end of
+the array. If both `off` and `len` are omitted, removes everything.
+
+#### 6.34. `split(sep, str)`
+
+Split the given string using the separator passed as first argument and return
+an array containing the resulting pieces.
+
+```javascript
+split(",", "foo,bar,baz") // ["foo", "bar", "baz"]
+split("", "foobar") // ["f", "o", "o", "b", "a", "r"]
+```
+
+#### 6.35. `sqrt(x)`
+
+Return the nonnegative square root of x.
+
+#### 6.36. `srand(n)`
+
+Seed the PRNG using the given number.
+
+#### 6.37. `substr(str, off, len)`
+
+Extracts a substring out of `str` and returns it. First character is at offset
+zero. If `off` is negative, starts that far back from the end of the string.
+If `len` is omitted, returns everything through the end of the string. If `len`
+is negative, leaves that many characters off the end of the string.
+
+```javascript
+s = "The black cat climbed the green tree";
+
+substr(s, 4, 5); // black
+substr(s, 4, -11); // black cat climbed the
+substr(s, 14); // climbed the green tree
+substr(s, -4); // tree
+substr(s, -4, 2); // tr
+```
+
+#### 6.38. `time()`
+
+Returns the current UNIX epoch.
+
+```javascript
+time(); // 1598043054
+
+#### 6.39. `trim()`
+
+Trim any of the specified characters in `c` from the start and end of `str`.
+If the second argument is omitted, trims the characters, `' '` (space), `\t`,
+`\r` and `\n`.
+
+```javascript
+ltrim(" foo \n") // "foo"
+ltrim("--bar--", "-") // "bar"
+```
+
+#### 6.40. `type(x)`
+
+Returns the type of the given value which might be one of `function`, `object`,
+`array`, `double`, `int` or `bool`.
+
+Returns `null` when no value or `null` is passed.
+
+#### 6.41. `uchr(n1, ...)`
+
+Converts each given numeric value to an utf8 escape sequence and return the resulting string. Invalid numeric values or values outside the range `0` .. `0x10FFFF` are represented by the unicode replacement character `0xFFFD`.
+
+```javascript
+uchr(0x2600, 0x26C6, 0x2601); // "☀⛆☁"
+uchr(-1, 0x20ffff, "foo"); // "���"
+```
+
+#### 6.42. `uc(str)`
+
+Convert the given string to uppercase and return the resulting string.
+Returns `null` if the given argument could not be converted to a string.
+
+#### 6.43. `unshift(arr, v1, ...)`
+
+Add the given values to the beginning of the array passed as first argument.
+Returns the last value added to the array.
+
+#### 6.44. `values(obj)`
+
+Returns an array containing all values of the given object. Returns `null? if
+no object was passed.
+
+```javascript
+values({ foo: true, bar: false }); // [true, false]
+``` \ No newline at end of file