OILS / ysh / func_proc.py View on Github | oilshell.org

597 lines, 399 significant
1#!/usr/bin/env python2
2"""
3User-defined funcs and procs
4"""
5from __future__ import print_function
6
7from _devbuild.gen.id_kind_asdl import Id
8from _devbuild.gen.runtime_asdl import cmd_value, ProcArgs, Cell
9from _devbuild.gen.syntax_asdl import (proc_sig, proc_sig_e, Param, ParamGroup,
10 NamedArg, Func, loc, ArgList, expr,
11 expr_e, expr_t)
12from _devbuild.gen.value_asdl import (value, value_e, value_t, ProcDefaults,
13 LeftName)
14
15from core import error
16from core.error import e_die
17from core import state
18from core import vm
19from frontend import lexer
20from frontend import typed_args
21from mycpp import mylib
22from mycpp.mylib import log, NewDict
23
24from typing import List, Tuple, Dict, Optional, cast, TYPE_CHECKING
25if TYPE_CHECKING:
26 from _devbuild.gen.syntax_asdl import command, loc_t
27 from osh import cmd_eval
28 from ysh import expr_eval
29
30_ = log
31
32# TODO:
33# - use _EvalExpr more?
34# - a single with state.ctx_YshExpr -- I guess that's faster
35# - although EvalExpr() can take param.blame_tok
36
37
38def _DisallowMutableDefault(val, blame_loc):
39 # type: (value_t, loc_t) -> None
40 if val.tag() in (value_e.List, value_e.Dict):
41 raise error.TypeErr(val, "Default values can't be mutable", blame_loc)
42
43
44def _EvalPosDefaults(expr_ev, pos_params):
45 # type: (expr_eval.ExprEvaluator, List[Param]) -> List[value_t]
46 """Shared between func and proc: Eval defaults for positional params"""
47
48 no_val = None # type: value_t
49 pos_defaults = [no_val] * len(pos_params)
50 for i, p in enumerate(pos_params):
51 if p.default_val:
52 val = expr_ev.EvalExpr(p.default_val, p.blame_tok)
53 _DisallowMutableDefault(val, p.blame_tok)
54 pos_defaults[i] = val
55 return pos_defaults
56
57
58def _EvalNamedDefaults(expr_ev, named_params):
59 # type: (expr_eval.ExprEvaluator, List[Param]) -> Dict[str, value_t]
60 """Shared between func and proc: Eval defaults for named params"""
61
62 named_defaults = NewDict() # type: Dict[str, value_t]
63 for i, p in enumerate(named_params):
64 if p.default_val:
65 val = expr_ev.EvalExpr(p.default_val, p.blame_tok)
66 _DisallowMutableDefault(val, p.blame_tok)
67 named_defaults[p.name] = val
68 return named_defaults
69
70
71def EvalFuncDefaults(
72 expr_ev, # type: expr_eval.ExprEvaluator
73 func, # type: Func
74):
75 # type: (...) -> Tuple[List[value_t], Dict[str, value_t]]
76 """Evaluate default args for funcs, at time of DEFINITION, not call."""
77
78 if func.positional:
79 pos_defaults = _EvalPosDefaults(expr_ev, func.positional.params)
80 else:
81 pos_defaults = None
82
83 if func.named:
84 named_defaults = _EvalNamedDefaults(expr_ev, func.named.params)
85 else:
86 named_defaults = None
87
88 return pos_defaults, named_defaults
89
90
91def EvalProcDefaults(expr_ev, sig):
92 # type: (expr_eval.ExprEvaluator, proc_sig.Closed) -> ProcDefaults
93 """Evaluate default args for procs, at time of DEFINITION, not call."""
94
95 no_val = None # type: value_t
96
97 if sig.word:
98 word_defaults = [no_val] * len(sig.word.params)
99 for i, p in enumerate(sig.word.params):
100 if p.default_val:
101 val = expr_ev.EvalExpr(p.default_val, p.blame_tok)
102 if val.tag() != value_e.Str:
103 raise error.TypeErr(
104 val, 'Default val for word param must be Str',
105 p.blame_tok)
106
107 word_defaults[i] = val
108 else:
109 word_defaults = None
110
111 if sig.positional:
112 pos_defaults = _EvalPosDefaults(expr_ev, sig.positional.params)
113 else:
114 pos_defaults = None # in case there's a block param
115
116 if sig.named:
117 named_defaults = _EvalNamedDefaults(expr_ev, sig.named.params)
118 else:
119 named_defaults = None
120
121 # cd /tmp (; ; myblock)
122 if sig.block_param:
123 exp = sig.block_param.default_val
124 if exp:
125 block_default = expr_ev.EvalExpr(exp, sig.block_param.blame_tok)
126 # It can only be ^() or null
127 if block_default.tag() not in (value_e.Null, value_e.Command):
128 raise error.TypeErr(
129 block_default,
130 "Default value for block should be Command or Null",
131 sig.block_param.blame_tok)
132 else:
133 block_default = None # no default, different than value.Null
134 else:
135 block_default = None
136
137 return ProcDefaults(word_defaults, pos_defaults, named_defaults,
138 block_default)
139
140
141def _EvalPosArgs(expr_ev, exprs, pos_args):
142 # type: (expr_eval.ExprEvaluator, List[expr_t], List[value_t]) -> None
143 """Shared between func and proc: evaluate positional args."""
144
145 for e in exprs:
146 UP_e = e
147 if e.tag() == expr_e.Spread:
148 e = cast(expr.Spread, UP_e)
149 val = expr_ev._EvalExpr(e.child)
150 if val.tag() != value_e.List:
151 raise error.TypeErr(val, 'Spread expected a List', e.left)
152 pos_args.extend(cast(value.List, val).items)
153 else:
154 pos_args.append(expr_ev._EvalExpr(e))
155
156
157def _EvalNamedArgs(expr_ev, named_exprs):
158 # type: (expr_eval.ExprEvaluator, List[NamedArg]) -> Dict[str, value_t]
159 """Shared between func and proc: evaluate named args."""
160
161 named_args = NewDict() # type: Dict[str, value_t]
162 for n in named_exprs:
163 val_expr = n.value
164 UP_val_expr = val_expr
165 if val_expr.tag() == expr_e.Spread:
166 val_expr = cast(expr.Spread, UP_val_expr)
167 val = expr_ev._EvalExpr(val_expr.child)
168 if val.tag() != value_e.Dict:
169 raise error.TypeErr(val, 'Spread expected a Dict',
170 val_expr.left)
171 named_args.update(cast(value.Dict, val).d)
172 else:
173 val = expr_ev.EvalExpr(n.value, n.name)
174 name = lexer.TokenVal(n.name)
175 named_args[name] = val
176
177 return named_args
178
179
180def _EvalArgList(
181 expr_ev, # type: expr_eval.ExprEvaluator
182 args, # type: ArgList
183 self_val=None # type: Optional[value_t]
184):
185 # type: (...) -> Tuple[List[value_t], Optional[Dict[str, value_t]]]
186 """Evaluate arg list for funcs.
187
188 This is a PRIVATE METHOD on ExprEvaluator, but it's in THIS FILE, because I
189 want it to be next to EvalTypedArgsToProc, which is similar.
190
191 It's not valid to call this without the EvalExpr() wrapper:
192
193 with state.ctx_YshExpr(...) # required to call this
194 ...
195 """
196 pos_args = [] # type: List[value_t]
197
198 if self_val: # self/this argument
199 pos_args.append(self_val)
200
201 _EvalPosArgs(expr_ev, args.pos_args, pos_args)
202
203 named_args = None # type: Dict[str, value_t]
204 if args.named_args is not None:
205 named_args = _EvalNamedArgs(expr_ev, args.named_args)
206
207 return pos_args, named_args
208
209
210def EvalTypedArgsToProc(
211 expr_ev, # type: expr_eval.ExprEvaluator
212 current_frame, # type: Dict[str, Cell]
213 module_frame, # type: Dict[str, Cell]
214 mutable_opts, # type: state.MutableOpts
215 node, # type: command.Simple
216 proc_args, # type: ProcArgs
217):
218 # type: (...) -> None
219 """Evaluate word, typed, named, and block args for a proc."""
220 proc_args.typed_args = node.typed_args
221
222 # We only got here if the call looks like
223 # p (x)
224 # p { echo hi }
225 # p () { echo hi }
226 # So allocate this unconditionally
227 proc_args.pos_args = []
228
229 ty = node.typed_args
230 if ty:
231 if ty.left.id == Id.Op_LBracket: # assert [42 === x]
232 # Defer evaluation by wrapping in value.Expr
233
234 for exp in ty.pos_args:
235 proc_args.pos_args.append(
236 value.Expr(exp, current_frame, module_frame))
237 # TODO: ...spread is illegal
238
239 n1 = ty.named_args
240 if n1 is not None:
241 proc_args.named_args = NewDict()
242 for named_arg in n1:
243 name = lexer.TokenVal(named_arg.name)
244 proc_args.named_args[name] = value.Expr(
245 named_arg.value, current_frame, module_frame)
246 # TODO: ...spread is illegal
247
248 else: # json write (x)
249 with state.ctx_YshExpr(mutable_opts): # What EvalExpr() does
250 _EvalPosArgs(expr_ev, ty.pos_args, proc_args.pos_args)
251
252 if ty.named_args is not None:
253 proc_args.named_args = _EvalNamedArgs(
254 expr_ev, ty.named_args)
255
256 if ty.block_expr and node.block:
257 e_die("Can't accept both block expression and block literal",
258 node.block.brace_group.left)
259
260 # p ( ; ; block) is an expression to be evaluated
261 if ty.block_expr:
262 # fallback location is (
263 proc_args.block_arg = expr_ev.EvalExpr(ty.block_expr, ty.left)
264
265 # p { echo hi } is an unevaluated block
266 if node.block:
267 # Attach current frame to command fragment
268 proc_args.block_arg = value.Command(node.block, current_frame,
269 module_frame)
270
271 # Add location info so the cmd_val looks the same for both:
272 # cd /tmp (; ; ^(echo hi))
273 # cd /tmp { echo hi }
274 if not proc_args.typed_args:
275 proc_args.typed_args = ArgList.CreateNull()
276
277 # Also add locations for error message: ls { echo invalid }
278 proc_args.typed_args.left = node.block.brace_group.left
279 proc_args.typed_args.right = node.block.brace_group.right
280
281
282def _BindWords(
283 proc_name, # type: str
284 group, # type: ParamGroup
285 defaults, # type: List[value_t]
286 cmd_val, # type: cmd_value.Argv
287 mem, # type: state.Mem
288 blame_loc, # type: loc_t
289):
290 # type: (...) -> None
291
292 argv = cmd_val.argv[1:]
293 num_args = len(argv)
294 for i, p in enumerate(group.params):
295 if i < num_args:
296 val = value.Str(argv[i]) # type: value_t
297 else: # default args were evaluated on definition
298 val = defaults[i]
299 if val is None:
300 raise error.Expr(
301 "proc %r wasn't passed word param %r" %
302 (proc_name, p.name), blame_loc)
303
304 mem.SetLocalName(LeftName(p.name, p.blame_tok), val)
305
306 # ...rest
307
308 num_params = len(group.params)
309 rest = group.rest_of
310 if rest:
311 lval = LeftName(rest.name, rest.blame_tok)
312
313 items = [value.Str(s)
314 for s in argv[num_params:]] # type: List[value_t]
315 rest_val = value.List(items)
316 mem.SetLocalName(lval, rest_val)
317 else:
318 if num_args > num_params:
319 if len(cmd_val.arg_locs):
320 # point to the first extra one
321 extra_loc = cmd_val.arg_locs[num_params + 1] # type: loc_t
322 else:
323 extra_loc = loc.Missing
324
325 # Too many arguments.
326 raise error.Expr(
327 "proc %r takes %d words, but got %d" %
328 (proc_name, num_params, num_args), extra_loc)
329
330
331def _BindTyped(
332 code_name, # type: str
333 group, # type: Optional[ParamGroup]
334 defaults, # type: List[value_t]
335 pos_args, # type: Optional[List[value_t]]
336 mem, # type: state.Mem
337 blame_loc, # type: loc_t
338):
339 # type: (...) -> None
340
341 if pos_args is None:
342 pos_args = []
343
344 num_args = len(pos_args)
345 num_params = 0
346
347 i = 0
348
349 if group:
350 for p in group.params:
351 if i < num_args:
352 val = pos_args[i]
353 else:
354 val = defaults[i]
355 if val is None:
356 raise error.Expr(
357 "%r wasn't passed typed param %r" %
358 (code_name, p.name), blame_loc)
359
360 mem.SetLocalName(LeftName(p.name, p.blame_tok), val)
361 i += 1
362 num_params += len(group.params)
363
364 # ...rest
365
366 if group:
367 rest = group.rest_of
368 if rest:
369 lval = LeftName(rest.name, rest.blame_tok)
370
371 rest_val = value.List(pos_args[num_params:])
372 mem.SetLocalName(lval, rest_val)
373 else:
374 if num_args > num_params:
375 # Too many arguments.
376 raise error.Expr(
377 "%r takes %d typed args, but got %d" %
378 (code_name, num_params, num_args), blame_loc)
379
380
381def _BindNamed(
382 code_name, # type: str
383 group, # type: ParamGroup
384 defaults, # type: Dict[str, value_t]
385 named_args, # type: Optional[Dict[str, value_t]]
386 mem, # type: state.Mem
387 blame_loc, # type: loc_t
388):
389 # type: (...) -> None
390
391 if named_args is None:
392 named_args = NewDict()
393
394 for p in group.params:
395 val = named_args.get(p.name)
396 if val is None:
397 val = defaults.get(p.name)
398 if val is None:
399 raise error.Expr(
400 "%r wasn't passed named param %r" % (code_name, p.name),
401 blame_loc)
402
403 mem.SetLocalName(LeftName(p.name, p.blame_tok), val)
404 # Remove bound args
405 mylib.dict_erase(named_args, p.name)
406
407 # ...rest
408 rest = group.rest_of
409 if rest:
410 lval = LeftName(rest.name, rest.blame_tok)
411 mem.SetLocalName(lval, value.Dict(named_args))
412 else:
413 num_args = len(named_args)
414 num_params = len(group.params)
415 if num_args > num_params:
416 # Too many arguments.
417 raise error.Expr(
418 "%r takes %d named args, but got %d" %
419 (code_name, num_params, num_args), blame_loc)
420
421
422def _BindFuncArgs(func, rd, mem):
423 # type: (value.Func, typed_args.Reader, state.Mem) -> None
424
425 node = func.parsed
426 blame_loc = rd.LeftParenToken()
427
428 ### Handle positional args
429
430 if node.positional:
431 _BindTyped(func.name, node.positional, func.pos_defaults, rd.pos_args,
432 mem, blame_loc)
433 else:
434 if rd.pos_args is not None:
435 num_pos = len(rd.pos_args)
436 if num_pos != 0:
437 raise error.Expr(
438 "Func %r takes no positional args, but got %d" %
439 (func.name, num_pos), blame_loc)
440
441 semi = rd.arg_list.semi_tok
442 if semi is not None:
443 blame_loc = semi
444
445 ### Handle named args
446
447 if node.named:
448 _BindNamed(func.name, node.named, func.named_defaults, rd.named_args,
449 mem, blame_loc)
450 else:
451 if rd.named_args is not None:
452 num_named = len(rd.named_args)
453 if num_named != 0:
454 raise error.Expr(
455 "Func %r takes no named args, but got %d" %
456 (func.name, num_named), blame_loc)
457
458
459def BindProcArgs(proc, cmd_val, mem):
460 # type: (value.Proc, cmd_value.Argv, state.Mem) -> None
461
462 proc_args = cmd_val.proc_args
463
464 UP_sig = proc.sig
465 if UP_sig.tag() != proc_sig_e.Closed: # proc is-closed ()
466 return
467
468 sig = cast(proc_sig.Closed, UP_sig)
469
470 # Note: we don't call _BindX() when there is no corresponding param group.
471 # This saves a few allocations, because most procs won't have all 3 types
472 # of args.
473
474 blame_loc = loc.Missing # type: loc_t
475
476 ### Handle word args
477
478 if len(cmd_val.arg_locs) > 0:
479 blame_loc = cmd_val.arg_locs[0]
480
481 if sig.word:
482 _BindWords(proc.name, sig.word, proc.defaults.for_word, cmd_val, mem,
483 blame_loc)
484 else:
485 num_word = len(cmd_val.argv)
486 if num_word != 1:
487 raise error.Expr(
488 "Proc %r takes no word args, but got %d" %
489 (proc.name, num_word - 1), blame_loc)
490
491 ### Handle typed positional args.
492
493 if proc_args and proc_args.typed_args: # blame ( of call site
494 blame_loc = proc_args.typed_args.left
495
496 if cmd_val.self_obj:
497 pos_args = [cmd_val.self_obj] # type: List[value_t]
498 if proc_args:
499 pos_args.extend(proc_args.pos_args)
500 else:
501 if proc_args: # save an allocation in this common case
502 pos_args = proc_args.pos_args
503 else:
504 pos_args = []
505
506 if sig.positional:
507 _BindTyped(proc.name, sig.positional, proc.defaults.for_typed,
508 pos_args, mem, blame_loc)
509 else:
510 if cmd_val.self_obj is not None:
511 raise error.Expr(
512 "Using proc %r as __invoke__ requires a 'self' param" %
513 proc.name, blame_loc)
514 if pos_args is not None:
515 num_pos = len(pos_args)
516 if num_pos != 0:
517 raise error.Expr(
518 "Proc %r takes no typed args, but got %d" %
519 (proc.name, num_pos), blame_loc)
520
521 ### Handle typed named args
522
523 if proc_args and proc_args.typed_args: # blame ; of call site if possible
524 semi = proc_args.typed_args.semi_tok
525 if semi is not None:
526 blame_loc = semi
527
528 named_args = proc_args.named_args if proc_args else None
529 if sig.named:
530 _BindNamed(proc.name, sig.named, proc.defaults.for_named, named_args,
531 mem, blame_loc)
532 else:
533 if named_args is not None:
534 num_named = len(named_args)
535 if num_named != 0:
536 raise error.Expr(
537 "Proc %r takes no named args, but got %d" %
538 (proc.name, num_named), blame_loc)
539
540 # Maybe blame second ; of call site. Because value_t doesn't generally
541 # have location info, as opposed to expr_t.
542 if proc_args and proc_args.typed_args:
543 semi = proc_args.typed_args.semi_tok2
544 if semi is not None:
545 blame_loc = semi
546
547 ### Handle block arg
548
549 block_param = sig.block_param
550 block_arg = proc_args.block_arg if proc_args else None
551
552 if block_param:
553 if block_arg is None:
554 block_arg = proc.defaults.for_block
555 if block_arg is None:
556 raise error.Expr(
557 "%r wasn't passed block param %r" %
558 (proc.name, block_param.name), blame_loc)
559
560 mem.SetLocalName(LeftName(block_param.name, block_param.blame_tok),
561 block_arg)
562
563 else:
564 if block_arg is not None:
565 raise error.Expr(
566 "Proc %r doesn't accept a block argument" % proc.name,
567 blame_loc)
568
569
570def CallUserFunc(
571 func, # type: value.Func
572 rd, # type: typed_args.Reader
573 mem, # type: state.Mem
574 cmd_ev, # type: cmd_eval.CommandEvaluator
575):
576 # type: (...) -> value_t
577
578 # Push a new stack frame
579
580 # TODO: ctx_Eval() can replace io with DummyIO type! It can possibly
581 # implement __getattr__ and __get_mutating__?
582 with state.ctx_FuncCall(mem, func):
583 _BindFuncArgs(func, rd, mem)
584
585 try:
586 cmd_ev._Execute(func.parsed.body)
587
588 return value.Null # implicit return
589 except vm.ValueControlFlow as e:
590 return e.value
591 except vm.IntControlFlow as e:
592 raise AssertionError('IntControlFlow in func')
593
594 raise AssertionError('unreachable')
595
596
597# vim: sw=4