32 self._elements = elements |
32 self._elements = elements |
33 self._methods = methods |
33 self._methods = methods |
34 self.current = None |
34 self.current = None |
35 |
35 |
36 def _advance(self): |
36 def _advance(self): |
37 'advance the tokenizer' |
37 b'advance the tokenizer' |
38 t = self.current |
38 t = self.current |
39 self.current = next(self._iter, None) |
39 self.current = next(self._iter, None) |
40 return t |
40 return t |
41 |
41 |
42 def _hasnewterm(self): |
42 def _hasnewterm(self): |
43 'True if next token may start new term' |
43 b'True if next token may start new term' |
44 return any(self._elements[self.current[0]][1:3]) |
44 return any(self._elements[self.current[0]][1:3]) |
45 |
45 |
46 def _match(self, m): |
46 def _match(self, m): |
47 'make sure the tokenizer matches an end condition' |
47 b'make sure the tokenizer matches an end condition' |
48 if self.current[0] != m: |
48 if self.current[0] != m: |
49 raise error.ParseError( |
49 raise error.ParseError( |
50 _("unexpected token: %s") % self.current[0], self.current[2] |
50 _(b"unexpected token: %s") % self.current[0], self.current[2] |
51 ) |
51 ) |
52 self._advance() |
52 self._advance() |
53 |
53 |
54 def _parseoperand(self, bind, m=None): |
54 def _parseoperand(self, bind, m=None): |
55 'gather right-hand-side operand until an end condition or binding met' |
55 b'gather right-hand-side operand until an end condition or binding met' |
56 if m and self.current[0] == m: |
56 if m and self.current[0] == m: |
57 expr = None |
57 expr = None |
58 else: |
58 else: |
59 expr = self._parse(bind) |
59 expr = self._parse(bind) |
60 if m: |
60 if m: |
68 if primary and not (prefix and self._hasnewterm()): |
68 if primary and not (prefix and self._hasnewterm()): |
69 expr = (primary, value) |
69 expr = (primary, value) |
70 elif prefix: |
70 elif prefix: |
71 expr = (prefix[0], self._parseoperand(*prefix[1:])) |
71 expr = (prefix[0], self._parseoperand(*prefix[1:])) |
72 else: |
72 else: |
73 raise error.ParseError(_("not a prefix: %s") % token, pos) |
73 raise error.ParseError(_(b"not a prefix: %s") % token, pos) |
74 # gather tokens until we meet a lower binding strength |
74 # gather tokens until we meet a lower binding strength |
75 while bind < self._elements[self.current[0]][0]: |
75 while bind < self._elements[self.current[0]][0]: |
76 token, value, pos = self._advance() |
76 token, value, pos = self._advance() |
77 # handle infix rules, take as suffix if unambiguous |
77 # handle infix rules, take as suffix if unambiguous |
78 infix, suffix = self._elements[token][3:] |
78 infix, suffix = self._elements[token][3:] |
79 if suffix and not (infix and self._hasnewterm()): |
79 if suffix and not (infix and self._hasnewterm()): |
80 expr = (suffix, expr) |
80 expr = (suffix, expr) |
81 elif infix: |
81 elif infix: |
82 expr = (infix[0], expr, self._parseoperand(*infix[1:])) |
82 expr = (infix[0], expr, self._parseoperand(*infix[1:])) |
83 else: |
83 else: |
84 raise error.ParseError(_("not an infix: %s") % token, pos) |
84 raise error.ParseError(_(b"not an infix: %s") % token, pos) |
85 return expr |
85 return expr |
86 |
86 |
87 def parse(self, tokeniter): |
87 def parse(self, tokeniter): |
88 'generate a parse tree from tokens' |
88 b'generate a parse tree from tokens' |
89 self._iter = tokeniter |
89 self._iter = tokeniter |
90 self._advance() |
90 self._advance() |
91 res = self._parse() |
91 res = self._parse() |
92 token, value, pos = self.current |
92 token, value, pos = self.current |
93 return res, pos |
93 return res, pos |
94 |
94 |
95 def eval(self, tree): |
95 def eval(self, tree): |
96 'recursively evaluate a parse tree using node methods' |
96 b'recursively evaluate a parse tree using node methods' |
97 if not isinstance(tree, tuple): |
97 if not isinstance(tree, tuple): |
98 return tree |
98 return tree |
99 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]]) |
99 return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]]) |
100 |
100 |
101 def __call__(self, tokeniter): |
101 def __call__(self, tokeniter): |
102 'parse tokens into a parse tree and evaluate if methods given' |
102 b'parse tokens into a parse tree and evaluate if methods given' |
103 t = self.parse(tokeniter) |
103 t = self.parse(tokeniter) |
104 if self._methods: |
104 if self._methods: |
105 return self.eval(t) |
105 return self.eval(t) |
106 return t |
106 return t |
107 |
107 |
119 ([], 'foo', [], None) |
119 ([], 'foo', [], None) |
120 >>> splitargspec(b'**foo') |
120 >>> splitargspec(b'**foo') |
121 ([], None, [], 'foo') |
121 ([], None, [], 'foo') |
122 """ |
122 """ |
123 optkey = None |
123 optkey = None |
124 pre, sep, post = spec.partition('**') |
124 pre, sep, post = spec.partition(b'**') |
125 if sep: |
125 if sep: |
126 posts = post.split() |
126 posts = post.split() |
127 if not posts: |
127 if not posts: |
128 raise error.ProgrammingError('no **optkey name provided') |
128 raise error.ProgrammingError(b'no **optkey name provided') |
129 if len(posts) > 1: |
129 if len(posts) > 1: |
130 raise error.ProgrammingError('excessive **optkey names provided') |
130 raise error.ProgrammingError(b'excessive **optkey names provided') |
131 optkey = posts[0] |
131 optkey = posts[0] |
132 |
132 |
133 pre, sep, post = pre.partition('*') |
133 pre, sep, post = pre.partition(b'*') |
134 pres = pre.split() |
134 pres = pre.split() |
135 posts = post.split() |
135 posts = post.split() |
136 if sep: |
136 if sep: |
137 if not posts: |
137 if not posts: |
138 raise error.ProgrammingError('no *varkey name provided') |
138 raise error.ProgrammingError(b'no *varkey name provided') |
139 return pres, posts[0], posts[1:], optkey |
139 return pres, posts[0], posts[1:], optkey |
140 return [], None, pres, optkey |
140 return [], None, pres, optkey |
141 |
141 |
142 |
142 |
143 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode): |
143 def buildargsdict(trees, funcname, argspec, keyvaluenode, keynode): |
161 (i for i, x in enumerate(trees) if x and x[0] == keyvaluenode), |
161 (i for i, x in enumerate(trees) if x and x[0] == keyvaluenode), |
162 len(trees), |
162 len(trees), |
163 ) |
163 ) |
164 if kwstart < len(poskeys): |
164 if kwstart < len(poskeys): |
165 raise error.ParseError( |
165 raise error.ParseError( |
166 _("%(func)s takes at least %(nargs)d positional " "arguments") |
166 _(b"%(func)s takes at least %(nargs)d positional " b"arguments") |
167 % {'func': funcname, 'nargs': len(poskeys)} |
167 % {b'func': funcname, b'nargs': len(poskeys)} |
168 ) |
168 ) |
169 if not varkey and kwstart > len(poskeys) + len(keys): |
169 if not varkey and kwstart > len(poskeys) + len(keys): |
170 raise error.ParseError( |
170 raise error.ParseError( |
171 _("%(func)s takes at most %(nargs)d positional " "arguments") |
171 _(b"%(func)s takes at most %(nargs)d positional " b"arguments") |
172 % {'func': funcname, 'nargs': len(poskeys) + len(keys)} |
172 % {b'func': funcname, b'nargs': len(poskeys) + len(keys)} |
173 ) |
173 ) |
174 args = util.sortdict() |
174 args = util.sortdict() |
175 # consume positional arguments |
175 # consume positional arguments |
176 for k, x in zip(poskeys, trees[:kwstart]): |
176 for k, x in zip(poskeys, trees[:kwstart]): |
177 args[k] = x |
177 args[k] = x |
184 if optkey: |
184 if optkey: |
185 args[optkey] = util.sortdict() |
185 args[optkey] = util.sortdict() |
186 for x in trees[kwstart:]: |
186 for x in trees[kwstart:]: |
187 if not x or x[0] != keyvaluenode or x[1][0] != keynode: |
187 if not x or x[0] != keyvaluenode or x[1][0] != keynode: |
188 raise error.ParseError( |
188 raise error.ParseError( |
189 _("%(func)s got an invalid argument") % {'func': funcname} |
189 _(b"%(func)s got an invalid argument") % {b'func': funcname} |
190 ) |
190 ) |
191 k = x[1][1] |
191 k = x[1][1] |
192 if k in keys: |
192 if k in keys: |
193 d = args |
193 d = args |
194 elif not optkey: |
194 elif not optkey: |
195 raise error.ParseError( |
195 raise error.ParseError( |
196 _("%(func)s got an unexpected keyword " "argument '%(key)s'") |
196 _(b"%(func)s got an unexpected keyword " b"argument '%(key)s'") |
197 % {'func': funcname, 'key': k} |
197 % {b'func': funcname, b'key': k} |
198 ) |
198 ) |
199 else: |
199 else: |
200 d = args[optkey] |
200 d = args[optkey] |
201 if k in d: |
201 if k in d: |
202 raise error.ParseError( |
202 raise error.ParseError( |
203 _( |
203 _( |
204 "%(func)s got multiple values for keyword " |
204 b"%(func)s got multiple values for keyword " |
205 "argument '%(key)s'" |
205 b"argument '%(key)s'" |
206 ) |
206 ) |
207 % {'func': funcname, 'key': k} |
207 % {b'func': funcname, b'key': k} |
208 ) |
208 ) |
209 d[k] = x[2] |
209 d[k] = x[2] |
210 return args |
210 return args |
211 |
211 |
212 |
212 |
221 def _prettyformat(tree, leafnodes, level, lines): |
221 def _prettyformat(tree, leafnodes, level, lines): |
222 if not isinstance(tree, tuple): |
222 if not isinstance(tree, tuple): |
223 lines.append((level, stringutil.pprint(tree))) |
223 lines.append((level, stringutil.pprint(tree))) |
224 elif tree[0] in leafnodes: |
224 elif tree[0] in leafnodes: |
225 rs = map(stringutil.pprint, tree[1:]) |
225 rs = map(stringutil.pprint, tree[1:]) |
226 lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs)))) |
226 lines.append((level, b'(%s %s)' % (tree[0], b' '.join(rs)))) |
227 else: |
227 else: |
228 lines.append((level, '(%s' % tree[0])) |
228 lines.append((level, b'(%s' % tree[0])) |
229 for s in tree[1:]: |
229 for s in tree[1:]: |
230 _prettyformat(s, leafnodes, level + 1, lines) |
230 _prettyformat(s, leafnodes, level + 1, lines) |
231 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')] |
231 lines[-1:] = [(lines[-1][0], lines[-1][1] + b')')] |
232 |
232 |
233 |
233 |
234 def prettyformat(tree, leafnodes): |
234 def prettyformat(tree, leafnodes): |
235 lines = [] |
235 lines = [] |
236 _prettyformat(tree, leafnodes, 0, lines) |
236 _prettyformat(tree, leafnodes, 0, lines) |
237 output = '\n'.join((' ' * l + s) for l, s in lines) |
237 output = b'\n'.join((b' ' * l + s) for l, s in lines) |
238 return output |
238 return output |
239 |
239 |
240 |
240 |
241 def simplifyinfixops(tree, targetnodes): |
241 def simplifyinfixops(tree, targetnodes): |
242 """Flatten chained infix operations to reduce usage of Python stack |
242 """Flatten chained infix operations to reduce usage of Python stack |
337 ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2'))) |
337 ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2'))) |
338 >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2')) |
338 >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2')) |
339 ('and', ('symbol', '1'), ('not', ('symbol', '2'))) |
339 ('and', ('symbol', '1'), ('not', ('symbol', '2'))) |
340 """ |
340 """ |
341 if not isinstance(placeholder, tuple): |
341 if not isinstance(placeholder, tuple): |
342 raise error.ProgrammingError('placeholder must be a node tuple') |
342 raise error.ProgrammingError(b'placeholder must be a node tuple') |
343 replstack = list(reversed(repls)) |
343 replstack = list(reversed(repls)) |
344 r = _buildtree(template, placeholder, replstack) |
344 r = _buildtree(template, placeholder, replstack) |
345 if replstack: |
345 if replstack: |
346 raise error.ProgrammingError('too many replacements') |
346 raise error.ProgrammingError(b'too many replacements') |
347 return r |
347 return r |
348 |
348 |
349 |
349 |
350 def _matchtree(pattern, tree, placeholder, incompletenodes, matches): |
350 def _matchtree(pattern, tree, placeholder, incompletenodes, matches): |
351 if pattern == tree: |
351 if pattern == tree: |
396 >>> _ = None |
396 >>> _ = None |
397 >>> f((b'func', (b'symbol', b'ancestors'), None), |
397 >>> f((b'func', (b'symbol', b'ancestors'), None), |
398 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0'))) |
398 ... (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0'))) |
399 """ |
399 """ |
400 if placeholder is not None and not isinstance(placeholder, tuple): |
400 if placeholder is not None and not isinstance(placeholder, tuple): |
401 raise error.ProgrammingError('placeholder must be a node tuple') |
401 raise error.ProgrammingError(b'placeholder must be a node tuple') |
402 matches = [tree] |
402 matches = [tree] |
403 if _matchtree(pattern, tree, placeholder, incompletenodes, matches): |
403 if _matchtree(pattern, tree, placeholder, incompletenodes, matches): |
404 return matches |
404 return matches |
405 |
405 |
406 |
406 |
407 def parseerrordetail(inst): |
407 def parseerrordetail(inst): |
408 """Compose error message from specified ParseError object |
408 """Compose error message from specified ParseError object |
409 """ |
409 """ |
410 if len(inst.args) > 1: |
410 if len(inst.args) > 1: |
411 return _('at %d: %s') % (inst.args[1], inst.args[0]) |
411 return _(b'at %d: %s') % (inst.args[1], inst.args[0]) |
412 else: |
412 else: |
413 return inst.args[0] |
413 return inst.args[0] |
414 |
414 |
415 |
415 |
416 class alias(object): |
416 class alias(object): |
541 return (decl, None, parseerrordetail(inst)) |
541 return (decl, None, parseerrordetail(inst)) |
542 |
542 |
543 if tree[0] == cls._symbolnode: |
543 if tree[0] == cls._symbolnode: |
544 # "name = ...." style |
544 # "name = ...." style |
545 name = tree[1] |
545 name = tree[1] |
546 if name.startswith('$'): |
546 if name.startswith(b'$'): |
547 return (decl, None, _("invalid symbol '%s'") % name) |
547 return (decl, None, _(b"invalid symbol '%s'") % name) |
548 return (name, None, None) |
548 return (name, None, None) |
549 |
549 |
550 func = cls._trygetfunc(tree) |
550 func = cls._trygetfunc(tree) |
551 if func: |
551 if func: |
552 # "name(arg, ....) = ...." style |
552 # "name(arg, ....) = ...." style |
553 name, args = func |
553 name, args = func |
554 if name.startswith('$'): |
554 if name.startswith(b'$'): |
555 return (decl, None, _("invalid function '%s'") % name) |
555 return (decl, None, _(b"invalid function '%s'") % name) |
556 if any(t[0] != cls._symbolnode for t in args): |
556 if any(t[0] != cls._symbolnode for t in args): |
557 return (decl, None, _("invalid argument list")) |
557 return (decl, None, _(b"invalid argument list")) |
558 if len(args) != len(set(args)): |
558 if len(args) != len(set(args)): |
559 return (name, None, _("argument names collide with each other")) |
559 return ( |
|
560 name, |
|
561 None, |
|
562 _(b"argument names collide with each other"), |
|
563 ) |
560 return (name, [t[1] for t in args], None) |
564 return (name, [t[1] for t in args], None) |
561 |
565 |
562 return (decl, None, _("invalid format")) |
566 return (decl, None, _(b"invalid format")) |
563 |
567 |
564 @classmethod |
568 @classmethod |
565 def _relabelargs(cls, tree, args): |
569 def _relabelargs(cls, tree, args): |
566 """Mark alias arguments as ``_aliasarg``""" |
570 """Mark alias arguments as ``_aliasarg``""" |
567 if not isinstance(tree, tuple): |
571 if not isinstance(tree, tuple): |
571 return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:]) |
575 return (op,) + tuple(cls._relabelargs(x, args) for x in tree[1:]) |
572 |
576 |
573 assert len(tree) == 2 |
577 assert len(tree) == 2 |
574 sym = tree[1] |
578 sym = tree[1] |
575 if sym in args: |
579 if sym in args: |
576 op = '_aliasarg' |
580 op = b'_aliasarg' |
577 elif sym.startswith('$'): |
581 elif sym.startswith(b'$'): |
578 raise error.ParseError(_("invalid symbol '%s'") % sym) |
582 raise error.ParseError(_(b"invalid symbol '%s'") % sym) |
579 return (op, sym) |
583 return (op, sym) |
580 |
584 |
581 @classmethod |
585 @classmethod |
582 def _builddefn(cls, defn, args): |
586 def _builddefn(cls, defn, args): |
583 """Parse an alias definition into a tree and marks substitutions |
587 """Parse an alias definition into a tree and marks substitutions |
636 def build(cls, decl, defn): |
640 def build(cls, decl, defn): |
637 """Parse an alias declaration and definition into an alias object""" |
641 """Parse an alias declaration and definition into an alias object""" |
638 repl = efmt = None |
642 repl = efmt = None |
639 name, args, err = cls._builddecl(decl) |
643 name, args, err = cls._builddecl(decl) |
640 if err: |
644 if err: |
641 efmt = _('bad declaration of %(section)s "%(name)s": %(error)s') |
645 efmt = _(b'bad declaration of %(section)s "%(name)s": %(error)s') |
642 else: |
646 else: |
643 try: |
647 try: |
644 repl = cls._builddefn(defn, args) |
648 repl = cls._builddefn(defn, args) |
645 except error.ParseError as inst: |
649 except error.ParseError as inst: |
646 err = parseerrordetail(inst) |
650 err = parseerrordetail(inst) |
647 efmt = _('bad definition of %(section)s "%(name)s": %(error)s') |
651 efmt = _(b'bad definition of %(section)s "%(name)s": %(error)s') |
648 if err: |
652 if err: |
649 err = efmt % {'section': cls._section, 'name': name, 'error': err} |
653 err = efmt % { |
|
654 b'section': cls._section, |
|
655 b'name': name, |
|
656 b'error': err, |
|
657 } |
650 return alias(name, args, err, repl) |
658 return alias(name, args, err, repl) |
651 |
659 |
652 @classmethod |
660 @classmethod |
653 def buildmap(cls, items): |
661 def buildmap(cls, items): |
654 """Parse a list of alias (name, replacement) pairs into a dict of |
662 """Parse a list of alias (name, replacement) pairs into a dict of |
684 """Replace _aliasarg instances with the substitution value of the |
692 """Replace _aliasarg instances with the substitution value of the |
685 same name in args, recursively. |
693 same name in args, recursively. |
686 """ |
694 """ |
687 if not isinstance(tree, tuple): |
695 if not isinstance(tree, tuple): |
688 return tree |
696 return tree |
689 if tree[0] == '_aliasarg': |
697 if tree[0] == b'_aliasarg': |
690 sym = tree[1] |
698 sym = tree[1] |
691 return args[sym] |
699 return args[sym] |
692 return tuple(cls._expandargs(t, args) for t in tree) |
700 return tuple(cls._expandargs(t, args) for t in tree) |
693 |
701 |
694 @classmethod |
702 @classmethod |
703 a, l = r |
711 a, l = r |
704 if a.error: |
712 if a.error: |
705 raise error.Abort(a.error) |
713 raise error.Abort(a.error) |
706 if a in expanding: |
714 if a in expanding: |
707 raise error.ParseError( |
715 raise error.ParseError( |
708 _('infinite expansion of %(section)s ' '"%(name)s" detected') |
716 _(b'infinite expansion of %(section)s ' b'"%(name)s" detected') |
709 % {'section': cls._section, 'name': a.name} |
717 % {b'section': cls._section, b'name': a.name} |
710 ) |
718 ) |
711 # get cacheable replacement tree by expanding aliases recursively |
719 # get cacheable replacement tree by expanding aliases recursively |
712 expanding.append(a) |
720 expanding.append(a) |
713 if a.name not in cache: |
721 if a.name not in cache: |
714 cache[a.name] = cls._expand( |
722 cache[a.name] = cls._expand( |
719 if a.args is None: |
727 if a.args is None: |
720 return result |
728 return result |
721 # substitute function arguments in replacement tree |
729 # substitute function arguments in replacement tree |
722 if len(l) != len(a.args): |
730 if len(l) != len(a.args): |
723 raise error.ParseError( |
731 raise error.ParseError( |
724 _('invalid number of arguments: %d') % len(l) |
732 _(b'invalid number of arguments: %d') % len(l) |
725 ) |
733 ) |
726 l = [cls._expand(aliases, t, [], cache) for t in l] |
734 l = [cls._expand(aliases, t, [], cache) for t in l] |
727 return cls._expandargs(result, dict(zip(a.args, l))) |
735 return cls._expandargs(result, dict(zip(a.args, l))) |
728 |
736 |
729 @classmethod |
737 @classmethod |