| 1 | #!/usr/bin/env python2
 | 
| 2 | # Copyright 2016 Andy Chu. All rights reserved.
 | 
| 3 | # Licensed under the Apache License, Version 2.0 (the "License");
 | 
| 4 | # you may not use this file except in compliance with the License.
 | 
| 5 | # You may obtain a copy of the License at
 | 
| 6 | #
 | 
| 7 | #   http://www.apache.org/licenses/LICENSE-2.0
 | 
| 8 | """
 | 
| 9 | sh_expr_eval.py -- Shell boolean and arithmetic expressions.
 | 
| 10 | """
 | 
| 11 | from __future__ import print_function
 | 
| 12 | 
 | 
| 13 | from _devbuild.gen.id_kind_asdl import Id
 | 
| 14 | from _devbuild.gen.runtime_asdl import scope_t
 | 
| 15 | from _devbuild.gen.syntax_asdl import (
 | 
| 16 |     word_t,
 | 
| 17 |     CompoundWord,
 | 
| 18 |     Token,
 | 
| 19 |     loc,
 | 
| 20 |     loc_t,
 | 
| 21 |     source,
 | 
| 22 |     arith_expr,
 | 
| 23 |     arith_expr_e,
 | 
| 24 |     arith_expr_t,
 | 
| 25 |     bool_expr,
 | 
| 26 |     bool_expr_e,
 | 
| 27 |     bool_expr_t,
 | 
| 28 |     sh_lhs,
 | 
| 29 |     sh_lhs_e,
 | 
| 30 |     sh_lhs_t,
 | 
| 31 |     BracedVarSub,
 | 
| 32 | )
 | 
| 33 | from _devbuild.gen.option_asdl import option_i
 | 
| 34 | from _devbuild.gen.types_asdl import bool_arg_type_e
 | 
| 35 | from _devbuild.gen.value_asdl import (
 | 
| 36 |     value,
 | 
| 37 |     value_e,
 | 
| 38 |     value_t,
 | 
| 39 |     sh_lvalue,
 | 
| 40 |     sh_lvalue_e,
 | 
| 41 |     sh_lvalue_t,
 | 
| 42 |     LeftName,
 | 
| 43 |     eggex_ops,
 | 
| 44 |     regex_match,
 | 
| 45 |     RegexMatch,
 | 
| 46 | )
 | 
| 47 | from core import alloc
 | 
| 48 | from core import error
 | 
| 49 | from core.error import e_die, e_die_status, e_strict, e_usage
 | 
| 50 | from core import num
 | 
| 51 | from core import state
 | 
| 52 | from core import ui
 | 
| 53 | from core import util
 | 
| 54 | from frontend import consts
 | 
| 55 | from frontend import lexer
 | 
| 56 | from frontend import location
 | 
| 57 | from frontend import match
 | 
| 58 | from frontend import parse_lib
 | 
| 59 | from frontend import reader
 | 
| 60 | from mycpp import mops
 | 
| 61 | from mycpp import mylib
 | 
| 62 | from mycpp.mylib import log, tagswitch, switch, str_cmp
 | 
| 63 | from osh import bool_stat
 | 
| 64 | from osh import word_eval
 | 
| 65 | 
 | 
| 66 | import libc  # for fnmatch
 | 
| 67 | # Import these names directly because the C++ translation uses macros literally.
 | 
| 68 | from libc import FNM_CASEFOLD, REG_ICASE
 | 
| 69 | 
 | 
| 70 | from typing import Tuple, Optional, cast, TYPE_CHECKING
 | 
| 71 | if TYPE_CHECKING:
 | 
| 72 |     from core.ui import ErrorFormatter
 | 
| 73 |     from core import optview
 | 
| 74 | 
 | 
| 75 | _ = log
 | 
| 76 | 
 | 
| 77 | #
 | 
| 78 | # Arith and Command/Word variants of assignment
 | 
| 79 | #
 | 
| 80 | # Calls EvalShellLhs()
 | 
| 81 | #   a[$key]=$val             # osh/cmd_eval.py:814  (command_e.ShAssignment)
 | 
| 82 | # Calls EvalArithLhs()
 | 
| 83 | #   (( a[key] = val ))       # osh/sh_expr_eval.py:326 (_EvalLhsArith)
 | 
| 84 | #
 | 
| 85 | # Calls OldValue()
 | 
| 86 | #   a[$key]+=$val            # osh/cmd_eval.py:795     (assign_op_e.PlusEqual)
 | 
| 87 | #   (( a[key] += val ))      # osh/sh_expr_eval.py:308 (_EvalLhsAndLookupArith)
 | 
| 88 | #
 | 
| 89 | # RHS Indexing
 | 
| 90 | #   val=${a[$key]}           # osh/word_eval.py:639 (bracket_op_e.ArrayIndex)
 | 
| 91 | #   (( val = a[key] ))       # osh/sh_expr_eval.py:509 (Id.Arith_LBracket)
 | 
| 92 | #
 | 
| 93 | 
 | 
| 94 | 
 | 
| 95 | def OldValue(lval, mem, exec_opts):
 | 
| 96 |     # type: (sh_lvalue_t, state.Mem, Optional[optview.Exec]) -> value_t
 | 
| 97 |     """Look up for augmented assignment.
 | 
| 98 | 
 | 
| 99 |     For s+=val and (( i += 1 ))
 | 
| 100 | 
 | 
| 101 |     Args:
 | 
| 102 |       lval: value we need to
 | 
| 103 |       exec_opts: can be None if we don't want to check set -u!
 | 
| 104 |         Because s+=val doesn't check it.
 | 
| 105 | 
 | 
| 106 |     TODO: A stricter and less ambiguous version for YSH.
 | 
| 107 |     - Problem: why does sh_lvalue have Indexed and Keyed, while sh_lhs only has
 | 
| 108 |       IndexedName?
 | 
| 109 |       - should I have location.LName and sh_lvalue.Indexed only?
 | 
| 110 |       - and Indexed uses the index_t type?
 | 
| 111 |         - well that might be Str or Int
 | 
| 112 |     """
 | 
| 113 |     assert isinstance(lval, sh_lvalue_t), lval
 | 
| 114 | 
 | 
| 115 |     # TODO: refactor sh_lvalue_t to make this simpler
 | 
| 116 |     UP_lval = lval
 | 
| 117 |     with tagswitch(lval) as case:
 | 
| 118 |         if case(sh_lvalue_e.Var):  # (( i++ ))
 | 
| 119 |             lval = cast(LeftName, UP_lval)
 | 
| 120 |             var_name = lval.name
 | 
| 121 |         elif case(sh_lvalue_e.Indexed):  # (( a[i]++ ))
 | 
| 122 |             lval = cast(sh_lvalue.Indexed, UP_lval)
 | 
| 123 |             var_name = lval.name
 | 
| 124 |         elif case(sh_lvalue_e.Keyed):  # (( A['K']++ )) ?  I think this works
 | 
| 125 |             lval = cast(sh_lvalue.Keyed, UP_lval)
 | 
| 126 |             var_name = lval.name
 | 
| 127 |         else:
 | 
| 128 |             raise AssertionError()
 | 
| 129 | 
 | 
| 130 |     val = mem.GetValue(var_name)
 | 
| 131 |     if exec_opts and exec_opts.nounset() and val.tag() == value_e.Undef:
 | 
| 132 |         e_die('Undefined variable %r' % var_name)  # TODO: location info
 | 
| 133 | 
 | 
| 134 |     UP_val = val
 | 
| 135 |     with tagswitch(lval) as case:
 | 
| 136 |         if case(sh_lvalue_e.Var):
 | 
| 137 |             return val
 | 
| 138 | 
 | 
| 139 |         elif case(sh_lvalue_e.Indexed):
 | 
| 140 |             lval = cast(sh_lvalue.Indexed, UP_lval)
 | 
| 141 | 
 | 
| 142 |             array_val = None  # type: value.BashArray
 | 
| 143 |             with tagswitch(val) as case2:
 | 
| 144 |                 if case2(value_e.Undef):
 | 
| 145 |                     array_val = value.BashArray([])
 | 
| 146 |                 elif case2(value_e.BashArray):
 | 
| 147 |                     tmp = cast(value.BashArray, UP_val)
 | 
| 148 |                     # mycpp rewrite: add tmp.  cast() creates a new var in inner scope
 | 
| 149 |                     array_val = tmp
 | 
| 150 |                 else:
 | 
| 151 |                     e_die("Can't use [] on value of type %s" % ui.ValType(val))
 | 
| 152 | 
 | 
| 153 |             s = word_eval.GetArrayItem(array_val.strs, lval.index)
 | 
| 154 | 
 | 
| 155 |             if s is None:
 | 
| 156 |                 val = value.Str('')  # NOTE: Other logic is value.Undef?  0?
 | 
| 157 |             else:
 | 
