OILS / frontend / typed_args.py View on Github | oilshell.org

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