kalc 1.2.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7dd87399e45a1819f532e20a2e4fed4be6824b6e
4
- data.tar.gz: 17167d66287fe9869eed4c788eb8942b85551d76
2
+ SHA256:
3
+ metadata.gz: c6b57e87fb3af0e7410e84529380e3b6e6b37d79ded5ba5956807803dfe4e9b1
4
+ data.tar.gz: 280bbed579405d13d7c31f0931ddd4d63904bb5759f0468409307b040ba4b4fd
5
5
  SHA512:
6
- metadata.gz: 9019d6437db9acf668148ababebfa77e8120d44f916d84d082011d58836f7c2e09bda5b1bf55f50d82dc96df98525f105db3d82709f0c64c5ba37d03fe90614c
7
- data.tar.gz: 930b53a089d8e62a093ef09bda37a4aab7004ab4d4b64a1f8636840819ae8c5b67c60c04c4bee6e84ceb469c551bba8365a5cb071e23c5e0fe4ce9c15bf7d474
6
+ metadata.gz: 22a28f8e0fc573736bf356a6e0744eef81a6e9d0ea7710ee1559094ffe1549d9ef01e4f9ca221dd93ff9d1f3813284b7e35cbfb79c63cbc18dc71c4a7d28b4ca
7
+ data.tar.gz: 70332cf06859be1a6ceb52ad2951acd5c5553bde9239d24210cec7a0ef142fe74bfa666ace03dc58cb3487039a76a0f3d0f9faec92ed154a002ed6a7515b54fa
@@ -1,30 +1,28 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kalc (1.2.0)
5
- parslet (~> 1.7)
4
+ kalc (1.7.0)
5
+ parslet (~> 1.8.2)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- blankslate (3.1.3)
11
- diff-lcs (1.2.5)
12
- parslet (1.7.1)
13
- blankslate (>= 2.0, <= 4.0)
14
- rake (11.2.2)
15
- rspec (3.5.0)
16
- rspec-core (~> 3.5.0)
17
- rspec-expectations (~> 3.5.0)
18
- rspec-mocks (~> 3.5.0)
19
- rspec-core (3.5.0)
20
- rspec-support (~> 3.5.0)
21
- rspec-expectations (3.5.0)
10
+ diff-lcs (1.3)
11
+ parslet (1.8.2)
12
+ rake (12.1.0)
13
+ rspec (3.6.0)
14
+ rspec-core (~> 3.6.0)
15
+ rspec-expectations (~> 3.6.0)
16
+ rspec-mocks (~> 3.6.0)
17
+ rspec-core (3.6.0)
18
+ rspec-support (~> 3.6.0)
19
+ rspec-expectations (3.6.0)
22
20
  diff-lcs (>= 1.2.0, < 2.0)
23
- rspec-support (~> 3.5.0)
24
- rspec-mocks (3.5.0)
21
+ rspec-support (~> 3.6.0)
22
+ rspec-mocks (3.6.0)
25
23
  diff-lcs (>= 1.2.0, < 2.0)
26
- rspec-support (~> 3.5.0)
27
- rspec-support (3.5.0)
24
+ rspec-support (~> 3.6.0)
25
+ rspec-support (3.6.0)
28
26
 
29
27
  PLATFORMS
30
28
  java
@@ -36,4 +34,4 @@ DEPENDENCIES
36
34
  rspec
37
35
 
38
36
  BUNDLED WITH
39
- 1.12.5
37
+ 1.16.2
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Chris Parker
1
+ Copyright (c) 2012-2018 Chris Parker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,25 +1,23 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path('../lib', __FILE__)
1
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
3
2
  require 'kalc/version'
4
- #
5
3
  Gem::Specification.new do |s|
6
4
  s.name = 'kalc'
7
5
  s.version = Kalc::VERSION
8
6
  s.authors = ['Chris Parker']
9
- s.email = %w(mrcsparker@gmail.com)
7
+ s.email = %w[mrcsparker@gmail.com]
10
8
  s.homepage = 'https://github.com/mrcsparker/kalc'
