diff options
Diffstat (limited to 'src/net/sourceforge/plantuml/tim/expression')
10 files changed, 1196 insertions, 0 deletions
diff --git a/src/net/sourceforge/plantuml/tim/expression/Expression.java b/src/net/sourceforge/plantuml/tim/expression/Expression.java new file mode 100644 index 0000000..ca65b9f --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/Expression.java @@ -0,0 +1,40 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + + +public class Expression { + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/Knowledge.java b/src/net/sourceforge/plantuml/tim/expression/Knowledge.java new file mode 100644 index 0000000..8a102f5 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/Knowledge.java @@ -0,0 +1,46 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import net.sourceforge.plantuml.tim.TFunction; +import net.sourceforge.plantuml.tim.TFunctionSignature; + +public interface Knowledge { + + public TValue getVariable(String name); + + public TFunction getFunction(TFunctionSignature signature); + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/ReversePolishInterpretor.java b/src/net/sourceforge/plantuml/tim/expression/ReversePolishInterpretor.java new file mode 100644 index 0000000..b45b2c5 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/ReversePolishInterpretor.java @@ -0,0 +1,126 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import net.sourceforge.plantuml.LineLocation; +import net.sourceforge.plantuml.tim.EaterExceptionLocated; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TContext; +import net.sourceforge.plantuml.tim.TFunction; +import net.sourceforge.plantuml.tim.TFunctionSignature; +import net.sourceforge.plantuml.tim.TMemory; + +public class ReversePolishInterpretor { + + private final TValue result; + private boolean trace = false; + + public ReversePolishInterpretor(TokenStack queue, Knowledge knowledge, TMemory memory, TContext context) + throws EaterException, EaterExceptionLocated { + this(null, queue, knowledge, memory, context); + } + + public ReversePolishInterpretor(LineLocation location, TokenStack queue, Knowledge knowledge, TMemory memory, + TContext context) throws EaterException, EaterExceptionLocated { + + final Deque<TValue> stack = new ArrayDeque<TValue>(); + if (trace) + System.err.println("ReversePolishInterpretor::queue=" + queue); + for (TokenIterator it = queue.tokenIterator(); it.hasMoreTokens();) { + final Token token = it.nextToken(); + if (trace) + System.err.println("rpn " + token); + if (token.getTokenType() == TokenType.NUMBER) { + stack.addFirst(TValue.fromNumber(token)); + } else if (token.getTokenType() == TokenType.QUOTED_STRING) { + stack.addFirst(TValue.fromString(token)); + } else if (token.getTokenType() == TokenType.JSON_DATA) { + stack.addFirst(TValue.fromJson(token.getJson())); + } else if (token.getTokenType() == TokenType.OPERATOR) { + final TValue v2 = stack.removeFirst(); + final TValue v1 = stack.removeFirst(); + final TokenOperator op = token.getTokenOperator(); + if (op == null) { + throw EaterException.unlocated("bad op"); + } + final TValue tmp = op.operate(v1, v2); + stack.addFirst(tmp); + } else if (token.getTokenType() == TokenType.OPEN_PAREN_FUNC) { + final int nb = Integer.parseInt(token.getSurface()); + final Token token2 = it.nextToken(); + if (token2.getTokenType() != TokenType.FUNCTION_NAME) { + throw EaterException.unlocated("rpn43"); + } + if (trace) + System.err.println("token2=" + token2); + final TFunction function = knowledge.getFunction(new TFunctionSignature(token2.getSurface(), nb)); + if (trace) + System.err.println("function=" + function); + if (function == null) { + throw EaterException.unlocated("Unknow built-in function " + token2.getSurface()); + } + if (function.canCover(nb) == false) { + throw EaterException + .unlocated("Bad number of arguments for " + function.getSignature().getFunctionName()); + } + final List<TValue> args = new ArrayList<TValue>(); + for (int i = 0; i < nb; i++) { + args.add(0, stack.removeFirst()); + } + if (trace) + System.err.println("args=" + args); + if (location == null) { + throw EaterException.unlocated("rpn44"); + } + final TValue r = function.executeReturn(context, memory, location, args); + if (trace) + System.err.println("r=" + r); + stack.addFirst(r); + } else { + throw EaterException.unlocated("rpn41"); + } + } + result = stack.removeFirst(); + } + + public final TValue getResult() { + return result; + } +} diff --git a/src/net/sourceforge/plantuml/tim/expression/ShuntingYard.java b/src/net/sourceforge/plantuml/tim/expression/ShuntingYard.java new file mode 100644 index 0000000..4a0265c --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/ShuntingYard.java @@ -0,0 +1,153 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import java.util.ArrayDeque; +import java.util.Deque; + +// https://en.wikipedia.org/wiki/Shunting-yard_algorithm +// https://en.cppreference.com/w/c/language/operator_precedence +public class ShuntingYard { + + final private TokenStack ouputQueue = new TokenStack(); + final private Deque<Token> operatorStack = new ArrayDeque<Token>(); + + private static final boolean TRACE = false; + + private void traceMe() { + if (TRACE == false) + return; + System.err.println("-------------------"); + System.err.println("operatorStack=" + operatorStack); + System.err.println("ouputQueue=" + ouputQueue); + System.err.println(""); + } + + public ShuntingYard(TokenIterator it, Knowledge knowledge) { + + while (it.hasMoreTokens()) { + final Token token = it.nextToken(); + traceMe(); + if (TRACE) + System.err.println("token=" + token); + if (token.getTokenType() == TokenType.NUMBER || token.getTokenType() == TokenType.QUOTED_STRING) { + ouputQueue.add(token); + } else if (token.getTokenType() == TokenType.FUNCTION_NAME) { + operatorStack.addFirst(token); + } else if (token.getTokenType() == TokenType.PLAIN_TEXT) { + final String name = token.getSurface(); + final TValue variable = knowledge.getVariable(name); + if (variable == null) { + ouputQueue.add(new Token("undefined", TokenType.QUOTED_STRING, null)); + } else { + ouputQueue.add(variable.toToken()); + } + } else if (token.getTokenType() == TokenType.OPERATOR) { + while ((thereIsAFunctionAtTheTopOfTheOperatorStack(token) // + || thereIsAnOperatorAtTheTopOfTheOperatorStackWithGreaterPrecedence(token) // + || theOperatorAtTheTopOfTheOperatorStackHasEqualPrecedenceAndIsLeftAssociative(token)) // + && theOperatorAtTheTopOfTheOperatorStackIsNotALeftParenthesis(token)) { + ouputQueue.add(operatorStack.removeFirst()); + } + // push it onto the operator stack. + operatorStack.addFirst(token); + } else if (token.getTokenType() == TokenType.OPEN_PAREN_FUNC) { + operatorStack.addFirst(token); + } else if (token.getTokenType() == TokenType.OPEN_PAREN_MATH) { + operatorStack.addFirst(token); + } else if (token.getTokenType() == TokenType.CLOSE_PAREN_FUNC) { + final Token first = operatorStack.removeFirst(); + ouputQueue.add(first); + } else if (token.getTokenType() == TokenType.CLOSE_PAREN_MATH) { + while (operatorStack.peekFirst().getTokenType() != TokenType.OPEN_PAREN_MATH) { + ouputQueue.add(operatorStack.removeFirst()); + } + if (operatorStack.peekFirst().getTokenType() == TokenType.OPEN_PAREN_MATH) { + operatorStack.removeFirst(); + } + } else if (token.getTokenType() == TokenType.COMMA) { + while (operatorStack.peekFirst() != null + && operatorStack.peekFirst().getTokenType() != TokenType.OPEN_PAREN_FUNC) { + ouputQueue.add(operatorStack.removeFirst()); + } + } else { + throw new UnsupportedOperationException(token.toString()); + } + } + + while (operatorStack.isEmpty() == false) { + final Token token = operatorStack.removeFirst(); + ouputQueue.add(token); + } + + // System.err.println("ouputQueue=" + ouputQueue); + } + + private boolean thereIsAFunctionAtTheTopOfTheOperatorStack(Token token) { + final Token top = operatorStack.peekFirst(); + return top != null && top.getTokenType() == TokenType.FUNCTION_NAME; + } + + private boolean thereIsAnOperatorAtTheTopOfTheOperatorStackWithGreaterPrecedence(Token token) { + final Token top = operatorStack.peekFirst(); + if (top != null && top.getTokenType() == TokenType.OPERATOR + && top.getTokenOperator().getPrecedence() > token.getTokenOperator().getPrecedence()) { + return true; + } + return false; + } + + private boolean theOperatorAtTheTopOfTheOperatorStackHasEqualPrecedenceAndIsLeftAssociative(Token token) { + final Token top = operatorStack.peekFirst(); + if (top != null && top.getTokenType() == TokenType.OPERATOR && top.getTokenOperator().isLeftAssociativity() + && top.getTokenOperator().getPrecedence() == token.getTokenOperator().getPrecedence()) { + return true; + } + return false; + } + + private boolean theOperatorAtTheTopOfTheOperatorStackIsNotALeftParenthesis(Token token) { + final Token top = operatorStack.peekFirst(); + if (top != null && top.getTokenType() == TokenType.OPEN_PAREN_MATH) { + return true; + } + return true; + } + + public TokenStack getQueue() { + return this.ouputQueue; + } + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/TValue.java b/src/net/sourceforge/plantuml/tim/expression/TValue.java new file mode 100644 index 0000000..921f8d6 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/TValue.java @@ -0,0 +1,218 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import net.sourceforge.plantuml.json.JsonValue; + +public class TValue { + + private final int intValue; + private final String stringValue; + private final JsonValue jsonValue; + + private TValue(int value) { + this.intValue = value; + this.stringValue = null; + this.jsonValue = null; + } + + private TValue(String stringValue) { + if (stringValue == null) { + throw new IllegalArgumentException(); + } + this.intValue = 0; + this.jsonValue = null; + this.stringValue = stringValue; + } + + public TValue(JsonValue json) { + this.jsonValue = json; + this.intValue = 0; + this.stringValue = null; + } + + public static TValue fromInt(int v) { + return new TValue(v); + } + + public static TValue fromBoolean(boolean b) { + return new TValue(b ? 1 : 0); + } + + public static TValue fromJson(JsonValue json) { + return new TValue(json); + } + + @Override + public String toString() { + if (jsonValue != null) { + return jsonValue.toString(); + } + if (stringValue == null) { + return "" + intValue; + } + return stringValue; + } + + public static TValue fromString(Token token) { + if (token.getTokenType() != TokenType.QUOTED_STRING) { + throw new IllegalArgumentException(); + } + return new TValue(token.getSurface()); + } + + public static TValue fromString(String s) { + return new TValue(s); + } + + public static TValue fromNumber(Token token) { + if (token.getTokenType() != TokenType.NUMBER) { + throw new IllegalArgumentException(); + } + return new TValue(Integer.parseInt(token.getSurface())); + } + + public TValue add(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return new TValue(this.intValue + v2.intValue); + } + return new TValue(toString() + v2.toString()); + } + + public TValue minus(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return new TValue(this.intValue - v2.intValue); + } + return new TValue(toString() + v2.toString()); + } + + public TValue multiply(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return new TValue(this.intValue * v2.intValue); + } + return new TValue(toString() + v2.toString()); + } + + public TValue dividedBy(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return new TValue(this.intValue / v2.intValue); + } + return new TValue(toString() + v2.toString()); + } + + public boolean isNumber() { + return this.jsonValue == null && this.stringValue == null; + } + + public boolean isJson() { + return this.jsonValue != null; + } + + public Token toToken() { + if (isNumber()) { + return new Token(toString(), TokenType.NUMBER, null); + } + if (isJson()) { + return new Token(toString(), TokenType.JSON_DATA, jsonValue); + } + return new Token(toString(), TokenType.QUOTED_STRING, null); + } + + public TValue greaterThanOrEquals(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue >= v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) >= 0); + } + + public TValue greaterThan(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue > v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) > 0); + } + + public TValue lessThanOrEquals(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue <= v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) <= 0); + } + + public TValue lessThan(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue < v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) < 0); + } + + public TValue equalsOperation(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue == v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) == 0); + } + + public TValue notEquals(TValue v2) { + if (this.isNumber() && v2.isNumber()) { + return fromBoolean(this.intValue != v2.intValue); + } + return fromBoolean(toString().compareTo(v2.toString()) != 0); + } + + public boolean toBoolean() { + if (this.isNumber()) { + return this.intValue != 0; + } + return toString().length() > 0; + } + + public int toInt() { + return this.intValue; + } + + public TValue logicalAnd(TValue v2) { + return fromBoolean(this.toBoolean() && v2.toBoolean()); + } + + public TValue logicalOr(TValue v2) { + return fromBoolean(this.toBoolean() || v2.toBoolean()); + } + + public JsonValue toJson() { + return jsonValue; + } + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/Token.java b/src/net/sourceforge/plantuml/tim/expression/Token.java new file mode 100644 index 0000000..245dd07 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/Token.java @@ -0,0 +1,90 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import net.sourceforge.plantuml.json.JsonValue; + +public class Token { + + private final String surface; + private final JsonValue json; + private final TokenType tokenType; + + @Override + public String toString() { + return tokenType + "{" + surface + "}"; + } + + public Token(char surface, TokenType tokenType, JsonValue json) { + this("" + surface, tokenType, json); + } + + public Token(String surface, TokenType tokenType, JsonValue json) { + this.surface = surface; + this.tokenType = tokenType; + this.json = json; + } + + public TokenOperator getTokenOperator() { + if (this.tokenType != TokenType.OPERATOR) { + throw new IllegalStateException(); + } + final char ch2 = surface.length() > 1 ? surface.charAt(1) : 0; + return TokenOperator.getTokenOperator(surface.charAt(0), ch2); + } + + public final String getSurface() { + return surface; + } + + public final TokenType getTokenType() { + return tokenType; + } + + public Token muteToFunction() { + if (this.tokenType != TokenType.PLAIN_TEXT) { + throw new IllegalStateException(); + } + return new Token(surface, TokenType.FUNCTION_NAME, null); + } + + public JsonValue getJson() { + if (this.tokenType != TokenType.JSON_DATA) { + throw new IllegalStateException(); + } + return json; + } + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/TokenIterator.java b/src/net/sourceforge/plantuml/tim/expression/TokenIterator.java new file mode 100644 index 0000000..5f3e8a2 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/TokenIterator.java @@ -0,0 +1,45 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +public interface TokenIterator { + + public Token nextToken(); + + public Token peekToken(); + + public boolean hasMoreTokens(); + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/TokenOperator.java b/src/net/sourceforge/plantuml/tim/expression/TokenOperator.java new file mode 100644 index 0000000..97964ab --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/TokenOperator.java @@ -0,0 +1,138 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + + +//https://en.cppreference.com/w/c/language/operator_precedence + +public enum TokenOperator { + MULTIPLICATION(100 - 3, "*") { + public TValue operate(TValue v1, TValue v2) { + return v1.multiply(v2); + } + }, + DIVISION(100 - 3, "/") { + public TValue operate(TValue v1, TValue v2) { + return v1.dividedBy(v2); + } + }, + ADDITION(100 - 4, "+") { + public TValue operate(TValue v1, TValue v2) { + return v1.add(v2); + } + }, + SUBSTRACTION(100 - 4, "-") { + public TValue operate(TValue v1, TValue v2) { + return v1.minus(v2); + } + }, + LESS_THAN(100 - 6, "<") { + public TValue operate(TValue v1, TValue v2) { + return v1.lessThan(v2); + } + }, + GREATER_THAN(100 - 6, ">") { + public TValue operate(TValue v1, TValue v2) { + return v1.greaterThan(v2); + } + }, + LESS_THAN_OR_EQUALS(100 - 6, "<=") { + public TValue operate(TValue v1, TValue v2) { + return v1.lessThanOrEquals(v2); + } + }, + GREATER_THAN_OR_EQUALS(100 - 6, ">=") { + public TValue operate(TValue v1, TValue v2) { + return v1.greaterThanOrEquals(v2); + } + }, + EQUALS(100 - 7, "==") { + public TValue operate(TValue v1, TValue v2) { + return v1.equalsOperation(v2); + } + }, + NOT_EQUALS(100 - 7, "!=") { + public TValue operate(TValue v1, TValue v2) { + return v1.notEquals(v2); + } + }, + LOGICAL_AND(100 - 11, "&&") { + public TValue operate(TValue v1, TValue v2) { + return v1.logicalAnd(v2); + } + }, + LOGICAL_OR(100 - 12, "||") { + public TValue operate(TValue v1, TValue v2) { + return v1.logicalOr(v2); + } + }, + ; + + private final int precedence; + private final String display; + + private TokenOperator(int precedence, String display) { + this.precedence = precedence; + this.display = display; + } + + public boolean isLeftAssociativity() { + return true; + } + + public static TokenOperator getTokenOperator(char ch, char ch2) { + for (TokenOperator op : TokenOperator.values()) { + if (op.display.length() == 2 && op.display.charAt(0) == ch && op.display.charAt(1) == ch2) { + return op; + } + } + for (TokenOperator op : TokenOperator.values()) { + if (op.display.length() == 1 && op.display.charAt(0) == ch) { + return op; + } + } + return null; + } + + public final int getPrecedence() { + return precedence; + } + + public abstract TValue operate(TValue v1, TValue v2); + + public final String getDisplay() { + return display; + } +} diff --git a/src/net/sourceforge/plantuml/tim/expression/TokenStack.java b/src/net/sourceforge/plantuml/tim/expression/TokenStack.java new file mode 100644 index 0000000..6547bf6 --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/TokenStack.java @@ -0,0 +1,223 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sourceforge.plantuml.LineLocation; +import net.sourceforge.plantuml.tim.Eater; +import net.sourceforge.plantuml.tim.EaterExceptionLocated; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TContext; +import net.sourceforge.plantuml.tim.TMemory; + +public class TokenStack { + + final private List<Token> tokens; + + public TokenStack() { + this(new ArrayList<Token>()); + } + + private TokenStack(List<Token> list) { + this.tokens = list; + } + + public int size() { + return tokens.size(); + } + + public TokenStack subTokenStack(int i) { + return new TokenStack(Collections.unmodifiableList(tokens.subList(i, tokens.size()))); + } + + @Override + public String toString() { + return tokens.toString(); + } + + public void add(Token token) { + this.tokens.add(token); + } + + public TokenStack withoutSpace() { + final TokenStack result = new TokenStack(); + for (Token token : tokens) { + if (token.getTokenType() != TokenType.SPACES) { + result.add(token); + } + } + return result; + } + + static public TokenStack eatUntilCloseParenthesisOrComma(Eater eater) throws EaterException { + final TokenStack result = new TokenStack(); + int level = 0; + while (true) { + eater.skipSpaces(); + final char ch = eater.peekChar(); + if (ch == 0) { + throw EaterException.unlocated("until001"); + } + if (level == 0 && (ch == ',' || ch == ')')) { + return result; + } + final Token token = TokenType.eatOneToken(eater, false); + final TokenType type = token.getTokenType(); + if (type == TokenType.OPEN_PAREN_MATH) { + level++; + } else if (type == TokenType.CLOSE_PAREN_MATH) { + level--; + } + result.add(token); + } + } + + static public void eatUntilCloseParenthesisOrComma(TokenIterator it) throws EaterException { + int level = 0; + while (true) { + final Token ch = it.peekToken(); + if (ch == null) { + throw EaterException.unlocated("until002"); + } + final TokenType typech = ch.getTokenType(); + if (level == 0 && (typech == TokenType.COMMA || typech == TokenType.CLOSE_PAREN_MATH) + || typech == TokenType.CLOSE_PAREN_FUNC) { + return; + } + final Token token = it.nextToken(); + final TokenType type = token.getTokenType(); + if (type == TokenType.OPEN_PAREN_MATH || type == TokenType.OPEN_PAREN_FUNC) { + level++; + } else if (type == TokenType.CLOSE_PAREN_MATH || type == TokenType.CLOSE_PAREN_FUNC) { + level--; + } + } + } + + private int countFunctionArg(TokenIterator it) throws EaterException { + // return 42; + final TokenType type1 = it.peekToken().getTokenType(); + if (type1 == TokenType.CLOSE_PAREN_MATH || type1 == TokenType.CLOSE_PAREN_FUNC) { + return 0; + } + int result = 1; + while (it.hasMoreTokens()) { + eatUntilCloseParenthesisOrComma(it); + final Token token = it.nextToken(); + final TokenType type = token.getTokenType(); + if (type == TokenType.CLOSE_PAREN_MATH || type == TokenType.CLOSE_PAREN_FUNC) { + return result; + } else if (type == TokenType.COMMA) { + result++; + } else { + throw EaterException.unlocated("count13"); + } + } + throw EaterException.unlocated("count12"); + } + + public void guessFunctions() throws EaterException { + final Deque<Integer> open = new ArrayDeque<Integer>(); + final Map<Integer, Integer> parens = new HashMap<Integer, Integer>(); + for (int i = 0; i < tokens.size(); i++) { + final Token token = tokens.get(i); + if (token.getTokenType().equals(TokenType.OPEN_PAREN_MATH)) { + open.addFirst(i); + } else if (token.getTokenType().equals(TokenType.CLOSE_PAREN_MATH)) { + parens.put(open.pollFirst(), i); + } + } + // System.err.println("before=" + toString()); + // System.err.println("guessFunctions2" + parens); + for (Map.Entry<Integer, Integer> ids : parens.entrySet()) { + final int iopen = ids.getKey(); + final int iclose = ids.getValue(); + assert tokens.get(iopen).getTokenType() == TokenType.OPEN_PAREN_MATH; + assert tokens.get(iclose).getTokenType() == TokenType.CLOSE_PAREN_MATH; + if (iopen > 0 && tokens.get(iopen - 1).getTokenType() == TokenType.PLAIN_TEXT) { + tokens.set(iopen - 1, new Token(tokens.get(iopen - 1).getSurface(), TokenType.FUNCTION_NAME, null)); + final int nbArg = countFunctionArg(subTokenStack(iopen + 1).tokenIterator()); + tokens.set(iopen, new Token("" + nbArg, TokenType.OPEN_PAREN_FUNC, null)); + tokens.set(iclose, new Token(")", TokenType.CLOSE_PAREN_FUNC, null)); + } + } + // System.err.println("after=" + toString()); + } + + class InternalIterator implements TokenIterator { + + private int pos = 0; + + public Token peekToken() { + return tokens.get(pos); + } + + public Token nextToken() { + if (hasMoreTokens() == false) { + return null; + } + return tokens.get(pos++); + } + + public boolean hasMoreTokens() { + return pos < tokens.size(); + } + + } + + public TokenIterator tokenIterator() { + return new InternalIterator(); + } + + public TValue getResult(LineLocation location, TContext context, TMemory memory) throws EaterException, EaterExceptionLocated { + final Knowledge knowledge = context.asKnowledge(memory); + final TokenStack tmp = withoutSpace(); + tmp.guessFunctions(); + final TokenIterator it = tmp.tokenIterator(); + final ShuntingYard shuntingYard = new ShuntingYard(it, knowledge); + final ReversePolishInterpretor rpn = new ReversePolishInterpretor(location, shuntingYard.getQueue(), knowledge, + memory, context); + return rpn.getResult(); + + } + +} diff --git a/src/net/sourceforge/plantuml/tim/expression/TokenType.java b/src/net/sourceforge/plantuml/tim/expression/TokenType.java new file mode 100644 index 0000000..f39951b --- /dev/null +++ b/src/net/sourceforge/plantuml/tim/expression/TokenType.java @@ -0,0 +1,117 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009-2020, Arnaud Roques + * + * Project Info: http://plantuml.com + * + * If you like this project or if you find it useful, you can support us at: + * + * http://plantuml.com/patreon (only 1$ per month!) + * http://plantuml.com/paypal + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.tim.expression; + +import net.sourceforge.plantuml.tim.Eater; +import net.sourceforge.plantuml.tim.EaterException; +import net.sourceforge.plantuml.tim.TLineType; + +public enum TokenType { + QUOTED_STRING, JSON_DATA, OPERATOR, OPEN_PAREN_MATH, COMMA, CLOSE_PAREN_MATH, NUMBER, PLAIN_TEXT, SPACES, FUNCTION_NAME, OPEN_PAREN_FUNC, CLOSE_PAREN_FUNC; + + private boolean isSingleChar1() { + return this == OPEN_PAREN_MATH || this == COMMA || this == CLOSE_PAREN_MATH; + } + + private static boolean isPlainTextBreak(char ch, char ch2) { + final TokenType tmp = fromChar(ch, ch2); + if (tmp.isSingleChar1() || tmp == TokenType.OPERATOR || tmp == SPACES) { + return true; + } + return false; + } + + private static TokenType fromChar(char ch, char ch2) { + TokenType result = PLAIN_TEXT; + if (TLineType.isQuote(ch)) { + result = QUOTED_STRING; + } else if (TokenOperator.getTokenOperator(ch, ch2) != null) { + result = OPERATOR; + } else if (ch == '(') { + result = OPEN_PAREN_MATH; + } else if (ch == ')') { + result = CLOSE_PAREN_MATH; + } else if (ch == ',') { + result = COMMA; + } else if (TLineType.isLatinDigit(ch)) { + result = NUMBER; + } else if (TLineType.isSpaceChar(ch)) { + result = SPACES; + } + return result; + } + + final static public Token eatOneToken(Eater eater, boolean manageColon) throws EaterException { + final char ch = eater.peekChar(); + if (ch == 0) { + return null; + } + if (manageColon && ch == ':') { + return null; + } + final TokenOperator tokenOperator = TokenOperator.getTokenOperator(ch, eater.peekCharN2()); + if (TLineType.isQuote(ch)) { + return new Token(eater.eatAndGetQuotedString(), TokenType.QUOTED_STRING, null); + } else if (tokenOperator != null) { + if (tokenOperator.getDisplay().length() == 1) { + return new Token(eater.eatOneChar(), TokenType.OPERATOR, null); + } + return new Token("" + eater.eatOneChar() + eater.eatOneChar(), TokenType.OPERATOR, null); + } else if (ch == '(') { + return new Token(eater.eatOneChar(), TokenType.OPEN_PAREN_MATH, null); + } else if (ch == ')') { + return new Token(eater.eatOneChar(), TokenType.CLOSE_PAREN_MATH, null); + } else if (ch == ',') { + return new Token(eater.eatOneChar(), TokenType.COMMA, null); + } else if (TLineType.isLatinDigit(ch)) { + return new Token(eater.eatAndGetNumber(), TokenType.NUMBER, null); + } else if (TLineType.isSpaceChar(ch)) { + return new Token(eater.eatAndGetSpaces(), TokenType.SPACES, null); + } + return new Token(eatAndGetTokenPlainText(eater), TokenType.PLAIN_TEXT, null); + } + + static private String eatAndGetTokenPlainText(Eater eater) throws EaterException { + final StringBuilder result = new StringBuilder(); + while (true) { + final char ch = eater.peekChar(); + if (ch == 0 || TokenType.isPlainTextBreak(ch, eater.peekCharN2())) { + return result.toString(); + } + result.append(eater.eatOneChar()); + } + } + +} |