Skip to content

UXExpression

UXExpression parses an arithmetic expression once and evaluates it against a set of variable bindings as often as you like.

#use <UXKit> // or #import "UXExpression.xc"
UXExpression* e = UXExpression.parse((u8*)"qty * price + tax");
UXBindings* b = new UXBindings();
b.set((u8*)"qty", 3);
b.set((u8*)"price", 200);
b.set((u8*)"tax", 50);
e.evaluate(b); // 650

It has the shape of NSExpression. A recursive-descent parser builds a small AST, which evaluation walks. Arithmetic is integer-only, so results are exact on every backend.

Use it for a computed column, a rule value ("price * qty"), or the arithmetic half of a light UI scripting layer.

UXExpression* e = UXExpression.parse((u8*)"qty * price");
e.evaluate(b); // 600
b.set((u8*)"qty", 10);
e.evaluate(b); // 2000

The expression holds structure only, no values. A computed column parses its formula when the column is defined and re-evaluates it per row.

evaluate does not mutate the expression, so one parsed expression can be evaluated against different bindings.

Loosest first:

comparison== != < > <= >=
additive+ -
multiplicative* / %
unary-
primarynumber, variable, ( … )

Comparisons yield 1 or 0, so an expression can also serve as a predicate:

UXExpression.parse((u8*)"qty * price > 500").evaluate(b); // 1

A rule such as “flag rows where the total exceeds the limit” is then one expression, with no separate comparison written in code.

Three cases that would abort in a stricter language return a value instead:

UXExpression.parse((u8*)"qty / 0").evaluate(b); // 0
UXExpression.parse((u8*)"qty % 0").evaluate(b); // 0
UXExpression.parse((u8*)"qty + missing").evaluate(b); // 3 — missing is 0

Division and modulo by zero give 0, and an unbound variable reads as 0.

This suits a formula a user typed into a cell: a typo in a name produces a wrong number, not a crashed application. It does not suit code that needs to know the name was wrong. UXBindings has no “is this bound” query, so check the names you intend to allow before evaluating.

UXExpression* e = UXExpression.parse((u8*)"qty * ");
e.isValid(); // false
e.evaluate(b); // 0 — do not use it

parse always returns an object. Check isValid before evaluating. An invalid expression evaluates to 0 instead of refusing, which cannot be told apart from a valid formula whose result is zero.

UXJSON.parse, by contrast, returns null on malformed input. Here the object exists because it also records the failure: trailing operators, unbalanced parentheses and unexpected characters all set the flag.

parse · isValid · evaluate

static UXExpression* parse(u8* s)

Parses a whole string. Trailing text that is not part of the expression, such as the ) in "1 + 2)", makes it invalid instead of being ignored.

Whitespace and tabs are skipped. Variable names are letters, digits and underscore, not starting with a digit.

bool isValid(void)

Whether the parse succeeded. Always check it.

i32 evaluate(UXBindings* b)

Walks the tree against the bindings. A null b is allowed; every variable then reads as 0, which is useful for a constant expression.

qty * price + tax = 650
(qty + 1) * price = 800
-qty * 10 = -30
7 / 2 = 3
7 % 2 = 1
qty * price > 500 = 1
qty == 3 = 1
qty / 0 = 0
qty % 0 = 0
qty + missing = 3
before: 600
after qty=10: 2000
qty * = INVALID
1 + 2) = INVALID

7 / 2 = 3 is integer division truncating toward zero, not rounding. The program is website/site/examples/uxkit/rules.xc; the doc-examples gate compiles it, and the output above is what it prints.

  • A plain class (not an Object subclass)