| 1 | #!/usr/bin/env python2
 | 
| 2 | from __future__ import print_function
 | 
| 3 | 
 | 
| 4 | from _devbuild.gen import arg_types
 | 
| 5 | from _devbuild.gen.option_asdl import builtin_i
 | 
| 6 | from _devbuild.gen.runtime_asdl import (
 | 
| 7 |     scope_e,
 | 
| 8 |     cmd_value,
 | 
| 9 |     AssignArg,
 | 
| 10 | )
 | 
| 11 | from _devbuild.gen.value_asdl import (value, value_e, value_t, LeftName)
 | 
| 12 | from _devbuild.gen.syntax_asdl import loc, loc_t, word_t
 | 
| 13 | 
 | 
| 14 | from core import error
 | 
| 15 | from core.error import e_usage
 | 
| 16 | from core import state
 | 
| 17 | from core import vm
 | 
| 18 | from frontend import flag_util
 | 
| 19 | from frontend import args
 | 
| 20 | from mycpp import mylib
 | 
| 21 | from mycpp.mylib import log
 | 
| 22 | from osh import cmd_eval
 | 
| 23 | from osh import sh_expr_eval
 | 
| 24 | from data_lang import j8_lite
 | 
| 25 | 
 | 
| 26 | from typing import cast, Optional, Dict, List, TYPE_CHECKING
 | 
| 27 | if TYPE_CHECKING:
 | 
| 28 |     from core.state import Mem
 | 
| 29 |     from core import optview
 | 
| 30 |     from core import ui
 | 
| 31 |     from frontend.args import _Attributes
 | 
| 32 | 
 | 
| 33 | _ = log
 | 
| 34 | 
 | 
| 35 | _OTHER = 0
 | 
| 36 | _READONLY = 1
 | 
| 37 | _EXPORT = 2
 | 
| 38 | 
 | 
| 39 | 
 | 
| 40 | def _PrintVariables(mem, cmd_val, attrs, print_flags, builtin=_OTHER):
 | 
| 41 |     # type: (Mem, cmd_value.Assign, _Attributes, bool, int) -> int
 | 
| 42 |     """
 | 
| 43 |     Args:
 | 
| 44 |       print_flags: whether to print flags
 | 
| 45 |       builtin: is it the readonly or export builtin?
 | 
| 46 |     """
 | 
| 47 |     flag = attrs.attrs
 | 
| 48 | 
 | 
| 49 |     # Turn dynamic vars to static.
 | 
| 50 |     tmp_g = flag.get('g')
 | 
| 51 |     tmp_a = flag.get('a')
 | 
| 52 |     tmp_A = flag.get('A')
 | 
| 53 | 
 | 
| 54 |     flag_g = (cast(value.Bool, tmp_g).b
 | 
| 55 |               if tmp_g and tmp_g.tag() == value_e.Bool else False)
 | 
| 56 |     flag_a = (cast(value.Bool, tmp_a).b
 | 
| 57 |               if tmp_a and tmp_a.tag() == value_e.Bool else False)
 | 
| 58 |     flag_A = (cast(value.Bool, tmp_A).b
 | 
| 59 |               if tmp_A and tmp_A.tag() == value_e.Bool else False)
 | 
| 60 | 
 | 
| 61 |     tmp_n = flag.get('n')
 | 
| 62 |     tmp_r = flag.get('r')
 | 
| 63 |     tmp_x = flag.get('x')
 | 
| 64 | 
 | 
| 65 |     #log('FLAG %r', flag)
 | 
| 66 | 
 | 
| 67 |     # SUBTLE: export -n vs. declare -n.  flag vs. OPTION.
 | 
| 68 |     # flags are value.Bool, while options are Undef or Str.
 | 
| 69 |     # '+', '-', or None
 | 
| 70 |     flag_n = (cast(value.Str, tmp_n).s if tmp_n and tmp_n.tag() == value_e.Str
 | 
| 71 |               else None)  # type: Optional[str]
 | 
