OILS / frontend / location.py View on Github | oils.pub

577 lines, 397 significant
1#!/usr/bin/env python2
2"""
3location.py - Library to get source location info from nodes.
4
5This makes syntax errors nicer.
6"""
7from __future__ import print_function
8
9from _devbuild.gen.syntax_asdl import (
10 expr,
11 expr_t,
12 expr_e,
13 loc,
14 loc_t,
15 loc_e,
16 loc_str,
17 command,
18 command_e,
19 command_t,
20 sh_lhs,
21 sh_lhs_e,
22 sh_lhs_t,
23 word,
24 word_e,
25 word_t,
26 word_part,
27 word_part_e,
28 word_part_t,
29 CompoundWord,
30 Token,
31 SimpleVarSub,
32 ShArrayLiteral,
33 SingleQuoted,
34 DoubleQuoted,
35 CommandSub,
36 BracedVarSub,
37 BraceGroup,
38 Subscript,
39 Attribute,
40 arith_expr,
41 arith_expr_e,
42 arith_expr_t,
43 Eggex,
44)
45from _devbuild.gen.value_asdl import LeftName
46from mycpp.mylib import log
47from mycpp.mylib import tagswitch
48
49_ = log
50
51from typing import cast, Optional
52
53
54def LName(name):
55 # type: (str) -> LeftName
56 """Wrapper for LeftName() with location.
57
58 TODO: add locations and remove this.
59 """
60 return LeftName(name, loc.Missing)
61
62
63def TokenFor(loc_):
64 # type: (loc_t) -> Optional[Token]
65 """Given a location, get a Token.
66
67 This is useful because a Token points to a single line.
68 """
69 UP_location = loc_
70 with tagswitch(loc_) as case:
71 if case(loc_e.Missing):
72 return None
73
74 elif case(loc_e.Token):
75 tok = cast(Token, UP_location)
76 if tok:
77 return tok
78 else:
79 return None
80
81 elif case(loc_e.ArgWord):
82 w = cast(CompoundWord, UP_location)
83 return LeftTokenForWord(w)
84
85 elif case(loc_e.WordPart):
86 loc_ = cast(loc.WordPart, UP_location)
87 if loc_.p:
88 return LeftTokenForWordPart(loc_.p)
89 else:
90 return None
91
92 elif case(loc_e.Word):
93 loc_ = cast(loc.Word, UP_location)
94 if loc_.w:
95 return LeftTokenForWord(loc_.w)
96 else:
97 return None
98
99 elif case(loc_e.Command):
100 loc_ = cast(loc.Command, UP_location)
101 if loc_.c:
102 return TokenForCommand(loc_.c)
103 else:
104 return None
105
106 elif case(loc_e.Arith):
107 loc_ = cast(loc.Arith, UP_location)
108 if loc_.a:
109 return TokenForArith(loc_.a)
110 else:
111 return None
112
113 else:
114 raise AssertionError(loc_str(loc_.tag()))
115
116 raise AssertionError()
117
118
119def TokenForCommand(node):
120 # type: (command_t) -> Optional[Token]
121 """Used directly in _CheckStatus()"""
122 UP_node = node # type: command_t
123 tag = node.tag()
124
125 if tag == command_e.Redirect:
126 node = cast(command.Redirect, UP_node)
127 first = node.redirects[0]
128 return first.op
129
130 if tag == command_e.Sentence:
131 node = cast(command.Sentence, UP_node)
132 #log("node.child %s", node.child)
133 return node.terminator # & or ;
134
135 if tag == command_e.Simple:
136 node = cast(command.Simple, UP_node)
137 return node.blame_tok
138
139 if tag == command_e.ShAssignment:
140 node = cast(command.ShAssignment, UP_node)
141 return node.left
142
143 if tag == command_e.Pipeline:
144 node = cast(command.Pipeline, UP_node)
145 if len(node.ops):
146 return node.ops[0] # first | or |&
147 else:
148 assert node.negated is not None
149 return node.negated # ! false
150
151 if tag == command_e.AndOr:
152 node = cast(command.AndOr, UP_node)
153 return node.ops[0] # first && or ||
154
155 if tag == command_e.DoGroup:
156 node = cast(command.DoGroup, UP_node)
157 return node.left # 'do' token
158 if tag == command_e.BraceGroup:
159 node = cast(BraceGroup, UP_node)
160 return node.left # { token
161 if tag == command_e.Subshell:
162 node = cast(command.Subshell, UP_node)
163 return node.left # ( token
164
165 if tag == command_e.WhileUntil:
166 node = cast(command.WhileUntil, UP_node)
167 return node.keyword # while
168 if tag == command_e.If:
169 node = cast(command.If, UP_node)
170 return node.if_kw
171 if tag == command_e.Case:
172 node = cast(command.Case, UP_node)
173 return node.case_kw
174 if tag == command_e.TimeBlock:
175 node = cast(command.TimeBlock, UP_node)
176 return node.keyword
177
178 # We never have this case?
179 #if node.tag == command_e.CommandList:
180 # pass
181
182 return None
183
184
185def TokenForArith(node):
186 # type: (arith_expr_t) -> Optional[Token]
187 UP_node = node
188 with tagswitch(node) as case:
189 if case(arith_expr_e.VarSub):
190 vsub = cast(Token, UP_node)
191 # $(( x ))
192 return vsub
193
194 elif case(arith_expr_e.Word):
195 w = cast(CompoundWord, UP_node)
196 return LeftTokenForWord(w)
197
198 elif case(arith_expr_e.Unary):
199 node = cast(arith_expr.Unary, UP_node)
200 return TokenForArith(node.child)
201
202 elif case(arith_expr_e.Binary):
203 node = cast(arith_expr.Binary, UP_node)
204 return TokenForArith(node.op)
205
206 elif case(arith_expr_e.TernaryOp):
207 node = cast(arith_expr.TernaryOp, UP_node)
208
209 # TODO: should blame op
210 # blaming cond is arbitrary, but better than nothing
211 return TokenForArith(node.cond)
212
213 return None
214
215
216def LeftTokenForWordPart(part):
217 # type: (word_part_t) -> Optional[Token]
218 UP_part = part
219 with tagswitch(part) as case:
220 if case(word_part_e.ShArrayLiteral):
221 part = cast(ShArrayLiteral, UP_part)
222 return part.left
223
224 elif case(word_part_e.BashAssocLiteral):
225 part = cast(word_part.BashAssocLiteral, UP_part)
226 return part.left
227
228 elif case(word_part_e.Literal):
229 tok = cast(Token, UP_part)
230 return tok
231
232 elif case(word_part_e.EscapedLiteral):
233 part = cast(word_part.EscapedLiteral, UP_part)
234 return part.token
235
236 elif case(word_part_e.SingleQuoted):
237 part = cast(SingleQuoted, UP_part)
238 return part.left
239
240 elif case(word_part_e.DoubleQuoted):
241 part = cast(DoubleQuoted, UP_part)
242 return part.left
243
244 elif case(word_part_e.SimpleVarSub):
245 part = cast(SimpleVarSub, UP_part)
246 return part.tok
247
248 elif case(word_part_e.BracedVarSub):
249 part = cast(BracedVarSub, UP_part)
250 return part.left
251
252 elif case(word_part_e.CommandSub):
253 part = cast(CommandSub, UP_part)
254 return part.left_token
255
256 elif case(word_part_e.TildeSub):
257 part = cast(word_part.TildeSub, UP_part)
258 return part.left
259
260 elif case(word_part_e.ArithSub):
261 part = cast(word_part.ArithSub, UP_part)
262 return part.left
263
264 elif case(word_part_e.ExtGlob):
265 part = cast(word_part.ExtGlob, UP_part)
266 return part.op
267
268 elif case(word_part_e.BracedRange):
269 part = cast(word_part.BracedRange, UP_part)
270 return part.blame_tok
271
272 elif case(word_part_e.BracedTuple):
273 part = cast(word_part.BracedTuple, UP_part)
274 # TODO: Derive token from part.words[0]
275 return None
276
277 elif case(word_part_e.Splice):
278 part = cast(word_part.Splice, UP_part)
279 return part.blame_tok
280
281 elif case(word_part_e.ExprSub):
282 part = cast(word_part.ExprSub, UP_part)
283 return part.left # $[
284
285 else:
286 raise AssertionError(part.tag())
287
288
289def _RightTokenForWordPart(part):
290 # type: (word_part_t) -> Token
291 UP_part = part
292 with tagswitch(part) as case:
293 if case(word_part_e.ShArrayLiteral):
294 part = cast(ShArrayLiteral, UP_part)
295 return part.right
296
297 elif case(word_part_e.BashAssocLiteral):
298 part = cast(word_part.BashAssocLiteral, UP_part)
299 return part.right
300
301 elif case(word_part_e.Literal):
302 tok = cast(Token, UP_part)
303 # Just use the token
304 return tok
305
306 elif case(word_part_e.EscapedLiteral):
307 part = cast(word_part.EscapedLiteral, UP_part)
308 return part.token
309
310 elif case(word_part_e.SingleQuoted):
311 part = cast(SingleQuoted, UP_part)
312 return part.right # right '
313
314 elif case(word_part_e.DoubleQuoted):
315 part = cast(DoubleQuoted, UP_part)
316 return part.right # right "
317
318 elif case(word_part_e.SimpleVarSub):
319 part = cast(SimpleVarSub, UP_part)
320 # left and right are the same for $myvar
321 return part.tok
322
323 elif case(word_part_e.BracedVarSub):
324 part = cast(BracedVarSub, UP_part)
325 return part.right
326
327 elif case(word_part_e.CommandSub):
328 part = cast(CommandSub, UP_part)
329 return part.right
330
331 elif case(word_part_e.TildeSub):
332 part = cast(word_part.TildeSub, UP_part)
333 if part.name is not None:
334 return part.name # ~bob/
335 else:
336 return part.left # ~/
337
338 elif case(word_part_e.ArithSub):
339 part = cast(word_part.ArithSub, UP_part)
340 return part.right
341
342 elif case(word_part_e.ExtGlob):
343 part = cast(word_part.ExtGlob, UP_part)
344 return part.right
345
346 elif case(word_part_e.BracedRange):
347 part = cast(word_part.BracedRange, UP_part)
348 return part.blame_tok
349
350 elif case(word_part_e.BracedTuple):
351 part = cast(word_part.BracedTuple, UP_part)
352 # TODO: Derive token from part.words[0]
353 return None
354
355 elif case(word_part_e.Splice):
356 part = cast(word_part.Splice, UP_part)
357 return part.blame_tok
358
359 elif case(word_part_e.ExprSub):
360 part = cast(word_part.ExprSub, UP_part)
361 return part.right
362
363 else:
364 raise AssertionError(part.tag())
365
366
367def LeftTokenForCompoundWord(w):
368 # type: (CompoundWord) -> Optional[Token]
369 if len(w.parts):
370 return LeftTokenForWordPart(w.parts[0])
371 else:
372 # This is possible for empty brace sub alternative {a,b,}
373 return None
374
375
376def LeftTokenForWord(w):
377 # type: (word_t) -> Optional[Token]
378 if w is None:
379 return None # e.g. builtin_bracket word.String() EOF
380
381 UP_w = w
382 with tagswitch(w) as case:
383 if case(word_e.Compound):
384 w = cast(CompoundWord, UP_w)
385 return LeftTokenForCompoundWord(w)
386
387 elif case(word_e.Operator):
388 tok = cast(Token, UP_w)
389 return tok
390
391 elif case(word_e.BracedTree):
392 w = cast(word.BracedTree, UP_w)
393 # This should always have one part?
394 return LeftTokenForWordPart(w.parts[0])
395
396 elif case(word_e.String):
397 w = cast(word.String, UP_w)
398 # See _StringWordEmitter in osh/builtin_bracket.py
399 return LeftTokenForWord(w.blame_loc)
400
401 else:
402 raise AssertionError(w.tag())
403
404 raise AssertionError('for -Wreturn-type in C++')
405
406
407def RightTokenForWord(w):
408 # type: (word_t) -> Token
409 """Used for alias expansion and history substitution.
410
411 and here doc delimiters?
412 """
413 UP_w = w
414 with tagswitch(w) as case:
415 if case(word_e.Compound):
416 w = cast(CompoundWord, UP_w)
417 if len(w.parts):
418 end = w.parts[-1]
419 return _RightTokenForWordPart(end)
420 else:
421 # This is possible for empty brace sub alternative {a,b,}
422 return None
423
424 elif case(word_e.Operator):
425 tok = cast(Token, UP_w)
426 return tok
427
428 elif case(word_e.BracedTree):
429 w = cast(word.BracedTree, UP_w)
430 # Note: this case may be unused
431 return _RightTokenForWordPart(w.parts[-1])
432
433 elif case(word_e.String):
434 w = cast(word.String, UP_w)
435 # Note: this case may be unused
436 return RightTokenForWord(w.blame_loc)
437
438 else:
439 raise AssertionError(w.tag())
440
441 raise AssertionError('for -Wreturn-type in C++')
442
443
444def TokenForLhsExpr(node):
445 # type: (sh_lhs_t) -> Token
446 """Currently unused?
447
448 Will be useful for translating YSH assignment
449 """
450 # This switch is annoying but we don't have inheritance from the sum type
451 # (because of diamond issue). We might change the schema later, which maeks
452 # it moot. See the comment in frontend/syntax.asdl.
453 UP_node = node
454 with tagswitch(node) as case:
455 if case(sh_lhs_e.Name):
456 node = cast(sh_lhs.Name, UP_node)
457 return node.left
458 elif case(sh_lhs_e.IndexedName):
459 node = cast(sh_lhs.IndexedName, UP_node)
460 return node.left
461 else:
462 # Should not see UnparsedIndex
463 raise AssertionError()
464
465 raise AssertionError()
466
467
468# TODO: Token instead of loc_t once all cases are implemented
469def TokenForExpr(node):
470 # type: (expr_t) -> loc_t
471 """Returns the token associated with the given expression."""
472
473 UP_node = node # type: expr_t
474 with tagswitch(node) as case:
475 if case(expr_e.Const):
476 node = cast(expr.Const, UP_node)
477 return node.c
478
479 elif case(expr_e.Var):
480 node = cast(expr.Var, UP_node)
481 return node.left
482
483 elif case(expr_e.Place):
484 node = cast(expr.Place, UP_node)
485 return node.blame_tok
486
487 elif case(expr_e.CommandSub):
488 node = cast(CommandSub, UP_node)
489 return node.left_token
490
491 elif case(expr_e.ShArrayLiteral):
492 node = cast(ShArrayLiteral, UP_node)
493 return node.left
494
495 elif case(expr_e.DoubleQuoted):
496 node = cast(DoubleQuoted, UP_node)
497 return node.left
498
499 elif case(expr_e.SingleQuoted):
500 node = cast(SingleQuoted, UP_node)
501 return node.left
502
503 elif case(expr_e.BracedVarSub):
504 node = cast(BracedVarSub, UP_node)
505 return node.left
506
507 elif case(expr_e.SimpleVarSub):
508 node = cast(SimpleVarSub, UP_node)
509 return node.tok
510
511 elif case(expr_e.Unary):
512 node = cast(expr.Unary, UP_node)
513 return node.op
514
515 elif case(expr_e.Binary):
516 node = cast(expr.Binary, UP_node)
517 return node.op
518
519 elif case(expr_e.Slice):
520 node = cast(expr.Slice, UP_node)
521 return node.op
522
523 elif case(expr_e.Range):
524 node = cast(expr.Range, UP_node)
525 return node.op
526
527 elif case(expr_e.Compare):
528 node = cast(expr.Compare, UP_node)
529 # TODO: use operator instead?
530 return TokenForExpr(node.left)
531
532 elif case(expr_e.IfExp):
533 # TODO
534 return loc.Missing
535
536 elif case(expr_e.List):
537 node = cast(expr.List, UP_node)
538 return node.left
539
540 elif case(expr_e.Tuple):
541 node = cast(expr.Tuple, UP_node)
542 return node.left
543
544 elif case(expr_e.Dict):
545 node = cast(expr.Dict, UP_node)
546 return node.left
547
548 elif case(expr_e.ListComp):
549 node = cast(expr.ListComp, UP_node)
550 return node.left
551
552 elif case(expr_e.GeneratorExp):
553 # TODO
554 return loc.Missing
555
556 elif case(expr_e.Lambda):
557 # TODO
558 return loc.Missing
559
560 elif case(expr_e.FuncCall):
561 node = cast(expr.FuncCall, UP_node)
562 return node.args.left
563
564 elif case(expr_e.Subscript):
565 node = cast(Subscript, UP_node)
566 return node.left
567
568 elif case(expr_e.Attribute):
569 node = cast(Attribute, UP_node)
570 return node.op
571
572 elif case(expr_e.Eggex):
573 node = cast(Eggex, UP_node)
574 return node.left
575
576 else:
577 raise AssertionError(node.__class__.__name__)