コード例 #1
0
    def EvalWordToAny(self, word, glob_escape=False):
        """
    Used for RHS of assignment

    Also used for default value?  e.g. "${a:-"a" "b"}" and so forth.

    Returns:
      arg_value
      Or maybe just string?  Whatever would go in ConstArg and GlobArg.
      But you don't need to distinguish it later.
      You could also have EvalWord and EvalGlobWord methods or EvalPatternWord
    """
        # Special case for a=(1 2).  ArrayLiteralPart won't appear in words that
        # don't look like assignments.
        if (len(word.parts) == 1
                and word.parts[0].tag == word_part_e.ArrayLiteralPart):
            array_words = word.parts[0].words
            words = braces.BraceExpandWords(array_words)
            strs = self._EvalWordSequence(words)
            #log('ARRAY LITERAL EVALUATED TO -> %s', strs)
            return runtime.StrArray(strs)

        part_vals = self._EvalParts(word)
        #log('part_vals %s', part_vals)

        # Instead of splitting, do a trivial transformation to frag array.
        # Example:
        # foo="-$@-${a[@]}-" requires fragment, reframe, and simple join
        frag_arrays = []
        for p in part_vals:
            if p.tag == part_value_e.StringPartValue:
                frag_arrays.append([runtime.fragment(p.s, False, False)])
            elif p.tag == part_value_e.ArrayPartValue:
                frag_arrays.append(
                    [runtime.fragment(s, False, False) for s in p.strs])
            else:
                raise AssertionError

        frag_arrays = _Reframe(frag_arrays)
        #log('frag_arrays %s', frag_arrays)

        # Simple join
        args = []
        for frag_array in frag_arrays:
            args.append(''.join(frag.s for frag in frag_array))

        # Example:
        # a=(1 2)
        # b=$a  # one word
        # c="${a[@]}"  # two words
        if len(args) == 1:
            val = runtime.Str(args[0])
        else:
            # NOTE: For bash compatibility, could have an option to join them here.
            # foo="-$@-${a[@]}-"  -- join with IFS again, like "$*" ?
            # Or maybe do that in cmd_exec in assignment.
            val = runtime.StrArray(args)

        return val
コード例 #2
0
ファイル: state.py プロジェクト: xydinesh/oil
  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 = list(reversed(self.func_name_stack))
      # TODO: Reuse this object too?
      return runtime.StrArray(strs)

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

    # Instead of BASH_SOURCE.  Using Oil _ convnetion.
    if name == 'SOURCE_NAME':
      return self.source_name

    cell, _ = self._FindCellAndNamespace(name, lookup_mode, is_read=True)

    if cell:
      return cell.val

    return runtime.Undef()
コード例 #3
0
    def _EvalSpecialVar(self, op_id, quoted):
        # $@ is special -- it need to know whether it is in a double quoted
        # context.
        #
        # - If it's $@ in a double quoted context, return an ARRAY.
        # - If it's $@ in a normal context, return a STRING, which then will be
        # subject to splitting.

        if op_id in (Id.VSub_At, Id.VSub_Star):
            argv = self.mem.GetArgv()
            val = runtime.StrArray(argv)
            if op_id == Id.VSub_At:
                # "$@" evaluates to an array, $@ should be decayed
                return val, not quoted
            else:  # $@ $* "$*"
                return val, True

        elif op_id == Id.VSub_QMark:  # $?
            # TODO: Have to parse status somewhere.
            # External commands need WIFEXITED test.  What about subshells?
            return runtime.Str(str(self.mem.last_status)), False

        elif op_id == Id.VSub_Pound:  # $#
            n = self.mem.GetNumArgs()
            return runtime.Str(str(n)), False

        else:
            raise NotImplementedError(op_id)
コード例 #4
0
ファイル: word_eval.py プロジェクト: lheckemann/oil
    def _ApplyUnarySuffixOp(self, val, op):
        assert val.tag != value_e.Undef

        op_kind = LookupKind(op.op_id)

        if op_kind == Kind.VOp1:
            #log('%s', op)
            arg_val = self.word_ev.EvalWordToString(op.arg_word,
                                                    do_fnmatch=True)
            assert arg_val.tag == value_e.Str

            if val.tag == value_e.Str:
                s = _DoUnarySuffixOp(val.s, op, arg_val.s)
                new_val = runtime.Str(s)
            else:  # val.tag == value_e.StrArray:
                # ${a[@]#prefix} is VECTORIZED on arrays.  Oil should have this too.
                strs = []
                for s in val.strs:
                    strs.append(_DoUnarySuffixOp(s, op, arg_val.s))
                new_val = runtime.StrArray(strs)

        else:
            raise AssertionError(op_kind)

        return new_val
