summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLV-426 <lv-426@taproot.org.il>2015-03-04 13:55:31 +0200
committerLV-426 <lv-426@taproot.org.il>2015-03-04 13:55:31 +0200
commitfc494bab65876068bc47f808d7a00e1fd25f630b (patch)
treebb12176d389364baf6861ceab841f34340e0ed75 /src
parentd21464f4b8063945e7657e71832c1b4c3e5b9e86 (diff)
wip: cypher language parser
Diffstat (limited to 'src')
-rw-r--r--src/server/neo4j_cypher.py312
1 files changed, 312 insertions, 0 deletions
diff --git a/src/server/neo4j_cypher.py b/src/server/neo4j_cypher.py
new file mode 100644
index 00000000..7eb609db
--- /dev/null
+++ b/src/server/neo4j_cypher.py
@@ -0,0 +1,312 @@
+"""
+Cypher language parser
+ - clear logical separation between lexing/parsing still missing
+ - e_XXX class object should be considered internal
+"""
+import re
+
+#
+# Tokens
+#
+tok_set__quote = ['`', '\'']
+
+#
+# Expressions
+#
+class e_abs(object):
+ """
+ Abstract expression
+ """
+ def __init__(self): pass
+
+class e_keyword(e_abs):
+
+ def __init__(self, keyword):
+ super(e_keyword, self).__init__()
+ self.keyword = keyword
+
+ def __str__(self): return self.keyword
+
+class e_value(e_abs):
+
+ def __init__(self):
+ super(e_value, self).__init__()
+
+ @classmethod
+ def rgx(self, g_name='value'):
+ return '(?P<%s>[\w\d]+)' % (g_name)
+
+class e_identifier(e_abs):
+
+ def __init__(self):
+ super(e_identifier, self).__init__()
+
+ @classmethod
+ def rgx(self, g_name='ident'):
+ return '(?P<%s>[\w_]+)' % (g_name)
+
+#
+# Parse Tree
+#
+class pt_abs_node(object):
+ """
+ Abstract parse tree node
+ """
+ def __init__(self):
+ self.sub_exp_set = []
+ self.id = None
+ self.parent = None
+
+ def to_tree_str(self):
+ return '\n'.join(self.__to_tree_line_arr_rec(0))
+
+ def __to_tree_line_arr_rec(self, depth):
+ ret = ['%s%s' % (('.' + 2 * ' ') * depth, str(self))]
+ for e in self.sub_exp_set:
+ if isinstance(e, e_abs):
+ ret += ['%s' % (e)]
+ else:
+ ret += (e.__to_tree_line_arr_rec(depth + 1))
+ return ret
+
+ def spawn_child(self, lex_token_type):
+ """
+ Spawn child node:
+ - set child's parent ref. to this object
+ - add child node as sub exp.
+ """
+ child_node = lex_token_type()
+ child_node.parent = self
+ self.sub_exp_set.append(child_node)
+ return child_node
+#
+# Cypher patterns
+#
+class pt_root(pt_abs_node):
+
+ def __init__(self):
+ super(pt_root, self).__init__()
+
+ def clause_set_by_kw(self, keyword):
+ """
+ select query clause set by keyword: eg. 'create' -> [pattern__cypher_clause, ...]
+ """
+ ret = []
+ for e in self.sub_exp_set():
+ if e.__class__ != pattern__cypher_clause:
+ continue
+ if e.keyword == keyword:
+ ret += e
+ return ret
+
+ def __str__(self): return 'root'
+
+class pattern__node(pt_abs_node): # node pattern: '(...)'
+
+ def __init__(self):
+ super(pattern__node, self).__init__()
+ self.label_set = []
+
+ def __str__(self):
+ return '(%s%s)' % (self.id if self.id else '',
+ ':' + ':'.join(self.label_set) if len(self.label_set) > 0 else '')
+
+ @classmethod
+ def rgx(self, g_name): return '\((?P<%s>.*?)\)' % (g_name)
+
+class pattern__rel(pt_abs_node): # rel pattern: '[...]'
+
+ def __init__(self):
+ super(pattern__rel, self).__init__()
+ self.type = None
+
+ def __str__(self):
+ return '-[%s%s]-' % (self.id if self.id else '',
+ ':' + self.type if self.type else '')
+
+ @classmethod
+ def rgx(self, g_name): return '-\[(?P<%s>.*?)\]->?' % (g_name)
+
+class pattern__path(pt_abs_node): # path pattern: '()-[]-()'
+
+ def __init__(self):
+ super(pattern__path, self).__init__()
+ self.n_src = None
+ self.rel = None
+ self.n_dst = None
+
+ def __str__(self): return '()-[]-()'
+
+class pattern__cypher_clause(pt_abs_node):
+
+ def __init__(self):
+ super(pattern__cypher_clause, self).__init__()
+ self.keyword = None
+
+ def __str__(self): return self.keyword
+
+class pattern__attr_set(pt_abs_node): # attr-set pattern: {k:v, ...}
+
+ def __init__(self):
+ super(pattern__attr_set, self).__init__()
+ self.key_val_dict = {}
+
+ def __str__(self): return '{%s}' % (self.id if self.id else ', '.join(['%s: %s' % (k, v) for k, v in self.key_val_dict.items()]))
+
+class Cypher_Parser(object):
+ """
+ Neo4J Cypher language parser
+ """
+
+ def parse_expression(self, input):
+ """
+ @return: Cypher parse tree
+ """
+ parent_node = pt_root()
+ self.__parse(input, parent_node)
+ return parent_node
+
+ def __parse(self, input, cur_node, lex_tok_type=None):
+
+ def rgx__suffix(): # suffix regular expression, '$' terminated
+ return '(?P<suffix>.*)$'
+
+ if None == input or 0 == len(input): # end of input or opt regex group not found
+ return
+
+ if input[0] in tok_set__quote: # handle various types of quotation
+ quote_tok = input[0]
+ rgx_quote = r'^%s(?P<quoted_exp>.+?)%s%s' % (quote_tok, quote_tok, rgx__suffix())
+ m = re.match(rgx_quote, input)
+ self.__parse(m.group('quoted_exp'), cur_node, e_value)
+ self.__parse(m.group('suffix'), cur_node)
+ return
+
+ if ' ' == input[0]: # consume
+ self.__parse(input[1:], cur_node)
+ return
+
+ if lex_tok_type == e_identifier:
+ cur_node.id = input
+ return
+
+ if lex_tok_type == e_value:
+ rgx_value = r'^%s%s' % (e_value.rgx(), rgx__suffix())
+ m = re.match(rgx_value, input)
+ val = m.group('value')
+ p_node_type = cur_node.__class__
+ if p_node_type == pattern__rel:
+ cur_node.type = val
+ if p_node_type == pattern__node:
+ cur_node.label_set.append(val)
+
+ self.__parse(m.group('suffix'), cur_node) # handle label sets: A:B:...
+ return
+
+ for kw in ['create', 'match']: # handle keywords
+ if input.startswith(kw):
+ cur_node = cur_node.spawn_child(pattern__cypher_clause)
+ cur_node.keyword = kw
+ self.__parse(input[len(kw):], cur_node)
+ return
+
+ if ':' == input[0]:
+ self.__parse(input[1:], cur_node, e_value)
+ return
+
+ if ',' == input[0]: # open sibling
+ self.__parse(input[1:], cur_node.parent)
+ return
+
+ if '{' == input[0]: # open param or attr-set
+ cur_node = cur_node.spawn_child(pattern__attr_set)
+ rgx_attr_set_or_param = r'^((%s:)|(%s))%s' % (e_identifier.rgx('ident_attr_set'),
+ e_identifier.rgx('ident_param'),
+ rgx__suffix())
+ m = re.match(rgx_attr_set_or_param, input[1:])
+ if m.group('ident_param'):
+ self.__parse(m.group('ident_param'), cur_node, e_identifier)
+ self.__parse(m.group('suffix'), cur_node)
+ return
+ if m.group('ident_attr_set'):
+ self.__parse(input[1:], cur_node)
+ return
+
+ assert False, 'failed parsing attr_set or identifier, input: %s' % (input)
+
+ if '(' == input[0]: # open node or path
+
+ if cur_node.__class__ == pattern__path:
+ cur_node = cur_node.spawn_child(pattern__node)
+ self.__parse(input[1:], cur_node)
+ return
+
+ rgx_path = r'^%s%s%s' % (pattern__node.rgx('src'),
+ pattern__rel.rgx('rel'),
+ pattern__node.rgx('dst'))
+
+ m = re.match(rgx_path, input)
+ if m:
+ cur_node = cur_node.spawn_child(pattern__path)
+ self.__parse(input, cur_node)
+ return
+
+ cur_node = cur_node.spawn_child(pattern__node)
+ self.__parse(input[1:], cur_node)
+ return
+
+ if input.startswith('-['): # open rel
+ cur_node = cur_node.spawn_child(pattern__rel)
+ self.__parse(input[2:], cur_node)
+ return
+
+ if ')' == input[0]: # close node or path
+ self.__parse(input[1:], cur_node.parent)
+
+
+ if input.startswith(']-'): # close rel
+ if input.startswith(']->'): # close directional-rel
+ self.__parse(input[3:], cur_node.parent)
+ return
+
+ self.__parse(input[2:], cur_node.parent)
+ return
+
+ if '}' == input[0]: # close attr set
+ self.__parse(input[1:], cur_node.parent)
+ return
+
+ if cur_node.__class__ == pattern__attr_set: # key-value pair
+ rgx_kv_pair = r'^%s:\s?((%s)|(\'%s\'))%s' % (e_identifier.rgx(),
+ e_value.rgx('val'),
+ e_value.rgx('qval'), # quoted
+ rgx__suffix())
+ m = re.match(rgx_kv_pair, input)
+ key = m.group('ident')
+ val = m.group('val') if m.group('val') else m.group('qval')
+ cur_node.key_val_dict[key] = val
+ self.__parse(m.group('suffix'), cur_node)
+ return
+
+ if cur_node.__class__ in [pattern__rel, pattern__node]: # node or rel
+
+ rgx_opt_id = r'^%s%s' % (e_identifier.rgx('ident'), rgx__suffix())
+ m = re.match(rgx_opt_id, input)
+ if m:
+ self.__parse(m.group('ident'), cur_node, e_identifier)
+ self.__parse(m.group('suffix'), cur_node)
+ return
+
+ rgx_opt_lable_set = r'^:%s%s' % (e_value.rgx(), rgx__suffix())
+ m = re.match(rgx_opt_lable_set, input)
+ if m:
+ self.__parse(m.group('value'), cur_node, e_value)
+ self.__parse(m.group('suffix'), cur_node)
+ return
+
+ rgx_opt_attr_set = r'^(?P<attr_set_or_ident>\{.+?\})%s' % (rgx__suffix())
+ m = re.match(rgx_opt_attr_set, input)
+ if m:
+ self.__parse(m.group('attr_set_or_ident'), cur_node)
+ self.__parse(m.group('suffix'), cur_node)
+ return