1"""@package grass.temporal 2 3Temporal vector algebra 4 5(C) 2014 by the GRASS Development Team 6This program is free software under the GNU General Public 7License (>=v2). Read the file COPYING that comes with GRASS 8for details. 9 10:authors: Thomas Leppelt and Soeren Gebbert 11 12.. code-block:: python 13 14 >>> import grass.temporal as tgis 15 >>> tgis.init(True) 16 >>> p = tgis.TemporalVectorAlgebraLexer() 17 >>> p.build() 18 >>> p.debug = True 19 >>> expression = 'E = A : B ^ C : D' 20 >>> p.test(expression) 21 E = A : B ^ C : D 22 LexToken(NAME,'E',1,0) 23 LexToken(EQUALS,'=',1,2) 24 LexToken(NAME,'A',1,4) 25 LexToken(T_SELECT,':',1,6) 26 LexToken(NAME,'B',1,8) 27 LexToken(XOR,'^',1,10) 28 LexToken(NAME,'C',1,12) 29 LexToken(T_SELECT,':',1,14) 30 LexToken(NAME,'D',1,16) 31 >>> expression = 'E = buff_a(A, 10)' 32 >>> p.test(expression) 33 E = buff_a(A, 10) 34 LexToken(NAME,'E',1,0) 35 LexToken(EQUALS,'=',1,2) 36 LexToken(BUFF_AREA,'buff_a',1,4) 37 LexToken(LPAREN,'(',1,10) 38 LexToken(NAME,'A',1,11) 39 LexToken(COMMA,',',1,12) 40 LexToken(INT,10,1,14) 41 LexToken(RPAREN,')',1,16) 42 43""" 44from __future__ import print_function 45 46try: 47 import ply.lex as lex 48 import ply.yacc as yacc 49except: 50 pass 51 52import grass.pygrass.modules as pygrass 53 54import copy 55from .temporal_algebra import TemporalAlgebraLexer, TemporalAlgebraParser, GlobalTemporalVar 56from .core import init_dbif, get_current_mapset 57from .abstract_dataset import AbstractDatasetComparisonKeyStartTime 58from .open_stds import open_new_stds 59from .spatio_temporal_relationships import SpatioTemporalTopologyBuilder 60from .space_time_datasets import VectorDataset 61 62 63############################################################################## 64 65class TemporalVectorAlgebraLexer(TemporalAlgebraLexer): 66 """Lexical analyzer for the GRASS GIS temporal vector algebra""" 67 68 def __init__(self): 69 TemporalAlgebraLexer.__init__(self) 70 71 # Buffer functions from v.buffer 72 vector_buff_functions = { 73 'buff_p' : 'BUFF_POINT', 74 'buff_l' : 'BUFF_LINE', 75 'buff_a' : 'BUFF_AREA', 76 } 77 78 # This is the list of token names. 79 vector_tokens = ( 80 'DISOR', 81 'XOR', 82 'NOT', 83 'T_OVERLAY_OPERATOR', 84 ) 85 86 # Build the token list 87 tokens = TemporalAlgebraLexer.tokens \ 88 + vector_tokens \ 89 + tuple(vector_buff_functions.values()) 90 91 # Regular expression rules for simple tokens 92 t_DISOR = r'\+' 93 t_XOR = r'\^' 94 t_NOT = r'\~' 95 #t_T_OVERLAY_OPERATOR = r'\{([a-zA-Z\|]+[,])?([\|&+=]?[\|&+=\^\~])\}' 96 t_T_OVERLAY_OPERATOR = r'\{[\|&+\^\~][,]?[a-zA-Z\| ]*([,])?([lrudi]|left|right|union|disjoint|intersect)?\}' 97 98 # Parse symbols 99 def temporal_symbol(self, t): 100 # Check for reserved words 101 if t.value in TemporalVectorAlgebraLexer.time_functions.keys(): 102 t.type = TemporalVectorAlgebraLexer.time_functions.get(t.value) 103 elif t.value in TemporalVectorAlgebraLexer.datetime_functions.keys(): 104 t.type = TemporalVectorAlgebraLexer.datetime_functions.get(t.value) 105 elif t.value in TemporalVectorAlgebraLexer.conditional_functions.keys(): 106 t.type = TemporalVectorAlgebraLexer.conditional_functions.get(t.value) 107 elif t.value in TemporalVectorAlgebraLexer.vector_buff_functions.keys(): 108 t.type = TemporalVectorAlgebraLexer.vector_buff_functions.get(t.value) 109 else: 110 t.type = 'NAME' 111 return t 112 113############################################################################## 114 115class TemporalVectorAlgebraParser(TemporalAlgebraParser): 116 """The temporal algebra class""" 117 118 # Get the tokens from the lexer class 119 tokens = TemporalVectorAlgebraLexer.tokens 120 121 # Setting equal precedence level for select and hash operations. 122 precedence = ( 123 ('left', 'T_SELECT_OPERATOR', 'T_SELECT', 'T_NOT_SELECT', 'T_HASH_OPERATOR', 'HASH'), # 1 124 ('left', 'AND', 'OR', 'T_COMP_OPERATOR', 'T_OVERLAY_OPERATOR', 'DISOR', \ 125 'NOT', 'XOR'), #2 126 ) 127 128 def __init__(self, pid=None, run=False, debug=True, spatial = False): 129 TemporalAlgebraParser.__init__(self, pid, run, debug, spatial) 130 131 self.m_overlay = pygrass.Module('v.overlay', quiet=True, run_=False) 132 self.m_rename = pygrass.Module('g.rename', quiet=True, run_=False) 133 self.m_patch = pygrass.Module('v.patch', quiet=True, run_=False) 134 self.m_mremove = pygrass.Module('g.remove', quiet=True, run_=False) 135 self.m_buffer = pygrass.Module('v.buffer', quiet=True, run_=False) 136 137 def parse(self, expression, basename = None, overwrite = False): 138 # Check for space time dataset type definitions from temporal algebra 139 l = TemporalVectorAlgebraLexer() 140 l.build() 141 l.lexer.input(expression) 142 143 while True: 144 tok = l.lexer.token() 145 if not tok: break 146 147 if tok.type == "STVDS" or tok.type == "STRDS" or tok.type == "STR3DS": 148 raise SyntaxError("Syntax error near '%s'" %(tok.type)) 149 150 self.lexer = TemporalVectorAlgebraLexer() 151 self.lexer.build() 152 self.parser = yacc.yacc(module=self, debug=self.debug, write_tables=False) 153 154 self.overwrite = overwrite 155 self.count = 0 156 self.stdstype = "stvds" 157 self.maptype = "vector" 158 self.mapclass = VectorDataset 159 self.basename = basename 160 self.expression = expression 161 self.parser.parse(expression) 162 163 ######################### Temporal functions ############################## 164 165 def build_spatio_temporal_topology_list(self, maplistA, maplistB = None, topolist = ["EQUAL"], 166 assign_val = False, count_map = False, compare_bool = False, 167 compare_cmd = False, compop = None, aggregate = None, 168 new = False, convert = False, overlay_cmd = False): 169 """Build temporal topology for two space time data sets, copy map objects 170 for given relation into map list. 171 172 :param maplistA: List of maps. 173 :param maplistB: List of maps. 174 :param topolist: List of strings of temporal relations. 175 :param assign_val: Boolean for assigning a boolean map value based on 176 the map_values from the compared map list by 177 topological relationships. 178 :param count_map: Boolean if the number of topological related maps 179 should be returned. 180 :param compare_bool: Boolean for comparing boolean map values based on 181 related map list and compariosn operator. 182 :param compare_cmd: Boolean for comparing command list values based on 183 related map list and compariosn operator. 184 :param compop: Comparison operator, && or ||. 185 :param aggregate: Aggregation operator for relation map list, & or |. 186 :param new: Boolean if new temporary maps should be created. 187 :param convert: Boolean if conditional values should be converted to 188 r.mapcalc command strings. 189 :param overlay_cmd: Boolean for aggregate overlay operators implicitly 190 in command list values based on related map lists. 191 192 :return: List of maps from maplistA that fulfil the topological relationships 193 to maplistB specified in topolist. 194 """ 195 topologylist = ["EQUAL", "FOLLOWS", "PRECEDES", "OVERLAPS", "OVERLAPPED", \ 196 "DURING", "STARTS", "FINISHES", "CONTAINS", "STARTED", \ 197 "FINISHED"] 198 complementdict = {"EQUAL": "EQUAL", "FOLLOWS" : "PRECEDES", 199 "PRECEDES" : "FOLLOWS", "OVERLAPS" : "OVERLAPPED", 200 "OVERLAPPED" : "OVERLAPS", "DURING" : "CONTAINS", 201 "CONTAINS" : "DURING", "STARTS" : "STARTED", 202 "STARTED" : "STARTS", "FINISHES" : "FINISHED", 203 "FINISHED" : "FINISHES"} 204 resultdict = {} 205 # Check if given temporal relation are valid. 206 for topo in topolist: 207 if topo.upper() not in topologylist: 208 raise SyntaxError("Unpermitted temporal relation name '" + topo + "'") 209 210 # Create temporal topology for maplistA to maplistB. 211 tb = SpatioTemporalTopologyBuilder() 212 # Dictionary with different spatial variables used for topology builder. 213 spatialdict = {'strds' : '2D', 'stvds' : '2D', 'str3ds' : '3D'} 214 # Build spatial temporal topology 215 if self.spatial: 216 tb.build(maplistA, maplistB, spatial = spatialdict[self.stdstype]) 217 else: 218 tb.build(maplistA, maplistB) 219 # Iterate through maps in maplistA and search for relationships given 220 # in topolist. 221 for map_i in maplistA: 222 tbrelations = map_i.get_temporal_relations() 223 # Check for boolean parameters for further calculations. 224 if assign_val: 225 self.assign_bool_value(map_i, tbrelations, topolist) 226 elif compare_bool: 227 self.compare_bool_value(map_i, tbrelations, compop, aggregate, topolist) 228 elif compare_cmd: 229 self.compare_cmd_value(map_i, tbrelations, compop, aggregate, topolist, convert) 230 elif overlay_cmd: 231 self.overlay_cmd_value(map_i, tbrelations, compop, topolist) 232 233 for topo in topolist: 234 if topo.upper() in tbrelations.keys(): 235 if count_map: 236 relationmaplist = tbrelations[topo.upper()] 237 gvar = GlobalTemporalVar() 238 gvar.td = len(relationmaplist) 239 if "map_value" in dir(map_i): 240 map_i.map_value.append(gvar) 241 else: 242 map_i.map_value = gvar 243 # Use unique identifier, since map names may be equal 244 resultdict[map_i.uid] = map_i 245 resultlist = resultdict.values() 246 247 # Sort list of maps chronological. 248 resultlist = sorted(resultlist, key = AbstractDatasetComparisonKeyStartTime) 249 250 return(resultlist) 251 252 def overlay_cmd_value(self, map_i, tbrelations, function, topolist = ["EQUAL"]): 253 """ Function to evaluate two map lists by given overlay operator. 254 255 :param map_i: Map object with temporal extent. 256 :param tbrelations: List of temporal relation to map_i. 257 :param topolist: List of strings for given temporal relations. 258 :param function: Overlay operator, &|+^~. 259 260 :return: Map object with command list with operators that has been 261 evaluated by implicit aggregration. 262 """ 263 # Build comandlist list with elements from related maps and given relation operator. 264 resultlist = [] 265 # Define overlay operation dictionary. 266 overlaydict = {"&":"and", "|":"or", "^":"xor", "~":"not", "+":"disor"} 267 operator = overlaydict[function] 268 # Set first input for overlay module. 269 mapainput = map_i.get_id() 270 # Append command list of given map to result command list. 271 if "cmd_list" in dir(map_i): 272 resultlist = resultlist + map_i.cmd_list 273 for topo in topolist: 274 if topo.upper() in tbrelations.keys(): 275 relationmaplist = tbrelations[topo.upper()] 276 for relationmap in relationmaplist: 277 # Append command list of given map to result command list. 278 if "cmd_list" in dir(relationmap): 279 resultlist = resultlist + relationmap.cmd_list 280 # Generate an intermediate name 281 name = self.generate_map_name() 282 # Put it into the removalbe map list 283 self.removable_maps[name] = VectorDataset(name + "@%s"%(self.mapset)) 284 map_i.set_id(name + "@" + self.mapset) 285 # Set second input for overlay module. 286 mapbinput = relationmap.get_id() 287 # Create module command in PyGRASS for v.overlay and v.patch. 288 if operator != "disor": 289 m = copy.deepcopy(self.m_overlay) 290 m.run_ = False 291 m.inputs["operator"].value = operator 292 m.inputs["ainput"].value = str(mapainput) 293 m.inputs["binput"].value = str(mapbinput) 294 m.outputs["output"].value = name 295 m.flags["overwrite"].value = self.overwrite 296 else: 297 patchinput = str(mapainput) + ',' + str(mapbinput) 298 m = copy.deepcopy(self.m_patch) 299 m.run_ = False 300 m.inputs["input"].value = patchinput 301 m.outputs["output"].value = name 302 m.flags["overwrite"].value = self.overwrite 303 # Conditional append of module command. 304 resultlist.append(m) 305 # Set new map name to temporary map name. 306 mapainput = name 307 # Add command list to result map. 308 map_i.cmd_list = resultlist 309 310 return(resultlist) 311 312 def set_temporal_extent_list(self, maplist, topolist = ["EQUAL"], temporal = 'l' ): 313 """ Change temporal extent of map list based on temporal relations to 314 other map list and given temporal operator. 315 316 :param maplist: List of map objects for which relations has been build 317 correctely. 318 :param topolist: List of strings of temporal relations. 319 :param temporal: The temporal operator specifying the temporal 320 extent operation (intersection, union, disjoint 321 union, right reference, left reference). 322 323 :return: Map list with specified temporal extent. 324 """ 325 resultdict = {} 326 327 for map_i in maplist: 328 # Loop over temporal related maps and create overlay modules. 329 tbrelations = map_i.get_temporal_relations() 330 # Generate an intermediate map for the result map list. 331 map_new = self.generate_new_map(base_map=map_i, bool_op = 'and', 332 copy = True, rename = False, 333 remove = True) 334 # Combine temporal and spatial extents of intermediate map with related maps. 335 for topo in topolist: 336 if topo in tbrelations.keys(): 337 for map_j in (tbrelations[topo]): 338 if temporal == 'r': 339 # Generate an intermediate map for the result map list. 340 map_new = self.generate_new_map(base_map=map_i, bool_op = 'and', 341 copy = True, rename = False, 342 remove = True) 343 # Create overlaid map extent. 344 returncode = self.overlay_map_extent(map_new, map_j, 'and', \ 345 temp_op = temporal) 346 # Stop the loop if no temporal or spatial relationship exist. 347 if returncode == 0: 348 break 349 # Append map to result map list. 350 elif returncode == 1: 351 # resultlist.append(map_new) 352 resultdict[map_new.get_id()] = map_new 353 if returncode == 0: 354 break 355 # Append map to result map list. 356 #if returncode == 1: 357 # resultlist.append(map_new) 358 # Get sorted map objects as values from result dictionoary. 359 resultlist = resultdict.values() 360 resultlist = sorted(resultlist, key = AbstractDatasetComparisonKeyStartTime) 361 362 return(resultlist) 363 364 ########################################################################### 365 366 def p_statement_assign(self, t): 367 # The expression should always return a list of maps. 368 """ 369 statement : stds EQUALS expr 370 371 """ 372 # Execute the command lists 373 if self.run: 374 # Open connection to temporal database. 375 dbif, connected = init_dbif(dbif=self.dbif) 376 if isinstance(t[3], list): 377 num = len(t[3]) 378 count = 0 379 returncode = 0 380 register_list = [] 381 leadzero = len(str(num)) 382 for i in range(num): 383 # Check if resultmap names exist in GRASS database. 384 vectorname = self.basename + "_" + str(i).zfill(leadzero) 385 vectormap = VectorDataset(vectorname + "@" + get_current_mapset()) 386 if vectormap.map_exists() and self.overwrite == False: 387 self.msgr.fatal(_("Error vector maps with basename %s exist. " 388 "Use --o flag to overwrite existing file") \ 389 %(vectorname)) 390 for map_i in t[3]: 391 if "cmd_list" in dir(map_i): 392 # Execute command list. 393 for cmd in map_i.cmd_list: 394 try: 395 # We need to check if the input maps have areas in case of v.overlay 396 # otherwise v.overlay will break 397 if cmd.name == "v.overlay": 398 for name in (cmd.inputs["ainput"].value, 399 cmd.inputs["binput"].value): 400 #self.msgr.message("Check if map <" + name + "> exists") 401 if name.find("@") < 0: 402 name = name + "@" + get_current_mapset() 403 tmp_map = map_i.get_new_instance(name) 404 if not tmp_map.map_exists(): 405 raise Exception 406 #self.msgr.message("Check if map <" + name + "> has areas") 407 tmp_map.load() 408 if tmp_map.metadata.get_number_of_areas() == 0: 409 raise Exception 410 except Exception: 411 returncode = 1 412 break 413 414 # run the command 415 # print the command that will be executed 416 self.msgr.message("Run command:\n" + cmd.get_bash()) 417 cmd.run() 418 419 if cmd.returncode != 0: 420 self.msgr.fatal( 421 _("Error starting %s : \n%s") 422 % (cmd.get_bash(), cmd.outputs.stderr) 423 ) 424 mapname = cmd.outputs["output"].value 425 if mapname.find("@") >= 0: 426 map_test = map_i.get_new_instance(mapname) 427 else: 428 map_test = map_i.get_new_instance(mapname + "@" + self.mapset) 429 if not map_test.map_exists(): 430 returncode = 1 431 break 432 if returncode == 0: 433 # We remove the invalid vector name from the remove list. 434 if map_i.get_name() in self.removable_maps: 435 self.removable_maps.pop(map_i.get_name()) 436 mapset = map_i.get_mapset() 437 # Change map name to given basename. 438 newident = self.basename + "_" + str(count).zfill(leadzero) 439 m = copy.deepcopy(self.m_rename) 440 m.inputs["vector"].value = (map_i.get_name(),newident) 441 m.flags["overwrite"].value = self.overwrite 442 m.run() 443 map_i.set_id(newident + "@" + mapset) 444 count += 1 445 register_list.append(map_i) 446 else: 447 # Test if temporal extents have been changed by temporal 448 # relation operators (i|r). This is a code copy from temporal_algebra.py 449 map_i_extent = map_i.get_temporal_extent_as_tuple() 450 map_test = map_i.get_new_instance(map_i.get_id()) 451 map_test.select(dbif) 452 map_test_extent = map_test.get_temporal_extent_as_tuple() 453 if map_test_extent != map_i_extent: 454 # Create new map with basename 455 newident = self.basename + "_" + str(count).zfill(leadzero) 456 map_result = map_i.get_new_instance(newident + "@" + self.mapset) 457 458 if map_test.map_exists() and self.overwrite is False: 459 self.msgr.fatal("Error raster maps with basename %s exist. " 460 "Use --o flag to overwrite existing file" 461 %(mapname)) 462 463 map_result.set_temporal_extent(map_i.get_temporal_extent()) 464 map_result.set_spatial_extent(map_i.get_spatial_extent()) 465 # Attention we attach a new attribute 466 map_result.is_new = True 467 count += 1 468 register_list.append(map_result) 469 470 # Copy the map 471 m = copy.deepcopy(self.m_copy) 472 m.inputs["vector"].value = map_i.get_id(), newident 473 m.flags["overwrite"].value = self.overwrite 474 m.run() 475 else: 476 register_list.append(map_i) 477 478 if len(register_list) > 0: 479 # Create result space time dataset. 480 resultstds = open_new_stds(t[1], self.stdstype, 481 'absolute', t[1], t[1], 482 "temporal vector algebra", self.dbif, 483 overwrite=self.overwrite) 484 for map_i in register_list: 485 # Check if modules should be executed from command list. 486 if hasattr(map_i, "cmd_list") or hasattr(map_i, "is_new"): 487 # Get meta data from grass database. 488 map_i.load() 489 if map_i.is_in_db(dbif=dbif) and self.overwrite: 490 # Update map in temporal database. 491 map_i.update_all(dbif=dbif) 492 elif map_i.is_in_db(dbif=dbif) and self.overwrite == False: 493 # Raise error if map exists and no overwrite flag is given. 494 self.msgr.fatal( 495 _( 496 "Error vector map %s exist in temporal database. " 497 "Use overwrite flag. : \n%s" 498 ) 499 % (map_i.get_map_id(), cmd.outputs.stderr) 500 ) 501 else: 502 # Insert map into temporal database. 503 map_i.insert(dbif=dbif) 504 else: 505 # Map is original from an input STVDS 506 map_i.load() 507 # Register map in result space time dataset. 508 print(map_i.get_temporal_extent_as_tuple()) 509 success = resultstds.register_map(map_i, dbif=dbif) 510 resultstds.update_from_registered_maps(dbif) 511 512 # Remove intermediate maps 513 self.remove_maps() 514 if connected: 515 dbif.close() 516 t[0] = t[3] 517 518 def p_overlay_operation(self, t): 519 """ 520 expr : stds AND stds 521 | expr AND stds 522 | stds AND expr 523 | expr AND expr 524 | stds OR stds 525 | expr OR stds 526 | stds OR expr 527 | expr OR expr 528 | stds XOR stds 529 | expr XOR stds 530 | stds XOR expr 531 | expr XOR expr 532 | stds NOT stds 533 | expr NOT stds 534 | stds NOT expr 535 | expr NOT expr 536 | stds DISOR stds 537 | expr DISOR stds 538 | stds DISOR expr 539 | expr DISOR expr 540 """ 541 if self.run: 542 # Check input stds and operator. 543 maplistA = self.check_stds(t[1]) 544 maplistB = self.check_stds(t[3]) 545 relations = ["EQUAL"] 546 temporal = 'l' 547 function = t[2] 548 # Build command list for related maps. 549 complist = self.build_spatio_temporal_topology_list(maplistA, maplistB, topolist = relations, 550 compop = function, overlay_cmd = True) 551 # Set temporal extent based on topological relationships. 552 resultlist = self.set_temporal_extent_list(complist, topolist = relations, 553 temporal = temporal) 554 555 t[0] = resultlist 556 if self.debug: 557 str(t[1]) + t[2] + str(t[3]) 558 559 def p_overlay_operation_relation(self, t): 560 """ 561 expr : stds T_OVERLAY_OPERATOR stds 562 | expr T_OVERLAY_OPERATOR stds 563 | stds T_OVERLAY_OPERATOR expr 564 | expr T_OVERLAY_OPERATOR expr 565 """ 566 if self.run: 567 # Check input stds and operator. 568 maplistA = self.check_stds(t[1]) 569 maplistB = self.check_stds(t[3]) 570 relations, temporal, function, aggregate = self.eval_toperator(t[2], optype = 'overlay') 571 # Build command list for related maps. 572 complist = self.build_spatio_temporal_topology_list(maplistA, maplistB, topolist = relations, 573 compop = function, overlay_cmd = True) 574 # Set temporal extent based on topological relationships. 575 resultlist = self.set_temporal_extent_list(complist, topolist = relations, 576 temporal = temporal) 577 578 t[0] = resultlist 579 if self.debug: 580 str(t[1]) + t[2] + str(t[3]) 581 582 def p_buffer_operation(self,t): 583 """ 584 expr : buff_function LPAREN stds COMMA number RPAREN 585 | buff_function LPAREN expr COMMA number RPAREN 586 """ 587 588 if self.run: 589 # Check input stds. 590 bufflist = self.check_stds(t[3]) 591 # Create empty result list. 592 resultlist = [] 593 594 for map_i in bufflist: 595 # Generate an intermediate name for the result map list. 596 map_new = self.generate_new_map(base_map=map_i, bool_op=None, 597 copy=True, remove = True) 598 # Change spatial extent based on buffer size. 599 map_new.spatial_buffer(float(t[5])) 600 # Check buff type. 601 if t[1] == "buff_p": 602 buff_type = "point" 603 elif t[1] == "buff_l": 604 buff_type = "line" 605 elif t[1] == "buff_a": 606 buff_type = "area" 607 m = copy.deepcopy(self.m_buffer) 608 m.run_ = False 609 m.inputs["type"].value = buff_type 610 m.inputs["input"].value = str(map_i.get_id()) 611 m.inputs["distance"].value = float(t[5]) 612 m.outputs["output"].value = map_new.get_name() 613 m.flags["overwrite"].value = self.overwrite 614 615 # Conditional append of module command. 616 if "cmd_list" in dir(map_new): 617 map_new.cmd_list.append(m) 618 else: 619 map_new.cmd_list = [m] 620 resultlist.append(map_new) 621 622 t[0] = resultlist 623 624 def p_buff_function(self, t): 625 """buff_function : BUFF_POINT 626 | BUFF_LINE 627 | BUFF_AREA 628 """ 629 t[0] = t[1] 630 631 # Handle errors. 632 def p_error(self, t): 633 raise SyntaxError("syntax error on line %d near '%s' expression '%s'" % 634 (t.lineno, t.value, self.expression)) 635 636############################################################################### 637 638if __name__ == "__main__": 639 import doctest 640 doctest.testmod() 641