Expr is intended for configuration logic, custom dialogs, G-code expression evaluation, probing scripts, ATC scripts, file processing helpers, and other host automation tasks.
It is not a real-time motion-control language. Time-critical motion control, hardware safety, and low-level device scheduling belong to the host application and controller firmware.
This page describes practical limits and performance behavior for user scripts.
Expr has internal guards that stop scripts before runaway parsing, recursion, or loops consume too much stack or CPU time.
Current release guards are:
| Limit | Current value | Notes |
|---|---|---|
| Parse depth | `3800` | Maximum nested expression/parser depth. Deeply nested parentheses, nested calls, and nested expressions contribute to this. |
| Include nesting depth | `16` | Include chains deeper than this are rejected. Recursive include chains are also rejected. |
| Evaluation depth | `5000` | Guard for recursive evaluation depth. Deep function recursion and deeply nested expression evaluation can reach this. |
| Iterative evaluation stack depth | `7600` | Guard for the evaluator's internal iterative stack. |
| `loop` iteration guard | `1000000` | A `loop` stops with an error after too many iterations. |
| `while` iteration guard | `1000000` | A `while` stops with an error after too many iterations. |
| `for` iteration guard | `2000000` | A `for` stops with an error after too many iterations. |
These values are safety limits, not design targets. Scripts should finish normally well before reaching them.
The exact limits can differ between builds or future versions. Treat them as protection against mistakes, not as capacity guarantees.
Parse depth is affected by nested expression structure.
Examples that increase parse depth include:
Prefer readable intermediate variables over very large nested expressions:
// harder to read and easier to make deeply nested result = clamp(round((a + b) * scale, 3), min_value, max_value); // easier to inspect and usually easier to debug raw = (a + b) * scale; rounded = round(raw, 3); result = clamp(rounded, min_value, max_value);
A parse-depth error means the script structure is too deeply nested for the parser guard.
Include files are parsed when the host parses the script.
Expr rejects:
Example of an indirect recursive include:
// A.expr include 'B.expr'; // B.expr include 'A.expr'; // recursive include error
Keep include trees shallow. Use a small number of library files grouped by purpose instead of long include chains.
Good include files usually define functions and classes. Avoid expensive top-level side effects in reusable include files, because those effects run when the file is included.
See Include system.
Evaluation depth is affected by nested evaluation work at runtime.
Common causes include:
Small recursion is fine:
function SumTo(n) { if(n <= 0) { return 0; } return n + SumTo(n - 1); } SumTo(10);
For large counts, prefer an iterative loop:
function SumTo(n) { total = 0; for(i = 1; i <= n; i += 1) { total += i; } return total; } SumTo(10000);
Iterative code is usually easier to interrupt, inspect, and tune.
`loop`, `while`, and `for` have iteration guards to catch accidental infinite loops.
A guard error looks like a runtime error. The exact message includes the loop type and iteration count.
Example of a loop that cannot finish normally:
position = 0; while(position < 10) { print(position); // position is never changed }
Fix the loop by changing the condition state, using `break`, or using `loop` when the count is known:
position = 0; while(position < 10) { print(position); position += 1; }
Use loop guards as error protection only. Do not write scripts that intentionally rely on the guard to stop execution.
Expr numbers are 64-bit floating-point values.
This gives a wide numeric range, but not unlimited precision. Very large integer-like values can lose exact integer precision, because they are still stored as floating-point numbers.
For ordinary CNC coordinates, feed rates, counters, and calculations this is usually appropriate.
Use `.is_num_notnan()` before important numeric calculations:
function RequireNumber(value) { if(!value.is_num_notnan()) { error('Expected a valid number'); } return value; }
Some numeric functions reject values outside their valid mathematical domain. For example, logarithms require positive input, some inverse trigonometric functions require a limited range, and exponent functions reject values that would overflow.
Bitwise and shift operations convert numeric values to integer-like values internally and require finite numbers. Shift counts must be in the valid range.
See Type system, none(), nan(), and errors, none and nan functions, and Functions.
String, array, map, and data buffer performance depends mostly on size and repeated allocation.
Avoid repeatedly building large strings inside tight loops when a smaller structure would work.
// avoid unnecessary repeated formatting in tight loops text = ''; for(i = 0; i < 10000; i += 1) { text += format('%d,', i); }
When possible:
See Collections and bytes.
Host operations are usually much slower than pure numeric expressions.
Examples include:
Avoid placing slow host operations inside tight loops unless the operation really must happen each iteration.
// usually better: read setting once safe_z = settings.get('safeZ'); for(i = 0; i < count; i += 1) { MoveToSafeZ(safe_z); }
For GUI scripts, prefer updating visible controls only when the displayed value changes. For serial or timer callbacks, keep the callback short and move larger work into explicit script steps when possible.
See Host integration and Built-in objects.
A session can persist variables, user functions, and user classes across evaluations.
For interactive workflows, reusing a named session can avoid redefining the same functions and classes manually in every command.
// first evaluation in a named session function Scale(value) { return value * 25.4; } // later evaluation in the same session Scale(2);
However, each submitted source text still has to be parsed. Large scripts that repeatedly include the same large library files can spend noticeable time in parsing and initialization.
Practical guidelines:
See Execution model and sessions.
Callbacks can run later, sometimes from host-managed asynchronous work.
Keep callbacks short:
Long-running callbacks can make timers, dialogs, serial monitoring, or other host features feel unresponsive.
A callback that raises an error may stop the object that invoked it, depending on the object.
See timer, serial, and Error model.
When a script is slow, check these first:
Example:
// slower for(i = 0; i < count; i += 1) { scale = settings.get('unitScale'); print(format('item %d', i)); values[i] = values[i] * scale; } // faster and quieter scale = settings.get('unitScale'); for(i = 0; i < count; i += 1) { values[i] *= scale; } print(format('processed %d items', count));
Use host code, Python, or a purpose-built importer/exporter when the task needs:
Expr is best used to coordinate host features, validate values, make decisions, and express automation logic.
Previous: Host integration
Next: Formal reference