예제 #1
0
    def Run(self, cmd_val):
        arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids)
        arg_r.Next()  # skip 'json'

        action, action_spid = arg_r.Peek2()
        if action is None:
            raise error.Usage(_JSON_ACTION_ERROR)
        arg_r.Next()

        if action == 'write':
            arg, _ = JSON_WRITE_SPEC.Parse(arg_r)

            # GetVar() of each name and print it.

            for var_name in arg_r.Rest():
                if var_name.startswith(':'):
                    var_name = var_name[1:]

                val = self.mem.GetVar(var_name)
                with tagswitch(val) as case:
                    if case(value_e.Undef):
                        # TODO: blame the right span_id
                        self.errfmt.Print("no variable named %r is defined",
                                          var_name)
                        return 1
                    elif case(value_e.Str):
                        obj = val.s
                    elif case(value_e.MaybeStrArray):
                        obj = val.strs
                    elif case(value_e.AssocArray):
                        obj = val.d
                    elif case(value_e.Obj):
                        obj = val.obj
                    else:
                        raise AssertionError(val)

                if arg.pretty:
                    indent = arg.indent
                    extra_newline = False
                else:
                    # How yajl works: if indent is -1, then everything is on one line.
                    indent = -1
                    extra_newline = True

                j = yajl.dump(obj, sys.stdout, indent=indent)
                if extra_newline:
                    sys.stdout.write('\n')

            # TODO: Accept a block.  They aren't hooked up yet.
            if cmd_val.block:
                # TODO: flatten value.{Str,Obj} into a flat dict?
                namespace = self.cmd_ev.EvalBlock(cmd_val.block)

                print(yajl.dump(namespace))

        elif action == 'read':
            arg, _ = JSON_READ_SPEC.Parse(arg_r)
            # TODO:
            # Respect -validate=F

            var_name, name_spid = arg_r.ReadRequired2("expected variable name")
            if var_name.startswith(':'):
                var_name = var_name[1:]

            if not match.IsValidVarName(var_name):
                raise error.Usage('got invalid variable name %r' % var_name,
                                  span_id=name_spid)

            try:
                # Use a global _STDIN, because we get EBADF on a redirect if we use a
                # local.  A Py_DECREF closes the file, which we don't want, because the
                # redirect is responsible for freeing it.
                #
                # https://github.com/oilshell/oil/issues/675
                #
                # TODO: write a better binding like yajl.readfd()
                #
                # It should use streaming like here:
                # https://lloyd.github.io/yajl/

                obj = yajl.load(_STDIN)
            except ValueError as e:
                self.errfmt.Print('json read: %s', e, span_id=action_spid)
                return 1

            self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj),
                            scope_e.LocalOnly)

        else:
            raise error.Usage(_JSON_ACTION_ERROR, span_id=action_spid)

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

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

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

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

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

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

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

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

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

        if node.tag == expr_e.CommandSub:
            return self.ex.RunCommandSub(node.command_list)

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

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

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

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

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

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

            raise NotImplementedError(node.op.id)

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

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

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

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

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

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

            raise NotImplementedError(node.op.id)

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

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

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

                right = self.EvalExpr(right_expr)

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

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

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

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

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

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

                if not result:
                    return result

                left = right
            return result

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

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

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

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

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

            return dict(zip(keys, values))

        if node.tag == expr_e.ListComp:

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

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

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

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

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

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

            return result

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

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

            it = obj.__iter__()

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

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

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

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

            return _gen()

        if node.tag == expr_e.Lambda:
            return objects.Lambda(node, self.ex)

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

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

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

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

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

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

            raise AssertionError(id_)

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

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

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

        raise NotImplementedError(node.__class__.__name__)
