]> source.dussan.org Git - poi.git/commitdiff
whitespace (tabs to spaces)
authorJaven O'Neal <onealj@apache.org>
Fri, 10 Jun 2016 01:57:13 +0000 (01:57 +0000)
committerJaven O'Neal <onealj@apache.org>
Fri, 10 Jun 2016 01:57:13 +0000 (01:57 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xssf_structured_references@1747622 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java
src/java/org/apache/poi/ss/formula/FormulaParser.java

index e5f2afd2503fe21aa3778df3600635d8fa233c4d..563cfd54285da6d80becea20dc2c88ae01eb7c92 100644 (file)
@@ -31,53 +31,53 @@ import org.apache.poi.ss.formula.FormulaType;
  */
 public final class HSSFFormulaParser {
 
-       private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {
-               return HSSFEvaluationWorkbook.create(book);
-       }
+    private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {
+        return HSSFEvaluationWorkbook.create(book);
+    }
 
-       private HSSFFormulaParser() {
-               // no instances of this class
-       }
+    private HSSFFormulaParser() {
+        // no instances of this class
+    }
 
-       /**
-        * Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)}
-        */
-       public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException {
-               return parse(formula, workbook, FormulaType.CELL);
-       }
+    /**
+     * Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)}
+     */
+    public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException {
+        return parse(formula, workbook, FormulaType.CELL);
+    }
 
-       /**
-        * @param formulaType a constant from {@link FormulaType}
-        * @return the parsed formula tokens
+    /**
+     * @param formulaType a constant from {@link FormulaType}
+     * @return the parsed formula tokens
      * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
-        */
-       public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException {
-               return parse(formula, workbook, formulaType, -1);
-       }
+     */
+    public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException {
+        return parse(formula, workbook, formulaType, -1);
+    }
 
-       /**
-        * @param formula     the formula to parse
-        * @param workbook    the parent workbook
-        * @param formulaType a constant from {@link FormulaType}
-        * @param sheetIndex  the 0-based index of the sheet this formula belongs to.
-        * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
-        * the scope of the name will be ignored and  the parser will match named ranges only by name
-        *
-        * @return the parsed formula tokens
+    /**
+     * @param formula     the formula to parse
+     * @param workbook    the parent workbook
+     * @param formulaType a constant from {@link FormulaType}
+     * @param sheetIndex  the 0-based index of the sheet this formula belongs to.
+     * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
+     * the scope of the name will be ignored and  the parser will match named ranges only by name
+     *
+     * @return the parsed formula tokens
      * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
-        */
-       public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
-               return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex, -1);
-       }
+     */
+    public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
+        return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex, -1);
+    }
 
-       /**
-        * Static method to convert an array of {@link Ptg}s in RPN order
-        * to a human readable string format in infix mode.
-        * @param book  used for defined names and 3D references
-        * @param ptgs  must not be <code>null</code>
-        * @return a human readable String
-        */
-       public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
-               return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
-       }
+    /**
+     * Static method to convert an array of {@link Ptg}s in RPN order
+     * to a human readable string format in infix mode.
+     * @param book  used for defined names and 3D references
+     * @param ptgs  must not be <code>null</code>
+     * @return a human readable String
+     */
+    public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
+        return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
+    }
 }
index b2e7acf7301b14d16ad53f1525cdf69d26ea3979..357e875d8da7299c6952d7090fb6a78cd9c44bac 100644 (file)
@@ -6,7 +6,7 @@
    (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
+       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,
@@ -80,7 +80,7 @@ import org.apache.poi.util.POILogger;
 /**
  * This class parses a formula string into a List of tokens in RPN order.
  * Inspired by
- *                Lets Build a Compiler, by Jack Crenshaw
+ *           Lets Build a Compiler, by Jack Crenshaw
  * BNF for the formula expression is :
  * <expression> ::= <term> [<addop> <term>]*
  * <term> ::= <factor>  [ <mulop> <factor> ]*
@@ -91,81 +91,81 @@ import org.apache.poi.util.POILogger;
  * <p/>
  */
 public final class FormulaParser {
-       private final static POILogger log = POILogFactory.getLogger(FormulaParser.class);
-       private final String _formulaString;
-       private final int _formulaLength;
-       /** points at the next character to be read (after the {@link #look} char) */
-       private int _pointer;
+    private final static POILogger log = POILogFactory.getLogger(FormulaParser.class);
+    private final String _formulaString;
+    private final int _formulaLength;
+    /** points at the next character to be read (after the {@link #look} char) */
+    private int _pointer;
 
-       private ParseNode _rootNode;
+    private ParseNode _rootNode;
 
-       private final static char TAB = '\t'; // HSSF + XSSF
-       private final static char CR = '\r';  // Normally just XSSF
-       private final static char LF = '\n';  // Normally just XSSF
+    private final static char TAB = '\t'; // HSSF + XSSF
+    private final static char CR = '\r';  // Normally just XSSF
+    private final static char LF = '\n';  // Normally just XSSF
 
-       /**
-        * Lookahead Character.
-        * gets value '\0' when the input string is exhausted
-        */
-       private char look;
+    /**
+     * Lookahead Character.
+     * gets value '\0' when the input string is exhausted
+     */
+    private char look;
 
     /**
      * Tracks whether the run of whitespace preceeding "look" could be an
      * intersection operator.  See GetChar.
      */
-       private boolean _inIntersection = false;
-
-       private final FormulaParsingWorkbook _book;
-       private final SpreadsheetVersion _ssVersion;
-
-       private final int _sheetIndex;
-       private final int _rowIndex; // 0-based
-
-
-       /**
-        * Create the formula parser, with the string that is to be
-        *  parsed against the supplied workbook.
-        * A later call the parse() method to return ptg list in
-        *  rpn order, then call the getRPNPtg() to retrieve the
-        *  parse results.
-        * This class is recommended only for single threaded use.
-        *
-        * If you only have a usermodel.HSSFWorkbook, and not a
-        *  model.Workbook, then use the convenience method on
-        *  usermodel.HSSFFormulaEvaluator
-        */
-       private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){
-               _formulaString = formula;
-               _pointer=0;
-               _book = book;
-               _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
-               _formulaLength = _formulaString.length();
-               _sheetIndex = sheetIndex;
-               _rowIndex = rowIndex;
-       }
-
-       /**
-        * Parse a formula into a array of tokens
-        * Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs)
-        *
-        * @param formula        the formula to parse
-        * @param workbook      the parent workbook
-        * @param formulaType the type of the formula, see {@link FormulaType}
-        * @param sheetIndex  the 0-based index of the sheet this formula belongs to.
-        * @param rowIndex  - the related cell's row index in 0-based form (-1 if the formula is not cell related)
-        *                     used to handle structured references that have the "#This Row" quantifier.
-        * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
-        * the scope of the name will be ignored and  the parser will match names only by name
-        *
-        * @return array of parsed tokens
-        * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
-        */
-       public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex, int rowIndex) {
-               FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
-               fp.parse();
-               return fp.getRPNPtg(formulaType);
-       }
-       
+    private boolean _inIntersection = false;
+
+    private final FormulaParsingWorkbook _book;
+    private final SpreadsheetVersion _ssVersion;
+
+    private final int _sheetIndex;
+    private final int _rowIndex; // 0-based
+
+
+    /**
+     * Create the formula parser, with the string that is to be
+     *  parsed against the supplied workbook.
+     * A later call the parse() method to return ptg list in
+     *  rpn order, then call the getRPNPtg() to retrieve the
+     *  parse results.
+     * This class is recommended only for single threaded use.
+     *
+     * If you only have a usermodel.HSSFWorkbook, and not a
+     *  model.Workbook, then use the convenience method on
+     *  usermodel.HSSFFormulaEvaluator
+     */
+    private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){
+        _formulaString = formula;
+        _pointer=0;
+        _book = book;
+        _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
+        _formulaLength = _formulaString.length();
+        _sheetIndex = sheetIndex;
+        _rowIndex = rowIndex;
+    }
+
+    /**
+     * Parse a formula into a array of tokens
+     * Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs)
+     *
+     * @param formula     the formula to parse
+     * @param workbook    the parent workbook
+     * @param formulaType the type of the formula, see {@link FormulaType}
+     * @param sheetIndex  the 0-based index of the sheet this formula belongs to.
+     * @param rowIndex  - the related cell's row index in 0-based form (-1 if the formula is not cell related)
+     *                     used to handle structured references that have the "#This Row" quantifier.
+     * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
+     * the scope of the name will be ignored and  the parser will match names only by name
+     *
+     * @return array of parsed tokens
+     * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
+     */
+    public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex, int rowIndex) {
+        FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
+        fp.parse();
+        return fp.getRPNPtg(formulaType);
+    }
+    
     public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
         return parse(formula, workbook, formulaType, sheetIndex, -1);
     }
@@ -187,268 +187,268 @@ public final class FormulaParser {
         return (Area3DPxg) arr[0];
     }
     
