Example #1
0
    def ShortFlag(self, short_name, arg_type=None, help=None):
        # type: (str, Optional[int], Optional[str]) -> None
        """
    This is very similar to ShortFlag for FlagSpecAndMore, except we have
    separate arity0 and arity1 dicts.
    """
        assert short_name.startswith('-'), short_name
        assert len(short_name) == 2, short_name

        char = short_name[1]
        if arg_type is None:
            self.arity0.append(char)
        else:
            self.arity1[char] = SetToArg_(char, _FlagType(arg_type), False)

        typ = _FlagType(arg_type)
        default_val = _Default(arg_type)

        if self.typed:
            self.defaults[char] = default_val
        else:
            # TODO: remove when all builtins converted
            self.defaults[char] = value.Undef()

        self.fields[char] = typ
Example #2
0
    def Unset(self, lval, lookup_mode):
        """
    Returns:
      ok bool, found bool.

      ok is false if the cell is read-only.
      found is false if the name is not there.
    """
        if lval.tag == lvalue_e.Named:  # unset x
            cell, namespace = self._FindCellAndNamespace(
                lval.name, lookup_mode)
            if cell:
                found = True
                if cell.readonly:
                    return False, found
                namespace[lval.name].val = value.Undef()
                cell.exported = False
                return True, found  # found
            else:
                return True, False

        elif lval.tag == lvalue_e.Indexed:  # unset a[1]
            raise NotImplementedError

        else:
            raise AssertionError
    def ShortOption(self, char, help=None):
        # type: (str, Optional[str]) -> None
        """Define an option that can be turned off with + and on with -."""

        assert len(char) == 1  # 'r' for -r +r
        self.options[char] = True

        self.defaults[char] = value.Undef()
        # '+' or '-'.  TODO: Should we make it a bool?
        self.fields[char] = flag_type.Str()
Example #4
0
    def PlusFlag(self, char, help=None):
        # type: (str, Optional[str]) -> None
        """Define an option that can be turned off with + and on with -.

    It's actually a ternary value: plus, minus, or unset.
    """
        assert len(char) == 1  # 'r' for -r +r
        self.plus_flags.append(char)

        self.defaults[char] = value.Undef()
        # '+' or '-'.  TODO: Should we make it a bool?
        self.fields[char] = flag_type.Str()
Example #5
0
    def testGetVar(self):
        mem = _InitMem()

        # readonly a=x
        mem.SetVar(lvalue.Named('a'), value.Str('x'), (var_flags_e.ReadOnly, ),
                   scope_e.Dynamic)

        val = mem.GetVar('a', scope_e.Dynamic)
        test_lib.AssertAsdlEqual(self, value.Str('x'), val)

        val = mem.GetVar('undef', scope_e.Dynamic)
        test_lib.AssertAsdlEqual(self, value.Undef(), val)
Example #6
0
    def testGetValue(self):
        mem = _InitMem()

        # readonly a=x
        mem.SetValue(lvalue.Named('a'),
                     value.Str('x'),
                     scope_e.Dynamic,
                     flags=state.SetReadOnly)

        val = mem.GetValue('a', scope_e.Dynamic)
        test_lib.AssertAsdlEqual(self, value.Str('x'), val)

        val = mem.GetValue('undef', scope_e.Dynamic)
        test_lib.AssertAsdlEqual(self, value.Undef(), val)
Example #7
0
  def _EvalIndirectArrayExpansion(self, name, index):
    """Expands ${!ref} when $ref has the form `name[index]`.

    Args:
      name, index: arbitrary strings
    Returns:
      value, or None if invalid
    """
    if not match.IsValidVarName(name):
      return None
    val = self.mem.GetVar(name)
    if val.tag == value_e.StrArray:
      if index in ('@', '*'):
        # TODO: maybe_decay_array
        return value.StrArray(val.strs)
      try:
        index_num = int(index)
      except ValueError:
        return None
      try:
        return value.Str(val.strs[index_num])
      except IndexError:
        return value.Undef()
    elif val.tag == value_e.AssocArray:
      if index in ('@', '*'):
        raise NotImplementedError
      try:
        return value.Str(val.d[index])
      except KeyError:
        return value.Undef()
    elif val.tag == value_e.Undef:
      return value.Undef()
    elif val.tag == value_e.Str:
      return None
    else:
      raise AssertionError