コード例 #5
0
    def _ApplyUnarySuffixOp(self, val, op):
        # NOTES:
        # - These are VECTORIZED on arrays
        #   - I want to allow this with my shell dialect: @{files|slice 1
        #   2|upper} does the same thing to all of them.
        # - How to do longest and shortest possible match?  bash and mksh both
        #   call fnmatch() in a loop, with possible short-circuit optimizations.
        #   - TODO: Write a test program to show quadratic behavior?
        #   - original AT&T ksh has special glob routines that returned the match
        #   positions.
        #   Implementation:
        #   - Test if it is a LITERAL or a Glob.  Then do a plain string
        #   operation.
        #   - If it's a glob, do the quadratic algorithm.
        #   - NOTE: bash also has an optimization where it extracts the LITERAL
        #   parts of the string, and does a prematch.  If none of them match,
        #   then it SKIPs the quadratic algorithm.
        #   - The real solution is to compile a glob to RE2, but I want to avoid
        #   that dependency right now... libc regex is good for a bunch of
        #   things.
        # - Bash has WIDE CHAR support for this.  With wchar_t.
        #   - All sorts of functions like xdupmbstowcs
        #
        # And then pat_subst() does some special cases.  Geez.

        assert val.tag != value_e.Undef

        op_kind = LookupKind(op.op_id)
        new_val = None

        # TODO: Vectorization should be factored out of all the branches.
        if op_kind == Kind.VOp1:
            #log('%s', op)
            arg_val = self.word_ev.EvalWordToString(op.arg_word,
                                                    do_fnmatch=True)
            assert arg_val.tag == value_e.Str

            if val.tag == value_e.Str:
                s = self._DoUnarySuffixOp(val.s, op, arg_val.s)
                new_val = runtime.Str(s)
            else:  # val.tag == value_e.StrArray:
                strs = []
                for s in val.strs:
                    strs.append(self._DoUnarySuffixOp(s, op, arg_val.s))
                new_val = runtime.StrArray(strs)

        else:
            raise AssertionError(op_kind)

        if new_val:
            return new_val
        else:
            return val
コード例 #6
0
ファイル: state.py プロジェクト: optionalg/oil
  def GetVar(self, name, lookup_mode=scope.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 = list(reversed(self.func_name_stack))
      return runtime.StrArray(strs)

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

    if cell:
      return cell.val

    return runtime.Undef()
コード例 #7
0
  def _EvalSpecialVar(self, op_id, quoted):
    # $@ is special -- it need to know whether it is in a double quoted
    # context.
    #
    # - If it's $@ in a double quoted context, return an ARRAY.
    # - If it's $@ in a normal context, return a STRING, which then will be
    # subject to splitting.

    if op_id in (Id.VSub_At, Id.VSub_Star):
      argv = self.mem.GetArgv()
      val = runtime.StrArray(argv)
      if op_id == Id.VSub_At:
        # "$@" evaluates to an array, $@ should be decayed
        return val, not quoted
      else:  # $@ $* "$*"
        return val, True

    elif op_id == Id.VSub_Hyphen:
      s = self.exec_opts.GetDollarHyphen()
      return runtime.Str(s), False
    else:
      val = self.mem.GetSpecialVar(op_id)
      return val, False  # dont' decay
コード例 #8
0
  def EvalRhsWord(self, word):
    """word_t -> value_t.

    Used for RHS of assignment.  There is no splitting.

    Args:
      word.CompoundWord

    Returns:
      value
    """
    # Special case for a=(1 2).  ArrayLiteralPart won't appear in words that
    # don't look like assignments.
    if (len(word.parts) == 1 and
        word.parts[0].tag == word_part_e.ArrayLiteralPart):

      array_words = word.parts[0].words
      words = braces.BraceExpandWords(array_words)
      strs = self._EvalWordSequence(words)
      #log('ARRAY LITERAL EVALUATED TO -> %s', strs)
      return runtime.StrArray(strs)

    # If RHS doens't look like a=( ... ), then it must be a string.
    return self.EvalWordToString(word)
コード例 #9
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 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 decay_array here before returning.

    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, decay_array = self._EvalSpecialVar(part.token.id, quoted)

    # 2. Bracket: value -> (value v, bool decay_array)
    # 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:
            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:
            val = runtime.StrArray(val.strs)

        elif op_id == Id.Arith_Star:
          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:
            # Always decay_array with ${a[*]} or "${a[*]}"
            val = runtime.StrArray(val.strs)

        else:
          raise AssertionError(op_id)  # unknown

      elif part.bracket_op.tag == bracket_op_e.ArrayIndex:
        anode = part.bracket_op.expr
        index = self.arith_ev.Eval(anode)

        if val.tag == value_e.Undef:
          pass  # it will be checked later
        elif val.tag == value_e.Str:
          # TODO: Implement this as an extension. Requires unicode support.
          # Bash treats it as an array.
          e_die("Can't index string %r with integer", part.token.val)
        elif val.tag == value_e.StrArray:
          try:
            s = val.strs[index]
          except IndexError:
            val = runtime.Undef()
          else:
            val = runtime.Str(s)

      else:
        raise AssertionError(part.bracket_op.tag)

    if part.prefix_op:
      val = self._EmptyStrOrError(val)  # maybe error
      val = self._ApplyPrefixOp(val, part.prefix_op)
      # At least for length, we can't have a test or suffix afterward.

    elif part.suffix_op:
      op = part.suffix_op
      if 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.SpliceParts:
            return  # EARLY RETURN, part_vals mutated

          elif effect == Effect.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.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)

        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, do_fnmatch=True)
          assert replace_val.tag == value_e.Str, replace_val
          replace_str = replace_val.s
        else:
          replace_str = ''

        pat = pat_val.s
        if val.tag == value_e.Str:
          s = libstr.PatSub(val.s, op, pat, replace_str)
          val = runtime.Str(s)

        elif val.tag == value_e.StrArray:
          strs = []
          for s in val.strs:
            strs.append(libstr.PatSub(s, op, pat, replace_str))
          val = runtime.StrArray(strs)

        else:
          raise AssertionError(val.__class__.__name__)

      elif op.tag == suffix_op_e.Slice:
        # NOTE: The beginning can be negative, but Python handles this.  Might
        # want to make it explicit.
        # TODO: Check out of bounds errors?  begin > end?
        if op.begin:
          begin = self.arith_ev.Eval(op.begin)
        else:
          begin = 0

        if op.length:
          length = self.arith_ev.Eval(op.length)
          end = begin + length
        else:
          end = None  # Python supports None as the end

        if val.tag == value_e.Str:  # Slice characters in a string.
          # TODO: Need to support unicode?  Write spec # tests.
          val = runtime.Str(val.s[begin : end])

        elif val.tag == value_e.StrArray:  # Slice array entries.
          val = runtime.StrArray(val.strs[begin : end])

        else:
          raise AssertionError(val.__class__.__name__)

    # After applying suffixes, process decay_array here.
    if decay_array:
      val = self._DecayArray(val)

    # 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)
