diff options
| author | LV-426 <lv-426@taproot.org.il> | 2015-03-15 18:31:27 +0200 |
|---|---|---|
| committer | LV-426 <lv-426@taproot.org.il> | 2015-03-15 18:31:27 +0200 |
| commit | 8ef2e466ebfd663535b73c44614d7f4e2fae3920 (patch) | |
| tree | 49403bdf3cb16375f7e4dc4c61e3b601503653bc /src | |
| parent | bc514e3b6f254a17801c666b54d961615e90994e (diff) | |
wip: cypher parser
Diffstat (limited to 'src')
| -rw-r--r-- | src/server/neo4j_cypher.py | 608 |
1 files changed, 343 insertions, 265 deletions
diff --git a/src/server/neo4j_cypher.py b/src/server/neo4j_cypher.py index ddacd05e..32a16356 100644 --- a/src/server/neo4j_cypher.py +++ b/src/server/neo4j_cypher.py @@ -6,7 +6,7 @@ Cypher language parser import re from collections import defaultdict import logging -import Queue +from enum import Enum # # Tokens @@ -23,30 +23,55 @@ tok_set__kw__write = [ 'foreach' ] -tok_set__kw__supported = [ +tok_set__kw__supported = [ # order critical 'create', - 'match', 'optional match', - 'return', + 'match', ] -tok_set__kw__unsupported = [ +tok_set__kw__unsupported = [ # these are treated as generic clauses, capturing until the next keyword 'delete', 'foreach', 'limit', 'merge', - 'order', + 'order by', + 'return', 'remove', 'set', 'skip', 'start', 'union', - 'with', 'where', + 'with', ] +tok_set__kw__all = tok_set__kw__supported + tok_set__kw__unsupported + log = logging.getLogger('rhizi') +class Query_Struct_Type(Enum): + unkown = 1 + r = 2 + w = 3 + rw = 4 + + def __add__(self, other): + if self == other or self == pt_root.Query_Struct_Type.rw: + return self + if self == Query_Struct_Type.unkown: + return other + if self == Query_Struct_Type.r and other != Query_Struct_Type.r: + return Query_Struct_Type.rw + if self == Query_Struct_Type.w and other != Query_Struct_Type.w: + return Query_Struct_Type.rw + + def __str__(self): + if self == Query_Struct_Type.unkown: return 'unkown' + if self == Query_Struct_Type.r: return 'r' + if self == Query_Struct_Type.w: return 'w' + if self == Query_Struct_Type.rw: return 'rw' + assert False + # # Parse Tree # @@ -58,8 +83,7 @@ class pt_abs_node(object): self.value = None self.parent = None - def __iter__(self): - return iter([]) + def __iter__(self): return iter([]) # enable tree walking over pt_abs_node leaf nodes def collapse(self, n_type): return self.__collapse_common([n_type]) @@ -83,8 +107,18 @@ class pt_abs_node(object): return ret - def str__body(self): - return self.value if self.value else '' + def rotate__pin_under_new_parent(self, n_type): # pin current node under a new parent node + n_parent = self.parent + n_set = n_type() + n_set.parent = n_parent + n_set.sub_exp_set.append(self) + for i in range(0, len(n_parent.sub_exp_set)): + if self != self.parent.sub_exp_set[i]: # lookup self in parent child set + continue + self.parent.sub_exp_set[i] = n_set + return n_set + + def str__body(self): return self.value if self.value else '' def str__tok_open(self): return '' @@ -105,38 +139,22 @@ class pt_abs_composite_node(pt_abs_node): """ return self.__str__cypher_query() - def str__tok_sibling_delim(self): return '' + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return '' def __str__cypher_query(self): - def f_pre(n, ctx): - ctx[0] += n.str__tok_open() - - def f(n, ctx): - ctx[0] += n.str__body() + def f_pre(n, ctx): ctx[0] += n.str__tok_open() + def f_visit(n, ctx): ctx[0] += n.str__body() + def f_post(n, ctx): ctx[0] += n.str__tok_close() + def f_cascade(n, ctx): return True - def f_post(n, ctx): - ctx[0] += n.str__tok_close() - - def f_cascade(n, ctx): - return True - def f_inter(parent, n, n_next, ctx): - ctx[0] += parent.str__tok_sibling_delim() + ctx[0] += parent.str__tok_sibling_delim(n, n_next) ctx = [''] - self.tree_walk__pre(f_pre, f, f_post, f_cascade, f_inter, ctx) + self.tree_walk__pre(f_pre, f_visit, f_post, f_cascade, f_inter, ctx) return ''.join(ctx) - def __str__cypher_query__sub_node_set(self): - return ''.join([self.__str__cypher_query__sub_node_single(e) for e in self]) - - def __str__cypher_query__sub_node_single(self, e): - if isinstance(e, pt_abs_composite_node): # leaf node - return e.__str__cypher_query() - else: - return '%s%s%s' % (e.str__tok_open(), e.str__body(), e.str__tok_close()) - def __struct_as_arr__type(self, depth=0, depth_delim=''): ret = [] @@ -152,26 +170,13 @@ class pt_abs_composite_node(pt_abs_node): return ret - def __iter__(self): - """ - iterate over sub nodes - """ + def __iter__(self): # iterate over sub nodes for exp in self.sub_exp_set: yield exp def assert_child_spawn_type(self, n_type): # provide hook for child spawning assertions pass - def rotate__pin_under_set_node(self): - n_parent = self.parent - n_set = e_set() - n_set.parent = n_parent - n_set.sub_exp_set.append(self) - for i in range(0, len(n_parent.sub_exp_set)): - if self != self.parent.sub_exp_set[i]: continue - self.parent.sub_exp_set[i] = n_set - return n_set - def spawn_child(self, n_type, *args, **kwargs): """ Spawn child node: @@ -188,9 +193,8 @@ class pt_abs_composite_node(pt_abs_node): return child_node def spawn_sibling(self, n_type=None, *args, **kwargs): - - if not n_type: n_type = self.__class__ - + if not n_type: # by default spawn siblig of the same type as self + n_type = self.__class__ return self.parent.spawn_child(n_type, *args, **kwargs) def child_node_by_type(self, node_type): @@ -201,11 +205,11 @@ class pt_abs_composite_node(pt_abs_node): ret += [exp] return ret - def tree_walk__pre(self, f_pre, f, f_post, f_cascade, f_inter, ctx=None): + def tree_walk__pre(self, f_pre, f_visit, f_post, f_cascade, f_inter, ctx=None): # tree walk, preorder def walk__pre_rec(n): f_pre(n, ctx) - f(n, ctx) + f_visit(n, ctx) if f_cascade(n, ctx): sub_n_set = [e for e in n] @@ -213,25 +217,15 @@ class pt_abs_composite_node(pt_abs_node): for i in range(0, sub_exp_set_len): e = sub_n_set[i] walk__pre_rec(e) - if i + 1 < sub_exp_set_len: + if i + 1 < sub_exp_set_len: # call f_inter in between siblings e_first = e - e_next = sub_n_set[i+1] + e_next = sub_n_set[i + 1] f_inter(n, e_first, e_next, ctx) f_post(n, ctx) walk__pre_rec(self) - def list_interleave(self, l, f): - l_len = len(l) - ret = [] - for i in range(0,l_len): - if i + 1 >= l_len: continue - e_first = l[i] - e_second = l[i+1] - ret += [e_first] + f(e_first, e_second) - return ret - class e_keyword(pt_abs_node): def __init__(self, keyword): @@ -250,19 +244,19 @@ class e_value(pt_abs_node): @classmethod def rgx__unquoted(self, g_name='value'): - return '(?P<%s>[\w\d]+)' % (g_name) + return '(?P<%s>[\w\d_]+)' % (g_name) @classmethod def rgx__quoted(self, g_name='value', quote_tok='\''): - return '%s(?P<%s>[\w\d\s]+)%s' % (quote_tok, g_name, quote_tok) + return '%s(?P<%s>[\w\d_\-\s]+)%s' % (quote_tok, g_name, quote_tok) def str__tok_open(self): - if not self.quoted: return '' - return self.quote_tok + if self.quoted: return self.quote_tok + return '' def str__tok_close(self): - if not self.quoted: return '' - return self.quote_tok + if self.quoted: return self.quote_tok + return '' class e_ident(pt_abs_node): # identifier @@ -272,6 +266,14 @@ class e_ident(pt_abs_node): # identifier def rgx(self, g_name='ident'): return '(?P<%s>[\w_]+)' % (g_name) +class e_param(e_ident): # identifier + + def __init__(self): super(e_param, self).__init__() + + def str__tok_open(self): return '{' + + def str__tok_close(self): return '}' + class e_function(pt_abs_node): # identifier def __init__(self): super(e_function, self).__init__() @@ -284,7 +286,11 @@ class e_set(pt_abs_composite_node): # identifier def __init__(self): super(e_set, self).__init__() - def str__tok_sibling_delim(self): return ', ' + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return ', ' + +class op_dot(pt_abs_composite_node): + + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return '.' # # Cypher patterns @@ -299,12 +305,6 @@ class pt_root(pt_abs_composite_node): super(pt_root, self).__init__() self.kw_to_clause_set_map = defaultdict(list) - def spawn_child(self, lex_token_type, keyword): # pt_root: map by keyword on spawn_child() - ret = super(pt_root, self).spawn_child(lex_token_type) - self.kw_to_clause_set_map[keyword].append(ret) # update kw clause map - - return ret - def assert_child_spawn_type(self, n_type): assert issubclass(n_type, e_clause) @@ -314,6 +314,22 @@ class pt_root(pt_abs_composite_node): """ return self.kw_to_clause_set_map[keyword] + @property + def query_struct_type(self): + """ + calc query_structure_type: read|write|read-write + """ + ret = Query_Struct_Type.unkown + for kw, _clause_set in self: + if kw in self.q_tree.tok_set__kw__write: + ret += self.q_tree.Query_Struct_Type.w + if kw == 'match': + ret += self.q_tree.Query_Struct_Type.r + + return ret + + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return ' ' + class e_clause(pt_abs_composite_node): def __init__(self): @@ -321,66 +337,69 @@ class e_clause(pt_abs_composite_node): class e_clause__match(e_clause): - def __init__(self): super(e_clause, self).__init__() + def __init__(self): + super(e_clause__match, self).__init__() + self.is_optional = False def assert_child_spawn_type(self, n_type): - assert n_type in [e_keyword, p__node, p__path], n_type + assert n_type in [e_keyword, p_node, p_path], n_type class e_clause__create(e_clause): - def __init__(self): super(e_clause, self).__init__() + def __init__(self): super(e_clause__create, self).__init__() def assert_child_spawn_type(self, n_type): - assert n_type in [e_keyword, p__node, p__path] + assert n_type in [e_keyword, p_node, p_path] -class e__attr_set(e_set): # attr-set pattern: {k:v, ...} +class e_clause__where(e_clause): - def __init__(self): super(e__attr_set, self).__init__() + def __init__(self): super(e_clause__where, self).__init__() - def __str__cypher_query__sub_node_set(self): - return ', '.join([e.__str__cypher_query__sub_node_single() for e in self]) +class e_attr_set(e_set): # attr-set pattern: {k:v, ...} + + def __init__(self): super(e_attr_set, self).__init__() def str__tok_open(self): return '{' def str__tok_close(self): return '}' - - def str__tok_sibling_delim(self): return ', ' + + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return ', ' def assert_child_spawn_type(self, n_type): - assert n_type in [e_ident, e__kv_pair] + assert n_type in [e_ident, e_kv_pair] class e_label_set(e_set): def __init__(self): super(e_label_set, self).__init__() - def str__tok_open(self): return ':' - - def str__tok_close(self): return ' ' - - def str__tok_sibling_delim(self): return ':' + def str__tok_open(self): return ':' # required when no identifier is present, eg. '()-[:A]-()' - def __str__cypher_query__sub_node_set(self): - return ':'.join([e.__str__cypher_query__sub_node_single() for e in self]) + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return ':' def assert_child_spawn_type(self, n_type): assert n_type in [e_value] -class e__kv_pair(pt_abs_composite_node): # key value pair +class e_kv_pair(pt_abs_composite_node): # key value pair - def __init__(self): super(e__kv_pair, self).__init__() + def __init__(self): super(e_kv_pair, self).__init__() def assert_child_spawn_type(self, n_type): - assert n_type in [e_value, e_ident] + assert n_type in [e_value, e_ident, e_param] - def str__tok_sibling_delim(self): return ': ' + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): return ': ' -class p__node(pt_abs_composite_node): # node pattern: '(...)' +class p_node(pt_abs_composite_node): # node pattern: '(...)' """ node pattern """ def __init__(self): - super(p__node, self).__init__() + super(p_node, self).__init__() + + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): + if isinstance(sib_a, e_ident) and isinstance(sib_b, e_label_set): + return '' # handled by label_set + return ' ' def str__tok_open(self): return '(' @@ -393,23 +412,30 @@ class p__node(pt_abs_composite_node): # node pattern: '(...)' @classmethod def rgx(self, g_name): return '\((?P<%s>[^\(\)]*?)\)' % (g_name) -class p__rel(pt_abs_composite_node): # rel pattern: '[...]' +class p_rel(pt_abs_composite_node): # rel pattern: '[...]' """ relationship pattern """ - def __init__(self): super(p__rel, self).__init__() + def __init__(self): + super(p_rel, self).__init__() + self.is_directional = False - def __str__(self): - return '%s%s' % (self.id if self.id else '', - ':' + self.type if self.type else '') + def str__tok_sibling_delim(self, sib_a=None, sib_b=None): + if isinstance(sib_a, e_ident) and isinstance(sib_b, e_label_set): + return '' # handled by label_set + return ' ' def str__tok_open(self): return '-[' - def str__tok_close(self): return ']-' + def str__tok_close(self): + if self.is_directional == True: + return ']->' + else: + return ']-' @property - def type(self): + def type(self): # the single element equivalent of node label sets val_sub_n_set = self.child_node_by_type(e_value) assert len(val_sub_n_set) >= 1 @@ -421,20 +447,24 @@ class p__rel(pt_abs_composite_node): # rel pattern: '[...]' @classmethod def rgx(self, g_name): return '-\[(?P<%s>[^\[\]]*?)\]->?' % (g_name) -class p__path(pt_abs_composite_node): # path pattern: '()-[]-()' +class p_path(pt_abs_composite_node): # path pattern: '()-[]-()' """ path pattern """ - def __init__(self): super(p__path, self).__init__() - + def __init__(self): super(p_path, self).__init__() class Cypher_Parser(object): """ - Neo4J Cypher language parser + Neo4J Cypher language parser - conventions: + - read_xxx functions: should adjust argument node without modifying parse tree + - parse_xxx functions: + - caller should spawn correct current node + - should handle current node closing + - default parse case should recurse up via parent node """ def __init__(self): - pass + self.rgx__suffix = '(?P<suffix>.*)$' # suffix regular expression, '$' terminated def parse_expression(self, input): """ @@ -458,7 +488,7 @@ class Cypher_Parser(object): def first_sibling_root(self, n): ret = n - while not ret.__class__ in [e__attr_set, + while not ret.__class__ in [e_attr_set, e_clause]: ret = ret.parent @@ -468,205 +498,253 @@ class Cypher_Parser(object): if isinstance(n, e_label_set): pass - def __parse(self, input, n_cur): + def read__e_ident(self, input, n_ident): + rgx_ident = r'^%s%s' % (e_ident.rgx(), self.rgx__suffix) + m = self.__match(rgx_ident, input) + n_ident.value = m.group('ident') + return m.group('suffix') - rgx__suffix = '(?P<suffix>.*)$' # suffix regular expression, '$' terminated + def read__e_value(self, input, n_value): + if input[0] in tok_set__quote: # quoted value + quote_tok = input[0] + rgx_value = r'^%s%s' % (e_value.rgx__quoted(quote_tok=quote_tok), + self.rgx__suffix) + n_value.quoted = True + n_value.quote_tok = quote_tok + else: # non quoted value + rgx_value = r'^%s%s' % (e_value.rgx__unquoted(), self.rgx__suffix) - def parse__node(input, n_cur): - n_cur = n_cur.spawn_child(p__node) + m = self.__match(rgx_value, input) + n_value.value = m.group('value') + return m.group('suffix') + + def parse__e_attr_set(self, input, n_cur): + if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + + if '{' == input[0]: # open param + n_cur = n_cur.spawn_child(e_param) return self.__parse(input[1:], n_cur) - def parse__node_or_path(input, n_cur): - # path pattern - rgx_path = r'^%s%s%s' % (p__node.rgx('src'), - p__rel.rgx('rel'), - p__node.rgx('dst')) - m = re.match(rgx_path, input) - if m: - n_cur = n_cur.spawn_child(p__path) - n_cur = n_cur.spawn_child(p__node) - return self.__parse(input[1:], n_cur) + if '}' == input[0]: # close attr-set + n_cur = n_cur.collapse(e_attr_set).parent + return self.__parse(input[1:], n_cur) - # node pattern - rgx_path = r'^%s' % (p__node.rgx('src')) - m = re.match(rgx_path, input) - if m: - return parse__node(input, n_cur) + rgx_attr_set_or_param = r'^((%s:)|(%s))' % (e_ident.rgx('kv_pair__key'), + e_ident.rgx('ident')) + m = self.__match(rgx_attr_set_or_param, input) + if m.group('ident'): + n_cur = n_cur.spawn_child(e_ident) + return self.__parse(input, n_cur) + if m.group('kv_pair__key'): + n_cur = n_cur.spawn_child(e_kv_pair) + return self.__parse(input, n_cur) - assert False, 'failed parsing attr_set or identifier, input: "%s"' % (input) + n_cur = n_cur.parent + return self.__parse(input, n_cur) - def parse__rel_close(input, n_cur): - n_cur = n_cur.collapse(p__rel).parent - if input.startswith(']->'): # close directional-rel - return self.__parse(input[3:], n_cur) - if input.startswith(']-'): # close directional-rel - return self.__parse(input[2:], n_cur) + def parse__e_clause_create_or_match(self, input, n_cur): + if ' ' == input[0]: return self.parse__e_clause_create_or_match(input[1:], n_cur) # consume - assert False, 'failed parsing rel termination' + if '(' == input[0]: return self.parse__node_or_rel(input, n_cur) # open node or path - if None == input or 0 == len(input): # end of input or opt regex group not found - return + if ',' == input[0]: # open sibling + assert len(n_cur.sub_exp_set) >= 2 and e_keyword == n_cur.sub_exp_set[0].__class__ - if isinstance(n_cur, pt_root): - # - # keywords - # - for kw in tok_set__kw__supported: # handle keywords - if input.startswith(kw): - kw_clause_type = globals().get('e_clause__' + kw) - clause_type = kw_clause_type if kw_clause_type else e_clause - n_cur = n_cur.spawn_child(clause_type, kw) - n_cur.spawn_child(e_keyword, kw) - return self.__parse(input[len(kw):], n_cur) + last_child = n_cur.sub_exp_set[-1] + assert last_child.__class__ in [p_path, p_node] - for kw in tok_set__kw__unsupported: # currently unsupported keywords - if input.startswith(kw): - assert False, 'cypher parse: unsupported keyword: \'%s\', input: "%s"' % (kw, input) + n_cur = last_child.rotate__pin_under_new_parent(e_set) + return self.__parse(input[1:], n_cur) - assert False + n_cur = n_cur.parent + return self.__parse(input, n_cur) - if isinstance(n_cur, e_clause): - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + def parse__e_clause__common(self, input, n_cur): + assert len(n_cur.sub_exp_set) == 2 + assert n_cur.sub_exp_set[1].__class__ == e_value - if '(' == input[0]: return parse__node_or_path(input, n_cur) # open node or path + n_val = n_cur.sub_exp_set[1] + for kw in tok_set__kw__all: + if input.startswith(kw + ' '): + n_val.value = n_val.value.strip() # trim before handling new kw + n_cur = n_cur.parent + return self.__parse(input, n_cur) - if ',' == input[0]: # open sibling + n_val.value += input[0] + if len(input) == 1: # trim on EOI + n_val.value = n_val.value.strip() + return self.__parse(input[1:], n_cur) - assert len(n_cur.sub_exp_set) >= 2 and e_keyword == n_cur.sub_exp_set[0].__class__ + def parse__e_ident(self, input, n_cur): + suffix = self.read__e_ident(input, n_cur) + n_cur = n_cur.parent + return self.__parse(suffix, n_cur) - last_child = n_cur.sub_exp_set[-1] - assert last_child.__class__ in [p__path, p__node] + def parse__e_kv_pair(self, input, n_cur): + if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume - n_cur = last_child.rotate__pin_under_set_node() - return self.__parse(input[1:], n_cur) + if ',' == input[0]: # open sibling + n_cur = n_cur.spawn_sibling() + return self.__parse(input[1:], n_cur) - if isinstance(n_cur, e_ident): - rgx_ident = r'^%s%s' % (e_ident.rgx(), rgx__suffix) - m = self.__match(rgx_ident, input) + if ':' == input[0]: # open sibling + return self.parse__e_val_or_param(input[1:], n_cur) - n_cur.value = m.group('ident') - return self.__parse(m.group('suffix'), n_cur.parent) + if not n_cur.child_node_by_type(e_ident): # kv_pair key yet to be set + n_cur = n_cur.spawn_child(e_ident) + return self.__parse(input, n_cur) - # - # e_set & subclasses - ordered by specificity - # - if isinstance(n_cur, e_label_set): - if ' ' == input[0]: # close label set - return self.__parse(input[1:], n_cur.collapse_set([p__rel, p__node])) - if ':' == input[0]: # append to label set - n_cur = n_cur.spawn_child(e_value) - return self.__parse(input[1:], n_cur) - if ')' == input[0]: # close node/path - n_cur = n_cur.collapse(p__node).parent - return self.__parse(input[1:], n_cur) - if ']' == input[0]: return parse__rel_close(input, n_cur) # close rel - assert False + if len(n_cur.sub_exp_set) == 2: return self.__parse(input, n_cur.parent) # collapse - if isinstance(n_cur, e__attr_set): - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + def parse__e_val_or_param(self, input, n_cur): + if ' ' == input[0]: return self.parse__e_val_or_param(input[1:], n_cur) # consume - if '}' == input[0]: # close attr-set - n_cur = n_cur.collapse(e__attr_set).parent - return self.__parse(input[1:], n_cur) + if '{' == input[0]: + n_cur = n_cur.spawn_child(e_param) + return self.parse__e_param(input[1:], n_cur) - if ')' == input[0]: return parse__node(input[1:], n_cur.parent) # collapse + n_cur = n_cur.spawn_child(e_value) + return self.__parse(input, n_cur) - rgx_attr_set_or_param = r'^((%s:)|(%s))' % (e_ident.rgx('kv_pair__key'), - e_ident.rgx('ident')) - m = self.__match(rgx_attr_set_or_param, input) - if m.group('ident'): - n_cur = n_cur.spawn_child(e_ident) - return self.__parse(input, n_cur) - if m.group('kv_pair__key'): - n_cur = n_cur.spawn_child(e__kv_pair) - return self.__parse(input, n_cur) + def parse__e_label_set(self, input, n_cur): + if ' ' == input[0]: # close label set + return self.__parse(input[1:], n_cur.collapse_set([p_rel, p_node])) + if ':' == input[0]: # append to label set + n_cur = n_cur.spawn_child(e_value) + return self.__parse(input[1:], n_cur) + if input[0] in [')', ']']: + n_cur = n_cur.parent + return self.__parse(input, n_cur) + assert False - assert False, 'failed parsing attr_set or identifier, input: "%s"' % (input) + def parse__e_param(self, input, n_cur): + if input.startswith('}.'): + n_cur = n_cur.rotate__pin_under_new_parent(op_dot) + return self.__parse(input[2:], n_cur) - if isinstance(n_cur, e_set): - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + if '}' == input[0]: # close node/path + n_cur = n_cur.parent + return self.__parse(input[1:], n_cur) - if '(' == input[0]: # open node or path - return parse__node_or_path(input, n_cur) - # - # end of e_set & subclasses - # + suffix = self.read__e_ident(input, n_cur) + return self.__parse(suffix, n_cur) - if isinstance(n_cur, e_value): - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + def parse__e_set(self, input, n_cur): + if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume - if input[0] in tok_set__quote: # quoted value - quote_tok = input[0] - rgx_value = r'^%s%s' % (e_value.rgx__quoted(quote_tok=quote_tok), - rgx__suffix) - n_cur.quoted = True - n_cur.quote_tok = quote_tok - else: # non quoted value - rgx_value = r'^%s%s' % (e_value.rgx__unquoted(), rgx__suffix) + if '(' == input[0]: # open node or path + return self.parse__node_or_rel(input, n_cur) - m = self.__match(rgx_value, input) - n_cur.value = m.group('value') + n_cur = n_cur.parent + return self.__parse(input, n_cur) - return self.__parse(m.group('suffix'), n_cur.parent) + def parse__e_value(self, input, n_cur): + if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume - if isinstance(n_cur, e__kv_pair): - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + suffix = self.read__e_value(input, n_cur) + n_cur = n_cur.parent + return self.__parse(suffix, n_cur) - if ',' == input[0]: # open sibling - n_cur = n_cur.spawn_sibling() - return self.__parse(input[1:], n_cur) + def parse__node_or_rel(self, input, n_cur): + if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume - if ':' == input[0]: # open sibling - n_cur = n_cur.spawn_child(e_value) - return self.__parse(input[1:], n_cur) + if ':' == input[0]: # open label set + n_cur = n_cur.spawn_child(e_label_set) + n_cur = n_cur.spawn_child(e_value) + return self.__parse(input[1:], n_cur) - if '{' == input[0]: # open param - assert False, 'params not supported yet' + if '(' == input[0]: # open node + n_cur = n_cur.spawn_child(p_node) + return self.parse__node_or_rel(input[1:], n_cur) - if not n_cur.child_node_by_type(e_ident): # kv_pair key yet to be set - n_cur = n_cur.spawn_child(e_ident) - return self.__parse(input, n_cur) + if ')' == input[0]: # close node/path + if input.startswith(')-'): # open path + n_cur = n_cur.rotate__pin_under_new_parent(p_path) + return self.parse__node_or_rel(input[1:], n_cur) - if len(n_cur.sub_exp_set) == 2: return self.__parse(input, n_cur.parent) # collapse + n_cur = n_cur.parent + if n_cur.__class__ == p_path: + n_cur = n_cur.parent + return self.__parse(input[1:], n_cur) - assert False + if input.startswith('-['): # open rel + n_cur = n_cur.spawn_child(p_rel) + return self.parse__node_or_rel(input[2:], n_cur) - if n_cur.__class__ in [p__rel, p__node]: # node or rel - if ' ' == input[0]: return self.__parse(input[1:], n_cur) # consume + if input.startswith(']-'): # close directional-rel + if input.startswith(']->'): # close directional-rel + n_cur.is_directional = True + n_cur = n_cur.parent + return self.parse__node_or_rel(input[3:], n_cur) - if ':' == input[0]: # open label set - n_cur = n_cur.spawn_child(e_label_set) - n_cur = n_cur.spawn_child(e_value) - return self.__parse(input[1:], n_cur) + n_cur = n_cur.parent + return self.parse__node_or_rel(input[2:], n_cur) - if '{' == input[0]: # open attr-set - n_cur = n_cur.spawn_child(e__attr_set) - return self.__parse(input[1:], n_cur) + if '{' == input[0]: # open attr-set + n_cur = n_cur.spawn_child(e_attr_set) + return self.__parse(input[1:], n_cur) - if ')' == input[0]: # close node/path - n_cur = n_cur.collapse(p__node).parent - return self.__parse(input[1:], n_cur) + rgx_opt_id = r'^%s' % (e_ident.rgx('ident')) + m = re.match(rgx_opt_id, input) + if m: + n_cur = n_cur.spawn_child(e_ident) + return self.__parse(input, n_cur) - if ']' == input[0]: return parse__rel_close(input, n_cur) # close rel + assert False - rgx_opt_id = r'^%s' % (e_ident.rgx('ident')) - m = re.match(rgx_opt_id, input) - if m: - n_cur = n_cur.spawn_child(e_ident) - return self.__parse(input, n_cur) + def parse__op_dot(self, input, op_dot): + n_ident = op_dot.spawn_child(e_ident) + input_suffix = self.read__e_ident(input, n_ident) + n_cur = op_dot.parent # collapse + return self.__parse(input_suffix, n_cur) - return self.__parse(input, n_cur) + def parse__pt_root(self, input, n_root): + # + # keywords + # + for kw in tok_set__kw__all: # handle keywords + if input.startswith(kw): + clause_type = globals().get('e_clause__' + kw) + if 'optional match' == kw: + clause_type = e_clause__match + if not clause_type: + clause_type = e_clause - if isinstance(n_cur, p__path): + n_cur = n_root.spawn_child(clause_type) + n_root.kw_to_clause_set_map[kw].append(n_root) # update kw clause map + if 'optional match' == kw: + n_cur.is_optional = True - if len(n_cur.sub_exp_set) == 3: return self.__parse(input, n_cur.parent) # collapse + n_cur.spawn_child(e_keyword, kw) - if '(' == input[0]: return parse__node(input, n_cur) # open node or path + if kw in tok_set__kw__unsupported: # generic e_clause case + n_value = n_cur.spawn_child(e_value) + n_value.value = '' + + return self.__parse(input[len(kw):], n_cur) + + def __parse(self, input, n_cur): + + if None == input or 0 == len(input): # end of input or opt regex group not found + return + + # + # order sensitive to specificity: + # - e_clause_match, e_clause_create > e_clause + # - e_param > e_ident + # - e_label_set, e_attr_set > e_set + # + if isinstance(n_cur, e_clause__create): return self.parse__e_clause_create_or_match(input, n_cur) + if isinstance(n_cur, e_clause__match): return self.parse__e_clause_create_or_match(input, n_cur) + if isinstance(n_cur, e_clause__where): return self.parse__e_clause__common(input, n_cur) + if isinstance(n_cur, e_clause): return self.parse__e_clause__common(input, n_cur) - if input.startswith('-['): # open rel - n_cur = n_cur.spawn_child(p__rel) - return self.__parse(input[2:], n_cur) + if isinstance(n_cur, p_rel): return self.parse__node_or_rel(input, n_cur) + if isinstance(n_cur, p_node): return self.parse__node_or_rel(input, n_cur) - if ']' == input[0]: return parse__rel_close(input, n_cur) # close rel + if isinstance(n_cur, e_label_set): return self.parse__e_label_set(input, n_cur) + if isinstance(n_cur, e_attr_set): return self.parse__e_attr_set(input, n_cur) + if isinstance(n_cur, e_set): return self.parse__e_set(input, n_cur) - assert False + f_parse = getattr(self, 'parse__' + n_cur.__class__.__name__) + return f_parse(input, n_cur) |