Example #8
0
File: args.py Project: grahamc/oil
    def PyToValue(py_val):
        # type: (Any) -> value_t

        if py_val is None:
            val = value.Undef()  # type: value_t
        elif isinstance(py_val, bool):
            val = value.Bool(py_val)
        elif isinstance(py_val, int):
            val = value.Int(py_val)
        elif isinstance(py_val, float):
            val = value.Float()  # TODO: ASDL needs float primitive
        elif isinstance(py_val, str):
            val = value.Str(py_val)
        else:
            raise AssertionError(py_val)

        return val
Example #9
0
  def GetSpecialVar(self, op_id):
    if op_id == Id.VSub_Bang:  # $!
      n = self.last_job_id
      if n == -1:
        return value.Undef()  # could be an error

    elif op_id == Id.VSub_QMark:  # $?
      # External commands need WIFEXITED test.  What about subshells?
      n = self.last_status[-1]

    elif op_id == Id.VSub_Pound:  # $#
      n = self.argv_stack[-1].GetNumArgs()

    elif op_id == Id.VSub_Dollar:  # $$
      n = self.root_pid

    else:
      raise NotImplementedError(op_id)

    return value.Str(str(n))
    def ShortFlag(self, short_name, arg_type=None, help=None):
        # type: (str, Optional[int], Optional[str]) -> None
        """
    This is very similar to ShortFlag for FlagSpecAndMore, except we have
    separate arity0 and arity1 dicts.
    """
        assert short_name.startswith('-'), short_name
        assert len(short_name) == 2, short_name

        char = short_name[1]
        if arg_type is None:
            self.arity0[char] = True
        else:
            self.arity1[char] = args.SetToArg(char, _FlagType(arg_type))

        # TODO: callers should pass flag_type
        if arg_type is None:
            typ = flag_type.Bool()  # type: flag_type_t
            default = value.Bool(False)  # type: value_t
        elif arg_type == args.Int:
            typ = flag_type.Int()
            default = value.Int(-1)
        elif arg_type == args.Float:
            typ = flag_type.Float()
            default = value.Float(0.0)
        elif arg_type == args.String:
            typ = flag_type.Str()
            default = value.Str('')
        elif isinstance(arg_type, list):
            typ = flag_type.Enum(arg_type)
            default = value.Str('')  # This isn't valid
        else:
            raise AssertionError(arg_type)

        if self.typed:
            self.defaults[char] = default
        else:
            # TODO: remove when all builtins converted
            self.defaults[char] = value.Undef()

        self.fields[char] = typ
Example #11
0
def _Default(arg_type, arg_default=None):
    # type: (Union[None, int, List[str]], Optional[str]) -> value_t

    # for enum or string
    # note: not using this for integers yet
    if arg_default is not None:
        return value.Str(arg_default)  # early return

    if arg_type is None:
        default = value.Bool(False)  # type: value_t
    elif arg_type == args.Int:
        default = value.Int(-1)  # positive values aren't allowed now
    elif arg_type == args.Float:
        default = value.Float(0.0)
    elif arg_type == args.String:
        default = value.Undef()  # e.g. read -d '' is NOT the default
    elif isinstance(arg_type, list):
        default = value.Str('')  # This isn't valid
    else:
        raise AssertionError(arg_type)
    return default
Example #12
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)
                node = cast(arith_expr__VarRef, UP_node)
                tok = node.token
                return _LookupVar(tok.val, self.mem, self.exec_opts)

            elif case(
                    arith_expr_e.ArithWord):  # $(( $x )) $(( ${x}${y} )), etc.
                node = cast(arith_expr__ArithWord, UP_node)
                return self.word_ev.EvalWordToString(node.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:
                    lval = _EvalLhsArith(node.left, self.mem, self)
                    # Disallowing (( a = myarray ))
                    # It has to be an integer
                    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):
                            left = cast(value__MaybeStrArray, UP_left)
                            rhs_int = self.EvalToInt(node.right)
                            try:
                                # could be None because representation is sparse
                                s = left.strs[rhs_int]
                            except IndexError:
                                s = None

                        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.Eval(node.left)  # throw away result
                    return self.Eval(node.right)

                # 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_())