-       /** Read New Character From Input Stream */
-       private void GetChar() {
-               // The intersection operator is a space.  We track whether the run of 
-               // whitespace preceeding "look" counts as an intersection operator.  
-               if (IsWhite(look)) {
-                       if (look == ' ') {
-                               _inIntersection = true;
-                       }
-               }
-               else {
-                       _inIntersection = false;
-               }
-               
-               // Check to see if we've walked off the end of the string.
-               if (_pointer > _formulaLength) {
-                       throw new RuntimeException("too far");
-               }
-               if (_pointer < _formulaLength) {
-                       look=_formulaString.charAt(_pointer);
-               } else {
-                       // Just return if so and reset 'look' to something to keep
-                       // SkipWhitespace from spinning
-                       look = (char)0;
-                       _inIntersection = false;
-               }
-               _pointer++;
-               //System.out.println("Got char: "+ look);
-       }
-       private void resetPointer(int ptr) {
-               _pointer = ptr;
-               if (_pointer <= _formulaLength) {
-                       look=_formulaString.charAt(_pointer-1);
-               } else {
-                       // Just return if so and reset 'look' to something to keep
-                       // SkipWhitespace from spinning
-                       look = (char)0;
-               }
-       }
-
-       /** Report What Was Expected */
-       private RuntimeException expected(String s) {
-               String msg;
-
-               if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) {
-                       msg = "The specified formula '" + _formulaString
-                               + "' starts with an equals sign which is not allowed.";
-               } else {
-                       msg = "Parse error near char " + (_pointer-1) + " '" + look + "'"
-                               + " in specified formula '" + _formulaString + "'. Expected "
-                               + s;
-               }
-               return new FormulaParseException(msg);
-       }
-
-       /** Recognize an Alpha Character */
-       private static boolean IsAlpha(char c) {
-               return Character.isLetter(c) || c == '$' || c=='_';
-       }
-
-       /** Recognize a Decimal Digit */
-       private static boolean IsDigit(char c) {
-               return Character.isDigit(c);
-       }
-
-       /** Recognize White Space */
-       private static boolean IsWhite( char c) {
-               return  c ==' ' || c== TAB || c == CR || c == LF;
-       }
-
-       /** Skip Over Leading White Space */
-       private void SkipWhite() {
-               while (IsWhite(look)) {
-                       GetChar();
-               }
-       }
-
-       /**
-        *  Consumes the next input character if it is equal to the one specified otherwise throws an
-        *  unchecked exception. This method does <b>not</b> consume whitespace (before or after the
-        *  matched character).
-        */
-       private void Match(char x) {
-               if (look != x) {
-                       throw expected("'" + x + "'");
-               }
-               GetChar();
-       }
-
-       /** Get a Number */
-       private String GetNum() {
-               StringBuffer value = new StringBuffer();
-
-               while (IsDigit(this.look)){
-                       value.append(this.look);
-                       GetChar();
-               }
-               return value.length() == 0 ? null : value.toString();
-       }
-
-       private ParseNode parseRangeExpression() {
-               ParseNode result = parseRangeable();
-               boolean hasRange = false;
-               while (look == ':') {
-                       int pos = _pointer;
-                       GetChar();
-                       ParseNode nextPart = parseRangeable();
-                       // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
-                       // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
-                       // Furthermore, Excel doesn't seem to simplify
-                       // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"
-
-                       checkValidRangeOperand("LHS", pos, result);
-                       checkValidRangeOperand("RHS", pos, nextPart);
-
-                       ParseNode[] children = { result, nextPart, };
-                       result = new ParseNode(RangePtg.instance, children);
-                       hasRange = true;
-               }
-               if (hasRange) {
-                       return augmentWithMemPtg(result);
-               }
-               return result;
-       }
-
-       private static ParseNode augmentWithMemPtg(ParseNode root) {
-               Ptg memPtg;
-               if (needsMemFunc(root)) {
-                       memPtg = new MemFuncPtg(root.getEncodedSize());
-               } else {
-                       memPtg = new MemAreaPtg(root.getEncodedSize());
-               }
-               return new ParseNode(memPtg, root);
-       }
-       /**
-        * From OOO doc: "Whenever one operand of the reference subexpression is a function,
-        *  a defined name, a 3D reference, or an external reference (and no error occurs),
-        *  a tMemFunc token is used"
-        *
-        */
-       private static boolean needsMemFunc(ParseNode root) {
-               Ptg token = root.getToken();
-               if (token instanceof AbstractFunctionPtg) {
-                       return true;
-               }
-               if (token instanceof ExternSheetReferenceToken) { // 3D refs
-                       return true;
-               }
-               if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
-                       return true;
-               }
-
-               if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
-                       // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
-                       for(ParseNode child : root.getChildren()) {
-                               if (needsMemFunc(child)) {
-                                       return true;
-                               }
-                       }
-                       return false;
-               }
-               if (token instanceof OperandPtg) {
-                       return false;
-               }
-               if (token instanceof OperationPtg) {
-                       return true;
-               }
-
-               return false;
-       }
-
-       /**
-        * @param currentParsePosition used to format a potential error message
-        */
-       private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) {
-               if (!isValidRangeOperand(pn)) {
-                       throw new FormulaParseException("The " + sideName
-                                       + " of the range operator ':' at position "
-                                       + currentParsePosition + " is not a proper reference.");
-               }
-       }
-
-       /**
-        * @return <code>false</code> if sub-expression represented the specified ParseNode definitely
-        * cannot appear on either side of the range (':') operator
-        */
-       private static boolean isValidRangeOperand(ParseNode a) {
-               Ptg tkn = a.getToken();
-               // Note - order is important for these instance-of checks
-               if (tkn instanceof OperandPtg) {
-                       // notably cell refs and area refs
-                       return true;
-               }
-
-               // next 2 are special cases of OperationPtg
-               if (tkn instanceof AbstractFunctionPtg) {
-                       AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
-                       byte returnClass = afp.getDefaultOperandClass();
-                       return Ptg.CLASS_REF == returnClass;
-               }
-               if (tkn instanceof ValueOperatorPtg) {
-                       return false;
-               }
-               if (tkn instanceof OperationPtg) {
-                       return true;
-               }
-
-               // one special case of ControlPtg
-               if (tkn instanceof ParenthesisPtg) {
-                       // parenthesis Ptg should have only one child
-                       return isValidRangeOperand(a.getChildren()[0]);
-               }
-
-               // one special case of ScalarConstantPtg
-               if (tkn == ErrPtg.REF_INVALID) {
-                       return true;
-               }
-
-               // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
-               return false;
-       }
-
-       /**
-        * Parses area refs (things which could be the operand of ':') and simple factors
-        * Examples
-        * <pre>
-        *   A$1
-        *   $A$1 :  $B1
-        *   A1 ....... C2
-        *   Sheet1 !$A1
-        *   a..b!A1
-        *   'my sheet'!A1
-        *   .my.sheet!A1
-        *   'my sheet':'my alt sheet'!A1
-        *   .my.sheet1:.my.sheet2!$B$2
-        *   my.named..range.
-        *   'my sheet'!my.named.range
-        *   .my.sheet!my.named.range
-        *   foo.bar(123.456, "abc")
-        *   123.456
-        *   "abc"
-        *   true
+    /** Read New Character From Input Stream */
+    private void GetChar() {
+        // The intersection operator is a space.  We track whether the run of 
+        // whitespace preceeding "look" counts as an intersection operator.  
+        if (IsWhite(look)) {
+            if (look == ' ') {
+                _inIntersection = true;
+            }
+        }
+        else {
+            _inIntersection = false;
+        }
+        
+        // Check to see if we've walked off the end of the string.
+        if (_pointer > _formulaLength) {
+            throw new RuntimeException("too far");
+        }
+        if (_pointer < _formulaLength) {
+            look=_formulaString.charAt(_pointer);
+        } else {
+            // Just return if so and reset 'look' to something to keep
+            // SkipWhitespace from spinning
+            look = (char)0;
+            _inIntersection = false;
+        }
+        _pointer++;
+        //System.out.println("Got char: "+ look);
+    }
+    private void resetPointer(int ptr) {
+        _pointer = ptr;
+        if (_pointer <= _formulaLength) {
+            look=_formulaString.charAt(_pointer-1);
+        } else {
+            // Just return if so and reset 'look' to something to keep
+            // SkipWhitespace from spinning
+            look = (char)0;
+        }
+    }
+
+    /** Report What Was Expected */
+    private RuntimeException expected(String s) {
+        String msg;
+
+        if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) {
+            msg = "The specified formula '" + _formulaString
+                + "' starts with an equals sign which is not allowed.";
+        } else {
+            msg = "Parse error near char " + (_pointer-1) + " '" + look + "'"
+                + " in specified formula '" + _formulaString + "'. Expected "
+                + s;
+        }
+        return new FormulaParseException(msg);
+    }
+
+    /** Recognize an Alpha Character */
+    private static boolean IsAlpha(char c) {
+        return Character.isLetter(c) || c == '$' || c=='_';
+    }
+
+    /** Recognize a Decimal Digit */
+    private static boolean IsDigit(char c) {
+        return Character.isDigit(c);
+    }
+
+    /** Recognize White Space */
+    private static boolean IsWhite( char c) {
+        return  c ==' ' || c== TAB || c == CR || c == LF;
+    }
+
+    /** Skip Over Leading White Space */
+    private void SkipWhite() {
+        while (IsWhite(look)) {
+            GetChar();
+        }
+    }
+
+    /**
+     *  Consumes the next input character if it is equal to the one specified otherwise throws an
+     *  unchecked exception. This method does <b>not</b> consume whitespace (before or after the
+     *  matched character).
+     */
+    private void Match(char x) {
+        if (look != x) {
+            throw expected("'" + x + "'");
+        }
+        GetChar();
+    }
+
+    /** Get a Number */
+    private String GetNum() {
+        StringBuffer value = new StringBuffer();
+
+        while (IsDigit(this.look)){
+            value.append(this.look);
+            GetChar();
+        }
+        return value.length() == 0 ? null : value.toString();
+    }
+
+    private ParseNode parseRangeExpression() {
+        ParseNode result = parseRangeable();
+        boolean hasRange = false;
+        while (look == ':') {
+            int pos = _pointer;
+            GetChar();
+            ParseNode nextPart = parseRangeable();
+            // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
+            // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
+            // Furthermore, Excel doesn't seem to simplify
+            // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"
+
+            checkValidRangeOperand("LHS", pos, result);
+            checkValidRangeOperand("RHS", pos, nextPart);
+
+            ParseNode[] children = { result, nextPart, };
+            result = new ParseNode(RangePtg.instance, children);
+            hasRange = true;
+        }
+        if (hasRange) {
+            return augmentWithMemPtg(result);
+        }
+        return result;
+    }
+
+    private static ParseNode augmentWithMemPtg(ParseNode root) {
+        Ptg memPtg;
+        if (needsMemFunc(root)) {
+            memPtg = new MemFuncPtg(root.getEncodedSize());
+        } else {
+            memPtg = new MemAreaPtg(root.getEncodedSize());
+        }
+        return new ParseNode(memPtg, root);
+    }
+    /**
+     * From OOO doc: "Whenever one operand of the reference subexpression is a function,
+     *  a defined name, a 3D reference, or an external reference (and no error occurs),
+     *  a tMemFunc token is used"
+     *
+     */
+    private static boolean needsMemFunc(ParseNode root) {
+        Ptg token = root.getToken();
+        if (token instanceof AbstractFunctionPtg) {
+            return true;
+        }
+        if (token instanceof ExternSheetReferenceToken) { // 3D refs
+            return true;
+        }
+        if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
+            return true;
+        }
+
+        if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
+            // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
+            for(ParseNode child : root.getChildren()) {
+                if (needsMemFunc(child)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        if (token instanceof OperandPtg) {
+            return false;
+        }
+        if (token instanceof OperationPtg) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param currentParsePosition used to format a potential error message
+     */
+    private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) {
+        if (!isValidRangeOperand(pn)) {
+            throw new FormulaParseException("The " + sideName
+                    + " of the range operator ':' at position "
+                    + currentParsePosition + " is not a proper reference.");
+        }
+    }
+
+    /**
+     * @return <code>false</code> if sub-expression represented the specified ParseNode definitely
+     * cannot appear on either side of the range (':') operator
+     */
+    private static boolean isValidRangeOperand(ParseNode a) {
+        Ptg tkn = a.getToken();
+        // Note - order is important for these instance-of checks
+        if (tkn instanceof OperandPtg) {
+            // notably cell refs and area refs
+            return true;
+        }
+
+        // next 2 are special cases of OperationPtg
+        if (tkn instanceof AbstractFunctionPtg) {
+            AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
+            byte returnClass = afp.getDefaultOperandClass();
+            return Ptg.CLASS_REF == returnClass;
+        }
+        if (tkn instanceof ValueOperatorPtg) {
+            return false;
+        }
+        if (tkn instanceof OperationPtg) {
+            return true;
+        }
+
+        // one special case of ControlPtg
+        if (tkn instanceof ParenthesisPtg) {
+            // parenthesis Ptg should have only one child
+            return isValidRangeOperand(a.getChildren()[0]);
+        }
+
+        // one special case of ScalarConstantPtg
+        if (tkn == ErrPtg.REF_INVALID) {
+            return true;
+        }
+
+        // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
+        return false;
+    }
+
+    /**
+     * Parses area refs (things which could be the operand of ':') and simple factors
+     * Examples
+     * <pre>
+     *   A$1
+     *   $A$1 :  $B1
+     *   A1 .......    C2
+     *   Sheet1 !$A1
+     *   a..b!A1
+     *   'my sheet'!A1
+     *   .my.sheet!A1
+     *   'my sheet':'my alt sheet'!A1
+     *   .my.sheet1:.my.sheet2!$B$2
+     *   my.named..range.
+     *   'my sheet'!my.named.range
+     *   .my.sheet!my.named.range
+     *   foo.bar(123.456, "abc")
+     *   123.456
+     *   "abc"
+     *   true
      *   [Foo.xls]!$A$1
-        *   [Foo.xls]'my sheet'!$A$1
-        *   [Foo.xls]!my.named.range
-        * </pre>
-        *
-        */
-       private ParseNode parseRangeable() {
-               SkipWhite();
-               int savePointer = _pointer;
-               SheetIdentifier sheetIden = parseSheetName();
-               
-               if (sheetIden == null) {
-                       resetPointer(savePointer);
-               } else {
-                       SkipWhite();
-                       savePointer = _pointer;
-               }
-
-               SimpleRangePart part1 = parseSimpleRangePart();
-               if (part1 == null) {
-                       if (sheetIden != null) {
+     *   [Foo.xls]'my sheet'!$A$1
+     *   [Foo.xls]!my.named.range
+     * </pre>
+     *
+     */
+    private ParseNode parseRangeable() {
+        SkipWhite();
+        int savePointer = _pointer;
+        SheetIdentifier sheetIden = parseSheetName();
+        
+        if (sheetIden == null) {
+            resetPointer(savePointer);
+        } else {
+            SkipWhite();
+            savePointer = _pointer;
+        }
+
+        SimpleRangePart part1 = parseSimpleRangePart();
+        if (part1 == null) {
+            if (sheetIden != null) {
                 if(look == '#'){  // error ref like MySheet!#REF!
                     return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));  
                 } else {
@@ -465,131 +465,131 @@ public final class FormulaParser {
                     }
                     return new ParseNode(nameXPtg);
                 }
-                       }
-                       return parseNonRange(savePointer);
-               }
-               boolean whiteAfterPart1 = IsWhite(look);
-               if (whiteAfterPart1) {
-                       SkipWhite();
-               }
-
-               if (look == ':') {
-                       int colonPos = _pointer;
-                       GetChar();
-                       SkipWhite();
-                       SimpleRangePart part2 = parseSimpleRangePart();
-                       if (part2 != null && !part1.isCompatibleForArea(part2)) {
-                               // second part is not compatible with an area ref e.g. S!A1:S!B2
-                               // where S might be a sheet name (that looks like a column name)
-
-                               part2 = null;
-                       }
-                       if (part2 == null) {
-                               // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
-                               // reset and let caller use explicit range operator
-                               resetPointer(colonPos);
-                               if (!part1.isCell()) {
-                                       String prefix = "";
-                                       if (sheetIden != null) {
-                                               prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
-                                       }
-                                       throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
-                               }
-                       }
-                       return createAreaRefParseNode(sheetIden, part1, part2);
-               }
-
-               if (look == '.') {
-                       GetChar();
-                       int dotCount = 1;
-                       while (look =='.') {
-                               dotCount ++;
-                               GetChar();
-                       }
-                       boolean whiteBeforePart2 = IsWhite(look);
-
-                       SkipWhite();
-                       SimpleRangePart part2 = parseSimpleRangePart();
-                       String part1And2 = _formulaString.substring(savePointer-1, _pointer-1);
-                       if (part2 == null) {
-                               if (sheetIden != null) {
-                                       throw new FormulaParseException("Complete area reference expected after sheet name at index "
-                                                       + _pointer + ".");
-                               }
-                               return parseNonRange(savePointer);
-                       }
-
-
-                       if (whiteAfterPart1 || whiteBeforePart2) {
-                               if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
-                                       // "A .. B" not valid syntax for "A:B"
-                                       // and there's no other valid expression that fits this grammar
-                                       throw new FormulaParseException("Dotted range (full row or column) expression '"
-                                                       + part1And2 + "' must not contain whitespace.");
-                               }
-                               return createAreaRefParseNode(sheetIden, part1, part2);
-                       }
-
-                       if (dotCount == 1 && part1.isRow() && part2.isRow()) {
-                               // actually, this is looking more like a number
-                               return parseNonRange(savePointer);
-                       }
-
-                       if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
-                               if (dotCount != 2) {
-                                       throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2
-                                                       + "' must have exactly 2 dots.");
-                               }
-                       }
-                       return createAreaRefParseNode(sheetIden, part1, part2);
-               }
-               if (part1.isCell() && isValidCellReference(part1.getRep())) {
-                       return createAreaRefParseNode(sheetIden, part1, null);
-               }
-               if (sheetIden != null) {
-                       throw new FormulaParseException("Second part of cell reference expected after sheet name at index "
-                                       + _pointer + ".");
-               }
-
-               return parseNonRange(savePointer);
-       }
-
-
-       
-       private final static String specHeaders = "Headers";
-       private final static String specAll = "All";
-       private final static String specData = "Data";
-       private final static String specTotals = "Totals";
-       private final static String specThisRow = "This Row";
-       
-       /**
-        * Parses a structured reference, returns it as area reference.
-        * Examples:
-        * <pre>
-        * Table1[col]
-        * Table1[[#Totals],[col]]
-        * Table1[#Totals]
-        * Table1[#All]
-        * Table1[#Data]
-        * Table1[#Headers]
-        * Table1[#Totals]
-        * Table1[#This Row]
-        * Table1[[#All],[col]]
-        * Table1[[#Headers],[col]]
-        * Table1[[#Totals],[col]]
-        * Table1[[#All],[col1]:[col2]]
-        * Table1[[#Data],[col1]:[col2]]
-        * Table1[[#Headers],[col1]:[col2]]
-        * Table1[[#Totals],[col1]:[col2]]
-        * Table1[[#Headers],[#Data],[col2]]
-        * Table1[[#This Row], [col1]]
-        * Table1[ [col1]:[col2] ]
-        * </pre>
-        * @param tableName
-        * @return
-        */
-       private ParseNode parseStructuredReference(String tableName){
-           
+            }
+            return parseNonRange(savePointer);
+        }
+        boolean whiteAfterPart1 = IsWhite(look);
+        if (whiteAfterPart1) {
+            SkipWhite();
+        }
+
+        if (look == ':') {
+            int colonPos = _pointer;
+            GetChar();
+            SkipWhite();
+            SimpleRangePart part2 = parseSimpleRangePart();
+            if (part2 != null && !part1.isCompatibleForArea(part2)) {
+                // second part is not compatible with an area ref e.g. S!A1:S!B2
+                // where S might be a sheet name (that looks like a column name)
+
+                part2 = null;
+            }
+            if (part2 == null) {
+                // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
+                // reset and let caller use explicit range operator
+                resetPointer(colonPos);
+                if (!part1.isCell()) {
+                    String prefix = "";
+                    if (sheetIden != null) {
+                        prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
+                    }
+                    throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
+                }
+            }
+            return createAreaRefParseNode(sheetIden, part1, part2);
+        }
+
+        if (look == '.') {
+            GetChar();
+            int dotCount = 1;
+            while (look =='.') {
+                dotCount ++;
+                GetChar();
+            }
+            boolean whiteBeforePart2 = IsWhite(look);
+
+            SkipWhite();
+            SimpleRangePart part2 = parseSimpleRangePart();
+            String part1And2 = _formulaString.substring(savePointer-1, _pointer-1);
+            if (part2 == null) {
+                if (sheetIden != null) {
+                    throw new FormulaParseException("Complete area reference expected after sheet name at index "
+                            + _pointer + ".");
+                }
+                return parseNonRange(savePointer);
+            }
+
+
+            if (whiteAfterPart1 || whiteBeforePart2) {
+                if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
+                    // "A .. B" not valid syntax for "A:B"
+                    // and there's no other valid expression that fits this grammar
+                    throw new FormulaParseException("Dotted range (full row or column) expression '"
+                            + part1And2 + "' must not contain whitespace.");
+                }
+                return createAreaRefParseNode(sheetIden, part1, part2);
+            }
+
+            if (dotCount == 1 && part1.isRow() && part2.isRow()) {
+                // actually, this is looking more like a number
+                return parseNonRange(savePointer);
+            }
+
+            if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
+                if (dotCount != 2) {
+                    throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2
+                            + "' must have exactly 2 dots.");
+                }
+            }
+            return createAreaRefParseNode(sheetIden, part1, part2);
+        }
+        if (part1.isCell() && isValidCellReference(part1.getRep())) {
+            return createAreaRefParseNode(sheetIden, part1, null);
+        }
+        if (sheetIden != null) {
+            throw new FormulaParseException("Second part of cell reference expected after sheet name at index "
+                    + _pointer + ".");
+        }
+
+        return parseNonRange(savePointer);
+    }
+
+
+    
+    private final static String specHeaders = "Headers";
+    private final static String specAll = "All";
+    private final static String specData = "Data";
+    private final static String specTotals = "Totals";
+    private final static String specThisRow = "This Row";
+    
+    /**
+     * Parses a structured reference, returns it as area reference.
+     * Examples:
+     * <pre>
+     * Table1[col]
+     * Table1[[#Totals],[col]]
+     * Table1[#Totals]
+     * Table1[#All]
+     * Table1[#Data]
+     * Table1[#Headers]
+     * Table1[#Totals]
+     * Table1[#This Row]
+     * Table1[[#All],[col]]
+     * Table1[[#Headers],[col]]
+     * Table1[[#Totals],[col]]
+     * Table1[[#All],[col1]:[col2]]
+     * Table1[[#Data],[col1]:[col2]]
+     * Table1[[#Headers],[col1]:[col2]]
+     * Table1[[#Totals],[col1]:[col2]]
+     * Table1[[#Headers],[#Data],[col2]]
+     * Table1[[#This Row], [col1]]
+     * Table1[ [col1]:[col2] ]
+     * </pre>
+     * @param tableName
+     * @return
+     */
+    private ParseNode parseStructuredReference(String tableName){
+        
         if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) {
             throw new FormulaParseException("Strctured references work only on XSSF (Excel 2007)!");
         }
