/**
* 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.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import de.codesourcery.jasm16.AddressingMode;
import de.codesourcery.jasm16.Register;
import de.codesourcery.jasm16.compiler.CompilationError;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.ISymbol;
import de.codesourcery.jasm16.compiler.ISymbolTable;
import de.codesourcery.jasm16.compiler.Label;
import de.codesourcery.jasm16.exceptions.ParseException;
import de.codesourcery.jasm16.lexer.IToken;
import de.codesourcery.jasm16.lexer.TokenType;
import de.codesourcery.jasm16.parser.IParseContext;
import de.codesourcery.jasm16.parser.Operator;
/**
* An operand in an assembly opcode.
*
* @author tobias.gierke@code-sourcery.de
*/
public class OperandNode extends ASTNode
{
private AddressingMode addressingMode;
public enum OperandPosition {
SOURCE_OPERAND,
TARGET_OPERAND,
SOURCE_OR_TARGET,
NOT_POSSIBLE;
}
public OperandNode() {
this.addressingMode = null;
}
@Override
protected OperandNode parseInternal(IParseContext context) throws ParseException
{
/*
* OPERAND := INDIRECT | MEMORY | IMMEDIATE | REGISTER_REF
* --------------------------------------------------------
*
* INDIRECT := '[' REGISTER ']' | '[' REGISTER_EXPR ']' | '[SP++]' | '[--SP]'
*
* REGISTER_EXPR := CONST_EXPR '+' REGISTER | REGISTER '+' CONST_EXPR | CONSTR_EXPR '+' REGISTER '+' CONST_EXPR
*
* REGISTER := 'A' | 'B' | 'C' | 'X' | 'Y' | 'Z' | 'I' | 'J' | 'SP'
*
* CONST_EXPR = NUMBER | ADDITION | SUBTRACTION | MULTIPLICATION | DIVISION | '(' CONST_EXPR ')'
*
* ADDITION := CONST_EXPR '+' CONST_EXPR
* SUBTRACTION := CONST_EXPR '-' CONST_EXPR
* MULTIPLICATION := CONST_EXPR '*' CONST_EXPR
* DIVISION := CONST_EXPR '/' CONST_EXPR
*
* ----------------------------------------------------
*
* MEMORY := '[' NUMBER ']'
*
* ----------------------- IMMEDIATE --------------------
*
* IMMEDIATE := CONST_EXPR
*
* ------------------------------------------------------
*
* REGISTER_REF := REGISTER | 'SP' INCREMENT | DECREMENT 'SP'
*
* INCREMENT := '++'
* DECREMENT := '--'
*/
final IToken tok = context.peek();
if ( tok.hasType( TokenType.PICK ) )
{
this.addressingMode = AddressingMode.INTERNAL_EXPRESSION;
addChild( new RegisterReferenceNode().parse( context ) , context );
// parse operand
final ASTNode expr = new ExpressionNode().parse( context );
validateRegisterRefCount( context , expr , 0 );
addChild( expr , context );
return this;
} else if ( tok.hasType( TokenType.PUSH ) || tok.hasType( TokenType.POP) || tok.hasType( TokenType.PEEK ))
{
this.addressingMode = AddressingMode.INTERNAL_EXPRESSION;
addChild( new RegisterReferenceNode().parse( context ) , context );
return this;
}
else if ( tok.hasType( TokenType.ANGLE_BRACKET_OPEN) ) { // INDIRECT ADDRESSING
this.addressingMode = AddressingMode.INTERNAL_EXPRESSION;
mergeWithAllTokensTextRegion( context.read( TokenType.ANGLE_BRACKET_OPEN ) );
final ASTNode expr = wrapExpression( new ExpressionNode().parseInternal( context ) , context );
validateRegisterRefCount( context, expr , 1 );
addChild( expr , context );
mergeWithAllTokensTextRegion( context.read( TokenType.ANGLE_BRACKET_CLOSE ) );
return this;
}
else if ( tok.hasType( TokenType.CHARACTERS ) ||
tok.hasType( TokenType.DOT ) || // local label
tok.hasType(TokenType.STRING_DELIMITER) )
{
// REGISTER or IMMEDIATE (label reference)
if ( tok.hasType( TokenType.CHARACTERS ) && nextTokenIsRegisterIdentifier( context ) )
{
this.addressingMode = AddressingMode.REGISTER;
final ASTNode node = new RegisterReferenceNode().parse( context );
addChild( node , context );
}
else
{
// symbol reference or character literal
this.addressingMode = AddressingMode.IMMEDIATE;
ASTNode expr = new ExpressionNode().parse( context );
validateRegisterRefCount( context , expr , 0 );
addChild( expr , context );
}
} else if ( isMinusOperator( tok ) || tok.hasType( TokenType.NUMBER_LITERAL ) || tok.hasType( TokenType.PARENS_OPEN )) {
this.addressingMode = AddressingMode.IMMEDIATE;
final ASTNode expression = wrapExpression( new ExpressionNode().parse( context ) , context );
validateRegisterRefCount( context, expression , 0 );
addChild( expression, context );
} else {
throw new ParseException("Unexpected operand token: "+tok , tok );
}
return this;
}
private boolean isMinusOperator(IToken tok) {
return tok.hasType( TokenType.OPERATOR ) && Operator.fromString( tok.getContents() ) == Operator.MINUS;
}
private void validateRegisterRefCount(IParseContext context,ASTNode node,int maxNum) throws ParseException
{
final int count = ASTUtils.getRegisterReferenceCount( node );
if ( count > maxNum )
{
final String cardinality;
if ( maxNum == 0 ) {
cardinality ="a";
} else if ( maxNum == 1 ) {
cardinality ="more than one";
} else {
cardinality = "more than "+Integer.toString( maxNum );
}
final ICompilationUnit unit = context.getCompilationUnit();
final String error = "Expression must not contain "+cardinality+" register reference";
unit.addMarker( new CompilationError(error,unit,node ) );
}
}
private ASTNode wrapExpression(ASTNode input,IParseContext context)
{
if ( input instanceof ExpressionNode || ! ( input instanceof OperatorNode) ) {
return input;
}
final ExpressionNode result = new ExpressionNode();
result.addChild( input , context );
return result;
}
/**
* Returns whether this operand uses at least one reference
* to a location in the object code.
*
* <p>This method is used to determine whether a relocation table entry
* needs to be generated for this operand.</p>
* @return <code>NULL</code> if this operand has a symbol reference but the symbol
* could not be found in the symbol table (and thus we were unable to tell whether
* the referenced symbol is a label or something else) , <code>true</code>
* if this operand references a {@link Label}, <code>false</code> otherwise
*
* @see ISymbol
* @see Label
*/
public Boolean referencesLabel(final ISymbolTable symbolTable) {
final AtomicBoolean labelsFound = new AtomicBoolean(false);
final AtomicBoolean unresolvedSymbolsFound = new AtomicBoolean(false);
final ISimpleASTNodeVisitor<ASTNode> visitor=new ISimpleASTNodeVisitor<ASTNode>() {
@Override
public boolean visit(ASTNode node)
{
if ( node instanceof SymbolReferenceNode)
{
final ISymbol symbol = ((SymbolReferenceNode) node).resolve(symbolTable); // symbolTable.getSymbol( identifier , scope );
if ( symbol == null ) {
unresolvedSymbolsFound.set(true);
}
else
{
if ( symbol instanceof Label ) {
labelsFound.set(true);
}
}
}
return true;
}
};
ASTUtils.visitInOrder( this , visitor );
if ( labelsFound.get() ) {
return Boolean.TRUE; // important to return Boolean.FALSE here since calling code uses == comparison with Boolean.TRUE / Boolean.FALSE
}
if ( unresolvedSymbolsFound.get() ) {
return null;
}
return Boolean.FALSE; // important to return Boolean.FALSE here since calling code uses == comparison with Boolean.TRUE / Boolean.FALSE
}
public RegisterReferenceNode getRegisterReferenceNode() {
final List<RegisterReferenceNode> result =
ASTUtils.getNodesByType( this , RegisterReferenceNode.class , true );
if ( result.size() == 1 ) {
return result.get(0);
} else if ( result.size() > 1 ) {
throw new RuntimeException("Operand with more than one RegisterReferenceNode? "+this);
}
return null;
}
public Register getRegister()
{
final RegisterReferenceNode regNode = getRegisterReferenceNode();
return regNode != null ? regNode.getRegister() : null;
}
public Long getLiteralValue(ISymbolTable symbolTable)
{
if ( ! supportsLiteralValue() ) {
return null;
}
final List<TermNode> terms = new ArrayList<TermNode>();
final List<ConstantValueNode> literalValues = new ArrayList<ConstantValueNode>();
for ( ASTNode child : getChildren() ) {
if ( child instanceof ConstantValueNode ) { // careful, ConstantValueNode extends TermNode so order matters !!!
literalValues.add( (ConstantValueNode) child );
} else if ( child instanceof RegisterReferenceNode ) {
// ignore
}
else if ( child instanceof TermNode ) // careful, ConstantValueNode extends TermNode so order matters !!!
{
terms.add( (TermNode) child);
}
}
if ( terms.size() > 1 || literalValues.size() > 1 ) {
return null;
}
if ( terms.isEmpty() && literalValues.isEmpty() ) {
return null;
}
if ( terms.size() == 1 && literalValues.size() == 1 ) {
return null;
}
if ( ! terms.isEmpty() ) {
return terms.get(0).calculate( symbolTable );
}
return literalValues.get(0).calculate( symbolTable );
}
public boolean supportsLiteralValue()
{
switch( getAddressingMode() )
{
case INDIRECT:
case INDIRECT_REGISTER_OFFSET:
case IMMEDIATE:
return true;
}
return false;
}
public ConstantValueNode getLiteralValueNode()
{
if ( supportsLiteralValue() )
{
final List<ConstantValueNode> literals = ASTUtils.getNodesByType( this , ConstantValueNode.class , false );
if ( literals.size() == 1 ) {
return literals.get(0);
}
}
return null;
}
public enum ExpressionType {
CONSTANT,
REGISTER,
REGISTER_OFFSET,
REGISTER_POSTINCREMENT,
REGISTER_PREDECREMENT,
UNKNOWN;
}
private enum NodeType {
CONSTANT,
REGISTER,
REGISTER_INCREMENT,
REGISTER_POSTINCREMENT,
REGISTER_PREDECREMENT,
OPERATOR;
}
private ExpressionType getExpressionType()
{
final Set<NodeType> nodeTypes = getNodeTypes();
if ( equals( nodeTypes , NodeType.CONSTANT ) ) {
return ExpressionType.CONSTANT;
} else if ( equals( nodeTypes , NodeType.CONSTANT , NodeType.OPERATOR ) ) {
return ExpressionType.CONSTANT;
} else if ( equals( nodeTypes , NodeType.REGISTER ) ) {
return ExpressionType.REGISTER;
} else if ( equals( nodeTypes , NodeType.REGISTER_POSTINCREMENT ) ) {
return ExpressionType.REGISTER_POSTINCREMENT;
} else if ( equals( nodeTypes , NodeType.REGISTER_PREDECREMENT) ) {
return ExpressionType.REGISTER_PREDECREMENT;
} else if ( equals( nodeTypes , NodeType.CONSTANT , NodeType.OPERATOR , NodeType.REGISTER ) ||
equals( nodeTypes , NodeType.CONSTANT , NodeType.REGISTER ) ) // special case: PICK N
{
return ExpressionType.REGISTER_OFFSET;
}
return ExpressionType.UNKNOWN;
}
private Set<NodeType> getNodeTypes()
{
final Set<NodeType> nodeTypes = new HashSet<NodeType>();
final ISimpleASTNodeVisitor<ASTNode> visitor = new ISimpleASTNodeVisitor<ASTNode>() {
@Override
public boolean visit(ASTNode node)
{
final NodeType type = getNodeType(node);
if ( type != null ) {
nodeTypes.add( type );
}
return true;
}
private NodeType getNodeType(ASTNode node)
{
if ( node instanceof NumberNode || node instanceof SymbolReferenceNode) {
return NodeType.CONSTANT;
} else if ( node instanceof RegisterReferenceNode) {
final RegisterReferenceNode r = (RegisterReferenceNode) node;
if ( r.hasPostIncrement() ) {
return NodeType.REGISTER_POSTINCREMENT;
}
if ( r.hasPreDecrement() ) {
return NodeType.REGISTER_PREDECREMENT;
}
return NodeType.REGISTER;
} else if ( node instanceof OperatorNode) {
return NodeType.OPERATOR;
}
return null;
}
};
ASTUtils.visitInOrder( this , visitor );
return nodeTypes;
}
public AddressingMode getAddressingMode()
{
if ( addressingMode == AddressingMode.INTERNAL_EXPRESSION )
{
switch( getExpressionType() )
{
case CONSTANT:
return AddressingMode.INDIRECT;
case REGISTER:
return AddressingMode.INDIRECT_REGISTER;
case REGISTER_POSTINCREMENT:
return AddressingMode.INDIRECT_REGISTER_POSTINCREMENT;
case REGISTER_PREDECREMENT:
return AddressingMode.INDIRECT_REGISTER_PREDECREMENT;
case REGISTER_OFFSET:
return AddressingMode.INDIRECT_REGISTER_OFFSET;
default:
throw new RuntimeException("Internal error, unreachable code reached: "+getExpressionType()+" , this="+this);
}
}
return addressingMode;
}
private static boolean equals(Set<NodeType> set,NodeType... expected) {
for ( NodeType t : expected ) {
if ( ! set.contains( t ) ) {
return false;
}
}
return set.size() == expected.length;
}
private boolean nextTokenIsRegisterIdentifier(IParseContext context)
{
if ( context.eof() ) {
return false;
}
return context.peek().hasType( TokenType.CHARACTERS ) && Register.isRegisterIdentifier( context.peek().getContents() );
}
@Override
protected OperandNode copySingleNode()
{
final OperandNode result = new OperandNode();
result.addressingMode = getAddressingMode();
return result;
}
@Override
public boolean supportsChildNodes() {
return true;
}
}