| 158 |                 assert isinstance(s, str), s
 | 
| 159 |                 val = value.Str(s)
 | 
| 160 | 
 | 
| 161 |         elif case(sh_lvalue_e.Keyed):
 | 
| 162 |             lval = cast(sh_lvalue.Keyed, UP_lval)
 | 
| 163 | 
 | 
| 164 |             assoc_val = None  # type: value.BashAssoc
 | 
| 165 |             with tagswitch(val) as case2:
 | 
| 166 |                 if case2(value_e.Undef):
 | 
| 167 |                     # This never happens, because undef[x]+= is assumed to
 | 
| 168 |                     raise AssertionError()
 | 
| 169 |                 elif case2(value_e.BashAssoc):
 | 
| 170 |                     tmp2 = cast(value.BashAssoc, UP_val)
 | 
| 171 |                     # mycpp rewrite: add tmp.  cast() creates a new var in inner scope
 | 
| 172 |                     assoc_val = tmp2
 | 
| 173 |                 else:
 | 
| 174 |                     e_die("Can't use [] on value of type %s" % ui.ValType(val))
 | 
| 175 | 
 | 
| 176 |             s = assoc_val.d.get(lval.key)
 | 
| 177 |             if s is None:
 | 
| 178 |                 val = value.Str('')
 | 
| 179 |             else:
 | 
| 180 |                 val = value.Str(s)
 | 
| 181 | 
 | 
| 182 |         else:
 | 
| 183 |             raise AssertionError()
 | 
| 184 | 
 | 
| 185 |     return val
 | 
| 186 | 
 | 
| 187 | 
 | 
| 188 | # TODO: Should refactor for int/char-based processing
 | 
| 189 | if mylib.PYTHON:
 | 
| 190 | 
 | 
| 191 |     def IsLower(ch):
 | 
| 192 |         # type: (str) -> bool
 | 
| 193 |         return 'a' <= ch and ch <= 'z'
 | 
| 194 | 
 | 
| 195 |     def IsUpper(ch):
 | 
| 196 |         # type: (str) -> bool
 | 
| 197 |         return 'A' <= ch and ch <= 'Z'
 | 
| 198 | 
 | 
| 199 | 
 | 
| 200 | class UnsafeArith(object):
 | 
| 201 |     """For parsing a[i] at RUNTIME."""
 | 
| 202 | 
 | 
| 203 |     def __init__(
 | 
| 204 |             self,
 | 
| 205 |             mem,  # type: state.Mem
 | 
| 206 |             exec_opts,  # type: optview.Exec
 | 
| 207 |             mutable_opts,  # type: state.MutableOpts
 | 
| 208 |             parse_ctx,  # type: parse_lib.ParseContext
 | 
| 209 |             arith_ev,  # type: ArithEvaluator
 | 
| 210 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 211 |     ):
 | 
| 212 |         # type: (...) -> None
 | 
| 213 |         self.mem = mem
 | 
| 214 |         self.exec_opts = exec_opts
 | 
| 215 |         self.mutable_opts = mutable_opts
 | 
| 216 |         self.parse_ctx = parse_ctx
 | 
| 217 |         self.arith_ev = arith_ev
 | 
| 218 |         self.errfmt = errfmt
 | 
| 219 | 
 | 
| 220 |         self.arena = self.parse_ctx.arena
 | 
| 221 | 
 | 
| 222 |     def ParseLValue(self, s, location):
 | 
| 223 |         # type: (str, loc_t) -> sh_lvalue_t
 | 
| 224 |         """Parse sh_lvalue for 'unset' and 'printf -v'.
 | 
| 225 | 
 | 
| 226 |         It uses the arith parser, so it behaves like the LHS of (( a[i] = x ))
 | 
| 227 |         """
 | 
| 228 |         if not self.parse_ctx.parse_opts.parse_sh_arith():
 | 
| 229 |             # Do something simpler for YSH
 | 
| 230 |             if not match.IsValidVarName(s):
 | 
| 231 |                 e_die('Invalid variable name %r (parse_sh_arith is off)' % s,
 | 
| 232 |                       location)
 | 
| 233 |             return LeftName(s, location)
 | 
| 234 | 
 | 
| 235 |         a_parser = self.parse_ctx.MakeArithParser(s)
 | 
| 236 | 
 | 
| 237 |         with alloc.ctx_SourceCode(self.arena,
 | 
| 238 |                                   source.ArgvWord('dynamic LHS', location)):
 | 
| 239 |             try:
 | 
| 240 |                 anode = a_parser.Parse()
 | 
| 241 |             except error.Parse as e:
 | 
| 242 |                 self.errfmt.PrettyPrintError(e)
 | 
| 243 |                 # Exception for builtins 'unset' and 'printf'
 | 
| 244 |                 e_usage('got invalid LHS expression', location)
 | 
| 245 | 
 | 
| 246 |         # Note: we parse '1+2', and then it becomes a runtime error because
 | 
| 247 |         # it's not a valid LHS.  Could be a parse error.
 | 
| 248 | 
 | 
| 249 |         if self.exec_opts.eval_unsafe_arith():
 | 
| 250 |             lval = self.arith_ev.EvalArithLhs(anode)
 | 
| 251 |         else:
 | 
| 252 |             # Prevent attacks like these by default:
 | 
| 253 |             #
 | 
| 254 |             # unset -v 'A["$(echo K; rm *)"]'
 | 
| 255 |             with state.ctx_Option(self.mutable_opts,
 | 
| 256 |                                   [option_i._allow_command_sub], False):
 | 
| 257 |                 lval = self.arith_ev.EvalArithLhs(anode)
 | 
| 258 | 
 | 
| 259 |         return lval
 | 
| 260 | 
 | 
| 261 |     def ParseVarRef(self, ref_str, blame_tok):
 | 
| 262 |         # type: (str, Token) -> BracedVarSub
 | 
| 263 |         """Parse and evaluate value for ${!ref}
 | 
| 264 | 
 | 
| 265 |         This supports:
 | 
| 266 |         - 0 to 9 for $0 to $9
 | 
| 267 |         - @ for "$@" etc.
 | 
| 268 | 
 | 
| 269 |         See grammar in osh/word_parse.py, which is related to grammar in
 | 
| 270 |         osh/word_parse.py _ReadBracedVarSub
 | 
| 271 | 
 | 
| 272 |         Note: declare -n allows 'varname' and 'varname[i]' and 'varname[@]', but it
 | 
| 273 |         does NOT allow 0 to 9, @, *
 | 
| 274 | 
 | 
| 275 |         NamerefExpr = NAME Subscript?   # this allows @ and * too
 | 
| 276 | 
 | 
| 277 |         _ResolveNameOrRef currently gives you a 'cell'.  So it might not support
 | 
| 278 |         sh_lvalue.Indexed?
 | 
| 279 |         """
 | 
| 280 |         line_reader = reader.StringLineReader(ref_str, self.arena)
 | 
| 281 |         lexer = self.parse_ctx.MakeLexer(line_reader)
 | 
| 282 |         w_parser = self.parse_ctx.MakeWordParser(lexer, line_reader)
 | 
| 283 | 
 | 
| 284 |         src = source.VarRef(blame_tok)
 | 
| 285 |         with alloc.ctx_SourceCode(self.arena, src):
 | 
| 286 |             try:
 | 
| 287 |                 bvs_part = w_parser.ParseVarRef()
 | 
| 288 |             except error.Parse as e:
 | 
| 289 |                 # This prints the inner location
 | 
| 290 |                 self.errfmt.PrettyPrintError(e)
 | 
| 291 | 
 | 
| 292 |                 # this affects builtins 'unset' and 'printf'
 | 
| 293 |                 e_die("Invalid var ref expression", blame_tok)
 | 
| 294 | 
 | 
| 295 |         return bvs_part
 | 
| 296 | 
 | 
| 297 | 
 | 
| 298 | class ArithEvaluator(object):
 | 
| 299 |     """Shared between arith and bool evaluators.
 | 
| 300 | 
 | 
| 301 |     They both:
 | 
| 302 | 
 | 
| 303 |     1. Convert strings to integers, respecting shopt -s strict_arith.
 | 
| 304 |     2. Look up variables and evaluate words.
 | 
| 305 |     """
 | 
| 306 | 
 | 
| 307 |     def __init__(
 | 
| 308 |             self,
 | 
| 309 |             mem,  # type: state.Mem
 | 
| 310 |             exec_opts,  # type: optview.Exec
 | 
| 311 |             mutable_opts,  # type: state.MutableOpts
 | 
| 312 |             parse_ctx,  # type: Optional[parse_lib.ParseContext]
 | 
| 313 |             errfmt,  # type: ErrorFormatter
 | 
| 314 |     ):
 | 
| 315 |         # type: (...) -> None
 | 
| 316 |         self.word_ev = None  # type: word_eval.StringWordEvaluator
 | 