コード例 #10
0
ファイル: word_eval.py プロジェクト: silky/oil
 def _EmptyStrArrayOrError(self):
     if self.exec_opts.nounset:
         self._AddErrorContext('Undefined array')
         raise _EvalError()
     else:
         return runtime.StrArray([])
コード例 #11
0
    def _ApplyOtherSuffixOp(self, val, op):

        # NOTES:
        # - These are VECTORIZED on arrays
        #   - I want to allow this with my shell dialect: @{files|slice 1
        #   2|upper} does the same thing to all of them.
        # - How to do longest and shortest possible match?  bash and mksh both
        #   call fnmatch() in a loop, with possible short-circuit optimizations.
        #   - TODO: Write a test program to show quadratic behavior?
        #   - original AT&T ksh has special glob routines that returned the match
        #   positions.
        #   Implementation:
        #   - Test if it is a LITERAL or a Glob.  Then do a plain string
        #   operation.
        #   - If it's a glob, do the quadratic algorithm.
        #   - NOTE: bash also has an optimization where it extracts the LITERAL
        #   parts of the string, and does a prematch.  If none of them match,
        #   then it SKIPs the quadratic algorithm.
        #   - The real solution is to compile a glob to RE2, but I want to avoid
        #   that dependency right now... libc regex is good for a bunch of
        #   things.
        # - Bash has WIDE CHAR support for this.  With wchar_t.
        #   - All sorts of functions like xdupmbstowcs
        #
        # And then pat_subst() does some special cases.  Geez.

        assert val.tag != value_e.Undef

        op_kind = LookupKind(op.op_id)

        new_val = None

        if op_kind == Kind.VOp1:
            #log('%s', op)
            arg_val = self.word_ev.EvalWordToString(op.arg_word,
                                                    do_fnmatch=True)
            assert arg_val.tag == value_e.Str

            looks_like_glob = False
            if looks_like_glob:
                if op.op_id == Id.VOp1_Pound:  # shortest prefix
                    raise NotImplementedError
                elif op.op_id == Id.VOp1_DPound:  # longest prefix
                    raise NotImplementedError

                elif op.op_id == Id.VOp1_Percent:  # shortest suffix
                    raise NotImplementedError
                elif op.op_id == Id.VOp1_DPercent:  # longest suffix
                    raise NotImplementedError
                else:
                    raise AssertionError(op.op_id)

            else:
                op_str = arg_val.s

                # TODO: Factor these out into a common fuction?
                if op.op_id in (Id.VOp1_Pound, Id.VOp1_DPound):  # const prefix
                    prefix = op_str

                    if val.tag == value_e.Str:
                        if val.s.startswith(prefix):
                            # Mutate it so we preserve the flags.
                            new_val = runtime.Str(val.s[len(prefix):])
                        else:
                            #log("Str: %r doesn't end with %r", val.s, suffix)
                            pass

                    elif val.tag == value_e.StrArray:
                        new_val = runtime.StrArray()
                        for i, s in enumerate(val.strs):
                            if s.startswith(prefix):
                                # Mutate it so we preserve the flags.
                                new_s = s[len(prefix):]
                                #log('%s -> %s', s, s[:-len(suffix)])
                            else:
                                new_s = s
                                #log("Array: %r doesn't end with %r", s, suffix)
                            new_val.strs.append(new_s)

                elif op.op_id in (Id.VOp1_Percent,
                                  Id.VOp1_DPercent):  # const suffix
                    suffix = op_str

                    if val.tag == value_e.Str:
                        if val.s.endswith(suffix):
                            # Mutate it so we preserve the flags.
                            new_val = runtime.Str(val.s[:-len(suffix)])
                        else:
                            #log("Str: %r doesn't end with %r", val.s, suffix)
                            pass

                    elif val.tag == value_e.StrArray:
                        new_val = runtime.StrArray()
                        for i, s in enumerate(val.strs):
                            if s.endswith(suffix):
                                # Mutate it so we preserve the flags.
                                new_s = s[:-len(suffix)]
                                #log('%s -> %s', s, s[:-len(suffix)])
                            else:
                                new_s = s
                                #log("Array: %r doesn't end with %r", s, suffix)
                            new_val.strs.append(new_s)

                else:
                    raise AssertionError(op.op_id)

        elif op_kind == Kind.VOp2:
            if op.op_id == Id.VOp2_Slash:  # PatSub, vectorized
                raise NotImplementedError

            # Either string slicing or array slicing.  However string slicing has a
            # unicode problem?  TODO: Test bash out.  We need utf-8 parsing in C++?
            #
            # Or maybe have a different operator for byte slice and char slice.
            elif op.op_id == Id.VOp2_Colon:
                raise NotImplementedError

        else:
            raise NotImplementedError(op)

        if new_val:
            return new_val
        else:
            return val