Example #13
0
  def _EvalBracedVarSub(self, part, part_vals, quoted):
    """
    Args:
      part_vals: output param to append to.
    """
    # We have four types of operator that interact.
    #
    # 1. Bracket: value -> (value, bool maybe_decay_array)
    #
    # 2. Then these four cases are mutually exclusive:
    #
    #   a. Prefix length: value -> value
    #   b. Test: value -> part_value[]
    #   c. Other Suffix: value -> value
    #   d. no operator: you have a value
    #
    # That is, we don't have both prefix and suffix operators.
    #
    # 3. Process maybe_decay_array here before returning.

    maybe_decay_array = False  # for $*, ${a[*]}, etc.

    var_name = None  # For ${foo=default}

    # 1. Evaluate from (var_name, var_num, token Id) -> value
    if part.token.id == Id.VSub_Name:
      var_name = part.token.val
      val = self.mem.GetVar(var_name)
      #log('EVAL NAME %s -> %s', var_name, val)

    elif part.token.id == Id.VSub_Number:
      var_num = int(part.token.val)
      val = self._EvalVarNum(var_num)
    else:
      # $* decays
      val, maybe_decay_array = self._EvalSpecialVar(part.token.id, quoted)

    # 2. Bracket: value -> (value v, bool maybe_decay_array)
    # maybe_decay_array is for joining ${a[*]} and unquoted ${a[@]} AFTER
    # suffix ops are applied.  If we take the length with a prefix op, the
    # distinction is ignored.
    if part.bracket_op:
      if part.bracket_op.tag == bracket_op_e.WholeArray:
        op_id = part.bracket_op.op_id

        if op_id == Id.Lit_At:
          if not quoted:
            maybe_decay_array = True  # ${a[@]} decays but "${a[@]}" doesn't
          if val.tag == value_e.Undef:
            val = self._EmptyStrArrayOrError(part.token)
          elif val.tag == value_e.Str:
            e_die("Can't index string with @: %r", val, part=part)
          elif val.tag == value_e.StrArray:
            # TODO: Is this a no-op?  Just leave 'val' alone.
            val = value.StrArray(val.strs)

        elif op_id == Id.Arith_Star:
          maybe_decay_array = True  # both ${a[*]} and "${a[*]}" decay
          if val.tag == value_e.Undef:
            val = self._EmptyStrArrayOrError(part.token)
          elif val.tag == value_e.Str:
            e_die("Can't index string with *: %r", val, part=part)
          elif val.tag == value_e.StrArray:
            # TODO: Is this a no-op?  Just leave 'val' alone.
            # ${a[*]} or "${a[*]}" :  maybe_decay_array is always true
            val = value.StrArray(val.strs)

        else:
          raise AssertionError(op_id)  # unknown

      elif part.bracket_op.tag == bracket_op_e.ArrayIndex:
        anode = part.bracket_op.expr

        if val.tag == value_e.Undef:
          pass  # it will be checked later

        elif val.tag == value_e.Str:
          # Bash treats any string as an array, so we can't add our own
          # behavior here without making valid OSH invalid bash.
          e_die("Can't index string %r with integer", part.token.val,
                token=part.token)

        elif val.tag == value_e.StrArray:
          index = self.arith_ev.Eval(anode)
          try:
            # could be None because representation is sparse
            s = val.strs[index]
          except IndexError:
            s = None

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

        elif val.tag == value_e.AssocArray:
          key = self.arith_ev.Eval(anode, int_coerce=False)
          try:
            val = value.Str(val.d[key])
          except KeyError:
            val = value.Undef()

        else:
          raise AssertionError(val.__class__.__name__)

      else:
        raise AssertionError(part.bracket_op.tag)

    if part.prefix_op:
      val = self._EmptyStrOrError(val)  # maybe error
      val = self._ApplyPrefixOp(val, part.prefix_op, token=part.token)
      # NOTE: When applying the length operator, we can't have a test or
      # suffix afterward.  And we don't want to decay the array

    elif part.suffix_op:
      op = part.suffix_op
      if op.tag == suffix_op_e.StringNullary:
        if op.op_id == Id.VOp0_P:
          prompt = self.prompt_ev.EvalPrompt(val)
          val = value.Str(prompt)
        elif op.op_id == Id.VOp0_Q:
          val = value.Str(string_ops.ShellQuote(val.s))
        else:
          raise NotImplementedError(op.op_id)

      elif op.tag == suffix_op_e.StringUnary:
        if LookupKind(part.suffix_op.op_id) == Kind.VTest:
          # TODO: Change style to:
          # if self._ApplyTestOp(...)
          #   return
          # It should return whether anything was done.  If not, we continue to
          # the end, where we might throw an error.

          assign_part_vals, effect = self._ApplyTestOp(val, part.suffix_op,
                                                       quoted, part_vals)

          # NOTE: Splicing part_values is necessary because of code like
          # ${undef:-'a b' c 'd # e'}.  Each part_value can have a different
          # do_glob/do_elide setting.
          if effect == effect_e.SpliceParts:
            return  # EARLY RETURN, part_vals mutated

          elif effect == effect_e.SpliceAndAssign:
            if var_name is None:
              # TODO: error context
              e_die("Can't assign to special variable")
            else:
              # NOTE: This decays arrays too!  'set -o strict_array' could
              # avoid it.
              rhs_str = _DecayPartValuesToString(assign_part_vals,
                                                 self.splitter.GetJoinChar())
              state.SetLocalString(self.mem, var_name, rhs_str)
            return  # EARLY RETURN, part_vals mutated

          elif effect == effect_e.Error:
            raise NotImplementedError

          else:
            # The old one
            #val = self._EmptyStringPartOrError(part_val, quoted)
            pass  # do nothing, may still be undefined

        else:
          val = self._EmptyStrOrError(val)  # maybe error
          # Other suffix: value -> value
          val = self._ApplyUnarySuffixOp(val, part.suffix_op)

      elif op.tag == suffix_op_e.PatSub:  # PatSub, vectorized
        val = self._EmptyStrOrError(val)  # ${undef//x/y}

        # globs are supported in the pattern
        pat_val = self.EvalWordToString(op.pat, do_fnmatch=True)
        assert pat_val.tag == value_e.Str, pat_val

        if op.replace:
          replace_val = self.EvalWordToString(op.replace)
          assert replace_val.tag == value_e.Str, replace_val
          replace_str = replace_val.s
        else:
          replace_str = ''

        regex, warnings = glob_.GlobToERE(pat_val.s)
        if warnings:
          # TODO:
          # - Add 'set -o strict-glob' mode and expose warnings.
          #   "Glob is not in CANONICAL FORM".
          # - Propagate location info back to the 'op.pat' word.
          pass
        replacer = string_ops.GlobReplacer(regex, replace_str, op.spids[0])

        if val.tag == value_e.Str:
          s = replacer.Replace(val.s, op)
          val = value.Str(s)

        elif val.tag == value_e.StrArray:
          strs = []
          for s in val.strs:
            if s is not None:
              strs.append(replacer.Replace(s, op))
          val = value.StrArray(strs)

        else:
          raise AssertionError(val.__class__.__name__)

      elif op.tag == suffix_op_e.Slice:
        val = self._EmptyStrOrError(val)  # ${undef:3:1}

        if op.begin:
          begin = self.arith_ev.Eval(op.begin)
        else:
          begin = 0

        if op.length:
          length = self.arith_ev.Eval(op.length)
        else:
          length = None

        if val.tag == value_e.Str:  # Slice UTF-8 characters in a string.
          s = val.s

          try:
            if begin < 0:
              # It could be negative if we compute unicode length, but that's
              # confusing.

              # TODO: Instead of attributing it to the word part, it would be
              # better if we attributed it to arith_expr begin.
              raise util.InvalidSlice(
                  "The start index of a string slice can't be negative: %d",
                  begin, part=part)

            byte_begin = string_ops.AdvanceUtf8Chars(s, begin, 0)

            if length is None:
              byte_end = len(s)
            else:
              if length < 0:
                # TODO: Instead of attributing it to the word part, it would be
                # better if we attributed it to arith_expr begin.
                raise util.InvalidSlice(
                    "The length of a string slice can't be negative: %d",
                    length, part=part)

              byte_end = string_ops.AdvanceUtf8Chars(s, length, byte_begin)

          except (util.InvalidSlice, util.InvalidUtf8) as e:
            if self.exec_opts.strict_word_eval:
              raise
            else:
              # TODO:
              # - We don't see the error location here, but we see it when set
              #   -o strict-word-eval.
              # - Doesn't make the command exit with 1.  It just sets the word
              #   to empty string.
              util.warn(e.UserErrorString())
              substr = ''  # error condition
          else:
            substr = s[byte_begin : byte_end]

          val = value.Str(substr)

        elif val.tag == value_e.StrArray:  # Slice array entries.
          # NOTE: unset elements don't count towards the length.
          strs = []
          for s in val.strs[begin:]:
            if s is not None:
              strs.append(s)
              if len(strs) == length:  # never true for unspecified length
                break
          val = value.StrArray(strs)

        else:
          raise AssertionError(val.__class__.__name__)  # Not possible

    # After applying suffixes, process maybe_decay_array here.
    if maybe_decay_array and val.tag == value_e.StrArray:
      val = self._DecayArray(val)

    # For the case where there are no prefix or suffix ops.
    val = self._EmptyStrOrError(val)

    # For example, ${a} evaluates to value_t.Str(), but we want a
    # part_value.StringPartValue.
    part_val = _ValueToPartValue(val, quoted)
    part_vals.append(part_val)
