kalc 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +14 -13
- data/bin/ikalc +2 -2
- data/bin/kalc +2 -2
- data/kalc.gemspec +16 -16
- data/lib/kalc.rb +5 -5
- data/lib/kalc/ast.rb +36 -35
- data/lib/kalc/environment.rb +2 -2
- data/lib/kalc/grammar.rb +53 -53
- data/lib/kalc/interpreter.rb +39 -53
- data/lib/kalc/repl.rb +17 -21
- data/lib/kalc/transform.rb +18 -18
- data/lib/kalc/version.rb +1 -1
- data/spec/grammar_spec.rb +51 -51
- data/spec/interpreter_spec.rb +53 -53
- data/spec/stdlib_spec.rb +26 -26
- metadata +37 -32
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,27 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
kalc (0.
|
5
|
-
parslet (~> 1.
|
4
|
+
kalc (0.9.0)
|
5
|
+
parslet (~> 1.5)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
9
9
|
specs:
|
10
10
|
blankslate (2.1.2.4)
|
11
|
-
diff-lcs (1.
|
12
|
-
parslet (1.
|
11
|
+
diff-lcs (1.2.4)
|
12
|
+
parslet (1.5.0)
|
13
13
|
blankslate (~> 2.0)
|
14
|
-
rake (
|
15
|
-
rspec (2.
|
16
|
-
rspec-core (~> 2.
|
17
|
-
rspec-expectations (~> 2.
|
18
|
-
rspec-mocks (~> 2.
|
19
|
-
rspec-core (2.
|
20
|
-
rspec-expectations (2.
|
21
|
-
diff-lcs (
|
22
|
-
rspec-mocks (2.
|
14
|
+
rake (10.1.0)
|
15
|
+
rspec (2.14.1)
|
16
|
+
rspec-core (~> 2.14.0)
|
17
|
+
rspec-expectations (~> 2.14.0)
|
18
|
+
rspec-mocks (~> 2.14.0)
|
19
|
+
rspec-core (2.14.5)
|
20
|
+
rspec-expectations (2.14.2)
|
21
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
22
|
+
rspec-mocks (2.14.3)
|
23
23
|
|
24
24
|
PLATFORMS
|
25
|
+
java
|
25
26
|
ruby
|
26
27
|
|
27
28
|
DEPENDENCIES
|
data/bin/ikalc
CHANGED
data/bin/kalc
CHANGED
data/kalc.gemspec
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path(
|
3
|
-
require
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'kalc/version'
|
4
4
|
#
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name
|
7
|
-
s.version
|
8
|
-
s.authors
|
9
|
-
s.email
|
10
|
-
s.homepage
|
11
|
-
s.summary
|
6
|
+
s.name = 'kalc'
|
7
|
+
s.version = Kalc::VERSION
|
8
|
+
s.authors = ['Chris Parker']
|
9
|
+
s.email = %w(mrcsparker@gmail.com)
|
10
|
+
s.homepage = 'https://github.com/mrcsparker/kalc'
|
11
|
+
s.summary = %q{Small calculation language.}
|
12
12
|
s.description = %q{Calculation language slightly based on Excel's formula language.}
|
13
13
|
|
14
|
-
s.rubyforge_project =
|
14
|
+
s.rubyforge_project = 'kalc'
|
15
15
|
|
16
|
-
s.files
|
17
|
-
s.test_files
|
18
|
-
s.executables
|
19
|
-
s.require_paths =
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
s.require_paths = %w(lib)
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
s.add_development_dependency
|
23
|
-
s.add_development_dependency
|
24
|
-
s.add_runtime_dependency
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_runtime_dependency 'parslet', '~> 1.5'
|
25
25
|
end
|
data/lib/kalc.rb
CHANGED
@@ -11,7 +11,7 @@ require 'kalc/repl'
|
|
11
11
|
|
12
12
|
module Kalc
|
13
13
|
class Runner
|
14
|
-
|
14
|
+
|
15
15
|
attr_accessor :grammar
|
16
16
|
attr_accessor :transform
|
17
17
|
attr_accessor :interpreter
|
@@ -40,13 +40,13 @@ module Kalc
|
|
40
40
|
|
41
41
|
private
|
42
42
|
def load_environment
|
43
|
-
@log.debug
|
43
|
+
@log.debug 'Loading grammar' if @debug
|
44
44
|
@grammar = Kalc::Grammar.new
|
45
|
-
@log.debug
|
45
|
+
@log.debug 'Loading transform' if @debug
|
46
46
|
@transform = Kalc::Transform.new
|
47
|
-
@log.debug
|
47
|
+
@log.debug 'Loading interpreter' if @debug
|
48
48
|
@interpreter = Kalc::Interpreter.new
|
49
|
-
@log.debug
|
49
|
+
@log.debug 'Loading stdlib' if @debug
|
50
50
|
@interpreter.load_stdlib(@grammar, @transform)
|
51
51
|
end
|
52
52
|
end
|
data/lib/kalc/ast.rb
CHANGED
@@ -117,7 +117,7 @@ module Kalc
|
|
117
117
|
class Ops
|
118
118
|
attr_reader :left
|
119
119
|
attr_reader :ops
|
120
|
-
|
120
|
+
|
121
121
|
def initialize(left, ops)
|
122
122
|
@left = left
|
123
123
|
@ops = ops
|
@@ -145,40 +145,40 @@ module Kalc
|
|
145
145
|
|
146
146
|
def eval(context)
|
147
147
|
case @operator.to_s.strip
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
148
|
+
when '&&'
|
149
|
+
@left && @right
|
150
|
+
when 'and'
|
151
|
+
@left && @right
|
152
|
+
when '||'
|
153
|
+
@left || @right
|
154
|
+
when 'or'
|
155
|
+
@left || @right
|
156
|
+
when '<='
|
157
|
+
@left <= @right
|
158
|
+
when '>='
|
159
|
+
@left >= @right
|
160
|
+
when '='
|
161
|
+
@left == @right
|
162
|
+
when '=='
|
163
|
+
@left == @right
|
164
|
+
when '!='
|
165
|
+
@left != @right
|
166
|
+
when '-'
|
167
|
+
@left - @right
|
168
|
+
when '+'
|
169
|
+
@left + @right
|
170
|
+
when '^'
|
171
|
+
@left ** @right
|
172
|
+
when '*'
|
173
|
+
@left * @right
|
174
|
+
when '/'
|
175
|
+
@left / @right
|
176
|
+
when '%'
|
177
|
+
@left % @right
|
178
|
+
when '<'
|
179
|
+
@left < @right
|
180
|
+
when '>'
|
181
|
+
@left > @right
|
182
182
|
end
|
183
183
|
end
|
184
184
|
end
|
@@ -213,6 +213,7 @@ module Kalc
|
|
213
213
|
|
214
214
|
class Variable
|
215
215
|
attr_reader :variable
|
216
|
+
|
216
217
|
def initialize(variable)
|
217
218
|
@variable = variable
|
218
219
|
end
|
data/lib/kalc/environment.rb
CHANGED
@@ -12,7 +12,7 @@ module Kalc
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def add_function(name, value)
|
15
|
-
@functions.update({
|
15
|
+
@functions.update({name.to_s.strip => value})
|
16
16
|
end
|
17
17
|
|
18
18
|
def get_function(name)
|
@@ -26,7 +26,7 @@ module Kalc
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def add_variable(name, value)
|
29
|
-
@variables.update({
|
29
|
+
@variables.update({name.to_s.strip => value})
|
30
30
|
value
|
31
31
|
end
|
32
32
|
|
data/lib/kalc/grammar.rb
CHANGED
@@ -22,7 +22,7 @@ class Kalc::Grammar < Parslet::Parser
|
|
22
22
|
rule(:new_line) { match('[\n\r]').repeat(1) }
|
23
23
|
rule(:separator) { str(';') >> spaces? }
|
24
24
|
|
25
|
-
def self.symbols(symbols)
|
25
|
+
def self.symbols(symbols)
|
26
26
|
symbols.each do |name, symbol|
|
27
27
|
rule(name) { str(symbol) >> spaces? }
|
28
28
|
end
|
@@ -37,21 +37,21 @@ class Kalc::Grammar < Parslet::Parser
|
|
37
37
|
:question_mark => '?'
|
38
38
|
|
39
39
|
def self.operators(operators={})
|
40
|
-
trailing_chars = Hash.new { |hash,symbol| hash[symbol] = [] }
|
40
|
+
trailing_chars = Hash.new { |hash, symbol| hash[symbol] = [] }
|
41
41
|
|
42
42
|
operators.each_value do |symbol|
|
43
43
|
operators.each_value do |op|
|
44
|
-
if op[0,symbol.length] == symbol
|
45
|
-
char = op[symbol.length,1]
|
44
|
+
if op[0, symbol.length] == symbol
|
45
|
+
char = op[symbol.length, 1]
|
46
46
|
|
47
|
-
unless
|
47
|
+
unless char.nil? || char.empty?
|
48
48
|
trailing_chars[symbol] << char
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
operators.each do |name,symbol|
|
54
|
+
operators.each do |name, symbol|
|
55
55
|
trailing = trailing_chars[symbol]
|
56
56
|
|
57
57
|
if trailing.empty?
|
@@ -101,12 +101,12 @@ class Kalc::Grammar < Parslet::Parser
|
|
101
101
|
}
|
102
102
|
|
103
103
|
rule(:string) {
|
104
|
-
str('"') >>
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
104
|
+
str('"') >>
|
105
|
+
(
|
106
|
+
str('\\') >> any |
|
107
|
+
str('"').absnt? >> any
|
108
|
+
).repeat.as(:string) >>
|
109
|
+
str('"')
|
110
110
|
}
|
111
111
|
|
112
112
|
rule(:exponent) {
|
@@ -116,10 +116,10 @@ class Kalc::Grammar < Parslet::Parser
|
|
116
116
|
# We are using a really broad definition of what a number is.
|
117
117
|
# Numbers can be 1, 1.0, 0.1, 1.0e4, +1.0E10, etc
|
118
118
|
rule(:number) {
|
119
|
-
(match('[+-]').maybe >>
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
(match('[+-]').maybe >>
|
120
|
+
(str('.') >> digits >> exponent.maybe).as(:number) >> spaces?) |
|
121
|
+
(match('[+-]').maybe >>
|
122
|
+
digits >> (str('.') >> digits).maybe >> exponent.maybe).as(:number) >> spaces?
|
123
123
|
}
|
124
124
|
|
125
125
|
rule(:identifier) {
|
@@ -127,11 +127,11 @@ class Kalc::Grammar < Parslet::Parser
|
|
127
127
|
}
|
128
128
|
|
129
129
|
rule(:quoted_identifier) {
|
130
|
-
str("'") >>
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
str("'") >>
|
131
|
+
(
|
132
|
+
str('\\') >> any | str("'").absnt? >> any
|
133
|
+
).repeat(1) >>
|
134
|
+
str("'") >> spaces?
|
135
135
|
}
|
136
136
|
|
137
137
|
rule(:argument) {
|
@@ -176,31 +176,31 @@ class Kalc::Grammar < Parslet::Parser
|
|
176
176
|
}
|
177
177
|
|
178
178
|
rule(:non_ops_expression) {
|
179
|
-
(atom.as(:left) >>
|
180
|
-
|
181
|
-
|
179
|
+
(atom.as(:left) >>
|
180
|
+
power_of >>
|
181
|
+
atom.as(:right)).as(:non_ops) | atom
|
182
182
|
}
|
183
183
|
|
184
184
|
# IF(1, 2, 3)
|
185
185
|
# AND(1, 2, ...)
|
186
186
|
rule(:function_call_expression) {
|
187
187
|
(identifier.as(:name) >> paren_variable_list.as(:variable_list)).as(:function_call) |
|
188
|
-
|
189
|
-
|
188
|
+
(str('+') >> non_ops_expression).as(:positive) |
|
189
|
+
(str('-') >> non_ops_expression).as(:negative) | non_ops_expression
|
190
190
|
}
|
191
191
|
|
192
192
|
# 1 + 2
|
193
193
|
rule(:additive_expression) {
|
194
|
-
multiplicative_expression.as(:left) >>
|
195
|
-
|
196
|
-
|
194
|
+
multiplicative_expression.as(:left) >>
|
195
|
+
((add | subtract) >>
|
196
|
+
multiplicative_expression.as(:right)).repeat.as(:ops)
|
197
197
|
}
|
198
198
|
|
199
199
|
# 1 * 2
|
200
200
|
rule(:multiplicative_expression) {
|
201
|
-
function_call_expression.as(:left) >>
|
202
|
-
|
203
|
-
|
201
|
+
function_call_expression.as(:left) >>
|
202
|
+
((multiply | divide | modulus) >>
|
203
|
+
function_call_expression.as(:right)).repeat.as(:ops)
|
204
204
|
}
|
205
205
|
|
206
206
|
# 1 < 2
|
@@ -208,49 +208,49 @@ class Kalc::Grammar < Parslet::Parser
|
|
208
208
|
# 1 <= 2
|
209
209
|
# 1 >= 2
|
210
210
|
rule(:relational_expression) {
|
211
|
-
additive_expression.as(:left) >>
|
212
|
-
|
213
|
-
|
211
|
+
additive_expression.as(:left) >>
|
212
|
+
((less | greater | less_equal | greater_equal) >>
|
213
|
+
relational_expression.as(:right)).repeat.as(:ops)
|
214
214
|
}
|
215
215
|
|
216
216
|
# 1 = 2
|
217
217
|
rule(:equality_expression) {
|
218
218
|
relational_expression.as(:left) >>
|
219
|
-
|
220
|
-
|
219
|
+
((excel_equal | equal | not_equal) >>
|
220
|
+
equality_expression.as(:right)).repeat.as(:ops)
|
221
221
|
}
|
222
222
|
|
223
223
|
# 1 && 2
|
224
224
|
rule(:logical_and_expression) {
|
225
225
|
equality_expression.as(:left) >>
|
226
|
-
|
227
|
-
|
226
|
+
((logical_and | string_and) >>
|
227
|
+
logical_and_expression.as(:right)).repeat.as(:ops)
|
228
228
|
}
|
229
229
|
|
230
230
|
# 1 || 2
|
231
231
|
rule(:logical_or_expression) {
|
232
232
|
logical_and_expression.as(:left) >>
|
233
|
-
|
234
|
-
|
233
|
+
((logical_or | string_or) >>
|
234
|
+
logical_or_expression.as(:right)).repeat.as(:ops)
|
235
235
|
}
|
236
236
|
|
237
237
|
# 1 > 2 ? 3 : 4
|
238
238
|
rule(:conditional_expression) {
|
239
|
-
logical_or_expression.as(:condition) >>
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
239
|
+
logical_or_expression.as(:condition) >>
|
240
|
+
(question_mark >>
|
241
|
+
conditional_expression.as(:true_cond) >>
|
242
|
+
colon >>
|
243
|
+
conditional_expression.as(:false_cond)).maybe
|
244
244
|
}
|
245
245
|
|
246
246
|
# 'a' = 1
|
247
247
|
# We don't allow for nested assignments:
|
248
248
|
# IF('a' = 1, 1, 2)
|
249
249
|
rule(:assignment_expression) {
|
250
|
-
(variable.as(:identifier) >>
|
251
|
-
|
252
|
-
|
253
|
-
|
250
|
+
(variable.as(:identifier) >>
|
251
|
+
assign >>
|
252
|
+
assignment_expression.as(:value)).as(:assign) |
|
253
|
+
conditional_expression
|
254
254
|
}
|
255
255
|
|
256
256
|
rule(:expression) {
|
@@ -267,9 +267,9 @@ class Kalc::Grammar < Parslet::Parser
|
|
267
267
|
|
268
268
|
rule(:function_definition_expression) {
|
269
269
|
(str('DEFINE') >> spaces? >> identifier.as(:name) >>
|
270
|
-
|
271
|
-
|
272
|
-
|
270
|
+
paren_argument_list.as(:argument_list) >>
|
271
|
+
left_brace >> function_body.as(:body) >> right_brace).as(:function_definition) |
|
272
|
+
expressions.as(:expressions)
|
273
273
|
}
|
274
274
|
|
275
275
|
rule(:function_definition_expressions) {
|