예제 #3
0
    def __call__(self, cmd_val):
        arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids)
        arg_r.Next()  # skip 'json'

        action, action_spid = arg_r.Peek2()
        if action is None:
            raise args.UsageError(_JSON_ACTION_ERROR)
        arg_r.Next()

        if action == 'write':
            arg, _ = JSON_WRITE_SPEC.Parse(arg_r)

            # GetVar() of each name and print it.

            for var_name in arg_r.Rest():
                if var_name.startswith(':'):
                    var_name = var_name[1:]

                val = self.mem.GetVar(var_name)
                with tagswitch(val) as case:
                    if case(value_e.Undef):
                        # TODO: blame the right span_id
                        self.errfmt.Print("no variable named %r is defined",
                                          var_name)
                        return 1
                    elif case(value_e.Str):
                        obj = val.s
                    elif case(value_e.MaybeStrArray):
                        obj = val.strs
                    elif case(value_e.AssocArray):
                        obj = val.d
                    elif case(value_e.Obj):
                        obj = val.obj
                    else:
                        raise AssertionError(val)

                if arg.pretty:
                    indent = arg.indent
                    extra_newline = False
                else:
                    # How yajl works: if indent is -1, then everything is on one line.
                    indent = -1
                    extra_newline = True

                j = yajl.dump(obj, sys.stdout, indent=indent)
                if extra_newline:
                    sys.stdout.write('\n')

            # TODO: Accept a block.  They aren't hooked up yet.
            if cmd_val.block:
                # TODO: flatten value.{Str,Obj} into a flat dict?
                namespace = self.ex.EvalBlock(cmd_val.block)

                print(yajl.dump(namespace))

        elif action == 'read':
            arg, _ = JSON_READ_SPEC.Parse(arg_r)
            # TODO:
            # Respect -validate=F

            var_name, name_spid = arg_r.ReadRequired2("expected variable name")
            if var_name.startswith(':'):
                var_name = var_name[1:]

            if not match.IsValidVarName(var_name):
                raise args.UsageError('got invalid variable name %r' %
                                      var_name,
                                      span_id=name_spid)

            # Have to use this over sys.stdin because of redirects
            # TODO: change binding to yajl.readfd() ?
            stdin = posix_.fdopen(0)
            try:
                obj = yajl.load(stdin)
            except ValueError as e:
                self.errfmt.Print('json read: %s', e, span_id=action_spid)
                return 1

            self.mem.SetVar(sh_lhs_expr.Name(var_name), value.Obj(obj), (),
                            scope_e.LocalOnly)

        else:
            raise args.UsageError(_JSON_ACTION_ERROR, span_id=action_spid)

        return 0
def SetGlobalFunc(mem, name, func):
    # type: (Mem, str, Union[Callable, ParameterizedArray, type]) -> None
    """Used by bin/oil.py to set split(), etc."""
    assert callable(func), func
    mem.SetVar(sh_lhs_expr.Name(name), value.Obj(func), scope_e.GlobalOnly)
예제 #5
0
def SetGlobalFunc(mem, name, func):
    """Used by bin/oil.py to set split(), etc."""
    assert callable(func), func
    mem.SetVar(sh_lhs_expr.Name(name), value.Obj(func), (), scope_e.GlobalOnly)
