de.codesourcery.jasm16.utils.FormattingVisitor.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.jasm16.utils.FormattingVisitor.java

Source

/**
 * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.codesourcery.jasm16.utils;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import de.codesourcery.jasm16.ast.ASTNode;
import de.codesourcery.jasm16.ast.ASTUtils;
import de.codesourcery.jasm16.ast.ASTVisitor;
import de.codesourcery.jasm16.ast.CharacterLiteralNode;
import de.codesourcery.jasm16.ast.CommentNode;
import de.codesourcery.jasm16.ast.EndMacroNode;
import de.codesourcery.jasm16.ast.EquationNode;
import de.codesourcery.jasm16.ast.ExpressionNode;
import de.codesourcery.jasm16.ast.IASTVisitor;
import de.codesourcery.jasm16.ast.IIterationContext;
import de.codesourcery.jasm16.ast.IncludeBinaryFileNode;
import de.codesourcery.jasm16.ast.IncludeSourceFileNode;
import de.codesourcery.jasm16.ast.InitializedMemoryNode;
import de.codesourcery.jasm16.ast.InstructionNode;
import de.codesourcery.jasm16.ast.InvokeMacroNode;
import de.codesourcery.jasm16.ast.LabelNode;
import de.codesourcery.jasm16.ast.NumberNode;
import de.codesourcery.jasm16.ast.ObjectCodeOutputNode;
import de.codesourcery.jasm16.ast.OperandNode;
import de.codesourcery.jasm16.ast.OperatorNode;
import de.codesourcery.jasm16.ast.OriginNode;
import de.codesourcery.jasm16.ast.MacroArgumentNode;
import de.codesourcery.jasm16.ast.RegisterReferenceNode;
import de.codesourcery.jasm16.ast.StartMacroNode;
import de.codesourcery.jasm16.ast.StatementNode;
import de.codesourcery.jasm16.ast.SymbolReferenceNode;
import de.codesourcery.jasm16.ast.UninitializedMemoryNode;
import de.codesourcery.jasm16.ast.UnparsedContentNode;
import de.codesourcery.jasm16.compiler.ICompilationContext;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.Label;
import de.codesourcery.jasm16.compiler.io.AbstractObjectCodeWriter;
import de.codesourcery.jasm16.parser.Identifier;

/**
 * {@link IASTVisitor} implementation that outputs
 * formatted source code for an AST along with the generated
 * object code as comments.
 * 
 * @author tobias.gierke@code-sourcery.de
 */
public class FormattingVisitor extends ASTVisitor {

    private final int column0Width = 40;

    private final ICompilationContext context;
    private final boolean printOpcodesInHex;
    private final boolean printExpandedMacros;
    private final ICompilationUnit compilationUnit;

    public FormattingVisitor(ICompilationContext context) {
        this(context, context.getCurrentCompilationUnit(), false, true);
    }

    public FormattingVisitor(ICompilationContext context, boolean printExpandedMacros) {
        this(context, context.getCurrentCompilationUnit(), printExpandedMacros, true);
    }

    /**
     * 
     * @param context compilation context or <code>null</code>. It's valid for the context to be <code>NULL</code> only if
     * <code>printOpCodesInHex</code> is set to <code>false</code> as well.
     * @param compilationUnit
     * @param printExpandedMacros
     * @param printOpCodesInHex
     */
    public FormattingVisitor(ICompilationContext context, ICompilationUnit compilationUnit,
            boolean printExpandedMacros, boolean printOpCodesInHex) {
        this.context = context;
        if (context == null && printOpCodesInHex) {
            throw new IllegalArgumentException(
                    "When printOpCodesInHex is set to true, a compilation context needs to be given");
        }
        this.printOpcodesInHex = printOpCodesInHex;
        this.printExpandedMacros = printExpandedMacros;
        this.compilationUnit = compilationUnit;
    }

    protected void output(String s) {
        System.out.print(s);
    }

