/**
* 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.Collections;
import java.util.List;
import java.util.Stack;
import de.codesourcery.jasm16.Register;
import de.codesourcery.jasm16.compiler.GenericCompilationError;
import de.codesourcery.jasm16.compiler.ICompilationContext;
import de.codesourcery.jasm16.compiler.ISymbolTable;
import de.codesourcery.jasm16.exceptions.EOFException;
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;
import de.codesourcery.jasm16.utils.ITextRegion;
import de.codesourcery.jasm16.utils.TextRegion;
/**
* Represents an expression in the AST.
*
* <p>Note that while this class implements an
* shunting yard algorithm for getting
* operator precedence (including parens) right ,
* it currently only supports binary left-associative
* infix operators.</p>
*
* @author tobias.gierke@code-sourcery.de
* @see Operator
* @see OperatorNode
*/
public class ExpressionNode extends TermNode
{
public ExpressionNode() {
}
@Override
public boolean equals(Object obj)
{
return obj instanceof ExpressionNode;
}
private static boolean isEmptyExpression(ASTNode node)
{
return getTermCount( node ) == 0;
}
protected static int getTermCount(ASTNode node)
{
int termCount=0;
for ( ASTNode child : node.getChildren() )
{
if ( isTermNode( child ) ) {
termCount++;
}
}
return termCount;
}
@Override
public Long calculate(ISymbolTable symbolTable)
{
final List<TermNode> terms = new ArrayList<TermNode>();
final List<ConstantValueNode> literalValues = new ArrayList<ConstantValueNode>();
for ( ASTNode child : getChildren() ) {
if ( child instanceof ConstantValueNode ) {
literalValues.add( (ConstantValueNode) child );
}
else if ( child instanceof OperatorNode || child instanceof ExpressionNode )
{
terms.add( (TermNode) child);
} else if ( child instanceof RegisterReferenceNode ) {
// ignore
}
}
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 );
}
@Override
protected ASTNode parseInternal(IParseContext context) throws ParseException
{
final int offset = context.currentParseIndex();
final ASTNode result = recursivelyParseTerm( context );
if ( result == null ) {
throw new ParseException("Incomplete or empty expression",offset,0);
}
if ( this.getTextRegion() != null ) { // merge leading whitespace etc.
result.mergeWithAllTokensTextRegion( this.getTextRegion() );
}
if ( result.getChildCount() == 1 )
{
if ( ! hasAllRequiredArguments( result ) ) {
throw new ParseException("Incomplete expression, add missing argument to complete it.", result.getTextRegion().getEndOffset() ,0 );
}
}
return result;
}
private static enum ExpectedType {
NOTHING {
@Override
public boolean isTreatMinusAsOperator() { return true; }
},
LITERAL_OR_EXPRESSION {
@Override
public boolean isTreatMinusAsOperator() { return false; }
},
INFIX_OR_POSTFIX_OPERATOR {
@Override
public boolean isTreatMinusAsOperator() { return true; }
},
ANY_OPERATOR {
@Override
public boolean isTreatMinusAsOperator() { return true; }
},
INFIX_OP_OR_LITERAL_OR_EXPRESSION {
@Override
public boolean isTreatMinusAsOperator() { return true; }
};
public abstract boolean isTreatMinusAsOperator();
}
private ExpectedType getExpectedType(Stack<ASTNode> termStack,ASTNode previousNode)
{
final ASTNode lastSeen;
if ( termStack.isEmpty() )
{
if ( previousNode == null )
{
// may accept either an INFIX operator
// or Operator.MINUS that indicates we're about
// to parse a negative number
return ExpectedType.INFIX_OP_OR_LITERAL_OR_EXPRESSION;
}
lastSeen = previousNode;
}
else {
lastSeen = termStack.peek();
}
if ( isOperator( lastSeen ) )
{
if ( isParensOperator( lastSeen ) ) { // saw an parens expr. , now look for an operator
return ExpectedType.INFIX_OR_POSTFIX_OPERATOR;
}
final Operator op = ((OperatorNode) lastSeen).getOperator();
if ( op.isPrefixOperator() ) {
return ExpectedType.LITERAL_OR_EXPRESSION; // => PREFIX operator , we're expecting a value (or an expression) now
}
if ( op.isPostfixOperator() ) {
return ExpectedType.NOTHING; // POSTFIX operator , we're expecting NOTHING now
}
// INFIX operator
return ExpectedType.LITERAL_OR_EXPRESSION;
}
// last seen was something else than an operator
return ExpectedType.ANY_OPERATOR;
}
private ASTNode recursivelyParseTerm(IParseContext context) throws EOFException, ParseException
{
final Stack<ASTNode> termStack = new Stack<ASTNode>();
ASTNode previousNode=null;
do
{
if ( context.peek().isWhitespace() )
{
if ( termStack.isEmpty() )
{
mergeWithAllTokensTextRegion( context.parseWhitespace() );
} else {
termStack.peek().mergeWithAllTokensTextRegion( context.parseWhitespace() );
}
if ( ! isEmptyExpression(this) && context.eof() ) {
clearTermStack(termStack,previousNode,context);
return previousNode;
}
}
if ( context.peek().hasType( TokenType.STRING_DELIMITER) )
{
final int index = context.currentParseIndex();
final ASTNode newNode = new CharacterLiteralNode(1).parse( context );
previousNode = handleStack( termStack , newNode , index ,previousNode , context);
}
else if ( context.peek().hasType(TokenType.OPERATOR ) )
{
if ( context.peek().getContents().equals( Operator.DECREMENT.getLiteral() ) )
{
final int index = context.currentParseIndex();
final ASTNode parsed = new RegisterReferenceNode().parse( context );
previousNode = handleStack(termStack , parsed , index ,previousNode , context );
}
else
{
final int offset = context.currentParseIndex();
final ASTNode node = new OperatorNode().parse( context );
if ( node instanceof OperatorNode)
{
final OperatorNode operator = (OperatorNode) node;
// if ( ! operator.getOperator().isInfixOperator() ) // + - * /
// {
// throw new ParseException("Not implemented: Cannot handle operator "+operator.getOperator(), operator.getTextRegion() );
// }
boolean treatAsNegativeNumber = false;
if ( Operator.MINUS == operator.getOperator() ) {
if ( termStack.isEmpty() && previousNode == null ) {
treatAsNegativeNumber = true;
} else {
treatAsNegativeNumber = ! getExpectedType(termStack, previousNode).isTreatMinusAsOperator();
}
}
if ( treatAsNegativeNumber )
{
// consume any whitespace
ITextRegion whitespace =null;
if ( ! context.eof() && context.peek().isWhitespace() ) {
whitespace = new TextRegion( context.read( TokenType.WHITESPACE ) );
}
final ASTNode number = new NumberNode().parse( context );
if ( whitespace != null) {
number.mergeWithAllTokensTextRegion( whitespace );
}
number.mergeWithAllTokensTextRegion( node );
if ( number instanceof NumberNode) {
((NumberNode) number).convertToNegativeNumber();
}
previousNode = handleStack( termStack , number , offset ,previousNode , context);
}
else {
previousNode = handleStack( termStack , node , offset ,previousNode , context);
}
} else {
previousNode = handleStack( termStack , node , offset ,previousNode , context);
}
}
}
else if ( context.peek().hasType( TokenType.NUMBER_LITERAL ) )
{
final int index = context.currentParseIndex();
final ASTNode newNode = new NumberNode().parse( context );
previousNode = handleStack( termStack , newNode , index ,previousNode , context);
}
else if ( context.peek().hasType(TokenType.PARENS_OPEN ) )
{
final int index = context.currentParseIndex();
final IToken tok = context.read(); // read '('
final ASTNode node= new ExpressionNode().parse( context );
if ( context.eof() || ! context.peek().hasType( TokenType.PARENS_CLOSE ) )
{
throw new ParseException("Missing closing ')' ",context.currentParseIndex() ,0 );
}
final OperatorNode op = new OperatorNode(Operator.PARENS , node , context );
op.mergeWithAllTokensTextRegion( tok );
op.mergeWithAllTokensTextRegion( context.read(TokenType.PARENS_CLOSE ) );
previousNode = handleStack( termStack , op , index ,previousNode , context );
}
else if ( context.peek().hasType( TokenType.DOT ) || context.peek().hasType( TokenType.CHARACTERS ) )
{
// (local) identifier
final int index = context.currentParseIndex();
final ASTNode parsed;
if ( Register.isRegisterIdentifier( context.peek().getContents() ) ) {
parsed = new RegisterReferenceNode().parse( context );
} else {
parsed = new SymbolReferenceNode().parse( context );
}
previousNode = handleStack(termStack , parsed , index ,previousNode , context );
} else {
return clearTermStack(termStack,previousNode,context);
}
if ( ! context.eof() && context.peek().isWhitespace() )
{
if ( termStack.isEmpty() ) {
mergeWithAllTokensTextRegion( context.parseWhitespace() );
} else {
termStack.peek().mergeWithAllTokensTextRegion( context.parseWhitespace() );
}
if ( ! isEmptyExpression(this) && context.eof() ) {
return clearTermStack(termStack,previousNode,context);
}
}
} while ( ! context.eof() );
return clearTermStack(termStack,previousNode,context);
}
private static boolean isOperator(ASTNode node)
{
return node instanceof OperatorNode;
}
private static boolean isParensOperator(ASTNode node)
{
return (node instanceof OperatorNode) && ((OperatorNode) node).getOperator() == Operator.PARENS;
}
private ASTNode handleStack(Stack<ASTNode> termStack,ASTNode node,int parseIndex,ASTNode previousNode,IParseContext context) throws ParseException
{
if ( termStack.isEmpty() || ! isOperator( node ) )
{
termStack.push( node );
return previousNode;
}
final OperatorNode currentOperator = (OperatorNode) node;
final OperatorNode previousOperator = findLastOperatorOnStack( termStack , parseIndex );
if ( previousOperator == null ) {
termStack.push( currentOperator );
return previousNode;
}
/*
while there is an operator token, previousOperator, at the top of the stack, and
- nextOperator's precedence is less than or equal to that of previousOperator,
*/
ASTNode newPrevious = previousNode;
if ( currentOperator.getOperator().getPrecedence() <= previousOperator.getOperator().getPrecedence() ) {
newPrevious = clearTermStack( termStack , previousNode ,context);
}
termStack.push( currentOperator );
return newPrevious;
}
private OperatorNode findLastOperatorOnStack(Stack<ASTNode> stack, int parseIndex) throws ParseException
{
final int len = stack.size()-1;
for ( int i = len ; i >= 0 ; i-- ) {
if ( isOperator( stack.get(i) ) ) {
return (OperatorNode) stack.get(i);
}
}
if ( stack.size() > 1 ) {
throw new ParseException("Missing operator",parseIndex,0);
}
return null;
}
private boolean hasAllRequiredArguments(ASTNode node)
{
if ( isOperator( node ) ) {
return ((OperatorNode) node).hasAllRequiredArguments();
}
return false;
}
private ASTNode clearTermStack(final Stack<ASTNode> stack,ASTNode previousNode,IParseContext context) throws ParseException
{
if ( stack.isEmpty() ) {
return previousNode;
}
final Stack<ASTNode> operatorStack = new Stack<ASTNode>();
final Stack<ASTNode> argumentStack = new Stack<ASTNode>();
while ( ! stack.isEmpty() )
{
final ASTNode n = stack.pop();
if ( isOperator( n ) && ! isParensOperator( n ) )
{
operatorStack.push( n );
} else {
argumentStack.push( unwrapParens( n ) ); // Operator.PARENS has only one argument that is in fact an already evaluated expression
}
}
Collections.reverse( operatorStack );
Collections.reverse( argumentStack );
ASTNode rootNode = null;
ASTNode lastNode = null;
while( ! operatorStack.isEmpty() )
{
/*
* TODO: This code currently only handles left-associative INFIX operators
* with two arguments.
*/
OperatorNode operator = (OperatorNode) operatorStack.pop();
if ( argumentStack.isEmpty() ) {
throw new ParseException("Operator '"+operator.getOperator().getLiteral()+"' requires an operand",operator.getTextRegion().getEndOffset() , 0 );
}
ASTNode argument1 = argumentStack.pop();
ASTNode argument2 = operator.getOperator().getRequiredOperandCount() == 2 ? argumentStack.isEmpty() ? null : argumentStack.pop() : null;
operator.insertChild( 0 , argument1 , context );
if ( argument2 != null ) {
operator.insertChild( 0 , argument2 , context );
}
if ( rootNode == null ) {
rootNode = operator;
}
lastNode = operator;
argumentStack.push( operator );
}
if ( argumentStack.size() == 1 )
{
if ( lastNode == null ) {
rootNode = lastNode = argumentStack.pop();
} else {
ASTNode arg = argumentStack.pop();
lastNode = arg;
}
} else if ( argumentStack.size() > 1 ) {
throw new ParseException("Expression has no operators?",argumentStack.pop().getTextRegion() );
}
if ( ! operatorStack.isEmpty() || ! argumentStack.isEmpty() ) {
throw new RuntimeException("Post-condition failure");
}
if ( previousNode == null ) {
return lastNode;
}
if ( ! isOperator( previousNode ) ) {
throw new RuntimeException("Internal error");
}
if ( hasAllRequiredArguments( previousNode ) ) {
lastNode.insertChild( 0 , previousNode , context );
} else {
previousNode.addChild( rootNode , context );
}
return lastNode;
}
private ASTNode unwrapParens(ASTNode node)
{
if ( !(node instanceof OperatorNode) ) {
return node;
}
if ( ((OperatorNode) node).getOperator() == Operator.PARENS )
{
ASTNode result = node.child(0);
result.setTextRegionIncludingAllTokens( node.getTextRegion() ); // include '(' and ')'
return result;
}
return node;
}
public static boolean isTermNode(ASTNode n)
{
return n instanceof TermNode;
}
protected boolean isLiteralExpression(ICompilationContext context)
{
return getLiteralValueNode( context ) != null;
}
private ConstantValueNode getLiteralValueNode(ICompilationContext context)
{
final List<ConstantValueNode> nodes = new ArrayList<ConstantValueNode>();
for ( ASTNode child : getChildren() ) {
if ( child instanceof ConstantValueNode ) {
nodes.add( (ConstantValueNode) child );
}
}
if ( nodes.size() == 1 )
{
return nodes.get(0);
}
return null;
}
private static boolean containsRegisterReference(ASTNode node)
{
return ASTUtils.containsNodeWithType( node , RegisterReferenceNode.class );
}
private static boolean containsOnlyOperators(ASTNode root,final Operator operator)
{
final boolean[] result = { false };
final ISimpleASTNodeVisitor<OperatorNode> visitor = new ISimpleASTNodeVisitor<OperatorNode>() {
@Override
public boolean visit(OperatorNode node)
{
if ( node.getOperator() ==operator ) {
result[0] = true;
}
return true;
}
};
ASTUtils.visitNodesByType( root , visitor , OperatorNode.class );
return result[0];
}
public TermNode reduce(ICompilationContext context)
{
ExpressionNode result = new ExpressionNode();
result.setTextRegionIncludingAllTokens( getTextRegion() );
for ( ASTNode child : getChildren() )
{
if ( child instanceof TermNode)
{
final ASTNode reduced = ((TermNode) child).reduce( context );
result.addChild( reduced , null );
} else {
result.mergeTextRegion( child.getTextRegion() );
}
}
// trivial case: resulting expression is a constant/number
if ( result.isLiteralExpression(context) )
{
try {
return result.getLiteralValueNode( context ).toNumberNode( context , result.getTextRegion() );
} catch (ParseException e) {
context.getCurrentCompilationUnit().addMarker( new GenericCompilationError( e.getMessage() , context.getCurrentCompilationUnit() , e ) );
}
}
if ( containsRegisterReference( result ) && containsOnlyOperators( result , Operator.PLUS ) )
{
// maybe NUMBER1 + a + NUMBER2 or ( a + NUMBER1 ) + NUMBER2 )
// since '+' is commutative , re-write the expression so that it becomes
// A + ( NUMBER1 + NUMBER 2) and thus can be simplified
// the code is based on the assumption that we can have at most one register reference
// per expression (which is something the parse() methods assures)
boolean tryAgain = false;
while ( rewriteRegisterExpression( result ) ) {
tryAgain = true;
}
if ( tryAgain )
{
ExpressionNode result2 = new ExpressionNode();
result.setTextRegionIncludingAllTokens( result.getTextRegion() );
for ( ASTNode child : result.getChildren() )
{
if ( child instanceof TermNode)
{
result2.addChild( ((TermNode) child).reduce( context ) , null );
} else {
result2.mergeTextRegion( child.getTextRegion() );
}
}
result=result2;
}
}
if ( result.getChildCount() == 1 && result.child(0) instanceof TermNode)
{
final TermNode realResult = (TermNode) result.child(0);
realResult.mergeWithAllTokensTextRegion( result );
return realResult;
}
return result;
}
protected static boolean rewriteRegisterExpression(ASTNode input) {
final List<RegisterReferenceNode> matches = ASTUtils.getNodesByType( input , RegisterReferenceNode.class , false );
if ( ! matches.isEmpty() ) {
ASTNode parent = matches.get(0).getParent();
if ( parent instanceof OperatorNode )
{
final OperatorNode op = (OperatorNode) parent;
return moveUp( op , matches.get(0) );
} else if ( parent instanceof ExpressionNode && parent.getParent() instanceof OperatorNode) {
final OperatorNode op = (OperatorNode) parent.getParent();
return moveUp( op , matches.get(0) );
}
}
return false;
}
private static boolean moveUp(OperatorNode node, RegisterReferenceNode nodeToSwap)
{
/* (a+3) + 4
*
* + +
* / \ / \
* + 4 ===> + (a)
* / \ / \
* (a) 3 4 3
*
* 4+a+3
*
* + +
* / \ / \
* 4 + ===> (a) +
* / \ / \
* (a) 3 4 3
*/
final ASTNode parent = node.getParent();
if ( ! ( parent instanceof OperatorNode) ) {
return false;
}
final OperatorNode op = (OperatorNode) parent;
final TermNode child0 = op.getTerm(0);
final TermNode child1 = op.getTerm(1);
if ( child0.isNumberLiteral() && child1 == node ) {
op.swapChild( child0 , nodeToSwap );
return true;
} else if ( child1.isNumberLiteral() && child0 == node ) {
op.swapChild( child1 , nodeToSwap );
return true;
}
return false;
}
@Override
protected ExpressionNode copySingleNode()
{
return new ExpressionNode();
}
@Override
public boolean supportsChildNodes() {
return true;
}
}