| 317 |         self.mem = mem
 | 
| 318 |         self.exec_opts = exec_opts
 | 
| 319 |         self.mutable_opts = mutable_opts
 | 
| 320 |         self.parse_ctx = parse_ctx
 | 
| 321 |         self.errfmt = errfmt
 | 
| 322 | 
 | 
| 323 |     def CheckCircularDeps(self):
 | 
| 324 |         # type: () -> None
 | 
| 325 |         assert self.word_ev is not None
 | 
| 326 | 
 | 
| 327 |     def _StringToBigInt(self, s, blame_loc):
 | 
| 328 |         # type: (str, loc_t) -> mops.BigInt
 | 
| 329 |         """Use bash-like rules to coerce a string to an integer.
 | 
| 330 | 
 | 
| 331 |         Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13
 | 
| 332 | 
 | 
| 333 |         0xAB -- hex constant
 | 
| 334 |         042  -- octal constant
 | 
| 335 |         42   -- decimal constant
 | 
| 336 |         64#z -- arbitrary base constant
 | 
| 337 | 
 | 
| 338 |         bare word: variable
 | 
| 339 |         quoted word: string (not done?)
 | 
| 340 |         """
 | 
| 341 |         if s.startswith('0x'):
 | 
| 342 |             try:
 | 
| 343 |                 integer = mops.FromStr(s, 16)
 | 
| 344 |             except ValueError:
 | 
| 345 |                 e_strict('Invalid hex constant %r' % s, blame_loc)
 | 
| 346 |             # TODO: don't truncate
 | 
| 347 |             return integer
 | 
| 348 | 
 | 
| 349 |         if s.startswith('0'):
 | 
| 350 |             try:
 | 
| 351 |                 integer = mops.FromStr(s, 8)
 | 
| 352 |             except ValueError:
 | 
| 353 |                 e_strict('Invalid octal constant %r' % s, blame_loc)
 | 
| 354 |             return integer
 | 
| 355 | 
 | 
| 356 |         b, digits = mylib.split_once(s, '#')  # see if it has #
 | 
| 357 |         if digits is not None:
 | 
| 358 |             try:
 | 
| 359 |                 base = int(b)  # machine integer, not BigInt
 | 
| 360 |             except ValueError:
 | 
| 361 |                 e_strict('Invalid base for numeric constant %r' % b, blame_loc)
 | 
| 362 | 
 | 
| 363 |             integer = mops.ZERO
 | 
| 364 |             for ch in digits:
 | 
| 365 |                 if IsLower(ch):
 | 
| 366 |                     digit = ord(ch) - ord('a') + 10
 | 
| 367 |                 elif IsUpper(ch):
 | 
| 368 |                     digit = ord(ch) - ord('A') + 36
 | 
| 369 |                 elif ch == '@':  # horrible syntax
 | 
| 370 |                     digit = 62
 | 
| 371 |                 elif ch == '_':
 | 
| 372 |                     digit = 63
 | 
| 373 |                 elif ch.isdigit():
 | 
| 374 |                     digit = int(ch)
 | 
| 375 |                 else:
 | 
| 376 |                     e_strict('Invalid digits for numeric constant %r' % digits,
 | 
| 377 |                              blame_loc)
 | 
| 378 | 
 | 
| 379 |                 if digit >= base:
 | 
| 380 |                     e_strict(
 | 
| 381 |                         'Digits %r out of range for base %d' % (digits, base),
 | 
| 382 |                         blame_loc)
 | 
| 383 | 
 | 
| 384 |                 #integer = integer * base + digit
 | 
| 385 |                 integer = mops.Add(mops.Mul(integer, mops.BigInt(base)),
 | 
| 386 |                                    mops.BigInt(digit))
 | 
| 387 |             return integer
 | 
| 388 | 
 | 
| 389 |         try:
 | 
| 390 |             # Normal base 10 integer.  This includes negative numbers like '-42'.
 | 
| 391 |             integer = mops.FromStr(s)
 | 
| 392 |         except ValueError:
 | 
| 393 |             # doesn't look like an integer
 | 
| 394 | 
 | 
| 395 |             # note: 'test' and '[' never evaluate recursively
 | 
| 396 |             if self.parse_ctx:
 | 
| 397 |                 arena = self.parse_ctx.arena
 | 
| 398 | 
 | 
| 399 |                 # Special case so we don't get EOF error
 | 
| 400 |                 if len(s.strip()) == 0:
 | 
| 401 |                     return mops.ZERO
 | 
| 402 | 
 | 
| 403 |                 # For compatibility: Try to parse it as an expression and evaluate it.
 | 
| 404 |                 a_parser = self.parse_ctx.MakeArithParser(s)
 | 
| 405 | 
 | 
| 406 |                 # TODO: Fill in the variable name
 | 
| 407 |                 with alloc.ctx_SourceCode(arena,
 | 
| 408 |                                           source.Variable(None, blame_loc)):
 | 
| 409 |                     try:
 | 
| 410 |                         node2 = a_parser.Parse()  # may raise error.Parse
 | 
| 411 |                     except error.Parse as e:
 | 
| 412 |                         self.errfmt.PrettyPrintError(e)
 | 
| 413 |                         e_die('Parse error in recursive arithmetic',
 | 
| 414 |                               e.location)
 | 
| 415 | 
 | 
| 416 |                 # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
 | 
| 417 |                 # to itself, and you don't want to reparse it as a word.
 | 
| 418 |                 if node2.tag() == arith_expr_e.Word:
 | 
| 419 |                     e_die("Invalid integer constant %r" % s, blame_loc)
 | 
| 420 | 
 | 
| 421 |                 if self.exec_opts.eval_unsafe_arith():
 | 
| 422 |                     integer = self.EvalToBigInt(node2)
 | 
| 423 |                 else:
 | 
| 424 |                     # BoolEvaluator doesn't have parse_ctx or mutable_opts
 | 
| 425 |                     assert self.mutable_opts is not None
 | 
| 426 | 
 | 
| 427 |                     # We don't need to flip _allow_process_sub, because they can't be
 | 
| 428 |                     # parsed.  See spec/bugs.test.sh.
 | 
| 429 |                     with state.ctx_Option(self.mutable_opts,
 | 
| 430 |                                           [option_i._allow_command_sub],
 | 
| 431 |                                           False):
 | 
| 432 |                         integer = self.EvalToBigInt(node2)
 | 
| 433 | 
 | 
| 434 |             else:
 | 
| 435 |                 if len(s.strip()) == 0 or match.IsValidVarName(s):
 | 
| 436 |                     # x42 could evaluate to 0
 | 
| 437 |                     e_strict("Invalid integer constant %r" % s, blame_loc)
 | 
| 438 |                 else:
 | 
| 439 |                     # 42x is always fatal!
 | 
| 440 |                     e_die("Invalid integer constant %r" % s, blame_loc)
 | 
| 441 | 
 | 
| 442 |         return integer
 | 
| 443 | 
 | 
| 444 |     def _ValToIntOrError(self, val, blame):
 | 
| 445 |         # type: (value_t, arith_expr_t) -> mops.BigInt
 | 
| 446 |         try:
 | 
| 447 |             UP_val = val
 | 
| 448 |             with tagswitch(val) as case:
 | 
| 449 |                 if case(value_e.Undef):
 | 
| 450 |                     # 'nounset' already handled before got here
 | 
| 451 |                     # Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
 | 
| 452 |                     e_strict('Undefined value in arithmetic context',
 | 
| 453 |                              loc.Arith(blame))
 | 
| 454 | 
 | 
| 455 |                 elif case(value_e.Int):
 | 
| 456 |                     val = cast(value.Int, UP_val)
 | 
| 457 |                     return val.i
 | 
| 458 | 
 | 
| 459 |                 elif case(value_e.Str):
 | 
| 460 |                     val = cast(value.Str, UP_val)
 | 
| 461 |                     # calls e_strict
 | 
| 462 |                     return self._StringToBigInt(val.s, loc.Arith(blame))
 | 
| 463 | 
 | 
| 464 |         except error.Strict as e:
 | 
| 465 |             if self.exec_opts.strict_arith():
 | 
| 466 |                 raise
 | 
| 467 |             else:
 | 
| 468 |                 return mops.ZERO
 | 
| 469 | 
 | 
| 470 |         # Arrays and associative arrays always fail -- not controlled by
 | 
| 471 |         # strict_arith.
 | 
| 472 |         # In bash, (( a )) is like (( a[0] )), but I don't want that.
 | 
| 473 |         # And returning '0' gives different results.
 | 
| 474 |         e_die(
 | 
| 475 |             "Expected a value convertible to integer, got %s" %
 | 
| 476 |             ui.ValType(val), loc.Arith(blame))
 | 
| 477 | 
 | 
| 478 |     def _EvalLhsAndLookupArith(self, node):
 | 
