kalc 0.3
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.
- 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
|