kalculator 0.6.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b8d9f63d9aa766d2a26f0b17d63f2ed90dccb7ea1cc98013f2e5703445a2fc9
4
- data.tar.gz: 54bdd33465e18514a700e1f2a49d24b399ff19556a375c68fda95d868f05145a
3
+ metadata.gz: 96f3aedf0934ea881aac16720904825d1861878d7e00d55b8ce81f3f01b4ffdd
4
+ data.tar.gz: c5ceb6b7b8b9b232b31a81c08f03499c3fdd46b7968fb99f48f240929dfcbb40
5
5
  SHA512:
6
- metadata.gz: 2162cfd1c00584151fc1148dc8d0dc3d2e779bf634a623ea9d816270b7d5ceff241421307905b5eb5b72bf7bc979251c1be7232867d2499ea195fc549f466971
7
- data.tar.gz: cf4a5db28cb6280c2b7761c6a6ace135103341f987a63b7b6d4bc1ef3476213346e4992590c686f88938b2c4da40bebcb71720247c7c0220e615099741342fec
6
+ metadata.gz: eeb67bb90056d4d8ffc9d3b9d0f07dc0a1441c9da6a3285dca9c6102653e2fb95b0057223ab88d0171dd2030ea3f9cbe43e2986b6ebf8e7aabf7c6acadb645fd
7
+ data.tar.gz: 581292d72c21d1d8d593a50863ccab63c776570e725bf1444c9b1fa8db63a4e4448a74cff0201932d4191b6e42284ff06b2761fc9e9397646119bf453df27f54
@@ -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
@@ -1,5 +1,11 @@
1
1
  class Kalculator
2
- class Error < ::StandardError; end
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
@@ -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 +(_, left, right)
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 contains(_, collection, item)
66
- collection = evaluate(collection)
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 date(_, expression)
84
- value = evaluate(expression)
85
- raise TypeError, "date only works with Strings, got #{value.inspect}" unless value.is_a?(String)
86
- Date.parse(value)
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 exists(_, variable)
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 max(_, left, right)
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 sum(_, array)
145
- array = evaluate(array)
146
- unless array.is_a?(Array) && array.all?{|n| n.is_a?(Numeric)}
147
- raise TypeError, "sum only works with lists of numbers, got #{array.inspect}"
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
- array.inject(0){|sum, num| sum + num}
150
- end
151
-
152
- def variable(_, name)
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
@@ -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 evaluate(data_source = {})
11
- Kalculator::Evaluator.new(data_source).evaluate(ast)
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
@@ -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-9\._]*/) { |t| [:IDENT, t] }
40
+ rule(/[A-Za-z][A-Za-z0-9_]*/) { |t| [:IDENT, t] }
46
41
  end
47
42
  end
@@ -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, [:variable, n]]
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 GT expression') { |e0, _, e1| [:>, e0, e1] }
42
- clause('expression GTE expression') { |e0, _, e1| [:>=, e0, e1] }
43
- clause('expression LT expression') { |e0, _, e1| [:<, e0, e1] }
44
- clause('expression LTE expression') { |e0, _, e1| [:<=, e0, e1] }
45
- clause('expression EQ expression') { |e0, _, e1| [:==, e0, e1] }
46
- clause('expression NEQ expression') { |e0, _, e1| [:!=, e0, e1] }
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
@@ -0,0 +1,16 @@
1
+ class Kalculator
2
+ class Pointer
3
+ attr_reader :p
4
+ def initialize( p)
5
+ @p = p
6
+ end
7
+ end
8
+
9
+ class AnonymousPointer
10
+ attr_reader :p
11
+
12
+ def initialize(p)
13
+ @p = p
14
+ end
15
+ end
16
+ end
@@ -2,13 +2,18 @@ class Kalculator
2
2
  module Transform
3
3
  def self.run(node, &block)
4
4
  new_node = yield node
5
- return new_node if is_terminal?(new_node)
6
- args = new_node[1..-1].map{ |arg| run(arg, &block) }
7
- args.unshift(new_node.first)
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) && node.first.is_a?(Symbol)
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
@@ -1,3 +1,3 @@
1
1
  class Kalculator
2
- VERSION = "0.6.4"
2
+ VERSION = "1.0.0"
3
3
  end
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.6.4
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: 2018-11-16 00:00:00.000000000 Z
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.6
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