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

663 lines, 381 significant
1#!/usr/bin/env python2
2from __future__ import print_function
3
4from _devbuild.gen.runtime_asdl import cmd_value, ProcArgs, Cell
5from _devbuild.gen.syntax_asdl import (loc, loc_t, ArgList, command_t, Token,
6 debug_frame_t)
7from _devbuild.gen.value_asdl import (value, value_e, value_t, RegexMatch, Obj,
8 cmd_frag, cmd_frag_e, cmd_frag_str,
9 LiteralBlock)
10from core import error
11from core.error import e_usage
12from frontend import location
13from mycpp import mops
14from mycpp import mylib
15from mycpp.mylib import log, tagswitch, NewDict
16
17from typing import Dict, List, Optional, cast
18
19_ = log
20
21
22def DoesNotAccept(proc_args):
23 # type: (Optional[ProcArgs]) -> None
24 if proc_args is not None:
25 e_usage('got unexpected typed args', proc_args.typed_args.left)
26
27
28if 0:
29
30 def OptionalCommandBlock(cmd_val):
31 # type: (cmd_value.Argv) -> Optional[value.Command]
32 """
33 Unused, the builtins don't take value.Command - they take a command_t CommandFrag
34 """
35 cmd = None # type: Optional[value.Command]
36 if cmd_val.proc_args:
37 r = ReaderForProc(cmd_val)
38 cmd = r.OptionalCommandBlock()
39 r.Done()
40 return cmd
41
42
43def OptionalBlockAsFrag(cmd_val):
44 # type: (cmd_value.Argv) -> Optional[command_t]
45 """Helper for cd, etc."""
46
47 r = ReaderForProc(cmd_val)
48 cmd = r.OptionalBlockAsFrag()
49 r.Done()
50 return cmd
51
52
53def RequiredBlockAsFrag(cmd_val):
54 # type: (cmd_value.Argv) -> Optional[command_t]
55 """Helper for try, shopt, etc."""
56
57 r = ReaderForProc(cmd_val)
58 cmd = r.RequiredBlockAsFrag()
59 r.Done()
60 return cmd
61
62
63def GetCommandFrag(bound):
64 # type: (value.Command) -> command_t
65
66 frag = bound.frag
67 with tagswitch(frag) as case:
68 if case(cmd_frag_e.LiteralBlock):
69 lit = cast(LiteralBlock, frag)
70 return lit.brace_group
71 elif case(cmd_frag_e.Expr):
72 expr = cast(cmd_frag.Expr, frag)
73 return expr.c
74 else:
75 raise AssertionError(cmd_frag_str(frag.tag()))
76
77
78def ReaderForProc(cmd_val):
79 # type: (cmd_value.Argv) -> Reader
80
81 proc_args = cmd_val.proc_args
82
83 if proc_args:
84 # mycpp rewrite: doesn't understand 'or' pattern
85 pos_args = (proc_args.pos_args
86 if proc_args.pos_args is not None else [])
87 # mycpp rewrite: mycpp should support NewDict() within conditional expression
88 if proc_args.named_args is not None:
89 named_args = proc_args.named_args
90 else:
91 named_args = NewDict()
92
93 arg_list = (proc_args.typed_args if proc_args.typed_args is not None
94 else ArgList.CreateNull())
95 block_arg = proc_args.block_arg
96 else:
97 pos_args = []
98 named_args = NewDict()
99 arg_list = ArgList.CreateNull()
100 block_arg = None
101
102 rd = Reader(pos_args, named_args, block_arg, arg_list)
103
104 # Fix location info bug with 'try' or try foo' -- it should get a typed arg
105 rd.SetFallbackLocation(cmd_val.arg_locs[0])
106 return rd
107
108
109class Reader(object):
110 """
111 func f(a Str) { echo hi }
112
113 is equivalent to
114
115 t = typed_args.Reader(pos_args, named_args)
116 a = t.PosStr()
117 t.Done() # checks for no more args
118
119 func f(a Str, b Int, ...args; c=0, d='foo', ...named) { echo hi }
120 echo hi
121 }
122
123 is equivalent to
124
125 t = typed_args.Reader(pos_args, named_args)
126 a = t.PosStr()
127 b = t.PosInt()
128 args = t.RestPos()
129
130 t.NamedInt('c', 0)
131 t.NamedStr('d', 'foo')
132 named = t.RestNamed()
133
134 t.Done()
135
136 procs have more options:
137
138 proc p(a, b; a Str, b Int; c=0; block) { echo hi }
139
140 Builtin procs use:
141
142 - args.Reader() and generated flag_def.py APIs for the words
143 - typed_args.Reader() for the positional/named typed args, constructed with
144 ReaderForProc()
145 """
146
147 def __init__(
148 self,
149 pos_args, # type: List[value_t]
150 named_args, # type: Dict[str, value_t]
151 block_arg, # type: Optional[value_t]
152 arg_list, # type: ArgList
153 is_bound=False, # type: bool
154 ):
155 # type: (...) -> None
156
157 self.pos_args = pos_args
158 self.pos_consumed = 0
159 # TODO: Add LHS of attribute expression to value.BoundFunc and pass
160 # that through to here?
161 self.is_bound = is_bound
162 self.named_args = named_args
163 self.block_arg = block_arg
164
165 # Note: may be ArgList.CreateNull()
166 self.arg_list = arg_list
167
168 self.fallback_loc = loc.Missing # type: loc_t
169
170 def SetFallbackLocation(self, blame_loc):
171 # type: (loc_t) -> None
172 """ In case of empty ArgList, the location we'll blame """
173 self.fallback_loc = blame_loc
174
175 def LeftParenToken(self):
176 # type: () -> Token
177 """ Used by functions in library/func_misc.py """
178 return self.arg_list.left
179
180 def LeastSpecificLocation(self):
181 # type: () -> loc_t
182 """Returns the least specific blame location.
183
184 Applicable to procs as well.
185 """
186 # arg_list.left may be None for 'json write', which uses ReaderForProc,
187 # ArgList.CreateNull()
188 if self.arg_list.left:
189 return self.arg_list.left
190
191 return self.fallback_loc
192
193 ### Typed positional args
194
195 def BlamePos(self):
196 # type: () -> loc_t
197 """Returns the location of the most recently consumed argument.
198
199 If no arguments have been consumed, the location of the function call
200 is returned.
201 """
202 pos = self.pos_consumed - 1
203 if self.is_bound:
204 # Token for the first "argument" of a bound function call isn't in
205 # the same part of the expression
206 pos -= 1
207
208 if self.arg_list.pos_args is None:
209 # PluginCall() and CallConvertFunc() don't have pos_args
210 return self.LeastSpecificLocation()
211
212 if 0 <= pos and pos < len(self.arg_list.pos_args):
213 l = location.TokenForExpr(self.arg_list.pos_args[pos])
214
215 if l is not None:
216 return l
217
218 # Fall back on call
219 return self.LeastSpecificLocation()
220
221 def PosValue(self):
222 # type: () -> value_t
223 if len(self.pos_args) == 0:
224 # TODO: Print the builtin name
225 raise error.TypeErrVerbose(
226 'Expected at least %d typed args, but only got %d' %
227 (self.pos_consumed + 1, self.pos_consumed),
228 self.LeastSpecificLocation())
229
230 self.pos_consumed += 1
231 val = self.pos_args.pop(0)
232
233 # Should be value.Null
234 assert val is not None
235 return val
236
237 def OptionalValue(self):
238 # type: () -> Optional[value_t]
239 if len(self.pos_args) == 0:
240 return None
241 self.pos_consumed += 1
242 return self.pos_args.pop(0)
243
244 def _ToStr(self, val):
245 # type: (value_t) -> str
246 if val.tag() == value_e.Str:
247 return cast(value.Str, val).s
248
249 raise error.TypeErr(val, 'Arg %d should be a Str' % self.pos_consumed,
250 self.BlamePos())
251
252 def _ToBool(self, val):
253 # type: (value_t) -> bool
254 if val.tag() == value_e.Bool:
255 return cast(value.Bool, val).b
256
257 raise error.TypeErr(val, 'Arg %d should be a Bool' % self.pos_consumed,
258 self.BlamePos())
259
260 def _ToInt(self, val):
261 # type: (value_t) -> mops.BigInt
262 if val.tag() == value_e.Int:
263 return cast(value.Int, val).i
264
265 raise error.TypeErr(val, 'Arg %d should be an Int' % self.pos_consumed,
266 self.BlamePos())
267
268 def _ToFloat(self, val):
269 # type: (value_t) -> float
270 if val.tag() == value_e.Float:
271 return cast(value.Float, val).f
272
273 raise error.TypeErr(val,
274 'Arg %d should be a Float' % self.pos_consumed,
275 self.BlamePos())
276
277 def _ToInternalStringArray(self, val):
278 # type: (value_t) -> List[str]
279 if val.tag() == value_e.InternalStringArray:
280 return cast(value.InternalStringArray, val).strs
281
282 raise error.TypeErr(
283 val, 'Arg %d should be an InternalStringArray' % self.pos_consumed,
284 self.BlamePos())
285
286 def _ToBashArray(self, val):
287 # type: (value_t) -> value.BashArray
288 if val.tag() == value_e.BashArray:
289 return cast(value.BashArray, val)
290
291 raise error.TypeErr(val,
292 'Arg %d should be a BashArray' % self.pos_consumed,
293 self.BlamePos())
294
295 def _ToList(self, val):
296 # type: (value_t) -> List[value_t]
297 if val.tag() == value_e.List:
298 return cast(value.List, val).items
299
300 raise error.TypeErr(val, 'Arg %d should be a List' % self.pos_consumed,
301 self.BlamePos())
302
303 def _ToDict(self, val):
304 # type: (value_t) -> Dict[str, value_t]
305 if val.tag() == value_e.Dict:
306 return cast(value.Dict, val).d
307
308 raise error.TypeErr(val, 'Arg %d should be a Dict' % self.pos_consumed,
309 self.BlamePos())
310
311 def _ToObj(self, val):
312 # type: (value_t) -> Obj
313 if val.tag() == value_e.Obj:
314 return cast(Obj, val)
315
316 raise error.TypeErr(val, 'Arg %d should be a Obj' % self.pos_consumed,
317 self.BlamePos())
318
319 def _ToPlace(self, val):
320 # type: (value_t) -> value.Place
321 if val.tag() == value_e.Place:
322 return cast(value.Place, val)
323
324 raise error.TypeErr(val,
325 'Arg %d should be a Place' % self.pos_consumed,
326 self.BlamePos())
327
328 def _ToMatch(self, val):
329 # type: (value_t) -> RegexMatch
330 if val.tag() == value_e.Match:
331 return cast(RegexMatch, val)
332
333 raise error.TypeErr(val,
334 'Arg %d should be a Match' % self.pos_consumed,
335 self.BlamePos())
336
337 def _ToEggex(self, val):
338 # type: (value_t) -> value.Eggex
339 if val.tag() == value_e.Eggex:
340 return cast(value.Eggex, val)
341
342 raise error.TypeErr(val,
343 'Arg %d should be an Eggex' % self.pos_consumed,
344 self.BlamePos())
345
346 def _ToExpr(self, val):
347 # type: (value_t) -> value.Expr
348 if val.tag() == value_e.Expr:
349 return cast(value.Expr, val)
350
351 raise error.TypeErr(val, 'Arg %d should be a Expr' % self.pos_consumed,
352 self.BlamePos())
353
354 def _ToFrame(self, val):
355 # type: (value_t) -> Dict[str, Cell]
356 if val.tag() == value_e.Frame:
357 return cast(value.Frame, val).frame
358
359 raise error.TypeErr(val,
360 'Arg %d should be a Frame' % self.pos_consumed,
361 self.BlamePos())
362
363 def _ToDebugFrame(self, val):
364 # type: (value_t) -> debug_frame_t
365 if val.tag() == value_e.DebugFrame:
366 return cast(value.DebugFrame, val).frame
367
368 raise error.TypeErr(
369 val, 'Arg %d should be a DebugFrame' % self.pos_consumed,
370 self.BlamePos())
371
372 def _ToCommandFrag(self, val):
373 # type: (value_t) -> command_t
374 if val.tag() == value_e.CommandFrag:
375 return cast(value.CommandFrag, val).c
376
377 # Builtins like shopt, cd, try rely on this, because proc argument
378 # evaluation gives you a value.Command, yet they operate on a
379 # CommandFrag.
380 #
381 # In YSH, we do this with the getCommandFrag() builtin, which returns
382 # an UNBOUND version of the command. Hm.
383
384 if val.tag() == value_e.Command:
385 bound = cast(value.Command, val)
386 return GetCommandFrag(bound)
387
388 raise error.TypeErr(
389 val, 'Arg %d should be a CommandFrag' % self.pos_consumed,
390 self.BlamePos())
391
392 def _ToCommand(self, val):
393 # type: (value_t) -> value.Command
394 if val.tag() == value_e.Command:
395 return cast(value.Command, val)
396 raise error.TypeErr(val,
397 'Arg %d should be a Command' % self.pos_consumed,
398 self.BlamePos())
399
400 def PosStr(self):
401 # type: () -> str
402 val = self.PosValue()
403 return self._ToStr(val)
404
405 def OptionalStr(self, default_=None):
406 # type: (Optional[str]) -> Optional[str]
407 val = self.OptionalValue()
408 if val is None:
409 return default_
410 return self._ToStr(val)
411
412 def PosBool(self):
413 # type: () -> bool
414 val = self.PosValue()
415 return self._ToBool(val)
416
417 def PosInt(self):
418 # type: () -> mops.BigInt
419 val = self.PosValue()
420 return self._ToInt(val)
421
422 def OptionalInt(self, default_):
423 # type: (int) -> mops.BigInt
424 val = self.OptionalValue()
425 if val is None:
426 return mops.IntWiden(default_)
427 return self._ToInt(val)
428
429 def PosFloat(self):
430 # type: () -> float
431 val = self.PosValue()
432 return self._ToFloat(val)
433
434 def PosInternalStringArray(self):
435 # type: () -> List[str]
436 val = self.PosValue()
437 return self._ToInternalStringArray(val)
438
439 def PosBashArray(self):
440 # type: () -> value.BashArray
441 val = self.PosValue()
442 return self._ToBashArray(val)
443
444 def PosList(self):
445 # type: () -> List[value_t]
446 val = self.PosValue()
447 return self._ToList(val)
448
449 def PosDict(self):
450 # type: () -> Dict[str, value_t]
451 val = self.PosValue()
452 return self._ToDict(val)
453
454 def PosObj(self):
455 # type: () -> Obj
456 val = self.PosValue()
457 return self._ToObj(val)
458
459 def PosPlace(self):
460 # type: () -> value.Place
461 val = self.PosValue()
462 return self._ToPlace(val)
463
464 def PosEggex(self):
465 # type: () -> value.Eggex
466 val = self.PosValue()
467 return self._ToEggex(val)
468
469 def PosMatch(self):
470 # type: () -> RegexMatch
471 val = self.PosValue()
472 return self._ToMatch(val)
473
474 def PosFrame(self):
475 # type: () -> Dict[str, Cell]
476 val = self.PosValue()
477 return self._ToFrame(val)
478
479 def PosDebugFrame(self):
480 # type: () -> debug_frame_t
481 val = self.PosValue()
482 return self._ToDebugFrame(val)
483
484 def PosCommandFrag(self):
485 # type: () -> command_t
486 val = self.PosValue()
487 return self._ToCommandFrag(val)
488
489 def PosCommand(self):
490 # type: () -> value.Command
491 val = self.PosValue()
492 return self._ToCommand(val)
493
494 def PosExpr(self):
495 # type: () -> value.Expr
496 val = self.PosValue()
497 return self._ToExpr(val)
498
499 #
500 # Block arg
501 #
502
503 def OptionalCommand(self):
504 # type: () -> Optional[value.Command]
505 if self.block_arg is None:
506 return None
507 return self._ToCommand(self.block_arg)
508
509 def RequiredBlockAsFrag(self):
510 # type: () -> command_t
511 if self.block_arg is None:
512 raise error.Usage('expected a block arg',
513 self.LeastSpecificLocation())
514 return self._ToCommandFrag(self.block_arg)
515
516 def OptionalBlockAsFrag(self):
517 # type: () -> Optional[command_t]
518 if self.block_arg is None:
519 return None
520 return self._ToCommandFrag(self.block_arg)
521
522 def RestPos(self):
523 # type: () -> List[value_t]
524 ret = self.pos_args
525 self.pos_args = []
526 return ret
527
528 ### Typed named args
529
530 def _BlameNamed(self, name):
531 # type: (str) -> loc_t
532 """Returns the location of the given named argument."""
533 # TODO: be more specific
534 return self.LeastSpecificLocation()
535
536 def NamedStr(self, param_name, default_):
537 # type: (str, str) -> str
538 if param_name not in self.named_args:
539 return default_
540
541 val = self.named_args[param_name]
542 UP_val = val
543 if val.tag() == value_e.Str:
544 mylib.dict_erase(self.named_args, param_name)
545 val = cast(value.Str, UP_val)
546 return val.s
547
548 raise error.TypeErr(val, 'Named arg %r should be a Str' % param_name,
549 self._BlameNamed(param_name))
550
551 def NamedBool(self, param_name, default_):
552 # type: (str, bool) -> bool
553 if param_name not in self.named_args:
554 return default_
555
556 val = self.named_args[param_name]
557 UP_val = val
558 if val.tag() == value_e.Bool:
559 val = cast(value.Bool, UP_val)
560 mylib.dict_erase(self.named_args, param_name)
561 return val.b
562
563 raise error.TypeErr(val, 'Named arg %r should be a Bool' % param_name,
564 self._BlameNamed(param_name))
565
566 def NamedInt(self, param_name, default_):
567 # type: (str, int) -> mops.BigInt
568 if param_name not in self.named_args:
569 return mops.IntWiden(default_)
570
571 val = self.named_args[param_name]
572 UP_val = val
573 if val.tag() == value_e.Int:
574 val = cast(value.Int, UP_val)
575 mylib.dict_erase(self.named_args, param_name)
576 return val.i
577
578 raise error.TypeErr(val, 'Named arg %r should be a Int' % param_name,
579 self._BlameNamed(param_name))
580
581 def NamedFloat(self, param_name, default_):
582 # type: (str, float) -> float
583 if param_name not in self.named_args:
584 return default_
585
586 val = self.named_args[param_name]
587 UP_val = val
588 if val.tag() == value_e.Float:
589 val = cast(value.Float, UP_val)
590 mylib.dict_erase(self.named_args, param_name)
591 return val.f
592
593 raise error.TypeErr(val, 'Named arg %r should be a Float' % param_name,
594 self._BlameNamed(param_name))
595
596 def NamedList(self, param_name, default_):
597 # type: (str, List[value_t]) -> List[value_t]
598 if param_name not in self.named_args:
599 return default_
600
601 val = self.named_args[param_name]
602 UP_val = val
603 if val.tag() == value_e.List:
604 val = cast(value.List, UP_val)
605 mylib.dict_erase(self.named_args, param_name)
606 return val.items
607
608 raise error.TypeErr(val, 'Named arg %r should be a List' % param_name,
609 self._BlameNamed(param_name))
610
611 def NamedDict(self, param_name, default_):
612 # type: (str, Dict[str, value_t]) -> Dict[str, value_t]
613 if param_name not in self.named_args:
614 return default_
615
616 val = self.named_args[param_name]
617 UP_val = val
618 if val.tag() == value_e.Dict:
619 val = cast(value.Dict, UP_val)
620 mylib.dict_erase(self.named_args, param_name)
621 return val.d
622
623 raise error.TypeErr(val, 'Named arg %r should be a Dict' % param_name,
624 self._BlameNamed(param_name))
625
626 def RestNamed(self):
627 # type: () -> Dict[str, value_t]
628 ret = self.named_args
629 self.named_args = NewDict()
630 return ret
631
632 def Done(self):
633 # type: () -> None
634 """
635 Check that no extra arguments were passed
636
637 4 checks: words, pos, named, block
638
639 It's a little weird that we report all errors at the end, but no
640 problem
641 """
642 # Note: Python throws TypeError on mismatch
643 if len(self.pos_args):
644 n = self.pos_consumed
645 # Excluding implicit first arg should make errors less confusing
646 if self.is_bound:
647 n -= 1
648
649 self.pos_consumed += 1 # point to the first uncomsumed arg
650
651 raise error.TypeErrVerbose(
652 'Expected %d typed args, but got %d' %
653 (n, n + len(self.pos_args)), self.BlamePos())
654
655 if len(self.named_args):
656 bad_args = ', '.join(self.named_args.keys())
657
658 blame = self.arg_list.semi_tok # type: loc_t
659 if blame is None:
660 blame = self.LeastSpecificLocation()
661
662 raise error.TypeErrVerbose(
663 'Got unexpected named args: %s' % bad_args, blame)