1 | #!/usr/bin/env python2
|
2 | """
|
3 | User-defined funcs and procs
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | from _devbuild.gen.id_kind_asdl import Id
|
8 | from _devbuild.gen.runtime_asdl import cmd_value, ProcArgs, Cell
|
9 | from _devbuild.gen.syntax_asdl import (proc_sig, proc_sig_e, Param, ParamGroup,
|
10 | NamedArg, Func, loc, ArgList, expr,
|
11 | expr_e, expr_t)
|
12 | from _devbuild.gen.value_asdl import (value, value_e, value_t, ProcDefaults,
|
13 | LeftName)
|
14 |
|
15 | from core import error
|
16 | from core.error import e_die
|
17 | from core import state
|
18 | from core import vm
|
19 | from frontend import lexer
|
20 | from frontend import typed_args
|
21 | from mycpp import mylib
|
22 | from mycpp.mylib import log, NewDict
|
23 |
|
24 | from typing import List, Tuple, Dict, Optional, cast, TYPE_CHECKING
|
25 | if 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 |
|
38 | def _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 |
|
44 | def _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 |
|
58 | def _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 |
|
71 | def 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 |
|
91 | def 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 |
|
141 | def _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 |
|
157 | def _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 |
|
180 | def _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 |
|
210 | def 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 |
|
282 | def _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 |
|
331 | def _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 |
|
381 | def _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 |
|
422 | def _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 |
|
459 | def 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 |
|
570 | def 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
|