| 479 |         # type: (arith_expr_t) -> Tuple[mops.BigInt, sh_lvalue_t]
 | 
| 480 |         """ For x = y  and   x += y  and  ++x """
 | 
| 481 | 
 | 
| 482 |         lval = self.EvalArithLhs(node)
 | 
| 483 |         val = OldValue(lval, self.mem, self.exec_opts)
 | 
| 484 | 
 | 
| 485 |         # BASH_LINENO, arr (array name without strict_array), etc.
 | 
| 486 |         if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
 | 
| 487 |                 lval.tag() == sh_lvalue_e.Var):
 | 
| 488 |             named_lval = cast(LeftName, lval)
 | 
| 489 |             if word_eval.ShouldArrayDecay(named_lval.name, self.exec_opts):
 | 
| 490 |                 if val.tag() == value_e.BashArray:
 | 
| 491 |                     lval = sh_lvalue.Indexed(named_lval.name, 0, loc.Missing)
 | 
| 492 |                 elif val.tag() == value_e.BashAssoc:
 | 
| 493 |                     lval = sh_lvalue.Keyed(named_lval.name, '0', loc.Missing)
 | 
| 494 |                 val = word_eval.DecayArray(val)
 | 
| 495 | 
 | 
| 496 |         # This error message could be better, but we already have one
 | 
| 497 |         #if val.tag() == value_e.BashArray:
 | 
| 498 |         #  e_die("Can't use assignment like ++ or += on arrays")
 | 
| 499 | 
 | 
| 500 |         i = self._ValToIntOrError(val, node)
 | 
| 501 |         return i, lval
 | 
| 502 | 
 | 
| 503 |     def _Store(self, lval, new_int):
 | 
| 504 |         # type: (sh_lvalue_t, mops.BigInt) -> None
 | 
| 505 |         val = value.Str(mops.ToStr(new_int))
 | 
| 506 |         state.OshLanguageSetValue(self.mem, lval, val)
 | 
| 507 | 
 | 
| 508 |     def EvalToBigInt(self, node):
 | 
| 509 |         # type: (arith_expr_t) -> mops.BigInt
 | 
| 510 |         """Used externally by ${a[i+1]} and ${a:start:len}.
 | 
| 511 | 
 | 
| 512 |         Also used internally.
 | 
| 513 |         """
 | 
| 514 |         val = self.Eval(node)
 | 
| 515 | 
 | 
| 516 |         # BASH_LINENO, arr (array name without strict_array), etc.
 | 
| 517 |         if (val.tag() in (value_e.BashArray, value_e.BashAssoc) and
 | 
| 518 |                 node.tag() == arith_expr_e.VarSub):
 | 
| 519 |             vsub = cast(Token, node)
 | 
| 520 |             if word_eval.ShouldArrayDecay(lexer.LazyStr(vsub), self.exec_opts):
 | 
| 521 |                 val = word_eval.DecayArray(val)
 | 
| 522 | 
 | 
| 523 |         i = self._ValToIntOrError(val, node)
 | 
| 524 |         return i
 | 
| 525 | 
 | 
| 526 |     def EvalToInt(self, node):
 | 
| 527 |         # type: (arith_expr_t) -> int
 | 
| 528 |         return mops.BigTruncate(self.EvalToBigInt(node))
 | 
| 529 | 
 | 
| 530 |     def Eval(self, node):
 | 
| 531 |         # type: (arith_expr_t) -> value_t
 | 
| 532 |         """
 | 
| 533 |         Returns:
 | 
| 534 |           None for Undef  (e.g. empty cell)  TODO: Don't return 0!
 | 
| 535 |           int for Str
 | 
| 536 |           List[int] for BashArray
 | 
| 537 |           Dict[str, str] for BashAssoc (TODO: Should we support this?)
 | 
| 538 | 
 | 
| 539 |         NOTE: (( A['x'] = 'x' )) and (( x = A['x'] )) are syntactically valid in
 | 
| 540 |         bash, but don't do what you'd think.  'x' sometimes a variable name and
 | 
| 541 |         sometimes a key.
 | 
| 542 |         """
 | 
| 543 |         # OSH semantics: Variable NAMES cannot be formed dynamically; but INTEGERS
 | 
| 544 |         # can.  ${foo:-3}4 is OK.  $? will be a compound word too, so we don't have
 | 
| 545 |         # to handle that as a special case.
 | 
| 546 | 
 | 
| 547 |         UP_node = node
 | 
| 548 |         with tagswitch(node) as case:
 | 
| 549 |             if case(arith_expr_e.EmptyZero):  # $(( ))
 | 
| 550 |                 return value.Int(mops.ZERO)  # Weird axiom
 | 
| 551 | 
 | 
| 552 |             elif case(arith_expr_e.EmptyOne):  # for (( ; ; ))
 | 
| 553 |                 return value.Int(mops.ONE)
 | 
| 554 | 
 | 
| 555 |             elif case(arith_expr_e.VarSub):  # $(( x ))  (can be array)
 | 
| 556 |                 vsub = cast(Token, UP_node)
 | 
| 557 |                 var_name = lexer.LazyStr(vsub)
 | 
| 558 |                 val = self.mem.GetValue(var_name)
 | 
| 559 |                 if val.tag() == value_e.Undef and self.exec_opts.nounset():
 | 
| 560 |                     e_die('Undefined variable %r' % var_name, vsub)
 | 
| 561 |                 return val
 | 
| 562 | 
 | 
| 563 |             elif case(arith_expr_e.Word):  # $(( $x )) $(( ${x}${y} )), etc.
 | 
| 564 |                 w = cast(CompoundWord, UP_node)
 | 
| 565 |                 return self.word_ev.EvalWordToString(w)
 | 
| 566 | 
 | 
| 567 |             elif case(arith_expr_e.UnaryAssign):  # a++
 | 
| 568 |                 node = cast(arith_expr.UnaryAssign, UP_node)
 | 
| 569 | 
 | 
| 570 |                 op_id = node.op_id
 | 
| 571 |                 old_big, lval = self._EvalLhsAndLookupArith(node.child)
 | 
| 572 | 
 | 
| 573 |                 if op_id == Id.Node_PostDPlus:  # post-increment
 | 
| 574 |                     new_big = mops.Add(old_big, mops.ONE)
 | 
| 575 |                     result = old_big
 | 
| 576 | 
 | 
| 577 |                 elif op_id == Id.Node_PostDMinus:  # post-decrement
 | 
| 578 |                     new_big = mops.Sub(old_big, mops.ONE)
 | 
| 579 |                     result = old_big
 | 
| 580 | 
 | 
| 581 |                 elif op_id == Id.Arith_DPlus:  # pre-increment
 | 
| 582 |                     new_big = mops.Add(old_big, mops.ONE)
 | 
| 583 |                     result = new_big
 | 
| 584 | 
 | 
| 585 |                 elif op_id == Id.Arith_DMinus:  # pre-decrement
 | 
| 586 |                     new_big = mops.Sub(old_big, mops.ONE)
 | 
| 587 |                     result = new_big
 | 
| 588 | 
 | 
| 589 |                 else:
 | 
| 590 |                     raise AssertionError(op_id)
 | 
| 591 | 
 | 
| 592 |                 self._Store(lval, new_big)
 | 
| 593 |                 return value.Int(result)
 | 
| 594 | 
 | 
| 595 |             elif case(arith_expr_e.BinaryAssign):  # a=1, a+=5, a[1]+=5
 | 
| 596 |                 node = cast(arith_expr.BinaryAssign, UP_node)
 | 
| 597 |                 op_id = node.op_id
 | 
| 598 | 
 | 
| 599 |                 if op_id == Id.Arith_Equal:
 | 
| 600 |                     # Don't really need a span ID here, because tdop.CheckLhsExpr should
 | 
| 601 |                     # have done all the validation.
 | 
| 602 |                     lval = self.EvalArithLhs(node.left)
 | 
| 603 |                     rhs_big = self.EvalToBigInt(node.right)
 | 
| 604 | 
 | 
| 605 |                     self._Store(lval, rhs_big)
 | 
| 606 |                     return value.Int(rhs_big)
 | 
| 607 | 
 | 
| 608 |                 old_big, lval = self._EvalLhsAndLookupArith(node.left)
 | 
| 609 |                 rhs_big = self.EvalToBigInt(node.right)
 | 
| 610 | 
 | 
| 611 |                 if op_id == Id.Arith_PlusEqual:
 | 
| 612 |                     new_big = mops.Add(old_big, rhs_big)
 | 
| 613 |                 elif op_id == Id.Arith_MinusEqual:
 | 
| 614 |                     new_big = mops.Sub(old_big, rhs_big)
 | 
| 615 |                 elif op_id == Id.Arith_StarEqual:
 | 
| 616 |                     new_big = mops.Mul(old_big, rhs_big)
 | 
