1    
2    /* ====================================================================
3     * The Apache Software License, Version 1.1
4     *
5     * Copyright (c) 2002 The Apache Software Foundation.  All rights
6     * reserved.
7     *
8     * Redistribution and use in source and binary forms, with or without
9     * modification, are permitted provided that the following conditions
10    * are met:
11    *
12    * 1. Redistributions of source code must retain the above copyright
13    *    notice, this list of conditions and the following disclaimer.
14    *
15    * 2. Redistributions in binary form must reproduce the above copyright
16    *    notice, this list of conditions and the following disclaimer in
17    *    the documentation and/or other materials provided with the
18    *    distribution.
19    *
20    * 3. The end-user documentation included with the redistribution,
21    *    if any, must include the following acknowledgment:
22    *       "This product includes software developed by the
23    *        Apache Software Foundation (http://www.apache.org/)."
24    *    Alternately, this acknowledgment may appear in the software itself,
25    *    if and wherever such third-party acknowledgments normally appear.
26    *
27    * 4. The names "Apache" and "Apache Software Foundation" and
28    *    "Apache POI" must not be used to endorse or promote products
29    *    derived from this software without prior written permission. For
30    *    written permission, please contact apache@apache.org.
31    *
32    * 5. Products derived from this software may not be called "Apache",
33    *    "Apache POI", nor may "Apache" appear in their name, without
34    *    prior written permission of the Apache Software Foundation.
35    *
36    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    * SUCH DAMAGE.
48    * ====================================================================
49    *
50    * This software consists of voluntary contributions made by many
51    * individuals on behalf of the Apache Software Foundation.  For more
52    * information on the Apache Software Foundation, please see
53    * <http://www.apache.org/>.
54    */
55   
56   
57   package org.apache.poi.hssf.record.formula;
58   
59   import java.util.List;
60   import java.util.ArrayList;
61   import java.util.Stack;
62   
63   import java.io.FileOutputStream;
64   import java.io.File;
65   
66   
67   /**
68    * This class parses a formula string into a List of tokens in RPN order
69    * Inspired by 
70    *           Lets Build a Compiler, by Jack Crenshaw
71    * BNF for the formula expression is :
72    * <expression> ::= <term> [<addop> <term>]*
73    * <term> ::= <factor>  [ <mulop> <factor ]*
74    * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
75    * <function> ::= <functionName> ([expression [, expression]*])
76    *
77    *  @author Avik Sengupta <avik AT Avik Sengupta DOT com>
78    *  @author Andrew C. oliver (acoliver at apache dot org)
79    */
80   public class FormulaParser {
81       
82       private String formulaString;
83       private int pointer=0;
84       
85       private List tokens = new java.util.Stack();
86       //private Stack tokens = new java.util.Stack();
87       private List result = new ArrayList();
88       private int numParen;
89       
90       private static char TAB = '\t';
91       private static char CR = '\n';
92       
93      private char Look;              // Lookahead Character 
94       
95       
96       /** create the parser with the string that is to be parsed
97        *    later call the parse() method to return ptg list in rpn order
98        *    then call the getRPNPtg() to retrive the parse results
99        *  This class is recommended only for single threaded use
100       *  The parse and getPRNPtg are internally synchronized for safety, thus
101       *  while it is safe to use in a multithreaded environment, you will get long lock waits.  
102       */
103      public FormulaParser(String formula){
104          formulaString = formula;
105          pointer=0;
106      }
107      
108  
109      /** Read New Character From Input Stream */
110      private void GetChar() {
111          Look=formulaString.charAt(pointer++);
112          //System.out.println("Got char: "+Look);
113      }
114      
115  
116      /** Report an Error */
117      private void Error(String s) {
118          System.out.println("Error: "+s);
119      }
120      
121      
122   
123      /** Report Error and Halt */
124      private void Abort(String s) {
125          Error(s);
126          //System.exit(1);  //throw exception??
127          throw new RuntimeException("Cannot Parse, sorry");
128      }
129      
130      
131  
132      /** Report What Was Expected */
133      private void Expected(String s) {
134          Abort(s + " Expected");
135      }
136      
137      
138   
139      /** Recognize an Alpha Character */
140      private boolean IsAlpha(char c) {
141          return Character.isLetter(c) || c == '$';
142      }
143      
144      
145   
146      /** Recognize a Decimal Digit */
147      private boolean IsDigit(char c) {
148          //System.out.println("Checking digit for"+c);
149          return Character.isDigit(c);
150      }
151      
152      
153  
154      /** Recognize an Alphanumeric */
155      private boolean  IsAlNum(char c) {
156          return  (IsAlpha(c) || IsDigit(c));
157      }
158      
159      
160  
161      /** Recognize an Addop */
162      private boolean IsAddop( char c) {
163          return (c =='+' || c =='-');
164      }
165      
166  
167      /** Recognize White Space */
168      private boolean IsWhite( char c) {
169          return  (c ==' ' || c== TAB);
170      }
171      
172      
173  
174      /** Skip Over Leading White Space */
175      private void SkipWhite() {
176          while (IsWhite(Look)) {
177              GetChar();
178          }
179      }
180      
181      
182  
183      /** Match a Specific Input Character */
184      private void Match(char x) {
185          if (Look != x) {
186              Expected("" + x + "");
187          }else {
188              GetChar();
189              SkipWhite();
190          }
191      }
192      
193      
194      /** Get an Identifier */
195      private String GetName() {
196          String Token;
197          Token = "";
198          if (!IsAlpha(Look)) {
199              Expected("Name");
200          }
201          while (IsAlNum(Look)) {
202              Token = Token + Character.toUpperCase(Look);
203              GetChar();
204          }
205          
206          SkipWhite();
207          return Token;
208      }
209      
210      
211      /** Get a Number */
212      private String GetNum() {
213          String Value ="";
214          if  (!IsDigit(Look)) Expected("Integer");
215          while (IsDigit(Look)){
216              Value = Value + Look;
217              GetChar();
218          }
219          SkipWhite();
220          return Value;
221      }
222  
223      /** Output a String with Tab */
224      private void  Emit(String s){
225          System.out.print(TAB+s);
226      }
227  
228      /** Output a String with Tab and CRLF */
229      private void EmitLn(String s) {
230          Emit(s);
231          System.out.println();;
232      }
233      
234      /** Parse and Translate a Identifier */
235      private void Ident() {
236          String name;
237          name = GetName();
238          if (Look == '('){
239              //This is a function 
240              Match('(');
241              int numArgs = Arguments(); 
242              Match(')');
243              //this is the end of the function
244              tokens.add(function(name,(byte)numArgs));
245          } else if (Look == ':') { // this is a AreaReference
246              String first = name;
247              Match(':');
248              String second = GetName();
249              tokens.add(new AreaPtg(first+":"+second));
250          } else {
251              //this can be either a cell ref or a named range !!
252              boolean cellRef = true ; //we should probably do it with reg exp??
253              if (cellRef) {
254                  tokens.add(new ReferencePtg(name)); 
255              }else {
256                  //handle after named range is integrated!!
257              }
258          }
259      }
260      
261      private Ptg function(String name,byte numArgs) {
262          Ptg retval = null;
263          
264          if (numArgs == 1 && name.equals("SUM")) {
265              AttrPtg ptg = new AttrPtg();
266              ptg.setData((short)1); //sums don't care but this is what excel does.
267              ptg.setSum(true);
268              retval = ptg;
269          } else {
270              FunctionPtg ptg = new FunctionPtg(name,numArgs);
271              retval = ptg;
272          }
273          
274          return retval;
275      }
276      
277      /** get arguments to a function */
278      private int Arguments() {
279          int numArgs = 0;
280          if (Look != ')')  {
281              numArgs++; 
282              Expression();
283          }
284          while (Look == ',') { //TODO handle EmptyArgs
285              Match(',');
286              Expression();
287              numArgs++;
288          }
289          return numArgs;
290      }
291  
292     /** Parse and Translate a Math Factor  */
293      private void Factor() {
294          if (Look == '(' ) {
295              Match('(');
296              Expression();
297              Match(')');
298              tokens.add(new ParenthesisPtg());
299              return;
300          } else if (IsAlpha(Look)){
301              Ident();
302          }else{
303              String number = GetNum();
304              if (Look=='.') { 
305                  Match('.');
306                  String decimalPart = null;
307                  if (IsDigit(Look)) number = number +"."+ GetNum(); //this also takes care of someone entering "1234."
308                  tokens.add(new NumberPtg(number));
309              } else {
310                  tokens.add(new IntPtg(number));  //TODO:what if the number is too big to be a short? ..add factory to return Int or Number!
311              }
312          }
313      }
314  
315      
316      /** Recognize and Translate a Multiply */
317      private void Multiply(){
318          Match('*');
319          Factor();
320          tokens.add(new MultiplyPtg());
321    
322      }
323      
324      
325      /** Recognize and Translate a Divide */
326      private void Divide() {
327          Match('/');
328          Factor();
329          tokens.add(new DividePtg());
330  
331      }
332      
333      
334      /** Parse and Translate a Math Term */
335      private void  Term(){
336          Factor();
337          while (Look == '*' || Look == '/' || Look == '^' || Look == '&') {
338              ///TODO do we need to do anything here??
339              if (Look == '*') Multiply();
340              if (Look == '/') Divide();
341              if (Look == '^') Power();
342              if (Look == '&') Concat();
343          }
344      }
345      
346      /** Recognize and Translate an Add */
347      private void Add() {
348          Match('+');
349          Term();
350          tokens.add(new AddPtg());
351      }
352      
353      /** Recognize and Translate an Add */
354      private void Concat() {
355          Match('&');
356          Term();
357          tokens.add(new ConcatPtg());
358      }
359      
360      
361      
362      /** Recognize and Translate a Subtract */
363      private void Subtract() {
364          Match('-');
365          Term();
366          tokens.add(new SubtractPtg());
367      }
368      
369      private void Power() {
370          Match('^');
371          Term();
372          tokens.add(new PowerPtg());
373      }
374      
375      
376      /** Parse and Translate an Expression */
377      private void Expression() {
378          if (IsAddop(Look)) {
379              EmitLn("CLR D0");  //unaryAdd ptg???
380          } else {
381              Term();
382          }
383          while (IsAddop(Look)) {
384              if ( Look == '+' )  Add();
385              if (Look == '-') Subtract();
386              // if (Look == '*') Multiply();
387             // if (Look == '/') Divide();
388          }
389      }
390      
391      
392      //{--------------------------------------------------------------}
393      //{ Parse and Translate an Assignment Statement }
394      /**
395  procedure Assignment;
396  var Name: string[8];
397  begin
398     Name := GetName;
399     Match('=');
400     Expression;
401  
402  end;
403       **/
404      
405   
406      /** Initialize */
407      
408      private void  init() {
409          GetChar();
410          SkipWhite();
411      }
412      
413      /** API call to execute the parsing of the formula
414       *
415       */
416      public void parse() {
417          synchronized (tokens) {
418              init();
419              Expression();
420          }
421      }
422      
423      /** API call to retrive the array of Ptgs created as 
424       * a result of the parsing
425       */
426      public Ptg[] getRPNPtg() {
427         synchronized (tokens) {
428              if (tokens == null) throw new IllegalStateException("Please parse a string before trying to access the parse result");
429              Ptg[] retval = new Ptg[tokens.size()];
430              return (Ptg[]) tokens.toArray(retval);
431         }
432      }
433    
434      /**
435       * Convience method which takes in a list then passes it to the other toFormulaString
436       * signature
437       */
438      public static String toFormulaString(List lptgs) {
439          String retval = null;
440          Ptg[] ptgs = new Ptg[lptgs.size()];
441          ptgs = (Ptg[])lptgs.toArray(ptgs);
442          retval = toFormulaString(ptgs);
443          return retval;
444      }
445      
446      /** Static method to convert an array of Ptgs in RPN order 
447       *  to a human readable string format in infix mode
448       */
449      public static String toFormulaString(Ptg[] ptgs) {
450          java.util.Stack stack = new java.util.Stack();
451          int numPtgs = ptgs.length;
452          OperationPtg o;
453          int numOperands;
454          String[] operands;
455          for (int i=0;i<numPtgs;i++) {
456              if (ptgs[i] instanceof OperationPtg) {
457                  o = (OperationPtg) ptgs[i];
458                  numOperands = o.getNumberOfOperands();
459                  operands = new String[numOperands];
460                  for (int j=0;j<numOperands;j++) {
461                      operands[numOperands-j-1] = (String) stack.pop(); //TODO: catch stack underflow and throw parse exception. 
462                      
463                  }  
464                  String result = o.toFormulaString(operands);
465                  stack.push(result);
466              } else {
467                  stack.push(ptgs[i].toFormulaString());
468              }
469          }
470          return (String) stack.pop(); //TODO: catch stack underflow and throw parse exception. 
471      }
472     
473      /** toString on the parser instance returns the RPN ordered list of tokens
474       *   Useful for testing
475       */
476      public String toString() {
477          StringBuffer buf = new StringBuffer();
478             for (int i=0;i<tokens.size();i++) {
479              buf.append( ( (Ptg)tokens.get(i)).toFormulaString());
480              buf.append(' ');
481          } 
482          return buf.toString();
483      }
484      
485      
486  }