lxl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +46 -0
 - data/VERSION +1 -0
 - data/lib/lxl.rb +309 -0
 - data/test/lxl_test.rb +27 -0
 - metadata +42 -0
 
    
        data/README
    ADDED
    
    | 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            LXL README
         
     | 
| 
      
 2 
     | 
    
         
            +
            ============
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
         
     | 
| 
      
 5 
     | 
    
         
            +
            Easily extended with new constants and functions.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Usage
         
     | 
| 
      
 8 
     | 
    
         
            +
            -----
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              formulas = %{
         
     | 
| 
      
 11 
     | 
    
         
            +
                ((1+2)*(10-6))/2;
         
     | 
| 
      
 12 
     | 
    
         
            +
                DATETIME("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00);
         
     | 
| 
      
 13 
     | 
    
         
            +
                IN(" is ", "this is a string");
         
     | 
| 
      
 14 
     | 
    
         
            +
                LIST(1, "two", 3.0);
         
     | 
| 
      
 15 
     | 
    
         
            +
                IN("b", LIST("a", "b", "c"));
         
     | 
| 
      
 16 
     | 
    
         
            +
                AND(TRUE, NULL);
         
     | 
| 
      
 17 
     | 
    
         
            +
                OR(TRUE, FALSE);
         
     | 
| 
      
 18 
     | 
    
         
            +
                IF(1+1=2, "yes", "no");
         
     | 
| 
      
 19 
     | 
    
         
            +
              }
         
     | 
| 
      
 20 
     | 
    
         
            +
              
         
     | 
| 
      
 21 
     | 
    
         
            +
              # single formula
         
     | 
| 
      
 22 
     | 
    
         
            +
              puts LXL.eval('5+5').inspect # => 10
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              # multiple formulas separated by semi-colon
         
     | 
| 
      
 25 
     | 
    
         
            +
              puts LXL.eval(formulas).inspect # => [6, true, true, [1, "two", 3.0], true, false, true, "yes"]
         
     | 
| 
      
 26 
     | 
    
         
            +
              
         
     | 
| 
      
 27 
     | 
    
         
            +
              See API docs for more information.
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            Requirements
         
     | 
| 
      
 30 
     | 
    
         
            +
            ------------
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              * Ruby 1.8.2
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            Install
         
     | 
| 
      
 35 
     | 
    
         
            +
            -------
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              gem install lxl
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            License
         
     | 
| 
      
 40 
     | 
    
         
            +
            -------
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              LXL uses John Carter's LittleLexer for parsing.
         
     | 
| 
      
 43 
     | 
    
         
            +
              Distributes under the same terms as LittleLexer.
         
     | 
| 
      
 44 
     | 
    
         
            +
              http://www.rubyforge.org/projects/littlelexer/
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            Kevin Howe <kh@newclear.ca>
         
     | 
    
        data/VERSION
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            0.1.0
         
     | 
    
        data/lib/lxl.rb
    ADDED
    
    | 
         @@ -0,0 +1,309 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Easily extended with new constants and functions.
         
     | 
| 
      
 3 
     | 
    
         
            +
            #
         
     | 
| 
      
 4 
     | 
    
         
            +
            # =Constants
         
     | 
| 
      
 5 
     | 
    
         
            +
            #
         
     | 
| 
      
 6 
     | 
    
         
            +
            #  TRUE  # true
         
     | 
| 
      
 7 
     | 
    
         
            +
            #  FALSE # false
         
     | 
| 
      
 8 
     | 
    
         
            +
            #  NULL  # nil
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
            # =Operators
         
     | 
| 
      
 11 
     | 
    
         
            +
            #
         
     | 
| 
      
 12 
     | 
    
         
            +
            #   a +  b # Add
         
     | 
| 
      
 13 
     | 
    
         
            +
            #   a -  b # Subtract
         
     | 
| 
      
 14 
     | 
    
         
            +
            #   a *  b # Multiply
         
     | 
| 
      
 15 
     | 
    
         
            +
            #   a /  b # Divide
         
     | 
| 
      
 16 
     | 
    
         
            +
            #   a =  b # Equal to
         
     | 
| 
      
 17 
     | 
    
         
            +
            #   a <> b # Not equal to
         
     | 
| 
      
 18 
     | 
    
         
            +
            #   a <  b # Less than
         
     | 
| 
      
 19 
     | 
    
         
            +
            #   a >  b # Greater than
         
     | 
| 
      
 20 
     | 
    
         
            +
            #   a <= b # Less than or equal to
         
     | 
| 
      
 21 
     | 
    
         
            +
            #   a >= b # Greater than or equal to
         
     | 
| 
      
 22 
     | 
    
         
            +
            #
         
     | 
| 
      
 23 
     | 
    
         
            +
            # =Logical Functions
         
     | 
| 
      
 24 
     | 
    
         
            +
            #
         
     | 
| 
      
 25 
     | 
    
         
            +
            #   AND (a,b)
         
     | 
| 
      
 26 
     | 
    
         
            +
            #   OR  (a,b)
         
     | 
| 
      
 27 
     | 
    
         
            +
            #   IF  (cond,true,false)
         
     | 
| 
      
 28 
     | 
    
         
            +
            #
         
     | 
| 
      
 29 
     | 
    
         
            +
            # =Date/Time Functions
         
     | 
| 
      
 30 
     | 
    
         
            +
            #
         
     | 
| 
      
 31 
     | 
    
         
            +
            #  TODAY    ()      # current date value
         
     | 
| 
      
 32 
     | 
    
         
            +
            #  NOW      ()      # current date/time value
         
     | 
| 
      
 33 
     | 
    
         
            +
            #  DATE     (y,m,d) # date value
         
     | 
| 
      
 34 
     | 
    
         
            +
            #  TIME     (h,m,s) # time value
         
     | 
| 
      
 35 
     | 
    
         
            +
            #  DATETIME (value) # convert a date/time string into a date/time value
         
     | 
| 
      
 36 
     | 
    
         
            +
            #
         
     | 
| 
      
 37 
     | 
    
         
            +
            # =List Functions
         
     | 
| 
      
 38 
     | 
    
         
            +
            #
         
     | 
| 
      
 39 
     | 
    
         
            +
            #   LIST (a,b,..)    # Variable length list
         
     | 
| 
      
 40 
     | 
    
         
            +
            #   IN   (find,list) # True if find value is found in the given list (works on strings too)
         
     | 
| 
      
 41 
     | 
    
         
            +
            #
         
     | 
| 
      
 42 
     | 
    
         
            +
            # =Notes
         
     | 
| 
      
 43 
     | 
    
         
            +
            #
         
     | 
| 
      
 44 
     | 
    
         
            +
            #  * The number zero is interpereted as FALSE
         
     | 
| 
      
 45 
     | 
    
         
            +
            #  * Return multiple results in an array by separating formulas with a semi-colon (;)
         
     | 
| 
      
 46 
     | 
    
         
            +
            #
         
     | 
| 
      
 47 
     | 
    
         
            +
            # =Lexical Types
         
     | 
| 
      
 48 
     | 
    
         
            +
            #
         
     | 
| 
      
 49 
     | 
    
         
            +
            #  w  Whitespace (includes Commas)
         
     | 
| 
      
 50 
     | 
    
         
            +
            #  ;  Semi-Colon (statement separator)
         
     | 
| 
      
 51 
     | 
    
         
            +
            #  o  Operator
         
     | 
| 
      
 52 
     | 
    
         
            +
            #  i  Integer
         
     | 
| 
      
 53 
     | 
    
         
            +
            #  f  Float
         
     | 
| 
      
 54 
     | 
    
         
            +
            #  t  Token
         
     | 
| 
      
 55 
     | 
    
         
            +
            #  s  String (single quoted)
         
     | 
| 
      
 56 
     | 
    
         
            +
            #  S  String (double quoted)
         
     | 
| 
      
 57 
     | 
    
         
            +
            #  (  Open (
         
     | 
| 
      
 58 
     | 
    
         
            +
            #  )  Close )
         
     | 
| 
      
 59 
     | 
    
         
            +
            #  C  Constant
         
     | 
| 
      
 60 
     | 
    
         
            +
            #  F  Function
         
     | 
| 
      
 61 
     | 
    
         
            +
            #
         
     | 
| 
      
 62 
     | 
    
         
            +
            module LXL
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              module_function
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
              # Evaluate a formula
         
     | 
| 
      
 67 
     | 
    
         
            +
              #
         
     | 
| 
      
 68 
     | 
    
         
            +
              def eval(formula)
         
     | 
| 
      
 69 
     | 
    
         
            +
                LXL::Parser.eval(formula)
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            class LXL::Parser
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              attr_reader :constants, :functions, :lexer, :tokens, :types
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              RUBY_OPERATORS  = ['+', '-', '*', '/', '<=', '>=', '==', '!=', '<', '>']
         
     | 
| 
      
 79 
     | 
    
         
            +
              EXCEL_OPERATORS = ['+', '-', '*', '/', '<=', '>=', '=',  '<>', '<', '>']
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              # Evaluate a formula
         
     | 
| 
      
 82 
     | 
    
         
            +
              #
         
     | 
| 
      
 83 
     | 
    
         
            +
              def self.eval(formula)
         
     | 
| 
      
 84 
     | 
    
         
            +
                self.new.eval(formula)
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              # Initialize parser
         
     | 
| 
      
 88 
     | 
    
         
            +
              #
         
     | 
| 
      
 89 
     | 
    
         
            +
              def initialize
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                # Constants
         
     | 
| 
      
 92 
     | 
    
         
            +
                @constants = {
         
     | 
| 
      
 93 
     | 
    
         
            +
                  :TRUE  => true,
         
     | 
| 
      
 94 
     | 
    
         
            +
                  :FALSE => false,
         
     | 
| 
      
 95 
     | 
    
         
            +
                  :NULL  => nil,
         
     | 
| 
      
 96 
     | 
    
         
            +
                }
         
     | 
| 
      
 97 
     | 
    
         
            +
                
         
     | 
| 
      
 98 
     | 
    
         
            +
                # Functions
         
     | 
| 
      
 99 
     | 
    
         
            +
                @functions = Hash.new
         
     | 
| 
      
 100 
     | 
    
         
            +
                register(:AND)   { |a,b| to_b(a) && to_b(b) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                register(:OR)    { |a,b| to_b(a) || to_b(b) }
         
     | 
| 
      
 102 
     | 
    
         
            +
                register(:IF)    { |v,a,b| to_b(v) ? a : b }
         
     | 
| 
      
 103 
     | 
    
         
            +
                register(:LIST)  { |*args| args }
         
     | 
| 
      
 104 
     | 
    
         
            +
                register(:IN)    { |n,h| h.respond_to?(:include?) ? h.include?(n) : false }
         
     | 
| 
      
 105 
     | 
    
         
            +
                register(:TODAY) { Date.today.ajd.to_f }
         
     | 
| 
      
 106 
     | 
    
         
            +
                register(:NOW)   { DateTime.now.ajd.to_f }
         
     | 
| 
      
 107 
     | 
    
         
            +
                register(:DATE)  { |y,m,d| Date.new(y,m,d).ajd.to_f }
         
     | 
| 
      
 108 
     | 
    
         
            +
                register(:TIME)  { |h,m,s|
         
     | 
| 
      
 109 
     | 
    
         
            +
                  DateTime.valid_time?(h,m,s) ? DateTime.valid_time?(h,m,s).to_f : raise(ArgumentError, 'invalid time')
         
     | 
| 
      
 110 
     | 
    
         
            +
                }
         
     | 
| 
      
 111 
     | 
    
         
            +
                register(:DATETIME) { |value| DateTime.parse(value).ajd.to_f }
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                # Lexer
         
     | 
| 
      
 114 
     | 
    
         
            +
                ops = EXCEL_OPERATORS.collect { |v| Regexp.quote(v) }.join('|')
         
     | 
| 
      
 115 
     | 
    
         
            +
                #
         
     | 
| 
      
 116 
     | 
    
         
            +
                @lexer = LXL::LittleLexer.new([
         
     | 
| 
      
 117 
     | 
    
         
            +
                                    [/\A[\s,]+/,?w] ,        # Whitespace (includes Commas)
         
     | 
| 
      
 118 
     | 
    
         
            +
                                    [/\A;+/, ?;],            # Semi-Colon (statement separator)
         
     | 
| 
      
 119 
     | 
    
         
            +
                                    [/\A(#{ops})/,?o],       # Operator
         
     | 
| 
      
 120 
     | 
    
         
            +
                                    [/\A[0-9]+\.[0-9]+/,?f], # Float
         
     | 
| 
      
 121 
     | 
    
         
            +
                                    [/\A[0-9]+/,?i],         # Integer
         
     | 
| 
      
 122 
     | 
    
         
            +
                                    [/\A("[^\"]*")/m,?s],    # String (single quoted)
         
     | 
| 
      
 123 
     | 
    
         
            +
                                    [/\A('[^\']*')/m,?S],    # String (double quoted)
         
     | 
| 
      
 124 
     | 
    
         
            +
                                    [/\A\w+/,?t],            # Token
         
     | 
| 
      
 125 
     | 
    
         
            +
                                    [/\A\(/,?(],             # Open (
         
     | 
| 
      
 126 
     | 
    
         
            +
                                    [/\A\)/,?)],             # Close )
         
     | 
| 
      
 127 
     | 
    
         
            +
                                  ], false)
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                # Other
         
     | 
| 
      
 130 
     | 
    
         
            +
                @tokens = Array.new
         
     | 
| 
      
 131 
     | 
    
         
            +
                @types = Array.new
         
     | 
| 
      
 132 
     | 
    
         
            +
              end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
              # Evaluate formula
         
     | 
| 
      
 135 
     | 
    
         
            +
              #
         
     | 
| 
      
 136 
     | 
    
         
            +
              def eval(formula)
         
     | 
| 
      
 137 
     | 
    
         
            +
                tokenize(formula.to_s.strip)
         
     | 
| 
      
 138 
     | 
    
         
            +
                @tokens.pop if @tokens.last == ';'
         
     | 
| 
      
 139 
     | 
    
         
            +
                if @tokens.include?(';')
         
     | 
| 
      
 140 
     | 
    
         
            +
                  expr = [ [] ]
         
     | 
| 
      
 141 
     | 
    
         
            +
                  @tokens.each do |token|
         
     | 
| 
      
 142 
     | 
    
         
            +
                    if token == ';'
         
     | 
| 
      
 143 
     | 
    
         
            +
                      expr << []
         
     | 
| 
      
 144 
     | 
    
         
            +
                    else
         
     | 
| 
      
 145 
     | 
    
         
            +
                      expr.last << token
         
     | 
| 
      
 146 
     | 
    
         
            +
                    end
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
                  expr.collect { |e| Kernel.eval(e.join, binding) }
         
     | 
| 
      
 149 
     | 
    
         
            +
                else
         
     | 
| 
      
 150 
     | 
    
         
            +
                  Kernel.eval(@tokens.join, binding)
         
     | 
| 
      
 151 
     | 
    
         
            +
                end
         
     | 
| 
      
 152 
     | 
    
         
            +
              end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
              protected
         
     | 
| 
      
 155 
     | 
    
         
            +
              
         
     | 
| 
      
 156 
     | 
    
         
            +
              # Register a function
         
     | 
| 
      
 157 
     | 
    
         
            +
              #
         
     | 
| 
      
 158 
     | 
    
         
            +
              # * Converts name to symbol
         
     | 
| 
      
 159 
     | 
    
         
            +
              # * Wraps function with a debugging procedure
         
     | 
| 
      
 160 
     | 
    
         
            +
              #
         
     | 
| 
      
 161 
     | 
    
         
            +
              def register(name, &block)
         
     | 
| 
      
 162 
     | 
    
         
            +
                @functions[name.to_sym] = debug(name.to_sym, &block)
         
     | 
| 
      
 163 
     | 
    
         
            +
              end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
              # Wrap a procedure in a debugging procedure
         
     | 
| 
      
 166 
     | 
    
         
            +
              #
         
     | 
| 
      
 167 
     | 
    
         
            +
              # * Raise an error unless given the correct number of arguments
         
     | 
| 
      
 168 
     | 
    
         
            +
              #
         
     | 
| 
      
 169 
     | 
    
         
            +
              def debug(name, &func)
         
     | 
| 
      
 170 
     | 
    
         
            +
                raise ArgumentError, 'block not given to debug' unless block_given?
         
     | 
| 
      
 171 
     | 
    
         
            +
                Proc.new { |*args|
         
     | 
| 
      
 172 
     | 
    
         
            +
                  if func.arity >= 0 and func.arity != args.size
         
     | 
| 
      
 173 
     | 
    
         
            +
                    raise ArgumentError, "wrong number of arguments (#{args.size} for #{func.arity}) for #{name}"
         
     | 
| 
      
 174 
     | 
    
         
            +
                  end
         
     | 
| 
      
 175 
     | 
    
         
            +
                  func.call(*args)
         
     | 
| 
      
 176 
     | 
    
         
            +
                }
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
              
         
     | 
| 
      
 179 
     | 
    
         
            +
              # Tokenize formula (String => Array)
         
     | 
| 
      
 180 
     | 
    
         
            +
              #
         
     | 
| 
      
 181 
     | 
    
         
            +
              def tokenize(formula)
         
     | 
| 
      
 182 
     | 
    
         
            +
                ops = Hash[*EXCEL_OPERATORS.zip(RUBY_OPERATORS).flatten]
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                # Parse formula
         
     | 
| 
      
 185 
     | 
    
         
            +
                types, @tokens = @lexer.scan(formula)
         
     | 
| 
      
 186 
     | 
    
         
            +
                @types = types.split(//)
         
     | 
| 
      
 187 
     | 
    
         
            +
                raise SyntaxError, 'unbalanced parentheses' unless balanced?
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                # Parse tokens
         
     | 
| 
      
 190 
     | 
    
         
            +
                @tokens.each_index do |i|
         
     | 
| 
      
 191 
     | 
    
         
            +
                  type, token = @types[i], @tokens[i]
         
     | 
| 
      
 192 
     | 
    
         
            +
                  token = token.to_i if type == 'i'
         
     | 
| 
      
 193 
     | 
    
         
            +
                  token = token.to_f if type == 'f'
         
     | 
| 
      
 194 
     | 
    
         
            +
                  if type == 't'
         
     | 
| 
      
 195 
     | 
    
         
            +
                    token = token.to_sym
         
     | 
| 
      
 196 
     | 
    
         
            +
                    if @functions.key?(token)
         
     | 
| 
      
 197 
     | 
    
         
            +
                      if @tokens[i+1] != '('
         
     | 
| 
      
 198 
     | 
    
         
            +
                        raise ArgumentError, "wrong number of arguments for #{token}"
         
     | 
| 
      
 199 
     | 
    
         
            +
                      else
         
     | 
| 
      
 200 
     | 
    
         
            +
                        @types[i] = 'F'
         
     | 
| 
      
 201 
     | 
    
         
            +
                        token = '@functions['+token.inspect+'].call'
         
     | 
| 
      
 202 
     | 
    
         
            +
                      end
         
     | 
| 
      
 203 
     | 
    
         
            +
                    elsif @constants.key?(token)
         
     | 
| 
      
 204 
     | 
    
         
            +
                      @types[i] = 'C'
         
     | 
| 
      
 205 
     | 
    
         
            +
                      token = '@constants['+token.inspect+']'
         
     | 
| 
      
 206 
     | 
    
         
            +
                    else
         
     | 
| 
      
 207 
     | 
    
         
            +
                      raise NameError, "unknown constant #{token}"
         
     | 
| 
      
 208 
     | 
    
         
            +
                    end
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
                  token = ops[token] if EXCEL_OPERATORS.include?(token)
         
     | 
| 
      
 211 
     | 
    
         
            +
                  @tokens[i] = token
         
     | 
| 
      
 212 
     | 
    
         
            +
                end
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                @tokens
         
     | 
| 
      
 215 
     | 
    
         
            +
              end
         
     | 
| 
      
 216 
     | 
    
         
            +
              
         
     | 
| 
      
 217 
     | 
    
         
            +
              # Check that parentheses are balanced
         
     | 
| 
      
 218 
     | 
    
         
            +
              #
         
     | 
| 
      
 219 
     | 
    
         
            +
              def balanced?
         
     | 
| 
      
 220 
     | 
    
         
            +
                @tokens.find_all { |t| ['(', ')'].include?(t) }.size % 2 == 0
         
     | 
| 
      
 221 
     | 
    
         
            +
              end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
              # False if nil, false or zero
         
     | 
| 
      
 224 
     | 
    
         
            +
              #
         
     | 
| 
      
 225 
     | 
    
         
            +
              def to_b(value)
         
     | 
| 
      
 226 
     | 
    
         
            +
                [nil,false,0].include?(value) ? false : true
         
     | 
| 
      
 227 
     | 
    
         
            +
              end
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
      
 229 
     | 
    
         
            +
            end
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
            # John Carter's LittleLexer
         
     | 
| 
      
 232 
     | 
    
         
            +
            #
         
     | 
| 
      
 233 
     | 
    
         
            +
            # http://www.rubyforge.org/projects/littlelexer
         
     | 
| 
      
 234 
     | 
    
         
            +
            #
         
     | 
| 
      
 235 
     | 
    
         
            +
            class LXL::LittleLexer #:nodoc: all
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
              class LexerJammed < Exception; end
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
              def initialize(regex_to_char,skip_white_space = true)
         
     | 
| 
      
 240 
     | 
    
         
            +
                @skip_white_space = skip_white_space
         
     | 
| 
      
 241 
     | 
    
         
            +
                @regex_to_char = regex_to_char
         
     | 
| 
      
 242 
     | 
    
         
            +
              end
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
              def scan(string,string_token_list=nil)
         
     | 
| 
      
 245 
     | 
    
         
            +
                result_string  = ''
         
     | 
| 
      
 246 
     | 
    
         
            +
                token_list = []
         
     | 
| 
      
 247 
     | 
    
         
            +
                
         
     | 
| 
      
 248 
     | 
    
         
            +
                if string_token_list
         
     | 
| 
      
 249 
     | 
    
         
            +
                  next_token(string) do |t,token, tail|
         
     | 
| 
      
 250 
     | 
    
         
            +
                    result_string << t
         
     | 
| 
      
 251 
     | 
    
         
            +
                    token_list << [string_token_list[0...tail], string[0...tail]]
         
     | 
| 
      
 252 
     | 
    
         
            +
                    string = string[tail..-1]
         
     | 
| 
      
 253 
     | 
    
         
            +
                    string_token_list = string_token_list[tail..-1]
         
     | 
| 
      
 254 
     | 
    
         
            +
                  end
         
     | 
| 
      
 255 
     | 
    
         
            +
                else
         
     | 
| 
      
 256 
     | 
    
         
            +
                  next_token(string) do |t,token, tail|
         
     | 
| 
      
 257 
     | 
    
         
            +
                    result_string << t
         
     | 
| 
      
 258 
     | 
    
         
            +
                    token_list << token
         
     | 
| 
      
 259 
     | 
    
         
            +
                  end
         
     | 
| 
      
 260 
     | 
    
         
            +
                end
         
     | 
| 
      
 261 
     | 
    
         
            +
                return result_string, token_list
         
     | 
| 
      
 262 
     | 
    
         
            +
              end
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
      
 264 
     | 
    
         
            +
              private
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
              def next_token( string)
         
     | 
| 
      
 267 
     | 
    
         
            +
                match_data = nil
         
     | 
| 
      
 268 
     | 
    
         
            +
                while string != ''
         
     | 
| 
      
 269 
     | 
    
         
            +
                  failed = true
         
     | 
| 
      
 270 
     | 
    
         
            +
                  @regex_to_char.each do |regex,char|
         
     | 
| 
      
 271 
     | 
    
         
            +
                    match_data = regex.match(string)
         
     | 
| 
      
 272 
     | 
    
         
            +
                    next unless match_data
         
     | 
| 
      
 273 
     | 
    
         
            +
                    token = match_data[0]
         
     | 
| 
      
 274 
     | 
    
         
            +
                    yield char,token, match_data.end(0) unless char == ?\s && @skip_white_space 
         
     | 
| 
      
 275 
     | 
    
         
            +
                    string = match_data.post_match
         
     | 
| 
      
 276 
     | 
    
         
            +
                    failed = false
         
     | 
| 
      
 277 
     | 
    
         
            +
                    break
         
     | 
| 
      
 278 
     | 
    
         
            +
                  end
         
     | 
| 
      
 279 
     | 
    
         
            +
                  raise LexerJammed, string if failed
         
     | 
| 
      
 280 
     | 
    
         
            +
                end
         
     | 
| 
      
 281 
     | 
    
         
            +
                
         
     | 
| 
      
 282 
     | 
    
         
            +
              rescue RegexpError => details
         
     | 
| 
      
 283 
     | 
    
         
            +
                raise RegexpError, "Choked while '#{@scanner}' was trying to match '#{string}' : #{details}"
         
     | 
| 
      
 284 
     | 
    
         
            +
              end
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
            end
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
            # Test
         
     | 
| 
      
 289 
     | 
    
         
            +
            #
         
     | 
| 
      
 290 
     | 
    
         
            +
            if $0 == __FILE__
         
     | 
| 
      
 291 
     | 
    
         
            +
              
         
     | 
| 
      
 292 
     | 
    
         
            +
              formulas = %{
         
     | 
| 
      
 293 
     | 
    
         
            +
                ((1+2)*(10-6))/2;
         
     | 
| 
      
 294 
     | 
    
         
            +
                DATETIME("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00);
         
     | 
| 
      
 295 
     | 
    
         
            +
                IN(" is ", "this is a string");
         
     | 
| 
      
 296 
     | 
    
         
            +
                LIST(1, "two", 3.0);
         
     | 
| 
      
 297 
     | 
    
         
            +
                IN("b", LIST("a", "b", "c"));
         
     | 
| 
      
 298 
     | 
    
         
            +
                AND(TRUE, NULL);
         
     | 
| 
      
 299 
     | 
    
         
            +
                OR(TRUE, FALSE);
         
     | 
| 
      
 300 
     | 
    
         
            +
                IF(1+1=2, "yes", "no");
         
     | 
| 
      
 301 
     | 
    
         
            +
              }
         
     | 
| 
      
 302 
     | 
    
         
            +
              
         
     | 
| 
      
 303 
     | 
    
         
            +
              # single formula
         
     | 
| 
      
 304 
     | 
    
         
            +
              puts LXL.eval('5+5').inspect # => 10
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
              # multiple formulas separated by semi-colon
         
     | 
| 
      
 307 
     | 
    
         
            +
              puts LXL.eval(formulas).inspect # => [6, true, true, [1, "two", 3.0], true, false, true, "yes"]
         
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
            end
         
     | 
    
        data/test/lxl_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $LOAD_PATH.unshift('../lib')
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'test/unit'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'lxl'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class LXLTest < Test::Unit::TestCase
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def test_single_formula
         
     | 
| 
      
 8 
     | 
    
         
            +
                assert_equal(10, LXL.eval('5+5'))
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def test_multiple_formula
         
     | 
| 
      
 12 
     | 
    
         
            +
                formulas = %{
         
     | 
| 
      
 13 
     | 
    
         
            +
                  ((1+2)*(10-6))/2;
         
     | 
| 
      
 14 
     | 
    
         
            +
                  DATETIME("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00);
         
     | 
| 
      
 15 
     | 
    
         
            +
                  IN(" is ", "this is a string");
         
     | 
| 
      
 16 
     | 
    
         
            +
                  LIST(1, "two", 3.0);
         
     | 
| 
      
 17 
     | 
    
         
            +
                  IN("b", LIST("a", "b", "c"));
         
     | 
| 
      
 18 
     | 
    
         
            +
                  AND(TRUE, NULL);
         
     | 
| 
      
 19 
     | 
    
         
            +
                  OR(TRUE, FALSE);
         
     | 
| 
      
 20 
     | 
    
         
            +
                  IF(1+1=2, "yes", "no");
         
     | 
| 
      
 21 
     | 
    
         
            +
                }
         
     | 
| 
      
 22 
     | 
    
         
            +
                expected = [6, true, true, [1, "two", 3.0], true, false, true, "yes"]
         
     | 
| 
      
 23 
     | 
    
         
            +
                results = LXL.eval(formulas)
         
     | 
| 
      
 24 
     | 
    
         
            +
                expected.each_index { |i| assert_equal(expected[i], results[i]) }
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            rubygems_version: 0.8.3
         
     | 
| 
      
 3 
     | 
    
         
            +
            specification_version: 1
         
     | 
| 
      
 4 
     | 
    
         
            +
            name: lxl
         
     | 
| 
      
 5 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 6 
     | 
    
         
            +
              version: 0.1.0
         
     | 
| 
      
 7 
     | 
    
         
            +
            date: 2005-02-08
         
     | 
| 
      
 8 
     | 
    
         
            +
            summary: LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas. Easily extended with new constants and functions.
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 10 
     | 
    
         
            +
              - lib
         
     | 
| 
      
 11 
     | 
    
         
            +
            email: kh@newclear.ca
         
     | 
| 
      
 12 
     | 
    
         
            +
            homepage: lxl.rubyforge.org
         
     | 
| 
      
 13 
     | 
    
         
            +
            rubyforge_project: lxl
         
     | 
| 
      
 14 
     | 
    
         
            +
            description: 
         
     | 
| 
      
 15 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 16 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 17 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 18 
     | 
    
         
            +
            has_rdoc: false
         
     | 
| 
      
 19 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Version::Requirement 
         
     | 
| 
      
 20 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 21 
     | 
    
         
            +
                - 
         
     | 
| 
      
 22 
     | 
    
         
            +
                  - ">"
         
     | 
| 
      
 23 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 24 
     | 
    
         
            +
                    version: 0.0.0
         
     | 
| 
      
 25 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 26 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 27 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 28 
     | 
    
         
            +
              - Kevin Howe
         
     | 
| 
      
 29 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 30 
     | 
    
         
            +
              - lib
         
     | 
| 
      
 31 
     | 
    
         
            +
              - test
         
     | 
| 
      
 32 
     | 
    
         
            +
              - README
         
     | 
| 
      
 33 
     | 
    
         
            +
              - VERSION
         
     | 
| 
      
 34 
     | 
    
         
            +
              - lib/lxl.rb
         
     | 
| 
      
 35 
     | 
    
         
            +
              - test/lxl_test.rb
         
     | 
| 
      
 36 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 37 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 38 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 39 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 40 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 41 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 42 
     | 
    
         
            +
            dependencies: []
         
     |