| 72 |     flag_r = (cast(value.Str, tmp_r).s if tmp_r and tmp_r.tag() == value_e.Str
 | 
| 73 |               else None)  # type: Optional[str]
 | 
| 74 |     flag_x = (cast(value.Str, tmp_x).s if tmp_x and tmp_x.tag() == value_e.Str
 | 
| 75 |               else None)  # type: Optional[str]
 | 
| 76 | 
 | 
| 77 |     if cmd_val.builtin_id == builtin_i.local:
 | 
| 78 |         if flag_g and not mem.IsGlobalScope():
 | 
| 79 |             return 1
 | 
| 80 |         which_scopes = scope_e.LocalOnly
 | 
| 81 |     elif flag_g:
 | 
| 82 |         which_scopes = scope_e.GlobalOnly
 | 
| 83 |     else:
 | 
| 84 |         which_scopes = mem.ScopesForReading()  # reading
 | 
| 85 | 
 | 
| 86 |     if len(cmd_val.pairs) == 0:
 | 
| 87 |         print_all = True
 | 
| 88 |         cells = mem.GetAllCells(which_scopes)
 | 
| 89 |         names = sorted(cells)  # type: List[str]
 | 
| 90 |     else:
 | 
| 91 |         print_all = False
 | 
| 92 |         names = []
 | 
| 93 |         cells = {}
 | 
| 94 |         for pair in cmd_val.pairs:
 | 
| 95 |             name = pair.var_name
 | 
| 96 |             if pair.rval and pair.rval.tag() == value_e.Str:
 | 
| 97 |                 # Invalid: declare -p foo=bar
 | 
| 98 |                 # Add a sentinel so we skip it, but know to exit with status 1.
 | 
| 99 |                 s = cast(value.Str, pair.rval).s
 | 
| 100 |                 invalid = "%s=%s" % (name, s)
 | 
| 101 |                 names.append(invalid)
 | 
| 102 |                 cells[invalid] = None
 | 
| 103 |             else:
 | 
| 104 |                 names.append(name)
 | 
| 105 |                 cells[name] = mem.GetCell(name, which_scopes)
 | 
| 106 | 
 | 
| 107 |     count = 0
 | 
| 108 |     for name in names:
 | 
| 109 |         cell = cells[name]
 | 
| 110 |         if cell is None:
 | 
| 111 |             continue  # Invalid
 | 
| 112 |         val = cell.val
 | 
| 113 |         #log('name %r %s', name, val)
 | 
| 114 | 
 | 
| 115 |         if val.tag() == value_e.Undef:
 | 
| 116 |             continue
 | 
| 117 |         if builtin == _READONLY and not cell.readonly:
 | 
| 118 |             continue
 | 
| 119 |         if builtin == _EXPORT and not cell.exported:
 | 
| 120 |             continue
 | 
| 121 | 
 | 
| 122 |         if flag_n == '-' and not cell.nameref:
 | 
| 123 |             continue
 | 
| 124 |         if flag_n == '+' and cell.nameref:
 | 
| 125 |             continue
 | 
| 126 |         if flag_r == '-' and not cell.readonly:
 | 
| 127 |             continue
 | 
| 128 |         if flag_r == '+' and cell.readonly:
 | 
| 129 |             continue
 | 
| 130 |         if flag_x == '-' and not cell.exported:
 | 
| 131 |             continue
 | 
| 132 |         if flag_x == '+' and cell.exported:
 | 
| 133 |             continue
 | 
| 134 | 
 | 
| 135 |         if flag_a and val.tag() != value_e.BashArray:
 | 
| 136 |             continue
 | 
| 137 |         if flag_A and val.tag() != value_e.BashAssoc:
 | 
| 138 |             continue
 | 
| 139 | 
 | 
| 140 |         decl = []  # type: List[str]
 | 
| 141 |         if print_flags:
 | 
| 142 |             flags = []  # type: List[str]
 | 
| 143 |             if cell.nameref:
 | 