コード例 #12
0
 def _EmptyStrArrayOrError(self, token):
   assert token is not None
   if self.exec_opts.nounset:
     e_die('Undefined array %r', token.val, token=token)
   else:
     return runtime.StrArray([])
コード例 #13
0
ファイル: cmd_exec.py プロジェクト: harlowja/oil
 def SetGlobalArray(self, name, a):
   """Helper for completion."""
   assert isinstance(a, list)
   val = runtime.StrArray(a)
   pairs = [(ast.LeftVar(name), val)]
   self.SetGlobals(pairs)
コード例 #14
0
ファイル: cmd_exec.py プロジェクト: neuroradiology/oil
  def _Dispatch(self, node, fork_external):
    argv0 = None  # for error message
    check_errexit = True  # for errexit

    if node.tag == command_e.SimpleCommand:
      # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
      # redirected here, which screws up loggnig.  For example, 'echo hi
      # >/dev/null 2>&1'.  We want to evaluate argv and log it BEFORE applying
      # redirects.

      # Another problem:
      # - tracing can be called concurrently from multiple processes, leading
      # to overlap.  Maybe have a mode that creates a file per process.
      # xtrace-proc
      # - line numbers for every command would be very nice.  But then you have
      # to print the filename too.

      words = braces.BraceExpandWords(node.words)
      argv = self.ev.EvalWordSequence(words)
      if argv:
        argv0 = argv[0]

      environ = self.mem.GetExported()
      self._EvalEnv(node.more_env, environ)

      if self.exec_opts.xtrace:
        log('+ %s', argv)
        #print('+ %s' % argv, file=sys.stderr)
        #print('+ %s' % argv, file=self.XFILE)
        #os.write(2, '+ %s\n' % argv)

      status = self._RunSimpleCommand(argv, environ, fork_external)

      if self.exec_opts.xtrace:
        #log('+ %s -> %d', argv, status)
        pass

    elif node.tag == command_e.Sentence:
      if node.terminator.id == Id.Op_Semi:
        # Don't check_errexit since this isn't a real node!
        check_errexit = False
        status = self._Execute(node.child)
      else:
        status = self._RunJobInBackground(node.child)

    elif node.tag == command_e.Pipeline:
      if node.stderr_indices:
        raise NotImplementedError('|&')

      if node.negated:
        self._PushErrExit()
        try:
          status2 = self._RunPipeline(node)
        finally:
          self._PopErrExit()

        # errexit is disabled for !.
        check_errexit = False
        status = 1 if status2 == 0 else 0
      else:
        status = self._RunPipeline(node)

    elif node.tag == command_e.Subshell:
      # This makes sure we don't waste a process if we'd launch one anyway.
      p = self._MakeProcess(node.child)
      status = p.Run(self.waiter)

    elif node.tag == command_e.DBracket:
      result = self.bool_ev.Eval(node.expr)
      status = 0 if result else 1

    elif node.tag == command_e.DParen:
      i = self.arith_ev.Eval(node.child)
      status = 0 if i != 0 else 1

    elif node.tag == command_e.Assignment:
      pairs = []
      if node.keyword == Id.Assign_Local:
        lookup_mode = scope.LocalOnly
        flags = ()
      elif node.keyword == Id.Assign_Declare:
        # declare is like local, except it can also be used outside functions?
        lookup_mode = scope.LocalOnly
        # TODO: Respect flags.  -r and -x matter, but -a and -A might be
        # implicit in the RHS?
        flags = ()
      elif node.keyword == Id.Assign_Readonly:
        lookup_mode = scope.Dynamic
        flags = (var_flags.ReadOnly,)
      elif node.keyword == Id.Assign_None:  # mutate existing local or global
        lookup_mode = scope.Dynamic
        flags = ()
      else:
        # TODO: typeset, declare, etc.  Those are dynamic though.
        raise NotImplementedError(node.keyword)

      for pair in node.pairs:
        if pair.rhs:
          # RHS can be a string or array.
          val = self.ev.EvalWordToAny(pair.rhs)
          assert isinstance(val, runtime.value), val
        else:
          # 'local x' is equivalent to local x=""
          val = runtime.Str('')

        if pair.op == assign_op.PlusEqual:
          old_val, lval = expr_eval.EvalLhs(pair.lhs, self.arith_ev, self.mem,
                                            self.exec_opts)
          sig = (old_val.tag, val.tag)
          if sig == (value_e.Str, value_e.Str):
            val = runtime.Str(old_val.s + val.s)
          elif sig == (value_e.Str, value_e.StrArray):
            e_die("Can't append array to string")
          elif sig == (value_e.StrArray, value_e.Str):
            e_die("Can't append string to array")
          elif sig == (value_e.StrArray, value_e.StrArray):
            val = runtime.StrArray(old_val.strs + val.strs)
        else:
          lval = self._EvalLhs(pair.lhs)

        #log('ASSIGNING %s -> %s', lval, val)
        self.mem.SetVar(lval, val, flags, lookup_mode)

      # TODO: This should be eval of RHS, unlike bash!
      status = 0

    elif node.tag == command_e.ControlFlow:
      if node.arg_word:  # Evaluate the argument
        val = self.ev.EvalWordToString(node.arg_word)
        assert val.tag == value_e.Str
        arg = int(val.s)  # They all take integers
      else:
        arg = 0  # return 0, break 0 levels, etc.

      # NOTE: always raises so we don't set status.
      raise _ControlFlow(node.token, arg)

    # The only difference between these two is that CommandList has no
    # redirects.  We already took care of that above.
    elif node.tag in (command_e.CommandList, command_e.BraceGroup):
      status = self._ExecuteList(node.children)

    elif node.tag == command_e.AndOr:
      #print(node.children)
      left, right = node.children

      # This is everything except the last one.
      self._PushErrExit()
      try:
        status = self._Execute(left)
      finally:
        self._PopErrExit()

      if node.op_id == Id.Op_DPipe:
        if status != 0:
          status = self._Execute(right)
      elif node.op_id == Id.Op_DAmp:
        if status == 0:
          status = self._Execute(right)
      else:
        raise AssertionError

    elif node.tag in (command_e.While, command_e.Until):
      # TODO: Compile this out?
      if node.tag == command_e.While:
        _DonePredicate = lambda status: status != 0
      else:
        _DonePredicate = lambda status: status == 0

      status = 0
      while True:
        self._PushErrExit()
        try:
          cond_status = self._ExecuteList(node.cond)
        finally:
          self._PopErrExit()

        done = cond_status != 0
        if _DonePredicate(cond_status):
          break
        try:
          status = self._Execute(node.body)  # last one wins
        except _ControlFlow as e:
          if e.IsBreak():
            status = 0
            break
          elif e.IsContinue():
            status = 0
            continue
          else:  # return needs to pop up more
            raise

    elif node.tag == command_e.ForEach:
      iter_name = node.iter_name
      if node.do_arg_iter:
        iter_list = self.mem.GetArgv()
      else:
        words = braces.BraceExpandWords(node.iter_words)
        iter_list = self.ev.EvalWordSequence(words)
        # We need word splitting and so forth
        # NOTE: This expands globs too.  TODO: We should pass in a Globber()
        # object.
      status = 0  # in case we don't loop
      for x in iter_list:
        #log('> ForEach setting %r', x)
        state.SetLocalString(self.mem, iter_name, x)
        #log('<')

        try:
          status = self._Execute(node.body)  # last one wins
        except _ControlFlow as e:
          if e.IsBreak():
            status = 0
            break
          elif e.IsContinue():
            status = 0
            continue
          else:  # return needs to pop up more
            raise

    elif node.tag == command_e.ForExpr:
      raise NotImplementedError(node.tag)

    elif node.tag == command_e.DoGroup:
      status = self._ExecuteList(node.children)

    elif node.tag == command_e.FuncDef:
      # NOTE: Would it make sense to evaluate the redirects BEFORE entering?
      # It will save time on function calls.
      self.funcs[node.name] = node
      status = 0

    elif node.tag == command_e.If:
      done = False
      for arm in node.arms:
        self._PushErrExit()
        try:
          status = self._ExecuteList(arm.cond)
        finally:
          self._PopErrExit()

        if status == 0:
          status = self._ExecuteList(arm.action)
          done = True
          break
      # TODO: The compiler should flatten this
      if not done and node.else_action is not None:
        status = self._ExecuteList(node.else_action)

    elif node.tag == command_e.NoOp:
      status = 0  # make it true

    elif node.tag == command_e.Case:
      val = self.ev.EvalWordToString(node.to_match)
      to_match = val.s

      status = 0  # If there are no arms, it should be zero?
      done = False

      for arm in node.arms:
        for pat_word in arm.pat_list:
          # NOTE: Is it OK that we're evaluating these as we go?
          pat_val = self.ev.EvalWordToString(pat_word, do_fnmatch=True)
          #log('Matching word %r against pattern %r', to_match, pat_val.s)
          if libc.fnmatch(pat_val.s, to_match):
            status = self._ExecuteList(arm.action)
            done = True  # TODO: Parse ;;& and for fallthrough and such?
        if done:
          break

    elif node.tag == command_e.TimeBlock:
      # TODO:
      # - When do we need RUSAGE_CHILDREN?
      # - Respect TIMEFORMAT environment variable.
      # "If this variable is not set, Bash acts as if it had the value"
      # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
      # "A trailing newline is added when the format string is displayed."

      start_t = time.time()  # calls gettimeofday() under the hood
      start_u = resource.getrusage(resource.RUSAGE_SELF)
      status = self._Execute(node.pipeline)
      end_t = time.time()
      end_u = resource.getrusage(resource.RUSAGE_SELF)

      real = end_t - start_t
      user = end_u.ru_utime - start_u.ru_utime
      sys_ = end_u.ru_stime - start_u.ru_stime
      print('real\t%.3f' % real, file=sys.stderr)
      print('user\t%.3f' % user, file=sys.stderr)
      print('sys\t%.3f' % sys_, file=sys.stderr)

    else:
      raise AssertionError(node.tag)

    return status, check_errexit
