/* * 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 java.util.Collections; import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Stack; public final class InternFormulaUtils { // Suppress default constructor for noninstantiability private InternFormulaUtils() { throw new AssertionError(); } public static List<InternToken> getFunctionByFunctionBracketClose(List<InternToken> internTokenList, int functionBracketCloseInternTokenListIndex) { if (functionBracketCloseInternTokenListIndex == 0 || functionBracketCloseInternTokenListIndex == internTokenList.size()) { return null; } List<InternToken> functionInternTokenList = new LinkedList<InternToken>(); functionInternTokenList.add(internTokenList.get(functionBracketCloseInternTokenListIndex)); int functionIndex = functionBracketCloseInternTokenListIndex - 1; InternToken tempSearchToken; int nestedFunctionsCounter = 1; do { if (functionIndex < 0) { return null; } tempSearchToken = internTokenList.get(functionIndex); functionIndex--; switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter--; break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter++; break; } functionInternTokenList.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN || nestedFunctionsCounter != 0); if (functionIndex < 0) { return null; } tempSearchToken = internTokenList.get(functionIndex); if (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_NAME) { return null; } functionInternTokenList.add(tempSearchToken); Collections.reverse(functionInternTokenList); return functionInternTokenList; } public static List<InternToken> getFunctionByParameterDelimiter(List<InternToken> internTokenList, int functionParameterDelimiterInternTokenListIndex) { if (functionParameterDelimiterInternTokenListIndex == 0 || functionParameterDelimiterInternTokenListIndex == internTokenList.size()) { return null; } List<InternToken> functionInternTokenList = new LinkedList<InternToken>(); functionInternTokenList.add(internTokenList.get(functionParameterDelimiterInternTokenListIndex)); int functionIndex = functionParameterDelimiterInternTokenListIndex - 1; InternToken tempSearchToken; int nestedFunctionsCounter = 1; do { if (functionIndex < 0) { return null; } tempSearchToken = internTokenList.get(functionIndex); functionIndex--; switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter--; break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter++; break; } functionInternTokenList.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN || nestedFunctionsCounter != 0); if (functionIndex < 0) { return null; } tempSearchToken = internTokenList.get(functionIndex); if (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_NAME) { return null; } functionInternTokenList.add(tempSearchToken); Collections.reverse(functionInternTokenList); functionIndex = functionParameterDelimiterInternTokenListIndex + 1; nestedFunctionsCounter = 1; do { if (functionIndex >= internTokenList.size()) { return null; } tempSearchToken = internTokenList.get(functionIndex); functionIndex++; switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter++; break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter--; break; } functionInternTokenList.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE || nestedFunctionsCounter != 0); return functionInternTokenList; } public static List<InternToken> getFunctionByFunctionBracketOpen(List<InternToken> internTokenList, int functionBracketOpenInternTokenListIndex) { if (functionBracketOpenInternTokenListIndex <= 0 || functionBracketOpenInternTokenListIndex >= internTokenList.size()) { return null; } InternToken functionNameInternToken = internTokenList.get(functionBracketOpenInternTokenListIndex - 1); if (functionNameInternToken.getInternTokenType() != InternTokenType.FUNCTION_NAME) { return null; } List<InternToken> functionInternTokenList = getFunctionByName(internTokenList, functionBracketOpenInternTokenListIndex - 1); return functionInternTokenList; } public static List<InternToken> getFunctionByName(List<InternToken> internTokenList, int functionStartListIndex) { InternToken functionNameToken = internTokenList.get(functionStartListIndex); List<InternToken> functionInternTokenList = new LinkedList<InternToken>(); if (functionNameToken.getInternTokenType() != InternTokenType.FUNCTION_NAME) { return null; } functionInternTokenList.add(functionNameToken); int functionIndex = functionStartListIndex + 1; if (functionIndex >= internTokenList.size()) { return functionInternTokenList; } InternToken functionStartParameter = internTokenList.get(functionIndex); if (!functionStartParameter.isFunctionParameterBracketOpen()) { return functionInternTokenList; } functionInternTokenList.add(functionStartParameter); functionIndex++; InternToken tempSearchToken; int nestedFunctionsCounter = 1; do { if (functionIndex >= internTokenList.size()) { return null; } tempSearchToken = internTokenList.get(functionIndex); functionIndex++; switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter++; break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter--; break; } functionInternTokenList.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE || nestedFunctionsCounter != 0); return functionInternTokenList; } public static List<InternToken> generateTokenListByBracketOpen(List<InternToken> internTokenList, int internTokenListIndex) { if (internTokenListIndex == internTokenList.size() || internTokenList.get(internTokenListIndex).getInternTokenType() != InternTokenType.BRACKET_OPEN) { return null; } List<InternToken> bracketInternTokenListToReturn = new LinkedList<InternToken>(); bracketInternTokenListToReturn.add(internTokenList.get(internTokenListIndex)); int bracketsIndex = internTokenListIndex + 1; int nestedBracketsCounter = 1; InternToken tempSearchToken; do { if (bracketsIndex >= internTokenList.size()) { return null; } tempSearchToken = internTokenList.get(bracketsIndex); bracketsIndex++; switch (tempSearchToken.getInternTokenType()) { case BRACKET_OPEN: nestedBracketsCounter++; break; case BRACKET_CLOSE: nestedBracketsCounter--; break; } bracketInternTokenListToReturn.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.BRACKET_CLOSE || nestedBracketsCounter != 0); return bracketInternTokenListToReturn; } public static List<InternToken> generateTokenListByBracketClose(List<InternToken> internTokenList, int internTokenListIndex) { if (internTokenListIndex == internTokenList.size() || internTokenList.get(internTokenListIndex).getInternTokenType() != InternTokenType.BRACKET_CLOSE) { return null; } List<InternToken> bracketInternTokenListToReturn = new LinkedList<InternToken>(); bracketInternTokenListToReturn.add(internTokenList.get(internTokenListIndex)); int bracketSearchIndex = internTokenListIndex - 1; int nestedBracketsCounter = 1; InternToken tempSearchToken; do { if (bracketSearchIndex < 0) { return null; } tempSearchToken = internTokenList.get(bracketSearchIndex); bracketSearchIndex--; switch (tempSearchToken.getInternTokenType()) { case BRACKET_CLOSE: nestedBracketsCounter++; break; case BRACKET_OPEN: nestedBracketsCounter--; break; } bracketInternTokenListToReturn.add(tempSearchToken); } while (tempSearchToken.getInternTokenType() != InternTokenType.BRACKET_OPEN || nestedBracketsCounter != 0); Collections.reverse(bracketInternTokenListToReturn); return bracketInternTokenListToReturn; } public static List<List<InternToken>> getFunctionParameterInternTokensAsLists( List<InternToken> functionInternTokenList) { List<List<InternToken>> functionParameterInternTokenList = new LinkedList<List<InternToken>>(); if (functionInternTokenList == null || functionInternTokenList.size() < 4 || functionInternTokenList.get(0).getInternTokenType() != InternTokenType.FUNCTION_NAME || functionInternTokenList.get(1).getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN) { return null; } int searchIndex = 2; List<InternToken> currentParameterInternTokenList = new LinkedList<InternToken>(); InternToken tempSearchToken; int nestedFunctionsCounter = 1; do { if (searchIndex >= functionInternTokenList.size()) { return null; } tempSearchToken = functionInternTokenList.get(searchIndex); switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter++; currentParameterInternTokenList.add(tempSearchToken); break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter--; if (nestedFunctionsCounter != 0) { currentParameterInternTokenList.add(tempSearchToken); } break; case FUNCTION_PARAMETER_DELIMITER: if (nestedFunctionsCounter == 1) { functionParameterInternTokenList.add(currentParameterInternTokenList); currentParameterInternTokenList = new LinkedList<InternToken>(); break; } default: currentParameterInternTokenList.add(tempSearchToken); break; } searchIndex++; } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE || nestedFunctionsCounter != 0); if (currentParameterInternTokenList.size() > 0) { functionParameterInternTokenList.add(currentParameterInternTokenList); } return functionParameterInternTokenList; } public static boolean isFunction(List<InternToken> internTokenList) { List<InternToken> functionList = getFunctionByName(internTokenList, 0); return !(functionList == null || functionList.size() != internTokenList.size()); } private static InternTokenType getFirstInternTokenType(List<InternToken> internTokens) { if (internTokens == null || internTokens.size() == 0) { return null; } return internTokens.get(0).getInternTokenType(); } public static boolean isPeriodToken(List<InternToken> internTokens) { if (internTokens == null || internTokens.size() != 1) { return false; } InternTokenType firstInternTokenType = internTokens.get(0).getInternTokenType(); if (firstInternTokenType == InternTokenType.PERIOD) { return true; } return false; } public static boolean isFunctionToken(List<InternToken> internTokens) { InternTokenType firstInternTokenType = getFirstInternTokenType(internTokens); if (firstInternTokenType != null && firstInternTokenType == InternTokenType.FUNCTION_NAME) { return true; } return false; } public static boolean isNumberToken(List<InternToken> internTokens) { InternTokenType firstInternTokenType = getFirstInternTokenType(internTokens); if (firstInternTokenType != null && internTokens.size() <= 1 && firstInternTokenType == InternTokenType.NUMBER) { return true; } return false; } public static List<InternToken> replaceFunctionByTokens(List<InternToken> functionToReplace, List<InternToken> internTokensToReplaceWith) { if (isFunctionToken(internTokensToReplaceWith)) { return replaceFunctionButKeepParameters(functionToReplace, internTokensToReplaceWith); } return internTokensToReplaceWith; } public static List<InternToken> insertOperatorToNumberToken(InternToken numberTokenToBeModified, int externNumberOffset, InternToken operatorToInsert) { List<InternToken> replaceTokenList = new LinkedList<InternToken>(); String numberString = numberTokenToBeModified.getTokenStringValue(); String leftPart = numberString.substring(0, externNumberOffset); String rightPart = numberString.substring(externNumberOffset); InternToken leftNumber = new InternToken(InternTokenType.NUMBER, leftPart); replaceTokenList.add(leftNumber); replaceTokenList.add(operatorToInsert); InternToken rightNumber = new InternToken(InternTokenType.NUMBER, rightPart); replaceTokenList.add(rightNumber); return replaceTokenList; } public static InternToken insertIntoNumberToken(InternToken numberTokenToBeModified, int externNumberOffset, String numberToInsert) { String numberString = numberTokenToBeModified.getTokenStringValue(); String leftPart = numberString.substring(0, externNumberOffset); String rightPart = numberString.substring(externNumberOffset); numberTokenToBeModified.setTokenStringValue(leftPart + numberToInsert + rightPart); return numberTokenToBeModified; } public static List<InternToken> replaceFunctionButKeepParameters(List<InternToken> functionToReplace, List<InternToken> functionToReplaceWith) { List<List<InternToken>> keepParameterInternTokenList = getFunctionParameterInternTokensAsLists(functionToReplace); List<InternToken> replacedParametersFunction = new LinkedList<InternToken>(); List<List<InternToken>> originalParameterInternTokenList = getFunctionParameterInternTokensAsLists(functionToReplaceWith); if (functionToReplace == null || keepParameterInternTokenList == null || originalParameterInternTokenList == null) { return functionToReplaceWith; } replacedParametersFunction.add(functionToReplaceWith.get(0)); replacedParametersFunction.add(new InternToken(InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN)); int functionParameterCount = getFunctionParameterCount(functionToReplaceWith); for (int index = 0; index < functionParameterCount; index++) { if (index < keepParameterInternTokenList.size() && keepParameterInternTokenList.get(index).size() > 0) { replacedParametersFunction.addAll(keepParameterInternTokenList.get(index)); } else { replacedParametersFunction.addAll(originalParameterInternTokenList.get(index)); } if (index < functionParameterCount - 1) { replacedParametersFunction.add(new InternToken(InternTokenType.FUNCTION_PARAMETER_DELIMITER)); } } replacedParametersFunction.add(new InternToken(InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE)); return replacedParametersFunction; } static int getFunctionParameterCount(List<InternToken> functionInternTokenList) { if (functionInternTokenList == null || functionInternTokenList.size() < 4 || functionInternTokenList.get(0).getInternTokenType() != InternTokenType.FUNCTION_NAME || functionInternTokenList.get(1).getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN) { return 0; } int searchIndex = 2; InternToken tempSearchToken; int nestedFunctionsCounter = 1; int functionParameterCount = 1; do { if (searchIndex >= functionInternTokenList.size()) { return 0; } tempSearchToken = functionInternTokenList.get(searchIndex); switch (tempSearchToken.getInternTokenType()) { case FUNCTION_PARAMETERS_BRACKET_OPEN: nestedFunctionsCounter++; break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: nestedFunctionsCounter--; break; case FUNCTION_PARAMETER_DELIMITER: if (nestedFunctionsCounter == 1) { functionParameterCount++; } break; } searchIndex++; } while (tempSearchToken.getInternTokenType() != InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE || nestedFunctionsCounter != 0); return functionParameterCount; } public static InternToken deleteNumberByOffset(InternToken cursorPositionInternToken, int externNumberOffset) { String numberString = cursorPositionInternToken.getTokenStringValue(); if (externNumberOffset < 1) { return cursorPositionInternToken; } if (externNumberOffset > numberString.length()) { externNumberOffset = numberString.length(); } String leftPart = numberString.substring(0, externNumberOffset - 1); String rightPart = numberString.substring(externNumberOffset); cursorPositionInternToken.setTokenStringValue(leftPart + rightPart); if (cursorPositionInternToken.getTokenStringValue().isEmpty()) { return null; } return cursorPositionInternToken; } public static boolean applyBracketCorrection(List<InternToken> internFormula) throws EmptyStackException { Stack<InternTokenType> stack = new Stack<InternTokenType>(); for (int index = 0; index < internFormula.size(); index++) { switch (internFormula.get(index).getInternTokenType()) { case BRACKET_OPEN: stack.push(InternTokenType.BRACKET_OPEN); break; case FUNCTION_PARAMETERS_BRACKET_OPEN: stack.push(InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN); break; case BRACKET_CLOSE: if (stack.peek() == InternTokenType.BRACKET_OPEN) { stack.pop(); } else { if (swapBrackets(internFormula, index, InternTokenType.FUNCTION_PARAMETERS_BRACKET_CLOSE)) { stack.pop(); continue; } return false; } break; case FUNCTION_PARAMETERS_BRACKET_CLOSE: if (stack.peek() == InternTokenType.FUNCTION_PARAMETERS_BRACKET_OPEN) { stack.pop(); } else { if (swapBrackets(internFormula, index, InternTokenType.BRACKET_CLOSE)) { stack.pop(); continue; } return false; } break; } } return true; } private static boolean swapBrackets(List<InternToken> internFormula, int firstBracketIndex, InternTokenType secondBracket) { for (int index = firstBracketIndex + 1; index < internFormula.size(); index++) { if (internFormula.get(index).getInternTokenType() == secondBracket) { InternToken firstBracket = internFormula.get(firstBracketIndex); internFormula.set(firstBracketIndex, internFormula.get(index)); internFormula.set(index, firstBracket); return true; } } return false; } }