Exemple #1
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val)
        arg = arg_types.read(attrs.attrs)
        names = arg_r.Rest()

        fd = self.stdin.fileno()

        if arg.t >= 0.0:
            if arg.t != 0.0:
                e_die("read -t isn't implemented (except t=0)")
            else:
                return 0 if pyos.InputAvailable(fd) else 1

        bits = 0
        if self.stdin.isatty():
            bits |= pyos.TERM_ICANON
            if arg.s:  # silent
                bits |= pyos.TERM_ECHO

            if arg.p is not None:  # only if tty
                mylib.Stderr().write(arg.p)

        if bits == 0:
            status = self._Read(arg, names)
        else:
            term = pyos.TermState(fd, ~bits)
            try:
                status = self._Read(arg, names)
            finally:
                term.Restore()
        return status
Exemple #2
0
  def EvalArithLhs(self, anode, span_id):
    # type: (arith_expr_t, int) -> lvalue_t
    """
    For (( a[x] = 1 )) etc.
    """
    UP_anode = anode
    if anode.tag_() == arith_expr_e.Binary:
      anode = cast(arith_expr__Binary, UP_anode)
      if anode.op_id == Id.Arith_LBracket:
        var_name, span_id = self._VarRefOrWord(anode.left)
        if var_name is not None:
          if self.mem.IsAssocArray(var_name, scope_e.Dynamic):
            key = self.EvalWordToString(anode.right)
            lval2 = lvalue.Keyed(var_name, key)
            lval2.spids.append(span_id)
            lval = lval2  # type: lvalue_t
            return lval
          else:
            index = self.EvalToInt(anode.right)
            lval3 = lvalue.Indexed(var_name, index)
            lval3.spids.append(span_id)
            lval = lval3
            return lval

    var_name, span_id = self._VarRefOrWord(anode)
    if var_name is not None:
      lval1 = lvalue.Named(var_name)
      lval1.spids.append(span_id)
      lval = lval1
      return lval

    # e.g. unset 'x-y'.  status 2 for runtime parse error
    e_die('Invalid place to modify', span_id=span_id, status=2)
Exemple #3
0
  def _MakeProcess(self, node, inherit_errexit=True):
    # type: (command_t, bool) -> process.Process
    """
    Assume we will run the node in another process.  Return a process.
    """
    UP_node = node
    if node.tag_() == command_e.ControlFlow:
      node = cast(command__ControlFlow, UP_node)
      # Pipeline or subshells with control flow are invalid, e.g.:
      # - break | less
      # - continue | less
      # - ( return )
      # NOTE: This could be done at parse time too.
      if node.token.id != Id.ControlFlow_Exit:
        e_die('Invalid control flow %r in pipeline / subshell / background',
              node.token.val, token=node.token)

    # NOTE: If ErrExit(), we could be verbose about subprogram errors?  This
    # only really matters when executing 'exit 42', because the child shell
    # inherits errexit and will be verbose.  Other notes:
    #
    # - We might want errors to fit on a single line so they don't get
    # interleaved.
    # - We could turn the `exit` builtin into a FatalRuntimeError exception and
    # get this check for "free".
    thunk = process.SubProgramThunk(self.cmd_ev, node,
                                    inherit_errexit=inherit_errexit)
    p = process.Process(thunk, self.job_state)
    return p
Exemple #4
0
    def Replace(self, s, op):
        # type: (str, suffix_op__PatSub) -> str

        regex = '(%s)' % self.regex  # make it a group

        if op.replace_mode == Id.Lit_Slash:
            try:
                return _PatSubAll(s, regex,
                                  self.replace_str)  # loop over matches
            except RuntimeError as e:
                # libc.regex_first_group_match raises RuntimeError.
                # note: MyPy doesn't know RuntimeError has e.message (and e.args)
                msg = e.message  # type: str
                e_die('Error matching regex %r: %s',
                      regex,
                      msg,
                      span_id=self.slash_spid)

        if op.replace_mode == Id.Lit_Pound:
            regex = '^' + regex
        elif op.replace_mode == Id.Lit_Percent:
            regex = regex + '$'

        m = libc.regex_first_group_match(regex, s, 0)
        #log('regex = %r, s = %r, match = %r', regex, s, m)
        if m is None:
            return s
        start, end = m
        return s[:start] + self.replace_str + s[end:]
Exemple #5
0
def isatty(fd, s, blame_word):
    # type: (int, str, word_t) -> bool
    try:
        return posix.isatty(fd)
    # fd is user input, and causes this exception in the binding.
    except OverflowError:
        e_die('File descriptor %r is too big', s, word=blame_word)
Exemple #6
0
  def EvalWordToString(self, node):
    # type: (arith_expr_t) -> str
    """
    Args:
      node: arith_expr_t

    Returns:
      str

    Raises:
      error.FatalRuntime if the expression isn't a string
      Or if it contains a bare variable like a[x]

    These are allowed because they're unambiguous, unlike a[x]

    a[$x] a["$x"] a["x"] a['x']
    """
    UP_node = node
    if node.tag_() == arith_expr_e.Word:  # $(( $x )) $(( ${x}${y} )), etc.
      w = cast(compound_word, UP_node)
      val = self.word_ev.EvalWordToString(w)
      return val.s
    else:
      # TODO: location info for orginal
      e_die("Associative array keys must be strings: $x 'x' \"$x\" etc.")
Exemple #7
0
    def RunSimpleCommand(self, cmd_val, do_fork, call_procs=True):
        # type: (cmd_value__Argv, bool, bool) -> int
        argv = cmd_val.argv
        span_id = cmd_val.arg_spids[0] if len(
            cmd_val.arg_spids) else runtime.NO_SPID

        arg0 = argv[0]

        builtin_id = consts.LookupSpecialBuiltin(arg0)
        if builtin_id != consts.NO_INDEX:
            return self.RunBuiltin(builtin_id, cmd_val)

        # Copied from core/executor.py
        if call_procs:
            proc_node = self.procs.get(arg0)
            if proc_node is not None:
                if (self.exec_opts.strict_errexit()
                        and self.mutable_opts.ErrExitIsDisabled()):
                    # TODO: make errfmt a member
                    #self.errfmt.Print_('errexit was disabled for this construct',
                    #                   span_id=self.mutable_opts.errexit.spid_stack[0])
                    #stderr_line('')
                    e_die(
                        "Can't run a proc while errexit is disabled. "
                        "Use 'catch' or wrap it in a process with $0 myproc",
                        span_id=span_id)

                # NOTE: Functions could call 'exit 42' directly, etc.
                status = self.cmd_ev.RunProc(proc_node, argv[1:])
                return status

        builtin_id = consts.LookupNormalBuiltin(arg0)
        if builtin_id != consts.NO_INDEX:
            return self.RunBuiltin(builtin_id, cmd_val)

        # See how many tests will pass
        #if mylib.PYTHON:
        if 0:  # osh_eval.cc will pass 1078 rather than 872 by enabling
            import subprocess
            try:
                status = subprocess.call(cmd_val.argv)
            except OSError as e:
                log('Error running %s: %s', cmd_val.argv, e)
                return 1
            return status

        log('Unhandled SimpleCommand')
        f = mylib.Stdout()
        #ast_f = fmt.DetectConsoleOutput(f)
        # Stupid Eclipse debugger doesn't display ANSI
        ast_f = fmt.TextOutput(f)
        tree = cmd_val.PrettyTree()

        ast_f.FileHeader()
        fmt.PrintTree(tree, ast_f)
        ast_f.FileFooter()
        ast_f.write('\n')

        return 0
Exemple #8
0
def _LookupVar(name, mem, exec_opts):
  # type: (str, Mem, optview.Exec) -> value_t
  val = mem.GetVar(name)
  # By default, undefined variables are the ZERO value.  TODO: Respect
  # nounset and raise an exception.
  if val.tag_() == value_e.Undef and exec_opts.nounset():
    e_die('Undefined variable %r', name)  # TODO: need token
  return val
Exemple #9
0
  def EvalPlusEquals(self, lval, rhs_py):
    # type: (lvalue_t, Union[int, float]) -> Union[int, float]
    lhs_py = self.LookupVar(lval.name)
    if not isinstance(lhs_py, (int, float)):
      # TODO: Could point at the variable name
      e_die("Object of type %r doesn't support +=", lhs_py.__class__.__name__)

    return lhs_py + rhs_py
Exemple #10
0
    def RunSimpleCommand(self, cmd_val, do_fork, call_procs=True):
        # type: (cmd_value__Argv, bool, bool) -> int
        argv = cmd_val.argv
        span_id = cmd_val.arg_spids[0] if len(
            cmd_val.arg_spids) else runtime.NO_SPID

        arg0 = argv[0]

        builtin_id = consts.LookupSpecialBuiltin(arg0)
        if builtin_id != consts.NO_INDEX:
            return self.RunBuiltin(builtin_id, cmd_val)

        func_node = self.procs.get(arg0)
        if func_node is not None:
            if (self.exec_opts.strict_errexit()
                    and self.mutable_opts.errexit.SpidIfDisabled() !=
                    runtime.NO_SPID):
                # NOTE: This would be checked below, but this gives a better error
                # message.
                e_die(
                    "can't disable errexit running a function. "
                    "Maybe wrap the function in a process with the at-splice "
                    "pattern.",
                    span_id=span_id)

            # NOTE: Functions could call 'exit 42' directly, etc.
            status = self.cmd_ev.RunProc(func_node, argv[1:])
            return status

        builtin_id = consts.LookupNormalBuiltin(arg0)
        if builtin_id != consts.NO_INDEX:
            return self.RunBuiltin(builtin_id, cmd_val)

        # See how many tests will pass
        #if mylib.PYTHON:
        if 0:  # osh_eval.cc will pass 1078 rather than 872 by enabling
            import subprocess
            try:
                status = subprocess.call(cmd_val.argv)
            except OSError as e:
                log('Error running %s: %s', cmd_val.argv, e)
                return 1
            return status

        log('Unhandled SimpleCommand')
        f = mylib.Stdout()
        #ast_f = fmt.DetectConsoleOutput(f)
        # Stupid Eclipse debugger doesn't display ANSI
        ast_f = fmt.TextOutput(f)
        tree = cmd_val.PrettyTree()

        ast_f.FileHeader()
        fmt.PrintTree(tree, ast_f)
        ast_f.FileFooter()
        ast_f.write('\n')

        return 0
