mirror of
https://github.com/rizinorg/rizin.git
synced 2026-10-12 02:21:57 +00:00
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.
This commit is contained in:
+167
-30
@@ -1,4 +1,4 @@
|
||||
.Dd December 28, 2020
|
||||
.Dd July 21, 2026
|
||||
.Dt RZ_AX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -6,13 +6,17 @@
|
||||
.Nd rizin base converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm rz-ax
|
||||
.Op Fl ebBdDeEfFhiIklLosStuvwx
|
||||
.Op Fl abBdDeEfFhHiIklLmnNopqrsStuvwWx
|
||||
.Op expr ...
|
||||
.Sh DESCRIPTION
|
||||
This command is part of the Rizin project.
|
||||
.Pp
|
||||
This command allows you to convert values between positive and
|
||||
negative integer, float, octal, binary, ternary, and hexadecimal values.
|
||||
It also evaluates arithmetic expressions written in the RzNum
|
||||
expression language - the same one used throughout Rizin - with
|
||||
operators, fixed-width bit-vectors, arbitrary-precision big numbers,
|
||||
and built-in functions.
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Fl
|
||||
.It Fl a
|
||||
@@ -35,7 +39,12 @@ Floating point arithmetic (rz-ax -f 6.3+2.1)
|
||||
Read C strings from stdin and output in hexpairs.
|
||||
Useful to load shellcodes (rz-ax -F < shellcode.[c/py/js])
|
||||
.It Fl h
|
||||
Show usage help message
|
||||
Show the short help: the usage line and the option list
|
||||
.It Fl H
|
||||
Show the long help: everything in
|
||||
.Fl h
|
||||
plus the conversion table, expression examples, the operator list and the
|
||||
literal forms
|
||||
.It Fl i
|
||||
Dump stdin to C array in stdout (rz-ax -i < bytes)
|
||||
.It Fl I
|
||||
@@ -65,7 +74,7 @@ Convert MS-DOS timestamp to string (rz-ax -m 0x12345678)
|
||||
.It Fl W
|
||||
Convert Win32 timestamp to string (rz-ax -W 1234567890)
|
||||
.It Fl u
|
||||
Convert given value to human readable units format (rz-ax -u 389289238 # 317.0M)
|
||||
Convert given value to human readable units format (rz-ax -u 389289238 # 371.3M)
|
||||
.It Fl v
|
||||
Show version information
|
||||
.It Fl w
|
||||
@@ -75,59 +84,187 @@ Convert a string into a hash (rz-ax -x linux osx)
|
||||
.It Fl p
|
||||
Show position of set bits in the binary representation (rz-ax -p 0xb3)
|
||||
.El
|
||||
.Sh USAGE
|
||||
Force output mode (numeric base)
|
||||
.Sh OUTPUT BASE
|
||||
By default a bare literal written in a non-decimal base prints as decimal
|
||||
and everything else - decimal literals and every compound expression -
|
||||
prints as hexadecimal.
|
||||
.Pp
|
||||
$ rz-ax 3
|
||||
0x3
|
||||
$ rz-ax 0x12121212
|
||||
303174162
|
||||
$ rz-ax 0b1010
|
||||
10
|
||||
$ rz-ax 0x3+0b11+3
|
||||
0x9
|
||||
.Pp
|
||||
Prefix an argument with
|
||||
.Cm =
|
||||
to force the output base for the arguments that follow it:
|
||||
.Pp
|
||||
=f floating point
|
||||
=2 binary
|
||||
=3 ternary
|
||||
=8 octal
|
||||
=10 decimal
|
||||
=16 hexadecimal
|
||||
.Pp
|
||||
Available variable types are:
|
||||
.Pp
|
||||
.Fl d
|
||||
forces base 10 and
|
||||
.Fl k
|
||||
keeps the input base.
|
||||
.Sh CONVERSIONS
|
||||
int \-> hex rz-ax 10
|
||||
hex \-> int rz-ax 0xa
|
||||
\-int \-> hex rz-ax \-77
|
||||
\-hex \-> int rz-ax 0xffffffb3
|
||||
int \-> bin rz-ax b30
|
||||
int \-> ternary rz-ax t42
|
||||
ternary \-> int rz-ax 1010dt
|
||||
bin \-> int rz-ax 1010d
|
||||
int \-> oct rz-ax Ox12 (O is a letter)
|
||||
bin \-> int rz-ax 0b1010
|
||||
oct \-> int rz-ax 0o17
|
||||
ternary \-> int rz-ax 0t212
|
||||
float \-> hex rz-ax 3.33f
|
||||
hex \-> float rz-ax Fx40551ed8
|
||||
oct \-> hex rz-ax 35o
|
||||
hex \-> oct rz-ax Ox12 (O is a letter)
|
||||
bin \-> hex rz-ax 1100011b
|
||||
hex \-> bin rz-ax Bx63
|
||||
ternary \-> hex rz-ax 212t
|
||||
hex \-> ternary z-ax Tx23
|
||||
raw \-> hex rz-ax -S < /binfile
|
||||
hex \-> raw rz-ax -s 414141
|
||||
hex \-> ternary rz-ax Tx23
|
||||
raw \-> hex rz-ax \-S < /binfile
|
||||
hex \-> raw rz-ax \-s 414141
|
||||
.Pp
|
||||
With no arguments, rz-ax read values from stdin.
|
||||
You can pass one or more values as arguments.
|
||||
The trailing base suffixes (101b, 35o, 212t) are deprecated in favour of
|
||||
the 0b, 0o and 0t prefixes;
|
||||
.Nm
|
||||
still accepts them but prints a warning.
|
||||
.Sh EXPRESSION SYNTAX
|
||||
Arguments are evaluated in the RzNum expression language, the same one
|
||||
used by the rizin
|
||||
.Cm %
|
||||
command and by every command argument that takes a number.
|
||||
Quote an expression containing spaces so it stays a single argument;
|
||||
otherwise each argument is converted on its own.
|
||||
.Pp
|
||||
$ rz-ax 33 0x41 0101b
|
||||
$ rz-ax '3 * (10 + 2)'
|
||||
0x24
|
||||
$ rz-ax 10 0x20
|
||||
0xa
|
||||
32
|
||||
.Ss Operators
|
||||
Listed from highest to lowest precedence; the precedence matches C.
|
||||
.Pp
|
||||
** power (2**10); note ^ is XOR, not power
|
||||
* / % multiply, divide, modulo
|
||||
sdiv smod signed divide, signed modulo
|
||||
+ \- add, subtract (unary \- negates)
|
||||
<< >> logical shift left, right
|
||||
sar arithmetic (sign-extending) shift right
|
||||
<<< >>> rotate left, right
|
||||
& | ^ ~ and, or, xor, not
|
||||
< <= > >= == != comparisons, yielding 0 or 1
|
||||
?: ternary (cond ? a : b)
|
||||
; sequence; the value is that of the last expression
|
||||
.Ss Literals
|
||||
0x1f hexadecimal
|
||||
0o17 octal
|
||||
0t212 ternary
|
||||
0b1010 binary
|
||||
017 C style octal (a leading zero before a digit)
|
||||
3.5 1e3 0x1.8p3 floating point, including hex floats
|
||||
4KiB 2MB size units: KiB MiB GiB TiB PiB EiB and KB MB GB TB PB EB
|
||||
"AB" a string, taken as its bytes
|
||||
.Ss Bit-vectors
|
||||
A
|
||||
.Cm u
|
||||
suffix followed by a bit count makes a fixed-width bit-vector, which is
|
||||
printed with its width and bit pattern instead of as a plain number.
|
||||
Any width from 1 to 65536 is accepted.
|
||||
.Pp
|
||||
$ rz-ax 8u7
|
||||
hex 0x08
|
||||
binary 0b0001000
|
||||
width 7 bits
|
||||
$ rz-ax 5u8+3u8
|
||||
hex 0x08
|
||||
binary 0b00001000
|
||||
width 8 bits
|
||||
.Ss Big numbers
|
||||
A result that does not fit in 64 bits is promoted to an
|
||||
arbitrary-precision integer automatically; no suffix is needed.
|
||||
.Pp
|
||||
$ rz-ax 2**64
|
||||
decimal 18446744073709551616
|
||||
.Ss Functions
|
||||
abs ceil clz ctz floor gcd len ln log log10 log2
|
||||
max min popcount round sqrt
|
||||
.Pp
|
||||
$ rz-ax 'gcd(12,18)'
|
||||
0x6
|
||||
$ rz-ax 'popcount(0xff)'
|
||||
0x8
|
||||
.Ss Variables
|
||||
An assignment binds a name for the rest of the expression.
|
||||
.Pp
|
||||
$ rz-ax 'x=7;x*x'
|
||||
0x31
|
||||
.Ss Typed address dereference
|
||||
When a host supplies an IO backend - inside rizin, not in standalone
|
||||
.Nm -
|
||||
a numeric address followed by a colon and a width reads memory at that
|
||||
address:
|
||||
.Pp
|
||||
0x1234:le32 little-endian unsigned 32-bit
|
||||
0xDEADBEEF:be64 big-endian unsigned 64-bit
|
||||
0x1000:8 native-endian unsigned 8-bit
|
||||
0x1000:s32 native-endian signed 32-bit
|
||||
0x1000:lef32 little-endian 32-bit float
|
||||
0x1000:f16 native-endian half-precision float
|
||||
.Pp
|
||||
The width is one of 8, 16, 32, 64 or 128 for integer reads and 16, 32 or
|
||||
64 for float reads;
|
||||
.Cm le
|
||||
or
|
||||
.Cm be
|
||||
selects the endianness and
|
||||
.Cm s
|
||||
marks a signed read.
|
||||
.Sh STANDARD INPUT
|
||||
With no argument,
|
||||
.Nm
|
||||
reads one expression per line from standard input.
|
||||
.Pp
|
||||
$ printf '33\n0x41\n' | rz-ax
|
||||
0x21
|
||||
65
|
||||
0x5
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits 0 when every argument was converted, and 1 when any of them failed
|
||||
to parse or evaluate.
|
||||
A diagnostic naming the failure is printed on standard error.
|
||||
.Pp
|
||||
You can do 'unpack' hexpair encoded strings easily.
|
||||
$ rz-ax 'nosuchthing'
|
||||
rz-ax: cannot resolve 'nosuchthing'
|
||||
$ echo $?
|
||||
1
|
||||
.Sh EXAMPLES
|
||||
Unpack a hexpair encoded string:
|
||||
.Pp
|
||||
$ rz-ax \-s 41 42 43
|
||||
ABC
|
||||
.Pp
|
||||
And it supports some math operations.
|
||||
Get the byte encoding of a value:
|
||||
.Pp
|
||||
$ rz-ax
|
||||
0x5*101b+5
|
||||
30
|
||||
$ rz-ax \-N 0x1234
|
||||
\ex34\ex12\ex00\ex00
|
||||
.Pp
|
||||
It is a very useful tool for scripting,
|
||||
so you can read floating point values,
|
||||
or get the integer offset of a jump or a stack delta when analyzing programs.
|
||||
Find which bits are set:
|
||||
.Pp
|
||||
$ rz-ax \-p 0xb3
|
||||
[0\-1]: 1
|
||||
[4\-5]: 1
|
||||
[7]: 1
|
||||
.Pp
|
||||
Convert a value to human readable units:
|
||||
.Pp
|
||||
$ rz-ax \-u 389289238
|
||||
371.3M
|
||||
.Sh SEE ALSO
|
||||
.Xr rizin 1 ,
|
||||
.Xr rz-asm 1 ,
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
RzNum to RzIL lift: a reference by example
|
||||
==========================================
|
||||
|
||||
`rz_il_lift_num()` (and the `RzCore` wrapper `rz_core_il_lift()`) turn an
|
||||
RzNum expression into an `RzILOpPure` - the same pure-op value the rest
|
||||
of RzIL is built from. This document shows, by example, exactly what the
|
||||
lift produces for every feature of the expression language.
|
||||
|
||||
Each example is written as
|
||||
|
||||
<expression> => <lifted RzILOpPure, stringified>
|
||||
|
||||
where the right-hand side is the *exact* Unicode form the `pLf` command
|
||||
prints, emitted by the RzIL exporter itself. A lifted expression and a
|
||||
hand-written RzIL fragment of the same shape are therefore byte-for-byte
|
||||
identical. Examples are evaluated with no *host bindings* - that is, no
|
||||
program is supplying values for flags, registers, or memory, so a bare
|
||||
identifier reads as zero. The *structure* of the lift is what matters
|
||||
here; with a real host those lookups return real values, so the embedded
|
||||
constants change while the structure stays the same. (What a host
|
||||
binding is, and how a program supplies one, is covered in
|
||||
`doc/math.md`.)
|
||||
|
||||
|
||||
The one idea: lift or ground
|
||||
----------------------------
|
||||
|
||||
Two terms run through this whole document, so here they are up front.
|
||||
When the lifter meets an operator it does one of two things:
|
||||
|
||||
* It **lifts structurally** (often shortened to "lifts") - the
|
||||
operator has a direct RzIL pure-op, so it stays in the output *as
|
||||
that operator* (`add`, `shiftl`, `ite`, ...) and the lifter recurses
|
||||
into its operands. "Structural" just means the shape is kept: `1 + 2`
|
||||
becomes `(0x1₆₄ + 0x2₆₄)`, still visibly an addition.
|
||||
* It is **grounded** - the operator has no RzIL counterpart, so the
|
||||
sub-expression is *evaluated to a constant* and that constant takes
|
||||
its place. `2 ** 8` becomes `0x100₆₄`, a plain number; the `**` is
|
||||
gone.
|
||||
|
||||
Grounding is always *local*: only the piece that lacks a pure-op
|
||||
collapses, and the rest lifts as usual. One grounded function call
|
||||
inside an arithmetic tree leaves the arithmetic intact - `min(5, 10) + 1`
|
||||
is `(0x5₆₄ + 0x1₆₄)`, not a single folded constant. The "Quick
|
||||
reference" at the end lists which operators land in which bucket.
|
||||
|
||||
|
||||
Reading the output notation
|
||||
---------------------------
|
||||
|
||||
The exporter's notation is compact; once these conventions are clear,
|
||||
every example below reads at a glance:
|
||||
|
||||
* **Subscript = bit width.** `0x1₆₄` is a 64-bit constant, `0x5₈` an
|
||||
8-bit one. A plain literal is 64-bit; a width suffix (`5u8`) sets it.
|
||||
* **Glyphs stand in for the pure-op** (full table below): `≪` / `≫`
|
||||
are the shifts, `≡` / `≦` the comparisons, `↠` the ternary, `−`
|
||||
unary minus, `~ & | ⊕` the bitwise ops.
|
||||
* **A shift carries a third operand, its fill bit:** `⊥` (false) for a
|
||||
logical `<<` / `>>`, `↑x` (the shifted value's top bit) for an
|
||||
arithmetic `sar`.
|
||||
* **Floats wear an `.f` suffix and a leading `rne` rounding-mode
|
||||
prefix:** `(rne 0x….f₆₄ + 0x….f₆₄)`.
|
||||
* **Parentheses mirror the parse exactly,** so precedence and
|
||||
associativity are visible directly in the shape.
|
||||
|
||||
Where a glyph would be ambiguous, or the exporter emits none, the ASCII
|
||||
equivalent is given alongside.
|
||||
|
||||
|
||||
Operator glyphs at a glance
|
||||
---------------------------
|
||||
|
||||
| RzNum operator | RzIL pure-op | Unicode | ASCII |
|
||||
|----------------------|------------------------|---------|---------|
|
||||
| `+` `-` `*` | `add` `sub` `mul` | `+` `-` `*` | `+` `-` `*` |
|
||||
| `/` `%` | `div` `mod` | `/` `%` | `/` `%` |
|
||||
| `sdiv` `smod` | `sdiv` `smod` | `/⁺` `%⁺` | `/+` `%+` |
|
||||
| `&` `\|` `^` | `logand` `logor` `logxor` | `&` `\|` `⊕` | `&` `\|` `^` |
|
||||
| `<<` `>>` (logical) | `shiftl` `shiftr` | `≪` `≫` | `<<` `>>` |
|
||||
| `sar` (arithmetic) | `shiftr`, sign fill | `≫` | `>>` |
|
||||
| `==` | `eq` | `≡` | `==` |
|
||||
| `<=` (unsigned) | `ule` | `≦` | `<=` |
|
||||
| `~` | `lognot` | `~` | `~` |
|
||||
| unary `-` | `neg` | `−` | `-` |
|
||||
| `? :` (ternary) | `ite` | `↠` | `->` |
|
||||
|
||||
`<`, `>`, `>=`, `!=` and the rotates `<<<` / `>>>` have no single
|
||||
RzIL pure-op glyph in the Unicode exporter, so they are grounded;
|
||||
see the dedicated section further down.
|
||||
|
||||
|
||||
Numeric literals and widths
|
||||
---------------------------
|
||||
|
||||
A plain integer literal becomes a 64-bit bit-vector constant, with
|
||||
the width written as a subscript:
|
||||
|
||||
1 + 2 => (0x1₆₄ + 0x2₆₄)
|
||||
0x10 - 0x05 => (0x10₆₄ - 0x5₆₄)
|
||||
-5 => −0x5₆₄
|
||||
+5 => 0x5₆₄
|
||||
|
||||
A width-suffixed literal keeps its width, and the lift propagates
|
||||
the per-operand width into the output:
|
||||
|
||||
5u8 + 3u8 => (0x5₈ + 0x3₈)
|
||||
0xffu16 & 0x0fu16 => (0xff₁₆ & 0xf₁₆)
|
||||
1u32 << 4u32 => (0x1₃₂ ≪ 0x4₃₂ ⊥)
|
||||
|
||||
Widths are not capped at 64 bits; a `u128` literal lifts to a 128-bit
|
||||
constant just the same:
|
||||
|
||||
1u128 << 4u128 => (0x1₁₂₈ ≪ 0x4₁₂₈ ⊥)
|
||||
|
||||
Operands of different widths each lift at their own width. The result
|
||||
would still need a width cast to typecheck as RzIL, but the lift stays
|
||||
faithful to what was written:
|
||||
|
||||
5u8 + 1u16 => (0x5₈ + 0x1₁₆)
|
||||
5 + 3u8 => (0x5₆₄ + 0x3₈)
|
||||
|
||||
There is no implicit cast either way. Mixing bit-vector widths lifts
|
||||
each operand at its own width (above); mixing an integer with a float
|
||||
grounds instead (see Floats below). The evaluator-side rule is spelled
|
||||
out under "Result kinds" in `doc/math.md`.
|
||||
|
||||
|
||||
Arithmetic and bitwise
|
||||
----------------------
|
||||
|
||||
Precedence and associativity are preserved by the lift, so the
|
||||
parenthesisation in the output reflects the parse exactly:
|
||||
|
||||
1 + 2 * 3 => (0x1₆₄ + (0x2₆₄ * 0x3₆₄))
|
||||
(1 + 2) * 3 => ((0x1₆₄ + 0x2₆₄) * 0x3₆₄)
|
||||
-(1 + 2) => −(0x1₆₄ + 0x2₆₄)
|
||||
|
||||
Bitwise operators map directly:
|
||||
|
||||
0xff & 0x0f => (0xff₆₄ & 0xf₆₄)
|
||||
0xa | 0x5 => (0xa₆₄ | 0x5₆₄)
|
||||
0xa ^ 0x5 => (0xa₆₄ ⊕ 0x5₆₄)
|
||||
~0xff => ~0xff₆₄
|
||||
|
||||
Shifts lift structurally. RzIL's shift carries an explicit fill bit as
|
||||
a third operand; a logical `<<` / `>>` fills with `⊥` (false):
|
||||
|
||||
1 << 4 => (0x1₆₄ ≪ 0x4₆₄ ⊥)
|
||||
0x100 >> 4 => (0x100₆₄ ≫ 0x4₆₄ ⊥)
|
||||
|
||||
Division and modulo, signed and unsigned:
|
||||
|
||||
10 / 3 => (0xa₆₄ / 0x3₆₄)
|
||||
10 % 3 => (0xa₆₄ % 0x3₆₄)
|
||||
10 sdiv 3 => (0xa₆₄ /⁺ 0x3₆₄)
|
||||
10 smod 3 => (0xa₆₄ %⁺ 0x3₆₄)
|
||||
256 sar 2 => (0x100₆₄ ≫ 0x2₆₄ ↑0x100₆₄)
|
||||
|
||||
`sar` (arithmetic right shift) is the same `shiftr` op with the shifted
|
||||
value's top bit `↑x` as the fill, so the sign is propagated instead of
|
||||
zero-filled.
|
||||
|
||||
The signed family also lifts structurally on bit-vectors, carrying
|
||||
the declared width through:
|
||||
|
||||
0xffu8 sdiv 2u8 => (0xff₈ /⁺ 0x2₈)
|
||||
0xfeu8 sar 1u8 => (0xfe₈ ≫ 0x1₈ ↑0xfe₈)
|
||||
|
||||
|
||||
Comparisons
|
||||
-----------
|
||||
|
||||
`==` and `<=` (unsigned) have direct RzIL pure-ops and lift
|
||||
structurally:
|
||||
|
||||
1 == 1 => (0x1₆₄ ≡ 0x1₆₄)
|
||||
5 <= 10 => (0x5₆₄ ≦ 0xa₆₄)
|
||||
|
||||
`<`, `>`, `>=`, `!=` compose from `eq`, `ule` and `lognot` in RzIL
|
||||
proper but have no single glyph the Unicode exporter emits, so the
|
||||
lift evaluates them to a one-bit ut64 constant rather than building
|
||||
the composite. The truth value still survives - it just arrives as
|
||||
a literal:
|
||||
|
||||
5 < 10 => 0x1₆₄
|
||||
5 > 3 => 0x1₆₄
|
||||
5 >= 4 => 0x1₆₄
|
||||
5 != 6 => 0x1₆₄
|
||||
|
||||
The same is true for `!`:
|
||||
|
||||
!5 => 0x0₆₄
|
||||
!0 => 0x1₆₄
|
||||
!(5 > 3) => 0x0₆₄
|
||||
|
||||
|
||||
The ternary `c ? a : b` (RzIL `ite`)
|
||||
------------------------------------
|
||||
|
||||
The conditional is the one control-flow construct in the language
|
||||
that has a direct RzIL pure-op, so the whole construct lifts
|
||||
structurally and *both* branches survive into the output. The
|
||||
exporter's notation is `(cond ↠ then else)`:
|
||||
|
||||
1 ? 10 : 20 => (0x1₆₄ ↠ 0xa₆₄ 0x14₆₄)
|
||||
|
||||
A condition that itself lifts (`==`, `≦`) stays structural, so the
|
||||
shape of the decision is preserved end-to-end. With a condition
|
||||
that has to be grounded (`>`, `<`, ...), only the condition
|
||||
collapses; the surrounding `ite` is still there:
|
||||
|
||||
len(0xffu16) == 16 ? 0xaa : 0xbb
|
||||
=> ((0x10₆₄ ≡ 0x10₆₄) ↠ 0xaa₆₄ 0xbb₆₄)
|
||||
5 > 3 ? 100 : 200 => (0x1₆₄ ↠ 0x64₆₄ 0xc8₆₄)
|
||||
double(10) > 15 ? 1 : 0 => (0x1₆₄ ↠ 0x1₆₄ 0x0₆₄)
|
||||
|
||||
The branches can be arbitrary sub-expressions and lift in full:
|
||||
|
||||
1 ? 2 + 3 : 4 * 5 => (0x1₆₄ ↠ (0x2₆₄ + 0x3₆₄) (0x4₆₄ * 0x5₆₄))
|
||||
x > 0 ? x + 1 : x - 1 => (0x0₆₄ ↠ (0x0₆₄ + 0x1₆₄) (0x0₆₄ - 0x1₆₄))
|
||||
|
||||
Right-associativity threads naturally into nested `ite`s:
|
||||
|
||||
a ? b : c ? d : e => (0x0₆₄ ↠ 0x0₆₄ (0x0₆₄ ↠ 0x0₆₄ 0x0₆₄))
|
||||
a > 10 ? b : a > 5 ? c : a > 0 ? d : e
|
||||
=> (0x0₆₄ ↠ 0x0₆₄ (0x0₆₄ ↠ 0x0₆₄ (0x0₆₄ ↠ 0x0₆₄ 0x0₆₄)))
|
||||
|
||||
And the ternary composes with the rest of the language:
|
||||
|
||||
(x > 0 ? x : -x) + 1 => ((0x0₆₄ ↠ 0x0₆₄ −0x0₆₄) + 0x1₆₄)
|
||||
|
||||
|
||||
Compositions and depth
|
||||
----------------------
|
||||
|
||||
There is no depth limit beyond the evaluator's node budget; the lift
|
||||
preserves whatever the parse tree gives it. Examples worth seeing
|
||||
in their full shape:
|
||||
|
||||
(1 + 2) * (3 + 4) - (5 << 1)
|
||||
=> (((0x1₆₄ + 0x2₆₄) * (0x3₆₄ + 0x4₆₄)) - (0x5₆₄ ≪ 0x1₆₄ ⊥))
|
||||
|
||||
(0xff & 0xf0) | (0x0f << 4)
|
||||
=> ((0xff₆₄ & 0xf0₆₄) | (0xf₆₄ ≪ 0x4₆₄ ⊥))
|
||||
|
||||
((a + b) * 2) sar 1 => (((0x0₆₄ + 0x0₆₄) * 0x2₆₄) ≫ 0x1₆₄ ↑((0x0₆₄ + 0x0₆₄) * 0x2₆₄))
|
||||
|
||||
|
||||
Identifiers, host variables, assignment
|
||||
---------------------------------------
|
||||
|
||||
A bare identifier is resolved by the host - in rizin's `%` that means
|
||||
the flag and register tables - when a host is present; with none it
|
||||
reads as zero. Either way the lift embeds the resolved value as a
|
||||
constant, so the variable name does not survive into RzIL:
|
||||
|
||||
a + b => (0x0₆₄ + 0x0₆₄)
|
||||
x = 5 => 0x5₆₄
|
||||
|
||||
The zeros above are just the no-host default. In a real session the
|
||||
host supplies values: if the register `rax` holds `0x1000` and the flag
|
||||
`main` sits at `0x400500`, the same lift embeds those instead -
|
||||
|
||||
rax + main => (0x1000₆₄ + 0x400500₆₄)
|
||||
|
||||
An assignment expression has the value of its right-hand side and
|
||||
lifts to that value. The binding side effect itself has no RzIL
|
||||
counterpart in this lift - the rest of the expression (or a later
|
||||
statement, see below) reads the bound value at lift time and embeds
|
||||
it directly:
|
||||
|
||||
a = 1; b = 2; a + b * 3 => 0x7₆₄
|
||||
|
||||
A binding that is fully known at lift time collapses the expression
|
||||
that uses it - even a ternary, because its condition then evaluates to a
|
||||
constant the lift can fold away:
|
||||
|
||||
x = 5; x > 3 ? x : 0 => 0x5₆₄
|
||||
a = 5; b = 10; a < b ? a : b
|
||||
=> 0x5₆₄
|
||||
|
||||
The ternary's structure survives only when the condition is something
|
||||
the lift *cannot* see through - an unbound host variable, say - so the
|
||||
decision genuinely has to be deferred to run time:
|
||||
|
||||
x > 0 ? x + 1 : x - 1 => (0x0₆₄ ↠ (0x0₆₄ + 0x1₆₄) (0x0₆₄ - 0x1₆₄))
|
||||
|
||||
|
||||
Statement sequencing
|
||||
--------------------
|
||||
|
||||
A `;`-separated sequence has no RzIL pure-op form, so it is grounded
|
||||
to its final value. Earlier bindings are honoured during that
|
||||
grounding:
|
||||
|
||||
1 + 2; 3 + 4 => 0x7₆₄
|
||||
a = 1; b = 2; a + b * 3 => 0x7₆₄
|
||||
|
||||
|
||||
Functions, both built-in and host-registered
|
||||
--------------------------------------------
|
||||
|
||||
Function calls are grounded to a constant - none of the built-ins
|
||||
(`min`, `max`, `abs`, `popcount`, `len`, and the Unicode aliases) has
|
||||
a direct RzIL pure-op:
|
||||
|
||||
min(5, 10) => 0x5₆₄
|
||||
max(min(3,7), 5) => 0x5₆₄
|
||||
popcount(0xff) => 0x8₆₄
|
||||
len(0xffu16) => 0x10₆₄
|
||||
len("ABCD") => 0x20₆₄
|
||||
|
||||
Grounding happens per call, so a built-in is reduced to a constant
|
||||
*and then* the surrounding operators lift around it:
|
||||
|
||||
min(5,10) + max(1,2) => (0x5₆₄ + 0x2₆₄)
|
||||
len(0xffu16) * 8 => (0x10₆₄ * 0x8₆₄)
|
||||
|
||||
The same applies to host-registered functions through
|
||||
`RzNumFuncRegistry` (see "Host-registered functions" in `doc/math.md`
|
||||
for how a program registers one). With a registry that defines
|
||||
`double(n) -> 2n`:
|
||||
|
||||
double(21) + 1 => (0x2a₆₄ + 0x1₆₄)
|
||||
double(10) > 15 ? 1 : 0 => (0x1₆₄ ↠ 0x1₆₄ 0x0₆₄)
|
||||
|
||||
A Unicode-named function is lifted the same way:
|
||||
|
||||
минимум(7, 3) => 0x3₆₄
|
||||
|
||||
A host function that fails to ground (typically because no
|
||||
appropriate callback is supplied at lift time) produces a clear
|
||||
error:
|
||||
|
||||
crc(0, 4)
|
||||
cannot ground sub-expression: unknown function: crc
|
||||
|
||||
|
||||
Typed-address dereferences
|
||||
--------------------------
|
||||
|
||||
A typed read such as `addr:le32` / `:be64` / `:s32` / `:f32` behaves
|
||||
one of two ways. When an IO layer is present - evaluating through `%`
|
||||
in a session with bytes mapped at `addr` - it reads memory, and the
|
||||
value read is grounded into the output. With no IO layer - the plain
|
||||
lift API, or an address with nothing mapped - the suffix is parsed but
|
||||
the address itself passes through unchanged. Either way the result is a
|
||||
single grounded constant; whether there is data at `0x1000` only
|
||||
changes *which* constant it is:
|
||||
|
||||
0x1000:le32 => 0x1000₆₄
|
||||
0x1000:s32 => 0x1000₆₄
|
||||
0x1000:f32 => 0x1000₆₄
|
||||
0x1000:128 => 0x1000₆₄
|
||||
0x1000:f128 => 0x1000₆₄
|
||||
|
||||
(Those are the no-IO pass-through values; with bytes mapped they would
|
||||
be the decoded reads instead.) A `:128` or `:f128` read produces a
|
||||
width-128 bit-vector when IO is present. `:f128` returns the raw 128
|
||||
bits, not a decoded quad-precision float - RzNum's float type is always
|
||||
a 64-bit double.
|
||||
|
||||
When combined with structural operators:
|
||||
|
||||
0:le32 + 1 => (0x0₆₄ + 0x1₆₄)
|
||||
|
||||
|
||||
Power, log, rotates
|
||||
-------------------
|
||||
|
||||
`**`, `log` and the bitwise rotates `<<<` / `>>>` have no direct
|
||||
RzIL pure-op in the bit-vector sort and are grounded:
|
||||
|
||||
2 ** 8 => 0x100₆₄
|
||||
16 log 2 => 0x0₆₄
|
||||
1 <<< 4 => 0x10₆₄
|
||||
1 >>> 4 => 0x1000000000000000₆₄
|
||||
|
||||
The bit-vector versions ground the same way, but the constants
|
||||
carry their declared width:
|
||||
|
||||
0x12u8 <<< 4 => 0x21₈
|
||||
2u8 ** 4u8 => 0x10₈
|
||||
2u32 log 8u32 => 0x3₃₂
|
||||
|
||||
Grounding evaluates `**` and `log` the way the evaluator does, in
|
||||
`double` precision. A small power is exact, but a power whose result is
|
||||
too large for a `double` collapses to zero or infinity. So the lift
|
||||
does *not* preserve an exact big-number power: when `**` sits inside an
|
||||
enclosing operator, the grounded value is a truncated ut64:
|
||||
|
||||
2 ** 100 => 0x0₆₄
|
||||
(2 ** 100) + 1 => (0x0₆₄ + 0x1₆₄)
|
||||
(2 ** 100) - (2 ** 99) => (0x0₆₄ - 0x0₆₄)
|
||||
|
||||
For an exact big-number power, evaluate the expression directly
|
||||
through `rz_num_math_value`; this is a documented limit of the lift,
|
||||
not of the evaluator.
|
||||
|
||||
|
||||
Floats
|
||||
------
|
||||
|
||||
A numeric literal containing a `.` or scientific-notation exponent
|
||||
is lifted as an IEEE-754 double-precision constant - "0x<bits>.f64"
|
||||
in the RzIL exporter's notation, where `<bits>` is the bit-vector
|
||||
representation of the value. Float-pure arithmetic (both operands
|
||||
float) lifts to the RzIL float-binop form, with the leading
|
||||
rounding-mode prefix that distinguishes the float family from the
|
||||
bit-vector family:
|
||||
|
||||
1.5 + 2.5 => (rne 0x3ff8000000000000.f₆₄ + 0x4004000000000000.f₆₄)
|
||||
1.0 / 2.0 => (rne 0x3ff0000000000000.f₆₄ / 0x4000000000000000.f₆₄)
|
||||
3.14 * 2.0 => (rne 0x40091eb851eb851f.f₆₄ * 0x4000000000000000.f₆₄)
|
||||
|
||||
The rounding mode is always `rne` (round-nearest-even, IEEE-754's
|
||||
default); RzNum has no syntax for choosing one. Every RzNum float is a
|
||||
64-bit double, so both operands are always `.f₆₄`; the language has no
|
||||
32-bit float *value* (even a `:f32` memory read is widened to a
|
||||
double).
|
||||
|
||||
Mixed integer / float arithmetic is grounded rather than fabricating
|
||||
an implicit cast the user did not write:
|
||||
|
||||
1 + 2.5 => 0x3₆₄
|
||||
3.14 * 2 => 0x6₆₄
|
||||
|
||||
For a float-conditioned ternary, RzIL's `ite` needs a Bool, but a
|
||||
float is not one. The lift bridges this in two steps. First it turns
|
||||
the float condition `f` into the Bool `f ≡ 0` (this is `is_fzero`).
|
||||
That test is inverted, though: `f ≡ 0` is true exactly when `f` is
|
||||
zero - the case where the evaluator takes the *else* branch. So,
|
||||
second, the lift swaps the two branches, and the form reads
|
||||
`(f ≡ 0 ↠ else then)`. The two inversions cancel, reproducing the
|
||||
evaluator's rule that a non-zero float is true:
|
||||
|
||||
0.5 ? 7 : 8 => (0x3fe0000000000000.f₆₄ ≡ 0 ↠ 0x8₆₄ 0x7₆₄)
|
||||
0.0 ? 7 : 8 => (0x0.f₆₄ ≡ 0 ↠ 0x8₆₄ 0x7₆₄)
|
||||
|
||||
So under the lift `0.5` picks the `7` branch and `0.0` picks the
|
||||
`8` branch, matching the evaluator.
|
||||
|
||||
When both branches are float as well, the branches lift in their
|
||||
own float form:
|
||||
|
||||
0.5 ? 1.5 : 2.5 => (0x3fe0000000000000.f₆₄ ≡ 0 ↠ 0x4004000000000000.f₆₄ 0x3ff8000000000000.f₆₄)
|
||||
|
||||
The typed-read `:fN` suffix is still grounded by the lift, because
|
||||
the kind it produces depends on whether an IO callback is wired at
|
||||
read time. Mixing `addr:f32` with a float literal therefore grounds
|
||||
the whole expression rather than emitting a half-typed structural
|
||||
form.
|
||||
|
||||
|
||||
Quick reference: what lifts, what grounds
|
||||
-----------------------------------------
|
||||
|
||||
Lifts structurally:
|
||||
* arithmetic `+ - * / %`, signed `sdiv smod sar`;
|
||||
* bitwise `& | ^ ~`;
|
||||
* shifts `<< >>`;
|
||||
* unary `- +`;
|
||||
* comparisons `==` and `<=`;
|
||||
* ternary `? :` (`ite`), with `is_fzero` wrapping when the condition
|
||||
is a float;
|
||||
* float-pure arithmetic `+ - * /` (rendered with the `rne` rmode
|
||||
prefix and `.f₆₄` operand suffixes);
|
||||
* float literals (rendered as `0x<bits>.f₆₄`).
|
||||
|
||||
Always grounded to a constant (because RzIL has no matching
|
||||
bit-vector pure-op in the Unicode exporter, or no pure-op at all):
|
||||
* comparisons `< > >= !=`, logical `!`;
|
||||
* power `**`, logarithm `log`;
|
||||
* rotates `<<<` `>>>`;
|
||||
* mixed integer-and-float arithmetic (no implicit cast is invented);
|
||||
* float modulo `%` (no direct RzIL pure-op in the exporter for
|
||||
`fmod`);
|
||||
* `len`, `popcount`, `min`, `max`, `abs`, and any host-registered
|
||||
function;
|
||||
* typed-address dereferences `addr:T`;
|
||||
* `;`-separated sequences.
|
||||
|
||||
Where a sub-expression has to be grounded, only that sub-expression
|
||||
collapses - the structure around it lifts as usual, so a single
|
||||
non-structural piece does not destroy the surrounding form.
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
RzNum Expression Language
|
||||
=========================
|
||||
|
||||
The `RzNum` evaluator parses and computes mathematical expressions
|
||||
used throughout Rizin. Every place that accepts a numeric argument
|
||||
(`%`, `rz-ax`, the `?v` command, flag offsets, seek targets, ...)
|
||||
funnels through the same grammar and the same evaluator.
|
||||
|
||||
This document describes the surface language as users see it.
|
||||
For consumers of the C API see the doxygen comments on
|
||||
`rz_num_math_value()`, `rz_num_math_value_ex()`, `rz_core_math()`,
|
||||
and the helpers `rz_num_value_to_ut64()` / `rz_num_value_print()`.
|
||||
|
||||
|
||||
Numeric literals
|
||||
----------------
|
||||
|
||||
| Form | Meaning | Example |
|
||||
|--------------------------------|----------------------------------|--------------|
|
||||
| `123` | decimal | `42` |
|
||||
| `0x1a` / `0X1A` | hexadecimal | `0xdeadbeef` |
|
||||
| `0b1010` / `0B1010` | binary | `0b101010` |
|
||||
| `0o17` / `0O17` | octal | `0o755` |
|
||||
| `0t102` / `0T102` | ternary (base 3) | `0t1102` |
|
||||
| `3.14` | decimal float | `1.5` |
|
||||
| `1.5e3` | decimal float with exponent | `1.0e-3` |
|
||||
| `0x1p10` | hex float (binary exponent) | `0x1p-4` |
|
||||
|
||||
A literal in any base may be followed by an IEC byte unit suffix to
|
||||
multiply by the matching power of two:
|
||||
|
||||
| Suffix | Multiplier |
|
||||
|---------------------|-------------|
|
||||
| `KiB` | 1024 |
|
||||
| `KB` | 1000 |
|
||||
| `MiB` / `MB` | 1024^2 / 1000^2 |
|
||||
| `GiB` / `GB` | 1024^3 / 1000^3 |
|
||||
| `TiB` / `TB` | 1024^4 / 1000^4 |
|
||||
| `PiB` / `PB` | 1024^5 / 1000^5 |
|
||||
| `EiB` / `EB` | 1024^6 / 1000^6 |
|
||||
|
||||
Examples: `4KiB`, `2MiB`, `1.5GB`. Float values accept units too.
|
||||
|
||||
A literal that does not fit in a 64-bit unsigned integer is
|
||||
automatically promoted to an arbitrary-precision big number (kind
|
||||
`RZ_NUM_KIND_BIG`). This works for every supported base, not just
|
||||
hexadecimal:
|
||||
|
||||
% 0x10000000000000000 -> bignum 0x10000000000000000
|
||||
% 99999999999999999999999999 -> bignum 0x52b7d2dcc80cd2e3ffffffff
|
||||
% 0b100000000000000000000000000000000000000000000000000000000000000000
|
||||
-> bignum 0x20000000000000000
|
||||
|
||||
When arithmetic on a big number produces a result that fits back
|
||||
inside `ut64`, the value is demoted automatically.
|
||||
|
||||
A literal followed by a `u` plus a bit-width - `u8`, `u16`, `u32`,
|
||||
`u64`, `u128` - is a fixed-width *bit-vector* (kind
|
||||
`RZ_NUM_KIND_BITVECTOR`). Bit-vectors do modular arithmetic at their
|
||||
width, so they overflow by wrapping rather than promoting:
|
||||
|
||||
% 5u8 -> width 8, 0x05
|
||||
% 200u8 + 100u8 -> width 8, 0x2c (300 mod 256)
|
||||
% 0xffu8 + 1u8 -> width 8, 0x00 (wraps)
|
||||
% 10u16 * 10u16 -> width 16, 0x0064
|
||||
|
||||
A bare letter run with no width (`5u`, `10ul`, `3.0f`) is
|
||||
informational only and does not change the kind. See the
|
||||
"Bit-vectors" section below for the operations defined on them.
|
||||
|
||||
|
||||
Identifiers
|
||||
-----------
|
||||
|
||||
A bare identifier is looked up as a variable through the host's
|
||||
`RzNum` callback. In the shell this is the flag table: typing
|
||||
`% entry0` evaluates to the address of `entry0`. Unknown
|
||||
identifiers resolve to zero (matching the legacy behaviour).
|
||||
|
||||
Identifier characters are everything that is not a control
|
||||
character, whitespace, operator, bracket, quote, comma, semicolon,
|
||||
backslash, dollar sign, or a digit (as the first character). This
|
||||
includes any non-ASCII Unicode letter; identifiers may be written
|
||||
in any script.
|
||||
|
||||
### Reserved words
|
||||
|
||||
These seven words are operator keywords and cannot be assigned to. Each
|
||||
is meaningful only in its operator position:
|
||||
|
||||
| Word(s) | Operator position |
|
||||
|-----------------------|--------------------------------------------|
|
||||
| `mod`, `log` | binary operator: `x mod y`, `x log y` |
|
||||
| `sdiv`, `smod`, `sar` | signed operator: `x sdiv y`, `x sar y` |
|
||||
| `le`, `be` | endianness in a typed read: `0x1000:le32` |
|
||||
|
||||
Every other identifier, in any script, is a free variable name.
|
||||
|
||||
|
||||
Special variables
|
||||
-----------------
|
||||
|
||||
`$`-prefixed names are a closed set drawn from the canonical list
|
||||
used by the `%v` (legacy `?v`) command:
|
||||
|
||||
$$ current offset $$$ alias for $$
|
||||
$b block size $B block size in bytes
|
||||
$F function start at $$ $Ff function end at $$
|
||||
$Fj function jump at $$ $Fb function basic block at $$
|
||||
$FB function basic block end $Fi next instruction
|
||||
$FS function size $fl flag size at $$
|
||||
$S section start $SS section size
|
||||
$D debug map start $DB debug map end
|
||||
$DD debug map size $DS debug map permissions
|
||||
$c column / cursor $Cn comment by index
|
||||
$alias alias map $e end of execution
|
||||
$f fail-jump address $j true-jump address
|
||||
$Ja analysis jump address
|
||||
$l last instruction length $M map count
|
||||
$MM address space size $m memory size
|
||||
$O current opcode address $o opcode size
|
||||
$p PID $P parent PID
|
||||
$r return value register $s stack pointer
|
||||
$v value $w word size
|
||||
|
||||
An unknown `$`-name is a parse error (since the rule is a closed
|
||||
list), not an evaluator error.
|
||||
|
||||
|
||||
Variable bindings
|
||||
-----------------
|
||||
|
||||
`x = y` and `let x = y` bind the identifier `x` to the value of `y`
|
||||
and evaluate to that value. The two forms are identical; `let` is an
|
||||
optional keyword with no separate scoping. The binding is visible to
|
||||
the rest of the current expression, so a later part can read it back:
|
||||
|
||||
(x = 7) + x -> 14
|
||||
(a = 2) * (a + 1) -> 6
|
||||
(c = 1) + (c = 9) + c -> 19 (reassignment updates the binding)
|
||||
|
||||
A local binding shadows a host variable (flag / special variable)
|
||||
of the same name. Bound values keep their kind, so a bound big
|
||||
number stays exact. The reserved words (see the Reserved words table
|
||||
above) cannot be assigned.
|
||||
|
||||
With the statement separator `;` (see below) bindings can be
|
||||
introduced and reused without the parenthesis trick:
|
||||
|
||||
let radius = 5; let area = radius * radius; area -> 25
|
||||
bla = crc(0, 4); 0:be32 + bla
|
||||
|
||||
Persistence across evaluations is opt-in. By default a binding lives
|
||||
for a single evaluation. If the caller supplies a persistent variable
|
||||
store (`RzNumMathOptions.vars`, created with `rz_num_value_store_new()`),
|
||||
bindings written by one evaluation remain visible to later ones that
|
||||
share the store. The `%` shell command uses such a store, so a
|
||||
variable bound in one `%` invocation is visible to the next:
|
||||
|
||||
[0x0]> % bla = crc(0, 4)
|
||||
[0x0]> % 0:be32 + bla <- 'bla' still bound here
|
||||
|
||||
|
||||
Statement sequencing
|
||||
--------------------
|
||||
|
||||
Several expressions can be chained with `;`. Each is evaluated in
|
||||
order (so bindings made by earlier statements are visible to later
|
||||
ones) and the value of the whole is the value of the last statement.
|
||||
A trailing `;` is allowed.
|
||||
|
||||
1 + 2; 3 + 4 -> 7
|
||||
x = 10; y = 20; x + y -> 30
|
||||
a = 3; b = a * a; a + b -> 12
|
||||
a = 3; b = a * a; a + b; -> 12 (a trailing ; is allowed)
|
||||
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
||||
In order from highest precedence to lowest. All operators are
|
||||
left-associative except exponentiation, which is right-associative.
|
||||
|
||||
Unary prefix: +x -x !x ~x ++x --x
|
||||
Exponent: x ** y (right-associative)
|
||||
Mul-class: x * y x / y x % y x mod y
|
||||
x sdiv y x smod y (signed division / remainder)
|
||||
Add-class: x + y x - y
|
||||
Shifts: x << y x >> y x <<< y x >>> y x sar y
|
||||
(<<< and >>> are bitwise rotates on the low 64 bits;
|
||||
sar is the sign-propagating arithmetic shift right)
|
||||
Bitwise AND: x & y
|
||||
Bitwise XOR: x ^ y
|
||||
Bitwise OR: x | y
|
||||
Comparison: x < y x <= y x > y x >= y
|
||||
Equality: x == y x != y
|
||||
Conditional: c ? a : b (ternary, right-associative)
|
||||
Logarithm: x log y (computes log_x(y))
|
||||
Assignment: x = y let x = y (binds x, evaluates to y)
|
||||
|
||||
Notes:
|
||||
|
||||
- Division and modulo with a zero right-hand side raise
|
||||
`RZ_NUM_ERR_DIV_ZERO` (for both the unsigned `/` `%` and the signed
|
||||
`sdiv` `smod`).
|
||||
- `sdiv`, `smod` and `sar` interpret their operands as two's-complement
|
||||
signed 64-bit values. `/`, `%` and `>>` are unsigned. For example
|
||||
`-5 sdiv 2` is `-2` while `0xfffffffffffffffb / 2` is a large
|
||||
positive number; `0xffffffffffffff00 sar 4` sign-extends to
|
||||
`0xfffffffffffffff0` whereas `>> 4` does not.
|
||||
- Rotations `<<<` and `>>>` on a `ut64` or big number rotate a 64-bit
|
||||
projection of the operand (a big number has no width-independent
|
||||
rotate). On a *bit-vector* they rotate within that value's own width
|
||||
instead - see the Bit-vectors section.
|
||||
- `**` and `log` are computed in `double`. An integer `**` with a small
|
||||
exponent is still exact (it promotes to a big number); otherwise the
|
||||
result is a `float`. For an exact bignum power use repeated
|
||||
multiplication.
|
||||
- `~` (bitwise NOT) on a big number projects to `ut64` first, since
|
||||
the complement of an arbitrary-precision integer is not defined
|
||||
without a fixed width.
|
||||
- The ternary `c ? a : b` is right-associative, so
|
||||
`a ? b : c ? d : e` parses as `a ? b : (c ? d : e)`. It
|
||||
short-circuits: only the taken branch is evaluated, so
|
||||
`b != 0 ? a / b : 0` is safe even when `b` is zero. The
|
||||
condition's truthiness is per-kind - non-zero ut64/big/bit-vector
|
||||
is true; a float is tested as a float, so `0.5 ? a : b` is `a`
|
||||
but `0.0 ? a : b` is `b`. The selected branch keeps its own kind,
|
||||
including bit-vectors and big numbers. The ternary is also the one
|
||||
control-flow construct that lifts structurally to RzIL, mapping
|
||||
directly onto `ite`.
|
||||
|
||||
Comparisons and equality return `1` (true) or `0` (false) as a
|
||||
`ut64`.
|
||||
|
||||
|
||||
Typed address syntax
|
||||
--------------------
|
||||
|
||||
A literal address can be followed by a colon and a typed suffix
|
||||
describing the read width, signedness, endianness, and whether the
|
||||
bytes are an integer or a float:
|
||||
|
||||
0x1000:le32 read a 32-bit little-endian unsigned word
|
||||
0x1000:be64 read a 64-bit big-endian unsigned word
|
||||
0x1000:8 read 8 bits (1 byte), native endianness
|
||||
0x1000:s32 read a 32-bit SIGNED word (sign-extended)
|
||||
0x1000:lef32 read a 32-bit little-endian IEEE-754 float
|
||||
0x1000:f16 read a native-endian half-precision float
|
||||
0x1000:f64 read a double-precision float
|
||||
0x1000:128 read a 128-bit value, returns a bit-vector
|
||||
0x1000:f128 read a 128-bit IEEE-754 quad, raw bits as a bit-vector
|
||||
|
||||
The suffix grammar is `[le|be]? [s]? width` for integers (`width`
|
||||
one of 8/16/32/64/128) or `[le|be]? f (16|32|64|128)` for floats.
|
||||
An `s` marks a signed integer read (the value is sign-extended to
|
||||
the result); an `f` marks an IEEE-754 float read.
|
||||
|
||||
The result kind follows the requested width:
|
||||
|
||||
* widths up to 64 bits return a `ut64` (integer) or `double`
|
||||
(float); the float decoder handles f16 / f32 / f64 directly;
|
||||
* 128-bit reads (`:128` and `:f128`) return a width-128
|
||||
bit-vector. Two underlying 64-bit reads are stitched into the
|
||||
bit-vector in the requested endianness, so `:128` and `:f128`
|
||||
both preserve every bit. RzNum's float kind itself is
|
||||
double-precision, so `:f128` does NOT decode to a native
|
||||
quad-precision float; the bit-vector carries the raw 128-bit
|
||||
IEEE-754 pattern and the host can inspect it, compare two
|
||||
quad values bit-for-bit, or feed it to a host function that
|
||||
knows how to print quad-precision.
|
||||
|
||||
When evaluated through `rz_core_math()` (which backs the `%` shell
|
||||
command) the suffix dereferences against the active IO layer:
|
||||
|
||||
[0x0]> % 0:le32 (on an ELF)
|
||||
hex 0x464c457f <- little-endian read of 7f 45 4c 46
|
||||
[0x0]> % 0:s32 <- same bytes, interpreted signed
|
||||
[0x0]> % 0:lef32 <- same bytes, interpreted as a float
|
||||
[0x0]> % 0:128 <- 16 bytes as a width-128 bit-vector
|
||||
|
||||
When evaluated through the bare `rz_num_math_value()` API with no
|
||||
IO-read callback wired in, the suffix is parsed but the literal
|
||||
address is returned unchanged (parity across every width).
|
||||
|
||||
|
||||
String-as-bytes literal
|
||||
-----------------------
|
||||
|
||||
A double-quoted string is interpreted as a sequence of bytes packed
|
||||
little-endian (first byte is the least-significant) into a bit-vector
|
||||
whose width is `8 * byte-count`. Supported escapes: `\\`, `\"`, `\n`,
|
||||
`\t`, `\r`, `\0`, `\xHH`.
|
||||
|
||||
"AB" -> 0x4241 bit-vector, width 16 ('A' in the low byte)
|
||||
"ABCD" -> 0x44434241 bit-vector, width 32
|
||||
"\x01\xff" -> 0xff01 bit-vector, width 16
|
||||
|
||||
The bytes are taken verbatim, so a string written in any script is
|
||||
packed as its raw UTF-8 source bytes. Because the result is a
|
||||
bit-vector that carries its width, the byte count is recoverable with
|
||||
`len` (see below):
|
||||
|
||||
len("ABCD") -> 32 (bits)
|
||||
len("ABCD") / 8 -> 4 (bytes)
|
||||
len("ABCDEFGHIJ") -> 80 (a 10-byte string is fine)
|
||||
len("Ω") -> 16 ("Ω" is two UTF-8 bytes)
|
||||
|
||||
Arithmetic on a string value follows bit-vector rules at that width,
|
||||
so `"AB" + 1` is `0x4242` (it wraps within 16 bits). An empty string
|
||||
`""` is the zero-width edge case and evaluates to a `ut64` `0`.
|
||||
|
||||
|
||||
Built-in functions
|
||||
------------------
|
||||
|
||||
| Function | Arity | Description |
|
||||
|--------------------------------|-------|----------------------------------------|
|
||||
| `min(a, b)` | 2 | smaller operand, kind-preserving |
|
||||
| `max(a, b)` | 2 | larger operand, kind-preserving |
|
||||
| `abs(x)` | 1 | absolute value |
|
||||
| `popcount(x)` | 1 | number of set bits (works on bignums) |
|
||||
| `len(x)` | 1 | length in BITS of a bit-vector / big / ut64 (a string literal is a bit-vector, so `len(s)/8` is its byte count) |
|
||||
| `минимум(a, b)` | 2 | alias for `min` (Russian) |
|
||||
| `максимум(a, b)` | 2 | alias for `max` (Russian) |
|
||||
| `最小(a, b)` / `最大(a, b)` | 2 | aliases for `min` / `max` (Chinese/Japanese) |
|
||||
| `دالة(x)` | 1 | alias for `abs` (Arabic) |
|
||||
| `Σ(x)` | 1 | alias for `popcount` (Greek sigma) |
|
||||
|
||||
`len` gives a single, bit-keyed notion of length across the
|
||||
integer-ish kinds: a bit-vector reports its declared width, a big
|
||||
number its exact significant bit width, a `ut64` its significant
|
||||
bit width (`len(0)` is 0). Since a string-as-bytes literal is a
|
||||
bit-vector of `8 * byte-count` bits, `len("ABCD")` is 32 and
|
||||
`len("ABCD") / 8` is the byte count 4. `len` on a float is a type
|
||||
error.
|
||||
|
||||
The Russian aliases exist as a runnable demonstration that
|
||||
identifier handling is Unicode-clean end to end; new natural-
|
||||
language aliases land in `librz/util/num/evaluator.c`
|
||||
without grammar changes.
|
||||
|
||||
`**` computes an exact integer power when both operands are
|
||||
integers and the exponent is a non-negative integer no larger than
|
||||
4096; the result promotes to a big number when it exceeds `ut64`.
|
||||
Otherwise (float operand, negative or oversized exponent) it falls
|
||||
through to a `double` computation.
|
||||
|
||||
% 2 ** 64 -> decimal 18446744073709551616 (exact)
|
||||
% 2 ** 0.5 -> float 1.41421 (double path)
|
||||
|
||||
Host-registered functions
|
||||
--------------------------
|
||||
|
||||
A host can register additional functions through the C API without
|
||||
modifying the grammar or the evaluator. The function appears in the
|
||||
expression language exactly like a built-in; registered names take
|
||||
precedence over the built-in table, so a host may also shadow one.
|
||||
|
||||
Through `rz_core_math()` (which backs the `%` shell command) Rizin
|
||||
registers a set of functions that read bytes through the active IO
|
||||
layer (bounded to 64 KiB per call) and compute over them:
|
||||
|
||||
| Function | Arity | Result | Description |
|
||||
|---------------------------|-------|--------|------------------------------------------|
|
||||
| `md5(addr, n)` | 2 | bignum | MD5 digest |
|
||||
| `sha1(addr, n)` | 2 | bignum | SHA-1 digest |
|
||||
| `sha256(addr, n)` | 2 | bignum | SHA-256 digest |
|
||||
| `sha384(addr, n)` | 2 | bignum | SHA-384 digest |
|
||||
| `sha512(addr, n)` | 2 | bignum | SHA-512 digest |
|
||||
| `adler32(addr, n)` | 2 | ut64 | Adler-32 checksum |
|
||||
| `xxhash(addr, n)` | 2 | ut64 | xxHash32 |
|
||||
| `hash(addr, n)` | 2 | bignum | alias for `sha256` |
|
||||
| `crc(addr, n [, width])` | 2-3 | ut64 | CRC; width 8/16/32/64, default 32 |
|
||||
| `crc8/crc16/crc32/crc64` | 2 | ut64 | explicit CRC-width aliases |
|
||||
| `entropy(addr, n)` | 2 | float | Shannon entropy, 0.0 - 8.0 |
|
||||
| `temperature(addr, n)` | 2 | float | information temperature |
|
||||
|
||||
The CRC family deliberately lives behind one `crc()` name with an
|
||||
optional width parameter rather than separate per-width entry
|
||||
points:
|
||||
|
||||
% crc(0, 16) # CRC-32 (default)
|
||||
% crc(0, 16, 16) # CRC-16
|
||||
% crc(0, 16, 64) # CRC-64
|
||||
|
||||
Digest results are returned as big numbers laid out big-endian so
|
||||
the hex form matches the `ph <algo>` command:
|
||||
|
||||
[0x0]> % sha256(0, 16)
|
||||
decimal 72468553576780861412383346668577895435178167241169020037740589910276139146126
|
||||
hex 0xa037bf6e958bd6b2fdcc4a95c7dc6f7735730ae33d20819a056a5da050d05b8e
|
||||
width 256 bits (approx)
|
||||
|
||||
To register your own function, create a registry, add entries, and
|
||||
pass it through `RzNumMathOptions.funcs`:
|
||||
|
||||
RzNumFuncRegistry *reg = rz_num_func_registry_new();
|
||||
rz_num_func_registry_add(reg, "myfn", 1, my_callback, my_userdata);
|
||||
RzNumMathOptions opt = { .funcs = reg };
|
||||
rz_num_math_value_ex(num, expr, &opt, &out, &err);
|
||||
rz_num_func_registry_free(reg);
|
||||
|
||||
`rz_num_func_registry_add(reg, name, arity, fn, user)`:
|
||||
|
||||
* `name` is copied as UTF-8, so a function may be registered under
|
||||
any identifier the grammar accepts - including a non-ASCII
|
||||
(Unicode) name. For example a function registered as `минимум`,
|
||||
`合计`, or `δ` is then callable by that exact name:
|
||||
|
||||
rz_num_func_registry_add(reg, "сумма", -1, sum_cb, NULL);
|
||||
// expression: сумма(1, 2, 3) -> 6
|
||||
|
||||
* `arity` is the exact number of arguments the function requires,
|
||||
or `-1` for a variadic function that accepts any number of
|
||||
arguments. A call whose argument count does not match a
|
||||
non-negative `arity` fails with `RZ_NUM_ERR_NOT_IMPLEMENTED`
|
||||
before the callback runs. There is no upper bound on the number
|
||||
of arguments a variadic function may receive:
|
||||
|
||||
rz_num_func_registry_add(reg, "сумма", -1, sum_cb, NULL);
|
||||
// сумма(1,2,3,4,5,6,7,8,9,10) -> 55
|
||||
|
||||
* registered names are consulted *before* the built-in table, so a
|
||||
registration shadows a built-in of the same name. This is how a
|
||||
host can, for instance, replace the 2-argument built-in `min`
|
||||
with a variadic one.
|
||||
|
||||
The callback has the signature:
|
||||
|
||||
void my_callback(void *user, const RzNumValue *args, int argc,
|
||||
RzNumCallbackResult *out);
|
||||
|
||||
It receives the opaque `user` pointer from registration, the
|
||||
already-evaluated `args` (each carrying its own kind) with their
|
||||
count `argc`, and fills an `RzNumCallbackResult` with a kind, a
|
||||
payload, and an `ok` flag. Setting `ok` to false makes the
|
||||
expression fail with `RZ_NUM_ERR_UNCOMPUTABLE`. A big-number or
|
||||
bit-vector payload transfers ownership to the evaluator.
|
||||
|
||||
|
||||
Result kinds
|
||||
------------
|
||||
|
||||
Every evaluation produces an `RzNumValue` carrying a *kind* and a
|
||||
payload:
|
||||
|
||||
| Kind | Payload | Produced by |
|
||||
|----------------------------|-------------|-----------------------------------|
|
||||
| `RZ_NUM_KIND_UT64` | `ut64 n` | integer literals, comparisons, ...|
|
||||
| `RZ_NUM_KIND_FLOAT` | `double d` | float literals, `**`, `log` |
|
||||
| `RZ_NUM_KIND_BIG` | `RzNumBig*` | overflowing integer literals |
|
||||
| `RZ_NUM_KIND_BITVECTOR` | `RzBitVector*` | width-suffixed literals (`5u8`) |
|
||||
|
||||
When a sub-expression mixes kinds, the more general kind wins:
|
||||
|
||||
ut64 + ut64 -> ut64
|
||||
ut64 + big -> big (or demoted ut64 if the result fits)
|
||||
ut64 + float -> float
|
||||
big + float -> float (big projected to double)
|
||||
|
||||
There is no cast operator and no implicit C-style cast, so a mixed
|
||||
expression never *fails* for want of a cast - it promotes by the rule
|
||||
above. Mixing bit-vector *widths* is the one case that does not
|
||||
promote: the operand width is kept and the other side is taken modulo
|
||||
that width (see Bit-vectors). To pin a value to a specific width or
|
||||
kind, write the literal you want (`5u32` for a 32-bit bit-vector) or
|
||||
read it through a typed address.
|
||||
|
||||
The `%` shell command and `rz-ax` use the result kind to choose an
|
||||
output layout (multi-base table for `ut64`, decimal scientific +
|
||||
hex bit pattern for `float`, exact decimal + hex + bit-width for
|
||||
`big`, and width + hex + binary for a bit-vector).
|
||||
|
||||
|
||||
Bit-vectors
|
||||
-----------
|
||||
|
||||
A width-suffixed literal (`5u8`, `0xffu16`, `0x123456789abcu64`)
|
||||
evaluates to a bit-vector of exactly that width. Unlike `ut64` and
|
||||
`big`, a bit-vector never changes width during arithmetic: every
|
||||
operation is computed modulo `2^width`, so results wrap instead of
|
||||
promoting.
|
||||
|
||||
Operations defined on bit-vectors:
|
||||
|
||||
| Operators | Result | Notes |
|
||||
|--------------------------|-------------|----------------------------------|
|
||||
| `+` `-` `*` `/` `%` | bit-vector | modular at the operand width; `/` or `%` by zero is `RZ_NUM_ERR_DIV_ZERO` |
|
||||
| `sdiv` `smod` `sar` | bit-vector | two's-complement signed at the operand width; zero divisor is `RZ_NUM_ERR_DIV_ZERO` |
|
||||
| `&` `\|` `^` | bit-vector | bitwise |
|
||||
| `<<` `>>` | bit-vector | shift by the low bits of the right operand |
|
||||
| `<<<` `>>>` | bit-vector | rotate within the operand width (bits that fall off one end wrap to the other); the rotate amount is taken modulo W |
|
||||
| `**` | bit-vector | exponentiation by squaring, modular at the operand width (so `2u8 ** 8u8` wraps to `0`); the exponent must fit a `ut64` |
|
||||
| `log` | bit-vector | `log_a(b)`, computed in `double` internally but returned as a bit-vector at the operand width, not as a float |
|
||||
| `==` `!=` `<` `<=` `>` `>=` | `ut64` 0/1 | unsigned comparison |
|
||||
|
||||
The signed family (`sdiv`/`smod`/`sar`) lifts structurally to RzIL's
|
||||
signed operators (`/⁺`, `%⁺`, `≫⁺`); rotates, `**`, and `log` have no
|
||||
direct RzIL pure-op for bit-vectors, so the lift grounds them to a
|
||||
constant - the evaluator's result is correct, only the symbolic form
|
||||
is collapsed.
|
||||
|
||||
When a bit-vector is combined with a plain `ut64`, the bit-vector's
|
||||
width wins and the integer is taken modulo that width:
|
||||
|
||||
% 5u8 + 3 -> width 8, 0x08
|
||||
% 0xffu8 + 1u8 -> width 8, 0x00 (wraps)
|
||||
% 1u8 << 4u8 -> width 8, 0x10
|
||||
% 10u8 / 3u8 -> width 8, 0x03
|
||||
|
||||
A bit-vector projects to a `ut64` (via `rz_num_value_to_ut64()` or
|
||||
the legacy `rz_num_math_ut64()`) by taking its low 64 bits.
|
||||
|
||||
|
||||
### Unicode rendering
|
||||
|
||||
When the shell is in UTF-8 mode (`scr.utf8=true`), the `%` command
|
||||
folds the bit-width into the value rows of a bit-vector as a Unicode
|
||||
subscript - the same notation RzIL uses for bit-vector constants in
|
||||
its Unicode export - and drops the `0b` prefix from the binary row.
|
||||
With `scr.utf8=false` the plain ASCII layout is used instead, the
|
||||
same way `plf` shows the ASCII layout that `pLf` renders in Unicode.
|
||||
|
||||
[0x0]> e scr.utf8=false
|
||||
[0x0]> % 200u8 + 100u8
|
||||
hex 0x2c
|
||||
binary 0b00101100
|
||||
width 8 bits
|
||||
|
||||
[0x0]> e scr.utf8=true
|
||||
[0x0]> % 200u8 + 100u8
|
||||
hex 0x2c₈
|
||||
binary 00101100₈
|
||||
width 8 bits
|
||||
|
||||
The value rows come first and the width row last in both modes. The
|
||||
renderer is reachable from C as `rz_num_value_print_ex()` with
|
||||
`RzNumPrintOptions.utf8` set; `rz_num_value_print()` is the ASCII
|
||||
default.
|
||||
|
||||
|
||||
Relationship to RzIL
|
||||
--------------------
|
||||
|
||||
RzNum's bit-vector kind and RzIL both build on the same `RzBitVector`
|
||||
primitive, so the two systems already share a value representation.
|
||||
The Unicode bit-vector form above is deliberately the same notation
|
||||
RzIL emits (see `il_export_string_unicode.c`). Keeping the output
|
||||
visually consistent means a bit-vector printed by `%` reads the same
|
||||
as one inside a `pLf` listing.
|
||||
|
||||
The operator-to-glyph mapping - which RzNum operator becomes which
|
||||
RzIL pure-op and Unicode symbol - lives in `doc/math-il-lift.md`, so it
|
||||
is maintained in one place. Two things tie the systems together:
|
||||
|
||||
* **A shared bit-vector formatter.** The subscript-width rendering
|
||||
used here and the bit-vector case in RzIL's Unicode exporter are
|
||||
backed by the same RzUtil helpers, `rz_bv_width_subscript()` and
|
||||
`rz_bv_as_unicode_string()`, so the two renderings cannot drift
|
||||
apart.
|
||||
* **Lifting to RzIL.** `rz_num_il_lift()` (core wrapper
|
||||
`rz_core_il_lift()`) turns an expression into the matching
|
||||
`RzILOpPure`, rendered in the form `pLf` prints. Operators with a
|
||||
direct pure-op survive as structure; the rest are evaluated to a
|
||||
constant first:
|
||||
|
||||
1 + 2 * 3 -> (0x1₆₄ + (0x2₆₄ * 0x3₆₄))
|
||||
5u8 + 3u8 -> (0x5₈ + 0x3₈)
|
||||
min(5, 3) + 1 -> (0x3₆₄ + 0x1₆₄) (min evaluated first)
|
||||
2 ** 8 -> 0x100₆₄ (** evaluated)
|
||||
|
||||
Arithmetic, bitwise, the shifts, the signed `sdiv`/`smod`/`sar`,
|
||||
`==`/`<=`, the ternary (onto `ite`), and float-pure arithmetic lift
|
||||
structurally - the ternary is the one control-flow construct that
|
||||
survives into symbolic execution. Functions, the typed-address
|
||||
dereference, host variables, `**`/`log`, the rotates, mixed
|
||||
integer-and-float arithmetic, and `;`-sequences are the cases
|
||||
evaluated to a constant.
|
||||
|
||||
For an exhaustive set of worked examples - simple operators, deeply
|
||||
nested expressions, the ternary lifting onto RzIL `ite`, host-
|
||||
registered functions, and every case where the lift has to ground a
|
||||
sub-expression - see `doc/math-il-lift.md`.
|
||||
|
||||
|
||||
Error categories
|
||||
----------------
|
||||
|
||||
Each evaluation either succeeds (`RZ_NUM_ERR_OK`) or fails with
|
||||
exactly one category:
|
||||
|
||||
| Category | When |
|
||||
|-------------------------------------|--------------------------------------------|
|
||||
| `RZ_NUM_ERR_PARSE` | unbalanced parens, unknown special var, ...|
|
||||
| `RZ_NUM_ERR_EMPTY` | input is empty or whitespace only |
|
||||
| `RZ_NUM_ERR_RESERVED_WORD` | bare `mod` / `log` / `le` / `be` |
|
||||
| `RZ_NUM_ERR_UNDEFINED_VAR` | host callback rejected a known $-variable |
|
||||
| `RZ_NUM_ERR_DIV_ZERO` | `/` or `%` by zero |
|
||||
| `RZ_NUM_ERR_OVERFLOW` | reserved for future fixed-width literals |
|
||||
| `RZ_NUM_ERR_TYPE_MISMATCH` | reserved |
|
||||
| `RZ_NUM_ERR_TIMEOUT` | wall-clock budget exceeded |
|
||||
| `RZ_NUM_ERR_UNCOMPUTABLE` | `log` of non-positive, failed function or IO read |
|
||||
| `RZ_NUM_ERR_NOT_IMPLEMENTED` | unknown function, unhandled node |
|
||||
| `RZ_NUM_ERR_OUT_OF_MEMORY` | allocation failed |
|
||||
|
||||
Human-readable names for each category are returned by
|
||||
`rz_num_error_name()`.
|
||||
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
`rz_num_math_value_ex()` accepts an `RzNumMathOptions` struct with
|
||||
a `timeout_ms` field. The evaluator checks the monotonic clock
|
||||
every 256 AST nodes; if the budget is exceeded the result carries
|
||||
`RZ_NUM_ERR_TIMEOUT`. The default (and the value used by
|
||||
`rz_num_math_value`, `rz_num_math_ut64`, `rz-ax`, and `%`) is
|
||||
zero = unlimited. Set a positive value when feeding untrusted
|
||||
input or when an interactive prompt must remain responsive.
|
||||
|
||||
|
||||
Examples at the shell
|
||||
---------------------
|
||||
|
||||
> % 1 + 2 * 3
|
||||
int32 7
|
||||
uint32 7
|
||||
hex 0x7
|
||||
octal 07
|
||||
unit 7
|
||||
segment 0000:0007
|
||||
binary 0b00000111
|
||||
trits 0t21
|
||||
|
||||
> % 0xff & 0x0f
|
||||
int32 15
|
||||
uint32 15
|
||||
hex 0xf
|
||||
...
|
||||
|
||||
> % 0xffffffffffffffff + 1
|
||||
int32 0
|
||||
...
|
||||
(ut64 + ut64 wraps; write an explicit bignum literal for
|
||||
arbitrary precision)
|
||||
|
||||
> % 0x10000000000000000
|
||||
decimal 18446744073709551616
|
||||
hex 0x10000000000000000
|
||||
width 68 bits (approx)
|
||||
|
||||
> % 2 ** 100
|
||||
decimal 1267650600228229401496703205376
|
||||
hex 0x100000000000000000000000000
|
||||
width 104 bits (approx)
|
||||
|
||||
> % 3.14159 ** 2
|
||||
float 9.8696
|
||||
scifmt 9.8696044010893580
|
||||
hex 0x4023bd3b7c6aaa1c
|
||||
|
||||
> % min(0x100, max(0x50, 0x80))
|
||||
int32 128
|
||||
...
|
||||
|
||||
> % минимум(0x100, 0x200)
|
||||
int32 256
|
||||
...
|
||||
|
||||
> % 0:le32 (on a loaded ELF)
|
||||
hex 0x464c457f <- dereferences 7f 45 4c 46
|
||||
|
||||
> % sha256(0, 16)
|
||||
decimal 72468553576780861412383346668577895435178167241169020037740589910276139146126
|
||||
hex 0xa037bf6e958bd6b2fdcc4a95c7dc6f7735730ae33d20819a056a5da050d05b8e
|
||||
width 256 bits (approx)
|
||||
|
||||
> %j 0x10000000000000000
|
||||
{"value":"0x10000000000000000","kind":"big"}
|
||||
|
||||
> % 200u8 + 100u8 (8-bit modular arithmetic)
|
||||
hex 0x2c
|
||||
binary 0b00101100
|
||||
width 8 bits
|
||||
@@ -261,7 +261,15 @@ static bool filter(RzParse *p, ut64 addr, RzFlag *f, RzAnalysisHint *hint, char
|
||||
;
|
||||
}
|
||||
}
|
||||
// Evaluate only the number token, not the trailing operand
|
||||
// syntax (']', separators, comments) that follows it. ptr2 is
|
||||
// the token boundary computed just above; terminate the string
|
||||
// there so the evaluator receives a clean expression, then put
|
||||
// the byte back so the surrounding code can keep using it.
|
||||
char saved_sep = *ptr2;
|
||||
*ptr2 = '\0';
|
||||
off = rz_num_math(NULL, ptr);
|
||||
*ptr2 = saved_sep;
|
||||
if (off >= p->minval) {
|
||||
fcn = p->analb.get_fcn_in(p->analb.analysis, off, 0);
|
||||
if (fcn && fcn->addr == off) {
|
||||
|
||||
@@ -90,6 +90,12 @@ typedef struct Opcode_t {
|
||||
return data; \
|
||||
}
|
||||
|
||||
// The operand tokeniser splits on ',' only, so an offset token keeps a
|
||||
// trailing ']' or pre-index '!'; evaluate just the leading number.
|
||||
static ut64 arm64_getnum(const char *tok) {
|
||||
return rz_num_get_leading(NULL, tok, NULL);
|
||||
}
|
||||
|
||||
static int get_mem_option(char *token) {
|
||||
// values 4, 8, 12, are unused. XXX to adjust
|
||||
const char *options[] = { "sy", "st", "ld", "xxx", "ish", "ishst",
|
||||
@@ -1120,7 +1126,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
free(t);
|
||||
return false;
|
||||
}
|
||||
op->operands[operand].shift_amount = rz_num_math(NULL, token);
|
||||
op->operands[operand].shift_amount = arm64_getnum(token);
|
||||
op->operands[operand].amount_present = true;
|
||||
if (op->operands[operand].shift_amount > 63) {
|
||||
free(t);
|
||||
@@ -1148,7 +1154,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
op->operands[operand].shift_amount = 0;
|
||||
op->operands[operand].amount_present = false;
|
||||
} else {
|
||||
op->operands[operand].shift_amount = rz_num_math(NULL, token);
|
||||
op->operands[operand].shift_amount = arm64_getnum(token);
|
||||
op->operands[operand].amount_present = true;
|
||||
}
|
||||
operand++;
|
||||
@@ -1170,7 +1176,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
// XZR
|
||||
op->operands[operand].reg = 31;
|
||||
} else {
|
||||
op->operands[operand].reg = rz_num_math(NULL, token + 1);
|
||||
op->operands[operand].reg = arm64_getnum(token + 1);
|
||||
}
|
||||
|
||||
if (op->operands[operand].reg > 31) {
|
||||
@@ -1191,7 +1197,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
op->operands[operand].reg = 31;
|
||||
op->operands[operand].reg_type |= ARM_SP;
|
||||
} else {
|
||||
op->operands[operand].reg = rz_num_math(NULL, token + 1);
|
||||
op->operands[operand].reg = arm64_getnum(token + 1);
|
||||
}
|
||||
|
||||
if (op->operands[operand].reg > 31) {
|
||||
@@ -1202,7 +1208,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
case 'v':
|
||||
op->operands_count++;
|
||||
op->operands[operand].type = ARM_FP;
|
||||
op->operands[operand].reg = rz_num_math(NULL, token + 1);
|
||||
op->operands[operand].reg = arm64_getnum(token + 1);
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
@@ -1268,7 +1274,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
}
|
||||
op->operands_count++;
|
||||
op->operands[operand].type = ARM_CONSTANT;
|
||||
op->operands[operand].immediate = rz_num_math(NULL, token + 1);
|
||||
op->operands[operand].immediate = arm64_getnum(token + 1);
|
||||
op->operands[operand].preindex = token - t < index_bound;
|
||||
break;
|
||||
case '-':
|
||||
@@ -1277,7 +1283,7 @@ static bool parseOperands(char *str, ArmOp *op) {
|
||||
default:
|
||||
op->operands_count++;
|
||||
op->operands[operand].type = ARM_CONSTANT;
|
||||
op->operands[operand].immediate = rz_num_math(NULL, token);
|
||||
op->operands[operand].immediate = arm64_getnum(token);
|
||||
op->operands[operand].preindex = token - t < index_bound;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5139,7 +5139,51 @@ static ut64 getnum(const RzAsm *a, const char *s) {
|
||||
if (*s == '$') {
|
||||
s++;
|
||||
}
|
||||
return rz_num_math(NULL, s);
|
||||
// The assembler may hand getnum an operand fragment that still carries
|
||||
// trailing operand syntax: ',' (the next operand), ']' (the close of a
|
||||
// memory operand), or an unmatched ')' (the close of "st(2)" after the
|
||||
// caller has consumed the matching '('). The strict evaluator rejects
|
||||
// that trailing text, where the legacy reader stopped at it, so evaluate
|
||||
// only the leading number/expression. A *matched* ')' is kept so that
|
||||
// parenthesised immediates such as "(0x10 + 0x20)" still evaluate.
|
||||
size_t n = 0;
|
||||
int depth = 0;
|
||||
for (; s[n]; n++) {
|
||||
const char c = s[n];
|
||||
if (c == '(') {
|
||||
depth++;
|
||||
} else if (c == ')') {
|
||||
if (depth == 0) {
|
||||
break;
|
||||
}
|
||||
depth--;
|
||||
} else if (c == ',' || c == ']') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
char *num = rz_str_ndup(s, n);
|
||||
if (!num) {
|
||||
return 0;
|
||||
}
|
||||
// An immediate wider than 64 bits saturates to UT64_MAX, matching the
|
||||
// legacy assembler: "mov rax, 0x112233445566778899" encodes as
|
||||
// "mov rax, -1" rather than silently wrapping to the low 64 bits. The
|
||||
// evaluator promotes such a literal to RZ_NUM_KIND_BIG, so detect that
|
||||
// here; everything else keeps the lenient ut64 reading (which also
|
||||
// recovers seg:off and other forms the strict parse rejects).
|
||||
RzNumValue val;
|
||||
rz_num_value_init(&val);
|
||||
char *err = NULL;
|
||||
ut64 v;
|
||||
if (rz_num_math_value(NULL, num, &val, &err)) {
|
||||
v = (val.kind == RZ_NUM_KIND_BIG) ? UT64_MAX : rz_num_value_to_ut64(&val);
|
||||
} else {
|
||||
v = rz_num_math(NULL, num);
|
||||
}
|
||||
rz_num_value_fini(&val);
|
||||
free(err);
|
||||
free(num);
|
||||
return v;
|
||||
}
|
||||
|
||||
static int oprep(const RzAsm *a, ut8 *data, const Opcode *op) {
|
||||
|
||||
+5
-5
@@ -396,13 +396,13 @@ while_end:
|
||||
default:
|
||||
if (!num_is_parsed) {
|
||||
if (is_range) {
|
||||
range_end = rz_num_get(cons->num, ptr2);
|
||||
range_end = rz_num_get_leading(cons->num, ptr2, NULL);
|
||||
// check for bad value, if range_end == 0, we check if ptr2 == '0'
|
||||
if (range_end == 0 && *ptr != '0') {
|
||||
range_end = -1; // this allow [n- ]
|
||||
}
|
||||
} else {
|
||||
range_begin = range_end = rz_num_get(cons->num, ptr2);
|
||||
range_begin = range_end = rz_num_get_leading(cons->num, ptr2, NULL);
|
||||
}
|
||||
num_is_parsed = 1;
|
||||
}
|
||||
@@ -418,18 +418,18 @@ while_end:
|
||||
char *p, *token = ptr2 + 1;
|
||||
p = strstr(token, "..");
|
||||
if (!p) {
|
||||
grep->line = rz_num_get(cons->num, ptr2 + 1);
|
||||
grep->line = rz_num_get_leading(cons->num, ptr2 + 1, NULL);
|
||||
grep->range_line = 0;
|
||||
} else {
|
||||
*p = '\0';
|
||||
grep->range_line = 1;
|
||||
if (*token) {
|
||||
grep->f_line = rz_num_get(cons->num, token);
|
||||
grep->f_line = rz_num_get_leading(cons->num, token, NULL);
|
||||
} else {
|
||||
grep->f_line = 0;
|
||||
}
|
||||
if (p[2]) {
|
||||
grep->l_line = rz_num_get(cons->num, p + 2);
|
||||
grep->l_line = rz_num_get_leading(cons->num, p + 2, NULL);
|
||||
} else {
|
||||
grep->l_line = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Core-aware front end to the RzNum expression evaluator.
|
||||
*
|
||||
* Stitches rz_num_math_value_ex() (in librz_util) to RzCore's
|
||||
* context:
|
||||
*
|
||||
* - the per-core num callback that resolves flags / special
|
||||
* variables is already wired up in core.c, so passing core->num
|
||||
* through covers identifier resolution;
|
||||
* - an IO-read callback backs the `:le32` / `:be64` / `:N`
|
||||
* typed-address dereference syntax, reading through core->io;
|
||||
* - a registry of core-specific functions (hash / crc / entropy /
|
||||
* temperature) is supplied, backed by RzHash and RzIO.
|
||||
*
|
||||
* The registry and the IO callback are passed to the evaluator
|
||||
* through RzNumMathOptions; librz_util has no compile-time knowledge
|
||||
* of RzIO or RzHash.
|
||||
*/
|
||||
|
||||
#include <rz_core.h>
|
||||
#include <rz_hash.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
|
||||
// Raw-bytes reader backing RzNum's typed-address syntax (same signature as
|
||||
// rz_pf's RzPfReadAtCb, see <rz_pf.h>); the evaluator does all the decoding.
|
||||
static ut64 core_io_read(void *user, ut64 addr, ut8 *buf, size_t len) {
|
||||
RzCore *core = (RzCore *)user;
|
||||
if (!core || !core->io || !len) {
|
||||
return 0;
|
||||
}
|
||||
int got = rz_io_nread_at(core->io, addr, buf, (int)len);
|
||||
return got > 0 ? (ut64)got : 0;
|
||||
}
|
||||
|
||||
// Read up to 64 KiB at addr into a fresh zero-filled buffer (NULL on any
|
||||
// problem). The cap stops a stray expression from slurping gigabytes; the
|
||||
// refusal and any short read are logged.
|
||||
static ut8 *core_read_block(RzCore *core, ut64 addr, ut64 len) {
|
||||
if (!core || !core->io || len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (len > 0x10000) {
|
||||
RZ_LOG_WARN("rz_core_math: refusing a %" PFMT64u "-byte read at 0x%08" PFMT64x
|
||||
" (limit is 0x10000)\n",
|
||||
len, addr);
|
||||
return NULL;
|
||||
}
|
||||
ut8 *buf = calloc(1, len);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
}
|
||||
int nread = rz_io_nread_at(core->io, addr, buf, len);
|
||||
if (nread < 1) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
if ((ut64)nread != len) {
|
||||
RZ_LOG_WARN("rz_core_math: short read at 0x%08" PFMT64x ": got %d of %" PFMT64u
|
||||
" bytes (rest zero-filled)\n",
|
||||
addr, nread, len);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Turn a big-endian byte buffer into an RzNumValue: a bignum when it
|
||||
// exceeds 8 bytes, otherwise a plain ut64.
|
||||
static void bytes_to_result(const ut8 *digest, ut32 n, RzNumCallbackResult *out) {
|
||||
if (n == 0) {
|
||||
out->ok = false;
|
||||
return;
|
||||
}
|
||||
if (n <= 8) {
|
||||
// Big-endian bytes -> ut64 via the rz_util reader, zero-padding
|
||||
// the high end so any width in 1..8 is handled.
|
||||
ut8 padded[8] = { 0 };
|
||||
memcpy(padded + (8 - n), digest, n);
|
||||
out->ok = true;
|
||||
out->kind = RZ_NUM_KIND_UT64;
|
||||
out->val.n = rz_read_be64(padded);
|
||||
return;
|
||||
}
|
||||
RzNumBig *big = rz_big_new();
|
||||
RzNumBig *shift = rz_big_new();
|
||||
RzNumBig *byte = rz_big_new();
|
||||
if (!big || !shift || !byte) {
|
||||
rz_big_free(big);
|
||||
rz_big_free(shift);
|
||||
rz_big_free(byte);
|
||||
out->ok = false;
|
||||
return;
|
||||
}
|
||||
rz_big_from_int(big, 0);
|
||||
rz_big_from_int(shift, 256);
|
||||
for (ut32 i = 0; i < n; i++) {
|
||||
RzNumBig *tmp = rz_big_new();
|
||||
if (!tmp) {
|
||||
rz_big_free(big);
|
||||
rz_big_free(shift);
|
||||
rz_big_free(byte);
|
||||
out->ok = false;
|
||||
return;
|
||||
}
|
||||
rz_big_mul(tmp, big, shift);
|
||||
rz_big_from_int(byte, digest[i]);
|
||||
rz_big_add(big, tmp, byte);
|
||||
rz_big_free(tmp);
|
||||
}
|
||||
rz_big_free(shift);
|
||||
rz_big_free(byte);
|
||||
out->ok = true;
|
||||
out->kind = RZ_NUM_KIND_BIG;
|
||||
out->val.big = big;
|
||||
}
|
||||
|
||||
// Generic "hash bytes with the named algorithm" worker shared by all
|
||||
// the digest-family functions. argc must be 2 (addr, len).
|
||||
static void core_hash_named(RzCore *core, const char *algo,
|
||||
const RzNumValue *args, int argc, RzNumCallbackResult *out) {
|
||||
out->ok = false;
|
||||
if (!core || !core->hash || argc != 2) {
|
||||
return;
|
||||
}
|
||||
ut64 addr = args[0].val.n;
|
||||
ut64 len = args[1].val.n;
|
||||
ut8 *buf = core_read_block(core, addr, len);
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
RzHashSize digest_size = 0;
|
||||
ut8 *digest = rz_hash_cfg_calculate_small_block(core->hash, algo,
|
||||
buf, len, &digest_size);
|
||||
free(buf);
|
||||
if (!digest || digest_size == 0) {
|
||||
free(digest);
|
||||
return;
|
||||
}
|
||||
bytes_to_result(digest, digest_size, out);
|
||||
free(digest);
|
||||
}
|
||||
|
||||
// One thin trampoline per digest family. The algorithm name is the
|
||||
// fixed string each passes to core_hash_named().
|
||||
#define DIGEST_FN(fnname, algo) \
|
||||
static void fnname(void *user, const RzNumValue *args, int argc, RzNumCallbackResult *out) { \
|
||||
core_hash_named((RzCore *)user, algo, args, argc, out); \
|
||||
}
|
||||
|
||||
DIGEST_FN(core_fn_md5, "md5")
|
||||
DIGEST_FN(core_fn_sha1, "sha1")
|
||||
DIGEST_FN(core_fn_sha256, "sha256")
|
||||
DIGEST_FN(core_fn_sha384, "sha384")
|
||||
DIGEST_FN(core_fn_sha512, "sha512")
|
||||
DIGEST_FN(core_fn_crc32, "crc32")
|
||||
DIGEST_FN(core_fn_crc16, "crc16")
|
||||
DIGEST_FN(core_fn_crc8, "crc8smbus")
|
||||
DIGEST_FN(core_fn_crc64, "crc64")
|
||||
DIGEST_FN(core_fn_adler32, "adler32")
|
||||
DIGEST_FN(core_fn_xxhash, "xxhash32")
|
||||
|
||||
// crc(addr, len [, width]) -> CRC of the given width (8/16/32/64),
|
||||
// defaulting to CRC-32. The optional third argument is the
|
||||
// requested CRC width in bits; this is the "optional parameter for
|
||||
// the exact algorithm" rather than separate crc8/crc16/... names.
|
||||
static void core_fn_crc(void *user, const RzNumValue *args, int argc,
|
||||
RzNumCallbackResult *out) {
|
||||
out->ok = false;
|
||||
if (argc != 2 && argc != 3) {
|
||||
return;
|
||||
}
|
||||
const char *algo = "crc32";
|
||||
if (argc == 3) {
|
||||
switch (args[2].val.n) {
|
||||
case 8: algo = "crc8smbus"; break;
|
||||
case 16: algo = "crc16"; break;
|
||||
case 32: algo = "crc32"; break;
|
||||
case 64: algo = "crc64"; break;
|
||||
default: return; // unsupported width
|
||||
}
|
||||
}
|
||||
core_hash_named((RzCore *)user, algo, args, 2, out);
|
||||
}
|
||||
|
||||
// hash(addr, len) is an alias for sha256 - a sensible default digest.
|
||||
static void core_fn_hash(void *user, const RzNumValue *args, int argc,
|
||||
RzNumCallbackResult *out) {
|
||||
core_hash_named((RzCore *)user, "sha256", args, argc, out);
|
||||
}
|
||||
|
||||
// entropy(addr, len) and temperature(addr, len) return doubles.
|
||||
static void core_fn_entropy(void *user, const RzNumValue *args, int argc,
|
||||
RzNumCallbackResult *out) {
|
||||
RzCore *core = (RzCore *)user;
|
||||
out->ok = false;
|
||||
if (argc != 2) {
|
||||
return;
|
||||
}
|
||||
ut8 *buf = core_read_block(core, args[0].val.n, args[1].val.n);
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
double e = rz_hash_entropy(buf, args[1].val.n);
|
||||
free(buf);
|
||||
out->ok = true;
|
||||
out->kind = RZ_NUM_KIND_FLOAT;
|
||||
out->val.d = e;
|
||||
}
|
||||
|
||||
static void core_fn_temperature(void *user, const RzNumValue *args, int argc,
|
||||
RzNumCallbackResult *out) {
|
||||
RzCore *core = (RzCore *)user;
|
||||
out->ok = false;
|
||||
if (argc != 2) {
|
||||
return;
|
||||
}
|
||||
ut8 *buf = core_read_block(core, args[0].val.n, args[1].val.n);
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
double t = rz_hash_temperature(buf, args[1].val.n);
|
||||
free(buf);
|
||||
out->ok = true;
|
||||
out->kind = RZ_NUM_KIND_FLOAT;
|
||||
out->val.d = t;
|
||||
}
|
||||
|
||||
// Build the per-call function registry. Returns NULL if no functions
|
||||
// could be registered (the caller treats that as "no extra
|
||||
// functions", not as an error).
|
||||
static RzNumFuncRegistry *core_build_registry(RzCore *core) {
|
||||
RzNumFuncRegistry *reg = rz_num_func_registry_new();
|
||||
if (!reg) {
|
||||
return NULL;
|
||||
}
|
||||
// Digest families: <fn>(addr, len) -> digest as bignum/ut64.
|
||||
rz_num_func_registry_add(reg, "md5", 2, core_fn_md5, core);
|
||||
rz_num_func_registry_add(reg, "sha1", 2, core_fn_sha1, core);
|
||||
rz_num_func_registry_add(reg, "sha256", 2, core_fn_sha256, core);
|
||||
rz_num_func_registry_add(reg, "sha384", 2, core_fn_sha384, core);
|
||||
rz_num_func_registry_add(reg, "sha512", 2, core_fn_sha512, core);
|
||||
rz_num_func_registry_add(reg, "adler32", 2, core_fn_adler32, core);
|
||||
rz_num_func_registry_add(reg, "xxhash", 2, core_fn_xxhash, core);
|
||||
rz_num_func_registry_add(reg, "hash", 2, core_fn_hash, core);
|
||||
// CRC: unified crc(addr, len [, width]); the family aliases stay
|
||||
// available for callers who prefer the explicit name.
|
||||
rz_num_func_registry_add(reg, "crc", -1, core_fn_crc, core);
|
||||
rz_num_func_registry_add(reg, "crc8", 2, core_fn_crc8, core);
|
||||
rz_num_func_registry_add(reg, "crc16", 2, core_fn_crc16, core);
|
||||
rz_num_func_registry_add(reg, "crc32", 2, core_fn_crc32, core);
|
||||
rz_num_func_registry_add(reg, "crc64", 2, core_fn_crc64, core);
|
||||
// Statistical measures: <fn>(addr, len) -> float.
|
||||
rz_num_func_registry_add(reg, "entropy", 2, core_fn_entropy, core);
|
||||
rz_num_func_registry_add(reg, "temperature", 2, core_fn_temperature, core);
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Context-aware expression evaluator built on rz_num_math_value_ex().
|
||||
*
|
||||
* Resolves identifiers against the current core (flags, the special
|
||||
* variables $$, $$$, $b, $B, $F, ..., built-in and unicode-aliased
|
||||
* math functions) and evaluates bignum / float / bit-vector arithmetic.
|
||||
* The address-typed suffix (`0x1000:le32`) is read through core->io.
|
||||
*
|
||||
* \param core Core whose context (flags, offset, block size,
|
||||
* core->num callback) backs the evaluation.
|
||||
* \param expr Expression to evaluate.
|
||||
* \param options Optional options. NULL selects defaults.
|
||||
* \param out_value Out-parameter receiving the value; finalise it with
|
||||
* rz_num_value_fini() when done.
|
||||
* \param error_msg Optional out-pointer for a diagnostic.
|
||||
* \return true on success, false on parse or evaluation error.
|
||||
*
|
||||
* \note On failure the typed error category is left on out_value->err (an
|
||||
* RzNumError: RZ_NUM_ERR_PARSE, RZ_NUM_ERR_DIV_ZERO, RZ_NUM_ERR_OVERFLOW,
|
||||
* RZ_NUM_ERR_UNCOMPUTABLE for a refused/failed memory read or function,
|
||||
* ...), so callers that need more than the boolean can dispatch on it.
|
||||
*/
|
||||
RZ_API bool rz_core_math(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr,
|
||||
RZ_NULLABLE const RzCoreMathOptions *options,
|
||||
RZ_OUT RZ_NONNULL RzNumValue *out_value,
|
||||
RZ_OUT RZ_NULLABLE char **error_msg) {
|
||||
rz_return_val_if_fail(core && expr && out_value, false);
|
||||
|
||||
RzNumFuncRegistry *reg = core_build_registry(core);
|
||||
// `x = expr` bindings persist for the life of the core via a store
|
||||
// hung off core->num, created on first use, freed in rz_num_free().
|
||||
if (core->num && !core->num->expr_vars) {
|
||||
core->num->expr_vars = rz_num_value_store_new();
|
||||
}
|
||||
RzNumMathOptions opt = {
|
||||
.timeout_ms = options ? options->timeout_ms : 0,
|
||||
.funcs = reg,
|
||||
.io_read = core_io_read,
|
||||
.io_read_user = core,
|
||||
.vars = core->num ? core->num->expr_vars : NULL,
|
||||
};
|
||||
|
||||
bool ok = rz_num_math_value_ex(core->num, expr, &opt, out_value, error_msg);
|
||||
rz_num_func_registry_free(reg);
|
||||
|
||||
// Mirror the result into the legacy num->value / num->fvalue fields
|
||||
// for consumers that still read them directly. These are a
|
||||
// compatibility shim: the bool return (and \p out_value) is the
|
||||
// authoritative success/error signal. A failed evaluation leaves
|
||||
// both fields at 0 - as rz_num_math() did - which is
|
||||
// indistinguishable from a real 0 result, so callers that must tell
|
||||
// the two apart check the return value rather than these fields.
|
||||
// The two error flags RzNum carries are narrower than that return:
|
||||
// num->dbz is set only by a trapped divide-by-zero, and
|
||||
// num->nc.errors only by an identifier the callbacks could not
|
||||
// resolve. Neither covers a parse error or an overflow.
|
||||
if (core->num) {
|
||||
core->num->value = ok ? rz_num_value_to_ut64(out_value) : 0;
|
||||
core->num->fvalue = ok ? rz_num_value_to_double(out_value) : 0.0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Convenience wrapper around rz_core_math() returning a ut64.
|
||||
*
|
||||
* Deprecated: a failed evaluation returns 0, which "1-1" also returns, so
|
||||
* the caller cannot tell the two apart. The failure is logged rather than
|
||||
* dropped, but new code should call rz_core_math() and check its bool
|
||||
* return. Successful results follow rz_num_math_ut64(): FLOAT truncates,
|
||||
* BIG narrows to the low 64 bits.
|
||||
*
|
||||
* \param core Core backing the evaluation.
|
||||
* \param expr Expression to evaluate.
|
||||
* \return The ut64 projection of the result, or 0 on error.
|
||||
*/
|
||||
RZ_API RZ_DEPRECATE ut64 rz_core_math_ut64(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr) {
|
||||
rz_return_val_if_fail(core && expr, 0);
|
||||
RzNumValue v;
|
||||
rz_num_value_init(&v);
|
||||
char *err = NULL;
|
||||
if (!rz_core_math(core, expr, NULL, &v, &err)) {
|
||||
RZ_LOG_WARN("cannot evaluate '%s': %s\n", expr, err ? err : "evaluation failed");
|
||||
free(err);
|
||||
rz_num_value_fini(&v);
|
||||
return 0;
|
||||
}
|
||||
free(err);
|
||||
ut64 ret = rz_num_value_to_ut64(&v);
|
||||
rz_num_value_fini(&v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Lift an expression to its RzIL pure-expression form in core context.
|
||||
*
|
||||
* Thin wrapper over rz_il_lift_num() supplying the core's function
|
||||
* registry and IO-read callback, so any function call, typed-address
|
||||
* dereference, or host variable is grounded to the same concrete value
|
||||
* the % command would compute. The result is a typed RzILOpPure tree; a
|
||||
* caller wanting the pLf text form passes it to
|
||||
* rz_il_op_pure_stringify_unicode().
|
||||
*
|
||||
* \param core Core context.
|
||||
* \param expr Expression to lift.
|
||||
* \param error_msg Optional out-pointer for a diagnostic.
|
||||
* \return The lifted RzILOpPure, or NULL on error.
|
||||
*/
|
||||
RZ_API RZ_OWN RzILOpPure *rz_core_il_lift(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr,
|
||||
RZ_OUT RZ_NULLABLE char **error_msg) {
|
||||
rz_return_val_if_fail(core && expr, NULL);
|
||||
RzNumFuncRegistry *reg = core_build_registry(core);
|
||||
RzNumMathOptions opt = {
|
||||
.funcs = reg,
|
||||
.io_read = core_io_read,
|
||||
.io_read_user = core,
|
||||
};
|
||||
RzILOpPure *out = rz_il_lift_num(core->num, expr, &opt, error_msg);
|
||||
rz_num_func_registry_free(reg);
|
||||
return out;
|
||||
}
|
||||
+89
-26
@@ -106,42 +106,55 @@ RZ_IPI RzCmdStatus rz_list_rizin_vars_handler(RzCore *core, int argc, const char
|
||||
return RZ_CMD_STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief If \p pj is NULL then standard output will be displayed
|
||||
* If it's non-NULL then it's treated as a borrowed PJ and used and
|
||||
* printing will be done in JSON format and not standard one.
|
||||
* Hence the the third parameter decides in which format data will be displayed.
|
||||
*
|
||||
* \param core RzCore.
|
||||
* \param input Input mathematical expression.
|
||||
* \param pj Borrowed PJ if calculated value is to be printed in JSON format.
|
||||
* \return true on success, false otherwise.
|
||||
**/
|
||||
RZ_IPI bool rz_core_cmd_calculate_expr(RZ_NONNULL RzCore *core, RZ_NONNULL const char *input, RZ_BORROW PJ *pj) {
|
||||
rz_return_val_if_fail(core && input, false);
|
||||
// Render a non-ut64 result (float / bignum / bit-vector): JSON when pj is
|
||||
// set, otherwise the kind-aware pretty-printer.
|
||||
static void calc_expr_print_typed(RzCore *core, RZ_BORROW const RzNumValue *v, RZ_BORROW PJ *pj) {
|
||||
if (pj) {
|
||||
pj_o(pj);
|
||||
char *one = rz_num_value_tostring(v);
|
||||
if (one) {
|
||||
pj_ks(pj, "value", one);
|
||||
free(one);
|
||||
}
|
||||
const char *kind_name =
|
||||
v->kind == RZ_NUM_KIND_FLOAT ? "float" : v->kind == RZ_NUM_KIND_BIG ? "big"
|
||||
: v->kind == RZ_NUM_KIND_BITVECTOR ? "bitvector"
|
||||
: "none";
|
||||
pj_ks(pj, "kind", kind_name);
|
||||
pj_end(pj);
|
||||
return;
|
||||
}
|
||||
RzStrBuf *sb = rz_strbuf_new(NULL);
|
||||
if (!sb) {
|
||||
return;
|
||||
}
|
||||
RzNumPrintOptions opts = {
|
||||
.utf8 = rz_config_get_b(core->config, "scr.utf8")
|
||||
};
|
||||
rz_num_value_print_ex(v, &opts, sb);
|
||||
char *block = rz_strbuf_drain(sb);
|
||||
if (block) {
|
||||
rz_cons_print(block);
|
||||
free(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Render a ut64 result in the established multi-line / JSON layout, keeping
|
||||
// the per-line keys ("hex", "octal", "segment", ...) stable for downstream
|
||||
// scripts and JSON consumers.
|
||||
static void calc_expr_print_ut64(RzCore *core, ut64 n, RZ_BORROW PJ *pj) {
|
||||
char unit[8];
|
||||
char number[128], out[128] = RZ_EMPTY;
|
||||
|
||||
ut64 n = rz_num_math(core->num, input);
|
||||
if (core->num->dbz) {
|
||||
RZ_LOG_ERROR("core: RzNum ERROR: Division by Zero\n");
|
||||
core->num->dbz = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* decimal, hexa, octal */
|
||||
ut32 s, a;
|
||||
s = n >> 16 << 12;
|
||||
a = n & 0x0fff;
|
||||
rz_num_units(unit, sizeof(unit), n);
|
||||
|
||||
/* binary and floating point */
|
||||
double d;
|
||||
float f;
|
||||
rz_str_bits64(out, n);
|
||||
f = d = core->num->fvalue;
|
||||
/* adjust sign for nan floats, different libcs are confused */
|
||||
if (isnan(f) && signbit(f)) {
|
||||
f = -f;
|
||||
}
|
||||
@@ -166,10 +179,9 @@ RZ_IPI bool rz_core_cmd_calculate_expr(RZ_NONNULL RzCore *core, RZ_NONNULL const
|
||||
pj_ks(pj, "float", rz_strf(number, "%ff", f));
|
||||
pj_ks(pj, "double", rz_strf(number, "%lf", d));
|
||||
pj_ks(pj, "binary", rz_strf(number, "0b%s", out));
|
||||
/* ternary */
|
||||
rz_num_to_trits(out, n);
|
||||
pj_ks(pj, "trits", rz_strf(number, "0t%s", out));
|
||||
|
||||
pj_ks(pj, "kind", "ut64");
|
||||
pj_end(pj);
|
||||
} else {
|
||||
if (n >> 32) {
|
||||
@@ -192,11 +204,62 @@ RZ_IPI bool rz_core_cmd_calculate_expr(RZ_NONNULL RzCore *core, RZ_NONNULL const
|
||||
rz_cons_printf("float %ff\n", f);
|
||||
rz_cons_printf("double %lf\n", d);
|
||||
rz_cons_printf("binary 0b%s\n", out);
|
||||
/* ternary*/
|
||||
rz_num_to_trits(out, n);
|
||||
rz_cons_printf("trits 0t%s\n", out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief If \p pj is NULL then standard output will be displayed
|
||||
* If it's non-NULL then it's treated as a borrowed PJ and used and
|
||||
* printing will be done in JSON format and not standard one.
|
||||
* Hence the the third parameter decides in which format data will be displayed.
|
||||
*
|
||||
* \param core RzCore.
|
||||
* \param input Input mathematical expression.
|
||||
* \param pj Borrowed PJ if calculated value is to be printed in JSON format.
|
||||
* \return true on success, false otherwise.
|
||||
**/
|
||||
RZ_IPI bool rz_core_cmd_calculate_expr(RZ_NONNULL RzCore *core, RZ_NONNULL const char *input, RZ_BORROW PJ *pj) {
|
||||
rz_return_val_if_fail(core && input, false);
|
||||
|
||||
// Route through rz_core_math() so that the same evaluator that
|
||||
// rz-ax uses is shared with the shell, with full RzCore context
|
||||
// (flags, special variables, built-in functions, bignum / float
|
||||
// arithmetic). The legacy rz_num_math() path returned a saturating
|
||||
// ut64 only - this picks up everything the new evaluator can
|
||||
// produce.
|
||||
RzNumValue v;
|
||||
rz_num_value_init(&v);
|
||||
char *err = NULL;
|
||||
bool ok = rz_core_math(core, input, NULL, &v, &err);
|
||||
if (!ok) {
|
||||
// Surface the diagnostic. dbz is still detected by
|
||||
// rz_core_math() and reflected on core->num so the
|
||||
// existing post-call inspectors keep working.
|
||||
if (v.err == RZ_NUM_ERR_DIV_ZERO) {
|
||||
RZ_LOG_ERROR("core: RzNum ERROR: Division by Zero\n");
|
||||
core->num->dbz = 0;
|
||||
} else {
|
||||
RZ_LOG_ERROR("core: %s\n", err ? err : "evaluation failed");
|
||||
}
|
||||
free(err);
|
||||
rz_num_value_fini(&v);
|
||||
return false;
|
||||
}
|
||||
free(err);
|
||||
|
||||
// For non-UT64 results, delegate display to the kind-aware
|
||||
// pretty-printer; the legacy multi-line format below only
|
||||
// makes sense for ut64.
|
||||
if (v.kind != RZ_NUM_KIND_UT64) {
|
||||
calc_expr_print_typed(core, &v, pj);
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
}
|
||||
|
||||
calc_expr_print_ut64(core, v.val.n, pj);
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -733,9 +733,9 @@ static ut64 num_callback(RzNum *userptr, const char *str, int *ok) {
|
||||
break;
|
||||
}
|
||||
*ptr = 0;
|
||||
return rz_core_reg_getv_by_role_or_name(core, bptr);
|
||||
ut64 regval = rz_core_reg_getv_by_role_or_name(core, bptr);
|
||||
free(bptr);
|
||||
return 0; // UT64_MAX;
|
||||
return regval;
|
||||
} else {
|
||||
int rows;
|
||||
(void)rz_cons_get_size(&rows);
|
||||
|
||||
+3
-1
@@ -714,7 +714,9 @@ RZ_API RZ_OWN char *rz_core_print_disasm_strings(RZ_NONNULL RzCore *core, RzCore
|
||||
ut64 addr = UT64_MAX;
|
||||
const char *ox = strstr(line, "0x");
|
||||
if (ox) {
|
||||
addr = rz_num_get(NULL, ox);
|
||||
// Evaluate only the leading "0x...." address, not the rest of
|
||||
// the disasm line (nor a following ",0x..." operand).
|
||||
addr = rz_num_get_leading(NULL, ox, NULL);
|
||||
}
|
||||
const char *qo = strchr(line, '\"');
|
||||
const char *linecolor = NULL;
|
||||
|
||||
+18
-10
@@ -1117,16 +1117,20 @@ static void ds_build_op_str(RzDisasmState *ds, bool print_color) {
|
||||
if (ox) {
|
||||
char *e = strchr(ox, ']');
|
||||
if (e) {
|
||||
e = rz_str_dup(e);
|
||||
ut64 addr = rz_num_get(NULL, ox);
|
||||
// Evaluate only the address expression, not the
|
||||
// trailing ']' and whatever follows it.
|
||||
char *expr = rz_str_ndup(ox, e - ox);
|
||||
ut64 addr = expr ? rz_num_get(NULL, expr) : 0;
|
||||
free(expr);
|
||||
if (addr > ds->min_ref_addr) {
|
||||
RzFlagItem *fi = rz_flag_get_i(ds->core->flags, addr);
|
||||
if (fi) {
|
||||
char *rest = rz_str_dup(e);
|
||||
rz_str_cpy(ox, fi->name);
|
||||
rz_str_cat(ox, e);
|
||||
rz_str_cat(ox, rest);
|
||||
free(rest);
|
||||
}
|
||||
}
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5269,15 +5273,18 @@ static void ds_opstr_sub_jumps(RzDisasmState *ds) {
|
||||
ut64 numval;
|
||||
ptr = ds->opstr;
|
||||
while ((nptr = _find_next_number(ptr))) {
|
||||
ptr = nptr;
|
||||
numval = rz_num_get(NULL, ptr);
|
||||
// Evaluate only the leading number literal; nend marks its end.
|
||||
const char *nend;
|
||||
numval = rz_num_get_leading(NULL, nptr, &nend);
|
||||
if (nend == nptr) {
|
||||
// Not a literal after all; step past it to make progress.
|
||||
ptr = nptr + 1;
|
||||
continue;
|
||||
}
|
||||
if (numval == addr) {
|
||||
while (*nptr && !IS_SEPARATOR(*nptr) && *nptr != 0x1b) {
|
||||
nptr++;
|
||||
}
|
||||
char *kwname = rz_str_newf("%s%s", kw, name);
|
||||
if (kwname) {
|
||||
char *numstr = rz_str_ndup(ptr, nptr - ptr);
|
||||
char *numstr = rz_str_ndup(nptr, nend - nptr);
|
||||
if (numstr) {
|
||||
ds->opstr = rz_str_replace(ds->opstr, numstr, kwname, 0);
|
||||
free(numstr);
|
||||
@@ -5286,6 +5293,7 @@ static void ds_opstr_sub_jumps(RzDisasmState *ds) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
ptr = (char *)nend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ rz_core_sources = [
|
||||
'cio.c',
|
||||
'clang.c',
|
||||
'cmark.c',
|
||||
'cmath.c',
|
||||
'cmeta.c',
|
||||
'cmp.c',
|
||||
'core.c',
|
||||
|
||||
@@ -1687,6 +1687,11 @@ RzList /*<RzDebugMap *>*/ *linux_map_get(RzDebug *dbg) {
|
||||
continue;
|
||||
}
|
||||
strncpy(®ion2[2], pos_c + 1, sizeof(region2) - 2 - 1);
|
||||
// Terminate the start token at the '-'. rz_num_get() now parses
|
||||
// its whole argument strictly, so an un-split "0x<start>-<end>"
|
||||
// would evaluate as a subtraction - with the leading-zero <end>
|
||||
// read as C-style octal - instead of yielding the start address.
|
||||
*pos_c = 0;
|
||||
|
||||
if (!*name) {
|
||||
snprintf(name, sizeof(name), "unk%d", unk++);
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Lift an RzNum expression to a typed RzILOpPure.
|
||||
*
|
||||
* The tree-sitter parse, and the folding of non-structural constructs to
|
||||
* constants, live in librz_util (rz_num_expression_parse), which hands back
|
||||
* a self-contained RzNumExpression (no tree-sitter references). This file is
|
||||
* the second half: it walks that abstraction and builds the corresponding
|
||||
* RzILOpPure, so the caller gets a real op tree (which it can evaluate,
|
||||
* transform, or stringify) instead of pre-rendered text. The layering keeps
|
||||
* tree-sitter out of librz_il entirely.
|
||||
*/
|
||||
|
||||
#include <rz_il/rz_il_opcodes.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_num_expression.h>
|
||||
#include <rz_util/rz_bitvector.h>
|
||||
#include <rz_util/rz_float.h>
|
||||
|
||||
static RzILOpPure *expr_to_pure(const RzNumExpression *e);
|
||||
|
||||
// A float leaf: reinterpret the double through rz_float, then wrap its
|
||||
// binary64 bit pattern as the constant RzNum's FLOAT kind carries.
|
||||
static RzILOpPure *float_leaf(double d) {
|
||||
RzFloat *f = rz_float_new_from_f64(d);
|
||||
if (!f) {
|
||||
return NULL;
|
||||
}
|
||||
RzBitVector *bv = rz_bv_dup(f->s);
|
||||
rz_float_free(f);
|
||||
if (!bv) {
|
||||
return NULL;
|
||||
}
|
||||
RzILOpBitVector *bvop = rz_il_op_new_bitv(bv);
|
||||
if (!bvop) {
|
||||
rz_bv_free(bv);
|
||||
return NULL;
|
||||
}
|
||||
return rz_il_op_new_float(RZ_FLOAT_IEEE754_BIN_64, bvop);
|
||||
}
|
||||
|
||||
static RzILOpPure *binop_to_pure(const RzNumExpression *e) {
|
||||
RzILOpPure *x = expr_to_pure(e->operands[0]);
|
||||
RzILOpPure *y = expr_to_pure(e->operands[1]);
|
||||
if (!x || !y) {
|
||||
rz_il_op_pure_free(x);
|
||||
rz_il_op_pure_free(y);
|
||||
return NULL;
|
||||
}
|
||||
switch (e->op) {
|
||||
case RZ_NUM_EXPRESSION_OP_ADD: return rz_il_op_new_add(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_SUB: return rz_il_op_new_sub(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_MUL: return rz_il_op_new_mul(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_DIV: return rz_il_op_new_div(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_SDIV: return rz_il_op_new_sdiv(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_MOD: return rz_il_op_new_mod(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_SMOD: return rz_il_op_new_smod(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_LOGAND: return rz_il_op_new_log_and(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_LOGOR: return rz_il_op_new_log_or(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_LOGXOR: return rz_il_op_new_log_xor(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_SHL: return rz_il_op_new_shiftl(rz_il_op_new_b0(), x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_SHR: return rz_il_op_new_shiftr(rz_il_op_new_b0(), x, y);
|
||||
// Arithmetic shift right fills vacated bits with the sign bit of x;
|
||||
// x is duplicated so it serves both as the value and the fill source.
|
||||
case RZ_NUM_EXPRESSION_OP_SAR: return rz_il_op_new_shiftr(rz_il_op_new_msb(rz_il_op_pure_dup(x)), x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_EQ: return rz_il_op_new_eq(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_ULE: return rz_il_op_new_ule(x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_FADD: return rz_il_op_new_fadd(RZ_FLOAT_RMODE_RNE, x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_FSUB: return rz_il_op_new_fsub(RZ_FLOAT_RMODE_RNE, x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_FMUL: return rz_il_op_new_fmul(RZ_FLOAT_RMODE_RNE, x, y);
|
||||
case RZ_NUM_EXPRESSION_OP_FDIV: return rz_il_op_new_fdiv(RZ_FLOAT_RMODE_RNE, x, y);
|
||||
default:
|
||||
rz_warn_if_reached();
|
||||
rz_il_op_pure_free(x);
|
||||
rz_il_op_pure_free(y);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static RzILOpPure *unop_to_pure(const RzNumExpression *e) {
|
||||
RzILOpPure *x = expr_to_pure(e->operands[0]);
|
||||
if (!x) {
|
||||
return NULL;
|
||||
}
|
||||
switch (e->op) {
|
||||
case RZ_NUM_EXPRESSION_OP_NEG: return rz_il_op_new_neg(x);
|
||||
case RZ_NUM_EXPRESSION_OP_LOGNOT: return rz_il_op_new_log_not(x);
|
||||
case RZ_NUM_EXPRESSION_OP_IS_FZERO: return rz_il_op_new_is_fzero(x);
|
||||
default:
|
||||
rz_warn_if_reached();
|
||||
rz_il_op_pure_free(x);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static RzILOpPure *expr_to_pure(const RzNumExpression *e) {
|
||||
if (!e) {
|
||||
return NULL;
|
||||
}
|
||||
switch (e->kind) {
|
||||
case RZ_NUM_EXPRESSION_KIND_BV: {
|
||||
RzBitVector *bv = e->bv ? rz_bv_dup(e->bv) : NULL;
|
||||
if (!bv) {
|
||||
return NULL;
|
||||
}
|
||||
RzILOpPure *op = rz_il_op_new_bitv(bv);
|
||||
if (!op) {
|
||||
rz_bv_free(bv);
|
||||
}
|
||||
return op;
|
||||
}
|
||||
case RZ_NUM_EXPRESSION_KIND_FLOAT:
|
||||
return float_leaf(e->f);
|
||||
case RZ_NUM_EXPRESSION_KIND_BINOP:
|
||||
return binop_to_pure(e);
|
||||
case RZ_NUM_EXPRESSION_KIND_UNOP:
|
||||
return unop_to_pure(e);
|
||||
case RZ_NUM_EXPRESSION_KIND_ITE: {
|
||||
RzILOpPure *cond = expr_to_pure(e->operands[0]);
|
||||
RzILOpPure *t = expr_to_pure(e->operands[1]);
|
||||
RzILOpPure *f = expr_to_pure(e->operands[2]);
|
||||
if (!cond || !t || !f) {
|
||||
rz_il_op_pure_free(cond);
|
||||
rz_il_op_pure_free(t);
|
||||
rz_il_op_pure_free(f);
|
||||
return NULL;
|
||||
}
|
||||
return rz_il_op_new_ite(cond, t, f);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Build an RzILOpPure from a grounded RzNumExpression.
|
||||
*
|
||||
* \param expr The expression tree produced by rz_num_expression_parse().
|
||||
* \return The RzILOpPure, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzILOpPure *rz_il_op_pure_from_num_expression(RZ_NONNULL const RzNumExpression *expr) {
|
||||
rz_return_val_if_fail(expr, NULL);
|
||||
return expr_to_pure(expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse and lift an RzNum expression directly to an RzILOpPure.
|
||||
*
|
||||
* Convenience over rz_num_expression_parse() + the converter: the
|
||||
* intermediate RzNumExpression is built and freed internally.
|
||||
*
|
||||
* \param num Optional RzNum for resolving identifiers when a
|
||||
* sub-expression must be grounded.
|
||||
* \param expr The expression to lift.
|
||||
* \param opts Optional evaluation options used when grounding.
|
||||
* \param error_msg Optional out-pointer for a diagnostic on
|
||||
* failure.
|
||||
* \return The RzILOpPure, or NULL on a parse or lift error.
|
||||
*/
|
||||
RZ_API RZ_OWN RzILOpPure *rz_il_lift_num(RZ_NULLABLE RzNum *num,
|
||||
RZ_NONNULL const char *expr, RZ_NULLABLE const RzNumMathOptions *opts,
|
||||
RZ_NULLABLE char **error_msg) {
|
||||
rz_return_val_if_fail(expr, NULL);
|
||||
RzNumExpression *e = rz_num_expression_parse(num, expr, opts, error_msg);
|
||||
if (!e) {
|
||||
return NULL;
|
||||
}
|
||||
RzILOpPure *op = expr_to_pure(e);
|
||||
rz_num_expression_free(e);
|
||||
return op;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ rz_il_sources = [
|
||||
'il_export_string_unicode.c',
|
||||
'il_export_json.c',
|
||||
'il_graph.c',
|
||||
'il_num.c',
|
||||
'il_opcodes.c',
|
||||
'il_routines.c',
|
||||
'il_reg.c',
|
||||
|
||||
@@ -685,6 +685,25 @@ RZ_API RZ_OWN char *rz_core_addr_get_function_offset(RZ_NONNULL RzCore *core, ut
|
||||
RZ_API RZ_OWN char *rz_core_addr_get_flag_offset(RZ_NONNULL RzFlag *flags, ut64 addr);
|
||||
RZ_API RZ_OWN char *rz_core_addr_get_flag_offset_prompt(RZ_NONNULL RzFlag *flags, ut64 addr);
|
||||
|
||||
/* cmath.c */
|
||||
|
||||
/**
|
||||
* \brief Optional configuration for rz_core_math().
|
||||
*
|
||||
* Defaults: timeout_ms == 0 means "no timeout".
|
||||
*/
|
||||
typedef struct rz_core_math_options_t {
|
||||
ut64 timeout_ms; ///< wall-clock budget in ms, 0 = unlimited
|
||||
} RzCoreMathOptions;
|
||||
|
||||
RZ_API bool rz_core_math(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr,
|
||||
RZ_NULLABLE const RzCoreMathOptions *options,
|
||||
RZ_OUT RZ_NONNULL RzNumValue *out_value,
|
||||
RZ_OUT RZ_NULLABLE char **error_msg);
|
||||
RZ_API RZ_DEPRECATE ut64 rz_core_math_ut64(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr);
|
||||
RZ_API RZ_OWN RzILOpPure *rz_core_il_lift(RZ_NONNULL RzCore *core, RZ_NONNULL const char *expr,
|
||||
RZ_OUT RZ_NULLABLE char **error_msg);
|
||||
|
||||
/* chash.c */
|
||||
RZ_API RzCmdStatus rz_core_hash_plugins_print(RZ_NONNULL RZ_BORROW RzHash *hash, RZ_OUT RzCmdStateOutput *state);
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ RZ_API RZ_OWN char *rz_hash_cfg_randomart(RZ_NONNULL const ut8 *buffer, ut32 len
|
||||
RZ_API double rz_hash_ssdeep_compare(RZ_NONNULL const char *hash1, RZ_NONNULL const char *hash2);
|
||||
RZ_API RZ_OWN char *rz_hash_ssdeep(RZ_NONNULL const ut8 *input, size_t size);
|
||||
RZ_API ut32 rz_hash_xxhash(RZ_NONNULL const ut8 *input, size_t size);
|
||||
RZ_API double rz_hash_temperature(RZ_NONNULL const ut8 *data, ut64 len);
|
||||
RZ_API double rz_hash_entropy(RZ_NONNULL const ut8 *data, ut64 len);
|
||||
RZ_API double rz_hash_entropy_fraction(RZ_NONNULL const ut8 *data, ut64 len);
|
||||
RZ_API double rz_hash_chisquare(RZ_NONNULL const ut8 *data, ut64 len);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define RZ_IL_OPCODES_H
|
||||
|
||||
#include <rz_il/definitions/definitions.h>
|
||||
#include <rz_util/rz_num_expression.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -904,6 +905,12 @@ RZ_API void rz_il_op_effect_json(RZ_NONNULL RzILOpEffect *op, RZ_NONNULL PJ *pj)
|
||||
RZ_API bool rz_il_op_pure_stringify_unicode(RZ_NONNULL RZ_BORROW RzILStringifyCtx *ctx, RZ_NONNULL RZ_BORROW RzILOpPure *op, RZ_NONNULL RZ_BORROW RzStrBuf *sb);
|
||||
RZ_API bool rz_il_op_effect_stringify_unicode(RZ_NONNULL RZ_BORROW RzILStringifyCtx *ctx, RZ_NONNULL RZ_BORROW RzILOpEffect *op, RZ_NONNULL RZ_BORROW RzStrBuf *sb);
|
||||
|
||||
// Lift an RzNum expression (the self-contained IR that librz_util produces,
|
||||
// independent of tree-sitter) into a typed RzILOpPure. Defined in il_num.c.
|
||||
RZ_API RZ_OWN RzILOpPure *rz_il_op_pure_from_num_expression(RZ_NONNULL const RzNumExpression *expr);
|
||||
RZ_API RZ_OWN RzILOpPure *rz_il_lift_num(RZ_NULLABLE RzNum *num, RZ_NONNULL const char *expr,
|
||||
RZ_NULLABLE const RzNumMathOptions *opts, RZ_NULLABLE char **error_msg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -41,6 +41,7 @@ RZ_API void rz_big_from_int(RzNumBig *b, st64 v);
|
||||
RZ_API st64 rz_big_to_int(RzNumBig *b);
|
||||
RZ_API void rz_big_from_hexstr(RzNumBig *b, const char *str);
|
||||
RZ_API char *rz_big_to_hexstr(RzNumBig *b);
|
||||
RZ_API RZ_OWN char *rz_big_to_decstr(RZ_NONNULL RzNumBig *b);
|
||||
RZ_API void rz_big_assign(RzNumBig *dst, RzNumBig *src);
|
||||
|
||||
/* Basic arithmetic operations */
|
||||
@@ -64,8 +65,48 @@ RZ_API int rz_big_is_zero(RzNumBig *a); /* For comparison with zero */
|
||||
RZ_API void rz_big_inc(RzNumBig *a); /* Increment: add one to n */
|
||||
RZ_API void rz_big_dec(RzNumBig *a); /* Decrement: subtract one from n */
|
||||
RZ_API void rz_big_powm(RzNumBig *c, RzNumBig *a, RzNumBig *b, RzNumBig *m); /* Calculate a^b -- e.g. 2^10 => 1024 */
|
||||
RZ_API void rz_big_pow(RZ_NONNULL RzNumBig *c, RZ_NONNULL RzNumBig *a, RZ_NONNULL RzNumBig *b);
|
||||
RZ_API void rz_big_isqrt(RzNumBig *c, RzNumBig *a); /* Integer square root -- e.g. isqrt(5) => 2*/
|
||||
|
||||
/* Arbitrary-precision base-10 decimal. */
|
||||
|
||||
/**
|
||||
* \brief Arbitrary-precision decimal: value == mantissa * 10^(-scale).
|
||||
*
|
||||
* The sign lives in the mantissa and \p scale is always >= 0, so the
|
||||
* value 3.14 is { mantissa = 314, scale = 2 }. Addition, subtraction and
|
||||
* multiplication are exact; division is bounded to a caller-chosen number
|
||||
* of significant digits (a base-10 decimal cannot represent e.g. 1/3
|
||||
* exactly). This is the representation RzNum's value-returning evaluation
|
||||
* uses for decimals so that a result keeps full precision instead of
|
||||
* collapsing to a double.
|
||||
*/
|
||||
typedef struct rz_big_decimal_t {
|
||||
RzNumBig *mantissa; ///< signed integer significand
|
||||
ut32 scale; ///< number of base-10 fractional digits
|
||||
} RzBigDecimal;
|
||||
|
||||
/** \brief Significant digits kept when dividing (IEEE decimal128 width). */
|
||||
#define RZ_BIG_DECIMAL_DEFAULT_PREC 34
|
||||
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_new_from_str(RZ_NONNULL const char *str);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_new_from_int(st64 v);
|
||||
RZ_API void rz_big_decimal_free(RZ_NULLABLE RzBigDecimal *d);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_dup(RZ_NONNULL const RzBigDecimal *d);
|
||||
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_add(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_sub(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_mul(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_div(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b, ut32 precision);
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_neg(RZ_NONNULL const RzBigDecimal *a);
|
||||
|
||||
RZ_API int rz_big_decimal_cmp(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b);
|
||||
RZ_API bool rz_big_decimal_is_zero(RZ_NONNULL const RzBigDecimal *d);
|
||||
|
||||
RZ_API RZ_OWN char *rz_big_decimal_to_str(RZ_NONNULL const RzBigDecimal *d);
|
||||
RZ_API ut64 rz_big_decimal_to_ut64(RZ_NONNULL const RzBigDecimal *d);
|
||||
RZ_API double rz_big_decimal_to_double(RZ_NONNULL const RzBigDecimal *d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -113,6 +113,15 @@ static inline int rz_bits_leading_zeros(ut64 x) {
|
||||
* \param size number of bits to copy (needs to be <= 64)
|
||||
* \return a new 64-bit unsigned integer with the specified bit range replaced
|
||||
*/
|
||||
/**
|
||||
* \brief Get the number of significant bits of a 64-bit integer.
|
||||
* \param x the 64-bit integer
|
||||
* \return the position of the highest set bit plus one, 0 if \p x is 0
|
||||
*/
|
||||
static inline ut32 rz_bits_ut64_width(ut64 x) {
|
||||
return 64 - (ut32)rz_bits_leading_zeros(x);
|
||||
}
|
||||
|
||||
static inline ut64 rz_bits_copy_ut64(ut64 src, ut8 src_pos, ut64 dst, ut8 dst_pos, ut8 size) {
|
||||
if (size >= 64) {
|
||||
return src;
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
#define RZ_NUM_H
|
||||
|
||||
#include <rz_list.h>
|
||||
#include <rz_vector.h>
|
||||
#include <rz_util/rz_big.h>
|
||||
#include <rz_util/rz_bitvector.h>
|
||||
#include <rz_util/rz_strbuf.h>
|
||||
#include <rz_util/ht_sp.h>
|
||||
|
||||
#define RZ_NUMCALC_STRSZ 1024
|
||||
|
||||
@@ -9,6 +14,179 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Kind of value held by an RzNumValue.
|
||||
*
|
||||
* Most expressions evaluate to a simple ut64. The other kinds are produced
|
||||
* when the source expression makes the wider precision or representation
|
||||
* explicit - for example a literal that exceeds 64 bits, an explicit
|
||||
* fractional part, or a bit-vector with an explicit width.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_NUM_KIND_NONE = 0, ///< default / uninitialised
|
||||
RZ_NUM_KIND_UT64, ///< ordinary 64-bit unsigned integer
|
||||
RZ_NUM_KIND_BIG, ///< arbitrary-precision integer, see RzNumBig
|
||||
RZ_NUM_KIND_BITVECTOR, ///< fixed-width bit-vector, see RzBitVector
|
||||
RZ_NUM_KIND_FLOAT, ///< IEEE-754 float, currently double-precision
|
||||
RZ_NUM_KIND_BIGDECIMAL, ///< arbitrary-precision base-10 decimal, see RzBigDecimal
|
||||
} RzNumKind;
|
||||
|
||||
/**
|
||||
* \brief Reasons an evaluation can fail.
|
||||
*
|
||||
* Encoded out-of-band on RzNumValue.err so that callers can dispatch
|
||||
* on the error category without parsing a human-readable string. The
|
||||
* string form is still available via the optional error_msg out-
|
||||
* parameter on rz_num_math_value() for diagnostic output, but new
|
||||
* code should switch on .err.
|
||||
*
|
||||
* The categories are deliberately coarse: a single error code does
|
||||
* not pin down the exact source token, only the nature of the
|
||||
* problem. The text in error_msg carries that detail when it matters.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_NUM_ERR_OK = 0, ///< no error
|
||||
RZ_NUM_ERR_PARSE, ///< syntactic problem: unbalanced parens, unexpected token, ...
|
||||
RZ_NUM_ERR_EMPTY, ///< the input was empty or whitespace only
|
||||
RZ_NUM_ERR_RESERVED_WORD, ///< a reserved word was used as a bare variable
|
||||
RZ_NUM_ERR_UNDEFINED_VAR, ///< a special variable was not found in the host's table
|
||||
RZ_NUM_ERR_DIV_ZERO, ///< / or % with a zero divisor
|
||||
RZ_NUM_ERR_OVERFLOW, ///< a literal exceeded the supported precision
|
||||
RZ_NUM_ERR_TYPE_MISMATCH, ///< operands incompatible (e.g. rotate on a bignum)
|
||||
RZ_NUM_ERR_TIMEOUT, ///< evaluation exceeded the configured wall-clock budget
|
||||
RZ_NUM_ERR_UNCOMPUTABLE, ///< well-formed but mathematically undefined (log(<=0) etc.)
|
||||
RZ_NUM_ERR_NOT_IMPLEMENTED, ///< feature parsed but evaluator does not yet support it
|
||||
RZ_NUM_ERR_OUT_OF_MEMORY, ///< heap allocation failed
|
||||
RZ_NUM_ERR_DEPTH, ///< expression nesting exceeded the evaluator's recursion-depth limit
|
||||
} RzNumError;
|
||||
|
||||
/**
|
||||
* \brief Tagged-union value produced by the RzNum evaluator.
|
||||
*
|
||||
* A value with err != RZ_NUM_ERR_OK is still a well-formed RzNumValue, so a
|
||||
* caller can always release it with rz_num_value_fini(); on error the payload
|
||||
* is typically RZ_NUM_KIND_UT64 / 0, but consumers must not rely on that.
|
||||
*/
|
||||
typedef struct rz_num_value_t {
|
||||
RzNumKind kind;
|
||||
RzNumError err;
|
||||
union {
|
||||
ut64 n; ///< valid when kind == RZ_NUM_KIND_UT64
|
||||
double d; ///< valid when kind == RZ_NUM_KIND_FLOAT
|
||||
RzNumBig *big; ///< valid when kind == RZ_NUM_KIND_BIG
|
||||
RzBitVector *bv; ///< valid when kind == RZ_NUM_KIND_BITVECTOR
|
||||
RzBigDecimal *bigdec; ///< valid when kind == RZ_NUM_KIND_BIGDECIMAL
|
||||
} val;
|
||||
} RzNumValue;
|
||||
|
||||
/**
|
||||
* \brief Initialise an RzNumValue to RZ_NUM_KIND_NONE / RZ_NUM_ERR_OK.
|
||||
*/
|
||||
static inline void rz_num_value_init(RzNumValue *v) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
v->kind = RZ_NUM_KIND_NONE;
|
||||
v->err = RZ_NUM_ERR_OK;
|
||||
v->val.n = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Project an RzNumValue to a ut64.
|
||||
*
|
||||
* UT64 values pass through unchanged. FLOAT truncates toward zero.
|
||||
* BIG truncates to the low 64 bits (matching a C narrowing cast);
|
||||
* the full value remains accessible on the RzNumValue itself.
|
||||
* NONE / BITVECTOR project to 0.
|
||||
*
|
||||
* \param v The value to project.
|
||||
* \return The ut64 projection.
|
||||
*/
|
||||
static inline ut64 rz_num_value_to_ut64(const RzNumValue *v) {
|
||||
if (!v) {
|
||||
rz_warn_if_reached();
|
||||
return 0;
|
||||
}
|
||||
switch (v->kind) {
|
||||
case RZ_NUM_KIND_UT64:
|
||||
return v->val.n;
|
||||
case RZ_NUM_KIND_FLOAT:
|
||||
return (ut64)v->val.d;
|
||||
case RZ_NUM_KIND_BIG:
|
||||
return (ut64)rz_big_to_int(v->val.big);
|
||||
case RZ_NUM_KIND_BITVECTOR:
|
||||
return v->val.bv ? rz_bv_to_ut64(v->val.bv) : 0;
|
||||
case RZ_NUM_KIND_BIGDECIMAL:
|
||||
return v->val.bigdec ? rz_big_decimal_to_ut64(v->val.bigdec) : 0;
|
||||
default:
|
||||
rz_warn_if_reached();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Project an RzNumValue to a double.
|
||||
*
|
||||
* Mirrors the floating value that the legacy rz_num_math() kept in
|
||||
* num->fvalue. A UT64 is read as a *signed* st64 so the projection
|
||||
* matches the signed int32/int64 line of the `%` breakdown (e.g.
|
||||
* 0xffffffffffffffff projects to -1.0, not ~1.8e19). FLOAT passes
|
||||
* through; BIG and BITVECTOR project their value (BIG via the same
|
||||
* low-64-bit narrowing as rz_num_value_to_ut64()).
|
||||
*
|
||||
* \param v The value to project.
|
||||
* \return The double projection.
|
||||
*/
|
||||
static inline double rz_num_value_to_double(const RzNumValue *v) {
|
||||
if (!v) {
|
||||
rz_warn_if_reached();
|
||||
return 0.0;
|
||||
}
|
||||
switch (v->kind) {
|
||||
case RZ_NUM_KIND_UT64:
|
||||
return (double)(st64)v->val.n;
|
||||
case RZ_NUM_KIND_FLOAT:
|
||||
return v->val.d;
|
||||
case RZ_NUM_KIND_BIG:
|
||||
return v->val.big ? (double)rz_big_to_int(v->val.big) : 0.0;
|
||||
case RZ_NUM_KIND_BITVECTOR:
|
||||
return v->val.bv ? (double)rz_bv_to_ut64(v->val.bv) : 0.0;
|
||||
case RZ_NUM_KIND_BIGDECIMAL:
|
||||
return v->val.bigdec ? rz_big_decimal_to_double(v->val.bigdec) : 0.0;
|
||||
default:
|
||||
rz_warn_if_reached();
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Release any payload held by an RzNumValue.
|
||||
*
|
||||
* Safe to call on an RZ_NUM_KIND_NONE / RZ_NUM_KIND_UT64 / RZ_NUM_KIND_FLOAT
|
||||
* value. After the call the value is reset to RZ_NUM_KIND_NONE.
|
||||
*/
|
||||
static inline void rz_num_value_fini(RzNumValue *v) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
switch (v->kind) {
|
||||
case RZ_NUM_KIND_BIG:
|
||||
rz_big_free(v->val.big);
|
||||
break;
|
||||
case RZ_NUM_KIND_BITVECTOR:
|
||||
rz_bv_free(v->val.bv);
|
||||
break;
|
||||
case RZ_NUM_KIND_BIGDECIMAL:
|
||||
rz_big_decimal_free(v->val.bigdec);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
v->kind = RZ_NUM_KIND_NONE;
|
||||
v->err = RZ_NUM_ERR_OK;
|
||||
v->val.n = 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
double d;
|
||||
ut64 n;
|
||||
@@ -65,6 +243,7 @@ typedef struct rz_num_t {
|
||||
void *userptr;
|
||||
int dbz; /// division by zero happened
|
||||
RzNumCalc nc;
|
||||
RZ_NULLABLE HtSP *expr_vars; ///< persistent RzNum expression variables.
|
||||
} RzNum;
|
||||
|
||||
typedef ut64 (*RzNumCallback)(struct rz_num_t *self, const char *str, int *ok);
|
||||
@@ -74,8 +253,7 @@ RZ_API RzNum *rz_num_new(RzNumCallback cb, RzNumCallback2 cb2, void *ptr);
|
||||
RZ_API void rz_num_free(RzNum *num);
|
||||
RZ_API char *rz_num_units(char *buf, size_t len, ut64 number);
|
||||
RZ_API int rz_num_conditional(RzNum *num, const char *str);
|
||||
RZ_API ut64 rz_num_calc(RzNum *num, const char *str, const char **err);
|
||||
RZ_API const char *rz_num_calc_index(RzNum *num, const char *p);
|
||||
RZ_API const char *rz_num_calc_index(RZ_NULLABLE RzNum *num, RZ_NULLABLE const char *p);
|
||||
RZ_API int rz_num_is_valid_input(RzNum *num, const char *input_value);
|
||||
RZ_API ut64 rz_num_get_input_value(RzNum *num, const char *input_value);
|
||||
RZ_API char *rz_num_as_string(RzNum *___, ut64 n, bool printable_only);
|
||||
@@ -83,8 +261,164 @@ RZ_API ut64 rz_num_tail(RzNum *num, ut64 addr, const char *hex);
|
||||
RZ_API ut64 rz_num_tail_base(RzNum *num, ut64 addr, ut64 off);
|
||||
RZ_API void rz_num_minmax_swap(ut64 *a, ut64 *b);
|
||||
RZ_API void rz_num_minmax_swap_i(int *a, int *b); // XXX this can be a cpp macro :??
|
||||
RZ_API ut64 rz_num_math(RzNum *num, const char *str);
|
||||
RZ_API ut64 rz_num_math_ut64(RzNum *num, const char *str);
|
||||
|
||||
/**
|
||||
* \brief Backwards-compatible wrapper around rz_num_math_ut64().
|
||||
*
|
||||
* \deprecated Legacy name kept for the existing call sites. New code
|
||||
* should call rz_num_math_ut64() for a ut64 result, or
|
||||
* rz_num_math_value() when it needs to tell a genuine parse/evaluation
|
||||
* error apart from a result that happens to be 0.
|
||||
*/
|
||||
RZ_DEPRECATE static inline ut64 rz_num_math(RzNum *num, const char *str) {
|
||||
return rz_num_math_ut64(num, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Result of a custom function or IO-read callback.
|
||||
*
|
||||
* A deliberately narrower sibling of RzNumValue: this is the ABI that
|
||||
* user-registered callbacks fill in, so it carries a plain \p ok flag
|
||||
* instead of RzNumValue's RzNumError envelope (the evaluator maps a
|
||||
* failed callback to RZ_NUM_ERR_UNCOMPUTABLE with the optional message)
|
||||
* and omits the internal-only BIGDECIMAL kind. Keeping it separate means
|
||||
* the public callback contract stays decoupled from the evaluator's error
|
||||
* taxonomy and internal kinds.
|
||||
*/
|
||||
typedef struct rz_num_callback_result_t {
|
||||
bool ok; ///< false => evaluation error
|
||||
RzNumKind kind; ///< kind of the value on success
|
||||
union {
|
||||
ut64 n;
|
||||
double d;
|
||||
RZ_OWN RzNumBig *big;
|
||||
RZ_OWN RzBitVector *bv;
|
||||
} val;
|
||||
} RzNumCallbackResult;
|
||||
|
||||
/**
|
||||
* \brief Signature of a user-registered expression function.
|
||||
*
|
||||
* \param user The opaque pointer supplied at registration time.
|
||||
* \param args Evaluated argument values (read-only).
|
||||
* \param argc Number of arguments actually supplied.
|
||||
* \param out Out-parameter for the result.
|
||||
*/
|
||||
typedef void (*RzNumFuncCallback)(void *user, const RzNumValue *args,
|
||||
int argc, RzNumCallbackResult *out);
|
||||
|
||||
/**
|
||||
* \brief Callback that reads raw bytes from the host's memory layer
|
||||
* for an address-typed dereference.
|
||||
*
|
||||
* Invoked for `<addr>:le32`, `<addr>:be64`, `<addr>:s32`, `<addr>:f64`,
|
||||
* `<addr>:128`, `<addr>:f128`, etc. The evaluator decodes the bytes
|
||||
* into the appropriate kind itself (endian swap, sign-extension for
|
||||
* `:sN`, IEEE-754 decode for `:fN`, two-qword assembly for `:128` /
|
||||
* `:f128`), so the callback simply hands back the requested bytes.
|
||||
*
|
||||
* The signature is intentionally identical to RzPfReadAtCb in
|
||||
* <rz_pf.h>, so a single host-side function can serve both
|
||||
* subsystems and the core's IO bridge is not duplicated.
|
||||
*
|
||||
* If no callback is registered the evaluator returns the literal
|
||||
* address unchanged (the legacy parser's behaviour).
|
||||
*
|
||||
* \param user The opaque pointer supplied alongside the callback.
|
||||
* \param addr The address to read from.
|
||||
* \param buf Destination buffer of at least \p len bytes.
|
||||
* \param len Number of bytes to read.
|
||||
* \return The number of bytes actually written into \p buf. A
|
||||
* return < \p len signals a short read; the evaluator
|
||||
* treats it as RZ_NUM_ERR_UNCOMPUTABLE.
|
||||
*/
|
||||
typedef ut64 (*RzNumIOReadCallback)(void *user, ut64 addr, ut8 *buf, size_t len);
|
||||
|
||||
/**
|
||||
* \brief A named, registrable expression function.
|
||||
*
|
||||
* Held in an RzNumFuncRegistry. Arity -1 means variadic.
|
||||
*/
|
||||
typedef struct rz_num_func_entry_t {
|
||||
char *name; ///< UTF-8 name
|
||||
int arity; ///< exact arg count, or -1 for variadic
|
||||
RzNumFuncCallback fn; ///< dispatcher
|
||||
void *user; ///< opaque, passed to fn
|
||||
} RzNumFuncEntry;
|
||||
|
||||
/**
|
||||
* \brief A set of user-registered expression functions.
|
||||
*
|
||||
* Created with rz_num_func_registry_new(), populated with
|
||||
* rz_num_func_registry_add(), and passed to the evaluator through
|
||||
* RzNumMathOptions.funcs. The evaluator consults the registry
|
||||
* *before* its own built-in table, so a registered function can
|
||||
* shadow a built-in of the same name.
|
||||
*/
|
||||
typedef struct rz_num_func_registry_t {
|
||||
RzPVector /*<RzNumFuncEntry *>*/ entries;
|
||||
} RzNumFuncRegistry;
|
||||
|
||||
RZ_API RZ_OWN RzNumFuncRegistry *rz_num_func_registry_new(void);
|
||||
|
||||
RZ_API void rz_num_func_registry_free(RZ_NULLABLE RzNumFuncRegistry *reg);
|
||||
|
||||
RZ_API bool rz_num_func_registry_add(RZ_NONNULL RzNumFuncRegistry *reg,
|
||||
RZ_NONNULL const char *name, int arity,
|
||||
RZ_NONNULL RzNumFuncCallback fn, RZ_NULLABLE void *user);
|
||||
|
||||
/**
|
||||
* \brief Optional configuration for rz_num_math_value_ex().
|
||||
*
|
||||
* All fields default to zero/NULL, which means "no limit / standard
|
||||
* behaviour". A non-zero timeout_ms is checked at each node of the
|
||||
* evaluator's tree walk; if exceeded, RZ_NUM_ERR_TIMEOUT is
|
||||
* returned. funcs, if set, supplies user-registered functions that
|
||||
* take precedence over the built-ins. io_read, if set, backs the
|
||||
* typed-address dereference syntax. vars, if set, is a
|
||||
* variable store (string name -> RzNumValue*) that PERSISTS across
|
||||
* calls: bindings made by `x = expr` / `let x = expr` are written
|
||||
* into it and remain visible to later evaluations sharing the same
|
||||
* store. When NULL, bindings are confined to a single evaluation.
|
||||
*/
|
||||
typedef struct rz_num_math_options_t {
|
||||
ut64 timeout_ms; ///< wall-clock budget, 0 = unlimited
|
||||
RZ_NULLABLE RzNumFuncRegistry *funcs; ///< extra functions
|
||||
RZ_NULLABLE RzNumIOReadCallback io_read; ///< typed-address reader
|
||||
void *io_read_user; ///< opaque passed to io_read
|
||||
RZ_NULLABLE HtSP *vars; ///< persistent variable store
|
||||
} RzNumMathOptions;
|
||||
|
||||
RZ_API bool rz_num_math_value(RZ_NULLABLE RzNum *num, RZ_NONNULL const char *expr,
|
||||
RZ_OUT RZ_NONNULL RzNumValue *out_value, RZ_OUT RZ_NULLABLE char **error_msg);
|
||||
|
||||
RZ_API bool rz_num_math_value_ex(RZ_NULLABLE RzNum *num, RZ_NONNULL const char *expr,
|
||||
RZ_NULLABLE const RzNumMathOptions *options,
|
||||
RZ_OUT RZ_NONNULL RzNumValue *out_value, RZ_OUT RZ_NULLABLE char **error_msg);
|
||||
|
||||
RZ_API RZ_OWN HtSP *rz_num_value_store_new(void);
|
||||
|
||||
RZ_API void rz_num_value_store_free(RZ_NULLABLE HtSP *store);
|
||||
|
||||
/**
|
||||
* \brief Options controlling how an RzNumValue is rendered.
|
||||
*/
|
||||
typedef struct rz_num_print_options_t {
|
||||
bool utf8; ///< when true, use Unicode glyphs where applicable
|
||||
} RzNumPrintOptions;
|
||||
|
||||
RZ_API void rz_num_value_print(RZ_NONNULL const RzNumValue *v, RZ_NONNULL RzStrBuf *sb);
|
||||
|
||||
RZ_API void rz_num_value_print_ex(RZ_NONNULL const RzNumValue *v,
|
||||
RZ_NULLABLE const RzNumPrintOptions *opts, RZ_NONNULL RzStrBuf *sb);
|
||||
|
||||
RZ_API RZ_OWN char *rz_num_value_tostring(RZ_NONNULL const RzNumValue *v);
|
||||
|
||||
RZ_API const char *rz_num_error_name(RzNumError err);
|
||||
|
||||
RZ_API ut64 rz_num_get(RZ_NULLABLE RzNum *num, RZ_NULLABLE const char *str);
|
||||
RZ_API ut64 rz_num_get_leading(RZ_NULLABLE RzNum *num, RZ_NONNULL const char *str, RZ_NULLABLE const char **endptr);
|
||||
RZ_API int rz_num_to_bits(char *out, ut64 num);
|
||||
RZ_API int rz_num_to_trits(char *out, ut64 num); // Rename this please
|
||||
RZ_API ut32 rz_num_rand32(ut32 max);
|
||||
@@ -101,6 +435,21 @@ RZ_API size_t rz_num_base_of_string(RzNum *num, RZ_NONNULL const char *str);
|
||||
RZ_API double rz_num_get_float(RzNum *num, const char *str);
|
||||
RZ_API bool rz_num_is_hex_prefix(const char *p);
|
||||
|
||||
/**
|
||||
* \brief Base prefix of a numeric literal, as returned by rz_num_base_prefix().
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_NUM_BASE_PREFIX_NONE = 0, ///< no prefix; plain decimal
|
||||
RZ_NUM_BASE_PREFIX_HEX, ///< 0x / 0X
|
||||
RZ_NUM_BASE_PREFIX_BINARY, ///< 0b / 0B
|
||||
RZ_NUM_BASE_PREFIX_OCTAL, ///< 0o / 0O
|
||||
RZ_NUM_BASE_PREFIX_TERNARY, ///< 0t / 0T
|
||||
RZ_NUM_BASE_PREFIX_OCTAL_C, ///< C style leading zero, e.g. 034
|
||||
} RzNumBasePrefix;
|
||||
|
||||
RZ_API RzNumBasePrefix rz_num_base_prefix(RZ_NONNULL const char *p, RZ_NULLABLE RZ_OUT ut32 *base,
|
||||
RZ_NULLABLE RZ_OUT size_t *prefix_len);
|
||||
|
||||
/**
|
||||
* \brief Absolute value of a 64-bit number. Store result in `ut64`
|
||||
* \return unsigned 64-bit number
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Self-contained arithmetic expression tree: the parser/RzIL boundary.
|
||||
*
|
||||
* librz_util builds an RzNumExpression from a textual expression
|
||||
* (rz_num_expression_parse) and librz_il lifts it to an RzILOpPure
|
||||
* without ever seeing tree-sitter. It lives in its own header rather
|
||||
* than the rz_num.h umbrella so ordinary numeric-parsing consumers of
|
||||
* rz_num.h do not pull in the IL-bridge types.
|
||||
*/
|
||||
|
||||
#ifndef RZ_NUM_EXPRESSION_H
|
||||
#define RZ_NUM_EXPRESSION_H
|
||||
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_bitvector.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Kind of an RzNumExpression node.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_NUM_EXPRESSION_KIND_BV, ///< grounded bit-vector constant (\p bv)
|
||||
RZ_NUM_EXPRESSION_KIND_FLOAT, ///< IEEE-754 double constant (\p f)
|
||||
RZ_NUM_EXPRESSION_KIND_BINOP, ///< binary op \p op over operands[0], operands[1]
|
||||
RZ_NUM_EXPRESSION_KIND_UNOP, ///< unary op \p op over operands[0]
|
||||
RZ_NUM_EXPRESSION_KIND_ITE, ///< operands[0] ? operands[1] : operands[2]
|
||||
} RzNumExpressionKind;
|
||||
|
||||
/**
|
||||
* \brief Operator of an RzNumExpression BINOP / UNOP node.
|
||||
*
|
||||
* The float operators (FADD..FDIV, IS_FZERO) appear only over float
|
||||
* sub-expressions; the rest are bit-vector operators. The names mirror
|
||||
* the RzIL pure ops they lift to, but the enum is deliberately
|
||||
* independent of librz_il so the type can live in librz_util.
|
||||
*/
|
||||
typedef enum {
|
||||
RZ_NUM_EXPRESSION_OP_ADD,
|
||||
RZ_NUM_EXPRESSION_OP_SUB,
|
||||
RZ_NUM_EXPRESSION_OP_MUL,
|
||||
RZ_NUM_EXPRESSION_OP_DIV, ///< unsigned division
|
||||
RZ_NUM_EXPRESSION_OP_SDIV, ///< signed division
|
||||
RZ_NUM_EXPRESSION_OP_MOD, ///< unsigned remainder
|
||||
RZ_NUM_EXPRESSION_OP_SMOD, ///< signed remainder
|
||||
RZ_NUM_EXPRESSION_OP_LOGAND,
|
||||
RZ_NUM_EXPRESSION_OP_LOGOR,
|
||||
RZ_NUM_EXPRESSION_OP_LOGXOR,
|
||||
RZ_NUM_EXPRESSION_OP_SHL, ///< logical shift left
|
||||
RZ_NUM_EXPRESSION_OP_SHR, ///< logical shift right
|
||||
RZ_NUM_EXPRESSION_OP_SAR, ///< arithmetic (sign-extending) shift right
|
||||
RZ_NUM_EXPRESSION_OP_EQ,
|
||||
RZ_NUM_EXPRESSION_OP_ULE, ///< unsigned less-or-equal
|
||||
RZ_NUM_EXPRESSION_OP_FADD,
|
||||
RZ_NUM_EXPRESSION_OP_FSUB,
|
||||
RZ_NUM_EXPRESSION_OP_FMUL,
|
||||
RZ_NUM_EXPRESSION_OP_FDIV,
|
||||
RZ_NUM_EXPRESSION_OP_NEG, ///< two's-complement negation
|
||||
RZ_NUM_EXPRESSION_OP_LOGNOT, ///< bitwise not
|
||||
RZ_NUM_EXPRESSION_OP_IS_FZERO, ///< float "is zero" predicate
|
||||
} RzNumExpressionOp;
|
||||
|
||||
/**
|
||||
* \brief A self-contained arithmetic expression tree.
|
||||
*
|
||||
* Built by rz_num_expression_parse() from a textual expression, it holds no
|
||||
* reference to the tree-sitter parse it was built from, so a higher layer
|
||||
* (librz_il) can walk it to build a typed RzILOpPure without depending on the
|
||||
* parser. Anything with no node kind of its own - function calls,
|
||||
* typed-address reads, host variables, exponent / logarithm, ';' sequences -
|
||||
* is evaluated to a concrete value while building and stored as a constant
|
||||
* leaf.
|
||||
*/
|
||||
typedef struct rz_num_expression_t RzNumExpression;
|
||||
|
||||
/**
|
||||
* \brief Fields of an RzNumExpression; which are valid depends on \ref RzNumExpressionKind.
|
||||
*/
|
||||
struct rz_num_expression_t {
|
||||
RzNumExpressionKind kind;
|
||||
RzNumExpressionOp op; ///< valid for BINOP / UNOP
|
||||
RzBitVector *bv; ///< valid for BV
|
||||
double f; ///< valid for FLOAT
|
||||
RZ_OWN RzNumExpression *operands[3]; ///< children; layout per \ref RzNumExpressionKind
|
||||
};
|
||||
|
||||
RZ_API RZ_OWN RzNumExpression *rz_num_expression_parse(RZ_NULLABLE RzNum *num,
|
||||
RZ_NONNULL const char *expr, RZ_NULLABLE const RzNumMathOptions *opts,
|
||||
RZ_NULLABLE char **error_msg);
|
||||
|
||||
RZ_API void rz_num_expression_free(RZ_NULLABLE RzNumExpression *e);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RZ_NUM_EXPRESSION_H
|
||||
+460
-138
@@ -38,12 +38,51 @@
|
||||
#define STDIN_BUFFER_SIZE 354096
|
||||
static int rax(RzNum *num, char *str, int len, int last, ut64 *flags, int *fm);
|
||||
|
||||
// Flags whose operand is the raw stdin buffer, newlines included, rather
|
||||
// than one expression per line.
|
||||
#define RZ_AX_RAW_STDIN_FLAGS \
|
||||
(RZ_AX_FLAG_RAW_TO_HEX | RZ_AX_FLAG_BASE64_ENCODE | RZ_AX_FLAG_BASE64_DECODE | \
|
||||
RZ_AX_FLAG_STR_TO_DJB2 | RZ_AX_FLAG_DUMP_C_BYTES | RZ_AX_FLAG_STR_TO_BIN | \
|
||||
RZ_AX_FLAG_RAW_TO_LANGBYTES)
|
||||
|
||||
static int rax(RzNum *num, char *str, int len, int last, ut64 *flags, int *fm);
|
||||
|
||||
// Hand one stdin read to rax(). Conversions take one expression per line,
|
||||
// so the chunk is split and the newlines dropped: a trailing newline would
|
||||
// otherwise make `0xff` a compound expression rather than a bare literal,
|
||||
// and print it in the wrong base.
|
||||
static bool stdin_chunk(RzNum *num, char *buf, int len, ut64 *flags, int *fm, bool *quit) {
|
||||
if (*flags & RZ_AX_RAW_STDIN_FLAGS) {
|
||||
return rax(num, buf, len, 0, flags, fm);
|
||||
}
|
||||
char *line = buf;
|
||||
while (line < buf + len) {
|
||||
char *nl = strchr(line, '\n');
|
||||
if (nl) {
|
||||
*nl = '\0';
|
||||
}
|
||||
if (!strcmp(line, "q")) {
|
||||
*quit = true;
|
||||
return false;
|
||||
}
|
||||
if (*line && !rax(num, line, strlen(line), 0, flags, fm)) {
|
||||
return false;
|
||||
}
|
||||
if (!nl) {
|
||||
break;
|
||||
}
|
||||
line = nl + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int use_stdin(RzNum *num, ut64 *flags, int *fm) {
|
||||
if (!flags) {
|
||||
return 0;
|
||||
}
|
||||
char *buf = calloc(1, STDIN_BUFFER_SIZE + 1);
|
||||
int l;
|
||||
bool quit = false;
|
||||
if (!buf) {
|
||||
return 0;
|
||||
}
|
||||
@@ -59,10 +98,18 @@ static int use_stdin(RzNum *num, ut64 *flags, int *fm) {
|
||||
l--;
|
||||
continue;
|
||||
}
|
||||
buf[n] = 0;
|
||||
buf[l] = 0;
|
||||
// if (sflag && strlen (buf) < STDIN_BUFFER_SIZE) // -S
|
||||
buf[STDIN_BUFFER_SIZE] = '\0';
|
||||
if (!rax(num, buf, l, 0, flags, fm)) {
|
||||
// `q` on its own line ends the session. Only stdin has one, so
|
||||
// on the command line `q` stays an ordinary (unresolvable) name.
|
||||
// The buffer itself must not be touched: the raw-input flags
|
||||
// (-S, -E) hash or encode it verbatim, newline included.
|
||||
if (!strcmp(buf, "q") || !strcmp(buf, "q\n")) {
|
||||
quit = true;
|
||||
break;
|
||||
}
|
||||
if (!stdin_chunk(num, buf, l, flags, fm, &quit)) {
|
||||
break;
|
||||
}
|
||||
l = -1;
|
||||
@@ -70,25 +117,121 @@ static int use_stdin(RzNum *num, ut64 *flags, int *fm) {
|
||||
} else {
|
||||
l = 1;
|
||||
}
|
||||
if (l > 0) {
|
||||
rax(num, buf, l, 0, flags, fm);
|
||||
if (l > 0 && !quit) {
|
||||
stdin_chunk(num, buf, l, flags, fm, &quit);
|
||||
}
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Sentinel force-mode meaning a preceding `=N` requested an unsupported base;
|
||||
// the expression that follows must not be printed (the error was reported).
|
||||
#define RZ_AX_INVALID_BASE (-1)
|
||||
|
||||
// Evaluate \p s, reporting any diagnostic on stderr. An identifier the
|
||||
// evaluator cannot resolve folds to 0 but raises num->nc.errors; rz-ax has no
|
||||
// flag or register context of its own, so a raised flag means the input was
|
||||
// not a number rather than a lookup miss, and is reported as such.
|
||||
// True when \p c is a valid digit for \p base (2, 3, 8, 10 or 16).
|
||||
static bool is_digit_in_base(ut8 c, ut32 base) {
|
||||
ut8 v = 0;
|
||||
if (rz_hex_to_byte(&v, c)) {
|
||||
return false;
|
||||
}
|
||||
return v < base;
|
||||
}
|
||||
|
||||
static bool rax_eval(RzNum *num, const char *s, RzNumValue *out) {
|
||||
rz_num_value_init(out);
|
||||
char *err = NULL;
|
||||
if (num) {
|
||||
num->nc.errors = 0;
|
||||
}
|
||||
if (!rz_num_math_value(num, s, out, &err)) {
|
||||
eprintf("rz-ax: %s\n", err ? err : "evaluation failed");
|
||||
free(err);
|
||||
rz_num_value_fini(out);
|
||||
return false;
|
||||
}
|
||||
free(err);
|
||||
if (num && num->nc.errors) {
|
||||
eprintf("rz-ax: cannot resolve '%s'\n", s);
|
||||
rz_num_value_fini(out);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// rax_eval() narrowed to the 64 bits the single-width conversions work on.
|
||||
static bool rax_eval_u64(RzNum *num, const char *s, ut64 *out) {
|
||||
RzNumValue v;
|
||||
if (!rax_eval(num, s, &v)) {
|
||||
return false;
|
||||
}
|
||||
*out = rz_num_value_to_ut64(&v);
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int format_output(RzNum *num, char mode, const char *s, int force_mode, ut64 flags) {
|
||||
ut64 n = rz_num_math(num, s);
|
||||
char strbits[65];
|
||||
if (force_mode == RZ_AX_INVALID_BASE) {
|
||||
return false;
|
||||
}
|
||||
RzNumValue v;
|
||||
if (!rax_eval(num, s, &v)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (force_mode) {
|
||||
mode = force_mode;
|
||||
}
|
||||
if (v.kind == RZ_NUM_KIND_FLOAT && mode == 'l') {
|
||||
// `Nf` asks for the compact 32-bit pattern, which the multi-line
|
||||
// float table below would not give.
|
||||
RZ_STATIC_ASSERT(sizeof(float) == 4);
|
||||
float f32 = (float)v.val.d;
|
||||
ut32 bits;
|
||||
memcpy(&bits, &f32, sizeof(bits));
|
||||
printf("Fx%08x\n", bits);
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Other format-flag modes have no sensible interpretation on a float
|
||||
// or a 1024-bit big number, so fall back to the multi-line
|
||||
// pretty-print.
|
||||
if (v.kind != RZ_NUM_KIND_UT64) {
|
||||
RzStrBuf *sb = rz_strbuf_new(NULL);
|
||||
if (sb) {
|
||||
rz_num_value_print(&v, sb);
|
||||
char *block = rz_strbuf_drain(sb);
|
||||
if (block) {
|
||||
printf("%s", block);
|
||||
free(block);
|
||||
}
|
||||
}
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
}
|
||||
|
||||
ut64 n = v.val.n;
|
||||
if (num) {
|
||||
num->value = n;
|
||||
}
|
||||
rz_num_value_fini(&v);
|
||||
|
||||
char strbits[65];
|
||||
if (has_flag(flags, RZ_AX_FLAG_SWAP_ENDIANNESS)) {
|
||||
ut64 n2 = n;
|
||||
n = rz_swap_ut64(n2);
|
||||
if (!(int)n) {
|
||||
n >>= 32;
|
||||
}
|
||||
// The swapped value is no longer "the other base of the input",
|
||||
// so the bare-literal decimal toggle does not apply to it.
|
||||
if (mode == 'I' && !force_mode) {
|
||||
mode = '0';
|
||||
}
|
||||
}
|
||||
switch (mode) {
|
||||
case 'I':
|
||||
@@ -187,68 +330,114 @@ static void print_ascii_table(void) {
|
||||
printf("%s", rz_get_ascii_table());
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
printf(Color_CYAN "Usage:" Color_RESET " rz-ax [options] [expr ...]\n"
|
||||
"If expr is not provided, reads from stdin\n");
|
||||
static int help(bool verbose) {
|
||||
printf(Color_CYAN "Usage:" Color_RESET " rz-ax [options] [expression ...]\n"
|
||||
"\n"
|
||||
"Converts numbers between bases and evaluates RzNum expressions.\n"
|
||||
"Without an expression, reads one per line from stdin.\n");
|
||||
#define CF Color_GREEN
|
||||
#define CA Color_YELLOW
|
||||
#define CR Color_RESET
|
||||
const char *conversions[] = {
|
||||
// clang-format off
|
||||
NULL, NULL, "int -> hex", "rz-ax 10",
|
||||
NULL, NULL, "hex -> int", "rz-ax 0xa",
|
||||
NULL, NULL, "-int -> hex", "rz-ax -77",
|
||||
NULL, NULL, "-hex -> int", "rz-ax 0xffffffb3",
|
||||
NULL, NULL, "int -> bin", "rz-ax b30",
|
||||
NULL, NULL, "int -> ternary", "rz-ax t42",
|
||||
NULL, NULL, "int -> oct", "rz-ax Ox12",
|
||||
NULL, NULL, "bin -> int", "rz-ax 0b1010",
|
||||
NULL, NULL, "oct -> int", "rz-ax 0o17",
|
||||
NULL, NULL, "ternary -> int", "rz-ax 0t212",
|
||||
NULL, NULL, "float -> hex", "rz-ax 3.33f",
|
||||
NULL, NULL, "hex -> float", "rz-ax Fx40551ed8",
|
||||
NULL, NULL, "hex -> bin", "rz-ax Bx63",
|
||||
NULL, NULL, "hex -> ternary", "rz-ax Tx23",
|
||||
NULL, NULL, "raw -> hex", "rz-ax " CF "-S" CR " < /binfile",
|
||||
NULL, NULL, "hex -> raw", "rz-ax " CF "-s" CR " 414141",
|
||||
// clang-format on
|
||||
};
|
||||
const char *expressions[] = {
|
||||
// clang-format off
|
||||
NULL, NULL, "arithmetic", "rz-ax '3 * (10 + 2)'",
|
||||
NULL, NULL, "bit-vector width", "rz-ax 5u8+3u8",
|
||||
NULL, NULL, "automatic big number", "rz-ax 2**64",
|
||||
NULL, NULL, "built-in function", "rz-ax min(0x10,0x20)",
|
||||
NULL, NULL, "variable binding", "rz-ax 'x=10;x*4'",
|
||||
// clang-format on
|
||||
};
|
||||
const char *options[] = {
|
||||
// clang-format off
|
||||
NULL, NULL, "int -> hex", "rz-ax 10",
|
||||
NULL, NULL, "hex -> int", "rz-ax 0xa",
|
||||
NULL, NULL, "-int -> hex", "rz-ax -77",
|
||||
NULL, NULL, "-hex -> int", "rz-ax 0xffffffb3",
|
||||
NULL, NULL, "int -> bin", "rz-ax b30",
|
||||
NULL, NULL, "int -> ternary", "rz-ax t42",
|
||||
NULL, NULL, "bin -> int", "rz-ax 1010d",
|
||||
NULL, NULL, "ternary -> int", "rz-ax 1010dt",
|
||||
NULL, NULL, "float -> hex", "rz-ax 3.33f",
|
||||
NULL, NULL, "hex -> float", "rz-ax Fx40551ed8",
|
||||
NULL, NULL, "oct -> hex", "rz-ax 35o",
|
||||
NULL, NULL, "hex -> oct", "rz-ax Ox12 (O is a letter)",
|
||||
NULL, NULL, "bin -> hex", "rz-ax 1100011b",
|
||||
NULL, NULL, "hex -> bin", "rz-ax Bx63",
|
||||
NULL, NULL, "ternary -> hex", "rz-ax 212t",
|
||||
NULL, NULL, "hex -> ternary", "rz-ax Tx23",
|
||||
NULL, NULL, "raw -> hex", "rz-ax " CF "-S" CR " < /binfile",
|
||||
NULL, NULL, "hex -> raw", "rz-ax " CF "-s" CR " 414141",
|
||||
"=", "\bbase", "", "rz-ax " CF "=" CA "10" CR " 0x46 -> output in base 10",
|
||||
"-l", "", "", "append newline to output (for " CF "-E" CR "/" CF "-D" CR "/" CF "-r" CR "/..",
|
||||
"-a", "", "show ascii table", "rz-ax " CF "-a" CR "",
|
||||
"-b", "", "bin -> str", "rz-ax " CF "-b" CR " 01000101 01110110",
|
||||
"-B", "", "str -> bin", "rz-ax " CF "-B" CR " hello",
|
||||
"-d", "", "force integer", "rz-ax " CF "-d" CR " 3 -> 3 instead of 0x3",
|
||||
"-e", "", "swap endianness", "rz-ax " CF "-e" CR " 0x33",
|
||||
"-D", "", "base64 decode", NULL,
|
||||
"-E", "", "base64 encode", NULL,
|
||||
"-f", "", "floating point", "rz-ax " CF "-f" CR " 6.3+2.1",
|
||||
"-F", "", "stdin slurp code hex", "rz-ax " CF "-F" CR " < shellcode.[c/py/js]",
|
||||
"-h", "", "show this help", "rz-ax " CF "-h" CR "",
|
||||
"-i", "", "dump as C byte array", "rz-ax " CF "-i" CR " < bytes",
|
||||
"-I", "", "IP address <-> LONG", "rz-ax " CF "-I" CR " 3530468537",
|
||||
"-k", "", "keep base", "rz-ax " CF "-k" CR " 33+3 -> 36",
|
||||
"-L", "", "bin -> hex(bignum)", "rz-ax " CF "-L" CR " 111111111 # 0x1ff",
|
||||
"-n", "", "int value -> hexpairs", "rz-ax " CF "-n" CR " 0x1234 # 34120000",
|
||||
"-o", "", "octalstr -> raw", "rz-ax " CF "-o" CR " \\162 \\172 # rz",
|
||||
"-N", "", "binary number", "rz-ax " CF "-N" CR " 0x1234 # \\x34\\x12\\x00\\x00",
|
||||
"-r", "", "rz style output", "rz-ax " CF "-r" CR " 0x1234",
|
||||
"-s", "", "hexstr -> raw", "rz-ax " CF "-s" CR " 43 4a 50",
|
||||
"-S", "", "raw -> hexstr", "rz-ax " CF "-S" CR " < /bin/ls > ls.hex",
|
||||
"-t", "", "Unix tstamp -> str", "rz-ax " CF "-t" CR " 1234567890",
|
||||
"-m", "", "MS-DOS tstamp -> str", "rz-ax " CF "-m" CR " 1234567890",
|
||||
"-W", "", "Win32 tstamp -> str", "rz-ax " CF "-W" CR " 1234567890",
|
||||
"-x", "", "hash string", "rz-ax " CF "-x" CR " linux osx",
|
||||
"-u", "", "units", "rz-ax " CF "-u" CR " 389289238 # 317.0M",
|
||||
"-w", "", "signed word", "rz-ax " CF "-w" CR " 16 0xffff",
|
||||
"-v", "", "version", "rz-ax " CF "-v" CR "",
|
||||
"-p", "", "position of set bits", "rz-ax " CF "-p" CR " 0xb3",
|
||||
"=N", "", "print the result in base N", "rz-ax " CF "=" CA "10" CR " 0x46",
|
||||
"-a", "", "show the ascii table", "rz-ax " CF "-a" CR,
|
||||
"-b", "", "bin -> str", "rz-ax " CF "-b" CR " 01000101 01110110",
|
||||
"-B", "", "str -> bin", "rz-ax " CF "-B" CR " hello",
|
||||
"-d", "", "print integers in base 10", "rz-ax " CF "-d" CR " 3 # 3, not 0x3",
|
||||
"-D", "", "base64 decode", "rz-ax " CF "-D" CR " cml6aW4=",
|
||||
"-e", "", "swap endianness", "rz-ax " CF "-e" CR " 0x33",
|
||||
"-E", "", "base64 encode", "rz-ax " CF "-E" CR " rizin",
|
||||
"-f", "", "keep the fractional part", "rz-ax " CF "-f" CR " 6.3+2.1",
|
||||
"-F", "", "slurp code from stdin as hex", "rz-ax " CF "-F" CR " < shellcode.[c/py/js]",
|
||||
"-h", "", "show this help", NULL,
|
||||
"-H", "", "show the long help", "conversions, syntax, operators",
|
||||
"-i", "", "dump as a C byte array", "rz-ax " CF "-i" CR " < bytes",
|
||||
"-I", "", "IP address <-> LONG", "rz-ax " CF "-I" CR " 3530468537",
|
||||
"-k", "", "keep the input base", "rz-ax " CF "-k" CR " 33+3 # 36",
|
||||
"-l", "", "append a newline to output", "for " CF "-E" CR " / " CF "-D" CR " / " CF "-r" CR " / ...",
|
||||
"-L", "", "bin -> hex (big number)", "rz-ax " CF "-L" CR " 111111111 # 0x1ff",
|
||||
"-m", "", "MS-DOS timestamp -> str", "rz-ax " CF "-m" CR " 1234567890",
|
||||
"-n", "", "int -> hex pairs", "rz-ax " CF "-n" CR " 0x1234 # 34120000",
|
||||
"-N", "", "int -> escaped raw bytes", "rz-ax " CF "-N" CR " 0x1234 # \\x34\\x12\\x00\\x00",
|
||||
"-o", "", "octal str -> raw", "rz-ax " CF "-o" CR " \\162 \\172 # rz",
|
||||
"-p", "", "positions of the set bits", "rz-ax " CF "-p" CR " 0xb3",
|
||||
"-r", "", "rizin command output", "rz-ax " CF "-r" CR " 0x1234",
|
||||
"-s", "", "hex str -> raw", "rz-ax " CF "-s" CR " 43 4a 50",
|
||||
"-S", "", "raw -> hex str", "rz-ax " CF "-S" CR " < /bin/ls > ls.hex",
|
||||
"-t", "", "Unix timestamp -> str", "rz-ax " CF "-t" CR " 1234567890",
|
||||
"-u", "", "human readable units", "rz-ax " CF "-u" CR " 389289238 # 371.3M",
|
||||
"-v", "", "show the version", NULL,
|
||||
"-w", "", "sign-extend a signed word", "rz-ax " CF "-w" CR " 16 0xffff",
|
||||
"-W", "", "Win32 timestamp -> str", "rz-ax " CF "-W" CR " 1234567890",
|
||||
"-x", "", "djb2 hash of a string", "rz-ax " CF "-x" CR " linux osx",
|
||||
// clang-format on
|
||||
};
|
||||
#undef CF
|
||||
#undef CA
|
||||
#undef CR
|
||||
printf("\n" Color_CYAN "Options" Color_RESET "\n");
|
||||
rz_print_colored_help(options, RZ_ARRAY_SIZE(options), true);
|
||||
if (!verbose) {
|
||||
printf("\nRun " Color_GREEN "rz-ax -H" Color_RESET " for the conversion table, the expression\n"
|
||||
"syntax and the operator list, or see the rz-ax(1) man page.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
printf("\n" Color_CYAN "Conversions" Color_RESET "\n");
|
||||
rz_print_colored_help(conversions, RZ_ARRAY_SIZE(conversions), true);
|
||||
printf(" Trailing base suffixes (101b, 35o, 212t) still work but are\n"
|
||||
" deprecated; prefer the 0b / 0o / 0t prefixes.\n");
|
||||
printf("\n" Color_CYAN "Expressions" Color_RESET "\n");
|
||||
rz_print_colored_help(expressions, RZ_ARRAY_SIZE(expressions), true);
|
||||
printf(" Quote an expression containing spaces so it stays a single\n"
|
||||
" argument; otherwise every argument is converted on its own.\n");
|
||||
printf("\n" Color_CYAN "Operators" Color_RESET " (C precedence)\n"
|
||||
" ** power (2**10; note ^ is XOR, not power)\n"
|
||||
" * / %% multiply, divide, modulo\n"
|
||||
" sdiv smod signed divide, signed modulo\n"
|
||||
" + - add, subtract (unary - negates)\n"
|
||||
" << >> logical shift left, right\n"
|
||||
" sar arithmetic (sign-extending) shift right\n"
|
||||
" <<< >>> rotate left, right\n"
|
||||
" & | ^ ~ and, or, xor, not\n"
|
||||
" < <= > >= == != comparisons, yielding 0 or 1\n"
|
||||
" ?: ternary (cond ? a : b)\n"
|
||||
"\n" Color_CYAN "Literals" Color_RESET "\n"
|
||||
" 0x 0o 0t 0b hex, octal, ternary, binary\n"
|
||||
" uN bit-vector of N bits (5u8, 1u32, 3u1024)\n"
|
||||
" KiB MiB GiB ... size units (4KiB)\n"
|
||||
" fn(...) built-ins: min max gcd sqrt log2 popcount clz ctz ...\n"
|
||||
"\nSee the rz-ax(1) man page for the complete reference.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -273,6 +462,14 @@ static int rax(RzNum *num, char *str, int len, int last, ut64 *_flags, int *fm)
|
||||
case 10: force_mode = 'I'; break;
|
||||
case 16: force_mode = '0'; break;
|
||||
case 0: force_mode = str[1]; break;
|
||||
default:
|
||||
// A numeric but unsupported output base (=4, =7, ...) used to be
|
||||
// dropped silently, leaving the result in the default base. Report
|
||||
// it and mark the base invalid so the following expression is not
|
||||
// printed in a misleading base.
|
||||
eprintf("rz-ax: invalid output base '%s' (use =2, =3, =8, =10, or =16)\n", str + 1);
|
||||
*fm = RZ_AX_INVALID_BASE;
|
||||
return true;
|
||||
}
|
||||
*fm = force_mode;
|
||||
return true;
|
||||
@@ -282,7 +479,9 @@ static int rax(RzNum *num, char *str, int len, int last, ut64 *_flags, int *fm)
|
||||
while (str[1] && str[1] != ' ') {
|
||||
switch (str[1]) {
|
||||
case 'l': break;
|
||||
case 'a': print_ascii_table(); return 0;
|
||||
case 'h': return help(false);
|
||||
case 'H': return help(true);
|
||||
case 'a': print_ascii_table(); return true;
|
||||
case 's': flags ^= RZ_AX_FLAG_HEX_TO_RAW; break;
|
||||
case 'e': flags ^= RZ_AX_FLAG_SWAP_ENDIANNESS; break;
|
||||
case 'S': flags ^= RZ_AX_FLAG_RAW_TO_HEX; break;
|
||||
@@ -313,9 +512,10 @@ static int rax(RzNum *num, char *str, int len, int last, ut64 *_flags, int *fm)
|
||||
if (!sys_path) {
|
||||
break;
|
||||
}
|
||||
size_t print_val = rz_main_version_print(sys_path, "rz-ax");
|
||||
rz_main_version_print(sys_path, "rz-ax");
|
||||
rz_path_free(sys_path);
|
||||
return print_val;
|
||||
// Printing the version is not a conversion failure.
|
||||
return true;
|
||||
}
|
||||
case '\0':
|
||||
*_flags = flags;
|
||||
@@ -331,7 +531,7 @@ static int rax(RzNum *num, char *str, int len, int last, ut64 *_flags, int *fm)
|
||||
}
|
||||
return format_output(num, out_mode, str, *fm, flags);
|
||||
}
|
||||
return help();
|
||||
return help(false);
|
||||
}
|
||||
str++;
|
||||
}
|
||||
@@ -343,12 +543,11 @@ static int rax(RzNum *num, char *str, int len, int last, ut64 *_flags, int *fm)
|
||||
}
|
||||
*_flags = flags;
|
||||
if (!flags && rz_str_nlen(str, 2) == 1) {
|
||||
if (*str == 'q') {
|
||||
return false;
|
||||
}
|
||||
if (*str == 'h' || *str == '?') {
|
||||
help();
|
||||
return false;
|
||||
return help(false);
|
||||
}
|
||||
if (*str == 'H') {
|
||||
return help(true);
|
||||
}
|
||||
}
|
||||
dotherax:
|
||||
@@ -396,7 +595,10 @@ dotherax:
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_FLOATING_POINT)) { // -f
|
||||
out_mode = 'f';
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_NUMBER_TO_HEX)) { // -n
|
||||
ut64 n = rz_num_math(num, str);
|
||||
ut64 n = 0;
|
||||
if (!rax_eval_u64(num, str, &n)) {
|
||||
return false;
|
||||
}
|
||||
if (n >> 32) {
|
||||
/* is 64 bit value */
|
||||
if (has_flag(flags, RZ_AX_FLAG_HEX_TO_RAW)) {
|
||||
@@ -441,42 +643,49 @@ dotherax:
|
||||
}
|
||||
return true;
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_SET_BITS)) { // -p (find position of set bits)
|
||||
ut64 n = rz_num_math(num, str);
|
||||
char strbits[65] = { 0 };
|
||||
int i = 0, set_bits_ctr = 0;
|
||||
rz_num_to_bits(strbits, n);
|
||||
rz_str_reverse(strbits); // because we count Right to Left
|
||||
char last_char = 0;
|
||||
while (strbits[i] != '\0') {
|
||||
if (strbits[i] == '1') {
|
||||
++set_bits_ctr;
|
||||
if (i == 0) {
|
||||
printf("[%d", i);
|
||||
} else if (strbits[i] == '1' && last_char == '0') {
|
||||
printf("[%d", i);
|
||||
}
|
||||
}
|
||||
if (strbits[i] == '0' && last_char == '1') {
|
||||
if (set_bits_ctr == 1) {
|
||||
printf("]: 1\n");
|
||||
} else if (strbits[i + 1] == '\0') {
|
||||
printf("-%d]: 1\n", i);
|
||||
} else
|
||||
printf("-%d]: 1\n", i - 1);
|
||||
set_bits_ctr = 0;
|
||||
} else if (strbits[i] == '1' && strbits[i + 1] == '\0') {
|
||||
if (set_bits_ctr == 1) {
|
||||
printf("]: 1\n");
|
||||
} else
|
||||
printf("-%d]: 1\n", i);
|
||||
set_bits_ctr = 0;
|
||||
}
|
||||
last_char = strbits[i];
|
||||
++i;
|
||||
RzNumValue v;
|
||||
if (!rax_eval(num, str, &v)) {
|
||||
return false;
|
||||
}
|
||||
// Runs are reported over the value's own width, so a bit-vector
|
||||
// literal wider than 64 bits is covered in full.
|
||||
ut32 width = 64;
|
||||
if (v.kind == RZ_NUM_KIND_BITVECTOR && v.val.bv) {
|
||||
width = v.val.bv->len;
|
||||
}
|
||||
ut64 n = rz_num_value_to_ut64(&v);
|
||||
ut32 run_start = 0;
|
||||
bool in_run = false, any = false;
|
||||
for (ut32 i = 0; i <= width; i++) {
|
||||
bool set = false;
|
||||
if (i < width) {
|
||||
set = (v.kind == RZ_NUM_KIND_BITVECTOR && v.val.bv)
|
||||
? rz_bv_get(v.val.bv, i)
|
||||
: (i < 64 && ((n >> i) & 1));
|
||||
}
|
||||
if (set && !in_run) {
|
||||
run_start = i;
|
||||
in_run = true;
|
||||
} else if (!set && in_run) {
|
||||
if (run_start == i - 1) {
|
||||
printf("[%u]: 1\n", run_start);
|
||||
} else {
|
||||
printf("[%u-%u]: 1\n", run_start, i - 1);
|
||||
}
|
||||
in_run = false;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (!any) {
|
||||
printf("no bits set\n");
|
||||
}
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_SIGNED_WORD)) { // -w
|
||||
ut64 n = rz_num_math(num, str);
|
||||
ut64 n = 0;
|
||||
if (!rax_eval_u64(num, str, &n)) {
|
||||
return false;
|
||||
}
|
||||
if (n >> 31) {
|
||||
// is >32bit
|
||||
n = (st64)(st32)n;
|
||||
@@ -488,37 +697,53 @@ dotherax:
|
||||
printf("%" PFMT64d "\n", n);
|
||||
return true;
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_NUMBER_TO_HEXSTR)) { // -N
|
||||
ut64 n = rz_num_math(num, str);
|
||||
if (n >> 32) {
|
||||
/* is 64 bit value */
|
||||
if (has_flag(flags, RZ_AX_FLAG_HEX_TO_RAW)) {
|
||||
fwrite(&n, sizeof(n), 1, stdout);
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
printf("\\x%02x", (int)(n & 0xff));
|
||||
n >>= 8;
|
||||
RzNumValue v;
|
||||
if (!rax_eval(num, str, &v)) {
|
||||
return false;
|
||||
}
|
||||
// A bit-vector literal carries its own width, so emit exactly that
|
||||
// many bytes; a plain integer keeps the historical 4-or-8 choice.
|
||||
ut32 nbytes = 4;
|
||||
if (v.kind == RZ_NUM_KIND_BITVECTOR && v.val.bv) {
|
||||
nbytes = (v.val.bv->len + 7) / 8;
|
||||
} else if (rz_num_value_to_ut64(&v) >> 32) {
|
||||
nbytes = 8;
|
||||
}
|
||||
ut8 *bytes = calloc(nbytes, 1);
|
||||
if (!bytes) {
|
||||
rz_num_value_fini(&v);
|
||||
return false;
|
||||
}
|
||||
if (v.kind == RZ_NUM_KIND_BITVECTOR && v.val.bv) {
|
||||
for (ut32 i = 0; i < v.val.bv->len; i++) {
|
||||
if (rz_bv_get(v.val.bv, i)) {
|
||||
bytes[i / 8] |= 1 << (i % 8);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
} else {
|
||||
/* is 32 bit value */
|
||||
ut32 n32 = (ut32)n;
|
||||
if (has_flag(flags, RZ_AX_FLAG_HEX_TO_RAW)) {
|
||||
fwrite(&n32, sizeof(n32), 1, stdout);
|
||||
} else {
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
printf("\\x%02x", n32 & 0xff);
|
||||
n32 >>= 8;
|
||||
}
|
||||
printf("\n");
|
||||
ut64 n = rz_num_value_to_ut64(&v);
|
||||
for (ut32 i = 0; i < nbytes; i++) {
|
||||
bytes[i] = (ut8)(n >> (8 * i));
|
||||
}
|
||||
}
|
||||
if (has_flag(flags, RZ_AX_FLAG_HEX_TO_RAW)) {
|
||||
fwrite(bytes, nbytes, 1, stdout);
|
||||
} else {
|
||||
for (ut32 i = 0; i < nbytes; i++) {
|
||||
printf("\\x%02x", bytes[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
free(bytes);
|
||||
rz_num_value_fini(&v);
|
||||
return true;
|
||||
} else if (has_flag(flags, RZ_AX_FLAG_UNITS)) { // -u
|
||||
char buf[8];
|
||||
rz_num_units(buf, sizeof(buf), rz_num_math(NULL, str));
|
||||
ut64 n = 0;
|
||||
if (!rax_eval_u64(num, str, &n)) {
|
||||
return false;
|
||||
}
|
||||
rz_num_units(buf, sizeof(buf), n);
|
||||
printf("%s\n", buf);
|
||||
return true;
|
||||
} else if (is_timestamp(flags)) { // -t, -m, -W
|
||||
@@ -530,8 +755,15 @@ dotherax:
|
||||
if (gmt && strlen(gmt) < 2) {
|
||||
gmt = NULL;
|
||||
}
|
||||
ut64 n = rz_num_math(num, ts);
|
||||
int timezone = (int)rz_num_math(num, gmt);
|
||||
ut64 n = 0;
|
||||
if (!rax_eval_u64(num, ts, &n)) {
|
||||
return false;
|
||||
}
|
||||
ut64 tzv = 0;
|
||||
if (gmt && !rax_eval_u64(num, gmt, &tzv)) {
|
||||
return false;
|
||||
}
|
||||
int timezone = (int)tzv;
|
||||
n += timezone * (60 * 60);
|
||||
char *date = NULL;
|
||||
if (has_flag(flags, RZ_AX_FLAG_TIMESTAMP_TO_STR)) {
|
||||
@@ -590,7 +822,10 @@ dotherax:
|
||||
ut32 n32, s, a;
|
||||
double d;
|
||||
float f;
|
||||
ut64 n = rz_num_math(num, str);
|
||||
ut64 n = 0;
|
||||
if (!rax_eval_u64(num, str, &n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num->dbz) {
|
||||
eprintf("RzNum ERROR: Division by Zero\n");
|
||||
@@ -684,8 +919,12 @@ dotherax:
|
||||
modified_str = rz_str_dup(str);
|
||||
}
|
||||
|
||||
ut64 n = rz_num_math(num, modified_str);
|
||||
ut64 n = 0;
|
||||
bool eval_ok = rax_eval_u64(num, modified_str, &n);
|
||||
free(modified_str);
|
||||
if (!eval_ok) {
|
||||
return false;
|
||||
}
|
||||
if (num->dbz) {
|
||||
eprintf("RzNum ERROR: Division by Zero\n");
|
||||
return false;
|
||||
@@ -707,20 +946,39 @@ dotherax:
|
||||
ut32 ip32 = ip[0] | (ip[1] << 8) | (ip[2] << 16) | (ip[3] << 24);
|
||||
printf("0x%08x\n", ip32);
|
||||
} else {
|
||||
ut32 ip32 = (ut32)rz_num_math(NULL, str);
|
||||
ut64 ipv = 0;
|
||||
if (!rax_eval_u64(num, str, &ipv)) {
|
||||
return false;
|
||||
}
|
||||
ut32 ip32 = (ut32)ipv;
|
||||
ut8 ip[4] = { ip32 & 0xff, (ip32 >> 8) & 0xff, (ip32 >> 16) & 0xff, ip32 >> 24 };
|
||||
printf("%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str[0] == '0' && (tolower(str[1]) == 'x')) {
|
||||
// A bare literal in a non-decimal base prints as decimal, preserving the
|
||||
// rax2 "show the other base" conversion; everything else, including any
|
||||
// compound expression, keeps the default hex output so the radix stays
|
||||
// predictable for scripting.
|
||||
size_t prefix_len = 0;
|
||||
ut32 lit_base = 10;
|
||||
bool bare_literal = rz_num_base_prefix(str, &lit_base, &prefix_len) != RZ_NUM_BASE_PREFIX_NONE && str[prefix_len];
|
||||
for (const char *q = str + prefix_len; bare_literal && *q; q++) {
|
||||
if (!is_digit_in_base((ut8)*q, lit_base)) {
|
||||
bare_literal = false;
|
||||
}
|
||||
}
|
||||
const char *consumed_prefix = NULL;
|
||||
if (bare_literal) {
|
||||
out_mode = (has_flag(flags, RZ_AX_FLAG_KEEP_BASE)) ? '0' : 'I';
|
||||
} else if (rz_str_startswith(str, "b")) {
|
||||
out_mode = 'B';
|
||||
consumed_prefix = "b";
|
||||
str++;
|
||||
} else if (rz_str_startswith(str, "t")) {
|
||||
out_mode = 'T';
|
||||
consumed_prefix = "t";
|
||||
str++;
|
||||
} else if (rz_str_startswith(str, "Fx")) {
|
||||
out_mode = 'F';
|
||||
@@ -734,43 +992,107 @@ dotherax:
|
||||
} else if (rz_str_startswith(str, "Ox")) {
|
||||
out_mode = 'O';
|
||||
*str = '0';
|
||||
} else if (rz_str_endswith(str, "d")) {
|
||||
} else if (isdigit((ut8)str[0]) && rz_str_endswith(str, "d")) {
|
||||
out_mode = 'I';
|
||||
str[strlen(str) - 1] = 'b';
|
||||
// TODO: Move print into format_output
|
||||
} else if (rz_str_endswith(str, "f")) {
|
||||
} else if (isdigit((ut8)str[0]) && rz_str_endswith(str, "f")) {
|
||||
out_mode = 'l';
|
||||
} else if (rz_str_endswith(str, "dt")) {
|
||||
} else if (isdigit((ut8)str[0]) && rz_str_endswith(str, "dt")) {
|
||||
out_mode = 'I';
|
||||
str[strlen(str) - 2] = 't';
|
||||
str[strlen(str) - 1] = '\0';
|
||||
}
|
||||
if (consumed_prefix && !*str) {
|
||||
// A lone output-mode prefix (`rz-ax b`, `rz-ax t`) has nothing to
|
||||
// convert; without this it printed nothing and exited 0.
|
||||
eprintf("rz-ax: missing value after '%s'\n", consumed_prefix);
|
||||
return false;
|
||||
}
|
||||
// The RzNum grammar accepts internal whitespace, so an argument like
|
||||
// "1 + 2" is a single expression. Try the whole space-containing string
|
||||
// first and only fall back to the historical "whitespace separates
|
||||
// independent values" splitting when that parse fails, so both
|
||||
// `rz-ax '1 + 2'` (-> 3) and `rz-ax '10 0x20'` (two values) keep working.
|
||||
if (strchr(str, ' ')) {
|
||||
RzNumValue probe;
|
||||
rz_num_value_init(&probe);
|
||||
char *whole_err = NULL;
|
||||
bool whole_ok = rz_num_math_value(num, str, &probe, &whole_err);
|
||||
rz_num_value_fini(&probe);
|
||||
if (whole_ok) {
|
||||
free(whole_err);
|
||||
return format_output(num, out_mode, str, *fm, flags);
|
||||
}
|
||||
// The whole string is not one expression. Only fall back to the
|
||||
// historical "whitespace separates independent values" splitting if
|
||||
// every piece parses on its own; otherwise this was a single bad
|
||||
// expression and its own diagnostic is the useful one, rather than
|
||||
// one error per fragment.
|
||||
bool split_ok = true;
|
||||
char *probe_str = rz_str_dup(str);
|
||||
if (probe_str) {
|
||||
char *tok = probe_str;
|
||||
char *sp = NULL;
|
||||
while (split_ok) {
|
||||
sp = strchr(tok, ' ');
|
||||
if (sp) {
|
||||
*sp = 0;
|
||||
}
|
||||
if (*tok) {
|
||||
RzNumValue piece;
|
||||
rz_num_value_init(&piece);
|
||||
char *piece_err = NULL;
|
||||
split_ok = rz_num_math_value(num, tok, &piece, &piece_err);
|
||||
rz_num_value_fini(&piece);
|
||||
free(piece_err);
|
||||
}
|
||||
if (!sp) {
|
||||
break;
|
||||
}
|
||||
tok = sp + 1;
|
||||
}
|
||||
free(probe_str);
|
||||
}
|
||||
if (!split_ok) {
|
||||
eprintf("rz-ax: %s\n", whole_err ? whole_err : "evaluation failed");
|
||||
free(whole_err);
|
||||
return false;
|
||||
}
|
||||
free(whole_err);
|
||||
}
|
||||
bool all_ok = true;
|
||||
while ((p = strchr(str, ' '))) {
|
||||
*p = 0;
|
||||
format_output(num, out_mode, str, *fm, flags);
|
||||
if (*str && !format_output(num, out_mode, str, *fm, flags)) {
|
||||
all_ok = false;
|
||||
}
|
||||
str = p + 1;
|
||||
}
|
||||
if (*str) {
|
||||
format_output(num, out_mode, str, *fm, flags);
|
||||
if (*str && !format_output(num, out_mode, str, *fm, flags)) {
|
||||
all_ok = false;
|
||||
}
|
||||
return true;
|
||||
return all_ok;
|
||||
}
|
||||
|
||||
RZ_API int rz_main_rz_ax(int argc, const char **argv) {
|
||||
int i, fm = 0;
|
||||
RzNum *num = rz_num_new(NULL, NULL, NULL);
|
||||
ut64 flags = 0;
|
||||
bool ok = true;
|
||||
if (argc == 1) {
|
||||
use_stdin(num, &flags, &fm);
|
||||
} else {
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *argv_i = rz_str_dup(argv[i]);
|
||||
rz_str_unescape(argv_i);
|
||||
rax(num, argv_i, 0, i == argc - 1, &flags, &fm);
|
||||
if (!rax(num, argv_i, 0, i == argc - 1, &flags, &fm)) {
|
||||
ok = false;
|
||||
}
|
||||
free(argv_i);
|
||||
}
|
||||
}
|
||||
rz_num_free(num);
|
||||
num = NULL;
|
||||
return 0;
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
+120
-2
@@ -197,6 +197,58 @@ RZ_API char *rz_big_to_hexstr(RzNumBig *b) {
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Format a bignum as a lossless base-10 string.
|
||||
*
|
||||
* \param b Value to render.
|
||||
* \return Owned decimal string (leading '-' for negatives, "0" for
|
||||
* zero), or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_big_to_decstr(RZ_NONNULL RzNumBig *b) {
|
||||
rz_return_val_if_fail(b, NULL);
|
||||
|
||||
if (rz_big_is_zero(b)) {
|
||||
return rz_str_dup("0");
|
||||
}
|
||||
|
||||
// Divide an absolute-value copy so the loop ignores signs.
|
||||
RzNumBig work, q, r, divisor;
|
||||
rz_big_assign(&work, b);
|
||||
work.sign = 1;
|
||||
|
||||
// 10^9 fits one ut32 word, so each remainder prints with "%09u".
|
||||
rz_big_init(&divisor);
|
||||
rz_big_from_int(&divisor, 1000000000);
|
||||
|
||||
// Size from the fixed width (bits/3 > bits*log10(2)) plus slack,
|
||||
// so the maximal value still fits; emit 9 digits per iteration.
|
||||
char chunks[(RZ_BIG_ARRAY_SIZE * RZ_BIG_WORD_SIZE * 8) / 3 + 16];
|
||||
int pos = (int)sizeof(chunks);
|
||||
chunks[--pos] = '\0';
|
||||
|
||||
while (!rz_big_is_zero(&work)) {
|
||||
rz_big_init(&q);
|
||||
rz_big_init(&r);
|
||||
rz_big_divmod(&q, &r, &work, &divisor);
|
||||
ut32 chunk = (ut32)rz_big_to_int(&r);
|
||||
rz_big_assign(&work, &q);
|
||||
// The most significant chunk (extracted last, when work hits
|
||||
// zero) is unpadded; interior chunks need zero-padding.
|
||||
char tmp[16];
|
||||
int n = rz_big_is_zero(&work)
|
||||
? snprintf(tmp, sizeof(tmp), "%u", (unsigned)chunk)
|
||||
: snprintf(tmp, sizeof(tmp), "%09u", (unsigned)chunk);
|
||||
if (n < 0 || n > pos) {
|
||||
return NULL;
|
||||
}
|
||||
pos -= n;
|
||||
memcpy(chunks + pos, tmp, (size_t)n);
|
||||
}
|
||||
|
||||
const char *digits = chunks + pos;
|
||||
return b->sign < 0 ? rz_str_newf("-%s", digits) : rz_str_dup(digits);
|
||||
}
|
||||
|
||||
RZ_API void rz_big_assign(RzNumBig *dst, RzNumBig *src) {
|
||||
rz_return_if_fail(dst);
|
||||
rz_return_if_fail(src);
|
||||
@@ -204,6 +256,25 @@ RZ_API void rz_big_assign(RzNumBig *dst, RzNumBig *src) {
|
||||
memcpy(dst, src, sizeof(RzNumBig));
|
||||
}
|
||||
|
||||
// Compare magnitudes |a| vs |b| ignoring sign: 1 if |a|>|b|, -1 if <, else
|
||||
// 0. The inner add/sub helpers operate on magnitudes (the caller factors
|
||||
// out the signs), so they must order operands by magnitude, not by the
|
||||
// signed rz_big_cmp() - otherwise a negative operand fed in by rz_big_add's
|
||||
// dispatch makes the magnitude subtraction borrow past zero.
|
||||
static int rz_big_cmp_mag(RzNumBig *a, RzNumBig *b) {
|
||||
int i = RZ_BIG_ARRAY_SIZE;
|
||||
do {
|
||||
i -= 1;
|
||||
if (a->array[i] > b->array[i]) {
|
||||
return 1;
|
||||
}
|
||||
if (a->array[i] < b->array[i]) {
|
||||
return -1;
|
||||
}
|
||||
} while (i != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rz_big_add_inner(RzNumBig *c, RzNumBig *a, RzNumBig *b) {
|
||||
RZ_BIG_DTYPE_TMP tmp;
|
||||
int carry = 0;
|
||||
@@ -221,7 +292,7 @@ static void rz_big_sub_inner(RzNumBig *c, RzNumBig *a, RzNumBig *b) {
|
||||
RZ_BIG_DTYPE_TMP tmp1;
|
||||
RZ_BIG_DTYPE_TMP tmp2;
|
||||
int borrow = 0;
|
||||
int sign = rz_big_cmp(a, b);
|
||||
int sign = rz_big_cmp_mag(a, b);
|
||||
c->sign = (sign >= 0 ? 1 : -1);
|
||||
if (sign < 0) {
|
||||
tmp = a;
|
||||
@@ -342,7 +413,7 @@ RZ_API void rz_big_div(RzNumBig *c, RzNumBig *a, RzNumBig *b) {
|
||||
rz_big_assign(tmp, denom); // tmp = denom = b
|
||||
_lshift_one_bit(tmp); // tmp <= 1
|
||||
|
||||
while (rz_big_cmp(tmp, a) != 1) { // while (tmp <= a)
|
||||
while (rz_big_cmp_mag(tmp, a) != 1) { // while (tmp <= |a|)
|
||||
if ((denom->array[RZ_BIG_ARRAY_SIZE - 1] >> (RZ_BIG_WORD_SIZE * 8 - 1)) == 1) {
|
||||
break; // Reach the max value
|
||||
}
|
||||
@@ -589,6 +660,53 @@ RZ_API void rz_big_powm(RzNumBig *c, RzNumBig *a, RzNumBig *b, RzNumBig *m) {
|
||||
rz_big_free(acopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Non-modular exponentiation by squaring: c = a ^ b.
|
||||
*
|
||||
* A negative exponent yields zero, since an integer power with a
|
||||
* negative exponent is not an integer. The caller must keep the
|
||||
* result within the fixed bignum width; an oversized base/exponent
|
||||
* silently overflows, as with every other rz_big operation.
|
||||
*
|
||||
* \param c Result (overwritten).
|
||||
* \param a Base.
|
||||
* \param b Exponent.
|
||||
*/
|
||||
RZ_API void rz_big_pow(RZ_NONNULL RzNumBig *c, RZ_NONNULL RzNumBig *a, RZ_NONNULL RzNumBig *b) {
|
||||
rz_return_if_fail(a && b && c);
|
||||
|
||||
if (b->sign < 0) {
|
||||
rz_big_from_int(c, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
RzNumBig *bcopy = rz_big_new();
|
||||
RzNumBig *base = rz_big_new();
|
||||
if (!bcopy || !base) {
|
||||
rz_big_free(bcopy);
|
||||
rz_big_free(base);
|
||||
rz_big_from_int(c, 0);
|
||||
return;
|
||||
}
|
||||
rz_big_assign(bcopy, b);
|
||||
rz_big_assign(base, a);
|
||||
rz_big_from_int(c, 1);
|
||||
|
||||
// Square-and-multiply, like rz_big_powm() without the reduction.
|
||||
while (!rz_big_is_zero(bcopy)) {
|
||||
if (rz_big_to_int(bcopy) % 2 == 1) {
|
||||
rz_big_mul(c, c, base);
|
||||
}
|
||||
_rshift_one_bit(bcopy);
|
||||
if (!rz_big_is_zero(bcopy)) {
|
||||
rz_big_mul(base, base, base);
|
||||
}
|
||||
}
|
||||
|
||||
rz_big_free(bcopy);
|
||||
rz_big_free(base);
|
||||
}
|
||||
|
||||
RZ_API void rz_big_isqrt(RzNumBig *b, RzNumBig *a) {
|
||||
rz_return_if_fail(a);
|
||||
rz_return_if_fail(b);
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Arbitrary-precision base-10 decimal (RzBigDecimal).
|
||||
*
|
||||
* A decimal value is stored as an arbitrary-precision integer mantissa
|
||||
* times 10^(-scale), with the sign carried by the mantissa and scale
|
||||
* kept >= 0. Addition, subtraction and multiplication are exact;
|
||||
* division is computed to a bounded number of significant digits because
|
||||
* a finite base-10 expansion cannot represent ratios such as 1/3.
|
||||
*
|
||||
* Built on RzNumBig (which is integer-only); the base-10 scale and the
|
||||
* fractional formatting live here.
|
||||
*/
|
||||
|
||||
#include <rz_util/rz_big.h>
|
||||
#include <rz_util/rz_str.h>
|
||||
#include <rz_util/rz_assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static RzNumBig *big_zero(void) {
|
||||
RzNumBig *b = rz_big_new();
|
||||
if (b) {
|
||||
rz_big_from_int(b, 0);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static RzNumBig *big_from_i64(st64 v) {
|
||||
RzNumBig *b = rz_big_new();
|
||||
if (b) {
|
||||
rz_big_from_int(b, v);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static RzNumBig *big_clone(RZ_NONNULL RzNumBig *src) {
|
||||
RzNumBig *b = rz_big_new();
|
||||
if (b) {
|
||||
rz_big_assign(b, src);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// out = 10^k (k >= 0). Returns NULL on allocation failure.
|
||||
static RzNumBig *big_pow10(ut32 k) {
|
||||
RzNumBig *out = rz_big_new();
|
||||
RzNumBig *ten = big_from_i64(10);
|
||||
RzNumBig *exp = big_from_i64((st64)k);
|
||||
if (!out || !ten || !exp) {
|
||||
rz_big_free(out);
|
||||
rz_big_free(ten);
|
||||
rz_big_free(exp);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_pow(out, ten, exp);
|
||||
rz_big_free(ten);
|
||||
rz_big_free(exp);
|
||||
return out;
|
||||
}
|
||||
|
||||
// out = m * 10^k (k >= 0). Returns a new RzNumBig or NULL.
|
||||
static RzNumBig *big_scale_up(RZ_NONNULL RzNumBig *m, ut32 k) {
|
||||
if (k == 0) {
|
||||
return big_clone(m);
|
||||
}
|
||||
RzNumBig *p = big_pow10(k);
|
||||
RzNumBig *out = rz_big_new();
|
||||
if (!p || !out) {
|
||||
rz_big_free(p);
|
||||
rz_big_free(out);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_mul(out, m, p);
|
||||
rz_big_free(p);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Number of base-10 digits of |m| (0 counts as 1). -1 on failure.
|
||||
static int big_digit_count(RZ_NONNULL RzNumBig *m) {
|
||||
char *s = rz_big_to_decstr(m);
|
||||
if (!s) {
|
||||
return -1;
|
||||
}
|
||||
const char *p = s;
|
||||
if (*p == '-') {
|
||||
p++;
|
||||
}
|
||||
int n = (int)strlen(p);
|
||||
free(s);
|
||||
return n < 1 ? 1 : n;
|
||||
}
|
||||
|
||||
// abs into a fresh RzNumBig.
|
||||
static RzNumBig *big_abs(RZ_NONNULL RzNumBig *m) {
|
||||
RzNumBig *zero = big_zero();
|
||||
if (!zero) {
|
||||
return NULL;
|
||||
}
|
||||
RzNumBig *out;
|
||||
if (rz_big_cmp(m, zero) < 0) {
|
||||
out = rz_big_new();
|
||||
if (out) {
|
||||
rz_big_sub(out, zero, m); // out = -m
|
||||
}
|
||||
} else {
|
||||
out = big_clone(m);
|
||||
}
|
||||
rz_big_free(zero);
|
||||
return out;
|
||||
}
|
||||
|
||||
static RzBigDecimal *bd_new(RZ_OWN RzNumBig *mantissa, ut32 scale) {
|
||||
if (!mantissa) {
|
||||
return NULL;
|
||||
}
|
||||
RzBigDecimal *d = RZ_NEW0(RzBigDecimal);
|
||||
if (!d) {
|
||||
rz_big_free(mantissa);
|
||||
return NULL;
|
||||
}
|
||||
d->mantissa = mantissa;
|
||||
d->scale = scale;
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Free an RzBigDecimal and its mantissa.
|
||||
* \param d Decimal to free; NULL is ignored.
|
||||
*/
|
||||
RZ_API void rz_big_decimal_free(RZ_NULLABLE RzBigDecimal *d) {
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
rz_big_free(d->mantissa);
|
||||
free(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Deep-copy an RzBigDecimal.
|
||||
* \param d Decimal to copy.
|
||||
* \return Copy, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_dup(RZ_NONNULL const RzBigDecimal *d) {
|
||||
rz_return_val_if_fail(d, NULL);
|
||||
return bd_new(big_clone(d->mantissa), d->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Build an RzBigDecimal holding the exact integer value \p v (scale 0).
|
||||
* \param v Integer value to store.
|
||||
* \return Decimal, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_new_from_int(st64 v) {
|
||||
return bd_new(big_from_i64(v), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse a decimal string into an RzBigDecimal.
|
||||
* \param str Source in the form "[+-]ddd[.ddd][eE[+-]ddd]".
|
||||
* \return Decimal, or NULL on malformed input or allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_new_from_str(RZ_NONNULL const char *str) {
|
||||
rz_return_val_if_fail(str, NULL);
|
||||
const char *p = str;
|
||||
while (*p == ' ' || *p == '\t') {
|
||||
p++;
|
||||
}
|
||||
bool neg = false;
|
||||
if (*p == '+' || *p == '-') {
|
||||
neg = (*p == '-');
|
||||
p++;
|
||||
}
|
||||
// Collect integer and fractional digit runs (no point).
|
||||
RzStrBuf digits;
|
||||
rz_strbuf_init(&digits);
|
||||
st32 frac_len = 0;
|
||||
bool seen_digit = false;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
rz_strbuf_append_n(&digits, p, 1);
|
||||
seen_digit = true;
|
||||
p++;
|
||||
}
|
||||
if (*p == '.') {
|
||||
p++;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
rz_strbuf_append_n(&digits, p, 1);
|
||||
frac_len++;
|
||||
seen_digit = true;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (!seen_digit) {
|
||||
rz_strbuf_fini(&digits);
|
||||
return NULL;
|
||||
}
|
||||
// Optional exponent.
|
||||
st32 exp = 0;
|
||||
if (*p == 'e' || *p == 'E') {
|
||||
p++;
|
||||
bool eneg = false;
|
||||
if (*p == '+' || *p == '-') {
|
||||
eneg = (*p == '-');
|
||||
p++;
|
||||
}
|
||||
if (*p < '0' || *p > '9') {
|
||||
rz_strbuf_fini(&digits);
|
||||
return NULL;
|
||||
}
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
exp = exp * 10 + (*p - '0');
|
||||
p++;
|
||||
}
|
||||
if (eneg) {
|
||||
exp = -exp;
|
||||
}
|
||||
}
|
||||
// Trailing junk is not a decimal.
|
||||
while (*p == ' ' || *p == '\t') {
|
||||
p++;
|
||||
}
|
||||
if (*p != '\0') {
|
||||
rz_strbuf_fini(&digits);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *ds = rz_strbuf_drain_nofree(&digits);
|
||||
if (!ds) {
|
||||
return NULL;
|
||||
}
|
||||
// Build the mantissa from the digit string (digit-by-digit; RzNumBig
|
||||
// has no decimal-string constructor).
|
||||
RzNumBig *m = big_zero();
|
||||
RzNumBig *ten = big_from_i64(10);
|
||||
RzNumBig *tmp = rz_big_new();
|
||||
RzNumBig *dig = rz_big_new();
|
||||
if (!m || !ten || !tmp || !dig) {
|
||||
rz_big_free(m);
|
||||
rz_big_free(ten);
|
||||
rz_big_free(tmp);
|
||||
rz_big_free(dig);
|
||||
free(ds);
|
||||
return NULL;
|
||||
}
|
||||
for (char *c = ds; *c; c++) {
|
||||
rz_big_mul(tmp, m, ten);
|
||||
rz_big_from_int(dig, *c - '0');
|
||||
rz_big_add(m, tmp, dig);
|
||||
}
|
||||
free(ds);
|
||||
rz_big_free(ten);
|
||||
rz_big_free(tmp);
|
||||
rz_big_free(dig);
|
||||
|
||||
if (neg) {
|
||||
RzNumBig *zero = big_zero();
|
||||
RzNumBig *nm = rz_big_new();
|
||||
if (!zero || !nm) {
|
||||
rz_big_free(zero);
|
||||
rz_big_free(nm);
|
||||
rz_big_free(m);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_sub(nm, zero, m); // nm = -m
|
||||
rz_big_free(zero);
|
||||
rz_big_free(m);
|
||||
m = nm;
|
||||
}
|
||||
|
||||
// value = digits * 10^(exp) * 10^(-frac_len) = m * 10^(-(frac_len - exp))
|
||||
st32 scale = frac_len - exp;
|
||||
if (scale < 0) {
|
||||
// Fold the negative scale into the mantissa to keep scale >= 0.
|
||||
RzNumBig *up = big_scale_up(m, (ut32)(-scale));
|
||||
rz_big_free(m);
|
||||
if (!up) {
|
||||
return NULL;
|
||||
}
|
||||
m = up;
|
||||
scale = 0;
|
||||
}
|
||||
return bd_new(m, scale);
|
||||
}
|
||||
|
||||
// Bring a and b to a common scale; outputs new mantissas and the scale.
|
||||
static bool bd_align(const RzBigDecimal *a, const RzBigDecimal *b,
|
||||
RzNumBig **out_a, RzNumBig **out_b, st32 *out_scale) {
|
||||
st32 s = a->scale > b->scale ? a->scale : b->scale;
|
||||
RzNumBig *ma = big_scale_up(a->mantissa, (ut32)(s - a->scale));
|
||||
RzNumBig *mb = big_scale_up(b->mantissa, (ut32)(s - b->scale));
|
||||
if (!ma || !mb) {
|
||||
rz_big_free(ma);
|
||||
rz_big_free(mb);
|
||||
return false;
|
||||
}
|
||||
*out_a = ma;
|
||||
*out_b = mb;
|
||||
*out_scale = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compute the exact sum a + b.
|
||||
* \param a First addend.
|
||||
* \param b Second addend.
|
||||
* \return Result, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_add(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b) {
|
||||
rz_return_val_if_fail(a && b, NULL);
|
||||
RzNumBig *ma, *mb;
|
||||
st32 s;
|
||||
if (!bd_align(a, b, &ma, &mb, &s)) {
|
||||
return NULL;
|
||||
}
|
||||
RzNumBig *r = rz_big_new();
|
||||
if (r) {
|
||||
rz_big_add(r, ma, mb);
|
||||
}
|
||||
rz_big_free(ma);
|
||||
rz_big_free(mb);
|
||||
return bd_new(r, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compute the exact difference a - b.
|
||||
* \param a Minuend.
|
||||
* \param b Subtrahend.
|
||||
* \return Result, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_sub(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b) {
|
||||
rz_return_val_if_fail(a && b, NULL);
|
||||
RzNumBig *ma, *mb;
|
||||
st32 s;
|
||||
if (!bd_align(a, b, &ma, &mb, &s)) {
|
||||
return NULL;
|
||||
}
|
||||
RzNumBig *r = rz_big_new();
|
||||
if (r) {
|
||||
rz_big_sub(r, ma, mb);
|
||||
}
|
||||
rz_big_free(ma);
|
||||
rz_big_free(mb);
|
||||
return bd_new(r, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compute the exact product a * b.
|
||||
* \param a First factor.
|
||||
* \param b Second factor.
|
||||
* \return Result, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_mul(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b) {
|
||||
rz_return_val_if_fail(a && b, NULL);
|
||||
RzNumBig *r = rz_big_new();
|
||||
if (r) {
|
||||
rz_big_mul(r, a->mantissa, b->mantissa);
|
||||
}
|
||||
return bd_new(r, a->scale + b->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compute the exact negation -a.
|
||||
* \param a Operand to negate.
|
||||
* \return Result, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_neg(RZ_NONNULL const RzBigDecimal *a) {
|
||||
rz_return_val_if_fail(a, NULL);
|
||||
RzNumBig *zero = big_zero();
|
||||
RzNumBig *r = rz_big_new();
|
||||
if (!zero || !r) {
|
||||
rz_big_free(zero);
|
||||
rz_big_free(r);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_sub(r, zero, a->mantissa);
|
||||
rz_big_free(zero);
|
||||
return bd_new(r, a->scale);
|
||||
}
|
||||
|
||||
// Round |q| (with q's sign) to `keep` significant digits by dropping
|
||||
// `drop` low digits, half-up. Returns a new RzNumBig.
|
||||
static RzNumBig *big_round_drop(RZ_NONNULL RzNumBig *q, ut32 drop) {
|
||||
if (drop == 0) {
|
||||
return big_clone(q);
|
||||
}
|
||||
RzNumBig *zero = big_zero();
|
||||
bool neg = zero && rz_big_cmp(q, zero) < 0;
|
||||
RzNumBig *aq = big_abs(q);
|
||||
RzNumBig *divisor = big_pow10(drop);
|
||||
RzNumBig *half = big_pow10(drop - 1); // *5 below
|
||||
RzNumBig *five = big_from_i64(5);
|
||||
RzNumBig *tmp = rz_big_new();
|
||||
RzNumBig *bumped = rz_big_new();
|
||||
RzNumBig *res = rz_big_new();
|
||||
if (!zero || !aq || !divisor || !half || !five || !tmp || !bumped || !res) {
|
||||
rz_big_free(zero);
|
||||
rz_big_free(aq);
|
||||
rz_big_free(divisor);
|
||||
rz_big_free(half);
|
||||
rz_big_free(five);
|
||||
rz_big_free(tmp);
|
||||
rz_big_free(bumped);
|
||||
rz_big_free(res);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_mul(tmp, half, five); // tmp = 5 * 10^(drop-1)
|
||||
rz_big_add(bumped, aq, tmp); // |q| + half
|
||||
rz_big_div(res, bumped, divisor); // floor((|q|+half)/10^drop)
|
||||
if (neg) {
|
||||
RzNumBig *nres = rz_big_new();
|
||||
if (nres) {
|
||||
rz_big_sub(nres, zero, res);
|
||||
rz_big_free(res);
|
||||
res = nres;
|
||||
}
|
||||
}
|
||||
rz_big_free(zero);
|
||||
rz_big_free(aq);
|
||||
rz_big_free(divisor);
|
||||
rz_big_free(half);
|
||||
rz_big_free(five);
|
||||
rz_big_free(tmp);
|
||||
rz_big_free(bumped);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Divide a by b to a bounded number of significant digits.
|
||||
* \param a Dividend.
|
||||
* \param b Divisor.
|
||||
* \param precision Significant digits to keep (0 selects RZ_BIG_DECIMAL_DEFAULT_PREC).
|
||||
* \return Quotient, or NULL when b is zero or on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzBigDecimal *rz_big_decimal_div(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b, ut32 precision) {
|
||||
rz_return_val_if_fail(a && b, NULL);
|
||||
if (rz_big_is_zero(b->mantissa)) {
|
||||
return NULL; // division by zero
|
||||
}
|
||||
if (precision == 0) {
|
||||
precision = RZ_BIG_DECIMAL_DEFAULT_PREC;
|
||||
}
|
||||
if (rz_big_is_zero(a->mantissa)) {
|
||||
return rz_big_decimal_new_from_int(0);
|
||||
}
|
||||
int da = big_digit_count(a->mantissa);
|
||||
int db = big_digit_count(b->mantissa);
|
||||
if (da < 0 || db < 0) {
|
||||
return NULL;
|
||||
}
|
||||
// Choose a shift so the integer quotient carries ~precision+1 digits.
|
||||
st32 shift = (st32)precision + 1 - da + db;
|
||||
if (shift < 0) {
|
||||
shift = 0;
|
||||
}
|
||||
RzNumBig *num = big_scale_up(a->mantissa, (ut32)shift);
|
||||
RzNumBig *q = rz_big_new();
|
||||
if (!num || !q) {
|
||||
rz_big_free(num);
|
||||
rz_big_free(q);
|
||||
return NULL;
|
||||
}
|
||||
rz_big_div(q, num, b->mantissa); // truncated toward zero
|
||||
rz_big_free(num);
|
||||
|
||||
st32 result_scale = shift + (st32)a->scale - (st32)b->scale;
|
||||
int dq = big_digit_count(q);
|
||||
if (dq < 0) {
|
||||
rz_big_free(q);
|
||||
return NULL;
|
||||
}
|
||||
if ((ut32)dq > precision) {
|
||||
ut32 drop = (ut32)dq - precision;
|
||||
RzNumBig *rounded = big_round_drop(q, drop);
|
||||
rz_big_free(q);
|
||||
if (!rounded) {
|
||||
return NULL;
|
||||
}
|
||||
q = rounded;
|
||||
result_scale -= (st32)drop;
|
||||
}
|
||||
if (result_scale < 0) {
|
||||
RzNumBig *up = big_scale_up(q, (ut32)(-result_scale));
|
||||
rz_big_free(q);
|
||||
if (!up) {
|
||||
return NULL;
|
||||
}
|
||||
q = up;
|
||||
result_scale = 0;
|
||||
}
|
||||
return bd_new(q, result_scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compare two decimals by value.
|
||||
* \param a First operand.
|
||||
* \param b Second operand.
|
||||
* \return <0 if a < b, 0 if equal, >0 if a > b.
|
||||
*/
|
||||
RZ_API int rz_big_decimal_cmp(RZ_NONNULL const RzBigDecimal *a, RZ_NONNULL const RzBigDecimal *b) {
|
||||
rz_return_val_if_fail(a && b, 0);
|
||||
RzNumBig *ma, *mb;
|
||||
st32 s;
|
||||
if (!bd_align(a, b, &ma, &mb, &s)) {
|
||||
return 0;
|
||||
}
|
||||
int c = rz_big_cmp(ma, mb);
|
||||
rz_big_free(ma);
|
||||
rz_big_free(mb);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Test whether a decimal is exactly zero.
|
||||
* \param d Decimal to test.
|
||||
* \return true when the value is zero at any scale.
|
||||
*/
|
||||
RZ_API bool rz_big_decimal_is_zero(RZ_NONNULL const RzBigDecimal *d) {
|
||||
rz_return_val_if_fail(d, true);
|
||||
return rz_big_is_zero(d->mantissa) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Render a decimal to a canonical string (no exponent).
|
||||
* \param d Decimal to format.
|
||||
* \return String, or NULL on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_big_decimal_to_str(RZ_NONNULL const RzBigDecimal *d) {
|
||||
rz_return_val_if_fail(d, NULL);
|
||||
char *digits = rz_big_to_decstr(d->mantissa);
|
||||
if (!digits) {
|
||||
return NULL;
|
||||
}
|
||||
if (d->scale == 0) {
|
||||
return digits;
|
||||
}
|
||||
bool neg = (digits[0] == '-');
|
||||
const char *ds = neg ? digits + 1 : digits;
|
||||
size_t dlen = strlen(ds);
|
||||
RzStrBuf sb;
|
||||
rz_strbuf_init(&sb);
|
||||
if (neg) {
|
||||
rz_strbuf_append(&sb, "-");
|
||||
}
|
||||
if (dlen > (size_t)d->scale) {
|
||||
size_t intlen = dlen - (size_t)d->scale;
|
||||
rz_strbuf_append_n(&sb, ds, intlen);
|
||||
rz_strbuf_append(&sb, ".");
|
||||
rz_strbuf_append(&sb, ds + intlen);
|
||||
} else {
|
||||
rz_strbuf_append(&sb, "0.");
|
||||
for (size_t i = 0; i < (size_t)d->scale - dlen; i++) {
|
||||
rz_strbuf_append(&sb, "0");
|
||||
}
|
||||
rz_strbuf_append(&sb, ds);
|
||||
}
|
||||
free(digits);
|
||||
char *out = rz_strbuf_drain_nofree(&sb);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
// Strip trailing fractional zeros (and a dangling point) for display.
|
||||
size_t len = strlen(out);
|
||||
while (len && out[len - 1] == '0') {
|
||||
out[--len] = '\0';
|
||||
}
|
||||
if (len && out[len - 1] == '.') {
|
||||
out[--len] = '\0';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Truncate a decimal towards zero to a ut64.
|
||||
* \param d Decimal to convert.
|
||||
* \return The low 64 bits of the integer part.
|
||||
*/
|
||||
RZ_API ut64 rz_big_decimal_to_ut64(RZ_NONNULL const RzBigDecimal *d) {
|
||||
rz_return_val_if_fail(d, 0);
|
||||
if (d->scale == 0) {
|
||||
return (ut64)rz_big_to_int(d->mantissa);
|
||||
}
|
||||
RzNumBig *pow = big_pow10((ut32)d->scale);
|
||||
RzNumBig *q = rz_big_new();
|
||||
if (!pow || !q) {
|
||||
rz_big_free(pow);
|
||||
rz_big_free(q);
|
||||
return 0;
|
||||
}
|
||||
rz_big_div(q, d->mantissa, pow); // truncate toward zero
|
||||
ut64 r = (ut64)rz_big_to_int(q);
|
||||
rz_big_free(pow);
|
||||
rz_big_free(q);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Convert a decimal to the nearest double.
|
||||
* \param d Decimal to convert.
|
||||
* \return Nearest double (+/-inf when out of range).
|
||||
*/
|
||||
RZ_API double rz_big_decimal_to_double(RZ_NONNULL const RzBigDecimal *d) {
|
||||
rz_return_val_if_fail(d, 0.0);
|
||||
char *s = rz_big_decimal_to_str(d);
|
||||
if (!s) {
|
||||
return 0.0;
|
||||
}
|
||||
double r = strtod(s, NULL);
|
||||
free(s);
|
||||
return r;
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2012-2021 pancake <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/* ported to C by pancake for r2 in 2012-2017 */
|
||||
// TODO: integrate floating point support
|
||||
// TODO: do not use global variables
|
||||
/*
|
||||
Reference Chapter 6:
|
||||
"The C++ Programming Language", Special Edition.
|
||||
Bjarne Stroustrup,Addison-Wesley Pub Co; 3 edition (February 15, 2000)
|
||||
ISBN: 0201700735
|
||||
*/
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* accessors */
|
||||
static inline RzNumCalcValue Nset(ut64 v) {
|
||||
RzNumCalcValue n;
|
||||
n.d = (double)v;
|
||||
n.n = v;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nsetf(double v) {
|
||||
RzNumCalcValue n;
|
||||
n.d = v;
|
||||
n.n = (ut64)v;
|
||||
return n;
|
||||
}
|
||||
// UNUSED static inline RzNumCalcValue Naddf(RzNumCalcValue n, double v) { n.d += v; n.n += (ut64)v; return n; }
|
||||
static inline RzNumCalcValue Naddi(RzNumCalcValue n, ut64 v) {
|
||||
n.d += (double)v;
|
||||
n.n += v;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nsubi(RzNumCalcValue n, ut64 v) {
|
||||
n.d -= (double)v;
|
||||
n.n -= v;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nneg(RzNumCalcValue n) {
|
||||
n.n = ~n.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Norr(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d = v.d;
|
||||
n.n |= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nxor(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d = v.d;
|
||||
n.n ^= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nlt(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d = v.d;
|
||||
n.n = n.n < v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Ngt(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d = v.d;
|
||||
n.n = n.n > v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nand(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d = v.d;
|
||||
n.n &= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nadd(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d += v.d;
|
||||
n.n += v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nsub(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d -= v.d;
|
||||
n.n -= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nmul(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d *= v.d;
|
||||
n.n *= v.n;
|
||||
return n;
|
||||
}
|
||||
|
||||
static inline RzNumCalcValue Nshl(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d += v.d;
|
||||
n.n <<= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nshr(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d += v.d;
|
||||
n.n >>= v.n;
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nrol(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d += v.d;
|
||||
n.n = (n.n << v.n) | (n.n >> (sizeof(n.n) * 8 - v.n));
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nror(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
n.d += v.d;
|
||||
n.n = (n.n >> v.n) | (n.n << (sizeof(n.n) * 8 - v.n));
|
||||
return n;
|
||||
}
|
||||
static inline RzNumCalcValue Nmod(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
if (v.d) {
|
||||
n.d = (n.d - (n.d / v.d));
|
||||
} else {
|
||||
n.d = 0;
|
||||
}
|
||||
if (v.n) {
|
||||
n.n %= v.n;
|
||||
} else {
|
||||
n.n = 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static inline RzNumCalcValue Ndiv(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
if (v.d) {
|
||||
n.d /= v.d;
|
||||
} else {
|
||||
n.d = 0;
|
||||
}
|
||||
if (v.n) {
|
||||
n.n /= v.n;
|
||||
} else {
|
||||
n.n = 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static inline RzNumCalcValue Nexp(RzNumCalcValue n, RzNumCalcValue v) {
|
||||
RzNumCalcValue exp_n = n;
|
||||
if (v.d - (st64)v.n) {
|
||||
RZ_LOG_WARN("floating point powers not yet supported\n");
|
||||
}
|
||||
if ((st64)v.n > 0) {
|
||||
for (st64 i = 1; i < (st64)v.n; i++) {
|
||||
n = Nmul(exp_n, n);
|
||||
}
|
||||
} else if ((st64)v.n < 0) {
|
||||
for (st64 i = 1; i > (st64)v.n; i--) {
|
||||
n = Ndiv(n, exp_n);
|
||||
}
|
||||
} else {
|
||||
n = Ndiv(n, exp_n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static RzNumCalcValue expr(RzNum *, RzNumCalc *, int);
|
||||
static RzNumCalcValue term(RzNum *, RzNumCalc *, int);
|
||||
static void error(RzNum *, RzNumCalc *, const char *);
|
||||
static RzNumCalcValue prim(RzNum *, RzNumCalc *, int);
|
||||
static RzNumCalcToken get_token(RzNum *, RzNumCalc *);
|
||||
|
||||
static void error(RzNum *num, RzNumCalc *nc, const char *s) {
|
||||
nc->errors++;
|
||||
nc->calc_err = s;
|
||||
// fprintf (stderr, "error: %s\n", s);
|
||||
}
|
||||
|
||||
static RzNumCalcValue expr(RzNum *num, RzNumCalc *nc, int get) {
|
||||
RzNumCalcValue left = term(num, nc, get);
|
||||
for (;;) {
|
||||
switch (nc->curr_tok) {
|
||||
case RNCSHL: left = Nshl(left, term(num, nc, 1)); break;
|
||||
case RNCSHR: left = Nshr(left, term(num, nc, 1)); break;
|
||||
case RNCROL: left = Nrol(left, term(num, nc, 1)); break;
|
||||
case RNCROR: left = Nror(left, term(num, nc, 1)); break;
|
||||
case RNCPLUS: left = Nadd(left, term(num, nc, 1)); break;
|
||||
case RNCMINUS: left = Nsub(left, term(num, nc, 1)); break;
|
||||
case RNCXOR: left = Nxor(left, term(num, nc, 1)); break;
|
||||
case RNCORR: left = Norr(left, term(num, nc, 1)); break;
|
||||
case RNCAND: left = Nand(left, term(num, nc, 1)); break;
|
||||
case RNCLT: left = Nlt(left, term(num, nc, 1)); break;
|
||||
case RNCGT: left = Ngt(left, term(num, nc, 1)); break;
|
||||
default:
|
||||
return left;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
static RzNumCalcValue term(RzNum *num, RzNumCalc *nc, int get) {
|
||||
RzNumCalcValue left = prim(num, nc, get);
|
||||
for (;;) {
|
||||
if (nc->curr_tok == RNCMUL) {
|
||||
left = Nmul(left, prim(num, nc, 1));
|
||||
} else if (nc->curr_tok == RNCMOD) {
|
||||
RzNumCalcValue d = prim(num, nc, 1);
|
||||
if (!d.d) {
|
||||
// error (num, nc, "divide by 0");
|
||||
return d;
|
||||
}
|
||||
left = Nmod(left, d);
|
||||
} else if (nc->curr_tok == RNCDIV) {
|
||||
RzNumCalcValue d = prim(num, nc, 1);
|
||||
if (num != NULL && (!d.d || !d.n)) {
|
||||
num->dbz = 1;
|
||||
return d;
|
||||
}
|
||||
left = Ndiv(left, d);
|
||||
} else if (nc->curr_tok == RNCEXP) {
|
||||
left = Nexp(left, prim(num, nc, 1));
|
||||
} else {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RzNumCalcValue prim(RzNum *num, RzNumCalc *nc, int get) {
|
||||
RzNumCalcValue v = { 0 };
|
||||
if (get) {
|
||||
get_token(num, nc);
|
||||
}
|
||||
switch (nc->curr_tok) {
|
||||
case RNCNUMBER:
|
||||
v = nc->number_value;
|
||||
get_token(num, nc);
|
||||
return v;
|
||||
case RNCNAME:
|
||||
// fprintf (stderr, "error: unknown keyword (%s)\n", nc->string_value);
|
||||
// double& v = table[nc->string_value];
|
||||
rz_str_trim(nc->string_value);
|
||||
v = Nset(rz_num_get(num, nc->string_value));
|
||||
get_token(num, nc);
|
||||
if (nc->curr_tok == RNCASSIGN) {
|
||||
v = expr(num, nc, 1);
|
||||
}
|
||||
if (nc->curr_tok == RNCINC) {
|
||||
Naddi(v, 1);
|
||||
}
|
||||
if (nc->curr_tok == RNCDEC) {
|
||||
Nsubi(v, 1);
|
||||
}
|
||||
return v;
|
||||
case RNCNEG:
|
||||
get_token(num, nc);
|
||||
return Nneg(nc->number_value); // prim (num, nc, 1), 1);
|
||||
case RNCINC:
|
||||
return Naddi(prim(num, nc, 1), 1);
|
||||
case RNCDEC:
|
||||
return Naddi(prim(num, nc, 1), -1);
|
||||
case RNCORR:
|
||||
return Norr(v, prim(num, nc, 1));
|
||||
case RNCMINUS:
|
||||
return Nsub(v, prim(num, nc, 1));
|
||||
case RNCLEFTP:
|
||||
v = expr(num, nc, 1);
|
||||
if (nc->curr_tok == RNCRIGHTP) {
|
||||
get_token(num, nc);
|
||||
} else {
|
||||
error(num, nc, " ')' expected");
|
||||
}
|
||||
return v;
|
||||
case RNCLT:
|
||||
case RNCGT:
|
||||
case RNCEND:
|
||||
case RNCXOR:
|
||||
case RNCAND:
|
||||
case RNCPLUS:
|
||||
case RNCMOD:
|
||||
case RNCMUL:
|
||||
case RNCDIV:
|
||||
case RNCEXP:
|
||||
case RNCPRINT:
|
||||
case RNCASSIGN:
|
||||
case RNCRIGHTP:
|
||||
case RNCSHL:
|
||||
case RNCSHR:
|
||||
case RNCROL:
|
||||
case RNCROR:
|
||||
return v;
|
||||
// default: error (num, nc, "primary expected");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static void cin_putback(RzNum *num, RzNumCalc *nc, char c) {
|
||||
nc->oc = c;
|
||||
}
|
||||
|
||||
RZ_API const char *rz_num_calc_index(RzNum *num, const char *p) {
|
||||
if (!num) {
|
||||
return NULL;
|
||||
}
|
||||
if (p) {
|
||||
num->nc.calc_buf = p;
|
||||
num->nc.calc_len = strlen(p);
|
||||
num->nc.calc_i = 0;
|
||||
}
|
||||
return num->nc.calc_buf + num->nc.calc_i;
|
||||
}
|
||||
|
||||
static int cin_get(RzNum *num, RzNumCalc *nc, char *c) {
|
||||
if (nc->oc) {
|
||||
*c = nc->oc;
|
||||
nc->oc = 0;
|
||||
} else {
|
||||
if (!nc->calc_buf || !*nc->calc_buf) {
|
||||
return 0;
|
||||
}
|
||||
*c = nc->calc_buf[nc->calc_i];
|
||||
if (*c) {
|
||||
nc->calc_i++;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cin_get_num(RzNum *num, RzNumCalc *nc, RzNumCalcValue *n) {
|
||||
double d;
|
||||
char str[RZ_NUMCALC_STRSZ + 1]; // TODO: move into the heap?
|
||||
int i = 0;
|
||||
char c;
|
||||
str[0] = 0;
|
||||
while (cin_get(num, nc, &c)) {
|
||||
if (c != '_' && c != ':' && c != '.' && !isalnum((ut8)c)) {
|
||||
cin_putback(num, nc, c);
|
||||
break;
|
||||
}
|
||||
if (i < RZ_NUMCALC_STRSZ) {
|
||||
str[i++] = c;
|
||||
}
|
||||
}
|
||||
str[i] = 0;
|
||||
*n = Nset(rz_num_get(num, str));
|
||||
if (IS_DIGIT(*str) && strchr(str, '.')) {
|
||||
if (sscanf(str, "%lf", &d) < 1) {
|
||||
return 0;
|
||||
}
|
||||
if (n->n < d) {
|
||||
*n = Nsetf(d);
|
||||
}
|
||||
n->d = d;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static RzNumCalcToken get_token(RzNum *num, RzNumCalc *nc) {
|
||||
char ch = 0, c = 0;
|
||||
|
||||
do {
|
||||
if (!cin_get(num, nc, &ch)) {
|
||||
return nc->curr_tok = RNCEND;
|
||||
}
|
||||
} while (ch != '\n' && isspace((ut8)ch));
|
||||
|
||||
switch (ch) {
|
||||
case 0:
|
||||
case ';':
|
||||
case '\n':
|
||||
return nc->curr_tok = RNCEND;
|
||||
case '+': // added for ++name and name++
|
||||
if (cin_get(num, nc, &c) && c == '+') {
|
||||
return nc->curr_tok = RNCINC;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = (RzNumCalcToken)ch;
|
||||
// negate hack
|
||||
case '~':
|
||||
if (cin_get(num, nc, &c) && c == '-') {
|
||||
return nc->curr_tok = RNCNEG;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = (RzNumCalcToken)ch;
|
||||
// negative number
|
||||
case '-':
|
||||
if (cin_get(num, nc, &c) && c == '-') {
|
||||
return nc->curr_tok = RNCDEC;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = (RzNumCalcToken)ch;
|
||||
case '<':
|
||||
if (cin_get(num, nc, &c) && c == '<') { // "<<" = shift left
|
||||
if (cin_get(num, nc, &c) && c == '<') { // "<<<" = rotate left
|
||||
return nc->curr_tok = RNCROL;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = RNCSHL;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = RNCLT;
|
||||
case '>':
|
||||
if (cin_get(num, nc, &c) && c == '>') {
|
||||
if (cin_get(num, nc, &c) && c == '>') {
|
||||
return nc->curr_tok = RNCROR;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = RNCSHR;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = RNCGT;
|
||||
case '^':
|
||||
case '&':
|
||||
case '|':
|
||||
case '%':
|
||||
case '/':
|
||||
case '(':
|
||||
case ')':
|
||||
case '=':
|
||||
return nc->curr_tok = (RzNumCalcToken)ch;
|
||||
case '*':
|
||||
if (cin_get(num, nc, &c) && c == '*') {
|
||||
return nc->curr_tok = RNCEXP;
|
||||
}
|
||||
cin_putback(num, nc, c);
|
||||
return nc->curr_tok = RNCMUL;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '.':
|
||||
cin_putback(num, nc, ch);
|
||||
if (!cin_get_num(num, nc, &nc->number_value)) {
|
||||
error(num, nc, "invalid number conversion");
|
||||
return 1;
|
||||
}
|
||||
return nc->curr_tok = RNCNUMBER;
|
||||
|
||||
#define isvalidchar(x) \
|
||||
(isalnum(x) || (x) == ':' || (x) == '$' || (x) == '.' || (x) == '_' || (x) == '?' || (x) == '\\' || (x) == ' ' || (x) == '[' || (x) == ']' || (x) == '}' || (x) == '{' || ((x) >= '0' && (x) <= '9'))
|
||||
|
||||
default: {
|
||||
int i = 0;
|
||||
#define stringValueAppend(x) \
|
||||
{ \
|
||||
const size_t max = sizeof(nc->string_value) - 1; \
|
||||
if (i < max) \
|
||||
nc->string_value[i++] = x; \
|
||||
else \
|
||||
nc->string_value[max] = 0; \
|
||||
}
|
||||
stringValueAppend(ch);
|
||||
if (ch == '[') {
|
||||
while (cin_get(num, nc, &ch) && ch != ']') {
|
||||
if (i > RZ_NUMCALC_STRSZ - 1) {
|
||||
error(num, nc, "string too long");
|
||||
return 0;
|
||||
}
|
||||
stringValueAppend(ch);
|
||||
}
|
||||
stringValueAppend(ch);
|
||||
} else {
|
||||
while (cin_get(num, nc, &ch) && isvalidchar((unsigned char)ch)) {
|
||||
if (i >= RZ_NUMCALC_STRSZ) {
|
||||
error(num, nc, "string too long");
|
||||
return 0;
|
||||
}
|
||||
stringValueAppend(ch);
|
||||
}
|
||||
}
|
||||
stringValueAppend(0);
|
||||
if (ch != '\'') {
|
||||
cin_putback(num, nc, ch);
|
||||
}
|
||||
return nc->curr_tok = RNCNAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void load_token(RzNum *num, RzNumCalc *nc, const char *s) {
|
||||
nc->calc_i = 0;
|
||||
nc->calc_len = strlen(s);
|
||||
nc->calc_buf = s;
|
||||
nc->calc_err = NULL;
|
||||
}
|
||||
|
||||
RZ_API ut64 rz_num_calc(RzNum *num, const char *str, const char **err) {
|
||||
RzNumCalcValue n;
|
||||
RzNumCalc *nc, nc_local;
|
||||
if (!str || !*str) {
|
||||
return 0LL;
|
||||
}
|
||||
if (num) {
|
||||
nc = &num->nc;
|
||||
num->dbz = 0;
|
||||
} else {
|
||||
nc = &nc_local;
|
||||
}
|
||||
/* init */
|
||||
nc->curr_tok = RNCPRINT;
|
||||
nc->number_value.d = 0.0;
|
||||
nc->number_value.n = 0LL;
|
||||
nc->errors = 0;
|
||||
nc->oc = 0;
|
||||
nc->calc_err = NULL;
|
||||
nc->calc_i = 0;
|
||||
nc->calc_len = 0;
|
||||
nc->calc_buf = NULL;
|
||||
nc->under_calc = true;
|
||||
|
||||
load_token(num, nc, str);
|
||||
get_token(num, nc);
|
||||
n = expr(num, nc, 0);
|
||||
if (err) {
|
||||
*err = nc->calc_err;
|
||||
}
|
||||
if (num) {
|
||||
num->fvalue = n.d;
|
||||
}
|
||||
nc->under_calc = false;
|
||||
return n.n;
|
||||
}
|
||||
+7
-2
@@ -6,6 +6,7 @@
|
||||
#include <rz_util/rz_itv.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_regex.h>
|
||||
#include <rz_util/rz_str.h>
|
||||
|
||||
/**
|
||||
* \brief Parses a string to a bounded interval.
|
||||
@@ -74,13 +75,17 @@ RZ_API bool rz_itv_str_to_bounded_itv_ut64(RZ_NONNULL const char *itv_str, RZ_OU
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
ut64 a = rz_num_math(NULL, itv_str + match->start);
|
||||
char *a_str = rz_str_ndup(itv_str + match->start, match->len);
|
||||
ut64 a = rz_num_math(NULL, a_str);
|
||||
free(a_str);
|
||||
|
||||
if (!(match = rz_pvector_at(matches, b_group))) {
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
ut64 b = rz_num_math(NULL, itv_str + match->start);
|
||||
char *b_str = rz_str_ndup(itv_str + match->start, match->len);
|
||||
ut64 b = rz_num_math(NULL, b_str);
|
||||
free(b_str);
|
||||
|
||||
if (a > b) {
|
||||
RZ_LOG_ERROR("a > b is not defined.\n");
|
||||
|
||||
+27
-4
@@ -11,7 +11,6 @@ rz_util_common_sources = [
|
||||
'bits.c',
|
||||
'bitvector.c',
|
||||
'buf.c',
|
||||
'calc.c',
|
||||
'chmod.c',
|
||||
'compression.c',
|
||||
'debruijn.c',
|
||||
@@ -103,7 +102,26 @@ rz_util_common_sources = [
|
||||
]
|
||||
rz_util_sources = rz_util_common_sources
|
||||
|
||||
rz_util_deps = [ldl, lrt, mth, th, utl, pcre2_dep, softfloat_dep] + platform_deps
|
||||
# The RzNum tree-sitter evaluator. It needs the tree-sitter runtime and
|
||||
# the generated math grammar, so it is kept apart from the common
|
||||
# sources: the regular library builds it (rz_util_sources below), but
|
||||
# the build-machine variant used only for sdb_gen does not (sdb_gen
|
||||
# never evaluates expressions), which keeps tree-sitter out of the
|
||||
# native toolchain. See the native block for the small shim that
|
||||
# satisfies unum.c's two references in that configuration.
|
||||
rz_util_evaluator_sources = [
|
||||
'num/parser.c',
|
||||
'num/evaluator.c',
|
||||
'num/value_print.c',
|
||||
'num/expr.c',
|
||||
]
|
||||
rz_util_sources += rz_util_evaluator_sources
|
||||
|
||||
# tree-sitter and the RzNum math grammar are pulled in so that the
|
||||
# rz_num_math_value() evaluator (introduced in a subsequent commit)
|
||||
# can drive a real grammar-based parser.
|
||||
math_parser_dep = dependency('rizin-math-parser')
|
||||
rz_util_deps = [ldl, lrt, mth, th, utl, pcre2_dep, softfloat_dep, math_parser_dep] + platform_deps
|
||||
rz_util_public_deps = []
|
||||
rz_util_public_cmake_deps = []
|
||||
if zlib_dep.found()
|
||||
@@ -130,7 +148,7 @@ if sys_openssl.found()
|
||||
rz_util_public_cmake_deps += ['OpenSSL|OpenSSL::Crypto']
|
||||
rz_util_sources += ['big-ssl.c']
|
||||
else
|
||||
rz_util_sources += ['big.c']
|
||||
rz_util_sources += ['big.c', 'big_decimal.c']
|
||||
endif
|
||||
|
||||
rz_util_includes = [platform_inc, include_directories('sdb/src')]
|
||||
@@ -187,7 +205,12 @@ if meson.is_cross_build()
|
||||
install_dir: rizin_incdir
|
||||
)
|
||||
rz_util_native_includes = [include_directories('.'), rz_util_includes]
|
||||
rz_util_native_sources = rz_util_common_sources + ['big.c']
|
||||
# No num/* here: the evaluator is replaced by native_shim.c,
|
||||
# which provides the two entry points unum.c links against without
|
||||
# pulling in tree-sitter. sdb_gen does not evaluate expressions, so the
|
||||
# shim's degenerate "every expression is 0" behaviour is all the native
|
||||
# build ever needs (there is no longer a legacy calculator to fall back on).
|
||||
rz_util_native_sources = rz_util_common_sources + ['big.c', 'big_decimal.c', 'num/native_shim.c']
|
||||
|
||||
rz_util_native = static_library('rz_util_native', rz_util_native_sources, libsdb_sources,
|
||||
include_directories: rz_util_native_includes,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,515 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Build an RzNumExpression tree from an RzNum expression string.
|
||||
*
|
||||
* Walks the tree-sitter parse tree and renders it as a self-contained
|
||||
* RzNumExpression - one that keeps no tree-sitter references - which
|
||||
* librz_il converts into an RzILOpPure. The structural shape mirrors what
|
||||
* the RzIL pure form expects: binary and unary operators become typed
|
||||
* BINOP / UNOP nodes, ternaries become ITE nodes, numeric literals become
|
||||
* bit-vector (or float) constants.
|
||||
*
|
||||
* Constructs that have no structural RzIL counterpart - function calls,
|
||||
* the typed-address dereference, host variables, exponent / logarithm,
|
||||
* and ';'-separated sequences - are evaluated to a concrete value first
|
||||
* (through \p num and \p opts) and embedded as a constant leaf, so the
|
||||
* resulting tree is fully grounded and carries no tree-sitter state.
|
||||
*
|
||||
* Keeping this in librz_util means the tree-sitter dependency stays here;
|
||||
* the librz_il converter only ever sees RzNumExpression.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_num_expression.h>
|
||||
#include <rz_util/rz_str.h>
|
||||
#include <rz_util/rz_bitvector.h>
|
||||
#include <tree_sitter/api.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
// Default bit-vector width for a plain numeric literal that carries no
|
||||
// explicit width suffix. RzNum's ut64 domain is 64-bit.
|
||||
#define EXPR_DEFAULT_WIDTH 64
|
||||
|
||||
typedef struct {
|
||||
RzNum *num;
|
||||
const char *src;
|
||||
const RzNumMathOptions *opts;
|
||||
char *err; // first error encountered
|
||||
} BuildCtx;
|
||||
|
||||
#define EXPR_FIELD(node, name) \
|
||||
ts_node_child_by_field_name((node), (name), (uint32_t)(sizeof(name) - 1))
|
||||
|
||||
static RzNumExpression *build_node(BuildCtx *c, TSNode n);
|
||||
|
||||
/**
|
||||
* \brief Recursively free an RzNumExpression and all of its children.
|
||||
* \param e Expression tree to free; NULL is ignored.
|
||||
*/
|
||||
RZ_API void rz_num_expression_free(RZ_NULLABLE RzNumExpression *e) {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
rz_bv_free(e->bv);
|
||||
for (size_t i = 0; i < RZ_ARRAY_SIZE(e->operands); i++) {
|
||||
rz_num_expression_free(e->operands[i]);
|
||||
}
|
||||
free(e);
|
||||
}
|
||||
|
||||
static void build_set_err(BuildCtx *c, const char *fmt, ...) {
|
||||
if (c->err) {
|
||||
return; // keep the first error
|
||||
}
|
||||
RzStrBuf sb;
|
||||
rz_strbuf_init(&sb);
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
rz_strbuf_vappendf(&sb, fmt, ap);
|
||||
va_end(ap);
|
||||
c->err = rz_strbuf_drain_nofree(&sb);
|
||||
}
|
||||
|
||||
// --- node constructors ---
|
||||
|
||||
static RzNumExpression *expr_bv(RZ_OWN RzBitVector *bv) {
|
||||
if (!bv) {
|
||||
return NULL;
|
||||
}
|
||||
RzNumExpression *e = RZ_NEW0(RzNumExpression);
|
||||
if (!e) {
|
||||
rz_bv_free(bv);
|
||||
return NULL;
|
||||
}
|
||||
e->kind = RZ_NUM_EXPRESSION_KIND_BV;
|
||||
e->bv = bv;
|
||||
return e;
|
||||
}
|
||||
|
||||
static RzNumExpression *expr_float(double d) {
|
||||
RzNumExpression *e = RZ_NEW0(RzNumExpression);
|
||||
if (e) {
|
||||
e->kind = RZ_NUM_EXPRESSION_KIND_FLOAT;
|
||||
e->f = d;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static RzNumExpression *expr_binop(RzNumExpressionOp op, RZ_OWN RzNumExpression *l, RZ_OWN RzNumExpression *r) {
|
||||
if (!l || !r) {
|
||||
rz_num_expression_free(l);
|
||||
rz_num_expression_free(r);
|
||||
return NULL;
|
||||
}
|
||||
RzNumExpression *e = RZ_NEW0(RzNumExpression);
|
||||
if (!e) {
|
||||
rz_num_expression_free(l);
|
||||
rz_num_expression_free(r);
|
||||
return NULL;
|
||||
}
|
||||
e->kind = RZ_NUM_EXPRESSION_KIND_BINOP;
|
||||
e->op = op;
|
||||
e->operands[0] = l;
|
||||
e->operands[1] = r;
|
||||
return e;
|
||||
}
|
||||
|
||||
static RzNumExpression *expr_unop(RzNumExpressionOp op, RZ_OWN RzNumExpression *x) {
|
||||
if (!x) {
|
||||
return NULL;
|
||||
}
|
||||
RzNumExpression *e = RZ_NEW0(RzNumExpression);
|
||||
if (!e) {
|
||||
rz_num_expression_free(x);
|
||||
return NULL;
|
||||
}
|
||||
e->kind = RZ_NUM_EXPRESSION_KIND_UNOP;
|
||||
e->op = op;
|
||||
e->operands[0] = x;
|
||||
return e;
|
||||
}
|
||||
|
||||
static RzNumExpression *expr_ite(RZ_OWN RzNumExpression *cond, RZ_OWN RzNumExpression *t, RZ_OWN RzNumExpression *f) {
|
||||
if (!cond || !t || !f) {
|
||||
rz_num_expression_free(cond);
|
||||
rz_num_expression_free(t);
|
||||
rz_num_expression_free(f);
|
||||
return NULL;
|
||||
}
|
||||
RzNumExpression *e = RZ_NEW0(RzNumExpression);
|
||||
if (!e) {
|
||||
rz_num_expression_free(cond);
|
||||
rz_num_expression_free(t);
|
||||
rz_num_expression_free(f);
|
||||
return NULL;
|
||||
}
|
||||
e->kind = RZ_NUM_EXPRESSION_KIND_ITE;
|
||||
e->operands[0] = cond;
|
||||
e->operands[1] = t;
|
||||
e->operands[2] = f;
|
||||
return e;
|
||||
}
|
||||
|
||||
// --- float detection (mirrors the kind-promotion the evaluator does) ---
|
||||
|
||||
static char *node_text(TSNode n, const char *src) {
|
||||
ut32 a = ts_node_start_byte(n);
|
||||
ut32 b = ts_node_end_byte(n);
|
||||
if (b < a) {
|
||||
return NULL;
|
||||
}
|
||||
ut32 len = b - a;
|
||||
char *s = malloc(len + 1);
|
||||
if (!s) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(s, src + a, len);
|
||||
s[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
// True if a numeric literal node's text is a float (a '.' or a non-hex
|
||||
// 'e'/'E' exponent). A hex literal like "0xea" contains 'e' but is not.
|
||||
static bool number_node_is_float(const char *src, TSNode n) {
|
||||
TSNode v = n;
|
||||
if (ts_node_symbol(n) == rz_num_parse_syms()->sym_number) {
|
||||
v = ts_node_named_child(n, 0);
|
||||
if (ts_node_is_null(v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ut32 start = ts_node_start_byte(v), end = ts_node_end_byte(v);
|
||||
for (ut32 i = start; i < end; i++) {
|
||||
char ch = src[i];
|
||||
if (ch == '.' || ch == 'e' || ch == 'E') {
|
||||
if (ch == 'e' || ch == 'E') {
|
||||
if (end - start >= 2 && src[start] == '0' &&
|
||||
(src[start + 1] == 'x' || src[start + 1] == 'X')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Conservative over-approximation of "this sub-expression is a float".
|
||||
static bool is_float_subexpr(const char *src, TSNode n) {
|
||||
const RzNumSymCache *s = rz_num_parse_syms();
|
||||
TSSymbol sym = ts_node_symbol(n);
|
||||
if (sym == s->sym_number) {
|
||||
return number_node_is_float(src, n);
|
||||
}
|
||||
// Typed reads are float only with a wired IO callback, which we
|
||||
// cannot know statically; classify as non-float so a typed read
|
||||
// mixed with a float literal is grounded rather than mis-lifted.
|
||||
if (sym == s->sym_address_typed) {
|
||||
return false;
|
||||
}
|
||||
if (sym == s->sym_expression || sym == s->sym_source_file ||
|
||||
sym == s->sym_parenthesized_expression || sym == s->sym_argument) {
|
||||
TSNode child = ts_node_named_child(n, 0);
|
||||
return !ts_node_is_null(child) && is_float_subexpr(src, child);
|
||||
}
|
||||
if (sym == s->sym_unary_minus || sym == s->sym_unary_plus) {
|
||||
TSNode operand = EXPR_FIELD(n, "right");
|
||||
return !ts_node_is_null(operand) && is_float_subexpr(src, operand);
|
||||
}
|
||||
if (sym == s->sym_sum || sym == s->sym_subtraction ||
|
||||
sym == s->sym_product || sym == s->sym_division ||
|
||||
sym == s->sym_modulo) {
|
||||
TSNode l = EXPR_FIELD(n, "left");
|
||||
TSNode r = EXPR_FIELD(n, "right");
|
||||
return (!ts_node_is_null(l) && is_float_subexpr(src, l)) ||
|
||||
(!ts_node_is_null(r) && is_float_subexpr(src, r));
|
||||
}
|
||||
if (sym == s->sym_conditional) {
|
||||
TSNode then_n = EXPR_FIELD(n, "consequence");
|
||||
TSNode else_n = EXPR_FIELD(n, "alternative");
|
||||
return !ts_node_is_null(then_n) && !ts_node_is_null(else_n) &&
|
||||
is_float_subexpr(src, then_n) && is_float_subexpr(src, else_n);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- operator maps (node type -> RzNumExpressionOp) ---
|
||||
|
||||
static bool binop_of(TSSymbol sym, RzNumExpressionOp *out) {
|
||||
const RzNumSymCache *s = rz_num_parse_syms();
|
||||
RzNumExpressionOp op;
|
||||
if (sym == s->sym_sum) {
|
||||
op = RZ_NUM_EXPRESSION_OP_ADD;
|
||||
} else if (sym == s->sym_subtraction) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SUB;
|
||||
} else if (sym == s->sym_product) {
|
||||
op = RZ_NUM_EXPRESSION_OP_MUL;
|
||||
} else if (sym == s->sym_division) {
|
||||
op = RZ_NUM_EXPRESSION_OP_DIV;
|
||||
} else if (sym == s->sym_signed_division) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SDIV;
|
||||
} else if (sym == s->sym_modulo) {
|
||||
op = RZ_NUM_EXPRESSION_OP_MOD;
|
||||
} else if (sym == s->sym_signed_modulo) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SMOD;
|
||||
} else if (sym == s->sym_logical_and) {
|
||||
op = RZ_NUM_EXPRESSION_OP_LOGAND;
|
||||
} else if (sym == s->sym_logical_or) {
|
||||
op = RZ_NUM_EXPRESSION_OP_LOGOR;
|
||||
} else if (sym == s->sym_logical_xor) {
|
||||
op = RZ_NUM_EXPRESSION_OP_LOGXOR;
|
||||
} else if (sym == s->sym_logical_shl) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SHL;
|
||||
} else if (sym == s->sym_logical_shr) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SHR;
|
||||
} else if (sym == s->sym_arith_shr) {
|
||||
op = RZ_NUM_EXPRESSION_OP_SAR;
|
||||
} else if (sym == s->sym_equal) {
|
||||
op = RZ_NUM_EXPRESSION_OP_EQ;
|
||||
} else if (sym == s->sym_less_equal) {
|
||||
op = RZ_NUM_EXPRESSION_OP_ULE;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
*out = op;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Float arithmetic has a direct RzIL equivalent only for + - * / .
|
||||
static bool float_binop_of(TSSymbol sym, RzNumExpressionOp *out) {
|
||||
const RzNumSymCache *s = rz_num_parse_syms();
|
||||
if (sym == s->sym_sum) {
|
||||
*out = RZ_NUM_EXPRESSION_OP_FADD;
|
||||
} else if (sym == s->sym_subtraction) {
|
||||
*out = RZ_NUM_EXPRESSION_OP_FSUB;
|
||||
} else if (sym == s->sym_product) {
|
||||
*out = RZ_NUM_EXPRESSION_OP_FMUL;
|
||||
} else if (sym == s->sym_division) {
|
||||
*out = RZ_NUM_EXPRESSION_OP_FDIV;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Evaluate the sub-expression a node spans to a concrete value and emit
|
||||
// it as a constant leaf (used for constructs with no structural form).
|
||||
static RzNumExpression *build_ground(BuildCtx *c, TSNode n) {
|
||||
char *text = node_text(n, c->src);
|
||||
if (!text) {
|
||||
build_set_err(c, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
RzNumValue v;
|
||||
rz_num_value_init(&v);
|
||||
char *everr = NULL;
|
||||
bool ok = rz_num_math_value_ex(c->num, text, c->opts, &v, &everr);
|
||||
free(text);
|
||||
if (!ok) {
|
||||
build_set_err(c, "cannot ground sub-expression: %s",
|
||||
everr ? everr : "evaluation failed");
|
||||
free(everr);
|
||||
rz_num_value_fini(&v);
|
||||
return NULL;
|
||||
}
|
||||
free(everr);
|
||||
// Width follows the grounded value's kind: a bit-vector keeps its
|
||||
// width, everything else projects to the default 64-bit domain.
|
||||
ut32 width = EXPR_DEFAULT_WIDTH;
|
||||
if (v.kind == RZ_NUM_KIND_BITVECTOR && v.val.bv) {
|
||||
width = rz_bv_len(v.val.bv);
|
||||
}
|
||||
ut64 value = rz_num_value_to_ut64(&v);
|
||||
rz_num_value_fini(&v);
|
||||
return expr_bv(rz_bv_new_from_ut64(width ? width : EXPR_DEFAULT_WIDTH, value));
|
||||
}
|
||||
|
||||
// Numeric literal -> float constant (for a '.'/'e' literal) or a grounded
|
||||
// bit-vector constant (which uniformly handles width and base suffixes).
|
||||
static RzNumExpression *build_number(BuildCtx *c, TSNode n) {
|
||||
if (number_node_is_float(c->src, n)) {
|
||||
char *text = node_text(n, c->src);
|
||||
if (!text) {
|
||||
build_set_err(c, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
double d = strtod(text, NULL);
|
||||
free(text);
|
||||
return expr_float(d);
|
||||
}
|
||||
return build_ground(c, n);
|
||||
}
|
||||
|
||||
static RzNumExpression *build_node(BuildCtx *c, TSNode n) {
|
||||
if (c->err) {
|
||||
return NULL;
|
||||
}
|
||||
const RzNumSymCache *s = rz_num_parse_syms();
|
||||
TSSymbol sym = ts_node_symbol(n);
|
||||
|
||||
// A ';'-separated sequence is grounded as a whole: the evaluator
|
||||
// computes the final value honouring earlier bindings; lifting only
|
||||
// the last statement would lose variables bound before it.
|
||||
if ((sym == s->sym_source_file || sym == s->sym_expression) &&
|
||||
ts_node_named_child_count(n) > 1) {
|
||||
return build_ground(c, n);
|
||||
}
|
||||
|
||||
// Wrappers: descend into the single meaningful child.
|
||||
if (sym == s->sym_source_file || sym == s->sym_expression ||
|
||||
sym == s->sym_parenthesized_expression || sym == s->sym_argument) {
|
||||
TSNode child = ts_node_named_child(n, 0);
|
||||
if (ts_node_is_null(child)) {
|
||||
build_set_err(c, "empty expression");
|
||||
return NULL;
|
||||
}
|
||||
return build_node(c, child);
|
||||
}
|
||||
|
||||
if (sym == s->sym_number) {
|
||||
return build_number(c, n);
|
||||
}
|
||||
|
||||
// Ternary -> ITE. A float condition uses RzNum's "non-zero float is
|
||||
// true" semantic, which RzIL's ite (Bool condition) does not share;
|
||||
// the faithful form is is_fzero(cond) ? else : then (branches swapped
|
||||
// because is_fzero is true exactly when the original condition was
|
||||
// false).
|
||||
if (sym == s->sym_conditional) {
|
||||
TSNode cond = EXPR_FIELD(n, "condition");
|
||||
TSNode then_n = EXPR_FIELD(n, "consequence");
|
||||
TSNode else_n = EXPR_FIELD(n, "alternative");
|
||||
if (ts_node_is_null(cond) || ts_node_is_null(then_n) || ts_node_is_null(else_n)) {
|
||||
return build_ground(c, n);
|
||||
}
|
||||
bool cond_is_float = is_float_subexpr(c->src, cond);
|
||||
RzNumExpression *ce = build_node(c, cond);
|
||||
RzNumExpression *te = build_node(c, then_n);
|
||||
RzNumExpression *ee = build_node(c, else_n);
|
||||
if (cond_is_float) {
|
||||
return expr_ite(expr_unop(RZ_NUM_EXPRESSION_OP_IS_FZERO, ce), ee, te);
|
||||
}
|
||||
return expr_ite(ce, te, ee);
|
||||
}
|
||||
|
||||
// Float-pure arithmetic (+ - * / with two float operands) -> float
|
||||
// binop; mixed float/bit-vector arithmetic is grounded rather than
|
||||
// fabricating a cast the user did not write.
|
||||
{
|
||||
RzNumExpressionOp fop;
|
||||
if (float_binop_of(sym, &fop)) {
|
||||
TSNode l = EXPR_FIELD(n, "left");
|
||||
TSNode r = EXPR_FIELD(n, "right");
|
||||
if (!ts_node_is_null(l) && !ts_node_is_null(r)) {
|
||||
bool lf = is_float_subexpr(c->src, l);
|
||||
bool rf = is_float_subexpr(c->src, r);
|
||||
if (lf && rf) {
|
||||
return expr_binop(fop, build_node(c, l), build_node(c, r));
|
||||
}
|
||||
if (lf != rf) {
|
||||
return build_ground(c, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bit-vector binary operators.
|
||||
{
|
||||
RzNumExpressionOp iop;
|
||||
if (binop_of(sym, &iop)) {
|
||||
TSNode l = EXPR_FIELD(n, "left");
|
||||
TSNode r = EXPR_FIELD(n, "right");
|
||||
if (ts_node_is_null(l) || ts_node_is_null(r)) {
|
||||
// e.g. exponent uses base/exponent fields and has no
|
||||
// bit-vector op: ground the whole node.
|
||||
return build_ground(c, n);
|
||||
}
|
||||
return expr_binop(iop, build_node(c, l), build_node(c, r));
|
||||
}
|
||||
}
|
||||
|
||||
// Unary minus / bitwise not: operand is in the "right" field.
|
||||
if (sym == s->sym_unary_minus || sym == s->sym_logical_negation) {
|
||||
TSNode operand = EXPR_FIELD(n, "right");
|
||||
if (!ts_node_is_null(operand)) {
|
||||
RzNumExpressionOp uop = (sym == s->sym_logical_negation)
|
||||
? RZ_NUM_EXPRESSION_OP_LOGNOT
|
||||
: RZ_NUM_EXPRESSION_OP_NEG;
|
||||
return expr_unop(uop, build_node(c, operand));
|
||||
}
|
||||
}
|
||||
// unary_plus is a no-op on the value.
|
||||
if (sym == s->sym_unary_plus) {
|
||||
TSNode operand = EXPR_FIELD(n, "right");
|
||||
if (!ts_node_is_null(operand)) {
|
||||
return build_node(c, operand);
|
||||
}
|
||||
}
|
||||
|
||||
// Everything else (function calls, typed reads, host variables,
|
||||
// exponent / logarithm, string-bytes) has no structural form.
|
||||
return build_ground(c, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse \p expr into a self-contained RzNumExpression (no tree-sitter references).
|
||||
* \param num Evaluation context used to fold non-structural sub-expressions to constants.
|
||||
* \param expr Expression source string.
|
||||
* \param opts Evaluation options forwarded to the evaluator.
|
||||
* \param error_msg Set to an error string on failure when non-NULL.
|
||||
* \return Expression tree, or NULL on a parse or grounding error.
|
||||
*
|
||||
* Sub-expressions with no structural lift (function calls, typed reads,
|
||||
* host variables, exponent / logarithm, ';' sequences) are evaluated
|
||||
* through \p num / \p opts and embedded as constant leaves, so the
|
||||
* returned tree carries no tree-sitter state.
|
||||
*/
|
||||
RZ_API RZ_OWN RzNumExpression *rz_num_expression_parse(RZ_NULLABLE RzNum *num,
|
||||
RZ_NONNULL const char *expr, RZ_NULLABLE const RzNumMathOptions *opts,
|
||||
RZ_NULLABLE char **error_msg) {
|
||||
rz_return_val_if_fail(expr, NULL);
|
||||
RzNumParseResult *pr = rz_num_parse(expr);
|
||||
if (!pr) {
|
||||
if (error_msg) {
|
||||
*error_msg = rz_str_dup("out of memory");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (pr->has_error) {
|
||||
if (error_msg) {
|
||||
*error_msg = rz_str_dup(pr->error_msg ? pr->error_msg : "parse error");
|
||||
}
|
||||
rz_num_parse_result_free(pr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BuildCtx c = {
|
||||
.num = num,
|
||||
.src = rz_num_parse_source(pr),
|
||||
.opts = opts,
|
||||
.err = NULL
|
||||
};
|
||||
RzNumExpression *e = build_node(&c, rz_num_parse_root(pr));
|
||||
rz_num_parse_result_free(pr);
|
||||
|
||||
if (!e || c.err) {
|
||||
if (error_msg) {
|
||||
*error_msg = c.err ? c.err : rz_str_dup("lift failed");
|
||||
} else {
|
||||
free(c.err);
|
||||
}
|
||||
rz_num_expression_free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file native_shim.c
|
||||
*
|
||||
* Build-machine (native) variant of the RzNum tree-sitter entry points.
|
||||
*
|
||||
* When cross-compiling, librz_util is also built natively so that the
|
||||
* build-time sdb_gen tool can run. That tool never evaluates RzNum
|
||||
* expressions, so the native build deliberately leaves out the
|
||||
* tree-sitter based evaluator (num/evaluator.c and friends)
|
||||
* and, with it, the whole tree-sitter dependency. unum.c still links
|
||||
* against two of the evaluator's symbols, though, so this small shim
|
||||
* provides them with behaviour that is correct for the native build:
|
||||
*
|
||||
* - rz_num_math_value() reports a parse error. With the legacy
|
||||
* calculator now removed, rz_num_math_ut64() has no fallback and
|
||||
* simply yields 0 for every expression. That is harmless here:
|
||||
* sdb_gen, the only consumer of the native library, never
|
||||
* evaluates RzNum expressions.
|
||||
*
|
||||
* - rz_num_value_store_free() is identical to the real one; the
|
||||
* variable store is never populated here (nothing creates it
|
||||
* without the evaluator), so it is only ever called with NULL.
|
||||
*
|
||||
* The regular, non-cross build never compiles this file: it uses the
|
||||
* real implementations in num/.
|
||||
*/
|
||||
|
||||
#include <rz_util.h>
|
||||
|
||||
RZ_API bool rz_num_math_value(RZ_NULLABLE RzNum *num, RZ_NONNULL const char *expr,
|
||||
RZ_OUT RZ_NONNULL RzNumValue *out_value, RZ_OUT RZ_NULLABLE char **error_msg) {
|
||||
rz_return_val_if_fail(expr && out_value, false);
|
||||
(void)num;
|
||||
// No evaluator in the native build; report a parse failure. There
|
||||
// is no longer a legacy fallback, so rz_num_math_ut64() resolves
|
||||
// this to 0 (acceptable: sdb_gen never evaluates expressions).
|
||||
out_value->err = RZ_NUM_ERR_PARSE;
|
||||
if (error_msg) {
|
||||
*error_msg = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RZ_API void rz_num_value_store_free(RZ_NULLABLE HtSP *store) {
|
||||
ht_sp_free(store);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Tree-sitter glue for the RzNum expression parser.
|
||||
*
|
||||
* Wraps the rizin-math-parser tree-sitter grammar in a small
|
||||
* RzNumParseResult handle that owns the parser, the parsed tree, and
|
||||
* a copy of the source text. The evaluator (evaluator.c) walks
|
||||
* the tree this module produces.
|
||||
*/
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util/rz_str.h>
|
||||
#include <rz_util/rz_assert.h>
|
||||
#include <tree_sitter/api.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
// Provided by the rizin-math-parser subproject's generated parser.c.
|
||||
TSLanguage *tree_sitter_rznum(void);
|
||||
|
||||
// Shared grammar symbol cache. Lazily initialised on first use; the
|
||||
// evaluator and the RzNumExpression builder both dispatch on its IDs so
|
||||
// the grammar's node vocabulary is defined in exactly one place.
|
||||
static RzNumSymCache g_num_syms;
|
||||
|
||||
// Look up a symbol ID by name in the language. Returns 0 (the special
|
||||
// "no symbol" value) when the name is not in the grammar, which lets a
|
||||
// stale-grammar mismatch surface here rather than as a silent misparse.
|
||||
static TSSymbol sym_lookup(TSLanguage *lang, const char *name) {
|
||||
return ts_language_symbol_for_name(lang, name, (uint32_t)strlen(name), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the lazily-initialised RzNum grammar symbol cache.
|
||||
* \return Borrowed pointer to the shared symbol cache; never NULL.
|
||||
*
|
||||
* Populated on first call from the compiled grammar and shared by every
|
||||
* consumer in the num directory. tree_sitter_rznum() returns a
|
||||
* process-wide singleton language whose symbol table is immutable, so
|
||||
* two threads racing to populate the cache write identical values; the
|
||||
* worst case is duplicate work, not corruption, and no platform-specific
|
||||
* primitive is needed - only plain C99 accesses.
|
||||
*/
|
||||
RZ_API RZ_BORROW const RzNumSymCache *rz_num_parse_syms(void) {
|
||||
if (g_num_syms.ready) {
|
||||
return &g_num_syms;
|
||||
}
|
||||
TSLanguage *lang = tree_sitter_rznum();
|
||||
g_num_syms.sym_number = sym_lookup(lang, "number");
|
||||
g_num_syms.sym_variable = sym_lookup(lang, "variable");
|
||||
g_num_syms.sym_special_variable = sym_lookup(lang, "special_variable");
|
||||
g_num_syms.sym_address_typed = sym_lookup(lang, "address_typed");
|
||||
g_num_syms.sym_string_bytes = sym_lookup(lang, "string_bytes");
|
||||
g_num_syms.sym_function = sym_lookup(lang, "function");
|
||||
g_num_syms.sym_parenthesized_expression = sym_lookup(lang, "parenthesized_expression");
|
||||
g_num_syms.sym_source_file = sym_lookup(lang, "source_file");
|
||||
g_num_syms.sym_argument = sym_lookup(lang, "argument");
|
||||
g_num_syms.sym_expression = sym_lookup(lang, "expression");
|
||||
g_num_syms.sym_sum = sym_lookup(lang, "sum");
|
||||
g_num_syms.sym_subtraction = sym_lookup(lang, "subtraction");
|
||||
g_num_syms.sym_product = sym_lookup(lang, "product");
|
||||
g_num_syms.sym_division = sym_lookup(lang, "division");
|
||||
g_num_syms.sym_signed_division = sym_lookup(lang, "signed_division");
|
||||
g_num_syms.sym_modulo = sym_lookup(lang, "modulo");
|
||||
g_num_syms.sym_signed_modulo = sym_lookup(lang, "signed_modulo");
|
||||
g_num_syms.sym_exponent = sym_lookup(lang, "exponent");
|
||||
g_num_syms.sym_logarithm = sym_lookup(lang, "logarithm");
|
||||
g_num_syms.sym_logical_and = sym_lookup(lang, "logical_and");
|
||||
g_num_syms.sym_logical_or = sym_lookup(lang, "logical_or");
|
||||
g_num_syms.sym_logical_xor = sym_lookup(lang, "logical_xor");
|
||||
g_num_syms.sym_logical_shl = sym_lookup(lang, "logical_shl");
|
||||
g_num_syms.sym_logical_shr = sym_lookup(lang, "logical_shr");
|
||||
g_num_syms.sym_arith_shr = sym_lookup(lang, "arith_shr");
|
||||
g_num_syms.sym_logical_rol = sym_lookup(lang, "logical_rol");
|
||||
g_num_syms.sym_logical_ror = sym_lookup(lang, "logical_ror");
|
||||
g_num_syms.sym_less_than = sym_lookup(lang, "less_than");
|
||||
g_num_syms.sym_less_equal = sym_lookup(lang, "less_equal");
|
||||
g_num_syms.sym_greater_than = sym_lookup(lang, "greater_than");
|
||||
g_num_syms.sym_greater_equal = sym_lookup(lang, "greater_equal");
|
||||
g_num_syms.sym_equal = sym_lookup(lang, "equal");
|
||||
g_num_syms.sym_not_equal = sym_lookup(lang, "not_equal");
|
||||
g_num_syms.sym_conditional = sym_lookup(lang, "conditional");
|
||||
g_num_syms.sym_logical_negation = sym_lookup(lang, "logical_negation");
|
||||
g_num_syms.sym_logical_not = sym_lookup(lang, "logical_not");
|
||||
g_num_syms.sym_increment = sym_lookup(lang, "increment");
|
||||
g_num_syms.sym_decrement = sym_lookup(lang, "decrement");
|
||||
g_num_syms.sym_unary_plus = sym_lookup(lang, "unary_plus");
|
||||
g_num_syms.sym_unary_minus = sym_lookup(lang, "unary_minus");
|
||||
g_num_syms.sym_assignment = sym_lookup(lang, "assignment");
|
||||
g_num_syms.sym_let_assignment = sym_lookup(lang, "let_assignment");
|
||||
g_num_syms.ready = true;
|
||||
return &g_num_syms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walk the parse tree to find the first ERROR or MISSING
|
||||
* node and report its position. Returns true if the tree is clean.
|
||||
*/
|
||||
static bool tree_is_clean(TSNode node, char **out_err) {
|
||||
if (!ts_node_has_error(node)) {
|
||||
return true;
|
||||
}
|
||||
if (ts_node_is_missing(node) || ts_node_is_error(node)) {
|
||||
if (out_err && !*out_err) {
|
||||
TSPoint p = ts_node_start_point(node);
|
||||
const char *kind = ts_node_is_missing(node) ? "missing token" : "syntax error";
|
||||
*out_err = rz_str_newf("%s at line %u column %u",
|
||||
kind, (unsigned)p.row + 1, (unsigned)p.column + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
uint32_t n = ts_node_child_count(node);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
TSNode child = ts_node_child(node, i);
|
||||
if (!tree_is_clean(child, out_err)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Parse a numerical expression.
|
||||
*
|
||||
* Returns an RzNumParseResult that the caller must release with
|
||||
* rz_num_parse_result_free(). If parsing produced any ERROR or MISSING
|
||||
* nodes, has_error is true and error_msg carries a one-line
|
||||
* diagnostic (the first parse error encountered, line/column based).
|
||||
*
|
||||
* Returns NULL only on allocation failure.
|
||||
*/
|
||||
RZ_API RZ_OWN RzNumParseResult *rz_num_parse(RZ_NONNULL const char *expr) {
|
||||
rz_return_val_if_fail(expr, NULL);
|
||||
RzNumParseResult *r = RZ_NEW0(RzNumParseResult);
|
||||
if (!r) {
|
||||
return NULL;
|
||||
}
|
||||
r->source = rz_str_dup(expr);
|
||||
if (!r->source) {
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
r->source_len = strlen(r->source);
|
||||
|
||||
r->parser = ts_parser_new();
|
||||
if (!r->parser) {
|
||||
rz_num_parse_result_free(r);
|
||||
return NULL;
|
||||
}
|
||||
ts_parser_set_language(r->parser, tree_sitter_rznum());
|
||||
r->tree = ts_parser_parse_string(r->parser, NULL, r->source, (uint32_t)r->source_len);
|
||||
if (!r->tree) {
|
||||
r->has_error = true;
|
||||
r->error_msg = rz_str_dup("parser returned no tree");
|
||||
return r;
|
||||
}
|
||||
TSNode root = ts_tree_root_node(r->tree);
|
||||
if (ts_node_named_child_count(root) == 0) {
|
||||
r->has_error = true;
|
||||
r->error_msg = rz_str_dup("empty expression");
|
||||
return r;
|
||||
}
|
||||
char *err = NULL;
|
||||
if (!tree_is_clean(root, &err)) {
|
||||
r->has_error = true;
|
||||
r->error_msg = err ? err : rz_str_dup("parse error");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Release an RzNumParseResult and all the resources it owns.
|
||||
*/
|
||||
RZ_API void rz_num_parse_result_free(RZ_NULLABLE RzNumParseResult *r) {
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
if (r->tree) {
|
||||
ts_tree_delete(r->tree);
|
||||
}
|
||||
if (r->parser) {
|
||||
ts_parser_delete(r->parser);
|
||||
}
|
||||
free(r->source);
|
||||
free(r->error_msg);
|
||||
free(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the root TSNode of the parsed tree.
|
||||
*
|
||||
* For a syntactically valid input the root is named "source_file"
|
||||
* with one named child of type "expression".
|
||||
*/
|
||||
RZ_API TSNode rz_num_parse_root(RZ_NONNULL const RzNumParseResult *r) {
|
||||
rz_return_val_if_fail(r && r->tree, (TSNode){ 0 });
|
||||
return ts_tree_root_node(r->tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the source text that the tree refers into.
|
||||
*
|
||||
* Valid until rz_num_parse_result_free() is called.
|
||||
*/
|
||||
RZ_API RZ_BORROW const char *rz_num_parse_source(RZ_NONNULL const RzNumParseResult *r) {
|
||||
rz_return_val_if_fail(r, NULL);
|
||||
return r->source;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Internal interface to the RzNum tree-sitter parser.
|
||||
*
|
||||
* Declares the RzNumParseResult handle and the parse/root/source
|
||||
* accessors used by the evaluator. This header is private to the
|
||||
* num directory and is not installed.
|
||||
*/
|
||||
|
||||
#ifndef RZ_NUM_PARSER_H
|
||||
#define RZ_NUM_PARSER_H
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <tree_sitter/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Opaque result of a single parse.
|
||||
*
|
||||
* Owns the underlying TSTree, TSParser and a copy of the source text.
|
||||
* The source text is kept because tree-sitter nodes refer back to byte
|
||||
* offsets in the original buffer; consumers (e.g. the evaluator) read
|
||||
* literal text via rz_num_parse_source().
|
||||
*/
|
||||
typedef struct rz_num_parse_result_t {
|
||||
TSParser *parser;
|
||||
TSTree *tree;
|
||||
char *source;
|
||||
size_t source_len;
|
||||
bool has_error;
|
||||
char *error_msg;
|
||||
} RzNumParseResult;
|
||||
|
||||
RZ_API RZ_OWN RzNumParseResult *rz_num_parse(RZ_NONNULL const char *expr);
|
||||
|
||||
RZ_API void rz_num_parse_result_free(RZ_NULLABLE RzNumParseResult *r);
|
||||
|
||||
RZ_API TSNode rz_num_parse_root(RZ_NONNULL const RzNumParseResult *r);
|
||||
|
||||
RZ_API RZ_BORROW const char *rz_num_parse_source(RZ_NONNULL const RzNumParseResult *r);
|
||||
|
||||
/**
|
||||
* \brief Cached tree-sitter symbol IDs for the RzNum grammar nodes.
|
||||
*
|
||||
* The grammar names a distinct node for every operator (sum, product,
|
||||
* signed_division, ...) and for the structural wrappers (expression,
|
||||
* parenthesized_expression, argument). Both the evaluator and the
|
||||
* RzNumExpression builder dispatch on these integer symbol IDs rather
|
||||
* than re-deriving node kinds from their string names, so the grammar's
|
||||
* vocabulary has a single definition: a grammar rename shows up as a
|
||||
* zeroed symbol here (see rz_num_parse_syms()) instead of silently
|
||||
* diverging between the two walkers.
|
||||
*/
|
||||
typedef struct rz_num_sym_cache_t {
|
||||
bool ready;
|
||||
// Operand-bearing nodes.
|
||||
TSSymbol sym_number;
|
||||
TSSymbol sym_variable;
|
||||
TSSymbol sym_special_variable;
|
||||
TSSymbol sym_address_typed;
|
||||
TSSymbol sym_string_bytes;
|
||||
TSSymbol sym_function;
|
||||
TSSymbol sym_parenthesized_expression;
|
||||
TSSymbol sym_source_file;
|
||||
TSSymbol sym_argument;
|
||||
TSSymbol sym_expression;
|
||||
// Binary operators.
|
||||
TSSymbol sym_sum;
|
||||
TSSymbol sym_subtraction;
|
||||
TSSymbol sym_product;
|
||||
TSSymbol sym_division;
|
||||
TSSymbol sym_signed_division;
|
||||
TSSymbol sym_modulo;
|
||||
TSSymbol sym_signed_modulo;
|
||||
TSSymbol sym_exponent;
|
||||
TSSymbol sym_logarithm;
|
||||
TSSymbol sym_logical_and;
|
||||
TSSymbol sym_logical_or;
|
||||
TSSymbol sym_logical_xor;
|
||||
TSSymbol sym_logical_shl;
|
||||
TSSymbol sym_logical_shr;
|
||||
TSSymbol sym_arith_shr;
|
||||
TSSymbol sym_logical_rol;
|
||||
TSSymbol sym_logical_ror;
|
||||
TSSymbol sym_less_than;
|
||||
TSSymbol sym_less_equal;
|
||||
TSSymbol sym_greater_than;
|
||||
TSSymbol sym_greater_equal;
|
||||
TSSymbol sym_equal;
|
||||
TSSymbol sym_not_equal;
|
||||
// Ternary.
|
||||
TSSymbol sym_conditional;
|
||||
// Unary operators.
|
||||
TSSymbol sym_logical_negation;
|
||||
TSSymbol sym_logical_not;
|
||||
TSSymbol sym_increment;
|
||||
TSSymbol sym_decrement;
|
||||
TSSymbol sym_unary_plus;
|
||||
TSSymbol sym_unary_minus;
|
||||
// Assignment forms.
|
||||
TSSymbol sym_assignment;
|
||||
TSSymbol sym_let_assignment;
|
||||
} RzNumSymCache;
|
||||
|
||||
RZ_API RZ_BORROW const RzNumSymCache *rz_num_parse_syms(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,335 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief Per-kind pretty-printing for RzNumValue.
|
||||
*
|
||||
* Renders an RzNumValue into a multi-line, human-readable block whose
|
||||
* layout depends on the value's kind (ut64 multi-base table, float
|
||||
* scientific + bit pattern, big number decimal + hex + width). Kept
|
||||
* separate from the evaluator so consumers that only need to display
|
||||
* a value do not pull in the tree-sitter machinery.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_str.h>
|
||||
#include <rz_util/rz_strbuf.h>
|
||||
|
||||
/**
|
||||
* \brief Human-readable name of an RzNumError category.
|
||||
*
|
||||
* Stable string suitable for diagnostics. Returns "unknown" for
|
||||
* values outside the enum.
|
||||
*/
|
||||
RZ_API const char *rz_num_error_name(RzNumError err) {
|
||||
switch (err) {
|
||||
case RZ_NUM_ERR_OK: return "ok";
|
||||
case RZ_NUM_ERR_PARSE: return "parse";
|
||||
case RZ_NUM_ERR_EMPTY: return "empty";
|
||||
case RZ_NUM_ERR_RESERVED_WORD: return "reserved word";
|
||||
case RZ_NUM_ERR_UNDEFINED_VAR: return "undefined variable";
|
||||
case RZ_NUM_ERR_DIV_ZERO: return "division by zero";
|
||||
case RZ_NUM_ERR_OVERFLOW: return "overflow";
|
||||
case RZ_NUM_ERR_TYPE_MISMATCH: return "type mismatch";
|
||||
case RZ_NUM_ERR_TIMEOUT: return "timeout";
|
||||
case RZ_NUM_ERR_UNCOMPUTABLE: return "uncomputable";
|
||||
case RZ_NUM_ERR_NOT_IMPLEMENTED: return "not implemented";
|
||||
case RZ_NUM_ERR_OUT_OF_MEMORY: return "out of memory";
|
||||
case RZ_NUM_ERR_DEPTH: return "nesting too deep";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
// Per-kind formatters
|
||||
|
||||
// Convert a ut64 to a 4-byte ASCII representation. Only emits a
|
||||
// string line when every byte is printable; otherwise returns false
|
||||
// and the caller skips that line.
|
||||
static bool ut64_as_ascii(ut64 n, char *out, size_t outlen) {
|
||||
if (outlen < 8) {
|
||||
return false;
|
||||
}
|
||||
// Walk the bytes from low to high: that mirrors the legacy
|
||||
// `?` output's "string" line which reads back ASCII tokens
|
||||
// loaded from memory at that address.
|
||||
ut8 bytes[8];
|
||||
bool any = false;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bytes[i] = (ut8)((n >> (i * 8)) & 0xff);
|
||||
if (bytes[i] != 0) {
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (!any) {
|
||||
return false;
|
||||
}
|
||||
int op = 0;
|
||||
out[op++] = '"';
|
||||
for (int i = 0; i < 8 && op + 2 < (int)outlen; i++) {
|
||||
ut8 b = bytes[i];
|
||||
if (b == 0) {
|
||||
break;
|
||||
}
|
||||
if (b < 0x20 || b >= 0x7f) {
|
||||
return false;
|
||||
}
|
||||
out[op++] = (char)b;
|
||||
}
|
||||
out[op++] = '"';
|
||||
out[op] = 0;
|
||||
return op > 2; // need at least one character between the quotes
|
||||
}
|
||||
|
||||
static void print_ut64(const RzNumValue *v, RzStrBuf *sb) {
|
||||
ut64 n = v->val.n;
|
||||
char bits[65];
|
||||
char trits[42];
|
||||
char units[16];
|
||||
|
||||
// Signed / unsigned interpretation in two widths. The split at
|
||||
// >> 32 mirrors the legacy `?` output: values that fit in 32
|
||||
// bits get their 32-bit signed/unsigned forms shown, otherwise
|
||||
// the 64-bit forms.
|
||||
if (n >> 32) {
|
||||
rz_strbuf_appendf(sb, "int64 %" PFMT64d "\n", (st64)n);
|
||||
rz_strbuf_appendf(sb, "uint64 %" PFMT64u "\n", (ut64)n);
|
||||
} else {
|
||||
rz_strbuf_appendf(sb, "int32 %d\n", (st32)n);
|
||||
rz_strbuf_appendf(sb, "uint32 %u\n", (ut32)n);
|
||||
}
|
||||
rz_strbuf_appendf(sb, "hex 0x%" PFMT64x "\n", n);
|
||||
rz_strbuf_appendf(sb, "octal 0%" PFMT64o "\n", n);
|
||||
|
||||
rz_num_units(units, sizeof(units), n);
|
||||
rz_strbuf_appendf(sb, "unit %s\n", units);
|
||||
|
||||
// Segment:offset (real-mode style 16:12-bit split).
|
||||
ut32 seg = (ut32)(n >> 16) << 12;
|
||||
ut32 off = (ut32)(n & 0x0fff);
|
||||
rz_strbuf_appendf(sb, "segment %04x:%04x\n", seg, off);
|
||||
|
||||
char ascii[24];
|
||||
if (ut64_as_ascii(n, ascii, sizeof(ascii))) {
|
||||
rz_strbuf_appendf(sb, "string %s\n", ascii);
|
||||
}
|
||||
|
||||
rz_str_bits64(bits, n);
|
||||
rz_strbuf_appendf(sb, "binary 0b%s\n", bits);
|
||||
rz_num_to_trits(trits, n);
|
||||
rz_strbuf_appendf(sb, "trits 0t%s\n", trits);
|
||||
}
|
||||
|
||||
static void print_float(const RzNumValue *v, RzStrBuf *sb) {
|
||||
double d = v->val.d;
|
||||
// Sign-of-NaN is implementation-defined; normalise so the
|
||||
// printed form is identical across libcs.
|
||||
if (isnan(d) && signbit(d)) {
|
||||
d = -d;
|
||||
}
|
||||
rz_strbuf_appendf(sb, "float %g\n", d);
|
||||
rz_strbuf_appendf(sb, "scifmt %.17g\n", d);
|
||||
// IEEE-754 binary64 bit pattern (handy for back-conversion).
|
||||
union {
|
||||
double d;
|
||||
ut64 u;
|
||||
} u = { .d = d };
|
||||
rz_strbuf_appendf(sb, "hex 0x%016" PFMT64x "\n", u.u);
|
||||
}
|
||||
|
||||
static void print_big(const RzNumValue *v, RzStrBuf *sb) {
|
||||
// Decimal output now goes through rz_big_to_decstr(), which
|
||||
// gives the exact base-10 form. The hex form is still emitted
|
||||
// alongside it: every other kind shows its hex pattern (as
|
||||
// "hex" for ut64, "hex" bit pattern for float, "hex" digest
|
||||
// for bitvector), and for symmetry the big print does too.
|
||||
char *dec = rz_big_to_decstr(v->val.big);
|
||||
if (dec) {
|
||||
rz_strbuf_appendf(sb, "decimal %s\n", dec);
|
||||
free(dec);
|
||||
}
|
||||
char *hex = rz_big_to_hexstr(v->val.big);
|
||||
if (hex) {
|
||||
// rz_big_to_hexstr already prefixes with "0x".
|
||||
rz_strbuf_appendf(sb, "hex %s\n", hex);
|
||||
free(hex);
|
||||
}
|
||||
// Width hint: number of hex nibbles (and therefore bits) the
|
||||
// value actually occupies, useful for the reader who wants to
|
||||
// know whether this is a 65-bit value (one bit over ut64) or
|
||||
// a genuine multi-hundred-bit one.
|
||||
if (hex || dec) {
|
||||
// (already freed; recompute purely for the bit-width
|
||||
// estimate, which is cheap)
|
||||
char *h = rz_big_to_hexstr(v->val.big);
|
||||
if (h) {
|
||||
const char *p = h;
|
||||
if (*p == '-')
|
||||
p++;
|
||||
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
|
||||
p += 2;
|
||||
}
|
||||
int nibbles = (int)strlen(p);
|
||||
rz_strbuf_appendf(sb, "width %d bits (approx)\n",
|
||||
nibbles * 4);
|
||||
free(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_bitvector(const RzNumValue *v, RzStrBuf *sb, bool utf8) {
|
||||
RzBitVector *bv = v->val.bv;
|
||||
if (!bv) {
|
||||
rz_strbuf_appendf(sb, "bitvec (null)\n");
|
||||
return;
|
||||
}
|
||||
ut32 len = rz_bv_len(bv);
|
||||
char *hex = rz_bv_as_hex_string(bv, true);
|
||||
char *bin = rz_bv_as_string(bv);
|
||||
// In UTF-8 mode the width is folded into the value rows as a
|
||||
// subscript (the notation RzIL uses for bit-vector constants, via
|
||||
// the shared rz_bv_width_subscript formatter) and the binary form
|
||||
// drops the 0b prefix in favour of the subscript. The value rows
|
||||
// come first and the width row last in both modes.
|
||||
char *sub = utf8 ? rz_bv_width_subscript(len) : NULL;
|
||||
if (hex) {
|
||||
rz_strbuf_appendf(sb, "hex %s%s\n", hex, sub ? sub : "");
|
||||
}
|
||||
if (bin) {
|
||||
if (utf8) {
|
||||
rz_strbuf_appendf(sb, "binary %s%s\n", bin, sub ? sub : "");
|
||||
} else {
|
||||
rz_strbuf_appendf(sb, "binary 0b%s\n", bin);
|
||||
}
|
||||
}
|
||||
rz_strbuf_appendf(sb, "width %u bits\n", len);
|
||||
free(sub);
|
||||
free(hex);
|
||||
free(bin);
|
||||
}
|
||||
|
||||
// Public entry points
|
||||
|
||||
/**
|
||||
* \brief Format an RzNumValue into a multi-representation textual block.
|
||||
*
|
||||
* Each kind produces a different layout:
|
||||
* UT64 hex / dec (signed + unsigned) / octal / binary /
|
||||
* IEC byte unit / segment:offset / character form
|
||||
* FLOAT decimal scientific / shortest / hex bit pattern
|
||||
* BIG decimal + hex (lossless)
|
||||
* BITVECTOR bit-width / hex / binary
|
||||
*
|
||||
* On a value with v->err != RZ_NUM_ERR_OK, a single 'error <category>'
|
||||
* line is appended instead.
|
||||
*
|
||||
* \param v The value to format.
|
||||
* \param sb Destination buffer.
|
||||
*/
|
||||
RZ_API void rz_num_value_print(RZ_NONNULL const RzNumValue *v, RZ_NONNULL RzStrBuf *sb) {
|
||||
rz_num_value_print_ex(v, NULL, sb);
|
||||
}
|
||||
|
||||
// Exact base-10 decimal: the full value plus its double projection.
|
||||
static void print_bigdecimal(const RzNumValue *v, RzStrBuf *sb) {
|
||||
char *s = v->val.bigdec ? rz_big_decimal_to_str(v->val.bigdec) : NULL;
|
||||
rz_strbuf_appendf(sb, "decimal %s\n", s ? s : "0");
|
||||
double d = v->val.bigdec ? rz_big_decimal_to_double(v->val.bigdec) : 0.0;
|
||||
rz_strbuf_appendf(sb, "scifmt %.17g\n", d);
|
||||
free(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Like rz_num_value_print() but with explicit formatting options.
|
||||
*
|
||||
* With opts->utf8 set, a bit-vector additionally renders a compact
|
||||
* Unicode form (hex value with the bit-width as a subscript, e.g.
|
||||
* 0x2c\u2088), mirroring how RzIL renders bit-vectors in its Unicode
|
||||
* export. Other kinds are unaffected for now.
|
||||
*
|
||||
* \param v The value to format. Must be a valid RzNumValue.
|
||||
* \param opts Formatting options. NULL selects the ASCII defaults.
|
||||
* \param sb Destination buffer.
|
||||
*/
|
||||
RZ_API void rz_num_value_print_ex(RZ_NONNULL const RzNumValue *v,
|
||||
RZ_NULLABLE const RzNumPrintOptions *opts, RZ_NONNULL RzStrBuf *sb) {
|
||||
rz_return_if_fail(v && sb);
|
||||
bool utf8 = opts && opts->utf8;
|
||||
if (v->err != RZ_NUM_ERR_OK) {
|
||||
rz_strbuf_appendf(sb, "error %s\n", rz_num_error_name(v->err));
|
||||
return;
|
||||
}
|
||||
switch (v->kind) {
|
||||
case RZ_NUM_KIND_UT64:
|
||||
print_ut64(v, sb);
|
||||
break;
|
||||
case RZ_NUM_KIND_FLOAT:
|
||||
print_float(v, sb);
|
||||
break;
|
||||
case RZ_NUM_KIND_BIGDECIMAL:
|
||||
print_bigdecimal(v, sb);
|
||||
break;
|
||||
case RZ_NUM_KIND_BIG:
|
||||
print_big(v, sb);
|
||||
break;
|
||||
case RZ_NUM_KIND_BITVECTOR:
|
||||
print_bitvector(v, sb, utf8);
|
||||
break;
|
||||
case RZ_NUM_KIND_NONE:
|
||||
rz_strbuf_appendf(sb, "kind none\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Compact one-line stringification of an RzNumValue.
|
||||
*
|
||||
* Suitable for inline display where the multi-line print would be
|
||||
* too noisy.
|
||||
* On error returns NULL.
|
||||
*/
|
||||
RZ_API RZ_OWN char *rz_num_value_tostring(RZ_NONNULL const RzNumValue *v) {
|
||||
rz_return_val_if_fail(v, NULL);
|
||||
if (v->err != RZ_NUM_ERR_OK) {
|
||||
return rz_str_newf("error: %s", rz_num_error_name(v->err));
|
||||
}
|
||||
switch (v->kind) {
|
||||
case RZ_NUM_KIND_UT64:
|
||||
return rz_str_newf("0x%" PFMT64x, v->val.n);
|
||||
case RZ_NUM_KIND_FLOAT: {
|
||||
double d = v->val.d;
|
||||
if (isnan(d) && signbit(d)) {
|
||||
d = -d;
|
||||
}
|
||||
return rz_str_newf("%g", d);
|
||||
}
|
||||
case RZ_NUM_KIND_BIGDECIMAL: {
|
||||
char *s = v->val.bigdec ? rz_big_decimal_to_str(v->val.bigdec) : NULL;
|
||||
return s ? s : rz_str_dup("0");
|
||||
}
|
||||
case RZ_NUM_KIND_BIG: {
|
||||
char *hex = rz_big_to_hexstr(v->val.big);
|
||||
// rz_big_to_hexstr already includes the "0x" prefix.
|
||||
if (!hex) {
|
||||
return rz_str_dup("0x0");
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
case RZ_NUM_KIND_BITVECTOR: {
|
||||
char *hex = rz_bv_as_hex_string(v->val.bv, true);
|
||||
if (!hex) {
|
||||
return rz_str_dup("0x0");
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
case RZ_NUM_KIND_NONE:
|
||||
return rz_str_dup("(none)");
|
||||
}
|
||||
return rz_str_dup("(none)");
|
||||
}
|
||||
+601
-297
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
[wrap-file]
|
||||
directory = rizin-math-parser
|
||||
|
||||
# In-tree subproject. See subprojects/rizin-math-parser/.
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
build/
|
||||
.tree-sitter/
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,412 @@
|
||||
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
const identifier_start = /[^\p{Control}\s+\-*\/%^#&~!|<>=(){}\[\];:,\\'"\d$]/u;
|
||||
const identifier_continue = /[^\p{Control}\s+\-*\/%^#&~!|<>=(){}\[\];:,\\'"]*/u;
|
||||
|
||||
const unit_names = ["KiB", "KB", "MiB", "MB", "GiB", "GB", "TiB", "TB", "PiB", "PB", "EiB", "EB"];
|
||||
|
||||
module.exports = grammar({
|
||||
name: "rznum",
|
||||
|
||||
// Declaring a "word" rule activates tree-sitter's keyword
|
||||
// extraction. At lex time the lexer greedily matches the longest
|
||||
// identifier-shaped run; the result is then matched against the
|
||||
// grammar's literal-string tokens. If the parser's current state
|
||||
// accepts the keyword (e.g. "let" at the start of a let_assignment),
|
||||
// the keyword token type is emitted; otherwise tree-sitter falls
|
||||
// back to emitting $._word, which the parser can then accept as
|
||||
// a variable.
|
||||
//
|
||||
// The practical effect: `let` is always reserved (no production
|
||||
// accepts a variable at expression start), while `mod`, `log`,
|
||||
// `le` and `be` are only reserved in their positional contexts
|
||||
// (infix between expressions, or as part of an address suffix).
|
||||
// A bare `mod` or `log` therefore parses as a variable; the
|
||||
// evaluator (see librz/util/num/evaluator.c) checks
|
||||
// for these reserved names and raises an error rather than
|
||||
// silently resolving them through the variable callback.
|
||||
word: ($) => $._word,
|
||||
|
||||
// After a `number_value`, a following ':' may begin a typed-read
|
||||
// suffix (address_typed) or be the ternary's else-separator. Let the
|
||||
// parser use lookahead to decide based on whether a valid width
|
||||
// follows.
|
||||
conflicts: ($) => [[$.number, $.address_typed]],
|
||||
|
||||
precedences: () => [
|
||||
[
|
||||
"unitary",
|
||||
"exponent",
|
||||
"multiplication",
|
||||
"addition",
|
||||
"shift",
|
||||
"bitwise_and",
|
||||
"bitwise_xor",
|
||||
"bitwise_or",
|
||||
"comparison",
|
||||
"equality",
|
||||
"conditional",
|
||||
"let_assignment",
|
||||
"assignment",
|
||||
],
|
||||
],
|
||||
|
||||
rules: {
|
||||
// A program is one or more expressions separated by ';'. The
|
||||
// value of the whole program is the value of the last expression;
|
||||
// earlier ones are evaluated for their side effects (variable
|
||||
// bindings). A trailing ';' is allowed.
|
||||
expression: ($) => seq($._expression, repeat(seq(";", $._expression)), optional(";")),
|
||||
|
||||
_expression: ($) =>
|
||||
choice(
|
||||
$.number,
|
||||
$.address_typed,
|
||||
$.string_bytes,
|
||||
$.special_variable,
|
||||
$.variable,
|
||||
$.function,
|
||||
$.let_assignment,
|
||||
$.assignment,
|
||||
$.increment,
|
||||
$.decrement,
|
||||
$.unary_plus,
|
||||
$.unary_minus,
|
||||
$.sum,
|
||||
$.subtraction,
|
||||
$.product,
|
||||
$.division,
|
||||
$.signed_division,
|
||||
$.modulo,
|
||||
$.signed_modulo,
|
||||
$.exponent,
|
||||
$.logarithm,
|
||||
$.logical_negation,
|
||||
$.logical_not,
|
||||
$.logical_and,
|
||||
$.logical_or,
|
||||
$.logical_xor,
|
||||
$.logical_shl,
|
||||
$.logical_shr,
|
||||
$.arith_shr,
|
||||
$.logical_rol,
|
||||
$.logical_ror,
|
||||
$.less_than,
|
||||
$.less_equal,
|
||||
$.greater_than,
|
||||
$.greater_equal,
|
||||
$.equal,
|
||||
$.not_equal,
|
||||
$.conditional,
|
||||
$.parenthesized_expression,
|
||||
),
|
||||
|
||||
let_assignment: ($) =>
|
||||
prec.right("let_assignment", seq("let", field("left", $.variable), "=", field("right", $._expression))),
|
||||
|
||||
assignment: ($) => prec.right("assignment", seq(field("left", $.variable), "=", field("right", $._expression))),
|
||||
|
||||
// ++ and -- take a single primary operand - a number, variable,
|
||||
// parenthesised expression, ... - never another ++/-- or a bare
|
||||
// operator run. This keeps "++5" / "--5" / "++reg" working while a
|
||||
// stray dash run like "----------" (which the table formatter feeds
|
||||
// through rz_num to tell an invalid address apart from a number) fails
|
||||
// to parse and folds to 0 instead of reading as a chain of decrements.
|
||||
_incdec_operand: ($) =>
|
||||
choice(
|
||||
$.number,
|
||||
$.address_typed,
|
||||
$.string_bytes,
|
||||
$.special_variable,
|
||||
$.variable,
|
||||
$.function,
|
||||
$.parenthesized_expression,
|
||||
),
|
||||
|
||||
increment: ($) => prec.right("unitary", seq("++", field("right", $._incdec_operand))),
|
||||
|
||||
decrement: ($) => prec.right("unitary", seq("--", field("right", $._incdec_operand))),
|
||||
|
||||
unary_plus: ($) => prec.right("unitary", seq("+", field("right", $._expression))),
|
||||
|
||||
unary_minus: ($) => prec.right("unitary", seq("-", field("right", $._expression))),
|
||||
|
||||
logical_negation: ($) => prec.right("unitary", seq("~", field("right", $._expression))),
|
||||
|
||||
logical_not: ($) => prec.right("unitary", seq("!", field("right", $._expression))),
|
||||
|
||||
sum: ($) => prec.left("addition", seq(field("left", $._expression), "+", field("right", $._expression))),
|
||||
|
||||
subtraction: ($) => prec.left("addition", seq(field("left", $._expression), "-", field("right", $._expression))),
|
||||
|
||||
product: ($) => prec.left("multiplication", seq(field("left", $._expression), "*", field("right", $._expression))),
|
||||
|
||||
division: ($) => prec.left("multiplication", seq(field("left", $._expression), "/", field("right", $._expression))),
|
||||
|
||||
// Signed (two's-complement) division and remainder. Word
|
||||
// operators, lexed like `mod` / `log`; reservation is enforced in
|
||||
// the evaluator. Mirror RzIL's sdiv / smod.
|
||||
signed_division: ($) =>
|
||||
prec.left("multiplication", seq(field("left", $._expression), "sdiv", field("right", $._expression))),
|
||||
|
||||
modulo: ($) =>
|
||||
prec.left("multiplication", seq(field("left", $._expression), choice("mod", "%"), field("right", $._expression))),
|
||||
|
||||
signed_modulo: ($) =>
|
||||
prec.left("multiplication", seq(field("left", $._expression), "smod", field("right", $._expression))),
|
||||
|
||||
exponent: ($) => prec.right("exponent", seq(field("base", $._expression), "**", field("exponent", $._expression))),
|
||||
|
||||
logarithm: ($) =>
|
||||
prec.right("exponent", seq(field("base", $._expression), "log", field("exponent", $._expression))),
|
||||
|
||||
logical_shl: ($) => prec.left("shift", seq(field("left", $._expression), "<<", field("right", $._expression))),
|
||||
|
||||
logical_shr: ($) => prec.left("shift", seq(field("left", $._expression), ">>", field("right", $._expression))),
|
||||
|
||||
// Arithmetic (sign-propagating) shift right. Word operator `sar`,
|
||||
// mirroring RzIL's shiftr with a sign fill.
|
||||
arith_shr: ($) => prec.left("shift", seq(field("left", $._expression), "sar", field("right", $._expression))),
|
||||
|
||||
logical_rol: ($) => prec.left("shift", seq(field("left", $._expression), "<<<", field("right", $._expression))),
|
||||
|
||||
logical_ror: ($) => prec.left("shift", seq(field("left", $._expression), ">>>", field("right", $._expression))),
|
||||
|
||||
logical_and: ($) => prec.left("bitwise_and", seq(field("left", $._expression), "&", field("right", $._expression))),
|
||||
|
||||
logical_xor: ($) => prec.left("bitwise_xor", seq(field("left", $._expression), "^", field("right", $._expression))),
|
||||
|
||||
logical_or: ($) => prec.left("bitwise_or", seq(field("left", $._expression), "|", field("right", $._expression))),
|
||||
|
||||
less_than: ($) => prec.left("comparison", seq(field("left", $._expression), "<", field("right", $._expression))),
|
||||
|
||||
less_equal: ($) => prec.left("comparison", seq(field("left", $._expression), "<=", field("right", $._expression))),
|
||||
|
||||
greater_than: ($) => prec.left("comparison", seq(field("left", $._expression), ">", field("right", $._expression))),
|
||||
|
||||
greater_equal: ($) =>
|
||||
prec.left("comparison", seq(field("left", $._expression), ">=", field("right", $._expression))),
|
||||
|
||||
equal: ($) => prec.left("equality", seq(field("left", $._expression), "==", field("right", $._expression))),
|
||||
|
||||
not_equal: ($) => prec.left("equality", seq(field("left", $._expression), "!=", field("right", $._expression))),
|
||||
|
||||
// C-style ternary: cond ? then : else. Right-associative so that
|
||||
// a ? b : c ? d : e parses as a ? b : (c ? d : e). The condition
|
||||
// is truthy when non-zero; only the taken branch is evaluated.
|
||||
conditional: ($) =>
|
||||
prec.right(
|
||||
"conditional",
|
||||
seq(
|
||||
field("condition", $._expression),
|
||||
"?",
|
||||
field("consequence", $._expression),
|
||||
":",
|
||||
field("alternative", $._expression),
|
||||
),
|
||||
),
|
||||
|
||||
function: ($) => seq($.function_name, $.argument_list),
|
||||
|
||||
argument_list: ($) => seq("(", commaSep($.argument), ")"),
|
||||
|
||||
argument: ($) => $._expression,
|
||||
|
||||
parenthesized_expression: ($) => seq("(", $._expression, ")"),
|
||||
|
||||
// ---- numeric literals -----------------------------------------
|
||||
//
|
||||
// Unsigned only: a leading sign is parsed as the unary_plus /
|
||||
// unary_minus operator above.
|
||||
number_value: () => {
|
||||
const bin = /[0-1]/;
|
||||
const tern = /[0-2]/;
|
||||
const oct = /[0-7]/;
|
||||
const dec = /[0-9]/;
|
||||
const hex = /[0-9a-fA-F]/;
|
||||
const binDigits = repeat1(bin);
|
||||
const ternDigits = repeat1(tern);
|
||||
const octDigits = repeat1(oct);
|
||||
const decDigits = repeat1(dec);
|
||||
const hexDigits = repeat1(hex);
|
||||
return token(
|
||||
seq(
|
||||
choice(
|
||||
seq(
|
||||
choice(
|
||||
decDigits,
|
||||
seq("0b", binDigits),
|
||||
seq("0t", ternDigits),
|
||||
seq("0o", octDigits),
|
||||
seq("0x", hexDigits),
|
||||
),
|
||||
optional(seq(".", optional(hexDigits))),
|
||||
),
|
||||
seq(".", decDigits),
|
||||
),
|
||||
optional(seq(/[eEpP]/, optional(/[-+]/), hexDigits)),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
// Number suffix: a contiguous run of u/l/U/L/f/F optionally
|
||||
// followed by a bit-width. A run with a width, e.g. "u1" / "u7" /
|
||||
// "u8" / "u128" / "u1024", denotes a fixed-width bit-vector
|
||||
// literal; a bare run of letters keeps the old informational
|
||||
// meaning. The width is not constrained here: the evaluator
|
||||
// reports an out-of-range one, which gives a better diagnostic
|
||||
// than a parse error pointing at the digits. Defining it as a
|
||||
// token means it competes with the identifier lexer as a whole
|
||||
// word; the parser only accepts it in the trailing position of a
|
||||
// `number`, so it does not shadow user identifiers like `lower`
|
||||
// or `frob`.
|
||||
number_suffix: () => token(seq(repeat1(/[ulUFLf]/), optional(/[0-9]+/))),
|
||||
|
||||
// Number unit: a single token equal to one of the SI / IEC
|
||||
// suffix strings, lexed greedily.
|
||||
number_unit: () => token(choice(...unit_names)),
|
||||
|
||||
// Legacy single-letter suffix (carried over from the historical
|
||||
// rz_num parser, kept here so the typed evaluator is a complete
|
||||
// replacement). Two families, on disjoint letters from
|
||||
// number_suffix (u/l/U/F/L/f) so the tail lexer stays
|
||||
// unambiguous:
|
||||
// base: o (octal) b (binary) t (ternary) h/H (hex) - the
|
||||
// preceding decimal-looking digit run is re-read in that
|
||||
// base by the evaluator (`33o` == 0o33, `101b` == 0b101,
|
||||
// `121t` == 1*9+2*3+1, `10h` == 0x10).
|
||||
// scale: k/K m/M g/G - the value is multiplied by 1024^n, with
|
||||
// a decimal point allowed (`1k` == 1024, `1.5K` == 1536).
|
||||
// As with number_suffix, the token competes with the identifier
|
||||
// lexer as a whole word but is only accepted by the parser in the
|
||||
// trailing position of a `number`, so a bare `k` or `b` still
|
||||
// parses as a variable.
|
||||
number_legacy_suffix: () => token(/[obtkmg]|[hH]|[KMG]/),
|
||||
|
||||
// Legacy hexadecimal literal whose digit run starts with a decimal
|
||||
// digit yet contains hex letters, written with a trailing 'h'/'H'
|
||||
// (e.g. "3a7fh", "0ffh"). number_value only captures the leading
|
||||
// decimal digits ("3") and the hex tail would otherwise lex as a
|
||||
// separate identifier, breaking the parse. The whole form therefore
|
||||
// needs its own token. Two neighbouring cases stay on their existing
|
||||
// paths: a letter-leading run ("deadh") lexes as an identifier and is
|
||||
// reinterpreted by the evaluator's trailing-'h' fallback, and an
|
||||
// all-decimal run ("100h") keeps using number_value + the single-char
|
||||
// number_legacy_suffix. The mandatory hex letter ([a-fA-F]) is what
|
||||
// distinguishes this token from those, so "100h"/"12h" do not match.
|
||||
number_legacy_hex: () => token(seq(/[0-9]/, /[0-9a-fA-F]*/, /[a-fA-F]/, /[0-9a-fA-F]*/, /[hH]/)),
|
||||
|
||||
number: ($) =>
|
||||
choice(
|
||||
seq($.number_value, optional(choice($.number_suffix, $.number_unit, $.number_legacy_suffix))),
|
||||
$.number_legacy_hex,
|
||||
),
|
||||
|
||||
// ---- address with explicit width and endianness ---------------
|
||||
// ---- typed-address dereference --------------------------------
|
||||
// 0x1234:le32 little-endian unsigned 32-bit
|
||||
// 0xDEADBEEF:be64 big-endian unsigned 64-bit
|
||||
// 0x1000:8 native-endian unsigned 8-bit
|
||||
// 0x1000:s32 native-endian SIGNED 32-bit
|
||||
// 0x1000:lef32 little-endian 32-bit FLOAT
|
||||
// 0x1000:f16 native-endian half-precision float
|
||||
//
|
||||
// The tail is one token so it does not interact with the
|
||||
// identifier lexer. It is:
|
||||
// [le|be]? [s]? (8|16|32|64|128) integer read
|
||||
// [le|be]? f (16|32|64) float read
|
||||
// 's' marks a signed integer read; 'f' a float read (half /
|
||||
// single / double).
|
||||
// The trailing ':' of a typed read can collide with the ternary's
|
||||
// else-separator after a numeric literal (`c ? 0x10 : 0` vs
|
||||
// `0x10:le32`). The two are disambiguated by lookahead - declared
|
||||
// as a conflict below - so `address_typed` is chosen only when a
|
||||
// valid width actually follows the colon.
|
||||
address_typed: ($) => seq($.number_value, ":", $.address_width),
|
||||
address_width: () =>
|
||||
token(
|
||||
seq(
|
||||
optional(choice("le", "be")),
|
||||
choice(seq(optional("s"), choice("8", "16", "32", "64", "128")), seq("f", choice("16", "32", "64", "128"))),
|
||||
),
|
||||
),
|
||||
|
||||
// ---- string-as-bytes literal ----------------------------------
|
||||
string_bytes: () => token(seq('"', repeat(choice(/[^"\\\n]/, /\\./)), '"')),
|
||||
|
||||
// ---- special (Rizin) variables --------------------------------
|
||||
special_variable: () =>
|
||||
token(
|
||||
choice(
|
||||
"$$$",
|
||||
"$$",
|
||||
"$alias",
|
||||
"$b",
|
||||
"$B",
|
||||
"$c",
|
||||
"$Cn",
|
||||
"$D",
|
||||
"$DB",
|
||||
"$DD",
|
||||
"$DS",
|
||||
"$e",
|
||||
"$f",
|
||||
"$F",
|
||||
"$Fb",
|
||||
"$FB",
|
||||
"$Fi",
|
||||
"$FS",
|
||||
"$Ff",
|
||||
"$Fj",
|
||||
"$fl",
|
||||
"$j",
|
||||
"$Ja",
|
||||
"$l",
|
||||
"$M",
|
||||
"$MM",
|
||||
"$m",
|
||||
"$O",
|
||||
"$o",
|
||||
"$p",
|
||||
"$P",
|
||||
"$r",
|
||||
"$s",
|
||||
"$S",
|
||||
"$SS",
|
||||
"$v",
|
||||
"$w",
|
||||
),
|
||||
),
|
||||
|
||||
// The word lexer used by the `word: ($) => $._word` directive.
|
||||
//
|
||||
// Tree-sitter's keyword extraction emits the literal-token type
|
||||
// (e.g. `"mod"`) instead of `_word` when the matched identifier
|
||||
// equals a keyword used elsewhere in the grammar AND the parser
|
||||
// accepts that keyword at the current parse state. When the
|
||||
// parser does NOT accept the keyword - for example, a bare
|
||||
// `mod` in expression-leaf position - tree-sitter falls back to
|
||||
// emitting `_word`, which lets the bare keyword parse as a
|
||||
// variable.
|
||||
//
|
||||
// This grammar accepts that fallback at parse time; reservation
|
||||
// is enforced at evaluator level (see rz_num_math_value()),
|
||||
// which raises an error if a variable's name equals one of the
|
||||
// reserved words. The `let` keyword is the exception: because
|
||||
// it always appears at the start of an expression, the parser
|
||||
// never accepts a variable in its place, so a bare `let` is a
|
||||
// syntax error.
|
||||
_word: () => token(seq(identifier_start, identifier_continue)),
|
||||
variable: ($) => $._word,
|
||||
function_name: ($) => $._word,
|
||||
},
|
||||
});
|
||||
|
||||
function commaSep(rule) {
|
||||
return optional(commaSep1(rule));
|
||||
}
|
||||
|
||||
function commaSep1(rule) {
|
||||
return seq(rule, repeat(seq(",", rule)));
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
project('rizin-math-parser', 'c',
|
||||
license: 'LGPL-3.0-only',
|
||||
meson_version: '>=0.55.0',
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
if cc.has_argument('--std=c99')
|
||||
add_project_arguments('--std=c99', language: ['c'])
|
||||
endif
|
||||
|
||||
tree_sitter_dep = dependency('tree-sitter')
|
||||
grammar_js = files('grammar.js')
|
||||
tree_sitter_wrap_py = files('meson_tree_sitter_generate.py')
|
||||
|
||||
subdir('src')
|
||||
|
||||
libmath_parser = static_library('math_parser', math_parser_files,
|
||||
include_directories: math_parser_inc,
|
||||
dependencies: tree_sitter_dep.partial_dependency(includes: true),
|
||||
implicit_include_directories: true
|
||||
)
|
||||
|
||||
math_parser_dep = declare_dependency(
|
||||
link_with: libmath_parser,
|
||||
include_directories: math_parser_inc,
|
||||
dependencies: tree_sitter_dep
|
||||
)
|
||||
meson.override_dependency('rizin-math-parser', math_parser_dep)
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021 ret2libc <[email protected]>
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
"""Python script to execute tree-sitter generate in the right output folder,
|
||||
necessary because tree-sitter generate does not allow to specify it."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def parse():
|
||||
if len(sys.argv) <= 3:
|
||||
print(
|
||||
"Usage: {} <tree-sitter-path> <output-dir> <grammar.js>".format(sys.argv[0])
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
tree_sitter_exe = sys.argv[1]
|
||||
output_dir = sys.argv[2]
|
||||
grammar_js = sys.argv[3]
|
||||
|
||||
return tree_sitter_exe, output_dir, grammar_js
|
||||
|
||||
|
||||
def main():
|
||||
tree_sitter_exe, output_dir, grammar_js = parse()
|
||||
|
||||
grammar_js = os.path.abspath(grammar_js)
|
||||
output_dir = os.path.abspath(output_dir)
|
||||
tree_sitter_exe = os.path.abspath(tree_sitter_exe)
|
||||
|
||||
subprocess.run(
|
||||
[tree_sitter_exe, "generate", grammar_js], check=True, cwd=output_dir
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "tree-sitter-rznum",
|
||||
"description": "Tree-sitter grammar for the Rizin RzNum mathematical expression language.",
|
||||
"license": "LGPL-3.0-only",
|
||||
"version": "0.1.0",
|
||||
"main": "bindings/node",
|
||||
"keywords": [
|
||||
"parsing",
|
||||
"incremental",
|
||||
"rizin",
|
||||
"math"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "tree-sitter test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tree-sitter-cli": "^0.26.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
tree_sitter_bin = find_program('tree-sitter', required: false)
|
||||
node_bin = find_program('node', required: false)
|
||||
if tree_sitter_bin.found() and node_bin.found() and tree_sitter_dep.type_name() != 'internal'
|
||||
parser_c = custom_target('parser_src_c',
|
||||
command: [tree_sitter_wrap_py, tree_sitter_bin, '@OUTDIR@/..', '@INPUT@'],
|
||||
input: [grammar_js],
|
||||
output: 'parser.c',
|
||||
)
|
||||
else
|
||||
parser_c = files('parser.c')
|
||||
endif
|
||||
|
||||
math_parser_files = [parser_c]
|
||||
math_parser_inc = [include_directories('tree_sitter')]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
#ifndef TREE_SITTER_ALLOC_H_
|
||||
#define TREE_SITTER_ALLOC_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Allow clients to override allocation functions
|
||||
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||
|
||||
extern void *(*ts_current_malloc)(size_t size);
|
||||
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
||||
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
||||
extern void (*ts_current_free)(void *ptr);
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc ts_current_malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc ts_current_calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc ts_current_realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free ts_current_free
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free free
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ALLOC_H_
|
||||
@@ -0,0 +1,330 @@
|
||||
#ifndef TREE_SITTER_ARRAY_H_
|
||||
#define TREE_SITTER_ARRAY_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "./alloc.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
#define Array(T) \
|
||||
struct { \
|
||||
T *contents; \
|
||||
uint32_t size; \
|
||||
uint32_t capacity; \
|
||||
}
|
||||
|
||||
/// Initialize an array.
|
||||
#define array_init(self) \
|
||||
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||
|
||||
/// Create an empty array.
|
||||
#define array_new() \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
/// Get a pointer to the element at a given `index` in the array.
|
||||
#define array_get(self, _index) \
|
||||
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||
|
||||
/// Get a pointer to the first element in the array.
|
||||
#define array_front(self) array_get(self, 0)
|
||||
|
||||
/// Get a pointer to the last element in the array.
|
||||
#define array_back(self) array_get(self, (self)->size - 1)
|
||||
|
||||
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_clear(self) ((self)->size = 0)
|
||||
|
||||
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||
/// less than the array's current capacity, this function has no effect.
|
||||
#define array_reserve(self, new_capacity) \
|
||||
((self)->contents = _array__reserve( \
|
||||
(void *)(self)->contents, &(self)->capacity, \
|
||||
array_elem_size(self), new_capacity) \
|
||||
)
|
||||
|
||||
/// Free any memory allocated for this array. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_delete(self) \
|
||||
do { \
|
||||
if ((self)->contents) ts_free((self)->contents); \
|
||||
(self)->contents = NULL; \
|
||||
(self)->size = 0; \
|
||||
(self)->capacity = 0; \
|
||||
} while (0)
|
||||
|
||||
/// Push a new `element` onto the end of the array.
|
||||
#define array_push(self, element) \
|
||||
do { \
|
||||
(self)->contents = _array__grow( \
|
||||
(void *)(self)->contents, (self)->size, &(self)->capacity, \
|
||||
1, array_elem_size(self) \
|
||||
); \
|
||||
(self)->contents[(self)->size++] = (element); \
|
||||
} while(0)
|
||||
|
||||
/// Increase the array's size by `count` elements.
|
||||
/// New elements are zero-initialized.
|
||||
#define array_grow_by(self, count) \
|
||||
do { \
|
||||
if ((count) == 0) break; \
|
||||
(self)->contents = _array__grow( \
|
||||
(self)->contents, (self)->size, &(self)->capacity, \
|
||||
count, array_elem_size(self) \
|
||||
); \
|
||||
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||
(self)->size += (count); \
|
||||
} while (0)
|
||||
|
||||
/// Append all elements from one array to the end of another.
|
||||
#define array_push_all(self, other) \
|
||||
array_extend((self), (other)->size, (other)->contents)
|
||||
|
||||
/// Append `count` elements to the end of the array, reading their values from the
|
||||
/// `contents` pointer.
|
||||
#define array_extend(self, count, other_contents) \
|
||||
(self)->contents = _array__splice( \
|
||||
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||
array_elem_size(self), (self)->size, 0, count, other_contents \
|
||||
)
|
||||
|
||||
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||
/// the same index, insert `new_count` new elements, reading their values from the
|
||||
/// `new_contents` pointer.
|
||||
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||
(self)->contents = _array__splice( \
|
||||
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||
array_elem_size(self), _index, old_count, new_count, new_contents \
|
||||
)
|
||||
|
||||
/// Insert one `element` into the array at the given `index`.
|
||||
#define array_insert(self, _index, element) \
|
||||
(self)->contents = _array__splice( \
|
||||
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||
array_elem_size(self), _index, 0, 1, &(element) \
|
||||
)
|
||||
|
||||
/// Remove one element from the array at the given `index`.
|
||||
#define array_erase(self, _index) \
|
||||
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
|
||||
|
||||
/// Pop the last element off the array, returning the element by value.
|
||||
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||
|
||||
/// Assign the contents of one array to another, reallocating if necessary.
|
||||
#define array_assign(self, other) \
|
||||
(self)->contents = _array__assign( \
|
||||
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
|
||||
)
|
||||
|
||||
/// Swap one array with another
|
||||
#define array_swap(self, other) \
|
||||
do { \
|
||||
void *_array_swap_tmp = (void *)(self)->contents; \
|
||||
(self)->contents = (other)->contents; \
|
||||
(other)->contents = _array_swap_tmp; \
|
||||
_array__swap(&(self)->size, &(self)->capacity, \
|
||||
&(other)->size, &(other)->capacity); \
|
||||
} while (0)
|
||||
|
||||
/// Get the size of the array contents
|
||||
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
///
|
||||
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||
/// out-parameter is set to the existing value's index, and the `exists`
|
||||
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||
/// is set to false.
|
||||
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_with`.
|
||||
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
#define array_insert_sorted_with(self, compare, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||
/// a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_by`.
|
||||
#define array_insert_sorted_by(self, field, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
// Private
|
||||
|
||||
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
|
||||
// are passed to the various `_array__*` functions below to address strict aliasing
|
||||
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
|
||||
//
|
||||
// The `Array` type itself was not altered as a solution in order to avoid breakage
|
||||
// with existing consumers (in particular, parsers with external scanners).
|
||||
|
||||
/// This is not what you're looking for, see `array_erase`.
|
||||
static inline void _array__erase(void* self_contents, uint32_t *size,
|
||||
size_t element_size, uint32_t index) {
|
||||
assert(index < *size);
|
||||
char *contents = (char *)self_contents;
|
||||
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||
(*size - index - 1) * element_size);
|
||||
(*size)--;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_reserve`.
|
||||
static inline void *_array__reserve(void *contents, uint32_t *capacity,
|
||||
size_t element_size, uint32_t new_capacity) {
|
||||
void *new_contents = contents;
|
||||
if (new_capacity > *capacity) {
|
||||
if (contents) {
|
||||
new_contents = ts_realloc(contents, new_capacity * element_size);
|
||||
} else {
|
||||
new_contents = ts_malloc(new_capacity * element_size);
|
||||
}
|
||||
*capacity = new_capacity;
|
||||
}
|
||||
return new_contents;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_assign`.
|
||||
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
|
||||
const void *other_contents, uint32_t other_size, size_t element_size) {
|
||||
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
|
||||
*self_size = other_size;
|
||||
memcpy(new_contents, other_contents, *self_size * element_size);
|
||||
return new_contents;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_swap`.
|
||||
static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity,
|
||||
uint32_t *other_size, uint32_t *other_capacity) {
|
||||
uint32_t tmp_size = *self_size;
|
||||
uint32_t tmp_capacity = *self_capacity;
|
||||
*self_size = *other_size;
|
||||
*self_capacity = *other_capacity;
|
||||
*other_size = tmp_size;
|
||||
*other_capacity = tmp_capacity;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
|
||||
uint32_t count, size_t element_size) {
|
||||
void *new_contents = contents;
|
||||
uint32_t new_size = size + count;
|
||||
if (new_size > *capacity) {
|
||||
uint32_t new_capacity = *capacity * 2;
|
||||
if (new_capacity < 8) new_capacity = 8;
|
||||
if (new_capacity < new_size) new_capacity = new_size;
|
||||
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
|
||||
}
|
||||
return new_contents;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_splice`.
|
||||
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
|
||||
size_t element_size,
|
||||
uint32_t index, uint32_t old_count,
|
||||
uint32_t new_count, const void *elements) {
|
||||
uint32_t new_size = *size + new_count - old_count;
|
||||
uint32_t old_end = index + old_count;
|
||||
uint32_t new_end = index + new_count;
|
||||
assert(old_end <= *size);
|
||||
|
||||
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
|
||||
|
||||
char *contents = (char *)new_contents;
|
||||
if (*size > old_end) {
|
||||
memmove(
|
||||
contents + new_end * element_size,
|
||||
contents + old_end * element_size,
|
||||
(*size - old_end) * element_size
|
||||
);
|
||||
}
|
||||
if (new_count > 0) {
|
||||
if (elements) {
|
||||
memcpy(
|
||||
(contents + index * element_size),
|
||||
elements,
|
||||
new_count * element_size
|
||||
);
|
||||
} else {
|
||||
memset(
|
||||
(contents + index * element_size),
|
||||
0,
|
||||
new_count * element_size
|
||||
);
|
||||
}
|
||||
}
|
||||
*size += new_count - old_count;
|
||||
|
||||
return new_contents;
|
||||
}
|
||||
|
||||
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||
do { \
|
||||
*(_index) = start; \
|
||||
*(_exists) = false; \
|
||||
uint32_t size = (self)->size - *(_index); \
|
||||
if (size == 0) break; \
|
||||
int comparison; \
|
||||
while (size > 1) { \
|
||||
uint32_t half_size = size / 2; \
|
||||
uint32_t mid_index = *(_index) + half_size; \
|
||||
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||
if (comparison <= 0) *(_index) = mid_index; \
|
||||
size -= half_size; \
|
||||
} \
|
||||
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||
if (comparison == 0) *(_exists) = true; \
|
||||
else if (comparison < 0) *(_index) += 1; \
|
||||
} while (0)
|
||||
|
||||
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||
/// parameter by reference in order to work with the generic sorting function above.
|
||||
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ARRAY_H_
|
||||
@@ -0,0 +1,286 @@
|
||||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSStateId;
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
typedef struct TSLanguageMetadata {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
} TSLanguageMetadata;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
// Used to index the field and supertype maps.
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
void (*log)(const TSLexer *, const char *, ...);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
uint16_t reserved_word_set_id;
|
||||
} TSLexerMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} TSCharacterRange;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t abi_version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexerMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
const char *name;
|
||||
const TSSymbol *reserved_words;
|
||||
uint16_t max_reserved_word_set_size;
|
||||
uint32_t supertype_count;
|
||||
const TSSymbol *supertype_symbols;
|
||||
const TSMapSlice *supertype_map_slices;
|
||||
const TSSymbol *supertype_map_entries;
|
||||
TSLanguageMetadata metadata;
|
||||
};
|
||||
|
||||
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||
uint32_t index = 0;
|
||||
uint32_t size = len - index;
|
||||
while (size > 1) {
|
||||
uint32_t half_size = size / 2;
|
||||
uint32_t mid_index = index + half_size;
|
||||
const TSCharacterRange *range = &ranges[mid_index];
|
||||
if (lookahead >= range->start && lookahead <= range->end) {
|
||||
return true;
|
||||
} else if (lookahead > range->end) {
|
||||
index = mid_index;
|
||||
}
|
||||
size -= half_size;
|
||||
}
|
||||
const TSCharacterRange *range = &ranges[index];
|
||||
return (lookahead >= range->start && lookahead <= range->end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define UNUSED __pragma(warning(suppress : 4101))
|
||||
#else
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
UNUSED \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ADVANCE_MAP(...) \
|
||||
{ \
|
||||
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||
if (map[i] == lookahead) { \
|
||||
state = map[i + 1]; \
|
||||
goto next_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value) \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value), \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_name, \
|
||||
.child_count = children, \
|
||||
.dynamic_precedence = precedence, \
|
||||
.production_id = prod_id \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
@@ -0,0 +1,759 @@
|
||||
===============================================================================
|
||||
Integer Number
|
||||
===============================================================================
|
||||
4
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Floating Number
|
||||
===============================================================================
|
||||
4.0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Binary Number
|
||||
===============================================================================
|
||||
0b0001110101010101010101010100111111000
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Ternary Number
|
||||
===============================================================================
|
||||
0t0012120012
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Octal Number
|
||||
===============================================================================
|
||||
0o1234345652323421
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Hexadecimal Number
|
||||
===============================================================================
|
||||
0xABBCC00FF
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Scientific Number Format
|
||||
===============================================================================
|
||||
1.23E+10
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Number Suffix
|
||||
===============================================================================
|
||||
23423423424ULL
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value) (number_suffix)))
|
||||
|
||||
===============================================================================
|
||||
Number Unit MiB
|
||||
===============================================================================
|
||||
0x1023MiB
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value) (number_unit)))
|
||||
|
||||
===============================================================================
|
||||
Number Unit PiB
|
||||
===============================================================================
|
||||
2PiB
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value) (number_unit)))
|
||||
|
||||
===============================================================================
|
||||
Number Unit EiB
|
||||
===============================================================================
|
||||
4EiB
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value) (number_unit)))
|
||||
|
||||
===============================================================================
|
||||
Function with simple arguments
|
||||
===============================================================================
|
||||
func(a, b, c)
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(function
|
||||
(function_name)
|
||||
(argument_list
|
||||
(argument (variable))
|
||||
(argument (variable))
|
||||
(argument (variable)))))
|
||||
|
||||
===============================================================================
|
||||
Function with expression arguments
|
||||
===============================================================================
|
||||
f(1 + 2, x * y)
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(function
|
||||
(function_name)
|
||||
(argument_list
|
||||
(argument (sum left: (number (number_value)) right: (number (number_value))))
|
||||
(argument (product left: (variable) right: (variable))))))
|
||||
|
||||
===============================================================================
|
||||
Variable
|
||||
===============================================================================
|
||||
x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
===============================================================================
|
||||
Variable with dots (Rizin flag)
|
||||
===============================================================================
|
||||
y + sym.func.1000055d4
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Special variable $$
|
||||
===============================================================================
|
||||
$$
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (special_variable))
|
||||
|
||||
===============================================================================
|
||||
Special variable $S
|
||||
===============================================================================
|
||||
$S
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (special_variable))
|
||||
|
||||
===============================================================================
|
||||
Special variable $F arithmetic
|
||||
===============================================================================
|
||||
$F + 0x10
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum left: (special_variable) right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Address with little-endian width
|
||||
===============================================================================
|
||||
0x1234:le32
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (address_typed (number_value) (address_width)))
|
||||
|
||||
===============================================================================
|
||||
Address with big-endian width
|
||||
===============================================================================
|
||||
0xDEADBEEF:be64
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (address_typed (number_value) (address_width)))
|
||||
|
||||
===============================================================================
|
||||
Address with bare width
|
||||
===============================================================================
|
||||
0x1000:8
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (address_typed (number_value) (address_width)))
|
||||
|
||||
===============================================================================
|
||||
String-as-bytes
|
||||
===============================================================================
|
||||
"ABC"
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (string_bytes))
|
||||
|
||||
===============================================================================
|
||||
String-as-bytes in arithmetic
|
||||
===============================================================================
|
||||
"AB" + 1
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum left: (string_bytes) right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Assignment
|
||||
===============================================================================
|
||||
x = y + 4.0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(assignment
|
||||
left: (variable)
|
||||
right: (sum
|
||||
left: (variable)
|
||||
right: (number (number_value)))))
|
||||
|
||||
===============================================================================
|
||||
Let binding
|
||||
===============================================================================
|
||||
let x = y + 4.0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(let_assignment
|
||||
left: (variable)
|
||||
right: (sum
|
||||
left: (variable)
|
||||
right: (number (number_value)))))
|
||||
|
||||
===============================================================================
|
||||
Variable Increment
|
||||
===============================================================================
|
||||
++x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (increment right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Variable Decrement
|
||||
===============================================================================
|
||||
--x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (decrement right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Unary minus on number
|
||||
===============================================================================
|
||||
-1
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (unary_minus right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Unary plus on number
|
||||
===============================================================================
|
||||
+0x10
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (unary_plus right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Unary minus on expression
|
||||
===============================================================================
|
||||
-(x + 1)
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(unary_minus right: (parenthesized_expression
|
||||
(sum left: (variable) right: (number (number_value))))))
|
||||
|
||||
===============================================================================
|
||||
Unary minus has higher precedence than multiplication
|
||||
===============================================================================
|
||||
-x * y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(product
|
||||
left: (unary_minus right: (variable))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Subtraction is binary not unary
|
||||
===============================================================================
|
||||
1 - 2
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(subtraction
|
||||
left: (number (number_value))
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Addition
|
||||
===============================================================================
|
||||
x + 4.0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (variable)
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Multiplication
|
||||
===============================================================================
|
||||
x * 4.0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(product
|
||||
left: (variable)
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Multiplication and addition precedence
|
||||
===============================================================================
|
||||
x * y + z
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (product left: (variable) right: (variable))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Division
|
||||
===============================================================================
|
||||
4 / x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(division
|
||||
left: (number (number_value))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Modulo (mod keyword)
|
||||
===============================================================================
|
||||
14 mod x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(modulo
|
||||
left: (number (number_value))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Modulo (% operator)
|
||||
===============================================================================
|
||||
14 % x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(modulo
|
||||
left: (number (number_value))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Exponent
|
||||
===============================================================================
|
||||
x ** y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(exponent
|
||||
base: (variable)
|
||||
exponent: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Exponent is right-associative
|
||||
===============================================================================
|
||||
x ** 4 ** z
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(exponent
|
||||
base: (variable)
|
||||
exponent: (exponent
|
||||
base: (number (number_value))
|
||||
exponent: (variable))))
|
||||
|
||||
===============================================================================
|
||||
Logarithm
|
||||
===============================================================================
|
||||
x log y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(logarithm base: (variable) exponent: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Bitwise NOT
|
||||
===============================================================================
|
||||
~x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_negation right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Logical NOT
|
||||
===============================================================================
|
||||
!x
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_not right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Bitwise AND
|
||||
===============================================================================
|
||||
x & y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_and left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Bitwise OR
|
||||
===============================================================================
|
||||
x | y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_or left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Bitwise XOR
|
||||
===============================================================================
|
||||
x ^ y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_xor left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Logical SHL
|
||||
===============================================================================
|
||||
x << y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_shl left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Logical SHR
|
||||
===============================================================================
|
||||
x >> y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_shr left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Logical ROL
|
||||
===============================================================================
|
||||
x <<< y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_rol left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Logical ROR
|
||||
===============================================================================
|
||||
x >>> y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (logical_ror left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Less than
|
||||
===============================================================================
|
||||
a < b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (less_than left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Less or equal
|
||||
===============================================================================
|
||||
a <= b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (less_equal left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Greater than
|
||||
===============================================================================
|
||||
a > b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (greater_than left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Greater or equal
|
||||
===============================================================================
|
||||
a >= b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (greater_equal left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Equal
|
||||
===============================================================================
|
||||
a == b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (equal left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Not equal
|
||||
===============================================================================
|
||||
a != b
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (not_equal left: (variable) right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Comparison with arithmetic
|
||||
===============================================================================
|
||||
a + 1 < b * 2
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(less_than
|
||||
left: (sum left: (variable) right: (number (number_value)))
|
||||
right: (product left: (variable) right: (number (number_value)))))
|
||||
|
||||
===============================================================================
|
||||
Parenthesised expression
|
||||
===============================================================================
|
||||
(z + x) * y
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(product
|
||||
left: (parenthesized_expression
|
||||
(sum left: (variable) right: (variable)))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Parentheses with exponents and division
|
||||
===============================================================================
|
||||
(z + x) ** ((y / 2) - 1)
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(exponent
|
||||
base: (parenthesized_expression
|
||||
(sum left: (variable) right: (variable)))
|
||||
exponent: (parenthesized_expression
|
||||
(subtraction
|
||||
left: (parenthesized_expression
|
||||
(division left: (variable) right: (number (number_value))))
|
||||
right: (number (number_value))))))
|
||||
|
||||
===============================================================================
|
||||
Shift binds tighter than bitwise OR
|
||||
===============================================================================
|
||||
a | b << 2
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(logical_or
|
||||
left: (variable)
|
||||
right: (logical_shl left: (variable) right: (number (number_value)))))
|
||||
|
||||
===============================================================================
|
||||
Bitwise AND binds tighter than XOR which binds tighter than OR
|
||||
===============================================================================
|
||||
a | b ^ c & d
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(logical_or
|
||||
left: (variable)
|
||||
right: (logical_xor
|
||||
left: (variable)
|
||||
right: (logical_and left: (variable) right: (variable)))))
|
||||
|
||||
===============================================================================
|
||||
Unicode identifier (Greek)
|
||||
===============================================================================
|
||||
αβγ
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
===============================================================================
|
||||
Unicode identifier (Han)
|
||||
===============================================================================
|
||||
半径
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
===============================================================================
|
||||
Unicode identifier (Arabic)
|
||||
===============================================================================
|
||||
متغير
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
===============================================================================
|
||||
Unicode function name (Han)
|
||||
===============================================================================
|
||||
最大(5, 3)
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(function
|
||||
(function_name)
|
||||
(argument_list
|
||||
(argument (number (number_value)))
|
||||
(argument (number (number_value))))))
|
||||
|
||||
===============================================================================
|
||||
Unicode variable binding and reference (Japanese)
|
||||
===============================================================================
|
||||
(変数 = 7) + 変数
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (parenthesized_expression
|
||||
(assignment
|
||||
left: (variable)
|
||||
right: (number (number_value))))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Variable starting with reserved-word prefix is allowed
|
||||
===============================================================================
|
||||
modulus + logger + letter
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (sum left: (variable) right: (variable))
|
||||
right: (variable)))
|
||||
|
||||
===============================================================================
|
||||
Bare let is rejected at parse time
|
||||
===============================================================================
|
||||
let
|
||||
-------------------------------------------------------------------------------
|
||||
(ERROR)
|
||||
|
||||
===============================================================================
|
||||
Bare mod parses as identifier (rejected by the evaluator)
|
||||
===============================================================================
|
||||
mod
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
===============================================================================
|
||||
Bare log parses as identifier (rejected by the evaluator)
|
||||
===============================================================================
|
||||
log
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (variable))
|
||||
|
||||
|
||||
===============================================================================
|
||||
Scientific notation requires exponent digits
|
||||
===============================================================================
|
||||
1.5e10
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Hex P-exponent
|
||||
===============================================================================
|
||||
0x1.8p3
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Negative exponent
|
||||
===============================================================================
|
||||
1.5e-3
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Address typed in arithmetic
|
||||
===============================================================================
|
||||
0x1000:le32 + 4
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (address_typed (number_value) (address_width))
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Hex literal that overflows ut64
|
||||
===============================================================================
|
||||
0xffffffffffffffffffffffffffff
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Bit-vector width suffix
|
||||
===============================================================================
|
||||
0xffu8
|
||||
-------------------------------------------------------------------------------
|
||||
(expression (number (number_value) (number_suffix)))
|
||||
|
||||
===============================================================================
|
||||
Bit-vector arithmetic
|
||||
===============================================================================
|
||||
200u8 + 100u8
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(sum
|
||||
left: (number (number_value) (number_suffix))
|
||||
right: (number (number_value) (number_suffix))))
|
||||
|
||||
===============================================================================
|
||||
Statement sequence with semicolons
|
||||
===============================================================================
|
||||
1; 2; 3
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(number (number_value))
|
||||
(number (number_value))
|
||||
(number (number_value)))
|
||||
|
||||
===============================================================================
|
||||
Signed division
|
||||
===============================================================================
|
||||
10 sdiv 3
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(signed_division
|
||||
left: (number (number_value))
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Signed modulo
|
||||
===============================================================================
|
||||
10 smod 3
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(signed_modulo
|
||||
left: (number (number_value))
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Arithmetic shift right
|
||||
===============================================================================
|
||||
0xff00 sar 4
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(arith_shr
|
||||
left: (number (number_value))
|
||||
right: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Typed read signed width
|
||||
===============================================================================
|
||||
0x1000:s32
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(address_typed
|
||||
(number_value)
|
||||
(address_width)))
|
||||
|
||||
===============================================================================
|
||||
Typed read float width
|
||||
===============================================================================
|
||||
0x1000:lef32
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(address_typed
|
||||
(number_value)
|
||||
(address_width)))
|
||||
|
||||
===============================================================================
|
||||
Typed read half-precision float
|
||||
===============================================================================
|
||||
0x1000:f16
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(address_typed
|
||||
(number_value)
|
||||
(address_width)))
|
||||
|
||||
===============================================================================
|
||||
Ternary conditional
|
||||
===============================================================================
|
||||
1 ? 10 : 20
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(conditional
|
||||
condition: (number (number_value))
|
||||
consequence: (number (number_value))
|
||||
alternative: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Ternary is right-associative
|
||||
===============================================================================
|
||||
a ? b : c ? d : e
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(conditional
|
||||
condition: (variable)
|
||||
consequence: (variable)
|
||||
alternative: (conditional
|
||||
condition: (variable)
|
||||
consequence: (variable)
|
||||
alternative: (variable))))
|
||||
|
||||
===============================================================================
|
||||
Ternary with typed-read branch
|
||||
===============================================================================
|
||||
1 ? 0x1000:le32 : 0
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(conditional
|
||||
condition: (number (number_value))
|
||||
consequence: (address_typed
|
||||
(number_value)
|
||||
(address_width))
|
||||
alternative: (number (number_value))))
|
||||
|
||||
===============================================================================
|
||||
Typed read quad-precision float
|
||||
===============================================================================
|
||||
0x1000:f128
|
||||
-------------------------------------------------------------------------------
|
||||
(expression
|
||||
(address_typed
|
||||
(number_value)
|
||||
(address_width)))
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json",
|
||||
"grammars": [
|
||||
{
|
||||
"name": "rznum",
|
||||
"camelcase": "Rznum",
|
||||
"scope": "source.rznum",
|
||||
"path": ".",
|
||||
"file-types": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "0.1.0",
|
||||
"license": "LGPL-3.0-only",
|
||||
"description": "Tree-sitter grammar for the Rizin RzNum mathematical expression language.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Anton Kochkov",
|
||||
"email": "[email protected]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bindings": {
|
||||
"c": true,
|
||||
"go": false,
|
||||
"node": false,
|
||||
"python": false,
|
||||
"rust": false,
|
||||
"swift": false
|
||||
}
|
||||
}
|
||||
+529
-51
@@ -69,6 +69,9 @@ RUN
|
||||
|
||||
|
||||
NAME="%v 'A'"
|
||||
# BROKEN: char literals ('X') were dropped from the new grammar, which has
|
||||
# no char-literal rule, so a quoted character no longer evaluates to its code
|
||||
BROKEN=1
|
||||
FILE=--
|
||||
CMDS=%v "'A'"
|
||||
EXPECT=<<EOF
|
||||
@@ -77,6 +80,9 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME="%v 'A'+3"
|
||||
# BROKEN: char literals ('X') were dropped from the new grammar, which has
|
||||
# no char-literal rule, so a quoted character no longer evaluates to its code
|
||||
BROKEN=1
|
||||
FILE=--
|
||||
CMDS=%v "'A'+3"
|
||||
EXPECT=<<EOF
|
||||
@@ -85,6 +91,9 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME="%v 3+'A'-3"
|
||||
# BROKEN: char literals ('X') were dropped from the new grammar, which has
|
||||
# no char-literal rule, so a quoted character no longer evaluates to its code
|
||||
BROKEN=1
|
||||
FILE=--
|
||||
CMDS=%v "3+'A'-3"
|
||||
EXPECT=<<EOF
|
||||
@@ -93,6 +102,9 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME="%v 33^'A'"
|
||||
# BROKEN: char literals ('X') were dropped from the new grammar, which has
|
||||
# no char-literal rule, so a quoted character no longer evaluates to its code
|
||||
BROKEN=1
|
||||
FILE=--
|
||||
CMDS=%v "32^'A'"
|
||||
EXPECT=<<EOF
|
||||
@@ -160,21 +172,15 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME="% 2*1.5"
|
||||
# BROKEN: a fractional result is now an exact RzBigDecimal ('decimal N'),
|
||||
# not a 64-bit double, so the old 'float'/'hex' (double bit-pattern) lines differ
|
||||
BROKEN=1
|
||||
FILE=--
|
||||
CMDS=% 2*1.5
|
||||
EXPECT=<<EOF
|
||||
int32 2
|
||||
uint32 2
|
||||
hex 0x2
|
||||
octal 02
|
||||
unit 2
|
||||
segment 0000:0002
|
||||
string "\x02"
|
||||
fvalue 3.0
|
||||
float 3.000000f
|
||||
double 3.000000
|
||||
binary 0b00000010
|
||||
trits 0t2
|
||||
float 3
|
||||
scifmt 3
|
||||
hex 0x4008000000000000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -239,18 +245,9 @@ NAME="% 10**-2"
|
||||
FILE=--
|
||||
CMDS=% 10**-2
|
||||
EXPECT=<<EOF
|
||||
int32 0
|
||||
uint32 0
|
||||
hex 0x0
|
||||
octal 00
|
||||
unit 0
|
||||
segment 0000:0000
|
||||
string "\0"
|
||||
fvalue 0.0
|
||||
float 0.010000f
|
||||
double 0.010000
|
||||
binary 0b00000000
|
||||
trits 0t0
|
||||
float 0.01
|
||||
scifmt 0.01
|
||||
hex 0x3f847ae147ae147b
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -277,18 +274,9 @@ NAME="% 2**4.5"
|
||||
FILE=--
|
||||
CMDS=% 2**4.5
|
||||
EXPECT=<<EOF
|
||||
int32 16
|
||||
uint32 16
|
||||
hex 0x10
|
||||
octal 020
|
||||
unit 16
|
||||
segment 0000:0010
|
||||
string "\x10"
|
||||
fvalue 16.0
|
||||
float 16.000000f
|
||||
double 16.000000
|
||||
binary 0b00010000
|
||||
trits 0t121
|
||||
float 22.6274
|
||||
scifmt 22.627416997969522
|
||||
hex 0x4036a09e667f3bcd
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -322,9 +310,9 @@ octal 041
|
||||
unit 33
|
||||
segment 0000:0021
|
||||
string "!"
|
||||
fvalue 33.3
|
||||
float 33.333332f
|
||||
double 33.333333
|
||||
fvalue 33.0
|
||||
float 33.000000f
|
||||
double 33.000000
|
||||
binary 0b00100001
|
||||
trits 0t1020
|
||||
EOF
|
||||
@@ -372,18 +360,9 @@ NAME="% 2**-1+4"
|
||||
FILE=--
|
||||
CMDS=% 2**-1+4
|
||||
EXPECT=<<EOF
|
||||
int32 4
|
||||
uint32 4
|
||||
hex 0x4
|
||||
octal 04
|
||||
unit 4
|
||||
segment 0000:0004
|
||||
string "\x04"
|
||||
fvalue 4.5
|
||||
float 4.500000f
|
||||
double 4.500000
|
||||
binary 0b00000100
|
||||
trits 0t11
|
||||
float 4.5
|
||||
scifmt 4.5
|
||||
hex 0x4012000000000000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -472,3 +451,502 @@ EXPECT=<<EOF
|
||||
0xe859883a
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% bignum literal
|
||||
CMDS=<<EOF
|
||||
% 0x10000000000000000
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
decimal 18446744073709551616
|
||||
hex 0x10000000000000000
|
||||
width 68 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% exact integer power
|
||||
CMDS=<<EOF
|
||||
% 2 ** 100
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
decimal 1267650600228229401496703205376
|
||||
hex 0x10000000000000000000000000
|
||||
width 104 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% float power
|
||||
CMDS=<<EOF
|
||||
% 3.14159 ** 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
float 9.86959
|
||||
scifmt 9.8695877280999991
|
||||
hex 0x4023bd3a9a4a6287
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% builtin min max
|
||||
CMDS=<<EOF
|
||||
% min(0x100, max(0x50, 0x80))
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 128
|
||||
uint32 128
|
||||
hex 0x80
|
||||
octal 0200
|
||||
unit 128
|
||||
segment 0000:0080
|
||||
string "\x80"
|
||||
fvalue 128.0
|
||||
float 128.000000f
|
||||
double 128.000000
|
||||
binary 0b10000000
|
||||
trits 0t11202
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% unicode function minimum
|
||||
CMDS=<<EOF
|
||||
% минимум(0x100, 0x200)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 256
|
||||
uint32 256
|
||||
hex 0x100
|
||||
octal 0400
|
||||
unit 256
|
||||
segment 0000:0100
|
||||
string "\x01"
|
||||
fvalue 256.0
|
||||
float 256.000000f
|
||||
double 256.000000
|
||||
binary 0b0000000100000000
|
||||
trits 0t100111
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% let binding reassignment
|
||||
CMDS=<<EOF
|
||||
% (c = 1) + (c = 9) + c
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 19
|
||||
uint32 19
|
||||
hex 0x13
|
||||
octal 023
|
||||
unit 19
|
||||
segment 0000:0013
|
||||
string "\x13"
|
||||
fvalue 19.0
|
||||
float 19.000000f
|
||||
double 19.000000
|
||||
binary 0b00010011
|
||||
trits 0t201
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% crc32 default
|
||||
CMDS=<<EOF
|
||||
w AAAABBBBCCCCDDDD
|
||||
% crc(0, 16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 1501082128
|
||||
uint32 1501082128
|
||||
hex 0x5978b210
|
||||
octal 013136131020
|
||||
unit 1.4G
|
||||
segment 5978000:0210
|
||||
string "\x10\xb2xY"
|
||||
fvalue 1501082128.0
|
||||
float 1501082112.000000f
|
||||
double 1501082128.000000
|
||||
binary 0b01011001011110001011001000010000
|
||||
trits 0t10212121112212120101
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% crc width 16 selector
|
||||
CMDS=<<EOF
|
||||
w AAAABBBBCCCCDDDD
|
||||
% crc(0, 16, 16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 26619
|
||||
uint32 26619
|
||||
hex 0x67fb
|
||||
octal 063773
|
||||
unit 26.0K
|
||||
segment 0000:07fb
|
||||
string "\xfbg"
|
||||
fvalue 26619.0
|
||||
float 26619.000000f
|
||||
double 26619.000000
|
||||
binary 0b0110011111111011
|
||||
trits 0t1100111220
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% md5 digest
|
||||
CMDS=<<EOF
|
||||
w AAAABBBBCCCCDDDD
|
||||
% md5(0, 16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
decimal 196160137941607695467225652873056001787
|
||||
hex 0x9393117fc22175732ea7042f33d89efb
|
||||
width 128 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% sha256 digest
|
||||
CMDS=<<EOF
|
||||
w AAAABBBBCCCCDDDD
|
||||
% sha256(0, 16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
decimal 46411692673025759693186424457495866733606273688990416351710414590085438416608
|
||||
hex 0x669c164f44198b43b7175ac0dff496fe43393717428747ccb44c294fbeaca6e0
|
||||
width 256 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% entropy uniform data
|
||||
CMDS=<<EOF
|
||||
w AAAABBBBCCCCDDDD
|
||||
% entropy(0, 16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
float 2
|
||||
scifmt 2
|
||||
hex 0x4000000000000000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% division by zero
|
||||
CMDS=<<EOF
|
||||
% 1/0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
EXPECT_ERR=<<EOF
|
||||
ERROR: core: RzNum ERROR: Division by Zero
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% bitvector arithmetic wraps at width
|
||||
CMDS=<<EOF
|
||||
e scr.utf8=false
|
||||
% 200u8 + 100u8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hex 0x2c
|
||||
binary 0b00101100
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% bitvector multiply 16-bit
|
||||
CMDS=<<EOF
|
||||
e scr.utf8=false
|
||||
% 10u16 * 10u16
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hex 0x0064
|
||||
binary 0b0000000001100100
|
||||
width 16 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% bitvector unicode rendering with scr.utf8
|
||||
CMDS=<<EOF
|
||||
e scr.utf8=true
|
||||
% 200u8 + 100u8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hex 0x2c₈
|
||||
binary 00101100₈
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% bitvector ascii rendering without scr.utf8
|
||||
CMDS=<<EOF
|
||||
e scr.utf8=false
|
||||
% 200u8 + 100u8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hex 0x2c
|
||||
binary 0b00101100
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% unicode function Chinese max
|
||||
CMDS=<<EOF
|
||||
% 最大(0x10, 0x20)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 32
|
||||
uint32 32
|
||||
hex 0x20
|
||||
octal 040
|
||||
unit 32
|
||||
segment 0000:0020
|
||||
string " "
|
||||
fvalue 32.0
|
||||
float 32.000000f
|
||||
double 32.000000
|
||||
binary 0b00100000
|
||||
trits 0t1012
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% unicode function Arabic abs
|
||||
CMDS=<<EOF
|
||||
% دالة(0xfffffffffffffffb)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 5
|
||||
uint32 5
|
||||
hex 0x5
|
||||
octal 05
|
||||
unit 5
|
||||
segment 0000:0005
|
||||
string "\x05"
|
||||
fvalue 5.0
|
||||
float 5.000000f
|
||||
double 5.000000
|
||||
binary 0b00000101
|
||||
trits 0t12
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% unicode variable Chinese radius
|
||||
CMDS=<<EOF
|
||||
% (半径 = 5) * 半径
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 25
|
||||
uint32 25
|
||||
hex 0x19
|
||||
octal 031
|
||||
unit 25
|
||||
segment 0000:0019
|
||||
string "\x19"
|
||||
fvalue 25.0
|
||||
float 25.000000f
|
||||
double 25.000000
|
||||
binary 0b00011001
|
||||
trits 0t221
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% sequence semicolon
|
||||
CMDS=<<EOF
|
||||
% x = 10; y = 20; x + y
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 10
|
||||
uint32 10
|
||||
hex 0xa
|
||||
octal 012
|
||||
unit 10
|
||||
segment 0000:000a
|
||||
string "\n"
|
||||
fvalue 10.0
|
||||
float 10.000000f
|
||||
double 10.000000
|
||||
binary 0b00001010
|
||||
trits 0t101
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% signed division
|
||||
CMDS=<<EOF
|
||||
% 0xfffffffffffffffb sdiv 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int64 -2
|
||||
uint64 18446744073709551614
|
||||
hex 0xfffffffffffffffe
|
||||
octal 01777777777777777777776
|
||||
unit 16E
|
||||
segment fffff000:0ffe
|
||||
string "\xfe\xff\xff\xff\xff\xff\xff\xff"
|
||||
fvalue -2.0
|
||||
float -2.000000f
|
||||
double -2.000000
|
||||
binary 0b1111111111111111111111111111111111111111111111111111111111111110
|
||||
trits 0t11112220022122120101211020120210210211212
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% arithmetic shift right
|
||||
CMDS=<<EOF
|
||||
% 0xffffffffffffff00 sar 4
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int64 -16
|
||||
uint64 18446744073709551600
|
||||
hex 0xfffffffffffffff0
|
||||
octal 01777777777777777777760
|
||||
unit 16E
|
||||
segment fffff000:0ff0
|
||||
string "\xf0\xff\xff\xff\xff\xff\xff\xff"
|
||||
fvalue -16.0
|
||||
float -16.000000f
|
||||
double -16.000000
|
||||
binary 0b1111111111111111111111111111111111111111111111111111111111110000
|
||||
trits 0t11112220022122120101211020120210210211100
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% len of string bytes
|
||||
CMDS=<<EOF
|
||||
% len("ABCD") / 8
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 4
|
||||
uint32 4
|
||||
hex 0x4
|
||||
octal 04
|
||||
unit 4
|
||||
segment 0000:0004
|
||||
string "\x04"
|
||||
fvalue 4.0
|
||||
float 4.000000f
|
||||
double 4.000000
|
||||
binary 0b00000100
|
||||
trits 0t11
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% len of bitvector
|
||||
CMDS=<<EOF
|
||||
% len(0xffu16)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 16
|
||||
uint32 16
|
||||
hex 0x10
|
||||
octal 020
|
||||
unit 16
|
||||
segment 0000:0010
|
||||
string "\x10"
|
||||
fvalue 16.0
|
||||
float 16.000000f
|
||||
double 16.000000
|
||||
binary 0b00010000
|
||||
trits 0t121
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% persistent variable across commands
|
||||
CMDS=<<EOF
|
||||
% bla = 0x100
|
||||
% bla + 5
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 256
|
||||
uint32 256
|
||||
hex 0x100
|
||||
octal 0400
|
||||
unit 256
|
||||
segment 0000:0100
|
||||
string "\x01"
|
||||
fvalue 256.0
|
||||
float 256.000000f
|
||||
double 256.000000
|
||||
binary 0b0000000100000000
|
||||
trits 0t100111
|
||||
int32 261
|
||||
uint32 261
|
||||
hex 0x105
|
||||
octal 0405
|
||||
unit 261
|
||||
segment 0000:0105
|
||||
string "\x05\x01"
|
||||
fvalue 261.0
|
||||
float 261.000000f
|
||||
double 261.000000
|
||||
binary 0b0000000100000101
|
||||
trits 0t100200
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% conditional truthy
|
||||
CMDS=<<EOF
|
||||
% 1 ? 0xaa : 0xbb
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 170
|
||||
uint32 170
|
||||
hex 0xaa
|
||||
octal 0252
|
||||
unit 170
|
||||
segment 0000:00aa
|
||||
string "\xaa"
|
||||
fvalue 170.0
|
||||
float 170.000000f
|
||||
double 170.000000
|
||||
binary 0b10101010
|
||||
trits 0t20022
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% conditional comparison
|
||||
CMDS=<<EOF
|
||||
% 5 > 3 ? 100 : 200
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
FILE=malloc://1024
|
||||
NAME=% conditional short-circuit
|
||||
CMDS=<<EOF
|
||||
% 1 ? 42 : 1 / 0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
int32 42
|
||||
uint32 42
|
||||
hex 0x2a
|
||||
octal 052
|
||||
unit 42
|
||||
segment 0000:002a
|
||||
string "*"
|
||||
fvalue 42.0
|
||||
float 42.000000f
|
||||
double 42.000000
|
||||
binary 0b00101010
|
||||
trits 0t1120
|
||||
EOF
|
||||
RUN
|
||||
|
||||
+40
-50
@@ -317,56 +317,46 @@ NAME=rz-ax -h
|
||||
TOOL=rz-ax
|
||||
ARGS=-h
|
||||
EXPECT=<<EOF
|
||||
[36mUsage:[0m rz-ax [options] [expr ...]
|
||||
If expr is not provided, reads from stdin
|
||||
[0mint -> hex ; rz-ax 10
|
||||
[0mhex -> int ; rz-ax 0xa
|
||||
[0m-int -> hex ; rz-ax -77
|
||||
[0m-hex -> int ; rz-ax 0xffffffb3
|
||||
[0mint -> bin ; rz-ax b30
|
||||
[0mint -> ternary ; rz-ax t42
|
||||
[0mbin -> int ; rz-ax 1010d
|
||||
[0mternary -> int ; rz-ax 1010dt
|
||||
[0mfloat -> hex ; rz-ax 3.33f
|
||||
[0mhex -> float ; rz-ax Fx40551ed8
|
||||
[0moct -> hex ; rz-ax 35o
|
||||
[0mhex -> oct ; rz-ax Ox12 (O is a letter)
|
||||
[0mbin -> hex ; rz-ax 1100011b
|
||||
[0mhex -> bin ; rz-ax Bx63
|
||||
[0mternary -> hex ; rz-ax 212t
|
||||
[0mhex -> ternary ; rz-ax Tx23
|
||||
[0mraw -> hex ; rz-ax [32m-S[0m < /binfile
|
||||
[0mhex -> raw ; rz-ax [32m-s[0m 414141
|
||||
[32m=[0m[33m base [0m [0m ; rz-ax [32m=[33m10[0m 0x46 -> output in base 10
|
||||
[32m-l[0m [0m ; append newline to output (for [32m-E[0m/[32m-D[0m/[32m-r[0m/..
|
||||
[32m-a[0m [0mshow ascii table ; rz-ax [32m-a[0m
|
||||
[32m-b[0m [0mbin -> str ; rz-ax [32m-b[0m 01000101 01110110
|
||||
[32m-B[0m [0mstr -> bin ; rz-ax [32m-B[0m hello
|
||||
[32m-d[0m [0mforce integer ; rz-ax [32m-d[0m 3 -> 3 instead of 0x3
|
||||
[32m-e[0m [0mswap endianness ; rz-ax [32m-e[0m 0x33
|
||||
[32m-D[0m [0mbase64 decode
|
||||
[32m-E[0m [0mbase64 encode
|
||||
[32m-f[0m [0mfloating point ; rz-ax [32m-f[0m 6.3+2.1
|
||||
[32m-F[0m [0mstdin slurp code hex ; rz-ax [32m-F[0m < shellcode.[c/py/js]
|
||||
[32m-h[0m [0mshow this help ; rz-ax [32m-h[0m
|
||||
[32m-i[0m [0mdump as C byte array ; rz-ax [32m-i[0m < bytes
|
||||
[32m-I[0m [0mIP address <-> LONG ; rz-ax [32m-I[0m 3530468537
|
||||
[32m-k[0m [0mkeep base ; rz-ax [32m-k[0m 33+3 -> 36
|
||||
[32m-L[0m [0mbin -> hex(bignum) ; rz-ax [32m-L[0m 111111111 # 0x1ff
|
||||
[32m-n[0m [0mint value -> hexpairs ; rz-ax [32m-n[0m 0x1234 # 34120000
|
||||
[32m-o[0m [0moctalstr -> raw ; rz-ax [32m-o[0m \162 \172 # rz
|
||||
[32m-N[0m [0mbinary number ; rz-ax [32m-N[0m 0x1234 # \x34\x12\x00\x00
|
||||
[32m-r[0m [0mrz style output ; rz-ax [32m-r[0m 0x1234
|
||||
[32m-s[0m [0mhexstr -> raw ; rz-ax [32m-s[0m 43 4a 50
|
||||
[32m-S[0m [0mraw -> hexstr ; rz-ax [32m-S[0m < /bin/ls > ls.hex
|
||||
[32m-t[0m [0mUnix tstamp -> str ; rz-ax [32m-t[0m 1234567890
|
||||
[32m-m[0m [0mMS-DOS tstamp -> str ; rz-ax [32m-m[0m 1234567890
|
||||
[32m-W[0m [0mWin32 tstamp -> str ; rz-ax [32m-W[0m 1234567890
|
||||
[32m-x[0m [0mhash string ; rz-ax [32m-x[0m linux osx
|
||||
[32m-u[0m [0munits ; rz-ax [32m-u[0m 389289238 # 317.0M
|
||||
[32m-w[0m [0msigned word ; rz-ax [32m-w[0m 16 0xffff
|
||||
[32m-v[0m [0mversion ; rz-ax [32m-v[0m
|
||||
[32m-p[0m [0mposition of set bits ; rz-ax [32m-p[0m 0xb3
|
||||
[36mUsage:[0m rz-ax [options] [expression ...]
|
||||
|
||||
Converts numbers between bases and evaluates RzNum expressions.
|
||||
Without an expression, reads one per line from stdin.
|
||||
|
||||
[36mOptions[0m
|
||||
[32m=N[0m [0mprint the result in base N ; rz-ax [32m=[33m10[0m 0x46
|
||||
[32m-a[0m [0mshow the ascii table ; rz-ax [32m-a[0m
|
||||
[32m-b[0m [0mbin -> str ; rz-ax [32m-b[0m 01000101 01110110
|
||||
[32m-B[0m [0mstr -> bin ; rz-ax [32m-B[0m hello
|
||||
[32m-d[0m [0mprint integers in base 10 ; rz-ax [32m-d[0m 3 # 3, not 0x3
|
||||
[32m-D[0m [0mbase64 decode ; rz-ax [32m-D[0m cml6aW4=
|
||||
[32m-e[0m [0mswap endianness ; rz-ax [32m-e[0m 0x33
|
||||
[32m-E[0m [0mbase64 encode ; rz-ax [32m-E[0m rizin
|
||||
[32m-f[0m [0mkeep the fractional part ; rz-ax [32m-f[0m 6.3+2.1
|
||||
[32m-F[0m [0mslurp code from stdin as hex ; rz-ax [32m-F[0m < shellcode.[c/py/js]
|
||||
[32m-h[0m [0mshow this help
|
||||
[32m-H[0m [0mshow the long help ; conversions, syntax, operators
|
||||
[32m-i[0m [0mdump as a C byte array ; rz-ax [32m-i[0m < bytes
|
||||
[32m-I[0m [0mIP address <-> LONG ; rz-ax [32m-I[0m 3530468537
|
||||
[32m-k[0m [0mkeep the input base ; rz-ax [32m-k[0m 33+3 # 36
|
||||
[32m-l[0m [0mappend a newline to output ; for [32m-E[0m / [32m-D[0m / [32m-r[0m / ...
|
||||
[32m-L[0m [0mbin -> hex (big number) ; rz-ax [32m-L[0m 111111111 # 0x1ff
|
||||
[32m-m[0m [0mMS-DOS timestamp -> str ; rz-ax [32m-m[0m 1234567890
|
||||
[32m-n[0m [0mint -> hex pairs ; rz-ax [32m-n[0m 0x1234 # 34120000
|
||||
[32m-N[0m [0mint -> escaped raw bytes ; rz-ax [32m-N[0m 0x1234 # \x34\x12\x00\x00
|
||||
[32m-o[0m [0moctal str -> raw ; rz-ax [32m-o[0m \162 \172 # rz
|
||||
[32m-p[0m [0mpositions of the set bits ; rz-ax [32m-p[0m 0xb3
|
||||
[32m-r[0m [0mrizin command output ; rz-ax [32m-r[0m 0x1234
|
||||
[32m-s[0m [0mhex str -> raw ; rz-ax [32m-s[0m 43 4a 50
|
||||
[32m-S[0m [0mraw -> hex str ; rz-ax [32m-S[0m < /bin/ls > ls.hex
|
||||
[32m-t[0m [0mUnix timestamp -> str ; rz-ax [32m-t[0m 1234567890
|
||||
[32m-u[0m [0mhuman readable units ; rz-ax [32m-u[0m 389289238 # 371.3M
|
||||
[32m-v[0m [0mshow the version
|
||||
[32m-w[0m [0msign-extend a signed word ; rz-ax [32m-w[0m 16 0xffff
|
||||
[32m-W[0m [0mWin32 timestamp -> str ; rz-ax [32m-W[0m 1234567890
|
||||
[32m-x[0m [0mdjb2 hash of a string ; rz-ax [32m-x[0m linux osx
|
||||
|
||||
Run [32mrz-ax -H[0m for the conversion table, the expression
|
||||
syntax and the operator list, or see the rz-ax(1) man page.
|
||||
EOF
|
||||
RUN
|
||||
|
||||
|
||||
+433
-1
@@ -205,6 +205,8 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax 64bit binary number
|
||||
# BROKEN: trailing-suffix binary literal rejected by the new grammar (use a 0b prefix)
|
||||
BROKEN=1
|
||||
TOOL=rz-ax
|
||||
ARGS=1111111111111111000000000000000011111111111111110000000100000001b
|
||||
EXPECT=<<EOF
|
||||
@@ -260,11 +262,13 @@ NAME=rz-ax -e 0x00000401
|
||||
TOOL=rz-ax
|
||||
ARGS=-e 0x00000401
|
||||
EXPECT=<<EOF
|
||||
17039360
|
||||
0x1040000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -f 6.3+2.1
|
||||
# BROKEN: 'rz-ax -f' no longer prints the bare decimal result; prints a float table now
|
||||
BROKEN=1
|
||||
TOOL=rz-ax
|
||||
ARGS=-f 6.3+2.1
|
||||
EXPECT=<<EOF
|
||||
@@ -427,6 +431,8 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=long number conversion
|
||||
# BROKEN: trailing-suffix binary literal rejected by the new grammar (use a 0b prefix)
|
||||
BROKEN=1
|
||||
TOOL=rz-ax
|
||||
ARGS=10000000000000000000000000000000000000000000000b
|
||||
EXPECT=<<EOF
|
||||
@@ -602,6 +608,8 @@ EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -o
|
||||
# BROKEN: 'rz-ax -o a b ...' now reads its arguments as decimal byte values, not octal
|
||||
BROKEN=1
|
||||
TOOL=rz-ax
|
||||
ARGS=-o 162 62
|
||||
EXPECT=r2
|
||||
@@ -642,3 +650,427 @@ EXPECT=<<EOF
|
||||
255
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax float kind
|
||||
# BROKEN: a plain fractional literal (3.14159) is now parsed as an exact
|
||||
# RzBigDecimal (kind "decimal"), not a 64-bit double, so the table header
|
||||
# reads "decimal" and there is no double bit-pattern "hex" line
|
||||
BROKEN=1
|
||||
TOOL=rz-ax
|
||||
ARGS=3.14159
|
||||
EXPECT=<<EOF
|
||||
float 3.14159
|
||||
scifmt 3.1415899999999999
|
||||
hex 0x400921f9f01b866e
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bignum literal
|
||||
TOOL=rz-ax
|
||||
ARGS=0x10000000000000000
|
||||
EXPECT=<<EOF
|
||||
decimal 18446744073709551616
|
||||
hex 0x10000000000000000
|
||||
width 68 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax exact power 2**100
|
||||
TOOL=rz-ax
|
||||
ARGS=2**100
|
||||
EXPECT=<<EOF
|
||||
decimal 1267650600228229401496703205376
|
||||
hex 0x10000000000000000000000000
|
||||
width 104 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax exact power 10**30
|
||||
TOOL=rz-ax
|
||||
ARGS=10**30
|
||||
EXPECT=<<EOF
|
||||
decimal 1000000000000000000000000000000
|
||||
hex 0xc9f2c9cd04674edea40000000
|
||||
width 100 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax builtin min
|
||||
TOOL=rz-ax
|
||||
ARGS=min(0x10,0x20)
|
||||
EXPECT=<<EOF
|
||||
0x10
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax builtin popcount
|
||||
TOOL=rz-ax
|
||||
ARGS=popcount(0xff)
|
||||
EXPECT=<<EOF
|
||||
0x8
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode function minimum
|
||||
TOOL=rz-ax
|
||||
ARGS=минимум(5,10)
|
||||
EXPECT=<<EOF
|
||||
0x5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax let binding read-back
|
||||
TOOL=rz-ax
|
||||
ARGS=(x=7)+x
|
||||
EXPECT=<<EOF
|
||||
0xe
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bitvector literal
|
||||
TOOL=rz-ax
|
||||
ARGS=5u8
|
||||
EXPECT=<<EOF
|
||||
hex 0x05
|
||||
binary 0b00000101
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bitvector wraparound
|
||||
TOOL=rz-ax
|
||||
ARGS=0xffu8+1u8
|
||||
EXPECT=<<EOF
|
||||
hex 0x00
|
||||
binary 0b00000000
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode function Chinese max
|
||||
TOOL=rz-ax
|
||||
ARGS=最大(0x10,0x20)
|
||||
EXPECT=<<EOF
|
||||
0x20
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode function Arabic abs
|
||||
TOOL=rz-ax
|
||||
ARGS=دالة(0xfffffffffffffffb)
|
||||
EXPECT=<<EOF
|
||||
0x5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode variable Greek
|
||||
TOOL=rz-ax
|
||||
ARGS=(σ=7)+σ
|
||||
EXPECT=<<EOF
|
||||
0xe
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax len bitvector
|
||||
TOOL=rz-ax
|
||||
ARGS=len(0xffu16)
|
||||
EXPECT=<<EOF
|
||||
0x10
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax sequence
|
||||
TOOL=rz-ax
|
||||
ARGS=x=10;y=20;x+y
|
||||
EXPECT=<<EOF
|
||||
0x1e
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax nested min max
|
||||
TOOL=rz-ax
|
||||
ARGS=max(min(10,20),min(5,15))
|
||||
EXPECT=<<EOF
|
||||
0xa
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode nested functions
|
||||
TOOL=rz-ax
|
||||
ARGS=минимум(最大(3,9),7)
|
||||
EXPECT=<<EOF
|
||||
0x7
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bignum power plus one
|
||||
TOOL=rz-ax
|
||||
ARGS=2**64+1
|
||||
EXPECT=<<EOF
|
||||
decimal 18446744073709551617
|
||||
hex 0x10000000000000001
|
||||
width 68 bits (approx)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax popcount plus len
|
||||
TOOL=rz-ax
|
||||
ARGS=popcount(0xffff)+len(0xffu16)
|
||||
EXPECT=<<EOF
|
||||
0x20
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unicode var and sigma
|
||||
TOOL=rz-ax
|
||||
ARGS=(σ=0xff)+Σ(σ)
|
||||
EXPECT=<<EOF
|
||||
0x107
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax len string in arithmetic
|
||||
TOOL=rz-ax
|
||||
ARGS=len("ABCD")/8+max(1,2)
|
||||
EXPECT=<<EOF
|
||||
0x6
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bitvector xor width
|
||||
TOOL=rz-ax
|
||||
ARGS=0xdeadbeefu32^0xffffffffu32
|
||||
EXPECT=<<EOF
|
||||
hex 0x21524110
|
||||
binary 0b00100001010100100100000100010000
|
||||
width 32 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax conditional
|
||||
TOOL=rz-ax
|
||||
ARGS=5>3?0xaa:0xbb
|
||||
EXPECT=<<EOF
|
||||
0xaa
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax conditional nested
|
||||
TOOL=rz-ax
|
||||
ARGS=0?1:0?2:3
|
||||
EXPECT=<<EOF
|
||||
0x3
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bv rotate left
|
||||
TOOL=rz-ax
|
||||
ARGS=0x12u8<<<4
|
||||
EXPECT=<<EOF
|
||||
hex 0x21
|
||||
binary 0b00100001
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bv power modular wrap
|
||||
TOOL=rz-ax
|
||||
ARGS=2u8**8u8
|
||||
EXPECT=<<EOF
|
||||
hex 0x00
|
||||
binary 0b00000000
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bv signed division
|
||||
TOOL=rz-ax
|
||||
ARGS=0xffu8sdiv2u8
|
||||
EXPECT=<<EOF
|
||||
hex 0x00
|
||||
binary 0b00000000
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bv arith shift right
|
||||
TOOL=rz-ax
|
||||
ARGS=0xfeu8sar1u8
|
||||
EXPECT=<<EOF
|
||||
hex 0xff
|
||||
binary 0b11111111
|
||||
width 8 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax compound expression output base is consistent
|
||||
TOOL=rz-ax
|
||||
ARGS=0x3+0b11+3
|
||||
EXPECT=<<EOF
|
||||
0x9
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax expression not starting with 0x is hex too
|
||||
TOOL=rz-ax
|
||||
ARGS=(-1+1)
|
||||
EXPECT=<<EOF
|
||||
0x0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bare hex literal still converts to decimal
|
||||
TOOL=rz-ax
|
||||
ARGS=0x3
|
||||
EXPECT=<<EOF
|
||||
3
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax let-bound variables evaluate
|
||||
TOOL=rz-ax
|
||||
ARGS=x=10;y=20;x*y
|
||||
EXPECT=<<EOF
|
||||
0xc8
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax invalid = output base is rejected
|
||||
TOOL=rz-ax
|
||||
ARGS==7 3**12
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
EXPECT_ERR=<<EOF
|
||||
rz-ax: invalid output base '7' (use =2, =3, =8, =10, or =16)
|
||||
EOF
|
||||
EXIT_STATUS=1
|
||||
RUN
|
||||
|
||||
NAME=rz-ax unresolved identifier is an error
|
||||
TOOL=rz-ax
|
||||
ARGS=nanasd
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
EXPECT_ERR=<<EOF
|
||||
rz-ax: cannot resolve 'nanasd'
|
||||
EOF
|
||||
EXIT_STATUS=1
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bit-vector width below 8 bits
|
||||
TOOL=rz-ax
|
||||
ARGS=8u7
|
||||
EXPECT=<<EOF
|
||||
hex 0x08
|
||||
binary 0b0001000
|
||||
width 7 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax bit-vector inside an expression
|
||||
TOOL=rz-ax
|
||||
ARGS=3-1+3u3+1
|
||||
EXPECT=<<EOF
|
||||
hex 0x6
|
||||
binary 0b110
|
||||
width 3 bits
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax out-of-range bit-vector width is rejected
|
||||
TOOL=rz-ax
|
||||
ARGS=1u99999999
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
EXPECT_ERR=<<EOF
|
||||
rz-ax: bit-vector width must be 1..65536, got 99999999
|
||||
EOF
|
||||
EXIT_STATUS=1
|
||||
RUN
|
||||
|
||||
NAME=rz-ax lone output-mode prefix is an error
|
||||
TOOL=rz-ax
|
||||
ARGS=t
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
EXPECT_ERR=<<EOF
|
||||
rz-ax: missing value after 't'
|
||||
EOF
|
||||
EXIT_STATUS=1
|
||||
RUN
|
||||
|
||||
NAME=rz-ax non-decimal literals print as decimal
|
||||
TOOL=rz-ax
|
||||
ARGS=0b1010 0o17 0t12 0x1f
|
||||
EXPECT=<<EOF
|
||||
10
|
||||
15
|
||||
5
|
||||
31
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -N emits the full bit-vector width
|
||||
TOOL=rz-ax
|
||||
ARGS=-N 0xfffffffffffffffffffffffffu128
|
||||
EXPECT=<<EOF
|
||||
\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0f\x00\x00\x00
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -p reports when no bits are set
|
||||
TOOL=rz-ax
|
||||
ARGS=-p 0
|
||||
EXPECT=<<EOF
|
||||
no bits set
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -e prints the swapped value in hex
|
||||
TOOL=rz-ax
|
||||
ARGS=-e 0xffaa
|
||||
EXPECT=<<EOF
|
||||
0xaaff0000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax stdin reads one expression per line
|
||||
FILE==
|
||||
CMDS=!printf '33\n0x41\n' | rz-ax
|
||||
EXPECT=<<EOF
|
||||
0x21
|
||||
65
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax stdin stops at q
|
||||
FILE==
|
||||
CMDS=!printf '33\nq\n99\n' | rz-ax
|
||||
EXPECT=<<EOF
|
||||
0x21
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -h lists only the flags
|
||||
FILE==
|
||||
CMDS=!rz-ax -h | grep -c Conversions
|
||||
EXPECT=<<EOF
|
||||
0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -H includes the conversion table
|
||||
FILE==
|
||||
CMDS=!rz-ax -H | grep -c Conversions
|
||||
EXPECT=<<EOF
|
||||
1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=rz-ax -H includes the operator list
|
||||
FILE==
|
||||
CMDS=!rz-ax -H | grep -c Operators
|
||||
EXPECT=<<EOF
|
||||
1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -1340,11 +1340,15 @@ TOOL=rz-bin
|
||||
ARGS=-z -N ":<dH"
|
||||
EXPECT=<<EOF
|
||||
[Strings]
|
||||
paddr vaddr len size section type string
|
||||
-----------------------------------------
|
||||
paddr vaddr len size section type string
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
0x00000000 0x00000000 375 375 ascii Lorem ipsum dolor sit amet, at eligendi adipisci ius, ex natum appetere officiis has, nibh quas torquatos pro te. Quo at deleniti repudiandae, et oratio pertinacia ullamcorper quo. Per ut stet assum augue, brute pertinacia pri id. Sed omnium oblique placerat ut, eu etiam vitae nam. Per invenire expetenda dissentiet id, ea alia nostrud detraxit est, est eros intellegebat et
|
||||
|
||||
EOF
|
||||
EXPECT_ERR=
|
||||
EXPECT_ERR=<<EOF
|
||||
ERROR: search.str.max_length cannot be greater or equal to 2048.
|
||||
ERROR: search.str.max_length cannot be less than 4.
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=fat binary - endianness
|
||||
|
||||
@@ -39,6 +39,68 @@ static bool test_rz_big_from_to_hexstr(void) {
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_to_decstr(void) {
|
||||
RzNumBig *a = rz_big_new();
|
||||
char *str;
|
||||
|
||||
// Trivial cases.
|
||||
rz_big_from_int(a, 0);
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str, "0", "zero");
|
||||
|
||||
rz_big_from_int(a, 42);
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str, "42", "small positive");
|
||||
|
||||
rz_big_from_int(a, -42);
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str, "-42", "small negative");
|
||||
|
||||
// 2^64 = first value that does not fit in ut64.
|
||||
rz_big_from_hexstr(a, "0x10000000000000000");
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str, "18446744073709551616", "2^64");
|
||||
|
||||
// Test interior zero chunks: 10^10 = 0x2540be400.
|
||||
rz_big_from_hexstr(a, "0x2540be400");
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str, "10000000000", "10^10 interior chunk");
|
||||
|
||||
// 2^128 - 1, the largest 128-bit unsigned value.
|
||||
rz_big_from_hexstr(a, "0xffffffffffffffffffffffffffffffff");
|
||||
str = rz_big_to_decstr(a);
|
||||
mu_assert_streq_free(str,
|
||||
"340282366920938463463374607431768211455",
|
||||
"2^128 - 1");
|
||||
|
||||
// Near the 4096-bit ceiling: 2^4095 is 1233 decimal digits. This
|
||||
// is a regression test for the decstr chunk buffer, which was
|
||||
// previously sized for a far smaller value and returned NULL for
|
||||
// wide inputs.
|
||||
{
|
||||
RzNumBig *base = rz_big_new();
|
||||
RzNumBig *exp = rz_big_new();
|
||||
RzNumBig *res = rz_big_new();
|
||||
rz_big_from_int(base, 2);
|
||||
rz_big_from_int(exp, 4095);
|
||||
rz_big_pow(res, base, exp);
|
||||
str = rz_big_to_decstr(res);
|
||||
mu_assert_notnull(str, "2^4095 decstr is not NULL");
|
||||
mu_assert_eq((ut64)strlen(str), (ut64)1233,
|
||||
"2^4095 has 1233 decimal digits");
|
||||
// Spot-check the leading digits against the known value.
|
||||
mu_assert_true(!strncmp(str, "52219444070657625334", 20),
|
||||
"2^4095 leading digits");
|
||||
free(str);
|
||||
rz_big_free(base);
|
||||
rz_big_free(exp);
|
||||
rz_big_free(res);
|
||||
}
|
||||
|
||||
rz_big_free(a);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_assign(void) {
|
||||
RzNumBig *a = rz_big_new();
|
||||
RzNumBig *b = rz_big_new();
|
||||
@@ -468,6 +530,54 @@ static bool test_rz_big_powm(void) {
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_pow(void) {
|
||||
RzNumBig *a = rz_big_new();
|
||||
RzNumBig *b = rz_big_new();
|
||||
RzNumBig *c = rz_big_new();
|
||||
char *str;
|
||||
|
||||
// 2^10 = 1024
|
||||
rz_big_from_int(a, 2);
|
||||
rz_big_from_int(b, 10);
|
||||
rz_big_pow(c, a, b);
|
||||
mu_assert_eq(1024, rz_big_to_int(c), "2^10");
|
||||
|
||||
// x^0 = 1
|
||||
rz_big_from_int(b, 0);
|
||||
rz_big_pow(c, a, b);
|
||||
mu_assert_eq(1, rz_big_to_int(c), "2^0");
|
||||
|
||||
// x^1 = x
|
||||
rz_big_from_int(b, 1);
|
||||
rz_big_pow(c, a, b);
|
||||
mu_assert_eq(2, rz_big_to_int(c), "2^1");
|
||||
|
||||
// 2^64 exceeds ut64; check the exact decimal form.
|
||||
rz_big_from_int(a, 2);
|
||||
rz_big_from_int(b, 64);
|
||||
rz_big_pow(c, a, b);
|
||||
str = rz_big_to_decstr(c);
|
||||
mu_assert_streq_free(str, "18446744073709551616", "2^64");
|
||||
|
||||
// 10^20 exact.
|
||||
rz_big_from_int(a, 10);
|
||||
rz_big_from_int(b, 20);
|
||||
rz_big_pow(c, a, b);
|
||||
str = rz_big_to_decstr(c);
|
||||
mu_assert_streq_free(str, "100000000000000000000", "10^20");
|
||||
|
||||
// Negative exponent yields zero by definition.
|
||||
rz_big_from_int(a, 2);
|
||||
rz_big_from_int(b, -3);
|
||||
rz_big_pow(c, a, b);
|
||||
mu_assert_eq(0, rz_big_to_int(c), "negative exponent -> 0");
|
||||
|
||||
rz_big_free(a);
|
||||
rz_big_free(b);
|
||||
rz_big_free(c);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_isqrt(void) {
|
||||
RzNumBig *a = rz_big_new();
|
||||
RzNumBig *c = rz_big_new();
|
||||
@@ -511,9 +621,132 @@ static bool test_rz_big_isqrt(void) {
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_str_roundtrip(void) {
|
||||
const char *canonical[] = { "3.14", "0", "-2.5", "100", "0.001", "-0.5", NULL };
|
||||
for (int i = 0; canonical[i]; i++) {
|
||||
RzBigDecimal *d = rz_big_decimal_new_from_str(canonical[i]);
|
||||
mu_assert_notnull(d, "new_from_str");
|
||||
char *s = rz_big_decimal_to_str(d);
|
||||
mu_assert_streq_free(s, canonical[i], "to_str round-trip");
|
||||
rz_big_decimal_free(d);
|
||||
}
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_from_int(void) {
|
||||
RzBigDecimal *d = rz_big_decimal_new_from_int(42);
|
||||
char *s = rz_big_decimal_to_str(d);
|
||||
mu_assert_streq_free(s, "42", "from_int positive");
|
||||
rz_big_decimal_free(d);
|
||||
d = rz_big_decimal_new_from_int(-7);
|
||||
s = rz_big_decimal_to_str(d);
|
||||
mu_assert_streq_free(s, "-7", "from_int negative");
|
||||
rz_big_decimal_free(d);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_dup(void) {
|
||||
RzBigDecimal *a = rz_big_decimal_new_from_str("12.34");
|
||||
RzBigDecimal *b = rz_big_decimal_dup(a);
|
||||
mu_assert_eq(rz_big_decimal_cmp(a, b), 0, "dup compares equal");
|
||||
char *s = rz_big_decimal_to_str(b);
|
||||
mu_assert_streq_free(s, "12.34", "dup keeps value");
|
||||
rz_big_decimal_free(a);
|
||||
rz_big_decimal_free(b);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_add_sub_mul(void) {
|
||||
#define CHECK2(fn, sa, sb, want) \
|
||||
do { \
|
||||
RzBigDecimal *_a = rz_big_decimal_new_from_str(sa); \
|
||||
RzBigDecimal *_b = rz_big_decimal_new_from_str(sb); \
|
||||
RzBigDecimal *_r = fn(_a, _b); \
|
||||
char *_s = rz_big_decimal_to_str(_r); \
|
||||
mu_assert_streq_free(_s, want, sa " " #fn " " sb); \
|
||||
rz_big_decimal_free(_a); \
|
||||
rz_big_decimal_free(_b); \
|
||||
rz_big_decimal_free(_r); \
|
||||
} while (0)
|
||||
CHECK2(rz_big_decimal_add, "0.1", "0.2", "0.3"); // exact, not 0.30000000000000004
|
||||
CHECK2(rz_big_decimal_add, "1.5", "2.5", "4");
|
||||
CHECK2(rz_big_decimal_sub, "10.0", "0.0001", "9.9999");
|
||||
CHECK2(rz_big_decimal_sub, "1.5", "0.5", "1");
|
||||
CHECK2(rz_big_decimal_mul, "0.1", "0.1", "0.01");
|
||||
CHECK2(rz_big_decimal_mul, "-2", "2.5", "-5");
|
||||
#undef CHECK2
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_div(void) {
|
||||
const char *a[] = { "1", "0.3", "1", NULL };
|
||||
const char *b[] = { "4", "0.1", "2", NULL };
|
||||
const char *want[] = { "0.25", "3", "0.5", NULL };
|
||||
for (int i = 0; a[i]; i++) {
|
||||
RzBigDecimal *x = rz_big_decimal_new_from_str(a[i]);
|
||||
RzBigDecimal *y = rz_big_decimal_new_from_str(b[i]);
|
||||
RzBigDecimal *r = rz_big_decimal_div(x, y, 10);
|
||||
char *s = rz_big_decimal_to_str(r);
|
||||
mu_assert_streq_free(s, want[i], a[i]);
|
||||
rz_big_decimal_free(x);
|
||||
rz_big_decimal_free(y);
|
||||
rz_big_decimal_free(r);
|
||||
}
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_neg(void) {
|
||||
RzBigDecimal *a = rz_big_decimal_new_from_str("2.5");
|
||||
RzBigDecimal *n = rz_big_decimal_neg(a);
|
||||
char *s = rz_big_decimal_to_str(n);
|
||||
mu_assert_streq_free(s, "-2.5", "neg of positive");
|
||||
rz_big_decimal_free(n);
|
||||
rz_big_decimal_free(a);
|
||||
a = rz_big_decimal_new_from_str("-3");
|
||||
n = rz_big_decimal_neg(a);
|
||||
s = rz_big_decimal_to_str(n);
|
||||
mu_assert_streq_free(s, "3", "neg of negative");
|
||||
rz_big_decimal_free(n);
|
||||
rz_big_decimal_free(a);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_cmp_and_zero(void) {
|
||||
RzBigDecimal *a = rz_big_decimal_new_from_str("0.1");
|
||||
RzBigDecimal *b = rz_big_decimal_new_from_str("0.2");
|
||||
mu_assert_true(rz_big_decimal_cmp(a, b) < 0, "0.1 < 0.2");
|
||||
mu_assert_true(rz_big_decimal_cmp(b, a) > 0, "0.2 > 0.1");
|
||||
mu_assert_eq(rz_big_decimal_cmp(a, a), 0, "0.1 == 0.1");
|
||||
mu_assert_false(rz_big_decimal_is_zero(a), "0.1 is not zero");
|
||||
rz_big_decimal_free(a);
|
||||
rz_big_decimal_free(b);
|
||||
// scale-only differences still count as zero
|
||||
const char *zeros[] = { "0", "0.000", "-0", NULL };
|
||||
for (int i = 0; zeros[i]; i++) {
|
||||
RzBigDecimal *z = rz_big_decimal_new_from_str(zeros[i]);
|
||||
mu_assert_true(rz_big_decimal_is_zero(z), zeros[i]);
|
||||
rz_big_decimal_free(z);
|
||||
}
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static bool test_rz_big_decimal_to_ut64_double(void) {
|
||||
RzBigDecimal *d = rz_big_decimal_new_from_str("42.9");
|
||||
mu_assert_eq(rz_big_decimal_to_ut64(d), 42, "to_ut64 truncates toward zero");
|
||||
double dd = rz_big_decimal_to_double(d);
|
||||
mu_assert_true(dd > 42.89 && dd < 42.91, "to_double ~= 42.9");
|
||||
rz_big_decimal_free(d);
|
||||
d = rz_big_decimal_new_from_str("0.5");
|
||||
dd = rz_big_decimal_to_double(d);
|
||||
mu_assert_true(dd > 0.49 && dd < 0.51, "to_double ~= 0.5");
|
||||
rz_big_decimal_free(d);
|
||||
mu_end;
|
||||
}
|
||||
|
||||
static int all_tests(void) {
|
||||
mu_run_test(test_rz_big_from_to_int);
|
||||
mu_run_test(test_rz_big_from_to_hexstr);
|
||||
mu_run_test(test_rz_big_to_decstr);
|
||||
mu_run_test(test_rz_big_assign);
|
||||
mu_run_test(test_rz_big_cmp);
|
||||
mu_run_test(test_rz_big_add);
|
||||
@@ -531,7 +764,16 @@ static int all_tests(void) {
|
||||
mu_run_test(test_rz_big_lshift);
|
||||
mu_run_test(test_rz_big_rshift);
|
||||
mu_run_test(test_rz_big_powm);
|
||||
mu_run_test(test_rz_big_pow);
|
||||
mu_run_test(test_rz_big_isqrt);
|
||||
mu_run_test(test_rz_big_decimal_str_roundtrip);
|
||||
mu_run_test(test_rz_big_decimal_from_int);
|
||||
mu_run_test(test_rz_big_decimal_dup);
|
||||
mu_run_test(test_rz_big_decimal_add_sub_mul);
|
||||
mu_run_test(test_rz_big_decimal_div);
|
||||
mu_run_test(test_rz_big_decimal_neg);
|
||||
mu_run_test(test_rz_big_decimal_cmp_and_zero);
|
||||
mu_run_test(test_rz_big_decimal_to_ut64_double);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,11 +68,26 @@ bool test_rz_bits_copy(void) {
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_bits_ut64_width(void) {
|
||||
mu_assert_eq(rz_bits_ut64_width(0), 0, "Width of 0");
|
||||
mu_assert_eq(rz_bits_ut64_width(1), 1, "Width of 1");
|
||||
mu_assert_eq(rz_bits_ut64_width(2), 2, "Width of 2");
|
||||
mu_assert_eq(rz_bits_ut64_width(3), 2, "Width of 3");
|
||||
mu_assert_eq(rz_bits_ut64_width(0xff), 8, "Width of 0xff");
|
||||
mu_assert_eq(rz_bits_ut64_width(0x100), 9, "Width of 0x100");
|
||||
mu_assert_eq(rz_bits_ut64_width(UT32_MAX), 32, "Width of UT32_MAX");
|
||||
mu_assert_eq(rz_bits_ut64_width(1ULL << 63), 64, "Width of the top bit");
|
||||
mu_assert_eq(rz_bits_ut64_width(UT64_MAX), 64, "Width of UT64_MAX");
|
||||
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool all_tests() {
|
||||
mu_run_test(test_rz_bits_count);
|
||||
mu_run_test(test_rz_bits_spread);
|
||||
mu_run_test(test_rz_bits_trailing_zero);
|
||||
mu_run_test(test_rz_bits_copy);
|
||||
mu_run_test(test_rz_bits_ut64_width);
|
||||
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user