| 144 |                 flags.append('n')
 | 
| 145 |             if cell.readonly:
 | 
| 146 |                 flags.append('r')
 | 
| 147 |             if cell.exported:
 | 
| 148 |                 flags.append('x')
 | 
| 149 |             if val.tag() == value_e.BashArray:
 | 
| 150 |                 flags.append('a')
 | 
| 151 |             elif val.tag() == value_e.BashAssoc:
 | 
| 152 |                 flags.append('A')
 | 
| 153 |             if len(flags) == 0:
 | 
| 154 |                 flags.append('-')
 | 
| 155 | 
 | 
| 156 |             decl.extend(["declare -", ''.join(flags), " ", name])
 | 
| 157 |         else:
 | 
| 158 |             decl.append(name)
 | 
| 159 | 
 | 
| 160 |         if val.tag() == value_e.Str:
 | 
| 161 |             str_val = cast(value.Str, val)
 | 
| 162 |             # TODO: Use fastfunc.ShellEncode()
 | 
| 163 |             decl.extend(["=", j8_lite.MaybeShellEncode(str_val.s)])
 | 
| 164 | 
 | 
| 165 |         elif val.tag() == value_e.BashArray:
 | 
| 166 |             array_val = cast(value.BashArray, val)
 | 
| 167 | 
 | 
| 168 |             # mycpp rewrite: None in array_val.strs
 | 
| 169 |             has_holes = False
 | 
| 170 |             for s in array_val.strs:
 | 
| 171 |                 if s is None:
 | 
| 172 |                     has_holes = True
 | 
| 173 |                     break
 | 
| 174 | 
 | 
| 175 |             if has_holes:
 | 
| 176 |                 # Note: Arrays with unset elements are printed in the form:
 | 
| 177 |                 #   declare -p arr=(); arr[3]='' arr[4]='foo' ...
 | 
| 178 |                 decl.append("=()")
 | 
| 179 |                 first = True
 | 
| 180 |                 for i, element in enumerate(array_val.strs):
 | 
| 181 |                     if element is not None:
 | 
| 182 |                         if first:
 | 
| 183 |                             decl.append(";")
 | 
| 184 |                             first = False
 | 
| 185 |                         decl.extend([
 | 
| 186 |                             " ", name, "[",
 | 
| 187 |                             str(i), "]=",
 | 
| 188 |                             j8_lite.MaybeShellEncode(element)
 | 
| 189 |                         ])
 | 
| 190 |             else:
 | 
| 191 |                 body = []  # type: List[str]
 | 
| 192 |                 for element in array_val.strs:
 | 
| 193 |                     if len(body) > 0:
 | 
| 194 |                         body.append(" ")
 | 
| 195 |                     body.append(j8_lite.MaybeShellEncode(element))
 | 
| 196 |                 decl.extend(["=(", ''.join(body), ")"])
 | 
| 197 | 
 | 
| 198 |         elif val.tag() == value_e.BashAssoc:
 | 
| 199 |             assoc_val = cast(value.BashAssoc, val)
 | 
| 200 |             body = []
 | 
| 201 |             for key in sorted(assoc_val.d):
 | 
| 202 |                 if len(body) > 0:
 | 
| 203 |                     body.append(" ")
 | 
| 204 | 
 | 
| 205 |                 key_quoted = j8_lite.ShellEncode(key)
 | 
| 206 |                 value_quoted = j8_lite.MaybeShellEncode(assoc_val.d[key])
 | 
| 207 | 
 | 
| 208 |                 body.extend(["[", key_quoted, "]=", value_quoted])
 | 
| 209 |             if len(body) > 0:
 | 
| 210 |                 decl.extend(["=(", ''.join(body), ")"])
 | 
| 211 | 
 | 
| 212 |         else:
 | 
| 213 |             pass  # note: other types silently ignored
 | 
| 214 | 
 | 
| 215 |         print(''.join(decl))
 | 
| 216 |         count += 1
 | 