Example #14
0
  def GetVar(self, name, lookup_mode=scope_e.Dynamic):
    assert isinstance(name, str), name

    # TODO: Short-circuit down to _FindCellAndNamespace by doing a single hash
    # lookup:
    # COMPUTED_VARS = {'PIPESTATUS': 1, 'FUNCNAME': 1, ...}
    # if name not in COMPUTED_VARS: ...

    if name == 'PIPESTATUS':
      return value.StrArray([str(i) for i in self.pipe_status[-1]])

    # Do lookup of system globals before looking at user variables.  Note: we
    # could optimize this at compile-time like $?.  That would break
    # ${!varref}, but it's already broken for $?.
    if name == 'FUNCNAME':
      # bash wants it in reverse order.  This is a little inefficient but we're
      # not depending on deque().
      strs = []
      for func_name, source_name, _, _, _ in reversed(self.debug_stack):
        if func_name:
          strs.append(func_name)
        if source_name:
          strs.append('source')  # bash doesn't give name
        # Temp stacks are ignored

      if self.has_main:
        strs.append('main')  # bash does this
      return value.StrArray(strs)  # TODO: Reuse this object too?

    # This isn't the call source, it's the source of the function DEFINITION
    # (or the sourced # file itself).
    if name == 'BASH_SOURCE':
      return value.StrArray(list(reversed(self.bash_source)))

    # This is how bash source SHOULD be defined, but it's not!
    if name == 'CALL_SOURCE':
      strs = []
      for func_name, source_name, call_spid, _, _ in reversed(self.debug_stack):
        # should only happen for the first entry
        if call_spid == const.NO_INTEGER:
          continue
        span = self.arena.GetLineSpan(call_spid)
        source_str = self.arena.GetLineSourceString(span.line_id)
        strs.append(source_str)
      if self.has_main:
        strs.append('-')  # Bash does this to line up with main?
      return value.StrArray(strs)  # TODO: Reuse this object too?

    if name == 'BASH_LINENO':
      strs = []
      for _, _, call_spid, _, _ in reversed(self.debug_stack):
        # should only happen for the first entry
        if call_spid == const.NO_INTEGER:
          continue
        span = self.arena.GetLineSpan(call_spid)
        line_num = self.arena.GetLineNumber(span.line_id)
        strs.append(str(line_num))
      if self.has_main:
        strs.append('0')  # Bash does this to line up with main?
      return value.StrArray(strs)  # TODO: Reuse this object too?

    if name == 'LINENO':
      span = self.arena.GetLineSpan(self.current_spid)
      # TODO: maybe use interned GetLineNumStr?
      s = str(self.arena.GetLineNumber(span.line_id))

      # Perf bug: why is this slow?  Commenting it out reduces line count by
      if 1:
        self.line_num.s = s  # Python's configure takes 75 seconds!
      else:
        # WTF this does not show the per bug?
        self.line_num.s2 = s  # Python's configure takes 13 seconds!
      return self.line_num

    # This is OSH-specific.  Get rid of it in favor of ${BASH_SOURCE[0]} ?
    if name == 'SOURCE_NAME':
      # Update and reuse an object.
      span = self.arena.GetLineSpan(self.current_spid)
      self.source_name.s = self.arena.GetLineSourceString(span.line_id)
      return self.source_name

    cell, _ = self._FindCellAndNamespace(name, lookup_mode, writing=False)

    if cell:
      return cell.val

    return value.Undef()
