1 | const TokenTypes = require('./token-type');
|
2 | const SourceTag = Symbol('SourceTag');
|
3 | class Token {
|
4 | constructor(type, source) {
|
5 | this.$type = type;
|
6 | if (source) {
|
7 | this[SourceTag] = source;
|
8 | }
|
9 | }
|
10 | toJSON() {
|
11 | return `*${this.$type}*`;
|
12 | }
|
13 | toString() {
|
14 | return `*${this.$type}*`;
|
15 | }
|
16 | }
|
17 |
|
18 | class WrappedPrimitive {
|
19 | constructor(value) {
|
20 | this.$primitive = value;
|
21 | }
|
22 | toJSON() {
|
23 | return this.$primitive;
|
24 | }
|
25 | }
|
26 |
|
27 | function cloneToken(token) {
|
28 | return new Token(token.$type, token[SourceTag]);
|
29 | }
|
30 |
|
31 |
|
32 | function Clone(model) {
|
33 | if (model instanceof Token) {
|
34 | return cloneToken(model);
|
35 | } else if (model instanceof Expression) {
|
36 | return new Expression(...model.map(Clone));
|
37 | }
|
38 | return model;
|
39 | }
|
40 |
|
41 | const TokenTypeData = {
|
42 | and: new TokenTypes({nonChained: true, len: [2, Number.MAX_SAFE_INTEGER]}),
|
43 | or: new TokenTypes({nonChained: true, len: [2, Number.MAX_SAFE_INTEGER]}),
|
44 | array: new TokenTypes({nonChained: true, private: true, tryToHoist: true, stable: true}),
|
45 | object: new TokenTypes({nonChained: true, private: true, tryToHoist: true, stable: true}),
|
46 | not: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
47 | ternary: new TokenTypes({nonChained: true, chainIndex: 1, len: [4, 4]}),
|
48 | trace: new TokenTypes({chainIndex: 2, len: [2, 4]}),
|
49 | get: new TokenTypes({chainIndex: 2, len: [3, 3]}),
|
50 | root: new TokenTypes({nonVerb: true}),
|
51 | topLevel: new TokenTypes({nonVerb: true, private: true}),
|
52 | mapValues: new TokenTypes({collectionVerb: true, chainIndex: 2, len: [3, 4], stable: true}),
|
53 | map: new TokenTypes({collectionVerb: true, chainIndex: 2, arrayVerb: true, len: [3, 4], stable: true}),
|
54 | recursiveMapValues: new TokenTypes({collectionVerb: true, chainIndex: 2, recursive: true, len: [3, 4], stable: true}),
|
55 | recursiveMap: new TokenTypes({collectionVerb: true, chainIndex: 2, arrayVerb: true, recursive: true, len: [3, 4], stable: true}),
|
56 | any: new TokenTypes({
|
57 | collectionVerb: true,
|
58 | chainIndex: 2,
|
59 | arrayVerb: true,
|
60 | len: [3, 4]
|
61 | }),
|
62 | keyBy: new TokenTypes({
|
63 | collectionVerb: true,
|
64 | chainIndex: 2,
|
65 | arrayVerb: true,
|
66 | len: [3, 4],
|
67 | stable: true
|
68 | }),
|
69 | filter: new TokenTypes({
|
70 | collectionVerb: true,
|
71 | chainIndex: 2,
|
72 | arrayVerb: true,
|
73 | len: [3, 4],
|
74 | stable: true
|
75 | }),
|
76 | anyValues: new TokenTypes({
|
77 | collectionVerb: true,
|
78 | chainIndex: 2,
|
79 | len: [3, 4]
|
80 | }),
|
81 | filterBy: new TokenTypes({collectionVerb: true, chainIndex: 2, len: [3, 4], stable: true}),
|
82 | mapKeys: new TokenTypes({collectionVerb: true, chainIndex: 2, len: [3, 4], stable: true}),
|
83 | groupBy: new TokenTypes({collectionVerb: true, chainIndex: 2, len: [3, 4], stable: true}),
|
84 | values: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['object'], stable: true}),
|
85 | keys: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['object'], stable: true}),
|
86 | flatten: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array'], stable: true}),
|
87 | size: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array', 'object']}),
|
88 | isEmpty: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array', 'object']}),
|
89 | last: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array']}),
|
90 | sum: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array']}),
|
91 | range: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 4], stable: true}),
|
92 | assign: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array'], stable: true}),
|
93 | defaults: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array'], stable: true}),
|
94 | loop: new TokenTypes({nonVerb: true}),
|
95 | recur: new TokenTypes({chainIndex: 2, len: [3, 3]}),
|
96 | context: new TokenTypes({nonVerb: true}),
|
97 | func: new TokenTypes({private: true, len: [2, 2]}),
|
98 | invoke: new TokenTypes({private: true, len: [2, Number.MAX_SAFE_INTEGER]}),
|
99 | val: new TokenTypes({nonVerb: true}),
|
100 | key: new TokenTypes({nonVerb: true}),
|
101 | null: new TokenTypes({nonVerb: true, private: true}),
|
102 | arg0: new TokenTypes({nonVerb: true}),
|
103 | arg1: new TokenTypes({nonVerb: true}),
|
104 | arg2: new TokenTypes({nonVerb: true}),
|
105 | arg3: new TokenTypes({nonVerb: true}),
|
106 | arg4: new TokenTypes({nonVerb: true}),
|
107 | arg5: new TokenTypes({nonVerb: true}),
|
108 | arg6: new TokenTypes({nonVerb: true}),
|
109 | arg7: new TokenTypes({nonVerb: true}),
|
110 | arg8: new TokenTypes({nonVerb: true}),
|
111 | arg9: new TokenTypes({nonVerb: true}),
|
112 | cond: new TokenTypes({private: true}),
|
113 | eq: new TokenTypes({chainIndex: 1, len: [3, 3]}),
|
114 | gt: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
115 | lt: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
116 | gte: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
117 | lte: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
118 | plus: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number', 'string']}),
|
119 | minus: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
120 | mult: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
121 | div: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
122 | mod: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
123 | breakpoint: new TokenTypes({chainIndex: 1, len: [2, 2]}),
|
124 | call: new TokenTypes({nonChained: true, chainIndex: 2, len: [3, Number.MAX_SAFE_INTEGER], tryToHoist: true}),
|
125 | bind: new TokenTypes({nonChained: true, chainIndex: 2, len: [2, Number.MAX_SAFE_INTEGER], tryToHoist: true, stable: true}),
|
126 | effect: new TokenTypes({nonChained: true, chainIndex: 2, len: [3, Number.MAX_SAFE_INTEGER]}),
|
127 | startsWith: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3], expectedTypes: ['string']}),
|
128 | endsWith: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3], expectedTypes: ['string']}),
|
129 | toUpperCase: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
130 | toLowerCase: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
131 | stringLength: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
132 | floor: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
133 | ceil: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
134 | round: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
135 | parseInt: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 3], expectedTypes: ['string']}),
|
136 | parseFloat: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
137 | substring: new TokenTypes({nonChained: true, chainIndex: 1, len: [4, 4], expectedTypes: ['string']}),
|
138 | split: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3]}),
|
139 | isUndefined: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
140 | isBoolean: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
141 | isString: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
142 | isNumber: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
143 | isArray: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
144 | abstract: new TokenTypes({nonChained: true, len: [3, 3], private: true}),
|
145 | quote: new TokenTypes({nonChained: true, len: [2, 2], private: true}),
|
146 | trackPath: new TokenTypes({nonChained: true, len: [3, Number.MAX_SAFE_INTEGER], private: true})
|
147 | };
|
148 |
|
149 | const AllTokens = Object.keys(TokenTypeData).reduce((acc, k) => {
|
150 | acc[k[0].toUpperCase() + k.slice(1)] = new Token(k);
|
151 | return acc;
|
152 | }, {});
|
153 |
|
154 | class Expression extends Array {}
|
155 |
|
156 | class SetterExpression extends Array {
|
157 | toJSON() {
|
158 | return ['*setter*'].concat(this)
|
159 | }
|
160 | setterType() {
|
161 | return 'set'
|
162 | }
|
163 | }
|
164 | class SpliceSetterExpression extends SetterExpression {
|
165 | toJSON() {
|
166 | return ['*splice*'].concat(this)
|
167 | }
|
168 |
|
169 | setterType() {
|
170 | return 'splice'
|
171 | }
|
172 | }
|
173 | class PushSetterExpression extends SetterExpression {
|
174 | toJSON() {
|
175 | return ['*push*'].concat(this)
|
176 | }
|
177 |
|
178 | setterType() {
|
179 | return 'push'
|
180 | }
|
181 | }
|
182 |
|
183 | AllTokens.Token = Token;
|
184 | AllTokens.Expr = (...args) => new Expression(Clone(args[0]), ...args.slice(1));
|
185 |
|
186 | function validatePathSegmentArguments(args) {
|
187 | const invalidArgs = args.filter(arg =>
|
188 | typeof arg !== 'string' &&
|
189 | typeof arg !== 'number' &&
|
190 | !(arg instanceof Token &&
|
191 | (arg.$type === 'arg0' || arg.$type === 'arg1' || arg.$type === 'arg2')));
|
192 |
|
193 | if (invalidArgs.length > 0) {
|
194 | throw new Error(`Invalid arguments for setter/splice/push - can only accept path (use arg0/arg1/arg2 - to define placeholders in the path), received [${args}]`);
|
195 | }
|
196 | }
|
197 |
|
198 | AllTokens.Setter = (...args) => {
|
199 | if (args.length === 0) {
|
200 | throw new Error('Invalid arguments for setter/splice - must receive a path');
|
201 | }
|
202 | validatePathSegmentArguments(args);
|
203 | return new SetterExpression(...args);
|
204 | };
|
205 | AllTokens.Splice = (...args) => {
|
206 | validatePathSegmentArguments(args);
|
207 | return new SpliceSetterExpression(...args, new Token('key'));
|
208 | }
|
209 | AllTokens.Push = (...args) => {
|
210 | validatePathSegmentArguments(args);
|
211 | return new PushSetterExpression(...args);
|
212 | }
|
213 |
|
214 | AllTokens.withName = (name, val) => {
|
215 | if (val instanceof Expression) {
|
216 | const tokenType = val[0].$type;
|
217 | const tokenData = TokenTypeData[tokenType];
|
218 | if (tokenData.collectionVerb && tokenData.chainIndex === 2) {
|
219 | name = name.replace(/[\W_]+/g, '');
|
220 | val[0][SourceTag] = `${val[0][SourceTag]}:${name}`;
|
221 | } else {
|
222 | throw new Error(`can only name collection functions:${name}`);
|
223 | }
|
224 | return val;
|
225 | }
|
226 | }
|
227 |
|
228 | AllTokens.Expression = Expression;
|
229 | AllTokens.TokenTypeData = TokenTypeData;
|
230 | AllTokens.SetterExpression = SetterExpression;
|
231 | AllTokens.SpliceSetterExpression = SpliceSetterExpression;
|
232 | AllTokens.PushSetterExpression = PushSetterExpression;
|
233 |
|
234 | AllTokens.isSetterExpression = expression => expression instanceof SetterExpression;
|
235 | AllTokens.isSpliceExpression = expression => expression instanceof SpliceSetterExpression;
|
236 | AllTokens.isPushExpression = expression => expression instanceof PushSetterExpression;
|
237 | AllTokens.isExpression = expression => expression instanceof Expression;
|
238 | AllTokens.isToken = token => token instanceof Token;
|
239 |
|
240 | AllTokens.Clone = Clone;
|
241 | AllTokens.cloneToken = cloneToken;
|
242 | AllTokens.SourceTag = SourceTag;
|
243 | AllTokens.WrappedPrimitive = WrappedPrimitive;
|
244 | AllTokens.UnwrappedExpr = Symbol('UnwrappedExpr');
|
245 | module.exports = AllTokens;
|