| 617 | 
 | 
| 618 |                 elif op_id == Id.Arith_SlashEqual:
 | 
| 619 |                     if mops.Equal(rhs_big, mops.ZERO):
 | 
| 620 |                         e_die('Divide by zero')  # TODO: location
 | 
| 621 |                     new_big = num.IntDivide(old_big, rhs_big)
 | 
| 622 | 
 | 
| 623 |                 elif op_id == Id.Arith_PercentEqual:
 | 
| 624 |                     if mops.Equal(rhs_big, mops.ZERO):
 | 
| 625 |                         e_die('Divide by zero')  # TODO: location
 | 
| 626 |                     new_big = num.IntRemainder(old_big, rhs_big)
 | 
| 627 | 
 | 
| 628 |                 elif op_id == Id.Arith_DGreatEqual:
 | 
| 629 |                     new_big = mops.RShift(old_big, rhs_big)
 | 
| 630 |                 elif op_id == Id.Arith_DLessEqual:
 | 
| 631 |                     new_big = mops.LShift(old_big, rhs_big)
 | 
| 632 |                 elif op_id == Id.Arith_AmpEqual:
 | 
| 633 |                     new_big = mops.BitAnd(old_big, rhs_big)
 | 
| 634 |                 elif op_id == Id.Arith_PipeEqual:
 | 
| 635 |                     new_big = mops.BitOr(old_big, rhs_big)
 | 
| 636 |                 elif op_id == Id.Arith_CaretEqual:
 | 
| 637 |                     new_big = mops.BitXor(old_big, rhs_big)
 | 
| 638 |                 else:
 | 
| 639 |                     raise AssertionError(op_id)  # shouldn't get here
 | 
| 640 | 
 | 
| 641 |                 self._Store(lval, new_big)
 | 
| 642 |                 return value.Int(new_big)
 | 
| 643 | 
 | 
| 644 |             elif case(arith_expr_e.Unary):
 | 
| 645 |                 node = cast(arith_expr.Unary, UP_node)
 | 
| 646 |                 op_id = node.op_id
 | 
| 647 | 
 | 
| 648 |                 i = self.EvalToBigInt(node.child)
 | 
| 649 | 
 | 
| 650 |                 if op_id == Id.Node_UnaryPlus:  # +i
 | 
| 651 |                     result = i
 | 
| 652 |                 elif op_id == Id.Node_UnaryMinus:  # -i
 | 
| 653 |                     result = mops.Sub(mops.ZERO, i)
 | 
| 654 | 
 | 
| 655 |                 elif op_id == Id.Arith_Bang:  # logical negation
 | 
| 656 |                     if mops.Equal(i, mops.ZERO):
 | 
| 657 |                         result = mops.ONE
 | 
| 658 |                     else:
 | 
| 659 |                         result = mops.ZERO
 | 
| 660 |                 elif op_id == Id.Arith_Tilde:  # bitwise complement
 | 
| 661 |                     result = mops.BitNot(i)
 | 
| 662 |                 else:
 | 
| 663 |                     raise AssertionError(op_id)  # shouldn't get here
 | 
| 664 | 
 | 
| 665 |                 return value.Int(result)
 | 
| 666 | 
 | 
| 667 |             elif case(arith_expr_e.Binary):
 | 
| 668 |                 node = cast(arith_expr.Binary, UP_node)
 | 
| 669 |                 op_id = node.op.id
 | 
| 670 | 
 | 
| 671 |                 # Short-circuit evaluation for || and &&.
 | 
| 672 |                 if op_id == Id.Arith_DPipe:
 | 
| 673 |                     lhs_big = self.EvalToBigInt(node.left)
 | 
| 674 |                     if mops.Equal(lhs_big, mops.ZERO):
 | 
| 675 |                         rhs_big = self.EvalToBigInt(node.right)
 | 
| 676 |                         if mops.Equal(rhs_big, mops.ZERO):
 | 
| 677 |                             result = mops.ZERO  # false
 | 
| 678 |                         else:
 | 
| 679 |                             result = mops.ONE  # true
 | 
| 680 |                     else:
 | 
| 681 |                         result = mops.ONE  # true
 | 
| 682 |                     return value.Int(result)
 | 
| 683 | 
 | 
| 684 |                 if op_id == Id.Arith_DAmp:
 | 
| 685 |                     lhs_big = self.EvalToBigInt(node.left)
 | 
| 686 |                     if mops.Equal(lhs_big, mops.ZERO):
 | 
| 687 |                         result = mops.ZERO  # false
 | 
| 688 |                     else:
 | 
| 689 |                         rhs_big = self.EvalToBigInt(node.right)
 | 
| 690 |                         if mops.Equal(rhs_big, mops.ZERO):
 | 
| 691 |                             result = mops.ZERO  # false
 | 
| 692 |                         else:
 | 
| 693 |                             result = mops.ONE  # true
 | 
| 694 |                     return value.Int(result)
 | 
| 695 | 
 | 
| 696 |                 if op_id == Id.Arith_LBracket:
 | 
| 697 |                     # NOTE: Similar to bracket_op_e.ArrayIndex in osh/word_eval.py
 | 
| 698 | 
 | 
| 699 |                     left = self.Eval(node.left)
 | 
| 700 |                     UP_left = left
 | 
| 701 |                     with tagswitch(left) as case:
 | 
| 702 |                         if case(value_e.BashArray):
 | 
| 703 |                             array_val = cast(value.BashArray, UP_left)
 | 
| 704 |                             small_i = mops.BigTruncate(
 | 
| 705 |                                 self.EvalToBigInt(node.right))
 | 
| 706 |                             s = word_eval.GetArrayItem(array_val.strs, small_i)
 | 
| 707 | 
 | 
| 708 |                         elif case(value_e.BashAssoc):
 | 
| 709 |                             left = cast(value.BashAssoc, UP_left)
 | 
| 710 |                             key = self.EvalWordToString(node.right)
 | 
| 711 |                             s = left.d.get(key)
 | 
| 712 | 
 | 
| 713 |                         elif case(value_e.Str):
 | 
| 714 |                             left = cast(value.Str, UP_left)
 | 
| 715 |                             if self.exec_opts.strict_arith():
 | 
| 716 |                                 e_die(
 | 
| 717 |                                     "Value of type Str can't be indexed (strict_arith)",
 | 
| 718 |                                     node.op)
 | 
| 719 |                             index = self.EvalToBigInt(node.right)
 | 
| 720 |                             # s[0] evaluates to s
 | 
| 721 |                             # s[1] evaluates to Undef
 | 
| 722 |                             s = left.s if mops.Equal(index,
 | 
| 723 |                                                      mops.ZERO) else None
 | 
| 724 | 
 | 
| 725 |                         elif case(value_e.Undef):
 | 
| 726 |                             if self.exec_opts.strict_arith():
 | 
| 727 |                                 e_die(
 | 
| 728 |                                     "Value of type Undef can't be indexed (strict_arith)",
 | 
| 729 |                                     node.op)
 | 
| 730 |                             s = None  # value.Undef
 | 
| 731 | 
 | 
| 732 |                             # There isn't a way to distinguish Undef vs. empty
 | 
| 733 |                             # string, even with set -o nounset?
 | 
| 734 |                             # s = ''
 | 
| 735 | 
 | 
| 736 |                         else:
 | 
| 737 |                             # TODO: Add error context
 | 
| 738 |                             e_die(
 | 
| 739 |                                 "Value of type %s can't be indexed" %
 | 
| 740 |                                 ui.ValType(left), node.op)
 | 
| 741 | 
 | 
| 742 |                     if s is None:
 | 
| 743 |                         val = value.Undef
 | 
| 744 |                     else:
 | 
| 745 |                         val = value.Str(s)
 | 
| 746 | 
 | 
| 747 |                     return val
 | 
| 748 | 
 | 
| 749 |                 if op_id == Id.Arith_Comma:
 | 
| 750 |                     self.EvalToBigInt(node.left)  # throw away result
 | 
| 751 |                     result = self.EvalToBigInt(node.right)
 | 
| 752 |                     return value.Int(result)
 | 
| 753 | 
 | 
| 754 |                 # Rest are integers
 | 
| 755 |                 lhs_big = self.EvalToBigInt(node.left)
 | 
| 756 |                 rhs_big = self.EvalToBigInt(node.right)
 | 
| 757 | 
 | 
| 758 |                 if op_id == Id.Arith_Plus:
 | 
| 759 |                     result = mops.Add(lhs_big, rhs_big)
 | 
| 760 |                 elif op_id == Id.Arith_Minus:
 | 
| 761 |                     result = mops.Sub(lhs_big, rhs_big)
 | 
| 762 |                 elif op_id == Id.Arith_Star:
 | 
| 763 |                     result = mops.Mul(lhs_big, rhs_big)
 | 
