OILS / core / state.py View on Github | oils.pub

2873 lines, 1476 significant
1# Copyright 2016 Andy Chu. All rights reserved.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7"""
8state.py - Interpreter state
9"""
10from __future__ import print_function
11import time as time_ # avoid name conflict
12
13from _devbuild.gen.id_kind_asdl import Id
14from _devbuild.gen.option_asdl import option_i
15from _devbuild.gen.runtime_asdl import (error_code_e, scope_e, scope_t, Cell)
16from _devbuild.gen.syntax_asdl import (CompoundWord, loc, loc_t, Token,
17 debug_frame, debug_frame_e,
18 debug_frame_t)
19from _devbuild.gen.types_asdl import opt_group_i
20from _devbuild.gen.value_asdl import (value, value_e, value_t, Obj, sh_lvalue,
21 sh_lvalue_e, sh_lvalue_t, LeftName,
22 y_lvalue_e, regex_match, regex_match_e,
23 regex_match_t, RegexMatch)
24from core import bash_impl
25from core import error
26from core.error import e_usage, e_die
27from core import num
28from core import optview
29from display import ui
30from core import util
31from frontend import consts
32from frontend import location
33from frontend import match
34from mycpp import mops
35from mycpp import mylib
36from mycpp.mylib import (log, print_stderr, str_switch, tagswitch, iteritems,
37 NewDict)
38from pylib import os_path
39
40from libc import HAVE_GLOB_PERIOD
41import posix_ as posix
42
43from typing import Tuple, List, Dict, Optional, Any, cast, TYPE_CHECKING
44
45if TYPE_CHECKING:
46 from _devbuild.gen.option_asdl import option_t
47 from core import alloc
48 from osh import sh_expr_eval
49
50_ = log
51
52# flags for mem.SetValue()
53SetReadOnly = 1 << 0
54ClearReadOnly = 1 << 1
55SetExport = 1 << 2
56ClearExport = 1 << 3
57SetNameref = 1 << 4
58ClearNameref = 1 << 5
59
60
61class ctx_Source(object):
62 """For source builtin."""
63
64 def __init__(self, mem, source_name, argv, source_loc):
65 # type: (Mem, str, List[str], CompoundWord) -> None
66 mem.PushSource(source_name, argv, source_loc)
67 self.mem = mem
68 self.argv = argv
69
70 # Whenever we're sourcing, the 'is-main' builtin will return 1 (false)
71 self.to_restore = self.mem.is_main
72 self.mem.is_main = False
73
74 def __enter__(self):
75 # type: () -> None
76 pass
77
78 def __exit__(self, type, value, traceback):
79 # type: (Any, Any, Any) -> None
80 self.mem.PopSource(self.argv)
81
82 self.mem.is_main = self.to_restore
83
84
85class ctx_DebugTrap(object):
86 """For trap DEBUG."""
87
88 def __init__(self, mem):
89 # type: (Mem) -> None
90 mem.running_debug_trap = True
91 self.mem = mem
92
93 def __enter__(self):
94 # type: () -> None
95 pass
96
97 def __exit__(self, type, value, traceback):
98 # type: (Any, Any, Any) -> None
99 self.mem.running_debug_trap = False
100
101
102class ctx_ErrTrap(object):
103 """For trap ERR."""
104
105 def __init__(self, mem):
106 # type: (Mem) -> None
107 mem.running_err_trap = True
108 self.mem = mem
109
110 def __enter__(self):
111 # type: () -> None
112 pass
113
114 def __exit__(self, type, value, traceback):
115 # type: (Any, Any, Any) -> None
116 self.mem.running_err_trap = False
117
118
119class ctx_Option(object):
120 """Shopt --unset errexit { false }"""
121
122 def __init__(self, mutable_opts, opt_nums, b):
123 # type: (MutableOpts, List[int], bool) -> None
124 for opt_num in opt_nums:
125 mutable_opts.Push(opt_num, b)
126 if opt_num == option_i.errexit:
127 # it wasn't disabled
128 mutable_opts.errexit_disabled_tok.append(None)
129
130 self.mutable_opts = mutable_opts
131 self.opt_nums = opt_nums
132
133 def __enter__(self):
134 # type: () -> None
135 pass
136
137 def __exit__(self, type, value, traceback):
138 # type: (Any, Any, Any) -> None
139 for opt_num in self.opt_nums: # don't bother to do it in reverse order
140 if opt_num == option_i.errexit:
141 self.mutable_opts.errexit_disabled_tok.pop()
142 self.mutable_opts.Pop(opt_num)
143
144
145class ctx_AssignBuiltin(object):
146 """Local x=$(false) is disallowed."""
147
148 def __init__(self, mutable_opts):
149 # type: (MutableOpts) -> None
150 self.strict = False
151 if mutable_opts.Get(option_i.strict_errexit):
152 mutable_opts.Push(option_i._allow_command_sub, False)
153 mutable_opts.Push(option_i._allow_process_sub, False)
154 self.strict = True
155
156 self.mutable_opts = mutable_opts
157
158 def __enter__(self):
159 # type: () -> None
160 pass
161
162 def __exit__(self, type, value, traceback):
163 # type: (Any, Any, Any) -> None
164 if self.strict:
165 self.mutable_opts.Pop(option_i._allow_command_sub)
166 self.mutable_opts.Pop(option_i._allow_process_sub)
167
168
169class ctx_YshExpr(object):
170 """Command sub must fail in 'mystring' ++ $(false)"""
171
172 def __init__(self, mutable_opts):
173 # type: (MutableOpts) -> None
174
175 # Similar to $LIB_OSH/bash-strict.sh
176
177 # TODO: consider errexit:all group, or even ysh:all
178 # It would be nice if this were more efficient
179 mutable_opts.Push(option_i.command_sub_errexit, True)
180 mutable_opts.Push(option_i.errexit, True)
181 mutable_opts.Push(option_i.pipefail, True)
182 mutable_opts.Push(option_i.inherit_errexit, True)
183 mutable_opts.Push(option_i.strict_errexit, True)
184
185 # What about nounset? This has a similar pitfall -- it's not running
186 # like YSH.
187 # e.g. var x = $(echo $zz)
188
189 self.mutable_opts = mutable_opts
190
191 def __enter__(self):
192 # type: () -> None
193 pass
194
195 def __exit__(self, type, value, traceback):
196 # type: (Any, Any, Any) -> None
197 self.mutable_opts.Pop(option_i.command_sub_errexit)
198 self.mutable_opts.Pop(option_i.errexit)
199 self.mutable_opts.Pop(option_i.pipefail)
200 self.mutable_opts.Pop(option_i.inherit_errexit)
201 self.mutable_opts.Pop(option_i.strict_errexit)
202
203
204class ctx_ErrExit(object):
205 """Manages the errexit setting.
206
207 - The user can change it with builtin 'set' at any point in the code.
208 - These constructs implicitly disable 'errexit':
209 - if / while / until conditions
210 - ! (part of pipeline)
211 - && ||
212 """
213
214 def __init__(self, mutable_opts, b, disabled_tok):
215 # type: (MutableOpts, bool, Optional[Token]) -> None
216
217 # If we're disabling it, we need a span ID. If not, then we should NOT
218 # have one.
219 assert b == (disabled_tok is None)
220
221 mutable_opts.Push(option_i.errexit, b)
222 mutable_opts.errexit_disabled_tok.append(disabled_tok)
223
224 self.strict = False
225 if mutable_opts.Get(option_i.strict_errexit):
226 mutable_opts.Push(option_i._allow_command_sub, False)
227 mutable_opts.Push(option_i._allow_process_sub, False)
228 self.strict = True
229
230 self.mutable_opts = mutable_opts
231
232 def __enter__(self):
233 # type: () -> None
234 pass
235
236 def __exit__(self, type, value, traceback):
237 # type: (Any, Any, Any) -> None
238 self.mutable_opts.errexit_disabled_tok.pop()
239 self.mutable_opts.Pop(option_i.errexit)
240
241 if self.strict:
242 self.mutable_opts.Pop(option_i._allow_command_sub)
243 self.mutable_opts.Pop(option_i._allow_process_sub)
244
245
246class OptHook(object):
247 """Interface for option hooks."""
248
249 def __init__(self):
250 # type: () -> None
251 """Empty constructor for mycpp."""
252 pass
253
254 def OnChange(self, opt0_array, opt_name, b):
255 # type: (List[bool], str, bool) -> bool
256 """This method is called whenever an option is changed.
257
258 Returns success or failure.
259 """
260 return True
261
262
263def InitOpts():
264 # type: () -> List[bool]
265
266 opt0_array = [False] * option_i.ARRAY_SIZE
267 for opt_num in consts.DEFAULT_TRUE:
268 opt0_array[opt_num] = True
269 return opt0_array
270
271
272def MakeOpts(
273 mem, # type: Mem
274 environ, # type: Dict[str, str]
275 opt_hook, # type: OptHook
276):
277 # type: (...) -> Tuple[optview.Parse, optview.Exec, MutableOpts]
278
279 # Unusual representation: opt0_array + opt_stacks. For two features:
280 #
281 # - POSIX errexit disable semantics
282 # - YSH shopt --set nullglob { ... }
283 #
284 # We could do it with a single List of stacks. But because shopt --set
285 # random_option { ... } is very uncommon, we optimize and store the ZERO
286 # element of the stack in a flat array opt0_array (default False), and then
287 # the rest in opt_stacks, where the value could be None. By allowing the
288 # None value, we save ~50 or so list objects in the common case.
289
290 opt0_array = InitOpts()
291 # Overrides, including errexit
292 no_stack = None # type: List[bool] # for mycpp
293 opt_stacks = [no_stack] * option_i.ARRAY_SIZE # type: List[List[bool]]
294
295 parse_opts = optview.Parse(opt0_array, opt_stacks)
296 exec_opts = optview.Exec(opt0_array, opt_stacks)
297 mutable_opts = MutableOpts(mem, environ, opt0_array, opt_stacks, opt_hook)
298
299 return parse_opts, exec_opts, mutable_opts
300
301
302def _SetGroup(opt0_array, opt_nums, b):
303 # type: (List[bool], List[int], bool) -> None
304 for opt_num in opt_nums:
305 b2 = not b if opt_num in consts.DEFAULT_TRUE else b
306 opt0_array[opt_num] = b2
307
308
309def MakeYshParseOpts():
310 # type: () -> optview.Parse
311 opt0_array = InitOpts()
312 _SetGroup(opt0_array, consts.YSH_ALL, True)
313
314 no_stack = None # type: List[bool]
315 opt_stacks = [no_stack] * option_i.ARRAY_SIZE # type: List[List[bool]]
316
317 parse_opts = optview.Parse(opt0_array, opt_stacks)
318 return parse_opts
319
320
321def _AnyOptionNum(opt_name, ignore_shopt_not_impl):
322 # type: (str, bool) -> option_t
323 opt_num = consts.OptionNum(opt_name)
324 if opt_num == 0:
325 if ignore_shopt_not_impl:
326 opt_num = consts.UnimplOptionNum(opt_name)
327 if opt_num == 0:
328 e_usage('got invalid option %r' % opt_name, loc.Missing)
329
330 # Note: we relaxed this for YSH so we can do 'shopt --unset errexit' consistently
331 #if opt_num not in consts.SHOPT_OPTION_NUMS:
332 # e_usage("doesn't own option %r (try 'set')" % opt_name)
333
334 return opt_num
335
336
337def _SetOptionNum(opt_name):
338 # type: (str) -> option_t
339 opt_num = consts.OptionNum(opt_name)
340 if opt_num == 0:
341 e_usage('got invalid option %r' % opt_name, loc.Missing)
342
343 if opt_num not in consts.SET_OPTION_NUMS:
344 e_usage("invalid option %r (try shopt)" % opt_name, loc.Missing)
345
346 return opt_num
347
348
349def _MaybeWarnDotglob():
350 # type: () -> None
351 if HAVE_GLOB_PERIOD == 0:
352 # GNU libc and musl libc have GLOB_PERIOD, but Android doesn't
353 print_stderr(
354 "osh warning: GLOB_PERIOD wasn't found in libc, so 'shopt -s dotglob' won't work"
355 )
356
357
358class MutableOpts(object):
359
360 def __init__(self, mem, environ, opt0_array, opt_stacks, opt_hook):
361 # type: (Mem, Dict[str, str], List[bool], List[List[bool]], OptHook) -> None
362 self.mem = mem
363 self.environ = environ
364 self.opt0_array = opt0_array
365 self.opt_stacks = opt_stacks
366 self.errexit_disabled_tok = [] # type: List[Token]
367
368 # Used for 'set -o vi/emacs'
369 self.opt_hook = opt_hook
370
371 def Init(self):
372 # type: () -> None
373
374 # This comes after all the 'set' options.
375 shellopts = self.mem.GetValue('SHELLOPTS')
376
377 # True in OSH, but not in YSH (no_init_globals)
378 if shellopts.tag() == value_e.Str:
379 s = cast(value.Str, shellopts).s
380 self._InitOptionsFromEnv(s)
381
382 def _InitOptionsFromEnv(self, shellopts):
383 # type: (str) -> None
384 # e.g. errexit:nounset:pipefail
385 lookup = shellopts.split(':')
386 for opt_num in consts.SET_OPTION_NUMS:
387 name = consts.OptionName(opt_num)
388 if name in lookup:
389 self._SetOldOption(name, True)
390
391 def Push(self, opt_num, b):
392 # type: (int, bool) -> None
393 if opt_num == option_i.dotglob:
394 _MaybeWarnDotglob()
395
396 overlay = self.opt_stacks[opt_num]
397 if overlay is None or len(overlay) == 0:
398 self.opt_stacks[opt_num] = [b] # Allocate a new list
399 else:
400 overlay.append(b)
401
402 def Pop(self, opt_num):
403 # type: (int) -> bool
404 overlay = self.opt_stacks[opt_num]
405 assert overlay is not None
406 return overlay.pop()
407
408 def PushDynamicScope(self, b):
409 # type: (bool) -> None
410 """
411 Args
412 b: dynamic scope? False if it's a proc, and True if it's a shell
413 function.
414 """
415 # If it's already disabled, keep it disabled
416 if not self.Get(option_i.dynamic_scope):
417 b = False
418 self.Push(option_i.dynamic_scope, b)
419
420 def PopDynamicScope(self):
421 # type: () -> None
422 self.Pop(option_i.dynamic_scope)
423
424 def Get(self, opt_num):
425 # type: (int) -> bool
426 # Like _Getter in core/optview.py
427 overlay = self.opt_stacks[opt_num]
428 if overlay is None or len(overlay) == 0:
429 return self.opt0_array[opt_num]
430 else:
431 return overlay[-1] # the top value
432
433 def _Set(self, opt_num, b):
434 # type: (int, bool) -> None
435 """Used to disable errexit.
436
437 For bash compatibility in command sub.
438 """
439 if opt_num == option_i.dotglob:
440 _MaybeWarnDotglob()
441
442 # Like _Getter in core/optview.py
443 overlay = self.opt_stacks[opt_num]
444 if overlay is None or len(overlay) == 0:
445 self.opt0_array[opt_num] = b
446 else:
447 overlay[-1] = b # The top value
448
449 def set_interactive(self):
450 # type: () -> None
451 self._Set(option_i.interactive, True)
452
453 def set_redefine_const(self):
454 # type: () -> None
455 """For interactive shells."""
456 self._Set(option_i.redefine_const, True)
457
458 def set_redefine_source(self):
459 # type: () -> None
460 """For interactive shells. For source-guard"""
461 self._Set(option_i.redefine_source, True)
462
463 def set_emacs(self):
464 # type: () -> None
465 self._Set(option_i.emacs, True)
466
467 def _SetArrayByNum(self, opt_num, b):
468 # type: (int, bool) -> None
469 if (opt_num in consts.PARSE_OPTION_NUMS and
470 not self.mem.ParsingChangesAllowed()):
471 e_die('Syntax options must be set at the top level '
472 '(outside any function)')
473
474 self._Set(opt_num, b)
475
476 def SetDeferredErrExit(self, b):
477 # type: (bool) -> None
478 """Set the errexit flag, possibly deferring it.
479
480 Implements the unusual POSIX "defer" behavior. Callers: set -o
481 errexit, shopt -s ysh:all, ysh:upgrade
482 """
483 #log('Set %s', b)
484
485 # Defer it until we pop by setting the BOTTOM OF THE STACK.
486 self.opt0_array[option_i.errexit] = b
487
488 def DisableErrExit(self):
489 # type: () -> None
490 """Called by core/process.py to implement bash quirks."""
491 self._Set(option_i.errexit, False)
492
493 def ErrExitDisabledToken(self):
494 # type: () -> Optional[Token]
495 """If errexit is disabled by POSIX rules, return Token for construct.
496
497 e.g. the Token for 'if' or '&&' etc.
498 """
499 # Bug fix: The errexit disabling inherently follows a STACK DISCIPLINE.
500 # But we run trap handlers in the MAIN LOOP, which break this. So just
501 # declare that it's never disabled in a trap.
502 if self.Get(option_i._running_trap):
503 return None
504
505 if len(self.errexit_disabled_tok) == 0:
506 return None
507
508 return self.errexit_disabled_tok[-1]
509
510 def ErrExitIsDisabled(self):
511 # type: () -> bool
512 """
513 Similar to ErrExitDisabledToken, for ERR trap
514 """
515 if len(self.errexit_disabled_tok) == 0:
516 return False
517
518 return self.errexit_disabled_tok[-1] is not None
519
520 def _SetOldOption(self, opt_name, b):
521 # type: (str, bool) -> None
522 """Private version for synchronizing from SHELLOPTS."""
523 assert '_' not in opt_name
524 assert opt_name in consts.SET_OPTION_NAMES
525
526 opt_num = consts.OptionNum(opt_name)
527 assert opt_num != 0, opt_name
528
529 if opt_num == option_i.errexit:
530 self.SetDeferredErrExit(b)
531 else:
532 if opt_num == option_i.verbose and b:
533 print_stderr('osh warning: set -o verbose not implemented')
534 self._SetArrayByNum(opt_num, b)
535
536 # note: may FAIL before we get here.
537
538 success = self.opt_hook.OnChange(self.opt0_array, opt_name, b)
539
540 def SetOldOption(self, opt_name, b):
541 # type: (str, bool) -> None
542 """For set -o, set +o, or shopt -s/-u -o."""
543 unused = _SetOptionNum(opt_name) # validate it
544 self._SetOldOption(opt_name, b)
545
546 if not self.Get(option_i.no_init_globals):
547 UP_val = self.mem.GetValue('SHELLOPTS')
548 assert UP_val.tag() == value_e.Str, UP_val
549 val = cast(value.Str, UP_val)
550 shellopts = val.s
551
552 # Now check if SHELLOPTS needs to be updated. It may be exported.
553 #
554 # NOTE: It might be better to skip rewriting SEHLLOPTS in the common case
555 # where it is not used. We could do it lazily upon GET.
556
557 # Also, it would be slightly more efficient to update SHELLOPTS if
558 # settings were batched, Examples:
559 # - set -eu
560 # - shopt -s foo bar
561 if b:
562 if opt_name not in shellopts:
563 new_val = value.Str('%s:%s' % (shellopts, opt_name))
564 self.mem.InternalSetGlobal('SHELLOPTS', new_val)
565 else:
566 if opt_name in shellopts:
567 names = [n for n in shellopts.split(':') if n != opt_name]
568 new_val = value.Str(':'.join(names))
569 self.mem.InternalSetGlobal('SHELLOPTS', new_val)
570
571 def SetAnyOption(self, opt_name, b, ignore_shopt_not_impl=False):
572 # type: (str, bool, bool) -> None
573 """For shopt -s/-u and sh -O/+O."""
574
575 # shopt -s ysh:all turns on all YSH options, which includes all strict
576 # options
577 opt_group = consts.OptionGroupNum(opt_name)
578 if opt_group == opt_group_i.YshUpgrade:
579 _SetGroup(self.opt0_array, consts.YSH_UPGRADE, b)
580 self.SetDeferredErrExit(b) # Special case
581 if b: # ENV dict
582 self.mem.MaybeInitEnvDict(self.environ)
583 return
584
585 if opt_group == opt_group_i.YshAll:
586 _SetGroup(self.opt0_array, consts.YSH_ALL, b)
587 self.SetDeferredErrExit(b) # Special case
588 if b: # ENV dict
589 self.mem.MaybeInitEnvDict(self.environ)
590 return
591
592 if opt_group == opt_group_i.StrictAll:
593 _SetGroup(self.opt0_array, consts.STRICT_ALL, b)
594 return
595
596 opt_num = _AnyOptionNum(opt_name, ignore_shopt_not_impl)
597
598 if opt_num == option_i.errexit:
599 self.SetDeferredErrExit(b)
600 return
601
602 self._SetArrayByNum(opt_num, b)
603
604
605class _ArgFrame(object):
606 """Stack frame for arguments array."""
607
608 def __init__(self, argv):
609 # type: (List[str]) -> None
610 self.argv = argv
611 self.num_shifted = 0
612
613 def __repr__(self):
614 # type: () -> str
615 return '<_ArgFrame %s %d at %x>' % (self.argv, self.num_shifted,
616 id(self))
617
618 def Dump(self):
619 # type: () -> Dict[str, value_t]
620 items = [value.Str(s) for s in self.argv] # type: List[value_t]
621 argv = value.List(items)
622 return {
623 'argv': argv,
624 'num_shifted': num.ToBig(self.num_shifted),
625 }
626
627 def GetArgNum(self, arg_num):
628 # type: (int) -> value_t
629
630 # $0 is handled elsewhere
631 assert 1 <= arg_num, arg_num
632
633 index = self.num_shifted + arg_num - 1
634 if index >= len(self.argv):
635 return value.Undef
636
637 return value.Str(self.argv[index])
638
639 def GetArgv(self):
640 # type: () -> List[str]
641 return self.argv[self.num_shifted:]
642
643 def GetNumArgs(self):
644 # type: () -> int
645 return len(self.argv) - self.num_shifted
646
647 def SetArgv(self, argv):
648 # type: (List[str]) -> None
649 self.argv = argv
650 self.num_shifted = 0
651
652
653def _DumpVarFrame(frame):
654 # type: (Dict[str, Cell]) -> Dict[str, value_t]
655 """Dump the stack frame as reasonably compact and readable JSON."""
656
657 vars_json = {} # type: Dict[str, value_t]
658 for name, cell in iteritems(frame):
659 cell_json = {} # type: Dict[str, value_t]
660
661 buf = mylib.BufWriter()
662 if cell.exported:
663 buf.write('x')
664 if cell.readonly:
665 buf.write('r')
666 flags = buf.getvalue()
667 if len(flags):
668 cell_json['flags'] = value.Str(flags)
669
670 # TODO:
671 # - Use packle for crash dumps! Then we can represent object cycles
672 # - Right now the JSON serializer will probably crash
673 # - although BashArray and BashAssoc may need 'type' tags
674 # - they don't round trip correctly
675 # - maybe add value.Tombstone here or something?
676 # - value.{Func,Eggex,...} may have value.Tombstone and
677 # vm.ValueIdString()?
678
679 with tagswitch(cell.val) as case:
680 if case(value_e.Undef):
681 cell_json['val'] = value.Null
682
683 elif case(value_e.Str, value_e.BashArray, value_e.BashAssoc,
684 value_e.SparseArray):
685 cell_json['val'] = cell.val
686
687 else:
688 # TODO: should we show the object ID here?
689 pass
690
691 vars_json[name] = value.Dict(cell_json)
692
693 return vars_json
694
695
696def _LineNumber(tok):
697 # type: (Optional[Token]) -> str
698 """ For $BASH_LINENO """
699 if tok is None:
700 return '-1'
701 return str(tok.line.line_num)
702
703
704def _AddCallToken(d, token):
705 # type: (Dict[str, value_t], Optional[Token]) -> None
706 if token is None:
707 return
708 d['call_source'] = value.Str(ui.GetLineSourceString(token.line))
709 d['call_line_num'] = num.ToBig(token.line.line_num)
710 d['call_line'] = value.Str(token.line.content)
711
712
713class ctx_FuncCall(object):
714 """For func calls."""
715
716 def __init__(self, mem, func, blame_tok):
717 # type: (Mem, value.Func, Token) -> None
718
719 self.saved_globals = mem.var_stack[0]
720
721 assert func.module_frame is not None
722 mem.var_stack[0] = func.module_frame
723
724 frame = NewDict() # type: Dict[str, Cell]
725 mem.var_stack.append(frame)
726
727 # blame the location of (
728 mem.debug_stack.append(blame_tok)
729
730 self.mem = mem
731
732 def __enter__(self):
733 # type: () -> None
734 pass
735
736 def __exit__(self, type, value, traceback):
737 # type: (Any, Any, Any) -> None
738 self.mem.debug_stack.pop()
739 self.mem.var_stack.pop()
740
741 self.mem.var_stack[0] = self.saved_globals
742
743
744class ctx_ProcCall(object):
745 """For proc calls, including shell functions."""
746
747 def __init__(self, mem, mutable_opts, proc, argv, invoke_loc):
748 # type: (Mem, MutableOpts, value.Proc, List[str], CompoundWord) -> None
749
750 # TODO:
751 # should we separate procs and shell functions?
752 # - dynamic scope is one difference
753 # - '$@" shift etc. are another difference
754
755 self.saved_globals = mem.var_stack[0]
756
757 assert proc.module_frame is not None
758 mem.var_stack[0] = proc.module_frame
759
760 frame = NewDict() # type: Dict[str, Cell]
761
762 assert argv is not None
763 if proc.sh_compat:
764 # shell function
765 mem.argv_stack.append(_ArgFrame(argv))
766 else:
767 # procs
768 # - open: is equivalent to ...ARGV
769 # - closed: ARGV is empty list
770 frame['ARGV'] = _MakeArgvCell(argv)
771
772 mem.var_stack.append(frame)
773
774 mem.debug_stack.append(
775 debug_frame.ProcLike(invoke_loc, proc.name_tok, proc.name))
776
777 # Dynamic scope is only for shell functions
778 mutable_opts.PushDynamicScope(proc.sh_compat)
779
780 # It may have been disabled with ctx_ErrExit for 'if echo $(false)', but
781 # 'if p' should be allowed.
782 self.mem = mem
783 self.mutable_opts = mutable_opts
784 self.sh_compat = proc.sh_compat
785
786 def __enter__(self):
787 # type: () -> None
788 pass
789
790 def __exit__(self, type, value, traceback):
791 # type: (Any, Any, Any) -> None
792 self.mutable_opts.PopDynamicScope()
793 self.mem.debug_stack.pop()
794 self.mem.var_stack.pop()
795
796 if self.sh_compat:
797 self.mem.argv_stack.pop()
798
799 self.mem.var_stack[0] = self.saved_globals
800
801
802class ctx_Temp(object):
803 """ POSIX shell FOO=bar mycommand """
804
805 def __init__(self, mem):
806 # type: (Mem) -> None
807 self.mem = mem
808 mem.PushTemp()
809
810 def __enter__(self):
811 # type: () -> None
812 pass
813
814 def __exit__(self, type, value, traceback):
815 # type: (Any, Any, Any) -> None
816 self.mem.PopTemp()
817
818
819class ctx_EnvObj(object):
820 """YSH FOO=bar my-command"""
821
822 def __init__(self, mem, bindings):
823 # type: (Mem, Dict[str, value_t]) -> None
824 self.mem = mem
825 mem.PushEnvObj(bindings)
826
827 def __enter__(self):
828 # type: () -> None
829 pass
830
831 def __exit__(self, type, value, traceback):
832 # type: (Any, Any, Any) -> None
833 self.mem.PopEnvObj()
834
835
836class ctx_Registers(object):
837 """For $PS1, $PS4, $PROMPT_COMMAND, traps, and headless EVAL.
838
839 This is tightly coupled to state.Mem, so it's not in builtin/pure_ysh.
840 """
841
842 def __init__(self, mem):
843 # type: (Mem) -> None
844
845 # Because some prompts rely on the status leaking. See issue #853.
846 # PS1 also does.
847 last = mem.last_status[-1]
848 mem.last_status.append(last)
849 mem.try_status.append(0)
850 mem.try_error.append(value.Dict({}))
851
852 # TODO: We should also copy these values! Turn the whole thing into a
853 # frame.
854 mem.pipe_status.append([])
855 mem.process_sub_status.append([])
856
857 mem.regex_match.append(regex_match.No)
858
859 self.mem = mem
860
861 def __enter__(self):
862 # type: () -> None
863 pass
864
865 def __exit__(self, type, value, traceback):
866 # type: (Any, Any, Any) -> None
867 self.mem.regex_match.pop()
868
869 self.mem.process_sub_status.pop()
870 self.mem.pipe_status.pop()
871
872 self.mem.try_error.pop()
873 self.mem.try_status.pop()
874 self.mem.last_status.pop()
875
876
877class ctx_ThisDir(object):
878 """For $_this_dir."""
879
880 def __init__(self, mem, filename):
881 # type: (Mem, Optional[str]) -> None
882 self.do_pop = False
883 if filename is not None: # script_name in main() may be -c, etc.
884 d = os_path.dirname(os_path.abspath(filename))
885 mem.this_dir.append(d)
886 self.do_pop = True
887
888 self.mem = mem
889
890 def __enter__(self):
891 # type: () -> None
892 pass
893
894 def __exit__(self, type, value, traceback):
895 # type: (Any, Any, Any) -> None
896 if self.do_pop:
897 self.mem.this_dir.pop()
898
899
900def _MakeArgvCell(argv):
901 # type: (List[str]) -> Cell
902 items = [value.Str(a) for a in argv] # type: List[value_t]
903 return Cell(False, False, False, value.List(items))
904
905
906class ctx_LoopFrame(object):
907
908 def __init__(self, mem, do_new_frame):
909 # type: (Mem, bool) -> None
910 self.mem = mem
911 self.do_new_frame = do_new_frame
912
913 if self.do_new_frame:
914 to_enclose = self.mem.var_stack[-1]
915 self.new_frame = NewDict() # type: Dict[str, Cell]
916 self.new_frame['__E__'] = Cell(False, False, False,
917 value.Frame(to_enclose))
918 mem.var_stack.append(self.new_frame)
919
920 def __enter__(self):
921 # type: () -> None
922 pass
923
924 def __exit__(self, type, value, traceback):
925 # type: (Any, Any, Any) -> None
926 if self.do_new_frame:
927 self.mem.var_stack.pop()
928
929
930class ctx_EnclosedFrame(object):
931 """
932 Usages:
933
934 - io->evalToDict(), which is a primitive used for Hay and the Dict proc
935 - lexical scope aka static scope for block args to user-defined procs
936 - Including the "closures in a loop" problem, which will be used for Hay
937
938 var mutated = 'm'
939 var shadowed = 's'
940
941 Dict (&d) {
942 shadowed = 42
943 mutated = 'new' # this is equivalent to var mutated
944
945 setvar mutated = 'new'
946 }
947 echo $shadowed # restored to 's'
948 echo $mutated # new
949
950 Or maybe we disallow the setvar lookup?
951 """
952
953 def __init__(
954 self,
955 mem, # type: Mem
956 to_enclose, # type: Dict[str, Cell]
957 module_frame, # type: Dict[str, Cell]
958 out_dict, # type: Optional[Dict[str, value_t]]
959 ):
960 # type: (...) -> None
961 self.mem = mem
962 self.to_enclose = to_enclose
963 self.module_frame = module_frame
964 self.out_dict = out_dict
965
966 if module_frame is not None:
967 self.saved_globals = self.mem.var_stack[0]
968 self.mem.var_stack[0] = module_frame
969
970 # __E__ gets a lookup rule
971 self.new_frame = NewDict() # type: Dict[str, Cell]
972 self.new_frame['__E__'] = Cell(False, False, False,
973 value.Frame(to_enclose))
974
975 mem.var_stack.append(self.new_frame)
976
977 def __enter__(self):
978 # type: () -> None
979 pass
980
981 def __exit__(self, type, value, traceback):
982 # type: (Any, Any, Any) -> None
983
984 if self.out_dict is not None:
985 for name, cell in iteritems(self.new_frame):
986 #log('name %r', name)
987 #log('cell %r', cell)
988
989 # User can hide variables with _ suffix
990 # e.g. for i_ in foo bar { echo $i_ }
991 if name.endswith('_'):
992 continue
993
994 self.out_dict[name] = cell.val
995
996 # Restore
997 self.mem.var_stack.pop()
998
999 if self.module_frame is not None:
1000 self.mem.var_stack[0] = self.saved_globals
1001
1002
1003class ctx_CompoundWordDebugFrame(object):
1004
1005 def __init__(self, mem, w):
1006 # type: (Mem, CompoundWord) -> None
1007 mem.debug_stack.append(w)
1008 self.mem = mem
1009
1010 def __enter__(self):
1011 # type: () -> None
1012 pass
1013
1014 def __exit__(self, type, value_, traceback):
1015 # type: (Any, Any, Any) -> None
1016 self.mem.debug_stack.pop()
1017
1018
1019class ctx_TokenDebugFrame(object):
1020
1021 def __init__(self, mem, tok):
1022 # type: (Mem, Token) -> None
1023 mem.debug_stack.append(tok)
1024 self.mem = mem
1025
1026 def __enter__(self):
1027 # type: () -> None
1028 pass
1029
1030 def __exit__(self, type, value_, traceback):
1031 # type: (Any, Any, Any) -> None
1032 self.mem.debug_stack.pop()
1033
1034
1035class ctx_ModuleEval(object):
1036 """Evaluate a module with a new global stack frame.
1037
1038 e.g. setglobal in the new module doesn't leak
1039
1040 Different from ctx_EnclosedFrame because the new code can't see variables in
1041 the old frame.
1042 """
1043
1044 def __init__(self, mem, use_loc, out_dict, out_errors):
1045 # type: (Mem, CompoundWord, Dict[str, value_t], List[str]) -> None
1046 self.mem = mem
1047 self.out_dict = out_dict
1048 self.out_errors = out_errors
1049
1050 self.new_frame = NewDict() # type: Dict[str, Cell]
1051 self.saved_frame = mem.var_stack[0]
1052
1053 # Somewhat of a hack for tracing within a module.
1054 # Other solutions:
1055 # - PS4 can be __builtin__, but that would break shell compatibility
1056 # - We can have a separate YSH mechanism that uses a different settings
1057 # - We probably still want it to be scoped, like shvar PS4=z { ... }
1058 #
1059 # Note: there's a similar issue with HOSTNAME UID EUID etc. But those
1060 # could be io.hostname() io.getuid(), or lazy constants, etc.
1061
1062 ps4 = self.saved_frame.get('PS4')
1063 if ps4:
1064 self.new_frame['PS4'] = ps4
1065 # ENV is not in __builtins__ because it's mutable -- we want
1066 # 'setglobal' to work
1067 env = self.saved_frame.get('ENV')
1068 if env:
1069 self.new_frame['ENV'] = env
1070
1071 assert len(mem.var_stack) == 1
1072 mem.var_stack[0] = self.new_frame
1073
1074 # Whenever we're use-ing, the 'is-main' builtin will return 1 (false)
1075 self.to_restore = self.mem.is_main
1076 self.mem.is_main = False
1077
1078 # Equivalent of PushSource()
1079 mem.debug_stack.append(use_loc)
1080
1081 def __enter__(self):
1082 # type: () -> None
1083 pass
1084
1085 def __exit__(self, type, value_, traceback):
1086 # type: (Any, Any, Any) -> None
1087
1088 self.mem.debug_stack.pop()
1089
1090 self.mem.is_main = self.to_restore
1091
1092 assert len(self.mem.var_stack) == 1
1093 self.mem.var_stack[0] = self.saved_frame
1094
1095 # Now look in __export__ for the list of names to expose
1096
1097 cell = self.new_frame.get('__provide__')
1098 if cell is None:
1099 self.out_errors.append("Module is missing __provide__ List")
1100 return
1101
1102 provide_val = cell.val
1103 with tagswitch(provide_val) as case:
1104 if case(value_e.List):
1105 for val in cast(value.List, provide_val).items:
1106 if val.tag() == value_e.Str:
1107 name = cast(value.Str, val).s
1108
1109 cell = self.new_frame.get(name)
1110 if cell is None:
1111 self.out_errors.append(
1112 "Name %r was provided, but not defined" % name)
1113 continue
1114
1115 self.out_dict[name] = cell.val
1116 else:
1117 self.out_errors.append(
1118 "Expected Str in __provide__ List, got %s" %
1119 ui.ValType(val))
1120
1121 else:
1122 self.out_errors.append("__provide__ should be a List, got %s" %
1123 ui.ValType(provide_val))
1124
1125
1126class ctx_Eval(object):
1127 """Push temporary set of variables, $0, $1, $2, etc."""
1128
1129 def __init__(
1130 self,
1131 mem, # type: Mem
1132 dollar0, # type: Optional[str]
1133 pos_args, # type: Optional[List[str]]
1134 vars, # type: Optional[Dict[str, value_t]]
1135 ):
1136 # type: (...) -> None
1137 self.mem = mem
1138 self.dollar0 = dollar0
1139 self.pos_args = pos_args
1140 self.vars = vars
1141
1142 # $0 needs to have lexical scoping. So we store it with other locals.
1143 # As "0" cannot be parsed as an lvalue, we can safely store dollar0 there.
1144 if dollar0 is not None:
1145 #assert mem.GetValue("0", scope_e.LocalOnly).tag() == value_e.Undef
1146 #self.dollar0_lval = LeftName("0", loc.Missing)
1147 #mem.SetLocalName(self.dollar0_lval, value.Str(dollar0))
1148
1149 self.restore_dollar0 = self.mem.dollar0
1150 self.mem.dollar0 = dollar0
1151
1152 if pos_args is not None:
1153 mem.argv_stack.append(_ArgFrame(pos_args))
1154
1155 if vars is not None:
1156 self.restore = [] # type: List[Tuple[LeftName, value_t]]
1157 self._Push(vars)
1158
1159 def __enter__(self):
1160 # type: () -> None
1161 pass
1162
1163 def __exit__(self, type, value_, traceback):
1164 # type: (Any, Any, Any) -> None
1165 if self.vars is not None:
1166 self._Pop()
1167
1168 if self.pos_args is not None:
1169 self.mem.argv_stack.pop()
1170
1171 if self.dollar0 is not None:
1172 #self.mem.SetLocalName(self.dollar0_lval, value.Undef)
1173 self.mem.dollar0 = self.restore_dollar0
1174
1175 # Note: _Push and _Pop are separate methods because the C++ translation
1176 # doesn't like when they are inline in __init__ and __exit__.
1177 def _Push(self, vars):
1178 # type: (Dict[str, value_t]) -> None
1179 for name in vars:
1180 lval = location.LName(name)
1181 # LocalOnly because we are only overwriting the current scope
1182 old_val = self.mem.GetValue(name, scope_e.LocalOnly)
1183 self.restore.append((lval, old_val))
1184 self.mem.SetNamed(lval, vars[name], scope_e.LocalOnly)
1185
1186 def _Pop(self):
1187 # type: () -> None
1188 for lval, old_val in self.restore:
1189 if old_val.tag() == value_e.Undef:
1190 self.mem.Unset(lval, scope_e.LocalOnly)
1191 else:
1192 self.mem.SetNamed(lval, old_val, scope_e.LocalOnly)
1193
1194
1195def _FrameLookup(frame, name):
1196 # type: (Dict[str, Cell], str) -> Tuple[Optional[Cell], Dict[str, Cell]]
1197 """
1198 Look for a name in the frame, then recursively into the enclosing __E__
1199 frame, if it exists
1200 """
1201 cell = frame.get(name)
1202 if cell:
1203 return cell, frame
1204
1205 rear_cell = frame.get('__E__') # ctx_EnclosedFrame() sets this
1206 if rear_cell:
1207 rear_val = rear_cell.val
1208 assert rear_val, rear_val
1209 if rear_val.tag() == value_e.Frame:
1210 to_enclose = cast(value.Frame, rear_val).frame
1211 return _FrameLookup(to_enclose, name) # recursive call
1212
1213 return None, None
1214
1215
1216class Mem(object):
1217 """For storing variables.
1218
1219 Callers:
1220 User code: assigning and evaluating variables, in command context or
1221 arithmetic context.
1222 Completion engine: for COMP_WORDS, etc.
1223 Builtins call it implicitly: read, cd for $PWD, $OLDPWD, etc.
1224
1225 Modules: cmd_eval, word_eval, expr_eval, completion
1226 """
1227
1228 def __init__(
1229 self,
1230 dollar0, # type: str
1231 argv, # type: List[str]
1232 arena, # type: alloc.Arena
1233 debug_stack, # type: List[debug_frame_t]
1234 env_dict, # type: Dict[str, value_t]
1235 defaults=None, # type: Dict[str, value_t]
1236 ):
1237 # type: (...) -> None
1238 """
1239 Args:
1240 arena: currently unused
1241 """
1242 # circular dep initialized out of line
1243 self.exec_opts = None # type: optview.Exec
1244 self.unsafe_arith = None # type: sh_expr_eval.UnsafeArith
1245
1246 self.dollar0 = dollar0
1247 # If you only use YSH procs and funcs, this will remain at length 1.
1248 self.argv_stack = [_ArgFrame(argv)]
1249
1250 frame = NewDict() # type: Dict[str, Cell]
1251
1252 frame['ARGV'] = _MakeArgvCell(argv)
1253
1254 self.var_stack = [frame]
1255
1256 # The debug_stack isn't strictly necessary for execution. We use it
1257 # for crash dumps and for 3 parallel arrays: BASH_SOURCE, FUNCNAME, and
1258 # BASH_LINENO.
1259 self.debug_stack = debug_stack
1260
1261 self.env_dict = env_dict
1262 self.env_object = Obj(None, env_dict) # initial state
1263
1264 if defaults is None: # for unit tests only
1265 self.defaults = NewDict() # type: Dict[str, value_t]
1266 else:
1267 self.defaults = defaults
1268
1269 self.pwd = None # type: Optional[str]
1270 self.seconds_start = time_.time()
1271
1272 self.token_for_line = None # type: Optional[Token]
1273 self.loc_for_expr = loc.Missing # type: loc_t
1274
1275 self.last_arg = '' # $_ is initially empty, NOT unset
1276 self.line_num = value.Str('')
1277
1278 # Done ONCE on initialization
1279 self.root_pid = posix.getpid()
1280
1281 # TODO:
1282 # - These are REGISTERS mutated by user code.
1283 # - Call it self.reg_stack? with ctx_Registers
1284 # - push-registers builtin
1285 self.last_status = [0] # type: List[int] # a stack
1286 self.try_status = [0] # type: List[int] # a stack
1287 self.try_error = [value.Dict({})] # type: List[value.Dict] # a stack
1288 self.pipe_status = [[]] # type: List[List[int]] # stack
1289 self.process_sub_status = [[]] # type: List[List[int]] # stack
1290
1291 # A stack but NOT a register?
1292 self.this_dir = [] # type: List[str]
1293 self.regex_match = [regex_match.No] # type: List[regex_match_t]
1294
1295 self.last_bg_pid = -1 # Uninitialized value mutable public variable
1296
1297 self.running_debug_trap = False # set by ctx_DebugTrap()
1298 self.running_err_trap = False # set by ctx_ErrTrap
1299 self.is_main = True # we start out in main
1300
1301 # For the ctx builtin
1302 self.ctx_stack = [] # type: List[Dict[str, value_t]]
1303
1304 self.builtins = NewDict() # type: Dict[str, value_t]
1305
1306 # Note: Python 2 and 3 have __builtins__
1307 # This is just for inspection
1308 builtins_module = Obj(None, self.builtins)
1309
1310 # Code in any module can see __builtins__
1311 self.builtins['__builtins__'] = builtins_module
1312
1313 self.did_ysh_env = False # only initialize ENV once per process
1314
1315 from core import sh_init
1316 self.env_config = sh_init.EnvConfig(self, defaults)
1317
1318 def __repr__(self):
1319 # type: () -> str
1320 parts = [] # type: List[str]
1321 parts.append('<Mem')
1322 for i, frame in enumerate(self.var_stack):
1323 parts.append(' -- %d --' % i)
1324 for n, v in frame.iteritems():
1325 parts.append(' %s %s' % (n, v))
1326 parts.append('>')
1327 return '\n'.join(parts) + '\n'
1328
1329 def AddBuiltin(self, name, val):
1330 # type: (str, value_t) -> None
1331 self.builtins[name] = val
1332
1333 def SetPwd(self, pwd):
1334 # type: (str) -> None
1335 """Used by builtins."""
1336 self.pwd = pwd
1337
1338 def ParsingChangesAllowed(self):
1339 # type: () -> bool
1340 """For checking that syntax options are only used at the top level."""
1341
1342 # DISALLOW proc calls : they push argv_stack, var_stack, debug_stack
1343 # ALLOW source foo.sh arg1: pushes argv_stack, debug_stack
1344 # ALLOW FOO=bar : pushes var_stack
1345 return len(self.var_stack) == 1 or len(self.argv_stack) == 1
1346
1347 def Dump(self):
1348 # type: () -> Tuple[List[value_t], List[value_t], List[value_t]]
1349 """Copy state before unwinding the stack."""
1350 var_stack = [
1351 value.Dict(_DumpVarFrame(frame)) for frame in self.var_stack
1352 ] # type: List[value_t]
1353 argv_stack = [value.Dict(frame.Dump())
1354 for frame in self.argv_stack] # type: List[value_t]
1355
1356 debug_stack = [] # type: List[value_t]
1357
1358 # Reuse these immutable objects
1359 t_call = value.Str('Call')
1360 t_source = value.Str('Source')
1361
1362 for frame in reversed(self.debug_stack):
1363 UP_frame = frame
1364 d = None # type: Optional[Dict[str, value_t]]
1365 with tagswitch(frame) as case:
1366 if case(debug_frame_e.ProcLike):
1367 frame = cast(debug_frame.ProcLike, UP_frame)
1368 d = {
1369 'type': t_call,
1370 'func_name': value.Str(frame.proc_name)
1371 }
1372
1373 invoke_token = location.LeftTokenForCompoundWord(
1374 frame.invoke_loc)
1375 assert invoke_token is not None, frame.invoke_loc
1376 _AddCallToken(d, invoke_token)
1377 # TODO: Add def_tok
1378
1379 elif case(debug_frame_e.Source):
1380 frame = cast(debug_frame.Source, UP_frame)
1381 d = {
1382 'type': t_source,
1383 'source_name': value.Str(frame.source_name)
1384 }
1385 invoke_token = location.LeftTokenForCompoundWord(
1386 frame.source_loc)
1387 assert invoke_token is not None, frame.source_loc
1388 _AddCallToken(d, invoke_token)
1389
1390 # TODO: func_reflect.py DebugFrameToString handles these cases
1391 # We might also want to use CrashDumper there? For 'set -u'
1392 # etc.
1393 elif case(debug_frame_e.CompoundWord):
1394 pass
1395
1396 elif case(debug_frame_e.Token):
1397 pass
1398
1399 # Note: Skip debug_frame.MainFile
1400 if d is not None:
1401 debug_stack.append(value.Dict(d))
1402
1403 return var_stack, argv_stack, debug_stack
1404
1405 def SetLastArgument(self, s):
1406 # type: (str) -> None
1407 """For $_"""
1408 self.last_arg = s
1409
1410 def SetTokenForLine(self, tok):
1411 # type: (Token) -> None
1412 """Set a token to compute $LINENO
1413
1414 This means it should be set on SimpleCommand, ShAssignment, ((, [[,
1415 case, etc. -- anything that evaluates a word. Example: there was a bug
1416 with 'case $LINENO'
1417
1418 This token also used as a "least-specific" / fallback location for
1419 errors in ExecuteAndCatch().
1420
1421 Although most of that should be taken over by 'with ui.ctx_Location()`,
1422 for the errfmt.
1423 """
1424 if self.running_debug_trap or self.running_err_trap:
1425 return
1426
1427 #if tok.span_id == runtime.NO_SPID:
1428 # NOTE: This happened in the osh-runtime benchmark for yash.
1429 #log('Warning: span_id undefined in SetTokenForLine')
1430
1431 #import traceback
1432 #traceback.print_stack()
1433 #return
1434
1435 # Reset the expression fallback location on every line. It REFINES the
1436 # line-based fallback location.
1437 self.loc_for_expr = loc.Missing
1438
1439 self.token_for_line = tok
1440
1441 def SetLocationForExpr(self, blame_loc):
1442 # type: (loc_t) -> None
1443 """
1444 A more specific fallback location, like the $[ in
1445
1446 echo $[len(42)]
1447 """
1448 self.loc_for_expr = blame_loc
1449
1450 def GetFallbackLocation(self):
1451 # type: () -> loc_t
1452
1453 if self.loc_for_expr != loc.Missing: # more specific
1454 return self.loc_for_expr
1455
1456 if self.token_for_line: # less specific
1457 return self.token_for_line
1458
1459 return loc.Missing
1460
1461 #
1462 # Status Variable Stack (for isolating $PS1 and $PS4)
1463 #
1464
1465 def LastStatus(self):
1466 # type: () -> int
1467 return self.last_status[-1]
1468
1469 def TryStatus(self):
1470 # type: () -> int
1471 return self.try_status[-1]
1472
1473 def TryError(self):
1474 # type: () -> value.Dict
1475 return self.try_error[-1]
1476
1477 def PipeStatus(self):
1478 # type: () -> List[int]
1479 return self.pipe_status[-1]
1480
1481 def SetLastStatus(self, x):
1482 # type: (int) -> None
1483 self.last_status[-1] = x
1484
1485 def SetTryStatus(self, x):
1486 # type: (int) -> None
1487 self.try_status[-1] = x
1488
1489 def SetTryError(self, x):
1490 # type: (value.Dict) -> None
1491 self.try_error[-1] = x
1492
1493 def SetPipeStatus(self, x):
1494 # type: (List[int]) -> None
1495 self.pipe_status[-1] = x
1496
1497 def SetSimplePipeStatus(self, status):
1498 # type: (int) -> None
1499
1500 # Optimization to avoid allocations
1501 top = self.pipe_status[-1]
1502 if len(top) == 1:
1503 top[0] = status
1504 else:
1505 self.pipe_status[-1] = [status]
1506
1507 def SetProcessSubStatus(self, x):
1508 # type: (List[int]) -> None
1509 self.process_sub_status[-1] = x
1510
1511 #
1512 # Call Stack
1513 #
1514
1515 def ShouldRunDebugTrap(self):
1516 # type: () -> bool
1517
1518 # TODO: RunLastPart of pipeline can disable this
1519
1520 # Don't recursively run DEBUG trap
1521 if self.running_debug_trap:
1522 return False
1523
1524 # Don't run it inside functions
1525 if len(self.var_stack) > 1:
1526 return False
1527
1528 return True
1529
1530 def IsGlobalScope(self):
1531 # type: () -> bool
1532 """
1533 local -g uses this, probably because bash does the wrong thing and
1534 prints LOCALS, not globals.
1535 """
1536 return len(self.var_stack) == 1
1537
1538 def InsideFunction(self):
1539 # type: () -> bool
1540 """For the ERR trap, and use builtin"""
1541
1542 # TODO: Should this be unified with ParsingChangesAllowed()? Slightly
1543 # different logic.
1544
1545 # Don't run it inside functions
1546 return len(self.var_stack) > 1
1547
1548 def GlobalFrame(self):
1549 # type: () -> Dict[str, Cell]
1550 """For defining the global scope of modules.
1551
1552 It's affected by ctx_ModuleEval()
1553 """
1554 return self.var_stack[0]
1555
1556 def CurrentFrame(self):
1557 # type: () -> Dict[str, Cell]
1558 """For attaching a stack frame to a value.Block"""
1559 return self.var_stack[-1]
1560
1561 def PushSource(self, source_name, argv, source_loc):
1562 # type: (str, List[str], CompoundWord) -> None
1563 """ For 'source foo.sh 1 2 3' """
1564 if len(argv):
1565 self.argv_stack.append(_ArgFrame(argv))
1566
1567 self.debug_stack.append(debug_frame.Source(source_loc, source_name))
1568
1569 def PopSource(self, argv):
1570 # type: (List[str]) -> None
1571 self.debug_stack.pop()
1572
1573 if len(argv):
1574 self.argv_stack.pop()
1575
1576 def PushTemp(self):
1577 # type: () -> None
1578 """For the temporary scope in 'FOO=bar BAR=baz echo'.
1579
1580 Also for PS4 evaluation with more variables.
1581 """
1582 # We don't want the 'read' builtin to write to this frame!
1583 frame = NewDict() # type: Dict[str, Cell]
1584 self.var_stack.append(frame)
1585
1586 def PopTemp(self):
1587 # type: () -> None
1588 self.var_stack.pop()
1589
1590 def _BindEnvObj(self):
1591 # type: () -> None
1592 self.SetNamed(location.LName('ENV'), self.env_object,
1593 scope_e.GlobalOnly)
1594
1595 def MaybeInitEnvDict(self, environ):
1596 # type: (Dict[str, str]) -> None
1597 if self.did_ysh_env:
1598 return
1599
1600 for name, s in iteritems(environ):
1601 self.env_dict[name] = value.Str(s)
1602
1603 self._BindEnvObj()
1604 self.did_ysh_env = True
1605
1606 def PushEnvObj(self, bindings):
1607 # type: (Dict[str, value_t]) -> None
1608 """Push "bindings" as the MOST visible part of the ENV Obj
1609
1610 i.e. first() / propView()
1611 """
1612 self.env_object = Obj(self.env_object, bindings)
1613 self._BindEnvObj()
1614
1615 def PopEnvObj(self):
1616 # type: () -> None
1617 """Pop a Dict of bindings."""
1618 self.env_object = self.env_object.prototype
1619 if self.env_object is None:
1620 # Note: there isn't a way to hit this now, but let's be defensive.
1621 # See test case in spec/ysh-env.test.sh.
1622 e_die('PopEnvObj: env.prototype is null', loc.Missing)
1623
1624 self._BindEnvObj()
1625
1626 #
1627 # Argv
1628 #
1629
1630 def Shift(self, n):
1631 # type: (int) -> int
1632 frame = self.argv_stack[-1]
1633 num_args = len(frame.argv)
1634
1635 if (frame.num_shifted + n) <= num_args:
1636 frame.num_shifted += n
1637 return 0 # success
1638 else:
1639 return 1 # silent error
1640
1641 def GetArg0(self):
1642 # type: () -> value.Str
1643 """Like GetArgNum(0) but with a more specific type."""
1644 return value.Str(self.dollar0)
1645
1646 def GetArgNum(self, arg_num):
1647 # type: (int) -> value_t
1648 if arg_num == 0:
1649 # Disabled
1650 if 0:
1651 # Problem: Doesn't obey enclosing frame?
1652 # Yeah it needs FrameLookup
1653 cell, _ = _FrameLookup(self.var_stack[-1], '0')
1654 if cell is not None:
1655 val = cell.val
1656 if val.tag() != value_e.Undef:
1657 return val
1658
1659 return value.Str(self.dollar0)
1660
1661 return self.argv_stack[-1].GetArgNum(arg_num)
1662
1663 def GetArgv(self):
1664 # type: () -> List[str]
1665 """For $* and $@."""
1666 return self.argv_stack[-1].GetArgv()
1667
1668 def SetArgv(self, argv):
1669 # type: (List[str]) -> None
1670 """For set -- 1 2 3."""
1671 # from set -- 1 2 3
1672 self.argv_stack[-1].SetArgv(argv)
1673
1674 #
1675 # Special Vars
1676 #
1677
1678 def GetSpecialVar(self, op_id):
1679 # type: (int) -> value_t
1680 if op_id == Id.VSub_Bang: # $!
1681 n = self.last_bg_pid
1682 if n == -1:
1683 return value.Undef # could be an error
1684
1685 elif op_id == Id.VSub_QMark: # $?
1686 # External commands need WIFEXITED test. What about subshells?
1687 n = self.last_status[-1]
1688
1689 elif op_id == Id.VSub_Pound: # $#
1690 n = self.argv_stack[-1].GetNumArgs()
1691
1692 elif op_id == Id.VSub_Dollar: # $$
1693 n = self.root_pid
1694
1695 else:
1696 raise NotImplementedError(op_id)
1697
1698 return value.Str(str(n))
1699
1700 #
1701 # Named Vars
1702 #
1703
1704 def _ResolveNameOnly(self, name, which_scopes):
1705 # type: (str, scope_t) -> Tuple[Optional[Cell], Dict[str, Cell]]
1706 """Helper for getting and setting variable.
1707
1708 Returns:
1709 cell: The cell corresponding to looking up 'name' with the given mode, or
1710 None if it's not found.
1711 var_frame: The frame it should be set to or deleted from.
1712 """
1713 if which_scopes == scope_e.Dynamic:
1714 for i in xrange(len(self.var_stack) - 1, -1, -1):
1715 var_frame = self.var_stack[i]
1716 cell, result_frame = _FrameLookup(var_frame, name)
1717 if cell:
1718 return cell, result_frame
1719 return None, self.var_stack[0] # set in global var_frame
1720
1721 if which_scopes == scope_e.LocalOnly:
1722 var_frame = self.var_stack[-1]
1723 cell, result_frame = _FrameLookup(var_frame, name)
1724 if cell:
1725 return cell, result_frame
1726 return None, var_frame
1727
1728 if which_scopes == scope_e.GlobalOnly:
1729 var_frame = self.var_stack[0]
1730 cell, result_frame = _FrameLookup(var_frame, name)
1731 if cell:
1732 return cell, result_frame
1733
1734 return None, var_frame
1735
1736 if which_scopes == scope_e.LocalOrGlobal:
1737 # Local
1738 var_frame = self.var_stack[-1]
1739 cell, result_frame = _FrameLookup(var_frame, name)
1740 if cell:
1741 return cell, result_frame
1742
1743 # Global
1744 var_frame = self.var_stack[0]
1745 cell, result_frame = _FrameLookup(var_frame, name)
1746 if cell:
1747 return cell, result_frame
1748
1749 return None, var_frame
1750
1751 raise AssertionError()
1752
1753 def _ResolveNameOrRef(
1754 self,
1755 name, # type: str
1756 which_scopes, # type: scope_t
1757 ref_trail=None, # type: Optional[List[str]]
1758 ):
1759 # type: (...) -> Tuple[Optional[Cell], Dict[str, Cell], str]
1760 """Look up a cell and namespace, but respect the nameref flag.
1761
1762 Resolving namerefs does RECURSIVE calls.
1763 """
1764 cell, var_frame = self._ResolveNameOnly(name, which_scopes)
1765
1766 if cell is None or not cell.nameref:
1767 return cell, var_frame, name # not a nameref
1768
1769 val = cell.val
1770 UP_val = val
1771 with tagswitch(val) as case:
1772 if case(value_e.Undef):
1773 # This is 'local -n undef_ref', which is kind of useless, because the
1774 # more common idiom is 'local -n ref=$1'. Note that you can mutate
1775 # references themselves with local -n ref=new.
1776 if self.exec_opts.strict_nameref():
1777 e_die('nameref %r is undefined' % name)
1778 else:
1779 return cell, var_frame, name # fallback
1780
1781 elif case(value_e.Str):
1782 val = cast(value.Str, UP_val)
1783 new_name = val.s
1784
1785 else:
1786 # SetValue() protects the invariant that nameref is Undef or Str
1787 raise AssertionError(val.tag())
1788
1789 # TODO: Respect eval_unsafe_arith here (issue 881). See how it's done in
1790 # 'printf -v' with MakeArithParser
1791 if not match.IsValidVarName(new_name):
1792 # e.g. '#' or '1' or ''
1793 if self.exec_opts.strict_nameref():
1794 e_die('nameref %r contains invalid variable name %r' %
1795 (name, new_name))
1796 else:
1797 # Bash has this odd behavior of clearing the nameref bit when
1798 # ref=#invalid#. strict_nameref avoids it.
1799 cell.nameref = False
1800 return cell, var_frame, name # fallback
1801
1802 # Check for circular namerefs.
1803 if ref_trail is None:
1804 ref_trail = [name]
1805 else:
1806 if new_name in ref_trail:
1807 e_die('Circular nameref %s' % ' -> '.join(ref_trail))
1808 ref_trail.append(new_name)
1809
1810 # 'declare -n' uses dynamic scope.
1811 cell, var_frame, cell_name = self._ResolveNameOrRef(
1812 new_name, scope_e.Dynamic, ref_trail=ref_trail)
1813 return cell, var_frame, cell_name
1814
1815 def IsBashAssoc(self, name):
1816 # type: (str) -> bool
1817 """Returns whether a name resolve to a cell with an associative array.
1818
1819 We need to know this to evaluate the index expression properly
1820 -- should it be coerced to an integer or not?
1821 """
1822 cell, _, _ = self._ResolveNameOrRef(name, self.ScopesForReading())
1823 # foo=([key]=value)
1824 return cell is not None and cell.val.tag() == value_e.BashAssoc
1825
1826 def SetPlace(self, place, val, blame_loc):
1827 # type: (value.Place, value_t, loc_t) -> None
1828
1829 yval = place.lval
1830 UP_yval = yval
1831 with tagswitch(yval) as case:
1832 if case(y_lvalue_e.Local):
1833 yval = cast(LeftName, UP_yval)
1834
1835 if 0:
1836 # Check that the frame is still alive
1837 # Note: Disabled because it doesn't work with modules. the
1838 # Place captures a frame in def-test.ysh, which we want to
1839 # mutate while Dict is executing in the module_frame for
1840 # def.ysh. See ctx_ModuleEval
1841 found = False
1842 for i in xrange(len(self.var_stack) - 1, -1, -1):
1843 frame = self.var_stack[i]
1844 if frame is place.frame:
1845 found = True
1846 #log('FOUND %s', found)
1847 break
1848 if not found:
1849 e_die(
1850 "Can't assign to place that's no longer on the call stack.",
1851 blame_loc)
1852
1853 frame = place.frame
1854 cell = frame.get(yval.name)
1855 if cell is None:
1856 cell = Cell(False, False, False, val)
1857 frame[yval.name] = cell
1858 else:
1859 cell.val = val
1860
1861 elif case(y_lvalue_e.Container):
1862 e_die('Container place not implemented', blame_loc)
1863
1864 else:
1865 raise AssertionError()
1866
1867 def SetLocalName(self, lval, val):
1868 # type: (LeftName, value_t) -> None
1869 """
1870 Set a name in the local scope - used for func/proc param binding, etc.
1871 """
1872
1873 # Equivalent to
1874 # self._ResolveNameOnly(lval.name, scope_e.LocalOnly)
1875 var_frame = self.var_stack[-1]
1876 cell = var_frame.get(lval.name)
1877
1878 if cell:
1879 if cell.readonly:
1880 e_die("Can't assign to readonly value %r" % lval.name,
1881 lval.blame_loc)
1882 cell.val = val # Mutate value_t
1883 else:
1884 cell = Cell(False, False, False, val)
1885 var_frame[lval.name] = cell
1886
1887 def SetNamed(self, lval, val, which_scopes, flags=0):
1888 # type: (LeftName, value_t, scope_t, int) -> None
1889 if flags & SetNameref or flags & ClearNameref:
1890 # declare -n ref=x # refers to the ref itself
1891 cell, var_frame = self._ResolveNameOnly(lval.name, which_scopes)
1892 cell_name = lval.name
1893 else:
1894 # ref=x # mutates THROUGH the reference
1895
1896 # Note on how to implement declare -n ref='a[42]'
1897 # 1. Call _ResolveNameOnly()
1898 # 2. If cell.nameref, call self.unsafe_arith.ParseVarRef() ->
1899 # BracedVarSub
1900 # 3. Turn BracedVarSub into an sh_lvalue, and call
1901 # self.unsafe_arith.SetValue() wrapper with ref_trail
1902 cell, var_frame, cell_name = self._ResolveNameOrRef(
1903 lval.name, which_scopes)
1904
1905 if cell:
1906 # Clear before checking readonly bit.
1907 # NOTE: Could be cell.flags &= flag_clear_mask
1908 if flags & ClearExport:
1909 cell.exported = False
1910 if flags & ClearReadOnly:
1911 cell.readonly = False
1912 if flags & ClearNameref:
1913 cell.nameref = False
1914
1915 if val is not None: # e.g. declare -rx existing
1916 # Note: this DYNAMIC check means we can't have 'const' in a loop.
1917 # But that's true for 'readonly' too, and hoisting it makes more
1918 # sense anyway.
1919 if cell.readonly:
1920 e_die("Can't assign to readonly value %r" % lval.name,
1921 lval.blame_loc)
1922 cell.val = val # CHANGE VAL
1923
1924 # NOTE: Could be cell.flags |= flag_set_mask
1925 if flags & SetExport:
1926 cell.exported = True
1927 if flags & SetReadOnly:
1928 cell.readonly = True
1929 if flags & SetNameref:
1930 cell.nameref = True
1931
1932 else:
1933 if val is None: # declare -rx nonexistent
1934 # set -o nounset; local foo; echo $foo # It's still undefined!
1935 val = value.Undef # export foo, readonly foo
1936
1937 cell = Cell(bool(flags & SetExport), bool(flags & SetReadOnly),
1938 bool(flags & SetNameref), val)
1939 var_frame[cell_name] = cell
1940
1941 # Maintain invariant that only strings and undefined cells can be
1942 # exported.
1943 assert cell.val is not None, cell
1944
1945 if cell.val.tag() not in (value_e.Undef, value_e.Str):
1946 if cell.exported:
1947 if self.exec_opts.strict_array():
1948 e_die("Only strings can be exported (strict_array)",
1949 lval.blame_loc)
1950 if cell.nameref:
1951 e_die("nameref must be a string", lval.blame_loc)
1952
1953 def SetValue(self, lval, val, which_scopes, flags=0):
1954 # type: (sh_lvalue_t, value_t, scope_t, int) -> None
1955 """
1956 Args:
1957 lval: sh_lvalue
1958 val: value, or None if only changing flags
1959 which_scopes:
1960 Local | Global | Dynamic - for builtins, PWD, etc.
1961 flags: packed pair (keyword_id, bit mask of set/clear flags)
1962
1963 Note: in bash, PWD=/ changes the directory. But not in dash.
1964 """
1965 # STRICTNESS / SANENESS:
1966 #
1967 # 1) Don't create arrays automatically, e.g. a[1000]=x
1968 # 2) Never change types? yeah I think that's a good idea, at least for YSH
1969 # (not sh, for compatibility). set -o strict_types or something. That
1970 # means arrays have to be initialized with let arr = [], which is fine.
1971 # This helps with stuff like IFS. It starts off as a string, and assigning
1972 # it to a list is an error. I guess you will have to turn this no for
1973 # bash?
1974 #
1975 # TODO:
1976 # - COMPUTED vars can't be set
1977 # - What about PWD / OLDPWD / UID / EUID ? You can simply make them
1978 # readonly.
1979 # - Maybe PARSE $PS1 and $PS4 when they're set, to avoid the error on use?
1980 # - Other validity: $HOME could be checked for existence
1981
1982 UP_lval = lval
1983 with tagswitch(lval) as case:
1984 if case(sh_lvalue_e.Var):
1985 lval = cast(LeftName, UP_lval)
1986
1987 self.SetNamed(lval, val, which_scopes, flags=flags)
1988
1989 elif case(sh_lvalue_e.Indexed):
1990 lval = cast(sh_lvalue.Indexed, UP_lval)
1991
1992 # There is no syntax 'declare a[x]'
1993 assert val is not None, val
1994
1995 # TODO: relax this for YSH
1996 assert val.tag() == value_e.Str, val
1997 rval = cast(value.Str, val)
1998
1999 # Note: location could be a[x]=1 or (( a[ x ] = 1 ))
2000 left_loc = lval.blame_loc
2001
2002 # bash/mksh have annoying behavior of letting you do LHS assignment to
2003 # Undef, which then turns into an INDEXED array. (Undef means that set
2004 # -o nounset fails.)
2005 cell, var_frame, _ = self._ResolveNameOrRef(
2006 lval.name, which_scopes)
2007 if not cell:
2008 self._BindNewArrayWithEntry(var_frame, lval, rval, flags)
2009 return
2010
2011 if cell.readonly:
2012 e_die("Can't assign to readonly array", left_loc)
2013
2014 UP_cell_val = cell.val
2015 # undef[0]=y is allowed
2016 with tagswitch(UP_cell_val) as case2:
2017 if case2(value_e.Undef):
2018 self._BindNewArrayWithEntry(var_frame, lval, rval,
2019 flags)
2020 return
2021
2022 elif case2(value_e.Str):
2023 # s=x
2024 # s[1]=y # invalid
2025 e_die("Can't assign to items in a string", left_loc)
2026
2027 elif case2(value_e.BashArray):
2028 cell_val = cast(value.BashArray, UP_cell_val)
2029 error_code = bash_impl.BashArray_SetElement(
2030 cell_val, lval.index, rval.s)
2031 if error_code == error_code_e.IndexOutOfRange:
2032 n = bash_impl.BashArray_Length(cell_val)
2033 e_die(
2034 "Index %d is out of bounds for array of length %d"
2035 % (lval.index, n), left_loc)
2036 return
2037
2038 elif case2(value_e.SparseArray):
2039 lhs_sp = cast(value.SparseArray, UP_cell_val)
2040 error_code = bash_impl.SparseArray_SetElement(
2041 lhs_sp, mops.IntWiden(lval.index), rval.s)
2042 if error_code == error_code_e.IndexOutOfRange:
2043 n_big = bash_impl.SparseArray_Length(lhs_sp)
2044 e_die(
2045 "Index %d is out of bounds for array of length %s"
2046 % (lval.index, mops.ToStr(n_big)), left_loc)
2047 return
2048
2049 # This could be an object, eggex object, etc. It won't be
2050 # BashAssoc shouldn because we query IsBashAssoc before evaluating
2051 # sh_lhs. Could conslidate with s[i] case above
2052 e_die(
2053 "Value of type %s can't be indexed" % ui.ValType(cell.val),
2054 left_loc)
2055
2056 elif case(sh_lvalue_e.Keyed):
2057 lval = cast(sh_lvalue.Keyed, UP_lval)
2058
2059 # There is no syntax 'declare A["x"]'
2060 assert val is not None, val
2061 assert val.tag() == value_e.Str, val
2062 rval = cast(value.Str, val)
2063
2064 left_loc = lval.blame_loc
2065
2066 cell, var_frame, _ = self._ResolveNameOrRef(
2067 lval.name, which_scopes)
2068 if cell.readonly:
2069 e_die("Can't assign to readonly associative array",
2070 left_loc)
2071
2072 # We already looked it up before making the sh_lvalue
2073 assert cell.val.tag() == value_e.BashAssoc, cell
2074 cell_val2 = cast(value.BashAssoc, cell.val)
2075 bash_impl.BashAssoc_SetElement(cell_val2, lval.key, rval.s)
2076
2077 else:
2078 raise AssertionError(lval.tag())
2079
2080 def _BindNewArrayWithEntry(self, var_frame, lval, val, flags):
2081 # type: (Dict[str, Cell], sh_lvalue.Indexed, value.Str, int) -> None
2082 """Fill 'var_frame' with a new indexed array entry."""
2083 no_str = None # type: Optional[str]
2084 items = [no_str] * lval.index
2085 items.append(val.s)
2086 new_value = value.BashArray(items)
2087
2088 # arrays can't be exported; can't have BashAssoc flag
2089 readonly = bool(flags & SetReadOnly)
2090 var_frame[lval.name] = Cell(False, readonly, False, new_value)
2091
2092 def InternalSetGlobal(self, name, new_val):
2093 # type: (str, value_t) -> None
2094 """For setting read-only globals internally.
2095
2096 Args:
2097 name: string (not Lhs)
2098 new_val: value
2099
2100 The variable must already exist.
2101
2102 Use case: SHELLOPTS.
2103 """
2104 cell = self.var_stack[0][name]
2105 cell.val = new_val
2106
2107 def GetValue(self, name, which_scopes=scope_e.Shopt):
2108 # type: (str, scope_t) -> value_t
2109 """Used by the WordEvaluator, ArithEvaluator, ExprEvaluator, etc."""
2110 assert isinstance(name, str), name
2111
2112 if which_scopes == scope_e.Shopt:
2113 which_scopes = self.ScopesForReading()
2114
2115 with str_switch(name) as case:
2116 # "Registers"
2117 if case('_status'): # deprecated in favor of _error.code
2118 return num.ToBig(self.TryStatus())
2119
2120 elif case('_error'):
2121 return self.TryError()
2122
2123 elif case('_this_dir'):
2124 if len(self.this_dir) == 0:
2125 # e.g. osh -c '' doesn't have it set
2126 # Should we give a custom error here?
2127 # If you're at the interactive shell, 'source mymodule.ysh' will still
2128 # work because 'source' sets it.
2129 return value.Undef
2130 else:
2131 return value.Str(self.this_dir[-1]) # top of stack
2132
2133 elif case('PIPESTATUS'):
2134 strs2 = [str(i)
2135 for i in self.pipe_status[-1]] # type: List[str]
2136 return value.BashArray(strs2)
2137
2138 elif case('_pipeline_status'):
2139 items = [num.ToBig(i)
2140 for i in self.pipe_status[-1]] # type: List[value_t]
2141 return value.List(items)
2142
2143 elif case('_process_sub_status'): # YSH naming convention
2144 items = [num.ToBig(i) for i in self.process_sub_status[-1]]
2145 return value.List(items)
2146
2147 elif case('BASH_REMATCH'):
2148 top_match = self.regex_match[-1]
2149 with tagswitch(top_match) as case2:
2150 if case2(regex_match_e.No):
2151 groups = [] # type: List[str]
2152 elif case2(regex_match_e.Yes):
2153 m = cast(RegexMatch, top_match)
2154 groups = util.RegexGroupStrings(m.s, m.indices)
2155 return value.BashArray(groups)
2156
2157 # Do lookup of system globals before looking at user variables. Note: we
2158 # could optimize this at compile-time like $?. That would break
2159 # ${!varref}, but it's already broken for $?.
2160
2161 elif case('FUNCNAME'):
2162 # bash wants it in reverse order. This is a little inefficient but we're
2163 # not depending on deque().
2164 strs = [] # type: List[str]
2165 for frame in reversed(self.debug_stack):
2166 UP_frame = frame
2167 with tagswitch(frame) as case2:
2168 if case2(debug_frame_e.ProcLike):
2169 frame = cast(debug_frame.ProcLike, UP_frame)
2170 strs.append(frame.proc_name)
2171
2172 elif case2(debug_frame_e.Source):
2173 # bash doesn't tell you the filename sourced
2174 strs.append('source')
2175
2176 elif case2(debug_frame_e.MainFile):
2177 strs.append('main') # also bash behavior
2178
2179 else: # ignore
2180 pass
2181
2182 return value.BashArray(strs) # TODO: Reuse this object too?
2183
2184 # $BASH_SOURCE and $BASH_LINENO have OFF BY ONE design bugs:
2185 #
2186 # ${BASH_LINENO[$i]} is the line number in the source file
2187 # (${BASH_SOURCE[$i+1]}) where ${FUNCNAME[$i]} was called (or
2188 # ${BASH_LINENO[$i-1]} if referenced within another shell function).
2189 #
2190 # https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html
2191
2192 elif case('BASH_SOURCE'):
2193 strs = []
2194 for frame in reversed(self.debug_stack):
2195 UP_frame = frame
2196 with tagswitch(frame) as case2:
2197 if case2(debug_frame_e.ProcLike):
2198 frame = cast(debug_frame.ProcLike, UP_frame)
2199
2200 # Weird bash behavior
2201 assert frame.def_tok.line is not None
2202 source_str = ui.GetLineSourceString(
2203 frame.def_tok.line)
2204 strs.append(source_str)
2205
2206 elif case2(debug_frame_e.Source):
2207 frame = cast(debug_frame.Source, UP_frame)
2208 # Is this right?
2209 strs.append(frame.source_name)
2210
2211 elif case2(debug_frame_e.MainFile):
2212 frame = cast(debug_frame.MainFile, UP_frame)
2213 strs.append(frame.main_filename)
2214
2215 else: # ignore
2216 pass
2217
2218 return value.BashArray(strs) # TODO: Reuse this object too?
2219
2220 elif case('BASH_LINENO'):
2221 strs = []
2222 for frame in reversed(self.debug_stack):
2223 UP_frame = frame
2224 with tagswitch(frame) as case2:
2225 if case2(debug_frame_e.ProcLike):
2226 frame = cast(debug_frame.ProcLike, UP_frame)
2227 invoke_token = location.LeftTokenForCompoundWord(
2228 frame.invoke_loc)
2229 assert invoke_token is not None, frame.invoke_loc
2230 strs.append(_LineNumber(invoke_token))
2231
2232 elif case2(debug_frame_e.Source):
2233 frame = cast(debug_frame.Source, UP_frame)
2234 invoke_token = location.LeftTokenForCompoundWord(
2235 frame.source_loc)
2236 assert invoke_token is not None, frame.source_loc
2237 strs.append(_LineNumber(invoke_token))
2238
2239 elif case2(debug_frame_e.MainFile):
2240 # Bash does this to line up with 'main'
2241 strs.append('0')
2242
2243 else: # ignore
2244 pass
2245
2246 return value.BashArray(strs) # TODO: Reuse this object too?
2247
2248 elif case('LINENO'):
2249 assert self.token_for_line is not None
2250 # Reuse object with mutation
2251 # TODO: maybe use interned GetLineNumStr?
2252 self.line_num.s = str(self.token_for_line.line.line_num)
2253 return self.line_num
2254
2255 elif case('BASHPID'): # TODO: YSH io->getpid()
2256 return value.Str(str(posix.getpid()))
2257
2258 elif case('_'):
2259 return value.Str(self.last_arg)
2260
2261 elif case('SECONDS'):
2262 f = time_.time() - self.seconds_start
2263 ok, big_int = mops.FromFloat(f)
2264 assert ok, f # should never be NAN or INFINITY
2265 return value.Int(big_int)
2266
2267 else:
2268 # In the case 'declare -n ref='a[42]', the result won't be a cell. Idea to
2269 # fix this:
2270 # 1. Call self.unsafe_arith.ParseVarRef() -> BracedVarSub
2271 # 2. Call self.unsafe_arith.GetNameref(bvs_part), and get a value_t
2272 # We still need a ref_trail to detect cycles.
2273 cell, _, _ = self._ResolveNameOrRef(name, which_scopes)
2274 if cell:
2275 return cell.val
2276
2277 builtin_val = self.builtins.get(name)
2278 if builtin_val:
2279 return builtin_val
2280
2281 # TODO: Can look in the builtins module, which is a value.Obj
2282 return value.Undef
2283
2284 def GetCell(self, name, which_scopes=scope_e.Shopt):
2285 # type: (str, scope_t) -> Cell
2286 """Get both the value and flags.
2287
2288 Usages:
2289 - the 'pp' builtin.
2290 - declare -p
2291 - ${x@a}
2292 - to test of 'TZ' is exported in printf? Why?
2293
2294 Note: consulting __builtins__ doesn't see necessary for any of these
2295 """
2296 if which_scopes == scope_e.Shopt:
2297 which_scopes = self.ScopesForReading()
2298
2299 cell, _ = self._ResolveNameOnly(name, which_scopes)
2300 return cell
2301
2302 def Unset(self, lval, which_scopes):
2303 # type: (sh_lvalue_t, scope_t) -> bool
2304 """
2305 Returns:
2306 Whether the cell was found.
2307 """
2308 # TODO: Refactor sh_lvalue type to avoid this
2309 UP_lval = lval
2310
2311 with tagswitch(lval) as case:
2312 if case(sh_lvalue_e.Var): # unset x
2313 lval = cast(LeftName, UP_lval)
2314 var_name = lval.name
2315 elif case(sh_lvalue_e.Indexed): # unset 'a[1]'
2316 lval = cast(sh_lvalue.Indexed, UP_lval)
2317 var_name = lval.name
2318 elif case(sh_lvalue_e.Keyed): # unset 'A["K"]'
2319 lval = cast(sh_lvalue.Keyed, UP_lval)
2320 var_name = lval.name
2321 else:
2322 raise AssertionError()
2323
2324 if which_scopes == scope_e.Shopt:
2325 which_scopes = self.ScopesForWriting()
2326
2327 cell, var_frame, cell_name = self._ResolveNameOrRef(
2328 var_name, which_scopes)
2329 if not cell:
2330 return False # 'unset' builtin falls back on functions
2331 if cell.readonly:
2332 raise error.Runtime("Can't unset readonly variable %r" % var_name)
2333
2334 with tagswitch(lval) as case:
2335 if case(sh_lvalue_e.Var): # unset x
2336 # Make variables in higher scopes visible.
2337 # example: test/spec.sh builtin-vars -r 24 (ble.sh)
2338 mylib.dict_erase(var_frame, cell_name)
2339
2340 # alternative that some shells use:
2341 # var_frame[cell_name].val = value.Undef
2342 # cell.exported = False
2343
2344 # This should never happen because we do recursive lookups of namerefs.
2345 assert not cell.nameref, cell
2346
2347 elif case(sh_lvalue_e.Indexed): # unset 'a[1]'
2348 lval = cast(sh_lvalue.Indexed, UP_lval)
2349 # Note: Setting an entry to None and shifting entries are pretty
2350 # much the same in shell.
2351
2352 val = cell.val
2353 UP_val = val
2354 if val.tag() == value_e.BashArray:
2355 val = cast(value.BashArray, UP_val)
2356 error_code = bash_impl.BashArray_UnsetElement(
2357 val, lval.index)
2358 if error_code == error_code_e.IndexOutOfRange:
2359 n = bash_impl.BashArray_Length(val)
2360 raise error.Runtime(
2361 "%s[%d]: Index is out of bounds for array of length %d"
2362 % (var_name, lval.index, n))
2363 elif val.tag() == value_e.SparseArray:
2364 val = cast(value.SparseArray, UP_val)
2365 error_code = bash_impl.SparseArray_UnsetElement(
2366 val, mops.IntWiden(lval.index))
2367 if error_code == error_code_e.IndexOutOfRange:
2368 big_length = bash_impl.SparseArray_Length(val)
2369 raise error.Runtime(
2370 "%s[%d]: Index is out of bounds for array of length %s"
2371 % (var_name, lval.index, mops.ToStr(big_length)))
2372 else:
2373 raise error.Runtime("%r isn't an array" % var_name)
2374
2375 elif case(sh_lvalue_e.Keyed): # unset 'A["K"]'
2376 lval = cast(sh_lvalue.Keyed, UP_lval)
2377
2378 val = cell.val
2379 UP_val = val
2380
2381 # note: never happens because of mem.IsBashAssoc test for sh_lvalue.Keyed
2382 #if val.tag() != value_e.BashAssoc:
2383 # raise error.Runtime("%r isn't an associative array" % lval.name)
2384
2385 val = cast(value.BashAssoc, UP_val)
2386 bash_impl.BashAssoc_UnsetElement(val, lval.key)
2387
2388 else:
2389 raise AssertionError(lval)
2390
2391 return True
2392
2393 def ScopesForReading(self):
2394 # type: () -> scope_t
2395 """Read scope."""
2396 return (scope_e.Dynamic
2397 if self.exec_opts.dynamic_scope() else scope_e.LocalOrGlobal)
2398
2399 def ScopesForWriting(self):
2400 # type: () -> scope_t
2401 """Write scope."""
2402 return (scope_e.Dynamic
2403 if self.exec_opts.dynamic_scope() else scope_e.LocalOnly)
2404
2405 def ClearFlag(self, name, flag):
2406 # type: (str, int) -> bool
2407 """Used for export -n.
2408
2409 We don't use SetValue() because even if rval is None, it will make an
2410 Undef value in a scope.
2411 """
2412 cell, var_frame = self._ResolveNameOnly(name, self.ScopesForReading())
2413 if cell:
2414 if flag & ClearExport:
2415 cell.exported = False
2416 if flag & ClearNameref:
2417 cell.nameref = False
2418 return True
2419 else:
2420 return False
2421
2422 def _FillWithExported(self, new_env):
2423 # type: (Dict[str, str]) -> None
2424
2425 # Search from globals up. Names higher on the stack will overwrite
2426 # names lower on the stack.
2427 for scope in self.var_stack:
2428 for name, cell in iteritems(scope):
2429 if cell.exported and cell.val.tag() == value_e.Str:
2430 val = cast(value.Str, cell.val)
2431 new_env[name] = val.s
2432
2433 def _FillEnvObj(self, new_env, env_object):
2434 # type: (Dict[str, str], Obj) -> None
2435
2436 # Do the LEAST visible parts first
2437 if env_object.prototype is not None:
2438 self._FillEnvObj(new_env, env_object.prototype)
2439
2440 # Overwrite with MOST visible parts
2441 for name, val in iteritems(env_object.d):
2442 if val.tag() != value_e.Str:
2443 continue
2444 new_env[name] = cast(value.Str, val).s
2445
2446 def GetEnv(self):
2447 # type: () -> Dict[str, str]
2448 """
2449 Get the environment that should be used for launching processes.
2450
2451 Note: This is run on every SimpleCommand. Should we have a dirty
2452 flag? We could notice these things:
2453
2454 - If an exported variable is changed
2455 - If the set of exported variables changes.
2456 """
2457 new_env = {} # type: Dict[str, str]
2458
2459 # Note: ysh:upgrade has both of these behaviors
2460
2461 # OSH: Consult exported vars
2462 if not self.exec_opts.no_exported():
2463 self._FillWithExported(new_env)
2464
2465 # YSH: Consult the ENV dict
2466 if self.exec_opts.env_obj():
2467 self._FillEnvObj(new_env, self.env_object)
2468
2469 return new_env
2470
2471 def VarNames(self):
2472 # type: () -> List[str]
2473 """For internal OSH completion and compgen -A variable.
2474
2475 NOTE: We could also add $? $$ etc.?
2476 """
2477 ret = [] # type: List[str]
2478 # Look up the stack, yielding all variables. Bash seems to do this.
2479 for scope in self.var_stack:
2480 for name in scope:
2481 ret.append(name)
2482 return ret
2483
2484 def VarNamesStartingWith(self, prefix):
2485 # type: (str) -> List[str]
2486 """For ${!prefix@}"""
2487 # Look up the stack, yielding all variables. Bash seems to do this.
2488 names = [] # type: List[str]
2489 for scope in self.var_stack:
2490 for name in scope:
2491 if name.startswith(prefix):
2492 names.append(name)
2493 return names
2494
2495 def GetAllVars(self):
2496 # type: () -> Dict[str, str]
2497 """Get all variables and their values, for 'set' builtin."""
2498 result = {} # type: Dict[str, str]
2499 for scope in self.var_stack:
2500 for name, cell in iteritems(scope):
2501 # TODO: Show other types?
2502 val = cell.val
2503 if val.tag() == value_e.Str:
2504 str_val = cast(value.Str, val)
2505 result[name] = str_val.s
2506 return result
2507
2508 def GetAllCells(self, which_scopes):
2509 # type: (scope_t) -> Dict[str, Cell]
2510 """Get all variables and their values, for 'set' builtin."""
2511 result = {} # type: Dict[str, Cell]
2512
2513 if which_scopes == scope_e.Dynamic:
2514 scopes = self.var_stack
2515 elif which_scopes == scope_e.LocalOnly:
2516 scopes = self.var_stack[-1:]
2517 elif which_scopes == scope_e.GlobalOnly:
2518 scopes = self.var_stack[0:1]
2519 elif which_scopes == scope_e.LocalOrGlobal:
2520 scopes = [self.var_stack[0]]
2521 if len(self.var_stack) > 1:
2522 scopes.append(self.var_stack[-1])
2523 else:
2524 raise AssertionError()
2525
2526 for scope in scopes:
2527 for name, cell in iteritems(scope):
2528 result[name] = cell
2529 return result
2530
2531 def SetRegexMatch(self, match):
2532 # type: (regex_match_t) -> None
2533 self.regex_match[-1] = match
2534
2535 def GetRegexMatch(self):
2536 # type: () -> regex_match_t
2537 return self.regex_match[-1]
2538
2539 def PushContextStack(self, context):
2540 # type: (Dict[str, value_t]) -> None
2541 self.ctx_stack.append(context)
2542
2543 def GetContext(self):
2544 # type: () -> Optional[Dict[str, value_t]]
2545 if len(self.ctx_stack):
2546 return self.ctx_stack[-1]
2547 return None
2548
2549 def PopContextStack(self):
2550 # type: () -> Dict[str, value_t]
2551 assert self.ctx_stack, "Empty context stack"
2552 return self.ctx_stack.pop()
2553
2554
2555def ValueIsInvokableObj(val):
2556 # type: (value_t) -> Tuple[Optional[value_t], Optional[Obj]]
2557 """
2558 Returns:
2559 (__invoke__ Proc or BuiltinProc, self Obj) if the value is invokable
2560 (None, None) otherwise
2561 """
2562 if val.tag() != value_e.Obj:
2563 return None, None
2564
2565 obj = cast(Obj, val)
2566 if not obj.prototype:
2567 return None, None
2568
2569 invoke_val = obj.prototype.d.get('__invoke__')
2570 if invoke_val is None:
2571 return None, None
2572
2573 # TODO: __invoke__ of wrong type could be fatal error?
2574 if invoke_val.tag() in (value_e.Proc, value_e.BuiltinProc):
2575 return invoke_val, obj
2576
2577 return None, None
2578
2579
2580def _AddNames(unique, frame):
2581 # type: (Dict[str, bool], Dict[str, Cell]) -> None
2582 for name in frame:
2583 val = frame[name].val
2584 if val.tag() == value_e.Proc:
2585 unique[name] = True
2586 proc, _ = ValueIsInvokableObj(val)
2587 if proc is not None:
2588 unique[name] = True
2589
2590
2591class Procs(object):
2592 """
2593 Terminology:
2594
2595 - invokable - these are INTERIOR
2596 - value.Proc - which can be shell function in __sh_funcs__ namespace, or
2597 YSH proc
2598 - value.Obj with __invoke__
2599 - exterior - external commands, extern builtin
2600
2601 Note: the YSH 'invoke' builtin can generalize YSH 'runproc' builtin, shell command/builtin,
2602 and also type / type -a
2603 """
2604
2605 def __init__(self, mem):
2606 # type: (Mem) -> None
2607 self.mem = mem
2608 self.sh_funcs = {} # type: Dict[str, value.Proc]
2609
2610 def DefineShellFunc(self, name, proc):
2611 # type: (str, value.Proc) -> None
2612 self.sh_funcs[name] = proc
2613
2614 def IsShellFunc(self, name):
2615 # type: (str) -> bool
2616 return name in self.sh_funcs
2617
2618 def GetShellFunc(self, name):
2619 # type: (str) -> Optional[value.Proc]
2620 return self.sh_funcs.get(name)
2621
2622 def EraseShellFunc(self, to_del):
2623 # type: (str) -> None
2624 """Undefine a sh-func with name `to_del`, if it exists."""
2625 mylib.dict_erase(self.sh_funcs, to_del)
2626
2627 def ShellFuncNames(self):
2628 # type: () -> List[str]
2629 """Returns a *sorted* list of all shell function names
2630
2631 Callers:
2632 declare -f -F
2633 """
2634 names = self.sh_funcs.keys()
2635 names.sort()
2636 return names
2637
2638 def DefineProc(self, name, proc):
2639 # type: (str, value.Proc) -> None
2640 """
2641 procs are defined in the local scope.
2642 """
2643 self.mem.var_stack[-1][name] = Cell(False, False, False, proc)
2644
2645 def IsProc(self, name):
2646 # type: (str) -> bool
2647
2648 maybe_proc = self.mem.GetValue(name)
2649 # Could be Undef
2650 return maybe_proc.tag() == value_e.Proc
2651
2652 def IsInvokableObj(self, name):
2653 # type: (str) -> bool
2654
2655 val = self.mem.GetValue(name)
2656 proc, _ = ValueIsInvokableObj(val)
2657 return proc is not None
2658
2659 def InvokableNames(self):
2660 # type: () -> List[str]
2661 """Returns a *sorted* list of all invokable names
2662
2663 Callers:
2664 complete -A function
2665 pp proc - should deprecate this
2666 """
2667 unique = NewDict() # type: Dict[str, bool]
2668 for name in self.sh_funcs:
2669 unique[name] = True
2670
2671 top_frame = self.mem.var_stack[-1]
2672 _AddNames(unique, top_frame)
2673
2674 global_frame = self.mem.var_stack[0]
2675 #log('%d %d', id(top_frame), id(global_frame))
2676 if global_frame is not top_frame:
2677 _AddNames(unique, global_frame)
2678
2679 #log('%s', unique)
2680
2681 names = unique.keys()
2682 names.sort()
2683
2684 return names
2685
2686 def GetInvokable(self, name):
2687 # type: (str) -> Tuple[Optional[value_t], Optional[Obj]]
2688 """Find a proc, invokable Obj, or sh-func, in that order
2689
2690 Callers:
2691 executor.py: to actually run
2692 meta_oils.py runproc lookup - this is not 'invoke', because it is
2693 INTERIOR shell functions, procs, invokable Obj
2694 """
2695 val = self.mem.GetValue(name)
2696
2697 if val.tag() == value_e.Proc:
2698 return cast(value.Proc, val), None
2699
2700 proc, self_val = ValueIsInvokableObj(val)
2701 if proc:
2702 return proc, self_val
2703
2704 if name in self.sh_funcs:
2705 return self.sh_funcs[name], None
2706
2707 return None, None
2708
2709
2710#
2711# Wrappers to Set Variables
2712#
2713
2714
2715def OshLanguageSetValue(mem, lval, val, flags=0):
2716 # type: (Mem, sh_lvalue_t, value_t, int) -> None
2717 """Like 'setvar' (scope_e.LocalOnly), unless dynamic scope is on.
2718
2719 That is, it respects shopt --unset dynamic_scope.
2720
2721 Used for assignment builtins, (( a = b )), {fd}>out, ${x=}, etc.
2722 """
2723 which_scopes = mem.ScopesForWriting()
2724 mem.SetValue(lval, val, which_scopes, flags=flags)
2725
2726
2727def BuiltinSetValue(mem, lval, val):
2728 # type: (Mem, sh_lvalue_t, value_t) -> None
2729 """Equivalent of x=$y
2730
2731 Called by BuiltinSetString and BuiltinSetArray Used directly by
2732 printf -v because it can mutate an array
2733 """
2734 mem.SetValue(lval, val, mem.ScopesForWriting())
2735
2736
2737def BuiltinSetString(mem, name, s):
2738 # type: (Mem, str, str) -> None
2739 """Set a string by looking up the stack.
2740
2741 Used for 'read', 'getopts', completion builtins, etc.
2742 """
2743 assert isinstance(s, str)
2744 BuiltinSetValue(mem, location.LName(name), value.Str(s))
2745
2746
2747def BuiltinSetArray(mem, name, a):
2748 # type: (Mem, str, List[str]) -> None
2749 """Set an array by looking up the stack.
2750
2751 Used by compadjust, read -a, etc.
2752 """
2753 assert isinstance(a, list)
2754 BuiltinSetValue(mem, location.LName(name), value.BashArray(a))
2755
2756
2757def SetGlobalString(mem, name, s):
2758 # type: (Mem, str, str) -> None
2759 """Helper for completion, etc."""
2760 assert isinstance(s, str)
2761 val = value.Str(s)
2762 mem.SetNamed(location.LName(name), val, scope_e.GlobalOnly)
2763
2764
2765def SetGlobalArray(mem, name, a):
2766 # type: (Mem, str, List[str]) -> None
2767 """Used by completion, shell initialization, etc."""
2768 assert isinstance(a, list)
2769 mem.SetNamed(location.LName(name), value.BashArray(a), scope_e.GlobalOnly)
2770
2771
2772def SetGlobalValue(mem, name, val):
2773 # type: (Mem, str, value_t) -> None
2774 """Helper for completion, etc."""
2775 mem.SetNamed(location.LName(name), val, scope_e.GlobalOnly)
2776
2777
2778def SetLocalValue(mem, name, val):
2779 # type: (Mem, str, value_t) -> None
2780 """For 'use' builtin."""
2781 mem.SetNamed(location.LName(name), val, scope_e.LocalOnly)
2782
2783
2784def ExportGlobalString(mem, name, s):
2785 # type: (Mem, str, str) -> None
2786 """Helper for completion, $PWD, $OLDPWD, etc."""
2787 assert isinstance(s, str)
2788 val = value.Str(s)
2789 mem.SetNamed(location.LName(name),
2790 val,
2791 scope_e.GlobalOnly,
2792 flags=SetExport)
2793
2794
2795# TODO: remove in favor of EnvConfig
2796def SetStringInEnv(mem, var_name, s):
2797 # type: (Mem, str, str) -> None
2798 if mem.exec_opts.env_obj(): # e.g. ENV.YSH_HISTFILE
2799 mem.env_dict[var_name] = value.Str(s)
2800 else: # e.g. $YSH_HISTFILE
2801 SetGlobalString(mem, var_name, s)
2802
2803
2804#
2805# Wrappers to Get Variables
2806#
2807
2808
2809def DynamicGetVar(mem, name, which_scopes):
2810 # type: (Mem, str, scope_t) -> value_t
2811 """
2812 For getVar() and shvarGet()
2813 """
2814 val = mem.GetValue(name, which_scopes=which_scopes)
2815
2816 # Undef is not a user-visible value!
2817 # There's no way to distinguish null from undefined.
2818 if val.tag() == value_e.Undef:
2819 return value.Null
2820
2821 return val
2822
2823
2824def GetString(mem, name):
2825 # type: (Mem, str) -> str
2826 """Wrapper around GetValue().
2827
2828 Check that HOME, PWD, OLDPWD, etc. are strings. bash doesn't have these
2829 errors because ${array} is ${array[0]}.
2830
2831 TODO: We could also check this when you're storing variables?
2832 """
2833 val = mem.GetValue(name)
2834 UP_val = val
2835 with tagswitch(val) as case:
2836 if case(value_e.Undef):
2837 raise error.Runtime("$%s isn't defined" % name)
2838 elif case(value_e.Str):
2839 return cast(value.Str, UP_val).s
2840 else:
2841 # User would have to 'unset HOME' to get rid of exported flag
2842 raise error.Runtime("$%s should be a string" % name)
2843
2844
2845def MaybeString(mem, name):
2846 # type: (Mem, str) -> Optional[str]
2847 """Like GetString(), but doesn't throw an exception."""
2848 try:
2849 return GetString(mem, name)
2850 except error.Runtime:
2851 return None
2852
2853
2854def GetInteger(mem, name):
2855 # type: (Mem, str) -> int
2856 """For OPTIND variable used in getopts builtin.
2857
2858 TODO: it could be value.Int() ?
2859 """
2860 val = mem.GetValue(name)
2861 if val.tag() != value_e.Str:
2862 raise error.Runtime('$%s should be a string, got %s' %
2863 (name, ui.ValType(val)))
2864 s = cast(value.Str, val).s
2865 try:
2866 i = int(s)
2867 except ValueError:
2868 raise error.Runtime("$%s doesn't look like an integer, got %r" %
2869 (name, s))
2870 return i
2871
2872
2873# vim: sw=4