"""@package grass.temporal Temporal operator evaluation with PLY (C) 2013 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. :authors: Thomas Leppelt and Soeren Gebbert .. code-block:: python >>> p = TemporalOperatorParser() >>> expression = "{equal|equivalent|cover|in|meet|contain|overlap}" >>> p.parse(expression, optype = 'relation') >>> print((p.relations, p.temporal, p.function)) (['equal', 'equivalent', 'cover', 'in', 'meet', 'contain', 'overlap'], None, None) >>> p = TemporalOperatorParser() >>> expression = "{equal| during}" >>> p.parse(expression, optype = 'relation') >>> print((p.relations, p.temporal, p.function)) (['equal', 'during'], None, None) >>> p = TemporalOperatorParser() >>> expression = "{contains | starts}" >>> p.parse(expression) >>> print((p.relations, p.temporal, p.function)) (['contains', 'starts'], None, None) >>> p = TemporalOperatorParser() >>> expression = "{&&, during}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['during'], 'l', '&&', '&') >>> p = TemporalOperatorParser() >>> expression = "{||, equal | during}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['equal', 'during'], 'l', '||', '|') >>> p = TemporalOperatorParser() >>> expression = "{||, equal | during, &}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['equal', 'during'], 'l', '||', '&') >>> p = TemporalOperatorParser() >>> expression = "{&&, during, |}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['during'], 'l', '&&', '|') >>> p = TemporalOperatorParser() >>> expression = "{&&, during, |, r}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['during'], 'r', '&&', '|') >>> p = TemporalOperatorParser() >>> expression = "{&&, during, u}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['during'], 'u', '&&', '&') >>> p = TemporalOperatorParser() >>> expression = "{:, during, r}" >>> p.parse(expression, optype = 'select') >>> print((p.relations, p.temporal, p.function)) (['during'], 'r', ':') >>> p = TemporalOperatorParser() >>> expression = "{!:, equal | contains, d}" >>> p.parse(expression, optype = 'select') >>> print((p.relations, p.temporal, p.function)) (['equal', 'contains'], 'd', '!:') >>> p = TemporalOperatorParser() >>> expression = "{#, during, r}" >>> p.parse(expression, optype = 'hash') >>> print((p.relations, p.temporal, p.function)) (['during'], 'r', '#') >>> p = TemporalOperatorParser() >>> expression = "{#, equal | contains}" >>> p.parse(expression, optype = 'hash') >>> print((p.relations, p.temporal, p.function)) (['equal', 'contains'], 'l', '#') >>> p = TemporalOperatorParser() >>> expression = "{+, during, r}" >>> p.parse(expression, optype = 'raster') >>> print((p.relations, p.temporal, p.function)) (['during'], 'r', '+') >>> p = TemporalOperatorParser() >>> expression = "{/, equal | contains}" >>> p.parse(expression, optype = 'raster') >>> print((p.relations, p.temporal, p.function)) (['equal', 'contains'], 'l', '/') >>> p = TemporalOperatorParser() >>> expression = "{+, equal | contains,intersect}" >>> p.parse(expression, optype = 'raster') >>> print((p.relations, p.temporal, p.function)) (['equal', 'contains'], 'i', '+') >>> p = TemporalOperatorParser() >>> expression = "{*, contains,disjoint}" >>> p.parse(expression, optype = 'raster') >>> print((p.relations, p.temporal, p.function)) (['contains'], 'd', '*') >>> p = TemporalOperatorParser() >>> expression = "{~, equal,left}" >>> p.parse(expression, optype = 'overlay') >>> print((p.relations, p.temporal, p.function)) (['equal'], 'l', '~') >>> p = TemporalOperatorParser() >>> expression = "{^, over,right}" >>> p.parse(expression, optype = 'overlay') >>> print((p.relations, p.temporal, p.function)) (['overlaps', 'overlapped'], 'r', '^') >>> p = TemporalOperatorParser() >>> expression = "{&&, equal | during | contains | starts, &}" >>> p.parse(expression, optype = 'boolean') >>> print((p.relations, p.temporal, p.function, p.aggregate)) (['equal', 'during', 'contains', 'starts'], 'l', '&&', '&') >>> p = TemporalOperatorParser() >>> expression = "{&&, equal | during | contains | starts, &&&&&}" >>> p.parse(expression, optype = 'boolean') Traceback (most recent call last): SyntaxError: Unexpected syntax error in expression "{&&, equal | during | contains | starts, &&&&&}" at position 42 near & >>> p = TemporalOperatorParser() >>> expression = "{+, starting}" >>> p.parse(expression) Traceback (most recent call last): SyntaxError: syntax error on line 1 position 4 near 'starting' >>> p = TemporalOperatorParser() >>> expression = "{nope, start, |, l}" >>> p.parse(expression) Traceback (most recent call last): SyntaxError: syntax error on line 1 position 1 near 'nope' >>> p = TemporalOperatorParser() >>> expression = "{++, start, |, l}" >>> p.parse(expression) Traceback (most recent call last): SyntaxError: Unexpected syntax error in expression "{++, start, |, l}" at position 2 near + >>> p = TemporalOperatorParser() >>> expression = "{^, over, right}" >>> p.parse(expression, optype='rter') Traceback (most recent call last): SyntaxError: Unknown optype rter, must be one of ['select', 'boolean', 'raster', 'hash', 'relation', 'overlay'] """ from __future__ import print_function try: import ply.lex as lex import ply.yacc as yacc except ImportError: pass class TemporalOperatorLexer(object): """Lexical analyzer for the GRASS GIS temporal operator""" # Functions that defines topological relations. relations = { # temporal relations "equal": "EQUAL", "follows": "FOLLOWS", "precedes": "PRECEDES", "overlaps": "OVERLAPS", "overlapped": "OVERLAPPED", "during": "DURING", "starts": "STARTS", "finishes": "FINISHES", "contains": "CONTAINS", "started": "STARTED", "finished": "FINISHED", "over": "OVER", # spatial relations "equivalent": "EQUIVALENT", "cover": "COVER", "overlap": "OVERLAP", "in": "IN", "contain": "CONTAIN", "meet": "MEET", } # This is the list of token names. tokens = ( "COMMA", "LEFTREF", "RIGHTREF", "UNION", "DISJOINT", "INTERSECT", "HASH", "OR", "AND", "DISOR", "XOR", "NOT", "MOD", "DIV", "MULT", "ADD", "SUB", "T_SELECT", "T_NOT_SELECT", "CLPAREN", "CRPAREN", ) # Build the token list tokens = tokens + tuple(relations.values()) # Regular expression rules for simple tokens t_T_SELECT = r":" t_T_NOT_SELECT = r"!:" t_COMMA = r"," t_LEFTREF = "^[l|left]" t_RIGHTREF = "^[r|right]" t_UNION = "^[u|union]" t_DISJOINT = "^[d|disjoint]" t_INTERSECT = "^[i|intersect]" t_HASH = r"\#" t_OR = r"[\|]" t_AND = r"[&]" t_DISOR = r"\+" t_XOR = r"\^" t_NOT = r"\~" t_MOD = r"[\%]" t_DIV = r"[\/]" t_MULT = r"[\*]" t_ADD = r"[\+]" t_SUB = r"[-]" t_CLPAREN = r"\{" t_CRPAREN = r"\}" # These are the things that should be ignored. t_ignore = " \t\n" # Track line numbers. def t_newline(self, t): r"\n+" t.lineno += len(t.value) def t_NAME(self, t): r"[a-zA-Z_][a-zA-Z_0-9]*" return self.temporal_symbol(t) # Parse symbols def temporal_symbol(self, t): # Check for reserved words if t.value in TemporalOperatorLexer.relations.keys(): t.type = TemporalOperatorLexer.relations.get(t.value) elif t.value == "l" or t.value == "left": t.value = "l" t.type = "LEFTREF" elif t.value == "r" or t.value == "right": t.value = "r" t.type = "RIGHTREF" elif t.value == "u" or t.value == "union": t.value = "u" t.type = "UNION" elif t.value == "d" or t.value == "disjoint": t.value = "d" t.type = "DISJOINT" elif t.value == "i" or t.value == "intersect": t.value = "i" t.type = "INTERSECT" else: self.t_error(t) return t # Handle errors. def t_error(self, t): raise SyntaxError( "syntax error on line %d position %i near '%s'" % (t.lineno, t.lexpos, t.value) ) # Build the lexer def build(self, **kwargs): self.lexer = lex.lex( module=self, optimize=False, nowarn=True, debug=0, **kwargs ) # Just for testing def test(self, data): self.name_list = {} print(data) self.lexer.input(data) while True: tok = self.lexer.token() if not tok: break print(tok) ############################################################################### class TemporalOperatorParser(object): """The temporal operator class""" def __init__(self): self.lexer = TemporalOperatorLexer() self.lexer.build() self.parser = yacc.yacc(module=self, debug=0) self.relations = None # Temporal relations (equals, contain, during, ...) self.temporal = None # Temporal operation (intersect, left, right, ...) self.function = None # Actual operation (+, -, /, *, ... ) self.aggregate = None # Aggregation function (|, &) self.optype_list = [ "select", "boolean", "raster", "hash", "relation", "overlay", ] def parse(self, expression, optype="relation"): """Parse the expression and fill the object variables :param expression: :param optype: The parameter optype can be of type: - select { :, during, r} - boolean {&&, contains, |} - raster { *, equal, |} - overlay { |, starts, &} - hash { #, during, l} - relation {during} :return: """ self.optype = optype if optype not in self.optype_list: raise SyntaxError( "Unknown optype %s, must be one of %s" % (self.optype, str(self.optype_list)) ) self.expression = expression self.parser.parse(expression) # Error rule for syntax errors. def p_error(self, t): raise SyntaxError( "Unexpected syntax error in expression" ' "%s" at position %i near %s' % (self.expression, t.lexpos, t.value) ) # Get the tokens from the lexer class tokens = TemporalOperatorLexer.tokens def p_relation_operator(self, t): # {during} # {during | equal | starts} """ operator : CLPAREN relation CRPAREN | CLPAREN relationlist CRPAREN """ # Check for correct type. if not self.optype == "relation": raise SyntaxError('Wrong optype "%s" must be "relation"' % self.optype) else: # Set three operator components. if isinstance(t[2], list): self.relations = t[2] else: self.relations = [t[2]] self.temporal = None self.function = None t[0] = t[2] def p_relation_bool_operator(self, t): # {||, during} # {&&, during | equal | starts} """ operator : CLPAREN OR OR COMMA relation CRPAREN | CLPAREN AND AND COMMA relation CRPAREN | CLPAREN OR OR COMMA relationlist CRPAREN | CLPAREN AND AND COMMA relationlist CRPAREN """ if not self.optype == "boolean": raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype) else: # Set three operator components. if isinstance(t[5], list): self.relations = t[5] else: self.relations = [t[5]] self.temporal = "l" self.function = t[2] + t[3] self.aggregate = t[2] t[0] = t[2] def p_relation_bool_combi_operator(self, t): # {||, during, &} # {&&, during | equal | starts, |} """ operator : CLPAREN OR OR COMMA relation COMMA OR CRPAREN | CLPAREN OR OR COMMA relation COMMA AND CRPAREN | CLPAREN AND AND COMMA relation COMMA OR CRPAREN | CLPAREN AND AND COMMA relation COMMA AND CRPAREN | CLPAREN OR OR COMMA relationlist COMMA OR CRPAREN | CLPAREN OR OR COMMA relationlist COMMA AND CRPAREN | CLPAREN AND AND COMMA relationlist COMMA OR CRPAREN | CLPAREN AND AND COMMA relationlist COMMA AND CRPAREN """ if not self.optype == "boolean": raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype) else: # Set three operator components. if isinstance(t[5], list): self.relations = t[5] else: self.relations = [t[5]] self.temporal = "l" self.function = t[2] + t[3] self.aggregate = t[7] t[0] = t[2] def p_relation_bool_combi_operator2(self, t): # {||, during, left} # {&&, during | equal | starts, union} """ operator : CLPAREN OR OR COMMA relation COMMA temporal CRPAREN | CLPAREN AND AND COMMA relation COMMA temporal CRPAREN | CLPAREN OR OR COMMA relationlist COMMA temporal CRPAREN | CLPAREN AND AND COMMA relationlist COMMA temporal CRPAREN """ if not self.optype == "boolean": raise SyntaxError('Wrong optype "%s" must be "boolean"' % self.optype) else: # Set three operator components. if isinstance(t[5], list): self.relations = t[5] else: self.relations = [t[5]] self.temporal = t[7] self.function = t[2] + t[3] self.aggregate = t[2] t[0] = t[2] def p_relation_bool_combi_operator3(self, t): # {||, during, |, left} # {&&, during | equal | starts, &, union} """ operator : CLPAREN OR OR COMMA relation COMMA OR COMMA temporal CRPAREN | CLPAREN OR OR COMMA relation COMMA AND COMMA temporal CRPAREN | CLPAREN AND AND COMMA relation COMMA OR COMMA temporal CRPAREN | CLPAREN AND AND COMMA relation COMMA AND COMMA temporal CRPAREN | CLPAREN OR OR COMMA relationlist COMMA OR COMMA temporal CRPAREN | CLPAREN OR OR COMMA relationlist COMMA AND COMMA temporal CRPAREN | CLPAREN AND AND COMMA relationlist COMMA OR COMMA temporal CRPAREN | CLPAREN AND AND COMMA relationlist COMMA AND COMMA temporal CRPAREN """ if not self.optype == "boolean": raise SyntaxError('Wrong optype "%s" must be "relation"' % self.optype) else: # Set three operator components. if isinstance(t[5], list): self.relations = t[5] else: self.relations = [t[5]] self.temporal = t[9] self.function = t[2] + t[3] self.aggregate = t[7] t[0] = t[2] def p_select_relation_operator(self, t): # {!:} # { :, during} # {!:, during | equal | starts} # { :, during | equal | starts, l} """ operator : CLPAREN select CRPAREN | CLPAREN select COMMA relation CRPAREN | CLPAREN select COMMA relationlist CRPAREN | CLPAREN select COMMA relation COMMA temporal CRPAREN | CLPAREN select COMMA relationlist COMMA temporal CRPAREN """ if not self.optype == "select": raise SyntaxError('Wrong optype "%s" must be "select"' % self.optype) else: if len(t) == 4: # Set three operator components. self.relations = ["equal", "equivalent"] self.temporal = "l" self.function = t[2] elif len(t) == 6: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = "l" self.function = t[2] elif len(t) == 8: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = t[6] self.function = t[2] t[0] = t[2] def p_hash_relation_operator(self, t): # {#} # {#, during} # {#, during | equal | starts} # {#, during | equal | starts, l} """ operator : CLPAREN HASH CRPAREN | CLPAREN HASH COMMA relation CRPAREN | CLPAREN HASH COMMA relationlist CRPAREN | CLPAREN HASH COMMA relation COMMA temporal CRPAREN | CLPAREN HASH COMMA relationlist COMMA temporal CRPAREN """ if not self.optype == "hash": raise SyntaxError('Wrong optype "%s" must be "hash"' % self.optype) else: if len(t) == 4: # Set three operator components. self.relations = ["equal"] self.temporal = "l" self.function = t[2] elif len(t) == 6: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = "l" self.function = t[2] elif len(t) == 8: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = t[6] self.function = t[2] t[0] = t[2] def p_raster_relation_operator(self, t): # {+} # {-, during} # {*, during | equal | starts} # {/, during | equal | starts, l} """ operator : CLPAREN arithmetic CRPAREN | CLPAREN arithmetic COMMA relation CRPAREN | CLPAREN arithmetic COMMA relationlist CRPAREN | CLPAREN arithmetic COMMA relation COMMA temporal CRPAREN | CLPAREN arithmetic COMMA relationlist COMMA temporal CRPAREN """ if not self.optype == "raster": raise SyntaxError('Wrong optype "%s" must be "raster"' % self.optype) else: if len(t) == 4: # Set three operator components. self.relations = ["equal"] self.temporal = "l" self.function = t[2] elif len(t) == 6: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = "l" self.function = t[2] elif len(t) == 8: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = t[6] self.function = t[2] t[0] = t[2] def p_overlay_relation_operator(self, t): # {+} # {-, during} # {~, during | equal | starts} # {^, during | equal | starts, l} """ operator : CLPAREN overlay CRPAREN | CLPAREN overlay COMMA relation CRPAREN | CLPAREN overlay COMMA relationlist CRPAREN | CLPAREN overlay COMMA relation COMMA temporal CRPAREN | CLPAREN overlay COMMA relationlist COMMA temporal CRPAREN """ if not self.optype == "overlay": raise SyntaxError('Wrong optype "%s" must be "overlay"' % self.optype) else: if len(t) == 4: # Set three operator components. self.relations = ["equal"] self.temporal = "l" self.function = t[2] elif len(t) == 6: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = "l" self.function = t[2] elif len(t) == 8: if isinstance(t[4], list): self.relations = t[4] else: self.relations = [t[4]] self.temporal = t[6] self.function = t[2] t[0] = t[2] def p_relation(self, t): # The list of relations. Temporal and spatial relations are supported """ relation : EQUAL | FOLLOWS | PRECEDES | OVERLAPS | OVERLAPPED | DURING | STARTS | FINISHES | CONTAINS | STARTED | FINISHED | EQUIVALENT | COVER | OVERLAP | IN | CONTAIN | MEET """ t[0] = t[1] def p_over(self, t): # The the over keyword """ relation : OVER """ over_list = ["overlaps", "overlapped"] t[0] = over_list def p_relationlist(self, t): # The list of relations. """ relationlist : relation OR relation | relation OR relationlist """ rel_list = [] rel_list.append(t[1]) if isinstance(t[3], list): rel_list = rel_list + t[3] else: rel_list.append(t[3]) t[0] = rel_list def p_temporal_operator(self, t): # The list of relations. """ temporal : LEFTREF | RIGHTREF | UNION | DISJOINT | INTERSECT """ t[0] = t[1] def p_select_operator(self, t): # The list of relations. """ select : T_SELECT | T_NOT_SELECT """ t[0] = t[1] def p_arithmetic_operator(self, t): # The list of relations. """ arithmetic : MOD | DIV | MULT | ADD | SUB """ t[0] = t[1] def p_overlay_operator(self, t): # The list of relations. """ overlay : AND | OR | XOR | DISOR | NOT """ t[0] = t[1] ############################################################################### if __name__ == "__main__": import doctest doctest.testmod()