| 764 |                 elif op_id == Id.Arith_Slash:
 | 
| 765 |                     if mops.Equal(rhs_big, mops.ZERO):
 | 
| 766 |                         e_die('Divide by zero', node.op)
 | 
| 767 |                     result = num.IntDivide(lhs_big, rhs_big)
 | 
| 768 | 
 | 
| 769 |                 elif op_id == Id.Arith_Percent:
 | 
| 770 |                     if mops.Equal(rhs_big, mops.ZERO):
 | 
| 771 |                         e_die('Divide by zero', node.op)
 | 
| 772 |                     result = num.IntRemainder(lhs_big, rhs_big)
 | 
| 773 | 
 | 
| 774 |                 elif op_id == Id.Arith_DStar:
 | 
| 775 |                     if mops.Greater(mops.ZERO, rhs_big):
 | 
| 776 |                         e_die("Exponent can't be a negative number",
 | 
| 777 |                               loc.Arith(node.right))
 | 
| 778 |                     result = num.Exponent(lhs_big, rhs_big)
 | 
| 779 | 
 | 
| 780 |                 elif op_id == Id.Arith_DEqual:
 | 
| 781 |                     result = mops.FromBool(mops.Equal(lhs_big, rhs_big))
 | 
| 782 |                 elif op_id == Id.Arith_NEqual:
 | 
| 783 |                     result = mops.FromBool(not mops.Equal(lhs_big, rhs_big))
 | 
| 784 |                 elif op_id == Id.Arith_Great:
 | 
| 785 |                     result = mops.FromBool(mops.Greater(lhs_big, rhs_big))
 | 
| 786 |                 elif op_id == Id.Arith_GreatEqual:
 | 
| 787 |                     result = mops.FromBool(
 | 
| 788 |                         mops.Greater(lhs_big, rhs_big) or
 | 
| 789 |                         mops.Equal(lhs_big, rhs_big))
 | 
| 790 |                 elif op_id == Id.Arith_Less:
 | 
| 791 |                     result = mops.FromBool(mops.Greater(rhs_big, lhs_big))
 | 
| 792 |                 elif op_id == Id.Arith_LessEqual:
 | 
| 793 |                     result = mops.FromBool(
 | 
| 794 |                         mops.Greater(rhs_big, lhs_big) or
 | 
| 795 |                         mops.Equal(lhs_big, rhs_big))
 | 
| 796 | 
 | 
| 797 |                 elif op_id == Id.Arith_Pipe:
 | 
| 798 |                     result = mops.BitOr(lhs_big, rhs_big)
 | 
| 799 |                 elif op_id == Id.Arith_Amp:
 | 
| 800 |                     result = mops.BitAnd(lhs_big, rhs_big)
 | 
| 801 |                 elif op_id == Id.Arith_Caret:
 | 
| 802 |                     result = mops.BitXor(lhs_big, rhs_big)
 | 
| 803 | 
 | 
| 804 |                 # Note: how to define shift of negative numbers?
 | 
| 805 |                 elif op_id == Id.Arith_DLess:
 | 
| 806 |                     result = mops.LShift(lhs_big, rhs_big)
 | 
| 807 |                 elif op_id == Id.Arith_DGreat:
 | 
| 808 |                     result = mops.RShift(lhs_big, rhs_big)
 | 
| 809 |                 else:
 | 
| 810 |                     raise AssertionError(op_id)
 | 
| 811 | 
 | 
| 812 |                 return value.Int(result)
 | 
| 813 | 
 | 
| 814 |             elif case(arith_expr_e.TernaryOp):
 | 
| 815 |                 node = cast(arith_expr.TernaryOp, UP_node)
 | 
| 816 | 
 | 
| 817 |                 cond = self.EvalToBigInt(node.cond)
 | 
| 818 |                 if mops.Equal(cond, mops.ZERO):
 | 
| 819 |                     return self.Eval(node.false_expr)
 | 
| 820 |                 else:
 | 
| 821 |                     return self.Eval(node.true_expr)
 | 
| 822 | 
 | 
| 823 |             else:
 | 
| 824 |                 raise AssertionError(node.tag())
 | 
| 825 | 
 | 
| 826 |         raise AssertionError('for -Wreturn-type in C++')
 | 
| 827 | 
 | 
| 828 |     def EvalWordToString(self, node, blame_loc=loc.Missing):
 | 
| 829 |         # type: (arith_expr_t, loc_t) -> str
 | 
| 830 |         """
 | 
| 831 |         Raises:
 | 
| 832 |           error.FatalRuntime if the expression isn't a string
 | 
| 833 |                              or if it contains a bare variable like a[x]
 | 
| 834 | 
 | 
| 835 |         These are allowed because they're unambiguous, unlike a[x]
 | 
| 836 | 
 | 
| 837 |         a[$x] a["$x"] a["x"] a['x']
 | 
| 838 |         """
 | 
| 839 |         UP_node = node
 | 
| 840 |         if node.tag() == arith_expr_e.Word:  # $(( $x )) $(( ${x}${y} )), etc.
 | 
| 841 |             w = cast(CompoundWord, UP_node)
 | 
| 842 |             val = self.word_ev.EvalWordToString(w)
 | 
| 843 |             return val.s
 | 
| 844 |         else:
 | 
| 845 |             # A[x] is the "Parsing Bash is Undecidable" problem
 | 
| 846 |             # It is a string or var name?
 | 
| 847 |             # (It's parsed as arith_expr.VarSub)
 | 
| 848 |             e_die(
 | 
| 849 |                 "Assoc array keys must be strings: $x 'x' \"$x\" etc. (OILS-ERR-101)",
 | 
| 850 |                 blame_loc)
 | 
| 851 | 
 | 
| 852 |     def EvalShellLhs(self, node, which_scopes):
 | 
| 853 |         # type: (sh_lhs_t, scope_t) -> sh_lvalue_t
 | 
| 854 |         """Evaluate a shell LHS expression
 | 
| 855 | 
 | 
| 856 |         For  a=b  and  a[x]=b  etc.
 | 
| 857 |         """
 | 
| 858 |         assert isinstance(node, sh_lhs_t), node
 | 
| 859 | 
 | 
| 860 |         UP_node = node
 | 
| 861 |         lval = None  # type: sh_lvalue_t
 | 
| 862 |         with tagswitch(node) as case:
 | 
| 863 |             if case(sh_lhs_e.Name):  # a=x
 | 
| 864 |                 node = cast(sh_lhs.Name, UP_node)
 | 
| 865 |                 assert node.name is not None
 | 
| 866 | 
 | 
| 867 |                 lval1 = LeftName(node.name, node.left)
 | 
| 868 |                 lval = lval1
 | 
| 869 | 
 | 
| 870 |             elif case(sh_lhs_e.IndexedName):  # a[1+2]=x
 | 
| 871 |                 node = cast(sh_lhs.IndexedName, UP_node)
 | 
| 872 |                 assert node.name is not None
 | 
| 873 | 
 | 
| 874 |                 if self.mem.IsBashAssoc(node.name):
 | 
| 875 |                     key = self.EvalWordToString(node.index,
 | 
| 876 |                                                 blame_loc=node.left)
 | 
| 877 |                     # node.left points to A[ in A[x]=1
 | 
| 878 |                     lval2 = sh_lvalue.Keyed(node.name, key, node.left)
 | 
| 879 |                     lval = lval2
 | 
| 880 |                 else:
 | 
| 881 |                     index = mops.BigTruncate(self.EvalToBigInt(node.index))
 | 
| 882 |                     lval3 = sh_lvalue.Indexed(node.name, index, node.left)
 | 
| 883 |                     lval = lval3
 | 
| 884 | 
 | 
| 885 |             else:
 | 
| 886 |                 raise AssertionError(node.tag())
 | 
| 887 | 
 | 
| 888 |         return lval
 | 
| 889 | 
 | 
| 890 |     def _VarNameOrWord(self, anode):
 | 
| 891 |         # type: (arith_expr_t) -> Tuple[Optional[str], loc_t]
 | 
| 892 |         """
 | 
| 893 |         Returns a variable name if the arith node can be interpreted that way.
 | 
| 894 |         """
 | 
| 895 |         UP_anode = anode
 | 
| 896 |         with tagswitch(anode) as case:
 | 
| 897 |             if case(arith_expr_e.VarSub):
 | 
| 898 |                 tok = cast(Token, UP_anode)
 | 
| 899 |                 return (lexer.LazyStr(tok), tok)
 | 
| 900 | 
 | 
| 901 |             elif case(arith_expr_e.Word):
 | 
| 902 |                 w = cast(CompoundWord, UP_anode)
 | 
| 903 |                 var_name = self.EvalWordToString(w)
 | 
| 904 |                 return (var_name, w)
 | 
| 905 | 
 | 
| 906 |         no_str = None  # type: str
 | 
