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