Exemple #11
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int

        # TODO: Also hard usage error here too?
        attrs, arg_r = flag_spec.ParseOilCmdVal('run', cmd_val)
        arg = arg_types.run(attrs.attrs)

        if arg_r.Peek() is None:
            # HARD ERROR, not e_usage(), because errexit is often disabled!
            e_die("'run' expected a command to run", status=2)

        argv, spids = arg_r.Rest2()
        cmd_val2 = cmd_value.Argv(argv, spids, cmd_val.block)

        # Set in the 'except' block, e.g. if 'myfunc' failed
        failure_spid = runtime.NO_SPID
        try:
            # Temporarily turn ON errexit, and blame the 'run' spid.  Note that
            # 'if run myproc' disables it and then enables it!
            with state.ctx_ErrExit(self.mutable_opts, True,
                                   cmd_val.arg_spids[0]):
                # Pass do_fork=True.  Slight annoyance: the real value is a field of
                # command.Simple().  See _NoForkLast() in CommandEvaluator We have an
                # extra fork (miss out on an optimization) of code like ( status ls )
                # or forkwait { status ls }, but that is NOT idiomatic code.  status is
                # for functions.
                status = self.shell_ex.RunSimpleCommand(cmd_val2, True)
                #log('st %d', status)
        except error.ErrExit as e:  # from functino call
            #log('e %d', e.exit_status)
            status = e.exit_status
            failure_spid = e.span_id

        # Do this before -allow-status-01
        if arg.status_ok is not None:
            status = _AdjustStatus(arg.status_ok, status)

        if arg.allow_status_01 and status not in (0, 1):
            if failure_spid != runtime.NO_SPID:
                self.errfmt.Print_('(original failure)', span_id=failure_spid)
                self.errfmt.StderrLine('')

            raise error.ErrExit('fatal: status %d when --allow-status-01' %
                                status,
                                span_id=spids[0],
                                status=status)

        if arg.assign_status is not None:
            var_name = arg.assign_status
            if var_name.startswith(':'):
                var_name = var_name[1:]

            state.SetRefString(self.mem, var_name, str(status))
            return 0  # don't fail

        return status
Exemple #12
0
def isatty(fd_str, blame_word):
  # type: (str, word_t) -> bool
  try:
    fd = int(fd_str)
  except ValueError:
    e_die('Invalid file descriptor %r', fd_str, word=blame_word)

  try:
    return posix.isatty(fd)
  # fd is user input, and causes this exception in the binding.
  except OverflowError:
    e_die('File descriptor %r is too big', fd_str, word=blame_word)
Exemple #13
0
    def _UnsetVar(self, arg, spid, proc_fallback):
        # type: (str, int, bool) -> bool
        """
    Returns:
      bool: whether the 'unset' builtin should succeed with code 0.
    """
        arena = self.parse_ctx.arena

        a_parser = self.parse_ctx.MakeArithParser(arg)
        arena.PushSource(source.ArgvWord(spid))
        try:
            anode = a_parser.Parse()
        except error.Parse as e:
            # show parse error
            ui.PrettyPrintError(e, arena)
            # point to word
            e_usage('Invalid unset expression', span_id=spid)
        finally:
            arena.PopSource()

        lval = self.arith_ev.EvalArithLhs(anode, spid)

        # Prevent attacks like these by default:
        #
        # unset -v 'A["$(echo K; rm *)"]'
        if not self.exec_opts.eval_unsafe_arith(
        ) and lval.tag_() != lvalue_e.Named:
            e_die(
                'Expected a variable name.  Expressions are allowed with shopt -s eval_unsafe_arith',
                span_id=spid)

        #log('lval %s', lval)
        found = False
        try:
            # not strict
            found = self.mem.Unset(lval, scope_e.Dynamic, False)
        except error.Runtime as e:
            # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't
            # fail because it's a builtin.  So I guess the same is true of 'unset'.
            e.span_id = spid
            ui.PrettyPrintError(e, arena)
            return False

        if proc_fallback and not found:
            if arg in self.funcs:
                del self.funcs[arg]

        return True
Exemple #14
0
    def LookupVar(self, var_name):
        """Convert to a Python object so we can calculate on it natively."""

        # Lookup WITHOUT dynamic scope.
        val = self.mem.GetValue(var_name, which_scopes=scope_e.LocalOrGlobal)
        if val.tag == value_e.Undef:
            # TODO: Location info
            e_die('Undefined variable %r', var_name)

        if val.tag == value_e.Str:
            return val.s
        if val.tag == value_e.MaybeStrArray:
            return val.strs  # node: has None
        if val.tag == value_e.AssocArray:
            return val.d
        if val.tag == value_e.Obj:
            return val.obj
Exemple #15
0
    def _ValToIntOrError(self, val, span_id=runtime.NO_SPID):
        # type: (value_t, int) -> int
        try:
            UP_val = val
            with tagswitch(val) as case:
                if case(value_e.Undef
                        ):  # 'nounset' already handled before got here
                    # Happens upon a[undefined]=42, which unfortunately turns into a[0]=42.
                    #log('blame_word %s   arena %s', blame_word, self.arena)
                    e_strict('Undefined value in arithmetic context',
                             span_id=span_id)

                elif case(value_e.Int):
                    val = cast(value__Int, UP_val)
                    return val.i

                elif case(value_e.Str):
                    val = cast(value__Str, UP_val)
                    return self._StringToInteger(
                        val.s, span_id=span_id)  # calls e_strict

                elif case(value_e.Obj):
                    # Note: this handles var x = 42; echo $(( x > 2 )).
                    if mylib.PYTHON:
                        val = cast(value__Obj, UP_val)
                        if isinstance(val.obj, int):
                            return val.obj
                    raise AssertionError()  # not in C++

        except error.Strict as e:
            if self.exec_opts.strict_arith():
                raise
            else:
                return 0

        # Arrays and associative arrays always fail -- not controlled by
        # strict_arith.
        # In bash, (( a )) is like (( a[0] )), but I don't want that.
        # And returning '0' gives different results.
        e_die("Expected a value convertible to integer, got %s",
              ui.ValType(val),
              span_id=span_id)
Exemple #16
0
def _ClassLiteralToPosixEre(term, parts):
    # type: (class_literal_term_t, List[str]) -> None

    UP_term = term
    tag = term.tag_()

    if tag == class_literal_term_e.Range:
        term = cast(class_literal_term__Range, UP_term)
        # \\ \^ \- can be used in ranges?
        start = glob_.EreCharClassEscape(term.start)
        end = glob_.EreCharClassEscape(term.end)
        parts.append('%s-%s' % (start, end))
        return

    if tag == class_literal_term_e.ByteSet:
        term = cast(class_literal_term__ByteSet, UP_term)
        # This escaping is different than ExtendedRegexEscape.
        parts.append(glob_.EreCharClassEscape(term.bytes))
        return

    if tag == class_literal_term_e.CodePoint:
        term = cast(class_literal_term__CodePoint, UP_term)
        code_point = term.i
        if code_point < 128:
            parts.append(chr(code_point))
        else:
            e_die("ERE can't express code point %d",
                  code_point,
                  span_id=term.spid)
        return

    if tag == class_literal_term_e.PerlClass:
        term = cast(perl_class, UP_term)
        n = term.name
        chars = PERL_CLASS[term.name]  # looks like '[:digit:]'
        if term.negated:
            e_die("Perl classes can't be negated in ERE",
                  span_id=term.negated.span_id)
        else:
            pat = '%s' % chars
        parts.append(pat)
        return

    if tag == class_literal_term_e.PosixClass:
        term = cast(posix_class, UP_term)
        n = term.name  # looks like 'digit'
        if term.negated:
            e_die("POSIX classes can't be negated in ERE",
                  span_id=term.negated.span_id)
        else:
            pat = '[:%s:]' % n
        parts.append(pat)
        return

    if tag == class_literal_term_e.CharLiteral:
        term = cast(class_literal_term__CharLiteral, UP_term)
        parts.append(term.tok.val)
        return

    raise NotImplementedError(tag)
Exemple #17
0
    def _MutateClassLiteral(self, node):
        # type: (re_t) -> None
        for i, term in enumerate(node.terms):
            s = None
            if term.tag == class_literal_term_e.SingleQuoted:
                s = word_eval.EvalSingleQuoted(term)
                spid = term.left.span_id

            elif term.tag == class_literal_term_e.DoubleQuoted:
                s = self.word_ev.EvalDoubleQuotedToString(term)
                spid = term.left.span_id

            elif term.tag == class_literal_term_e.BracedVarSub:
                s = self.word_ev.EvalBracedVarSubToString(term)
                spid = term.spids[0]

            elif term.tag == class_literal_term_e.SimpleVarSub:
                s = self.word_ev.EvalSimpleVarSubToString(term.token)
                spid = term.token.span_id

            elif term.tag == class_literal_term_e.CharLiteral:
                # What about \0?
                # At runtime, ERE should disallow it.  But we can also disallow it here.
                new_leaf = word_compile.EvalCharLiteralForRegex(term.tok)
                if new_leaf:
                    node.terms[i] = new_leaf

            if s is not None:
                # A string like '\x7f\xff' should be presented like
                if len(s) > 1:
                    for c in s:
                        if ord(c) > 128:
                            e_die(
                                "Express these bytes as character literals to avoid "
                                "confusing them with encoded characters",
                                span_id=spid)

                node.terms[i] = class_literal_term.ByteSet(s, spid)
Exemple #18
0
def _Maybe(obj):
    """
  func join(items Array[Str]) Str ...
  """
    if obj is None:
        return []

    # TODO: Need proper span IDs
    if not isinstance(obj, str):
        raise e_die('maybe() passed arg of invalid type %r',
                    obj.__class__.__name__)

    s = obj
    if len(s):
        return [s]
    else:
        return []
