Beispiel #1
0
        /*
         * Initializes the visitor with visitor functions.
         * The visitor functions evaluate the node's children, execute the node's action and
         * return a new ASTNode which contains the return value or null.
         */
        public Interpreter(ASTNode ast, IO io) : base("Interpreter", ast, io)
        {
            this.visitorFunctions.Add(ASTNodeType.NUMBER, x => x);
            this.visitorFunctions.Add(ASTNodeType.STRING, x => x);
            this.visitorFunctions.Add(ASTNodeType.BOOLEAN, x => x);
            this.visitorFunctions.Add(ASTNodeType.TYPENAME, x => x);
            this.visitorFunctions.Add(ASTNodeType.UNARYOPERATOR, x =>
            {
                var unaryOperation = As <UnaryOperatorNode>(x, ASTNodeType.UNARYOPERATOR);
                var operand        = ExpectNotNull(Visit(unaryOperation.operand), unaryOperation.operand);
                // Make sure the operand is boolean before checking boolean operators
                if (Is <BooleanNode>(operand))
                {
                    switch (unaryOperation.unaryOperator)
                    {
                    case "!":
                        return(new BooleanNode(!As <BooleanNode>(operand, ASTNodeType.BOOLEAN, unaryOperation.operand).value));
                    }
                    throw new VisitorException(string.Format("unrecognized boolean unary operator {0}", unaryOperation.unaryOperator), unaryOperation);
                }
                throw new VisitorException(string.Format("unrecognized unary operator {0} for operand type {1}", unaryOperation.unaryOperator, operand.type), unaryOperation);
            });
            this.visitorFunctions.Add(ASTNodeType.EXPRESSION, x =>
            {
                var expression = As <ExpressionNode>(x, ASTNodeType.EXPRESSION);
                if (expression.binaryOperator == null)
                {
                    return(ExpectNotNull(Visit(expression.leftOperand), expression.leftOperand)); // is not a binary operation
                }
                // is a binary operation
                // fetch all values
                var binaryOperation = As <BinaryOperatorNode>(expression.binaryOperator, ASTNodeType.BINARYOPERATOR);
                var operandLeft     = ExpectNotNull(Visit(expression.leftOperand), expression.leftOperand);
                var operandRight    = ExpectNotNull(Visit(binaryOperation.rightOperand), binaryOperation.rightOperand);
                // Do number operations
                if (Is <NumberNode>(operandLeft) && Is <NumberNode>(operandRight))
                {
                    var l = As <NumberNode>(operandLeft, ASTNodeType.NUMBER, expression.leftOperand);
                    var r = As <NumberNode>(operandRight, ASTNodeType.NUMBER, binaryOperation.rightOperand);
                    switch (binaryOperation.binaryOperator)
                    {
                    case "+":
                        return(new NumberNode(l.value + r.value));

                    case "-":
                        return(new NumberNode(l.value - r.value));

                    case "*":
                        return(new NumberNode(l.value * r.value));

                    case "/":
                        return(new NumberNode(l.value / r.value));

                    case "=":
                        return(new BooleanNode(l.value == r.value));

                    case "<":
                        return(new BooleanNode(l.value < r.value));
                    }
                    throw new VisitorException(string.Format("unknown integer binary operator {0}", binaryOperation.binaryOperator), binaryOperation);
                }
                // do string operations
                if (Is <StringNode>(operandLeft) && Is <StringNode>(operandRight))
                {
                    var l = As <StringNode>(operandLeft, ASTNodeType.STRING, expression.leftOperand);
                    var r = As <StringNode>(operandRight, ASTNodeType.STRING, binaryOperation.rightOperand);
                    switch (binaryOperation.binaryOperator)
                    {
                    case "+":
                        return(new StringNode(l.value + r.value));

                    case "=":
                        return(new BooleanNode(l.value == r.value));

                    case "<":
                        return(new BooleanNode(string.Compare(l.value, r.value) < 0));
                    }
                    throw new VisitorException(string.Format("unknown string binary operator {0}", binaryOperation.binaryOperator), binaryOperation);
                }
                // do boolean operations
                if (Is <BooleanNode>(operandLeft) && Is <BooleanNode>(operandRight))
                {
                    var l = As <BooleanNode>(operandLeft, ASTNodeType.BOOLEAN, expression.leftOperand);
                    var r = As <BooleanNode>(operandRight, ASTNodeType.BOOLEAN, binaryOperation.rightOperand);
                    switch (binaryOperation.binaryOperator)
                    {
                    case "&":
                        return(new BooleanNode(l.value && r.value));

                    case "=":
                        return(new BooleanNode(l.value == r.value));

                    case "<":
                        return(new BooleanNode(!l.value && r.value));
                    }
                    throw new VisitorException(string.Format("unknown boolean binary operator {0}", binaryOperation.binaryOperator), binaryOperation);
                }
                throw new VisitorException(string.Format("unknown binary operator {0} for operand types left: {1}, right: {2}", binaryOperation.binaryOperator, Visit(operandLeft).type, Visit(operandRight).type), binaryOperation);
            });
            this.visitorFunctions.Add(ASTNodeType.PRINT, x =>
            {
                var printNode = As <PrintNode>(x, ASTNodeType.PRINT);
                var printed   = As <ASTVariableNode>(Visit(printNode.printedValue), ASTNodeType.VARIABLE, printNode.printedValue);
                io.Write("{0}", printed.Value());
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.STATEMENT, x =>
            {
                var statements = As <StatementsNode>(x, ASTNodeType.STATEMENT);
                ExpectNull(Visit(statements.statement), statements.statement);
                if (statements.statementtail != null) // statement has another statement after it
                {
                    ExpectNull(Visit(statements.statementtail), statements.statementtail);
                }
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.ASSERT, x =>
            {
                var assertNode = As <AssertNode>(x, ASTNodeType.ASSERT);
                var condition  = As <BooleanNode>(Visit(assertNode.condition), ASTNodeType.BOOLEAN, assertNode.condition);
                if (!condition.value)
                {
                    // assertion failed.
                    // visit the assertion condition with a printer
                    // and collect the output to string IO.
                    var stringIO          = new StringIO();
                    var expressionPrinter = new ExpressionPrinter(assertNode.condition, stringIO);
                    expressionPrinter.Visit();

                    // stop execution by throwing an exception with the assertion error message.
                    // error message contains the asserted expression if visitor did not fail.
                    throw new VisitorException(
                        expressionPrinter.errored ?
                        "assertion failed" :
                        string.Format("assertion failed with condition {0}", stringIO.output),
                        assertNode
                        );
                }
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.READ, x =>
            {
                var readNode     = As <ReadNode>(x, ASTNodeType.READ);
                var identifier   = As <IdentifierNode>(readNode.identifierToRead, ASTNodeType.IDENTIFIER);
                var variable     = ExpectMutable(identifier, readNode.identifierToRead);
                var variableType = variable.value.type;
                switch (variableType)
                {
                case ASTNodeType.NUMBER:
                    while (true)
                    {
                        // keep trying to read input until a number is read successfully
                        try
                        {
                            variables[identifier.name] = new Variable(new NumberNode(ReadInput()));
                            break;
                        }
                        catch (System.FormatException /*e*/)
                        {
                            // Occurs when user inputs non number
                            // Do nothing
                        }
                    }
                    break;

                case ASTNodeType.STRING:
                    // read a string from input
                    variables[identifier.name] = new Variable(new StringNode(ReadInput()));
                    break;

                default:
                    throw new VisitorException(string.Format("variable {0} has unsupported type {1} to read from input", identifier.name, variableType), readNode.identifierToRead);
                }
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.IDENTIFIER, x =>
            {
                var identifier = As <IdentifierNode>(x, ASTNodeType.IDENTIFIER);
                return(GetVariable(identifier).value);
            });
            this.visitorFunctions.Add(ASTNodeType.DECLARATION, x =>
            {
                var declarationNode = As <DeclarationNode>(x, ASTNodeType.DECLARATION);
                var typeNameNode    = As <TypeNameNode>(declarationNode.identifierType, ASTNodeType.TYPENAME);
                var identifier      = As <IdentifierNode>(declarationNode.identifier, ASTNodeType.IDENTIFIER);

                // get the real type from string type
                // error on unknown type
                ASTNodeType variableType;
                switch (typeNameNode.typeName)
                {
                case "int":
                    variableType = ASTNodeType.NUMBER;
                    break;

                case "string":
                    variableType = ASTNodeType.STRING;
                    break;

                case "bool":
                    variableType = ASTNodeType.BOOLEAN;
                    break;

                default:
                    throw new VisitorException(string.Format("unknown identifier type name {0}", typeNameNode.typeName), declarationNode.identifierType);
                }

                // make sure identifier is not taken
                if (variables.ContainsKey(identifier.name))
                {
                    throw new VisitorException(string.Format("variable {0} already defined", identifier.name), declarationNode.identifier);
                }

                if (declarationNode.identifierValue == null)
                {
                    // if there is no value given, then lets initialize with appropriate default value
                    switch (typeNameNode.typeName)
                    {
                    case "int":
                        variables[identifier.name] = new Variable(new NumberNode(0));
                        break;

                    case "string":
                        variables[identifier.name] = new Variable(new StringNode(""));
                        break;

                    case "bool":
                        variables[identifier.name] = new Variable(new BooleanNode(false));
                        break;
                    }
                }
                else
                {
                    // make sure variable type and value type match and then do the assignment
                    var value = Visit(declarationNode.identifierValue);
                    if (value == null || value.type != variableType)
                    {
                        throw new VisitorException(string.Format("variable {0} type {1} does not match value type {2}", identifier.name, variableType, value == null ? "null" : value.type.ToString()), identifier);
                    }
                    variables[identifier.name] = new Variable(As <ASTVariableNode>(value, ASTNodeType.VARIABLE));
                }
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.FORLOOP, x =>
            {
                var forLoopNode = As <ForLoopNode>(x, ASTNodeType.FORLOOP);

                // get control variable and make sure it is correct type and is mutable
                var identifier      = As <IdentifierNode>(forLoopNode.loopVariableIdentifier, ASTNodeType.IDENTIFIER);
                var controlVariable = ExpectMutable(identifier, forLoopNode.loopVariableIdentifier);
                Expect <NumberNode>(controlVariable.value, ASTNodeType.NUMBER, forLoopNode.loopVariableIdentifier);

                // make sure begin and end are numbers
                var begin = As <NumberNode>(Visit(forLoopNode.beginValue), ASTNodeType.NUMBER, forLoopNode.beginValue);
                var end   = As <NumberNode>(Visit(forLoopNode.endValue), ASTNodeType.NUMBER, forLoopNode.endValue);

                // set control variable immutable
                controlVariable.immutable = true;

                // do the for-loop
                int i = begin.value;
                for (; i <= end.value; ++i)
                {
                    // update the control variable
                    variables[identifier.name] = new Variable(new NumberNode(i, identifier.lexeme), true);
                    // execute statements
                    ExpectNull(Visit(forLoopNode.statements), forLoopNode.statements);
                }

                // A small quirk that seemed to be a part of an example program
                // the quirk is that after for loop ends, the control variable is one over the end
                variables[identifier.name] = new Variable(new NumberNode(i, identifier.lexeme));

                // make control variable mutable again
                controlVariable.immutable = false;
                return(null);
            });
            this.visitorFunctions.Add(ASTNodeType.ASSIGNMENT, x =>
            {
                var assignmentNode = As <AssignmentNode>(x, ASTNodeType.ASSIGNMENT);
                // get variable and assigned value
                var identifier = As <IdentifierNode>(assignmentNode.identifier, ASTNodeType.IDENTIFIER);
                var variable   = ExpectMutable(identifier, assignmentNode.identifier);
                var value      = Visit(assignmentNode.value);
                // make sure value matches variable type
                if (value == null || value.type != variable.value.type)
                {
                    throw new VisitorException(string.Format("variable {0} type {1} does not match value type {2}", identifier.name, variable.value.type, value == null ? "null" : value.type.ToString()), x);
                }
                // do the assignment
                variables[identifier.name] = new Variable(As <ASTVariableNode>(value, ASTNodeType.VARIABLE));
                return(null);
            });
        }