Example #15
0
  def SetVar(self, lval, val, new_flags, lookup_mode):
    """
    Args:
      lval: lvalue
      val: value, or None if only changing flags
      new_flags: tuple of flags to set: ReadOnly | Exported
        () means no flags to start with
        None means unchanged?
      scope:
        Local | Global | Dynamic - for builtins, PWD, etc.

      NOTE: in bash, PWD=/ changes the directory.  But not in dash.
    """
    # STRICTNESS / SANENESS:
    #
    # 1) Don't create arrays automatically, e.g. a[1000]=x
    # 2) Never change types?  yeah I think that's a good idea, at least for oil
    # (not sh, for compatibility).  set -o strict-types or something.  That
    # means arrays have to be initialized with let arr = [], which is fine.
    # This helps with stuff like IFS.  It starts off as a string, and assigning
    # it to a list is en error.  I guess you will have to turn this no for
    # bash?
    #
    # TODO:
    # - COMPUTED_VARS can't be set
    # - What about PWD / OLDPWD / UID / EUID ?  You can simply make them
    # readonly.
    # - $PS1 and $PS4 should be PARSED when they are set, to avoid the error on use
    # - Other validity: $HOME could be checked for existence

    assert new_flags is not None

    if lval.tag == lvalue_e.LhsName:
      #if lval.name == 'ldflags':
      # TODO: Turn this into a tracing feature.  Like osh --tracevar ldflags
      # --tracevar foo.  Has to respect environment variables too.
      if 0:
        util.log('--- SETTING ldflags to %s', val)
        if lval.spids:
          span_id = lval.spids[0]
          line_span = self.arena.GetLineSpan(span_id)
          line_id = line_span.line_id
          source_str = self.arena.GetLineSourceString(line_id)
          line_num = self.arena.GetLineNumber(line_id)
          #length = line_span.length
          util.log('--- spid %s: %s, line %d, col %d', span_id, source_str,
                   line_num+1, line_span.col)

          # TODO: Need the arena to look it up the line spid and line number.

      cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode)
      if cell:
        if val is not None:
          if cell.readonly:
            # TODO: error context
            e_die("Can't assign to readonly value %r", lval.name)
          cell.val = val
        if var_flags_e.Exported in new_flags:
          cell.exported = True
        if var_flags_e.ReadOnly in new_flags:
          cell.readonly = True
        if var_flags_e.AssocArray in new_flags:
          cell.is_assoc_array = True
      else:
        if val is None:
          # set -o nounset; local foo; echo $foo  # It's still undefined!
          val = value.Undef()  # export foo, readonly foo
        cell = runtime_asdl.cell(val,
                                 var_flags_e.Exported in new_flags,
                                 var_flags_e.ReadOnly in new_flags,
                                 var_flags_e.AssocArray in new_flags)
        namespace[lval.name] = cell

      if (cell.val is not None and cell.val.tag == value_e.StrArray and
          cell.exported):
        e_die("Can't export array")  # TODO: error context

    elif lval.tag == lvalue_e.LhsIndexedName:
      # TODO: All paths should have this?  We can get here by a[x]=1 or
      # (( a[ x ] = 1 )).  Maybe we should make them different?
      if lval.spids:
        left_spid = lval.spids[0]
      else:
        left_spid = const.NO_INTEGER

      # TODO: This is a parse error!
      # a[1]=(1 2 3)
      if val.tag == value_e.StrArray:
        e_die("Can't assign array to array member", span_id=left_spid)

      # bash/mksh have annoying behavior of letting you do LHS assignment to
      # Undef, which then turns into an INDEXED array.  (Undef means that set
      # -o nounset fails.)
      cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode)
      if not cell:
        self._BindNewArrayWithEntry(namespace, lval, val, new_flags)
        return

      cell_tag = cell.val.tag
      if cell_tag == value_e.Str:
        # s=x
        # s[1]=y  # invalid
        e_die("Entries in value of type %s can't be assigned to",
              cell.val.__class__.__name__, span_id=left_spid)

      if cell.readonly:
        e_die("Can't assign to readonly value", span_id=left_spid)

      # This is for the case where we did declare -a foo or declare -A foo.
      # There IS a cell, but it's still undefined.
      if cell_tag == value_e.Undef:
        if cell.is_assoc_array:
          self._BindNewAssocArrayWithEntry(namespace, lval, val, new_flags)
        else:
          self._BindNewArrayWithEntry(namespace, lval, val, new_flags)
        return

      if cell_tag == value_e.StrArray:
        strs = cell.val.strs
        try:
          strs[lval.index] = val.s
        except IndexError:
          # Fill it in with None.  It could look like this:
          # ['1', 2, 3, None, None, '4', None]
          # Then ${#a[@]} counts the entries that are not None.
          #
          # TODO: strict-array for Oil arrays won't auto-fill.
          n = lval.index - len(strs) + 1
          strs.extend([None] * n)
          strs[lval.index] = val.s
        return

      if cell_tag == value_e.AssocArray:
        cell.val.d[lval.index] = val.s
        return

    else:
      raise AssertionError(lval.__class__.__name__)