| 217 | 
 | 
| 218 |     if print_all or count == len(names):
 | 
| 219 |         return 0
 | 
| 220 |     else:
 | 
| 221 |         return 1
 | 
| 222 | 
 | 
| 223 | 
 | 
| 224 | def _ExportReadonly(mem, pair, flags):
 | 
| 225 |     # type: (Mem, AssignArg, int) -> None
 | 
| 226 |     """For 'export' and 'readonly' to respect += and flags.
 | 
| 227 | 
 | 
| 228 |     Like 'setvar' (scope_e.LocalOnly), unless dynamic scope is on.  That is, it
 | 
| 229 |     respects shopt --unset dynamic_scope.
 | 
| 230 | 
 | 
| 231 |     Used for assignment builtins, (( a = b )), {fd}>out, ${x=}, etc.
 | 
| 232 |     """
 | 
| 233 |     which_scopes = mem.ScopesForWriting()
 | 
| 234 | 
 | 
| 235 |     lval = LeftName(pair.var_name, pair.blame_word)
 | 
| 236 |     if pair.plus_eq:
 | 
| 237 |         old_val = sh_expr_eval.OldValue(lval, mem, None)  # ignore set -u
 | 
| 238 |         # When 'export e+=', then rval is value.Str('')
 | 
| 239 |         # When 'export foo', the pair.plus_eq flag is false.
 | 
| 240 |         assert pair.rval is not None
 | 
| 241 |         val = cmd_eval.PlusEquals(old_val, pair.rval)
 | 
| 242 |     else:
 | 
| 243 |         # NOTE: when rval is None, only flags are changed
 | 
| 244 |         val = pair.rval
 | 
| 245 | 
 | 
| 246 |     mem.SetNamed(lval, val, which_scopes, flags=flags)
 | 
| 247 | 
 | 
| 248 | 
 | 
| 249 | class Export(vm._AssignBuiltin):
 | 
| 250 | 
 | 
| 251 |     def __init__(self, mem, errfmt):
 | 
| 252 |         # type: (Mem, ui.ErrorFormatter) -> None
 | 
| 253 |         self.mem = mem
 | 
| 254 |         self.errfmt = errfmt
 | 
| 255 | 
 | 
| 256 |     def Run(self, cmd_val):
 | 
| 257 |         # type: (cmd_value.Assign) -> int
 | 
| 258 |         arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
 | 
| 259 |         arg_r.Next()
 | 
| 260 |         attrs = flag_util.Parse('export_', arg_r)
 | 
| 261 |         arg = arg_types.export_(attrs.attrs)
 | 
| 262 |         #arg = attrs
 | 
| 263 | 
 | 
| 264 |         if arg.f:
 | 
| 265 |             e_usage(
 | 
| 266 |                 "doesn't accept -f because it's dangerous.  "
 | 
| 267 |                 "(The code can usually be restructured with 'source')",
 | 
| 268 |                 loc.Missing)
 | 
| 269 | 
 | 
| 270 |         if arg.p or len(cmd_val.pairs) == 0:
 | 
| 271 |             return _PrintVariables(self.mem,
 | 
| 272 |                                    cmd_val,
 | 
| 273 |                                    attrs,
 | 
| 274 |                                    True,
 | 
| 275 |                                    builtin=_EXPORT)
 | 
| 276 | 
 | 
| 277 |         if arg.n:
 | 
| 278 |             for pair in cmd_val.pairs:
 | 
| 279 |                 if pair.rval is not None:
 | 
| 280 |                     e_usage("doesn't accept RHS with -n",
 | 
| 281 |                             loc.Word(pair.blame_word))
 | 
| 282 | 
 | 
| 283 |                 # NOTE: we don't care if it wasn't found, like bash.
 | 
| 284 |                 self.mem.ClearFlag(pair.var_name, state.ClearExport)
 | 
| 285 |         else:
 | 
| 286 |             for pair in cmd_val.pairs:
 | 
