rizin/subprojects/rizin-math-parser
Anton Kochkov 689bafb7a4
Rewrite the RzNum parser and calculator on tree-sitter (#4326)
Replace the hand-written parser in calc.c with a tree-sitter grammar
(subprojects/rizin-math-parser) and a typed evaluator. The old parser
could only ever produce a ut64 and folded anything it failed to read to
0, which left callers unable to tell a failed expression from one that
evaluated to zero.

Expressions now evaluate to an RzNumValue, a tagged union over ut64,
double, RzBitVector, arbitrary-precision integer and arbitrary-precision
decimal, carrying an RzNumError rather than signalling failure as 0.
Literals keep the width they were written with (5u8, 0xffu128, any width
from 1 to 65536), results that outgrow 64 bits promote to a big number on
their own, and a parse error, division by zero or unresolved identifier
reaches the caller.

rz_num_math() is deprecated. rz_num_math_ut64() keeps its exact behaviour
for callers that want a ut64, and rz_num_math_value() exposes the typed
result. rz_core_math() adds the RzCore-backed form used by the % command,
with rz_core_math_ut64() deprecated alongside it. rz-ax routes through the
typed API, so it prints values at full precision, reports errors on stderr
and exits non-zero. rz_il_lift_num() converts an expression to an
RzILOpPure, so a numeric argument can be lifted instead of pre-evaluated.

Legacy input still works: trailing base suffixes (101b, 35o, 212t), the
trailing-'h' hex form and the k/m/g scale suffixes are all accepted and
warn once, pointing at the 0b/0o/0t prefixes. doc/math.md documents the
language and doc/math-il-lift.md the lift; the grammar, the evaluator,
rz-ax and the % command are covered by unit and db tests.
2026-07-24 02:57:27 +08:00
..
src Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
test/corpus Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
.gitignore Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
grammar.js Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
meson.build Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
meson_tree_sitter_generate.py Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
package.json Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
README.md Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00
tree-sitter.json Rewrite the RzNum parser and calculator on tree-sitter (#4326) 2026-07-24 02:57:27 +08:00

rizin-math-parser

A tree-sitter grammar for the Rizin RzNum mathematical expression language. Used by librz_util to evaluate user-supplied numerical expressions such as those accepted by the ? and ?v commands.

Regenerating the parser

The parser source files (src/parser.c, src/grammar.json, src/node-types.json, src/tree_sitter/parser.h) are produced by tree-sitter generate from grammar.js. They are regenerated by the build system when the tree-sitter CLI and node are available; the pre-generated files committed in the repository are used otherwise.

Manual regeneration:

cd subprojects/rizin-math-parser
tree-sitter generate

Running the corpus tests

cd subprojects/rizin-math-parser
tree-sitter test

Supported features

See test/corpus/main.txt for the full reference. Highlights:

  • numeric literals in decimal, binary (0b), ternary (0t), octal (0o) and hexadecimal (0x) bases, with an optional fractional part and [eEpP] exponent (whose digits are required)
  • C-style integer suffixes (u, l, U, L, f, F)
  • SI / IEC byte units (KiB, KB, MiB, MB, GiB, GB, TiB, TB, PiB, PB, EiB, EB)
  • arithmetic + - * / % and the mod keyword
  • exponentiation ** and the log keyword
  • bitwise & | ^ ~, logical not !, shifts << >> and rotations <<< >>>
  • comparisons < <= > >= and equality == !=
  • unary + and - as operators (not part of the number literal)
  • function calls name(arg1, arg2, ...)
  • variables, including Rizin-style names with dots (e.g. sym.func.1000055d4) and Unicode identifiers
  • Rizin special variables as a dedicated special_variable rule ($$, $$$, $F, $S, $SS, ...)
  • address with explicit width and endianness: 0x1234:le32 / 0xDEADBEEF:be64 / 0x1000:8
  • string-as-bytes literal: "ABC"
  • let and plain assignment to a variable
  • pre-increment ++x and pre-decrement --x

Reserved words

let is always reserved (always produces a parse error in variable position). mod, log, le and be are reserved within their positional contexts (modulo, logarithm, address suffix); bare uses parse as variables at the grammar level and are rejected by the evaluator.