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