@fieldia/core 0.1.0
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/LICENSE +21 -0
- package/README.md +31 -0
- package/package.json +40 -0
- package/schema/page.schema.json +2142 -0
- package/src/index.d.mts +15 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +46 -0
- package/src/index.js.map +1 -0
- package/src/index.mjs +14 -0
- package/src/index.mjs.map +1 -0
- package/src/lib/expression/evaluateModifier.d.mts +55 -0
- package/src/lib/expression/evaluateModifier.d.ts +55 -0
- package/src/lib/expression/evaluateModifier.js +121 -0
- package/src/lib/expression/evaluateModifier.js.map +1 -0
- package/src/lib/expression/evaluateModifier.mjs +117 -0
- package/src/lib/expression/evaluateModifier.mjs.map +1 -0
- package/src/lib/expression/evaluator.d.mts +23 -0
- package/src/lib/expression/evaluator.d.ts +23 -0
- package/src/lib/expression/evaluator.js +122 -0
- package/src/lib/expression/evaluator.js.map +1 -0
- package/src/lib/expression/evaluator.mjs +118 -0
- package/src/lib/expression/evaluator.mjs.map +1 -0
- package/src/lib/expression/extractFieldNames.d.mts +41 -0
- package/src/lib/expression/extractFieldNames.d.ts +41 -0
- package/src/lib/expression/extractFieldNames.js +97 -0
- package/src/lib/expression/extractFieldNames.js.map +1 -0
- package/src/lib/expression/extractFieldNames.mjs +93 -0
- package/src/lib/expression/extractFieldNames.mjs.map +1 -0
- package/src/lib/expression/modifier.d.mts +14 -0
- package/src/lib/expression/modifier.d.ts +14 -0
- package/src/lib/expression/modifier.js +55 -0
- package/src/lib/expression/modifier.js.map +1 -0
- package/src/lib/expression/modifier.mjs +52 -0
- package/src/lib/expression/modifier.mjs.map +1 -0
- package/src/lib/expression/parser.d.mts +109 -0
- package/src/lib/expression/parser.d.ts +109 -0
- package/src/lib/expression/parser.js +317 -0
- package/src/lib/expression/parser.js.map +1 -0
- package/src/lib/expression/parser.mjs +313 -0
- package/src/lib/expression/parser.mjs.map +1 -0
- package/src/lib/expression/tokenizer.d.mts +31 -0
- package/src/lib/expression/tokenizer.d.ts +31 -0
- package/src/lib/expression/tokenizer.js +100 -0
- package/src/lib/expression/tokenizer.js.map +1 -0
- package/src/lib/expression/tokenizer.mjs +96 -0
- package/src/lib/expression/tokenizer.mjs.map +1 -0
- package/src/lib/format/field.d.mts +967 -0
- package/src/lib/format/field.d.ts +967 -0
- package/src/lib/format/field.js +147 -0
- package/src/lib/format/field.js.map +1 -0
- package/src/lib/format/field.mjs +144 -0
- package/src/lib/format/field.mjs.map +1 -0
- package/src/lib/format/json-schema.d.mts +6 -0
- package/src/lib/format/json-schema.d.ts +6 -0
- package/src/lib/format/json-schema.js +14 -0
- package/src/lib/format/json-schema.js.map +1 -0
- package/src/lib/format/json-schema.mjs +11 -0
- package/src/lib/format/json-schema.mjs.map +1 -0
- package/src/lib/format/json.d.mts +7 -0
- package/src/lib/format/json.d.ts +7 -0
- package/src/lib/format/json.js +7 -0
- package/src/lib/format/json.js.map +1 -0
- package/src/lib/format/json.mjs +4 -0
- package/src/lib/format/json.mjs.map +1 -0
- package/src/lib/format/layout.d.mts +521 -0
- package/src/lib/format/layout.d.ts +521 -0
- package/src/lib/format/layout.js +150 -0
- package/src/lib/format/layout.js.map +1 -0
- package/src/lib/format/layout.mjs +147 -0
- package/src/lib/format/layout.mjs.map +1 -0
- package/src/lib/format/layout.types.d.mts +22 -0
- package/src/lib/format/layout.types.d.ts +22 -0
- package/src/lib/format/layout.types.js +3 -0
- package/src/lib/format/layout.types.js.map +1 -0
- package/src/lib/format/layout.types.mjs +2 -0
- package/src/lib/format/layout.types.mjs.map +1 -0
- package/src/lib/format/page.d.mts +511 -0
- package/src/lib/format/page.d.ts +511 -0
- package/src/lib/format/page.js +27 -0
- package/src/lib/format/page.js.map +1 -0
- package/src/lib/format/page.mjs +24 -0
- package/src/lib/format/page.mjs.map +1 -0
- package/src/lib/format/validate.d.mts +23 -0
- package/src/lib/format/validate.d.ts +23 -0
- package/src/lib/format/validate.js +210 -0
- package/src/lib/format/validate.js.map +1 -0
- package/src/lib/format/validate.mjs +207 -0
- package/src/lib/format/validate.mjs.map +1 -0
- package/src/lib/record/check.d.mts +14 -0
- package/src/lib/record/check.d.ts +14 -0
- package/src/lib/record/check.js +167 -0
- package/src/lib/record/check.js.map +1 -0
- package/src/lib/record/check.mjs +163 -0
- package/src/lib/record/check.mjs.map +1 -0
- package/src/lib/record/data-source.d.mts +109 -0
- package/src/lib/record/data-source.d.ts +109 -0
- package/src/lib/record/data-source.js +3 -0
- package/src/lib/record/data-source.js.map +1 -0
- package/src/lib/record/data-source.mjs +2 -0
- package/src/lib/record/data-source.mjs.map +1 -0
- package/src/lib/record/form.d.mts +106 -0
- package/src/lib/record/form.d.ts +106 -0
- package/src/lib/record/form.js +545 -0
- package/src/lib/record/form.js.map +1 -0
- package/src/lib/record/form.mjs +542 -0
- package/src/lib/record/form.mjs.map +1 -0
- package/src/lib/record/memory-data-source.d.mts +28 -0
- package/src/lib/record/memory-data-source.d.ts +28 -0
- package/src/lib/record/memory-data-source.js +132 -0
- package/src/lib/record/memory-data-source.js.map +1 -0
- package/src/lib/record/memory-data-source.mjs +129 -0
- package/src/lib/record/memory-data-source.mjs.map +1 -0
- package/src/lib/record/messages.d.mts +35 -0
- package/src/lib/record/messages.d.ts +35 -0
- package/src/lib/record/messages.js +111 -0
- package/src/lib/record/messages.js.map +1 -0
- package/src/lib/record/messages.mjs +107 -0
- package/src/lib/record/messages.mjs.map +1 -0
- package/src/lib/record/scheduler.d.mts +12 -0
- package/src/lib/record/scheduler.d.ts +12 -0
- package/src/lib/record/scheduler.js +16 -0
- package/src/lib/record/scheduler.js.map +1 -0
- package/src/lib/record/scheduler.mjs +12 -0
- package/src/lib/record/scheduler.mjs.map +1 -0
- package/src/lib/record/values.d.mts +54 -0
- package/src/lib/record/values.d.ts +54 -0
- package/src/lib/record/values.js +78 -0
- package/src/lib/record/values.js.map +1 -0
- package/src/lib/record/values.mjs +71 -0
- package/src/lib/record/values.mjs.map +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression parser for modifiers, ported from the React form engine.
|
|
3
|
+
*
|
|
4
|
+
* Recursive descent parser that converts tokens into an Abstract Syntax Tree (AST).
|
|
5
|
+
* Implements Python-like operator precedence:
|
|
6
|
+
* 1. OR (lowest precedence)
|
|
7
|
+
* 2. AND
|
|
8
|
+
* 3. NOT
|
|
9
|
+
* 4. Comparison operators (==, !=, <, >, <=, >=, in, not in)
|
|
10
|
+
* 5. Values/Identifiers (highest precedence)
|
|
11
|
+
*/
|
|
12
|
+
import { CONSTANTS } from './tokenizer.mjs';
|
|
13
|
+
/**
|
|
14
|
+
* Recursive Descent Expression Parser
|
|
15
|
+
*/
|
|
16
|
+
export class ExpressionParser {
|
|
17
|
+
tokens;
|
|
18
|
+
position = 0;
|
|
19
|
+
constructor(tokens) {
|
|
20
|
+
this.tokens = tokens;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse tokens into an AST
|
|
24
|
+
*/
|
|
25
|
+
parse() {
|
|
26
|
+
if (this.tokens.length === 0) {
|
|
27
|
+
throw new Error('Cannot parse empty expression');
|
|
28
|
+
}
|
|
29
|
+
const ast = this.parseOr();
|
|
30
|
+
// Ensure all tokens were consumed
|
|
31
|
+
if (!this.isAtEnd()) {
|
|
32
|
+
throw new Error(`Unexpected token at position ${this.position}: ${this.peek()?.value}`);
|
|
33
|
+
}
|
|
34
|
+
return ast;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse OR expressions (lowest precedence)
|
|
38
|
+
* or_expr: and_expr ('or' and_expr)*
|
|
39
|
+
*/
|
|
40
|
+
parseOr() {
|
|
41
|
+
let left = this.parseAnd();
|
|
42
|
+
while (this.matchLogical('or')) {
|
|
43
|
+
const operator = this.previous().value;
|
|
44
|
+
const right = this.parseAnd();
|
|
45
|
+
left = {
|
|
46
|
+
type: 'BinaryOp',
|
|
47
|
+
operator: operator.toLowerCase(),
|
|
48
|
+
left,
|
|
49
|
+
right,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return left;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse AND expressions
|
|
56
|
+
* and_expr: not_expr ('and' not_expr)*
|
|
57
|
+
*/
|
|
58
|
+
parseAnd() {
|
|
59
|
+
let left = this.parseNot();
|
|
60
|
+
while (this.matchLogical('and')) {
|
|
61
|
+
const operator = this.previous().value;
|
|
62
|
+
const right = this.parseNot();
|
|
63
|
+
left = {
|
|
64
|
+
type: 'BinaryOp',
|
|
65
|
+
operator: operator.toLowerCase(),
|
|
66
|
+
left,
|
|
67
|
+
right,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return left;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse NOT expressions
|
|
74
|
+
* not_expr: 'not' comparison | comparison
|
|
75
|
+
*/
|
|
76
|
+
parseNot() {
|
|
77
|
+
if (this.matchLogical('not')) {
|
|
78
|
+
const operator = this.previous().value;
|
|
79
|
+
const operand = this.parseComparison();
|
|
80
|
+
return {
|
|
81
|
+
type: 'UnaryOp',
|
|
82
|
+
operator: operator.toLowerCase(),
|
|
83
|
+
operand,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return this.parseComparison();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse comparison expressions
|
|
90
|
+
* comparison: value (comp_op value)?
|
|
91
|
+
* comp_op: '==' | '!=' | '<>' | '<' | '>' | '<=' | '>=' | 'in' | 'not in'
|
|
92
|
+
*/
|
|
93
|
+
parseComparison() {
|
|
94
|
+
const left = this.parseValue();
|
|
95
|
+
const compOperators = ['==', '!=', '<>', '<', '>', '<=', '>=', 'in', 'not in'];
|
|
96
|
+
if (this.matchOperator(...compOperators)) {
|
|
97
|
+
const operator = this.previous().value;
|
|
98
|
+
const right = this.parseValue();
|
|
99
|
+
return {
|
|
100
|
+
type: 'BinaryOp',
|
|
101
|
+
operator: operator.toLowerCase(),
|
|
102
|
+
left,
|
|
103
|
+
right,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// If no comparison operator, return value as-is (truthiness check)
|
|
107
|
+
return left;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse value expressions (highest precedence)
|
|
111
|
+
* value: '(' or_expr ')' | literal | identifier
|
|
112
|
+
*/
|
|
113
|
+
parseValue() {
|
|
114
|
+
// Parenthesized expression
|
|
115
|
+
if (this.match('PAREN', '(')) {
|
|
116
|
+
const expr = this.parseOr();
|
|
117
|
+
this.consume('PAREN', ')', 'Expected closing parenthesis');
|
|
118
|
+
return expr;
|
|
119
|
+
}
|
|
120
|
+
// String literal
|
|
121
|
+
if (this.check('STRING')) {
|
|
122
|
+
const token = this.advance();
|
|
123
|
+
// Remove quotes
|
|
124
|
+
const value = token.value.slice(1, -1);
|
|
125
|
+
return { type: 'Literal', value };
|
|
126
|
+
}
|
|
127
|
+
// True, False, None
|
|
128
|
+
if (this.check('CONSTANT')) {
|
|
129
|
+
const token = this.advance();
|
|
130
|
+
return { type: 'Literal', value: CONSTANTS.get(token.value) ?? null };
|
|
131
|
+
}
|
|
132
|
+
// Number literal
|
|
133
|
+
if (this.check('NUMBER')) {
|
|
134
|
+
const token = this.advance();
|
|
135
|
+
return { type: 'Literal', value: Number(token.value) };
|
|
136
|
+
}
|
|
137
|
+
// List literal
|
|
138
|
+
if (this.check('LIST')) {
|
|
139
|
+
const token = this.advance();
|
|
140
|
+
const value = parseListLiteral(token.value);
|
|
141
|
+
return { type: 'Literal', value };
|
|
142
|
+
}
|
|
143
|
+
// Identifier
|
|
144
|
+
if (this.check('IDENTIFIER')) {
|
|
145
|
+
const token = this.advance();
|
|
146
|
+
return { type: 'Identifier', name: token.value };
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`Unexpected token: ${this.peek()?.value || 'EOF'}`);
|
|
149
|
+
}
|
|
150
|
+
// ========== Helper Methods ==========
|
|
151
|
+
/**
|
|
152
|
+
* Check if current token matches any of the given types/values
|
|
153
|
+
*/
|
|
154
|
+
match(type, ...values) {
|
|
155
|
+
if (this.check(type)) {
|
|
156
|
+
if (values.length === 0) {
|
|
157
|
+
this.advance();
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
const token = this.peek();
|
|
161
|
+
if (token && values.includes(token.value)) {
|
|
162
|
+
this.advance();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Match logical operators (case-insensitive)
|
|
170
|
+
*/
|
|
171
|
+
matchLogical(...operators) {
|
|
172
|
+
const token = this.peek();
|
|
173
|
+
if (token && token.type === 'LOGICAL') {
|
|
174
|
+
const lowerValue = token.value.toLowerCase();
|
|
175
|
+
if (operators.map(op => op.toLowerCase()).includes(lowerValue)) {
|
|
176
|
+
this.advance();
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Match comparison operators (case-insensitive)
|
|
184
|
+
*/
|
|
185
|
+
matchOperator(...operators) {
|
|
186
|
+
const token = this.peek();
|
|
187
|
+
if (token && token.type === 'OPERATOR') {
|
|
188
|
+
const lowerValue = token.value.toLowerCase();
|
|
189
|
+
if (operators.map(op => op.toLowerCase()).includes(lowerValue)) {
|
|
190
|
+
this.advance();
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if current token has given type
|
|
198
|
+
*/
|
|
199
|
+
check(type) {
|
|
200
|
+
const token = this.peek();
|
|
201
|
+
return token?.type === type;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Consume current token and advance
|
|
205
|
+
*/
|
|
206
|
+
advance() {
|
|
207
|
+
if (!this.isAtEnd()) {
|
|
208
|
+
this.position++;
|
|
209
|
+
}
|
|
210
|
+
return this.previous();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get current token without advancing
|
|
214
|
+
*/
|
|
215
|
+
peek() {
|
|
216
|
+
if (this.isAtEnd()) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
return this.tokens[this.position];
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get previous token
|
|
223
|
+
*/
|
|
224
|
+
previous() {
|
|
225
|
+
return this.tokens[this.position - 1];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if we've consumed all tokens
|
|
229
|
+
*/
|
|
230
|
+
isAtEnd() {
|
|
231
|
+
return this.position >= this.tokens.length;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Consume a token of specific type/value or throw error
|
|
235
|
+
*/
|
|
236
|
+
consume(type, value, message) {
|
|
237
|
+
if (this.check(type)) {
|
|
238
|
+
const token = this.peek();
|
|
239
|
+
if (token && token.value === value) {
|
|
240
|
+
return this.advance();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
throw new Error(message);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Parse a list literal string into an array
|
|
248
|
+
* Example: "['draft', 'sent']" -> ['draft', 'sent']
|
|
249
|
+
*/
|
|
250
|
+
function parseListLiteral(listStr) {
|
|
251
|
+
try {
|
|
252
|
+
// Remove brackets
|
|
253
|
+
const content = listStr.slice(1, -1).trim();
|
|
254
|
+
if (!content) {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
// Split by comma and parse each element
|
|
258
|
+
const elements = [];
|
|
259
|
+
let current = '';
|
|
260
|
+
let inString = false;
|
|
261
|
+
let stringChar = '';
|
|
262
|
+
for (let i = 0; i < content.length; i++) {
|
|
263
|
+
const char = content[i];
|
|
264
|
+
if ((char === "'" || char === '"') && (i === 0 || content[i - 1] !== '\\')) {
|
|
265
|
+
if (!inString) {
|
|
266
|
+
inString = true;
|
|
267
|
+
stringChar = char;
|
|
268
|
+
}
|
|
269
|
+
else if (char === stringChar) {
|
|
270
|
+
inString = false;
|
|
271
|
+
stringChar = '';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (char === ',' && !inString) {
|
|
275
|
+
elements.push(parseElement(current.trim()));
|
|
276
|
+
current = '';
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
current += char;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (current.trim()) {
|
|
283
|
+
elements.push(parseElement(current.trim()));
|
|
284
|
+
}
|
|
285
|
+
return elements;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
// A list that cannot be read is a syntax error, not an empty list.
|
|
289
|
+
throw new Error(`Cannot read the list ${listStr}`, { cause: error });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Parse a single element from a list
|
|
294
|
+
*/
|
|
295
|
+
function parseElement(element) {
|
|
296
|
+
// String
|
|
297
|
+
if ((element.startsWith("'") && element.endsWith("'")) ||
|
|
298
|
+
(element.startsWith('"') && element.endsWith('"'))) {
|
|
299
|
+
return element.slice(1, -1);
|
|
300
|
+
}
|
|
301
|
+
// Number
|
|
302
|
+
if (!isNaN(Number(element))) {
|
|
303
|
+
return Number(element);
|
|
304
|
+
}
|
|
305
|
+
// Boolean
|
|
306
|
+
if (element.toLowerCase() === 'true')
|
|
307
|
+
return true;
|
|
308
|
+
if (element.toLowerCase() === 'false')
|
|
309
|
+
return false;
|
|
310
|
+
// Default to string
|
|
311
|
+
return element;
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=parser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenizer for modifier expressions.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the React form engine, with one change in behaviour: a
|
|
5
|
+
* character the language does not know is an error, not something to skip.
|
|
6
|
+
* The React version dropped it silently, so `amount > -5` read as
|
|
7
|
+
* `amount > 5` and `a && b` read as `a b`.
|
|
8
|
+
*/
|
|
9
|
+
export type TokenType = 'IDENTIFIER' | 'NUMBER' | 'STRING' | 'LIST' | 'OPERATOR' | 'LOGICAL' | 'CONSTANT' | 'PAREN';
|
|
10
|
+
export interface Token {
|
|
11
|
+
type: TokenType;
|
|
12
|
+
value: string;
|
|
13
|
+
position: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Tokenizes a Flectra modifier expression.
|
|
17
|
+
*
|
|
18
|
+
* @param expr - The expression string to tokenize
|
|
19
|
+
* @returns Array of tokens
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* tokenize("state == 'draft'")
|
|
23
|
+
* // Returns: [
|
|
24
|
+
* // { type: 'IDENTIFIER', value: 'state', position: 0 },
|
|
25
|
+
* // { type: 'OPERATOR', value: '==', position: 1 },
|
|
26
|
+
* // { type: 'STRING', value: "'draft'", position: 2 }
|
|
27
|
+
* // ]
|
|
28
|
+
*/
|
|
29
|
+
/** The words that are values, not field names. */
|
|
30
|
+
export declare const CONSTANTS: ReadonlyMap<string, boolean | null>;
|
|
31
|
+
export declare function tokenize(expr: string): Token[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenizer for modifier expressions.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the React form engine, with one change in behaviour: a
|
|
5
|
+
* character the language does not know is an error, not something to skip.
|
|
6
|
+
* The React version dropped it silently, so `amount > -5` read as
|
|
7
|
+
* `amount > 5` and `a && b` read as `a b`.
|
|
8
|
+
*/
|
|
9
|
+
export type TokenType = 'IDENTIFIER' | 'NUMBER' | 'STRING' | 'LIST' | 'OPERATOR' | 'LOGICAL' | 'CONSTANT' | 'PAREN';
|
|
10
|
+
export interface Token {
|
|
11
|
+
type: TokenType;
|
|
12
|
+
value: string;
|
|
13
|
+
position: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Tokenizes a Flectra modifier expression.
|
|
17
|
+
*
|
|
18
|
+
* @param expr - The expression string to tokenize
|
|
19
|
+
* @returns Array of tokens
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* tokenize("state == 'draft'")
|
|
23
|
+
* // Returns: [
|
|
24
|
+
* // { type: 'IDENTIFIER', value: 'state', position: 0 },
|
|
25
|
+
* // { type: 'OPERATOR', value: '==', position: 1 },
|
|
26
|
+
* // { type: 'STRING', value: "'draft'", position: 2 }
|
|
27
|
+
* // ]
|
|
28
|
+
*/
|
|
29
|
+
/** The words that are values, not field names. */
|
|
30
|
+
export declare const CONSTANTS: ReadonlyMap<string, boolean | null>;
|
|
31
|
+
export declare function tokenize(expr: string): Token[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tokenizer for modifier expressions.
|
|
4
|
+
*
|
|
5
|
+
* Ported from the React form engine, with one change in behaviour: a
|
|
6
|
+
* character the language does not know is an error, not something to skip.
|
|
7
|
+
* The React version dropped it silently, so `amount > -5` read as
|
|
8
|
+
* `amount > 5` and `a && b` read as `a b`.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.CONSTANTS = void 0;
|
|
12
|
+
exports.tokenize = tokenize;
|
|
13
|
+
/**
|
|
14
|
+
* Tokenizes a Flectra modifier expression.
|
|
15
|
+
*
|
|
16
|
+
* @param expr - The expression string to tokenize
|
|
17
|
+
* @returns Array of tokens
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* tokenize("state == 'draft'")
|
|
21
|
+
* // Returns: [
|
|
22
|
+
* // { type: 'IDENTIFIER', value: 'state', position: 0 },
|
|
23
|
+
* // { type: 'OPERATOR', value: '==', position: 1 },
|
|
24
|
+
* // { type: 'STRING', value: "'draft'", position: 2 }
|
|
25
|
+
* // ]
|
|
26
|
+
*/
|
|
27
|
+
/** The words that are values, not field names. */
|
|
28
|
+
exports.CONSTANTS = new Map([
|
|
29
|
+
['True', true],
|
|
30
|
+
['False', false],
|
|
31
|
+
['None', null],
|
|
32
|
+
['true', true],
|
|
33
|
+
['false', false],
|
|
34
|
+
['null', null],
|
|
35
|
+
]);
|
|
36
|
+
function tokenize(expr) {
|
|
37
|
+
const tokens = [];
|
|
38
|
+
if (!expr)
|
|
39
|
+
return tokens;
|
|
40
|
+
// Alternatives in priority order: "not in" before "not", operators of two
|
|
41
|
+
// characters before one, a signed number before anything else that could
|
|
42
|
+
// start with a digit. Sticky (`y`), so a position nothing matches is an
|
|
43
|
+
// error instead of a gap.
|
|
44
|
+
const token = /(\(|\)|==|!=|<=|>=|<>|<|>|not\s+in\b|in\b|and\b|or\b|not\b|\[[^\]]*\]|'[^']*'|"[^"]*"|-?\d+\.?\d*|\w+(?:\.\w+)*)/iy;
|
|
45
|
+
const space = /\s*/y;
|
|
46
|
+
let at = 0;
|
|
47
|
+
for (;;) {
|
|
48
|
+
space.lastIndex = at;
|
|
49
|
+
space.exec(expr);
|
|
50
|
+
at = space.lastIndex;
|
|
51
|
+
if (at >= expr.length)
|
|
52
|
+
return tokens;
|
|
53
|
+
token.lastIndex = at;
|
|
54
|
+
const match = token.exec(expr);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new Error(`Unexpected "${expr[at]}" at character ${at + 1} of: ${expr}`);
|
|
57
|
+
}
|
|
58
|
+
tokens.push({ type: getTokenType(match[0]), value: match[0], position: tokens.length });
|
|
59
|
+
at = token.lastIndex;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Determines the type of a token based on its text value.
|
|
64
|
+
*/
|
|
65
|
+
function getTokenType(text) {
|
|
66
|
+
const lowerText = text.toLowerCase().trim();
|
|
67
|
+
// Python's constants, and their JSON spellings
|
|
68
|
+
if (exports.CONSTANTS.has(text)) {
|
|
69
|
+
return 'CONSTANT';
|
|
70
|
+
}
|
|
71
|
+
// Logical operators (case-insensitive)
|
|
72
|
+
if (lowerText === 'and' || lowerText === 'or' || lowerText === 'not') {
|
|
73
|
+
return 'LOGICAL';
|
|
74
|
+
}
|
|
75
|
+
// Comparison operators (case-insensitive for word operators)
|
|
76
|
+
if (['==', '!=', '<>', '<', '>', '<=', '>='].includes(text) ||
|
|
77
|
+
lowerText === 'in' ||
|
|
78
|
+
lowerText === 'not in') {
|
|
79
|
+
return 'OPERATOR';
|
|
80
|
+
}
|
|
81
|
+
// Parentheses
|
|
82
|
+
if (text === '(' || text === ')') {
|
|
83
|
+
return 'PAREN';
|
|
84
|
+
}
|
|
85
|
+
// Lists (start with '[')
|
|
86
|
+
if (text.startsWith('[')) {
|
|
87
|
+
return 'LIST';
|
|
88
|
+
}
|
|
89
|
+
// Strings (start with ' or ")
|
|
90
|
+
if (text.startsWith("'") || text.startsWith('"')) {
|
|
91
|
+
return 'STRING';
|
|
92
|
+
}
|
|
93
|
+
// Numbers (matches integer or float, either sign)
|
|
94
|
+
if (/^-?\d+\.?\d*$/.test(text)) {
|
|
95
|
+
return 'NUMBER';
|
|
96
|
+
}
|
|
97
|
+
// Default to identifier
|
|
98
|
+
return 'IDENTIFIER';
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/tokenizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenizer for modifier expressions.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the React form engine, with one change in behaviour: a
|
|
5
|
+
* character the language does not know is an error, not something to skip.
|
|
6
|
+
* The React version dropped it silently, so `amount > -5` read as
|
|
7
|
+
* `amount > 5` and `a && b` read as `a b`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Tokenizes a Flectra modifier expression.
|
|
11
|
+
*
|
|
12
|
+
* @param expr - The expression string to tokenize
|
|
13
|
+
* @returns Array of tokens
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* tokenize("state == 'draft'")
|
|
17
|
+
* // Returns: [
|
|
18
|
+
* // { type: 'IDENTIFIER', value: 'state', position: 0 },
|
|
19
|
+
* // { type: 'OPERATOR', value: '==', position: 1 },
|
|
20
|
+
* // { type: 'STRING', value: "'draft'", position: 2 }
|
|
21
|
+
* // ]
|
|
22
|
+
*/
|
|
23
|
+
/** The words that are values, not field names. */
|
|
24
|
+
export const CONSTANTS = new Map([
|
|
25
|
+
['True', true],
|
|
26
|
+
['False', false],
|
|
27
|
+
['None', null],
|
|
28
|
+
['true', true],
|
|
29
|
+
['false', false],
|
|
30
|
+
['null', null],
|
|
31
|
+
]);
|
|
32
|
+
export function tokenize(expr) {
|
|
33
|
+
const tokens = [];
|
|
34
|
+
if (!expr)
|
|
35
|
+
return tokens;
|
|
36
|
+
// Alternatives in priority order: "not in" before "not", operators of two
|
|
37
|
+
// characters before one, a signed number before anything else that could
|
|
38
|
+
// start with a digit. Sticky (`y`), so a position nothing matches is an
|
|
39
|
+
// error instead of a gap.
|
|
40
|
+
const token = /(\(|\)|==|!=|<=|>=|<>|<|>|not\s+in\b|in\b|and\b|or\b|not\b|\[[^\]]*\]|'[^']*'|"[^"]*"|-?\d+\.?\d*|\w+(?:\.\w+)*)/iy;
|
|
41
|
+
const space = /\s*/y;
|
|
42
|
+
let at = 0;
|
|
43
|
+
for (;;) {
|
|
44
|
+
space.lastIndex = at;
|
|
45
|
+
space.exec(expr);
|
|
46
|
+
at = space.lastIndex;
|
|
47
|
+
if (at >= expr.length)
|
|
48
|
+
return tokens;
|
|
49
|
+
token.lastIndex = at;
|
|
50
|
+
const match = token.exec(expr);
|
|
51
|
+
if (!match) {
|
|
52
|
+
throw new Error(`Unexpected "${expr[at]}" at character ${at + 1} of: ${expr}`);
|
|
53
|
+
}
|
|
54
|
+
tokens.push({ type: getTokenType(match[0]), value: match[0], position: tokens.length });
|
|
55
|
+
at = token.lastIndex;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Determines the type of a token based on its text value.
|
|
60
|
+
*/
|
|
61
|
+
function getTokenType(text) {
|
|
62
|
+
const lowerText = text.toLowerCase().trim();
|
|
63
|
+
// Python's constants, and their JSON spellings
|
|
64
|
+
if (CONSTANTS.has(text)) {
|
|
65
|
+
return 'CONSTANT';
|
|
66
|
+
}
|
|
67
|
+
// Logical operators (case-insensitive)
|
|
68
|
+
if (lowerText === 'and' || lowerText === 'or' || lowerText === 'not') {
|
|
69
|
+
return 'LOGICAL';
|
|
70
|
+
}
|
|
71
|
+
// Comparison operators (case-insensitive for word operators)
|
|
72
|
+
if (['==', '!=', '<>', '<', '>', '<=', '>='].includes(text) ||
|
|
73
|
+
lowerText === 'in' ||
|
|
74
|
+
lowerText === 'not in') {
|
|
75
|
+
return 'OPERATOR';
|
|
76
|
+
}
|
|
77
|
+
// Parentheses
|
|
78
|
+
if (text === '(' || text === ')') {
|
|
79
|
+
return 'PAREN';
|
|
80
|
+
}
|
|
81
|
+
// Lists (start with '[')
|
|
82
|
+
if (text.startsWith('[')) {
|
|
83
|
+
return 'LIST';
|
|
84
|
+
}
|
|
85
|
+
// Strings (start with ' or ")
|
|
86
|
+
if (text.startsWith("'") || text.startsWith('"')) {
|
|
87
|
+
return 'STRING';
|
|
88
|
+
}
|
|
89
|
+
// Numbers (matches integer or float, either sign)
|
|
90
|
+
if (/^-?\d+\.?\d*$/.test(text)) {
|
|
91
|
+
return 'NUMBER';
|
|
92
|
+
}
|
|
93
|
+
// Default to identifier
|
|
94
|
+
return 'IDENTIFIER';
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=tokenizer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/tokenizer.ts"],"names":[],"mappings":"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"}
|