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 | sum: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array']}),
|
89 | range: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 4], stable: true}),
|
90 | assign: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array'], stable: true}),
|
91 | defaults: new TokenTypes({collectionVerb: true, chainIndex: 1, len: [2, 2], expectedTypes: ['array'], stable: true}),
|
92 | loop: new TokenTypes({nonVerb: true}),
|
93 | recur: new TokenTypes({chainIndex: 2, len: [3, 3]}),
|
94 | context: new TokenTypes({nonVerb: true}),
|
95 | func: new TokenTypes({private: true, len: [2, 2]}),
|
96 | invoke: new TokenTypes({private: true, len: [2, Number.MAX_SAFE_INTEGER]}),
|
97 | val: new TokenTypes({nonVerb: true}),
|
98 | key: new TokenTypes({nonVerb: true}),
|
99 | null: new TokenTypes({nonVerb: true, private: true}),
|
100 | arg0: new TokenTypes({nonVerb: true}),
|
101 | arg1: new TokenTypes({nonVerb: true}),
|
102 | arg2: new TokenTypes({nonVerb: true}),
|
103 | arg3: new TokenTypes({nonVerb: true}),
|
104 | arg4: new TokenTypes({nonVerb: true}),
|
105 | arg5: new TokenTypes({nonVerb: true}),
|
106 | arg6: new TokenTypes({nonVerb: true}),
|
107 | arg7: new TokenTypes({nonVerb: true}),
|
108 | arg8: new TokenTypes({nonVerb: true}),
|
109 | arg9: new TokenTypes({nonVerb: true}),
|
110 | cond: new TokenTypes({private: true}),
|
111 | eq: new TokenTypes({chainIndex: 1, len: [3, 3]}),
|
112 | gt: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
113 | lt: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
114 | gte: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
115 | lte: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
116 | plus: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number', 'string']}),
|
117 | minus: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
118 | mult: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
119 | div: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
120 | mod: new TokenTypes({chainIndex: 1, len: [3, 3], expectedTypes: ['number']}),
|
121 | breakpoint: new TokenTypes({chainIndex: 1, len: [2, 2]}),
|
122 | call: new TokenTypes({nonChained: true, chainIndex: 2, len: [3, Number.MAX_SAFE_INTEGER], tryToHoist: true}),
|
123 | bind: new TokenTypes({nonChained: true, chainIndex: 2, len: [2, Number.MAX_SAFE_INTEGER], tryToHoist: true, stable: true}),
|
124 | effect: new TokenTypes({nonChained: true, chainIndex: 2, len: [3, Number.MAX_SAFE_INTEGER]}),
|
125 | startsWith: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3], expectedTypes: ['string']}),
|
126 | endsWith: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3], expectedTypes: ['string']}),
|
127 | toUpperCase: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
128 | toLowerCase: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
129 | stringLength: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['string']}),
|
130 | floor: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
131 | ceil: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
132 | round: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2], expectedTypes: ['number']}),
|
133 | parseInt: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 3], expectedTypes: ['string']}),
|
134 | substring: new TokenTypes({nonChained: true, chainIndex: 1, len: [4, 4], expectedTypes: ['string']}),
|
135 | split: new TokenTypes({nonChained: true, chainIndex: 1, len: [3, 3]}),
|
136 | isUndefined: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
137 | isBoolean: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
138 | isString: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
139 | isNumber: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
140 | isArray: new TokenTypes({nonChained: true, chainIndex: 1, len: [2, 2]}),
|
141 | abstract: new TokenTypes({nonChained: true, len: [3, 3], private: true}),
|
142 | quote: new TokenTypes({nonChained: true, len: [2, 2], private: true}),
|
143 | trackPath: new TokenTypes({nonChained: true, len: [3, Number.MAX_SAFE_INTEGER], private: true})
|
144 | };
|
145 |
|
146 | const AllTokens = Object.keys(TokenTypeData).reduce((acc, k) => {
|
147 | acc[k[0].toUpperCase() + k.slice(1)] = new Token(k);
|
148 | return acc;
|
149 | }, {});
|
150 |
|
151 | class Expression extends Array {}
|
152 |
|
153 | class SetterExpression extends Array {
|
154 | toJSON() {
|
155 | return ['*setter*'].concat(this)
|
156 | }
|
157 | setterType() {
|
158 | return 'set'
|
159 | }
|
160 | }
|
161 | class SpliceSetterExpression extends SetterExpression {
|
162 | toJSON() {
|
163 | return ['*splice*'].concat(this)
|
164 | }
|
165 |
|
166 | setterType() {
|
167 | return 'splice'
|
168 | }
|
169 | }
|
170 | class PushSetterExpression extends SetterExpression {
|
171 | toJSON() {
|
172 | return ['*push*'].concat(this)
|
173 | }
|
174 |
|
175 | setterType() {
|
176 | return 'push'
|
177 | }
|
178 | }
|
179 |
|
180 | AllTokens.Token = Token;
|
181 | AllTokens.Expr = (...args) => new Expression(Clone(args[0]), ...args.slice(1));
|
182 |
|
183 | function validatePathSegmentArguments(args) {
|
184 | const invalidArgs = args.filter(arg =>
|
185 | typeof arg !== 'string' &&
|
186 | typeof arg !== 'number' &&
|
187 | !(arg instanceof Token &&
|
188 | (arg.$type === 'arg0' || arg.$type === 'arg1' || arg.$type === 'arg2')));
|
189 |
|
190 | if (invalidArgs.length > 0) {
|
191 | 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}]`);
|
192 | }
|
193 | }
|
194 |
|
195 | AllTokens.Setter = (...args) => {
|
196 | if (args.length === 0) {
|
197 | throw new Error('Invalid arguments for setter/splice - must receive a path');
|
198 | }
|
199 | validatePathSegmentArguments(args);
|
200 | return new SetterExpression(...args);
|
201 | };
|
202 | AllTokens.Splice = (...args) => {
|
203 | validatePathSegmentArguments(args);
|
204 | return new SpliceSetterExpression(...args, new Token('key'));
|
205 | }
|
206 | AllTokens.Push = (...args) => {
|
207 | validatePathSegmentArguments(args);
|
208 | return new PushSetterExpression(...args);
|
209 | }
|
210 |
|
211 | AllTokens.withName = (name, val) => {
|
212 | if (val instanceof Expression) {
|
213 | const tokenType = val[0].$type;
|
214 | const tokenData = TokenTypeData[tokenType];
|
215 | if (tokenData.collectionVerb && tokenData.chainIndex === 2) {
|
216 | name = name.replace(/[\W_]+/g, '');
|
217 | val[0][SourceTag] = `${val[0][SourceTag]}:${name}`;
|
218 | } else {
|
219 | throw new Error(`can only name collection functions:${name}`);
|
220 | }
|
221 | return val;
|
222 | }
|
223 | }
|
224 |
|
225 | AllTokens.Expression = Expression;
|
226 | AllTokens.TokenTypeData = TokenTypeData;
|
227 | AllTokens.SetterExpression = SetterExpression;
|
228 | AllTokens.SpliceSetterExpression = SpliceSetterExpression;
|
229 | AllTokens.PushSetterExpression = PushSetterExpression;
|
230 |
|
231 | AllTokens.isSetterExpression = expression => expression instanceof SetterExpression;
|
232 | AllTokens.isSpliceExpression = expression => expression instanceof SpliceSetterExpression;
|
233 | AllTokens.isPushExpression = expression => expression instanceof PushSetterExpression;
|
234 | AllTokens.isExpression = expression => expression instanceof Expression;
|
235 | AllTokens.isToken = token => token instanceof Token;
|
236 |
|
237 | AllTokens.Clone = Clone;
|
238 | AllTokens.cloneToken = cloneToken;
|
239 | AllTokens.SourceTag = SourceTag;
|
240 | AllTokens.WrappedPrimitive = WrappedPrimitive;
|
241 | AllTokens.UnwrappedExpr = Symbol('UnwrappedExpr');
|
242 | module.exports = AllTokens;
|