Exemple #19
0
  def _StringToInteger(self, s, span_id=runtime.NO_SPID):
    # type: (str, int) -> int
    """Use bash-like rules to coerce a string to an integer.

    Runtime parsing enables silly stuff like $(( $(echo 1)$(echo 2) + 1 )) => 13

    0xAB -- hex constant
    042  -- octal constant
    42   -- decimal constant
    64#z -- arbitary base constant

    bare word: variable
    quoted word: string (not done?)
    """
    if s.startswith('0x'):
      try:
        integer = int(s, 16)
      except ValueError:
        e_strict('Invalid hex constant %r', s, span_id=span_id)
      return integer

    if s.startswith('0'):
      try:
        integer = int(s, 8)
      except ValueError:
        e_strict('Invalid octal constant %r', s, span_id=span_id)
      return integer

    if '#' in s:
      b, digits = mylib.split_once(s, '#')
      try:
        base = int(b)
      except ValueError:
        e_strict('Invalid base for numeric constant %r',  b, span_id=span_id)

      integer = 0
      for ch in digits:
        if IsLower(ch):
          digit = ord(ch) - ord('a') + 10
        elif IsUpper(ch):
          digit = ord(ch) - ord('A') + 36
        elif ch == '@':  # horrible syntax
          digit = 62
        elif ch == '_':
          digit = 63
        elif ch.isdigit():
          digit = int(ch)
        else:
          e_strict('Invalid digits for numeric constant %r', digits, span_id=span_id)

        if digit >= base:
          e_strict('Digits %r out of range for base %d', digits, base, span_id=span_id)

        integer = integer * base + digit
      return integer

    try:
      # Normal base 10 integer.  This includes negative numbers like '-42'.
      integer = int(s)
    except ValueError:
      # doesn't look like an integer

      # note: 'test' and '[' never evaluate recursively
      if self.exec_opts.eval_unsafe_arith() and self.parse_ctx:
        # Special case so we don't get EOF error
        if len(s.strip()) == 0:
          return 0

        # For compatibility: Try to parse it as an expression and evaluate it.

        arena = self.parse_ctx.arena

        a_parser = self.parse_ctx.MakeArithParser(s)
        with alloc.ctx_Location(arena, source.Variable(span_id)):
          try:
            node2 = a_parser.Parse()  # may raise error.Parse
          except error.Parse as e:
            ui.PrettyPrintError(e, arena)
            e_die('Parse error in recursive arithmetic', span_id=e.span_id)

        # Prevent infinite recursion of $(( 1x )) -- it's a word that evaluates
        # to itself, and you don't want to reparse it as a word.
        if node2.tag_() == arith_expr_e.Word:
          e_die("Invalid integer constant %r", s, span_id=span_id)
        else:
          integer = self.EvalToInt(node2)
      else:
        if len(s.strip()) == 0 or match.IsValidVarName(s):
          # x42 could evaluate to 0
          e_strict("Invalid integer constant %r", s, span_id=span_id)
        else:
          # 42x is always fatal!
          e_die("Invalid integer constant %r", s, span_id=span_id)

    return integer
Exemple #20
0
    def _MaybeReplaceLeaf(self, node):
        # type: (re_t) -> Tuple[Optional[re_t], bool]
        """
    If a leaf node needs to be evaluated, do it and return the replacement.
    Otherwise return None.
    """
        new_leaf = None
        recurse = True

        if node.tag == re_e.Speck:
            id_ = node.id
            if id_ == Id.Expr_Dot:
                new_leaf = re.Primitive(Id.Re_Dot)
            elif id_ == Id.Arith_Caret:  # ^
                new_leaf = re.Primitive(Id.Re_Start)
            elif id_ == Id.Expr_Dollar:  # $
                new_leaf = re.Primitive(Id.Re_End)
            else:
                raise NotImplementedError(id_)

        elif node.tag == re_e.Token:
            id_ = node.id
            val = node.val

            if id_ == Id.Expr_Name:
                if val == 'dot':
                    new_leaf = re.Primitive(Id.Re_Dot)
                else:
                    raise NotImplementedError(val)

            elif id_ == Id.Expr_Symbol:
                if val == '%start':
                    new_leaf = re.Primitive(Id.Re_Start)
                elif val == '%end':
                    new_leaf = re.Primitive(Id.Re_End)
                else:
                    raise NotImplementedError(val)

            else:  # Must be Id.Char_{OneChar,Hex,Unicode4,Unicode8}
                kind = consts.GetKind(id_)
                assert kind == Kind.Char, id_
                s = word_compile.EvalCStringToken(node)
                new_leaf = re.LiteralChars(s, node.span_id)

        elif node.tag == re_e.SingleQuoted:
            s = word_eval.EvalSingleQuoted(node)
            new_leaf = re.LiteralChars(s, node.left.span_id)

        elif node.tag == re_e.DoubleQuoted:
            s = self.word_ev.EvalDoubleQuotedToString(node)
            new_leaf = re.LiteralChars(s, node.left.span_id)

        elif node.tag == re_e.BracedVarSub:
            s = self.word_ev.EvalBracedVarSubToString(node)
            new_leaf = re.LiteralChars(s, node.spids[0])

        elif node.tag == re_e.SimpleVarSub:
            s = self.word_ev.EvalSimpleVarSubToString(node.token)
            new_leaf = re.LiteralChars(s, node.token.span_id)

        elif node.tag == re_e.Splice:
            obj = self.LookupVar(node.name.val)
            if not isinstance(obj, objects.Regex):
                e_die("Can't splice object of type %r into regex",
                      obj.__class__,
                      token=node.name)
            # Note: we only splice the regex, and ignore flags.
            # Should we warn about this?
            new_leaf = obj.regex

        # These are leaves we don't need to do anything with.
        elif node.tag == re_e.PosixClass:
            recurse = False
        elif node.tag == re_e.PerlClass:
            recurse = False

        return new_leaf, recurse