@@ -767,9 +767,9 @@ public final class FormulaParser {
         SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true));
         Ptg ptg = _book.get3DReferencePtg(new AreaReference(tl, br), sheetIden);
         return new ParseNode(ptg);
-       }
-       
-       /**
+    }
+    
+    /**
      * Tries to parse the next as column - can contain whitespace
      * Caller should save pointer.
      * @return
@@ -816,61 +816,61 @@ public final class FormulaParser {
     }
 
 
-       /**
-        * Parses simple factors that are not primitive ranges or range components
-        * i.e. '!', ':'(and equiv '...') do not appear
-        * Examples
-        * <pre>
-        *   my.named...range.
-        *   foo.bar(123.456, "abc")
-        *   123.456
-        *   "abc"
-        *   true
-        * </pre>
-        */
-       private ParseNode parseNonRange(int savePointer) {
-               resetPointer(savePointer);
-
-               if (Character.isDigit(look)) {
-                       return new ParseNode(parseNumber());
-               }
-               if (look == '"') {
-                       return new ParseNode(new StringPtg(parseStringLiteral()));
-               }
-               
-               // from now on we can only be dealing with non-quoted identifiers
-               // which will either be named ranges or functions
-               String name = parseAsName();
-
-               if (look == '(') {
-                       return function(name);
-               }
-               //TODO Livshen's code
-               if(look == '['){
-                   return parseStructuredReference(name);
-               }
-               //TODO End of Livshen's code
-               if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
-                       return  new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
-               }
-               if (_book == null) {
-                       // Only test cases omit the book (expecting it not to be needed)
-                       throw new IllegalStateException("Need book to evaluate name '" + name + "'");
-               }
-               EvaluationName evalName = _book.getName(name, _sheetIndex);
-               if (evalName == null) {
-                       throw new FormulaParseException("Specified named range '"
-                                       + name + "' does not exist in the current workbook.");
-               }
-               if (evalName.isRange()) {
-                       return new ParseNode(evalName.createPtg());
-               }
-               // TODO - what about NameX ?
-               throw new FormulaParseException("Specified name '"
-                               + name + "' is not a range as expected.");
-       }
-       
-       private String parseAsName() {
+    /**
+     * Parses simple factors that are not primitive ranges or range components
+     * i.e. '!', ':'(and equiv '...') do not appear
+     * Examples
+     * <pre>
+     *   my.named...range.
+     *   foo.bar(123.456, "abc")
+     *   123.456
+     *   "abc"
+     *   true
+     * </pre>
+     */
+    private ParseNode parseNonRange(int savePointer) {
+        resetPointer(savePointer);
+
+        if (Character.isDigit(look)) {
+            return new ParseNode(parseNumber());
+        }
+        if (look == '"') {
+            return new ParseNode(new StringPtg(parseStringLiteral()));
+        }
+        
+        // from now on we can only be dealing with non-quoted identifiers
+        // which will either be named ranges or functions
+        String name = parseAsName();
+
+        if (look == '(') {
+            return function(name);
+        }
+        //TODO Livshen's code
+        if(look == '['){
+            return parseStructuredReference(name);
+        }
+        //TODO End of Livshen's code
+        if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+            return  new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
+        }
+        if (_book == null) {
+            // Only test cases omit the book (expecting it not to be needed)
+            throw new IllegalStateException("Need book to evaluate name '" + name + "'");
+        }
+        EvaluationName evalName = _book.getName(name, _sheetIndex);
+        if (evalName == null) {
+            throw new FormulaParseException("Specified named range '"
+                    + name + "' does not exist in the current workbook.");
+        }
+        if (evalName.isRange()) {
+            return new ParseNode(evalName.createPtg());
+        }
+        // TODO - what about NameX ?
+        throw new FormulaParseException("Specified name '"
+                + name + "' is not a range as expected.");
+    }
+    
+    private String parseAsName() {
         StringBuilder sb = new StringBuilder();
 
         // defined names may begin with a letter or underscore or backslash
@@ -884,1093 +884,1093 @@ public final class FormulaParser {
         SkipWhite();
         
         return sb.toString();
-       }
-
-       /**
-        *
-        * @return <code>true</code> if the specified character may be used in a defined name
-        */
-       private static boolean isValidDefinedNameChar(char ch) {
-               if (Character.isLetterOrDigit(ch)) {
-                       return true;
-               }
-               switch (ch) {
-                       case '.':
-                       case '_':
-                       case '?':
-                       case '\\': // of all things
-                               return true;
-               }
-               return false;
-       }
-       
-       /**
-        *
-        * @param sheetIden may be <code>null</code>
-        * @param part1
-        * @param part2 may be <code>null</code>
-        */
-       private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1,
-                       SimpleRangePart part2) throws FormulaParseException {
-               Ptg ptg;
-               if (part2 == null) {
-                       CellReference cr = part1.getCellReference();
-                       if (sheetIden == null) {
-                               ptg = new RefPtg(cr);
-                       } else {
-                               ptg = _book.get3DReferencePtg(cr, sheetIden);
-                       }
-               } else {
-                       AreaReference areaRef = createAreaRef(part1, part2);
-
-                       if (sheetIden == null) {
-                               ptg = new AreaPtg(areaRef);
-                       } else {
-                               ptg = _book.get3DReferencePtg(areaRef, sheetIden);
-                       }
-               }
-               return new ParseNode(ptg);
-       }
-
-       private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) {
-               if (!part1.isCompatibleForArea(part2)) {
-                       throw new FormulaParseException("has incompatible parts: '"
-                                       + part1.getRep() + "' and '" + part2.getRep() + "'.");
-               }
-               if (part1.isRow()) {
-                       return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep());
-               }
-               if (part1.isColumn()) {
-                       return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep());
-               }
-               return new AreaReference(part1.getCellReference(), part2.getCellReference());
-       }
-
-       /**
-        * Matches a zero or one letter-runs followed by zero or one digit-runs.
-        * Either or both runs man optionally be prefixed with a single '$'.
-        * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN})
-        */
-       private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?");
-
-       /**
-        * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef.  Normally these are
-        * proper cell references but they could also be row or column refs like "$AC" or "10"
-        * @return <code>null</code> (and leaves {@link #_pointer} unchanged if a proper range part does not parse out
-        */
-       private SimpleRangePart parseSimpleRangePart() {
-               int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds
-               boolean hasDigits = false;
-               boolean hasLetters = false;
-               while (ptr < _formulaLength) {
-                       char ch = _formulaString.charAt(ptr);
-                       if (Character.isDigit(ch)) {
-                               hasDigits = true;
-                       } else if (Character.isLetter(ch)) {
-                               hasLetters = true;
-                       } else if (ch =='$' || ch =='_') {
-                               //
-                       } else {
-                               break;
-                       }
-                       ptr++;
-               }
-               if (ptr <= _pointer-1) {
-                       return null;
-               }
-               String rep = _formulaString.substring(_pointer-1, ptr);
-               if (!CELL_REF_PATTERN.matcher(rep).matches()) {
-                       return null;
-               }
-               // Check range bounds against grid max
-               if (hasLetters && hasDigits) {
-                       if (!isValidCellReference(rep)) {
-                               return null;
-                       }
-               } else if (hasLetters) {
-                       if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) {
-                               return null;
-                       }
-               } else if (hasDigits) {
-                       int i;
-                       try {
-                               i = Integer.parseInt(rep.replace("$", ""));
-                       } catch (NumberFormatException e) {
-                               return null;
-                       }
-                       if (i<1 || i>_ssVersion.getMaxRows()) {
-                               return null;
-                       }
-               } else {
-                       // just dollars ? can this happen?
-                       return null;
-               }
-
-
-               resetPointer(ptr+1); // stepping forward
-               return new SimpleRangePart(rep, hasLetters, hasDigits);
-       }
-
-
-       /**
-        * A1, $A1, A$1, $A$1, A, 1
-        */
-       private static final class SimpleRangePart {
-               private enum Type {
-                       CELL, ROW, COLUMN;
-
-                       public static Type get(boolean hasLetters, boolean hasDigits) {
-                               if (hasLetters) {
-                                       return hasDigits ? CELL : COLUMN;
-                               }
-                               if (!hasDigits) {
-                                       throw new IllegalArgumentException("must have either letters or numbers");
-                               }
-                               return ROW;
-                       }
-               }
-
-               private final Type _type;
-               private final String _rep;
-
-               public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) {
-                       _rep = rep;
-                       _type = Type.get(hasLetters, hasNumbers);
-               }
-
-               public boolean isCell() {
-                       return _type == Type.CELL;
-               }
-
-               public boolean isRowOrColumn() {
-                       return _type != Type.CELL;
-               }
-
-               public CellReference getCellReference() {
-                       if (_type != Type.CELL) {
-                               throw new IllegalStateException("Not applicable to this type");
-                       }
-                       return new CellReference(_rep);
-               }
-
-               public boolean isColumn() {
-                       return _type == Type.COLUMN;
-               }
-
-               public boolean isRow() {
-                       return _type == Type.ROW;
-               }
-
-               public String getRep() {
-                       return _rep;
-               }
-
-               /**
-                * @return <code>true</code> if the two range parts can be combined in an
-                * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid
-                * when this method returns <code>false</code> )
-                */
-               public boolean isCompatibleForArea(SimpleRangePart part2) {
-                       return _type == part2._type;
-               }
-
-               @Override
-               public String toString() {
-                       StringBuilder sb = new StringBuilder(64);
-                       sb.append(getClass().getName()).append(" [");
-                       sb.append(_rep);
-                       sb.append("]");
-                       return sb.toString();
-               }
-       }
-
-       /**
-        * Note - caller should reset {@link #_pointer} upon <code>null</code> result
-        * @return The sheet name as an identifier <code>null</code> if '!' is not found in the right place
-        */
-       private SheetIdentifier parseSheetName() {
-               String bookName;
-               if (look == '[') {
-                       StringBuilder sb = new StringBuilder();
-                       GetChar();
-                       while (look != ']') {
-                               sb.append(look);
-                               GetChar();
-                       }
-                       GetChar();
-                       bookName = sb.toString();
-               } else {
-                       bookName = null;
-               }
-
-               if (look == '\'') {
-                       StringBuffer sb = new StringBuffer();
-
-                       Match('\'');
-                       boolean done = look == '\'';
-                       while(!done) {
-                               sb.append(look);
-                               GetChar();
-                               if(look == '\'')
-                               {
-                                       Match('\'');
-                                       done = look != '\'';
-                               }
-                       }
-
-                       NameIdentifier iden = new NameIdentifier(sb.toString(), true);
-                       // quoted identifier - can't concatenate anything more
-                       SkipWhite();
-                       if (look == '!') {
-                               GetChar();
-                               return new SheetIdentifier(bookName, iden);
-                       }
-                       // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
-            if (look == ':') {
-                return parseSheetRange(bookName, iden);
+    }
+
+    /**
+     *
+     * @return <code>true</code> if the specified character may be used in a defined name
+     */
+    private static boolean isValidDefinedNameChar(char ch) {
+        if (Character.isLetterOrDigit(ch)) {
+            return true;
+        }
+        switch (ch) {
+            case '.':
+            case '_':
+            case '?':
+            case '\\': // of all things
+                return true;
+        }
+        return false;
+    }
+    
+    /**
+     *
+     * @param sheetIden may be <code>null</code>
+     * @param part1
+     * @param part2 may be <code>null</code>
+     */
+    private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1,
+            SimpleRangePart part2) throws FormulaParseException {
+        Ptg ptg;
+        if (part2 == null) {
+            CellReference cr = part1.getCellReference();
+            if (sheetIden == null) {
+                ptg = new RefPtg(cr);
+            } else {
+                ptg = _book.get3DReferencePtg(cr, sheetIden);
             }
-                       return null;
-               }
-
-               // unquoted sheet names must start with underscore or a letter
-               if (look =='_' || Character.isLetter(look)) {
-                       StringBuilder sb = new StringBuilder();
-                       // can concatenate idens with dots
-                       while (isUnquotedSheetNameChar(look)) {
-                               sb.append(look);
-                               GetChar();
-                       }
-            NameIdentifier iden = new NameIdentifier(sb.toString(), false);
-                       SkipWhite();
-                       if (look == '!') {
-                               GetChar();
-                               return new SheetIdentifier(bookName, iden);
-                       }
-            // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
-            if (look == ':') {
-                return parseSheetRange(bookName, iden);
+        } else {
+            AreaReference areaRef = createAreaRef(part1, part2);
+
+            if (sheetIden == null) {
+                ptg = new AreaPtg(areaRef);
+            } else {
+                ptg = _book.get3DReferencePtg(areaRef, sheetIden);
             }
-                       return null;
-               }
-               if (look == '!' && bookName != null) {
-                   // Raw book reference, without a sheet
-            GetChar();
-                   return new SheetIdentifier(bookName, null);
-               }
-               return null;
-       }
-       
-       /**
-        * If we have something that looks like [book]Sheet1: or 
-        *  Sheet1, see if it's actually a range eg Sheet1:Sheet2!
-        */
-       private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) {
-        GetChar();
-        SheetIdentifier sheet2 = parseSheetName();
-        if (sheet2 != null) {
-           return new SheetRangeIdentifier(bookname, sheet1Name, sheet2.getSheetIdentifier());
         }
-        return null;
-       }
-
-       /**
-        * very similar to {@link SheetNameFormatter#isSpecialChar(char)}
-        */
-       private static boolean isUnquotedSheetNameChar(char ch) {
-               if(Character.isLetterOrDigit(ch)) {
-                       return true;
-               }
-               switch(ch) {
-                       case '.': // dot is OK
-                       case '_': // underscore is OK
-                               return true;
-               }
-               return false;
-       }
-
-       /**
-        * @return <code>true</code> if the specified name is a valid cell reference
-        */
-       private boolean isValidCellReference(String str) {
-               //check range bounds against grid max
-               boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL;
-
-               if(result){
-                       /**
-                        * Check if the argument is a function. Certain names can be either a cell reference or a function name
-                        * depending on the contenxt. Compare the following examples in Excel 2007:
-                        * (a) LOG10(100) + 1
-                        * (b) LOG10 + 1
-                        * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference
-                        */
-                       boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null;
-                       if(isFunc){
-                               int savePointer = _pointer;
-                               resetPointer(_pointer + str.length());
-                               SkipWhite();
-                               // open bracket indicates that the argument is a function,
-                               // the returning value should be false, i.e. "not a valid cell reference"
-                               result = look != '(';
-                               resetPointer(savePointer);
-                       }
-               }
-               return result;
-       }
-
-
-       /**
-        * Note - Excel function names are 'case aware but not case sensitive'.  This method may end
-        * up creating a defined name record in the workbook if the specified name is not an internal
-        * Excel function, and has not been encountered before.
-        * 
-        * Side effect: creates workbook name if name is not recognized (name is probably a UDF)
-        *
-        * @param name case preserved function name (as it was entered/appeared in the formula).
-        */
-       private ParseNode function(String name) {
-               Ptg nameToken = null;
-               if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
-                       // user defined function
-                       // in the token tree, the name is more or less the first argument
-
-                       if (_book == null) {
-                               // Only test cases omit the book (expecting it not to be needed)
-                               throw new IllegalStateException("Need book to evaluate name '" + name + "'");
-                       }
-                       // Check to see if name is a named range in the workbook
-                       EvaluationName hName = _book.getName(name, _sheetIndex);
-                       if (hName != null) {
-                               if (!hName.isFunctionName()) {
-                                       throw new FormulaParseException("Attempt to use name '" + name
-                                                       + "' as a function, but defined name in workbook does not refer to a function");
-                               }
-       
-                               // calls to user-defined functions within the workbook
-                               // get a Name token which points to a defined name record
-                               nameToken = hName.createPtg();
-                       } else {
-                               // Check if name is an external names table
-                               nameToken = _book.getNameXPtg(name, null);
-                               if (nameToken == null) {
-                                       // name is not an internal or external name
-                                       if (log.check(POILogger.WARN)) {
-                                               log.log(POILogger.WARN,
-                                                               "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook.");
-                                       }
-                                       // name is probably the name of an unregistered User-Defined Function
-                                       switch (_book.getSpreadsheetVersion()) {
-                                               case EXCEL97:
-                                                       // HSSFWorkbooks require a name to be added to Workbook defined names table
-                                                       addName(name);
-                                                       hName = _book.getName(name, _sheetIndex);
-                                                       nameToken = hName.createPtg();
-                                                       break;
-                                               case EXCEL2007:
-                                                       // XSSFWorkbooks store formula names as strings.
-                                                       nameToken = new NameXPxg(name);
-                                                       break;
-                                               default:
-                                                       throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name());
-                                       }
-                               }
-                       }
-               }
-
-               Match('(');
-               ParseNode[] args = Arguments();
-               Match(')');
-
-               return getFunction(name, nameToken, args);
-       }
-       
-       /**
-        * Adds a name (named range or user defined function) to underlying workbook's names table
-        * @param functionName
-        */
-       private final void addName(String functionName) {
-               final Name name = _book.createName();
-               name.setFunction(true);
-               name.setNameName(functionName);
-               name.setSheetIndex(_sheetIndex);
-       }
-
-       /**
-        * Generates the variable function ptg for the formula.
-        * <p>
-        * For IF Formulas, additional PTGs are added to the tokens
-        * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
-        * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
-        */
-       private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
-
-               FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
-               int numArgs = args.length;
-               if(fm == null) {
-                       if (namePtg == null) {
-                               throw new IllegalStateException("NamePtg must be supplied for external functions");
-                       }
-                       // must be external function
-                       ParseNode[] allArgs = new ParseNode[numArgs+1];
-                       allArgs[0] = new ParseNode(namePtg);
-                       System.arraycopy(args, 0, allArgs, 1, numArgs);
-                       return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs);
-               }
-
-               if (namePtg != null) {
-                       throw new IllegalStateException("NamePtg no applicable to internal functions");
-               }
-               boolean isVarArgs = !fm.hasFixedArgsLength();
-               int funcIx = fm.getIndex();
-               if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
-                       // Excel encodes the sum of a single argument as tAttrSum
-                       // POI does the same for consistency, but this is not critical
-                       return new ParseNode(AttrPtg.getSumSingle(), args);
-                       // The code below would encode tFuncVar(SUM) which seems to do no harm
-               }
-               validateNumArgs(args.length, fm);
-
-               AbstractFunctionPtg retval;
-               if(isVarArgs) {
-                       retval = FuncVarPtg.create(name, numArgs);
-               } else {
-                       retval = FuncPtg.create(funcIx);
-               }
-               return new ParseNode(retval, args);
-       }
-
-       private void validateNumArgs(int numArgs, FunctionMetadata fm) {
-               if(numArgs < fm.getMinParams()) {
-                       String msg = "Too few arguments to function '" + fm.getName() + "'. ";
-                       if(fm.hasFixedArgsLength()) {
-                               msg += "Expected " + fm.getMinParams();
-                       } else {
-                               msg += "At least " + fm.getMinParams() + " were expected";
-                       }
-                       msg += " but got " + numArgs + ".";
-                       throw new FormulaParseException(msg);
-               }
-               //the maximum number of arguments depends on the Excel version
-               int maxArgs;
-               if (fm.hasUnlimitedVarags()) {
-                       if(_book != null) {
-                               maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
-                       } else {
-                               //_book can be omitted by test cases
-                               maxArgs = fm.getMaxParams(); // just use BIFF8
-                       }
-               } else {
-                       maxArgs = fm.getMaxParams();
-               }
-
-               if(numArgs > maxArgs) {
-                       String msg = "Too many arguments to function '" + fm.getName() + "'. ";
-                       if(fm.hasFixedArgsLength()) {
-                               msg += "Expected " + maxArgs;
-                       } else {
-                               msg += "At most " + maxArgs + " were expected";
-                       }
-                       msg += " but got " + numArgs + ".";
-                       throw new FormulaParseException(msg);
-          }
-       }
-
-       private static boolean isArgumentDelimiter(char ch) {
-               return ch ==  ',' || ch == ')';
-       }
-
-       /** get arguments to a function */
-       private ParseNode[] Arguments() {
-               //average 2 args per function
-               List<ParseNode> temp = new ArrayList<ParseNode>(2);
-               SkipWhite();
-               if(look == ')') {
-                       return ParseNode.EMPTY_ARRAY;
-               }
-
-               boolean missedPrevArg = true;
-               while (true) {
-                       SkipWhite();
-                       if (isArgumentDelimiter(look)) {
-                               if (missedPrevArg) {
-                                       temp.add(new ParseNode(MissingArgPtg.instance));
-                               }
-                               if (look == ')') {
-                                       break;
-                               }
-                               Match(',');
-                               missedPrevArg = true;
-                               continue;
-                       }
-                       temp.add(comparisonExpression());
-                       missedPrevArg = false;
-                       SkipWhite();
-                       if (!isArgumentDelimiter(look)) {
-                               throw expected("',' or ')'");
-                       }
-               }
-               ParseNode[] result = new ParseNode[temp.size()];
-               temp.toArray(result);
-               return result;
-       }
+        return new ParseNode(ptg);
+    }
 
