@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,304 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Parser,
|
|
3
|
+
AnyNode,
|
|
4
|
+
PathNode,
|
|
5
|
+
ConcatenatedNode,
|
|
6
|
+
ValueNode,
|
|
7
|
+
QueryNode,
|
|
8
|
+
ExpressionNode,
|
|
9
|
+
} from '../ast';
|
|
10
|
+
import {
|
|
11
|
+
toValue,
|
|
12
|
+
toPath,
|
|
13
|
+
toConcatenatedNode,
|
|
14
|
+
toQuery,
|
|
15
|
+
toExpression,
|
|
16
|
+
} from '../ast';
|
|
17
|
+
|
|
18
|
+
const SEGMENT_SEPARATOR = '.';
|
|
19
|
+
const OPEN_CURL = '{';
|
|
20
|
+
const CLOSE_CURL = '}';
|
|
21
|
+
const OPEN_BRACKET = '[';
|
|
22
|
+
const CLOSE_BRACKET = ']';
|
|
23
|
+
const EQUALS = '=';
|
|
24
|
+
const SINGLE_QUOTE = "'";
|
|
25
|
+
const DOUBLE_QUOTE = '"';
|
|
26
|
+
const BACK_TICK = '`';
|
|
27
|
+
// const IDENTIFIER_REGEX = /[\w\-@]+/;
|
|
28
|
+
|
|
29
|
+
/** A _faster_ way to match chars instead of a regex (/[\w\-@]+/) */
|
|
30
|
+
const isIdentifierChar = (char?: string): boolean => {
|
|
31
|
+
if (!char) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const charCode = char.charCodeAt(0);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
(charCode >= 48 && charCode <= 57) || // 0 - 9
|
|
39
|
+
(charCode >= 65 && charCode <= 90) || // A-Z
|
|
40
|
+
(charCode >= 97 && charCode <= 122) || // a-z
|
|
41
|
+
charCode === 95 || // _
|
|
42
|
+
charCode === 45 || // -
|
|
43
|
+
charCode === 64 // @
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Parse out a binding AST from a path */
|
|
48
|
+
export const parse: Parser = (path) => {
|
|
49
|
+
let index = 1;
|
|
50
|
+
let ch = path.charAt(0);
|
|
51
|
+
|
|
52
|
+
/** get the next char in the string */
|
|
53
|
+
const next = (expected?: string) => {
|
|
54
|
+
if (expected && ch !== expected) {
|
|
55
|
+
throw new Error(`Expected char: ${expected} but got: ${ch}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ch = path.charAt(index);
|
|
59
|
+
index += 1;
|
|
60
|
+
// console.log(`Index: ${index} Char: ${ch}`);
|
|
61
|
+
return ch;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** gobble all whitespace */
|
|
65
|
+
const whitespace = () => {
|
|
66
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
67
|
+
while (ch === ' ') {
|
|
68
|
+
next();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** get an identifier if you can */
|
|
73
|
+
const identifier = (): ValueNode | undefined => {
|
|
74
|
+
if (!isIdentifierChar(ch)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let value = ch;
|
|
79
|
+
|
|
80
|
+
while (next()) {
|
|
81
|
+
if (!isIdentifierChar(ch)) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
value += ch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (value) {
|
|
89
|
+
return toValue(value);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** get an expression node if you can */
|
|
94
|
+
const expression = (): ExpressionNode | undefined => {
|
|
95
|
+
if (ch === BACK_TICK) {
|
|
96
|
+
next(BACK_TICK);
|
|
97
|
+
|
|
98
|
+
let exp = ch;
|
|
99
|
+
|
|
100
|
+
while (next()) {
|
|
101
|
+
if (ch === BACK_TICK) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exp += ch;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
next(BACK_TICK);
|
|
109
|
+
|
|
110
|
+
if (exp) {
|
|
111
|
+
return toExpression(exp);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Grab a value using a regex */
|
|
117
|
+
const regex = (match: RegExp): ValueNode | undefined => {
|
|
118
|
+
if (!ch?.match(match)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let value = ch;
|
|
123
|
+
|
|
124
|
+
while (next()) {
|
|
125
|
+
if (!ch?.match(match)) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
value += ch;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (value) {
|
|
133
|
+
return toValue(value);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** parse out a nestedPath if you can */
|
|
138
|
+
const nestedPath = (): PathNode | undefined => {
|
|
139
|
+
if (ch === OPEN_CURL) {
|
|
140
|
+
next(OPEN_CURL);
|
|
141
|
+
if (ch === OPEN_CURL) {
|
|
142
|
+
next(OPEN_CURL);
|
|
143
|
+
|
|
144
|
+
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
|
145
|
+
const modelRef = parsePath();
|
|
146
|
+
next(CLOSE_CURL);
|
|
147
|
+
next(CLOSE_CURL);
|
|
148
|
+
return modelRef;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** get a simple segment node */
|
|
154
|
+
const simpleSegment = () => nestedPath() ?? expression() ?? identifier();
|
|
155
|
+
|
|
156
|
+
/** Parse a segment */
|
|
157
|
+
const segment = ():
|
|
158
|
+
| ConcatenatedNode
|
|
159
|
+
| PathNode
|
|
160
|
+
| ValueNode
|
|
161
|
+
| ExpressionNode
|
|
162
|
+
| undefined => {
|
|
163
|
+
// Either a string, modelRef, or concatenated version (both)
|
|
164
|
+
const segments: Array<ValueNode | PathNode | ExpressionNode> = [];
|
|
165
|
+
let nextSegment = simpleSegment();
|
|
166
|
+
|
|
167
|
+
while (nextSegment !== undefined) {
|
|
168
|
+
segments.push(nextSegment);
|
|
169
|
+
nextSegment = simpleSegment();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (segments.length === 0) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return toConcatenatedNode(segments);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** get an optionally quoted block */
|
|
180
|
+
const optionallyQuotedSegment = ():
|
|
181
|
+
| ValueNode
|
|
182
|
+
| PathNode
|
|
183
|
+
| ExpressionNode
|
|
184
|
+
| undefined => {
|
|
185
|
+
whitespace();
|
|
186
|
+
|
|
187
|
+
// see if we have a quote
|
|
188
|
+
|
|
189
|
+
if (ch === SINGLE_QUOTE || ch === DOUBLE_QUOTE) {
|
|
190
|
+
const singleQuote = ch === SINGLE_QUOTE;
|
|
191
|
+
next(singleQuote ? SINGLE_QUOTE : DOUBLE_QUOTE);
|
|
192
|
+
const id = regex(/[^'"]+/);
|
|
193
|
+
next(singleQuote ? SINGLE_QUOTE : DOUBLE_QUOTE);
|
|
194
|
+
return id;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return simpleSegment();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** eat equals signs */
|
|
201
|
+
const equals = (): boolean => {
|
|
202
|
+
if (ch !== EQUALS) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
while (ch === EQUALS) {
|
|
207
|
+
next();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return true;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/** Parse out a bracket */
|
|
214
|
+
const parseBracket = ():
|
|
215
|
+
| ValueNode
|
|
216
|
+
| QueryNode
|
|
217
|
+
| PathNode
|
|
218
|
+
| ExpressionNode
|
|
219
|
+
| undefined => {
|
|
220
|
+
if (ch === OPEN_BRACKET) {
|
|
221
|
+
next(OPEN_BRACKET);
|
|
222
|
+
whitespace();
|
|
223
|
+
let value: ValueNode | QueryNode | PathNode | ExpressionNode | undefined =
|
|
224
|
+
optionallyQuotedSegment();
|
|
225
|
+
if (value) {
|
|
226
|
+
whitespace();
|
|
227
|
+
if (equals()) {
|
|
228
|
+
whitespace();
|
|
229
|
+
const second = optionallyQuotedSegment();
|
|
230
|
+
value = toQuery(value, second);
|
|
231
|
+
whitespace();
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
throw new Error(`Expected identifier`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (value) {
|
|
238
|
+
next(CLOSE_BRACKET);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/** Parse a segment and any number of brackets following it */
|
|
246
|
+
const parseSegmentAndBrackets = (): Array<AnyNode> => {
|
|
247
|
+
// try to parse a segment first
|
|
248
|
+
|
|
249
|
+
const parsed = [];
|
|
250
|
+
|
|
251
|
+
const firstSegment = segment();
|
|
252
|
+
|
|
253
|
+
if (firstSegment) {
|
|
254
|
+
parsed.push(firstSegment);
|
|
255
|
+
|
|
256
|
+
let bracketSegment = parseBracket();
|
|
257
|
+
|
|
258
|
+
while (bracketSegment !== undefined) {
|
|
259
|
+
parsed.push(bracketSegment);
|
|
260
|
+
bracketSegment = parseBracket();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return parsed;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/** Parse out a path segment */
|
|
268
|
+
const parsePath = (): PathNode => {
|
|
269
|
+
const parts: AnyNode[] = [];
|
|
270
|
+
|
|
271
|
+
let nextSegment = parseSegmentAndBrackets();
|
|
272
|
+
|
|
273
|
+
while (nextSegment !== undefined) {
|
|
274
|
+
parts.push(...nextSegment);
|
|
275
|
+
|
|
276
|
+
if (!ch || ch === CLOSE_CURL) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (nextSegment.length === 0 && ch) {
|
|
281
|
+
throw new Error(`Unexpected character: ${ch}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
next(SEGMENT_SEPARATOR);
|
|
285
|
+
nextSegment = parseSegmentAndBrackets();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return toPath(parts);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const result = parsePath();
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
status: true,
|
|
296
|
+
path: result,
|
|
297
|
+
};
|
|
298
|
+
} catch (e: any) {
|
|
299
|
+
return {
|
|
300
|
+
status: false,
|
|
301
|
+
error: e.message,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
value ::= segment_and_bracket (SEGMENT_SEPARATOR segment_and_bracket)*
|
|
2
|
+
segment ::= concatenated | expression | modelRef | identifier
|
|
3
|
+
concatenated ::= (expression | modelRef | identifier)+
|
|
4
|
+
modelRef ::= OPEN_CURL OPEN_CURL value CLOSE_CURL CLOSE_CURL
|
|
5
|
+
identifier ::= [\\w\\-@]+
|
|
6
|
+
query ::= WHITESPACE* optionally_quoted_segment WHITESPACE* EQUALS EQUALS? EQUALS? WHITESPACE* optionally_quoted_segment WHITESPACE*
|
|
7
|
+
brackets ::= OPEN_BRACKET WHITESPACE* (query | optionally_quoted_segment) WHITESPACE* CLOSE_BRACKET
|
|
8
|
+
segment_and_bracket ::= segment brackets*
|
|
9
|
+
quoted_value ::= [^"']*
|
|
10
|
+
optionally_quoted_segment ::= WHITESPACE* SINGLE_QUOTE quoted_value SINGLE_QUOTE WHITESPACE* | WHITESPACE* DOUBLE_QUOTE quoted_value DOUBLE_QUOTE WHITESPACE* | WHITESPACE* segment WHITESPACE*
|
|
11
|
+
expression_value ::= [^`]*
|
|
12
|
+
expression ::= BACK_TICK expression_value BACK_TICK
|
|
13
|
+
EQUALS ::= "="
|
|
14
|
+
SEGMENT_SEPARATOR ::= "."
|
|
15
|
+
SINGLE_QUOTE ::= "'"
|
|
16
|
+
DOUBLE_QUOTE ::= '"'
|
|
17
|
+
WHITESPACE ::= " "
|
|
18
|
+
OPEN_CURL ::= "{"
|
|
19
|
+
CLOSE_CURL ::= "}"
|
|
20
|
+
OPEN_BRACKET ::= "["
|
|
21
|
+
CLOSE_BRACKET ::= "]"
|
|
22
|
+
BACK_TICK ::= "`"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
+
import { Grammars } from 'ebnf';
|
|
3
|
+
import type {
|
|
4
|
+
Parser,
|
|
5
|
+
AnyNode,
|
|
6
|
+
PathNode,
|
|
7
|
+
ValueNode,
|
|
8
|
+
ConcatenatedNode,
|
|
9
|
+
QueryNode,
|
|
10
|
+
ExpressionNode,
|
|
11
|
+
} from '../ast';
|
|
12
|
+
import {
|
|
13
|
+
toValue,
|
|
14
|
+
toQuery,
|
|
15
|
+
toPath,
|
|
16
|
+
toConcatenatedNode,
|
|
17
|
+
toExpression,
|
|
18
|
+
} from '../ast';
|
|
19
|
+
import type {
|
|
20
|
+
ValueToken,
|
|
21
|
+
ModelRefToken,
|
|
22
|
+
IdentifierToken,
|
|
23
|
+
ConcatenatedToken,
|
|
24
|
+
Token,
|
|
25
|
+
OptionallyQuotedSegment,
|
|
26
|
+
QueryToken,
|
|
27
|
+
QuotedValueToken,
|
|
28
|
+
ExpressionToken,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
const parser = new Grammars.W3C.Parser(`
|
|
32
|
+
value ::= segment_and_bracket (SEGMENT_SEPARATOR segment_and_bracket)*
|
|
33
|
+
segment ::= concatenated | expression | modelRef | identifier
|
|
34
|
+
concatenated ::= (expression | modelRef | identifier)+
|
|
35
|
+
modelRef ::= OPEN_CURL OPEN_CURL value CLOSE_CURL CLOSE_CURL
|
|
36
|
+
identifier ::= [\\w\\-@]+
|
|
37
|
+
query ::= WHITESPACE* optionally_quoted_segment WHITESPACE* EQUALS EQUALS? EQUALS? WHITESPACE* optionally_quoted_segment WHITESPACE*
|
|
38
|
+
brackets ::= OPEN_BRACKET WHITESPACE* (query | optionally_quoted_segment) WHITESPACE* CLOSE_BRACKET
|
|
39
|
+
segment_and_bracket ::= segment brackets*
|
|
40
|
+
quoted_value ::= [^"']*
|
|
41
|
+
optionally_quoted_segment ::= WHITESPACE* SINGLE_QUOTE quoted_value SINGLE_QUOTE WHITESPACE* | WHITESPACE* DOUBLE_QUOTE quoted_value DOUBLE_QUOTE WHITESPACE* | WHITESPACE* segment WHITESPACE*
|
|
42
|
+
expression_value ::= [^\`]*
|
|
43
|
+
expression ::= BACK_TICK expression_value BACK_TICK
|
|
44
|
+
|
|
45
|
+
EQUALS ::= "="
|
|
46
|
+
SEGMENT_SEPARATOR ::= "."
|
|
47
|
+
SINGLE_QUOTE ::= "'"
|
|
48
|
+
DOUBLE_QUOTE ::= '"'
|
|
49
|
+
WHITESPACE ::= " "
|
|
50
|
+
OPEN_CURL ::= "{"
|
|
51
|
+
CLOSE_CURL ::= "}"
|
|
52
|
+
OPEN_BRACKET ::= "["
|
|
53
|
+
CLOSE_BRACKET ::= "]"
|
|
54
|
+
BACK_TICK ::= "\`"
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
/** Map an identifier token to a value */
|
|
58
|
+
function convertIdentifierToken(token: IdentifierToken): ValueNode {
|
|
59
|
+
return toValue(token.text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Concert an expression token into a node */
|
|
63
|
+
function convertExpressionToken(token: ExpressionToken): ExpressionNode {
|
|
64
|
+
return toExpression(token.children[0].text);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** map a concatenated token to a node */
|
|
68
|
+
function convertConcatenatedToken(
|
|
69
|
+
token: ConcatenatedToken
|
|
70
|
+
): ConcatenatedNode | ValueNode | PathNode | ExpressionNode {
|
|
71
|
+
return toConcatenatedNode(
|
|
72
|
+
token.children.map((child) => {
|
|
73
|
+
if (child.type === 'identifier') {
|
|
74
|
+
return convertIdentifierToken(child);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (child.type === 'expression') {
|
|
78
|
+
return convertExpressionToken(child);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return convertModelRefToken(child);
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** map a quoted value token to a value node */
|
|
87
|
+
function convertQuotedValueToken(token: QuotedValueToken): ValueNode {
|
|
88
|
+
return toValue(token.text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** map a quoted value token to a value node */
|
|
92
|
+
function convertOptionallyQuotedToken(
|
|
93
|
+
token: OptionallyQuotedSegment
|
|
94
|
+
): ValueNode | ConcatenatedNode | PathNode | ExpressionNode {
|
|
95
|
+
const child = token.children[0];
|
|
96
|
+
if (child.type === 'quoted_value') {
|
|
97
|
+
return convertQuotedValueToken(child);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const grandChild = child.children[0];
|
|
101
|
+
if (grandChild.type === 'identifier') {
|
|
102
|
+
return convertIdentifierToken(grandChild);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return convertConcatenatedToken(grandChild);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** map a query token to a value node */
|
|
109
|
+
function convertQueryToken(token: QueryToken): QueryNode {
|
|
110
|
+
return toQuery(
|
|
111
|
+
convertOptionallyQuotedToken(token.children[0]),
|
|
112
|
+
convertOptionallyQuotedToken(token.children[1])
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Convert the IToken */
|
|
117
|
+
function convertValueToken(binding: ValueToken): PathNode {
|
|
118
|
+
const path: AnyNode[] = [];
|
|
119
|
+
|
|
120
|
+
/** Expand a token into it's path refs */
|
|
121
|
+
function expandPath(token: Token) {
|
|
122
|
+
switch (token.type) {
|
|
123
|
+
case 'modelRef':
|
|
124
|
+
path.push(convertModelRefToken(token));
|
|
125
|
+
break;
|
|
126
|
+
case 'identifier':
|
|
127
|
+
path.push(convertIdentifierToken(token));
|
|
128
|
+
break;
|
|
129
|
+
case 'quoted_value':
|
|
130
|
+
path.push(convertQuotedValueToken(token));
|
|
131
|
+
break;
|
|
132
|
+
case 'expression':
|
|
133
|
+
path.push(convertExpressionToken(token));
|
|
134
|
+
break;
|
|
135
|
+
case 'query':
|
|
136
|
+
path.push(convertQueryToken(token));
|
|
137
|
+
break;
|
|
138
|
+
case 'concatenated':
|
|
139
|
+
path.push(convertConcatenatedToken(token));
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
token.children.forEach(expandPath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
expandPath(binding);
|
|
147
|
+
|
|
148
|
+
return toPath(path);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** map a model ref token to a path node */
|
|
152
|
+
function convertModelRefToken(token: ModelRefToken): PathNode {
|
|
153
|
+
return convertValueToken(token.children[0]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Parse a binding using ebnf */
|
|
157
|
+
export const parse: Parser = (path) => {
|
|
158
|
+
if (path === '') {
|
|
159
|
+
return {
|
|
160
|
+
status: true,
|
|
161
|
+
path: toPath([]),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const ast = parser.getAST(path) as ValueToken;
|
|
166
|
+
|
|
167
|
+
if (!ast) {
|
|
168
|
+
return {
|
|
169
|
+
status: false,
|
|
170
|
+
error: 'Unable to parse binding',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (ast.errors.length > 0) {
|
|
175
|
+
// console.log(ast.errors);
|
|
176
|
+
return {
|
|
177
|
+
status: false,
|
|
178
|
+
error: ast.errors[0].message,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
status: true,
|
|
184
|
+
path: convertValueToken(ast),
|
|
185
|
+
};
|
|
186
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { IToken } from 'ebnf';
|
|
2
|
+
|
|
3
|
+
export interface ValueToken extends IToken {
|
|
4
|
+
/** A value type */
|
|
5
|
+
type: 'value';
|
|
6
|
+
|
|
7
|
+
/** Any children of the value */
|
|
8
|
+
children: Array<SegmentAndBracketToken>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SegmentAndBracketToken extends IToken {
|
|
12
|
+
/** A token for a segment + brackets */
|
|
13
|
+
type: 'segment_and_bracket';
|
|
14
|
+
|
|
15
|
+
/** The segment + brackets */
|
|
16
|
+
children: [SegmentToken, ...Array<BracketToken>];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SegmentToken extends IToken {
|
|
20
|
+
/** A segment token */
|
|
21
|
+
type: 'segment';
|
|
22
|
+
|
|
23
|
+
/** Any children of the token */
|
|
24
|
+
children: [ConcatenatedToken | IdentifierToken];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BracketToken extends IToken {
|
|
28
|
+
/** A bracket token */
|
|
29
|
+
type: 'bracket';
|
|
30
|
+
/** Any children of the token */
|
|
31
|
+
children: [OptionallyQuotedSegment | QueryToken];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ExpressionValueToken extends IToken {
|
|
35
|
+
/** Expression value token */
|
|
36
|
+
type: 'expression_value';
|
|
37
|
+
|
|
38
|
+
/** No children here */
|
|
39
|
+
children: [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ExpressionToken extends IToken {
|
|
43
|
+
/** Expression token */
|
|
44
|
+
type: 'expression';
|
|
45
|
+
|
|
46
|
+
/** Children is the expression value */
|
|
47
|
+
children: [ExpressionValueToken];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface QuotedValueToken extends IToken {
|
|
51
|
+
/** A quoted value */
|
|
52
|
+
type: 'quoted_value';
|
|
53
|
+
/** Any children of the token */
|
|
54
|
+
children: [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface IdentifierToken extends IToken {
|
|
58
|
+
/** Any identifier */
|
|
59
|
+
type: 'identifier';
|
|
60
|
+
/** Any children of the token */
|
|
61
|
+
children: [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ConcatenatedToken extends IToken {
|
|
65
|
+
/** A node of more than 1 identifier */
|
|
66
|
+
type: 'concatenated';
|
|
67
|
+
/** Any children of the token */
|
|
68
|
+
children: Array<IdentifierToken | ModelRefToken | ExpressionToken>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ModelRefToken extends IToken {
|
|
72
|
+
/** A nested model reference */
|
|
73
|
+
type: 'modelRef';
|
|
74
|
+
/** Any children of the token */
|
|
75
|
+
children: [ValueToken];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface OptionallyQuotedSegment extends IToken {
|
|
79
|
+
/** Any optionally quoted segment */
|
|
80
|
+
type: 'optionally_quoted_segment';
|
|
81
|
+
/** Any children of the token */
|
|
82
|
+
children: [QuotedValueToken | SegmentToken];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface QueryToken extends IToken {
|
|
86
|
+
/** A query */
|
|
87
|
+
type: 'query';
|
|
88
|
+
/** Any children of the token */
|
|
89
|
+
children: [OptionallyQuotedSegment, OptionallyQuotedSegment];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type Token =
|
|
93
|
+
| ValueToken
|
|
94
|
+
| QueryToken
|
|
95
|
+
| QuotedValueToken
|
|
96
|
+
| OptionallyQuotedSegment
|
|
97
|
+
| SegmentAndBracketToken
|
|
98
|
+
| SegmentToken
|
|
99
|
+
| BracketToken
|
|
100
|
+
| IdentifierToken
|
|
101
|
+
| ConcatenatedToken
|
|
102
|
+
| ModelRefToken
|
|
103
|
+
| ExpressionValueToken
|
|
104
|
+
| ExpressionToken;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import flatten from 'arr-flatten';
|
|
2
|
+
import type { Parser } from 'parsimmon';
|
|
3
|
+
import P from 'parsimmon';
|
|
4
|
+
import type { Parser as BindingParser } from '../ast';
|
|
5
|
+
import {
|
|
6
|
+
toValue,
|
|
7
|
+
toConcatenatedNode,
|
|
8
|
+
toQuery,
|
|
9
|
+
toPath,
|
|
10
|
+
toExpression,
|
|
11
|
+
} from '../ast';
|
|
12
|
+
|
|
13
|
+
const doubleQuote = P.string('"');
|
|
14
|
+
const singleQuote = P.string("'");
|
|
15
|
+
const backTick = P.string('`');
|
|
16
|
+
|
|
17
|
+
const identifier = P.regex(/[\w\-@]+/)
|
|
18
|
+
.desc('identifier')
|
|
19
|
+
.map(toValue);
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line prefer-const
|
|
22
|
+
let path: Parser<any>;
|
|
23
|
+
|
|
24
|
+
const futurePath = P.lazy(() => path);
|
|
25
|
+
const nestedPath = futurePath
|
|
26
|
+
.trim(P.optWhitespace)
|
|
27
|
+
.wrap(P.string('{{'), P.string('}}'))
|
|
28
|
+
.map(toPath);
|
|
29
|
+
|
|
30
|
+
const nestedExpression = P.regex(/[^`]*/)
|
|
31
|
+
.wrap(backTick, backTick)
|
|
32
|
+
.map(toExpression);
|
|
33
|
+
|
|
34
|
+
const segment = P.alt(identifier, nestedPath, nestedExpression)
|
|
35
|
+
.atLeast(1)
|
|
36
|
+
.map(flatten)
|
|
37
|
+
.map(toConcatenatedNode as any);
|
|
38
|
+
|
|
39
|
+
const optionallyQuotedSegment = P.alt(
|
|
40
|
+
P.regex(/[^"]*/).wrap(doubleQuote, doubleQuote).map(toValue),
|
|
41
|
+
P.regex(/[^']*/).wrap(singleQuote, singleQuote).map(toValue),
|
|
42
|
+
segment
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const query = P.seq(
|
|
46
|
+
optionallyQuotedSegment,
|
|
47
|
+
P.string('=').times(1, 3).trim(P.optWhitespace),
|
|
48
|
+
optionallyQuotedSegment
|
|
49
|
+
).map(([key, , value]) => toQuery(key as any, value as any));
|
|
50
|
+
|
|
51
|
+
const brackets = P.alt(query, optionallyQuotedSegment)
|
|
52
|
+
.trim(P.optWhitespace)
|
|
53
|
+
.wrap(P.string('['), P.string(']'))
|
|
54
|
+
.many();
|
|
55
|
+
|
|
56
|
+
const segmentAndBrackets = P.seqMap(segment, brackets, (s, bs) => [s, ...bs]);
|
|
57
|
+
|
|
58
|
+
path = P.sepBy(segmentAndBrackets, P.string('.')).map(flatten);
|
|
59
|
+
|
|
60
|
+
/** Parse a binding using parsimmon */
|
|
61
|
+
export const parse: BindingParser = (binding) => {
|
|
62
|
+
const result = path.parse(binding);
|
|
63
|
+
|
|
64
|
+
if (result.status) {
|
|
65
|
+
return {
|
|
66
|
+
status: true,
|
|
67
|
+
path: {
|
|
68
|
+
name: 'PathNode',
|
|
69
|
+
path: result.value,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
status: false,
|
|
76
|
+
error: result.expected[0],
|
|
77
|
+
};
|
|
78
|
+
};
|