author | Matt Mackall <mpm@selenic.com> |
Sun, 15 May 2011 13:15:41 -0500 | |
changeset 14329 | abaacdab38f6 |
parent 13665 | e798e430c5e5 |
child 14701 | 4b93bd041772 |
permissions | -rw-r--r-- |
11274 | 1 |
# parser.py - simple top-down operator precedence parser for mercurial |
2 |
# |
|
3 |
# Copyright 2010 Matt Mackall <mpm@selenic.com> |
|
4 |
# |
|
5 |
# This software may be used and distributed according to the terms of the |
|
6 |
# GNU General Public License version 2 or any later version. |
|
7 |
||
11449
05af334bac05
parser: fix URL to effbot
Julian Cowley <julian@lava.net>
parents:
11412
diff
changeset
|
8 |
# see http://effbot.org/zone/simple-top-down-parsing.htm and |
11274 | 9 |
# http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/ |
10 |
# for background |
|
11 |
||
12 |
# takes a tokenizer and elements |
|
13 |
# tokenizer is an iterator that returns type, value pairs |
|
14 |
# elements is a mapping of types to binding strength, prefix and infix actions |
|
15 |
# an action is a tree node name, a tree label, and an optional match |
|
16 |
# __call__(program) parses program into a labelled tree |
|
17 |
||
11289
4215ce511134
revset: raise ParseError exceptions
Matt Mackall <mpm@selenic.com>
parents:
11278
diff
changeset
|
18 |
import error |
4215ce511134
revset: raise ParseError exceptions
Matt Mackall <mpm@selenic.com>
parents:
11278
diff
changeset
|
19 |
|
11274 | 20 |
class parser(object): |
21 |
def __init__(self, tokenizer, elements, methods=None): |
|
22 |
self._tokenizer = tokenizer |
|
23 |
self._elements = elements |
|
24 |
self._methods = methods |
|
13176
895f54a79c6e
templater: use the parser.py parser to extend the templater syntax
Matt Mackall <mpm@selenic.com>
parents:
11449
diff
changeset
|
25 |
self.current = None |
11274 | 26 |
def _advance(self): |
27 |
'advance the tokenizer' |
|
28 |
t = self.current |
|
11278
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
29 |
try: |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
30 |
self.current = self._iter.next() |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
31 |
except StopIteration: |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
32 |
pass |
11274 | 33 |
return t |
11319
9d1cf337a78d
parser: fix missing param in _match
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents:
11305
diff
changeset
|
34 |
def _match(self, m, pos): |
11274 | 35 |
'make sure the tokenizer matches an end condition' |
36 |
if self.current[0] != m: |
|
11305
d4cafcb63f77
cleanups: undefined variables
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
11289
diff
changeset
|
37 |
raise error.ParseError("unexpected token: %s" % self.current[0], |
d4cafcb63f77
cleanups: undefined variables
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
11289
diff
changeset
|
38 |
self.current[2]) |
11274 | 39 |
self._advance() |
40 |
def _parse(self, bind=0): |
|
11289
4215ce511134
revset: raise ParseError exceptions
Matt Mackall <mpm@selenic.com>
parents:
11278
diff
changeset
|
41 |
token, value, pos = self._advance() |
11274 | 42 |
# handle prefix rules on current token |
43 |
prefix = self._elements[token][1] |
|
44 |
if not prefix: |
|
11289
4215ce511134
revset: raise ParseError exceptions
Matt Mackall <mpm@selenic.com>
parents:
11278
diff
changeset
|
45 |
raise error.ParseError("not a prefix: %s" % token, pos) |
11274 | 46 |
if len(prefix) == 1: |
47 |
expr = (prefix[0], value) |
|
48 |
else: |
|
49 |
if len(prefix) > 2 and prefix[2] == self.current[0]: |
|
11319
9d1cf337a78d
parser: fix missing param in _match
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents:
11305
diff
changeset
|
50 |
self._match(prefix[2], pos) |
11274 | 51 |
expr = (prefix[0], None) |
52 |
else: |
|
53 |
expr = (prefix[0], self._parse(prefix[1])) |
|
54 |
if len(prefix) > 2: |
|
11319
9d1cf337a78d
parser: fix missing param in _match
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents:
11305
diff
changeset
|
55 |
self._match(prefix[2], pos) |
11274 | 56 |
# gather tokens until we meet a lower binding strength |
57 |
while bind < self._elements[self.current[0]][0]: |
|
11289
4215ce511134
revset: raise ParseError exceptions
Matt Mackall <mpm@selenic.com>
parents:
11278
diff
changeset
|
58 |
token, value, pos = self._advance() |
11278
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
59 |
e = self._elements[token] |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
60 |
# check for suffix - next token isn't a valid prefix |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
61 |
if len(e) == 4 and not self._elements[self.current[0]][1]: |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
62 |
suffix = e[3] |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
63 |
expr = (suffix[0], expr) |
11274 | 64 |
else: |
11278
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
65 |
# handle infix rules |
11412
51ceb1571805
parser: improve infix error checking
Matt Mackall <mpm@selenic.com>
parents:
11319
diff
changeset
|
66 |
if len(e) < 3 or not e[2]: |
51ceb1571805
parser: improve infix error checking
Matt Mackall <mpm@selenic.com>
parents:
11319
diff
changeset
|
67 |
raise error.ParseError("not an infix: %s" % token, pos) |
51ceb1571805
parser: improve infix error checking
Matt Mackall <mpm@selenic.com>
parents:
11319
diff
changeset
|
68 |
infix = e[2] |
11278
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
69 |
if len(infix) == 3 and infix[2] == self.current[0]: |
11319
9d1cf337a78d
parser: fix missing param in _match
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents:
11305
diff
changeset
|
70 |
self._match(infix[2], pos) |
11278
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
71 |
expr = (infix[0], expr, (None)) |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
72 |
else: |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
73 |
expr = (infix[0], expr, self._parse(infix[1])) |
7df88cdf47fd
revset: add support for prefix and suffix versions of : and ::
Matt Mackall <mpm@selenic.com>
parents:
11274
diff
changeset
|
74 |
if len(infix) == 3: |
11319
9d1cf337a78d
parser: fix missing param in _match
Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
parents:
11305
diff
changeset
|
75 |
self._match(infix[2], pos) |
11274 | 76 |
return expr |
77 |
def parse(self, message): |
|
78 |
'generate a parse tree from a message' |
|
79 |
self._iter = self._tokenizer(message) |
|
13176
895f54a79c6e
templater: use the parser.py parser to extend the templater syntax
Matt Mackall <mpm@selenic.com>
parents:
11449
diff
changeset
|
80 |
self._advance() |
13665
e798e430c5e5
revset: report a parse error if a revset is not parsed completely (issue2654)
Bernhard Leiner <bleiner@gmail.com>
parents:
13176
diff
changeset
|
81 |
res = self._parse() |
e798e430c5e5
revset: report a parse error if a revset is not parsed completely (issue2654)
Bernhard Leiner <bleiner@gmail.com>
parents:
13176
diff
changeset
|
82 |
token, value, pos = self.current |
e798e430c5e5
revset: report a parse error if a revset is not parsed completely (issue2654)
Bernhard Leiner <bleiner@gmail.com>
parents:
13176
diff
changeset
|
83 |
return res, pos |
11274 | 84 |
def eval(self, tree): |
85 |
'recursively evaluate a parse tree using node methods' |
|
86 |
if not isinstance(tree, tuple): |
|
87 |
return tree |
|
88 |
return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]]) |
|
89 |
def __call__(self, message): |
|
90 |
'parse a message into a parse tree and evaluate if methods given' |
|
91 |
t = self.parse(message) |
|
92 |
if self._methods: |
|
93 |
return self.eval(t) |
|
94 |
return t |