@fieldia/core 0.9.1 → 0.10.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/package.json +1 -1
- package/schema/page.schema.json +1128 -89
- package/src/lib/expression/evaluator.d.mts +6 -5
- package/src/lib/expression/evaluator.d.ts +6 -5
- package/src/lib/expression/evaluator.js +37 -22
- package/src/lib/expression/evaluator.js.map +1 -1
- package/src/lib/expression/evaluator.mjs +32 -18
- package/src/lib/expression/evaluator.mjs.map +1 -1
- package/src/lib/expression/expression.d.mts +21 -0
- package/src/lib/expression/expression.d.ts +21 -0
- package/src/lib/expression/expression.js +29 -0
- package/src/lib/expression/expression.js.map +1 -0
- package/src/lib/expression/expression.mjs +26 -0
- package/src/lib/expression/expression.mjs.map +1 -0
- package/src/lib/expression/extractFieldNames.js +6 -1
- package/src/lib/expression/extractFieldNames.js.map +1 -1
- package/src/lib/expression/extractFieldNames.mjs +6 -1
- package/src/lib/expression/extractFieldNames.mjs.map +1 -1
- package/src/lib/expression/functions.d.mts +35 -0
- package/src/lib/expression/functions.d.ts +35 -0
- package/src/lib/expression/functions.js +129 -0
- package/src/lib/expression/functions.js.map +1 -0
- package/src/lib/expression/functions.mjs +124 -0
- package/src/lib/expression/functions.mjs.map +1 -0
- package/src/lib/expression/modifier.d.mts +3 -1
- package/src/lib/expression/modifier.d.ts +3 -1
- package/src/lib/expression/modifier.js +4 -27
- package/src/lib/expression/modifier.js.map +1 -1
- package/src/lib/expression/modifier.mjs +3 -26
- package/src/lib/expression/modifier.mjs.map +1 -1
- package/src/lib/expression/operations.d.mts +25 -0
- package/src/lib/expression/operations.d.ts +25 -0
- package/src/lib/expression/operations.js +81 -0
- package/src/lib/expression/operations.js.map +1 -0
- package/src/lib/expression/operations.mjs +72 -0
- package/src/lib/expression/operations.mjs.map +1 -0
- package/src/lib/expression/order.d.mts +11 -0
- package/src/lib/expression/order.d.ts +11 -0
- package/src/lib/expression/order.js +36 -0
- package/src/lib/expression/order.js.map +1 -0
- package/src/lib/expression/order.mjs +33 -0
- package/src/lib/expression/order.mjs.map +1 -0
- package/src/lib/expression/parser.d.mts +34 -4
- package/src/lib/expression/parser.d.ts +34 -4
- package/src/lib/expression/parser.js +72 -6
- package/src/lib/expression/parser.js.map +1 -1
- package/src/lib/expression/parser.mjs +72 -6
- package/src/lib/expression/parser.mjs.map +1 -1
- package/src/lib/expression/reads.d.mts +8 -0
- package/src/lib/expression/reads.d.ts +8 -0
- package/src/lib/expression/reads.js +48 -0
- package/src/lib/expression/reads.js.map +1 -0
- package/src/lib/expression/reads.mjs +44 -0
- package/src/lib/expression/reads.mjs.map +1 -0
- package/src/lib/expression/tokenizer.d.mts +7 -6
- package/src/lib/expression/tokenizer.d.ts +7 -6
- package/src/lib/expression/tokenizer.js +28 -18
- package/src/lib/expression/tokenizer.js.map +1 -1
- package/src/lib/expression/tokenizer.mjs +28 -18
- package/src/lib/expression/tokenizer.mjs.map +1 -1
- package/src/lib/format/field.d.mts +686 -5
- package/src/lib/format/field.d.ts +686 -5
- package/src/lib/format/field.js +115 -13
- package/src/lib/format/field.js.map +1 -1
- package/src/lib/format/field.mjs +114 -12
- package/src/lib/format/field.mjs.map +1 -1
- package/src/lib/format/layout.d.mts +388 -10
- package/src/lib/format/layout.d.ts +388 -10
- package/src/lib/format/layout.js +50 -3
- package/src/lib/format/layout.js.map +1 -1
- package/src/lib/format/layout.mjs +49 -2
- package/src/lib/format/layout.mjs.map +1 -1
- package/src/lib/format/names.d.mts +1 -1
- package/src/lib/format/names.d.ts +1 -1
- package/src/lib/format/names.js +1 -0
- package/src/lib/format/names.js.map +1 -1
- package/src/lib/format/names.mjs +1 -0
- package/src/lib/format/names.mjs.map +1 -1
- package/src/lib/format/page.d.mts +391 -4
- package/src/lib/format/page.d.ts +391 -4
- package/src/lib/format/page.js +13 -0
- package/src/lib/format/page.js.map +1 -1
- package/src/lib/format/page.mjs +13 -0
- package/src/lib/format/page.mjs.map +1 -1
- package/src/lib/format/references.d.mts +14 -1
- package/src/lib/format/references.d.ts +14 -1
- package/src/lib/format/references.js +96 -8
- package/src/lib/format/references.js.map +1 -1
- package/src/lib/format/references.mjs +96 -8
- package/src/lib/format/references.mjs.map +1 -1
- package/src/lib/format/translate.d.mts +20 -0
- package/src/lib/format/translate.d.ts +20 -0
- package/src/lib/format/translate.js +79 -21
- package/src/lib/format/translate.js.map +1 -1
- package/src/lib/format/translate.mjs +76 -21
- package/src/lib/format/translate.mjs.map +1 -1
- package/src/lib/record/check.d.mts +19 -1
- package/src/lib/record/check.d.ts +19 -1
- package/src/lib/record/check.js +84 -21
- package/src/lib/record/check.js.map +1 -1
- package/src/lib/record/check.mjs +82 -21
- package/src/lib/record/check.mjs.map +1 -1
- package/src/lib/record/compute.d.mts +24 -0
- package/src/lib/record/compute.d.ts +24 -0
- package/src/lib/record/compute.js +107 -0
- package/src/lib/record/compute.js.map +1 -0
- package/src/lib/record/compute.mjs +103 -0
- package/src/lib/record/compute.mjs.map +1 -0
- package/src/lib/record/data-source.d.mts +9 -1
- package/src/lib/record/data-source.d.ts +9 -1
- package/src/lib/record/data-source.js.map +1 -1
- package/src/lib/record/data-source.mjs.map +1 -1
- package/src/lib/record/env.d.mts +9 -0
- package/src/lib/record/env.d.ts +9 -0
- package/src/lib/record/env.js +28 -0
- package/src/lib/record/env.js.map +1 -0
- package/src/lib/record/env.mjs +25 -0
- package/src/lib/record/env.mjs.map +1 -0
- package/src/lib/record/form.d.mts +24 -0
- package/src/lib/record/form.d.ts +24 -0
- package/src/lib/record/form.js +160 -32
- package/src/lib/record/form.js.map +1 -1
- package/src/lib/record/form.mjs +160 -32
- package/src/lib/record/form.mjs.map +1 -1
- package/src/lib/record/inputs.d.mts +5 -0
- package/src/lib/record/inputs.d.ts +5 -0
- package/src/lib/record/inputs.js +47 -0
- package/src/lib/record/inputs.js.map +1 -0
- package/src/lib/record/inputs.mjs +44 -0
- package/src/lib/record/inputs.mjs.map +1 -0
- package/src/lib/record/limits.d.mts +13 -0
- package/src/lib/record/limits.d.ts +13 -0
- package/src/lib/record/limits.js +45 -0
- package/src/lib/record/limits.js.map +1 -0
- package/src/lib/record/limits.mjs +41 -0
- package/src/lib/record/limits.mjs.map +1 -0
- package/src/lib/record/locales/all.d.mts +7 -0
- package/src/lib/record/locales/all.d.ts +7 -0
- package/src/lib/record/locales/all.js +13 -0
- package/src/lib/record/locales/all.js.map +1 -0
- package/src/lib/record/locales/all.mjs +10 -0
- package/src/lib/record/locales/all.mjs.map +1 -0
- package/src/lib/record/locales/ar.d.mts +3 -0
- package/src/lib/record/locales/ar.d.ts +3 -0
- package/src/lib/record/locales/ar.js +55 -0
- package/src/lib/record/locales/ar.js.map +1 -0
- package/src/lib/record/locales/ar.mjs +52 -0
- package/src/lib/record/locales/ar.mjs.map +1 -0
- package/src/lib/record/locales/de.d.mts +3 -0
- package/src/lib/record/locales/de.d.ts +3 -0
- package/src/lib/record/locales/de.js +55 -0
- package/src/lib/record/locales/de.js.map +1 -0
- package/src/lib/record/locales/de.mjs +52 -0
- package/src/lib/record/locales/de.mjs.map +1 -0
- package/src/lib/record/locales/fr.d.mts +3 -0
- package/src/lib/record/locales/fr.d.ts +3 -0
- package/src/lib/record/locales/fr.js +55 -0
- package/src/lib/record/locales/fr.js.map +1 -0
- package/src/lib/record/locales/fr.mjs +52 -0
- package/src/lib/record/locales/fr.mjs.map +1 -0
- package/src/lib/record/memory-data-source.d.mts +3 -0
- package/src/lib/record/memory-data-source.d.ts +3 -0
- package/src/lib/record/memory-data-source.js +8 -0
- package/src/lib/record/memory-data-source.js.map +1 -1
- package/src/lib/record/memory-data-source.mjs +8 -0
- package/src/lib/record/memory-data-source.mjs.map +1 -1
- package/src/lib/record/messages.d.mts +46 -0
- package/src/lib/record/messages.d.ts +46 -0
- package/src/lib/record/messages.js +58 -101
- package/src/lib/record/messages.js.map +1 -1
- package/src/lib/record/messages.mjs +58 -101
- package/src/lib/record/messages.mjs.map +1 -1
- package/src/lib/record/rules.d.mts +33 -0
- package/src/lib/record/rules.d.ts +33 -0
- package/src/lib/record/rules.js +83 -0
- package/src/lib/record/rules.js.map +1 -0
- package/src/lib/record/rules.mjs +79 -0
- package/src/lib/record/rules.mjs.map +1 -0
- package/src/lib/record/score.d.mts +16 -0
- package/src/lib/record/score.d.ts +16 -0
- package/src/lib/record/score.js +35 -0
- package/src/lib/record/score.js.map +1 -0
- package/src/lib/record/score.mjs +32 -0
- package/src/lib/record/score.mjs.map +1 -0
- package/src/lib/record/set-when.d.mts +24 -0
- package/src/lib/record/set-when.d.ts +24 -0
- package/src/lib/record/set-when.js +68 -0
- package/src/lib/record/set-when.js.map +1 -0
- package/src/lib/record/set-when.mjs +65 -0
- package/src/lib/record/set-when.mjs.map +1 -0
- package/src/lib/record/structures.d.mts +14 -0
- package/src/lib/record/structures.d.ts +14 -0
- package/src/lib/record/structures.js +32 -0
- package/src/lib/record/structures.js.map +1 -0
- package/src/lib/record/structures.mjs +28 -0
- package/src/lib/record/structures.mjs.map +1 -0
- package/src/lib/record/values.d.mts +10 -5
- package/src/lib/record/values.d.ts +10 -5
- package/src/lib/record/values.js +20 -6
- package/src/lib/record/values.js.map +1 -1
- package/src/lib/record/values.mjs +19 -6
- package/src/lib/record/values.mjs.map +1 -1
- package/src/runtime.d.mts +10 -7
- package/src/runtime.d.ts +10 -7
- package/src/runtime.js +12 -1
- package/src/runtime.js.map +1 -1
- package/src/runtime.mjs +5 -2
- package/src/runtime.mjs.map +1 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AST evaluator for
|
|
2
|
+
* AST evaluator for expressions, ported from the React form engine.
|
|
3
3
|
*
|
|
4
4
|
* Python semantics where the React version used JavaScript's: an empty list
|
|
5
5
|
* or string is false, and an empty value is never smaller or larger than
|
|
6
|
-
* anything — modifiers come from backends that think in Python.
|
|
6
|
+
* anything — modifiers come from backends that think in Python. Arithmetic
|
|
7
|
+
* and functions give null for what they cannot work out, never an error.
|
|
7
8
|
*/
|
|
8
9
|
import type { ASTNode } from './parser.mjs';
|
|
10
|
+
import { type ExpressionEnv } from './functions.mjs';
|
|
11
|
+
export { truthy } from './operations.mjs';
|
|
9
12
|
/**
|
|
10
13
|
* Evaluate an AST node against a context object
|
|
11
14
|
*
|
|
@@ -18,6 +21,4 @@ import type { ASTNode } from './parser.mjs';
|
|
|
18
21
|
* const context = { state: 'draft', amount: 100 };
|
|
19
22
|
* evaluate(ast, context); // Returns boolean result
|
|
20
23
|
*/
|
|
21
|
-
export declare function evaluate(ast: ASTNode, context: Record<string, unknown
|
|
22
|
-
/** Python's truth test: empty lists, strings and objects, 0 and None are false. */
|
|
23
|
-
export declare function truthy(value: unknown): boolean;
|
|
24
|
+
export declare function evaluate(ast: ASTNode, context: Record<string, unknown>, env?: ExpressionEnv): unknown;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AST evaluator for
|
|
2
|
+
* AST evaluator for expressions, ported from the React form engine.
|
|
3
3
|
*
|
|
4
4
|
* Python semantics where the React version used JavaScript's: an empty list
|
|
5
5
|
* or string is false, and an empty value is never smaller or larger than
|
|
6
|
-
* anything — modifiers come from backends that think in Python.
|
|
6
|
+
* anything — modifiers come from backends that think in Python. Arithmetic
|
|
7
|
+
* and functions give null for what they cannot work out, never an error.
|
|
7
8
|
*/
|
|
8
9
|
import type { ASTNode } from './parser';
|
|
10
|
+
import { type ExpressionEnv } from './functions';
|
|
11
|
+
export { truthy } from './operations';
|
|
9
12
|
/**
|
|
10
13
|
* Evaluate an AST node against a context object
|
|
11
14
|
*
|
|
@@ -18,6 +21,4 @@ import type { ASTNode } from './parser';
|
|
|
18
21
|
* const context = { state: 'draft', amount: 100 };
|
|
19
22
|
* evaluate(ast, context); // Returns boolean result
|
|
20
23
|
*/
|
|
21
|
-
export declare function evaluate(ast: ASTNode, context: Record<string, unknown
|
|
22
|
-
/** Python's truth test: empty lists, strings and objects, 0 and None are false. */
|
|
23
|
-
export declare function truthy(value: unknown): boolean;
|
|
24
|
+
export declare function evaluate(ast: ASTNode, context: Record<string, unknown>, env?: ExpressionEnv): unknown;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* AST evaluator for
|
|
3
|
+
* AST evaluator for expressions, ported from the React form engine.
|
|
4
4
|
*
|
|
5
5
|
* Python semantics where the React version used JavaScript's: an empty list
|
|
6
6
|
* or string is false, and an empty value is never smaller or larger than
|
|
7
|
-
* anything — modifiers come from backends that think in Python.
|
|
7
|
+
* anything — modifiers come from backends that think in Python. Arithmetic
|
|
8
|
+
* and functions give null for what they cannot work out, never an error.
|
|
8
9
|
*/
|
|
9
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.truthy = void 0;
|
|
10
12
|
exports.evaluate = evaluate;
|
|
11
|
-
|
|
13
|
+
const functions_1 = require("./functions");
|
|
14
|
+
const operations_1 = require("./operations");
|
|
15
|
+
var operations_2 = require("./operations");
|
|
16
|
+
Object.defineProperty(exports, "truthy", { enumerable: true, get: function () { return operations_2.truthy; } });
|
|
12
17
|
/**
|
|
13
18
|
* Evaluate an AST node against a context object
|
|
14
19
|
*
|
|
@@ -21,12 +26,14 @@ exports.truthy = truthy;
|
|
|
21
26
|
* const context = { state: 'draft', amount: 100 };
|
|
22
27
|
* evaluate(ast, context); // Returns boolean result
|
|
23
28
|
*/
|
|
24
|
-
function evaluate(ast, context) {
|
|
29
|
+
function evaluate(ast, context, env = {}) {
|
|
25
30
|
switch (ast.type) {
|
|
26
31
|
case 'BinaryOp':
|
|
27
|
-
return evaluateBinaryOp(ast, context);
|
|
32
|
+
return evaluateBinaryOp(ast, context, env);
|
|
28
33
|
case 'UnaryOp':
|
|
29
|
-
return evaluateUnaryOp(ast, context);
|
|
34
|
+
return evaluateUnaryOp(ast, context, env);
|
|
35
|
+
case 'Call':
|
|
36
|
+
return evaluateCall(ast, context, env);
|
|
30
37
|
case 'Literal':
|
|
31
38
|
return ast.value;
|
|
32
39
|
case 'Identifier':
|
|
@@ -38,9 +45,9 @@ function evaluate(ast, context) {
|
|
|
38
45
|
/**
|
|
39
46
|
* Evaluate binary operations
|
|
40
47
|
*/
|
|
41
|
-
function evaluateBinaryOp(node, context) {
|
|
42
|
-
const left = normalize(evaluate(node.left, context));
|
|
43
|
-
const right = normalize(evaluate(node.right, context));
|
|
48
|
+
function evaluateBinaryOp(node, context, env) {
|
|
49
|
+
const left = normalize(evaluate(node.left, context, env));
|
|
50
|
+
const right = normalize(evaluate(node.right, context, env));
|
|
44
51
|
const operator = node.operator.toLowerCase();
|
|
45
52
|
const ordered = left !== null && right !== null;
|
|
46
53
|
switch (operator) {
|
|
@@ -63,11 +70,18 @@ function evaluateBinaryOp(node, context) {
|
|
|
63
70
|
return Array.isArray(right) && right.includes(left);
|
|
64
71
|
case 'not in':
|
|
65
72
|
return Array.isArray(right) && !right.includes(left);
|
|
73
|
+
// Arithmetic, and + joining text
|
|
74
|
+
case '+':
|
|
75
|
+
case '-':
|
|
76
|
+
case '*':
|
|
77
|
+
case '/':
|
|
78
|
+
case '%':
|
|
79
|
+
return (0, operations_1.arithmetic)(operator, left, right);
|
|
66
80
|
// Logical operators (use truthiness)
|
|
67
81
|
case 'and':
|
|
68
|
-
return truthy(left) && truthy(right);
|
|
82
|
+
return (0, operations_1.truthy)(left) && (0, operations_1.truthy)(right);
|
|
69
83
|
case 'or':
|
|
70
|
-
return truthy(left) || truthy(right);
|
|
84
|
+
return (0, operations_1.truthy)(left) || (0, operations_1.truthy)(right);
|
|
71
85
|
default:
|
|
72
86
|
throw new Error(`Unknown binary operator: ${node.operator}`);
|
|
73
87
|
}
|
|
@@ -75,28 +89,29 @@ function evaluateBinaryOp(node, context) {
|
|
|
75
89
|
/**
|
|
76
90
|
* Evaluate unary operations
|
|
77
91
|
*/
|
|
78
|
-
function evaluateUnaryOp(node, context) {
|
|
79
|
-
const operand = evaluate(node.operand, context);
|
|
92
|
+
function evaluateUnaryOp(node, context, env) {
|
|
93
|
+
const operand = evaluate(node.operand, context, env);
|
|
80
94
|
const operator = node.operator.toLowerCase();
|
|
81
95
|
switch (operator) {
|
|
82
96
|
case 'not':
|
|
83
|
-
return !truthy(operand);
|
|
97
|
+
return !(0, operations_1.truthy)(operand);
|
|
98
|
+
case '-':
|
|
99
|
+
return (0, operations_1.negate)(operand);
|
|
84
100
|
default:
|
|
85
101
|
throw new Error(`Unknown unary operator: ${node.operator}`);
|
|
86
102
|
}
|
|
87
103
|
}
|
|
104
|
+
/** A function called with its values, each read only when the function asks for it. */
|
|
105
|
+
function evaluateCall(node, context, env) {
|
|
106
|
+
const fn = functions_1.FUNCTIONS.get(node.name);
|
|
107
|
+
if (!fn)
|
|
108
|
+
throw new Error(`Unknown function: ${node.name}`);
|
|
109
|
+
return fn.call({ args: node.args, value: (arg) => evaluate(arg, context, env), env });
|
|
110
|
+
}
|
|
88
111
|
/** A missing value compares like None. */
|
|
89
112
|
function normalize(value) {
|
|
90
113
|
return value === undefined ? null : value;
|
|
91
114
|
}
|
|
92
|
-
/** Python's truth test: empty lists, strings and objects, 0 and None are false. */
|
|
93
|
-
function truthy(value) {
|
|
94
|
-
if (Array.isArray(value))
|
|
95
|
-
return value.length > 0;
|
|
96
|
-
if (value !== null && typeof value === 'object')
|
|
97
|
-
return Object.keys(value).length > 0;
|
|
98
|
-
return Boolean(value);
|
|
99
|
-
}
|
|
100
115
|
/**
|
|
101
116
|
* Get a nested value from an object using dot notation
|
|
102
117
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/evaluator.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AST evaluator for
|
|
2
|
+
* AST evaluator for expressions, ported from the React form engine.
|
|
3
3
|
*
|
|
4
4
|
* Python semantics where the React version used JavaScript's: an empty list
|
|
5
5
|
* or string is false, and an empty value is never smaller or larger than
|
|
6
|
-
* anything — modifiers come from backends that think in Python.
|
|
6
|
+
* anything — modifiers come from backends that think in Python. Arithmetic
|
|
7
|
+
* and functions give null for what they cannot work out, never an error.
|
|
7
8
|
*/
|
|
9
|
+
import { FUNCTIONS } from './functions.mjs';
|
|
10
|
+
import { arithmetic, negate, truthy } from './operations.mjs';
|
|
11
|
+
export { truthy } from './operations.mjs';
|
|
8
12
|
/**
|
|
9
13
|
* Evaluate an AST node against a context object
|
|
10
14
|
*
|
|
@@ -17,12 +21,14 @@
|
|
|
17
21
|
* const context = { state: 'draft', amount: 100 };
|
|
18
22
|
* evaluate(ast, context); // Returns boolean result
|
|
19
23
|
*/
|
|
20
|
-
export function evaluate(ast, context) {
|
|
24
|
+
export function evaluate(ast, context, env = {}) {
|
|
21
25
|
switch (ast.type) {
|
|
22
26
|
case 'BinaryOp':
|
|
23
|
-
return evaluateBinaryOp(ast, context);
|
|
27
|
+
return evaluateBinaryOp(ast, context, env);
|
|
24
28
|
case 'UnaryOp':
|
|
25
|
-
return evaluateUnaryOp(ast, context);
|
|
29
|
+
return evaluateUnaryOp(ast, context, env);
|
|
30
|
+
case 'Call':
|
|
31
|
+
return evaluateCall(ast, context, env);
|
|
26
32
|
case 'Literal':
|
|
27
33
|
return ast.value;
|
|
28
34
|
case 'Identifier':
|
|
@@ -34,9 +40,9 @@ export function evaluate(ast, context) {
|
|
|
34
40
|
/**
|
|
35
41
|
* Evaluate binary operations
|
|
36
42
|
*/
|
|
37
|
-
function evaluateBinaryOp(node, context) {
|
|
38
|
-
const left = normalize(evaluate(node.left, context));
|
|
39
|
-
const right = normalize(evaluate(node.right, context));
|
|
43
|
+
function evaluateBinaryOp(node, context, env) {
|
|
44
|
+
const left = normalize(evaluate(node.left, context, env));
|
|
45
|
+
const right = normalize(evaluate(node.right, context, env));
|
|
40
46
|
const operator = node.operator.toLowerCase();
|
|
41
47
|
const ordered = left !== null && right !== null;
|
|
42
48
|
switch (operator) {
|
|
@@ -59,6 +65,13 @@ function evaluateBinaryOp(node, context) {
|
|
|
59
65
|
return Array.isArray(right) && right.includes(left);
|
|
60
66
|
case 'not in':
|
|
61
67
|
return Array.isArray(right) && !right.includes(left);
|
|
68
|
+
// Arithmetic, and + joining text
|
|
69
|
+
case '+':
|
|
70
|
+
case '-':
|
|
71
|
+
case '*':
|
|
72
|
+
case '/':
|
|
73
|
+
case '%':
|
|
74
|
+
return arithmetic(operator, left, right);
|
|
62
75
|
// Logical operators (use truthiness)
|
|
63
76
|
case 'and':
|
|
64
77
|
return truthy(left) && truthy(right);
|
|
@@ -71,28 +84,29 @@ function evaluateBinaryOp(node, context) {
|
|
|
71
84
|
/**
|
|
72
85
|
* Evaluate unary operations
|
|
73
86
|
*/
|
|
74
|
-
function evaluateUnaryOp(node, context) {
|
|
75
|
-
const operand = evaluate(node.operand, context);
|
|
87
|
+
function evaluateUnaryOp(node, context, env) {
|
|
88
|
+
const operand = evaluate(node.operand, context, env);
|
|
76
89
|
const operator = node.operator.toLowerCase();
|
|
77
90
|
switch (operator) {
|
|
78
91
|
case 'not':
|
|
79
92
|
return !truthy(operand);
|
|
93
|
+
case '-':
|
|
94
|
+
return negate(operand);
|
|
80
95
|
default:
|
|
81
96
|
throw new Error(`Unknown unary operator: ${node.operator}`);
|
|
82
97
|
}
|
|
83
98
|
}
|
|
99
|
+
/** A function called with its values, each read only when the function asks for it. */
|
|
100
|
+
function evaluateCall(node, context, env) {
|
|
101
|
+
const fn = FUNCTIONS.get(node.name);
|
|
102
|
+
if (!fn)
|
|
103
|
+
throw new Error(`Unknown function: ${node.name}`);
|
|
104
|
+
return fn.call({ args: node.args, value: (arg) => evaluate(arg, context, env), env });
|
|
105
|
+
}
|
|
84
106
|
/** A missing value compares like None. */
|
|
85
107
|
function normalize(value) {
|
|
86
108
|
return value === undefined ? null : value;
|
|
87
109
|
}
|
|
88
|
-
/** Python's truth test: empty lists, strings and objects, 0 and None are false. */
|
|
89
|
-
export function truthy(value) {
|
|
90
|
-
if (Array.isArray(value))
|
|
91
|
-
return value.length > 0;
|
|
92
|
-
if (value !== null && typeof value === 'object')
|
|
93
|
-
return Object.keys(value).length > 0;
|
|
94
|
-
return Boolean(value);
|
|
95
|
-
}
|
|
96
110
|
/**
|
|
97
111
|
* Get a nested value from an object using dot notation
|
|
98
112
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/evaluator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"evaluator.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExpressionEnv } from './functions.mjs';
|
|
2
|
+
export type { ExpressionEnv } from './functions.mjs';
|
|
3
|
+
/** An expression read once, ready to work out a value from a record's values many times. */
|
|
4
|
+
export interface CompiledExpression {
|
|
5
|
+
readonly source: string;
|
|
6
|
+
/** The fields it reads, by their first name, in order of appearance. */
|
|
7
|
+
readonly fields: readonly string[];
|
|
8
|
+
/** The line fields it adds up, as `sum(lines, 'subtotal')` names them. */
|
|
9
|
+
readonly sums: readonly {
|
|
10
|
+
lines: string;
|
|
11
|
+
field: string;
|
|
12
|
+
}[];
|
|
13
|
+
/** The value, or null when it cannot be worked out. Never throws. */
|
|
14
|
+
evaluate(values: Readonly<Record<string, unknown>>, env?: ExpressionEnv): unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read an expression for a value, such as `price * qty` or
|
|
18
|
+
* `if(total > 1000, 10, 0)`. It is parsed now, so a mistake surfaces when the
|
|
19
|
+
* page is read and not on the first keystroke: throws when it cannot be read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function compileExpression(source: string): CompiledExpression;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExpressionEnv } from './functions';
|
|
2
|
+
export type { ExpressionEnv } from './functions';
|
|
3
|
+
/** An expression read once, ready to work out a value from a record's values many times. */
|
|
4
|
+
export interface CompiledExpression {
|
|
5
|
+
readonly source: string;
|
|
6
|
+
/** The fields it reads, by their first name, in order of appearance. */
|
|
7
|
+
readonly fields: readonly string[];
|
|
8
|
+
/** The line fields it adds up, as `sum(lines, 'subtotal')` names them. */
|
|
9
|
+
readonly sums: readonly {
|
|
10
|
+
lines: string;
|
|
11
|
+
field: string;
|
|
12
|
+
}[];
|
|
13
|
+
/** The value, or null when it cannot be worked out. Never throws. */
|
|
14
|
+
evaluate(values: Readonly<Record<string, unknown>>, env?: ExpressionEnv): unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read an expression for a value, such as `price * qty` or
|
|
18
|
+
* `if(total > 1000, 10, 0)`. It is parsed now, so a mistake surfaces when the
|
|
19
|
+
* page is read and not on the first keystroke: throws when it cannot be read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function compileExpression(source: string): CompiledExpression;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compileExpression = compileExpression;
|
|
4
|
+
const evaluator_1 = require("./evaluator");
|
|
5
|
+
const parser_1 = require("./parser");
|
|
6
|
+
const reads_1 = require("./reads");
|
|
7
|
+
const tokenizer_1 = require("./tokenizer");
|
|
8
|
+
/**
|
|
9
|
+
* Read an expression for a value, such as `price * qty` or
|
|
10
|
+
* `if(total > 1000, 10, 0)`. It is parsed now, so a mistake surfaces when the
|
|
11
|
+
* page is read and not on the first keystroke: throws when it cannot be read.
|
|
12
|
+
*/
|
|
13
|
+
function compileExpression(source) {
|
|
14
|
+
const ast = new parser_1.ExpressionParser((0, tokenizer_1.tokenize)(source)).parse();
|
|
15
|
+
return {
|
|
16
|
+
source,
|
|
17
|
+
fields: (0, reads_1.fieldsRead)(ast),
|
|
18
|
+
sums: (0, reads_1.sumsRead)(ast),
|
|
19
|
+
evaluate(values, env = {}) {
|
|
20
|
+
try {
|
|
21
|
+
return (0, evaluator_1.evaluate)(ast, values, env) ?? null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=expression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expression.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/expression.ts"],"names":[],"mappings":";;AAwBA,8CAcC;AAtCD,2CAAuC;AAEvC,qCAA4C;AAC5C,mCAA+C;AAC/C,2CAAuC;AAevC;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,MAAc;IAC9C,MAAM,GAAG,GAAG,IAAI,yBAAgB,CAAC,IAAA,oBAAQ,EAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IAC3D,OAAO;QACL,MAAM;QACN,MAAM,EAAE,IAAA,kBAAU,EAAC,GAAG,CAAC;QACvB,IAAI,EAAE,IAAA,gBAAQ,EAAC,GAAG,CAAC;QACnB,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE;YACvB,IAAI,CAAC;gBACH,OAAO,IAAA,oBAAQ,EAAC,GAAG,EAAE,MAAiC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;YACvE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { evaluate } from './evaluator.mjs';
|
|
2
|
+
import { ExpressionParser } from './parser.mjs';
|
|
3
|
+
import { fieldsRead, sumsRead } from './reads.mjs';
|
|
4
|
+
import { tokenize } from './tokenizer.mjs';
|
|
5
|
+
/**
|
|
6
|
+
* Read an expression for a value, such as `price * qty` or
|
|
7
|
+
* `if(total > 1000, 10, 0)`. It is parsed now, so a mistake surfaces when the
|
|
8
|
+
* page is read and not on the first keystroke: throws when it cannot be read.
|
|
9
|
+
*/
|
|
10
|
+
export function compileExpression(source) {
|
|
11
|
+
const ast = new ExpressionParser(tokenize(source)).parse();
|
|
12
|
+
return {
|
|
13
|
+
source,
|
|
14
|
+
fields: fieldsRead(ast),
|
|
15
|
+
sums: sumsRead(ast),
|
|
16
|
+
evaluate(values, env = {}) {
|
|
17
|
+
try {
|
|
18
|
+
return evaluate(ast, values, env) ?? null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=expression.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expression.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/expression.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAevC;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IAC3D,OAAO;QACL,MAAM;QACN,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC;QACvB,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC;QACnB,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE;YACvB,IAAI,CAAC;gBACH,OAAO,QAAQ,CAAC,GAAG,EAAE,MAAiC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;YACvE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -34,12 +34,17 @@ function extractFieldNames(expression) {
|
|
|
34
34
|
const fields = new Set();
|
|
35
35
|
// Keywords that look like identifiers but aren't field names
|
|
36
36
|
const keywords = new Set(['null', 'true', 'false', 'True', 'False', 'None']);
|
|
37
|
-
tokens.forEach(token => {
|
|
37
|
+
tokens.forEach((token, i) => {
|
|
38
38
|
if (token.type === 'IDENTIFIER') {
|
|
39
39
|
// Skip literal keywords
|
|
40
40
|
if (keywords.has(token.value)) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
+
// A name followed by "(" is a function called, not a field: round(x)
|
|
44
|
+
const next = tokens[i + 1];
|
|
45
|
+
if (next?.type === 'PAREN' && next.value === '(') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
// For nested fields like 'partner_id.country_id.name',
|
|
44
49
|
// we only need to watch the root field 'partner_id'
|
|
45
50
|
const rootField = token.value.split('.')[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractFieldNames.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/extractFieldNames.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAUH,
|
|
1
|
+
{"version":3,"file":"extractFieldNames.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/extractFieldNames.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,12 +30,17 @@ export function extractFieldNames(expression) {
|
|
|
30
30
|
const fields = new Set();
|
|
31
31
|
// Keywords that look like identifiers but aren't field names
|
|
32
32
|
const keywords = new Set(['null', 'true', 'false', 'True', 'False', 'None']);
|
|
33
|
-
tokens.forEach(token => {
|
|
33
|
+
tokens.forEach((token, i) => {
|
|
34
34
|
if (token.type === 'IDENTIFIER') {
|
|
35
35
|
// Skip literal keywords
|
|
36
36
|
if (keywords.has(token.value)) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
+
// A name followed by "(" is a function called, not a field: round(x)
|
|
40
|
+
const next = tokens[i + 1];
|
|
41
|
+
if (next?.type === 'PAREN' && next.value === '(') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
39
44
|
// For nested fields like 'partner_id.country_id.name',
|
|
40
45
|
// we only need to watch the root field 'partner_id'
|
|
41
46
|
const rootField = token.value.split('.')[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractFieldNames.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/extractFieldNames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAwC;IACxE,gDAAgD;IAChD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QAEjC,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QAE7E,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;
|
|
1
|
+
{"version":3,"file":"extractFieldNames.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/expression/extractFieldNames.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ASTNode } from './parser.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* The functions an expression may call. Each says how many values it takes,
|
|
4
|
+
* so a call with too many or too few is refused when the page is read; and
|
|
5
|
+
* each gives null rather than failing when what it is handed does not fit.
|
|
6
|
+
*/
|
|
7
|
+
/** What an expression is worked out with, besides the record's values. */
|
|
8
|
+
export interface ExpressionEnv {
|
|
9
|
+
/**
|
|
10
|
+
* The lines of a one2many, as rows of their values, for `sum` and `count`:
|
|
11
|
+
* the values hold only the lines' keys. Sections and notes are left out.
|
|
12
|
+
*/
|
|
13
|
+
lines?(field: string): readonly Readonly<Record<string, unknown>>[] | undefined;
|
|
14
|
+
/** Today, as YYYY-MM-DD. The computer's own clock when left out. */
|
|
15
|
+
today?(): string;
|
|
16
|
+
}
|
|
17
|
+
/** One call, as a function sees it: its values unread, a way to read each, and the env. */
|
|
18
|
+
interface Call {
|
|
19
|
+
args: readonly ASTNode[];
|
|
20
|
+
value(node: ASTNode): unknown;
|
|
21
|
+
env: ExpressionEnv;
|
|
22
|
+
}
|
|
23
|
+
interface ExpressionFunction {
|
|
24
|
+
/** How many values it takes: at least, at most. */
|
|
25
|
+
readonly takes: readonly [number, number];
|
|
26
|
+
/** What is wrong with the values it was given, found as the expression is read. */
|
|
27
|
+
check?(args: readonly ASTNode[]): string | undefined;
|
|
28
|
+
call(call: Call): unknown;
|
|
29
|
+
}
|
|
30
|
+
/** A day as YYYY-MM-DD, in the computer's own time zone. */
|
|
31
|
+
export declare function localDay(date: Date): string;
|
|
32
|
+
export declare const FUNCTIONS: ReadonlyMap<string, ExpressionFunction>;
|
|
33
|
+
/** How many values a function takes, in words: "round takes 1 or 2 values". */
|
|
34
|
+
export declare function takesInWords(name: string, [least, most]: readonly [number, number]): string;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ASTNode } from './parser';
|
|
2
|
+
/**
|
|
3
|
+
* The functions an expression may call. Each says how many values it takes,
|
|
4
|
+
* so a call with too many or too few is refused when the page is read; and
|
|
5
|
+
* each gives null rather than failing when what it is handed does not fit.
|
|
6
|
+
*/
|
|
7
|
+
/** What an expression is worked out with, besides the record's values. */
|
|
8
|
+
export interface ExpressionEnv {
|
|
9
|
+
/**
|
|
10
|
+
* The lines of a one2many, as rows of their values, for `sum` and `count`:
|
|
11
|
+
* the values hold only the lines' keys. Sections and notes are left out.
|
|
12
|
+
*/
|
|
13
|
+
lines?(field: string): readonly Readonly<Record<string, unknown>>[] | undefined;
|
|
14
|
+
/** Today, as YYYY-MM-DD. The computer's own clock when left out. */
|
|
15
|
+
today?(): string;
|
|
16
|
+
}
|
|
17
|
+
/** One call, as a function sees it: its values unread, a way to read each, and the env. */
|
|
18
|
+
interface Call {
|
|
19
|
+
args: readonly ASTNode[];
|
|
20
|
+
value(node: ASTNode): unknown;
|
|
21
|
+
env: ExpressionEnv;
|
|
22
|
+
}
|
|
23
|
+
interface ExpressionFunction {
|
|
24
|
+
/** How many values it takes: at least, at most. */
|
|
25
|
+
readonly takes: readonly [number, number];
|
|
26
|
+
/** What is wrong with the values it was given, found as the expression is read. */
|
|
27
|
+
check?(args: readonly ASTNode[]): string | undefined;
|
|
28
|
+
call(call: Call): unknown;
|
|
29
|
+
}
|
|
30
|
+
/** A day as YYYY-MM-DD, in the computer's own time zone. */
|
|
31
|
+
export declare function localDay(date: Date): string;
|
|
32
|
+
export declare const FUNCTIONS: ReadonlyMap<string, ExpressionFunction>;
|
|
33
|
+
/** How many values a function takes, in words: "round takes 1 or 2 values". */
|
|
34
|
+
export declare function takesInWords(name: string, [least, most]: readonly [number, number]): string;
|
|
35
|
+
export {};
|