/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.formulaeditor;
import android.util.Log;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.content.bricks.UserBrick;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
public class InternFormulaParser {
private class InternFormulaParserException extends Exception {
private static final long serialVersionUID = 1L;
public InternFormulaParserException(String errorMessage) {
super(errorMessage);
}
}
public static final int PARSER_OK = -1;
public static final int PARSER_STACK_OVERFLOW = -2;
public static final int PARSER_NO_INPUT = -4;
private static final int MAXIMUM_TOKENS_TO_PARSE = 1000;
private static final String TAG = InternFormulaParser.class.getSimpleName();
private List<InternToken> internTokensToParse;
private int currentTokenParseIndex;
private int errorTokenIndex;
private InternToken currentToken;
public InternFormulaParser(List<InternToken> internTokensToParse) {
this.internTokensToParse = internTokensToParse;
}
private void getNextToken() {
currentTokenParseIndex++;
currentToken = internTokensToParse.get(currentTokenParseIndex);
}
public int getErrorTokenIndex() {
return errorTokenIndex;
}
private FormulaElement findLowerOrEqualPriorityOperatorElement(Operators currentOperator,
FormulaElement currentElement) {
FormulaElement returnElement = currentElement.getParent();
FormulaElement notNullElement = currentElement;
boolean condition = true;
while (condition) {
if (returnElement == null) {
condition = false;
returnElement = notNullElement;
} else {
Operators parentOperator = Operators.getOperatorByValue(returnElement.getValue());
int compareOperator = parentOperator.compareOperatorTo(currentOperator);
if (compareOperator < 0) {
condition = false;
returnElement = notNullElement;
} else {
notNullElement = returnElement;
returnElement = returnElement.getParent();
}
}
}
return returnElement;
}
public void handleOperator(String operator, FormulaElement currentElement, FormulaElement newElement) {
if (currentElement.getParent() == null) {
new FormulaElement(FormulaElement.ElementType.OPERATOR, operator, null, currentElement, newElement);
return;
}
Operators parentOperator = Operators.getOperatorByValue(currentElement.getParent().getValue());
Operators currentOperator = Operators.getOperatorByValue(operator);
int compareOperator = parentOperator.compareOperatorTo(currentOperator);
if (compareOperator >= 0) {
FormulaElement newLeftChild = findLowerOrEqualPriorityOperatorElement(currentOperator, currentElement);
FormulaElement newParent = newLeftChild.getParent();
if (newParent != null) {
newLeftChild.replaceWithSubElement(operator, newElement);
} else {
new FormulaElement(FormulaElement.ElementType.OPERATOR, operator, null, newLeftChild, newElement);
}
} else {
currentElement.replaceWithSubElement(operator, newElement);
}
}
private void addEndOfFileToken() {
InternToken endOfFileParserToken = new InternToken(InternTokenType.PARSER_END_OF_FILE);
internTokensToParse.add(endOfFileParserToken);
}
private void removeEndOfFileToken() {
internTokensToParse.remove(internTokensToParse.size() - 1);
}
public FormulaElement parseFormula() {
errorTokenIndex = PARSER_OK;
currentTokenParseIndex = 0;
if (internTokensToParse == null || internTokensToParse.size() == 0) {
errorTokenIndex = PARSER_NO_INPUT;
return null;
}
if (internTokensToParse.size() > MAXIMUM_TOKENS_TO_PARSE) {
errorTokenIndex = PARSER_STACK_OVERFLOW;
errorTokenIndex = 0;
return null;
}
try {
List<InternToken> copyIternTokensToParse = new ArrayList<InternToken>(internTokensToParse);
if (InternFormulaUtils.applyBracketCorrection(copyIternTokensToParse)) {
internTokensToParse.clear();
internTokensToParse.addAll(copyIternTokensToParse);
}
} catch (EmptyStackException emptyStackException) {
Log.d(TAG, "Bracket correction failed.", emptyStackException);
}
addEndOfFileToken();
currentToken = internTokensToParse.get(0);
FormulaElement formulaParseTree = null;
try {
formulaParseTree = formula();
} catch (InternFormulaParserException parseExeption) {
errorTokenIndex = currentTokenParseIndex;
}
removeEndOfFileToken();
return formulaParseTree;
}
private FormulaElement formula() throws InternFormulaParserException {
FormulaElement termListTree = termList();
if (currentToken.isEndOfFileToken()) {
return termListTree;
}
throw new InternFormulaParserException("Parse Error");
}
private FormulaElement termList() throws InternFormulaParserException {
FormulaElement currentElement = term();
FormulaElement loopTermTree;
String operatorStringValue;
while (currentToken.isOperator() && !currentToken.getTokenStringValue().equals(Operators.LOGICAL_NOT.name())) {
operatorStringValue = currentToken.getTokenStringValue();
getNextToken();
loopTermTree = term();
handleOperator(operatorStringValue, currentElement, loopTermTree);
currentElement = loopTermTree;
}
return currentElement.getRoot();
}
private FormulaElement term() throws InternFormulaParserException {
FormulaElement termTree = new FormulaElement(FormulaElement.ElementType.NUMBER, null, null);
FormulaElement currentElement = termTree;
if (currentToken.isOperator() && currentToken.getTokenStringValue().equals(Operators.MINUS.name())) {
currentElement = new FormulaElement(FormulaElement.ElementType.NUMBER, null, termTree, null, null);
termTree.replaceElement(new FormulaElement(FormulaElement.ElementType.OPERATOR, Operators.MINUS.name(),
null, null, currentElement));
getNextToken();
} else if (currentToken.isOperator() && currentToken.getTokenStringValue().equals(Operators.LOGICAL_NOT.name())) {
currentElement = new FormulaElement(FormulaElement.ElementType.NUMBER, null, termTree, null, null);
termTree.replaceElement(new FormulaElement(FormulaElement.ElementType.OPERATOR, Operators.LOGICAL_NOT
.name(), null, null, currentElement));
getNextToken();
}
switch (currentToken.getInternTokenType()) {
case NUMBER:
currentElement.replaceElement(FormulaElement.ElementType.NUMBER, number());
break;
case BRACKET_OPEN:
getNextToken();
currentElement.replaceElement(new FormulaElement(FormulaElement.ElementType.BRACKET, null, null, null,
termList()));
if (!currentToken.isBracketClose()) {
throw new InternFormulaParserException("Parse Error");
}
getNextToken();
break;
case FUNCTION_NAME:
currentElement.replaceElement(function());
break;
case SENSOR:
currentElement.replaceElement(sensor());
break;
case USER_VARIABLE:
currentElement.replaceElement(userVariable());
break;
case USER_LIST:
currentElement.replaceElement(userList());
break;
case STRING:
currentElement.replaceElement(FormulaElement.ElementType.STRING, string());
break;
case COLLISION_FORMULA:
currentElement.replaceElement(collision());
break;
default:
throw new InternFormulaParserException("Parse Error");
}
return termTree;
}
private FormulaElement userVariable() throws InternFormulaParserException {
DataContainer dataContainer = ProjectManager.getInstance().getSceneToPlay().getDataContainer();
UserBrick currentBrick = ProjectManager.getInstance().getCurrentUserBrick();
Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite();
if (dataContainer.getUserVariable(currentToken.getTokenStringValue(), currentBrick, currentSprite) == null) {
throw new InternFormulaParserException("Parse Error");
}
FormulaElement lookTree = new FormulaElement(FormulaElement.ElementType.USER_VARIABLE,
currentToken.getTokenStringValue(), null);
getNextToken();
return lookTree;
}
private FormulaElement collision() throws InternFormulaParserException {
String firstSpriteName = ProjectManager.getInstance().getCurrentSprite().getName();
String secondSpriteName = currentToken.getTokenStringValue();
boolean formulaOk;
int spriteCount = 0;
for (Sprite sprite : ProjectManager.getInstance().getSceneToPlay().getSpriteList()) {
if (sprite.getName().compareTo(firstSpriteName) == 0 || sprite.getName().compareTo(secondSpriteName) == 0) {
spriteCount++;
}
}
formulaOk = (spriteCount == 2);
if (!formulaOk) {
throw new InternFormulaParserException("Parse Error, Sprite was not found");
}
FormulaElement lookTree = new FormulaElement(FormulaElement.ElementType.COLLISION_FORMULA,
currentToken.getTokenStringValue(), null);
getNextToken();
return lookTree;
}
private FormulaElement userList() throws InternFormulaParserException {
DataContainer dataContainer = ProjectManager.getInstance().getSceneToPlay().getDataContainer();
Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite();
if (dataContainer.getUserList(currentToken.getTokenStringValue(), currentSprite) == null) {
throw new InternFormulaParserException("Parse Error");
}
FormulaElement lookTree = new FormulaElement(FormulaElement.ElementType.USER_LIST,
currentToken.getTokenStringValue(), null);
getNextToken();
return lookTree;
}
private FormulaElement sensor() throws InternFormulaParserException {
if (!Sensors.isSensor(currentToken.getTokenStringValue())) {
throw new InternFormulaParserException("Parse Error");
}
FormulaElement sensorTree = new FormulaElement(FormulaElement.ElementType.SENSOR,
currentToken.getTokenStringValue(), null);
getNextToken();
return sensorTree;
}
private FormulaElement function() throws InternFormulaParserException {
if (!Functions.isFunction(currentToken.getTokenStringValue())) {
throw new InternFormulaParserException("Parse Error");
}
FormulaElement functionTree = new FormulaElement(FormulaElement.ElementType.FUNCTION, currentToken.getTokenStringValue(), null);
getNextToken();
if (currentToken.isFunctionParameterBracketOpen()) {
getNextToken();
functionTree.setLeftChild(termList());
if (currentToken.isFunctionParameterDelimiter()) {
getNextToken();
functionTree.setRightChild(termList());
}
if (!currentToken.isFunctionParameterBracketClose()) {
throw new InternFormulaParserException("Parse Error");
}
getNextToken();
}
return functionTree;
}
private String number() throws InternFormulaParserException {
String numberToCheck = currentToken.getTokenStringValue();
if (!numberToCheck.matches("(\\d)+(\\.(\\d)+)?")) {
throw new InternFormulaParserException("Parse Error");
}
getNextToken();
return numberToCheck;
}
private String string() {
String currentStringValue = currentToken.getTokenStringValue();
getNextToken();
return currentStringValue;
}
}