/** * 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.ast; import java.io.IOException; import java.util.List; import de.codesourcery.jasm16.AddressingMode; import de.codesourcery.jasm16.OpCode; import de.codesourcery.jasm16.Register; import de.codesourcery.jasm16.Size; import de.codesourcery.jasm16.ast.OperandNode.OperandPosition; import de.codesourcery.jasm16.compiler.ICompilationContext; import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption; import de.codesourcery.jasm16.compiler.RelocationTable; import de.codesourcery.jasm16.compiler.io.IObjectCodeWriter; import de.codesourcery.jasm16.exceptions.ParseException; import de.codesourcery.jasm16.lexer.IToken; import de.codesourcery.jasm16.lexer.TokenType; import de.codesourcery.jasm16.parser.IParseContext; /** * An AST node that represents an assembly opcode along with its operands. * * @author tobias.gierke@code-sourcery.de */ public class InstructionNode extends ObjectCodeOutputNode { private OpCode opCode; private int sizeInBytes = UNKNOWN_SIZE; private TokenType[] errorRecoveryTokenTypes = DEFAULT_ERROR_RECOVERY_TOKEN; public InstructionNode() { } public static boolean isValidOperandValue(long value) { return value >= 0 && value <= 0x0000FFFF; } public OpCode getOpCode() { return opCode; } public int getOperandCount() { int counter = 0; for ( ASTNode child : getChildren() ) { if ( child instanceof OperandNode) { counter++; } } return counter; } public int getOperandIndex(OperandNode node) { int counter = 0; for ( ASTNode child : getChildren() ) { if ( child instanceof OperandNode) { if ( child == node ) { return counter; } counter++; } } return -1; } public OperandNode getOperand(int index) { return getOperand( index , true ); } public OperandNode getOperand(int index,boolean failIfAbsent) { int counter = 0; for ( ASTNode child : getChildren() ) { if ( child instanceof OperandNode) { if ( counter == index ) { return (OperandNode) child; } counter++; } } if ( failIfAbsent ) { throw new RuntimeException("Internal error, no operand #"+index); } return null; } @Override protected ASTNode parseInternal(IParseContext context) throws ParseException { final IToken token = context.read( "Expected a DCPU-16 mnemonic", TokenType.INSTRUCTION ); final OpCode opCode = OpCode.fromIdentifier( token.getContents().toUpperCase() ); this.opCode = opCode; mergeWithAllTokensTextRegion( token ); mergeWithAllTokensTextRegion( context.parseWhitespace() ); errorRecoveryTokenTypes = new TokenType[] {TokenType.COMMA }; for ( int i = 0 ; i < opCode.getOperandCount() ; i++ ) { ASTNode operandNode = null; try { context.mark(); operandNode = parseOperand( opCode , i , context ); } catch(ParseException e) { addCompilationErrorAndAdvanceParser( e , new TokenType[] {TokenType.COMMA} , context ); } finally { context.clearMark(); } if ( opCode.isBasicOpCode() && i == 1 ) { /* * SET a, [--SP] is not possible because the instruction bitmask uses the same value for PUSH/POP * and the meaning only depends on whether is the source or target operand */ if ( operandNode instanceof OperandNode ) { final OperandNode opNode = (OperandNode) operandNode; if ( opNode.getAddressingMode() == AddressingMode.INDIRECT_REGISTER_PREDECREMENT && opNode.getRegister() == Register.SP ) { if ( opNode.getRegisterReferenceNode().hasPreDecrement() ) { context.addCompilationError("PUSH cannot be used as SOURCE operand",opNode); } } } } else if ( opCode.isBasicOpCode() && i == 0 ) // target operand of basic instruction { /* * SET [SP++] , a is not possible because the instruction bitmask uses the same value for PUSH/POP * and the meaning only depends on whether is the source or target operand */ if ( operandNode instanceof OperandNode ) { final OperandNode opNode = (OperandNode) operandNode; if ( opNode.getAddressingMode() == AddressingMode.INDIRECT_REGISTER_POSTINCREMENT && opNode.getRegister() == Register.SP ) { if ( opNode.getRegisterReferenceNode().hasPostIncrement() ) { context.addCompilationError("POP cannot be used as TARGET operand",opNode); } } } } if ( (i+1) < opCode.getOperandCount() ) { parseArgumentSeparator( context ); } } return this; } protected ASTNode parseOperand(OpCode opcode, int index , IParseContext context) throws ParseException { final OperandPosition position; switch( index ) { case 0: position = OperandPosition.TARGET_OPERAND; break; case 1: position = OperandPosition.SOURCE_OPERAND; break; default: throw new RuntimeException("Unreachable code reached"); } final ASTNode node = new OperandNode().parseInternal( context ); if ( node instanceof OperandNode) // parsing might've failed and thus the actual returned type may be UnparsedContentNode... { final OperandNode op = (OperandNode) node; if ( ! opcode.isValidAddressingMode( position , op.getAddressingMode() ) ) { throw new ParseException("Opcode "+opcode+" does not support addressing mode "+ op.getAddressingMode()+" for parameter "+(index+1) , op.getTextRegion() ); } /* * getRegister() chokes on 1+ register references but * this case is already flagged as an error by * OperandNode#parseInternal() */ if ( ASTUtils.getRegisterReferenceCount( op ) <= 1 && ! getOpCode().isOperandValidInPosition( position , op.getAddressingMode(), op.getRegister() ) ) { throw new ParseException("Operand cannot be used as "+position,op.getTextRegion()); } } addChild( node , context ); return node; } protected void parseArgumentSeparator(IParseContext context) throws ParseException { if ( ! context.eof() && context.peek().isWhitespace() ) { mergeWithAllTokensTextRegion( context.parseWhitespace() ); } mergeWithAllTokensTextRegion( context.read(TokenType.COMMA ) ); if ( context.peek().isWhitespace() ) { mergeWithAllTokensTextRegion( context.parseWhitespace() ); } } @Override protected InstructionNode copySingleNode() { final InstructionNode result= new InstructionNode(); result.opCode = opCode; result.setAddress( getAddress() ); result.sizeInBytes = sizeInBytes; return result; } @Override public int getSizeInBytes(long thisNodesObjectCodeOffsetInBytes) { return sizeInBytes; } @Override public void symbolsResolved(ICompilationContext context) { this.sizeInBytes = getOpCode().calculateSizeInBytes( context , this ); } @Override public void writeObjectCode(IObjectCodeWriter writer, ICompilationContext compContext) throws IOException { setAddress( writer.getCurrentWriteOffset() ); final byte[] objectCode = getOpCode().generateObjectCode( compContext , this ); if ( objectCode == null ) { throw new IllegalStateException("Could not generate instruction,operand size not known yet"); } if ( compContext.hasCompilerOption( CompilerOption.GENERATE_RELOCATION_INFORMATION ) ) { final RelocationTable table = compContext.getCurrentCompilationUnit().getRelocationTable(); // the following code assumes that operand literals have not been inlined by OpCode#generateObjectCode() // which is made sure because the CompilerOption#GENERATE_RELOCATION_TABLE flag is checked there as well // instructions are written in the following order // word 0 - instruction // word 1 - source operand // word 2 - target operand final int operandCount = getOperandCount(); if ( operandCount == 1 ) { final Boolean referencesLabel = getOperand(0).referencesLabel( compContext.getSymbolTable() ); if ( referencesLabel == Boolean.TRUE) { table.addRelocationEntry( getAddress().plus( Size.ONE_WORD , false ) ); } } else if ( operandCount == 2 ) { Boolean referencesLabel = getOperand(0).referencesLabel( compContext.getSymbolTable() ); if ( referencesLabel == Boolean.TRUE) { table.addRelocationEntry( getAddress().plus( Size.ONE_WORD , false ) ); } referencesLabel = getOperand(1).referencesLabel( compContext.getSymbolTable() ); if ( referencesLabel == Boolean.TRUE) { table.addRelocationEntry( getAddress().plus( Size.TWO_WORDS , false ) ); } } } writer.writeObjectCode( objectCode ); } public List<OperandNode> getOperands() { return ASTUtils.getNodesByType( this , OperandNode.class , true ); } @Override public boolean supportsChildNodes() { return true; } protected TokenType[] getParseRecoveryTokenTypes() { return errorRecoveryTokenTypes; } }