1"""Object-oriented tag-table generator objects 2 3The objectgenerator module is the core of the SimpleParse 4system, the various element token classes defined here 5implement transitions from EBNF-style abstractions into 6the low-level (assembly-like) instructions to the 7TextTools engine. 8 9Each class within the module is a sub-class of ElementToken, 10which provides a number of common facilities, the most 11obvious of which is the permute method, which takes care of 12the negative, optional, and repeating flags for the normal 13case (with character ranges and literals being non-normal). 14""" 15from __future__ import print_function 16 17from simpleparse.stt.TextTools.TextTools import * 18 19### Direct use of BMS is deprecated now... 20try: 21 TextSearch 22except NameError: 23 TextSearch = BMS 24 25from simpleparse.error import ParserSyntaxError 26import copy 27 28class ElementToken: 29 """Abstract base class for all ElementTokens 30 31 Common Attributes: 32 33 negative -- the element token should match 34 a character if the "base" definition 35 would not match at the current position 36 optional -- the element token will match even 37 if the base definition would not match 38 at the current position 39 repeating -- if the element is successfully 40 matched, attempt to match it again. 41 lookahead -- if true, the scanning position 42 of the engine will be reset after the 43 element matches 44 errorOnFail -- if true, the engine will call the 45 object stored in errorOnFail as a text- 46 matching object iff the element token fails 47 to match. This is used to signal 48 SyntaxErrors. 49 50 Attributes only used for top-level Productions: 51 52 report -- if true, the production's results 53 will be added to the result tree 54 expanded -- if true, the production's children's 55 results will be added to the result tree 56 but the production's own result will be ignored 57 """ 58 negative = 0 59 optional = 0 60 repeating = 0 61 report = 1 62 # note that optional and errorOnFail are mutually exclusive 63 errorOnFail = None 64 # any item may be marked as expanded, 65 # which says that it's a top-level declaration 66 # and that links to it should automatically expand 67 # as if the name wasn't present... 68 expanded = 0 69 lookahead = 0 70 71 72 def __init__( self, **namedarguments ): 73 """Initialize the object with named attributes 74 75 This method simply takes the named attributes and 76 updates the object's dictionary with them 77 """ 78 self.__dict__.update( namedarguments ) 79 def toParser( self, generator, noReport=0 ): 80 """Abstract interface for implementing the conversion to a text-tools table 81 82 generator -- an instance of generator.Generator 83 which provides various facilities for discovering 84 other productions. 85 noReport -- if true, we're being called recursively 86 for a terminal grammar fragment where one of our 87 parents has explicitly suppressed all reporting. 88 89 This method is called by the generator or by 90 another element-token's toParser method. 91 """ 92 raise NotImplementedError( '''Element token generator abstract function called''' ) 93 def permute( self, basetable ): 94 '''Given a positive, required, non-repeating table, convert to appropriately configured table 95 96 This method applies generic logic for applying the 97 operational flags to a basic recipe for an element. 98 99 It is normally called from the elements-token's own 100 toParser method. 101 ''' 102 flags = 0 103 if self.lookahead: 104 flags = flags + LookAhead 105 106 assert len(basetable) == 3, '''Attempt to permute a base table that already has fail flag set, can only permute unadorned tables''' 107 if self.negative: 108 # negative "matches" if it fails 109 # we add in the flags while we're at it... 110 basetable = (None, SubTable+flags, ( 111 basetable + (1,2), 112 (None, EOF, Here,2,1), # if we hit eof, this didn't match, otherwise, we matched 113 (None, Fail, Here),# either hit eof or matched the client 114 (None,Skip,1), 115 )) 116 elif flags: 117 # unpack, add the flags, and repack 118 tag, command, arg = basetable 119 basetable = ( tag, command+flags, arg) 120 121 if self.repeating: 122 ### There are a number of problems with repetition that we'd like to solve 123 ### via recursive table calls, but those are very expensive in the current 124 ### implementation, so we need to use something a little more hacky... 125 if self.optional: 126 return [ 127 ## this would be the "simplistic" implementation... 128 ## basetable + (1,0) 129 ## it doesn't work because of cases 130 ## where all-optional children "succeed" without consuming 131 ## when within a repeating parent 132 ## the EOF test isn't enough to fix the problem, 133 ## as it's only checking a common case, not the underlying failure 134 basetable +(2,1), # fail, done, succeed, check for eof and if not, try matching again 135 # if we hit eof, no chance of further matches, 136 # consider ourselves done 137 (None, EOF, Here,-1,1), 138 ] 139 elif self.errorOnFail: 140 return [ 141 basetable+(1,2), 142 (None, Call, self.errorOnFail), 143 # as for optional... 144 basetable +(2,1), 145 (None, EOF, Here,-1,1), 146 ] 147 else: 148 return [ 149 basetable, 150 # as for optional... 151 basetable +(2,1), 152 (None, EOF, Here,-1,1), 153 ] 154 else: # single 155 if self.optional: 156 return [ 157 basetable +(1,1) 158 ] 159 elif self.errorOnFail: 160 return [ 161 basetable+(1,2), 162 (None, Call, self.errorOnFail), 163 ] 164 else: # not optional 165 return [ 166 basetable 167 ] 168 def __repr__( self): 169 """Return a readily recognisable version of ourself""" 170 from simpleparse import printers 171 return printers.asObject( self ) 172 def terminal (self, generator): 173 """Determine if this element is terminal for the generator""" 174 return 0 175 176 177class Literal( ElementToken ): 178 """Literal string value to be matched 179 180 Literals are one of the most common elements within 181 any grammar. The implementation tries to use the 182 most efficient mechanism available for matching/searching 183 for a literal value, so the Literal class does not 184 use the permute method, instead defining explicit 185 parsing methodologies for each flag and value combination 186 187 Literals in the SimpleParse EBNF grammar are defined like so: 188 "test", "test"?, "test"*, "test"+ 189 -"test", -"test"?, -"test"*, -"test"+ 190 191 Attributes: 192 value -- a string storing the literal's value 193 194 Notes: 195 Currently we don't support Unicode literals 196 197 See also: 198 CILiteral -- case-insensitive Literal values 199 """ 200 value = "" 201 def toParser( self, generator=None, noReport=0 ): 202 """Create the parser for the element token""" 203 flags = 0 204 if self.lookahead: 205 flags = flags + LookAhead 206 base = self.baseToParser( generator ) 207 if flags or self.errorOnFail: 208 if self.errorOnFail: 209 return [(None, SubTable+flags, tuple(base),1,2),(None, Call, self.errorOnFail)] 210 else: 211 return [(None, SubTable+flags, tuple(base))] 212 else: 213 return base 214 def baseToParser( self, generator=None ): 215 """Parser generation without considering flag settings""" 216 svalue = self.value 217 if self.negative: 218 if self.repeating: # a repeating negative value, a "search" in effect 219 if self.optional: # if fails, then go to end of file 220 return [ (None, sWordStart, TextSearch( svalue ),1,2), (None, Move, ToEOF ) ] 221 else: # must first check to make sure the current position is not the word, then the same 222 return [ 223 (None, Word, svalue, 2,1), 224 (None, Fail, Here), 225 (None, sWordStart, TextSearch( svalue ),1,2), 226 (None, Move, ToEOF ) 227 ] 228 #return [ (None, Word, svalue, 2,1),(None, Fail, Here),(None, WordStart, svalue,1,2), (None, Move, ToEOF ) ] 229 else: # a single-character test saying "not a this" 230 if self.optional: # test for a success, move back if success, move one forward if failure 231 if len(svalue) > 1: 232 return [ (None, Word, svalue, 2,1), 233 (None, Skip, -len(svalue), 2,2), # backup if this was the word to start of word, succeed 234 (None, Skip, 1 ) ] # else just move one character and succeed 235 else: # Uses Is test instead of Word test, should be faster I'd imagine 236 return [ (None, Is, svalue, 2,1), 237 (None, Skip, -1, 2,2), # backtrack 238 (None, Skip, 1 ) ] # else just move one character and succeed 239 else: # must find at least one character not part of the word, so 240 if len(svalue) > 1: 241 return [ (None, Word, svalue, 2,1), 242 (None, Fail, Here), 243 (None, Skip, 1 ) ] # else just move one character and succeed 244 else: #must fail if it finds or move one forward 245 return [ (None, Is, svalue, 2,1), 246 (None, Fail, Here), 247 (None, Skip, 1 ) ] # else just move one character and succeed 248 else: # positive 249 if self.repeating: 250 if self.optional: 251 if len(svalue) > 1: 252 return [ (None, Word, svalue, 1,0) ] 253 else: 254 return [ (None, Is, svalue, 1,0) ] 255 else: # not optional 256 if len(svalue) > 1: 257 return [ (None, Word, svalue),(None, Word, svalue,1,0) ] 258 else: 259 return [ (None, Is, svalue),(None, Is, svalue,1,0) ] 260 else: # not repeating 261 if self.optional: 262 if len(svalue) > 1: 263 return [ (None, Word, svalue, 1,1) ] 264 else: 265 return [ (None, Is, svalue, 1,1) ] 266 else: # not optional 267 if len(svalue) > 1: 268 return [ (None, Word, svalue) ] 269 else: 270 return [ (None, Word, svalue) ] 271 def terminal (self, generator): 272 """Determine if this element is terminal for the generator""" 273 return 1 274 275class _Range( ElementToken ): 276 """Range of character values where any one of the characters may match 277 278 The Range token allows you to define a set of characters 279 (using a mini-grammar) of which any one may match. By using 280 the repetition flags, it is possible to easily create such 281 common structures as "names" and "numbers". For example: 282 283 name := [a-zA-Z]+ 284 number := [0-9.eE]+ 285 286 (Note: those are not beautifully defined examples :) ). 287 288 The mini-grammar for the simpleparsegrammar is defined as follows: 289 290 '[',CHARBRACE?,CHARDASH?, (CHARRANGE/CHARNOBRACE)*, CHARDASH?,']' 291 292 that is, if a literal ']' character is wanted, you must 293 define the character as the first item in the range. A literal 294 '-' character must appear as the first character after any 295 literal ']' character (or the beginning of the range) or as the 296 last character in the range. 297 298 Note: The expansion from the mini-grammar occurs before the 299 Range token is created (the simpleparse grammar does the 300 expansion), so the value attribute of the token is actually 301 the expanded string of characters. 302 """ 303 value = "" 304 requiresExpandedSet = 1 305 def toParser( self, generator=None, noReport=0 ): 306 """Create the parser for the element token""" 307 flags = 0 308 if self.lookahead: 309 flags = flags + LookAhead 310 base = self.baseToParser( generator ) 311 if flags or self.errorOnFail: 312 if self.errorOnFail: 313 return [(None, SubTable+flags, tuple(base),1,2),(None, Call, self.errorOnFail)] 314 else: 315 return [(None, SubTable+flags, tuple(base))] 316 else: 317 return base 318 319# this should be a faster and more generic character set 320# approach, but there's a bug with mxTextTools b3 which makes 321# it non-functional, so for now I'm using the old version. 322# Eventually this should also support the Unicode character sets 323##try: 324## CharSet 325## class Range( _Range ): 326## """Range type using the CharSet feature of mx.TextTools 2.1.0 327## 328## The CharSet type allows for both Unicode and 256-char strings, 329## so we can use it as our 2.1.0 primary parsing mechanism. 330## It also allows for simpler definitions (doesn't require that 331## we pre-exand the character set). That's going to require support 332## in the SimpleParse grammar, of course. 333## """ 334## requiresExpandedSet = 0 335## def baseToParser( self, generator=None ): 336## """Parser generation without considering flag settings""" 337## svalue = self.value 338## print 'generating range for ', repr(svalue) 339## if not svalue: 340## raise ValueError( '''Range defined with no member values, would cause infinite loop %s'''%(self)) 341## if self.negative: 342## svalue = '^' + svalue 343## print ' generated', repr(svalue) 344## svalue = CharSet(svalue) 345## if self.repeating: 346## if self.optional: 347## return [ (None, AllInCharSet, svalue, 1 ) ] 348## else: # not optional 349## #return [ (None, AllInSet, svalue ) ] 350## return [ (None, AllInCharSet, svalue ) ] 351## else: # not repeating 352## if self.optional: 353## #return [ (None, IsInSet, svalue, 1 ) ] 354## return [ (None, IsInCharSet, svalue, 1 ) ] 355## else: # not optional 356## #return [ (None, IsInSet, svalue ) ] 357## return [ (None, IsInCharSet, svalue ) ] 358##except NameError: 359class Range( _Range ): 360 """Range type which doesn't use the CharSet features in mx.TextTools 361 362 This is likely to be much slower than the CharSet version (below), and 363 is unable to handle unicode character sets. However, it will work with 364 TextTools 2.0.3, which may be needed in some cases. 365 """ 366 def baseToParser( self, generator=None ): 367 """Parser generation without considering flag settings""" 368 svalue = self.value 369 if not svalue: 370 raise ValueError( '''Range defined with no member values, would cause infinite loop %s'''%(self)) 371 if self.negative: 372 if self.repeating: 373 if self.optional: 374 #return [ (None, AllInSet, svalue, 1 ) ] 375 return [ (None, AllNotIn, svalue, 1 ) ] 376 else: # not optional 377 #return [ (None, AllInSet, svalue ) ] 378 return [ (None, AllNotIn, svalue ) ] 379 else: # not repeating 380 if self.optional: 381 #return [ (None, IsInSet, svalue, 1 ) ] 382 return [ (None, IsNotIn, svalue, 1 ) ] 383 else: # not optional 384 #return [ (None, IsInSet, svalue ) ] 385 return [ (None, IsNotIn, svalue ) ] 386 else: 387 if self.repeating: 388 if self.optional: 389 #return [ (None, AllInSet, svalue, 1 ) ] 390 return [ (None, AllIn, svalue, 1 ) ] 391 else: # not optional 392 #return [ (None, AllInSet, svalue ) ] 393 return [ (None, AllIn, svalue ) ] 394 else: # not repeating 395 if self.optional: 396 #return [ (None, IsInSet, svalue, 1 ) ] 397 return [ (None, IsIn, svalue, 1 ) ] 398 else: # not optional 399 #return [ (None, IsInSet, svalue ) ] 400 return [ (None, IsIn, svalue ) ] 401 def terminal (self, generator): 402 """Determine if this element is terminal for the generator""" 403 return 1 404 405class Group( ElementToken ): 406 """Abstract base class for all group element tokens 407 408 The primary feature of a group is that it has a set 409 of element tokens stored in the attribute "children". 410 """ 411 children = () 412 terminalValue = None 413 def terminal (self, generator): 414 """Determine if this element is terminal for the generator""" 415 if self.terminalValue in (0,1): 416 return self.terminalValue 417 self.terminalValue = 0 418 for item in self.children: 419 if not item.terminal( generator): 420 return self.terminalValue 421 self.terminalValue = 1 422 return self.terminalValue 423 424class SequentialGroup( Group ): 425 """A sequence of element tokens which must match in a particular order 426 427 A sequential group must match each child in turn 428 and all children must be satisfied to consider the 429 group matched. 430 431 Within the simpleparsegrammar, the sequential group 432 is defined like so: 433 ("a", b, c, "d") 434 i.e. a series of comma-separated element token definitions. 435 """ 436 def toParser( self, generator=None, noReport=0 ): 437 elset = [] 438 for child in self.children: 439 elset.extend( child.toParser( generator, noReport ) ) 440 basic = self.permute( (None, SubTable, tuple( elset)) ) 441 if len(basic) == 1: 442 first = basic[0] 443 if len(first) == 3 and first[0] is None and first[1] == SubTable: 444 return tuple(first[2]) 445 return basic 446 447class CILiteral( SequentialGroup ): 448 """Case-insensitive Literal values 449 450 The CILiteral is a sequence of literal and 451 character-range values, where each element is 452 positive and required. Literal values are 453 composed of those characters which are not 454 upper-case/lower-case pairs, while the ranges 455 are all two-character ranges with the upper 456 and lower forms. 457 458 CILiterals in the SimpleParse EBNF grammar are defined like so: 459 c"test", c"test"?, c"test"*, c"test"+ 460 -c"test", -c"test"?, -c"test"*, -c"test"+ 461 462 Attributes: 463 value -- a string storing the literal's value 464 465 Notes: 466 Currently we don't support Unicode literals 467 468 A CILiteral will be *much* slower than a 469 regular literal or character range 470 """ 471 value = "" 472 def toParser( self, generator=None, noReport=0 ): 473 elset = self.ciParse( self.value ) 474 if len(elset) == 1: 475 # XXX should be compressing these out during optimisation... 476 # pointless declaration of case-insensitivity, 477 # or a single-character value 478 pass 479 basic = self.permute( (None, SubTable, tuple( elset)) ) 480 if len(basic) == 1: 481 first = basic[0] 482 if len(first) == 3 and first[0] is None and first[1] == SubTable: 483 return tuple(first[2]) 484 return basic 485 def ciParse( self, value ): 486 """Break value into set of case-dependent groups...""" 487 def equalPrefix( a,b ): 488 for x in range(len(a)-1): 489 if a[x] != b[x]: 490 return x 491 result = [] 492 a,b = value.upper(), value.lower() 493 while a and b: 494 # is there an equal literal run at the start? 495 stringPrefix = equalPrefix( a,b ) 496 if stringPrefix: 497 result.append( (None, Word, a[:stringPrefix]) ) 498 a,b = a[stringPrefix:],b[stringPrefix:] 499 # if we hit the end of the string, that's fine, just return 500 if not a and b: 501 break 502 # otherwise, the next character must be a case-differing pair 503 result.append( (None, IsIn, a[0]+b[0]) ) 504 a,b = a[1:], b[1:] 505 return result 506 507 508class ErrorOnFail(ElementToken): 509 """When called as a matching function, raises a SyntaxError 510 511 Attributes: 512 expected -- list of strings describing expected productions 513 production -- string name of the production that's failing to parse 514 message -- overrides default message generation if non-null 515 516 517 (something,something)+! 518 (something,something)! 519 (something,something)+!"Unable to parse somethings in my production" 520 (something,something)!"Unable to parse somethings in my production" 521 522 if string -> give an explicit message (with optional % values) 523 else -> use a default string 524 525 """ 526 production = "" 527 message = "" 528 expected = "" 529 def __call__( self, text, position, end ): 530 """Method called by mxTextTools iff the base production fails""" 531 error = ParserSyntaxError( self.message ) 532 error.error_message = self.message 533 error.production = self.production 534 error.expected= self.expected 535 error.buffer = text 536 error.position = position 537 raise error 538 def copy( self ): 539 import copy 540 return copy.copy( self ) 541 542 543 544 545class FirstOfGroup( Group ): 546 """Set of tokens that matches (and stops searching) with the first successful child 547 548 A FirstOf group attempts to match each child in turn, 549 declaring success with the first successful child, 550 or failure if none of the children match. 551 552 Within the simpleparsegrammar, the FirstOf group 553 is defined like so: 554 ("a" / b / c / "d") 555 i.e. a series of slash-separated element token definitions. 556 """ 557 def toParser( self, generator=None, noReport=0 ): 558 elset = [] 559 # should catch condition where a child is optional 560 # and we are repeating (which causes a crash during 561 # parsing), but doing so is rather complex and 562 # requires analysis of the whole grammar. 563 for el in self.children: 564 assert not el.optional, """Optional child of a FirstOf group created, this would cause an infinite recursion in the engine, child was %s"""%el 565 dataset = el.toParser( generator, noReport ) 566 if len( dataset) == 1:# and len(dataset[0]) == 3: # we can alter the jump states with impunity 567 elset.append( dataset[0] ) 568 else: # for now I'm eating the inefficiency and doing an extra SubTable for all elements to allow for easy calculation of jumps within the FO group 569 elset.append( (None, SubTable, tuple( dataset )) ) 570 571 procset = [] 572 for i in range( len( elset) -1): # note that we have to treat last el specially 573 procset.append( elset[i] + (1,len(elset)-i) ) # if success, jump past end 574 procset.append( elset[-1] ) # will cause a failure if last element doesn't match 575 procset = tuple(procset) 576 577 basetable = (None, SubTable, procset ) 578 return self.permute( basetable ) 579 580class Prebuilt( ElementToken ): 581 """Holder for pre-built TextTools tag tables 582 583 You can pass in a Pre-built tag table when 584 creating your grammar, doing so creates 585 Prebuilt element tokens which can be referenced 586 by the other element tokens in your grammar. 587 """ 588 value = () 589 def toParser( self, generator=None, noReport=0 ): 590 return self.value 591class LibraryElement( ElementToken ): 592 """Holder for a prebuilt item with it's own generator""" 593 generator = None 594 production = "" 595 methodSource = None 596 def toParser( self, generator=None, noReport=0 ): 597 if self.methodSource is None: 598 source = generator.methodSource 599 else: 600 source = self.methodSource 601 basetable = self.generator.buildParser( self.production, source ) 602 try: 603 if type(basetable[0]) == type(()): 604 if len(basetable) == 1 and len(basetable[0]) == 3: 605 basetable = basetable[0] 606 else: 607 # this is a table that got returned! 608 basetable = (None, SubTable, basetable) 609 return self.permute( basetable ) 610 except: 611 print(basetable) 612 raise 613 614class Name( ElementToken ): 615 """Reference to another rule in the grammar 616 617 The Name element token allows you to reference another 618 production within the grammar. There are three major 619 sub-categories of reference depending on both the Name 620 element token and the referenced table's values. 621 622 if the Name token's report attribute is false, 623 or the target table's report attribute is false, 624 or the Name token negative attribute is true, 625 the Name reference will report nothing in the result tree 626 627 if the target's expand attribute is true, however, 628 the Name reference will report the children 629 of the target production without reporting the 630 target production's results (SubTable match) 631 632 finally: 633 if the target is not expanded and the Name token 634 should report something, the generator object is 635 asked to supply the tag object and flags for 636 processing the results of the target. See the 637 generator.MethodSource documentation for details. 638 639 Notes: 640 expanded and un-reported productions won't get any 641 methodsource methods called when 642 they are finished, that's just how I decided to 643 do it, not sure if there's some case where you'd 644 want it. As a result, it's possible to have a 645 method getting called for one instance (where a 646 name ref is reporting) and not for another (where 647 the name ref isn't reporting). 648 """ 649 value = "" 650 # following two flags are new ideas in the rewrite... 651 report = 1 652 def toParser( self, generator, noReport=0 ): 653 """Create the table for parsing a name-reference 654 655 Note that currently most of the "compression" optimisations 656 occur here. 657 """ 658 sindex = generator.getNameIndex( self.value ) 659 command = TableInList 660 target = generator.getRootObjects()[sindex] 661 662 reportSelf = ( 663 (not noReport) and # parent hasn't suppressed reporting 664 self.report and # we are not suppressing ourselves 665 target.report and # target doesn't suppress reporting 666 (not self.negative) and # we aren't a negation, which doesn't report anything by itself 667 (not target.expanded) # we don't report the expanded production 668 ) 669 reportChildren = ( 670 (not noReport) and # parent hasn't suppressed reporting 671 self.report and # we are not suppressing ourselves 672 target.report and # target doesn't suppress reporting 673 (not self.negative) # we aren't a negation, which doesn't report anything by itself 674 ) 675 if reportSelf: 676 svalue = self.value 677 else: 678 svalue = None 679 680 flags = 0 681 if target.expanded: 682 # the target is the root of an expandedname declaration 683 # so we need to do special processing to make sure that 684 # it gets properly reported... 685 command = SubTableInList 686 tagobject = None 687 # check for indirected reference to another name... 688 elif not reportSelf: 689 tagobject = svalue 690 else: 691 flags, tagobject = generator.getObjectForName( svalue ) 692 if flags: 693 command = command | flags 694 if tagobject is None and not flags: 695 if self.terminal(generator): 696 if extractFlags(self,reportChildren) != extractFlags(target): 697 composite = compositeFlags(self,target, reportChildren) 698 partial = generator.getCustomTerminalParser( sindex,composite) 699 if partial is not None: 700 return partial 701 partial = tuple(copyToNewFlags(target, composite).toParser( 702 generator, 703 not reportChildren 704 )) 705 generator.cacheCustomTerminalParser( sindex,composite, partial) 706 return partial 707 else: 708 partial = generator.getTerminalParser( sindex ) 709 if partial is not None: 710 return partial 711 partial = tuple(target.toParser( 712 generator, 713 not reportChildren 714 )) 715 generator.setTerminalParser( sindex, partial) 716 return partial 717 # base, required, positive table... 718 if ( 719 self.terminal( generator ) and 720 (not flags) and 721 isinstance(target, (SequentialGroup,Literal,Name,Range)) 722 ): 723 partial = generator.getTerminalParser( sindex ) 724 if partial is None: 725 partial = tuple(target.toParser( 726 generator, 727 #not reportChildren 728 )) 729 generator.setTerminalParser( sindex, partial) 730 if len(partial) == 1 and len(partial[0]) == 3 and ( 731 partial[0][0] is None or tagobject is None 732 ): 733 # there is a single child 734 # it doesn't report anything, or we don't 735 partial = (partial[0][0] or tagobject,)+ partial[0][1:] 736 else: 737 partial = (tagobject, Table, tuple(partial)) 738 return self.permute( partial ) 739 basetable = ( 740 tagobject, 741 command, ( 742 generator.getParserList (), 743 sindex, 744 ) 745 ) 746 return self.permute( basetable ) 747 terminalValue = None 748 def terminal (self, generator): 749 """Determine if this element is terminal for the generator""" 750 if self.terminalValue in (0,1): 751 return self.terminalValue 752 self.terminalValue = 0 753 target = generator.getRootObject( self.value ) 754 if target.terminal( generator): 755 self.terminalValue = 1 756 return self.terminalValue 757 758 759def extractFlags( item, report=1 ): 760 """Extract the flags from an item as a tuple""" 761 return ( 762 item.negative, 763 item.optional, 764 item.repeating, 765 item.errorOnFail, 766 item.lookahead, 767 item.report and report, 768 ) 769def compositeFlags( first, second, report=1 ): 770 """Composite flags from two items into overall flag-set""" 771 result = [] 772 for a,b in zip(extractFlags(first, report), extractFlags(second, report)): 773 result.append( a or b ) 774 return tuple(result) 775def copyToNewFlags( target, flags ): 776 """Copy target using combined flags""" 777 new = copy.copy( target ) 778 for name,value in zip( 779 ("negative","optional","repeating","errorOnFail","lookahead",'report'), 780 flags, 781 ): 782 setattr(new, name,value) 783 return new 784