@bpmnkit/feel 0.0.21 → 1.0.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/README.md +18 -14
- package/dist/ast.d.ts +4 -0
- package/dist/builtins.d.ts +7 -0
- package/dist/builtins.js +778 -164
- package/dist/evaluator.d.ts +4 -1
- package/dist/evaluator.js +251 -139
- package/dist/formatter.js +16 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/dist/lexer.d.ts +7 -0
- package/dist/lexer.js +92 -5
- package/dist/parser.d.ts +11 -2
- package/dist/parser.js +315 -98
- package/dist/types.js +35 -2
- package/package.json +8 -2
package/dist/lexer.js
CHANGED
|
@@ -36,6 +36,7 @@ const DOT = 0x2e;
|
|
|
36
36
|
const GT = 0x3e;
|
|
37
37
|
const LT = 0x3c;
|
|
38
38
|
const BANG = 0x21;
|
|
39
|
+
const PLUS = 0x2b;
|
|
39
40
|
const MINUS = 0x2d;
|
|
40
41
|
const EQ = 0x3d;
|
|
41
42
|
const UNDERSCORE = 0x5f;
|
|
@@ -43,13 +44,32 @@ function isDigit(c) {
|
|
|
43
44
|
return c >= 0x30 && c <= 0x39;
|
|
44
45
|
}
|
|
45
46
|
function isLetter(c) {
|
|
46
|
-
|
|
47
|
+
if ((c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a))
|
|
48
|
+
return true;
|
|
49
|
+
// FEEL names are not limited to ASCII: anything above the ASCII range is
|
|
50
|
+
// a name character, which covers accented letters and emoji alike.
|
|
51
|
+
return c > 0x7f;
|
|
47
52
|
}
|
|
48
53
|
function isWhitespace(c) {
|
|
49
54
|
return c === SPACE || c === TAB || c === LF || c === CR;
|
|
50
55
|
}
|
|
51
56
|
const SINGLE_OPS = new Set("+-*/=<>?".split("").map((c) => c.charCodeAt(0)));
|
|
52
57
|
const PUNCT = new Set("()[]{},:".split("").map((c) => c.charCodeAt(0)));
|
|
58
|
+
/** End of an exponent starting at `i` ("e4", "e+4", "e-4"), or `i` if none. */
|
|
59
|
+
function readExponent(input, i) {
|
|
60
|
+
const c = input.charCodeAt(i);
|
|
61
|
+
if (c !== 0x65 && c !== 0x45)
|
|
62
|
+
return i;
|
|
63
|
+
let j = i + 1;
|
|
64
|
+
const sign = input.charCodeAt(j);
|
|
65
|
+
if (sign === PLUS || sign === MINUS)
|
|
66
|
+
j++;
|
|
67
|
+
if (!isDigit(input.charCodeAt(j)))
|
|
68
|
+
return i;
|
|
69
|
+
while (j < input.length && isDigit(input.charCodeAt(j)))
|
|
70
|
+
j++;
|
|
71
|
+
return j;
|
|
72
|
+
}
|
|
53
73
|
export function tokenize(input) {
|
|
54
74
|
const tokens = [];
|
|
55
75
|
let i = 0;
|
|
@@ -150,14 +170,15 @@ export function tokenize(input) {
|
|
|
150
170
|
i++;
|
|
151
171
|
continue;
|
|
152
172
|
}
|
|
153
|
-
// Dot (not ..)
|
|
154
|
-
if (c === DOT) {
|
|
173
|
+
// Dot (not ".." and not the start of a number like ".872")
|
|
174
|
+
if (c === DOT && !isDigit(next)) {
|
|
155
175
|
tokens.push({ kind: "punct", value: ".", start, end: i + 1 });
|
|
156
176
|
i++;
|
|
157
177
|
continue;
|
|
158
178
|
}
|
|
159
|
-
// Number
|
|
160
|
-
|
|
179
|
+
// Number: digits, an optional fraction, an optional exponent. A leading
|
|
180
|
+
// "." is allowed (".872"), and ".." is never part of a number.
|
|
181
|
+
if (isDigit(c) || (c === DOT && isDigit(next))) {
|
|
161
182
|
while (i < len && isDigit(input.charCodeAt(i)))
|
|
162
183
|
i++;
|
|
163
184
|
// Consume decimal fraction only if next char is '.' followed by a digit (not '..')
|
|
@@ -166,6 +187,9 @@ export function tokenize(input) {
|
|
|
166
187
|
while (i < len && isDigit(input.charCodeAt(i)))
|
|
167
188
|
i++;
|
|
168
189
|
}
|
|
190
|
+
const exponent = readExponent(input, i);
|
|
191
|
+
if (exponent > i)
|
|
192
|
+
i = exponent;
|
|
169
193
|
tokens.push({ kind: "number", value: input.slice(start, i), start, end: i });
|
|
170
194
|
continue;
|
|
171
195
|
}
|
|
@@ -189,4 +213,67 @@ export function tokenize(input) {
|
|
|
189
213
|
}
|
|
190
214
|
return tokens;
|
|
191
215
|
}
|
|
216
|
+
const SIMPLE_ESCAPES = {
|
|
217
|
+
"'": "'",
|
|
218
|
+
'"': '"',
|
|
219
|
+
"\\": "\\",
|
|
220
|
+
n: "\n",
|
|
221
|
+
r: "\r",
|
|
222
|
+
t: "\t",
|
|
223
|
+
};
|
|
224
|
+
/** Decodes a \u/\U escape at `i`, or returns null when it is not a valid one. */
|
|
225
|
+
function readCodePoint(body, i) {
|
|
226
|
+
const kind = body[i + 1];
|
|
227
|
+
const maxDigits = kind === "u" ? 4 : 6;
|
|
228
|
+
const digits = /^[0-9a-fA-F]+/.exec(body.slice(i + 2, i + 2 + maxDigits))?.[0] ?? "";
|
|
229
|
+
// \u takes exactly four digits; \U takes as many as still form a code point,
|
|
230
|
+
// so "\U101EF0" is \U101EF followed by a literal "0".
|
|
231
|
+
const minLength = kind === "u" ? 4 : 1;
|
|
232
|
+
for (let len = digits.length; len >= minLength; len--) {
|
|
233
|
+
if (kind === "u" && len !== 4)
|
|
234
|
+
break;
|
|
235
|
+
const code = Number.parseInt(digits.slice(0, len), 16);
|
|
236
|
+
if (code <= 0x10ffff)
|
|
237
|
+
return { text: String.fromCodePoint(code), length: 2 + len };
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Decodes the escape sequences of a FEEL string literal body (the text between
|
|
243
|
+
* the quotes). Recognizes \' \" \\ \n \r \t, \uXXXX and the extended
|
|
244
|
+
* \UXXXXXX form; an unrecognized sequence is left as written, since dropping
|
|
245
|
+
* the backslash would silently alter the author's data.
|
|
246
|
+
*/
|
|
247
|
+
export function unescapeString(body) {
|
|
248
|
+
if (!body.includes("\\"))
|
|
249
|
+
return body;
|
|
250
|
+
let out = "";
|
|
251
|
+
let i = 0;
|
|
252
|
+
while (i < body.length) {
|
|
253
|
+
const c = body[i];
|
|
254
|
+
if (c !== "\\" || i + 1 >= body.length) {
|
|
255
|
+
out += c;
|
|
256
|
+
i++;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const simple = SIMPLE_ESCAPES[body[i + 1]];
|
|
260
|
+
if (simple !== undefined) {
|
|
261
|
+
out += simple;
|
|
262
|
+
i += 2;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const next = body[i + 1];
|
|
266
|
+
if (next === "u" || next === "U") {
|
|
267
|
+
const decoded = readCodePoint(body, i);
|
|
268
|
+
if (decoded) {
|
|
269
|
+
out += decoded.text;
|
|
270
|
+
i += decoded.length;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
out += c;
|
|
275
|
+
i++;
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
192
279
|
//# sourceMappingURL=lexer.js.map
|
package/dist/parser.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface ParseResult {
|
|
|
8
8
|
ast: FeelNode | null;
|
|
9
9
|
errors: ParseError[];
|
|
10
10
|
}
|
|
11
|
-
export
|
|
12
|
-
|
|
11
|
+
export interface ParseOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Names that are in scope where the expression is evaluated. FEEL names may
|
|
14
|
+
* contain spaces, so `a b + 1` can only be read as a reference to `a b`
|
|
15
|
+
* when the parser is told that `a b` exists. Without this, only multi-word
|
|
16
|
+
* built-in names are recognized.
|
|
17
|
+
*/
|
|
18
|
+
names?: Iterable<string>;
|
|
19
|
+
}
|
|
20
|
+
export declare function parseExpression(input: string, options?: ParseOptions): ParseResult;
|
|
21
|
+
export declare function parseUnaryTests(input: string, options?: ParseOptions): ParseResult;
|
|
13
22
|
//# sourceMappingURL=parser.d.ts.map
|