@player-ui/player 0.3.0-next.2 → 0.3.0-next.4
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/dist/index.cjs.js +4128 -891
- package/dist/index.d.ts +1227 -50
- package/dist/index.esm.js +4065 -836
- package/package.json +9 -15
- package/src/binding/binding.ts +108 -0
- package/src/binding/index.ts +188 -0
- package/src/binding/resolver.ts +157 -0
- package/src/binding/utils.ts +51 -0
- package/src/binding-grammar/ast.ts +113 -0
- package/src/binding-grammar/custom/index.ts +304 -0
- package/src/binding-grammar/ebnf/binding.ebnf +22 -0
- package/src/binding-grammar/ebnf/index.ts +186 -0
- package/src/binding-grammar/ebnf/types.ts +104 -0
- package/src/binding-grammar/index.ts +4 -0
- package/src/binding-grammar/parsimmon/index.ts +78 -0
- package/src/controllers/constants/index.ts +85 -0
- package/src/controllers/constants/utils.ts +37 -0
- package/src/{data.ts → controllers/data.ts} +6 -6
- package/src/controllers/flow/controller.ts +95 -0
- package/src/controllers/flow/flow.ts +205 -0
- package/src/controllers/flow/index.ts +2 -0
- package/src/controllers/index.ts +5 -0
- package/src/{validation → controllers/validation}/binding-tracker.ts +5 -5
- package/src/{validation → controllers/validation}/controller.ts +15 -14
- package/src/{validation → controllers/validation}/index.ts +0 -0
- package/src/{view → controllers/view}/asset-transform.ts +2 -3
- package/src/{view → controllers/view}/controller.ts +9 -8
- package/src/controllers/view/index.ts +4 -0
- package/src/{view → controllers/view}/store.ts +0 -0
- package/src/{view → controllers/view}/types.ts +2 -1
- package/src/data/dependency-tracker.ts +187 -0
- package/src/data/index.ts +4 -0
- package/src/data/local-model.ts +41 -0
- package/src/data/model.ts +216 -0
- package/src/data/noop-model.ts +18 -0
- package/src/expressions/evaluator-functions.ts +29 -0
- package/src/expressions/evaluator.ts +405 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.ts +889 -0
- package/src/expressions/types.ts +200 -0
- package/src/expressions/utils.ts +8 -0
- package/src/index.ts +9 -12
- package/src/logger/consoleLogger.ts +49 -0
- package/src/logger/index.ts +5 -0
- package/src/logger/noopLogger.ts +13 -0
- package/src/logger/proxyLogger.ts +25 -0
- package/src/logger/tapableLogger.ts +38 -0
- package/src/logger/types.ts +6 -0
- package/src/player.ts +21 -18
- package/src/plugins/flow-exp-plugin.ts +2 -3
- package/src/schema/index.ts +2 -0
- package/src/schema/schema.ts +220 -0
- package/src/schema/types.ts +60 -0
- package/src/string-resolver/index.ts +188 -0
- package/src/types.ts +11 -13
- package/src/utils/index.ts +1 -0
- package/src/utils/replaceParams.ts +17 -0
- package/src/validator/index.ts +3 -0
- package/src/validator/registry.ts +20 -0
- package/src/validator/types.ts +75 -0
- package/src/validator/validation-middleware.ts +114 -0
- package/src/view/builder/index.ts +81 -0
- package/src/view/index.ts +5 -4
- package/src/view/parser/index.ts +318 -0
- package/src/view/parser/types.ts +141 -0
- package/src/view/plugins/applicability.ts +78 -0
- package/src/view/plugins/index.ts +5 -0
- package/src/view/plugins/options.ts +4 -0
- package/src/view/plugins/plugin.ts +21 -0
- package/src/view/plugins/string-resolver.ts +149 -0
- package/src/view/plugins/switch.ts +120 -0
- package/src/view/plugins/template-plugin.ts +172 -0
- package/src/view/resolver/index.ts +397 -0
- package/src/view/resolver/types.ts +161 -0
- package/src/view/resolver/utils.ts +57 -0
- package/src/view/view.ts +149 -0
- package/src/utils/desc.d.ts +0 -2
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { SyncWaterfallHook, SyncBailHook } from 'tapable-ts';
|
|
2
|
+
import parse from './parser';
|
|
3
|
+
import * as DEFAULT_EXPRESSION_HANDLERS from './evaluator-functions';
|
|
4
|
+
import type {
|
|
5
|
+
ExpressionNode,
|
|
6
|
+
BinaryOperator,
|
|
7
|
+
UnaryOperator,
|
|
8
|
+
ExpressionType,
|
|
9
|
+
ExpressionContext,
|
|
10
|
+
ExpressionHandler,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { isExpressionNode } from '.';
|
|
13
|
+
|
|
14
|
+
/** a && b -- but handles short cutting if the first value is false */
|
|
15
|
+
const andandOperator: BinaryOperator = (ctx, a, b) => {
|
|
16
|
+
return ctx.evaluate(a) && ctx.evaluate(b);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
andandOperator.resolveParams = false;
|
|
20
|
+
|
|
21
|
+
/** a || b -- but with short cutting if first value is true */
|
|
22
|
+
const ororOperator: BinaryOperator = (ctx, a, b) => {
|
|
23
|
+
return ctx.evaluate(a) || ctx.evaluate(b);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
ororOperator.resolveParams = false;
|
|
27
|
+
|
|
28
|
+
const DEFAULT_BINARY_OPERATORS: Record<string, BinaryOperator> = {
|
|
29
|
+
// TODO: A lot of these functions used to do type coercion. Not sure if we want to keep that behavior or not.
|
|
30
|
+
'+': (a: any, b: any) => a + b,
|
|
31
|
+
'-': (a: any, b: any) => a - b,
|
|
32
|
+
'*': (a: any, b: any) => a * b,
|
|
33
|
+
'/': (a: any, b: any) => a / b,
|
|
34
|
+
'%': (a: any, b: any) => a % b,
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line
|
|
37
|
+
'==': (a: any, b: any) => a == b,
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
'!=': (a: any, b: any) => a != b,
|
|
41
|
+
'>': (a: any, b: any) => a > b,
|
|
42
|
+
'>=': (a: any, b: any) => a >= b,
|
|
43
|
+
'<': (a: any, b: any) => a < b,
|
|
44
|
+
'<=': (a: any, b: any) => a <= b,
|
|
45
|
+
'&&': andandOperator,
|
|
46
|
+
'||': ororOperator,
|
|
47
|
+
'!==': (a: any, b: any) => a !== b,
|
|
48
|
+
'===': (a: any, b: any) => a === b,
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line
|
|
51
|
+
'|': (a: any, b: any) => a | b,
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line
|
|
54
|
+
'&': (a: any, b: any) => a & b,
|
|
55
|
+
'+=': (a: any, b: any) => a + b,
|
|
56
|
+
'-=': (a: any, b: any) => a - b,
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line
|
|
59
|
+
'&=': (a: any, b: any) => a & b,
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line
|
|
62
|
+
'|=': (a: any, b: any) => a | b,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const DEFAULT_UNARY_OPERATORS: Record<string, UnaryOperator> = {
|
|
66
|
+
'-': (a: any) => -a,
|
|
67
|
+
'+': (a: any) => Number(a),
|
|
68
|
+
'!': (a: any) => !a,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export interface HookOptions extends ExpressionContext {
|
|
72
|
+
/** Given an expression node */
|
|
73
|
+
resolveNode: (node: ExpressionNode) => any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ExpressionEvaluatorOptions = Omit<
|
|
77
|
+
HookOptions,
|
|
78
|
+
'resolveNode' | 'evaluate'
|
|
79
|
+
>;
|
|
80
|
+
|
|
81
|
+
export type ExpressionEvaluatorFunction = (
|
|
82
|
+
exp: ExpressionType,
|
|
83
|
+
options?: ExpressionEvaluatorOptions
|
|
84
|
+
) => any;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The expression evaluator is responsible for parsing and executing anything in the custom expression language
|
|
88
|
+
* */
|
|
89
|
+
export class ExpressionEvaluator {
|
|
90
|
+
private readonly vars: Record<string, any> = {};
|
|
91
|
+
public readonly hooks = {
|
|
92
|
+
/** Resolve an AST node for an expression to a value */
|
|
93
|
+
resolve: new SyncWaterfallHook<[any, ExpressionNode, HookOptions]>(),
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* An optional means of handling an error in the expression execution
|
|
97
|
+
* Return true if handled, to stop propagation of the error
|
|
98
|
+
*/
|
|
99
|
+
onError: new SyncBailHook<[Error], true>(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private readonly expressionsCache: Map<string, ExpressionNode> = new Map();
|
|
103
|
+
|
|
104
|
+
private readonly defaultHookOptions: HookOptions;
|
|
105
|
+
|
|
106
|
+
public readonly operators = {
|
|
107
|
+
binary: new Map(Object.entries(DEFAULT_BINARY_OPERATORS)),
|
|
108
|
+
unary: new Map(Object.entries(DEFAULT_UNARY_OPERATORS)),
|
|
109
|
+
expressions: new Map<string, ExpressionHandler<any, any>>(
|
|
110
|
+
Object.entries(DEFAULT_EXPRESSION_HANDLERS)
|
|
111
|
+
),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
public reset(): void {
|
|
115
|
+
this.expressionsCache.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
constructor(defaultOptions: ExpressionEvaluatorOptions) {
|
|
119
|
+
this.defaultHookOptions = {
|
|
120
|
+
...defaultOptions,
|
|
121
|
+
evaluate: (expr) => this.evaluate(expr, this.defaultHookOptions),
|
|
122
|
+
resolveNode: (node: ExpressionNode) =>
|
|
123
|
+
this._execAST(node, this.defaultHookOptions),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
this.hooks.resolve.tap('ExpressionEvaluator', this._resolveNode.bind(this));
|
|
127
|
+
this.evaluate = this.evaluate.bind(this);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public evaluate(
|
|
131
|
+
expression: ExpressionType,
|
|
132
|
+
options?: ExpressionEvaluatorOptions
|
|
133
|
+
): any {
|
|
134
|
+
const opts = {
|
|
135
|
+
...this.defaultHookOptions,
|
|
136
|
+
...options,
|
|
137
|
+
resolveNode: (node: ExpressionNode) => this._execAST(node, opts),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Check for literals
|
|
141
|
+
if (
|
|
142
|
+
typeof expression === 'number' ||
|
|
143
|
+
typeof expression === 'boolean' ||
|
|
144
|
+
expression === undefined ||
|
|
145
|
+
expression === null
|
|
146
|
+
) {
|
|
147
|
+
return expression;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Skip doing anything with objects that are _actually_ just parsed expression nodes
|
|
151
|
+
if (isExpressionNode(expression)) {
|
|
152
|
+
return this._execAST(expression, opts);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (typeof expression === 'object') {
|
|
156
|
+
const values = Array.isArray(expression)
|
|
157
|
+
? expression
|
|
158
|
+
: Object.values(expression);
|
|
159
|
+
|
|
160
|
+
return values.reduce(
|
|
161
|
+
(_nothing, exp) => this.evaluate(exp, options),
|
|
162
|
+
null
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this._execString(String(expression), opts);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public addExpressionFunction<T extends readonly unknown[], R>(
|
|
170
|
+
name: string,
|
|
171
|
+
handler: ExpressionHandler<T, R>
|
|
172
|
+
): void {
|
|
173
|
+
this.operators.expressions.set(name, handler);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public addBinaryOperator(operator: string, handler: BinaryOperator) {
|
|
177
|
+
this.operators.binary.set(operator, handler);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public addUnaryOperator(operator: string, handler: UnaryOperator) {
|
|
181
|
+
this.operators.unary.set(operator, handler);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public setExpressionVariable(name: string, value: unknown) {
|
|
185
|
+
this.vars[name] = value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public getExpressionVariable(name: string): unknown {
|
|
189
|
+
return this.vars[name];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private _execAST(node: ExpressionNode, options: HookOptions): any {
|
|
193
|
+
return this.hooks.resolve.call(undefined, node, options);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private _execString(exp: string, options: HookOptions) {
|
|
197
|
+
if (exp === '') {
|
|
198
|
+
return exp;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const matches = exp.match(/^@\[(.*)\]@$/);
|
|
202
|
+
let matchedExp = exp;
|
|
203
|
+
|
|
204
|
+
if (matches) {
|
|
205
|
+
[, matchedExp] = Array.from(matches); // In case the expression was surrounded by @[ ]@
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const storedAST = this.expressionsCache.get(matchedExp);
|
|
210
|
+
|
|
211
|
+
if (storedAST) {
|
|
212
|
+
return this._execAST(storedAST, options);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const expAST = parse(matchedExp);
|
|
216
|
+
this.expressionsCache.set(matchedExp, expAST);
|
|
217
|
+
|
|
218
|
+
return this._execAST(expAST, options);
|
|
219
|
+
} catch (e: any) {
|
|
220
|
+
if (!this.hooks.onError.call(e)) {
|
|
221
|
+
// Only throw the error if it's not handled by the hook
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private _resolveNode(
|
|
228
|
+
_currentValue: any,
|
|
229
|
+
node: ExpressionNode,
|
|
230
|
+
options: HookOptions
|
|
231
|
+
) {
|
|
232
|
+
const { resolveNode, model } = options;
|
|
233
|
+
|
|
234
|
+
const expressionContext: ExpressionContext = {
|
|
235
|
+
...options,
|
|
236
|
+
evaluate: (expr) => this.evaluate(expr, options),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (node.type === 'Literal') {
|
|
240
|
+
return node.value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (node.type === 'Identifier') {
|
|
244
|
+
return this.vars[node.name];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (node.type === 'Compound' || node.type === 'ThisExpression') {
|
|
248
|
+
throw new Error(`Expression type: ${node.type} is not supported`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
|
|
252
|
+
const operator = this.operators.binary.get(node.operator);
|
|
253
|
+
|
|
254
|
+
if (operator) {
|
|
255
|
+
if ('resolveParams' in operator) {
|
|
256
|
+
if (operator.resolveParams === false) {
|
|
257
|
+
return operator(expressionContext, node.left, node.right);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return operator(
|
|
261
|
+
expressionContext,
|
|
262
|
+
resolveNode(node.left),
|
|
263
|
+
resolveNode(node.right)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return operator(resolveNode(node.left), resolveNode(node.right));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (node.type === 'UnaryExpression') {
|
|
274
|
+
const operator = this.operators.unary.get(node.operator);
|
|
275
|
+
|
|
276
|
+
if (operator) {
|
|
277
|
+
if ('resolveParams' in operator) {
|
|
278
|
+
return operator(
|
|
279
|
+
expressionContext,
|
|
280
|
+
operator.resolveParams === false
|
|
281
|
+
? node.argument
|
|
282
|
+
: resolveNode(node.argument)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return operator(resolveNode(node.argument));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (node.type === 'Object') {
|
|
293
|
+
const { attributes } = node;
|
|
294
|
+
const resolvedAttributes: any = {};
|
|
295
|
+
|
|
296
|
+
attributes.forEach((attr) => {
|
|
297
|
+
const key = resolveNode(attr.key);
|
|
298
|
+
const value = resolveNode(attr.value);
|
|
299
|
+
resolvedAttributes[key] = value;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return resolvedAttributes;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (node.type === 'CallExpression') {
|
|
306
|
+
const expressionName = node.callTarget.name;
|
|
307
|
+
|
|
308
|
+
// Treat the conditional operator as special.
|
|
309
|
+
// Don't exec the arguments that don't apply
|
|
310
|
+
if (expressionName === 'conditional') {
|
|
311
|
+
const condition = resolveNode(node.args[0]);
|
|
312
|
+
|
|
313
|
+
if (condition) {
|
|
314
|
+
return resolveNode(node.args[1]);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (node.args[2]) {
|
|
318
|
+
return resolveNode(node.args[2]);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const operator = this.operators.expressions.get(expressionName);
|
|
325
|
+
|
|
326
|
+
if (!operator) {
|
|
327
|
+
throw new Error(`Unknown expression function: ${expressionName}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const args = node.args.map((n) => resolveNode(n));
|
|
331
|
+
|
|
332
|
+
return operator(expressionContext, ...args);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (node.type === 'ModelRef') {
|
|
336
|
+
return model.get(node.ref);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (node.type === 'MemberExpression') {
|
|
340
|
+
const obj = resolveNode(node.object);
|
|
341
|
+
const prop = resolveNode(node.property);
|
|
342
|
+
|
|
343
|
+
return obj[prop];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (node.type === 'Assignment') {
|
|
347
|
+
if (node.left.type === 'ModelRef') {
|
|
348
|
+
const value = resolveNode(node.right);
|
|
349
|
+
model.set([[node.left.ref, value]]);
|
|
350
|
+
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (node.left.type === 'Identifier') {
|
|
355
|
+
const value = resolveNode(node.right);
|
|
356
|
+
this.vars[node.left.name] = value;
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (node.type === 'ConditionalExpression') {
|
|
364
|
+
const result = resolveNode(node.test) ? node.consequent : node.alternate;
|
|
365
|
+
|
|
366
|
+
return resolveNode(result);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (node.type === 'ArrayExpression') {
|
|
370
|
+
return node.elements.map((ele) => resolveNode(ele));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (node.type === 'Modification') {
|
|
374
|
+
const operation = this.operators.binary.get(node.operator);
|
|
375
|
+
|
|
376
|
+
if (operation) {
|
|
377
|
+
let newValue;
|
|
378
|
+
|
|
379
|
+
if ('resolveParams' in operation) {
|
|
380
|
+
if (operation.resolveParams === false) {
|
|
381
|
+
newValue = operation(expressionContext, node.left, node.right);
|
|
382
|
+
} else {
|
|
383
|
+
newValue = operation(
|
|
384
|
+
expressionContext,
|
|
385
|
+
resolveNode(node.left),
|
|
386
|
+
resolveNode(node.right)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
newValue = operation(resolveNode(node.left), resolveNode(node.right));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (node.left.type === 'ModelRef') {
|
|
394
|
+
model.set([[node.left.ref, newValue]]);
|
|
395
|
+
} else if (node.left.type === 'Identifier') {
|
|
396
|
+
this.vars[node.left.name] = newValue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return newValue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return resolveNode(node.left);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|