kalculator 0.6.4 → 1.0.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.
- checksums.yaml +4 -4
 - data/lib/kalculator.rb +15 -4
 - data/lib/kalculator/built_in_functions.rb +22 -0
 - data/lib/kalculator/errors.rb +7 -1
 - data/lib/kalculator/evaluator.rb +55 -74
 - data/lib/kalculator/formula.rb +8 -2
 - data/lib/kalculator/lexer.rb +2 -7
 - data/lib/kalculator/parser.rb +45 -38
 - data/lib/kalculator/pointer.rb +16 -0
 - data/lib/kalculator/transform.rb +9 -4
 - data/lib/kalculator/type_sources.rb +36 -0
 - data/lib/kalculator/types.rb +86 -0
 - data/lib/kalculator/validator.rb +240 -0
 - data/lib/kalculator/version.rb +1 -1
 - metadata +8 -4
 - data/lib/kalculator/nested_lookup.rb +0 -25
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 96f3aedf0934ea881aac16720904825d1861878d7e00d55b8ce81f3f01b4ffdd
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: c5ceb6b7b8b9b232b31a81c08f03499c3fdd46b7968fb99f48f240929dfcbb40
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: eeb67bb90056d4d8ffc9d3b9d0f07dc0a1441c9da6a3285dca9c6102653e2fb95b0057223ab88d0171dd2030ea3f9cbe43e2986b6ebf8e7aabf7c6acadb645fd
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 581292d72c21d1d8d593a50863ccab63c776570e725bf1444c9b1fa8db63a4e4448a74cff0201932d4191b6e42284ff06b2761fc9e9397646119bf453df27f54
         
     | 
    
        data/lib/kalculator.rb
    CHANGED
    
    | 
         @@ -1,19 +1,30 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "rltk"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "date"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "kalculator/built_in_functions"
         
     | 
| 
       2 
4 
     | 
    
         
             
            require "kalculator/data_sources"
         
     | 
| 
       3 
5 
     | 
    
         
             
            require "kalculator/errors"
         
     | 
| 
       4 
6 
     | 
    
         
             
            require "kalculator/evaluator"
         
     | 
| 
       5 
7 
     | 
    
         
             
            require "kalculator/formula"
         
     | 
| 
       6 
8 
     | 
    
         
             
            require "kalculator/lexer"
         
     | 
| 
       7 
     | 
    
         
            -
            require "kalculator/nested_lookup"
         
     | 
| 
       8 
9 
     | 
    
         
             
            require "kalculator/parser"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "kalculator/pointer"
         
     | 
| 
       9 
11 
     | 
    
         
             
            require "kalculator/transform"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require "kalculator/type_sources"
         
     | 
| 
      
 13 
     | 
    
         
            +
            require "kalculator/types"
         
     | 
| 
      
 14 
     | 
    
         
            +
            require "kalculator/validator"
         
     | 
| 
       10 
15 
     | 
    
         
             
            require "kalculator/version"
         
     | 
| 
       11 
16 
     | 
    
         | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       12 
21 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       13 
     | 
    
         
            -
              def self.evaluate(formula, data_source = {})
         
     | 
| 
       14 
     | 
    
         
            -
                Kalculator::Formula.new(formula).evaluate(data_source)
         
     | 
| 
      
 22 
     | 
    
         
            +
              def self.evaluate(formula, data_source = {}, custom_functions = {})
         
     | 
| 
      
 23 
     | 
    
         
            +
                Kalculator::Formula.new(formula).evaluate(data_source, custom_functions)
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
              def self.validate(formula,  type_source = Kalculator::TypeSources.new(Hash.new) )
         
     | 
| 
      
 26 
     | 
    
         
            +
                Kalculator::Formula.new(formula).validate(type_source)
         
     | 
| 
       15 
27 
     | 
    
         
             
              end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
28 
     | 
    
         
             
              def self.new(*args)
         
     | 
| 
       18 
29 
     | 
    
         
             
                Kalculator::Formula.new(*args)
         
     | 
| 
       19 
30 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Kalculator
         
     | 
