protected override Expression visitExpression(Expression expr)
        {
            var origExpr = expr;

            expr = base.visitExpression(expr);

            if (expr is BinaryExpression binExpr && binExpr.operator_ == "in")
            {
                expr = new UnresolvedCallExpression(new PropertyAccessExpression(binExpr.right, "hasKey"), new IType[0], new Expression[] { binExpr.left });
            }

            expr.parentNode = origExpr.parentNode;
            return(expr);
        }
        public Expression parse(int precedence = 0, bool required = true)
        {
            this.reader.skipWhitespace();
            var leftStart = this.reader.offset;
            var left      = this.parseLeft(required);

            if (left == null)
            {
                return(null);
            }
            this.addNode(left, leftStart);

            while (true)
            {
                if (this.hooks != null)
                {
                    var parsed = this.hooks.infixPrehook(left);
                    if (parsed != null)
                    {
                        left = parsed;
                        this.addNode(left, leftStart);
                        continue;
                    }
                }

                var op = this.parseOperator();
                if (op == null || op.precedence <= precedence)
                {
                    break;
                }
                this.reader.expectToken(op.text);
                var opText = this.config.aliases.hasKey(op.text) ? this.config.aliases.get(op.text) : op.text;

                if (op.isBinary)
                {
                    var right = this.parse(op.isRightAssoc ? op.precedence - 1 : op.precedence);
                    left = new BinaryExpression(left, opText, right);
                }
                else if (op.isPostfix)
                {
                    left = new UnaryExpression(UnaryType.Postfix, opText, left);
                }
                else if (op.text == "?")
                {
                    var whenTrue = this.parse();
                    this.reader.expectToken(":");
                    var whenFalse = this.parse(op.precedence - 1);
                    left = new ConditionalExpression(left, whenTrue, whenFalse);
                }
                else if (op.text == "(")
                {
                    var args = this.parseCallArguments();
                    left = new UnresolvedCallExpression(left, new IType[0], args);
                }
                else if (op.text == "[")
                {
                    var elementExpr = this.parse();
                    this.reader.expectToken("]");
                    left = new ElementAccessExpression(left, elementExpr);
                }
                else if (this.config.propertyAccessOps.includes(op.text))
                {
                    var prop = this.reader.expectIdentifier("expected identifier as property name");
                    left = new PropertyAccessExpression(left, prop);
                }
                else
                {
                    this.reader.fail($"parsing '{op.text}' is not yet implemented");
                }

                this.addNode(left, leftStart);
            }

            if (left is ParenthesizedExpression parExpr && parExpr.expression is Identifier ident)
            {
                var expr = this.parse(0, false);
                if (expr != null)
                {
                    return(new CastExpression(new UnresolvedType(ident.text, new IType[0]), expr));
                }
            }

            return(left);
        }