dentaku 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/.gitignore +6 -0
 - data/Gemfile +4 -0
 - data/README.md +81 -0
 - data/Rakefile +10 -0
 - data/dentaku.gemspec +24 -0
 - data/lib/dentaku.rb +14 -0
 - data/lib/dentaku/calculator.rb +68 -0
 - data/lib/dentaku/evaluator.rb +97 -0
 - data/lib/dentaku/token.rb +24 -0
 - data/lib/dentaku/token_matcher.rb +31 -0
 - data/lib/dentaku/token_scanner.rb +22 -0
 - data/lib/dentaku/tokenizer.rb +67 -0
 - data/lib/dentaku/version.rb +3 -0
 - data/spec/calculator_spec.rb +52 -0
 - data/spec/dentaku_spec.rb +11 -0
 - data/spec/evaluator_spec.rb +96 -0
 - data/spec/token_matcher_spec.rb +55 -0
 - data/spec/token_scanner_spec.rb +23 -0
 - data/spec/token_spec.rb +10 -0
 - data/spec/tokenizer_spec.rb +69 -0
 - metadata +85 -0
 
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Dentaku
         
     | 
| 
      
 2 
     | 
    
         
            +
            =======
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            http://github.com/rubysolo/dentaku
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            DESCRIPTION
         
     | 
| 
      
 7 
     | 
    
         
            +
            -----------
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            Dentaku is a parser and evaluator for a mathematical and logical formula
         
     | 
| 
      
 10 
     | 
    
         
            +
            language that allows run-time binding of values to variables referenced in the
         
     | 
| 
      
 11 
     | 
    
         
            +
            formulas.
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            EXAMPLE
         
     | 
| 
      
 14 
     | 
    
         
            +
            -------
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            This is probably simplest to illustrate in code:
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                calculator = Dentaku::Calculator.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                calculator.evaluate('10 * 2')
         
     | 
| 
      
 20 
     | 
    
         
            +
                => 20
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            Okay, not terribly exciting.  But what if you want to have a reference to a
         
     | 
| 
      
 23 
     | 
    
         
            +
            variable, and evaluate it at run-time?  Here's how that would look:
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                calculator.evaluate('kiwi + 5', :kiwi => 2)
         
     | 
| 
      
 26 
     | 
    
         
            +
                => 7
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            You can also store the variable values in the calculator's memory and then
         
     | 
| 
      
 29 
     | 
    
         
            +
            evaluate expressions against those stored values:
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                calculator.store(:peaches => 15)
         
     | 
| 
      
 32 
     | 
    
         
            +
                calculator.evaluate('peaches - 5')
         
     | 
| 
      
 33 
     | 
    
         
            +
                => 10
         
     | 
| 
      
 34 
     | 
    
         
            +
                calculator.evaluate('peaches >= 15')
         
     | 
| 
      
 35 
     | 
    
         
            +
                => true
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            For maximum CS geekery, `bind` is an alias of `store`.
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            If you're too lazy to be building calculator objects, there's a module-method
         
     | 
| 
      
 40 
     | 
    
         
            +
            shortcut just for you:
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                Dentaku['plums * 1.5', {:plums => 2}]
         
     | 
| 
      
 43 
     | 
    
         
            +
                => 3.0
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            SUPPORTED OPERATORS
         
     | 
| 
      
 47 
     | 
    
         
            +
            -------------------
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            Math: `+ - * /`
         
     | 
| 
      
 50 
     | 
    
         
            +
            Logic: `< > <= >= <> != = AND OR`
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            THANKS
         
     | 
