kalculator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/kalculator.gemspec +27 -0
- data/lib/kalculator.rb +23 -0
- data/lib/kalculator/data_sources.rb +23 -0
- data/lib/kalculator/evaluator.rb +74 -0
- data/lib/kalculator/lexer.rb +25 -0
- data/lib/kalculator/parser.rb +37 -0
- data/lib/kalculator/version.rb +3 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a18c933b621aa811fba3ca081470b348175529ea
|
4
|
+
data.tar.gz: 6ed07d4ffee1aa412db11c5e4d0eaa78316165b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f5c54366fb9740454b8fb110436a4e4c6c36927e9858549bd7be94c93feb3db12840d5dd2f9a603625f6a7a331e0f1111ac74a024b90bd1723f5a05e179e7615
|
7
|
+
data.tar.gz: e065a88e4cc6ebe6f944693fd4a55623fbe4213a96b4e39536d4fa56778e62cb7afa577aba6e1b63c48459b35138a7fb50d82b17c898f4ecb544103c0367eba9
|
data/kalculator.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "kalculator/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "kalculator"
|
7
|
+
spec.version = Kalculator::VERSION
|
8
|
+
spec.authors = ["Michael Ries"]
|
9
|
+
spec.email = ["michael@riesd.com"]
|
10
|
+
|
11
|
+
spec.summary = "A calculator that can safely and quickly interpret user-input"
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/mmmries/kalculator"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = ["kalculator.gemspec"] + Dir["lib/**/*"]
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "rltk", "~> 3.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "benchmark-ips", "~> 2.7"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/lib/kalculator.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rltk"
|
2
|
+
require "kalculator/data_sources"
|
3
|
+
require "kalculator/evaluator"
|
4
|
+
require "kalculator/lexer"
|
5
|
+
require "kalculator/parser"
|
6
|
+
require "kalculator/version"
|
7
|
+
|
8
|
+
class Kalculator
|
9
|
+
def self.evaluate(formula, data_source = {})
|
10
|
+
new(formula).evaluate(data_source)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :ast, :string
|
14
|
+
|
15
|
+
def initialize(string)
|
16
|
+
@ast = Kalculator::Parser.parse(Kalculator::Lexer.lex(string))
|
17
|
+
@string = string
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate(data_source = {})
|
21
|
+
Kalculator::Evaluator.new(data_source).evaluate(ast)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Kalculator
|
2
|
+
class DataSources
|
3
|
+
def initialize(*sources)
|
4
|
+
@sources = sources
|
5
|
+
end
|
6
|
+
|
7
|
+
def key?(name)
|
8
|
+
ret = false
|
9
|
+
@sources.each do |source|
|
10
|
+
break ret = true if source.key?(name)
|
11
|
+
end
|
12
|
+
ret
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
ret = nil
|
17
|
+
@sources.each do |source|
|
18
|
+
break ret = source[name] if source.key?(name)
|
19
|
+
end
|
20
|
+
ret
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Kalculator
|
2
|
+
class Evaluator
|
3
|
+
def initialize(data_source)
|
4
|
+
@data_source = data_source
|
5
|
+
end
|
6
|
+
|
7
|
+
def evaluate(ast)
|
8
|
+
send(ast.first, *ast)
|
9
|
+
end
|
10
|
+
|
11
|
+
def +(_, left, right)
|
12
|
+
evaluate(left) + evaluate(right)
|
13
|
+
end
|
14
|
+
|
15
|
+
def -(_, left, right)
|
16
|
+
evaluate(left) - evaluate(right)
|
17
|
+
end
|
18
|
+
|
19
|
+
def *(_, left, right)
|
20
|
+
evaluate(left) * evaluate(right)
|
21
|
+
end
|
22
|
+
|
23
|
+
def /(_, left, right)
|
24
|
+
evaluate(left) / evaluate(right)
|
25
|
+
end
|
26
|
+
|
27
|
+
def >(_, left, right)
|
28
|
+
evaluate(left) > evaluate(right)
|
29
|
+
end
|
30
|
+
|
31
|
+
def >=(_, left, right)
|
32
|
+
evaluate(left) >= evaluate(right)
|
33
|
+
end
|
34
|
+
|
35
|
+
def <(_, left, right)
|
36
|
+
evaluate(left) < evaluate(right)
|
37
|
+
end
|
38
|
+
|
39
|
+
def <=(_, left, right)
|
40
|
+
evaluate(left) <= evaluate(right)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(_, left, right)
|
44
|
+
evaluate(left) == evaluate(right)
|
45
|
+
end
|
46
|
+
|
47
|
+
def boolean(_, boolean)
|
48
|
+
boolean
|
49
|
+
end
|
50
|
+
|
51
|
+
def if(_, condition, true_clause, false_clause)
|
52
|
+
if evaluate(condition)
|
53
|
+
evaluate(true_clause)
|
54
|
+
else
|
55
|
+
evaluate(false_clause)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def number(_, number)
|
60
|
+
number
|
61
|
+
end
|
62
|
+
|
63
|
+
def sum(_, array)
|
64
|
+
evaluate(array).inject(0){|sum, num| sum + num}
|
65
|
+
end
|
66
|
+
|
67
|
+
def variable(_, names)
|
68
|
+
names.inject(@data_source) do |source, name|
|
69
|
+
raise "undefined variable #{names.join(".")} (could not find #{name} in #{source}" unless source.key?(name)
|
70
|
+
source[name]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Kalculator
|
2
|
+
class Lexer < RLTK::Lexer
|
3
|
+
rule(/\s/)
|
4
|
+
rule(/\(/) { :LPAREN }
|
5
|
+
rule(/\)/) { :RPAREN }
|
6
|
+
rule(/,/) { :COMMA }
|
7
|
+
rule(/\+/) { :PLUS }
|
8
|
+
rule(/-/) { :SUB }
|
9
|
+
rule(/\*/) { :MUL }
|
10
|
+
rule(/\//) { :DIV }
|
11
|
+
rule(/>/) { :GT }
|
12
|
+
rule(/>=/) { :GTE }
|
13
|
+
rule(/</) { :LT }
|
14
|
+
rule(/<=/) { :LTE }
|
15
|
+
rule(/==/) { :EQ }
|
16
|
+
rule(/\d+/) { |t| [:NUMBER, t.to_i] }
|
17
|
+
rule(/\.\d+/) { |t| [:NUMBER, t.to_f] }
|
18
|
+
rule(/\d+\.\d+/) { |t| [:NUMBER, t.to_f] }
|
19
|
+
rule(/if/) { |t| :IF }
|
20
|
+
rule(/sum/) { |t| :SUM }
|
21
|
+
rule(/true/) { |t| :TRUE }
|
22
|
+
rule(/false/) { |t| :FALSE }
|
23
|
+
rule(/[A-Za-z][A-Za-z0-9\._]*/) { |t| [:IDENT, t] }
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Kalculator
|
2
|
+
class Parser < RLTK::Parser
|
3
|
+
#Left 200 '&&' '||'.
|
4
|
+
left :GT, :GTE, :LT, :LTE, :EQ
|
5
|
+
left :PLUS, :SUB
|
6
|
+
left :MUL, :DIV
|
7
|
+
#Left 600 '.'.
|
8
|
+
|
9
|
+
production(:expression) do
|
10
|
+
clause('IF LPAREN expression COMMA expression COMMA expression RPAREN') do |_, _, condition, _, true_clause, _, false_clause, _|
|
11
|
+
[:if, condition, true_clause, false_clause]
|
12
|
+
end
|
13
|
+
clause('SUM LPAREN expression RPAREN') do |_, _, e0, _|
|
14
|
+
[:sum, e0]
|
15
|
+
end
|
16
|
+
clause('LPAREN expression RPAREN') { |_, expression, _| expression }
|
17
|
+
|
18
|
+
clause('NUMBER') { |n| [:number, n] }
|
19
|
+
clause('IDENT') { |n| [:variable, n.split(".")] }
|
20
|
+
clause('TRUE') { |n| [:boolean, true] }
|
21
|
+
clause('FALSE') { |n| [:boolean, false] }
|
22
|
+
|
23
|
+
clause('expression GT expression') { |e0, _, e1| [:>, e0, e1] }
|
24
|
+
clause('expression GTE expression') { |e0, _, e1| [:>=, e0, e1] }
|
25
|
+
clause('expression LT expression') { |e0, _, e1| [:<, e0, e1] }
|
26
|
+
clause('expression LTE expression') { |e0, _, e1| [:<=, e0, e1] }
|
27
|
+
clause('expression EQ expression') { |e0, _, e1| [:==, e0, e1] }
|
28
|
+
|
29
|
+
clause('expression PLUS expression') { |e0, _, e1| [:+, e0, e1] }
|
30
|
+
clause('expression SUB expression') { |e0, _, e1| [:-, e0, e1] }
|
31
|
+
clause('expression MUL expression') { |e0, _, e1| [:*, e0, e1] }
|
32
|
+
clause('expression DIV expression') { |e0, _, e1| [:/, e0, e1] }
|
33
|
+
end
|
34
|
+
|
35
|
+
finalize()
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kalculator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Ries
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rltk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark-ips
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: A calculator that can safely and quickly interpret user-input
|
84
|
+
email:
|
85
|
+
- michael@riesd.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- kalculator.gemspec
|
91
|
+
- lib/kalculator.rb
|
92
|
+
- lib/kalculator/data_sources.rb
|
93
|
+
- lib/kalculator/evaluator.rb
|
94
|
+
- lib/kalculator/lexer.rb
|
95
|
+
- lib/kalculator/parser.rb
|
96
|
+
- lib/kalculator/version.rb
|
97
|
+
homepage: https://github.com/mmmries/kalculator
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 2.6.14
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: A calculator that can safely and quickly interpret user-input
|
121
|
+
test_files: []
|