up: Table of contents | prev: Visitor | next: Program

3.7 Printer Visitor

We will now implement a concrete visitor that just prints code of a .minilang source file to standard output. First we will add a virtual function to the Node class that tells whether the node is in fact a compound statement node, and override it in the CompoundStatementNode:

        // Tree.hpp:

        class Node
        {
        public:
            virtual ~Node();
            virtual void AddArgument(Node* arg);
            virtual void Accept(Visitor& visitor) = 0;
            virtual bool IsCompoundStatement() const { return false; }
        };

        class CompoundStatementNode : public Node
        {
        public:
            void AddStatement(Node* statement);
            const std::vector<std::unique_ptr<Node>>& Statements() const { return statements; }
            void Accept(Visitor& visitor) override;
            bool IsCompoundStatement() const override { return true; }
        private:
            std::vector<std::unique_ptr<Node>> statements;
        };
    

Then we will create source files PrinterVisitor.hpp and PrinterVisitor.cpp and add them to the minilang project. The interface of the PrinterVisitor is as follows:

        // PrinterVisitor.hpp:

        class PrinterVisitor : public Visitor
        {
        public:
            PrinterVisitor(soulng::util::CodeFormatter& formatter_);
            void Visit(UnaryOpExprNode& node) override;
            void Visit(InvokeNode& node) override;
            void Visit(BinaryOpExprNode& node) override;
            void Visit(IntNode& node) override;
            void Visit(BoolNode& node) override;
            void Visit(VoidNode& node) override;
            void Visit(BooleanLiteralNode& node) override;
            void Visit(IntegerLiteralNode& node) override;
            void Visit(IdentifierNode& node) override;
            void Visit(ParenthesizedExpressionNode& node) override;
            void Visit(IfStatementNode& node) override;
            void Visit(WhileStatementNode& node) override;
            void Visit(ReturnStatementNode& node) override;
            void Visit(ConstructionStatementNode& node) override;
            void Visit(AssignmentStatementNode& node) override;
            void Visit(CompoundStatementNode& node) override;
            void Visit(ParameterNode& node) override;
            void Visit(FunctionNode& node) override;
            void Visit(SourceFileNode& node) override;
        private:
            soulng::util::CodeFormatter& formatter;
        };
    

The printer visitor uses the CodeFormatter utility class for printing indented text.

We add a function for returning a string representation of an operator:

        // PrinterVisitor.cpp:

        std::string OperatorStr(Operator op)
        {
            switch (op)
            {
                case Operator::equal: return "==";
                case Operator::notEqual: return "!=";
                case Operator::less: return "<";
                case Operator::greater: return ">";
                case Operator::lessOrEqual: return "<=";
                case Operator::greaterOrEqual: return ">=";
                case Operator::add: return "+";
                case Operator::sub: return "-";
                case Operator::mul: return "*";
                case Operator::div: return "/";
                case Operator::mod: return "%";
                case Operator::unaryPlus: return "+";
                case Operator::unaryMinus: return "-";
                case Operator::not_: return "!";
            }
            return std::string();
        }
    

We start with a syntax tree node for a unary expression. We will print the operator symbol of the expression and then call the Accept() member function to print the child expression of it:

        // PrinterVisitor.cpp:

        void PrinterVisitor::Visit(UnaryOpExprNode& node)
        {
            formatter.Write(OperatorStr(node.Op()));
            node.Child()->Accept(*this);
        }
    

The function invocation expression is next in line. We call the Accept member function to print the receiver of the invocation and then print the parenthesized list of arguments of the invocation:

        void PrinterVisitor::Visit(InvokeNode& node)
        {
            node.Child()->Accept(*this);
            formatter.Write("(");
            bool first = true;
            for (const auto& arg : node.Args())
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    formatter.Write(", ");
                }
                arg->Accept(*this);
            }
            formatter.Write(")");
        }
    

We continue with syntax tree nodes for binary expressions. We print the left operand, the operator symbol and then the right operand:

        void PrinterVisitor::Visit(BinaryOpExprNode& node)
        {
            node.Left()->Accept(*this);
            formatter.Write(" ");
            formatter.Write(OperatorStr(node.Op()));
            formatter.Write(" ");
            node.Right()->Accept(*this);
        }
    

The type nodes are very simple:

        void PrinterVisitor::Visit(IntNode& node)
        {
            formatter.Write("int");
        }

        void PrinterVisitor::Visit(BoolNode& node)
        {
            formatter.Write("bool");
        }

        void PrinterVisitor::Visit(VoidNode& node)
        {
            formatter.Write("void");
        }
    

