kalc 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.textile +74 -0
- data/bin/ikalc +8 -0
- data/bin/kalc +19 -0
- data/kalc.gemspec +25 -0
- data/lib/kalc.rb +14 -0
- data/lib/kalc/ast.rb +198 -0
- data/lib/kalc/environment.rb +43 -0
- data/lib/kalc/grammar.rb +273 -0
- data/lib/kalc/interpreter.rb +119 -0
- data/lib/kalc/repl.rb +76 -0
- data/lib/kalc/stdlib.kalc +17 -0
- data/lib/kalc/transform.rb +75 -0
- data/lib/kalc/version.rb +3 -0
- data/spec/grammar_spec.rb +109 -0
- data/spec/interpreter_spec.rb +27 -0
- data/spec/spec_helper.rb +3 -0
- metadata +101 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Chris Parker
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
h1. kalc
|
2
|
+
|
3
|
+
h2. Introduction
|
4
|
+
|
5
|
+
kalc is a small functional programming language that (gasp) borrows a lot of
|
6
|
+
its syntax from the Excel formula language.
|
7
|
+
|
8
|
+
h2. ikalc
|
9
|
+
|
10
|
+
kalc comes with its own repl, known as ikalc. It evaluates when you press return.
|
11
|
+
|
12
|
+
h2. Syntax
|
13
|
+
|
14
|
+
kalc is a tiny language, and it has very little syntax. It does support
|
15
|
+
functions, variable assignment, and arithmetic.
|
16
|
+
|
17
|
+
h3. Arithmetic
|
18
|
+
|
19
|
+
<pre>
|
20
|
+
1 + 1 / (10 * 100) - 3 + 3 - (3 - 2)
|
21
|
+
1 > 1
|
22
|
+
SUM(1, 2, 3, 4, 5)
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
Arithmetic is standard infix with nesting via parenthesis.
|
26
|
+
|
27
|
+
h3. Logical operations
|
28
|
+
|
29
|
+
<pre>
|
30
|
+
1 > 2 ? 1 : 3 # Ternary
|
31
|
+
(1 || 2) > 3
|
32
|
+
1 > 2 or 3 < 2 # false
|
33
|
+
OR(1 > 2, 3 < 2, 8 == 8) # true
|
34
|
+
</pre>
|
35
|
+
|
36
|
+
h3. Variable assignment
|
37
|
+
|
38
|
+
<pre>
|
39
|
+
a = 1
|
40
|
+
b = 2
|
41
|
+
d = a + b
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
h3. Creating functions
|
45
|
+
|
46
|
+
<pre>
|
47
|
+
DEFINE FOO(a, b) {
|
48
|
+
a + b
|
49
|
+
}
|
50
|
+
</pre>
|
51
|
+
|
52
|
+
There are a few examples of functions in lib/stdlib.kalc
|
53
|
+
|
54
|
+
h3. Loops
|
55
|
+
|
56
|
+
There are no looping mechanisms to speak of, but recursion works well.
|
57
|
+
|
58
|
+
<pre>
|
59
|
+
DEFINE SAMPLE_LOOP(a) {
|
60
|
+
PUTS(a)
|
61
|
+
IF(a == 1, 1, SAMPLE_LOOP(a - 1))
|
62
|
+
}
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
h4. More inside
|
66
|
+
|
67
|
+
Not everything is documented yet. As you can see, it is a mix of
|
68
|
+
a lot of different ideas. The goal is to have an excel-like language
|
69
|
+
that is somewhat functional.
|
70
|
+
|
71
|
+
h2. Contributing
|
72
|
+
|
73
|
+
Fork on GitHub and after you've committed tested patches, send a pull request.
|
74
|
+
|
data/bin/ikalc
ADDED
data/bin/kalc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
kalc_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
$LOAD_PATH.unshift(kalc_dir) unless $LOAD_PATH.include?(kalc_dir)
|
5
|
+
|
6
|
+
require "kalc"
|
7
|
+
require "pp"
|
8
|
+
|
9
|
+
input = File.read(ARGV.first)
|
10
|
+
|
11
|
+
grammar = Kalc::Grammar.new
|
12
|
+
g = grammar.parse(input)
|
13
|
+
|
14
|
+
transform = Kalc::Transform.new
|
15
|
+
ast = transform.apply(g)
|
16
|
+
|
17
|
+
|
18
|
+
interpreter = Kalc::Interpreter.new
|
19
|
+
puts interpreter.run(ast)
|
data/kalc.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "kalc/version"
|
4
|
+
#
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "kalc"
|
7
|
+
s.version = Kalc::VERSION
|
8
|
+
s.authors = ["Chris Parker"]
|
9
|
+
s.email = ["mrcsparker@gmail.com"]
|
10
|
+
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.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "kalc"
|
15
|
+
|
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 = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_runtime_dependency "parslet", "~> 1.2"
|
25
|
+
end
|
data/lib/kalc.rb
ADDED
data/lib/kalc/ast.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
module Kalc
|
2
|
+
module Ast
|
3
|
+
|
4
|
+
class Commands
|
5
|
+
attr_reader :commands
|
6
|
+
|
7
|
+
def initialize(commands)
|
8
|
+
@commands = commands
|
9
|
+
end
|
10
|
+
|
11
|
+
def eval(context)
|
12
|
+
last = nil
|
13
|
+
@commands.each do |command|
|
14
|
+
last = command.eval(context)
|
15
|
+
end
|
16
|
+
last
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class Expressions
|
22
|
+
attr_reader :expressions
|
23
|
+
|
24
|
+
def initialize(expressions)
|
25
|
+
@expressions = expressions
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval(context)
|
29
|
+
last = nil
|
30
|
+
@expressions.each do |exp|
|
31
|
+
last = exp.eval(context)
|
32
|
+
end
|
33
|
+
last
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class BooleanValue
|
38
|
+
attr_reader :value
|
39
|
+
|
40
|
+
def initialize(value)
|
41
|
+
@value = value
|
42
|
+
end
|
43
|
+
|
44
|
+
def eval(context)
|
45
|
+
@value == 'TRUE' ? true : false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FloatingPointNumber
|
50
|
+
attr_reader :value
|
51
|
+
|
52
|
+
def initialize(value)
|
53
|
+
@value = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def eval(context)
|
57
|
+
Float(@value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Arithmetic
|
62
|
+
attr_reader :left
|
63
|
+
attr_reader :right
|
64
|
+
attr_reader :operator
|
65
|
+
|
66
|
+
def initialize(left, right, operator)
|
67
|
+
@left = left
|
68
|
+
@right = right
|
69
|
+
@operator = operator
|
70
|
+
end
|
71
|
+
|
72
|
+
def eval(context)
|
73
|
+
|
74
|
+
@left = @left.eval(context)
|
75
|
+
@right = @right.eval(context)
|
76
|
+
|
77
|
+
case @operator.to_s.strip
|
78
|
+
when '&&'
|
79
|
+
@left && @right
|
80
|
+
when 'and'
|
81
|
+
@left && @right
|
82
|
+
when '||'
|
83
|
+
@left || @right
|
84
|
+
when 'or'
|
85
|
+
@left || @right
|
86
|
+
when '<='
|
87
|
+
@left <= @right
|
88
|
+
when '>='
|
89
|
+
@left >= @right
|
90
|
+
when '=='
|
91
|
+
@left == @right
|
92
|
+
when '!='
|
93
|
+
@left != @right
|
94
|
+
when '-'
|
95
|
+
@left - @right
|
96
|
+
when '+'
|
97
|
+
@left + @right
|
98
|
+
when '*'
|
99
|
+
@left * @right
|
100
|
+
when '/'
|
101
|
+
@left / @right
|
102
|
+
when '%'
|
103
|
+
@left % @right
|
104
|
+
when '<'
|
105
|
+
@left < @right
|
106
|
+
when '>'
|
107
|
+
@left > @right
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Conditional
|
113
|
+
attr_reader :condition, :true_cond, :false_cond
|
114
|
+
|
115
|
+
def initialize(condition, true_cond, false_cond)
|
116
|
+
@condition = condition
|
117
|
+
@true_cond = true_cond
|
118
|
+
@false_cond = false_cond
|
119
|
+
end
|
120
|
+
|
121
|
+
def eval(context)
|
122
|
+
@condition ? true_cond : false_cond
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Identifier
|
127
|
+
attr_reader :identifier
|
128
|
+
attr_reader :value
|
129
|
+
|
130
|
+
def initialize(identifier, value)
|
131
|
+
@variable = identifier.to_s.strip
|
132
|
+
@value = value
|
133
|
+
end
|
134
|
+
|
135
|
+
def eval(context)
|
136
|
+
context.add_variable(@variable, @value.eval(context))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Variable
|
141
|
+
attr_reader :variable
|
142
|
+
def initialize(variable)
|
143
|
+
@variable = variable
|
144
|
+
end
|
145
|
+
|
146
|
+
def eval(context)
|
147
|
+
context.get_variable(@variable)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class StringValue
|
152
|
+
attr_reader :value
|
153
|
+
|
154
|
+
def initialize(value)
|
155
|
+
@value = value
|
156
|
+
end
|
157
|
+
|
158
|
+
def eval(context)
|
159
|
+
value.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class FunctionCall
|
164
|
+
attr_reader :name
|
165
|
+
attr_reader :variable_list
|
166
|
+
|
167
|
+
def initialize(name, variable_list)
|
168
|
+
@name = name
|
169
|
+
@variable_list = variable_list
|
170
|
+
end
|
171
|
+
|
172
|
+
def eval(context)
|
173
|
+
to_call = context.get_function(@name)
|
174
|
+
to_call.call(context, *@variable_list) if to_call
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class FunctionDefinition
|
179
|
+
def initialize(name, argument_list, body)
|
180
|
+
@name = name
|
181
|
+
@argument_list = argument_list
|
182
|
+
@body = body
|
183
|
+
end
|
184
|
+
|
185
|
+
def eval(context)
|
186
|
+
context.add_function(@name.to_sym, lambda { |parent_context, *args|
|
187
|
+
dup_body = Marshal.load(Marshal.dump(@body))
|
188
|
+
cxt = Environment.new(parent_context)
|
189
|
+
args.each_with_index do |arg, idx|
|
190
|
+
cxt.add_variable(@argument_list[idx].value, arg.eval(cxt))
|
191
|
+
end
|
192
|
+
dup_body.eval(cxt)
|
193
|
+
})
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Kalc
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
attr_reader :functions
|
5
|
+
attr_reader :variables
|
6
|
+
|
7
|
+
def initialize(parent = nil)
|
8
|
+
@functions = {}
|
9
|
+
@variables = {}
|
10
|
+
@parent = parent
|
11
|
+
yield self if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_function(name, value)
|
15
|
+
@functions.update({ name.to_s.strip => value })
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_function(name)
|
19
|
+
if fun = @functions[name.to_s.strip]
|
20
|
+
fun
|
21
|
+
elsif !@parent.nil?
|
22
|
+
@parent.get_function(name)
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_variable(name, value)
|
29
|
+
@variables.update({ name.to_s.strip => value })
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_variable(name)
|
34
|
+
if var = @variables[name.to_s.strip]
|
35
|
+
var
|
36
|
+
elsif !@parent.nil?
|
37
|
+
@parent.get_variable(name)
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/kalc/grammar.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
# Started with https://github.com/postmodern/cparser code
|
2
|
+
# which is based on http://www.lysator.liu.se/c/ANSI-C-grammar-y.html
|
3
|
+
# and worked from there
|
4
|
+
#
|
5
|
+
# The second link really helped when it came to functions
|
6
|
+
|
7
|
+
class Kalc::Grammar < Parslet::Parser
|
8
|
+
|
9
|
+
rule(:new_line) { match('[\n\r]').repeat(1) }
|
10
|
+
|
11
|
+
rule(:space) { match('[ \t\v\n\f]') }
|
12
|
+
rule(:spaces) { space.repeat(1) }
|
13
|
+
rule(:space?) { space.maybe }
|
14
|
+
rule(:spaces?) { space.repeat }
|
15
|
+
|
16
|
+
rule(:digit) { match('[0-9]') }
|
17
|
+
rule(:digits) { digit.repeat(1) }
|
18
|
+
rule(:digits?) { digit.repeat }
|
19
|
+
|
20
|
+
rule(:alpha) { match('[_a-zA-Z]') }
|
21
|
+
|
22
|
+
rule(:new_line) { match('[\n\r]').repeat(1) }
|
23
|
+
rule(:separator) { str(';') >> spaces? }
|
24
|
+
|
25
|
+
def self.symbols(symbols)
|
26
|
+
symbols.each do |name, symbol|
|
27
|
+
rule(name) { str(symbol) >> spaces? }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
symbols :left_paren => '(',
|
32
|
+
:right_paren => ')',
|
33
|
+
:left_brace => '{',
|
34
|
+
:right_brace => '}',
|
35
|
+
:comma => ',',
|
36
|
+
:colon => ':'
|
37
|
+
|
38
|
+
def self.operators(operators={})
|
39
|
+
trailing_chars = Hash.new { |hash,symbol| hash[symbol] = [] }
|
40
|
+
|
41
|
+
operators.each_value do |symbol|
|
42
|
+
operators.each_value do |op|
|
43
|
+
if op[0,symbol.length] == symbol
|
44
|
+
char = op[symbol.length,1]
|
45
|
+
|
46
|
+
unless (char.nil? || char.empty?)
|
47
|
+
trailing_chars[symbol] << char
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
operators.each do |name,symbol|
|
54
|
+
trailing = trailing_chars[symbol]
|
55
|
+
|
56
|
+
if trailing.empty?
|
57
|
+
rule(name) { str(symbol).as(:operator) >> spaces? }
|
58
|
+
else
|
59
|
+
pattern = "[#{Regexp.escape(trailing.join)}]"
|
60
|
+
|
61
|
+
rule(name) {
|
62
|
+
(str(symbol) >> match(pattern).absnt?).as(:operator) >> spaces?
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
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
|
+
:question_mark => '?',
|
79
|
+
|
80
|
+
:subtract => '-',
|
81
|
+
:add => '+',
|
82
|
+
:multiply => '*',
|
83
|
+
:divide => '/',
|
84
|
+
:modulus => '%',
|
85
|
+
|
86
|
+
:less => '<',
|
87
|
+
:greater => '>'
|
88
|
+
|
89
|
+
rule(:true_keyword) {
|
90
|
+
str('TRUE') >> spaces?
|
91
|
+
}
|
92
|
+
|
93
|
+
rule(:false_keyword) {
|
94
|
+
str('FALSE') >> spaces?
|
95
|
+
}
|
96
|
+
|
97
|
+
rule(:boolean) {
|
98
|
+
(true_keyword | false_keyword).as(:boolean)
|
99
|
+
}
|
100
|
+
|
101
|
+
rule(:string) {
|
102
|
+
str('"') >>
|
103
|
+
(
|
104
|
+
str('\\') >> any |
|
105
|
+
str('"').absnt? >> any
|
106
|
+
).repeat.as(:string) >>
|
107
|
+
str('"')
|
108
|
+
}
|
109
|
+
|
110
|
+
rule(:number) {
|
111
|
+
(match('[+-]').maybe >> digits >> (str('.') >> digits).maybe).as(:number) >> spaces?
|
112
|
+
}
|
113
|
+
|
114
|
+
rule(:identifier) {
|
115
|
+
(alpha >> (alpha | digit).repeat) >> spaces?
|
116
|
+
}
|
117
|
+
|
118
|
+
rule(:argument) {
|
119
|
+
identifier.as(:argument)
|
120
|
+
}
|
121
|
+
|
122
|
+
# Should look like 'Name'
|
123
|
+
rule(:variable) {
|
124
|
+
identifier | (str("'") >> identifier >> str("'")) >> spaces?
|
125
|
+
}
|
126
|
+
|
127
|
+
# Does not self-evaluate
|
128
|
+
# Use to call function: FUNCTION_NAME(variable_list, ..)
|
129
|
+
rule(:variable_list) {
|
130
|
+
conditional_expression >> (comma >> conditional_expression).repeat
|
131
|
+
}
|
132
|
+
|
133
|
+
rule(:paren_variable_list) {
|
134
|
+
(left_paren >> variable_list.repeat >> right_paren).as(:paren_list)
|
135
|
+
}
|
136
|
+
|
137
|
+
# Does not self-evaluate
|
138
|
+
# Used to create function: DEF FUNCTION_NAME(argument_list, ..)
|
139
|
+
rule(:argument_list) {
|
140
|
+
argument >> (comma >> argument).repeat
|
141
|
+
}
|
142
|
+
|
143
|
+
rule(:paren_argument_list) {
|
144
|
+
(left_paren >> argument_list.repeat >> right_paren).as(:paren_list)
|
145
|
+
}
|
146
|
+
|
147
|
+
# Atoms can self-evaluate
|
148
|
+
# This where the grammar starts
|
149
|
+
rule(:atom) {
|
150
|
+
boolean | variable.as(:variable) | number | string | paren_expression
|
151
|
+
}
|
152
|
+
|
153
|
+
# (1 + 2)
|
154
|
+
rule(:paren_expression) {
|
155
|
+
left_paren >> conditional_expression >> right_paren
|
156
|
+
}
|
157
|
+
|
158
|
+
# IF(1, 2, 3)
|
159
|
+
# AND(1, 2, ...)
|
160
|
+
rule(:function_call_expression) {
|
161
|
+
(identifier.as(:name) >> paren_variable_list.as(:variable_list)).as(:function_call) |
|
162
|
+
atom
|
163
|
+
}
|
164
|
+
|
165
|
+
# 1 * 2
|
166
|
+
rule(:multiplicative_expression) {
|
167
|
+
function_call_expression.as(:left) >>
|
168
|
+
(multiply | divide | modulus) >>
|
169
|
+
multiplicative_expression.as(:right) |
|
170
|
+
function_call_expression
|
171
|
+
}
|
172
|
+
|
173
|
+
# 1 + 2
|
174
|
+
rule(:additive_expression) {
|
175
|
+
multiplicative_expression.as(:left) >>
|
176
|
+
(add | subtract) >>
|
177
|
+
additive_expression.as(:right) |
|
178
|
+
multiplicative_expression
|
179
|
+
}
|
180
|
+
|
181
|
+
# 1 < 2
|
182
|
+
# 1 > 2
|
183
|
+
# 1 <= 2
|
184
|
+
# 1 >= 2
|
185
|
+
rule(:relational_expression) {
|
186
|
+
additive_expression.as(:left) >>
|
187
|
+
(less | greater | less_equal | greater_equal) >>
|
188
|
+
relational_expression.as(:right) |
|
189
|
+
additive_expression
|
190
|
+
}
|
191
|
+
|
192
|
+
# 1 == 2
|
193
|
+
rule(:equality_expression) {
|
194
|
+
relational_expression.as(:left) >>
|
195
|
+
(equal | not_equal) >>
|
196
|
+
equality_expression.as(:right) |
|
197
|
+
relational_expression
|
198
|
+
}
|
199
|
+
|
200
|
+
# 1 && 2
|
201
|
+
rule(:logical_and_expression) {
|
202
|
+
equality_expression.as(:left) >>
|
203
|
+
(logical_and | string_and) >>
|
204
|
+
logical_and_expression.as(:right) |
|
205
|
+
equality_expression
|
206
|
+
}
|
207
|
+
|
208
|
+
# 1 || 2
|
209
|
+
rule(:logical_or_expression) {
|
210
|
+
logical_and_expression.as(:left) >>
|
211
|
+
(logical_or | string_or) >>
|
212
|
+
logical_or_expression.as(:right) |
|
213
|
+
logical_and_expression
|
214
|
+
}
|
215
|
+
|
216
|
+
# 1 > 2 ? 3 : 4
|
217
|
+
rule(:conditional_expression) {
|
218
|
+
logical_or_expression.as(:condition) >>
|
219
|
+
question_mark >>
|
220
|
+
conditional_expression.as(:true) >>
|
221
|
+
colon >>
|
222
|
+
conditional_expression.as(:false) |
|
223
|
+
logical_or_expression
|
224
|
+
}
|
225
|
+
|
226
|
+
# 'a' = 1
|
227
|
+
# We don't allow for nested assignments:
|
228
|
+
# IF('a' = 1, 1, 2)
|
229
|
+
rule(:assignment_expression) {
|
230
|
+
(variable.as(:identifier) >>
|
231
|
+
assign >>
|
232
|
+
assignment_expression.as(:value)).as(:assign) |
|
233
|
+
conditional_expression
|
234
|
+
}
|
235
|
+
|
236
|
+
rule(:expression) {
|
237
|
+
assignment_expression
|
238
|
+
}
|
239
|
+
|
240
|
+
rule(:expressions) {
|
241
|
+
expression >> (separator >> expressions).repeat
|
242
|
+
}
|
243
|
+
|
244
|
+
rule(:function_body) {
|
245
|
+
expressions.as(:expressions)
|
246
|
+
}
|
247
|
+
|
248
|
+
rule(:function_definition_expression) {
|
249
|
+
(str('DEFINE') >> spaces? >> identifier.as(:name) >>
|
250
|
+
paren_argument_list.as(:argument_list) >>
|
251
|
+
left_brace >> function_body.as(:body) >> right_brace).as(:function_definition) |
|
252
|
+
expressions.as(:expressions)
|
253
|
+
}
|
254
|
+
|
255
|
+
rule(:function_definition_expressions) {
|
256
|
+
function_definition_expression >> separator.maybe >> function_definition_expressions.repeat
|
257
|
+
}
|
258
|
+
|
259
|
+
rule(:commands) {
|
260
|
+
function_definition_expressions | expressions
|
261
|
+
}
|
262
|
+
|
263
|
+
rule(:line) {
|
264
|
+
commands.as(:commands)
|
265
|
+
}
|
266
|
+
|
267
|
+
rule(:lines) {
|
268
|
+
line >> (new_line >> lines).repeat
|
269
|
+
}
|
270
|
+
|
271
|
+
root :lines
|
272
|
+
end
|
273
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Code inspired by https://github.com/txus/schemer
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Kalc
|
6
|
+
class Interpreter
|
7
|
+
|
8
|
+
attr_reader :env
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@env = Environment.new do |env|
|
12
|
+
|
13
|
+
env.add_function(:IF, lambda { |cxt, cond, if_true, if_false|
|
14
|
+
cond.eval(cxt) ? if_true.eval(cxt) : if_false.eval(cxt)
|
15
|
+
})
|
16
|
+
|
17
|
+
env.add_function(:OR, lambda { |cxt, *args|
|
18
|
+
retval = false
|
19
|
+
args.each do |arg|
|
20
|
+
if arg.eval(cxt) == true
|
21
|
+
retval = true
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
retval
|
26
|
+
})
|
27
|
+
|
28
|
+
env.add_function(:NOT, lambda { |cxt, val|
|
29
|
+
!val.eval(cxt)
|
30
|
+
})
|
31
|
+
|
32
|
+
env.add_function(:AND, lambda { |cxt, *args|
|
33
|
+
retval = true
|
34
|
+
args.each do |arg|
|
35
|
+
if arg.eval(cxt) == false
|
36
|
+
retval = false
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
retval
|
41
|
+
})
|
42
|
+
|
43
|
+
# Math
|
44
|
+
env.add_function(:ABS, lambda { |cxt, val|
|
45
|
+
val.eval(cxt).abs
|
46
|
+
})
|
47
|
+
|
48
|
+
env.add_function(:DEGREES, lambda { | cxt, val|
|
49
|
+
val.eval(cxt) * (180.0 / Math::PI)
|
50
|
+
})
|
51
|
+
|
52
|
+
env.add_function(:PRODUCT, lambda { |cxt, *args|
|
53
|
+
args.map { |a| a.eval(cxt) }.inject(:*)
|
54
|
+
})
|
55
|
+
|
56
|
+
env.add_function(:RADIANS, lambda { |cxt, val|
|
57
|
+
val.eval(cxt) * (Math::PI / 180.0)
|
58
|
+
})
|
59
|
+
|
60
|
+
env.add_function(:ROUND, lambda { |cxt, num, digits|
|
61
|
+
num.eval(cxt).round(digits.eval(cxt))
|
62
|
+
})
|
63
|
+
|
64
|
+
env.add_function(:SUM, lambda { |cxt, *args|
|
65
|
+
args.map { |a| a.eval(cxt) }.inject(:+)
|
66
|
+
})
|
67
|
+
|
68
|
+
env.add_function(:TRUNC, lambda { |cxt, val|
|
69
|
+
Integer(val.eval(cxt))
|
70
|
+
})
|
71
|
+
|
72
|
+
env.add_function(:LN, lambda { |cxt, val|
|
73
|
+
Math.log(val.eval(cxt))
|
74
|
+
})
|
75
|
+
|
76
|
+
math_funs =
|
77
|
+
[ 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh',
|
78
|
+
'cbrt', 'cos', 'cosh',
|
79
|
+
'erf', 'erfc', 'exp',
|
80
|
+
'log', 'log2', 'log10',
|
81
|
+
'sin', 'sinh', 'sqrt',
|
82
|
+
'tan', 'tanh',
|
83
|
+
]
|
84
|
+
|
85
|
+
math_funs.each do |math_fun|
|
86
|
+
env.add_function(math_fun.upcase.to_sym, lambda { |cxt, val|
|
87
|
+
Math.send(math_fun.to_sym, val.eval(cxt))
|
88
|
+
})
|
89
|
+
end
|
90
|
+
|
91
|
+
env.add_function(:P, lambda { |cxt, *output|
|
92
|
+
p output
|
93
|
+
})
|
94
|
+
|
95
|
+
env.add_function(:PP, lambda { |cxt, *output|
|
96
|
+
pp output
|
97
|
+
})
|
98
|
+
|
99
|
+
env.add_function(:PUTS, lambda { |cxt, output|
|
100
|
+
puts output.eval(cxt)
|
101
|
+
})
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def load_stdlibs(grammar, transform)
|
108
|
+
stdlib = "#{File.dirname(__FILE__)}/stdlib.kalc"
|
109
|
+
input = File.read(stdlib)
|
110
|
+
g = grammar.parse(input)
|
111
|
+
ast = transform.apply(g)
|
112
|
+
run(ast)
|
113
|
+
end
|
114
|
+
|
115
|
+
def run(ast)
|
116
|
+
ast.eval(@env)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/kalc/repl.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
require 'parslet/convenience'
|
5
|
+
|
6
|
+
module Kalc
|
7
|
+
class Repl
|
8
|
+
|
9
|
+
def run
|
10
|
+
|
11
|
+
puts heading
|
12
|
+
|
13
|
+
puts "= Loading grammar"
|
14
|
+
@grammar = Kalc::Grammar.new
|
15
|
+
|
16
|
+
puts "= Loading transform"
|
17
|
+
@transform = Kalc::Transform.new
|
18
|
+
|
19
|
+
puts "= Loading interpreter"
|
20
|
+
@interpreter = Kalc::Interpreter.new
|
21
|
+
@interpreter.load_stdlibs(@grammar, @transform)
|
22
|
+
|
23
|
+
puts "You are ready to go. Have fun!"
|
24
|
+
puts ""
|
25
|
+
|
26
|
+
ast = nil
|
27
|
+
|
28
|
+
function_list = [
|
29
|
+
'quit', 'exit', 'functions', 'variables', 'ast'
|
30
|
+
] + @interpreter.env.functions.map { |f| f.first }
|
31
|
+
|
32
|
+
begin
|
33
|
+
comp = proc { |s| function_list.grep( /^#{Regexp.escape(s)}/ ) }
|
34
|
+
Readline.completion_append_character = ""
|
35
|
+
Readline.completion_proc = comp
|
36
|
+
|
37
|
+
while input = Readline.readline("kalc-#{Kalc::VERSION} > ", true)
|
38
|
+
begin
|
39
|
+
case
|
40
|
+
when (input == 'quit' || input == 'exit')
|
41
|
+
break
|
42
|
+
when input == "functions"
|
43
|
+
puts @interpreter.env.functions.map { |f| f.first }.join(", ")
|
44
|
+
when input == 'variables'
|
45
|
+
puts @interpreter.env.variables.map { |v| "#{v[0]} = #{v[1]}" }.join("\n\r")
|
46
|
+
when input == 'ast'
|
47
|
+
pp ast
|
48
|
+
when input != ""
|
49
|
+
g = @grammar.parse_with_debug(input)
|
50
|
+
ast = @transform.apply(g)
|
51
|
+
puts @interpreter.run(ast)
|
52
|
+
end
|
53
|
+
rescue Parslet::ParseFailed => e
|
54
|
+
puts e, g.root.error_tree
|
55
|
+
rescue Exception => e
|
56
|
+
puts e
|
57
|
+
puts e.backtrace
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue Exception => e
|
61
|
+
puts e
|
62
|
+
puts e.backtrace
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def heading
|
67
|
+
%q{
|
68
|
+
This is Kalc, a small line-based language.
|
69
|
+
More information about Kalc can be found at https://github.com/mrcsparker/kalc.
|
70
|
+
|
71
|
+
Kalc is free software, provided as is, with no warranty.
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
DEFINE SQUARE(x) {
|
2
|
+
x * x * x
|
3
|
+
}
|
4
|
+
|
5
|
+
DEFINE FIB(x) {
|
6
|
+
IF(x == 0, 0, IF(x == 1, 1, (FIB(x - 1) + FIB(x - 2))))
|
7
|
+
}
|
8
|
+
|
9
|
+
DEFINE FACTORIAL(x) {
|
10
|
+
IF(x == 0, 1, (x * FACTORIAL(x - 1)))
|
11
|
+
}
|
12
|
+
|
13
|
+
DEFINE TOWERS_OF_HANOI(x) {
|
14
|
+
IF(x == 1, 1, (2 * TOWERS_OF_HANOI(x - 1) + 1))
|
15
|
+
}
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Kalc
|
2
|
+
class Transform < Parslet::Transform
|
3
|
+
|
4
|
+
rule(:commands => sequence(:commands)) {
|
5
|
+
Ast::Commands.new(commands)
|
6
|
+
}
|
7
|
+
|
8
|
+
rule(:commands => simple(:commands)) {
|
9
|
+
Ast::Commands.new([commands])
|
10
|
+
}
|
11
|
+
|
12
|
+
rule(:expressions => sequence(:expressions)) {
|
13
|
+
Ast::Expressions.new(expressions)
|
14
|
+
}
|
15
|
+
|
16
|
+
rule(:expressions => simple(:expressions)) {
|
17
|
+
Ast::Expressions.new([expressions])
|
18
|
+
}
|
19
|
+
|
20
|
+
rule(:string => simple(:string)) {
|
21
|
+
Ast::StringValue.new(string)
|
22
|
+
}
|
23
|
+
|
24
|
+
rule(:boolean => simple(:boolean)) {
|
25
|
+
Ast::BooleanValue.new(boolean)
|
26
|
+
}
|
27
|
+
|
28
|
+
rule(:number => simple(:number)) {
|
29
|
+
Ast::FloatingPointNumber.new(number)
|
30
|
+
}
|
31
|
+
|
32
|
+
rule(:left => simple(:left), :right => simple(:right), :operator => simple(:operator)) {
|
33
|
+
Ast::Arithmetic.new(left, right, operator)
|
34
|
+
}
|
35
|
+
|
36
|
+
rule(:condition => simple(:condition), :true => simple(:true_cond), :false => simple(:false_cond)) {
|
37
|
+
Ast::Conditional.new(condition, true_cond, false_cond)
|
38
|
+
}
|
39
|
+
|
40
|
+
rule(:variable => simple(:variable)) {
|
41
|
+
Ast::Variable.new(variable)
|
42
|
+
}
|
43
|
+
|
44
|
+
rule(:identifier => simple(:identifier)) {
|
45
|
+
identifier
|
46
|
+
}
|
47
|
+
|
48
|
+
rule(:assign => {:value => simple(:value), :identifier => simple(:identifier), :operator => simple(:operator)}) {
|
49
|
+
Ast::Identifier.new(identifier, value)
|
50
|
+
}
|
51
|
+
|
52
|
+
rule(:paren_list => sequence(:paren_list)) {
|
53
|
+
paren_list
|
54
|
+
}
|
55
|
+
|
56
|
+
rule(:paren_list => "()") {
|
57
|
+
[]
|
58
|
+
}
|
59
|
+
|
60
|
+
rule(:function_call => {:name => simple(:name),
|
61
|
+
:variable_list => sequence(:variable_list)}) {
|
62
|
+
Ast::FunctionCall.new(name, variable_list)
|
63
|
+
}
|
64
|
+
|
65
|
+
rule(:argument => simple(:argument)) {
|
66
|
+
Ast::Identifier.new(argument, argument)
|
67
|
+
}
|
68
|
+
|
69
|
+
rule(:function_definition => {:name => simple(:name),
|
70
|
+
:argument_list => sequence(:argument_list),
|
71
|
+
:body => simple(:body)}) {
|
72
|
+
Ast::FunctionDefinition.new(name, argument_list, body)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
data/lib/kalc/version.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kalc::Grammar do
|
4
|
+
let(:grammar) { Kalc::Grammar.new }
|
5
|
+
|
6
|
+
context "integers" do
|
7
|
+
1.upto(10) do |i|
|
8
|
+
it { grammar.should parse("#{i}") }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "floats" do
|
13
|
+
1.upto(10) do |i|
|
14
|
+
1.upto(10) do |n|
|
15
|
+
it { grammar.should parse("#{i}.#{n}") }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "basic integer math" do
|
21
|
+
1.upto(10) do |i|
|
22
|
+
it { grammar.should parse("#{i} - #{i}") }
|
23
|
+
it { grammar.should parse("#{i} + #{i}") }
|
24
|
+
it { grammar.should parse("#{i} * #{i}") }
|
25
|
+
it { grammar.should parse("#{i} / #{i}") }
|
26
|
+
it { grammar.should parse("#{i} % #{i}") }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "basic float math" do
|
31
|
+
1.upto(10) do |i|
|
32
|
+
1.upto(10) do |n|
|
33
|
+
it { grammar.should parse("#{i}.#{n} - #{i}.#{n}") }
|
34
|
+
it { grammar.should parse("#{i}.#{n} + #{i}.#{n}") }
|
35
|
+
it { grammar.should parse("#{i}.#{n} * #{i}.#{n}") }
|
36
|
+
it { grammar.should parse("#{i}.#{n} / #{i}.#{n}") }
|
37
|
+
it { grammar.should parse("#{i}.#{n} % #{i}.#{n}") }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "Logical expressions" do
|
43
|
+
it { grammar.should parse("3 && 1") }
|
44
|
+
it { grammar.should_not parse("&& 1") }
|
45
|
+
|
46
|
+
it { grammar.should parse("3 || 1") }
|
47
|
+
it { grammar.should_not parse("|| 1") }
|
48
|
+
end
|
49
|
+
|
50
|
+
context "Comparison expressions" do
|
51
|
+
it { grammar.should parse("3 > 1") }
|
52
|
+
it { grammar.should parse("3 < 1") }
|
53
|
+
it { grammar.should parse("3 >= 1") }
|
54
|
+
it { grammar.should parse("3 <= 1") }
|
55
|
+
|
56
|
+
it { grammar.should_not parse("> 1") }
|
57
|
+
it { grammar.should_not parse("< 1") }
|
58
|
+
it { grammar.should_not parse(">= 1") }
|
59
|
+
it { grammar.should_not parse("<= 1") }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "Equality" do
|
63
|
+
it { grammar.should parse("3 == 1") }
|
64
|
+
it { grammar.should parse("2 != 1") }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "Block" do
|
68
|
+
it { grammar.should parse("(2 + 1)") }
|
69
|
+
it { grammar.should parse("(2 + 1) + 1") }
|
70
|
+
it { grammar.should parse("(2 + 1) * (1 / 2) + 3") }
|
71
|
+
it { grammar.should parse("(2 + 1) + (1 + 2) + ((3 + 2) / (2 + 1)) * 9") }
|
72
|
+
it { grammar.should parse("(2 + 1) - (1)") }
|
73
|
+
it { grammar.should parse("(2 ) + ( 1)") }
|
74
|
+
it { grammar.should parse("((2) + ( 1 ))") }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "Ternary expressions" do
|
78
|
+
it { grammar.should parse("3 > 2 ? 1 : 5") }
|
79
|
+
it { grammar.should parse("3 > 2 || 4 <= 5 ? 1 : 5") }
|
80
|
+
it { grammar.should parse("(3 > (2 + 4)) ? 1 : 5") }
|
81
|
+
it { grammar.should parse("IF(2 > 3, 3 > 2 ? 1 : 5, 7)") }
|
82
|
+
it { grammar.should parse("3 > 2 ? 1 : 5 > 4 ? 7 : 5") }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "AND statements" do
|
86
|
+
it { grammar.should parse("AND(1, 2, 3)") }
|
87
|
+
it { grammar.should parse("AND(1, 2, 3, 4, 5, 6)") }
|
88
|
+
it { grammar.should parse("AND(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)") }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "OR statements" do
|
92
|
+
it { grammar.should parse("OR(1, 2, 3)") }
|
93
|
+
it { grammar.should parse("OR(1, 2, 3, 4, 5, 6)") }
|
94
|
+
it { grammar.should parse("OR(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)") }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "IF statements" do
|
98
|
+
it { grammar.should parse("IF(3, 2, 1)") }
|
99
|
+
it { grammar.should parse("IF(3 > 1, 2, 1)") }
|
100
|
+
it { grammar.should parse("IF((3 > 1) || (2 < 1), 2, 1)") }
|
101
|
+
it { grammar.should parse("IF(OR(1 > 3, 2 < 5), 3, 2)") }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "Nested IF statements" do
|
105
|
+
it { grammar.should parse("IF(3 > 2, IF(2 < 3, 1, 3), 5)") }
|
106
|
+
it { grammar.should parse("IF(3 > 2, IF(2 < 3, 1, 3), IF(5 > 1, 3, 9))") }
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kalc::Interpreter do
|
4
|
+
before(:each) do
|
5
|
+
@grammar = Kalc::Grammar.new
|
6
|
+
@transform = Kalc::Transform.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it { evaluate("2 + 2").should == 4 }
|
10
|
+
it { evaluate("1 + 1").should == 2.0 }
|
11
|
+
it { evaluate("4 + 1").should == 5 }
|
12
|
+
it { evaluate("5 + 5").should == 10 }
|
13
|
+
|
14
|
+
it { evaluate("10 > 9").should == true }
|
15
|
+
it { evaluate("10 < 9").should == false }
|
16
|
+
|
17
|
+
it { evaluate("10 + 19 + 11 * 3").should == 62 }
|
18
|
+
|
19
|
+
it { evaluate("10 >= 10").should == true }
|
20
|
+
|
21
|
+
private
|
22
|
+
def evaluate(expression)
|
23
|
+
g = @grammar.parse(expression)
|
24
|
+
ast = @transform.apply(g)
|
25
|
+
Kalc::Interpreter.new.run(ast)
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kalc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Parker
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70212126361020 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70212126361020
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70212126360600 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70212126360600
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: parslet
|
38
|
+
requirement: &70212126360100 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.2'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70212126360100
|
47
|
+
description: Calculation language slightly based on Excel's formula language.
|
48
|
+
email:
|
49
|
+
- mrcsparker@gmail.com
|
50
|
+
executables:
|
51
|
+
- ikalc
|
52
|
+
- kalc
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.textile
|
59
|
+
- bin/ikalc
|
60
|
+
- bin/kalc
|
61
|
+
- kalc.gemspec
|
62
|
+
- lib/kalc.rb
|
63
|
+
- lib/kalc/ast.rb
|
64
|
+
- lib/kalc/environment.rb
|
65
|
+
- lib/kalc/grammar.rb
|
66
|
+
- lib/kalc/interpreter.rb
|
67
|
+
- lib/kalc/repl.rb
|
68
|
+
- lib/kalc/stdlib.kalc
|
69
|
+
- lib/kalc/transform.rb
|
70
|
+
- lib/kalc/version.rb
|
71
|
+
- spec/grammar_spec.rb
|
72
|
+
- spec/interpreter_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
homepage: https://github.com/mrcsparker/kalc
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project: kalc
|
94
|
+
rubygems_version: 1.8.15
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Small calculation language.
|
98
|
+
test_files:
|
99
|
+
- spec/grammar_spec.rb
|
100
|
+
- spec/interpreter_spec.rb
|
101
|
+
- spec/spec_helper.rb
|