| 
      
 53 
     | 
    
         
            +
            ------
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            Big thanks to [ElkStone Basements](http://www.elkstonebasements.com/) for
         
     | 
| 
      
 56 
     | 
    
         
            +
            allowing me to extract and open source this code.
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            LICENSE
         
     | 
| 
      
 59 
     | 
    
         
            +
            -------
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            (The MIT License)
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            Copyright © 2012 Solomon White
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy of
         
     | 
| 
      
 66 
     | 
    
         
            +
            this software and associated documentation files (the ‘Software’), to deal in
         
     | 
| 
      
 67 
     | 
    
         
            +
            the Software without restriction, including without limitation the rights to
         
     | 
| 
      
 68 
     | 
    
         
            +
            use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
         
     | 
| 
      
 69 
     | 
    
         
            +
            the Software, and to permit persons to whom the Software is furnished to do so,
         
     | 
| 
      
 70 
     | 
    
         
            +
            subject to the following conditions:
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be included in all
         
     | 
| 
      
 73 
     | 
    
         
            +
            copies or substantial portions of the Software.
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         
     | 
| 
      
 76 
     | 
    
         
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
         
     | 
| 
      
 77 
     | 
    
         
            +
            FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
         
     | 
| 
      
 78 
     | 
    
         
            +
            COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
         
     | 
| 
      
 79 
     | 
    
         
            +
            IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
         
     | 
| 
      
 80 
     | 
    
         
            +
            CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
    
        data/Rakefile
    ADDED
    
    
    
        data/dentaku.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            $:.push File.expand_path("../lib", __FILE__)
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "dentaku/version"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.name        = "dentaku"
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.version     = Dentaku::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.authors     = ["Solomon White"]
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.email       = ["rubysolo@gmail.com"]
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.homepage    = "http://github.com/rubysolo/dentaku"
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.summary     = %q{A formula language parser and evaluator}
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.description = <<-DESC
         
     | 
| 
      
 13 
     | 
    
         
            +
                Dentaku is a parser and evaluator for mathematical formulas
         
     | 
| 
      
 14 
     | 
    
         
            +
              DESC
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              s.rubyforge_project = "dentaku"
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              s.add_development_dependency('rspec')
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              s.files         = `git ls-files`.split("\n")
         
     | 
| 
      
 21 
     | 
    
         
            +
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         
     | 
| 
      
 22 
     | 
    
         
            +
              s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         
     | 
| 
      
 23 
     | 
    
         
            +
              s.require_paths = ["lib"]
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/dentaku.rb
    ADDED
    
    
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/evaluator'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'dentaku/token'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'dentaku/tokenizer'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Calculator
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :result
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 10 
     | 
    
         
            +
                  clear
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def evaluate(expression, data={})
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @tokenizer ||= Tokenizer.new
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @tokens = @tokenizer.tokenize(expression)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  store(data) do
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @evaluator ||= Evaluator.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @result = @evaluator.evaluate(replace_identifiers_with_values)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def memory(key=nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  key ? @memory[key.to_sym] : @memory
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def store(key_or_hash, value=nil)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  restore = @memory.dup
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  if value
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @memory[key_or_hash.to_sym] = value
         
     | 
| 
      
 32 
     | 
    
         
            +
                  else
         
     | 
| 
      
 33 
     | 
    
         
            +
                    key_or_hash.each do |key, value|
         
     | 
| 
      
 34 
     | 
    
         
            +
                      @memory[key.to_sym] = value if value
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    result = yield
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @memory = restore
         
     | 
| 
      
 41 
     | 
    
         
            +
                    return result
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  self
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
                alias_method :bind, :store
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @memory = {}
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def empty?
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @memory.empty?
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                private
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def replace_identifiers_with_values
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @tokens.map do |token|
         
     | 
| 
      
 60 
     | 
    
         
            +
                    if token.is?(:identifier)
         
     | 
| 
      
 61 
     | 
    
         
            +
                      Token.new(:numeric, memory(token.value))
         
     | 
| 
      
 62 
     | 
    
         
            +
                    else
         
     | 
| 
      
 63 
     | 
    
         
            +
                      token
         
     | 
| 
      
 64 
     | 
    
         
            +
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'dentaku/token_matcher'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Evaluator
         
     | 
| 
      
 6 
     | 
    
         
            +
                T_NUMERIC    = TokenMatcher.new(:numeric)
         
     | 
| 
      
 7 
     | 
    
         
            +
                T_ADDSUB     = TokenMatcher.new(:operator, [:add, :subtract])
         
     | 
| 
      
 8 
     | 
    
         
            +
                T_MULDIV     = TokenMatcher.new(:operator, [:multiply, :divide])
         
     | 
| 
      
 9 
     | 
    
         
            +
                T_COMPARATOR = TokenMatcher.new(:comparator)
         
     | 
| 
      
 10 
     | 
    
         
            +
                T_OPEN       = TokenMatcher.new(:grouping, :open)
         
     | 
| 
      
 11 
     | 
    
         
            +
                T_CLOSE      = TokenMatcher.new(:grouping, :close)
         
     | 
| 
      
 12 
     | 
    
         
            +
                T_NON_GROUP  = TokenMatcher.new(:grouping).invert
         
     | 
| 
      
 13 
     | 
    
         
            +
                T_LOGICAL    = TokenMatcher.new(:logical)
         
     | 
| 
      
 14 
     | 
    
         
            +
                T_COMBINATOR = TokenMatcher.new(:combinator)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                P_GROUP      = [T_OPEN,    T_NON_GROUP,  T_CLOSE]
         
     | 
| 
      
 17 
     | 
    
         
            +
                P_MATH_ADD   = [T_NUMERIC, T_ADDSUB,     T_NUMERIC]
         
     | 
| 
      
 18 
     | 
    
         
            +
                P_MATH_MUL   = [T_NUMERIC, T_MULDIV,     T_NUMERIC]
         
     | 
| 
      
 19 
     | 
    
         
            +
                P_COMPARISON = [T_NUMERIC, T_COMPARATOR, T_NUMERIC]
         
     | 
| 
      
 20 
     | 
    
         
            +
                P_COMBINE    = [T_LOGICAL, T_COMBINATOR, T_LOGICAL]
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                RULES = [
         
     | 
| 
      
 23 
     | 
    
         
            +
                  [P_GROUP,      :evaluate_group],
         
     | 
| 
      
 24 
     | 
    
         
            +
                  [P_MATH_MUL,   :apply],
         
     | 
| 
      
 25 
     | 
    
         
            +
                  [P_MATH_ADD,   :apply],
         
     | 
| 
      
 26 
     | 
    
         
            +
                  [P_COMPARISON, :apply],
         
     | 
| 
      
 27 
     | 
    
         
            +
                  [P_COMBINE,    :apply]
         
     | 
| 
      
 28 
     | 
    
         
            +
                ]
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def evaluate(tokens)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  evaluate_token_stream(tokens).value
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def evaluate_token_stream(tokens)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  while tokens.length > 1
         
     | 
| 
      
 36 
     | 
    
         
            +
                    matched = false
         
     | 
| 
      
 37 
     | 
    
         
            +
                    RULES.each do |pattern, evaluator|
         
     | 
| 
      
 38 
     | 
    
         
            +
                      if pos = find_rule_match(pattern, tokens)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        tokens = evaluate_step(tokens, pos, pattern.length, evaluator)
         
     | 
| 
      
 40 
     | 
    
         
            +
                        matched = true
         
     | 
| 
      
 41 
     | 
    
         
            +
                        break
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    raise "no rule matched #{ tokens.map(&:category).inspect }" unless matched
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  tokens << Token.new(:numeric, 0) if tokens.empty?
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  tokens.first
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                def evaluate_step(token_stream, start, length, evaluator)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  expr = token_stream.slice!(start, length)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  token_stream.insert start, self.send(evaluator, *expr)
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def find_rule_match(pattern, token_stream)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  position = 0
         
     | 
| 
      
 60 
     | 
    
         
            +
                  while position <= token_stream.length - pattern.length
         
     | 
| 
      
 61 
     | 
    
         
            +
                    substream = token_stream.slice(position, pattern.length)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    return position if pattern == substream
         
     | 
| 
      
 63 
     | 
    
         
            +
                    position += 1
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def evaluate_group(*args)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  evaluate_token_stream(args[1..-2])
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def apply(lvalue, operator, rvalue)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  l = lvalue.value
         
     | 
| 
      
 74 
     | 
    
         
            +
                  r = rvalue.value
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  case operator.value
         
     | 
| 
      
 77 
     | 
    
         
            +
                  when :add      then Token.new(:numeric, l + r)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  when :subtract then Token.new(:numeric, l - r)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  when :multiply then Token.new(:numeric, l * r)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  when :divide   then Token.new(:numeric, l / r)
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  when :le       then Token.new(:logical, l <= r)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  when :ge       then Token.new(:logical, l >= r)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  when :lt       then Token.new(:logical, l <  r)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  when :gt       then Token.new(:logical, l >  r)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  when :ne       then Token.new(:logical, l != r)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  when :eq       then Token.new(:logical, l == r)
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  when :and      then Token.new(:logical, l && r)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  when :or       then Token.new(:logical, l || r)
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  else
         
     | 
| 
      
 93 
     | 
    
         
            +
                    raise "unknown comparator '#{ comparator }'"
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
              end
         
     | 
| 
      
 97 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Token
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :category, :raw_value, :value
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(category, value, raw_value=nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @category  = category
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @value     = value
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @raw_value = raw_value
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def length
         
     | 
| 
      
 12 
     | 
    
         
            +
                  raw_value.to_s.length
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def is?(c)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  category == c
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  (category.nil? || other.category.nil? || category == other.category) &&
         
     | 
| 
      
 21 
     | 
    
         
            +
                  (value.nil?    || other.value.nil?    || value    == other.value)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 4 
     | 
    
         
            +
              class TokenMatcher
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(categories=nil, values=nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @categories = [categories].compact.flatten
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @values     = [values].compact.flatten
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @invert     = false
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def invert
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @invert = ! @invert
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def ==(token)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  (category_match(token.category) && value_match(token.value)) ^ @invert
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                private
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def category_match(category)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @categories.empty? || @categories.include?(category)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def value_match(value)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @values.empty? || @values.include?(value)
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 4 
     | 
    
         
            +
              class TokenScanner
         
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(category, regexp, converter=nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @category  = category
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @regexp    = %r{\A(#{ regexp })}
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @converter = converter
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def scan(string)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  if m = @regexp.match(string)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    value = raw = m.to_s
         
     | 
| 
      
 14 
     | 
    
         
            +
                    value = @converter.call(raw) if @converter
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    return Token.new(@category, value, raw)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  false
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'dentaku/token_matcher'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'dentaku/token_scanner'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Dentaku
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Tokenizer
         
     | 
| 
      
 7 
     | 
    
         
            +
                SCANNERS = [
         
     | 
| 
      
 8 
     | 
    
         
            +
                  TokenScanner.new(:whitespace, '\s+'),
         
     | 
| 
      
 9 
     | 
    
         
            +
                  TokenScanner.new(:numeric,    '(\d+(\.\d+)?|\.\d+)', lambda{|raw| raw =~ /\./ ? raw.to_f : raw.to_i }),
         
     | 
| 
      
 10 
     | 
    
         
            +
                  TokenScanner.new(:operator,   '\+|-|\*|\/', lambda do |raw|
         
     | 
| 
      
 11 
     | 
    
         
            +
                    case raw
         
     | 
| 
      
 12 
     | 
    
         
            +
                    when '+' then :add
         
     | 
| 
      
 13 
     | 
    
         
            +
                    when '-' then :subtract
         
     | 
| 
      
 14 
     | 
    
         
            +
                    when '*' then :multiply
         
     | 
| 
      
 15 
     | 
    
         
            +
                    when '/' then :divide
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end),
         
     | 
| 
      
 18 
     | 
    
         
            +
                  TokenScanner.new(:grouping,   '\(|\)', lambda do |raw|
         
     | 
| 
      
 19 
     | 
    
         
            +
                    raw == '(' ? :open : :close
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end),
         
     | 
| 
      
 21 
     | 
    
         
            +
                  TokenScanner.new(:comparator, '<=|>=|!=|<>|<|>|=', lambda do |raw|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    case raw
         
     | 
| 
      
 23 
     | 
    
         
            +
                    when '<=' then :le
         
     | 
| 
      
 24 
     | 
    
         
            +
                    when '>=' then :ge
         
     | 
| 
      
 25 
     | 
    
         
            +
                    when '!=' then :ne
         
     | 
| 
      
 26 
     | 
    
         
            +
                    when '<>' then :ne
         
     | 
| 
      
 27 
     | 
    
         
            +
                    when '<'  then :lt
         
     | 
| 
      
 28 
     | 
    
         
            +
                    when '>'  then :gt
         
     | 
| 
      
 29 
     | 
    
         
            +
                    when '='  then :eq
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end),
         
     | 
| 
      
 32 
     | 
    
         
            +
                  TokenScanner.new(:combinator, '(and|or)\b', lambda {|raw| raw.strip.to_sym }),
         
     | 
| 
      
 33 
     | 
    
         
            +
                  TokenScanner.new(:identifier, '[A-Za-z_]+', lambda {|raw| raw.to_sym })
         
     | 
| 
      
 34 
     | 
    
         
            +
                ]
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                LPAREN = TokenMatcher.new(:grouping, :open)
         
     | 
| 
      
 37 
     | 
    
         
            +
                RPAREN = TokenMatcher.new(:grouping, :close)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def tokenize(string)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  nesting = 0
         
     | 
| 
      
 41 
     | 
    
         
            +
                  tokens  = []
         
     | 
| 
      
 42 
     | 
    
         
            +
                  input   = string.dup.downcase
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  until input.empty?
         
     | 
| 
      
 45 
     | 
    
         
            +
                    raise "parse error at: '#{ input }'" unless SCANNERS.any? do |scanner|
         
     | 
| 
      
 46 
     | 
    
         
            +
                      if token = scanner.scan(input)
         
     | 
| 
      
 47 
     | 
    
         
            +
                        raise "unexpected zero-width match (:#{ token.category }) at '#{ input }'" if token.length == 0
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                        nesting += 1 if LPAREN == token
         
     | 
| 
      
 50 
     | 
    
         
            +
                        nesting -= 1 if RPAREN == token
         
     | 
| 
      
 51 
     | 
    
         
            +
                        raise "too many closing parentheses" if nesting < 0
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                        tokens << token unless token.is?(:whitespace)
         
     | 
| 
      
 54 
     | 
    
         
            +
                        input.slice!(0, token.length)
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                        true
         
     | 
| 
      
 57 
     | 
    
         
            +
                      else
         
     | 
| 
      
 58 
     | 
    
         
            +
                        false
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  raise "too many opening parentheses" if nesting > 0
         
     | 
| 
      
 64 
     | 
    
         
            +
                  tokens
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/calculator'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Dentaku::Calculator do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:calculator)  { described_class.new }
         
     | 
| 
      
 5 
     | 
    
         
            +
              let(:with_memory) { described_class.new.store(:apples => 3) }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              it 'evaluates an expression' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                calculator.evaluate('7+3').should eq(10)
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              describe 'memory' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                it { calculator.should be_empty }
         
     | 
| 
      
 13 
     | 
    
         
            +
                it { with_memory.should_not be_empty   }
         
     | 
| 
      
 14 
     | 
    
         
            +
                it { with_memory.clear.should be_empty }
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                it { with_memory.memory(:apples).should eq(3) }
         
     | 
| 
      
 17 
     | 
    
         
            +
                it { with_memory.memory('apples').should eq(3) }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                it { calculator.store(:apples, 3).memory('apples').should eq(3) }
         
     | 
| 
      
 20 
     | 
    
         
            +
                it { calculator.store('apples', 3).memory(:apples).should eq(3) }
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                it 'should discard local values' do
         
     | 
| 
      
 23 
     | 
    
         
            +
                  calculator.evaluate('pears * 2', :pears => 5).should eq(10)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  calculator.should be_empty
         
     | 
| 
      
 25 
     | 
    
         
            +
                  lambda { calculator.tokenize('pears * 2') }.should raise_error
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              it 'should evaluate a statement with no variables' do
         
     | 
| 
      
 30 
     | 
    
         
            +
                calculator.evaluate('5+3').should eq(8)
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              it 'should fail to evaluate unbound statements' do
         
     | 
| 
      
 34 
     | 
    
         
            +
                lambda { calculator.evaluate('foo * 1.5') }.should raise_error
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              it 'should evaluate unbound statements given a binding in memory' do
         
     | 
| 
      
 38 
     | 
    
         
            +
                calculator.evaluate('foo * 1.5', :foo => 2).should eq(3)
         
     | 
| 
      
 39 
     | 
    
         
            +
                calculator.bind(:monkeys => 3).evaluate('monkeys < 7').should be_true
         
     | 
| 
      
 40 
     | 
    
         
            +
                calculator.evaluate('monkeys / 1.5').should eq(2)
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              it 'should rebind for each evaluation' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                calculator.evaluate('foo * 2', :foo => 2).should eq(4)
         
     | 
| 
      
 45 
     | 
    
         
            +
                calculator.evaluate('foo * 2', :foo => 4).should eq(8)
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              it 'should accept strings or symbols for binding keys' do
         
     | 
| 
      
 49 
     | 
    
         
            +
                calculator.evaluate('foo * 2', :foo => 2).should eq(4)
         
     | 
| 
      
 50 
     | 
    
         
            +
                calculator.evaluate('foo * 2', 'foo' => 4).should eq(8)
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,96 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/evaluator'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Dentaku::Evaluator do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:evaluator) { Dentaku::Evaluator.new }
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              describe 'rule scanning' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                it 'should find a matching rule' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  rule   = [Dentaku::Token.new(:numeric, nil)]
         
     | 
| 
      
 9 
     | 
    
         
            +
                  stream = [Dentaku::Token.new(:numeric, 1), Dentaku::Token.new(:operator, :add), Dentaku::Token.new(:numeric, 1)]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  evaluator.find_rule_match(rule, stream).should eq(0)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              describe 'evaluating' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                it 'empty expression should be truthy' do
         
     | 
| 
      
 16 
     | 
    
         
            +
                  evaluator.evaluate([]).should be
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                it 'empty expression should equal 0' do
         
     | 
| 
      
 20 
     | 
    
         
            +
                  evaluator.evaluate([]).should eq(0)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                it 'single numeric should return value' do
         
     | 
| 
      
 24 
     | 
    
         
            +
                  evaluator.evaluate([Dentaku::Token.new(:numeric, 10)]).should eq(10)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                it 'should evaluate one apply step' do
         
     | 
| 
      
 28 
     | 
    
         
            +
                  stream   = ts(1, :add, 1, :add, 1)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  expected = ts(2, :add, 1)
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  evaluator.evaluate_step(stream, 0, 3, :apply).should eq(expected)
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                it 'should evaluate one grouping step' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                  stream   = ts(:open, 1, :add, 1, :close, :multiply, 5)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  expected = ts(2, :multiply, 5)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  evaluator.evaluate_step(stream, 0, 5, :evaluate_group).should eq(expected)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                describe 'maths' do
         
     | 
| 
      
 42 
     | 
    
         
            +
                  it 'should perform addition' do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    evaluator.evaluate(ts(1, :add, 1)).should eq(2)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  it 'should respect order of precedence' do
         
     | 
| 
      
 47 
     | 
    
         
            +
                    evaluator.evaluate(ts(1, :add, 1, :multiply, 5)).should eq(6)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  it 'should respect explicit grouping' do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    evaluator.evaluate(ts(:open, 1, :add, 1, :close, :multiply, 5)).should eq(10)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                describe 'logic' do
         
     | 
| 
      
 56 
     | 
    
         
            +
                  it 'should evaluate conditional' do
         
     | 
| 
      
 57 
     | 
    
         
            +
                    evaluator.evaluate(ts(5, :gt, 1)).should be_true
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  it 'should evaluate combined conditionals' do
         
     | 
| 
      
 61 
     | 
    
         
            +
                    evaluator.evaluate(ts(5, :gt, 1, :or, :false)).should be_true
         
     | 
| 
      
 62 
     | 
    
         
            +
                    evaluator.evaluate(ts(5, :gt, 1, :and, :false)).should be_false
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              private
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def ts(*args)
         
     | 
| 
      
 70 
     | 
    
         
            +
                args.map do |arg|
         
     | 
| 
      
 71 
     | 
    
         
            +
                  category = (arg.is_a? Fixnum) ? :numeric : category_for(arg)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  arg = (arg == :true) if category == :logical
         
     | 
| 
      
 73 
     | 
    
         
            +
                  Dentaku::Token.new(category, arg)
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              def category_for(value)
         
     | 
| 
      
 78 
     | 
    
         
            +
                case value
         
     | 
| 
      
 79 
     | 
    
         
            +
                when Numeric
         
     | 
| 
      
 80 
     | 
    
         
            +
                  :numeric
         
     | 
| 
      
 81 
     | 
    
         
            +
                when :add, :subtract, :multiply, :divide
         
     | 
| 
      
 82 
     | 
    
         
            +
                  :operator
         
     | 
| 
      
 83 
     | 
    
         
            +
                when :open, :close
         
     | 
| 
      
 84 
     | 
    
         
            +
                  :grouping
         
     | 
| 
      
 85 
     | 
    
         
            +
                when :le, :ge, :ne, :ne, :lt, :gt, :eq
         
     | 
| 
      
 86 
     | 
    
         
            +
                  :comparator
         
     | 
| 
      
 87 
     | 
    
         
            +
                when :and, :or
         
     | 
| 
      
 88 
     | 
    
         
            +
                  :combinator
         
     | 
| 
      
 89 
     | 
    
         
            +
                when :true, :false
         
     | 
| 
      
 90 
     | 
    
         
            +
                  :logical
         
     | 
| 
      
 91 
     | 
    
         
            +
                else
         
     | 
| 
      
 92 
     | 
    
         
            +
                  :identifier
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
            end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token_matcher'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Dentaku::TokenMatcher do
         
     | 
| 
      
 4 
     | 
    
         
            +
              it 'with single category should match token category' do
         
     | 
| 
      
 5 
     | 
    
         
            +
                matcher = described_class.new(:numeric)
         
     | 
| 
      
 6 
     | 
    
         
            +
                token   = Dentaku::Token.new(:numeric, 5)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                matcher.should == token
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              it 'with multiple categories should match any included token category' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                matcher    = described_class.new([:comparator, :operator])
         
     | 
| 
      
 13 
     | 
    
         
            +
                numeric    = Dentaku::Token.new(:numeric, 5)
         
     | 
| 
      
 14 
     | 
    
         
            +
                comparator = Dentaku::Token.new(:comparator, :lt)
         
     | 
| 
      
 15 
     | 
    
         
            +
                operator   = Dentaku::Token.new(:operator, :add)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                matcher.should == comparator
         
     | 
| 
      
 18 
     | 
    
         
            +
                matcher.should == operator
         
     | 
| 
      
 19 
     | 
    
         
            +
                matcher.should_not == numeric
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              it 'with single category and value should match token category and value' do
         
     | 
| 
      
 23 
     | 
    
         
            +
                matcher     = described_class.new(:operator, :add)
         
     | 
| 
      
 24 
     | 
    
         
            +
                addition    = Dentaku::Token.new(:operator, :add)
         
     | 
| 
      
 25 
     | 
    
         
            +
                subtraction = Dentaku::Token.new(:operator, :subtract)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                matcher.should == addition
         
     | 
| 
      
 28 
     | 
    
         
            +
                matcher.should_not == subtraction
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              it 'with multiple values should match any included token value' do
         
     | 
| 
      
 32 
     | 
    
         
            +
                matcher = described_class.new(:operator, [:add, :subtract])
         
     | 
| 
      
 33 
     | 
    
         
            +
                add = Dentaku::Token.new(:operator, :add)
         
     | 
| 
      
 34 
     | 
    
         
            +
                sub = Dentaku::Token.new(:operator, :subtract)
         
     | 
| 
      
 35 
     | 
    
         
            +
                mul = Dentaku::Token.new(:operator, :multiply)
         
     | 
| 
      
 36 
     | 
    
         
            +
                div = Dentaku::Token.new(:operator, :divide)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                matcher.should == add
         
     | 
| 
      
 39 
     | 
    
         
            +
                matcher.should == sub
         
     | 
| 
      
 40 
     | 
    
         
            +
                matcher.should_not == mul
         
     | 
| 
      
 41 
     | 
    
         
            +
                matcher.should_not == div
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              it 'should be invertible' do
         
     | 
| 
      
 45 
     | 
    
         
            +
                matcher = described_class.new(:operator, [:add, :subtract]).invert
         
     | 
| 
      
 46 
     | 
    
         
            +
                add = Dentaku::Token.new(:operator, :add)
         
     | 
| 
      
 47 
     | 
    
         
            +
                mul = Dentaku::Token.new(:operator, :multiply)
         
     | 
| 
      
 48 
     | 
    
         
            +
                cmp = Dentaku::Token.new(:comparator, :lt)
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                matcher.should_not == add
         
     | 
| 
      
 51 
     | 
    
         
            +
                matcher.should == mul
         
     | 
| 
      
 52 
     | 
    
         
            +
                matcher.should == cmp
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/token_scanner'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Dentaku::TokenScanner do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:whitespace) { described_class.new(:whitespace, '\s') }
         
     | 
| 
      
 5 
     | 
    
         
            +
              let(:numeric)    { described_class.new(:numeric,    '(\d+(\.\d+)?|\.\d+)', lambda{|raw| raw =~ /\./ ? raw.to_f : raw.to_i }) }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              it 'should return a token for a matching string' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                token = whitespace.scan(' ')
         
     | 
| 
      
 9 
     | 
    
         
            +
                token.category.should eq(:whitespace)
         
     | 
| 
      
 10 
     | 
    
         
            +
                token.value.should eq(' ')
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              it 'should return falsy for a non-matching string' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                whitespace.scan('A').should_not be
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              it 'should perform raw value conversion' do
         
     | 
| 
      
 18 
     | 
    
         
            +
                token = numeric.scan('5')
         
     | 
| 
      
 19 
     | 
    
         
            +
                token.category.should eq(:numeric)
         
     | 
| 
      
 20 
     | 
    
         
            +
                token.value.should eq(5)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
    
        data/spec/token_spec.rb
    ADDED
    
    
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dentaku/tokenizer'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Dentaku::Tokenizer do
         
     | 
| 
      
 4 
     | 
    
         
            +
              let(:tokenizer) { described_class.new }
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              it 'should handle an empty expression' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                tokenizer.tokenize('').should be_empty
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              it 'should tokenize addition' do
         
     | 
| 
      
 11 
     | 
    
         
            +
                tokens = tokenizer.tokenize('1+1')
         
     | 
| 
      
 12 
     | 
    
         
            +
                tokens.map(&:category).should eq([:numeric, :operator, :numeric])
         
     | 
| 
      
 13 
     | 
    
         
            +
                tokens.map(&:value).should eq([1, :add, 1])
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              it 'should ignore whitespace' do
         
     | 
| 
      
 17 
     | 
    
         
            +
                tokens = tokenizer.tokenize('1     / 1     ')
         
     | 
| 
      
 18 
     | 
    
         
            +
                tokens.map(&:category).should eq([:numeric, :operator, :numeric])
         
     | 
| 
      
 19 
     | 
    
         
            +
                tokens.map(&:value).should eq([1, :divide, 1])
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              it 'should handle floating point' do
         
     | 
| 
      
 23 
     | 
    
         
            +
                tokens = tokenizer.tokenize('1.5 * 3.7')
         
     | 
| 
      
 24 
     | 
    
         
            +
                tokens.map(&:category).should eq([:numeric, :operator, :numeric])
         
     | 
| 
      
 25 
     | 
    
         
            +
                tokens.map(&:value).should eq([1.5, :multiply, 3.7])
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              it 'should not require leading zero' do
         
     | 
| 
      
 29 
     | 
    
         
            +
                tokens = tokenizer.tokenize('.5 * 3.7')
         
     | 
| 
      
 30 
     | 
    
         
            +
                tokens.map(&:category).should eq([:numeric, :operator, :numeric])
         
     | 
| 
      
 31 
     | 
    
         
            +
                tokens.map(&:value).should eq([0.5, :multiply, 3.7])
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              it 'should accept arbitrary identifiers' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                tokens = tokenizer.tokenize('monkeys > 1500')
         
     | 
| 
      
 36 
     | 
    
         
            +
                tokens.map(&:category).should eq([:identifier, :comparator, :numeric])
         
     | 
| 
      
 37 
     | 
    
         
            +
                tokens.map(&:value).should eq([:monkeys, :gt, 1500])
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              it 'should match "<=" before "<"' do
         
     | 
| 
      
 41 
     | 
    
         
            +
                tokens = tokenizer.tokenize('perimeter <= 7500')
         
     | 
| 
      
 42 
     | 
    
         
            +
                tokens.map(&:category).should eq([:identifier, :comparator, :numeric])
         
     | 
| 
      
 43 
     | 
    
         
            +
                tokens.map(&:value).should eq([:perimeter, :le, 7500])
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              it 'should match "and" for logical expressions' do
         
     | 
| 
      
 47 
     | 
    
         
            +
                tokens = tokenizer.tokenize('octopi <= 7500 AND sharks > 1500')
         
     | 
| 
      
 48 
     | 
    
         
            +
                tokens.map(&:category).should eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
         
     | 
| 
      
 49 
     | 
    
         
            +
                tokens.map(&:value).should eq([:octopi, :le, 7500, :and, :sharks, :gt, 1500])
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              it 'should match "or" for logical expressions' do
         
     | 
| 
      
 53 
     | 
    
         
            +
                tokens = tokenizer.tokenize('size < 3 or admin = 1')
         
     | 
| 
      
 54 
     | 
    
         
            +
                tokens.map(&:category).should eq([:identifier, :comparator, :numeric, :combinator, :identifier, :comparator, :numeric])
         
     | 
| 
      
 55 
     | 
    
         
            +
                tokens.map(&:value).should eq([:size, :lt, 3, :or, :admin, :eq, 1])
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              it 'should detect unbalanced parentheses' do
         
     | 
| 
      
 59 
     | 
    
         
            +
                lambda { tokenizer.tokenize('(5+3') }.should raise_error
         
     | 
| 
      
 60 
     | 
    
         
            +
                lambda { tokenizer.tokenize(')')    }.should raise_error
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              it 'should recognize identifiers that share initial substrings with combinators' do
         
     | 
| 
      
 64 
     | 
    
         
            +
                tokens = tokenizer.tokenize('andover < 10')
         
     | 
| 
      
 65 
     | 
    
         
            +
                tokens.length.should eq(3)
         
     | 
| 
      
 66 
     | 
    
         
            +
                tokens.map(&:category).should eq([:identifier, :comparator, :numeric])
         
     | 
| 
      
 67 
     | 
    
         
            +
                tokens.map(&:value).should eq([:andover, :lt, 10])
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: dentaku
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.0
         
     | 
| 
      
 5 
     | 
    
         
            +
              prerelease: 
         
     | 
| 
      
 6 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 7 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Solomon White
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2012-01-20 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 14 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 15 
     | 
    
         
            +
              name: rspec
         
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: &70125220014660 !ruby/object:Gem::Requirement
         
     | 
| 
      
 17 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 18 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 19 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 20 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 21 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 22 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 23 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 24 
     | 
    
         
            +
              version_requirements: *70125220014660
         
     | 
| 
      
 25 
     | 
    
         
            +
            description: ! '    Dentaku is a parser and evaluator for mathematical formulas
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            '
         
     | 
| 
      
 28 
     | 
    
         
            +
            email:
         
     | 
| 
      
 29 
     | 
    
         
            +
            - rubysolo@gmail.com
         
     | 
| 
      
 30 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 31 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 32 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 33 
     | 
    
         
            +
            files:
         
     | 
| 
      
 34 
     | 
    
         
            +
            - .gitignore
         
     | 
| 
      
 35 
     | 
    
         
            +
            - Gemfile
         
     | 
| 
      
 36 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 37 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 38 
     | 
    
         
            +
            - dentaku.gemspec
         
     | 
| 
      
 39 
     | 
    
         
            +
            - lib/dentaku.rb
         
     | 
| 
      
 40 
     | 
    
         
            +
            - lib/dentaku/calculator.rb
         
     | 
| 
      
 41 
     | 
    
         
            +
            - lib/dentaku/evaluator.rb
         
     | 
| 
      
 42 
     | 
    
         
            +
            - lib/dentaku/token.rb
         
     | 
| 
      
 43 
     | 
    
         
            +
            - lib/dentaku/token_matcher.rb
         
     | 
| 
      
 44 
     | 
    
         
            +
            - lib/dentaku/token_scanner.rb
         
     | 
| 
      
 45 
     | 
    
         
            +
            - lib/dentaku/tokenizer.rb
         
     | 
| 
      
 46 
     | 
    
         
            +
            - lib/dentaku/version.rb
         
     | 
| 
      
 47 
     | 
    
         
            +
            - spec/calculator_spec.rb
         
     | 
| 
      
 48 
     | 
    
         
            +
            - spec/dentaku_spec.rb
         
     | 
| 
      
 49 
     | 
    
         
            +
            - spec/evaluator_spec.rb
         
     | 
| 
      
 50 
     | 
    
         
            +
            - spec/token_matcher_spec.rb
         
     | 
| 
      
 51 
     | 
    
         
            +
            - spec/token_scanner_spec.rb
         
     | 
| 
      
 52 
     | 
    
         
            +
            - spec/token_spec.rb
         
     | 
| 
      
 53 
     | 
    
         
            +
            - spec/tokenizer_spec.rb
         
     | 
| 
      
 54 
     | 
    
         
            +
            homepage: http://github.com/rubysolo/dentaku
         
     | 
| 
      
 55 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 56 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 57 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 58 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 59 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 60 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 61 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 62 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 63 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 64 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 65 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 66 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 67 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 68 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 69 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 70 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 71 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 72 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 73 
     | 
    
         
            +
            rubyforge_project: dentaku
         
     | 
| 
      
 74 
     | 
    
         
            +
            rubygems_version: 1.8.10
         
     | 
| 
      
 75 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 76 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 77 
     | 
    
         
            +
            summary: A formula language parser and evaluator
         
     | 
| 
      
 78 
     | 
    
         
            +
            test_files:
         
     | 
| 
      
 79 
     | 
    
         
            +
            - spec/calculator_spec.rb
         
     | 
| 
      
 80 
     | 
    
         
            +
            - spec/dentaku_spec.rb
         
     | 
| 
      
 81 
     | 
    
         
            +
            - spec/evaluator_spec.rb
         
     | 
| 
      
 82 
     | 
    
         
            +
            - spec/token_matcher_spec.rb
         
     | 
| 
      
 83 
     | 
    
         
            +
            - spec/token_scanner_spec.rb
         
     | 
| 
      
 84 
     | 
    
         
            +
            - spec/token_spec.rb
         
     | 
| 
      
 85 
     | 
    
         
            +
            - spec/tokenizer_spec.rb
         
     |