11
- s.summary = %q{Small calculation language.}
12
- s.description = %q{Calculation language slightly based on Excel's formula language.}
9
+ s.summary = 'Small calculation language.'
10
+ s.description = "Calculation language slightly based on Excel's formula language."
13
11
 
14
12
  s.rubyforge_project = 'kalc'
15
13
 
16
14
  s.files = `git ls-files`.split("\n")
17
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
16
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
- s.require_paths = %w(lib)
17
+ s.require_paths = %w[lib]
20
18
 
21
19
  # specify any dependencies here; for example:
22
20
  s.add_development_dependency 'rake'
23
21
  s.add_development_dependency 'rspec'
24
- s.add_runtime_dependency 'parslet', '~> 1.7'
22
+ s.add_runtime_dependency 'parslet', '~> 1.8.2'
25
23
  end
@@ -40,11 +40,9 @@ module Kalc
40
40
  end
41
41
 
42
42
  def eval(context)
43
- begin
44
- -(@value.eval(context))
45
- rescue
46
- @value.eval(context)
47
- end
43
+ -@value.eval(context)
44
+ rescue StandardError
45
+ @value.eval(context)
48
46
  end
49
47
  end
50
48
 
@@ -57,11 +55,9 @@ module Kalc
57
55
  end
58
56
 
59
57
  def eval(context)
60
- begin
61
- +(@value.eval(context))
62
- rescue
63
- @value.eval(context)
64
- end
58
+ +@value.eval(context)
59
+ rescue StandardError
60
+ @value.eval(context)
65
61
  end
66
62
  end
67
63
 
@@ -72,8 +68,8 @@ module Kalc
72
68
  @value = value
73
69
  end
74
70
 
75
- def eval(context)
76
- @value == 'TRUE' ? true : false
71
+ def eval(_context)
72
+ @value == 'TRUE'
77
73
  end
78
74
  end
79
75
 
@@ -81,11 +77,11 @@ module Kalc
81
77
  attr_reader :value
82
78
 
83
79
  def initialize(value)
84
- @value = BigDecimal.new(value.to_s)
80
+ @value = BigDecimal(value.to_s)
85
81
  end
86
82
 
87
- def eval(context)
88
- BigDecimal.new(@value)
83
+ def eval(_context)
84
+ BigDecimal(@value)
89
85
  end
90
86
  end
91
87
 
@@ -123,10 +119,10 @@ module Kalc
123
119
  end
124
120
 
125
121
  def eval(context)
126
- @ops.inject(@left.eval(context)) { |x, op|
122
+ @ops.inject(@left.eval(context)) do |x, op|
127
123
  a = Arithmetic.new(x, op[:right].eval(context), op[:operator])
128
124
  a.eval(context)
129
- }
125
+ end
130
126
  end
131
127
  end
132
128
 
@@ -141,7 +137,7 @@ module Kalc
141
137
  @operator = operator
142
138
  end
143
139
 
144
- def eval(context)
140
+ def eval(_context)
145
141
  case @operator.to_s.strip
146
142
  when '&&'
147
143
  @left && @right
@@ -218,7 +214,8 @@ module Kalc
218
214
 
219
215
  def eval(context)
220
216
  var = context.get_variable(@variable)
221
- fail "Invalid variable: #{@variable}" unless var
217
+ raise "Invalid variable: #{@variable}" unless var
218
+
222
219
  var.class == BigDecimal ? var : var.eval(context)
223
220
  end
224
221
  end
@@ -230,7 +227,7 @@ module Kalc
230
227
  @value = value
231
228
  end
232
229
 
233
- def eval(context)
230
+ def eval(_context)
234
231
  value.to_s
235
232
  end
236
233
  end
@@ -246,11 +243,12 @@ module Kalc
246
243
 
247
244
  def eval(context)
248
245
  to_call = context.get_function(@name)
249
- fail "Unknown function #{@name}" unless to_call
246
+ raise "Unknown function #{@name}" unless to_call
247
+
250
248
  to_call.call(context, *@variable_list)
251
249
  rescue ArgumentError
252
- fail "Argument Error. Function #{@name} was called with #{@variable_list.count} parameters. " +
253
- "It needs at least #{to_call.parameters.select{|a| a.first == :req}.count - 1 } parameters"
250
+ raise "Argument Error. Function #{@name} was called with #{@variable_list.count} parameters. " \
251
+ "It needs at least #{to_call.parameters.select { |a| a.first == :req }.count - 1} parameters"
254
252
  end
255
253
  end
256
254
 
@@ -11,31 +11,27 @@ module Kalc
11
11
  end
12
12
 
13
13
  def add_function(name, value)
14
- @functions.update({ name.to_s.strip => value })
14
+ @functions.update(name.to_s.strip => value)
15
15
  end
16
16
 
17
17
  def get_function(name)
18
- if fun = @functions[name.to_s.strip]
18
+ if (fun = @functions[name.to_s.strip])
19
19
  fun
20
20
  elsif !@parent.nil?
21
21
  @parent.get_function(name)
22
- else
23
- nil
24
22
  end
25
23
  end
26
24
 
27
25
  def add_variable(name, value)
28
- @variables.update({ name.to_s.strip => value })
26
+ @variables.update(name.to_s.strip => value)
29
27
  value
30
28
  end
31
29
 
32
30
  def get_variable(name)
33
- if var = @variables[name.to_s.strip]
31
+ if (var = @variables[name.to_s.strip])
34
32
  var
35
33
  elsif !@parent.nil?
36
34
  @parent.get_variable(name)
37
- else
38
- nil
39
35
  end
40
36
  end
41
37
  end
@@ -27,26 +27,24 @@ class Kalc::Grammar < Parslet::Parser
27
27
  end
28
28
  end
29
29
 
30
- symbols :left_paren => '(',
31
- :right_paren => ')',
32
- :left_brace => '{',
33
- :right_brace => '}',
34
- :comma => ',',
35
- :colon => ':',
36
- :question_mark => '?'
30
+ symbols left_paren: '(',
31
+ right_paren: ')',
32
+ left_brace: '{',
33
+ right_brace: '}',
34
+ comma: ',',
35
+ colon: ':',
36
+ question_mark: '?'
37
37
 
38
38
  def self.operators(operators = {})
39
39
  trailing_chars = Hash.new { |hash, symbol| hash[symbol] = [] }
40
40
 
41
41
  operators.each_value do |symbol|
42
42
  operators.each_value do |op|
43
- if op[0, symbol.length] == symbol
44
- char = op[symbol.length, 1]
43
+ next unless op[0, symbol.length] == symbol
45
44
 
46
- unless char.nil? || char.empty?
47
- trailing_chars[symbol] << char
48
- end
49
- end
45
+ char = op[symbol.length, 1]
46
+
47
+ trailing_chars[symbol] << char unless char.nil? || char.empty?
50
48
  end
51
49
  end
52
50
 
@@ -54,238 +52,232 @@ class Kalc::Grammar < Parslet::Parser
54
52
  trailing = trailing_chars[symbol]
55
53
 
56
54
  if trailing.empty?
57
- rule(name) { str(symbol).as(:operator) >> spaces? }
55
+ rule(name) {str(symbol).as(:operator) >> spaces?}
58
56
  else
59
57
  pattern = "[#{Regexp.escape(trailing.join)}]"
60
58
 
61
- rule(name) {
59
+ rule(name) do
62
60
  (str(symbol) >> match(pattern).absnt?).as(:operator) >> spaces?
63
- }
61
+ end
64
62
  end
65
63
  end
66
64
  end
67
65
 
68
- operators :logical_and => '&&',
69
- :string_and => 'and',
70
- :logical_or => '||',
71
- :string_or => 'or',
72
- :less_equal => '<=',
73
- :greater_equal => '>=',
74
- :equal => '==',
75
- :not_equal => '!=',
76
-
77
- :assign => ':=',
78
- :excel_equal => '=',
79
-
80
- :subtract => '-',
81
- :add => '+',
82
- :multiply => '*',
83
- :divide => '/',
84
- :modulus => '%',
85
- :power_of => '^',
86
-
87
- :less => '<',
88
- :greater => '>'
89
-
90
- rule(:true_keyword) {
66
+ operators logical_and: '&&',
67
+ string_and: 'and',
68
+ logical_or: '||',
69
+ string_or: 'or',
70
+ less_equal: '<=',
71
+ greater_equal: '>=',
72
+ equal: '==',
73
+ not_equal: '!=',
74
+
75
+ assign: ':=',
76
+ excel_equal: '=',
77
+
78
+ subtract: '-',
79
+ add: '+',
80
+ multiply: '*',
81
+ divide: '/',
82
+ modulus: '%',
83
+ power_of: '^',
84
+
85
+ less: '<',
86
+ greater: '>'
87
+
88
+ rule(:true_keyword) do
91
89
  str('TRUE') >> spaces?
92
- }
90
+ end
93
91
 
94
- rule(:false_keyword) {
92
+ rule(:false_keyword) do
95
93
  str('FALSE') >> spaces?
96
- }
94
+ end
97
95
 
98
- rule(:boolean) {
96
+ rule(:boolean) do
99
97
  (true_keyword | false_keyword).as(:boolean)
100
- }
98
+ end
101
99
 
102
- rule(:string) {
100
+ rule(:string) do
103
101
  str('"') >>
104
- (
105
- str('\\') >> any |
106
- str('"').absnt? >> any
107
- ).repeat.as(:string) >>
108
- str('"')
109
- }
110
-
111
- rule(:exponent) {
102
+ (
103
+ str('\\') >> any | str('"').absnt? >> any
104
+ ).repeat.as(:string) >> str('"')
105
+ end
106
+
107
+ rule(:exponent) do
112
108
  match('[eE]') >> match('[-+]').maybe >> digits
113
- }
109
+ end
114
110
 
115
111
  # We are using a really broad definition of what a number is.
116
112
  # Numbers can be 1, 1.0, 0.1, 1.0e4, +1.0E10, etc
117
- rule(:number) {
113
+ rule(:number) do
118
114
  (match('[+-]').maybe >>
119
- (str('.') >> digits >> exponent.maybe).as(:number) >> spaces?) |
120
- (match('[+-]').maybe >>
121
- digits >> (str('.') >> digits).maybe >> exponent.maybe).as(:number) >> spaces?
122
- }
115
+ (str('.') >> digits >> exponent.maybe).as(:number) >> spaces?) |
116
+ (match('[+-]').maybe >> digits >> (str('.') >> digits).maybe >> exponent.maybe).as(:number) >> spaces?
117
+ end
123
118
 
