/** * 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 ) ); } }