kalc 1.2.0 → 1.7.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
- 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: []