コード例 #15
0
ファイル: state_test.py プロジェクト: optionalg/oil
    def testSetVarClearFlag(self):
        mem = state.Mem('', [], {})
        print(mem)

        mem.PushCall('my-func', ['ONE'])
        self.assertEqual(2, len(mem.var_stack))  # internal details

        # local x=y
        mem.SetVar(runtime.LhsName('x'), runtime.Str('y'), (), scope.LocalOnly)
        self.assertEqual('y', mem.var_stack[-1]['x'].val.s)

        # New frame
        mem.PushCall('my-func', ['TWO'])
        self.assertEqual(3, len(mem.var_stack))  # internal details

        # x=y -- test out dynamic scope
        mem.SetVar(runtime.LhsName('x'), runtime.Str('YYY'), (), scope.Dynamic)
        self.assertEqual('YYY', mem.var_stack[-2]['x'].val.s)
        self.assertEqual(None, mem.var_stack[-1].get('x'))

        # myglobal=g
        mem.SetVar(runtime.LhsName('myglobal'), runtime.Str('g'), (),
                   scope.Dynamic)
        self.assertEqual('g', mem.var_stack[0]['myglobal'].val.s)
        self.assertEqual(False, mem.var_stack[0]['myglobal'].exported)

        # 'export PYTHONPATH=/'
        mem.SetVar(runtime.LhsName('PYTHONPATH'), runtime.Str('/'),
                   (var_flags.Exported, ), scope.Dynamic)
        self.assertEqual('/', mem.var_stack[0]['PYTHONPATH'].val.s)
        self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)

        self.assertEqual({'PYTHONPATH': '/'}, mem.GetExported())

        mem.SetVar(runtime.LhsName('PYTHONPATH'), None, (var_flags.Exported, ),
                   scope.Dynamic)
        self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)

        # 'export myglobal'.  None means don't touch it.  Undef would be confusing
        # because it might mean "unset", but we have a separated API for that.
        mem.SetVar(runtime.LhsName('myglobal'), None, (var_flags.Exported, ),
                   scope.Dynamic)
        self.assertEqual(True, mem.var_stack[0]['myglobal'].exported)

        # export g2  -- define and export empty
        mem.SetVar(runtime.LhsName('g2'), None, (var_flags.Exported, ),
                   scope.Dynamic)
        self.assertEqual('', mem.var_stack[0]['g2'].val.s)
        self.assertEqual(True, mem.var_stack[0]['g2'].exported)

        # readonly myglobal
        self.assertEqual(False, mem.var_stack[0]['myglobal'].readonly)
        mem.SetVar(runtime.LhsName('myglobal'), None, (var_flags.ReadOnly, ),
                   scope.Dynamic)
        self.assertEqual(True, mem.var_stack[0]['myglobal'].readonly)

        mem.SetVar(runtime.LhsName('PYTHONPATH'), runtime.Str('/lib'), (),
                   scope.Dynamic)
        self.assertEqual('/lib', mem.var_stack[0]['PYTHONPATH'].val.s)
        self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)

        # COMPREPLY=(1 2 3)
        # invariant to enforce: arrays can't be exported
        mem.SetVar(runtime.LhsName('COMPREPLY'),
                   runtime.StrArray(['1', '2', '3']), (), scope.GlobalOnly)
        self.assertEqual(['1', '2', '3'],
                         mem.var_stack[0]['COMPREPLY'].val.strs)

        # export COMPREPLY
        try:
            mem.SetVar(runtime.LhsName('COMPREPLY'), None,
                       (var_flags.Exported, ), scope.Dynamic)
        except util.FatalRuntimeError as e:
            pass
        else:
            self.fail("Expected failure")

        # readonly r=1
        mem.SetVar(runtime.LhsName('r'), runtime.Str('1'),
                   (var_flags.ReadOnly, ), scope.Dynamic)
        self.assertEqual('1', mem.var_stack[0]['r'].val.s)
        self.assertEqual(False, mem.var_stack[0]['r'].exported)
        self.assertEqual(True, mem.var_stack[0]['r'].readonly)
        print(mem)

        # r=newvalue
        try:
            mem.SetVar(runtime.LhsName('r'), runtime.Str('newvalue'), (),
                       scope.Dynamic)
        except util.FatalRuntimeError as e:
            pass
        else:
            self.fail("Expected failure")

        # readonly r2  -- define empty readonly
        mem.SetVar(runtime.LhsName('r2'), None, (var_flags.ReadOnly, ),
                   scope.Dynamic)
        self.assertEqual('', mem.var_stack[0]['r2'].val.s)
        self.assertEqual(True, mem.var_stack[0]['r2'].readonly)

        # export -n PYTHONPATH
        # Remove the exported property.  NOTE: scope is LocalOnly for Oil?
        self.assertEqual(True, mem.var_stack[0]['PYTHONPATH'].exported)
        mem.ClearFlag('PYTHONPATH', var_flags.Exported, scope.Dynamic)
        self.assertEqual(False, mem.var_stack[0]['PYTHONPATH'].exported)

        # a[2]=2
        mem.SetVar(runtime.LhsIndexedName('a', 1), runtime.Str('2'), (),
                   scope.Dynamic)
        self.assertEqual(['', '2'], mem.var_stack[0]['a'].val.strs)

        # a[2]=3
        mem.SetVar(runtime.LhsIndexedName('a', 1), runtime.Str('3'), (),
                   scope.Dynamic)
        self.assertEqual(['', '3'], mem.var_stack[0]['a'].val.strs)

        # a[2]=(x y z)  # illegal
        try:
            mem.SetVar(runtime.LhsIndexedName('a', 1),
                       runtime.StrArray(['x', 'y', 'z']), (), scope.Dynamic)
        except util.FatalRuntimeError as e:
            pass
        else:
            self.fail("Expected failure")

        # readonly a
        mem.SetVar(runtime.LhsName('a'), None, (var_flags.ReadOnly, ),
                   scope.Dynamic)
        self.assertEqual(True, mem.var_stack[0]['a'].readonly)

        try:
            # a[2]=3
            mem.SetVar(runtime.LhsIndexedName('a', 1), runtime.Str('3'), (),
                       scope.Dynamic)
        except util.FatalRuntimeError as e:
            pass
        else:
            self.fail("Expected failure")
