1 | import tokens from './tokens';
|
2 | import * as util from '../util';
|
3 | import newQuery from './new-query';
|
4 | import Type from './type';
|
5 | import { stateSelectorRegex } from './state';
|
6 |
|
7 |
|
8 |
|
9 | const cleanMetaChars = function( str ){
|
10 | return str.replace( new RegExp( '\\\\(' + tokens.metaChar + ')', 'g' ), function( match, $1 ){
|
11 | return $1;
|
12 | } );
|
13 | };
|
14 |
|
15 | const replaceLastQuery = ( selector, examiningQuery, replacementQuery ) => {
|
16 | selector[ selector.length - 1 ] = replacementQuery;
|
17 | };
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | let exprs = [
|
24 | {
|
25 | name: 'group',
|
26 | query: true,
|
27 | regex: '(' + tokens.group + ')',
|
28 | populate: function( selector, query, [ group ] ){
|
29 | query.checks.push({
|
30 | type: Type.GROUP,
|
31 | value: group === '*' ? group : group + 's'
|
32 | });
|
33 | }
|
34 | },
|
35 |
|
36 | {
|
37 | name: 'state',
|
38 | query: true,
|
39 | regex: stateSelectorRegex,
|
40 | populate: function( selector, query, [ state ] ){
|
41 | query.checks.push({
|
42 | type: Type.STATE,
|
43 | value: state
|
44 | });
|
45 | }
|
46 | },
|
47 |
|
48 | {
|
49 | name: 'id',
|
50 | query: true,
|
51 | regex: '\\#(' + tokens.id + ')',
|
52 | populate: function( selector, query,[ id ] ){
|
53 | query.checks.push({
|
54 | type: Type.ID,
|
55 | value: cleanMetaChars( id )
|
56 | });
|
57 | }
|
58 | },
|
59 |
|
60 | {
|
61 | name: 'className',
|
62 | query: true,
|
63 | regex: '\\.(' + tokens.className + ')',
|
64 | populate: function( selector, query, [ className ] ){
|
65 | query.checks.push({
|
66 | type: Type.CLASS,
|
67 | value: cleanMetaChars( className )
|
68 | });
|
69 | }
|
70 | },
|
71 |
|
72 | {
|
73 | name: 'dataExists',
|
74 | query: true,
|
75 | regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]',
|
76 | populate: function( selector, query, [ variable ] ){
|
77 | query.checks.push( {
|
78 | type: Type.DATA_EXIST,
|
79 | field: cleanMetaChars( variable )
|
80 | } );
|
81 | }
|
82 | },
|
83 |
|
84 | {
|
85 | name: 'dataCompare',
|
86 | query: true,
|
87 | regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]',
|
88 | populate: function( selector, query, [ variable, comparatorOp, value ] ){
|
89 | let valueIsString = new RegExp( '^' + tokens.string + '$' ).exec( value ) != null;
|
90 |
|
91 | if( valueIsString ){
|
92 | value = value.substring( 1, value.length - 1 );
|
93 | } else {
|
94 | value = parseFloat( value );
|
95 | }
|
96 |
|
97 | query.checks.push( {
|
98 | type: Type.DATA_COMPARE,
|
99 | field: cleanMetaChars( variable ),
|
100 | operator: comparatorOp,
|
101 | value: value
|
102 | } );
|
103 | }
|
104 | },
|
105 |
|
106 | {
|
107 | name: 'dataBool',
|
108 | query: true,
|
109 | regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]',
|
110 | populate: function( selector, query, [ boolOp, variable ] ){
|
111 | query.checks.push( {
|
112 | type: Type.DATA_BOOL,
|
113 | field: cleanMetaChars( variable ),
|
114 | operator: boolOp
|
115 | } );
|
116 | }
|
117 | },
|
118 |
|
119 | {
|
120 | name: 'metaCompare',
|
121 | query: true,
|
122 | regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]',
|
123 | populate: function( selector, query, [ meta, comparatorOp, number ] ){
|
124 | query.checks.push( {
|
125 | type: Type.META_COMPARE,
|
126 | field: cleanMetaChars( meta ),
|
127 | operator: comparatorOp,
|
128 | value: parseFloat( number )
|
129 | } );
|
130 | }
|
131 | },
|
132 |
|
133 | {
|
134 | name: 'nextQuery',
|
135 | separator: true,
|
136 | regex: tokens.separator,
|
137 | populate: function( selector, query ){
|
138 | let currentSubject = selector.currentSubject;
|
139 | let edgeCount = selector.edgeCount;
|
140 | let compoundCount = selector.compoundCount;
|
141 | let lastQ = selector[ selector.length - 1 ];
|
142 |
|
143 | if( currentSubject != null ){
|
144 | lastQ.subject = currentSubject;
|
145 | selector.currentSubject = null;
|
146 | }
|
147 |
|
148 | lastQ.edgeCount = edgeCount;
|
149 | lastQ.compoundCount = compoundCount;
|
150 |
|
151 | selector.edgeCount = 0;
|
152 | selector.compoundCount = 0;
|
153 |
|
154 |
|
155 | let nextQuery = selector[ selector.length++ ] = newQuery();
|
156 |
|
157 | return nextQuery;
|
158 | }
|
159 | },
|
160 |
|
161 | {
|
162 | name: 'directedEdge',
|
163 | separator: true,
|
164 | regex: tokens.directedEdge,
|
165 | populate: function( selector, query ){
|
166 | if( selector.currentSubject == null ){
|
167 | let edgeQuery = newQuery();
|
168 | let source = query;
|
169 | let target = newQuery();
|
170 |
|
171 | edgeQuery.checks.push({ type: Type.DIRECTED_EDGE, source, target });
|
172 |
|
173 |
|
174 | replaceLastQuery( selector, query, edgeQuery );
|
175 |
|
176 | selector.edgeCount++;
|
177 |
|
178 |
|
179 | return target;
|
180 | } else {
|
181 | let srcTgtQ = newQuery();
|
182 | let source = query;
|
183 | let target = newQuery();
|
184 |
|
185 | srcTgtQ.checks.push({ type: Type.NODE_SOURCE, source, target });
|
186 |
|
187 |
|
188 | replaceLastQuery( selector, query, srcTgtQ );
|
189 |
|
190 | selector.edgeCount++;
|
191 |
|
192 | return target;
|
193 | }
|
194 | }
|
195 | },
|
196 |
|
197 | {
|
198 | name: 'undirectedEdge',
|
199 | separator: true,
|
200 | regex: tokens.undirectedEdge,
|
201 | populate: function( selector, query ){
|
202 | if( selector.currentSubject == null ){
|
203 | let edgeQuery = newQuery();
|
204 | let source = query;
|
205 | let target = newQuery();
|
206 |
|
207 | edgeQuery.checks.push({ type: Type.UNDIRECTED_EDGE, nodes: [ source, target ] });
|
208 |
|
209 |
|
210 | replaceLastQuery( selector, query, edgeQuery );
|
211 |
|
212 | selector.edgeCount++;
|
213 |
|
214 |
|
215 | return target;
|
216 | } else {
|
217 | let nhoodQ = newQuery();
|
218 | let node = query;
|
219 | let neighbor = newQuery();
|
220 |
|
221 | nhoodQ.checks.push({ type: Type.NODE_NEIGHBOR, node, neighbor });
|
222 |
|
223 |
|
224 | replaceLastQuery( selector, query, nhoodQ );
|
225 |
|
226 | return neighbor;
|
227 | }
|
228 | }
|
229 | },
|
230 |
|
231 | {
|
232 | name: 'child',
|
233 | separator: true,
|
234 | regex: tokens.child,
|
235 | populate: function( selector, query ){
|
236 | if( selector.currentSubject == null ){
|
237 | let parentChildQuery = newQuery();
|
238 | let child = newQuery();
|
239 | let parent = selector[selector.length - 1];
|
240 |
|
241 | parentChildQuery.checks.push({ type: Type.CHILD, parent, child });
|
242 |
|
243 |
|
244 | replaceLastQuery( selector, query, parentChildQuery );
|
245 |
|
246 | selector.compoundCount++;
|
247 |
|
248 |
|
249 | return child;
|
250 | } else if( selector.currentSubject === query ){
|
251 | let compound = newQuery();
|
252 | let left = selector[ selector.length - 1 ];
|
253 | let right = newQuery();
|
254 | let subject = newQuery();
|
255 | let child = newQuery();
|
256 | let parent = newQuery();
|
257 |
|
258 |
|
259 | compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject });
|
260 |
|
261 |
|
262 | subject.checks = query.checks;
|
263 | query.checks = [ { type: Type.TRUE } ];
|
264 |
|
265 |
|
266 | parent.checks.push({ type: Type.TRUE });
|
267 | right.checks.push({
|
268 | type: Type.PARENT,
|
269 | parent,
|
270 | child
|
271 | });
|
272 |
|
273 | replaceLastQuery( selector, left, compound );
|
274 |
|
275 |
|
276 | selector.currentSubject = subject;
|
277 |
|
278 | selector.compoundCount++;
|
279 |
|
280 | return child;
|
281 | } else {
|
282 |
|
283 | let parent = newQuery();
|
284 | let child = newQuery();
|
285 | let pcQChecks = [ { type: Type.PARENT, parent, child } ];
|
286 |
|
287 |
|
288 | parent.checks = query.checks;
|
289 | query.checks = pcQChecks;
|
290 |
|
291 | selector.compoundCount++;
|
292 |
|
293 | return child;
|
294 | }
|
295 | }
|
296 | },
|
297 |
|
298 | {
|
299 | name: 'descendant',
|
300 | separator: true,
|
301 | regex: tokens.descendant,
|
302 | populate: function( selector, query ){
|
303 | if( selector.currentSubject == null ){
|
304 | let ancChQuery = newQuery();
|
305 | let descendant = newQuery();
|
306 | let ancestor = selector[selector.length - 1];
|
307 |
|
308 | ancChQuery.checks.push({ type: Type.DESCENDANT, ancestor, descendant });
|
309 |
|
310 |
|
311 | replaceLastQuery( selector, query, ancChQuery );
|
312 |
|
313 | selector.compoundCount++;
|
314 |
|
315 |
|
316 | return descendant;
|
317 | } else if( selector.currentSubject === query ){
|
318 | let compound = newQuery();
|
319 | let left = selector[ selector.length - 1 ];
|
320 | let right = newQuery();
|
321 | let subject = newQuery();
|
322 | let descendant = newQuery();
|
323 | let ancestor = newQuery();
|
324 |
|
325 |
|
326 | compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject });
|
327 |
|
328 |
|
329 | subject.checks = query.checks;
|
330 | query.checks = [ { type: Type.TRUE } ];
|
331 |
|
332 |
|
333 | ancestor.checks.push({ type: Type.TRUE });
|
334 | right.checks.push({
|
335 | type: Type.ANCESTOR,
|
336 | ancestor,
|
337 | descendant
|
338 | });
|
339 |
|
340 | replaceLastQuery( selector, left, compound );
|
341 |
|
342 |
|
343 | selector.currentSubject = subject;
|
344 |
|
345 | selector.compoundCount++;
|
346 |
|
347 | return descendant;
|
348 | } else {
|
349 |
|
350 | let ancestor = newQuery();
|
351 | let descendant = newQuery();
|
352 | let adQChecks = [ { type: Type.ANCESTOR, ancestor, descendant } ];
|
353 |
|
354 |
|
355 | ancestor.checks = query.checks;
|
356 | query.checks = adQChecks;
|
357 |
|
358 | selector.compoundCount++;
|
359 |
|
360 | return descendant;
|
361 | }
|
362 | }
|
363 | },
|
364 |
|
365 | {
|
366 | name: 'subject',
|
367 | modifier: true,
|
368 | regex: tokens.subject,
|
369 | populate: function( selector, query ){
|
370 | if( selector.currentSubject != null && selector.currentSubject !== query ){
|
371 | util.warn( 'Redefinition of subject in selector `' + selector.toString() + '`' );
|
372 | return false;
|
373 | }
|
374 |
|
375 | selector.currentSubject = query;
|
376 |
|
377 | let topQ = selector[selector.length - 1];
|
378 | let topChk = topQ.checks[0];
|
379 | let topType = topChk == null ? null : topChk.type;
|
380 |
|
381 | if( topType === Type.DIRECTED_EDGE ){
|
382 |
|
383 |
|
384 |
|
385 | topChk.type = Type.NODE_TARGET;
|
386 |
|
387 | } else if( topType === Type.UNDIRECTED_EDGE ){
|
388 |
|
389 |
|
390 |
|
391 | topChk.type = Type.NODE_NEIGHBOR;
|
392 | topChk.node = topChk.nodes[1];
|
393 | topChk.neighbor = topChk.nodes[0];
|
394 |
|
395 |
|
396 | topChk.nodes = null;
|
397 | }
|
398 | }
|
399 | }
|
400 | ];
|
401 |
|
402 | exprs.forEach( e => e.regexObj = new RegExp( '^' + e.regex ) );
|
403 |
|
404 | export default exprs;
|