flowquery 1.0.17 → 1.0.20

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.
Files changed (56) hide show
  1. package/.gitattributes +3 -0
  2. package/.github/workflows/python-publish.yml +58 -7
  3. package/.github/workflows/release.yml +25 -18
  4. package/README.md +37 -32
  5. package/dist/flowquery.min.js +1 -1
  6. package/dist/graph/data.d.ts.map +1 -1
  7. package/dist/graph/data.js +5 -3
  8. package/dist/graph/data.js.map +1 -1
  9. package/dist/graph/pattern.d.ts.map +1 -1
  10. package/dist/graph/pattern.js +11 -4
  11. package/dist/graph/pattern.js.map +1 -1
  12. package/dist/graph/pattern_expression.d.ts +1 -0
  13. package/dist/graph/pattern_expression.d.ts.map +1 -1
  14. package/dist/graph/pattern_expression.js +14 -3
  15. package/dist/graph/pattern_expression.js.map +1 -1
  16. package/dist/graph/relationship.d.ts.map +1 -1
  17. package/dist/graph/relationship.js +11 -4
  18. package/dist/graph/relationship.js.map +1 -1
  19. package/dist/index.d.ts +0 -7
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/parsing/parser.d.ts +5 -0
  24. package/dist/parsing/parser.d.ts.map +1 -1
  25. package/dist/parsing/parser.js +94 -73
  26. package/dist/parsing/parser.js.map +1 -1
  27. package/docs/flowquery.min.js +1 -1
  28. package/flowquery-py/CONTRIBUTING.md +127 -0
  29. package/flowquery-py/README.md +13 -112
  30. package/flowquery-py/misc/data/test.json +10 -0
  31. package/flowquery-py/misc/data/users.json +242 -0
  32. package/flowquery-py/notebooks/TestFlowQuery.ipynb +440 -0
  33. package/flowquery-py/pyproject.toml +4 -1
  34. package/flowquery-py/src/__init__.py +2 -0
  35. package/flowquery-py/src/graph/data.py +4 -3
  36. package/flowquery-py/src/graph/pattern.py +7 -4
  37. package/flowquery-py/src/graph/pattern_expression.py +6 -3
  38. package/flowquery-py/src/graph/relationship.py +7 -0
  39. package/flowquery-py/src/io/command_line.py +44 -2
  40. package/flowquery-py/src/parsing/base_parser.py +2 -2
  41. package/flowquery-py/src/parsing/operations/load.py +6 -0
  42. package/flowquery-py/src/parsing/parser.py +78 -62
  43. package/flowquery-py/src/tokenization/token.py +122 -176
  44. package/flowquery-py/src/tokenization/tokenizer.py +4 -4
  45. package/flowquery-py/tests/compute/test_runner.py +10 -7
  46. package/flowquery-py/tests/parsing/test_parser.py +6 -0
  47. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  48. package/package.json +1 -1
  49. package/src/graph/data.ts +5 -3
  50. package/src/graph/pattern.ts +13 -4
  51. package/src/graph/pattern_expression.ts +14 -3
  52. package/src/graph/relationship.ts +8 -0
  53. package/src/index.ts +5 -6
  54. package/src/parsing/parser.ts +93 -69
  55. package/tests/compute/runner.test.ts +71 -79
  56. package/tests/parsing/parser.test.ts +8 -0
@@ -21,7 +21,7 @@ class Token:
21
21
  and an optional value.
22
22
 
23
23
  Example:
24
- with_token = Token.WITH
24
+ with_token = Token.WITH()
25
25
  ident_token = Token.IDENTIFIER("myVar")
26
26
  num_token = Token.NUMBER("42")