| 287 |                 _ExportReadonly(self.mem, pair, state.SetExport)
 | 
| 288 | 
 | 
| 289 |         return 0
 | 
| 290 | 
 | 
| 291 | 
 | 
| 292 | def _ReconcileTypes(rval, flag_a, flag_A, blame_word):
 | 
| 293 |     # type: (Optional[value_t], bool, bool, word_t) -> value_t
 | 
| 294 |     """Check that -a and -A flags are consistent with RHS.
 | 
| 295 | 
 | 
| 296 |     Special case: () is allowed to mean empty indexed array or empty assoc array
 | 
| 297 |     if the context is clear.
 | 
| 298 | 
 | 
| 299 |     Shared between NewVar and Readonly.
 | 
| 300 |     """
 | 
| 301 |     if flag_a and rval is not None and rval.tag() != value_e.BashArray:
 | 
| 302 |         e_usage("Got -a but RHS isn't an array", loc.Word(blame_word))
 | 
| 303 | 
 | 
| 304 |     if flag_A and rval:
 | 
| 305 |         # Special case: declare -A A=() is OK.  The () is changed to mean an empty
 | 
| 306 |         # associative array.
 | 
| 307 |         if rval.tag() == value_e.BashArray:
 | 
| 308 |             array_val = cast(value.BashArray, rval)
 | 
| 309 |             if len(array_val.strs) == 0:
 | 
| 310 |                 return value.BashAssoc({})
 | 
| 311 |                 #return value.BashArray([])
 | 
| 312 | 
 | 
| 313 |         if rval.tag() != value_e.BashAssoc:
 | 
| 314 |             e_usage("Got -A but RHS isn't an associative array",
 | 
| 315 |                     loc.Word(blame_word))
 | 
| 316 | 
 | 
| 317 |     return rval
 | 
| 318 | 
 | 
| 319 | 
 | 
| 320 | class Readonly(vm._AssignBuiltin):
 | 
| 321 | 
 | 
| 322 |     def __init__(self, mem, errfmt):
 | 
| 323 |         # type: (Mem, ui.ErrorFormatter) -> None
 | 
| 324 |         self.mem = mem
 | 
| 325 |         self.errfmt = errfmt
 | 
| 326 | 
 | 
| 327 |     def Run(self, cmd_val):
 | 
| 328 |         # type: (cmd_value.Assign) -> int
 | 
| 329 |         arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
 | 
| 330 |         arg_r.Next()
 | 
| 331 |         attrs = flag_util.Parse('readonly', arg_r)
 | 
| 332 |         arg = arg_types.readonly(attrs.attrs)
 | 
| 333 | 
 | 
| 334 |         if arg.p or len(cmd_val.pairs) == 0:
 | 
| 335 |             return _PrintVariables(self.mem,
 | 
| 336 |                                    cmd_val,
 | 
| 337 |                                    attrs,
 | 
| 338 |                                    True,
 | 
| 339 |                                    builtin=_READONLY)
 | 
| 340 | 
 | 
| 341 |         for pair in cmd_val.pairs:
 | 
| 342 |             if pair.rval is None:
 | 
| 343 |                 if arg.a:
 | 
| 344 |                     rval = value.BashArray([])  # type: value_t
 | 
| 345 |                 elif arg.A:
 | 
| 346 |                     rval = value.BashAssoc({})
 | 
| 347 |                 else:
 | 
| 348 |                     rval = None
 | 
| 349 |             else:
 | 
| 350 |                 rval = pair.rval
 | 
| 351 | 
 | 
| 352 |             rval = _ReconcileTypes(rval, arg.a, arg.A, pair.blame_word)
 | 
| 353 | 
 | 
| 354 |             # NOTE:
 | 
| 355 |             # - when rval is None, only flags are changed
 | 
| 356 |             # - dynamic scope because flags on locals can be changed, etc.
 | 
| 357 |             _ExportReadonly(self.mem, pair, state.SetReadOnly)
 | 
| 358 | 
 | 