Exemple #21
0
    def EvalExpr(self, node):
        # type: (expr_t) -> Any
        """
    This is a naive PyObject evaluator!  It uses the type dispatch of the host
    Python interpreter.

    Returns:
      A Python object of ANY type.  Should be wrapped in value.Obj() for
      storing in Mem.
    """
        if 0:
            print('EvalExpr()')
            node.PrettyPrint()
            print('')

        if node.tag == expr_e.Const:
            id_ = node.c.id

            if id_ == Id.Expr_DecInt:
                return int(node.c.val)
            elif id_ == Id.Expr_BinInt:
                return int(node.c.val, 2)
            elif id_ == Id.Expr_OctInt:
                return int(node.c.val, 8)
            elif id_ == Id.Expr_HexInt:
                return int(node.c.val, 16)

            elif id_ == Id.Expr_Float:
                return float(node.c.val)

            elif id_ == Id.Expr_Null:
                return None
            elif id_ == Id.Expr_True:
                return True
            elif id_ == Id.Expr_False:
                return False

            elif id_ == Id.Expr_Name:
                # for {name: 'bob'}
                # Maybe also :Symbol?
                return node.c.val

            # NOTE: We could allow Ellipsis for a[:, ...] here, but we're not using
            # it yet.
            raise AssertionError(id_)

        if node.tag == expr_e.Var:
            return self.LookupVar(node.name.val)

        if node.tag == expr_e.CommandSub:
            id_ = node.left_token.id
            # &(echo block literal)
            if id_ == Id.Left_AmpParen:
                return 'TODO: value.Block'
            else:
                stdout = self.shell_ex.RunCommandSub(node.child)
                if id_ == Id.Left_AtParen:  # @(seq 3)
                    strs = self.splitter.SplitForWordEval(stdout)
                    return strs
                else:
                    return stdout

        if node.tag == expr_e.ShArrayLiteral:
            words = braces.BraceExpandWords(node.words)
            strs = self.word_ev.EvalWordSequence(words)
            #log('ARRAY LITERAL EVALUATED TO -> %s', strs)
            return objects.StrArray(strs)

        if node.tag == expr_e.DoubleQuoted:
            # In an ideal world, I would *statically* disallow:
            # - "$@" and "${array[@]}"
            # - backticks like `echo hi`
            # - $(( 1+2 )) and $[] -- although useful for refactoring
            #   - not sure: ${x%%} -- could disallow this
            #     - these enters the ArgDQ state: "${a:-foo bar}" ?
            # But that would complicate the parser/evaluator.  So just rely on
            # strict_array to disallow the bad parts.
            return self.word_ev.EvalDoubleQuotedToString(node)

        if node.tag == expr_e.SingleQuoted:
            return word_eval.EvalSingleQuoted(node)

        if node.tag == expr_e.BracedVarSub:
            return self.word_ev.EvalBracedVarSubToString(node)

        if node.tag == expr_e.SimpleVarSub:
            return self.word_ev.EvalSimpleVarSubToString(node.token)

        if node.tag == expr_e.Unary:
            child = self.EvalExpr(node.child)
            if node.op.id == Id.Arith_Minus:
                return -child
            if node.op.id == Id.Arith_Tilde:
                return ~child
            if node.op.id == Id.Expr_Not:
                return not child

            raise NotImplementedError(node.op.id)

        if node.tag == expr_e.Binary:
            left = self.EvalExpr(node.left)
            right = self.EvalExpr(node.right)

            if node.op.id == Id.Arith_Plus:
                return left + right
            if node.op.id == Id.Arith_Minus:
                return left - right
            if node.op.id == Id.Arith_Star:
                return left * right
            if node.op.id == Id.Arith_Slash:
                # NOTE: from __future__ import division changes 5/2!
                # But just make it explicit.
                return float(left) / right  # floating point division

            if node.op.id == Id.Expr_DSlash:
                return left // right  # integer divison
            if node.op.id == Id.Arith_Percent:
                return left % right

            if node.op.id == Id.Arith_DStar:  # Exponentiation
                return left**right

            if node.op.id == Id.Arith_DPlus:
                # list or string concatenation
                return left + right

            # Bitwise
            if node.op.id == Id.Arith_Amp:
                return left & right
            if node.op.id == Id.Arith_Pipe:
                return left | right
            if node.op.id == Id.Arith_Caret:
                return left ^ right
            if node.op.id == Id.Arith_DGreat:
                return left >> right
            if node.op.id == Id.Arith_DLess:
                return left << right

            # Logical
            if node.op.id == Id.Expr_And:
                return left and right
            if node.op.id == Id.Expr_Or:
                return left or right

            raise NotImplementedError(node.op.id)

        if node.tag == expr_e.Range:  # 1:10  or  1:10:2
            lower = self.EvalExpr(node.lower)
            upper = self.EvalExpr(node.upper)
            return xrange(lower, upper)

        if node.tag == expr_e.Slice:  # a[:0]
            lower = self.EvalExpr(node.lower) if node.lower else None
            upper = self.EvalExpr(node.upper) if node.upper else None
            return slice(lower, upper)

        if node.tag == expr_e.Compare:
            left = self.EvalExpr(node.left)
            result = True  # Implicit and
            for op, right_expr in zip(node.ops, node.comparators):

                right = self.EvalExpr(right_expr)

                if op.id == Id.Arith_Less:
                    result = left < right
                elif op.id == Id.Arith_Great:
                    result = left > right
                elif op.id == Id.Arith_GreatEqual:
                    result = left >= right
                elif op.id == Id.Arith_LessEqual:
                    result = left <= right
                elif op.id == Id.Arith_DEqual:
                    result = left == right

                elif op.id == Id.Expr_In:
                    result = left in right
                elif op.id == Id.Node_NotIn:
                    result = left not in right

                elif op.id == Id.Expr_Is:
                    result = left is right
                elif op.id == Id.Node_IsNot:
                    result = left is not right

                elif op.id == Id.Expr_DTilde:
                    e_die('~~ not implemented')
                elif op.id == Id.Expr_NotDTilde:
                    e_die('!~~ not implemented')

                else:
                    try:
                        if op.id == Id.Arith_Tilde:
                            result = self._EvalMatch(left, right, True)

                        elif op.id == Id.Expr_NotTilde:
                            result = not self._EvalMatch(left, right, False)

                        else:
                            raise AssertionError(op.id)
                    except RuntimeError as e:
                        # Status 2 indicates a regex parse error.  This is fatal in OSH but
                        # not in bash, which treats [[ like a command with an exit code.
                        e_die("Invalid regex %r",
                              right,
                              span_id=op.span_id,
                              status=2)

                if not result:
                    return result

                left = right
            return result

        if node.tag == expr_e.IfExp:
            b = self.EvalExpr(node.test)
            if b:
                return self.EvalExpr(node.body)
            else:
                return self.EvalExpr(node.orelse)

        if node.tag == expr_e.List:
            return [self.EvalExpr(e) for e in node.elts]

        if node.tag == expr_e.Tuple:
            return tuple(self.EvalExpr(e) for e in node.elts)

        if node.tag == expr_e.Dict:
            # NOTE: some keys are expr.Const
            keys = [self.EvalExpr(e) for e in node.keys]

            values = []
            for i, e in enumerate(node.values):
                if e.tag == expr_e.Implicit:
                    v = self.LookupVar(keys[i])  # {name}
                else:
                    v = self.EvalExpr(e)
                values.append(v)

            return dict(zip(keys, values))

        if node.tag == expr_e.ListComp:

            # TODO:
            # - Consolidate with command_e.OilForIn in osh/cmd_eval.py?
            # - Do I have to push a temp frame here?
            #   Hm... lexical or dynamic scope is an issue.
            result = []
            comp = node.generators[0]
            obj = self.EvalExpr(comp.iter)

            # TODO: Handle x,y etc.
            iter_name = comp.lhs[0].name.val

            if isinstance(obj, str):
                e_die("Strings aren't iterable")
            else:
                it = obj.__iter__()

            while True:
                try:
                    loop_val = it.next()  # e.g. x
                except StopIteration:
                    break
                self.mem.SetVar(lvalue.Named(iter_name), value.Obj(loop_val),
                                scope_e.LocalOnly)

                if comp.cond:
                    b = self.EvalExpr(comp.cond)
                else:
                    b = True

                if b:
                    item = self.EvalExpr(node.elt)  # e.g. x*2
                    result.append(item)

            return result

        if node.tag == expr_e.GeneratorExp:
            comp = node.generators[0]
            obj = self.EvalExpr(comp.iter)

            # TODO: Support (x for x, y in ...)
            iter_name = comp.lhs[0].name.val

            it = obj.__iter__()

            # TODO: There is probably a much better way to do this!
            #       The scope of the loop variable is wrong, etc.

            def _gen():
                while True:
                    try:
                        loop_val = it.next()  # e.g. x
                    except StopIteration:
                        break
                    self.mem.SetVar(lvalue.Named(iter_name),
                                    value.Obj(loop_val), scope_e.LocalOnly)

                    if comp.cond:
                        b = self.EvalExpr(comp.cond)
                    else:
                        b = True

                    if b:
                        item = self.EvalExpr(node.elt)  # e.g. x*2
                        yield item

            return _gen()

        if node.tag == expr_e.Lambda:
            raise NotImplementedError()
            # This used to depend on cmd_ev, but we no longer have it.
            #return objects.Lambda(node, None)

        if node.tag == expr_e.FuncCall:
            func = self.EvalExpr(node.func)
            pos_args, named_args = self.EvalArgList(node.args)
            ret = func(*pos_args, **named_args)
            return ret

        if node.tag == expr_e.Subscript:
            obj = self.EvalExpr(node.obj)
            index = self._EvalIndices(node.indices)
            return obj[index]

        # TODO: obj.method() should be separate
        if node.tag == expr_e.Attribute:  # obj.attr
            o = self.EvalExpr(node.obj)
            id_ = node.op.id
            if id_ == Id.Expr_Dot:
                name = node.attr.val
                # TODO: Does this do the bound method thing we do NOT want?
                return getattr(o, name)

            if id_ == Id.Expr_RArrow:  # d->key is like d['key']
                name = node.attr.val
                return o[name]

            if id_ == Id.Expr_DColon:  # StaticName::member
                raise NotImplementedError(id_)

                # TODO: We should prevent virtual lookup here?  This is a pure static
                # namespace lookup?
                # But Python doesn't any hook for this.
                # Maybe we can just check that it's a module?  And modules don't lookup
                # in a supertype or __class__, etc.

            raise AssertionError(id_)

        if node.tag == expr_e.RegexLiteral:  # obj.attr
            # TODO: Should this just be an object that ~ calls?
            return objects.Regex(self.EvalRegex(node.regex))

        if node.tag == expr_e.ArrayLiteral:  # obj.attr
            items = [self.EvalExpr(item) for item in node.items]
            if items:
                # Determine type at runtime?  If we have something like @[(i) (j)]
                # then we don't know its type until runtime.

                first = items[0]
                if isinstance(first, bool):
                    return objects.BoolArray(bool(x) for x in items)
                elif isinstance(first, int):
                    return objects.IntArray(int(x) for x in items)
                elif isinstance(first, float):
                    return objects.FloatArray(float(x) for x in items)
                elif isinstance(first, str):
                    return objects.StrArray(str(x) for x in items)
                else:
                    raise AssertionError(first)
            else:
                # TODO: Should this have an unknown type?
                # What happens when you mutate or extend it?  You have to make sure
                # that the type tags match?
                return objects.BoolArray(items)

        raise NotImplementedError(node.__class__.__name__)
