UNPKG

12.7 kBJavaScriptView Raw
1import tokens from './tokens';
2import * as util from '../util';
3import newQuery from './new-query';
4import Type from './type';
5import { stateSelectorRegex } from './state';
6
7// when a token like a variable has escaped meta characters, we need to clean the backslashes out
8// so that values get compared properly in Selector.filter()
9const cleanMetaChars = function( str ){
10 return str.replace( new RegExp( '\\\\(' + tokens.metaChar + ')', 'g' ), function( match, $1 ){
11 return $1;
12 } );
13};
14
15const replaceLastQuery = ( selector, examiningQuery, replacementQuery ) => {
16 selector[ selector.length - 1 ] = replacementQuery;
17};
18
19// NOTE: add new expression syntax here to have it recognised by the parser;
20// - a query contains all adjacent (i.e. no separator in between) expressions;
21// - the current query is stored in selector[i]
22// - you need to check the query objects in match() for it actually filter properly, but that's pretty straight forward
23let exprs = [
24 {
25 name: 'group', // just used for identifying when debugging
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 // go on to next query
155 let nextQuery = selector[ selector.length++ ] = newQuery();
156
157 return nextQuery; // this is the new query to be filled by the following exprs
158 }
159 },
160
161 {
162 name: 'directedEdge',
163 separator: true,
164 regex: tokens.directedEdge,
165 populate: function( selector, query ){
166 if( selector.currentSubject == null ){ // undirected edge
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 // the query in the selector should be the edge rather than the source
174 replaceLastQuery( selector, query, edgeQuery );
175
176 selector.edgeCount++;
177
178 // we're now populating the target query with expressions that follow
179 return target;
180 } else { // source/target
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 // the query in the selector should be the neighbourhood rather than the node
188 replaceLastQuery( selector, query, srcTgtQ );
189
190 selector.edgeCount++;
191
192 return target; // now populating the target with the following expressions
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 ){ // undirected edge
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 // the query in the selector should be the edge rather than the source
210 replaceLastQuery( selector, query, edgeQuery );
211
212 selector.edgeCount++;
213
214 // we're now populating the target query with expressions that follow
215 return target;
216 } else { // neighbourhood
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 // the query in the selector should be the neighbourhood rather than the node
224 replaceLastQuery( selector, query, nhoodQ );
225
226 return neighbor; // now populating the neighbor with following expressions
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 ){ // default: child query
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 // the query in the selector should be the '>' itself
244 replaceLastQuery( selector, query, parentChildQuery );
245
246 selector.compoundCount++;
247
248 // we're now populating the child query with expressions that follow
249 return child;
250 } else if( selector.currentSubject === query ){ // compound split 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 // set up the root compound q
259 compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject });
260
261 // populate the subject and replace the q at the old spot (within left) with TRUE
262 subject.checks = query.checks; // take the checks from the left
263 query.checks = [ { type: Type.TRUE } ]; // checks under left refs the subject implicitly
264
265 // set up the right q
266 parent.checks.push({ type: Type.TRUE }); // parent implicitly refs the subject
267 right.checks.push({
268 type: Type.PARENT, // type is swapped on right side queries
269 parent,
270 child // empty for now
271 });
272
273 replaceLastQuery( selector, left, compound );
274
275 // update the ref since we moved things around for `query`
276 selector.currentSubject = subject;
277
278 selector.compoundCount++;
279
280 return child; // now populating the right side's child
281 } else { // parent query
282 // info for parent query
283 let parent = newQuery();
284 let child = newQuery();
285 let pcQChecks = [ { type: Type.PARENT, parent, child } ];
286
287 // the parent-child query takes the place of the query previously being populated
288 parent.checks = query.checks; // the previous query contains the checks for the parent
289 query.checks = pcQChecks; // pc query takes over
290
291 selector.compoundCount++;
292
293 return child; // we're now populating the 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 ){ // default: descendant query
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 // the query in the selector should be the '>' itself
311 replaceLastQuery( selector, query, ancChQuery );
312
313 selector.compoundCount++;
314
315 // we're now populating the descendant query with expressions that follow
316 return descendant;
317 } else if( selector.currentSubject === query ){ // compound split 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 // set up the root compound q
326 compound.checks.push({ type: Type.COMPOUND_SPLIT, left, right, subject });
327
328 // populate the subject and replace the q at the old spot (within left) with TRUE
329 subject.checks = query.checks; // take the checks from the left
330 query.checks = [ { type: Type.TRUE } ]; // checks under left refs the subject implicitly
331
332 // set up the right q
333 ancestor.checks.push({ type: Type.TRUE }); // ancestor implicitly refs the subject
334 right.checks.push({
335 type: Type.ANCESTOR, // type is swapped on right side queries
336 ancestor,
337 descendant // empty for now
338 });
339
340 replaceLastQuery( selector, left, compound );
341
342 // update the ref since we moved things around for `query`
343 selector.currentSubject = subject;
344
345 selector.compoundCount++;
346
347 return descendant; // now populating the right side's descendant
348 } else { // ancestor query
349 // info for parent query
350 let ancestor = newQuery();
351 let descendant = newQuery();
352 let adQChecks = [ { type: Type.ANCESTOR, ancestor, descendant } ];
353
354 // the parent-child query takes the place of the query previously being populated
355 ancestor.checks = query.checks; // the previous query contains the checks for the parent
356 query.checks = adQChecks; // pc query takes over
357
358 selector.compoundCount++;
359
360 return descendant; // we're now populating the child
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 // directed edge with subject on the target
383
384 // change to target node check
385 topChk.type = Type.NODE_TARGET;
386
387 } else if( topType === Type.UNDIRECTED_EDGE ){
388 // undirected edge with subject on the second node
389
390 // change to neighbor check
391 topChk.type = Type.NODE_NEIGHBOR;
392 topChk.node = topChk.nodes[1]; // second node is subject
393 topChk.neighbor = topChk.nodes[0];
394
395 // clean up unused fields for new type
396 topChk.nodes = null;
397 }
398 }
399 }
400];
401
402exprs.forEach( e => e.regexObj = new RegExp( '^' + e.regex ) );
403
404export default exprs;