    @Override
    public void visit(EquationNode node, IIterationContext context) {
        final String source;
        try {
            source = compilationUnit.getSource(node.getValueNode().getTextRegion()).replaceAll("\t", " ").trim();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        output(".equ " + node.getIdentifier().getRawValue() + " " + source);
        context.dontGoDeeper();
    }

    @Override
    public void visit(MacroArgumentNode node, IIterationContext context) {
        output(node.getValue());
    }

    @Override
    public void visit(StartMacroNode node, IIterationContext context) {
        if (node.getArgumentCount() > 0) {
            String s = "";
            final List<Identifier> argNames = node.getArgumentNames();
            final int len = argNames.size();
            for (int i = 0; i < len; i++) {
                s += argNames.get(i).getRawValue();
                if ((i + 1) < len) {
                    s += ",";
                }
            }
            output(".macro " + node.getMacroName().getRawValue() + "(" + s + ")\n");
        } else {
            output(".macro " + node.getMacroName().getRawValue() + "\n");
        }
        output(node.getMacroBody());
        context.dontGoDeeper();
    }

    @Override
    public void visit(EndMacroNode node, IIterationContext context) {
        output(".endmacro");
        context.dontGoDeeper();
    }

    @Override
    public void visit(InvokeMacroNode node, IIterationContext context) {
        final String line;
        if (node.getArgumentCount() == 0) {
            line = node.getMacroName().getRawValue() + "\n";
        } else {
            final List<ASTNode> arguments = node.getArguments();
            final int len = arguments.size();
            final StringBuilder builder = new StringBuilder();
            for (int i = 0; i < len; i++) {
                final ASTNode argument = arguments.get(i);
                ASTUtils.visitInOrder(argument, new FormattingVisitor(this.context, this.compilationUnit,
                        this.printExpandedMacros, this.printOpcodesInHex) {
                    protected void output(String s) {
                        builder.append(s);
                    }
                });
                if ((i + 1) < len) {
                    builder.append(",");
                }
            }
            line = node.getMacroName().getRawValue() + " (" + builder + ")\n";
        }

        if (printExpandedMacros) {
            output("; macro expansion: " + line + "\n");
        } else {
            output(line);
            context.dontGoDeeper();
        }
    }

    @Override
    public void visit(IncludeSourceFileNode node, IIterationContext itContext) {
        output("include \"" + node.getResourceIdentifier() + "\"");
        itContext.dontGoDeeper();
    }

    @Override
    public void visit(CharacterLiteralNode node, IIterationContext context) {
    }

    @Override
    public void visit(CommentNode node, IIterationContext context) {
        if (node.getParent().getChildCount() == 1) {
            output(getSource(node));
        }
    }

    private String getSource(ASTNode node) {
        if (node.getTextRegion() == null) {
            return "<no text range on node " + node.getClass().getSimpleName() + ">";
        }
        try {
            return compilationUnit.getSource(node.getTextRegion()).replace("\t", " ").replace("\r", "")
                    .replace("\n", "").trim();
        } catch (IOException e) {
            return "<IO exception when reading text from node " + node.getClass().getSimpleName();
        }
    }

    @Override
    public void visit(ExpressionNode node, IIterationContext context) {
    }

    protected StatementNode getStatementNode(ASTNode node) {
        ASTNode current = node;
        while (current.getParent() != null) {
            if (current instanceof StatementNode) {
                return (StatementNode) current;
            }
            current = current.getParent();
        }
        return null;
    }

    private String toString(LabelNode node) {
        final Label symbol = node.getLabel();
        String address = "";
        if (symbol != null && symbol.getAddress() != null) {
            address = " (0x" + Misc.toHexString(symbol.getAddress().getValue()) + ")";
        }
        final String src = getSource(node);
        return Misc.padRight(src + address, column0Width);
    }

    @Override
    public void visit(InstructionNode node, IIterationContext context) {
        final LabelNode label = getLabelNode(node);
        final String labelText = label != null ? toString(label) : "";

        final StringBuilder result = new StringBuilder();
        if (label == null) {
            result.append(StringUtils.repeat(" ", column0Width));
        }
        result.append(node.getOpCode().getIdentifier() + " ");

        final List<OperandNode> operands = node.getOperands();
        for (Iterator<OperandNode> it = operands.iterator(); it.hasNext();) {
            final OperandNode operandNode = it.next();
            String sourceCode;
            try {
                final ITextRegion range = operandNode.getTextRegion();
                if (range != null) {
                    sourceCode = compilationUnit.getSource(range).replaceAll("\t", " ").trim();
                } else {
                    sourceCode = "<no text range available>";
                }
            } catch (IOException e) {
                sourceCode = "<could not read source: " + e.getMessage() + ">";
            }
            result.append(sourceCode);
            if (it.hasNext()) {
                result.append(",");
            }
        }

        final int width = 60 - labelText.length();
        final String txt = Misc.padRight(result.toString(), width);
        if (label != null) {
            output(txt);
        } else {
            output(txt);
        }

        if (printOpcodesInHex) {
            final HexStringWriter writer = new HexStringWriter(true);
            for (ObjectCodeOutputNode out : getStatementNode(node).getObjectOutputNodes()) {
                try {
                    out.writeObjectCode(writer, this.context);
                } catch (Exception e) {
                    /* ok */ }
            }
            output("; " + writer.toString());
        }
    }

    private static class HexStringWriter extends AbstractObjectCodeWriter {

        private final StringBuilder builder = new StringBuilder();
        private final List<Byte> buffer = new ArrayList<Byte>();
        private final boolean printFirstWordAsBinaryLiteral;

        private boolean firstWord = true;

        public HexStringWriter(boolean printFirstWordAsBinaryLiteral) {
            this.printFirstWordAsBinaryLiteral = printFirstWordAsBinaryLiteral;
        }

        private void flushBuffer(boolean force) {
            while ((force && !buffer.isEmpty()) || buffer.size() >= 2) {
                byte val1 = buffer.remove(0);
                if (!buffer.isEmpty()) {
                    byte val2 = buffer.remove(0);
                    if (firstWord && printFirstWordAsBinaryLiteral) {
                        final int word = (val1 << 8) | toUnsignedInt(val2);
                        builder.append("(").append(Misc.toBinaryString(word, 16)).append(") ");
                    }
                    builder.append(Misc.toHexString(val1)).append(Misc.toHexString(val2)).append(" ");
                    firstWord = false;
                } else {
                    if (firstWord && printFirstWordAsBinaryLiteral) {
                        final int word = toUnsignedInt(val1);
                        builder.append("(").append(Misc.toBinaryString(word, 16)).append(") ");
                    }
                    builder.append(Misc.toHexString(val1));
                    firstWord = false;
                }
            }
        }

        private int toUnsignedInt(byte b) {
            return b >= 0 ? b : b + 256;
        }

        @Override
        public String toString() {
            flushBuffer(true);
            return builder.toString();
        }

        @Override
        protected void closeHook() throws IOException {
        }

        @Override
        protected OutputStream createOutputStream() throws IOException {
            return new OutputStream() {

                @Override
                public void write(int b) throws IOException {
                    buffer.add((byte) (b & 0xff));
                    flushBuffer(false);
                }
            };
        }

        @Override
        protected void deleteOutputHook() throws IOException {
        }
    }

    @Override
    public void visit(LabelNode node, IIterationContext context) {
        if (node.getLabel().isLocalSymbol()) {
            output("." + node.getLabel().toString());
        } else {
            output(node.getLabel().toString() + ":");
        }
    }

    private LabelNode getLabelNode(ASTNode node) {

        ASTNode current = node;
        while (!(current instanceof StatementNode) && current.getParent() != null) {
            current = current.getParent();
        }
        if (current instanceof StatementNode) {
            for (ASTNode child : current.getChildren()) {
                if (child instanceof LabelNode) {
                    return (LabelNode) child;
                }
            }
        }
        return null;
    }

    @Override
    public void visit(SymbolReferenceNode node, IIterationContext context) {
    }

    @Override
    public void visit(NumberNode node, IIterationContext context) {
    }

    @Override
    public void visit(OperandNode node, IIterationContext context) {
    }

    @Override
    public void visit(IncludeBinaryFileNode node, IIterationContext context) {
        output(getSource(node));
    }

    @Override
    public void visit(OriginNode node, IIterationContext context) {
        output(getSource(node));
    }

    @Override
    public void visit(OperatorNode node, IIterationContext context) {
    }

    @Override
    public void visit(RegisterReferenceNode node, IIterationContext context) {
    }

    @Override
    public void visit(StatementNode node, IIterationContext context) {
        output("\n");
    }

    @Override
    public void visit(InitializedMemoryNode node, IIterationContext context) {

        final HexStringWriter writer = new HexStringWriter(false);

        try {
            node.writeObjectCode(writer, this.context);
        } catch (Exception e) {
            /* ok */ }

        output(getSource(node));
        output("; " + writer.toString());
    }

    @Override
    public void visit(UninitializedMemoryNode node, IIterationContext context) {
        output(getSource(node));
    }

    @Override
    public void visit(UnparsedContentNode node, IIterationContext context) {
        output(getSource(node));
    }
}