Exemple #22
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        """
    printf: printf [-v var] format [argument ...]
    """
        attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val)
        arg = arg_types.printf(attrs.attrs)

        fmt, fmt_spid = arg_r.ReadRequired2('requires a format string')
        varargs, spids = arg_r.Rest2()

        #log('fmt %s', fmt)
        #log('vals %s', vals)

        arena = self.parse_ctx.arena
        if fmt in self.parse_cache:
            parts = self.parse_cache[fmt]
        else:
            line_reader = reader.StringLineReader(fmt, arena)
            # TODO: Make public
            lexer = self.parse_ctx._MakeLexer(line_reader)
            parser = _FormatStringParser(lexer)

            with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)):
                try:
                    parts = parser.Parse()
                except error.Parse as e:
                    self.errfmt.PrettyPrintError(e)
                    return 2  # parse error

            self.parse_cache[fmt] = parts

        if 0:
            print()
            for part in parts:
                part.PrettyPrint()
                print()

        out = []  # type: List[str]
        arg_index = 0
        num_args = len(varargs)
        backslash_c = False

        while True:
            for part in parts:
                UP_part = part
                if part.tag_() == printf_part_e.Literal:
                    part = cast(printf_part__Literal, UP_part)
                    token = part.token
                    if token.id == Id.Format_EscapedPercent:
                        s = '%'
                    else:
                        s = word_compile.EvalCStringToken(token)
                    out.append(s)

                elif part.tag_() == printf_part_e.Percent:
                    part = cast(printf_part__Percent, UP_part)
                    flags = []  # type: List[str]
                    if len(part.flags) > 0:
                        for flag_token in part.flags:
                            flags.append(flag_token.val)

                    width = -1  # nonexistent
                    if part.width:
                        if part.width.id in (Id.Format_Num, Id.Format_Zero):
                            width_str = part.width.val
                            width_spid = part.width.span_id
                        elif part.width.id == Id.Format_Star:
                            if arg_index < num_args:
                                width_str = varargs[arg_index]
                                width_spid = spids[arg_index]
                                arg_index += 1
                            else:
                                width_str = ''  # invalid
                                width_spid = runtime.NO_SPID
                        else:
                            raise AssertionError()

                        try:
                            width = int(width_str)
                        except ValueError:
                            if width_spid == runtime.NO_SPID:
                                width_spid = part.width.span_id
                            self.errfmt.Print_("printf got invalid width %r" %
                                               width_str,
                                               span_id=width_spid)
                            return 1

                    precision = -1  # nonexistent
                    if part.precision:
                        if part.precision.id == Id.Format_Dot:
                            precision_str = '0'
                            precision_spid = part.precision.span_id
                        elif part.precision.id in (Id.Format_Num,
                                                   Id.Format_Zero):
                            precision_str = part.precision.val
                            precision_spid = part.precision.span_id
                        elif part.precision.id == Id.Format_Star:
                            if arg_index < num_args:
                                precision_str = varargs[arg_index]
                                precision_spid = spids[arg_index]
                                arg_index += 1
                            else:
                                precision_str = ''
                                precision_spid = runtime.NO_SPID
                        else:
                            raise AssertionError()

                        try:
                            precision = int(precision_str)
                        except ValueError:
                            if precision_spid == runtime.NO_SPID:
                                precision_spid = part.precision.span_id
                            self.errfmt.Print_(
                                'printf got invalid precision %r' %
                                precision_str,
                                span_id=precision_spid)
                            return 1

                    #log('index=%d n=%d', arg_index, num_args)
                    if arg_index < num_args:
                        s = varargs[arg_index]
                        word_spid = spids[arg_index]
                        arg_index += 1
                    else:
                        s = ''
                        word_spid = runtime.NO_SPID

                    typ = part.type.val
                    if typ == 's':
                        if precision >= 0:
                            s = s[:precision]  # truncate

                    elif typ == 'q':
                        s = qsn.maybe_shell_encode(s)

                    elif typ == 'b':
                        # Process just like echo -e, except \c handling is simpler.

                        c_parts = []  # type: List[str]
                        lex = match.EchoLexer(s)
                        while True:
                            id_, tok_val = lex.Next()
                            if id_ == Id.Eol_Tok:  # Note: This is really a NUL terminator
                                break

                            # TODO: add span_id from argv
                            tok = Token(id_, runtime.NO_SPID, tok_val)
                            p = word_compile.EvalCStringToken(tok)

                            # Unusual behavior: '\c' aborts processing!
                            if p is None:
                                backslash_c = True
                                break

                            c_parts.append(p)
                        s = ''.join(c_parts)

                    elif typ in 'diouxX' or part.type.id == Id.Format_Time:
                        try:
                            d = int(s)
                        except ValueError:
                            if len(s) >= 1 and s[0] in '\'"':
                                # TODO: utf-8 decode s[1:] to be more correct.  Probably
                                # depends on issue #366, a utf-8 library.
                                # Note: len(s) == 1 means there is a NUL (0) after the quote..
                                d = ord(s[1]) if len(s) >= 2 else 0
                            elif part.type.id == Id.Format_Time and len(
                                    s) == 0 and word_spid == runtime.NO_SPID:
                                # Note: No argument means -1 for %(...)T as in Bash Reference
                                #   Manual 4.2 "If no argument is specified, conversion behaves
                                #   as if -1 had been given."
                                d = -1
                            else:
                                if word_spid == runtime.NO_SPID:
                                    # Blame the format string
                                    blame_spid = part.type.span_id
                                else:
                                    blame_spid = word_spid
                                self.errfmt.Print_(
                                    'printf expected an integer, got %r' % s,
                                    span_id=blame_spid)
                                return 1

                        if typ in 'di':
                            s = str(d)
                        elif typ in 'ouxX':
                            if d < 0:
                                e_die(
                                    "Can't format negative number %d with %%%s",
                                    d,
                                    typ,
                                    span_id=part.type.span_id)
                            if typ == 'u':
                                s = str(d)
                            elif typ == 'o':
                                s = mylib.octal(d)
                            elif typ == 'x':
                                s = mylib.hex_lower(d)
                            elif typ == 'X':
                                s = mylib.hex_upper(d)

                        elif part.type.id == Id.Format_Time:
                            # %(...)T

                            # Initialize timezone:
                            #   `localtime' uses the current timezone information initialized
                            #   by `tzset'.  The function `tzset' refers to the environment
                            #   variable `TZ'.  When the exported variable `TZ' is present,
                            #   its value should be reflected in the real environment
                            #   variable `TZ' before call of `tzset'.
                            #
                            # Note: unlike LANG, TZ doesn't seem to change behavior if it's
                            # not exported.
                            #
                            # TODO: In Oil, provide an API that doesn't rely on libc's
                            # global state.

                            tzcell = self.mem.GetCell('TZ')
                            if tzcell and tzcell.exported and tzcell.val.tag_(
                            ) == value_e.Str:
                                tzval = cast(value__Str, tzcell.val)
                                posix.putenv('TZ', tzval.s)

                            time_.tzset()

                            # Handle special values:
                            #   User can specify two special values -1 and -2 as in Bash
                            #   Reference Manual 4.2: "Two special argument values may be
                            #   used: -1 represents the current time, and -2 represents the
                            #   time the shell was invoked." from
                            #   https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf
                            if d == -1:  # the current time
                                ts = time_.time()
                            elif d == -2:  # the shell start time
                                ts = self.shell_start_time
                            else:
                                ts = d

                            s = time_.strftime(typ[1:-2], time_.localtime(ts))
                            if precision >= 0:
                                s = s[:precision]  # truncate

                        else:
                            raise AssertionError()

                    else:
                        raise AssertionError()

                    if width >= 0:
                        if len(flags):
                            if '-' in flags:
                                s = s.ljust(width, ' ')
                            elif '0' in flags:
                                s = s.rjust(width, '0')
                            else:
                                pass
                        else:
                            s = s.rjust(width, ' ')

                    out.append(s)

                else:
                    raise AssertionError()

                if backslash_c:  # 'printf %b a\cb xx' - \c terminates processing!
                    break

            if arg_index >= num_args:
                break
            # Otherwise there are more args.  So cycle through the loop once more to
            # implement the 'arg recycling' behavior.

        result = ''.join(out)
        if arg.v is not None:
            # TODO: get the span_id for arg.v!
            v_spid = runtime.NO_SPID

            arena = self.parse_ctx.arena
            a_parser = self.parse_ctx.MakeArithParser(arg.v)

            with alloc.ctx_Location(arena, source.ArgvWord(v_spid)):
                try:
                    anode = a_parser.Parse()
                except error.Parse as e:
                    ui.PrettyPrintError(e, arena)  # show parse error
                    e_usage('Invalid -v expression', span_id=v_spid)

            lval = self.arith_ev.EvalArithLhs(anode, v_spid)

            if not self.exec_opts.eval_unsafe_arith(
            ) and lval.tag_() != lvalue_e.Named:
                e_usage(
                    '-v expected a variable name.  shopt -s eval_unsafe_arith allows expressions',
                    span_id=v_spid)

            state.SetRef(self.mem, lval, value.Str(result))
        else:
            mylib.Stdout().write(result)
        return 0
Exemple #23
0
def DoUnarySuffixOp(s, op, arg, extglob):
    # type: (str, suffix_op__Unary, str, bool) -> str
    """Helper for ${x#prefix} and family."""

    # Fast path for constant strings.
    if not glob_.LooksLikeGlob(arg):
        # It doesn't look like a glob, but we glob-escaped it (e.g. [ -> \[).  So
        # reverse it.  NOTE: We also do this check in Globber.Expand().  It would
        # be nice to somehow store the original string rather tahn
        # escaping/unescaping.
        arg = glob_.GlobUnescape(arg)

        if op.op_id in (Id.VOp1_Pound, Id.VOp1_DPound):  # const prefix
            # explicit check for non-empty arg (len for mycpp)
            if len(arg) and s.startswith(arg):
                return s[len(arg):]
            else:
                return s

        elif op.op_id in (Id.VOp1_Percent, Id.VOp1_DPercent):  # const suffix
            # need explicit check for non-empty arg (len for mycpp)
            if len(arg) and s.endswith(arg):
                return s[:-len(arg)]
            else:
                return s

        # These operators take glob arguments, we don't implement that obscure case.
        elif op.op_id == Id.VOp1_Comma:  # Only lowercase the first letter
            if arg != '':
                # TODO: location info for op
                e_die("%s can't have an argument", ui.PrettyId(op.op_id))
            if len(s):
                return s[0].lower() + s[1:]
            else:
                return s

        elif op.op_id == Id.VOp1_DComma:
            if arg != '':
                e_die("%s can't have an argument", ui.PrettyId(op.op_id))
            return s.lower()

        elif op.op_id == Id.VOp1_Caret:  # Only uppercase the first letter
            if arg != '':
                e_die("%s can't have an argument", ui.PrettyId(op.op_id))
            if len(s):
                return s[0].upper() + s[1:]
            else:
                return s

        elif op.op_id == Id.VOp1_DCaret:
            if arg != '':
                e_die("%s can't have an argument", ui.PrettyId(op.op_id))
            return s.upper()

        else:  # e.g. ^ ^^ , ,,
            raise AssertionError(op.op_id)

    # For patterns, do fnmatch() in a loop.
    #
    # TODO:
    # - Another potential fast path:
    #   v=aabbccdd
    #   echo ${v#*b}  # strip shortest prefix
    #
    # If the whole thing doesn't match '*b*', then no test can succeed.  So we
    # can fail early.  Conversely echo ${v%%c*} and '*c*'.
    #
    # (Although honestly this whole construct is nuts and should be deprecated.)

    n = len(s)

    if op.op_id == Id.VOp1_Pound:  # shortest prefix
        # 'abcd': match '', 'a', 'ab', 'abc', ...
        i = 0
        while True:
            assert i <= n
            #log('Matching pattern %r with %r', arg, s[:i])
            if libc.fnmatch(arg, s[:i], extglob):
                return s[i:]
            if i >= n:
                break
            i = _NextUtf8Char(s, i)
        return s

    elif op.op_id == Id.VOp1_DPound:  # longest prefix
        # 'abcd': match 'abc', 'ab', 'a'
        i = n
        while True:
            assert i >= 0
            #log('Matching pattern %r with %r', arg, s[:i])
            if libc.fnmatch(arg, s[:i], extglob):
                return s[i:]
            if i == 0:
                break
            i = PreviousUtf8Char(s, i)
        return s

    elif op.op_id == Id.VOp1_Percent:  # shortest suffix
        # 'abcd': match 'abcd', 'abc', 'ab', 'a'
        i = n
        while True:
            assert i >= 0
            #log('Matching pattern %r with %r', arg, s[:i])
            if libc.fnmatch(arg, s[i:], extglob):
                return s[:i]
            if i == 0:
                break
            i = PreviousUtf8Char(s, i)
        return s

    elif op.op_id == Id.VOp1_DPercent:  # longest suffix
        # 'abcd': match 'abc', 'bc', 'c', ...
        i = 0
        while True:
            assert i <= n
            #log('Matching pattern %r with %r', arg, s[:i])
            if libc.fnmatch(arg, s[i:], extglob):
                return s[:i]
            if i >= n:
                break
            i = _NextUtf8Char(s, i)
        return s

    else:
        raise NotImplementedError(ui.PrettyId(op.op_id))
