The format() global function in the Invercargill expression system has been redesigned from printf-style formatting to handlebars-style interpolation. This is a breaking change that affects all code using the format() function.
| Aspect | Before (v1) | After (v2) |
|---|---|---|
| Syntax | %s, %d, %f, etc. |
{{expression}} |
| Arguments | Template + variadic args | Template only |
| Value source | Positional arguments | EvaluationContext |
| Precision | %.2f, %5d supported |
No format specifiers |
{{name}} is more self-documenting than %s with positional argumentsFormat specifiers - All printf-style specifiers removed:
%s (string)%d, %i (integer)%f (float)%.Nf (float with precision)%x, %X (hexadecimal)%o (octal)%% (literal percent)Precision/width control - No more %.2f or %10s formatting
Positional arguments - No additional arguments after the template
Length modifiers - No more ll, h, l, etc.
| Scenario | Old Behavior | New Behavior |
|---|---|---|
| Missing argument | Error at runtime | N/A - no positional args |
Unclosed {{ |
N/A | INVALID_SYNTAX error |
Invalid expression in {{}} |
N/A | INVALID_SYNTAX with details |
| Missing variable | N/A | NON_EXISTANT_PROPERTY error |
Before:
format("Hello, %s!", name)
After:
format("Hello, {{name}}!")
Before:
format("You have %d items", count)
format("ID: %i", id)
After:
format("You have {{count}} items")
format("ID: {{id}}")
Before:
format("%s #%i: %s", category, id, title)
After:
format("{{category}} #{{id}}: {{title}}")
Before:
format("Price: %.2f", price) // With precision
format("Value: %f", amount) // Without precision
After:
format("Price: {{price}}") // No precision control
format("Value: {{amount}}")
Note: Precision formatting is not currently supported. If you need formatted numbers, consider pre-formatting values before passing to the expression context.
Before:
format("Hex: 0x%x", value)
format("HEX: 0x%X", value)
After:
// No direct equivalent - use stringify with pre-formatted value
// Or add a hex() helper function to your context
Before:
format("100%% complete")
After:
format("100% complete") // % is now a literal character
The new format() supports full expressions inside interpolation blocks:
// Arithmetic
format("Sum: {{a + b}}")
format("Product: {{x * y}}")
format("Total: {{price * quantity}}")
// Comparisons (outputs "true" or "false")
format("Is equal: {{a == b}}")
format("Is valid: {{age >= 18}}")
// Ternary expressions
format("Status: {{active ? 'active' : 'inactive'}}")
format("Role: {{isAdmin ? 'Administrator' : 'User'}}")
// Complex expressions
format("Result: {{(a + b) * c}}")
Access nested properties directly in templates:
// Object property access
format("User: {{user.name}}")
format("Email: {{user.email}}")
// Deep nesting
format("City: {{user.address.city}}")
format("ZIP: {{user.address.zipCode}}")
// Chained method calls
format("First: {{items.first().name}}")
format("Count: {{items.count()}}")
Whitespace inside {{}} is automatically trimmed:
format("Value: {{name}}") // Standard
format("Value: {{ name }}") // With spaces - equivalent
format("Value: {{ name }}") // Multiple spaces - equivalent
Empty expressions produce empty strings:
format("Hello{{}}World") // → "HelloWorld"
format("Hello{{ }}World") // → "HelloWorld"
To include literal braces in your output, use backslash escaping:
{{format("Template syntax: \\{{name}}")
// Output: "Template syntax: {{name}}"
}}format("End of block: \\}}")
// Output: "End of block: }}"
format("Path: C:\\\\Users")
// Output: "Path: C:\Users"
| Sequence | Output |
|---|---|
\{{ |
{{ |
\}} |
}} |
\\ |
\ |
stringify() FunctionA new stringify() global function is available as an alternative for simple concatenation:
stringify(42) // → "42"
stringify("Hello", " ", "World") // → "Hello World"
stringify(1, " + ", 2, " = ", 3) // → "1 + 2 = 3"
stringify("The answer is: ", answer)
stringify(name, " is ", age, " years old")
stringify("Sum: ", 2 + 3) // → "Sum: 5"
stringify("Is active: ", active) // → "Is active: true"
stringify(null) // → "" (empty string)
stringify("Value: ", null) // → "Value: "
stringify(true, " or ", false) // → "true or false"
stringify() vs format()Use format() when... |
Use stringify() when... |
|---|---|
| You have a template string | You need simple concatenation |
| You want inline expressions | Values are pre-computed |
| The structure is complex | The output is straightforward |
| You need escape sequences | You don't need templating |
format("Hello {{name")
// Error: INVALID_SYNTAX - "Unclosed expression at position 6: missing }}"
Fix: Ensure all {{ have matching }}
format("Hello {{name}}")
format("Value: {{1 +}}")
// Error: INVALID_SYNTAX - "Error in expression '1 +': ..."
Fix: Ensure expressions are syntactically valid
format("Value: {{1 + 2}}")
format("Hello {{name}}")
// Error: NON_EXISTANT_PROPERTY if 'name' not in context
Fix: Ensure all referenced variables exist in the EvaluationContext
// Before evaluating, ensure context has the variable:
props.set("name", new NativeElement<string?>("World"));
format() calls in your codebase%s with {{variableName}}%d, %i with {{variableName}}%f with {{variableName}}format() calls%% to single %%.2f) - pre-format if neededstringify() for simple concatenationA: Precision formatting is not currently supported in format(). Options:
Pre-format values before adding to context:
var formatted_price = "%.2f".printf(price);
props.set("price", new NativeElement<string?>(formatted_price));
Use stringify() with pre-formatted values:
stringify("$", formatted_price)
{{ in my template?A: Use the escape sequence \{{:
format("Use \\{{variable}} for interpolation")
// Output: "Use {{variable}} for interpolation"
A: A NON_EXISTANT_PROPERTY error is thrown. Ensure all variables referenced in templates exist in the EvaluationContext before evaluation.
{{a {{b}} c}}?A: No, nested expressions are not supported. The first }} will close the expression block. Consider restructuring your template or pre-computing values.
A: There is no direct equivalent. Pre-format the hexadecimal string before adding to context:
var hex_value = "0x%x".printf(value);
props.set("hex", new NativeElement<string?>(hex_value));
format() with no placeholders?A: Yes, templates without {{}} are returned as-is:
format("Just a plain string") // → "Just a plain string"
to_string() and stringify()?A:
Element.to_string() returns a debug representation like Element[string]Element.stringify() returns the actual value as a string (e.g., "hello" or "42")stringify() global function concatenates multiple stringified valuesA: Null values produce empty strings:
format("Value: '{{value}}'") // With null value → "Value: ''"
stringify(null) // → ""
// Setting up context
var props = new PropertyDictionary();
props.set("category", new NativeElement<string?>("Product"));
props.set("id", new NativeElement<int?>(123));
props.set("title", new NativeElement<string?>("Widget"));
props.set("price", new NativeElement<double?>(19.99));
props.set("discount", new NativeElement<double?>(0.15));
var context = new EvaluationContext(props);
// Format call with multiple arguments
var expr = ExpressionParser.parse(
"format(\"%s #%d: %s - $%.2f (%.0f%% off)\", category, id, title, price, discount * 100)"
);
var result = expr.evaluate(context);
// Output: "Product #123: Widget - $19.99 (15% off)"
// Setting up context - same as before
var props = new PropertyDictionary();
props.set("category", new NativeElement<string?>("Product"));
props.set("id", new NativeElement<int?>(123));
props.set("title", new NativeElement<string?>("Widget"));
props.set("price", new NativeElement<double?>(19.99));
props.set("discount", new NativeElement<double?>(0.15));
var context = new EvaluationContext(props);
// Format call - template only, expressions inline
var expr = ExpressionParser.parse(
"format(\"{{category}} #{{id}}: {{title}} - ${{price}} ({{discount * 100}}% off)\")"
);
var result = expr.evaluate(context);
// Output: "Product #123: Widget - $19.99 (15% off)"