124
- rule(:identifier) {
119
+ rule(:identifier) do
125
120
  (alpha >> (alpha | digit).repeat) >> spaces?
126
- }
121
+ end
127
122
 
128
- rule(:quoted_identifier) {
129
- str("'") >>
130
- (
131
- str('\\') >> any | str("'").absnt? >> any
132
- ).repeat(1) >>
133
- str("'") >> spaces?
134
- }
123
+ rule(:quoted_identifier) do
124
+ str("'") >> (
125
+ str('\\') >> any | str("'").absnt? >> any
126
+ ).repeat(1) >> str("'") >> spaces?
127
+ end
135
128
 
136
- rule(:argument) {
129
+ rule(:argument) do
137
130
  identifier.as(:argument)
138
- }
131
+ end
139
132
 
140
133
  # Should look like 'Name'
141
- rule(:variable) {
142
- #identifier | (str("'") >> spaces? >> identifier.repeat >> str("'")) >> spaces?
134
+ rule(:variable) do
143
135
  identifier | quoted_identifier
144
- }
136
+ end
145
137
 
146
138
  # Does not self-evaluate
147
139
  # Use to call function: FUNCTION_NAME(variable_list, ..)
148
- rule(:variable_list) {
140
+ rule(:variable_list) do
149
141
  conditional_expression >> (comma >> conditional_expression).repeat
150
- }
142
+ end
151
143
 
152
- rule(:paren_variable_list) {
144
+ rule(:paren_variable_list) do
153
145
  (left_paren >> variable_list.repeat >> right_paren).as(:paren_list)
154
- }
146
+ end
155
147
 
156
148
  # Does not self-evaluate
157
149
  # Used to create function: DEF FUNCTION_NAME(argument_list, ..)
158
- rule(:argument_list) {
150
+ rule(:argument_list) do
159
151
  argument >> (comma >> argument).repeat
160
- }
152
+ end
161
153
 
162
- rule(:paren_argument_list) {
154
+ rule(:paren_argument_list) do
163
155
  (left_paren >> argument_list.repeat >> right_paren).as(:paren_list)
164
- }
156
+ end
165
157
 
166
158
  # Atoms can self-evaluate
167
159
  # This where the grammar starts
168
- rule(:atom) {
160
+ rule(:atom) do
169
161
  paren_expression.as(:paren_expression) | boolean | variable.as(:variable) | number | string
170
- }
162
+ end
171
163
 
172
164
  # (1 + 2)
