Coverage Report - com.sdmetrics.math.ExpressionParser - www.sdmetrics.com
 
Classes in this File Line Coverage Branch Coverage Complexity
ExpressionParser
99%
274/276
99%
227/228
5,607
ExpressionParser$ParserException
100%
2/2
N/A
5,607
 
 1  
 /*
 2  
  * SDMetrics Open Core for UML design measurement
 3  
  * Copyright (c) Juergen Wuest
 4  
  * To contact the author, see <http://www.sdmetrics.com/Contact.html>.
 5  
  * 
 6  
  * This file is part of the SDMetrics Open Core.
 7  
  * 
 8  
  * SDMetrics Open Core is free software: you can redistribute it and/or modify
 9  
  * it under the terms of the GNU Affero General Public License as
 10  
  * published by the Free Software Foundation, either version 3 of the
 11  
  * License, or (at your option) any later version.
 12  
     
 13  
  * SDMetrics Open Core is distributed in the hope that it will be useful,
 14  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16  
  * GNU Affero General Public License for more details.
 17  
  *
 18  
  * You should have received a copy of the GNU Affero General Public License
 19  
  * along with SDMetrics Open Core.  If not, see <http://www.gnu.org/licenses/>.
 20  
  *
 21  
  */
 22  
 package com.sdmetrics.math;
 23  
 
 24  
 import java.util.ArrayList;
 25  
 import java.util.Collections;
 26  
 import java.util.List;
 27  
 import java.util.Set;
 28  
 
 29  
 /** Parses metric, set, and condition expressions. */
 30  
 public class ExpressionParser {
 31  
 
 32  
         /** Special token: opening parenthesis. */
 33  82
         private static final ExpressionNode OPENINGPARENTHESIS = new ExpressionNode(
 34  41
                         null, "(");
 35  
         /** Special token: closing parenthesis. */
 36  82
         private static final ExpressionNode CLOSINGPARENTHESIS = new ExpressionNode(
 37  41
                         null, ")");
 38  
         /** Special token: expression list separator. */
 39  82
         private static final ExpressionNode EXPRESSIONLISTSEPARATOR = new ExpressionNode(
 40  41
                         null, ",");
 41  
         /** Special token: end of expression. */
 42  41
         private static final ExpressionNode ENDNODE = new ExpressionNode(null, "");
 43  
 
 44  
         /** The expression to parse. */
 45  
         private String expression;
 46  
         /** Position in the expression string to process next. */
 47  
         private int currentPos;
 48  
         /** Node representation of the current parse position. */
 49  
         private ExpressionNode currentNode;
 50  
 
 51  
         /** Error message in case of syntax errors. */
 52  
         private String errorMessage;
 53  
 
 54  
         /** The set of names that the parser recognizes as functions. */
 55  
         private Set<String> functionNames;
 56  
         /**
 57  
          * The set of names that the parser recognizes as relations with high
 58  
          * precedence.
 59  
          */
 60  
         private Set<String> highPrecedenceRelationNames;
 61  
         /**
 62  
          * The set of names that the parser recognizes as relations with low
 63  
          * precedence.
 64  
          */
 65  
         private Set<String> lowPrecedenceRelationNames;
 66  
 
 67  
         /**
 68  
          * Creates a new parser instance without custom functions and relations.
 69  
          * 
 70  
          * @since 2.3
 71  
          */
 72  
         public ExpressionParser() {
 73  296
                 this(Collections.<String>emptySet(), Collections.<String>emptySet(),
 74  296
                                 Collections.<String>emptySet());
 75  296
         }
 76  
 
 77  
         /**
 78  
          * Creates a new parser instance with custom functions and relations. Note:
 79  
          * the feature to provide custom relations will probably be removed in
 80  
          * future versions of SDMetrics, and the existing special relation operators
 81  
          * (onlist, topmost etc) be replaced with functions.
 82  
          * 
 83  
          * @param functionNames The set of names the parser recognizes as function
 84  
          *        calls (e.g. ln, exp, ...)
 85  
          * @param highPrecedenceRelationNames The set of names the parser recognizes
 86  
          *        as high precedence relations. These relations have the same
 87  
          *        precedence as the comparison relations (
 88  
          *        <code>&gt;, &lt;, &gt;=, &lt;=</code>)
 89  
          * @param lowPrecedenceRelationNames The set of names the parser recognizes
 90  
          *        as low precedence relations. These relations all have the same
 91  
          *        precedence which is lower than the <code>and</code> relation
 92  
          *        (&amp;)
 93  
          * @since 2.3
 94  
          */
 95  597
         public ExpressionParser(Set<String> functionNames,
 96  
                         Set<String> highPrecedenceRelationNames,
 97  
                         Set<String> lowPrecedenceRelationNames) {
 98  597
                 this.functionNames = functionNames;
 99  597
                 this.highPrecedenceRelationNames = highPrecedenceRelationNames;
 100  597
                 this.lowPrecedenceRelationNames = lowPrecedenceRelationNames;
 101  597
         }
 102  
 
 103  
         /**
 104  
          * Parses an expression and returns its operator tree.
 105  
          * 
 106  
          * @param expr The expression to parse.
 107  
          * @return Root node of the operator tree for the expression, or
 108  
          *         <code>null</code> if the expression could not be parsed.
 109  
          */
 110  
         public ExpressionNode parseExpression(String expr) {
 111  21551
                 expression = expr;
 112  21551
                 currentPos = 0;
 113  21551
                 errorMessage = "";
 114  21551
                 ExpressionNode result = null;
 115  
                 try {
 116  21551
                         result = parseTopLevelExpression();
 117  21537
                         if (currentPos < expression.length() || currentNode != ENDNODE) {
 118  6
                                 throw new ParserException(
 119  6
                                                 "Unexpected token past end of expression: "
 120  6
                                                                 + currentNode.getValue(), currentPos);
 121  
                         }
 122  17
                 } catch (ParserException ex) {
 123  17
                         errorMessage = ex.getMessage();
 124  17
                         result = null;
 125  
                 }
 126  21551
                 return result;
 127  
         }
 128  
 
 129  
         /**
 130  
          * Retrieves a string describing the syntax error that occurred during
 131  
          * expression parsing.
 132  
          * 
 133  
          * @return Description of the parse error.
 134  
          */
 135  
         public String getErrorInfo() {
 136  374
                 return errorMessage;
 137  
         }
 138  
 
 139  
         /**
 140  
          * Exception thrown by the expression parser on syntax errors.
 141  
          */
 142  
         static class ParserException extends RuntimeException {
 143  
                 private static final long serialVersionUID = 4537409434255097180L;
 144  
 
 145  
                 /**
 146  
                  * Constructor.
 147  
                  * @param message Description of the syntax error.
 148  
                  * @param pos Index of the location of the error in the expression
 149  
                  *        string.
 150  
                  */
 151  
                 ParserException(String message, int pos) {
 152  17
                         super("Parse error at position " + pos + ": " + message);
 153  17
                 }
 154  
         }
 155  
 
 156  
         /**
 157  
          * Top level parser function to parse an arbitrary expression.
 158  
          * 
 159  
          * @return Root node of the operator tree for the expression.
 160  
          */
 161  
         private ExpressionNode parseTopLevelExpression() {
 162  23688
                 return lowPrecedenceRelationExpression();
 163  
         }
 164  
 
 165  
         /**
 166  
          * Parses a low precedence relation (operators upto, topmost).
 167  
          * 
 168  
          * @return Root node of the corresponding operator tree.
 169  
          */
 170  
         private ExpressionNode lowPrecedenceRelationExpression() {
 171  23688
                 ExpressionNode result = orExpression();
 172  47353
                 while (currentNode.isOperation()
 173  9
                                 && isLowPrecedenceRelation(currentNode.getValue())) {
 174  7
                         String operator = currentNode.getValue();
 175  7
                         result = handleRightHandSide(result, operator, orExpression());
 176  
                 }
 177  23673
                 return result;
 178  
         }
 179  
 
 180  
         /**
 181  
          * Tests if a string is the name of a function.
 182  
          * 
 183  
          * @param s String to test.
 184  
          * @return <code>true</code> if s is a function, else <code>false</code>.
 185  
          */
 186  
         private boolean isFunction(String s) {
 187  25318
                 return functionNames.contains(s);
 188  
         }
 189  
 
 190  
         /**
 191  
          * Tests if a string is the name of a high precedence relation.
 192  
          * 
 193  
          * @param s String to test.
 194  
          * @return <code>true</code> if s is a high precedence relation, else
 195  
          *         <code>false</code>.
 196  
          */
 197  
         private boolean isHighPrecedenceRelation(String s) {
 198  23868
                 return highPrecedenceRelationNames.contains(s);
 199  
         }
 200  
 
 201  
         /**
 202  
          * Tests if a string is the name of a low precedence relation.
 203  
          * 
 204  
          * @param s String to test.
 205  
          * @return <code>true</code> if s is a low precedence relation, else
 206  
          *         <code>false</code>.
 207  
          */
 208  
         private boolean isLowPrecedenceRelation(String s) {
 209  21885
                 return lowPrecedenceRelationNames.contains(s);
 210  
         }
 211  
 
 212  
         private boolean isCurrentNodeValue(String value) {
 213  37992
                 return value.equals(currentNode.getValue());
 214  
         }
 215  
 
 216  
         private ExpressionNode handleRightHandSide(ExpressionNode lhs,
 217  
                         String operator, ExpressionNode rhs) {
 218  4696
                 if (rhs == null) {
 219  0
                         throw new ParserException("Missing right hand side for operator "
 220  0
                                         + operator, currentPos);
 221  
                 }
 222  4696
                 return new ExpressionNode(operator, new ExpressionNode[] { lhs, rhs });
 223  
         }
 224  
 
 225  
         /**
 226  
          * Parses an "or" expression.
 227  
          * 
 228  
          * @return Root node of the corresponding operator tree.
 229  
          */
 230  
         private ExpressionNode orExpression() {
 231  23695
                 ExpressionNode result = andExpression();
 232  47504
                 while (currentNode.isOperation() && isCurrentNodeValue("|")) {
 233  144
                         result = handleRightHandSide(result, "|", andExpression());
 234  
                 }
 235  23680
                 return result;
 236  
         }
 237  
 
 238  
         /**
 239  
          * Parse an "and" expression.
 240  
          * 
 241  
          * @return Root node of the corresponding operator tree.
 242  
          */
 243  
         private ExpressionNode andExpression() {
 244  23839
                 ExpressionNode result = equalExpression();
 245  47673
                 while (currentNode.isOperation() && isCurrentNodeValue("&")) {
 246  25
                         result = handleRightHandSide(result, "&", equalExpression());
 247  
                 }
 248  23824
                 return result;
 249  
         }
 250  
 
 251  
         /**
 252  
          * Parses an "equal" expression (operators = and !=).
 253  
          * 
 254  
          * @return Root node of the corresponding operator tree.
 255  
          */
 256  
         private ExpressionNode equalExpression() {
 257  23864
                 ExpressionNode result = relExpression();
 258  23852
                 if (currentNode.isOperation()
 259  1422
                                 && (isCurrentNodeValue("=") || isCurrentNodeValue("!="))) {
 260  1259
                         String operator = currentNode.getValue();
 261  1259
                         result = handleRightHandSide(result, operator, relExpression());
 262  
                 }
 263  23849
                 return result;
 264  
         }
 265  
 
 266  
         /**
 267  
          * Parses a high precedence relational expression (comparators &gt;, &ge;
 268  
          * etc and relations "startswith", "endswith", "onlist").
 269  
          * 
 270  
          * @return Root node of the corresponding operator tree.
 271  
          */
 272  
         private ExpressionNode relExpression() {
 273  25123
                 ExpressionNode result = addExpression();
 274  25109
                 if (currentNode.isOperation()
 275  2938
                                 && (isCurrentNodeValue("<") || isCurrentNodeValue(">")
 276  1728
                                                 || isCurrentNodeValue("<=") || isCurrentNodeValue(">=") 
 277  1714
                                                 || isHighPrecedenceRelation(currentNode.getValue()))) {
 278  1502
                         String operator = currentNode.getValue();
 279  1502
                         result = handleRightHandSide(result, operator, addExpression());
 280  
                 }
 281  25108
                 return result;
 282  
         }
 283  
 
 284  
         /**
 285  
          * Parses an additive expression (binary operators + and -).
 286  
          * 
 287  
          * @return Root node of the corresponding operator tree.
 288  
          */
 289  
         private ExpressionNode addExpression() {
 290  26625
                 ExpressionNode result = multExpression();
 291  54098
                 while (currentNode.isOperation()
 292  3811
                                 && (isCurrentNodeValue("+") || isCurrentNodeValue("-"))) {
 293  872
                         String operator = currentNode.getValue();
 294  872
                         result = handleRightHandSide(result, operator, multExpression());
 295  
                 }
 296  26610
                 return result;
 297  
         }
 298  
 
 299  
         /**
 300  
          * Parses a multiplicative expression (operators *, /).
 301  
          * 
 302  
          * @return Root node of the corresponding operator tree.
 303  
          */
 304  
         private ExpressionNode multExpression() {
 305  27497
                 ExpressionNode result = powExpression();
 306  54989
                 while (currentNode.isOperation()
 307  3836
                                 && (isCurrentNodeValue("/") || isCurrentNodeValue("*"))) {
 308  25
                         String operator = currentNode.getValue();
 309  25
                         result = handleRightHandSide(result, operator, powExpression());
 310  
                 }
 311  27482
                 return result;
 312  
         }
 313  
 
 314  
         /**
 315  
          * Parses a power expression term (operators ^, -&gt;).
 316  
          * 
 317  
          * @return Root node of the corresponding operator tree.
 318  
          */
 319  
         private ExpressionNode powExpression() {
 320  27522
                 ExpressionNode result = dotExpression();
 321  55043
                 while (currentNode.isOperation()
 322  3865
                                 && (isCurrentNodeValue("^") || isCurrentNodeValue("->"))) {
 323  29
                         String operator = currentNode.getValue();
 324  29
                         result = handleRightHandSide(result, operator, dotExpression());
 325  
                 }
 326  27507
                 return result;
 327  
         }
 328  
 
 329  
         /**
 330  
          * Parses a dot expression "a.b"
 331  
          * 
 332  
          * @return Root node of the corresponding operator tree.
 333  
          */
 334  
         private ExpressionNode dotExpression() {
 335  27551
                 ExpressionNode result = unaryExpression();
 336  55915
                 while (currentNode.isOperation() && (isCurrentNodeValue("."))) {
 337  843
                         result = handleRightHandSide(result, ".", unaryExpression());
 338  
                 }
 339  27536
                 return result;
 340  
         }
 341  
 
 342  
         /**
 343  
          * Parses a unary expression (constants, prefixes +/-/!, function calls,
 344  
          * parenthesis).
 345  
          * 
 346  
          * @return Node representing the unary expression.
 347  
          */
 348  
         private ExpressionNode unaryExpression() {
 349  
 
 350  28779
                 nextToken();
 351  28775
                 String currentValue = currentNode.getValue();
 352  
 
 353  28775
                 if (currentNode.isNumberConstant() || currentNode.isStringConstant()
 354  22642
                                 || currentNode.isIdentifier()) {
 355  26723
                         ExpressionNode constant = currentNode;
 356  26723
                         nextToken();
 357  26723
                         return constant;
 358  2052
                 } else if (currentNode.isOperation() && (isFunction(currentValue))) {
 359  
 
 360  1387
                         final ExpressionNode fctCall = currentNode;
 361  1387
                         List<ExpressionNode> argList = new ArrayList<ExpressionNode>();
 362  1387
                         String operator = currentValue;
 363  1387
                         int startPos = currentPos;
 364  1387
                         nextToken();
 365  1387
                         if (currentNode != OPENINGPARENTHESIS) {
 366  2
                                 throw new ParserException(
 367  2
                                                 "Expected opening parenthesis following function call to '"
 368  2
                                                                 + operator + "'.", currentPos);
 369  
                         }
 370  
                         do {
 371  1863
                                 argList.add(parseTopLevelExpression());
 372  1863
                         } while (currentNode == EXPRESSIONLISTSEPARATOR);
 373  1386
                         if (currentNode != CLOSINGPARENTHESIS) {
 374  2
                                 throw new ParserException(
 375  2
                                                 "Missing closing parenthesis following function call to '"
 376  1
                                                                 + operator + "' at position " + startPos + ".",
 377  1
                                                 currentPos);
 378  
                         }
 379  2770
                         fctCall.setValue(NodeType.OPERATION, operator,
 380  1385
                                         argList.toArray(new ExpressionNode[argList.size()]));
 381  
 
 382  1385
                         nextToken();
 383  1385
                         return fctCall;
 384  
 
 385  665
                 } else if (currentNode.isOperation() && "-".equals(currentValue)
 386  540
                                 || "+".equals(currentValue) || "!".equals(currentValue)) {
 387  385
                         ExpressionNode unary = currentNode;
 388  770
                         unary.setValue(NodeType.OPERATION, currentValue,
 389  385
                                         new ExpressionNode[] { unaryExpression() });
 390  385
                         return unary;
 391  280
                 } else if (currentNode == OPENINGPARENTHESIS) {
 392  274
                         int startIndex = currentPos;
 393  274
                         ExpressionNode temp = parseTopLevelExpression();
 394  
 
 395  273
                         if (currentNode == CLOSINGPARENTHESIS) {
 396  271
                                 nextToken();
 397  271
                                 return temp;
 398  
                         }
 399  4
                         throw new ParserException(
 400  4
                                         "Missing closing parenthesis opened at position "
 401  4
                                                         + startIndex + ".", currentPos);
 402  6
                 } else if (currentNode == ENDNODE) {
 403  4
                         throw new ParserException("Unexpected end of expression.",
 404  2
                                         currentPos);
 405  
                 } else {
 406  8
                         throw new ParserException("Unexpected token: " + currentValue,
 407  4
                                         currentPos);
 408  
                 }
 409  
         }
 410  
 
 411  
         /**
 412  
          * Consumes a token, leave information about it in {@link #currentType} and
 413  
          * {@link #currentValue} variables.
 414  
          */
 415  
         private void nextToken() {
 416  120251
                 while (currentPos < expression.length()
 417  40167
                                 && Character.isSpaceChar(expression.charAt(currentPos))) {
 418  3161
                         currentPos++;
 419  
                 }
 420  
 
 421  58545
                 if (currentPos >= expression.length()) {
 422  21539
                         currentNode = ENDNODE;
 423  21539
                 } else if (expression.charAt(currentPos) == '+'
 424  36145
                                 || expression.charAt(currentPos) == '-'
 425  35992
                                 || expression.charAt(currentPos) == '*'
 426  35973
                                 || expression.charAt(currentPos) == '^'
 427  35954
                                 || expression.charAt(currentPos) == '/'
 428  35948
                                 || expression.charAt(currentPos) == '='
 429  35283
                                 || expression.charAt(currentPos) == '|'
 430  35257
                                 || expression.charAt(currentPos) == '!'
 431  34410
                                 || expression.charAt(currentPos) == '<'
 432  34391
                                 || expression.charAt(currentPos) == '>'
 433  34341
                                 || expression.charAt(currentPos) == '.'
 434  33498
                                 || expression.charAt(currentPos) == '&') {
 435  3533
                         handleSymbolicOperatorToken();
 436  3533
                 } else if (expression.charAt(currentPos) == '(') {
 437  1660
                         currentPos++;
 438  1660
                         currentNode = OPENINGPARENTHESIS;
 439  1660
                 } else if (expression.charAt(currentPos) == ')') {
 440  1658
                         currentPos++;
 441  1658
                         currentNode = CLOSINGPARENTHESIS;
 442  1658
                 } else if (expression.charAt(currentPos) == ',') {
 443  477
                         currentPos++;
 444  477
                         currentNode = EXPRESSIONLISTSEPARATOR;
 445  477
                 } else if (expression.charAt(currentPos) == '\'') {
 446  7147
                         currentNode = new ExpressionNode(NodeType.STRING,
 447  3574
                                         getStringConstant());
 448  3573
                 } else if (Character.isDigit(expression.charAt(currentPos))) {
 449  5122
                         currentNode = new ExpressionNode(NodeType.NUMBER,
 450  2562
                                         getNumberConstant());
 451  2560
                 } else {
 452  23542
                         handleIdentifier();
 453  
                 }
 454  58541
         }
 455  
 
 456  
         private void handleSymbolicOperatorToken() {
 457  3533
                 String currentValue = String.valueOf(expression.charAt(currentPos));
 458  3533
                 currentPos++;
 459  3533
                 if ("&".equals(currentValue)) {
 460  
                         // In case some XML do not translate &amp; and &lt;in quoted
 461  
                         // text, we accept &amp; as "and" operator and &lt; as "less
 462  
                         // than" operator
 463  25
                         if (currentPos + 3 < expression.length()) {
 464  28
                                 if ("amp;".equals(expression.substring(currentPos,
 465  28
                                                 currentPos + 4))) {
 466  1
                                         currentPos = currentPos + 4; // skip "amp;" part
 467  1
                                 } else if (expression.substring(currentPos, currentPos + 3)
 468  13
                                                 .equals("lt;")) {
 469  1
                                         currentPos = currentPos + 3;
 470  1
                                         currentValue = "<"; // skip "lt;" part
 471  
                                 }
 472  
                         }
 473  
                 }
 474  
                 // check >=, <= !=
 475  3533
                 if (currentPos < expression.length()
 476  3531
                                 && expression.charAt(currentPos) == '=') {
 477  609
                         if ("<".equals(currentValue) || ">".equals(currentValue)
 478  596
                                         || "!".equals(currentValue)) {
 479  608
                                 currentValue += expression.charAt(currentPos);
 480  608
                                 currentPos++;
 481  
                         }
 482  608
                 } else if (currentPos < expression.length()
 483  2922
                                 && expression.charAt(currentPos) == '>') {
 484  
                         // check ->
 485  10
                         if ("-".equals(currentValue)) {
 486  9
                                 currentValue += expression.charAt(currentPos);
 487  9
                                 currentPos++;
 488  
                         }
 489  
                 }
 490  3533
                 currentNode = new ExpressionNode(NodeType.OPERATION, currentValue);
 491  3533
         }
 492  
 
 493  
         private void handleIdentifier() {
 494  23542
                 String currentValue = getIdentifier();
 495  23541
                 NodeType currentType = NodeType.OPERATION;
 496  23541
                 if (currentValue.startsWith("fct_")) {
 497  
                         // In earlier versions, all functions had a prefix (e.g. fct_sqrt()
 498  
                         // instead of sqrt()). We support this prefix for backwards
 499  
                         // compatibility.
 500  3
                         String trail = currentValue.substring(4);
 501  3
                         if (isFunction(trail)) {
 502  2
                                 currentValue = trail;
 503  
                         }
 504  
                 }
 505  23541
                 if (isFunction(currentValue) || isHighPrecedenceRelation(currentValue)
 506  21876
                                 || isLowPrecedenceRelation(currentValue)) {
 507  1672
                         currentType = NodeType.OPERATION;
 508  1672
                 } else if ("and".equals(currentValue)) {
 509  1
                         currentValue = "&";
 510  1
                 } else if ("or".equals(currentValue)) {
 511  118
                         currentValue = "|";
 512  118
                 } else if ("not".equals(currentValue)) {
 513  1
                         currentValue = "!";
 514  1
                 } else if ("lt".equals(currentValue)) {
 515  685
                         currentValue = "<";
 516  685
                 } else if ("gt".equals(currentValue)) {
 517  469
                         currentValue = ">";
 518  469
                 } else if ("le".equals(currentValue)) {
 519  1
                         currentValue = "<=";
 520  1
                 } else if ("ge".equals(currentValue)) {
 521  1
                         currentValue = ">=";
 522  1
                 } else if ("in".equals(currentValue)) {
 523  1
                         currentValue = "->";
 524  1
                 } else {
 525  20592
                         currentType = NodeType.IDENTIFIER;
 526  
                 }
 527  
 
 528  23541
                 currentNode = new ExpressionNode(currentType, currentValue);
 529  23541
         }
 530  
 
 531  
         /**
 532  
          * Parses a number constant in the expression string.
 533  
          * 
 534  
          * @return The number constant, as a string.
 535  
          */
 536  
         private String getNumberConstant() {
 537  2562
                 final int startIndex = currentPos;
 538  
 
 539  8351
                 while (currentPos < expression.length()
 540  3556
                                 && Character.isDigit(expression.charAt(currentPos))) {
 541  3227
                         currentPos++;
 542  
                 }
 543  2562
                 if (currentPos < expression.length()
 544  329
                                 && expression.charAt(currentPos) == '.') {
 545  23
                         currentPos++;
 546  23
                         int fractionStart = currentPos;
 547  95
                         while (currentPos < expression.length()
 548  64
                                         && Character.isDigit(expression.charAt(currentPos))) {
 549  49
                                 currentPos++;
 550  
                         }
 551  23
                         if (fractionStart == currentPos) {
 552  2
                                 throw new ParserException(
 553  1
                                                 "Missing fraction part of number constant.", currentPos);
 554  
                         }
 555  
                 }
 556  2561
                 if (currentPos < expression.length()
 557  320
                                 && (expression.charAt(currentPos) == 'e' || expression
 558  317
                                                 .charAt(currentPos) == 'E')) {
 559  6
                         currentPos++;
 560  6
                         if (currentPos < expression.length()
 561  5
                                         && (expression.charAt(currentPos) == '+' || expression
 562  4
                                                         .charAt(currentPos) == '-')) {
 563  4
                                 currentPos++;
 564  
                         }
 565  6
                         int exponentStart = currentPos;
 566  17
                         while (currentPos < expression.length()
 567  6
                                         && Character.isDigit(expression.charAt(currentPos))) {
 568  5
                                 currentPos++;
 569  
                         }
 570  6
                         if (exponentStart == currentPos) {
 571  2
                                 throw new ParserException(
 572  1
                                                 "Missing exponent of scientific notation number constant.",
 573  1
                                                 currentPos);
 574  
                         }
 575  
                 }
 576  
 
 577  2560
                 return expression.substring(startIndex, currentPos);
 578  
         }
 579  
 
 580  
         /**
 581  
          * Parses an identifier in the expression string.
 582  
          * 
 583  
          * @return the identifier.
 584  
          */
 585  
         private String getIdentifier() {
 586  23542
                 int startIndex = currentPos;
 587  217721
                 while (currentPos < expression.length()
 588  178962
                                 && isIdentifierCharacter(expression.charAt(currentPos))) {
 589  170637
                         currentPos++;
 590  
                 }
 591  23542
                 if (startIndex == currentPos) {
 592  2
                         throw new ParserException("Unexpected character: "
 593  1
                                         + expression.charAt(currentPos), startIndex + 1);
 594  
                 }
 595  23541
                 return expression.substring(startIndex, currentPos);
 596  
         }
 597  
 
 598  
         /**
 599  
          * Tests if a character is a valid identifier character.
 600  
          * 
 601  
          * @param c Character to test.
 602  
          * @return <code>true</code> if the character is a letter, digit, or
 603  
          *         underscore.
 604  
          */
 605  
         public static boolean isIdentifierCharacter(char c) {
 606  179000
                 return Character.isLetter(c) || Character.isDigit(c) || c == '_';
 607  
         }
 608  
 
 609  
         /**
 610  
          * Parses a string constant in the expression string. String constants are
 611  
          * enclosed by apostrophes.
 612  
          * 
 613  
          * @return The string constant without the enclosing apostrophes.
 614  
          */
 615  
         private String getStringConstant() {
 616  
 
 617  3574
                 currentPos++; // skip leading apostrophe
 618  3574
                 int startIndex = currentPos;
 619  40031
                 while (currentPos < expression.length()
 620  36456
                                 && expression.charAt(currentPos) != '\'') {
 621  32883
                         currentPos++;
 622  
                 }
 623  3574
                 if (currentPos == expression.length()) {
 624  2
                         throw new ParserException(
 625  2
                                         "Missing closing apostrophe of string constant starting at position "
 626  2
                                                         + startIndex + ".", currentPos);
 627  
                 }
 628  3573
                 currentPos++; // skip the closing apostrophe
 629  
 
 630  3573
                 return expression.substring(startIndex, currentPos - 1);
 631  
         }
 632  
 }