| 359 |         return 0
 | 
| 360 | 
 | 
| 361 | 
 | 
| 362 | class NewVar(vm._AssignBuiltin):
 | 
| 363 |     """declare/typeset/local."""
 | 
| 364 | 
 | 
| 365 |     def __init__(self, mem, procs, exec_opts, errfmt):
 | 
| 366 |         # type: (Mem, Dict[str, value.Proc], optview.Exec, ui.ErrorFormatter) -> None
 | 
| 367 |         self.mem = mem
 | 
| 368 |         self.procs = procs
 | 
| 369 |         self.exec_opts = exec_opts
 | 
| 370 |         self.errfmt = errfmt
 | 
| 371 | 
 | 
| 372 |     def _PrintFuncs(self, names):
 | 
| 373 |         # type: (List[str]) -> int
 | 
| 374 |         status = 0
 | 
| 375 |         for name in names:
 | 
| 376 |             if name in self.procs:
 | 
| 377 |                 print(name)
 | 
| 378 |                 # TODO: Could print LST for -f, or render LST.  Bash does this.  'trap'
 | 
| 379 |                 # could use that too.
 | 
| 380 |             else:
 | 
| 381 |                 status = 1
 | 
| 382 |         return status
 | 
| 383 | 
 | 
| 384 |     def Run(self, cmd_val):
 | 
| 385 |         # type: (cmd_value.Assign) -> int
 | 
| 386 |         arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
 | 
| 387 |         arg_r.Next()
 | 
| 388 |         attrs = flag_util.Parse('new_var', arg_r)
 | 
| 389 |         arg = arg_types.new_var(attrs.attrs)
 | 
| 390 | 
 | 
| 391 |         status = 0
 | 
| 392 | 
 | 
| 393 |         if arg.f:
 | 
| 394 |             names = arg_r.Rest()
 | 
| 395 |             if len(names):
 | 
| 396 |                 # This is only used for a STATUS QUERY now.  We only show the name,
 | 
| 397 |                 # not the body.
 | 
| 398 |                 status = self._PrintFuncs(names)
 | 
| 399 |             else:
 | 
| 400 |                 # Disallow this since it would be incompatible.
 | 
| 401 |                 e_usage('with -f expects function names', loc.Missing)
 | 
| 402 |             return status
 | 
| 403 | 
 | 
| 404 |         if arg.F:
 | 
| 405 |             names = arg_r.Rest()
 | 
| 406 |             if len(names):
 | 
| 407 |                 status = self._PrintFuncs(names)
 | 
| 408 |             else:
 | 
| 409 |                 # bash quirk: with no names, they're printed in a different format!
 | 
| 410 |                 for func_name in sorted(self.procs):
 | 
| 411 |                     print('declare -f %s' % (func_name))
 | 
| 412 |             return status
 | 
| 413 | 
 | 
| 414 |         if arg.p:  # Lookup and print variables.
 | 
| 415 |             return _PrintVariables(self.mem, cmd_val, attrs, True)
 | 
| 416 |         elif len(cmd_val.pairs) == 0:
 | 
| 417 |             return _PrintVariables(self.mem, cmd_val, attrs, False)
 | 
| 418 | 
 | 
| 419 |         if not self.exec_opts.ignore_flags_not_impl():
 | 
| 420 |             if arg.i:
 | 
| 421 |                 e_usage(
 | 
| 422 |                     "doesn't implement flag -i (shopt --set ignore_flags_not_impl)",
 | 
| 423 |                     loc.Missing)
 | 
| 424 | 
 | 
| 425 |             if arg.l or arg.u:
 | 
| 426 |                 # Just print a warning!  The program may still run.
 | 
| 427 |                 self.errfmt.Print_(
 | 
| 428 |                     "Warning: OSH doesn't implement flags -l or -u (shopt --set ignore_flags_not_impl)",
 | 
| 429 |                     loc.Missing)
 | 
| 430 | 
 | 
| 431 |         #
 | 