Example #16
0
  def GetArgNum(self, arg_num):
    index = self.num_shifted + arg_num - 1
    if index >= len(self.argv):
      return value.Undef()

    return value.Str(str(self.argv[index]))
Example #17
0
    def GetVar(self, name, lookup_mode=scope_e.Dynamic):
        assert isinstance(name, str), name

        # Do lookup of system globals before looking at user variables.  Note: we
        # could optimize this at compile-time like $?.  That would break
        # ${!varref}, but it's already broken for $?.
        if name == 'FUNCNAME':
            # bash wants it in reverse order.  This is a little inefficient but we're
            # not depending on deque().
            strs = []
            for func_name, source_name, _, _, _ in reversed(self.debug_stack):
                if func_name:
                    strs.append(func_name)
                if source_name:
                    strs.append('source')  # bash doesn't give name
                # Temp stacks are ignored

            if self.has_main:
                strs.append('main')  # bash does this
            return value.StrArray(strs)  # TODO: Reuse this object too?

        # This isn't the call source, it's the source of the function DEFINITION
        # (or the sourced # file itself).
        if name == 'BASH_SOURCE':
            return value.StrArray(list(reversed(self.bash_source)))

        # This is how bash source SHOULD be defined, but it's not!
        if name == 'CALL_SOURCE':
            strs = []
            for func_name, source_name, call_spid, _, _ in reversed(
                    self.debug_stack):
                # should only happen for the first entry
                if call_spid == const.NO_INTEGER:
                    continue
                span = self.arena.GetLineSpan(call_spid)
                path, _ = self.arena.GetDebugInfo(span.line_id)
                strs.append(path)
            if self.has_main:
                strs.append('-')  # Bash does this to line up with main?
            return value.StrArray(strs)  # TODO: Reuse this object too?

        if name == 'BASH_LINENO':
            strs = []
            for func_name, source_name, call_spid, _, _ in reversed(
                    self.debug_stack):
                # should only happen for the first entry
                if call_spid == const.NO_INTEGER:
                    continue
                span = self.arena.GetLineSpan(call_spid)
                _, line_num = self.arena.GetDebugInfo(span.line_id)
                strs.append(str(line_num))
            if self.has_main:
                strs.append('0')  # Bash does this to line up with main?
            return value.StrArray(strs)  # TODO: Reuse this object too?

        if name == 'LINENO':
            return self.line_num

        # This is OSH-specific.  Get rid of it in favor of ${BASH_SOURCE[0]} ?
        if name == 'SOURCE_NAME':
            return self.source_name

        cell, _ = self._FindCellAndNamespace(name, lookup_mode, writing=False)

        if cell:
            return cell.val

        return value.Undef()