27
27
  """
@@ -153,73 +153,64 @@ class Token:
153
153
 
154
154
  # Symbol tokens
155
155
 
156
- @classmethod
157
- @property
158
- def LEFT_PARENTHESIS(cls) -> Token:
156
+ @staticmethod
157
+ def LEFT_PARENTHESIS() -> Token:
159
158
  return Token(TokenType.SYMBOL, Symbol.LEFT_PARENTHESIS.value)
160
159
 
161
160
  def is_left_parenthesis(self) -> bool:
162
161
  return self._type == TokenType.SYMBOL and self._value == Symbol.LEFT_PARENTHESIS.value
163
162
 
164
- @classmethod
165
- @property
166
- def RIGHT_PARENTHESIS(cls) -> Token:
163
+ @staticmethod
164
+ def RIGHT_PARENTHESIS() -> Token:
167
165
  return Token(TokenType.SYMBOL, Symbol.RIGHT_PARENTHESIS.value)
168
166
 
169
167
  def is_right_parenthesis(self) -> bool:
170
168
  return self._type == TokenType.SYMBOL and self._value == Symbol.RIGHT_PARENTHESIS.value
171
169
 
172
- @classmethod
173
- @property
174
- def COMMA(cls) -> Token:
170
+ @staticmethod
171
+ def COMMA() -> Token:
175
172
  return Token(TokenType.SYMBOL, Symbol.COMMA.value)
176
173
 
177
174
  def is_comma(self) -> bool:
178
175
  return self._type == TokenType.SYMBOL and self._value == Symbol.COMMA.value
179
176
 
180
- @classmethod
181
- @property
182
- def DOT(cls) -> Token:
177
+ @staticmethod
178
+ def DOT() -> Token:
183
179
  return Token(TokenType.SYMBOL, Symbol.DOT.value)
184
180
 
185
181
  def is_dot(self) -> bool:
186
182
  return self._type == TokenType.SYMBOL and self._value == Symbol.DOT.value
187
183
 
188
- @classmethod
189
- @property
190
- def COLON(cls) -> Token:
184
+ @staticmethod
185
+ def COLON() -> Token:
191
186
  return Token(TokenType.SYMBOL, Symbol.COLON.value)
192
187
 
193
188
  def is_colon(self) -> bool:
194
189
  return self._type == TokenType.SYMBOL and self._value == Symbol.COLON.value
195
190
 
196
- @classmethod
197
- @property
198
- def OPENING_BRACE(cls) -> Token:
191
+ @staticmethod
192
+ def OPENING_BRACE() -> Token:
199
193
  return Token(TokenType.SYMBOL, Symbol.OPENING_BRACE.value)
200
194
 
201
195
  def is_opening_brace(self) -> bool:
202
196
  return self._type == TokenType.SYMBOL and self._value == Symbol.OPENING_BRACE.value
203
197
 
204
- @classmethod
205
- @property
206
- def CLOSING_BRACE(cls) -> Token:
198
+ @staticmethod
199
+ def CLOSING_BRACE() -> Token:
207
200
  return Token(TokenType.SYMBOL, Symbol.CLOSING_BRACE.value)
208
201
 
209
202
  def is_closing_brace(self) -> bool:
210
203
  return self._type == TokenType.SYMBOL and self._value == Symbol.CLOSING_BRACE.value
211
204
 
212
- @classmethod
213
- @property
214
- def OPENING_BRACKET(cls) -> Token:
205
+ @staticmethod
206
+ def OPENING_BRACKET() -> Token:
215
207
  return Token(TokenType.SYMBOL, Symbol.OPENING_BRACKET.value)
216
208
 
217
209
  def is_opening_bracket(self) -> bool:
218
210
  return self._type == TokenType.SYMBOL and self._value == Symbol.OPENING_BRACKET.value
219
211
 
220
- @classmethod
221
- @property
222
- def CLOSING_BRACKET(cls) -> Token:
212
+ @staticmethod
213
+ def CLOSING_BRACKET() -> Token:
223
214
  return Token(TokenType.SYMBOL, Symbol.CLOSING_BRACKET.value)
224
215
 
225
216
  def is_closing_bracket(self) -> bool:
@@ -227,9 +218,8 @@ class Token:
227
218
 
228
219
  # Whitespace token
229
220
 
230
- @classmethod
231
- @property
232
- def WHITESPACE(cls) -> Token:
221
+ @staticmethod
222
+ def WHITESPACE() -> Token:
233
223
  return Token(TokenType.WHITESPACE)
234
224
 
235
225
  def is_whitespace(self) -> bool:
@@ -243,17 +233,15 @@ class Token:
243
233
  def is_unary_operator(self) -> bool:
244
234
  return self._type == TokenType.UNARY_OPERATOR
245
235
 
246
- @classmethod
247
- @property
248
- def ADD(cls) -> Token:
236
+ @staticmethod
237
+ def ADD() -> Token:
249
238
  return Token(TokenType.OPERATOR, Operator.ADD.value)
250
239
 
251
240
  def is_add(self) -> bool:
252
241
  return self._type == TokenType.OPERATOR and self._value == Operator.ADD.value
253
242
 
254
- @classmethod
255
- @property
256
- def SUBTRACT(cls) -> Token:
243
+ @staticmethod
244
+ def SUBTRACT() -> Token:
257
245
  return Token(TokenType.OPERATOR, Operator.SUBTRACT.value)
258
246
 
259
247
  def is_subtract(self) -> bool:
@@ -262,113 +250,99 @@ class Token:
262
250
  def is_negation(self) -> bool:
263
251
  return self.is_subtract()
264
252
 
265
- @classmethod
266
- @property
267
- def MULTIPLY(cls) -> Token:
253
+ @staticmethod
254
+ def MULTIPLY() -> Token:
268
255
  return Token(TokenType.OPERATOR, Operator.MULTIPLY.value)
269
256
 
270
257
  def is_multiply(self) -> bool:
271
258
  return self._type == TokenType.OPERATOR and self._value == Operator.MULTIPLY.value
272
259
 
273
- @classmethod
274
- @property
275
- def DIVIDE(cls) -> Token:
260
+ @staticmethod
261
+ def DIVIDE() -> Token:
276
262
  return Token(TokenType.OPERATOR, Operator.DIVIDE.value)
277
263
 
278
264
  def is_divide(self) -> bool:
279
265
  return self._type == TokenType.OPERATOR and self._value == Operator.DIVIDE.value
280
266
 
281
- @classmethod
282
- @property
283
- def EXPONENT(cls) -> Token:
267
+ @staticmethod
268
+ def EXPONENT() -> Token:
284
269
  return Token(TokenType.OPERATOR, Operator.EXPONENT.value)
285
270
 
286
271
  def is_exponent(self) -> bool:
287
272
  return self._type == TokenType.OPERATOR and self._value == Operator.EXPONENT.value
288
273
 
289
- @classmethod
290
- @property
291
- def MODULO(cls) -> Token:
274
+ @staticmethod
275
+ def MODULO() -> Token:
292
276
  return Token(TokenType.OPERATOR, Operator.MODULO.value)
293
277
 
294
278
  def is_modulo(self) -> bool:
295
279
  return self._type == TokenType.OPERATOR and self._value == Operator.MODULO.value
296
280
 
297
- @classmethod
298
- @property
299
- def EQUALS(cls) -> Token:
281
+ @staticmethod
282
+ def EQUALS() -> Token:
300
283
  return Token(TokenType.OPERATOR, Operator.EQUALS.value)
301
284
 
302
285
  def is_equals(self) -> bool:
303
286
  return self._type == TokenType.OPERATOR and self._value == Operator.EQUALS.value
304
287
 
305
- @classmethod
306
- @property
307
- def NOT_EQUALS(cls) -> Token:
288
+ @staticmethod
289
+ def NOT_EQUALS() -> Token:
308
290
  return Token(TokenType.OPERATOR, Operator.NOT_EQUALS.value)
309
291
 
310
292
  def is_not_equals(self) -> bool:
311
293
  return self._type == TokenType.OPERATOR and self._value == Operator.NOT_EQUALS.value
312
294
 
313
- @classmethod
314
- @property
315
- def LESS_THAN(cls) -> Token:
295
+ @staticmethod
296
+ def LESS_THAN() -> Token:
316
297
  return Token(TokenType.OPERATOR, Operator.LESS_THAN.value)
317
298
 
318
299
  def is_less_than(self) -> bool:
319
300
  return self._type == TokenType.OPERATOR and self._value == Operator.LESS_THAN.value
320
301
 
321
- @classmethod
322
- @property
323
- def LESS_THAN_OR_EQUAL(cls) -> Token:
302
+ @staticmethod
303
+ def LESS_THAN_OR_EQUAL() -> Token:
324
304
  return Token(TokenType.OPERATOR, Operator.LESS_THAN_OR_EQUAL.value)
325
305
 
326
306
  def is_less_than_or_equal(self) -> bool:
327
307
  return self._type == TokenType.OPERATOR and self._value == Operator.LESS_THAN_OR_EQUAL.value
328
308
 
329
- @classmethod
330
- @property
331
- def GREATER_THAN(cls) -> Token:
309
+ @staticmethod
310
+ def GREATER_THAN() -> Token:
332
311
  return Token(TokenType.OPERATOR, Operator.GREATER_THAN.value)
333
312
 
334
313
  def is_greater_than(self) -> bool:
335
314
  return self._type == TokenType.OPERATOR and self._value == Operator.GREATER_THAN.value
336
315
 
337
- @classmethod
338
- @property
339
- def GREATER_THAN_OR_EQUAL(cls) -> Token:
316
+ @staticmethod
317
+ def GREATER_THAN_OR_EQUAL() -> Token:
340
318
  return Token(TokenType.OPERATOR, Operator.GREATER_THAN_OR_EQUAL.value)
341
319
 
342
320
  def is_greater_than_or_equal(self) -> bool:
343
321
  return self._type == TokenType.OPERATOR and self._value == Operator.GREATER_THAN_OR_EQUAL.value
344
322
 
345
- @classmethod
346
- @property
347
- def AND(cls) -> Token:
323
+ @staticmethod
324
+ def AND() -> Token:
348
325
  return Token(TokenType.OPERATOR, Operator.AND.value)
349
326
 
350
327
  def is_and(self) -> bool:
351
328
  return self._type == TokenType.OPERATOR and self._value == Operator.AND.value
352
329
 
353
- @classmethod
354
- @property
355
- def OR(cls) -> Token:
330
+ @staticmethod
331
+ def OR() -> Token:
356
332
  return Token(TokenType.OPERATOR, Operator.OR.value)
357
333
 
358
334
  def is_or(self) -> bool:
359
335
  return self._type == TokenType.OPERATOR and self._value == Operator.OR.value
360
336
 
361
- @classmethod
362
- @property
363
- def NOT(cls) -> Token:
337
+ @staticmethod
338
+ def NOT() -> Token:
364
339
  return Token(TokenType.UNARY_OPERATOR, Operator.NOT.value)
365
340
 
366
341
  def is_not(self) -> bool:
367
342
  return self._type == TokenType.UNARY_OPERATOR and self._value == Operator.NOT.value
368
343
 
369
- @classmethod
370
- @property
371
- def IS(cls) -> Token:
344
+ @staticmethod
345
+ def IS() -> Token:
372
346
  return Token(TokenType.OPERATOR, Operator.IS.value)
373
347
 
374
348
  def is_is(self) -> bool:
@@ -379,249 +353,218 @@ class Token:
379
353
  def is_keyword(self) -> bool:
380
354
  return self._type == TokenType.KEYWORD
381
355
 
382
- @classmethod
383
- @property
384
- def WITH(cls) -> Token:
356
+ @staticmethod
357
+ def WITH() -> Token:
385
358
  return Token(TokenType.KEYWORD, Keyword.WITH.value)
386
359
 
387
360
  def is_with(self) -> bool:
388
361
  return self._type == TokenType.KEYWORD and self._value == Keyword.WITH.value
389
362
 
390
- @classmethod
391
- @property
392
- def RETURN(cls) -> Token:
363
+ @staticmethod
364
+ def RETURN() -> Token:
393
365
  return Token(TokenType.KEYWORD, Keyword.RETURN.value)
394
366
 
395
367
  def is_return(self) -> bool:
396
368
  return self._type == TokenType.KEYWORD and self._value == Keyword.RETURN.value
397
369
 
398
- @classmethod
399
- @property
400
- def LOAD(cls) -> Token:
370
+ @staticmethod
371
+ def LOAD() -> Token:
401
372
  return Token(TokenType.KEYWORD, Keyword.LOAD.value)
402
373
 
403
374
  def is_load(self) -> bool:
404
375
  return self._type == TokenType.KEYWORD and self._value == Keyword.LOAD.value
405
376
 
406
- @classmethod
407
- @property
408
- def CALL(cls) -> Token:
377
+ @staticmethod
378
+ def CALL() -> Token:
409
379
  return Token(TokenType.KEYWORD, Keyword.CALL.value)
410
380
 
411
381
  def is_call(self) -> bool:
412
382
  return self._type == TokenType.KEYWORD and self._value == Keyword.CALL.value
413
383
 
414
- @classmethod
415
- @property
416
- def YIELD(cls) -> Token:
384
+ @staticmethod
385
+ def YIELD() -> Token:
417
386
  return Token(TokenType.KEYWORD, Keyword.YIELD.value)
418
387
 
419
388
  def is_yield(self) -> bool:
420
389
  return self._type == TokenType.KEYWORD and self._value == Keyword.YIELD.value
421
390
 
422
- @classmethod
423
- @property
424
- def JSON(cls) -> Token:
391
+ @staticmethod
392
+ def JSON() -> Token:
425
393
  return Token(TokenType.KEYWORD, Keyword.JSON.value)
426
394
 
427
395
  def is_json(self) -> bool:
428
396
  return self._type == TokenType.KEYWORD and self._value == Keyword.JSON.value
429
397
 
430
- @classmethod
431
- @property
432
- def CSV(cls) -> Token:
398
+ @staticmethod
399
+ def CSV() -> Token:
433
400
  return Token(TokenType.KEYWORD, Keyword.CSV.value)
434
401
 
435
402
  def is_csv(self) -> bool:
436
403
  return self._type == TokenType.KEYWORD and self._value == Keyword.CSV.value
437
404
 
438
- @classmethod
439
- @property
440
- def TEXT(cls) -> Token:
405
+ @staticmethod
406
+ def TEXT() -> Token:
441
407
  return Token(TokenType.KEYWORD, Keyword.TEXT.value)
442
408
 
443
409
  def is_text(self) -> bool:
444
410
  return self._type == TokenType.KEYWORD and self._value == Keyword.TEXT.value
445
411
 
446
- @classmethod
447
- @property
448
- def FROM(cls) -> Token:
412
+ @staticmethod
413
+ def FROM() -> Token:
449
414
  return Token(TokenType.KEYWORD, Keyword.FROM.value)
450
415
 
451
416
  def is_from(self) -> bool:
452
417
  return self._type == TokenType.KEYWORD and self._value == Keyword.FROM.value
453
418
 
454
- @classmethod
455
- @property
456
- def HEADERS(cls) -> Token:
419
+ @staticmethod
420
+ def HEADERS() -> Token:
457
421
  return Token(TokenType.KEYWORD, Keyword.HEADERS.value)
458
422
 
459
423
  def is_headers(self) -> bool:
460
424
  return self._type == TokenType.KEYWORD and self._value == Keyword.HEADERS.value
461
425
 
462
- @classmethod
463
- @property
464
- def POST(cls) -> Token:
426
+ @staticmethod
427
+ def POST() -> Token:
465
428
  return Token(TokenType.KEYWORD, Keyword.POST.value)
466
429
 
467
430
  def is_post(self) -> bool:
468
431
  return self._type == TokenType.KEYWORD and self._value == Keyword.POST.value
469
432
 
470
- @classmethod
471
- @property
472
- def UNWIND(cls) -> Token:
433
+ @staticmethod
434
+ def UNWIND() -> Token:
473
435
  return Token(TokenType.KEYWORD, Keyword.UNWIND.value)
474
436
 
475
437
  def is_unwind(self) -> bool:
476
438
  return self._type == TokenType.KEYWORD and self._value == Keyword.UNWIND.value
477
439
 
478
- @classmethod
479
- @property
480
- def MATCH(cls) -> Token:
440
+ @staticmethod
441
+ def MATCH() -> Token:
481
442
  return Token(TokenType.KEYWORD, Keyword.MATCH.value)
482
443
 
483
444
  def is_match(self) -> bool:
484
445
  return self._type == TokenType.KEYWORD and self._value == Keyword.MATCH.value
485
446
 
486
- @classmethod
487
- @property
488
- def AS(cls) -> Token:
447
+ @staticmethod
448
+ def AS() -> Token:
489
449
  return Token(TokenType.KEYWORD, Keyword.AS.value)
490
450
 
491
451
  def is_as(self) -> bool:
492
452
  return self._type == TokenType.KEYWORD and self._value == Keyword.AS.value
493
453
 
494
- @classmethod
495
- @property
496
- def WHERE(cls) -> Token:
454
+ @staticmethod
455
+ def WHERE() -> Token:
497
456
  return Token(TokenType.KEYWORD, Keyword.WHERE.value)
498
457
 
499
458
  def is_where(self) -> bool:
500
459
  return self._type == TokenType.KEYWORD and self._value == Keyword.WHERE.value
501
460
 
502
- @classmethod
503
- @property
504
- def MERGE(cls) -> Token:
461
+ @staticmethod
462
+ def MERGE() -> Token:
505
463
  return Token(TokenType.KEYWORD, Keyword.MERGE.value)
506
464
 
507
465
  def is_merge(self) -> bool:
508
466
  return self._type == TokenType.KEYWORD and self._value == Keyword.MERGE.value
509
467
 
510
- @classmethod
511
- @property
512
- def CREATE(cls) -> Token:
468
+ @staticmethod
469
+ def CREATE() -> Token:
513
470
  return Token(TokenType.KEYWORD, Keyword.CREATE.value)
514
471
 
515
472
  def is_create(self) -> bool:
516
473
  return self._type == TokenType.KEYWORD and self._value == Keyword.CREATE.value
517
474
 
518
- @classmethod
519
- @property
520
- def VIRTUAL(cls) -> Token:
475
+ @staticmethod
476
+ def VIRTUAL() -> Token:
521
477
  return Token(TokenType.KEYWORD, Keyword.VIRTUAL.value)
522
478
 
523
479
  def is_virtual(self) -> bool:
524
480
  return self._type == TokenType.KEYWORD and self._value == Keyword.VIRTUAL.value
525
481
 
526
- @classmethod
527
- @property
528
- def DELETE(cls) -> Token:
482
+ @staticmethod
483
+ def DELETE() -> Token:
529
484
  return Token(TokenType.KEYWORD, Keyword.DELETE.value)
530
485
 
531
486
  def is_delete(self) -> bool:
532
487
  return self._type == TokenType.KEYWORD and self._value == Keyword.DELETE.value
533
488
 
534
- @classmethod
535
- @property
536
- def SET(cls) -> Token:
489
+ @staticmethod
490
+ def SET() -> Token:
537
491
  return Token(TokenType.KEYWORD, Keyword.SET.value)
538
492
 
539
493
  def is_set(self) -> bool:
540
494
  return self._type == TokenType.KEYWORD and self._value == Keyword.SET.value
541
495
 
542
- @classmethod
543
- @property
544
- def REMOVE(cls) -> Token:
496
+ @staticmethod
497
+ def REMOVE() -> Token:
545
498
  return Token(TokenType.KEYWORD, Keyword.REMOVE.value)
546
499
 
547
500
  def is_remove(self) -> bool:
548
501
  return self._type == TokenType.KEYWORD and self._value == Keyword.REMOVE.value
549
502
 
550
- @classmethod
551
- @property
552
- def CASE(cls) -> Token:
503
+ @staticmethod
504
+ def CASE() -> Token:
553
505
  return Token(TokenType.KEYWORD, Keyword.CASE.value)
554
506
 
555
507
  def is_case(self) -> bool:
556
508
  return self._type == TokenType.KEYWORD and self._value == Keyword.CASE.value
557
509
 
558
- @classmethod
559
- @property
560
- def WHEN(cls) -> Token:
510
+ @staticmethod
511
+ def WHEN() -> Token:
561
512
  return Token(TokenType.KEYWORD, Keyword.WHEN.value)
562
513
 
563
514
  def is_when(self) -> bool:
564
515
  return self._type == TokenType.KEYWORD and self._value == Keyword.WHEN.value
565
516
 
566
- @classmethod
567
- @property
568
- def THEN(cls) -> Token:
517
+ @staticmethod
518
+ def THEN() -> Token:
569
519
  return Token(TokenType.KEYWORD, Keyword.THEN.value)
570
520
 
571
521
  def is_then(self) -> bool:
572
522
  return self._type == TokenType.KEYWORD and self._value == Keyword.THEN.value
573
523
 
574
- @classmethod
575
- @property
576
- def ELSE(cls) -> Token:
524
+ @staticmethod
525
+ def ELSE() -> Token:
577
526
  return Token(TokenType.KEYWORD, Keyword.ELSE.value)
578
527
 
579
528
  def is_else(self) -> bool:
580
529
  return self._type == TokenType.KEYWORD and self._value == Keyword.ELSE.value
581
530
 
582
- @classmethod
583
- @property
584
- def END(cls) -> Token:
531
+ @staticmethod
532
+ def END() -> Token:
585
533
  return Token(TokenType.KEYWORD, Keyword.END.value)
586
534
 
587
535
  def is_end(self) -> bool:
588
536
  return self._type == TokenType.KEYWORD and self._value == Keyword.END.value
589
537
 
590
- @classmethod
591
- @property
592
- def NULL(cls) -> Token:
538
+ @staticmethod
539
+ def NULL() -> Token:
593
540
  return Token(TokenType.KEYWORD, Keyword.NULL.value)
594
541
 
595
542
  def is_null(self) -> bool:
596
543
  return self._type == TokenType.KEYWORD and self._value == Keyword.NULL.value
597
544
 
598
- @classmethod
599
- @property
600
- def IN(cls) -> Token:
545
+ @staticmethod
546
+ def IN() -> Token:
601
547
  return Token(TokenType.KEYWORD, Keyword.IN.value)
602
548
 
603
549
  def is_in(self) -> bool:
604
550
  return self._type == TokenType.KEYWORD and self._value == Keyword.IN.value
605
551
 
606
- @classmethod
607
- @property
608
- def PIPE(cls) -> Token:
552
+ @staticmethod
553
+ def PIPE() -> Token:
609
554
  return Token(TokenType.KEYWORD, Operator.PIPE.value)
610
555
 
611
556
  def is_pipe(self) -> bool:
612
557
  return self._type == TokenType.KEYWORD and self._value == Operator.PIPE.value
613
558
 
614
- @classmethod
615
- @property
616
- def DISTINCT(cls) -> Token:
559
+ @staticmethod
560
+ def DISTINCT() -> Token:
617
561
  return Token(TokenType.KEYWORD, Keyword.DISTINCT.value)
618
562
 
619
563
  def is_distinct(self) -> bool:
620
564
  return self._type == TokenType.KEYWORD and self._value == Keyword.DISTINCT.value
621
565
 
622
- @classmethod
623
- @property
624
- def LIMIT(cls) -> Token:
566
+ @staticmethod
567
+ def LIMIT() -> Token:
625
568
  return Token(TokenType.KEYWORD, Keyword.LIMIT.value)
626
569
 
627
570
  def is_limit(self) -> bool:
@@ -629,9 +572,8 @@ class Token:
629
572
 
630
573
  # End of file token
631
574
 
632
- @classmethod
633
- @property
634
- def EOF(cls) -> Token:
575
+ @staticmethod
576
+ def EOF() -> Token:
635
577
  return Token(TokenType.EOF)
636
578
 
637
579
  def is_eof(self) -> bool:
@@ -654,6 +596,10 @@ class Token:
654
596
  name_upper = name.upper()
655
597
  if hasattr(Token, name_upper):
656
598
  attr = getattr(Token, name_upper)
657
- if isinstance(attr, Token):
599
+ if callable(attr):
600
+ result = attr()
601
+ if isinstance(result, Token):
602
+ return result
603
+ elif isinstance(attr, Token):
658
604
  return attr
659
605
  return None
@@ -66,7 +66,7 @@ class Tokenizer:
66
66
 
67
67
  def _get_next_token(self, last: Optional[Token] = None) -> Optional[Token]:
68
68
  if self._walker.is_at_end:
69
- return Token.EOF
69
+ return Token.EOF()
70
70
  return (
71
71
  self._comment() or
72
72
  self._whitespace() or
@@ -144,7 +144,7 @@ class Tokenizer:
144
144
  if self._walker.opening_brace():
145
145
  yield Token.F_STRING(self._walker.get_string(position), quote_char)
146
146
  position = self._walker.position
147
- yield Token.OPENING_BRACE
147
+ yield Token.OPENING_BRACE()
148
148
  self._walker.move_next() # skip the opening brace
149
149
  position = self._walker.position
150
150
 
@@ -155,7 +155,7 @@ class Tokenizer:
155
155
  else:
156
156
  break
157
157
  if self._walker.closing_brace():
158
- yield Token.CLOSING_BRACE
158
+ yield Token.CLOSING_BRACE()
159
159
  self._walker.move_next() # skip the closing brace
160
160
  position = self._walker.position
161
161
  break
@@ -171,7 +171,7 @@ class Tokenizer:
171
171
  while not self._walker.is_at_end and self._walker.check_for_whitespace():
172
172
  self._walker.move_next()
173
173
  found_whitespace = True
174
- return Token.WHITESPACE if found_whitespace else None
174
+ return Token.WHITESPACE() if found_whitespace else None
175
175
 
176
176
  def _number(self) -> Optional[Token]:
177
177
  start_position = self._walker.position
@@ -871,10 +871,9 @@ class TestRunner:
871
871
  )
872
872
  await match.run()
873
873
  results = match.results
874
- assert len(results) == 3
875
- assert results[0] == {"name1": "Person 1", "name2": "Person 2"}
876
- assert results[1] == {"name1": "Person 1", "name2": "Person 3"}
877
- assert results[2] == {"name1": "Person 2", "name2": "Person 3"}
874
+ # With * meaning 0+ hops, each person also matches itself (zero-hop)
875
+ # Person 1→1, 1→2, 1→3, Person 2→2, 2→3, Person 3→3 + bidirectional = 7
876
+ assert len(results) == 7
878
877
 
879
878
  @pytest.mark.asyncio
880
879
  async def test_match_with_double_graph_pattern(self):
@@ -1175,7 +1174,8 @@ class TestRunner:
1175
1174
  )
1176
1175
  await match.run()
1177
1176
  results = match.results
1178
- assert len(results) == 6
1177
+ # With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
1178
+ assert len(results) == 10
1179
1179
 
1180
1180
  @pytest.mark.asyncio
1181
1181
  async def test_return_match_pattern_with_variable_length_relationships(self):
@@ -1213,7 +1213,8 @@ class TestRunner:
1213
1213
  )
1214
1214
  await match.run()
1215
1215
  results = match.results
1216
- assert len(results) == 6
1216
+ # With *0..3: Person 1 has 4 matches (0,1,2,3 hops), Person 2 has 3, Person 3 has 2, Person 4 has 1 = 10 total
1217
+ assert len(results) == 10
1217
1218
 
1218
1219
  @pytest.mark.asyncio
1219
1220
  async def test_statement_with_graph_pattern_in_where_clause(self):
@@ -1332,4 +1333,6 @@ class TestRunner:
1332
1333
  )
1333
1334
  await match.run()
1334
1335
  results = match.results
1335
- assert len(results) == 2
1336
+ # With * meaning 0+ hops, Employee 1 (CEO) also matches itself (zero-hop)
1337
+ # Employee 1→1 (zero-hop), 2→1, 3→2→1, 4→2→1 = 4 results
1338
+ assert len(results) == 4