| 432 |         # Set variables
 | 
| 433 |         #
 | 
| 434 | 
 | 
| 435 |         if cmd_val.builtin_id == builtin_i.local:
 | 
| 436 |             which_scopes = scope_e.LocalOnly
 | 
| 437 |         else:  # declare/typeset
 | 
| 438 |             if arg.g:
 | 
| 439 |                 which_scopes = scope_e.GlobalOnly
 | 
| 440 |             else:
 | 
| 441 |                 which_scopes = scope_e.LocalOnly
 | 
| 442 | 
 | 
| 443 |         flags = 0
 | 
| 444 |         if arg.x == '-':
 | 
| 445 |             flags |= state.SetExport
 | 
| 446 |         if arg.r == '-':
 | 
| 447 |             flags |= state.SetReadOnly
 | 
| 448 |         if arg.n == '-':
 | 
| 449 |             flags |= state.SetNameref
 | 
| 450 | 
 | 
| 451 |         if arg.x == '+':
 | 
| 452 |             flags |= state.ClearExport
 | 
| 453 |         if arg.r == '+':
 | 
| 454 |             flags |= state.ClearReadOnly
 | 
| 455 |         if arg.n == '+':
 | 
| 456 |             flags |= state.ClearNameref
 | 
| 457 | 
 | 
| 458 |         for pair in cmd_val.pairs:
 | 
| 459 |             rval = pair.rval
 | 
| 460 |             # declare -a foo=(a b); declare -a foo;  should not reset to empty array
 | 
| 461 |             if rval is None and (arg.a or arg.A):
 | 
| 462 |                 old_val = self.mem.GetValue(pair.var_name)
 | 
| 463 |                 if arg.a:
 | 
| 464 |                     if old_val.tag() != value_e.BashArray:
 | 
| 465 |                         rval = value.BashArray([])
 | 
| 466 |                 elif arg.A:
 | 
| 467 |                     if old_val.tag() != value_e.BashAssoc:
 | 
| 468 |                         rval = value.BashAssoc({})
 | 
| 469 | 
 | 
| 470 |             lval = LeftName(pair.var_name, pair.blame_word)
 | 
| 471 | 
 | 
| 472 |             if pair.plus_eq:
 | 
| 473 |                 old_val = sh_expr_eval.OldValue(lval, self.mem,
 | 
| 474 |                                                 None)  # ignore set -u
 | 
| 475 |                 # When 'typeset e+=', then rval is value.Str('')
 | 
| 476 |                 # When 'typeset foo', the pair.plus_eq flag is false.
 | 
| 477 |                 assert pair.rval is not None
 | 
| 478 |                 rval = cmd_eval.PlusEquals(old_val, pair.rval)
 | 
| 479 |             else:
 | 
| 480 |                 rval = _ReconcileTypes(rval, arg.a, arg.A, pair.blame_word)
 | 
| 481 | 
 | 
| 482 |             self.mem.SetNamed(lval, rval, which_scopes, flags=flags)
 | 
| 483 | 
 | 
| 484 |         return status
 | 
| 485 | 
 | 
| 486 | 
 | 
| 487 | # TODO:
 | 
| 488 | # - It would make more sense to treat no args as an error (bash doesn't.)
 | 
| 489 | #   - Should we have strict builtins?  Or just make it stricter?
 | 
| 490 | # - Typed args: unset (mylist[0]) is like Python's del
 | 
| 491 | #   - It has the same word as 'setvar', which makes sense
 | 
| 492 | 
 | 
| 493 | 
 | 
| 494 | class Unset(vm._Builtin):
 | 
| 495 | 
 | 
| 496 |     def __init__(
 | 
| 497 |             self,
 | 
| 498 |             mem,  # type: state.Mem
 | 
| 499 |             procs,  # type: Dict[str, value.Proc]
 | 
| 500 |             unsafe_arith,  # type: sh_expr_eval.UnsafeArith
 | 
| 501 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 502 |     ):
 | 