Exemple #24
0
  def RunSimpleCommand(self, cmd_val, do_fork, call_procs=True):
    # type: (cmd_value__Argv, bool, bool) -> int
    """
    Run builtins, functions, external commands

    Oil and other languages might have different, simpler rules.  No special
    builtins, etc.

    Oil might have OIL_PATH = @( ... ) or something.

    Interpreters might want to define all their own builtins.

    Args:
      procs: whether to look up procs.
    """
    argv = cmd_val.argv
    span_id = cmd_val.arg_spids[0] if len(cmd_val.arg_spids) else runtime.NO_SPID

    # This happens when you write "$@" but have no arguments.
    if len(argv) == 0:
      if self.exec_opts.strict_argv():
        e_die("Command evaluated to an empty argv array", span_id=span_id)
      else:
        return 0  # status 0, or skip it?

    arg0 = argv[0]

    builtin_id = consts.LookupAssignBuiltin(arg0)
    if builtin_id != consts.NO_INDEX:
      # command readonly is disallowed, for technical reasons.  Could relax it
      # later.
      self.errfmt.Print_("Can't run assignment builtin recursively",
                        span_id=span_id)
      return 1

    builtin_id = consts.LookupSpecialBuiltin(arg0)
    if builtin_id != consts.NO_INDEX:
      status = self.RunBuiltin(builtin_id, cmd_val)
      # TODO: Enable this and fix spec test failures.
      # Also update _SPECIAL_BUILTINS in osh/builtin.py.
      #if status != 0:
      #  e_die('special builtin failed', status=status)
      return status

    # TODO: if shopt -s namespaces, then look up in current namespace FIRST.
    #
    # Then fallback on self.procs, which should be renamed self.procs?
    #
    # honestly there is no real chance of colllision because
    # foo-bar() {} can't be accessed anyway
    # functions can have hyphens, but variables can't

    # Builtins like 'true' can be redefined as functions.
    if call_procs:
      proc_node = self.procs.get(arg0)
      if proc_node is not None:
        if (self.exec_opts.strict_errexit() and 
            self.mutable_opts.ErrExitIsDisabled()):
          self.errfmt.Print_('errexit was disabled for this construct',
                             span_id=self.mutable_opts.ErrExitSpanId())
          self.errfmt.StderrLine('')
          e_die("Can't run a proc while errexit is disabled. "
                "Use 'run' or wrap it in a process with $0 myproc",
                span_id=span_id)

        # NOTE: Functions could call 'exit 42' directly, etc.
        status = self.cmd_ev.RunProc(proc_node, argv[1:])
        return status

      # TODO:
      # look up arg0 in global namespace?  And see if the type is value.Obj
      # And it's a proc?
      # isinstance(val.obj, objects.Proc)
      UP_val = self.mem.GetVar(arg0)

      if mylib.PYTHON:  # Not reusing CPython objects
        if UP_val.tag_() == value_e.Obj:
          val = cast(value__Obj, UP_val)
          if isinstance(val.obj, objects.Proc):
            status = self.cmd_ev.RunOilProc(val.obj, argv[1:])
            return status

    builtin_id = consts.LookupNormalBuiltin(arg0)

    if builtin_id != consts.NO_INDEX:
      return self.RunBuiltin(builtin_id, cmd_val)

    environ = self.mem.GetExported()  # Include temporary variables

    if cmd_val.block:
      e_die('Unexpected block passed to external command %r', arg0,
            span_id=cmd_val.block.spids[0])

    # Resolve argv[0] BEFORE forking.
    argv0_path = self.search_path.CachedLookup(arg0)
    if argv0_path is None:
      self.errfmt.Print_('%r not found' % arg0, span_id=span_id)
      return 127

    # Normal case: ls /
    if do_fork:
      thunk = process.ExternalThunk(self.ext_prog, argv0_path, cmd_val, environ)
      p = process.Process(thunk, self.job_state)
      status = p.Run(self.waiter)
      return status

    # Already forked for pipeline: ls / | wc -l
    # TODO: count subshell?  ( ls / ) vs. ( ls /; ls / )
    self.ext_prog.Exec(argv0_path, cmd_val, environ)  # NEVER RETURNS
    assert False, "This line should never be reached" # makes mypy happy
Exemple #25
0
  def RunCommandSub(self, cs_part):
    # type: (command_sub) -> str

    if not self.exec_opts.allow_command_sub():
      # TODO: Add spid of $(
      e_die("Command subs not allowed when errexit disabled (strict_errexit)")

    node = cs_part.child

    # Hack for weird $(<file) construct
    if node.tag_() == command_e.Simple:
      simple = cast(command__Simple, node)
      # Detect '< file'
      if (len(simple.words) == 0 and
          len(simple.redirects) == 1 and
          simple.redirects[0].op.id == Id.Redir_Less):
        # change it to __cat < file
        # note: cmd_eval.py _Dispatch works around lack of spid
        tok = Token(Id.Lit_Chars, runtime.NO_SPID, '__cat')
        cat_word = compound_word([tok])
        # MUTATE the command.Simple node.  This will only be done the first
        # time in the parent process.
        simple.words.append(cat_word)

    p = self._MakeProcess(node,
                          inherit_errexit=self.exec_opts.inherit_errexit())

    r, w = posix.pipe()
    p.AddStateChange(process.StdoutToPipe(r, w))
    _ = p.Start()
    #log('Command sub started %d', pid)

    chunks = []  # type: List[str]
    posix.close(w)  # not going to write
    while True:
      byte_str = posix.read(r, 4096)
      if len(byte_str) == 0:
        break
      chunks.append(byte_str)
    posix.close(r)

    status = p.Wait(self.waiter)

    # OSH has the concept of aborting in the middle of a WORD.  We're not
    # waiting until the command is over!
    if self.exec_opts.command_sub_errexit():
      if status != 0:
        raise error.ErrExit(
            'Command sub exited with status %d (%s)' %
            (status, ui.CommandType(node)), span_id=cs_part.left_token.span_id,
            status=status)

    else:
      # Set a flag so we check errexit at the same time as bash.  Example:
      #
      # a=$(false)
      # echo foo  # no matter what comes here, the flag is reset
      #
      # Set ONLY until this command node has finished executing.

      # HACK: move this
      self.cmd_ev.check_command_sub_status = True
      self.mem.SetLastStatus(status)

    # Runtime errors test case: # $("echo foo > $@")
    # Why rstrip()?
    # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char
    return ''.join(chunks).rstrip('\n')
Exemple #26
0
def OldValue(lval, mem, exec_opts):
  # type: (lvalue_t, Mem, optview.Exec) -> value_t
  """
  Used by s+='x' and (( i += 1 ))

  TODO: We need a stricter and less ambiguous version for Oil.

  Problem:

  - why does lvalue have Indexed and Keyed, while sh_lhs_expr only has
    IndexedName?
    - should I have lvalue.Named and lvalue.Indexed only?
    - and Indexed uses the index_t type?
      - well that might be Str or Int
  """
  assert isinstance(lval, lvalue_t), lval

  # TODO: refactor lvalue_t to make this simpler
  UP_lval = lval
  with tagswitch(lval) as case:
    if case(lvalue_e.Named):  # (( i++ ))
      lval = cast(lvalue__Named, UP_lval)
      var_name = lval.name
    elif case(lvalue_e.Indexed):  # (( a[i]++ ))
      lval = cast(lvalue__Indexed, UP_lval)
      var_name = lval.name
    elif case(lvalue_e.Keyed):  # (( A['K']++ )) ?  I think this works
      lval = cast(lvalue__Keyed, UP_lval)
      var_name = lval.name
    else:
      raise AssertionError()

  val = _LookupVar(var_name, mem, exec_opts)

  UP_val = val
  with tagswitch(lval) as case:
    if case(lvalue_e.Named):
      return val

    elif case(lvalue_e.Indexed):
      lval = cast(lvalue__Indexed, UP_lval)

      array_val = None  # type: value__MaybeStrArray
      with tagswitch(val) as case2:
        if case2(value_e.Undef):
          array_val = value.MaybeStrArray([])
        elif case2(value_e.MaybeStrArray):
          tmp = cast(value__MaybeStrArray, UP_val)
          # mycpp rewrite: add tmp.  cast() creates a new var in inner scope
          array_val = tmp
        else:
          e_die("Can't use [] on value of type %s", ui.ValType(val))

      s = word_eval.GetArrayItem(array_val.strs, lval.index)

      if s is None:
        val = value.Str('')  # NOTE: Other logic is value.Undef()?  0?
      else:
        assert isinstance(s, str), s
        val = value.Str(s)

    elif case(lvalue_e.Keyed):
      lval = cast(lvalue__Keyed, UP_lval)

      assoc_val = None  # type: value__AssocArray
      with tagswitch(val) as case2:
        if case2(value_e.Undef):
          # This never happens, because undef[x]+= is assumed to
          raise AssertionError()
        elif case2(value_e.AssocArray):
          tmp2 = cast(value__AssocArray, UP_val)
          # mycpp rewrite: add tmp.  cast() creates a new var in inner scope
          assoc_val = tmp2
        else:
          e_die("Can't use [] on value of type %s", ui.ValType(val))

      s = assoc_val.d.get(lval.key)
      if s is None:
        val = value.Str('')
      else:
        val = value.Str(s)

    else:
      raise AssertionError()

  return val