-   /** Parse and Translate a Math Factor  */
-       private ParseNode powerFactor() {
-               ParseNode result = percentFactor();
-               while(true) {
-                       SkipWhite();
-                       if(look != '^') {
-                               return result;
-                       }
-                       Match('^');
-                       ParseNode other = percentFactor();
-                       result = new ParseNode(PowerPtg.instance, result, other);
-               }
-       }
-
-       private ParseNode percentFactor() {
-               ParseNode result = parseSimpleFactor();
-               while(true) {
-                       SkipWhite();
-                       if(look != '%') {
-                               return result;
-                       }
-                       Match('%');
-                       result = new ParseNode(PercentPtg.instance, result);
-               }
-       }
-
-
-       /**
-        * factors (without ^ or % )
-        */
-       private ParseNode parseSimpleFactor() {
-               SkipWhite();
-               switch(look) {
-                       case '#':
-                               return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
-                       case '-':
-                               Match('-');
-                               return parseUnary(false);
-                       case '+':
-                               Match('+');
-                               return parseUnary(true);
-                       case '(':
-                               Match('(');
-                               ParseNode inside = unionExpression();
-                               Match(')');
-                               return new ParseNode(ParenthesisPtg.instance, inside);
-                       case '"':
-                               return new ParseNode(new StringPtg(parseStringLiteral()));
-                       case '{':
-                               Match('{');
-                               ParseNode arrayNode = parseArray();
-                               Match('}');
-                               return arrayNode;
-               }
-               // named ranges and tables can start with underscore or backslash
-               // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names
-               if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) {
-                       return parseRangeExpression();
-               }
-               if (look == '.') {
-                       return new ParseNode(parseNumber());
-               }
-               throw expected("cell ref or constant literal");
-       }
-
-
-       private ParseNode parseUnary(boolean isPlus) {
-
-               boolean numberFollows = IsDigit(look) || look=='.';
-               ParseNode factor = powerFactor();
-
-               if (numberFollows) {
-                       // + or - directly next to a number is parsed with the number
-
-                       Ptg token = factor.getToken();
-                       if (token instanceof NumberPtg) {
-                               if (isPlus) {
-                                       return factor;
-                               }
-                               token = new NumberPtg(-((NumberPtg)token).getValue());
-                               return new ParseNode(token);
-                       }
-                       if (token instanceof IntPtg) {
-                               if (isPlus) {
-                                       return factor;
-                               }
-                               int intVal = ((IntPtg)token).getValue();
-                               // note - cannot use IntPtg for negatives
-                               token = new NumberPtg(-intVal);
-                               return new ParseNode(token);
-                       }
-               }
-               return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor);
-       }
-
-       private ParseNode parseArray() {
-               List<Object[]> rowsData = new ArrayList<Object[]>();
-               while(true) {
-                       Object[] singleRowData = parseArrayRow();
-                       rowsData.add(singleRowData);
-                       if (look == '}') {
-                               break;
-                       }
-                       if (look != ';') {
-                               throw expected("'}' or ';'");
-                       }
-                       Match(';');
-               }
-               int nRows = rowsData.size();
-               Object[][] values2d = new Object[nRows][];
-               rowsData.toArray(values2d);
-               int nColumns = values2d[0].length;
-               checkRowLengths(values2d, nColumns);
-
-               return new ParseNode(new ArrayPtg(values2d));
-       }
-       private void checkRowLengths(Object[][] values2d, int nColumns) {
-               for (int i = 0; i < values2d.length; i++) {
-                       int rowLen = values2d[i].length;
-                       if (rowLen != nColumns) {
-                               throw new FormulaParseException("Array row " + i + " has length " + rowLen
-                                               + " but row 0 has length " + nColumns);
-                       }
-               }
-       }
-
-       private Object[] parseArrayRow() {
-               List<Object> temp = new ArrayList<Object>();
-               while (true) {
-                       temp.add(parseArrayItem());
-                       SkipWhite();
-                       switch(look) {
-                               case '}':
-                               case ';':
-                                       break;
-                               case ',':
-                                       Match(',');
-                                       continue;
-                               default:
-                                       throw expected("'}' or ','");
-
-                       }
-                       break;
-               }
-
-               Object[] result = new Object[temp.size()];
-               temp.toArray(result);
-               return result;
-       }
-
-       private Object parseArrayItem() {
-               SkipWhite();
-               switch(look) {
-                       case '"': return parseStringLiteral();
-                       case '#': return ErrorConstant.valueOf(parseErrorLiteral());
-                       case 'F': case 'f':
-                       case 'T': case 't':
-                               return parseBooleanLiteral();
-                       case '-':
-                               Match('-');
-                               SkipWhite();
-                               return convertArrayNumber(parseNumber(), false);
-               }
-               // else assume number
-               return convertArrayNumber(parseNumber(), true);
-       }
-
-       private Boolean parseBooleanLiteral() {
-               String iden = parseUnquotedIdentifier();
-               if ("TRUE".equalsIgnoreCase(iden)) {
-                       return Boolean.TRUE;
-               }
-               if ("FALSE".equalsIgnoreCase(iden)) {
-                       return Boolean.FALSE;
-               }
-               throw expected("'TRUE' or 'FALSE'");
-       }
-
-       private static Double convertArrayNumber(Ptg ptg, boolean isPositive) {
-               double value;
-               if (ptg instanceof IntPtg) {
-                       value = ((IntPtg)ptg).getValue();
-               } else  if (ptg instanceof NumberPtg) {
-                       value = ((NumberPtg)ptg).getValue();
-               } else {
-                       throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
-               }
-               if (!isPositive) {
-                       value = -value;
-               }
-               return new Double(value);
-       }
-
-       private Ptg parseNumber() {
-               String number2 = null;
-               String exponent = null;
-               String number1 = GetNum();
-
-               if (look == '.') {
-                       GetChar();
-                       number2 = GetNum();
-               }
-
-               if (look == 'E') {
-                       GetChar();
-
-                       String sign = "";
-                       if (look == '+') {
-                               GetChar();
-                       } else if (look == '-') {
-                               GetChar();
-                               sign = "-";
-                       }
-
-                       String number = GetNum();
-                       if (number == null) {
-                               throw expected("Integer");
-                       }
-                       exponent = sign + number;
-               }
-
-               if (number1 == null && number2 == null) {
-                       throw expected("Integer");
-               }
-
-               return getNumberPtgFromString(number1, number2, exponent);
-       }
-
-
-       private int parseErrorLiteral() {
-               Match('#');
-               String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT);
-               if (part1 == null) {
-                       throw expected("remainder of error constant literal");
-               }
-
-               switch(part1.charAt(0)) {
-                       case 'V': {
-                           FormulaError fe = FormulaError.VALUE;
-                               if(part1.equals(fe.name())) {
-                                       Match('!');
-                                       return fe.getCode();
-                               }
-                               throw expected(fe.getString());
-                       }
-                       case 'R': {
-                           FormulaError fe = FormulaError.REF;
-                               if(part1.equals(fe.name())) {
-                                       Match('!');
-                                       return fe.getCode();
-                               }
-                               throw expected(fe.getString());
-            }
-                       case 'D': {
-                           FormulaError fe = FormulaError.DIV0;
-                               if(part1.equals("DIV")) {
-                                       Match('/');
-                                       Match('0');
-                                       Match('!');
-                                       return fe.getCode();
-                               }
-                               throw expected(fe.getString());
-                       }
-                       case 'N': {
-                           FormulaError fe = FormulaError.NAME;
-                               if(part1.equals(fe.name())) {
-                                   // only one that ends in '?'
-                                       Match('?');
-                                       return fe.getCode();
-                               }
-                               fe = FormulaError.NUM;
-                               if(part1.equals(fe.name())) {
-                                       Match('!');
-                                       return fe.getCode();
-                               }
-                               fe = FormulaError.NULL;
-                               if(part1.equals(fe.name())) {
-                                       Match('!');
-                                       return fe.getCode();
-                               }
-                               fe = FormulaError.NA;
-                               if(part1.equals("N")) {
-                                       Match('/');
-                                       if(look != 'A' && look != 'a') {
-                                               throw expected(fe.getString());
-                                       }
-                                       Match(look);
-                                       // Note - no '!' or '?' suffix
-                                       return fe.getCode();
-                               }
-                               throw expected("#NAME?, #NUM!, #NULL! or #N/A");
-                       }
-               }
-               throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
-       }
-
-       private String parseUnquotedIdentifier() {
-               if (look == '\'') {
-                       throw expected("unquoted identifier");
-               }
-               StringBuilder sb = new StringBuilder();
-               while (Character.isLetterOrDigit(look) || look == '.') {
-                       sb.append(look);
-                       GetChar();
-               }
-               if (sb.length() < 1) {
-                       return null;
-               }
-
-               return sb.toString();
-       }
-
-       /**
-        * Get a PTG for an integer from its string representation.
-        * return Int or Number Ptg based on size of input
-        */
-       private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
-               StringBuffer number = new StringBuffer();
-
-               if (number2 == null) {
-                       number.append(number1);
-
-                       if (exponent != null) {
-                               number.append('E');
-                               number.append(exponent);
-                       }
-
-                       String numberStr = number.toString();
-                       int intVal;
-                       try {
-                               intVal = Integer.parseInt(numberStr);
-                       } catch (NumberFormatException e) {
-                               return new NumberPtg(numberStr);
-                       }
-                       if (IntPtg.isInRange(intVal)) {
-                               return new IntPtg(intVal);
-                       }
-                       return new NumberPtg(numberStr);
-               }
-
-               if (number1 != null) {
-                       number.append(number1);
-               }
-
-               number.append('.');
-               number.append(number2);
-
-               if (exponent != null) {
-                       number.append('E');
-                       number.append(exponent);
-               }
-
-               return new NumberPtg(number.toString());
-       }
-
-
-       private String parseStringLiteral() {
-               Match('"');
-
-               StringBuffer token = new StringBuffer();
-               while (true) {
-                       if (look == '"') {
-                               GetChar();
-                               if (look != '"') {
-                                       break;
-                               }
-                        }
-                       token.append(look);
-                       GetChar();
-               }
-               return token.toString();
-       }
-
-       /** Parse and Translate a Math Term */
-       private ParseNode  Term() {
-               ParseNode result = powerFactor();
-               while(true) {
-                       SkipWhite();
-                       Ptg operator;
-                       switch(look) {
-                               case '*':
-                                       Match('*');
-                                       operator = MultiplyPtg.instance;
-                                       break;
-                               case '/':
-                                       Match('/');
-                                       operator = DividePtg.instance;
-                                       break;
-                               default:
-                                       return result; // finished with Term
-                       }
-                       ParseNode other = powerFactor();
-                       result = new ParseNode(operator, result, other);
-               }
-       }
-
-       private ParseNode unionExpression() {
-               ParseNode result = intersectionExpression();
-               boolean hasUnions = false;
-               while (true) {
-                       SkipWhite();
-                       switch(look) {
-                               case ',':
-                                       GetChar();
-                                       hasUnions = true;
-                                       ParseNode other = intersectionExpression();
-                                       result = new ParseNode(UnionPtg.instance, result, other);
-                                       continue;
-                       }
-                       if (hasUnions) {
-                               return augmentWithMemPtg(result);
-                       }
-                       return result;
-               }
-       }
+    private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) {
+        if (!part1.isCompatibleForArea(part2)) {
+            throw new FormulaParseException("has incompatible parts: '"
+                    + part1.getRep() + "' and '" + part2.getRep() + "'.");
+        }
+        if (part1.isRow()) {
+            return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep());
+        }
+        if (part1.isColumn()) {
+            return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep());
+        }
+        return new AreaReference(part1.getCellReference(), part2.getCellReference());
+    }
 
