jprx 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/parser.js +26 -8
- package/specs/expressions.json +71 -0
- package/specs/helpers.json +150 -0
package/package.json
CHANGED
package/parser.js
CHANGED
|
@@ -487,16 +487,17 @@ const tokenize = (expr) => {
|
|
|
487
487
|
// In expressions like "$++/count", the $ is just the JPRX delimiter
|
|
488
488
|
// and ++ is a prefix operator applied to /count
|
|
489
489
|
if (expr[i] === '$' && i + 1 < len) {
|
|
490
|
-
// Check if next chars are
|
|
491
|
-
|
|
492
|
-
|
|
490
|
+
// Check if next chars are a PREFIX operator (sort by length to match longest first)
|
|
491
|
+
const prefixOps = [...operators.prefix.keys()].sort((a, b) => b.length - a.length);
|
|
492
|
+
let isPrefixOp = false;
|
|
493
|
+
for (const op of prefixOps) {
|
|
493
494
|
if (expr.slice(i + 1, i + 1 + op.length) === op) {
|
|
494
|
-
|
|
495
|
+
isPrefixOp = true;
|
|
495
496
|
break;
|
|
496
497
|
}
|
|
497
498
|
}
|
|
498
|
-
if (
|
|
499
|
-
// Skip the $, it's just a delimiter
|
|
499
|
+
if (isPrefixOp) {
|
|
500
|
+
// Skip the $, it's just a delimiter for a prefix operator (e.g., $++/count)
|
|
500
501
|
i++;
|
|
501
502
|
continue;
|
|
502
503
|
}
|
|
@@ -814,7 +815,7 @@ class PrattParser {
|
|
|
814
815
|
const prec = this.getInfixPrecedence(tok.value);
|
|
815
816
|
if (prec < minPrecedence) break;
|
|
816
817
|
|
|
817
|
-
// Check if it's a postfix operator
|
|
818
|
+
// Check if it's a postfix-only operator
|
|
818
819
|
if (operators.postfix.has(tok.value) && !operators.infix.has(tok.value)) {
|
|
819
820
|
this.consume();
|
|
820
821
|
left = { type: 'Postfix', operator: tok.value, operand: left };
|
|
@@ -832,6 +833,12 @@ class PrattParser {
|
|
|
832
833
|
continue;
|
|
833
834
|
}
|
|
834
835
|
|
|
836
|
+
// Operator not registered as postfix or infix - treat as unknown and stop
|
|
837
|
+
// This prevents infinite loops when operators are tokenized but not registered
|
|
838
|
+
if (!operators.postfix.has(tok.value) && !operators.infix.has(tok.value)) {
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
|
|
835
842
|
// Postfix that's also infix - context determines
|
|
836
843
|
// If next token is a value, treat as infix
|
|
837
844
|
this.consume();
|
|
@@ -1019,7 +1026,18 @@ const evaluateAST = (ast, context, forMutation = false) => {
|
|
|
1019
1026
|
// For infix, typically first arg might be pathAware
|
|
1020
1027
|
const left = evaluateAST(ast.left, context, opts.pathAware);
|
|
1021
1028
|
const right = evaluateAST(ast.right, context, false);
|
|
1022
|
-
|
|
1029
|
+
|
|
1030
|
+
const finalArgs = [];
|
|
1031
|
+
|
|
1032
|
+
// Handle potentially exploded arguments (like in sum(/items...p))
|
|
1033
|
+
// Although infix operators usually take exactly 2 args, we treat them consistently
|
|
1034
|
+
if (Array.isArray(left) && ast.left.type === 'Explosion') finalArgs.push(...left);
|
|
1035
|
+
else finalArgs.push(unwrapSignal(left));
|
|
1036
|
+
|
|
1037
|
+
if (Array.isArray(right) && ast.right.type === 'Explosion') finalArgs.push(...right);
|
|
1038
|
+
else finalArgs.push(unwrapSignal(right));
|
|
1039
|
+
|
|
1040
|
+
return helper(...finalArgs);
|
|
1023
1041
|
}
|
|
1024
1042
|
|
|
1025
1043
|
default:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "Global Path Resolution",
|
|
4
|
+
"state": {
|
|
5
|
+
"userName": "Alice"
|
|
6
|
+
},
|
|
7
|
+
"expression": "$/userName",
|
|
8
|
+
"expected": "Alice"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "Deep Global Path Resolution",
|
|
12
|
+
"state": {
|
|
13
|
+
"user": {
|
|
14
|
+
"profile": {
|
|
15
|
+
"name": "Bob"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"expression": "$/user/profile/name",
|
|
20
|
+
"expected": "Bob"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "Relative Path Resolution",
|
|
24
|
+
"context": {
|
|
25
|
+
"age": 30
|
|
26
|
+
},
|
|
27
|
+
"expression": "./age",
|
|
28
|
+
"expected": 30
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Math: Addition",
|
|
32
|
+
"state": {
|
|
33
|
+
"a": 5,
|
|
34
|
+
"b": 10
|
|
35
|
+
},
|
|
36
|
+
"expression": "$+(/a, /b)",
|
|
37
|
+
"expected": 15
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Operator: Addition (Infix)",
|
|
41
|
+
"state": {
|
|
42
|
+
"a": 5,
|
|
43
|
+
"b": 10
|
|
44
|
+
},
|
|
45
|
+
"expression": "$/a + $/b",
|
|
46
|
+
"expected": 15
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "Logic: If (True)",
|
|
50
|
+
"state": {
|
|
51
|
+
"isVip": true
|
|
52
|
+
},
|
|
53
|
+
"expression": "$if(/isVip, \"Gold\", \"Silver\")",
|
|
54
|
+
"expected": "Gold"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "Explosion Operator",
|
|
58
|
+
"state": {
|
|
59
|
+
"items": [
|
|
60
|
+
{
|
|
61
|
+
"p": 10
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"p": 20
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"expression": "$sum(/items...p)",
|
|
69
|
+
"expected": 30
|
|
70
|
+
}
|
|
71
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "Math: Subtraction",
|
|
4
|
+
"expression": "$-(10, 3)",
|
|
5
|
+
"expected": 7
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "Math: Multiplication",
|
|
9
|
+
"expression": "$*(4, 5)",
|
|
10
|
+
"expected": 20
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "Math: Division",
|
|
14
|
+
"expression": "$/(20, 4)",
|
|
15
|
+
"expected": 5
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Math: Round",
|
|
19
|
+
"expression": "$round(3.7)",
|
|
20
|
+
"expected": 4
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "String: Upper",
|
|
24
|
+
"expression": "$upper('hello')",
|
|
25
|
+
"expected": "HELLO"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "String: Lower",
|
|
29
|
+
"expression": "$lower('WORLD')",
|
|
30
|
+
"expected": "world"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "String: Concat",
|
|
34
|
+
"expression": "$concat('Hello', ' ', 'World')",
|
|
35
|
+
"expected": "Hello World"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "String: Trim",
|
|
39
|
+
"expression": "$trim(' spaced ')",
|
|
40
|
+
"expected": "spaced"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Logic: And (true)",
|
|
44
|
+
"expression": "$and(true, true)",
|
|
45
|
+
"expected": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Logic: And (false)",
|
|
49
|
+
"expression": "$and(true, false)",
|
|
50
|
+
"expected": false
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "Logic: Or",
|
|
54
|
+
"expression": "$or(false, true)",
|
|
55
|
+
"expected": true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Logic: Not",
|
|
59
|
+
"expression": "$not(false)",
|
|
60
|
+
"expected": true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "Compare: Greater Than",
|
|
64
|
+
"expression": "$gt(10, 5)",
|
|
65
|
+
"expected": true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "Compare: Less Than",
|
|
69
|
+
"expression": "$lt(3, 7)",
|
|
70
|
+
"expected": true
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "Compare: Equal",
|
|
74
|
+
"expression": "$eq(5, 5)",
|
|
75
|
+
"expected": true
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "Conditional: If (true branch)",
|
|
79
|
+
"expression": "$if(true, 'Yes', 'No')",
|
|
80
|
+
"expected": "Yes"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "Conditional: If (false branch)",
|
|
84
|
+
"expression": "$if(false, 'Yes', 'No')",
|
|
85
|
+
"expected": "No"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "Stats: Average",
|
|
89
|
+
"expression": "$avg(10, 20, 30)",
|
|
90
|
+
"expected": 20
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "Stats: Min",
|
|
94
|
+
"expression": "$min(5, 2, 8)",
|
|
95
|
+
"expected": 2
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Stats: Max",
|
|
99
|
+
"expression": "$max(5, 2, 8)",
|
|
100
|
+
"expected": 8
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "Array: Length",
|
|
104
|
+
"state": {
|
|
105
|
+
"items": [
|
|
106
|
+
"a",
|
|
107
|
+
"b",
|
|
108
|
+
"c"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
"expression": "$len(/items)",
|
|
112
|
+
"expected": 3
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "Array: Join",
|
|
116
|
+
"state": {
|
|
117
|
+
"items": [
|
|
118
|
+
"a",
|
|
119
|
+
"b",
|
|
120
|
+
"c"
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"expression": "$join(/items, '-')",
|
|
124
|
+
"expected": "a-b-c"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "Array: First",
|
|
128
|
+
"state": {
|
|
129
|
+
"items": [
|
|
130
|
+
"a",
|
|
131
|
+
"b",
|
|
132
|
+
"c"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
"expression": "$first(/items)",
|
|
136
|
+
"expected": "a"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "Array: Last",
|
|
140
|
+
"state": {
|
|
141
|
+
"items": [
|
|
142
|
+
"a",
|
|
143
|
+
"b",
|
|
144
|
+
"c"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
"expression": "$last(/items)",
|
|
148
|
+
"expected": "c"
|
|
149
|
+
}
|
|
150
|
+
]
|