Exemple #27
0
  def EvalB(self, node):
    # type: (bool_expr_t) -> bool

    UP_node = node
    with tagswitch(node) as case:
      if case(bool_expr_e.WordTest):
        node = cast(bool_expr__WordTest, UP_node)
        s = self._EvalCompoundWord(node.w)
        return bool(s)

      elif case(bool_expr_e.LogicalNot):
        node = cast(bool_expr__LogicalNot, UP_node)
        b = self.EvalB(node.child)
        return not b

      elif case(bool_expr_e.LogicalAnd):
        node = cast(bool_expr__LogicalAnd, UP_node)
        # Short-circuit evaluation
        if self.EvalB(node.left):
          return self.EvalB(node.right)
        else:
          return False

      elif case(bool_expr_e.LogicalOr):
        node = cast(bool_expr__LogicalOr, UP_node)
        if self.EvalB(node.left):
          return True
        else:
          return self.EvalB(node.right)

      elif case(bool_expr_e.Unary):
        node = cast(bool_expr__Unary, UP_node)
        op_id = node.op_id
        s = self._EvalCompoundWord(node.child)

        # Now dispatch on arg type
        arg_type = consts.BoolArgType(op_id)  # could be static in the LST?

        if arg_type == bool_arg_type_e.Path:
          return bool_stat.DoUnaryOp(op_id, s)

        if arg_type == bool_arg_type_e.Str:
          if op_id == Id.BoolUnary_z:
            return not bool(s)
          if op_id == Id.BoolUnary_n:
            return bool(s)

          raise AssertionError(op_id)  # should never happen

        if arg_type == bool_arg_type_e.Other:
          if op_id == Id.BoolUnary_t:
            return bool_stat.isatty(s, node.child)

          # See whether 'set -o' options have been set
          if op_id == Id.BoolUnary_o:
            index = match.MatchOption(s)
            if index == 0:
              return False
            else:
              return self.exec_opts.opt_array[index]

          if op_id == Id.BoolUnary_v:
            val = self.mem.GetVar(s)
            return val.tag_() != value_e.Undef

          e_die("%s isn't implemented", ui.PrettyId(op_id))  # implicit location

        raise AssertionError(arg_type)  # should never happen

      elif case(bool_expr_e.Binary):
        node = cast(bool_expr__Binary, UP_node)

        op_id = node.op_id
        # Whether to glob escape
        with switch(op_id) as case2:
          if case2(Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual,
                   Id.BoolBinary_GlobNEqual):
            quote_kind = quote_e.FnMatch
          elif case2(Id.BoolBinary_EqualTilde):
            quote_kind = quote_e.ERE
          else:
            quote_kind = quote_e.Default

        s1 = self._EvalCompoundWord(node.left)
        s2 = self._EvalCompoundWord(node.right, quote_kind=quote_kind)

        # Now dispatch on arg type
        arg_type = consts.BoolArgType(op_id)

        if arg_type == bool_arg_type_e.Path:
          return bool_stat.DoBinaryOp(op_id, s1, s2)

        if arg_type == bool_arg_type_e.Int:
          # NOTE: We assume they are constants like [[ 3 -eq 3 ]].
          # Bash also allows [[ 1+2 -eq 3 ]].
          i1 = self._StringToIntegerOrError(s1, blame_word=node.left)
          i2 = self._StringToIntegerOrError(s2, blame_word=node.right)

          if op_id == Id.BoolBinary_eq:
            return i1 == i2
          if op_id == Id.BoolBinary_ne:
            return i1 != i2
          if op_id == Id.BoolBinary_gt:
            return i1 > i2
          if op_id == Id.BoolBinary_ge:
            return i1 >= i2
          if op_id == Id.BoolBinary_lt:
            return i1 < i2
          if op_id == Id.BoolBinary_le:
            return i1 <= i2

          raise AssertionError(op_id)  # should never happen

        if arg_type == bool_arg_type_e.Str:

          if op_id in (Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual):
            #log('Matching %s against pattern %s', s1, s2)
            return libc.fnmatch(s2, s1, self.exec_opts.extglob())

          if op_id == Id.BoolBinary_GlobNEqual:
            return not libc.fnmatch(s2, s1, self.exec_opts.extglob())

          if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
            return s1 == s2

          if op_id == Id.BoolBinary_NEqual:
            return s1 != s2

          if op_id == Id.BoolBinary_EqualTilde:
            # TODO: This should go to --debug-file
            #log('Matching %r against regex %r', s1, s2)
            try:
              matches = libc.regex_match(s2, s1)
            except RuntimeError as e:
              # Status 2 indicates a regex parse error.  This is fatal in OSH but
              # not in bash, which treats [[ like a command with an exit code.
              msg = e.message  # type: str
              e_die("Invalid regex %r: %s", s2, msg, word=node.right,
                    status=2)

            if matches is None:
              return False

            self._SetRegexMatches(matches)
            return True

          if op_id == Id.Op_Less:
            return str_cmp(s1, s2) < 0

          if op_id == Id.Op_Great:
            return str_cmp(s1, s2) > 0

          raise AssertionError(op_id)  # should never happen

    raise AssertionError(node.tag_())
Exemple #28
0
    def Run(self, cmd_val):
        # type: (cmd_value__Argv) -> int
        attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val)
        arg = arg_types.read(attrs.attrs)
        names = arg_r.Rest()

        # Don't respect any of the other options here?  This is buffered I/O.
        if arg.line:  # read --line
            var_name, var_spid = arg_r.Peek2()
            if var_name is None:
                var_name = '_line'
            else:
                if var_name.startswith(':'):  # optional : sigil
                    var_name = var_name[1:]
                arg_r.Next()

            next_arg, next_spid = arg_r.Peek2()
            if next_arg is not None:
                raise error.Usage('got extra argument', span_id=next_spid)

            return self._Line(arg, var_name)

        if arg.q:
            e_usage('--qsn can only be used with --line')

        if arg.all:  # read --all
            var_name, var_spid = arg_r.Peek2()
            if var_name is None:
                var_name = '_all'
            else:
                if var_name.startswith(':'):  # optional : sigil
                    var_name = var_name[1:]
                arg_r.Next()

            next_arg, next_spid = arg_r.Peek2()
            if next_arg is not None:
                raise error.Usage('got extra argument', span_id=next_spid)

            return self._All(var_name)

        if arg.q:
            e_usage('--qsn not implemented yet')

        fd = self.stdin.fileno()

        if arg.t >= 0.0:
            if arg.t != 0.0:
                e_die("read -t isn't implemented (except t=0)")
            else:
                return 0 if pyos.InputAvailable(fd) else 1

        bits = 0
        if self.stdin.isatty():
            bits |= pyos.TERM_ICANON
            if arg.s:  # silent
                bits |= pyos.TERM_ECHO

            if arg.p is not None:  # only if tty
                mylib.Stderr().write(arg.p)

        if bits == 0:
            status = self._Read(arg, names)
        else:
            term = pyos.TermState(fd, ~bits)
            try:
                status = self._Read(arg, names)
            finally:
                term.Restore()
        return status
Exemple #29
0
def AsPosixEre(node, parts):
    # type: (re_t, List[str]) -> None
    """Translate an Oil regex to a POSIX ERE.

  Appends to a list of parts that you hvae to join.
  """
    UP_node = node
    tag = node.tag_()

    if tag == re_e.Primitive:
        node = cast(re__Primitive, UP_node)
        if node.id == Id.Re_Dot:
            parts.append('.')
        elif node.id == Id.Re_Start:
            parts.append('^')
        elif node.id == Id.Re_End:
            parts.append('$')
        else:
            raise AssertionError(node.id)
        return

    if tag == re_e.LiteralChars:
        node = cast(re__LiteralChars, UP_node)
        # The bash [[ x =~ "." ]] construct also has to do this

        # TODO: What about \0 and unicode escapes?
        # Those won't be as LiteralChars I don't think?
        # Unless you put them there through \0
        # Maybe DISALLOW those.
        # "Unprintable chars should be written as \0 or \x00 or \u0000"

        parts.append(glob_.ExtendedRegexEscape(node.s))
        return

    if tag == re_e.Seq:
        node = cast(re__Seq, UP_node)
        for c in node.children:
            AsPosixEre(c, parts)
        return

    if tag == re_e.Alt:
        node = cast(re__Alt, UP_node)
        for i, c in enumerate(node.children):
            if i != 0:
                parts.append('|')
            AsPosixEre(c, parts)
        return

    if tag == re_e.Repeat:
        node = cast(re__Repeat, UP_node)
        # 'foo' or "foo" or $x or ${x} evaluated to too many chars
        if node.child.tag_() == re_e.LiteralChars:
            child = cast(re__LiteralChars, node.child)
            if len(child.s) > 1:
                # Note: Other regex dialects have non-capturing groups since we don't
                # need this.
                e_die(
                    "POSIX EREs don't have groups without capture, so this node "
                    "needs () around it.",
                    span_id=child.spid)

        AsPosixEre(node.child, parts)
        op = node.op
        op_tag = op.tag_()
        UP_op = op

        if op_tag == re_repeat_e.Op:
            op = cast(re_repeat__Op, UP_op)
            op_id = op.op.id
            if op_id == Id.Arith_Plus:
                parts.append('+')
            elif op_id == Id.Arith_Star:
                parts.append('*')
            elif op_id == Id.Arith_QMark:
                parts.append('?')
            else:
                raise AssertionError(op_id)
            return

        if op_tag == re_repeat_e.Num:
            op = cast(re_repeat__Num, UP_op)
            parts.append('{%s}' % op.times.val)
            return

        if op_tag == re_repeat_e.Range:
            op = cast(re_repeat__Range, UP_op)
            lower = op.lower.val if op.lower else ''
            upper = op.upper.val if op.upper else ''
            parts.append('{%s,%s}' % (lower, upper))
            return

        raise NotImplementedError(op_tag)

    # Special case for familiarity: () is acceptable as a group in ERE
    if tag in (re_e.Group, re_e.Capture):
        node = cast(re__Group, UP_node)
        parts.append('(')
        AsPosixEre(node.child, parts)
        parts.append(')')
        return

    if tag == re_e.PerlClass:
        node = cast(perl_class, UP_node)
        n = node.name
        chars = PERL_CLASS[node.name]  # looks like [:digit:]
        if node.negated:
            pat = '[^%s]' % chars
        else:
            pat = '[%s]' % chars
        parts.append(pat)
        return

    if tag == re_e.PosixClass:
        node = cast(posix_class, UP_node)
        n = node.name  # looks like 'digit'
        if node.negated:
            pat = '[^[:%s:]]' % n
        else:
            pat = '[[:%s:]]' % n
        parts.append(pat)
        return

    if tag == re_e.ClassLiteral:
        node = cast(re__ClassLiteral, UP_node)
        parts.append('[')
        if node.negated:
            parts.append('^')
        for term in node.terms:
            _ClassLiteralToPosixEre(term, parts)
        parts.append(']')
        return

    raise NotImplementedError(tag)