173
- rule(:paren_expression) {
165
+ rule(:paren_expression) do
174
166
  left_paren >> conditional_expression >> right_paren
175
- }
167
+ end
176
168
 
177
- rule(:non_ops_expression) {
169
+ rule(:non_ops_expression) do
178
170
  (atom.as(:left) >>
179
- power_of >>
180
- atom.as(:right)).as(:non_ops) | atom
181
- }
171
+ power_of >>
172
+ atom.as(:right)).as(:non_ops) | atom
173
+ end
182
174
 
183
175
  # IF(1, 2, 3)
184
176
  # AND(1, 2, ...)
185
- rule(:function_call_expression) {
177
+ rule(:function_call_expression) do
186
178
  (identifier.as(:name) >> paren_variable_list.as(:variable_list)).as(:function_call) |
187
- (str('+') >> non_ops_expression).as(:positive) |
188
- (str('-') >> non_ops_expression).as(:negative) | non_ops_expression
189
- }
179
+ (str('+') >> non_ops_expression).as(:positive) |
180
+ (str('-') >> non_ops_expression).as(:negative) | non_ops_expression
181
+ end
190
182
 
191
183
  # 1 + 2
192
- rule(:additive_expression) {
184
+ rule(:additive_expression) do
193
185
  multiplicative_expression.as(:left) >>
194
- ((add | subtract) >>
195
- multiplicative_expression.as(:right)).repeat.as(:ops)
196
- }
186
+ ((add | subtract) >>
187
+ multiplicative_expression.as(:right)).repeat.as(:ops)
188
+ end
197
189
 
198
190
  # 1 * 2
199
- rule(:multiplicative_expression) {
191
+ rule(:multiplicative_expression) do
200
192
  function_call_expression.as(:left) >>
201
- ((multiply | divide | modulus) >>
202
- function_call_expression.as(:right)).repeat.as(:ops)
203
- }
193
+ ((multiply | divide | modulus) >>
194
+ function_call_expression.as(:right)).repeat.as(:ops)
195
+ end
204
196
 
205
197
  # 1 < 2
206
198
  # 1 > 2
207
199
  # 1 <= 2
208
200
  # 1 >= 2
209
- rule(:relational_expression) {
201
+ rule(:relational_expression) do
210
202
  additive_expression.as(:left) >>
211
- ((less | greater | less_equal | greater_equal) >>
212
- relational_expression.as(:right)).repeat.as(:ops)
213
- }
203
+ ((less | greater | less_equal | greater_equal) >>
204
+ relational_expression.as(:right)).repeat.as(:ops)
205
+ end
214
206
 
215
207
  # 1 = 2
216
- rule(:equality_expression) {
208
+ rule(:equality_expression) do
217
209
  relational_expression.as(:left) >>
218
- ((excel_equal | equal | not_equal) >>
219
- equality_expression.as(:right)).repeat.as(:ops)
220
- }
210
+ ((excel_equal | equal | not_equal) >>
211
+ equality_expression.as(:right)).repeat.as(:ops)
212
+ end
221
213
 
222
214
  # 1 && 2
223
- rule(:logical_and_expression) {
215
+ rule(:logical_and_expression) do
224
216
  equality_expression.as(:left) >>
225
- ((logical_and | string_and) >>
226
- logical_and_expression.as(:right)).repeat.as(:ops)
227
- }
217
+ ((logical_and | string_and) >>
218
+ logical_and_expression.as(:right)).repeat.as(:ops)
219
+ end
228
220
 
229
221
  # 1 || 2
230
- rule(:logical_or_expression) {
222
+ rule(:logical_or_expression) do
231
223
  logical_and_expression.as(:left) >>
232
- ((logical_or | string_or) >>
233
- logical_or_expression.as(:right)).repeat.as(:ops)
234
- }
224
+ ((logical_or | string_or) >>
225
+ logical_or_expression.as(:right)).repeat.as(:ops)
226
+ end
235
227
 
236
228
  # 1 > 2 ? 3 : 4
237
- rule(:conditional_expression) {
229
+ rule(:conditional_expression) do
238
230
  logical_or_expression.as(:condition) >>
239
- (question_mark >>
240
- conditional_expression.as(:true_cond) >>
241
- colon >>
242
- conditional_expression.as(:false_cond)).maybe
243
- }
231
+ (question_mark >>
232
+ conditional_expression.as(:true_cond) >>
233
+ colon >>
234
+ conditional_expression.as(:false_cond)).maybe
235
+ end
244
236
 
245
237
  # 'a' = 1
246
238
  # We don't allow for nested assignments:
247
239
  # IF('a' = 1, 1, 2)
248
- rule(:assignment_expression) {
240
+ rule(:assignment_expression) do
249
241
  (variable.as(:identifier) >>
250
- assign >>
251
- assignment_expression.as(:value)).as(:assign) |
252
- conditional_expression
253
- }
242
+ assign >>
243
+ assignment_expression.as(:value)).as(:assign) |
244
+ conditional_expression
245
+ end
254
246
 
255
- rule(:expression) {
247
+ rule(:expression) do
256
248
  assignment_expression
257
- }
249
+ end
258
250
 
259
- rule(:expressions) {
251
+ rule(:expressions) do
260
252
  expression >> (separator >> expressions).repeat
261
- }
253
+ end
262
254
 
263
- rule(:function_body) {
255
+ rule(:function_body) do
264
256
  expressions.as(:expressions)
265
- }
257
+ end
266
258
 
267
- rule(:function_definition_expression) {
259
+ rule(:function_definition_expression) do
268
260
  (str('DEFINE') >> spaces? >> identifier.as(:name) >>
269
- paren_argument_list.as(:argument_list) >>
270
- left_brace >> function_body.as(:body) >> right_brace).as(:function_definition) |
271
- expressions.as(:expressions)
272
- }
261
+ paren_argument_list.as(:argument_list) >>
262
+ left_brace >> function_body.as(:body) >> right_brace).as(:function_definition) |
263
+ expressions.as(:expressions)
264
+ end
273
265
 
