/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 Heaton Research, Inc. * * 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. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package org.encog.parse.expression.common; import org.encog.ml.ea.exception.EACompileError; import org.encog.ml.prg.EncogProgram; import org.encog.ml.prg.ProgramNode; import org.encog.ml.prg.expvalue.ExpressionValue; import org.encog.ml.prg.extension.BasicTemplate; import org.encog.ml.prg.extension.NodeType; import org.encog.ml.prg.extension.ProgramExtensionTemplate; import org.encog.ml.prg.extension.StandardExtensions; import org.encog.util.SimpleParser; import org.encog.util.datastruct.StackObject; /** * This class is used to process a common format equation (in-fix) into the tree * format that Encog uses. To do this I make use of the shunting yard algorithm. * * One important note on definitions. I consider an operator to be simply a * special type of function. Really, an operator is just a shorthand for writing * certain types of functions. Therefore I do not distinguish between functions * and operators in this implementation. * * References: * * http://en.wikipedia.org/wiki/Shunting-yard_algorithm **/ public class ParseCommonExpression { private final EncogProgram holder; private SimpleParser parser; private ProgramNode rootNode; private boolean unary; private ProgramExtensionTemplate LEFT_PAREN = new BasicTemplate( ProgramExtensionTemplate.NO_PREC, "(", NodeType.None, false, 0) { @Override public ExpressionValue evaluate(ProgramNode actual) { return null; } }; private StackObject<ProgramExtensionTemplate> functionStack = new StackObject<ProgramExtensionTemplate>( 100); private StackObject<ProgramNode> outputStack = new StackObject<ProgramNode>( 100); public ParseCommonExpression(final EncogProgram theHolder) { this.holder = theHolder; } /** * Push a leaf onto the output stack. * * @param leaf * The leaf to push onto the output stack. **/ private void outputQueue(ProgramNode leaf) { outputStack.push(leaf); } private void outputQueue(ProgramExtensionTemplate opp) { if (opp == this.LEFT_PAREN) { throw new EACompileError("Unmatched parentheses"); } ProgramNode[] args = new ProgramNode[opp.getChildNodeCount()]; for (int i = args.length - 1; i >= 0; i--) { if (this.outputStack.isEmpty()) { throw new EACompileError("Not enough arguments"); } args[i] = this.outputStack.pop(); } this.rootNode = this.holder.getFunctions().factorProgramNode(opp, this.holder, args); outputStack.push(rootNode); } private void functionQueue(ProgramExtensionTemplate f) { // while there is an operator token, o2, at the top of the stack, and // either o1 is left-associative and o1 has precedence less than or // equal to that of o2, // or o1 has precedence less than that of o2, while (!this.functionStack.isEmpty() && this.functionStack.peek().getNodeType() != NodeType.None && ((f.getNodeType() == NodeType.OperatorLeft && f .getPrecedence() >= this.functionStack.peek() .getPrecedence()) || f.getPrecedence() > this.functionStack .peek().getPrecedence())) { outputQueue(this.functionStack.pop()); } functionStack.push(f); } private void handleNumeric() { double value, exponent; char sign = '+'; boolean isFloat = false; boolean neg = false; value = 0.0; exponent = 0; // should we just make this negative, due to an unary minus? if (!this.functionStack.isEmpty() && this.functionStack.peek() == StandardExtensions.EXTENSION_NEG) { this.functionStack.pop(); neg = true; } // whole number part while (Character.isDigit(this.parser.peek())) { value = (10.0 * value) + (this.parser.readChar() - '0'); } // Optional fractional if (this.parser.peek() == '.') { isFloat = true; this.parser.advance(); int i = 1; while (Character.isDigit(this.parser.peek())) { double f = (this.parser.readChar() - '0'); f /= Math.pow(10.0, i); value += f; i++; } } // Optional exponent if (Character.toUpperCase(this.parser.peek()) == 'E') { this.parser.advance(); if ((this.parser.peek() == '+') || (this.parser.peek() == '-')) { sign = this.parser.readChar(); } while (Character.isDigit(this.parser.peek())) { exponent = (int) (10.0 * exponent) + (this.parser.readChar() - '0'); } if (sign == '-') { isFloat = true; exponent = -exponent; } value = value * Math.pow(10, exponent); } if (neg) { value = -value; } ProgramNode v = this.holder.getFunctions().factorProgramNode("#const", holder, new ProgramNode[] {}); if (isFloat) { v.getData()[0] = new ExpressionValue(value); } else { v.getData()[0] = new ExpressionValue((int) value); } outputQueue(v); } private void handleAlpha(boolean neg) { final StringBuilder varName = new StringBuilder(); while (Character.isLetterOrDigit(this.parser.peek())) { varName.append(this.parser.readChar()); } this.parser.eatWhiteSpace(); if (varName.toString().equals("true")) { if (neg) { throw new EACompileError("Invalid negative sign."); } ProgramNode v = this.holder.getFunctions().factorProgramNode("#const", holder, new ProgramNode[] {}); v.getData()[0] = new ExpressionValue(true); outputQueue(v); } else if (varName.toString().equals("false")) { if (neg) { throw new EACompileError("Invalid negative sign."); } ProgramNode v = this.holder.getFunctions().factorProgramNode("#const", holder, new ProgramNode[] {}); v.getData()[0] = new ExpressionValue(false); outputQueue(v); } else if (this.parser.peek() != '(') { ProgramNode v; // either a variable or a const, see which if (this.holder.getFunctions().isDefined(varName.toString(), 0)) { v = this.holder.getFunctions().factorProgramNode( varName.toString(), holder, new ProgramNode[] {}); } else { this.holder.getVariables().setVariable(varName.toString(), new ExpressionValue(0)); v = this.holder.getFunctions().factorProgramNode("#var", holder, new ProgramNode[] {}); v.getData()[0] = new ExpressionValue((int) this.holder.getVariables() .getVariableIndex(varName.toString())); } if (neg) { v = this.holder.getFunctions().factorProgramNode("-", holder, new ProgramNode[] { v }); } outputQueue(v); } else { ProgramExtensionTemplate temp = this.holder.getFunctions() .findFunction(varName.toString()); if (temp == null) { throw new EACompileError("Undefined function: " + varName.toString()); } functionQueue(temp); } } private void handleSymbol() { char ch1 = this.parser.readChar(); // handle unary if (this.unary) { if (ch1 == '+') { return; } else if (ch1 == '-') { this.functionStack.push(StandardExtensions.EXTENSION_NEG); return; } } // handle regular operator char ch2 = 0; if (!this.parser.eol()) { ch2 = this.parser.peek(); } ProgramExtensionTemplate temp = this.holder.getFunctions() .findOperator(ch1, ch2); // did we find anything? if (temp != null) { // if we found a 2-char operator, then advance beyond the 2nd // char if (temp.getName().length() > 1) { this.parser.advance(); } functionQueue(temp); } else { throw new EACompileError("Unknown symbol: " + ch1); } } private void handleString() { final StringBuilder str = new StringBuilder(); char ch; if (this.parser.peek() == '\"') { this.parser.advance(); } do { ch = this.parser.readChar(); if (ch == 34) { // handle double quote if (this.parser.peek() == 34) { this.parser.advance(); str.append(ch); ch = this.parser.readChar(); } } else { str.append(ch); } } while ((ch != 34) && (ch > 0)); if (ch != 34) { throw (new EACompileError("Unterminated string")); } ProgramNode v = this.holder.getFunctions().factorProgramNode("#const", holder, new ProgramNode[] {}); v.getData()[0] = new ExpressionValue(str.toString()); outputQueue(v); } private void handleRightParen() { // move past the paren this.parser.advance(); // Until the token at the top of the stack is a left parenthesis, pop // operators off the stack onto the output queue. while (this.functionStack.peek() != this.LEFT_PAREN) { outputQueue(this.functionStack.pop()); } // Pop the left parenthesis from the stack, but not onto the output // queue. this.functionStack.pop(); // If the token at the top of the stack is a function token, pop it onto // the output queue. if (!this.functionStack.isEmpty() && this.functionStack.peek().getNodeType() == NodeType.Function) { outputQueue(this.functionStack.pop()); } // If the stack runs out without finding a left parenthesis, then there // are mismatched parentheses. } private void handleFunctionSeparator() { // advance past this.parser.advance(); // Until the token at the top of the stack is a left parenthesis, // pop operators off the stack onto the output queue. while (this.functionStack.peek() != this.LEFT_PAREN) { outputQueue(this.functionStack.pop()); } // If no left parentheses are encountered, either the separator was // misplaced or parentheses were mismatched. } public ProgramNode parse(final String expression) { this.parser = new SimpleParser(expression); this.unary = true; while (!parser.eol()) { parser.eatWhiteSpace(); char ch = parser.peek(); if (ch == '.' || Character.isDigit(ch)) { handleNumeric(); this.unary = false; } else if (Character.isLetter(ch)) { handleAlpha(false); this.unary = false; } else if (ch == '(') { this.parser.advance(); this.functionStack.push(LEFT_PAREN); this.unary = true; } else if (ch == ')') { handleRightParen(); this.unary = false; } else if ("<>^*/+-=&|".indexOf(ch) != -1) { handleSymbol(); this.unary = true; } else if (ch == '\"') { handleString(); this.unary = false; } else if (ch == ',') { handleFunctionSeparator(); this.unary = true; } else { throw new EACompileError("Unparsable character: " + ch); } } // pop off any functions still on the stack while (!this.functionStack.isEmpty()) { ProgramExtensionTemplate f = this.functionStack.pop(); outputQueue(f); } // were there no operators? if (this.rootNode == null) { this.rootNode = this.outputStack.pop(); } return this.rootNode; } }