Expr is dynamically typed. A variable does not have a fixed declared type; the value stored in it carries its type at runtime.
a = 10; // number b = 'text'; // string c = none(); // none
The same variable can later hold a different kind of value:
value = 123; value = 'ready';
This page defines the core value types, truth rules, conversion behavior, and invalid-number behavior used by Expr.
Expr has these core runtime value categories:
There is no separate stored boolean type. The literals `true` and `false` behave as numeric values `1` and `0`.
a = true; // numeric true value b = false; // numeric false value
Objects include user-defined class instances and built-in object types such as arrays and maps.
`none()` represents no value.
value = none(); value.is_none();
`none()` is used when a value is missing, unavailable, or intentionally cleared.
Reading an unknown variable also produces `none()`:
missingValue;
Assigning `none()` to a variable clears or unsets that variable:
a = 10; a = none(); a; // none()
`none()` is not the same as `0`, an empty string, or `false`.
none() == 0; // false none() == ''; // false none() == none(); // true
Do not use `none()` as a numeric value. Arithmetic with `none()` is an error.
5 + none(); // error
Numbers are 64-bit floating-point values.
a = 10; b = 3.14; c = -0.5; d = 1e3;
Numbers are used for arithmetic, comparisons, loop counters, coordinates, feed rates, and most numeric calculations.
radius = 5; area = pi() * radius ** 2;
The literals `true` and `false` are represented as numeric true and false values:
true == 1; // true false == 0; // true
Use value methods to check number state:
value.is_num(); value.is_num_notnan(); value.is_num_int();
`nan()` is a numeric value meaning not a valid number.
value = nan(); value.is_nan();
NaN is still a number for type-check purposes:
value = nan(); value.is_num(); // true value.is_num_notnan(); // false
Typical sources of NaN include:
NaN often propagates through arithmetic:
value = nan() + 1; value.is_nan(); // true
Use `.is_num_notnan()` before calculations that require a valid finite number.
if(value.is_num_notnan()) { result = value * 2; };
Bitwise and shift operations require finite numeric values. NaN is rejected by those operations.
Strings are text values.
name = 'Tool 1'; message = "Ready"; template = `Value`;
Strings support methods for text operations and parsing.
' text '.trim(); 'abc'.upper(); '255'.parse_num(); 'true'.parse_bool();
The `+` operator concatenates when a string is involved:
'Tool ' + 3; // 'Tool 3' 'A' + 'B'; // 'AB'
Other arithmetic operators require numeric operands.
'10' * 2; // error '10'.parse_num() * 2; // 20
Objects group behavior and, depending on object type, stored data.
Objects include:
Example:
items = array(); items.add('A'); items.add('B'); items.to_string();
Objects are usually used through methods:
object.Method(arguments);
Some objects expose readable properties:
tool.Number;
Exact object behavior is documented in object-related reference chapters.
Callable references are objects that refer to callable Expr behavior.
Expr currently uses callable reference objects for:
A callable reference can be stored in a variable and called with its `call(…)` method:
function Add(a, b) { return a + b; } f = Add; f.call(2, 3); // 5
Built-in functions can also be referenced:
f = str_spaces; f.call(3); // ' '
Callable references are still Object values in the type system. They are not a separate core type like Number or String.
Host objects can use callable references as callbacks:
function OnClick() { print('clicked'); } button.on_click(OnClick);
The type system defines that callable references are objects. The exact callback methods, event arguments, and lifetime rules belong to the relevant object or host-integration reference pages.
Boolean contexts convert values to true or false.
Boolean contexts include:
Truthiness rules:
| Value | Truth behavior |
|---|---|
| finite non-zero number | true |
| zero | false |
| NaN or infinite number | false |
| string `'true'` | true |
| string `'TRUE'` or other case variants of `'true'` | true |
| any other string | false |
| `none()` | false |
| object | false by default |
Examples:
if(1) { result = 'true'; }; // runs if(0) { result = 'false'; }; // does not run if('true') { result = 'true'; }; // runs if('hello') { result = 'false'; }; // does not run if(none()) { result = 'false'; }; // does not run if(nan()) { result = 'false'; }; // does not run
Use explicit checks when possible. They make scripts easier to read and safer to change.
if(speed.is_num_notnan() && speed > 0) { print('valid speed'); };
Type checks are value methods. They are called on the value being checked.
Common type-check methods:
| Method | Meaning |
|---|---|
| `x.is_none()` | true when `x` is `none()` |
| `x.is_nan()` | true when `x` is numeric NaN |
| `x.is_num()` | true when `x` is a number, including NaN |
| `x.is_num_notnan()` | true when `x` is a finite usable number |
| `x.is_num_int()` | true when `x` is an integer numeric value |
| `x.is_string()` | true when `x` is a string |
| `x.is_bool()` | true for finite numbers and for strings `true` or `false`, case-insensitive |
Examples:
none().is_none(); nan().is_nan(); (42).is_num_notnan(); 'abc'.is_string();
Parentheses are useful when calling methods on numeric literals:
(42).is_num();
Conversions are exposed as methods on values. They are not global conversion functions.
Use this style:
value.to_string(); text.parse_num();
Do not write old-style conversion functions such as:
to_str(value); // not current style to_num(text); // not current style
Common number conversion methods:
| Method | Result |
|---|---|
| `x.to_string()` | decimal string representation |
| `x.to_bool()` | boolean-style numeric result |
| `x.to_int()` | signed integer conversion |
| `x.to_uint()` | unsigned integer conversion |
| `x.to_s8()` | signed 8-bit integer conversion |
| `x.to_u8()` | unsigned 8-bit integer conversion |
| `x.to_s16()` | signed 16-bit integer conversion |
| `x.to_u16()` | unsigned 16-bit integer conversion |
| `x.to_s32()` | signed 32-bit integer conversion |
| `x.to_u32()` | unsigned 32-bit integer conversion |
| `x.to_s64()` | signed 64-bit integer conversion |
| `x.to_u64()` | unsigned 64-bit integer conversion |
| `x.to_hex()` | hexadecimal string |
| `x.to_bin()` | binary string |
| `x.chr()` | character string for the numeric code |
| `x.bit(index)` | selected bit value |
Examples:
(255).to_string(); (255).to_hex(); (5).bit(0);
Common string parsing methods:
| Method | Result |
|---|---|
| `s.parse_num()` | parses numeric text |
| `s.parse_hex()` | parses hexadecimal text |
| `s.parse_bin()` | parses binary text |
| `s.parse_bool()` | parses boolean-style text |
| `s.to_string()` | returns the string value |
Examples:
'255'.parse_num(); 'FF'.parse_hex(); '1010'.parse_bin(); 'true'.parse_bool();
Parsing can be chained with numeric conversion methods:
'255'.parse_num().to_u8();
Operators do not all convert values the same way.
Important rules:
Examples:
1 + 2; // 3 'A' + 10; // 'A10' 1 == '1'; // false '10'.parse_num() == 10; // true
See Operators and Expression rules for exact operator behavior.
For reliable scripts:
Example:
function RequireFeedRate(value) { if(!value.is_num_notnan()) { error('Feed rate must be a valid number'); }; if(value <= 0) { error('Feed rate must be positive'); }; return value; }
Previous: Lexical basics
Next: Variables and assignment