274
- rule(:function_definition_expressions) {
266
+ rule(:function_definition_expressions) do
275
267
  function_definition_expression >> separator.maybe >> function_definition_expressions.repeat
276
- }
268
+ end
277
269
 
278
- rule(:commands) {
270
+ rule(:commands) do
279
271
  function_definition_expressions | expressions
280
- }
272
+ end
281
273
 
282
- rule(:line) {
274
+ rule(:line) do
283
275
  commands.as(:commands)
284
- }
276
+ end
285
277
 
286
- rule(:lines) {
278
+ rule(:lines) do
287
279
  line >> (new_line >> lines).repeat
288
- }
280
+ end
289
281
 
290
282
  root :lines
291
283
  end
@@ -8,7 +8,6 @@ module Kalc
8
8
 
9
9
  def initialize
10
10
  @env = Environment.new do |env|
11
-
12
11
  env.add_function(:IF, lambda { |cxt, cond, if_true, if_false|
13
12
  cond.eval(cxt) ? if_true.eval(cxt) : if_false.eval(cxt)
14
13
  })
@@ -43,7 +42,7 @@ module Kalc
43
42
  rand(val.eval(cxt))
44
43
  })
45
44
 
46
- env.add_function(:SYSTEM, lambda { |cxt, val|
45
+ env.add_function(:SYSTEM, lambda { |_cxt, _val|
47
46
  throw "Nope. I don't think so!"
48
47
  })
49
48
 
@@ -99,15 +98,21 @@ module Kalc
99
98
  })
100
99
 
101
100
  env.add_function(:MAX, lambda { |cxt, first, *args|
102
- (args<<first).map { |a| a.eval(cxt) }.max
101
+ evaluated_args = (args << first).map { |a| a.eval(cxt) }
102
+ return BigDecimal('NaN') if evaluated_args.any? { |a| a.is_a?(BigDecimal) && a.nan? }
103
+
104
+ evaluated_args.max
103
105
  })
104
106
 
105
107
  env.add_function(:MIN, lambda { |cxt, first, *args|
106
- (args<<first).map { |a| a.eval(cxt) }.min
108
+ evaluated_args = (args << first).map { |a| a.eval(cxt) }
109
+ return BigDecimal('NaN') if evaluated_args.any? { |a| a.is_a?(BigDecimal) && a.nan? }
110
+
111
+ evaluated_args.min
107
112
  })
108
113
 
109
114
  math_funs =
110
- %w(acos acosh asin asinh atan atanh cbrt cos cosh erf erfc exp gamma lgamma log log2 log10 sin sinh sqrt tan tanh)
115
+ %w[acos acosh asin asinh atan atanh cbrt cos cosh erf erfc exp gamma lgamma log log2 log10 sin sinh sqrt tan tanh]
111
116
 
112
117
  math_funs.each do |math_fun|
113
118
  env.add_function(math_fun.upcase.to_sym, lambda { |cxt, val|
@@ -117,12 +122,12 @@ module Kalc
117
122
 
118
123
  # Strings
119
124
  string_funs =
120
- %w(chomp chop chr clear count
121
- downcase
122
- hex
123
- inspect intern
124
- to_sym length size lstrip succ next oct ord reverse rstrip strip
125
- swapcase to_c to_f to_i to_r upcase)
125
+ %w[chomp chop chr clear count
126
+ downcase
127
+ hex
128
+ inspect intern
129
+ to_sym length size lstrip succ next oct ord reverse rstrip strip
130
+ swapcase to_c to_f to_i to_r upcase]
126
131
 
127
132
  string_funs.each do |str_fun|
128
133
  env.add_function(str_fun.upcase.to_sym, lambda { |cxt, val|
@@ -147,7 +152,7 @@ module Kalc
147
152
  })
148
153
 
149
154
  env.add_function(:DOLLAR, lambda { |cxt, val, decimal_places|
150
- "%.#{Integer(decimal_places.eval(cxt))}f" % BigDecimal.new(val.eval(cxt))
155
+ format("%.#{Integer(decimal_places.eval(cxt))}f", BigDecimal(val.eval(cxt)))
151
156
  })
152
157
 
153
158
  env.add_function(:EXACT, lambda { |cxt, string1, string2|
@@ -160,8 +165,8 @@ module Kalc
160
165
  })
161
166
 
162
167
  env.add_function(:FIXED, lambda { |cxt, val, decimal_places, no_commas|
163
- output = "%.#{Integer(decimal_places.eval(cxt))}f" % BigDecimal.new(val.eval(cxt))
164
- output = output.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse if !no_commas.eval(cxt)
168
+ output = format("%.#{Integer(decimal_places.eval(cxt))}f", BigDecimal(val.eval(cxt)))
169
+ output = output.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse unless no_commas.eval(cxt)
165
170
  output
166
171
  })
167
172
 
@@ -179,7 +184,6 @@ module Kalc
179
184
  })
180
185
 
181
186
  env.add_function(:MID, lambda { |cxt, val, start_position, number_of_characters|
182
-
183
187
  start = Integer(start_position.eval(cxt)) - 1
184
188
  chars = Integer(number_of_characters.eval(cxt)) - 1
185
189
 
@@ -187,7 +191,7 @@ module Kalc
187
191
  })
188
192
 
189
193
  env.add_function(:PROPER, lambda { |cxt, val|
190
- val.eval(cxt).split(' ').map { |c| c.capitalize }.join(' ')
194
+ val.eval(cxt).split(' ').map(&:capitalize).join(' ')
191
195
  })
