# Format Function v2 Migration Guide ## Overview 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. ### What Changed | 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 | ### Why This Change 1. **Consistency**: Handlebars-style interpolation is more widely recognized in modern templating 2. **Expressiveness**: Full expressions can now be evaluated inside interpolation blocks 3. **Context integration**: Values come from the EvaluationContext, enabling more dynamic templates 4. **Readability**: `{{name}}` is more self-documenting than `%s` with positional arguments --- ## Breaking Changes ### Removed Features 1. **Format specifiers** - All printf-style specifiers removed: - `%s` (string) - `%d`, `%i` (integer) - `%f` (float) - `%.Nf` (float with precision) - `%x`, `%X` (hexadecimal) - `%o` (octal) - `%%` (literal percent) 2. **Precision/width control** - No more `%.2f` or `%10s` formatting 3. **Positional arguments** - No additional arguments after the template 4. **Length modifiers** - No more `ll`, `h`, `l`, etc. ### Error Handling Changes | 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 | --- ## Migration Examples ### Basic String Substitution **Before:** ```javascript format("Hello, %s!", name) ``` **After:** ```javascript format("Hello, {{name}}!") ``` ### Integer Formatting **Before:** ```javascript format("You have %d items", count) format("ID: %i", id) ``` **After:** ```javascript format("You have {{count}} items") format("ID: {{id}}") ``` ### Multiple Arguments **Before:** ```javascript format("%s #%i: %s", category, id, title) ``` **After:** ```javascript format("{{category}} #{{id}}: {{title}}") ``` ### Float/Double Values **Before:** ```javascript format("Price: %.2f", price) // With precision format("Value: %f", amount) // Without precision ``` **After:** ```javascript 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. ### Hexadecimal Output **Before:** ```javascript format("Hex: 0x%x", value) format("HEX: 0x%X", value) ``` **After:** ```javascript // No direct equivalent - use stringify with pre-formatted value // Or add a hex() helper function to your context ``` ### Literal Percent Sign **Before:** ```javascript format("100%% complete") ``` **After:** ```javascript format("100% complete") // % is now a literal character ``` --- ## New Features ### Expression Evaluation The new `format()` supports full expressions inside interpolation blocks: ```javascript // 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}}") ``` ### Property Access Access nested properties directly in templates: ```javascript // 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 Handling Whitespace inside `{{}}` is automatically trimmed: ```javascript format("Value: {{name}}") // Standard format("Value: {{ name }}") // With spaces - equivalent format("Value: {{ name }}") // Multiple spaces - equivalent ``` ### Empty Expressions Empty expressions produce empty strings: ```javascript format("Hello{{}}World") // → "HelloWorld" format("Hello{{ }}World") // → "HelloWorld" ``` --- ## Escape Sequences To include literal braces in your output, use backslash escaping: ### Literal `{{` ```javascript format("Template syntax: \\{{name}}") // Output: "Template syntax: {{name}}" ``` ### Literal `}}` ```javascript format("End of block: \\}}") // Output: "End of block: }}" ``` ### Literal Backslash ```javascript format("Path: C:\\\\Users") // Output: "Path: C:\Users" ``` ### Escape Sequence Summary | Sequence | Output | |----------|--------| | `\{{` | `{{` | | `\}}` | `}}` | | `\\` | `\` | --- ## The `stringify()` Function A new `stringify()` global function is available as an alternative for simple concatenation: ### Basic Usage ```javascript stringify(42) // → "42" stringify("Hello", " ", "World") // → "Hello World" stringify(1, " + ", 2, " = ", 3) // → "1 + 2 = 3" ``` ### With Variables ```javascript stringify("The answer is: ", answer) stringify(name, " is ", age, " years old") ``` ### With Expressions ```javascript stringify("Sum: ", 2 + 3) // → "Sum: 5" stringify("Is active: ", active) // → "Is active: true" ``` ### Null Handling ```javascript stringify(null) // → "" (empty string) stringify("Value: ", null) // → "Value: " ``` ### Boolean Output ```javascript stringify(true, " or ", false) // → "true or false" ``` ### When to Use `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 | --- ## Error Handling ### Common Errors #### Unclosed Expression ```javascript format("Hello {{name") // Error: INVALID_SYNTAX - "Unclosed expression at position 6: missing }}" ``` **Fix:** Ensure all `{{` have matching `}}` ```javascript format("Hello {{name}}") ``` #### Invalid Expression Syntax ```javascript format("Value: {{1 +}}") // Error: INVALID_SYNTAX - "Error in expression '1 +': ..." ``` **Fix:** Ensure expressions are syntactically valid ```javascript format("Value: {{1 + 2}}") ``` #### Missing Variable ```javascript format("Hello {{name}}") // Error: NON_EXISTANT_PROPERTY if 'name' not in context ``` **Fix:** Ensure all referenced variables exist in the EvaluationContext ```javascript // Before evaluating, ensure context has the variable: props.set("name", new NativeElement("World")); ``` --- ## Migration Checklist - [ ] Identify all `format()` calls in your codebase - [ ] Replace `%s` with `{{variableName}}` - [ ] Replace `%d`, `%i` with `{{variableName}}` - [ ] Replace `%f` with `{{variableName}}` - [ ] Remove variadic arguments from `format()` calls - [ ] Ensure variables are available in EvaluationContext - [ ] Update any `%%` to single `%` - [ ] Remove precision specifiers (`%.2f`) - pre-format if needed - [ ] Consider using `stringify()` for simple concatenation - [ ] Test all format strings with edge cases --- ## FAQ ### Q: How do I format numbers with precision? **A:** Precision formatting is not currently supported in `format()`. Options: 1. Pre-format values before adding to context: ```vala var formatted_price = "%.2f".printf(price); props.set("price", new NativeElement(formatted_price)); ``` 2. Use `stringify()` with pre-formatted values: ```javascript stringify("$", formatted_price) ``` ### Q: How do I output a literal `{{` in my template? **A:** Use the escape sequence `\{{`: ```javascript format("Use \\{{variable}} for interpolation") // Output: "Use {{variable}} for interpolation" ``` ### Q: What happens if a variable is missing from the context? **A:** A `NON_EXISTANT_PROPERTY` error is thrown. Ensure all variables referenced in templates exist in the EvaluationContext before evaluation. ### Q: Can I use nested expressions like `{{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. ### Q: How do I convert from hexadecimal formatting? **A:** There is no direct equivalent. Pre-format the hexadecimal string before adding to context: ```vala var hex_value = "0x%x".printf(value); props.set("hex", new NativeElement(hex_value)); ``` ### Q: Can I still use `format()` with no placeholders? **A:** Yes, templates without `{{}}` are returned as-is: ```javascript format("Just a plain string") // → "Just a plain string" ``` ### Q: What is the difference between `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 values ### Q: How do null values behave? **A:** Null values produce empty strings: ```javascript format("Value: '{{value}}'") // With null value → "Value: ''" stringify(null) // → "" ``` --- ## Complete Migration Example ### Before (v1) ```vala // Setting up context var props = new PropertyDictionary(); props.set("category", new NativeElement("Product")); props.set("id", new NativeElement(123)); props.set("title", new NativeElement("Widget")); props.set("price", new NativeElement(19.99)); props.set("discount", new NativeElement(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)" ``` ### After (v2) ```vala // Setting up context - same as before var props = new PropertyDictionary(); props.set("category", new NativeElement("Product")); props.set("id", new NativeElement(123)); props.set("title", new NativeElement("Widget")); props.set("price", new NativeElement(19.99)); props.set("discount", new NativeElement(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)" ```