Exemple #30
0
  def Eval(self, node):
    # type: (arith_expr_t) -> value_t
    """
    Args:
      node: arith_expr_t

    Returns:
      None for Undef  (e.g. empty cell)  TODO: Don't return 0!
      int for Str
      List[int] for MaybeStrArray
      Dict[str, str] for AssocArray (TODO: Should we support this?)

    NOTE: (( A['x'] = 'x' )) and (( x = A['x'] )) are syntactically valid in
    bash, but don't do what you'd think.  'x' sometimes a variable name and
    sometimes a key.
    """
    # OSH semantics: Variable NAMES cannot be formed dynamically; but INTEGERS
    # can.  ${foo:-3}4 is OK.  $? will be a compound word too, so we don't have
    # to handle that as a special case.

    UP_node = node
    with tagswitch(node) as case:
      if case(arith_expr_e.VarRef):  # $(( x ))  (can be array)
        tok = cast(Token, UP_node)
        return _LookupVar(tok.val, self.mem, self.exec_opts)

      elif case(arith_expr_e.Word):  # $(( $x )) $(( ${x}${y} )), etc.
        w = cast(compound_word, UP_node)
        return self.word_ev.EvalWordToString(w)

      elif case(arith_expr_e.UnaryAssign):  # a++
        node = cast(arith_expr__UnaryAssign, UP_node)

        op_id = node.op_id
        old_int, lval = self._EvalLhsAndLookupArith(node.child)

        if op_id == Id.Node_PostDPlus:  # post-increment
          new_int = old_int + 1
          ret = old_int

        elif op_id == Id.Node_PostDMinus:  # post-decrement
          new_int = old_int - 1
          ret = old_int

        elif op_id == Id.Arith_DPlus:  # pre-increment
          new_int = old_int + 1
          ret = new_int

        elif op_id == Id.Arith_DMinus:  # pre-decrement
          new_int = old_int - 1
          ret = new_int

        else:
          raise AssertionError(op_id)

        #log('old %d new %d ret %d', old_int, new_int, ret)
        self._Store(lval, new_int)
        return value.Int(ret)

      elif case(arith_expr_e.BinaryAssign):  # a=1, a+=5, a[1]+=5
        node = cast(arith_expr__BinaryAssign, UP_node)
        op_id = node.op_id

        if op_id == Id.Arith_Equal:
          # Don't really need a span ID here, because tdop.CheckLhsExpr should
          # have done all the validation.
          lval = self.EvalArithLhs(node.left, runtime.NO_SPID)
          rhs_int = self.EvalToInt(node.right)

          self._Store(lval, rhs_int)
          return value.Int(rhs_int)

        old_int, lval = self._EvalLhsAndLookupArith(node.left)
        rhs = self.EvalToInt(node.right)

        if op_id == Id.Arith_PlusEqual:
          new_int = old_int + rhs
        elif op_id == Id.Arith_MinusEqual:
          new_int = old_int - rhs
        elif op_id == Id.Arith_StarEqual:
          new_int = old_int * rhs

        elif op_id == Id.Arith_SlashEqual:
          if rhs == 0:
            e_die('Divide by zero')  # TODO: location
          new_int = old_int / rhs

        elif op_id == Id.Arith_PercentEqual:
          if rhs == 0:
            e_die('Divide by zero')  # TODO: location
          new_int = old_int % rhs

        elif op_id == Id.Arith_DGreatEqual:
          new_int = old_int >> rhs
        elif op_id == Id.Arith_DLessEqual:
          new_int = old_int << rhs
        elif op_id == Id.Arith_AmpEqual:
          new_int = old_int & rhs
        elif op_id == Id.Arith_PipeEqual:
          new_int = old_int | rhs
        elif op_id == Id.Arith_CaretEqual:
          new_int = old_int ^ rhs
        else:
          raise AssertionError(op_id)  # shouldn't get here

        self._Store(lval, new_int)
        return value.Int(new_int)

      elif case(arith_expr_e.Unary):
        node = cast(arith_expr__Unary, UP_node)
        op_id = node.op_id

        i = self.EvalToInt(node.child)

        if op_id == Id.Node_UnaryPlus:
          ret = i
        elif op_id == Id.Node_UnaryMinus:
          ret = -i

        elif op_id == Id.Arith_Bang:  # logical negation
          ret = 1 if i == 0 else 0
        elif op_id == Id.Arith_Tilde:  # bitwise complement
          ret = ~i
        else:
          raise AssertionError(op_id)  # shouldn't get here

        return value.Int(ret)

      elif case(arith_expr_e.Binary):
        node = cast(arith_expr__Binary, UP_node)
        op_id = node.op_id

        # Short-circuit evaluation for || and &&.
        if op_id == Id.Arith_DPipe:
          lhs = self.EvalToInt(node.left)
          if lhs == 0:
            rhs = self.EvalToInt(node.right)
            ret = int(rhs != 0)
          else:
            ret = 1  # true
          return value.Int(ret)

        if op_id == Id.Arith_DAmp:
          lhs = self.EvalToInt(node.left)
          if lhs == 0:
            ret = 0  # false
          else:
            rhs = self.EvalToInt(node.right)
            ret = int(rhs != 0)
          return value.Int(ret)

        if op_id == Id.Arith_LBracket:
          # NOTE: Similar to bracket_op_e.ArrayIndex in osh/word_eval.py

          left = self.Eval(node.left)
          UP_left = left
          with tagswitch(left) as case:
            if case(value_e.MaybeStrArray):
              array_val = cast(value__MaybeStrArray, UP_left)
              index = self.EvalToInt(node.right)
              s = word_eval.GetArrayItem(array_val.strs, index)

            elif case(value_e.AssocArray):
              left = cast(value__AssocArray, UP_left)
              key = self.EvalWordToString(node.right)
              s = left.d.get(key)

            else:
              # TODO: Add error context
              e_die('Expected array or assoc in index expression, got %s',
                    ui.ValType(left))

          if s is None:
            val = value.Undef()  # type: value_t
          else:
            val = value.Str(s)

          return val

        if op_id == Id.Arith_Comma:
          self.EvalToInt(node.left)  # throw away result
          ret = self.EvalToInt(node.right)
          return value.Int(ret)

        # Rest are integers
        lhs = self.EvalToInt(node.left)
        rhs = self.EvalToInt(node.right)

        if op_id == Id.Arith_Plus:
          ret = lhs + rhs
        elif op_id == Id.Arith_Minus:
          ret = lhs - rhs
        elif op_id == Id.Arith_Star:
          ret =  lhs * rhs
        elif op_id == Id.Arith_Slash:
          if rhs == 0:
            # TODO: Could also blame /
            e_die('Divide by zero',
                  span_id=location.SpanForArithExpr(node.right))

          ret = lhs / rhs

        elif op_id == Id.Arith_Percent:
          if rhs == 0:
            # TODO: Could also blame /
            e_die('Divide by zero',
                  span_id=location.SpanForArithExpr(node.right))

          ret = lhs % rhs

        elif op_id == Id.Arith_DStar:
          # OVM is stripped of certain functions that are somehow necessary for
          # exponentiation.
          # Python/ovm_stub_pystrtod.c:21: PyOS_double_to_string: Assertion `0'
          # failed.
          if rhs < 0:
            e_die("Exponent can't be less than zero")  # TODO: error location
          ret = 1
          for i in xrange(rhs):
            ret *= lhs

        elif op_id == Id.Arith_DEqual:
          ret = int(lhs == rhs)
        elif op_id == Id.Arith_NEqual:
          ret = int(lhs != rhs)
        elif op_id == Id.Arith_Great:
          ret = int(lhs > rhs)
        elif op_id == Id.Arith_GreatEqual:
          ret = int(lhs >= rhs)
        elif op_id == Id.Arith_Less:
          ret = int(lhs < rhs)
        elif op_id == Id.Arith_LessEqual:
          ret = int(lhs <= rhs)

        elif op_id == Id.Arith_Pipe:
          ret = lhs | rhs
        elif op_id == Id.Arith_Amp:
          ret = lhs & rhs
        elif op_id == Id.Arith_Caret:
          ret = lhs ^ rhs

        # Note: how to define shift of negative numbers?
        elif op_id == Id.Arith_DLess:
          ret = lhs << rhs
        elif op_id == Id.Arith_DGreat:
          ret = lhs >> rhs
        else:
          raise AssertionError(op_id)

        return value.Int(ret)

      elif case(arith_expr_e.TernaryOp):
        node = cast(arith_expr__TernaryOp, UP_node)

        cond = self.EvalToInt(node.cond)
        if cond:  # nonzero
          return self.Eval(node.true_expr)
        else:
          return self.Eval(node.false_expr)

      else:
        raise AssertionError(node.tag_())