192
196
 
193
197
  env.add_function(:REPLACE, lambda { |cxt, val, start_position, number_of_chars, new_text|
@@ -242,11 +246,11 @@ module Kalc
242
246
  })
243
247
 
244
248
  # Debug
245
- env.add_function(:P, lambda { |cxt, *output|
249
+ env.add_function(:P, lambda { |_cxt, *output|
246
250
  p output
247
251
  })
248
252
 
249
- env.add_function(:PP, lambda { |cxt, *output|
253
+ env.add_function(:PP, lambda { |_cxt, *output|
250
254
  pp output
251
255
  })
252
256
 
@@ -255,13 +259,18 @@ module Kalc
255
259
  })
256
260
 
257
261
  env.add_function(:FLOOR, lambda { |cxt, val|
258
- val.eval(cxt).floor
262
+ value = val.eval(cxt)
263
+ return value if value.is_a?(BigDecimal) && (value.nan? || value.infinite?)
264
+
265
+ value.floor
259
266
  })
260
267
 
261
268
  env.add_function(:CEILING, lambda { |cxt, val|
262
- val.eval(cxt).ceil
263
- })
269
+ value = val.eval(cxt)
270
+ return value if value.is_a?(BigDecimal) && (value.nan? || value.infinite?)
264
271
 
272
+ value.ceil
273
+ })
265
274
  end
266
275
  end
267
276
 
@@ -18,27 +18,26 @@ module Kalc
18
18
  puts 'You are ready to go. Have fun!'
19
19
  puts ''
20
20
 
21
- function_list = %w(quit exit functions variables ast) + @kalc.interpreter.env.functions.map { |f| f.first }
21
+ function_list = %w[quit exit functions variables ast] + @kalc.interpreter.env.functions.map(&:first)
22
22
 
23
23
  begin