コード例 #16
0
    def _EvalBracedVarSub(self, part, quoted):
        """
    Returns:
      part_value[]
    """
        # We have four types of operator that interact.
        #
        # 1. Bracket: value -> (value, bool 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 decay_array here before returning.

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

        # 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.Get(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, decay_array = self._EvalSpecialVar(part.token.id, quoted)

        # 2. Bracket: value -> (value v, bool decay_array)
        # 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:
                        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:
                        raise RuntimeError("Can't index string with @")
                    elif val.tag == value_e.StrArray:
                        val = runtime.StrArray(val.strs)

                elif op_id == Id.Arith_Star:
                    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:
                        raise RuntimeError("Can't index string with *")
                    elif val.tag == value_e.StrArray:
                        # Always decay_array with ${a[*]} or "${a[*]}"
                        val = runtime.StrArray(val.strs)

                else:
                    raise AssertionError(op_id)  # unknown

            elif part.bracket_op.tag == bracket_op_e.ArrayIndex:
                anode = part.bracket_op.expr
                # TODO: This should propagate errors
                arith_ev = expr_eval.ArithEvaluator(self.mem, self.word_ev)
                index = arith_ev.Eval(anode)

                if val.tag == value_e.Undef:
                    pass  # it will be checked later
                elif val.tag == value_e.Str:
                    # TODO: Implement this as an extension. Requires unicode support.
                    # Bash treats it as an array.
                    e_die("Can't index string %r with integer", part.token.val)
                elif val.tag == value_e.StrArray:
                    try:
                        s = val.strs[index]
                    except IndexError:
                        val = runtime.Undef()
                    else:
                        val = runtime.Str(s)

            else:
                raise AssertionError(part.bracket_op.tag)

        if part.prefix_op:
            val = self._EmptyStrOrError(val)  # maybe error
            val = self._ApplyPrefixOp(val, part.prefix_op)
            # At least for length, we can't have a test or suffix afterward.

        elif part.suffix_op:
            out_part_vals = []
            if LookupKind(part.suffix_op.op_id) == Kind.VTest:
                # VTest: value -> part_value[]
                new_part_vals, effect = self._ApplyTestOp(
                    val, part.suffix_op, quoted)

                # 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.SpliceParts:
                    return new_part_vals  # EARLY RETURN

                elif effect == Effect.SpliceAndAssign:
                    raise NotImplementedError

                elif effect == Effect.Error:
                    raise NotImplementedError

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

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

        # After applying suffixes, process decay_array here.
        if decay_array:
            val = self._DecayArray(val)

        # No prefix or suffix ops
        val = self._EmptyStrOrError(val)

        return [_ValueToPartValue(val, quoted)]