| 907 |         return (no_str, loc.Missing)
 | 
| 908 | 
 | 
| 909 |     def EvalArithLhs(self, anode):
 | 
| 910 |         # type: (arith_expr_t) -> sh_lvalue_t
 | 
| 911 |         """
 | 
| 912 |         For (( a[x] = 1 )) etc.
 | 
| 913 |         """
 | 
| 914 |         UP_anode = anode
 | 
| 915 |         if anode.tag() == arith_expr_e.Binary:
 | 
| 916 |             anode = cast(arith_expr.Binary, UP_anode)
 | 
| 917 |             if anode.op.id == Id.Arith_LBracket:
 | 
| 918 |                 var_name, blame_loc = self._VarNameOrWord(anode.left)
 | 
| 919 | 
 | 
| 920 |                 # (( 1[2] = 3 )) isn't valid
 | 
| 921 |                 if not match.IsValidVarName(var_name):
 | 
| 922 |                     e_die('Invalid variable name %r' % var_name, blame_loc)
 | 
| 923 | 
 | 
| 924 |                 if var_name is not None:
 | 
| 925 |                     if self.mem.IsBashAssoc(var_name):
 | 
| 926 |                         arith_loc = location.TokenForArith(anode)
 | 
| 927 |                         key = self.EvalWordToString(anode.right,
 | 
| 928 |                                                     blame_loc=arith_loc)
 | 
| 929 |                         return sh_lvalue.Keyed(var_name, key, blame_loc)
 | 
| 930 |                     else:
 | 
| 931 |                         index = mops.BigTruncate(self.EvalToBigInt(
 | 
| 932 |                             anode.right))
 | 
| 933 |                         return sh_lvalue.Indexed(var_name, index, blame_loc)
 | 
| 934 | 
 | 
| 935 |         var_name, blame_loc = self._VarNameOrWord(anode)
 | 
| 936 |         if var_name is not None:
 | 
| 937 |             return LeftName(var_name, blame_loc)
 | 
| 938 | 
 | 
| 939 |         # e.g. unset 'x-y'.  status 2 for runtime parse error
 | 
| 940 |         e_die_status(2, 'Invalid LHS to modify', blame_loc)
 | 
| 941 | 
 | 
| 942 | 
 | 
| 943 | class BoolEvaluator(ArithEvaluator):
 | 
| 944 |     """This is also an ArithEvaluator because it has to understand.
 | 
| 945 | 
 | 
| 946 |     [[ x -eq 3 ]]
 | 
| 947 | 
 | 
| 948 |     where x='1+2'
 | 
| 949 |     """
 | 
| 950 | 
 | 
| 951 |     def __init__(
 | 
| 952 |             self,
 | 
| 953 |             mem,  # type: state.Mem
 | 
| 954 |             exec_opts,  # type: optview.Exec
 | 
| 955 |             mutable_opts,  # type: Optional[state.MutableOpts]
 | 
| 956 |             parse_ctx,  # type: Optional[parse_lib.ParseContext]
 | 
| 957 |             errfmt,  # type: ErrorFormatter
 | 
| 958 |             always_strict=False  # type: bool
 | 
| 959 |     ):
 | 
| 960 |         # type: (...) -> None
 | 
| 961 |         ArithEvaluator.__init__(self, mem, exec_opts, mutable_opts, parse_ctx,
 | 
| 962 |                                 errfmt)
 | 
| 963 |         self.always_strict = always_strict
 | 
| 964 | 
 | 
| 965 |     def _IsDefined(self, s, blame_loc):
 | 
| 966 |         # type: (str, loc_t) -> bool
 | 
| 967 | 
 | 
| 968 |         m = util.RegexSearch(consts.TEST_V_RE, s)
 | 
| 969 |         if m is None:
 | 
| 970 |             if self.exec_opts.strict_word_eval():
 | 
| 971 |                 e_die('-v expected name or name[index]', blame_loc)
 | 
| 972 |             return False
 | 
| 973 | 
 | 
| 974 |         var_name = m[1]
 | 
| 975 |         index_str = m[3]
 | 
| 976 | 
 | 
| 977 |         val = self.mem.GetValue(var_name)
 | 
| 978 |         if len(index_str) == 0:  # it's just a variable name
 | 
| 979 |             return val.tag() != value_e.Undef
 | 
| 980 | 
 | 
| 981 |         UP_val = val
 | 
| 982 |         with tagswitch(val) as case:
 | 
| 983 |             if case(value_e.BashArray):
 | 
| 984 |                 val = cast(value.BashArray, UP_val)
 | 
| 985 | 
 | 
| 986 |                 # TODO: use mops.BigStr
 | 
| 987 |                 try:
 | 
| 988 |                     index = int(index_str)
 | 
| 989 |                 except ValueError as e:
 | 
| 990 |                     if self.exec_opts.strict_word_eval():
 | 
| 991 |                         e_die(
 | 
| 992 |                             '-v got BashArray and invalid index %r' %
 | 
| 993 |                             index_str, blame_loc)
 | 
| 994 |                     return False
 | 
| 995 | 
 | 
| 996 |                 if index < 0:
 | 
| 997 |                     if self.exec_opts.strict_word_eval():
 | 
| 998 |                         e_die('-v got invalid negative index %s' % index_str,
 | 
| 999 |                               blame_loc)
 | 
| 1000 |                     return False
 | 
| 1001 | 
 | 
| 1002 |                 if index < len(val.strs):
 | 
| 1003 |                     return val.strs[index] is not None
 | 
| 1004 | 
 | 
| 1005 |                 # out of range
 | 
| 1006 |                 return False
 | 
| 1007 | 
 | 
| 1008 |             elif case(value_e.BashAssoc):
 | 
| 1009 |                 val = cast(value.BashAssoc, UP_val)
 | 
| 1010 |                 return index_str in val.d
 | 
| 1011 | 
 | 
| 1012 |             else:
 | 
| 1013 |                 # work around mycpp bug!  parses as 'elif'
 | 
| 1014 |                 pass
 | 
| 1015 | 
 | 
| 1016 |                 if self.exec_opts.strict_word_eval():
 | 
| 1017 |                     raise error.TypeErr(val, 'Expected BashArray or BashAssoc',
 | 
| 1018 |                                         blame_loc)
 | 
| 1019 |                 return False
 | 
| 1020 |         raise AssertionError()
 | 
| 1021 | 
 | 
| 1022 |     def _StringToBigIntOrError(self, s, blame_word=None):
 | 
| 1023 |         # type: (str, Optional[word_t]) -> mops.BigInt
 | 
| 1024 |         """Used by both [[ $x -gt 3 ]] and (( $x ))."""
 | 
| 1025 |         if blame_word:
 | 
| 1026 |             location = loc.Word(blame_word)  # type: loc_t
 | 
| 1027 |         else:
 | 
| 1028 |             location = loc.Missing
 | 
| 1029 | 
 | 
| 1030 |         try:
 | 
| 1031 |             i = self._StringToBigInt(s, location)
 | 
| 1032 |         except error.Strict as e:
 | 
| 1033 |             if self.always_strict or self.exec_opts.strict_arith():
 | 
| 1034 |                 raise
 | 
| 1035 |             else:
 | 
| 1036 |                 i = mops.ZERO
 | 
| 1037 |         return i
 | 
| 1038 | 
 | 
| 1039 |     def _EvalCompoundWord(self, word, eval_flags=0):
 | 
| 1040 |         # type: (word_t, int) -> str
 | 
| 1041 |         val = self.word_ev.EvalWordToString(word, eval_flags)
 | 
| 1042 |         return val.s
 | 
| 1043 | 
 | 
| 1044 |     def EvalB(self, node):
 | 
| 1045 |         # type: (bool_expr_t) -> bool
 | 
| 1046 | 
 | 
| 1047 |         UP_node = node
 | 
| 1048 |         with tagswitch(node) as case:
 | 
| 1049 |             if case(bool_expr_e.WordTest):
 | 
| 1050 |                 node = cast(bool_expr.WordTest, UP_node)
 | 
| 1051 |                 s = self._EvalCompoundWord(node.w)
 | 
| 1052 |                 return bool(s)
 | 
| 1053 | 
 | 
| 1054 |             elif case(bool_expr_e.LogicalNot):
 | 
| 1055 |                 node = cast(bool_expr.LogicalNot, UP_node)
 | 
| 1056 |                 b = self.EvalB(node.child)
 | 
| 1057 |                 return not b
 | 
| 1058 | 
 | 
| 1059 |             elif case(bool_expr_e.LogicalAnd):
 | 
| 1060 |                 node = cast(bool_expr.LogicalAnd, UP_node)
 | 
| 1061 |                 # Short-circuit evaluation
 | 
| 1062 |                 if self.EvalB(node.left):
 | 
| 1063 |                     return self.EvalB(node.right)
 | 
| 1064 |                 else:
 | 