-   private ParseNode intersectionExpression() {
-               ParseNode result = comparisonExpression();
-               boolean hasIntersections = false;
-               while (true) {
-                       SkipWhite();
-                       if (_inIntersection) {
-                               int savePointer = _pointer;
-
-                               // Don't getChar() as the space has already been eaten and recorded by SkipWhite().
-                               try {
-                                       ParseNode other = comparisonExpression();
-                                       result = new ParseNode(IntersectionPtg.instance, result, other);
-                                       hasIntersections = true;
-                                       continue;
-                               } catch (FormulaParseException e) {
-                                       // if parsing for intersection fails we assume that we actually had an arbitrary
-                                       // whitespace and thus should simply skip this whitespace
-                                       resetPointer(savePointer);
-                               }
-                       }
-                       if (hasIntersections) {
-                               return augmentWithMemPtg(result);
-                       }
-                       return result;
-               }
-       }
-       
-       private ParseNode comparisonExpression() {
-               ParseNode result = concatExpression();
-               while (true) {
-                       SkipWhite();
-                       switch(look) {
-                               case '=':
-                               case '>':
-                               case '<':
-                                       Ptg comparisonToken = getComparisonToken();
-                                       ParseNode other = concatExpression();
-                                       result = new ParseNode(comparisonToken, result, other);
-                                       continue;
-                       }
-                       return result; // finished with predicate expression
-               }
-       }
-
-       private Ptg getComparisonToken() {
-               if(look == '=') {
-                       Match(look);
-                       return EqualPtg.instance;
-               }
-               boolean isGreater = look == '>';
-               Match(look);
-               if(isGreater) {
-                       if(look == '=') {
-                               Match('=');
-                               return GreaterEqualPtg.instance;
-                       }
-                       return GreaterThanPtg.instance;
-               }
-               switch(look) {
-                       case '=':
-                               Match('=');
-                               return LessEqualPtg.instance;
-                       case '>':
-                               Match('>');
-                               return NotEqualPtg.instance;
-               }
-               return LessThanPtg.instance;
-       }
-
-
-       private ParseNode concatExpression() {
-               ParseNode result = additiveExpression();
-               while (true) {
-                       SkipWhite();
-                       if(look != '&') {
-                               break; // finished with concat expression
-                       }
-                       Match('&');
-                       ParseNode other = additiveExpression();
-                       result = new ParseNode(ConcatPtg.instance, result, other);
-               }
-               return result;
-       }
-
-
-       /** Parse and Translate an Expression */
-       private ParseNode additiveExpression() {
-               ParseNode result = Term();
-               while (true) {
-                       SkipWhite();
-                       Ptg operator;
-                       switch(look) {
-                               case '+':
-                                       Match('+');
-                                       operator = AddPtg.instance;
-                                       break;
-                               case '-':
-                                       Match('-');
-                                       operator = SubtractPtg.instance;
-                                       break;
-                               default:
-                                       return result; // finished with additive expression
-                       }
-                       ParseNode other = Term();
-                       result = new ParseNode(operator, result, other);
-               }
-       }
-
-       //{--------------------------------------------------------------}
-       //{ Parse and Translate an Assignment Statement }
-       /**
-procedure Assignment;
-var Name: string[8];
-begin
-   Name := GetName;
-   Match('=');
-   Expression;
+    /**
+     * Matches a zero or one letter-runs followed by zero or one digit-runs.
+     * Either or both runs man optionally be prefixed with a single '$'.
+     * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN})
+     */
+    private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?");
 