コード例 #17
0
    def _EvalBracedVarSub(self, part, quoted):
        """
    Returns:
      part_value[]
    """
        # We have four types of operator that interact.
        #
        # 1. Bracket: value -> (value, bool 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 decay_array here before returning.

        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, decay_array = self._EvalSpecialVar(part.token.id, quoted)

        # 2. Bracket: value -> (value v, bool decay_array)
        # 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:
                        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:
                        raise RuntimeError("Can't index string with @")
                    elif val.tag == value_e.StrArray:
                        val = runtime.StrArray(val.strs)

                elif op_id == Id.Arith_Star:
                    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:
                        raise RuntimeError("Can't index string with *")
                    elif val.tag == value_e.StrArray:
                        # Always decay_array with ${a[*]} or "${a[*]}"
                        val = runtime.StrArray(val.strs)

                else:
                    raise AssertionError(op_id)  # unknown

            elif part.bracket_op.tag == bracket_op_e.ArrayIndex:
                anode = part.bracket_op.expr
                index = self.arith_ev.Eval(anode)

                if val.tag == value_e.Undef:
                    pass  # it will be checked later
                elif val.tag == value_e.Str:
                    # TODO: Implement this as an extension. Requires unicode support.
                    # Bash treats it as an array.
                    e_die("Can't index string %r with integer", part.token.val)
                elif val.tag == value_e.StrArray:
                    try:
                        s = val.strs[index]
                    except IndexError:
                        val = runtime.Undef()
                    else:
                        val = runtime.Str(s)

            else:
                raise AssertionError(part.bracket_op.tag)

        if part.prefix_op:
            val = self._EmptyStrOrError(val)  # maybe error
            val = self._ApplyPrefixOp(val, part.prefix_op)
            # At least for length, we can't have a test or suffix afterward.

        elif part.suffix_op:
            out_part_vals = []
            op = part.suffix_op
            if op.tag == suffix_op_e.StringUnary:
                if LookupKind(part.suffix_op.op_id) == Kind.VTest:
                    # VTest: value -> part_value[]
                    new_part_vals, effect = self._ApplyTestOp(
                        val, part.suffix_op, quoted)

                    # 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.SpliceParts:
                        return new_part_vals  # EARLY RETURN

                    elif effect == Effect.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(
                                new_part_vals, _GetJoinChar(self.mem))
                            state.SetLocalString(self.mem, var_name, rhs_str)
                        return new_part_vals

                    elif effect == Effect.Error:
                        raise NotImplementedError

                    else:
                        # The old one
                        #val = self._EmptyStringPartOrError(part_val, quoted)
                        #out_part_vals.append(part_val)
                        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
                pat_val = self.word_ev.EvalWordToString(op.pat,
                                                        do_fnmatch=True)
                assert pat_val.tag == value_e.Str, pat_val

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

                pat = pat_val.s
                if val.tag == value_e.Str:
                    s = self._PatSub(val.s, op, pat, replace_str)
                    val = runtime.Str(s)
                elif val.tag == value_e.StrArray:
                    strs = []
                    for s in val.strs:
                        strs.append(self._PatSub(s, op, pat, replace_str))
                    val = runtime.StrArray(strs)

                else:
                    raise AssertionError(val.tag)

            elif op.tag == suffix_op_e.Slice:
                # Either string slicing or array slicing.  However string slicing has
                # a unicode problem?
                # Or maybe have a different operator for byte slice and char slice.
                raise NotImplementedError(op)

        # After applying suffixes, process decay_array here.
        if decay_array:
            val = self._DecayArray(val)

        # No prefix or suffix ops
        val = self._EmptyStrOrError(val)

        return [_ValueToPartValue(val, quoted)]