예제 #6
0
파일: expr_eval.py 프로젝트: roryokane/oil
    def EvalExpr(self, node):
        # type: (expr_t) -> Any
        """
    This is a naive PyObject evaluator!  It uses the type dispatch of the host
    Python interpreter.

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

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

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

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

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

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

            raise AssertionError(id_)

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

        if node.tag == expr_e.CommandSub:
            return self.ex.RunCommandSub(node.command_list)

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

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

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

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

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

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

            raise NotImplementedError(node.op.id)

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

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

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

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

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

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

            raise NotImplementedError(node.op.id)

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

                right = self.EvalExpr(right_expr)

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

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

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

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

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

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

                if not result:
                    return result

                left = right
            return result

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

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

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

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

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

            return dict(zip(keys, values))

        if node.tag == expr_e.ListComp:

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

            # TODO: Handle x,y etc.
            iter_name = comp.target.name.val

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

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

                if comp.ifs:
                    b = self.EvalExpr(comp.ifs[0])
                else:
                    b = True

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

            return result

        if node.tag == expr_e.FuncCall:
            # TODO:
            #
            # Let Python handle type errors for now?

            # TODO: Lookup in equivalent of __builtins__
            #
            # shopt -s namespaces
            #
            # builtin log "hello"
            # builtin log "hello"

            #node.PrettyPrint()

            # TODO: All functions called like f(x, y) must be in 'mem'.
            # Only 'procs' are in self.funcs

            # First look up the name in 'funcs'.  And then look it up
            # in 'mem' for first-class functions?
            #if node.func.tag == expr_e.Var:
            #  func = self.funcs.get(node.func.name.val)

            func = self.EvalExpr(node.func)

            args = [self.EvalExpr(a) for a in node.args]

            ret = func(*args)
            return ret

        if node.tag == expr_e.Subscript:
            collection = self.EvalExpr(node.collection)

            # TODO: handle multiple indices like a[i, j]
            index = self.EvalExpr(node.indices[0])
            return collection[index]

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

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

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

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

            raise AssertionError(id_)

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

        raise NotImplementedError(node.__class__.__name__)
예제 #7
0
  def _Dispatch(self, node, fork_external):
    # If we call RunCommandSub in a recursive call to the executor, this will
    # be set true (if strict-errexit is false).  But it only lasts for one
    # command.
    self.check_command_sub_status = False

    #argv0 = None  # for error message
    check_errexit = False  # for errexit

    if node.tag == command_e.SimpleCommand:
      check_errexit = True

      # Find span_id for a basic implementation of $LINENO, e.g.
      # PS4='+$SOURCE_NAME:$LINENO:'
      # NOTE: osh2oil uses node.more_env, but we don't need that.
      span_id = const.NO_INTEGER
      if node.words:
        span_id = word.LeftMostSpanForWord(node.words[0])
      elif node.redirects:
        span_id = node.redirects[0].op  # note: this could be a here doc?

      self.mem.SetCurrentSpanId(span_id)

      # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
      # redirected here, which screws up logging.  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)
      cmd_val = self.word_ev.EvalWordSequence2(words, allow_assign=True)

      # STUB for compatibility.  TODO: Handle cmd_value_e.Assign.
      if cmd_val.tag == cmd_value_e.Argv:
        argv = cmd_val.argv
      else:
        argv = ['TODO: assignment builtin']

      # This comes before evaluating env, in case there are problems evaluating
      # it.  We could trace the env separately?  Also trace unevaluated code
      # with set-o verbose?
      self.tracer.OnSimpleCommand(argv)

      # NOTE: RunSimpleCommand never returns when fork_external=False!
      if node.more_env:  # I think this guard is necessary?
        is_other_special = False  # TODO: There are other special builtins too!
        if cmd_val.tag == cmd_value_e.Assign or is_other_special:
          # Special builtins have their temp env persisted.
          self._EvalTempEnv(node.more_env, ())
          status = self._RunSimpleCommand(cmd_val, fork_external)
        else:
          self.mem.PushTemp()
          try:
            self._EvalTempEnv(node.more_env, (var_flags_e.Exported,))
            status = self._RunSimpleCommand(cmd_val, fork_external)
          finally:
            self.mem.PopTemp()
      else:
        status = self._RunSimpleCommand(cmd_val, fork_external)

    elif node.tag == command_e.ExpandedAlias:
      # Expanded aliases need redirects and env bindings from the calling
      # context, as well as redirects in the expansion!

      # TODO: SetCurrentSpanId to OUTSIDE?  Don't bother with stuff inside
      # expansion, since aliases are discouarged.

      if node.more_env:
        self.mem.PushTemp()
        try:
          self._EvalTempEnv(node.more_env, (var_flags_e.Exported,))
          status = self._Execute(node.child)
        finally:
          self.mem.PopTemp()
      else:
        status = self._Execute(node.child)

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

    elif node.tag == command_e.Pipeline:
      check_errexit = True
      if node.stderr_indices:
        e_die("|& isn't supported", span_id=node.spids[0])

      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:
      check_errexit = True
      # This makes sure we don't waste a process if we'd launch one anyway.
      p = self._MakeProcess(node.command_list)
      status = p.Run(self.waiter)

    elif node.tag == command_e.DBracket:
      span_id = node.spids[0]
      self.mem.SetCurrentSpanId(span_id)

      check_errexit = True
      result = self.bool_ev.Eval(node.expr)
      status = 0 if result else 1

    elif node.tag == command_e.DParen:
      span_id = node.spids[0]
      self.mem.SetCurrentSpanId(span_id)

      check_errexit = True
      i = self.arith_ev.Eval(node.child)
      status = 0 if i != 0 else 1

    elif node.tag == command_e.OilAssign:
      # TODO: maybe pick out LHS and RHS here.
      # And then use mem and everything.

      lval = self.expr_ev.EvalLHS(node.lhs)
      py_val = self.expr_ev.EvalRHS(node.rhs)

      if node.op.id == Id.Arith_Equal:
        val = value.Obj(py_val)
        flags = ()
        self.mem.SetVar(lval, val, flags, scope_e.LocalOnly)

      elif node.op.id == Id.Arith_PlusEqual:
        old_py_val = self.expr_ev.LookupVar(lval.name)

        val = value.Obj(old_py_val + py_val)
        flags = ()
        self.mem.SetVar(lval, val, flags, scope_e.LocalOnly)

      else:
        raise NotImplementedError(node.op)

      status = 0  # TODO: what should status be?

    elif node.tag == command_e.Assignment:  # Only unqualified assignment

      lookup_mode = scope_e.Dynamic
      for pair in node.pairs:
        # Use the spid of each pair.
        self.mem.SetCurrentSpanId(pair.spids[0])

        if pair.op == assign_op_e.PlusEqual:
          assert pair.rhs, pair.rhs  # I don't think a+= is valid?
          val = self.word_ev.EvalRhsWord(pair.rhs)
          old_val, lval = expr_eval.EvalLhsAndLookup(pair.lhs, self.arith_ev,
                                                     self.mem, self.exec_opts,
                                                     lookup_mode=lookup_mode)
          sig = (old_val.tag, val.tag)
          if sig == (value_e.Undef, value_e.Str):
            pass  # val is RHS
          elif sig == (value_e.Undef, value_e.StrArray):
            pass  # val is RHS
          elif sig == (value_e.Str, value_e.Str):
            val = value.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 = value.StrArray(old_val.strs + val.strs)

        else:  # plain assignment
          spid = pair.spids[0]  # Source location for tracing
          lval = expr_eval.EvalLhs(pair.lhs, self.arith_ev, self.mem, spid,
                                   lookup_mode)

          # RHS can be a string or array.
          if pair.rhs:
            val = self.word_ev.EvalRhsWord(pair.rhs)
            assert isinstance(val, value_t), val

          else:  # e.g. 'readonly x' or 'local x'
            val = None

        # NOTE: In bash and mksh, declare -a myarray makes an empty cell with
        # Undef value, but the 'array' attribute.

        #log('setting %s to %s with flags %s', lval, val, flags)
        flags = ()
        self.mem.SetVar(lval, val, flags, lookup_mode)
        self.tracer.OnAssignment(lval, pair.op, val, flags, lookup_mode)

      # PATCH to be compatible with existing shells: If the assignment had a
      # command sub like:
      #
      # s=$(echo one; false)
      #
      # then its status will be in mem.last_status, and we can check it here.
      # If there was NOT a command sub in the assignment, then we don't want to
      # check it.

      # Only do this if there was a command sub?  How?  Look at node?
      # Set a flag in mem?   self.mem.last_status or
      if self.check_command_sub_status:
        last_status = self.mem.LastStatus()
        self._CheckStatus(last_status, node)
        status = last_status  # A global assignment shouldn't clear $?.
      else:
        status = 0

    elif node.tag == command_e.ControlFlow:
      tok = node.token

      if node.arg_word:  # Evaluate the argument
        val = self.word_ev.EvalWordToString(node.arg_word)
        assert val.tag == value_e.Str
        try:
          arg = int(val.s)  # They all take integers
        except ValueError:
          e_die('%r expected a number, got %r',
              node.token.val, val.s, word=node.arg_word)
      else:
        if tok.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
          arg = self.mem.LastStatus()
        else:
          arg = 0  # break 0 levels, nothing for continue

      # NOTE: A top-level 'return' is OK, unlike in bash.  If you can return
      # from a sourced script, it makes sense to return from a main script.
      ok = True
      if (tok.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
          self.loop_level == 0):
        ok = False

      if ok:
        if tok.id == Id.ControlFlow_Exit:
          raise util.UserExit(arg)  # handled differently than other control flow
        else:
          raise _ControlFlow(tok, arg)
      else:
        msg = 'Invalid control flow at top level'
        if self.exec_opts.strict_control_flow:
          e_die(msg, token=tok)
        else:
          # Only print warnings, never fatal.
          # Bash oddly only exits 1 for 'return', but no other shell does.
          self.errfmt.Print(msg, prefix='warning: ', span_id=tok.span_id)
          status = 0

    # 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)
      check_errexit = False

    elif node.tag == command_e.AndOr:
      # NOTE: && and || have EQUAL precedence in command mode.  See case #13
      # in dbracket.test.sh.

      left = node.children[0]

      # Suppress failure for every child except the last one.
      self._PushErrExit()
      try:
        status = self._Execute(left)
      finally:
        self._PopErrExit()

      i = 1
      n = len(node.children)
      while i < n:
        #log('i %d status %d', i, status)
        child = node.children[i]
        op_id = node.ops[i-1]

        #log('child %s op_id %s', child, op_id)

        if op_id == Id.Op_DPipe and status == 0:
          i += 1
          continue  # short circuit

        elif op_id == Id.Op_DAmp and status != 0:
          i += 1
          continue  # short circuit

        if i == n - 1:  # errexit handled differently for last child
          status = self._Execute(child)
          check_errexit = True
        else:
          self._PushErrExit()
          try:
            status = self._Execute(child)
          finally:
            self._PopErrExit()

        i += 1

    elif node.tag == command_e.WhileUntil:
      if node.keyword.id == Id.KW_While:
        _DonePredicate = lambda status: status != 0
      else:
        _DonePredicate = lambda status: status == 0

      status = 0

      self.loop_level += 1
      try:
        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
      finally:
        self.loop_level -= 1

    elif node.tag == command_e.ForEach:
      self.mem.SetCurrentSpanId(node.spids[0])  # for x in $LINENO

      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.word_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
      self.loop_level += 1
      try:
        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
            else:  # return needs to pop up more
              raise
      finally:
        self.loop_level -= 1

    elif node.tag == command_e.ForExpr:
      status = 0
      init, cond, body, update = node.init, node.cond, node.body, node.update
      if init:
        self.arith_ev.Eval(init)

      self.loop_level += 1
      try:
        while True:
          if cond:
            b = self.arith_ev.Eval(cond)
            if not b:
              break

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

          if update:
            self.arith_ev.Eval(update)

      finally:
        self.loop_level -= 1

    elif node.tag == command_e.DoGroup:
      status = self._ExecuteList(node.children)
      check_errexit = False  # not real statements

    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.word_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?

          # TODO: case "$@") shouldn't succeed?  That's a type error?
          # That requires strict-array?

          pat_val = self.word_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?
            break  # Only execute action ONCE
        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
      libc.print_time(real, user, sys_)

    else:
      raise NotImplementedError(node.__class__.__name__)

    return status, check_errexit