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