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