コード例 #18
0
ファイル: state.py プロジェクト: optionalg/oil
  def SetVar(self, lval, value, 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?

    assert new_flags is not None

    if lval.tag == lvalue_e.LhsName:
      # Maybe this should return one of (cell, scope).  existing cell, or the
      # scope to put it in?
      # _FindCellOrScope

      cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode)
      if cell:
        if value is not None:
          if cell.readonly:
            # TODO: error context
            e_die("Can't assign to readonly value %r", lval.name)
          cell.val = value
        if var_flags.Exported in new_flags:
          cell.exported = True
        if var_flags.ReadOnly in new_flags:
          cell.readonly = True
      else:
        if value is None:
          value = runtime.Str('')  # export foo, readonly foo
        cell = runtime.cell(value,
                            var_flags.Exported in new_flags ,
                            var_flags.ReadOnly 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:
      # a[1]=(1 2 3)
      if value.tag == value_e.StrArray:
        e_die("Can't assign array to array member")  # TODO: error context

      cell, namespace = self._FindCellAndNamespace(lval.name, lookup_mode)
      if cell:
        if cell.val.tag != value_e.StrArray:
          # s=x
          # s[1]=y
          e_die("Can't index non-array")  # TODO: error context

        if cell.readonly:
          e_die("Can't assign to readonly value")

        strs = cell.val.strs
        try:
          strs[lval.index] = value.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 = len(strs) - lval.index + 1
          strs.extend([None] * n)
          strs[lval.index] = value.s
      else:
        # TODO:
        # - This is a bug, because a[2]=2 creates an array of length ONE, even
        # though the index is two.
        # - Maybe represent as hash table?  Then it's not an ASDL type?

        # representations:
        # - array_item.Str array_item.Undef
        # - parallel array: val.strs, val.undefs
        # - or change ASDL type checking
        #   - ASDL language does not allow: StrArray(string?* strs)
        # - or add dict to ASDL?  Didn't it support obj?
        #   - finding the max index is linear time?
        #     - also you have to sort the indices
        #
        # array ops:
        # a=(1 2)
        # a[1]=x
        # a+=(1 2)
        # ${a[@]}  - get all
        # ${#a[@]} - length
        # ${!a[@]} - keys
        # That seems pretty minimal.

        items = [''] * lval.index
        items.append(value.s)
        new_value = runtime.StrArray(items)
        # arrays can't be exported
        cell = runtime.cell(new_value, False,
                            var_flags.ReadOnly in new_flags)
        namespace[lval.name] = cell

    else:
      raise AssertionError
コード例 #19
0
ファイル: state.py プロジェクト: optionalg/oil
def SetGlobalArray(mem, name, a):
  """Helper for completion."""
  assert isinstance(a, list)
  mem.SetVar(ast.LhsName(name), runtime.StrArray(a), (), scope.GlobalOnly)