11 import re |
11 import re |
12 |
12 |
13 from .i18n import _ |
13 from .i18n import _ |
14 from . import ( |
14 from . import ( |
15 error, |
15 error, |
|
16 filesetlang, |
16 match as matchmod, |
17 match as matchmod, |
17 merge, |
18 merge, |
18 parser, |
|
19 pycompat, |
19 pycompat, |
20 registrar, |
20 registrar, |
21 scmutil, |
21 scmutil, |
22 util, |
22 util, |
23 ) |
23 ) |
24 from .utils import ( |
24 from .utils import ( |
25 stringutil, |
25 stringutil, |
26 ) |
26 ) |
27 |
27 |
28 elements = { |
28 # helpers for processing parsed tree |
29 # token-type: binding-strength, primary, prefix, infix, suffix |
29 getsymbol = filesetlang.getsymbol |
30 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), |
30 getstring = filesetlang.getstring |
31 ":": (15, None, None, ("kindpat", 15), None), |
31 _getkindpat = filesetlang.getkindpat |
32 "-": (5, None, ("negate", 19), ("minus", 5), None), |
32 getpattern = filesetlang.getpattern |
33 "not": (10, None, ("not", 10), None, None), |
33 getargs = filesetlang.getargs |
34 "!": (10, None, ("not", 10), None, None), |
|
35 "and": (5, None, None, ("and", 5), None), |
|
36 "&": (5, None, None, ("and", 5), None), |
|
37 "or": (4, None, None, ("or", 4), None), |
|
38 "|": (4, None, None, ("or", 4), None), |
|
39 "+": (4, None, None, ("or", 4), None), |
|
40 ",": (2, None, None, ("list", 2), None), |
|
41 ")": (0, None, None, None, None), |
|
42 "symbol": (0, "symbol", None, None, None), |
|
43 "string": (0, "string", None, None, None), |
|
44 "end": (0, None, None, None, None), |
|
45 } |
|
46 |
|
47 keywords = {'and', 'or', 'not'} |
|
48 |
|
49 globchars = ".*{}[]?/\\_" |
|
50 |
|
51 def tokenize(program): |
|
52 pos, l = 0, len(program) |
|
53 program = pycompat.bytestr(program) |
|
54 while pos < l: |
|
55 c = program[pos] |
|
56 if c.isspace(): # skip inter-token whitespace |
|
57 pass |
|
58 elif c in "(),-:|&+!": # handle simple operators |
|
59 yield (c, None, pos) |
|
60 elif (c in '"\'' or c == 'r' and |
|
61 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings |
|
62 if c == 'r': |
|
63 pos += 1 |
|
64 c = program[pos] |
|
65 decode = lambda x: x |
|
66 else: |
|
67 decode = parser.unescapestr |
|
68 pos += 1 |
|
69 s = pos |
|
70 while pos < l: # find closing quote |
|
71 d = program[pos] |
|
72 if d == '\\': # skip over escaped characters |
|
73 pos += 2 |
|
74 continue |
|
75 if d == c: |
|
76 yield ('string', decode(program[s:pos]), s) |
|
77 break |
|
78 pos += 1 |
|
79 else: |
|
80 raise error.ParseError(_("unterminated string"), s) |
|
81 elif c.isalnum() or c in globchars or ord(c) > 127: |
|
82 # gather up a symbol/keyword |
|
83 s = pos |
|
84 pos += 1 |
|
85 while pos < l: # find end of symbol |
|
86 d = program[pos] |
|
87 if not (d.isalnum() or d in globchars or ord(d) > 127): |
|
88 break |
|
89 pos += 1 |
|
90 sym = program[s:pos] |
|
91 if sym in keywords: # operator keywords |
|
92 yield (sym, None, s) |
|
93 else: |
|
94 yield ('symbol', sym, s) |
|
95 pos -= 1 |
|
96 else: |
|
97 raise error.ParseError(_("syntax error"), pos) |
|
98 pos += 1 |
|
99 yield ('end', None, pos) |
|
100 |
|
101 def parse(expr): |
|
102 p = parser.parser(elements) |
|
103 tree, pos = p.parse(tokenize(expr)) |
|
104 if pos != len(expr): |
|
105 raise error.ParseError(_("invalid token"), pos) |
|
106 return parser.simplifyinfixops(tree, {'list', 'or'}) |
|
107 |
|
108 def getsymbol(x): |
|
109 if x and x[0] == 'symbol': |
|
110 return x[1] |
|
111 raise error.ParseError(_('not a symbol')) |
|
112 |
|
113 def getstring(x, err): |
|
114 if x and (x[0] == 'string' or x[0] == 'symbol'): |
|
115 return x[1] |
|
116 raise error.ParseError(err) |
|
117 |
|
118 def _getkindpat(x, y, allkinds, err): |
|
119 kind = getsymbol(x) |
|
120 pat = getstring(y, err) |
|
121 if kind not in allkinds: |
|
122 raise error.ParseError(_("invalid pattern kind: %s") % kind) |
|
123 return '%s:%s' % (kind, pat) |
|
124 |
|
125 def getpattern(x, allkinds, err): |
|
126 if x and x[0] == 'kindpat': |
|
127 return _getkindpat(x[1], x[2], allkinds, err) |
|
128 return getstring(x, err) |
|
129 |
|
130 def getlist(x): |
|
131 if not x: |
|
132 return [] |
|
133 if x[0] == 'list': |
|
134 return list(x[1:]) |
|
135 return [x] |
|
136 |
|
137 def getargs(x, min, max, err): |
|
138 l = getlist(x) |
|
139 if len(l) < min or len(l) > max: |
|
140 raise error.ParseError(err) |
|
141 return l |
|
142 |
34 |
143 def getmatch(mctx, x): |
35 def getmatch(mctx, x): |
144 if not x: |
36 if not x: |
145 raise error.ParseError(_("missing argument")) |
37 raise error.ParseError(_("missing argument")) |
146 return methods[x[0]](mctx, *x[1:]) |
38 return methods[x[0]](mctx, *x[1:]) |