| 503 |         # type: (...) -> None
 | 
| 504 |         self.mem = mem
 | 
| 505 |         self.procs = procs
 | 
| 506 |         self.unsafe_arith = unsafe_arith
 | 
| 507 |         self.errfmt = errfmt
 | 
| 508 | 
 | 
| 509 |     def _UnsetVar(self, arg, location, proc_fallback):
 | 
| 510 |         # type: (str, loc_t, bool) -> bool
 | 
| 511 |         """
 | 
| 512 |         Returns:
 | 
| 513 |           bool: whether the 'unset' builtin should succeed with code 0.
 | 
| 514 |         """
 | 
| 515 |         lval = self.unsafe_arith.ParseLValue(arg, location)
 | 
| 516 | 
 | 
| 517 |         #log('unsafe lval %s', lval)
 | 
| 518 |         found = False
 | 
| 519 |         try:
 | 
| 520 |             found = self.mem.Unset(lval, scope_e.Shopt)
 | 
| 521 |         except error.Runtime as e:
 | 
| 522 |             # note: in bash, myreadonly=X fails, but declare myreadonly=X doesn't
 | 
| 523 |             # fail because it's a builtin.  So I guess the same is true of 'unset'.
 | 
| 524 |             msg = e.UserErrorString()
 | 
| 525 |             self.errfmt.Print_(msg, blame_loc=location)
 | 
| 526 |             return False
 | 
| 527 | 
 | 
| 528 |         if proc_fallback and not found:
 | 
| 529 |             mylib.dict_erase(self.procs, arg)
 | 
| 530 | 
 | 
| 531 |         return True
 | 
| 532 | 
 | 
| 533 |     def Run(self, cmd_val):
 | 
| 534 |         # type: (cmd_value.Argv) -> int
 | 
| 535 |         attrs, arg_r = flag_util.ParseCmdVal('unset', cmd_val)
 | 
| 536 |         arg = arg_types.unset(attrs.attrs)
 | 
| 537 | 
 | 
| 538 |         argv, arg_locs = arg_r.Rest2()
 | 
| 539 |         for i, name in enumerate(argv):
 | 
| 540 |             location = arg_locs[i]
 | 
| 541 | 
 | 
| 542 |             if arg.f:
 | 
| 543 |                 mylib.dict_erase(self.procs, name)
 | 
| 544 | 
 | 
| 545 |             elif arg.v:
 | 
| 546 |                 if not self._UnsetVar(name, location, False):
 | 
| 547 |                     return 1
 | 
| 548 | 
 | 
| 549 |             else:
 | 
| 550 |                 # proc_fallback: Try to delete var first, then func.
 | 
| 551 |                 if not self._UnsetVar(name, location, True):
 | 
| 552 |                     return 1
 | 
| 553 | 
 | 
| 554 |         return 0
 | 
| 555 | 
 | 
| 556 | 
 | 
| 557 | class Shift(vm._Builtin):
 | 
| 558 | 
 | 
| 559 |     def __init__(self, mem):
 | 
| 560 |         # type: (Mem) -> None
 | 
| 561 |         self.mem = mem
 | 
| 562 | 
 | 
| 563 |     def Run(self, cmd_val):
 | 
| 564 |         # type: (cmd_value.Argv) -> int
 | 
| 565 |         num_args = len(cmd_val.argv) - 1
 | 
| 566 |         if num_args == 0:
 | 
| 567 |             n = 1
 | 
| 568 |         elif num_args == 1:
 | 
| 569 |             arg = cmd_val.argv[1]
 | 
| 570 |             try:
 | 
| 571 |                 n = int(arg)
 | 
| 572 |             except ValueError:
 | 
| 573 |                 e_usage("Invalid shift argument %r" % arg, loc.Missing)
 | 
| 574 |         else:
 | 
| 575 |             e_usage('got too many arguments', loc.Missing)
 | 
| 576 | 
 | 
| 577 |         return self.mem.Shift(n)
 |