Example #18
0
    def SetVar(self,
               lval,
               val,
               flags_to_set,
               lookup_mode,
               flags_to_clear=(),
               keyword_id=None):
        """
    Args:
      lval: lvalue
      val: value, or None if only changing flags
      flags_to_set: tuple of flags to set: ReadOnly | Exported
        () means no flags to start with
      scope:
        Local | Global | Dynamic - for builtins, PWD, etc.

      NOTE: in bash, PWD=/ changes the directory.  But not in dash.
    """
        # STRICTNESS / SANENESS:
        #
        # 1) Don't create arrays automatically, e.g. a[1000]=x
        # 2) Never change types?  yeah I think that's a good idea, at least for oil
        # (not sh, for compatibility).  set -o strict-types or something.  That
        # means arrays have to be initialized with let arr = [], which is fine.
        # This helps with stuff like IFS.  It starts off as a string, and assigning
        # it to a list is an error.  I guess you will have to turn this no for
        # bash?
        #
        # TODO:
        # - COMPUTED_VARS can't be set
        # - What about PWD / OLDPWD / UID / EUID ?  You can simply make them
        # readonly.
        # - $PS1 and $PS4 should be PARSED when they are set, to avoid the error on use
        # - Other validity: $HOME could be checked for existence

        assert flags_to_set is not None
        if lval.tag == lvalue_e.Named:
            cell, namespace = self._FindCellAndNamespace(
                lval.name, lookup_mode)
            self._CheckOilKeyword(keyword_id, lval, cell)
            if cell:
                # Clear before checking readonly bit.
                # NOTE: Could be cell.flags &= flag_clear_mask
                if var_flags_e.Exported in flags_to_clear:
                    cell.exported = False
                if var_flags_e.ReadOnly in flags_to_clear:
                    cell.readonly = False

                if val is not None:  # e.g. declare -rx existing
                    if cell.readonly:
                        # TODO: error context
                        e_die("Can't assign to readonly value %r", lval.name)
                    cell.val = val

                # NOTE: Could be cell.flags |= flag_set_mask
                if var_flags_e.Exported in flags_to_set:
                    cell.exported = True
                if var_flags_e.ReadOnly in flags_to_set:
                    cell.readonly = True

            else:
                if val is None:  # declare -rx nonexistent
                    # set -o nounset; local foo; echo $foo  # It's still undefined!
                    val = value.Undef()  # export foo, readonly foo

                cell = runtime_asdl.cell(val, var_flags_e.Exported
                                         in flags_to_set, var_flags_e.ReadOnly
                                         in flags_to_set)
                namespace[lval.name] = cell

            # Maintain invariant that only strings and undefined cells can be
            # exported.
            if (cell.val is not None
                    and cell.val.tag not in (value_e.Undef, value_e.Str)
                    and cell.exported):
                e_die("Can't export array")  # TODO: error context

        elif lval.tag == lvalue_e.Indexed:
            # There is no syntax 'declare a[x]'
            assert val is not None, val

            # TODO: All paths should have this?  We can get here by a[x]=1 or
            # (( a[ x ] = 1 )).  Maybe we should make them different?
            left_spid = lval.spids[0] if lval.spids else const.NO_INTEGER

            # bash/mksh have annoying behavior of letting you do LHS assignment to
            # Undef, which then turns into an INDEXED array.  (Undef means that set
            # -o nounset fails.)
            cell, namespace = self._FindCellAndNamespace(
                lval.name, lookup_mode)
            self._CheckOilKeyword(keyword_id, lval, cell)
            if not cell:
                self._BindNewArrayWithEntry(namespace, lval, val, flags_to_set)
                return

            if cell.readonly:
                e_die("Can't assign to readonly array", span_id=left_spid)

            cell_tag = cell.val.tag

            # undef[0]=y is allowed
            if cell_tag == value_e.Undef:
                self._BindNewArrayWithEntry(namespace, lval, val, flags_to_set)
                return

            if cell_tag == value_e.Str:
                # s=x
                # s[1]=y  # invalid
                e_die("Entries in value of type %s can't be assigned to",
                      cell.val.__class__.__name__,
                      span_id=left_spid)

            if cell_tag == value_e.MaybeStrArray:
                strs = cell.val.strs
                try:
                    strs[lval.index] = val.s
                except IndexError:
                    # Fill it in with None.  It could look like this:
                    # ['1', 2, 3, None, None, '4', None]
                    # Then ${#a[@]} counts the entries that are not None.
                    #
                    # TODO: strict-array for Oil arrays won't auto-fill.
                    n = lval.index - len(strs) + 1
                    strs.extend([None] * n)
                    strs[lval.index] = val.s
                return

            # AssocArray shouldn't happen because we query IsAssocArray before
            # evaluating lhs_expr.
            e_die("Object of this type can't be indexed: %s", cell.val)

        elif lval.tag == lvalue_e.Keyed:
            # There is no syntax 'declare A["x"]'
            assert val is not None, val

            left_spid = lval.spids[0] if lval.spids else const.NO_INTEGER

            cell, namespace = self._FindCellAndNamespace(
                lval.name, lookup_mode)
            self._CheckOilKeyword(keyword_id, lval, cell)
            # We already looked it up before making the lvalue
            assert cell.val.tag == value_e.AssocArray, cell
            if cell.readonly:
                e_die("Can't assign to readonly associative array",
                      span_id=left_spid)

            cell.val.d[lval.key] = val.s

        else:
            raise AssertionError(lval.__class__.__name__)