-end;
-        **/
-
-
-       /**
-        *  API call to execute the parsing of the formula
-        *
-        */
-       private void parse() {
-               _pointer=0;
-               GetChar();
-               _rootNode = unionExpression();
-
-               if(_pointer <= _formulaLength) {
-                       String msg = "Unused input [" + _formulaString.substring(_pointer-1)
-                               + "] after attempting to parse the formula [" + _formulaString + "]";
-                       throw new FormulaParseException(msg);
-               }
-       }
-
-       private Ptg[] getRPNPtg(int formulaType) {
-               OperandClassTransformer oct = new OperandClassTransformer(formulaType);
-               // RVA is for 'operand class': 'reference', 'value', 'array'
-               oct.transformFormula(_rootNode);
-               return ParseNode.toTokenArray(_rootNode);
-       }
+    /**
+     * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef.  Normally these are
+     * proper cell references but they could also be row or column refs like "$AC" or "10"
+     * @return <code>null</code> (and leaves {@link #_pointer} unchanged if a proper range part does not parse out
+     */
+    private SimpleRangePart parseSimpleRangePart() {
+        int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds
+        boolean hasDigits = false;
+        boolean hasLetters = false;
+        while (ptr < _formulaLength) {
+            char ch = _formulaString.charAt(ptr);
+            if (Character.isDigit(ch)) {
+                hasDigits = true;
+            } else if (Character.isLetter(ch)) {
+                hasLetters = true;
+            } else if (ch =='$' || ch =='_') {
+                //
+            } else {
+                break;
+            }
+            ptr++;
+        }
+        if (ptr <= _pointer-1) {
+            return null;
+        }
+        String rep = _formulaString.substring(_pointer-1, ptr);
+        if (!CELL_REF_PATTERN.matcher(rep).matches()) {
+            return null;
+        }
+        // Check range bounds against grid max
+        if (hasLetters && hasDigits) {
+            if (!isValidCellReference(rep)) {
+                return null;
+            }
+        } else if (hasLetters) {
+            if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) {
+                return null;
+            }
+        } else if (hasDigits) {
+            int i;
+            try {
+                i = Integer.parseInt(rep.replace("$", ""));
+            } catch (NumberFormatException e) {
+                return null;
+            }
+            if (i<1 || i>_ssVersion.getMaxRows()) {
+                return null;
+            }
+        } else {
+            // just dollars ? can this happen?
+            return null;
+        }
+
+
+        resetPointer(ptr+1); // stepping forward
+        return new SimpleRangePart(rep, hasLetters, hasDigits);
+    }
+
+
+    /**
+     * A1, $A1, A$1, $A$1, A, 1
+     */
+    private static final class SimpleRangePart {
+        private enum Type {
+            CELL, ROW, COLUMN;
+
+            public static Type get(boolean hasLetters, boolean hasDigits) {
+                if (hasLetters) {
+                    return hasDigits ? CELL : COLUMN;
+                }
+                if (!hasDigits) {
+                    throw new IllegalArgumentException("must have either letters or numbers");
+                }
+                return ROW;
+            }
+        }
+
+        private final Type _type;
+        private final String _rep;
+
+        public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) {
+            _rep = rep;
+            _type = Type.get(hasLetters, hasNumbers);
+        }
+
+        public boolean isCell() {
+            return _type == Type.CELL;
+        }
+
+        public boolean isRowOrColumn() {
+            return _type != Type.CELL;
+        }
+
+        public CellReference getCellReference() {
+            if (_type != Type.CELL) {
+                throw new IllegalStateException("Not applicable to this type");
+            }
+            return new CellReference(_rep);
+        }
+
+        public boolean isColumn() {
+            return _type == Type.COLUMN;
+        }
+
+        public boolean isRow() {
+            return _type == Type.ROW;
+        }
+
+        public String getRep() {
+            return _rep;
+        }
+
+        /**
+         * @return <code>true</code> if the two range parts can be combined in an
+         * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid
+         * when this method returns <code>false</code> )
+         */
+        public boolean isCompatibleForArea(SimpleRangePart part2) {
+            return _type == part2._type;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(64);
+            sb.append(getClass().getName()).append(" [");
+            sb.append(_rep);
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Note - caller should reset {@link #_pointer} upon <code>null</code> result
+     * @return The sheet name as an identifier <code>null</code> if '!' is not found in the right place
+     */
+    private SheetIdentifier parseSheetName() {
+        String bookName;
+        if (look == '[') {
+            StringBuilder sb = new StringBuilder();
+            GetChar();
+            while (look != ']') {
+                sb.append(look);
+                GetChar();
+            }
+            GetChar();
+            bookName = sb.toString();
+        } else {
+            bookName = null;
+        }
+
+        if (look == '\'') {
+            StringBuffer sb = new StringBuffer();
+
+            Match('\'');
+            boolean done = look == '\'';
+            while(!done) {
+                sb.append(look);
+                GetChar();
+                if(look == '\'')
+                {
+                    Match('\'');
+                    done = look != '\'';
+                }
+            }
+
+            NameIdentifier iden = new NameIdentifier(sb.toString(), true);
+            // quoted identifier - can't concatenate anything more
+            SkipWhite();
+            if (look == '!') {
+                GetChar();
+                return new SheetIdentifier(bookName, iden);
+            }
+            // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
+            if (look == ':') {
+                return parseSheetRange(bookName, iden);
+            }
+            return null;
+        }
+
+        // unquoted sheet names must start with underscore or a letter
+        if (look =='_' || Character.isLetter(look)) {
+            StringBuilder sb = new StringBuilder();
+            // can concatenate idens with dots
+            while (isUnquotedSheetNameChar(look)) {
+                sb.append(look);
+                GetChar();
+            }
+            NameIdentifier iden = new NameIdentifier(sb.toString(), false);
+            SkipWhite();
+            if (look == '!') {
+                GetChar();
+                return new SheetIdentifier(bookName, iden);
+            }
+            // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
+            if (look == ':') {
+                return parseSheetRange(bookName, iden);
+            }
+            return null;
+        }
+        if (look == '!' && bookName != null) {
+            // Raw book reference, without a sheet
+            GetChar();
+            return new SheetIdentifier(bookName, null);
+        }
+        return null;
+    }
+    
+    /**
+     * If we have something that looks like [book]Sheet1: or 
+     *  Sheet1, see if it's actually a range eg Sheet1:Sheet2!
+     */
+    private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) {
+        GetChar();
+        SheetIdentifier sheet2 = parseSheetName();
+        if (sheet2 != null) {
+           return new SheetRangeIdentifier(bookname, sheet1Name, sheet2.getSheetIdentifier());
+        }
+        return null;
+    }
+
+    /**
+     * very similar to {@link SheetNameFormatter#isSpecialChar(char)}
+     */
+    private static boolean isUnquotedSheetNameChar(char ch) {
+        if(Character.isLetterOrDigit(ch)) {
+            return true;
+        }
+        switch(ch) {
+            case '.': // dot is OK
+            case '_': // underscore is OK
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return <code>true</code> if the specified name is a valid cell reference
+     */
+    private boolean isValidCellReference(String str) {
+        //check range bounds against grid max
+        boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL;
+
+        if(result){
+            /**
+             * Check if the argument is a function. Certain names can be either a cell reference or a function name
+             * depending on the contenxt. Compare the following examples in Excel 2007:
+             * (a) LOG10(100) + 1
+             * (b) LOG10 + 1
+             * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference
+             */
+            boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null;
+            if(isFunc){
+                int savePointer = _pointer;
+                resetPointer(_pointer + str.length());
+                SkipWhite();
+                // open bracket indicates that the argument is a function,
+                // the returning value should be false, i.e. "not a valid cell reference"
+                result = look != '(';
+                resetPointer(savePointer);
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Note - Excel function names are 'case aware but not case sensitive'.  This method may end
+     * up creating a defined name record in the workbook if the specified name is not an internal
+     * Excel function, and has not been encountered before.
+     * 
+     * Side effect: creates workbook name if name is not recognized (name is probably a UDF)
+     *
+     * @param name case preserved function name (as it was entered/appeared in the formula).
+     */
+    private ParseNode function(String name) {
+        Ptg nameToken = null;
+        if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
+            // user defined function
+            // in the token tree, the name is more or less the first argument
+
+            if (_book == null) {
+                // Only test cases omit the book (expecting it not to be needed)
+                throw new IllegalStateException("Need book to evaluate name '" + name + "'");
+            }
+            // Check to see if name is a named range in the workbook
+            EvaluationName hName = _book.getName(name, _sheetIndex);
+            if (hName != null) {
+                if (!hName.isFunctionName()) {
+                    throw new FormulaParseException("Attempt to use name '" + name
+                            + "' as a function, but defined name in workbook does not refer to a function");
+                }
+    
+                // calls to user-defined functions within the workbook
+                // get a Name token which points to a defined name record
+                nameToken = hName.createPtg();
+            } else {
+                // Check if name is an external names table
+                nameToken = _book.getNameXPtg(name, null);
+                if (nameToken == null) {
+                    // name is not an internal or external name
+                    if (log.check(POILogger.WARN)) {
+                        log.log(POILogger.WARN,
+                                "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook.");
+                    }
+                    // name is probably the name of an unregistered User-Defined Function
+                    switch (_book.getSpreadsheetVersion()) {
+                        case EXCEL97:
+                            // HSSFWorkbooks require a name to be added to Workbook defined names table
+                            addName(name);
+                            hName = _book.getName(name, _sheetIndex);
+                            nameToken = hName.createPtg();
+                            break;
+                        case EXCEL2007:
+                            // XSSFWorkbooks store formula names as strings.
+                            nameToken = new NameXPxg(name);
+                            break;
+                        default:
+                            throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name());
+                    }
+                }
+            }
+        }
+
+        Match('(');
+        ParseNode[] args = Arguments();
+        Match(')');
+
+        return getFunction(name, nameToken, args);
+    }
+    
+    /**
+     * Adds a name (named range or user defined function) to underlying workbook's names table
+     * @param functionName
+     */
+    private final void addName(String functionName) {
+        final Name name = _book.createName();
+        name.setFunction(true);
+        name.setNameName(functionName);
+        name.setSheetIndex(_sheetIndex);
+    }
+
+    /**
+     * Generates the variable function ptg for the formula.
+     * <p>
+     * For IF Formulas, additional PTGs are added to the tokens
+     * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
+     * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
+     */
+    private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
+
+        FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
+        int numArgs = args.length;
+        if(fm == null) {
+            if (namePtg == null) {
+                throw new IllegalStateException("NamePtg must be supplied for external functions");
+            }
+            // must be external function
+            ParseNode[] allArgs = new ParseNode[numArgs+1];
+            allArgs[0] = new ParseNode(namePtg);
+            System.arraycopy(args, 0, allArgs, 1, numArgs);
+            return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs);
+        }
+
+        if (namePtg != null) {
+            throw new IllegalStateException("NamePtg no applicable to internal functions");
+        }
+        boolean isVarArgs = !fm.hasFixedArgsLength();
+        int funcIx = fm.getIndex();
+        if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
+            // Excel encodes the sum of a single argument as tAttrSum
+            // POI does the same for consistency, but this is not critical
+            return new ParseNode(AttrPtg.getSumSingle(), args);
+            // The code below would encode tFuncVar(SUM) which seems to do no harm
+        }
+        validateNumArgs(args.length, fm);
+
+        AbstractFunctionPtg retval;
+        if(isVarArgs) {
+            retval = FuncVarPtg.create(name, numArgs);
+        } else {
+            retval = FuncPtg.create(funcIx);
+        }
+        return new ParseNode(retval, args);
+    }
+
+    private void validateNumArgs(int numArgs, FunctionMetadata fm) {
+        if(numArgs < fm.getMinParams()) {
+            String msg = "Too few arguments to function '" + fm.getName() + "'. ";
+            if(fm.hasFixedArgsLength()) {
+                msg += "Expected " + fm.getMinParams();
+            } else {
+                msg += "At least " + fm.getMinParams() + " were expected";
+            }
+            msg += " but got " + numArgs + ".";
+            throw new FormulaParseException(msg);
+        }
+        //the maximum number of arguments depends on the Excel version
+        int maxArgs;
+        if (fm.hasUnlimitedVarags()) {
+            if(_book != null) {
+                maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
+            } else {
+                //_book can be omitted by test cases
+                maxArgs = fm.getMaxParams(); // just use BIFF8
+            }
+        } else {
+            maxArgs = fm.getMaxParams();
+        }
+
+        if(numArgs > maxArgs) {
+            String msg = "Too many arguments to function '" + fm.getName() + "'. ";
+            if(fm.hasFixedArgsLength()) {
+                msg += "Expected " + maxArgs;
+            } else {
+                msg += "At most " + maxArgs + " were expected";
+            }
+            msg += " but got " + numArgs + ".";
+            throw new FormulaParseException(msg);
+       }
+    }
+
+    private static boolean isArgumentDelimiter(char ch) {
+        return ch ==  ',' || ch == ')';
+    }
+
+    /** get arguments to a function */
+    private ParseNode[] Arguments() {
+        //average 2 args per function
+        List<ParseNode> temp = new ArrayList<ParseNode>(2);
+        SkipWhite();
+        if(look == ')') {
+            return ParseNode.EMPTY_ARRAY;
+        }
+
+        boolean missedPrevArg = true;
+        while (true) {
+            SkipWhite();
+            if (isArgumentDelimiter(look)) {
+                if (missedPrevArg) {
+                    temp.add(new ParseNode(MissingArgPtg.instance));
+                }
+                if (look == ')') {
+                    break;
+                }
+                Match(',');
+                missedPrevArg = true;
+                continue;
+            }
+            temp.add(comparisonExpression());
+            missedPrevArg = false;
+            SkipWhite();
+            if (!isArgumentDelimiter(look)) {
+                throw expected("',' or ')'");
+            }
+        }
+        ParseNode[] result = new ParseNode[temp.size()];
+        temp.toArray(result);
+        return result;
+    }
+
+   /** Parse and Translate a Math Factor  */
+    private ParseNode powerFactor() {
+        ParseNode result = percentFactor();
+        while(true) {
+            SkipWhite();
+            if(look != '^') {
+                return result;
+            }
+            Match('^');
+            ParseNode other = percentFactor();
+            result = new ParseNode(PowerPtg.instance, result, other);
+        }
+    }
+
+    private ParseNode percentFactor() {
+        ParseNode result = parseSimpleFactor();
+        while(true) {
+            SkipWhite();
+            if(look != '%') {
+                return result;
+            }
+            Match('%');
+            result = new ParseNode(PercentPtg.instance, result);
+        }
+    }
+
+
+    /**
+     * factors (without ^ or % )
+     */
+    private ParseNode parseSimpleFactor() {
+        SkipWhite();
+        switch(look) {
+            case '#':
+                return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
+            case '-':
+                Match('-');
+                return parseUnary(false);
+            case '+':
+                Match('+');
+                return parseUnary(true);
+            case '(':
+                Match('(');
+                ParseNode inside = unionExpression();
+                Match(')');
+                return new ParseNode(ParenthesisPtg.instance, inside);
+            case '"':
+                return new ParseNode(new StringPtg(parseStringLiteral()));
+            case '{':
+                Match('{');
+                ParseNode arrayNode = parseArray();
+                Match('}');
+                return arrayNode;
+        }
+        // named ranges and tables can start with underscore or backslash
+        // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names
+        if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) {
+            return parseRangeExpression();
+        }
+        if (look == '.') {
+            return new ParseNode(parseNumber());
+        }
+        throw expected("cell ref or constant literal");
+    }
+
+
+    private ParseNode parseUnary(boolean isPlus) {
+
+        boolean numberFollows = IsDigit(look) || look=='.';
+        ParseNode factor = powerFactor();
+
+        if (numberFollows) {
+            // + or - directly next to a number is parsed with the number
+
+            Ptg token = factor.getToken();
+            if (token instanceof NumberPtg) {
+                if (isPlus) {
+                    return factor;
+                }
+                token = new NumberPtg(-((NumberPtg)token).getValue());
+                return new ParseNode(token);
+            }
+            if (token instanceof IntPtg) {
+                if (isPlus) {
+                    return factor;
+                }
+                int intVal = ((IntPtg)token).getValue();
+                // note - cannot use IntPtg for negatives
+                token = new NumberPtg(-intVal);
+                return new ParseNode(token);
+            }
+        }
+        return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor);
+    }
+
+    private ParseNode parseArray() {
+        List<Object[]> rowsData = new ArrayList<Object[]>();
+        while(true) {
+            Object[] singleRowData = parseArrayRow();
+            rowsData.add(singleRowData);
+            if (look == '}') {
+                break;
+            }
+            if (look != ';') {
+                throw expected("'}' or ';'");
+            }
+            Match(';');
+        }
+        int nRows = rowsData.size();
+        Object[][] values2d = new Object[nRows][];
+        rowsData.toArray(values2d);
+        int nColumns = values2d[0].length;
+        checkRowLengths(values2d, nColumns);
+
+        return new ParseNode(new ArrayPtg(values2d));
+    }
+    private void checkRowLengths(Object[][] values2d, int nColumns) {
+        for (int i = 0; i < values2d.length; i++) {
+            int rowLen = values2d[i].length;
+            if (rowLen != nColumns) {
+                throw new FormulaParseException("Array row " + i + " has length " + rowLen
+                        + " but row 0 has length " + nColumns);
+            }
+        }
+    }
+
+    private Object[] parseArrayRow() {
+        List<Object> temp = new ArrayList<Object>();
+        while (true) {
+            temp.add(parseArrayItem());
+            SkipWhite();
+            switch(look) {
+                case '}':
+                case ';':
+                    break;
+                case ',':
+                    Match(',');
+                    continue;
+                default:
+                    throw expected("'}' or ','");
+
+            }
+            break;
+        }
+
+        Object[] result = new Object[temp.size()];
+        temp.toArray(result);
+        return result;
+    }
+
+    private Object parseArrayItem() {
+        SkipWhite();
+        switch(look) {
+            case '"': return parseStringLiteral();
+            case '#': return ErrorConstant.valueOf(parseErrorLiteral());
+            case 'F': case 'f':
+            case 'T': case 't':
+                return parseBooleanLiteral();
+            case '-':
+                Match('-');
+                SkipWhite();
+                return convertArrayNumber(parseNumber(), false);
+        }
+        // else assume number
+        return convertArrayNumber(parseNumber(), true);
+    }
+
+    private Boolean parseBooleanLiteral() {
+        String iden = parseUnquotedIdentifier();
+        if ("TRUE".equalsIgnoreCase(iden)) {
+            return Boolean.TRUE;
+        }
+        if ("FALSE".equalsIgnoreCase(iden)) {
+            return Boolean.FALSE;
+        }
+        throw expected("'TRUE' or 'FALSE'");
+    }
+
+    private static Double convertArrayNumber(Ptg ptg, boolean isPositive) {
+        double value;
+        if (ptg instanceof IntPtg) {
+            value = ((IntPtg)ptg).getValue();
+        } else  if (ptg instanceof NumberPtg) {
+            value = ((NumberPtg)ptg).getValue();
+        } else {
+            throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
+        }
+        if (!isPositive) {
+            value = -value;
+        }
+        return new Double(value);
+    }
+
+    private Ptg parseNumber() {
+        String number2 = null;
+        String exponent = null;
+        String number1 = GetNum();
+
+        if (look == '.') {
+            GetChar();
+            number2 = GetNum();
+        }
+
+        if (look == 'E') {
+            GetChar();
+
+            String sign = "";
+            if (look == '+') {
+                GetChar();
+            } else if (look == '-') {
+                GetChar();
+                sign = "-";
+            }
+
+            String number = GetNum();
+            if (number == null) {
+                throw expected("Integer");
+            }
+            exponent = sign + number;
+        }
+
+        if (number1 == null && number2 == null) {
+            throw expected("Integer");
+        }
+
+        return getNumberPtgFromString(number1, number2, exponent);
+    }
+
+
+    private int parseErrorLiteral() {
+        Match('#');
+        String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT);
+        if (part1 == null) {
+            throw expected("remainder of error constant literal");
+        }
+
+        switch(part1.charAt(0)) {
+            case 'V': {
+                FormulaError fe = FormulaError.VALUE;
+                if(part1.equals(fe.name())) {
+                    Match('!');
+                    return fe.getCode();
+                }
+                throw expected(fe.getString());
+            }
+            case 'R': {
+                FormulaError fe = FormulaError.REF;
+                if(part1.equals(fe.name())) {
+                    Match('!');
+                    return fe.getCode();
+                }
+                throw expected(fe.getString());
+            }
+            case 'D': {
+                FormulaError fe = FormulaError.DIV0;
+                if(part1.equals("DIV")) {
+                    Match('/');
+                    Match('0');
+                    Match('!');
+                    return fe.getCode();
+                }
+                throw expected(fe.getString());
+            }
+            case 'N': {
+                FormulaError fe = FormulaError.NAME;
+                if(part1.equals(fe.name())) {
+                    // only one that ends in '?'
+                    Match('?');
+                    return fe.getCode();
+                }
+                fe = FormulaError.NUM;
+                if(part1.equals(fe.name())) {
+                    Match('!');
+                    return fe.getCode();
+                }
+                fe = FormulaError.NULL;
+                if(part1.equals(fe.name())) {
+                    Match('!');
+                    return fe.getCode();
+                }
+                fe = FormulaError.NA;
+                if(part1.equals("N")) {
+                    Match('/');
+                    if(look != 'A' && look != 'a') {
+                        throw expected(fe.getString());
+                    }
+                    Match(look);
+                    // Note - no '!' or '?' suffix
+                    return fe.getCode();
+                }
+                throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+            }
+        }
+        throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+    }
+
+    private String parseUnquotedIdentifier() {
+        if (look == '\'') {
+            throw expected("unquoted identifier");
+        }
+        StringBuilder sb = new StringBuilder();
+        while (Character.isLetterOrDigit(look) || look == '.') {
+            sb.append(look);
+            GetChar();
+        }
+        if (sb.length() < 1) {
+            return null;
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Get a PTG for an integer from its string representation.
+     * return Int or Number Ptg based on size of input
+     */
+    private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+        StringBuffer number = new StringBuffer();
+
+        if (number2 == null) {
+            number.append(number1);
+
+            if (exponent != null) {
+                number.append('E');
+                number.append(exponent);
+            }
+
+            String numberStr = number.toString();
+            int intVal;
+            try {
+                intVal = Integer.parseInt(numberStr);
+            } catch (NumberFormatException e) {
+                return new NumberPtg(numberStr);
+            }
+            if (IntPtg.isInRange(intVal)) {
+                return new IntPtg(intVal);
+            }
+            return new NumberPtg(numberStr);
+        }
+
+        if (number1 != null) {
+            number.append(number1);
+        }
+
+        number.append('.');
+        number.append(number2);
+
+        if (exponent != null) {
+            number.append('E');
+            number.append(exponent);
+        }
+
+        return new NumberPtg(number.toString());
+    }
+
+
+    private String parseStringLiteral() {
+        Match('"');
+
+        StringBuffer token = new StringBuffer();
+        while (true) {
+            if (look == '"') {
+                GetChar();
+                if (look != '"') {
+                    break;
+                }
+             }
+            token.append(look);
+            GetChar();
+        }
+        return token.toString();
+    }
+
+    /** Parse and Translate a Math Term */
+    private ParseNode  Term() {
+        ParseNode result = powerFactor();
+        while(true) {
+            SkipWhite();
+            Ptg operator;
+            switch(look) {
+                case '*':
+                    Match('*');
+                    operator = MultiplyPtg.instance;
+                    break;
+                case '/':
+                    Match('/');
+                    operator = DividePtg.instance;
+                    break;
+                default:
+                    return result; // finished with Term
+            }
+            ParseNode other = powerFactor();
+            result = new ParseNode(operator, result, other);
+        }
+    }
+
+    private ParseNode unionExpression() {
+        ParseNode result = intersectionExpression();
+        boolean hasUnions = false;
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case ',':
+                    GetChar();
+                    hasUnions = true;
+                    ParseNode other = intersectionExpression();
+                    result = new ParseNode(UnionPtg.instance, result, other);
+                    continue;
+            }
+            if (hasUnions) {
+                return augmentWithMemPtg(result);
+            }
+            return result;
+        }
+    }
+
+   private ParseNode intersectionExpression() {
+        ParseNode result = comparisonExpression();
+        boolean hasIntersections = false;
+        while (true) {
+            SkipWhite();
+            if (_inIntersection) {
+                int savePointer = _pointer;
+
+                // Don't getChar() as the space has already been eaten and recorded by SkipWhite().
+                try {
+                    ParseNode other = comparisonExpression();
+                    result = new ParseNode(IntersectionPtg.instance, result, other);
+                    hasIntersections = true;
+                    continue;
+                } catch (FormulaParseException e) {
+                    // if parsing for intersection fails we assume that we actually had an arbitrary
+                    // whitespace and thus should simply skip this whitespace
+                    resetPointer(savePointer);
+                }
+            }
+            if (hasIntersections) {
+                return augmentWithMemPtg(result);
+            }
+            return result;
+        }
+    }
+    
+    private ParseNode comparisonExpression() {
+        ParseNode result = concatExpression();
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case '=':
+                case '>':
+                case '<':
+                    Ptg comparisonToken = getComparisonToken();
+                    ParseNode other = concatExpression();
+                    result = new ParseNode(comparisonToken, result, other);
+                    continue;
+            }
+            return result; // finished with predicate expression
+        }
+    }
+
+    private Ptg getComparisonToken() {
+        if(look == '=') {
+            Match(look);
+            return EqualPtg.instance;
+        }
+        boolean isGreater = look == '>';
+        Match(look);
+        if(isGreater) {
+            if(look == '=') {
+                Match('=');
+                return GreaterEqualPtg.instance;
+            }
+            return GreaterThanPtg.instance;
+        }
+        switch(look) {
+            case '=':
+                Match('=');
+                return LessEqualPtg.instance;
+            case '>':
+                Match('>');
+                return NotEqualPtg.instance;
+        }
+        return LessThanPtg.instance;
+    }
+
+
+    private ParseNode concatExpression() {
+        ParseNode result = additiveExpression();
+        while (true) {
+            SkipWhite();
+            if(look != '&') {
+                break; // finished with concat expression
+            }
+            Match('&');
+            ParseNode other = additiveExpression();
+            result = new ParseNode(ConcatPtg.instance, result, other);
+        }
+        return result;
+    }
+
+
+    /** Parse and Translate an Expression */
+    private ParseNode additiveExpression() {
+        ParseNode result = Term();
+        while (true) {
+            SkipWhite();
+            Ptg operator;
+            switch(look) {
+                case '+':
+                    Match('+');
+                    operator = AddPtg.instance;
+                    break;
+                case '-':
+                    Match('-');
+                    operator = SubtractPtg.instance;
+                    break;
+                default:
+                    return result; // finished with additive expression
+            }
+            ParseNode other = Term();
+            result = new ParseNode(operator, result, other);
+        }
+    }
+
+    //{--------------------------------------------------------------}
+    //{ Parse and Translate an Assignment Statement }
+    /**
+procedure Assignment;
+var Name: string[8];
+begin
+   Name := GetName;
+   Match('=');
+   Expression;
+
+end;
+     **/
+
+
+    /**
+     *  API call to execute the parsing of the formula
+     *
+     */
+    private void parse() {
+        _pointer=0;
+        GetChar();
+        _rootNode = unionExpression();
+
+        if(_pointer <= _formulaLength) {
+            String msg = "Unused input [" + _formulaString.substring(_pointer-1)
+                + "] after attempting to parse the formula [" + _formulaString + "]";
+            throw new FormulaParseException(msg);
+        }
+    }
+
+    private Ptg[] getRPNPtg(int formulaType) {
+        OperandClassTransformer oct = new OperandClassTransformer(formulaType);
+        // RVA is for 'operand class': 'reference', 'value', 'array'
+        oct.transformFormula(_rootNode);
+        return ParseNode.toTokenArray(_rootNode);
+    }
 }