| 1065 |                     return False
 | 
| 1066 | 
 | 
| 1067 |             elif case(bool_expr_e.LogicalOr):
 | 
| 1068 |                 node = cast(bool_expr.LogicalOr, UP_node)
 | 
| 1069 |                 if self.EvalB(node.left):
 | 
| 1070 |                     return True
 | 
| 1071 |                 else:
 | 
| 1072 |                     return self.EvalB(node.right)
 | 
| 1073 | 
 | 
| 1074 |             elif case(bool_expr_e.Unary):
 | 
| 1075 |                 node = cast(bool_expr.Unary, UP_node)
 | 
| 1076 |                 op_id = node.op_id
 | 
| 1077 |                 s = self._EvalCompoundWord(node.child)
 | 
| 1078 | 
 | 
| 1079 |                 # Now dispatch on arg type.  (arg_type could be static in the
 | 
| 1080 |                 # LST?)
 | 
| 1081 |                 arg_type = consts.BoolArgType(op_id)
 | 
| 1082 | 
 | 
| 1083 |                 if arg_type == bool_arg_type_e.Path:
 | 
| 1084 |                     return bool_stat.DoUnaryOp(op_id, s)
 | 
| 1085 | 
 | 
| 1086 |                 if arg_type == bool_arg_type_e.Str:
 | 
| 1087 |                     if op_id == Id.BoolUnary_z:
 | 
| 1088 |                         return not bool(s)
 | 
| 1089 |                     if op_id == Id.BoolUnary_n:
 | 
| 1090 |                         return bool(s)
 | 
| 1091 | 
 | 
| 1092 |                     raise AssertionError(op_id)  # should never happen
 | 
| 1093 | 
 | 
| 1094 |                 if arg_type == bool_arg_type_e.Other:
 | 
| 1095 |                     if op_id == Id.BoolUnary_t:
 | 
| 1096 |                         return bool_stat.isatty(s, node.child)
 | 
| 1097 | 
 | 
| 1098 |                     # See whether 'set -o' options have been set
 | 
| 1099 |                     if op_id == Id.BoolUnary_o:
 | 
| 1100 |                         index = consts.OptionNum(s)
 | 
| 1101 |                         if index == 0:
 | 
| 1102 |                             return False
 | 
| 1103 |                         else:
 | 
| 1104 |                             return self.exec_opts.opt0_array[index]
 | 
| 1105 | 
 | 
| 1106 |                     if op_id == Id.BoolUnary_v:
 | 
| 1107 |                         return self._IsDefined(s, loc.Word(node.child))
 | 
| 1108 | 
 | 
| 1109 |                     e_die("%s isn't implemented" %
 | 
| 1110 |                           ui.PrettyId(op_id))  # implicit location
 | 
| 1111 | 
 | 
| 1112 |                 raise AssertionError(arg_type)
 | 
| 1113 | 
 | 
| 1114 |             elif case(bool_expr_e.Binary):
 | 
| 1115 |                 node = cast(bool_expr.Binary, UP_node)
 | 
| 1116 | 
 | 
| 1117 |                 op_id = node.op_id
 | 
| 1118 |                 # Whether to glob escape
 | 
| 1119 |                 eval_flags = 0
 | 
| 1120 |                 with switch(op_id) as case2:
 | 
| 1121 |                     if case2(Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual,
 | 
| 1122 |                              Id.BoolBinary_GlobNEqual):
 | 
| 1123 |                         eval_flags |= word_eval.QUOTE_FNMATCH
 | 
| 1124 |                     elif case2(Id.BoolBinary_EqualTilde):
 | 
| 1125 |                         eval_flags |= word_eval.QUOTE_ERE
 | 
| 1126 | 
 | 
| 1127 |                 s1 = self._EvalCompoundWord(node.left)
 | 
| 1128 |                 s2 = self._EvalCompoundWord(node.right, eval_flags)
 | 
| 1129 | 
 | 
| 1130 |                 # Now dispatch on arg type
 | 
| 1131 |                 arg_type = consts.BoolArgType(op_id)
 | 
| 1132 | 
 | 
| 1133 |                 if arg_type == bool_arg_type_e.Path:
 | 
| 1134 |                     return bool_stat.DoBinaryOp(op_id, s1, s2)
 | 
| 1135 | 
 | 
| 1136 |                 if arg_type == bool_arg_type_e.Int:
 | 
| 1137 |                     # NOTE: We assume they are constants like [[ 3 -eq 3 ]].
 | 
| 1138 |                     # Bash also allows [[ 1+2 -eq 3 ]].
 | 
| 1139 |                     i1 = self._StringToBigIntOrError(s1, blame_word=node.left)
 | 
| 1140 |                     i2 = self._StringToBigIntOrError(s2, blame_word=node.right)
 | 
| 1141 | 
 | 
| 1142 |                     if op_id == Id.BoolBinary_eq:
 | 
| 1143 |                         return mops.Equal(i1, i2)
 | 
| 1144 |                     if op_id == Id.BoolBinary_ne:
 | 
| 1145 |                         return not mops.Equal(i1, i2)
 | 
| 1146 |                     if op_id == Id.BoolBinary_gt:
 | 
| 1147 |                         return mops.Greater(i1, i2)
 | 
| 1148 |                     if op_id == Id.BoolBinary_ge:
 | 
| 1149 |                         return mops.Greater(i1, i2) or mops.Equal(i1, i2)
 | 
| 1150 |                     if op_id == Id.BoolBinary_lt:
 | 
| 1151 |                         return mops.Greater(i2, i1)
 | 
| 1152 |                     if op_id == Id.BoolBinary_le:
 | 
| 1153 |                         return mops.Greater(i2, i1) or mops.Equal(i1, i2)
 | 
| 1154 | 
 | 
| 1155 |                     raise AssertionError(op_id)  # should never happen
 | 
| 1156 | 
 | 
| 1157 |                 if arg_type == bool_arg_type_e.Str:
 | 
| 1158 |                     fnmatch_flags = (FNM_CASEFOLD
 | 
| 1159 |                                      if self.exec_opts.nocasematch() else 0)
 | 
| 1160 | 
 | 
| 1161 |                     if op_id in (Id.BoolBinary_GlobEqual,
 | 
| 1162 |                                  Id.BoolBinary_GlobDEqual):
 | 
| 1163 |                         #log('Matching %s against pattern %s', s1, s2)
 | 
| 1164 |                         return libc.fnmatch(s2, s1, fnmatch_flags)
 | 
| 1165 | 
 | 
| 1166 |                     if op_id == Id.BoolBinary_GlobNEqual:
 | 
| 1167 |                         return not libc.fnmatch(s2, s1, fnmatch_flags)
 | 
| 1168 | 
 | 
| 1169 |                     if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
 | 
| 1170 |                         return s1 == s2
 | 
| 1171 | 
 | 
| 1172 |                     if op_id == Id.BoolBinary_NEqual:
 | 
| 1173 |                         return s1 != s2
 | 
| 1174 | 
 | 
| 1175 |                     if op_id == Id.BoolBinary_EqualTilde:
 | 
| 1176 |                         # TODO: This should go to --debug-file
 | 
| 1177 |                         #log('Matching %r against regex %r', s1, s2)
 | 
| 1178 |                         regex_flags = (REG_ICASE
 | 
| 1179 |                                        if self.exec_opts.nocasematch() else 0)
 | 
| 1180 | 
 | 
| 1181 |                         try:
 | 
| 1182 |                             indices = libc.regex_search(s2, regex_flags, s1, 0)
 | 
| 1183 |                         except ValueError as e:
 | 
| 1184 |                             # Status 2 indicates a regex parse error.  This is
 | 
| 1185 |                             # fatal in OSH but not in bash, which treats [[
 | 
| 1186 |                             # like a command with an exit code.
 | 
| 1187 |                             e_die_status(2, e.message, loc.Word(node.right))
 | 
| 1188 | 
 | 
| 1189 |                         if indices is not None:
 | 
| 1190 |                             self.mem.SetRegexMatch(
 | 
| 1191 |                                 RegexMatch(s1, indices, eggex_ops.No))
 | 
| 1192 |                             return True
 | 
| 1193 |                         else:
 | 
| 1194 |                             self.mem.SetRegexMatch(regex_match.No)
 | 
| 1195 |                             return False
 | 
| 1196 | 
 | 
| 1197 |                     if op_id == Id.Op_Less:
 | 
| 1198 |                         return str_cmp(s1, s2) < 0
 | 
| 1199 | 
 | 
| 1200 |                     if op_id == Id.Op_Great:
 | 
| 1201 |                         return str_cmp(s1, s2) > 0
 | 
| 1202 | 
 | 
| 1203 |                     raise AssertionError(op_id)  # should never happen
 | 
| 1204 | 
 | 
| 1205 |         raise AssertionError(node.tag())
 |