24
24
  comp = proc { |s| function_list.grep(/^#{Regexp.escape(s)}/) }
25
25
  Readline.completion_append_character = ''
26
26
  Readline.completion_proc = comp
27
27
 
28
- while input = Readline.readline("kalc-#{Kalc::VERSION} > ", true)
28
+ while (input = Readline.readline("kalc-#{Kalc::VERSION} > ", true))
29
29
  begin
30
- case
31
- when (input == 'quit' || input == 'exit')
30
+ if input == 'quit' || input == 'exit'
32
31
  break
33
- when input == 'functions'
34
- puts @kalc.interpreter.env.functions.map { |f| f.first }.join(', ')
35
- when input == 'variables'
32
+ elsif input == 'functions'
33
+ puts @kalc.interpreter.env.functions.map(&:first).join(', ')
34
+ elsif input == 'variables'
36
35
  puts @kalc.interpreter.env.variables.map { |v| "#{v[0]} = #{v[1]}" }.join("\n\r")
37
- when input == 'reload'
36
+ elsif input == 'reload'
38
37
  load_env
39
- when input == 'ast'
38
+ elsif input == 'ast'
40
39
  pp @kalc.ast
41
- when input != ''
40
+ elsif input != ''
42
41
  puts @kalc.run(input)
43
42
  end
44
43
  rescue Parslet::ParseFailed => e
@@ -55,12 +54,12 @@ module Kalc
55
54
  end
56
55
 
57
56
  def heading
58
- %q{
57
+ '
59
58
  This is Kalc, a small line-based language.
60
59
  More information about Kalc can be found at https://github.com/mrcsparker/kalc.
61
60
 
62
61
  Kalc is free software, provided as is, with no warranty.
63
- }
62
+ '
64
63
  end
65
64
  end
66
65
  end
@@ -1,116 +1,115 @@
1
1
  module Kalc
2
2
  class Transform < Parslet::Transform
3
-
4
- rule(:condition => subtree(:condition)) {
3
+ rule(condition: subtree(:condition)) do
5
4
  condition
6
- }
5
+ end
7
6
 
8
- rule(:right => subtree(:right), :ops => []) {
7
+ rule(right: subtree(:right), ops: []) do
9
8
  right
10
- }
9
+ end
11
10
 
12
- rule(:left => subtree(:left), :non_ops => []) {
11
+ rule(left: subtree(:left), non_ops: []) do
13
12
  left
14
- }
13
+ end
15
14
 
16
- rule(:right => subtree(:right), :non_ops => []) {
15
+ rule(right: subtree(:right), non_ops: []) do
17
16
  right
18
- }
17
+ end
19
18
 
20
- rule(:commands => sequence(:commands)) {
19
+ rule(commands: sequence(:commands)) do
21
20
  Ast::Commands.new(commands)
22
- }
21
+ end
23
22
 
24
- rule(:commands => simple(:commands)) {
23
+ rule(commands: simple(:commands)) do
25
24
  Ast::Commands.new([commands])
26
- }
25
+ end
27
26
 
28
- rule(:negative => simple(:negative)) {
27
+ rule(negative: simple(:negative)) do
29
28
  Ast::Negative.new(negative)
30
- }
29
+ end
31
30
 
32
- rule(:positive => simple(:positive)) {
31
+ rule(positive: simple(:positive)) do
33
32
  Ast::Positive.new(positive)
34
- }
33
+ end
35
34
 
36
- rule(:expressions => sequence(:expressions)) {
35
+ rule(expressions: sequence(:expressions)) do
37
36
  Ast::Expressions.new(expressions)
38
- }
37
+ end
39
38
 
40
- rule(:expressions => simple(:expressions)) {
39
+ rule(expressions: simple(:expressions)) do
41
40
  Ast::Expressions.new([expressions])
42
- }
41
+ end
43
42
 
44
- rule(:string => simple(:string)) {
43
+ rule(string: simple(:string)) do
45
44
  Ast::StringValue.new(string)
46
- }
45
+ end
47
46
 
48
- rule(:string => sequence(:string)) {
49
- string = '' if string == nil
47
+ rule(string: sequence(:string)) do
48
+ string = '' if string.nil?
50
49
  Ast::StringValue.new(string)
51
- }
50
+ end
52
51
 
53
- rule(:boolean => simple(:boolean)) {
52
+ rule(boolean: simple(:boolean)) do
54
53
  Ast::BooleanValue.new(boolean)
55
- }
54
+ end
56
55
 
57
- rule(:number => simple(:number)) {
56
+ rule(number: simple(:number)) do
58
57
  Ast::BigDecimalNumber.new(number)
59
- }
58
+ end
60
59
 
61
- rule(:non_ops => subtree(:non_ops)) {
60
+ rule(non_ops: subtree(:non_ops)) do
62
61
  Ast::NonOps.new(non_ops)
63
- }
62
+ end
64
63
 
65
- rule(:left => simple(:left), :ops => subtree(:ops)) {
64
+ rule(left: simple(:left), ops: subtree(:ops)) do
66
65
  ops.empty? ? left : Ast::Ops.new(left, ops)
67
- }
66
+ end
68
67
 
69
- rule(:left => simple(:left), :right => simple(:right), :operator => simple(:operator)) {
68
+ rule(left: simple(:left), right: simple(:right), operator: simple(:operator)) do
70
69
  Ast::Arithmetic.new(left, right, operator)
71
- }
70
+ end
72
71
 
73
- rule(:condition => simple(:condition), :true_cond => simple(:true_cond), :false_cond => simple(:false_cond)) {
72
+ rule(condition: simple(:condition), true_cond: simple(:true_cond), false_cond: simple(:false_cond)) do
74
73
  Ast::Conditional.new(condition, true_cond, false_cond)
75
- }
74
+ end
76
75
 
77
- rule(:variable => simple(:variable)) {
76
+ rule(variable: simple(:variable)) do
78
77
  Ast::Variable.new(variable)
79
- }
78
+ end
80
79
 
81
- rule(:identifier => simple(:identifier)) {
80
+ rule(identifier: simple(:identifier)) do
82
81
  identifier
83
- }
82
+ end
84
83
 
85
- rule(:assign => {:value => simple(:value), :identifier => simple(:identifier), :operator => simple(:operator)}) {
84
+ rule(assign: { value: simple(:value), identifier: simple(:identifier), operator: simple(:operator) }) do
86
85
  Ast::Identifier.new(identifier, value)
87
- }
86
+ end
88
87
 
89
- rule(:paren_list => sequence(:paren_list)) {
88
+ rule(paren_list: sequence(:paren_list)) do
90
89
  paren_list
91
- }
90
+ end
92
91
 
93
- rule(:paren_list => '()') {
92
+ rule(paren_list: '()') do
94
93
  []
95
- }
94
+ end
96
95
 
97
- rule(:paren_expression => simple(:paren_expression)) {
96
+ rule(paren_expression: simple(:paren_expression)) do
98
97
  Ast::ParenExpression.new(paren_expression)
99
- }
98
+ end
100
99
 
101
- rule(:function_call => {:name => simple(:name),
102
- :variable_list => sequence(:variable_list)}) {
100
+ rule(function_call: { name: simple(:name),
101
+ variable_list: sequence(:variable_list) }) do
103
102
  Ast::FunctionCall.new(name, variable_list)
104
- }
103
+ end
105
104
 
106
- rule(:argument => simple(:argument)) {
105
+ rule(argument: simple(:argument)) do
107
106
  Ast::Identifier.new(argument, argument)
108
- }
107
+ end
109
108
 
110
- rule(:function_definition => {:name => simple(:name),
111
- :argument_list => sequence(:argument_list),
112
- :body => simple(:body)}) {
109
+ rule(function_definition: { name: simple(:name),
110
+ argument_list: sequence(:argument_list),
111
+ body: simple(:body) }) do
113
112
  Ast::FunctionDefinition.new(name, argument_list, body)
114
- }
113
+ end
115
114
  end
116
115
  end
@@ -1,3 +1,3 @@
1
1
  module Kalc
2
- VERSION = '1.2.0'
2
+ VERSION = '1.7.0'.freeze
3
3
  end
@@ -5,7 +5,7 @@ describe Kalc::Grammar do
5
5
 
6
6
  context 'integers' do
7
7
  1.upto(10) do |i|
8
- it { expect(grammar).to parse("#{i}") }
8
+ it { expect(grammar).to parse(i.to_s) }
9
9
  end
10
10
  end
11
11
 
@@ -44,7 +44,7 @@ describe Kalc::Interpreter do
44
44
  expect(evaluate("'a b' := 1; 'a b' + 1")).to eq(2)
45
45
  end
46
46
 
47
- it { expect(evaluate('((75.0)*(25.0))+((125.0)*(25.0))+((150.0)*(25.0))+((250.0)*(25.0))')).to eq(15000) }
47
+ it { expect(evaluate('((75.0)*(25.0))+((125.0)*(25.0))+((150.0)*(25.0))+((250.0)*(25.0))')).to eq(15_000) }
48
48
 
49
49
  it { expect(evaluate('(((40.0)/1000*(4380.0)*(200.0))-((40.0)/1000*(4380.0)*((((75.0)*(25.0))+((125.0)*(25.0))+((150.0)*(25.0))+((250.0)*(25.0)))/(10.0)/(40.0)/(0.8))))*(0.05)')).to eq(1341.375) }
50
50
 
@@ -52,21 +52,21 @@ describe Kalc::Interpreter do
52
52
  it { expect(evaluate('-2')).to eq(-2) }
53
53
  it { expect(evaluate('-1000')).to eq(-1000) }
54
54
  it { expect(evaluate('-1000.0001')).to eq(-1000.0001) }
55
- it { expect(evaluate('1 + -1')).to eq(0 )}
55
+ it { expect(evaluate('1 + -1')).to eq(0) }
56
56
  it { expect(evaluate('1 + -10')).to eq(-9) }
57
57
  end
58
58
 
59
59
  context 'Positive numbers' do
60
60
  it { expect(evaluate('1 + +1')).to eq(2) }
61
61
  it { expect(evaluate('1 + +1 - 1')).to eq(1) }
62
- it { expect(evaluate('+10000.0001')).to eq(10000.0001) }
62
+ it { expect(evaluate('+10000.0001')).to eq(10_000.0001) }
63
63
  end
64
64
 
65
65
  context 'Boolean value' do
66
66
  it { expect(evaluate('TRUE')).to eq(true) }
67
67
  it { expect(evaluate('FALSE')).to eq(false) }
68
68
  it { expect(evaluate('FALSE || TRUE')).to eq(true) }
69
- it { expect(evaluate('FALSE && TRUE')).to eq(false) }
69
+ it { expect(evaluate('FALSE && TRUE')).to eq(false) }
70
70
  end
71
71
 
72
72
  context 'Decimal numbers' do
@@ -84,7 +84,7 @@ describe Kalc::Interpreter do
84
84
  end
85
85
 
86
86
  context 'Exponents' do
87
- it { expect(evaluate('1.23e+10')).to eq(12300000000.0) }
87
+ it { expect(evaluate('1.23e+10')).to eq(12_300_000_000.0) }
88
88
  it { expect(evaluate('1.23e-10')).to eq(1.23e-10) }
89
89
  end
90
90
 
@@ -96,8 +96,12 @@ describe Kalc::Interpreter do
96
96
  context 'Min and Max Functions' do
97
97
  it { expect(evaluate('MAX(3, 1, 2)')).to eq(3) }
98
98
  it { expect(evaluate('MAX(-1, 2*4, (3-1)*2, 5, 6)')).to eq(8) }
99
+ it { expect(evaluate('MAX(0/0, 2)').to_s).to eq('NaN') }
100
+ it { expect(evaluate('MAX(1/0, 3)').to_s).to eq('Infinity') }
99
101
  it { expect(evaluate('MIN(3, 1, 2)')).to eq(1) }
100
102
  it { expect(evaluate('MIN(-1, 2*4, (3-1)*2, 5, 6)')).to eq(-1) }
103
+ it { expect(evaluate('MIN(0/0, 2)').to_s).to eq('NaN') }
104
+ it { expect(evaluate('MIN(-1/0, 3)').to_s).to eq('-Infinity') }
101
105
  context 'having variables' do
102
106
  it { expect(evaluate('var := 15; MAX(1, var, 10)')).to eq(15) }
103
107
  it { expect(evaluate('var := 15; MIN(1, var, -10)')).to eq(-10) }
@@ -109,10 +113,14 @@ describe Kalc::Interpreter do
109
113
  it { expect(evaluate('FLOOR(3.8)')).to eq(3) }
110
114
  it { expect(evaluate('FLOOR(-3.4)')).to eq(-4) }
111
115
  it { expect(evaluate('FLOOR(3)')).to eq(3) }
116
+ it { expect(evaluate('FLOOR(0/0)').to_s).to eq('NaN') }
117
+ it { expect(evaluate('FLOOR(1/0)').to_s).to eq('Infinity') }
112
118
  it { expect(evaluate('CEILING(3)')).to eq(3) }
113
119
  it { expect(evaluate('CEILING(3.8)')).to eq(4) }
114
120
  it { expect(evaluate('CEILING(3.8)')).to eq(4) }
115
121
  it { expect(evaluate('CEILING(-3.2)')).to eq(-3) }
122
+ it { expect(evaluate('CEILING(0/0)').to_s).to eq('NaN') }
123
+ it { expect(evaluate('CEILING(1/0)').to_s).to eq('Infinity') }
116
124
 
117
125
  context 'having variables' do
118
126
  it { expect(evaluate('var := 2.456; FLOOR(var)')).to eq(2) }
@@ -135,6 +143,7 @@ describe Kalc::Interpreter do
135
143
  end
136
144
 
137
145
  private
146
+
138
147
  def evaluate(expression)
139
148
  g = @grammar.parse(expression)
140
149
  ast = @transform.apply(g)
@@ -1,11 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class Kalc::Stdlib
4
-
5
4
  end
6
5
 
7
6
  describe Kalc::Stdlib do
8
-
9
7
  before(:each) do
10
8
  @grammar = Kalc::Grammar.new
11
9
  @transform = Kalc::Transform.new
@@ -51,6 +49,7 @@ describe Kalc::Stdlib do
51
49
  end
52
50
 
53
51
  private
52
+
54
53
  def evaluate(expression)
55
54
  r = Kalc::Runner.new
56
55
  r.run(expression)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kalc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Parker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-08 00:00:00.000000000 Z
11
+ date: 2018-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.7'
47
+ version: 1.8.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.7'
54
+ version: 1.8.2
55
55
  description: Calculation language slightly based on Excel's formula language.
56
56
  email:
57
57
  - mrcsparker@gmail.com
@@ -103,13 +103,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  version: '0'
104
104
  requirements: []
105
105
  rubyforge_project: kalc
106
- rubygems_version: 2.6.4
106
+ rubygems_version: 2.7.7
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Small calculation language.
110
- test_files:
111
- - spec/grammar_spec.rb
112
- - spec/interpreter_spec.rb
113
- - spec/spec_helper.rb
114
- - spec/stdlib_spec.rb
115
- has_rdoc:
110
+ test_files: []