| 
      
 2 
     | 
    
         
            +
              BUILT_IN_FUNCTIONS = {
         
     | 
| 
      
 3 
     | 
    
         
            +
                ["contains", 2] => lambda { |collection, item|
         
     | 
| 
      
 4 
     | 
    
         
            +
                    collection.include?(item)
         
     | 
| 
      
 5 
     | 
    
         
            +
                },
         
     | 
| 
      
 6 
     | 
    
         
            +
                ["count", 1] => lambda { |list|
         
     | 
| 
      
 7 
     | 
    
         
            +
                  list.count
         
     | 
| 
      
 8 
     | 
    
         
            +
                },
         
     | 
| 
      
 9 
     | 
    
         
            +
                ["date", 1] => lambda { |str|
         
     | 
| 
      
 10 
     | 
    
         
            +
                  Date.parse(str)
         
     | 
| 
      
 11 
     | 
    
         
            +
                },
         
     | 
| 
      
 12 
     | 
    
         
            +
                ["max", 2] => lambda { |left, right|
         
     | 
| 
      
 13 
     | 
    
         
            +
                  [left, right].max
         
     | 
| 
      
 14 
     | 
    
         
            +
                },
         
     | 
| 
      
 15 
     | 
    
         
            +
                ["min", 2] => lambda { |left, right|
         
     | 
| 
      
 16 
     | 
    
         
            +
                  [left, right].min
         
     | 
| 
      
 17 
     | 
    
         
            +
                },
         
     | 
| 
      
 18 
     | 
    
         
            +
                ["sum", 1] => lambda { |list|
         
     | 
| 
      
 19 
     | 
    
         
            +
                  list.inject(0){|sum, num| sum + num}
         
     | 
| 
      
 20 
     | 
    
         
            +
                },
         
     | 
| 
      
 21 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/kalculator/errors.rb
    CHANGED
    
    | 
         @@ -1,5 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       2 
     | 
    
         
            -
              class Error < ::StandardError 
     | 
| 
      
 2 
     | 
    
         
            +
              class Error < ::StandardError ##an error that holds metadata
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :metadata
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(metadata)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @metadata = metadata
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
       3 
8 
     | 
    
         
             
              class TypeError < Error; end
         
     | 
| 
      
 9 
     | 
    
         
            +
              class UndefinedFunctionError < Error; end
         
     | 
| 
       4 
10 
     | 
    
         
             
              class UndefinedVariableError < Error; end
         
     | 
| 
       5 
11 
     | 
    
         
             
            end
         
     | 
    
        data/lib/kalculator/evaluator.rb
    CHANGED
    
    | 
         @@ -1,97 +1,96 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "date"
         
     | 
| 
       2 
1 
     | 
    
         | 
| 
       3 
2 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       4 
3 
     | 
    
         
             
              class Evaluator
         
     | 
| 
       5 
     | 
    
         
            -
                def initialize(data_source)
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(data_source, custom_functions = {})
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       6 
6 
     | 
    
         
             
                  @data_source = data_source
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @functions = Kalculator::BUILT_IN_FUNCTIONS.merge(custom_functions)
         
     | 
| 
       7 
8 
     | 
    
         
             
                end
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
                def evaluate(ast)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
       10 
12 
     | 
    
         
             
                  send(ast.first, *ast)
         
     | 
| 
       11 
13 
     | 
    
         
             
                end
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                def  
     | 
| 
      
 15 
     | 
    
         
            +
                def access(_, identifier, object, metadata)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  a = evaluate(object)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  if(a.key?(identifier))
         
     | 
| 
      
 18 
     | 
    
         
            +
                    b =a[identifier]
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if(b.is_a?(Kalculator::Pointer))
         
     | 
| 
      
 20 
     | 
    
         
            +
                      b = @data_source[b.p]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                    if(b.is_a?(Kalculator::AnonymousPointer))
         
     | 
| 
      
 23 
     | 
    
         
            +
                      b = b.p
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    return b
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  raise UndefinedVariableError.new(metadata), "object #{a} doesn't have attribute #{identifier}"
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
                def +(_, left, right, metadata)
         
     | 
| 
       14 
30 
     | 
    
         
             
                  evaluate(left) + evaluate(right)
         
     | 
| 
       15 
31 
     | 
    
         
             
                end
         
     | 
| 
       16 
32 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
                def -(_, left, right)
         
     | 
| 
      
 33 
     | 
    
         
            +
                def -(_, left, right, metadata)
         
     | 
| 
       18 
34 
     | 
    
         
             
                  evaluate(left) - evaluate(right)
         
     | 
| 
       19 
35 
     | 
    
         
             
                end
         
     | 
| 
       20 
36 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                def *(_, left, right)
         
     | 
| 
      
 37 
     | 
    
         
            +
                def *(_, left, right, metadata)
         
     | 
| 
       22 
38 
     | 
    
         
             
                  evaluate(left) * evaluate(right)
         
     | 
| 
       23 
39 
     | 
    
         
             
                end
         
     | 
| 
       24 
40 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                def /(_, left, right)
         
     | 
| 
      
 41 
     | 
    
         
            +
                def /(_, left, right, metadata)
         
     | 
| 
       26 
42 
     | 
    
         
             
                  evaluate(left) / evaluate(right)
         
     | 
| 
       27 
43 
     | 
    
         
             
                end
         
     | 
| 
       28 
44 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                def >(_, left, right)
         
     | 
| 
      
 45 
     | 
    
         
            +
                def >(_, left, right, metadata)
         
     | 
| 
       30 
46 
     | 
    
         
             
                  evaluate(left) > evaluate(right)
         
     | 
| 
       31 
47 
     | 
    
         
             
                end
         
     | 
| 
       32 
48 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                def >=(_, left, right)
         
     | 
| 
      
 49 
     | 
    
         
            +
                def >=(_, left, right, metadata)
         
     | 
| 
       34 
50 
     | 
    
         
             
                  evaluate(left) >= evaluate(right)
         
     | 
| 
       35 
51 
     | 
    
         
             
                end
         
     | 
| 
       36 
52 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                def <(_, left, right)
         
     | 
| 
      
 53 
     | 
    
         
            +
                def <(_, left, right, metadata)
         
     | 
| 
       38 
54 
     | 
    
         
             
                  evaluate(left) < evaluate(right)
         
     | 
| 
       39 
55 
     | 
    
         
             
                end
         
     | 
| 
       40 
56 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                def <=(_, left, right)
         
     | 
| 
      
 57 
     | 
    
         
            +
                def <=(_, left, right, metadata)
         
     | 
| 
       42 
58 
     | 
    
         
             
                  evaluate(left) <= evaluate(right)
         
     | 
| 
       43 
59 
     | 
    
         
             
                end
         
     | 
| 
       44 
60 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                def ==(_, left, right)
         
     | 
| 
      
 61 
     | 
    
         
            +
                def ==(_, left, right, metadata)
         
     | 
| 
       46 
62 
     | 
    
         
             
                  evaluate(left) == evaluate(right)
         
     | 
| 
       47 
63 
     | 
    
         
             
                end
         
     | 
| 
       48 
64 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                def !=(_, left, right)
         
     | 
| 
      
 65 
     | 
    
         
            +
                def !=(_, left, right, metadata)
         
     | 
| 
       50 
66 
     | 
    
         
             
                  evaluate(left) != evaluate(right)
         
     | 
| 
       51 
67 
     | 
    
         
             
                end
         
     | 
| 
       52 
68 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
                def and(_, left, right)
         
     | 
| 
      
 69 
     | 
    
         
            +
                def and(_, left, right, metadata)
         
     | 
| 
       54 
70 
     | 
    
         
             
                  evaluate(left) && evaluate(right)
         
     | 
| 
       55 
71 
     | 
    
         
             
                end
         
     | 
| 
       56 
72 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                def or(_, left, right)
         
     | 
| 
      
 73 
     | 
    
         
            +
                def or(_, left, right, metadata)
         
     | 
| 
       58 
74 
     | 
    
         
             
                  evaluate(left) || evaluate(right)
         
     | 
| 
       59 
75 
     | 
    
         
             
                end
         
     | 
| 
       60 
76 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                def boolean(_, boolean)
         
     | 
| 
      
 77 
     | 
    
         
            +
                def boolean(_, boolean,_, metadata)
         
     | 
| 
       62 
78 
     | 
    
         
             
                  boolean
         
     | 
| 
       63 
79 
     | 
    
         
             
                end
         
     | 
| 
       64 
80 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                def  
     | 
| 
       66 
     | 
    
         
            -
                   
     | 
| 
       67 
     | 
    
         
            -
                  item = evaluate(item)
         
     | 
| 
       68 
     | 
    
         
            -
                  if collection.is_a?(Array)
         
     | 
| 
       69 
     | 
    
         
            -
                    collection.include?(item)
         
     | 
| 
       70 
     | 
    
         
            -
                  elsif collection.is_a?(String) && item.is_a?(String)
         
     | 
| 
       71 
     | 
    
         
            -
                    collection.include?(item)
         
     | 
| 
       72 
     | 
    
         
            -
                  else
         
     | 
| 
       73 
     | 
    
         
            -
                    raise TypeError, "contains only works with strings or lists, got #{collection.inspect} and #{item.inspect}"
         
     | 
| 
       74 
     | 
    
         
            -
                  end
         
     | 
| 
       75 
     | 
    
         
            -
                end
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
                def count(_, collection)
         
     | 
| 
       78 
     | 
    
         
            -
                  collection = evaluate(collection)
         
     | 
| 
       79 
     | 
    
         
            -
                  raise TypeError, "count only works with Enumerable types, got #{collection.inspect}" unless collection.is_a?(Enumerable)
         
     | 
| 
       80 
     | 
    
         
            -
                  collection.count
         
     | 
| 
      
 81 
     | 
    
         
            +
                def exists(_, variable, metadata)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  @data_source.key?(variable)
         
     | 
| 
       81 
83 
     | 
    
         
             
                end
         
     | 
| 
       82 
84 
     | 
    
         | 
| 
       83 
     | 
    
         
            -
                def  
     | 
| 
       84 
     | 
    
         
            -
                   
     | 
| 
       85 
     | 
    
         
            -
                   
     | 
| 
       86 
     | 
    
         
            -
                   
     | 
| 
      
 85 
     | 
    
         
            +
                def fn_call(_, fn_name, expressions, metadata)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  key = [fn_name, expressions.count]
         
     | 
| 
      
 87 
     | 
    
         
            +
                  fn = @functions[key]
         
     | 
| 
      
 88 
     | 
    
         
            +
                  raise UndefinedFunctionError.new(metadata), "no such function #{fn_name}/#{expressions.count}" if fn.nil?
         
     | 
| 
      
 89 
     | 
    
         
            +
                  args = expressions.map{|expression| evaluate(expression) }
         
     | 
| 
      
 90 
     | 
    
         
            +
                  return fn.call(*args)
         
     | 
| 
       87 
91 
     | 
    
         
             
                end
         
     | 
| 
       88 
92 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
                def  
     | 
| 
       90 
     | 
    
         
            -
                  (_variable, name) = variable
         
     | 
| 
       91 
     | 
    
         
            -
                  @data_source.key?(name)
         
     | 
| 
       92 
     | 
    
         
            -
                end
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                def if(_, condition, true_clause, false_clause)
         
     | 
| 
      
 93 
     | 
    
         
            +
                def if(_, condition, true_clause, false_clause, metadata)
         
     | 
| 
       95 
94 
     | 
    
         
             
                  if evaluate(condition)
         
     | 
| 
       96 
95 
     | 
    
         
             
                    evaluate(true_clause)
         
     | 
| 
       97 
96 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -99,59 +98,41 @@ class Kalculator 
     | 
|
| 
       99 
98 
     | 
    
         
             
                  end
         
     | 
| 
       100 
99 
     | 
    
         
             
                end
         
     | 
| 
       101 
100 
     | 
    
         | 
| 
       102 
     | 
    
         
            -
                def list(_, expressions)
         
     | 
| 
      
 101 
     | 
    
         
            +
                def list(_, expressions, metadata)
         
     | 
| 
       103 
102 
     | 
    
         
             
                  expressions.map{|expression| evaluate(expression) }
         
     | 
| 
       104 
103 
     | 
    
         
             
                end
         
     | 
| 
       105 
104 
     | 
    
         | 
| 
       106 
     | 
    
         
            -
                def  
     | 
| 
       107 
     | 
    
         
            -
                  left = evaluate(left)
         
     | 
| 
       108 
     | 
    
         
            -
                  right = evaluate(right)
         
     | 
| 
       109 
     | 
    
         
            -
                  raise TypeError, "max only works with numbers, got #{left.inspect}" unless left.is_a?(Numeric)
         
     | 
| 
       110 
     | 
    
         
            -
                  raise TypeError, "max only works with numbers, got #{right.inspect}" unless right.is_a?(Numeric)
         
     | 
| 
       111 
     | 
    
         
            -
                  [left, right].max
         
     | 
| 
       112 
     | 
    
         
            -
                end
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
                def min(_, left, right)
         
     | 
| 
       115 
     | 
    
         
            -
                  left = evaluate(left)
         
     | 
| 
       116 
     | 
    
         
            -
                  right = evaluate(right)
         
     | 
| 
       117 
     | 
    
         
            -
                  raise TypeError, "min only works with numbers, got #{left.inspect}" unless left.is_a?(Numeric)
         
     | 
| 
       118 
     | 
    
         
            -
                  raise TypeError, "min only works with numbers, got #{right.inspect}" unless right.is_a?(Numeric)
         
     | 
| 
       119 
     | 
    
         
            -
                  [left, right].min
         
     | 
| 
       120 
     | 
    
         
            -
                end
         
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
                def not(_, expression)
         
     | 
| 
      
 105 
     | 
    
         
            +
                def not(_, expression, metadata)
         
     | 
| 
       123 
106 
     | 
    
         
             
                  bool = evaluate(expression)
         
     | 
| 
       124 
     | 
    
         
            -
                  raise TypeError, "! only works with booleans, got #{bool.inspect}" unless bool === true || bool === false
         
     | 
| 
       125 
107 
     | 
    
         
             
                  !bool
         
     | 
| 
       126 
108 
     | 
    
         
             
                end
         
     | 
| 
       127 
109 
     | 
    
         | 
| 
       128 
     | 
    
         
            -
                def null(_, _)
         
     | 
| 
      
 110 
     | 
    
         
            +
                def null(_, _,_, metadata)
         
     | 
| 
       129 
111 
     | 
    
         
             
                  nil
         
     | 
| 
       130 
112 
     | 
    
         
             
                end
         
     | 
| 
       131 
113 
     | 
    
         | 
| 
       132 
     | 
    
         
            -
                def number(_, number)
         
     | 
| 
      
 114 
     | 
    
         
            +
                def number(_, number,_, metadata)
         
     | 
| 
       133 
115 
     | 
    
         
             
                  number
         
     | 
| 
       134 
116 
     | 
    
         
             
                end
         
     | 
| 
       135 
117 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
                def percent(_, percent)
         
     | 
| 
      
 118 
     | 
    
         
            +
                def percent(_, percent,_, metadata)
         
     | 
| 
       137 
119 
     | 
    
         
             
                  percent / 100.0
         
     | 
| 
       138 
120 
     | 
    
         
             
                end
         
     | 
| 
       139 
121 
     | 
    
         | 
| 
       140 
     | 
    
         
            -
                def string(_, string)
         
     | 
| 
      
 122 
     | 
    
         
            +
                def string(_, string,_, metadata)
         
     | 
| 
       141 
123 
     | 
    
         
             
                  string
         
     | 
| 
       142 
124 
     | 
    
         
             
                end
         
     | 
| 
       143 
125 
     | 
    
         | 
| 
       144 
     | 
    
         
            -
                def  
     | 
| 
       145 
     | 
    
         
            -
                   
     | 
| 
       146 
     | 
    
         
            -
                   
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
      
 126 
     | 
    
         
            +
                def variable(_, name, metadata)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  raise UndefinedVariableError.new(metadata), "undefined variable #{name}" unless @data_source.key?(name)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  a = @data_source[name]
         
     | 
| 
      
 129 
     | 
    
         
            +
                  if(a.is_a?(Pointer))
         
     | 
| 
      
 130 
     | 
    
         
            +
                    a = @data_source[a.p]
         
     | 
| 
       148 
131 
     | 
    
         
             
                  end
         
     | 
| 
       149 
     | 
    
         
            -
                   
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
                  raise UndefinedVariableError, "undefined variable #{name}" unless @data_source.key?(name)
         
     | 
| 
       154 
     | 
    
         
            -
                  @data_source[name]
         
     | 
| 
      
 132 
     | 
    
         
            +
                  if(a.is_a?(Kalculator::AnonymousPointer))
         
     | 
| 
      
 133 
     | 
    
         
            +
                    a = a.p
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
                  return a
         
     | 
| 
       155 
136 
     | 
    
         
             
                end
         
     | 
| 
       156 
137 
     | 
    
         
             
              end
         
     | 
| 
       157 
138 
     | 
    
         
             
            end
         
     | 
    
        data/lib/kalculator/formula.rb
    CHANGED
    
    | 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
       1 
2 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       2 
3 
     | 
    
         
             
              class Formula
         
     | 
| 
       3 
4 
     | 
    
         
             
                attr_reader :ast, :string
         
     | 
| 
         @@ -7,8 +8,13 @@ class Kalculator 
     | 
|
| 
       7 
8 
     | 
    
         
             
                  @string = string
         
     | 
| 
       8 
9 
     | 
    
         
             
                end
         
     | 
| 
       9 
10 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                def  
     | 
| 
       11 
     | 
    
         
            -
                  Kalculator:: 
     | 
| 
      
 11 
     | 
    
         
            +
                def validate( type_source = Kalculator::TypeSources.new(Hash.new))
         
     | 
| 
      
 12 
     | 
    
         
            +
                  Kalculator::Validator.new(type_source).validate(ast)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def evaluate(data_source = {}, custom_functions = {})
         
     | 
| 
      
 16 
     | 
    
         
            +
                  Kalculator::Evaluator.new(data_source, custom_functions).evaluate(ast)
         
     | 
| 
       12 
17 
     | 
    
         
             
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       13 
19 
     | 
    
         
             
              end
         
     | 
| 
       14 
20 
     | 
    
         
             
            end
         
     | 
    
        data/lib/kalculator/lexer.rb
    CHANGED
    
    | 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Lexer < RLTK::Lexer
         
     | 
| 
       3 
3 
     | 
    
         
             
                rule(/\s/)
         
     | 
| 
      
 4 
     | 
    
         
            +
                rule(/\./)       { :PERIOD   }
         
     | 
| 
       4 
5 
     | 
    
         
             
                rule(/\(/)       { :LPAREN   }
         
     | 
| 
       5 
6 
     | 
    
         
             
                rule(/\)/)       { :RPAREN   }
         
     | 
| 
       6 
7 
     | 
    
         
             
                rule(/\[/)       { :LBRACKET }
         
     | 
| 
         @@ -27,14 +28,8 @@ class Kalculator 
     | 
|
| 
       27 
28 
     | 
    
         
             
                rule(/\-?\d+/)      { |t| [:NUMBER, t.to_i] }
         
     | 
| 
       28 
29 
     | 
    
         
             
                rule(/\-?\.\d+/)    { |t| [:NUMBER, t.to_f] }
         
     | 
| 
       29 
30 
     | 
    
         
             
                rule(/\-?\d+\.\d+/) { |t| [:NUMBER, t.to_f] }
         
     | 
| 
       30 
     | 
    
         
            -
                rule(/contains/) { |t| :CONTAINS }
         
     | 
| 
       31 
     | 
    
         
            -
                rule(/count/)    { |t| :COUNT }
         
     | 
| 
       32 
     | 
    
         
            -
                rule(/date/)     { |t| :DATE }
         
     | 
| 
       33 
31 
     | 
    
         
             
                rule(/exists/)   { |t| :EXISTS }
         
     | 
| 
       34 
32 
     | 
    
         
             
                rule(/if/)       { |t| :IF }
         
     | 
| 
       35 
     | 
    
         
            -
                rule(/max/)      { |t| :MAX }
         
     | 
| 
       36 
     | 
    
         
            -
                rule(/min/)      { |t| :MIN }
         
     | 
| 
       37 
     | 
    
         
            -
                rule(/sum/)      { |t| :SUM }
         
     | 
| 
       38 
33 
     | 
    
         
             
                rule(/true/)     { |t| :TRUE }
         
     | 
| 
       39 
34 
     | 
    
         
             
                rule(/false/)    { |t| :FALSE }
         
     | 
| 
       40 
35 
     | 
    
         
             
                rule(/null/)     { |t| :NULL }
         
     | 
| 
         @@ -42,6 +37,6 @@ class Kalculator 
     | 
|
| 
       42 
37 
     | 
    
         
             
                rule(/FALSE/)    { |t| :FALSE }
         
     | 
| 
       43 
38 
     | 
    
         
             
                rule(/NULL/)     { |t| :NULL }
         
     | 
| 
       44 
39 
     | 
    
         
             
                rule(/"[^"]*"/)  { |t| [:STRING, t[1..-2]] }
         
     | 
| 
       45 
     | 
    
         
            -
                rule(/[A-Za-z][A-Za-z0- 
     | 
| 
      
 40 
     | 
    
         
            +
                rule(/[A-Za-z][A-Za-z0-9_]*/) { |t| [:IDENT, t] }
         
     | 
| 
       46 
41 
     | 
    
         
             
              end
         
     | 
| 
       47 
42 
     | 
    
         
             
            end
         
     | 
    
        data/lib/kalculator/parser.rb
    CHANGED
    
    | 
         @@ -1,59 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
       1 
2 
     | 
    
         
             
            class Kalculator
         
     | 
| 
       2 
3 
     | 
    
         
             
              class Parser < RLTK::Parser
         
     | 
| 
      
 4 
     | 
    
         
            +
                #code taken from spiff-rb
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Environment < Environment
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def metadata(first_token = nil, last_token = nil)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    first_token ||= @positions.first
         
     | 
| 
      
 8 
     | 
    
         
            +
                    last_token  ||= @positions.last
         
     | 
| 
      
 9 
     | 
    
         
            +
                    begins_at = first_token.stream_offset
         
     | 
| 
      
 10 
     | 
    
         
            +
                    ends_at   = last_token.stream_offset + last_token.length - 1
         
     | 
| 
      
 11 
     | 
    
         
            +
                    {
         
     | 
| 
      
 12 
     | 
    
         
            +
                      offset: begins_at..ends_at,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    }
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
       3 
16 
     | 
    
         
             
                left :AND, :OR
         
     | 
| 
       4 
     | 
    
         
            -
                left :GT, :GTE, :LT, :LTE, :EQ
         
     | 
| 
      
 17 
     | 
    
         
            +
                left :GT, :GTE, :LT, :LTE, :EQ, :NEQ
         
     | 
| 
       5 
18 
     | 
    
         
             
                left :PLUS, :SUB
         
     | 
| 
       6 
19 
     | 
    
         
             
                left :MUL, :DIV
         
     | 
| 
      
 20 
     | 
    
         
            +
                left :PERIOD
         
     | 
| 
       7 
21 
     | 
    
         
             
                #Left     600 '.'.
         
     | 
| 
       8 
22 
     | 
    
         | 
| 
       9 
23 
     | 
    
         
             
                production(:expression) do
         
     | 
| 
       10 
     | 
    
         
            -
                  clause('CONTAINS LPAREN expression COMMA expression RPAREN') do |_, _, collection, _, item, _|
         
     | 
| 
       11 
     | 
    
         
            -
                    [:contains, collection, item]
         
     | 
| 
       12 
     | 
    
         
            -
                  end
         
     | 
| 
       13 
24 
     | 
    
         
             
                  clause('IF LPAREN expression COMMA expression COMMA expression RPAREN') do |_, _, condition, _, true_clause, _, false_clause, _|
         
     | 
| 
       14 
     | 
    
         
            -
                    [:if, condition, true_clause, false_clause]
         
     | 
| 
       15 
     | 
    
         
            -
                  end
         
     | 
| 
       16 
     | 
    
         
            -
                  clause('SUM LPAREN expression RPAREN') do |_, _, e0, _|
         
     | 
| 
       17 
     | 
    
         
            -
                    [:sum, e0]
         
     | 
| 
       18 
     | 
    
         
            -
                  end
         
     | 
| 
       19 
     | 
    
         
            -
                  clause('COUNT LPAREN expression RPAREN') do |_, _, e0, _|
         
     | 
| 
       20 
     | 
    
         
            -
                    [:count, e0]
         
     | 
| 
       21 
     | 
    
         
            -
                  end
         
     | 
| 
       22 
     | 
    
         
            -
                  clause('DATE LPAREN expression RPAREN') do |_, _, e0, _|
         
     | 
| 
       23 
     | 
    
         
            -
                    [:date, e0]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    [:if, condition, true_clause, false_clause, metadata]
         
     | 
| 
       24 
26 
     | 
    
         
             
                  end
         
     | 
| 
       25 
27 
     | 
    
         
             
                  clause('EXISTS LPAREN IDENT RPAREN') do |_, _, n, _|
         
     | 
| 
       26 
     | 
    
         
            -
                    [:exists,  
     | 
| 
      
 28 
     | 
    
         
            +
                    [:exists, n, metadata]
         
     | 
| 
       27 
29 
     | 
    
         
             
                  end
         
     | 
| 
       28 
     | 
    
         
            -
                  clause('MAX LPAREN expression COMMA expression RPAREN') { |_, _, left, _, right, _| [:max, left, right] }
         
     | 
| 
       29 
     | 
    
         
            -
                  clause('MIN LPAREN expression COMMA expression RPAREN') { |_, _, left, _, right, _| [:min, left, right] }
         
     | 
| 
       30 
30 
     | 
    
         
             
                  clause('LPAREN expression RPAREN') { |_, expression, _| expression }
         
     | 
| 
       31 
     | 
    
         
            -
                  clause('LBRACKET expressions RBRACKET') { |_, expressions, _| [:list, expressions] }
         
     | 
| 
      
 31 
     | 
    
         
            +
                  clause('LBRACKET expressions RBRACKET') { |_, expressions, _| [:list, expressions, metadata] }
         
     | 
| 
      
 32 
     | 
    
         
            +
                  clause('IDENT LPAREN expressions RPAREN') { |fn_name, _, expressions, _| [:fn_call, fn_name, expressions, metadata] }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  clause('expression LBRACKET STRING RBRACKET') { |object, _,ident, _| [:access, ident, object, metadata]}
         
     | 
| 
       32 
34 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                  clause('NUMBER') { |n| [:number, n] }
         
     | 
| 
       34 
     | 
    
         
            -
                  clause('PERCENT') { |n| [:percent, n] }
         
     | 
| 
       35 
     | 
    
         
            -
                  clause('STRING') { |s| [:string, s] }
         
     | 
| 
       36 
     | 
    
         
            -
                  clause('IDENT') { |n| [:variable, n] }
         
     | 
| 
       37 
     | 
    
         
            -
                  clause('TRUE') { |n| [:boolean, true] }
         
     | 
| 
       38 
     | 
    
         
            -
                  clause('FALSE') { |n| [:boolean, false] }
         
     | 
| 
       39 
     | 
    
         
            -
                  clause('NULL') { |n| [:null, nil] }
         
     | 
| 
      
 35 
     | 
    
         
            +
                  clause('NUMBER') { |n| [:number, n, Number, metadata] }
         
     | 
| 
      
 36 
     | 
    
         
            +
                  clause('PERCENT') { |n| [:percent, n, Percent, metadata] }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  clause('STRING') { |s| [:string, s, String.new, metadata] }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  clause('IDENT') { |n| [:variable, n, metadata] }
         
     | 
| 
      
 39 
     | 
    
         
            +
                  clause('TRUE') { |n| [:boolean, true, Bool, metadata] }
         
     | 
| 
      
 40 
     | 
    
         
            +
                  clause('FALSE') { |n| [:boolean, false, Bool, metadata] }
         
     | 
| 
      
 41 
     | 
    
         
            +
                  clause('NULL') { |n| [:null, nil, Object, metadata] }
         
     | 
| 
       40 
42 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                  clause('expression  
     | 
| 
       42 
     | 
    
         
            -
                  clause('expression  
     | 
| 
       43 
     | 
    
         
            -
                  clause('expression  
     | 
| 
       44 
     | 
    
         
            -
                  clause('expression  
     | 
| 
       45 
     | 
    
         
            -
                  clause('expression  
     | 
| 
       46 
     | 
    
         
            -
                  clause('expression  
     | 
| 
      
 43 
     | 
    
         
            +
                  clause('expression PERIOD IDENT')  {  |e0, _, i| [:access,i, e0, metadata] }
         
     | 
| 
      
 44 
     | 
    
         
            +
                  clause('expression GT expression') { |e0, _, e1| [:>, e0, e1, metadata] }
         
     | 
| 
      
 45 
     | 
    
         
            +
                  clause('expression GTE expression') { |e0, _, e1| [:>=, e0, e1, metadata] }
         
     | 
| 
      
 46 
     | 
    
         
            +
                  clause('expression LT expression') { |e0, _, e1| [:<, e0, e1, metadata] }
         
     | 
| 
      
 47 
     | 
    
         
            +
                  clause('expression LTE expression') { |e0, _, e1| [:<=, e0, e1, metadata] }
         
     | 
| 
      
 48 
     | 
    
         
            +
                  clause('expression EQ expression') { |e0, _, e1| [:==, e0, e1, metadata] }
         
     | 
| 
      
 49 
     | 
    
         
            +
                  clause('expression NEQ expression') { |e0, _, e1| [:!=, e0, e1, metadata] }
         
     | 
| 
       47 
50 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
                  clause('expression AND expression') { |e0, _, e1| [:and, e0, e1] }
         
     | 
| 
       49 
     | 
    
         
            -
                  clause('expression OR expression') { |e0, _, e1| [:or, e0, e1] }
         
     | 
| 
      
 51 
     | 
    
         
            +
                  clause('expression AND expression') { |e0, _, e1| [:and, e0, e1, metadata] }
         
     | 
| 
      
 52 
     | 
    
         
            +
                  clause('expression OR expression') { |e0, _, e1| [:or, e0, e1, metadata] }
         
     | 
| 
       50 
53 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                  clause('expression PLUS expression') { |e0, _, e1| [:+, e0, e1] }
         
     | 
| 
       52 
     | 
    
         
            -
                  clause('expression SUB expression')  { |e0, _, e1| [:-, e0, e1] }
         
     | 
| 
       53 
     | 
    
         
            -
                  clause('expression MUL expression')  { |e0, _, e1| [:*, e0, e1] }
         
     | 
| 
       54 
     | 
    
         
            -
                  clause('expression DIV expression')  { |e0, _, e1| [:/, e0, e1] }
         
     | 
| 
      
 54 
     | 
    
         
            +
                  clause('expression PLUS expression') { |e0, _, e1| [:+, e0, e1, metadata] }
         
     | 
| 
      
 55 
     | 
    
         
            +
                  clause('expression SUB expression')  { |e0, _, e1| [:-, e0, e1, metadata] }
         
     | 
| 
      
 56 
     | 
    
         
            +
                  clause('expression MUL expression')  { |e0, _, e1| [:*, e0, e1, metadata] }
         
     | 
| 
      
 57 
     | 
    
         
            +
                  clause('expression DIV expression')  { |e0, _, e1| [:/, e0, e1, metadata] }
         
     | 
| 
       55 
58 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                  clause('BANG expression')           { |_, e0| [:not, e0] }
         
     | 
| 
      
 59 
     | 
    
         
            +
                  clause('BANG expression')           { |_, e0| [:not, e0, metadata] }
         
     | 
| 
       57 
60 
     | 
    
         
             
                end
         
     | 
| 
       58 
61 
     | 
    
         | 
| 
       59 
62 
     | 
    
         
             
                production(:expressions) do
         
     | 
| 
         @@ -62,5 +65,9 @@ class Kalculator 
     | 
|
| 
       62 
65 
     | 
    
         
             
                end
         
     | 
| 
       63 
66 
     | 
    
         | 
| 
       64 
67 
     | 
    
         
             
                finalize()
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def metadata(num_tokens)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  {offset: pos(0).stream_offset..pos(num_tokens-1).stream_offset}
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
       65 
72 
     | 
    
         
             
              end
         
     | 
| 
       66 
73 
     | 
    
         
             
            end
         
     | 
    
        data/lib/kalculator/transform.rb
    CHANGED
    
    | 
         @@ -2,13 +2,18 @@ class Kalculator 
     | 
|
| 
       2 
2 
     | 
    
         
             
              module Transform
         
     | 
| 
       3 
3 
     | 
    
         
             
                def self.run(node, &block)
         
     | 
| 
       4 
4 
     | 
    
         
             
                  new_node = yield node
         
     | 
| 
       5 
     | 
    
         
            -
                   
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                   
     | 
| 
      
 5 
     | 
    
         
            +
                  if is_terminal?(new_node)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    new_node
         
     | 
| 
      
 7 
     | 
    
         
            +
                  elsif new_node.first.is_a?(Symbol)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    args = new_node[1..-1].map{ |arg| run(arg, &block) }
         
     | 
| 
      
 9 
     | 
    
         
            +
                    args.unshift(new_node.first)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  else
         
     | 
| 
      
 11 
     | 
    
         
            +
                    new_node.map{|node| run(node, &block) }
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
       8 
13 
     | 
    
         
             
                end
         
     | 
| 
       9 
14 
     | 
    
         | 
| 
       10 
15 
     | 
    
         
             
                def self.is_terminal?(node)
         
     | 
| 
       11 
     | 
    
         
            -
                  return false if node.is_a?(Array) 
     | 
| 
      
 16 
     | 
    
         
            +
                  return false if node.is_a?(Array)
         
     | 
| 
       12 
17 
     | 
    
         
             
                  true
         
     | 
| 
       13 
18 
     | 
    
         
             
                end
         
     | 
| 
       14 
19 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ##
         
     | 
| 
      
 2 
     | 
    
         
            +
            #Author: Benjamin Walter Newhall, 12/17/19, github: bennewhall
         
     | 
| 
      
 3 
     | 
    
         
            +
            #Explanation: much like the data sources class, this stores type data in the form {"variable-name"=>String}
         
     | 
| 
      
 4 
     | 
    
         
            +
            ##
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Kalculator
         
     | 
| 
      
 7 
     | 
    
         
            +
              class TypeSources
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(*sources)# sources is an array of Hashes
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @sources = sources
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def key?(name)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  ret = false
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @sources.each do |source|
         
     | 
| 
      
 16 
     | 
    
         
            +
                    break ret = true if source.key?(name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ret
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def [](name)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  ret = nil
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @sources.each do |source|
         
     | 
| 
      
 24 
     | 
    
         
            +
                    break ret = source[name] if source.key?(name)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  ret
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                #returns a hash of variable name to type of variable
         
     | 
| 
      
 30 
     | 
    
         
            +
                def toHash
         
     | 
| 
      
 31 
     | 
    
         
            +
                  a = Hash.new
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @sources.each { |i| a = a.merge(i)}
         
     | 
| 
      
 33 
     | 
    
         
            +
                  return a
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,86 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ##
         
     | 
| 
      
 2 
     | 
    
         
            +
            #Author: Benjamin Walter Newhall 12/17/19 github: bennewhall
         
     | 
| 
      
 3 
     | 
    
         
            +
            #Explanation: A class that holds the types used for the validator
         
     | 
| 
      
 4 
     | 
    
         
            +
            ##
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            class Kalculator
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Comparable; end #A Type that can be used in <, >, etc. operations
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Number< Comparable; end #A Type that can be used in +,-,etc operations
         
     | 
| 
      
 10 
     | 
    
         
            +
              class Percent < Number; end #a type of number
         
     | 
| 
      
 11 
     | 
    
         
            +
              class Bool; end
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Time; end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              #a generic collection type of type Collection<othertype> where othertype is stored in the type instance variable
         
     | 
| 
      
 15 
     | 
    
         
            +
              class Collection
         
     | 
| 
      
 16 
     | 
    
         
            +
                attr_reader :type
         
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize(type)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @type = type
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
                def generic_type?(possible_type) #possibleType has to either be a collection or a type
         
     | 
| 
      
 21 
     | 
    
         
            +
                  if(possible_type.class <= Kalculator::Collection)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    return possible_type.type <= @type
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  if(possible_type.class == Class)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    return possible_type <= @type
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def ==(other_type) #othertype has to be a collection or a type
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if(other_type.class <= self.class)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      return other_type.type == self.type
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def <=(other_object) # otherobject has to be a collection, a type, or some other object
         
     | 
| 
      
 38 
     | 
    
         
            +
                  if(other_object.class <= Kalculator::Collection)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return ((self.class<= other_object.class) and (self.type <= other_object.type))
         
     | 
| 
      
 40 
     | 
    
         
            +
                  elsif(other_object.class == Class)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if(other_object == Object)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      return true
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    return false
         
     | 
| 
      
 45 
     | 
    
         
            +
                  elsif(other_object.class != Class)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    return false
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def >=(other_object) # otherobject has to be a collection, a type, or some other object
         
     | 
| 
      
 53 
     | 
    
         
            +
                  if(other_object.class <=Kalculator::Collection)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    return other_object<= self
         
     | 
| 
      
 55 
     | 
    
         
            +
                  elsif(other_object.class == Class)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    return false
         
     | 
| 
      
 57 
     | 
    
         
            +
                  elsif(other_object.class != Class)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    return false
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  return false
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              class String < Collection #a collection that holds strings
         
     | 
| 
      
 68 
     | 
    
         
            +
                def initialize()
         
     | 
| 
      
 69 
     | 
    
         
            +
                  super(String)
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              class List < Collection #a collection that holds one type of data
         
     | 
| 
      
 74 
     | 
    
         
            +
                def initialize(type)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  super(type)
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              class MappedObject < Collection #a collection that represents an object and holds any type of data
         
     | 
| 
      
 80 
     | 
    
         
            +
                attr_reader :hash
         
     | 
| 
      
 81 
     | 
    
         
            +
                def initialize(hash)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  super(Object)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  @hash = hash
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,240 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ##
         
     | 
| 
      
 2 
     | 
    
         
            +
            #Author: Benjamin Walter Newhall 12/17/19, github: bennewhall
         
     | 
| 
      
 3 
     | 
    
         
            +
            #Explanation: A class that will either return the ending type of an ast, or throw an error with metadata information about error position
         
     | 
| 
      
 4 
     | 
    
         
            +
            #Usage: v = Validator.new(typesource)       (typesource is a TypeSources object)
         
     | 
| 
      
 5 
     | 
    
         
            +
            #       v.validate(ast)                     (ast is the ast returned by parsing a line)(this will either return a type or throw an Error as described in the errors.rb file)
         
     | 
| 
      
 6 
     | 
    
         
            +
            ##
         
     | 
| 
      
 7 
     | 
    
         
            +
            class Kalculator
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Validator
         
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(type_source)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @type_source = type_source
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #e stores built in function type data
         
     | 
| 
      
 12 
     | 
    
         
            +
                  #last index is ALWAYS the return type, and types before that are types of children from left to right
         
     | 
| 
      
 13 
     | 
    
         
            +
                  e = { "contains" => [Collection.new(Object), Object, Bool],
         
     | 
| 
      
 14 
     | 
    
         
            +
                    "count" => [List.new(Object), Number],
         
     | 
| 
      
 15 
     | 
    
         
            +
                    "date" => [String.new, Time],
         
     | 
| 
      
 16 
     | 
    
         
            +
                    "max"=> [Number,Number,Number],
         
     | 
| 
      
 17 
     | 
    
         
            +
                    "min" => [Number,Number,Number],
         
     | 
| 
      
 18 
     | 
    
         
            +
                    "sum" => [List.new(Number), Number] #this only accepts number Lists
         
     | 
| 
      
 19 
     | 
    
         
            +
                  }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @environment = e.merge(type_source.toHash)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def validate(ast)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  send(ast.first, *ast)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def access(_,identifier,object, metadata)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  objectType = validate(object)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if((objectType.is_a?(MappedObject)))
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if(objectType.hash.key?(identifier))
         
     | 
| 
      
 32 
     | 
    
         
            +
                      attribute =objectType.hash[identifier]
         
     | 
| 
      
 33 
     | 
    
         
            +
                      if(attribute.is_a?(Pointer))
         
     | 
| 
      
 34 
     | 
    
         
            +
                        attribute = @environment[attribute.p] #if the attribute is a pointer find an add
         
     | 
| 
      
 35 
     | 
    
         
            +
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
                      if(attribute.is_a?(Kalculator::AnonymousPointer))
         
     | 
| 
      
 37 
     | 
    
         
            +
                        attribute = attribute.p
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
                      return attribute
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    raise UndefinedVariableError.new(metadata), "object #{objectType} doesn't have type attribute #{identifier}"
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  raise TypeError.new(metadata), "trying to access something that isn't an object"
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def +(_, left, right, metadata)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType <=Number)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    return leftType
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
                raise TypeError.new(metadata), "not operating on two of the same Number types"
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def -(_, left, right, metadata)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType <=Number)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    return leftType
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
                raise TypeError.new(metadata), "not operating on two of the same Number types"
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def *(_, left, right, metadata)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType <=Number)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    return leftType
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                raise TypeError.new(metadata), "not operating on two of the same Number types"
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def /(_, left, right, metadata)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType <=Number)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    return leftType
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
                raise TypeError.new(metadata), "not operating on two of the same Number types"
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def >(_, left, right, metadata)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType<= Comparable)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same comparable types"
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                def >=(_, left, right, metadata)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType<= Comparable)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same comparable types"
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                def <(_, left, right, metadata)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType<= Comparable)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same types"
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                def <=(_, left, right, metadata)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  if((leftType==validate(right)) and leftType<= Comparable)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same comparable types"
         
     | 
| 
      
 108 
     | 
    
         
            +
                end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                def ==(_, left, right, metadata)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  if(leftType==validate(right))
         
     | 
| 
      
 113 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same comparable types"
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                def !=(_, left, right, metadata)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  leftType = validate(left)
         
     | 
| 
      
 120 
     | 
    
         
            +
                  if(leftType==validate(right))
         
     | 
| 
      
 121 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing two of the same comparable types"
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                def and(_, left, right, metadata)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  if(validate(left)<=Bool and validate(right)<=Bool)
         
     | 
| 
      
 128 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing (AND) two BOOL types"
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                def or(_, left, right, metadata)
         
     | 
| 
      
 134 
     | 
    
         
            +
                  if(validate(left)<=Bool and validate(right)<=Bool)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
                raise TypeError.new(metadata), "not comparing (OR) two BOOL types"
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                def boolean(_, boolean, type, metadata)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  return Bool
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                def exists(_, variable, metadata)
         
     | 
| 
      
 145 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 146 
     | 
    
         
            +
                end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                def fn_call(_, fn_name, expressions, metadata) #compare individually to make sure it is a subclass or class
         
     | 
| 
      
 149 
     | 
    
         
            +
                  ex =expressions.map{|expression| validate(expression) }
         
     | 
| 
      
 150 
     | 
    
         
            +
                  raise UndefinedVariableError.new(metadata), "undefined variable #{fn_name}" unless @environment.key?(fn_name)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  argumentMatch = ex.zip(@environment[fn_name]).all? do |(arg_type, expected_type)|
         
     | 
| 
      
 152 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 153 
     | 
    
         
            +
                      arg_type <= expected_type
         
     | 
| 
      
 154 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 155 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 156 
     | 
    
         
            +
                        expected_type >= arg_type
         
     | 
| 
      
 157 
     | 
    
         
            +
                      rescue
         
     | 
| 
      
 158 
     | 
    
         
            +
                        false
         
     | 
| 
      
 159 
     | 
    
         
            +
                      end
         
     | 
| 
      
 160 
     | 
    
         
            +
                    end
         
     | 
| 
      
 161 
     | 
    
         
            +
                  end
         
     | 
| 
      
 162 
     | 
    
         
            +
                  if(argumentMatch) # make sure each element is related to corresponding element in the funcion params
         
     | 
| 
      
 163 
     | 
    
         
            +
                    if(fn_name == "max" or fn_name == "min") #add functions here where output type depends on input type and all types must be exact same ie. max(Percent,Percent) => Percent max(Number,Percent)=> Error max(Number,Number)=>Number
         
     | 
| 
      
 164 
     | 
    
         
            +
                      #check all expressions are same
         
     | 
| 
      
 165 
     | 
    
         
            +
                      cmptype = ex[0]
         
     | 
| 
      
 166 
     | 
    
         
            +
                      if( ex.all?{|t| t == cmptype})
         
     | 
| 
      
 167 
     | 
    
         
            +
                        return cmptype
         
     | 
| 
      
 168 
     | 
    
         
            +
                      end
         
     | 
| 
      
 169 
     | 
    
         
            +
                      raise TypeError.new(metadata), "specialized function type error"
         
     | 
| 
      
 170 
     | 
    
         
            +
                    end
         
     | 
| 
      
 171 
     | 
    
         
            +
                    #add other specialized functions here
         
     | 
| 
      
 172 
     | 
    
         
            +
                    if(fn_name == "contains") #generic function in the format List<E>, E => ReturnType
         
     | 
| 
      
 173 
     | 
    
         
            +
                      if(ex[0].generic_type?(ex[1]))
         
     | 
| 
      
 174 
     | 
    
         
            +
                        return @environment[fn_name][@environment[fn_name].size - 1]
         
     | 
| 
      
 175 
     | 
    
         
            +
                      end
         
     | 
| 
      
 176 
     | 
    
         
            +
                      raise TypeError.new(metadata), "generic function type error"
         
     | 
| 
      
 177 
     | 
    
         
            +
                    end
         
     | 
| 
      
 178 
     | 
    
         
            +
                    return @environment[fn_name][@environment[fn_name].size - 1]
         
     | 
| 
      
 179 
     | 
    
         
            +
                  end
         
     | 
| 
      
 180 
     | 
    
         
            +
                  raise TypeError.new(metadata), "function type error"
         
     | 
| 
      
 181 
     | 
    
         
            +
                end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                def if(_, condition, true_clause, false_clause, metadata)
         
     | 
| 
      
 184 
     | 
    
         
            +
                  conditionType = validate(condition)
         
     | 
| 
      
 185 
     | 
    
         
            +
                  if(conditionType <= Object and validate(true_clause)==validate(false_clause))
         
     | 
| 
      
 186 
     | 
    
         
            +
                    return validate(true_clause)
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
                raise TypeError.new(metadata), "if statement type error"
         
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                def list(_, expressions, metadata)
         
     | 
| 
      
 192 
     | 
    
         
            +
                  ex =expressions.map{|expression| validate(expression) }
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                  cmptype = ex[0]
         
     | 
| 
      
 195 
     | 
    
         
            +
                  if( ex.all?{|t| t == cmptype})
         
     | 
| 
      
 196 
     | 
    
         
            +
                    return List.new(cmptype)
         
     | 
| 
      
 197 
     | 
    
         
            +
                  end
         
     | 
| 
      
 198 
     | 
    
         
            +
                raise TypeError.new(metadata), "list type error"
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                def not(_, expression, metadata)
         
     | 
| 
      
 203 
     | 
    
         
            +
                  if(validate(expression) ==Bool)
         
     | 
| 
      
 204 
     | 
    
         
            +
                    return Bool
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                  end
         
     | 
| 
      
 207 
     | 
    
         
            +
                  raise TypeError.new(metadata), "NOT expression type error"
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                def null(_, _, type, metadata)
         
     | 
| 
      
 211 
     | 
    
         
            +
                  return type
         
     | 
| 
      
 212 
     | 
    
         
            +
                end
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                def number(_, number, type, metadata)
         
     | 
| 
      
 215 
     | 
    
         
            +
                  return type
         
     | 
| 
      
 216 
     | 
    
         
            +
                end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                def percent(_, percent, type, metadata)
         
     | 
| 
      
 219 
     | 
    
         
            +
                  return type
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                def string(_, string, type, metadata)
         
     | 
| 
      
 223 
     | 
    
         
            +
                  return type
         
     | 
| 
      
 224 
     | 
    
         
            +
                end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                def variable(_, name, metadata)
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                  raise UndefinedVariableError.new(metadata), "undefined variable #{name}" unless @environment.key?(name)
         
     | 
| 
      
 229 
     | 
    
         
            +
                  variable_type = @environment[name]
         
     | 
| 
      
 230 
     | 
    
         
            +
                  if(variable_type.is_a?(Pointer))
         
     | 
| 
      
 231 
     | 
    
         
            +
                    variable_type = @environment[variable_type.p] #if the variable you are accessing is a pointer, then return the type of the variable it is pointing to
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
                  if(variable_type.is_a?(Kalculator::AnonymousPointer))
         
     | 
| 
      
 234 
     | 
    
         
            +
                    variable_type = variable_type.p
         
     | 
| 
      
 235 
     | 
    
         
            +
                  end
         
     | 
| 
      
 236 
     | 
    
         
            +
                  return variable_type
         
     | 
| 
      
 237 
     | 
    
         
            +
                end
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
              end
         
     | 
| 
      
 240 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/kalculator/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: kalculator
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Michael Ries
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2019-12-31 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: rltk
         
     | 
| 
         @@ -89,14 +89,18 @@ extra_rdoc_files: [] 
     | 
|
| 
       89 
89 
     | 
    
         
             
            files:
         
     | 
| 
       90 
90 
     | 
    
         
             
            - kalculator.gemspec
         
     | 
| 
       91 
91 
     | 
    
         
             
            - lib/kalculator.rb
         
     | 
| 
      
 92 
     | 
    
         
            +
            - lib/kalculator/built_in_functions.rb
         
     | 
| 
       92 
93 
     | 
    
         
             
            - lib/kalculator/data_sources.rb
         
     | 
| 
       93 
94 
     | 
    
         
             
            - lib/kalculator/errors.rb
         
     | 
| 
       94 
95 
     | 
    
         
             
            - lib/kalculator/evaluator.rb
         
     | 
| 
       95 
96 
     | 
    
         
             
            - lib/kalculator/formula.rb
         
     | 
| 
       96 
97 
     | 
    
         
             
            - lib/kalculator/lexer.rb
         
     | 
| 
       97 
     | 
    
         
            -
            - lib/kalculator/nested_lookup.rb
         
     | 
| 
       98 
98 
     | 
    
         
             
            - lib/kalculator/parser.rb
         
     | 
| 
      
 99 
     | 
    
         
            +
            - lib/kalculator/pointer.rb
         
     | 
| 
       99 
100 
     | 
    
         
             
            - lib/kalculator/transform.rb
         
     | 
| 
      
 101 
     | 
    
         
            +
            - lib/kalculator/type_sources.rb
         
     | 
| 
      
 102 
     | 
    
         
            +
            - lib/kalculator/types.rb
         
     | 
| 
      
 103 
     | 
    
         
            +
            - lib/kalculator/validator.rb
         
     | 
| 
       100 
104 
     | 
    
         
             
            - lib/kalculator/version.rb
         
     | 
| 
       101 
105 
     | 
    
         
             
            homepage: https://github.com/mmmries/kalculator
         
     | 
| 
       102 
106 
     | 
    
         
             
            licenses:
         
     | 
| 
         @@ -118,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       118 
122 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       119 
123 
     | 
    
         
             
            requirements: []
         
     | 
| 
       120 
124 
     | 
    
         
             
            rubyforge_project: 
         
     | 
| 
       121 
     | 
    
         
            -
            rubygems_version: 2.7. 
     | 
| 
      
 125 
     | 
    
         
            +
            rubygems_version: 2.7.9
         
     | 
| 
       122 
126 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       123 
127 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       124 
128 
     | 
    
         
             
            summary: A calculator that can safely and quickly interpret user-input
         
     | 
| 
         @@ -1,25 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            class Kalculator
         
     | 
| 
       2 
     | 
    
         
            -
              class NestedLookup
         
     | 
| 
       3 
     | 
    
         
            -
                def initialize(data_source)
         
     | 
| 
       4 
     | 
    
         
            -
                  @data_source = data_source
         
     | 
| 
       5 
     | 
    
         
            -
                end
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                def key?(name)
         
     | 
| 
       8 
     | 
    
         
            -
                  names = name.split(".")
         
     | 
| 
       9 
     | 
    
         
            -
                  source = @data_source
         
     | 
| 
       10 
     | 
    
         
            -
                  names.all? do |name|
         
     | 
| 
       11 
     | 
    
         
            -
                    break false unless source.key?(name)
         
     | 
| 
       12 
     | 
    
         
            -
                    source = source[name]
         
     | 
| 
       13 
     | 
    
         
            -
                    true
         
     | 
| 
       14 
     | 
    
         
            -
                  end
         
     | 
| 
       15 
     | 
    
         
            -
                end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                def [](name)
         
     | 
| 
       18 
     | 
    
         
            -
                  names = name.split(".")
         
     | 
| 
       19 
     | 
    
         
            -
                  names.inject(@data_source) do |source, name|
         
     | 
| 
       20 
     | 
    
         
            -
                    break nil unless source.key?(name)
         
     | 
| 
       21 
     | 
    
         
            -
                    source[name]
         
     | 
| 
       22 
     | 
    
         
            -
                  end
         
     | 
| 
       23 
     | 
    
         
            -
                end
         
     | 
| 
       24 
     | 
    
         
            -
              end
         
     | 
| 
       25 
     | 
    
         
            -
            end
         
     |