This article covers the syntax and functionality of the FormulaResolver – the core formula engine in the Cyncly Content Platform. It processes mathematical expressions, string manipulations, conditional logic, and variable/attribute resolution for product configuration.
Following the correct syntax ensures that formulas are properly interpreted and executed.
Getting started
Contextual Symbols and Variables
Within formulas, certain reserved symbols are used to represent item dimensions and attributes:
$PW$, $PH$, $PD$ – Represent the Parent Width, Parent Height, and Parent Depth.
$W$, $H$, $D$ – Refer to the Width, Height, and Depth of the current item.
#attribute_code# – Placeholder for referencing any attribute code dynamically.
Formula Declaration
To define a formula in supported fields, you must wrap the expression using @( ) to indicate that it should be evaluated as a formula.
Example:
@(#attribute_code# == '1' ? false : true)
? separates the condition from the true value
: separates the true value from the false value
== set the attribute value
' ' used to convert the sentence to text
Supported Formula Categories
Type Resolution
The FormulaResolver supports resolution to four primary types:
Number: Mathematical expressions and numeric values.
String: Text concatenation and manipulation.
Boolean: Logical expressions and conditions.
Vector3: 3D coordinate arrays.
Processing Pipeline
Input Formula → Sanitization → Marker Replacement → Syntax Translation → String Block Resolution → Formula Interpretation → Type Conversion → Result
Variables and Markers
Dollar Variables ( $...$ )
Variables enclosed in dollar signs represent dynamic properties from the evaluation context (current item).
Dimension Variables
| Variable | Description | Example |
|---|---|---|
| $W$ , $WIDTH$ | Current item width | $W$ + 100 → 600 (if width=500) |
| $H$ , $HEIGHT$ | Current item height | $H$ * 2 → 200 (if height=100) |
| $D$ , $DEPTH$ | Current item depth | $D$ - 50 → 450 (if depth=500) |
| $DW$ | Default width | $DW$ → 800 |
| $DH$ | Default height | $DH$ → 720 |
| $DD$ | Default depth | $DD$ → 600 |
| $CW$ | Configured width | $CW$ / 2 → 300 (if cWidth=600) |
| $CH$ | Configured height | $CH$ + 10 → 110 |
| $CD$ | Configured depth | $CD$ * 1.5 → 900 |
Parent/Root References
| Variable | Description | Example |
|---|---|---|
| $PW$ , $PARENTWIDTH$ | Parent item width | $PW$ / 2 → 400 (if parent width=800) |
| $PH$ , $PARENTHEIGHT$ | Parent item height | $PH$ - 100 → 620 |
| $PD$ , $PARENTDEPTH$ | Parent item depth | $PD$ → 600 |
| $RW$ | Root item width | $RW$ * 0.5 → 500 |
| $RH$ | Root item height | $RH$ → 2000 |
| $RD$ | Root item depth | $RD$ - $D$ → 100 |
| $parent.property$ | Any parent property | $parent.inverted$ → true |
| $p.property$ | Shorthand for parent | $p.width$ → 800 |
| $root.property$ | Any root property | $root.baseItemType$ → "cabinet" |
Instance Variables
| Variable | Description | Example |
|---|---|---|
| $N$ | Instance count (repetitions) | $N$ * 100 → 300 (if 3 repetitions) |
| $NI$ | Instance index (1-based) | $NI$ * 50 → 100 (if 2nd instance) |
| $I$ , $INVERTED$ | Item inverted state | $I$ ? -1 : 1 → -1 (if inverted) |
| $AI$, $accumulatedinverted$ | Item inversion accumulation state | $AI$ ? -1 : 1 (if inversion accumulation) |
Position Variables
| Variable | Description | Example |
|---|---|---|
| $X$ | X coordinate position | $X$ + 100 → 200 |
| $Y$ | Y coordinate position | $Y$ → 720 |
| $Z$ | Z coordinate position | $Z$ - 50 → -50 |
Display Units
| Variable | Description | Example |
|---|---|---|
| $UW$ | Display width (user units) | $UW$ → "24" (24 inches) |
| $UH$ | Display height | $UH$ → "30" |
| $UD$ | Display depth | $UD$ → "12" |
Other Variables
| Variable | Description | Example |
|---|---|---|
| $ENTITYNAME$ | Item name | $ENTITYNAME$ → "Door Panel" |
| $BASEITEMTYPE$ | Item classification | $BASEITEMTYPE$ → "door" |
| $characteristics.property$ | Classification characteristics | $characteristics.installationType$ → "Drop-In" |
Attribute Markers ( #...# )
Attributes enclosed in hash symbols reference catalog attributes.
Simple attribute reference
#MATERIAL_TYPE# → "Wood"
Parent attribute reference
#P.FINISH_COLOR# → "White"
- Nested path reference
#parent.DOOR_STYLE# → "Shaker"
- Root attribute reference
#root.CABINET_LINE# → "Premium"
Mathematical Operations
Basic Arithmetic
- Addition
"100 + 50" → 150
"$W$ + 200" → 800 (if width=600)
- Subtraction
"500 - 150" → 350
"$H$ - $D$" → 100 (if height=720, depth=620)
- Multiplication
"25 * 4" → 100
"$W$ * 0.5" → 300 (if width=600)
- Division
"100 / 4" → 25
"$PW$ / 2" → 400 (if parent width=800)
- Modulo
"10 % 3" → 1
"$W$ % 100" → 0 (if width=600)
- Parentheses
"(100 + 50) * 2" → 300
"$W$ / (2 + 1)" → 200 (if width=600)
Math Functions
- Rounding functions
"Math.floor(10.7)" → 10
"Math.ceil(10.3)" → 11
"Math.round(10.5)" → 11
"floor(10.7)" → 10 (Math. prefix optional)
- Power and roots
"Math.pow(2, 3)" → 8 (returns the base raised to the power of exponent)
"Math.sqrt(16)" → 4
"Math.sqr(9)" → 3
"pow($W$, 2)" → 360000 (if width=600)
- Absolute value
"Math.abs(-10)" → 10
"abs($X$ - $Y$)" → positive difference
- Min/Max
"Math.min(10, 20, 5)" → 5
"Math.max($W$, $H$)" → larger dimension
Trigonometric Functions
- Radian-based (standard) - compute sine, cosine, and tangent (radians)
"Math.sin(1.57)" → ~1 (sin of π/2)
"Math.cos(0)" → 1
"Math.tan(0.785)" → ~1 (tan of π/4)
- Degree-based (custom) - compute arc sine, cosine, and tangent (degrees)
"Math.dsin(90)" → 1 (sin of 90°)
"Math.dcos(45)" → 0.707...
"Math.dtan(45)" → 1
"dcos45" → 0.7071067811865476 (predefined constant)
- Inverse trigonometry
"Math.asin(1)" → 1.57... (radians)
"Math.dasin(1)" → 90 (degrees)
"Math.atan2(1, 1)" → 0.785... (radians)
"Math.datan2(1, 1)" → 45 (degrees)
Custom Mathematical Functions
Hypotenuse
Calculates the hypotenuse of a right triangle.
"hypotenuse(3, 4)" → 5
"hypotenuse($W$, $D$)" → diagonal measurement
MultipleOf
Rounds down to the nearest multiple of a given value.
"multipleOf(17, 5)" → 15 (nearest multiple of 5 below 17)
"multipleOf($W$, 32)" → 608 (if width=610, nearest 32mm increment)
"MultipleOf((1+3), 2)" → 4 (case-insensitive)
Scientific Notation
"1e10" → 10000000000
"1e-10" → 0.0000000001
"-1e-10" → -0.0000000001
String Operations
String Blocks ( @(...) )
String blocks allow dynamic content evaluation within strings.
Simple concatenation
"@('Hello' + ' ' + 'World')" → "Hello World"
String Concatenation
"#VARIABLE#" + "text"
Mixed types
"@('Count: ' + 5)" → "Count: 5"
"@($W$ + ' mm wide')" → "600 mm wide"
Nested evaluation
"Result: @(100 + 50)" → "Result: 150"
Variable substitution
"@('Item: ' + $ENTITYNAME$)" → "Item: Door Panel"
String Methods
Length
"length('Hello')" → 5
"length($ENTITYNAME$)" → length of item name
Contains/Includes
"contains('Hello World', 'World')" → true
"'Hello'.includes('ell')" → true (JavaScript style)
Starts/Ends With
"startsWith('Hello', 'He')" → true
"endsWith('World', 'ld')" → true
Substring
"substring('Hello', 0, 2)" → "He"
"substring($ENTITYNAME$, 0, 5)" → first 5 characters
Replace
"replace('Hello World', 'World', 'Universe')" → "Hello Universe"
"replace($ENTITYNAME$, ' ', '_')" → spaces replaced with underscores
Conditional Logic
Comparison Operators
- Equality (checks if two values are the same)
"10 == 10" → true
"'abc' == 'abc'" → true
"$W$ == 600" → true (if width=600)
- Inequality (checks if values are different)
"10 != 5" // → true
"'abc' != 'xyz'" → true
- Greater/Less than (checks if the left value is greater/smaller)
"10 > 5" → true
"10 = 10" → true
"10 >= 10" → true
"10 <= 5" → false
Boolean Operators
- AND operator
"true && true" → true
"(10 > 5) && (5 < 10)" → true
"$W$ > 500 && $H$ > 700" → depends on dimensions
- OR operator
"true || false" → true
"(10 > 20) || (5 < 10)" → true
- NOT operator
"!true" → false
"!(10 > 20)" → true
"!$INVERTED$" → opposite of inverted state
Ternary Operator
- Basic ternary
"true ? 'yes' : 'no'" → "yes"
"10 > 5 ? 100 : 200" → 100
"$W$ > 600 ? 'wide' : 'narrow'" → depends on width
- Nested ternary
"$W$ > 800 ? 'large' : $W$ > 600 ? 'medium' : 'small'"
- Complex conditions
"($W$ > 600 && $H$ > 700) ? 'big' : 'small'"
IN Operator
"'apple' in ['apple', 'banana', 'orange']" → true
"5 in [1, 2, 3, 4, 5]" → true
"$BASEITEMTYPE$ in ['door', 'drawer']" → depends on type
@(#attribute_code# in['1', '2', '3'])
This returns true if the attribute value matches any in the list.
Switch Statement
Not directly supported, but can be simulated with nested ternaries.
"@($TYPE$ == 'A' ? 'Type A' : $TYPE$ == 'B' ? 'Type B' : 'Default')"
Null Coalescing
Returns the right-hand value if the left-hand value is undefined.
"#OPTIONAL_ATTR# ?? 'default'" → "default" if OPTIONAL_ATTR undefined
"#COLOR#[#KEY#] ?? 'white'" → "white" if key not found in map
Attribute Maps
Attribute maps allow dictionary/lookup table functionality.
Basic Map Access
Map attribute with static key #COLOR_MAP#['red'] → "#FF0000"
Map attribute with dynamic key #SIZE_MAP#[#SELECTED_SIZE#] → value for selected size
Nested resolution #PRICE_MAP#[@($BASE_TYPE$ + '_' + $SIZE$)] → price for type-size combo
Map Types
- String map
#STRING_MAP# = {"key1": "value1", "key2": "value2"}
#STRING_MAP#['key1'] → "value1"
- Number map
#NUMBER_MAP# = {"small": 100, "medium": 200, "large": 300}
#NUMBER_MAP#[#SIZE#] → numeric value
- Boolean map
#BOOLEAN_MAP# = {"enabled": true, "disabled": false}
#BOOLEAN_MAP#[#STATUS#] → boolean value
- Map with Null Coalescing
"@(#COLOR_MAP#[#USER_COLOR#] ?? 'default_color')"
Returns 'default_color' if USER_COLOR key doesn't exist in COLOR_MAP
- String map
#Structure_Code#[#Structure#]
This syntax is used to dynamically reference and format values from a predefined data structure. Here's how each part works:
#Structure#: Refers to the structure name from which data will be retrieved.
#Structure_Code#: Specifies a specific field (in this case, the "Code") from the structure.
[#Structure#]: Used as a reference to the entire structure instance or record.
When combined as #Structure_Code#[#Structure#], it tells the system to:
Retrieve the Code field from the structure defined by #Structure#.
This is particularly useful when working with templates, mappings, or dynamic references, allowing the system to resolve field values based on structure definitions at runtime.
Unit Conversions
Explicit Conversions
- Inches to base unit (context-dependent)
"inches(1)" → 25.4 (if base unit is mm)
"@inches(12)" → 304.8 (12 inches to mm)
"inches(-1)" → -25.4 (negative values supported)
- Millimeters to base unit
"millimeters(100)" → 100 (if base unit is mm)
"millimeters(100)" → 3.937... (if base unit is inches)
Conversion Factors
The conversion depends on the catalog's base unit:
- mm (millimeters): Default metric unit
- cm (centimeters): Metric, larger scale
- m (meters): Metric, construction scale
- inches : Imperial unit 2032s : Special
- 2032nds of an inch unit
Complex Formula Example
Let's walk through a complex real-world formula:
Formula
"- 12.7 / 2 + $PW$ - @((#P.PullLocX#) ? #P.PullLocX# : 25.4)"
Step-by-Step Resolution
- Initial State:
Parent Width ( $PW$ ) = 600mm
Parent Pull Location X ( #P.PullLocX# ) = undefined
- Marker Replacement:
$PW$ → 600
#P.PullLocX# → undefined
- String Block Evaluation:
@((undefined) ? undefined : 25.4) → 25.4
- Mathematical Evaluation:
- 12.7 / 2 + 600 - 25.4
- 6.35 + 600 - 25.4
568.25
Another Complex Example
"@($W$ 600 ? multipleOf($W$ - 100, 32) + 16 : $W$ 400 ? $W$ - 50 : Math.max($W$, 350))"
Resolution with width = 650:
- First condition: 650 600 → true
- Execute first branch: multipleOf(650 - 100, 32) + 16
- Evaluate: multipleOf(550, 32) + 16
- Result: 544 + 16 = 560
Limitations and Quirks
String Handling Quirks
- Multiple quotes are collapsed
"''text''" → "'text'"
- Hash/Dollar signs without markers are preserved
"Price: $100 #1" → "Price: $100 #1"
- Empty string blocks are removed
"@()" → ""
Mathematical Edge Cases
- Division by zero returns Infinity (throws error)
"1/0" → Error (Infinity detected)
- NaN detection
"0/0" → Error (NaN detected)
- Double minus becomes plus
"10--5" → "10+5" → 15
Case Sensitivity
- Variables ( $...$ ) are case-insensitive for known symbols
- Attributes ( #...# ) are case-insensitive
- Function names are case-insensitive for most functions
- String comparisons are case-sensitive
Reserved Patterns
Certain patterns trigger special behavior:
- @(...) - String block evaluation
- !find(...)! - Legacy find syntax (deprecated)
- $p. - Converted to $parent.
- dcos45 - Replaced with precalculated value
Best Practices
Formula Design
- Keep formulas simple: Break complex logic into multiple attributes
- Use parentheses: Make precedence explicit to avoid confusion
- Validate inputs: Check for undefined values before use
- Use defaults: Leverage null coalescing for optional values
Performance Optimization
- Cache results: Use catalog attributes for expensive calculations
- Minimize string blocks: Only use @() when necessary
- Avoid deep nesting: Limit ternary operator depth
- Precompute when possible: Store common calculations
Debugging Tips
- Test incrementally: Build complex formulas step by step
- Use logging: The resolver supports ResolverLogTree for tracing
- Check types: Ensure type consistency in operations
- Validate context: Verify parent/root references exist
Common Patterns
- Safe parent reference with default
"$parent.width$ || 600"
- Percentage calculation
"$W$ * 0.75" // 75% of width
- Conditional sizing
"$W$ > 600 ? $W$ - 100 : $W$"
- Multi-condition logic
"($TYPE$ == 'A' && $SIZE$ 100) ? 'large-A' : 'standard'"
- Safe map access with default
"@(#OPTIONS_MAP#[#SELECTION#] ?? 'default_option')"
Error Handling
- Provide defaults for division
"$VALUE$ / ($DIVISOR$ || 1)" // Avoid division by zero
- Check for undefined attributes
"#OPTIONAL# ? #OPTIONAL# : 'default'"
- Validate ranges
"Math.max(100, Math.min(1000, $INPUT$))" // Clamp between 100-1000