And literals:

        void PrinterVisitor::Visit(BooleanLiteralNode& node)
        {
            if (node.Value())
            {
                formatter.Write("true");
            }
            else
            {
                formatter.Write("false");
            }
        }

        void PrinterVisitor::Visit(IntegerLiteralNode& node)
        {
            formatter.Write(std::to_string(node.Value()));
        }
    

The code formatter will convert UTF-8 text to UTF-16 when printing to Windows Console:

        void PrinterVisitor::Visit(IdentifierNode& node)
        {
            formatter.Write(soulng::unicode::ToUtf8(node.Str()));
        }
    

A parenthesized expression:

        void PrinterVisitor::Visit(ParenthesizedExpressionNode& node)
        {
            formatter.Write("(");
            node.Child()->Accept(*this);
            formatter.Write(")");
        }
    

Little more work for printing an if statement. Printing contained compound statements differs from the contained non-compound statements:

        void PrinterVisitor::Visit(IfStatementNode& node)
        {
            formatter.Write("if (");
            node.Condition()->Accept(*this);
            formatter.Write(")");
            if (node.ThenS()->IsCompoundStatement())
            {
                formatter.WriteLine();
            }
            else
            {
                formatter.Write(" ");
            }
            node.ThenS()->Accept(*this);
            if (node.ElseS() != nullptr)
            {
                if (node.ThenS()->IsCompoundStatement())
                {
                    formatter.WriteLine();
                }
                else
                {
                    formatter.Write(" ");
                }
                formatter.Write("else");
                if (node.ElseS()->IsCompoundStatement())
                {
                    formatter.WriteLine();
                }
                else
                {
                    formatter.Write(" ");
                }
                node.ElseS()->Accept(*this);
            }
        }
    

Printing code for the rest of the simple statements is as follows:

        void PrinterVisitor::Visit(WhileStatementNode& node)
        {
            formatter.Write("while (");
            node.Condition()->Accept(*this);
            formatter.Write(")");
            if (node.Statement()->IsCompoundStatement())
            {
                formatter.WriteLine();
            }
            else
            {
                formatter.Write(" ");
            }
            node.Statement()->Accept(*this);
        }

        void PrinterVisitor::Visit(ReturnStatementNode& node)
        {
            formatter.Write("return");
            if (node.ReturnValue() != nullptr)
            {
                formatter.Write(" ");
                node.ReturnValue()->Accept(*this);
            }
            formatter.WriteLine(";");
        }

        void PrinterVisitor::Visit(ConstructionStatementNode& node)
        {
            node.Type()->Accept(*this);
            formatter.Write(" ");
            node.VariableName()->Accept(*this);
            formatter.Write(" = ");
            node.Value()->Accept(*this);
            formatter.WriteLine(";");
        }

        void PrinterVisitor::Visit(AssignmentStatementNode& node)
        {
            node.VariableName()->Accept(*this);
            formatter.Write(" = ");
            node.Value()->Accept(*this);
            formatter.WriteLine(";");
        }
    

We increase the indentation when printing the statements contained by a compound statement:

        void PrinterVisitor::Visit(CompoundStatementNode& node)
        {
            formatter.WriteLine("{");
            formatter.IncIndent();
            for (const auto& statement : node.Statements())
            {
                statement->Accept(*this);
            }
            formatter.DecIndent();
            formatter.WriteLine("}");
        }
    

And finally the printing code for the rest of the syntax tree nodes:

        void PrinterVisitor::Visit(ParameterNode& node)
        {
            node.ParamType()->Accept(*this);
            formatter.Write(" ");
            node.ParamName()->Accept(*this);
        }

        void PrinterVisitor::Visit(FunctionNode& node)
        {
            node.ReturnType()->Accept(*this);
            formatter.Write(" ");
            node.FunctionName()->Accept(*this);
            formatter.Write("(");
            bool first = true;
            for (const auto& param : node.Parameters())
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    formatter.Write(", ");
                }
                param->Accept(*this);
            }
            formatter.WriteLine(")");
            node.Body()->Accept(*this);
        }

        void PrinterVisitor::Visit(SourceFileNode& node)
        {
            bool first = true;
            for (const auto& function : node.Functions())
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    formatter.WriteLine();
                }
                function->Accept(*this);
            }
        }
    

Next we will use the printer visitor in the main program...

up: Table of contents | prev: Visitor | next: Program