hesabu 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 569ad0bbcbed74d83d57ab12ea67b39377400394a7ee7821e37bb3d157f69232
4
- data.tar.gz: 3984eec0e0e7e1c193a5a2cd7cab98b84a1da9f02c93cd41ebb5e1dfc3a03ab8
3
+ metadata.gz: 431f06d479e890cf49a1cf8fb1f9a166b66a88aee4b6200be1c540996fecf4fd
4
+ data.tar.gz: e6d8a83f61a8e986658b6c574c0b51c5365787819f0fb2bff9331b7cdf3ba07b
5
5
  SHA512:
6
- metadata.gz: 58be8b83b70bbeaaf2d4c49b2d9aaaa315614a895bdd1b812d27bf029b6f5dfb48e98d3af4f6c2aae62f9a8890bfb1256807a636db38e3f699c549c2ef25e118
7
- data.tar.gz: ff042a9247f785f8d2ef40e800a85bd1330ded53e4ee0fc2a1bf7428ece4e2e532a364869596787af6016078c9b634c028d660a518e324f154e6e05b54dea306
6
+ metadata.gz: c22ea9e78df743ee0ea74d8fb3fc22291fe12fdd1cd7235d94e608c72627c665d9982c2e803eede67320cf42e453741682313d8a4aa87f59a449638d37187e8d
7
+ data.tar.gz: 96761de748fed74701bc6aa799ec99ff309c77da03521111cd0a4960874552d7018bd5998c883cb2a41a6d826d2ec8fa3415b0ad70ff419c41c93f373370ddcd
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hesabu (0.1.4)
5
- parslet
4
+ hesabu (0.1.5)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
@@ -34,7 +33,6 @@ GEM
34
33
  parallel (1.12.1)
35
34
  parser (2.5.1.0)
36
35
  ast (~> 2.4.0)
37
- parslet (1.8.2)
38
36
  path_expander (1.0.2)
39
37
  powerpack (0.1.1)
40
38
  pronto (0.9.5)
data/README.md CHANGED
@@ -25,13 +25,7 @@ The expressions can be more complex (excel like), see the supported functions [h
25
25
 
26
26
  Currently the solver is case sensitive (except function names)
27
27
 
28
- Nb: Hesabu is swahili word for arithemtic.
29
-
30
- ## Technical background
31
-
32
- * https://tomassetti.me/guide-parsing-algorithms-terminology/
33
- * https://github.com/PhilippeSigaud/Pegged/wiki/PEG-Basics
34
- * https://github.com/kschiess/parslet
28
+ Nb: Hesabu is swahili word for arithmetic.
35
29
 
36
30
  ## Alternatives
37
31
 
@@ -62,10 +56,7 @@ chmod 0600 ~/.gem/credentials
62
56
 
63
57
 
64
58
  ```
65
- gem bump
66
- gem build hesabu.gemspec
67
- gem push hesabu-x.x.x.gem
68
-
59
+ gem bump --tag --release
69
60
  ```
70
61
 
71
62
 
data/bin/hesabucli ADDED
Binary file
data/hesabu.gemspec CHANGED
@@ -23,8 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "parslet"
27
-
28
26
  spec.add_development_dependency "bundler", "~> 1.16"
29
27
  spec.add_development_dependency "rake", "~> 10.0"
30
28
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -0,0 +1,11 @@
1
+
2
+ module Hesabu
3
+ class EquationCleaner
4
+ def self.clean(eq)
5
+ clean_eq = eq.gsub(" AND ", " && ")
6
+ clean_eq.split(/(<=|>=|!=|==|=|\*|\s)/m)
7
+ .map { |a| a == "=" ? "==" : a }
8
+ .join("")
9
+ end
10
+ end
11
+ end
data/lib/hesabu/errors.rb CHANGED
@@ -1,17 +1,15 @@
1
1
 
2
2
  module Hesabu
3
3
  class Error < StandardError
4
+ attr_accessor :errors
5
+ def iniatialize(message)
6
+ super(message)
7
+ @errors = errors
8
+ end
4
9
  end
5
10
  class ArgumentError < Error
6
- end
7
- class ParseError < Error
8
- end
9
- class CalculationError < Error
10
- end
11
- class DivideByZeroError < Error
12
- end
13
- class CyclicError < Error
14
- end
15
- class UnboundVariableError < Error
11
+ def iniatialize(message)
12
+ super(message)
13
+ end
16
14
  end
17
15
  end
data/lib/hesabu/solver.rb CHANGED
@@ -1,127 +1,49 @@
1
- module Hesabu
2
- class Solver
3
- include TSort
4
1
 
5
- Equation = Struct.new(:name, :evaluable, :dependencies, :raw_expression)
6
- EMPTY_DEPENDENCIES = [].freeze
7
- FakeEvaluable = Struct.new(:eval)
8
2
 
3
+ module Hesabu
4
+ class Solver
9
5
  def initialize
10
- @parser = ::Hesabu::Parser.new
11
- @interpreter = ::Hesabu::Interpreter.new
12
6
  @equations = {}
13
- @bindings = {}
14
7
  end
15
8
 
16
9
  def add(name, raw_expression)
17
10
  if raw_expression.nil? || name.nil?
18
11
  raise Hesabu::ArgumentError, "name or expression can't be nil : '#{name}', '#{raw_expression}'"
19
12
  end
20
-
21
- if ::Hesabu::Types.as_numeric(raw_expression)
22
- add_numeric(name, raw_expression)
23
- else
24
- add_equation(name, raw_expression)
25
- end
13
+ @equations[name] = EquationCleaner.clean(raw_expression.to_s)
26
14
  end
27
15
 
28
16
  def solve!
29
- solving_order.each do |name|
30
- evaluate_equation(@equations[name])
17
+ result = nil
18
+ IO.popen(HESABUCLI, mode = "r+") do |io|
19
+ io.write @equations.to_json
20
+ io.close_write # let the process know you've given it all the data
21
+ result = io.read
31
22
  end
32
- solution = @bindings.dup
33
- @bindings.clear
34
- to_numerics(solution)
35
- rescue StandardError => e
36
- log_and_raise(e)
37
- end
23
+ solution = JSON.parse(result)
24
+ exit_status = $CHILD_STATUS.exitstatus
38
25
 
39
- def solving_order
40
- tsort
41
- rescue TSort::Cyclic => e
42
- raise Hesabu::CyclicError, "There's a cycle between the variables : " + e.message[25..-1]
43
- end
44
-
45
- def tsort_each_node(&block)
46
- @equations.each_key(&block)
47
- end
48
-
49
- def tsort_each_child(node, &block)
50
- equation = @equations[node]
51
- raise UnboundVariableError, unbound_message(node) unless equation
52
- equation.dependencies.each(&block)
53
- end
54
-
55
- private
56
-
57
- def to_numerics(solution)
58
- solution.each_with_object({}) do |kv, hash|
59
- hash[kv.first] = Hesabu::Types.as_numeric(kv.last) || kv.last
60
- end
26
+ log_everything(exit_status, result) if ENV["HESABU_DEBUG"] || exit_status != 0
27
+ handle_error(solution) if exit_status != 0
28
+ solution
61
29
  end
62
30
 
63
- def log_and_raise(e)
64
- log_error(e)
65
- raise e
66
- end
67
-
68
- def log_error(e)
69
- log "Error during processing: #{$ERROR_INFO}"
70
- log "Error : #{e.class} #{e.message}"
71
- log "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
72
- end
73
-
74
- def evaluate_equation(equation)
75
- raise "not evaluable #{equation.evaluable} #{equation}" unless equation.evaluable.respond_to?(:eval, false)
76
- begin
77
- @bindings[equation.name] = equation.evaluable.eval
78
- rescue StandardError => e
79
- raise CalculationError, "Failed to evaluate #{equation.name} due to #{e.message} in formula #{equation.raw_expression}"
80
- end
81
- end
82
-
83
- def log(message)
84
- puts message
85
- end
86
-
87
- def add_numeric(name, raw_expression)
88
- @equations[name] = Equation.new(
89
- name,
90
- FakeEvaluable.new(::Hesabu::Types.as_bigdecimal(raw_expression)),
91
- EMPTY_DEPENDENCIES,
92
- raw_expression
93
- )
94
- end
95
-
96
- def add_equation(name, raw_expression)
97
- expression = raw_expression.gsub(/\r\n?/, "")
98
- ast_tree = begin
99
- @parser.parse(expression)
100
- rescue Parslet::ParseFailed => e
101
- log(raw_expression)
102
- log_error(e)
103
- raise ParseError, "failed to parse #{name} := #{expression} : #{e.message}"
104
- end
105
- var_identifiers = Set.new
106
- interpretation = @interpreter.apply(
107
- ast_tree,
108
- doc: @bindings,
109
- var_identifiers: var_identifiers
110
- )
111
- if ENV["HESABU_DEBUG"]
112
- log expression
113
- log JSON.pretty_generate(ast_tree)
114
- end
115
- @equations[name] = Equation.new(name, interpretation, var_identifiers, raw_expression)
116
- end
31
+ def handle_error(solution)
32
+ puts
33
+ error = solution["errors"].first
34
+ message = "In equation #{error['source']} " + error["message"] + " #{error['source']} := #{error['expression']}"
117
35
 
118
- def unbound_message(node)
119
- ref = first_reference(node)
120
- "Unbound variable : #{node} used by #{ref.name} (#{ref.raw_expression})"
36
+ err = Hesabu::Error.new(message)
37
+ err.errors = solution["errors"]
38
+ raise err
121
39
  end
122
40
 
123
- def first_reference(variable_name)
124
- @equations.values.select { |v| v.dependencies.include?(variable_name) }.take(1).first
41
+ def log_everything(exit_status, result)
42
+ puts ["**************",
43
+ "exit_status:#{exit_status}",
44
+ @equations.to_json,
45
+ "=> ",
46
+ result].join("\n")
125
47
  end
126
48
  end
127
49
  end
@@ -1,3 +1,3 @@
1
1
  module Hesabu
2
- VERSION = "0.1.5".freeze
2
+ VERSION = "0.1.6".freeze
3
3
  end
data/lib/hesabu.rb CHANGED
@@ -1,17 +1,12 @@
1
1
  require "hesabu/version"
2
- require "parslet"
2
+
3
3
  require_relative "./hesabu/errors"
4
- require_relative "./hesabu/parser"
5
4
  require_relative "./hesabu/types/numeric"
6
- require_relative "./hesabu/types/float_lit"
7
- require_relative "./hesabu/types/fun_call"
8
- require_relative "./hesabu/types/indentifier_lit"
9
- require_relative "./hesabu/types/int_lit"
10
- require_relative "./hesabu/types/string_lit"
11
- require_relative "./hesabu/types/operation"
12
5
 
13
- require_relative "./hesabu/interpreter"
6
+ require_relative "./hesabu/equation_cleaner"
14
7
  require_relative "./hesabu/solver"
15
8
 
16
9
  module Hesabu
10
+ HESABUCLI = File.expand_path("../bin/hesabucli", File.dirname(__FILE__))
11
+ puts "************** HESABU cli location : " + HESABUCLI
17
12
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hesabu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stéphan Mestach
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-06 00:00:00.000000000 Z
11
+ date: 2018-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: parslet
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -171,20 +157,14 @@ files:
171
157
  - Rakefile
172
158
  - bin/console
173
159
  - bin/fast
160
+ - bin/hesabucli
174
161
  - bin/setup
175
162
  - hesabu.gemspec
176
163
  - lib/hesabu.rb
164
+ - lib/hesabu/equation_cleaner.rb
177
165
  - lib/hesabu/errors.rb
178
- - lib/hesabu/interpreter.rb
179
- - lib/hesabu/parser.rb
180
166
  - lib/hesabu/solver.rb
181
- - lib/hesabu/types/float_lit.rb
182
- - lib/hesabu/types/fun_call.rb
183
- - lib/hesabu/types/indentifier_lit.rb
184
- - lib/hesabu/types/int_lit.rb
185
167
  - lib/hesabu/types/numeric.rb
186
- - lib/hesabu/types/operation.rb
187
- - lib/hesabu/types/string_lit.rb
188
168
  - lib/hesabu/version.rb
189
169
  homepage: https://github.com/BLSQ/hesabu
190
170
  licenses:
@@ -1,25 +0,0 @@
1
- module Hesabu
2
- class Interpreter < Parslet::Transform
3
- rule(plist: sequence(:arr)) { arr }
4
- rule(plist: "()") { [] }
5
- rule(l: simple(:left),
6
- r: simple(:right),
7
- o: simple(:op)) do
8
- Hesabu::Types::Operation.new(left, op, right)
9
- end
10
- rule(identifier: simple(:id)) { id.to_s }
11
- rule(variable: simple(:variable)) do |d|
12
- d[:var_identifiers]&.add(d[:variable])
13
- Hesabu::Types::IdentifierLit.new(d[:variable], d[:doc])
14
- end
15
- rule(fcall: { name: simple(:name), varlist: sequence(:vars) }) do
16
- Hesabu::Types::FunCall.new(name, vars)
17
- end
18
- rule(str: subtree(:str)) do
19
- Hesabu::Types::StringLit.new(str.map { |char| char.values.first.str }.join)
20
- end
21
-
22
- rule(integer: simple(:integer)) { Hesabu::Types::IntLit.new(integer) }
23
- rule(float: simple(:float)) { Hesabu::Types::FloatLit.new(float) }
24
- end
25
- end
data/lib/hesabu/parser.rb DELETED
@@ -1,75 +0,0 @@
1
- module Hesabu
2
- class Parser < Parslet::Parser
3
- def cts(atom_arg)
4
- atom_arg >> space?
5
- end
6
-
7
- # simple things
8
- rule(:lparen) { str("(") >> space? }
9
- rule(:rparen) { str(")") >> space? }
10
- rule(:comma) { str(",") >> space? }
11
- rule(:space) { match["\s"] | match["\t"] | match["\n"] }
12
- rule(:spaces) { space.repeat }
13
- rule(:space?) { spaces.maybe }
14
-
15
- rule(:nonquote) { str("'").absnt? >> any }
16
- rule(:quote) { str("'") }
17
- rule(:string) { quote >> nonquote.as(:char).repeat(1).as(:str) >> quote >> space? }
18
-
19
- rule(:identifier) do
20
- cts((match["a-zA-Z"] >> match["a-zA-Z0-9_"].repeat).as(:identifier))
21
- end
22
-
23
- rule(:separator) { str(";") }
24
-
25
- rule(:digit) { match["0-9"] }
26
-
27
- rule(:integer) do
28
- cts((str("-").maybe >> match["1-9"] >> digit.repeat).as(:integer) | str("0").as(:integer))
29
- end
30
-
31
- rule(:float) do
32
- cts((str("-").maybe >> digit.repeat(1) >> str(".") >> digit.repeat(1)).as(:float)) |
33
- cts((str(".") >> digit.repeat(1)).as(:float))
34
- end
35
-
36
- # arithmetic
37
-
38
- rule(:expression) { iexpression | variable | pexpression }
39
- rule(:pexpression) { lparen >> expression >> rparen }
40
-
41
- rule(:variable) { identifier.as(:variable) }
42
- rule(:sum_op) { match("[+-]") >> space? }
43
- rule(:mul_op) { match("[*/]") >> space? }
44
- rule(:comparison_op) do
45
- (
46
- str("<=") | str(">=") | str("==") |
47
- str("!=") | str("<") | str("=") |
48
- str(">") | str("AND")
49
- ) >> space?
50
- end
51
-
52
- rule(:atom) { string | pexpression | float | integer | fcall.as(:fcall) | variable }
53
-
54
- rule(:iexpression) do
55
- infix_expression(atom,
56
- [mul_op, 3, :left],
57
- [sum_op, 2, :left],
58
- [comparison_op, 1, :left])
59
- end
60
-
61
- # lists
62
- rule(:varlist) { expression >> (comma >> expression).repeat }
63
- rule(:pvarlist) { (lparen >> varlist.repeat >> rparen).as(:plist) }
64
-
65
- # functions
66
- rule(:fcall) { identifier.as(:name) >> pvarlist.as(:varlist) }
67
-
68
- # root
69
- rule(:command) do
70
- iexpression | expression | atom
71
- end
72
- rule(:commands) { commands.repeat }
73
- root :command
74
- end
75
- end
@@ -1,9 +0,0 @@
1
- module Hesabu
2
- module Types
3
- FloatLit = Struct.new(:float) do
4
- def eval
5
- Hesabu::Types.as_bigdecimal(float)
6
- end
7
- end
8
- end
9
- end
@@ -1,123 +0,0 @@
1
- module Hesabu
2
- module Types
3
- class Function
4
- def divide(num, denum)
5
- num / denum
6
- end
7
- end
8
- class IfFunction < Function
9
- def call(args)
10
- raise "expected args #{name} : #{args}" unless args.size != 2
11
- condition_expression = args[0]
12
- condition = condition_expression.eval
13
- condition ? args[1].eval : args[2].eval
14
- end
15
- end
16
-
17
- class SumFunction < Function
18
- def call(args)
19
- values = args.map(&:eval)
20
- values.reduce(0, :+)
21
- end
22
- end
23
-
24
- class ScoreTableFunction < Function
25
- def call(args)
26
- values = args.map(&:eval)
27
- target = values.shift
28
- matching_rules = values.each_slice(3).find do |lower, greater, result|
29
- greater.nil? || result.nil? ? true : lower <= target && target < greater
30
- end
31
- matching_rules.last
32
- end
33
- end
34
-
35
- class AvgFunction < Function
36
- def call(args)
37
- values = args.map(&:eval)
38
- values.inject(0.0) { |acc, elem| acc + elem } / values.size
39
- end
40
- end
41
-
42
- class SafeDivFunction < Function
43
- def call(args)
44
- eval_denom = args[1].eval
45
- if eval_denom == 0
46
- 0
47
- else
48
- eval_num = args[0].eval
49
- eval_denom.zero? ? 0 : (eval_num / eval_denom)
50
- end
51
- end
52
- end
53
-
54
- class MinFunction
55
- def call(args)
56
- values = args.map(&:eval)
57
- values.min
58
- end
59
- end
60
-
61
- class MaxFunction
62
- def call(args)
63
- values = args.map(&:eval)
64
- values.max
65
- end
66
- end
67
-
68
- class RandbetweenFunction
69
- def call(args)
70
- values = args.map(&:eval)
71
- rand(values.first..values.last)
72
- end
73
- end
74
-
75
- class AbsFunction
76
- def call(args)
77
- raise "expected args #{self.class.name} : #{args}" if args.size != 1
78
- args.first.eval.abs
79
- end
80
- end
81
-
82
- class AccessFunction
83
- def call(args)
84
- values = args.map(&:eval)
85
- array = values[0..-2]
86
- index = values[-1]
87
- array[index]
88
- end
89
- end
90
-
91
- class RoundFunction
92
- def call(args)
93
- raise "expected args #{self.class.name} : #{args}" if args.size > 2 || args.empty?
94
- values = args.map(&:eval)
95
- decimals = args.size == 2 ? values[1] : 0
96
- values.first.round(decimals)
97
- end
98
- end
99
-
100
- FUNCTIONS = {
101
- "if" => IfFunction.new,
102
- "sum" => SumFunction.new,
103
- "avg" => AvgFunction.new,
104
- "min" => MinFunction.new,
105
- "max" => MaxFunction.new,
106
- "safe_div" => SafeDivFunction.new,
107
- "randbetween" => RandbetweenFunction.new,
108
- "score_table" => ScoreTableFunction.new,
109
- "abs" => AbsFunction.new,
110
- "access" => AccessFunction.new,
111
- "round" => RoundFunction.new
112
- }.freeze
113
-
114
- FunCall = Struct.new(:name, :args) do
115
- def eval
116
- function_name = name.strip.downcase
117
- function = FUNCTIONS[function_name]
118
- raise "unsupported function call : #{function_name} only knows #{FUNCTIONS.keys.join(', ')}" unless function
119
- function.call(args)
120
- end
121
- end
122
- end
123
- end
@@ -1,9 +0,0 @@
1
- module Hesabu
2
- module Types
3
- IdentifierLit = Struct.new(:var_identifier, :bindings) do
4
- def eval
5
- bindings[var_identifier] ||= 0
6
- end
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- module Hesabu
2
- module Types
3
- IntLit = Struct.new(:int) do
4
- def eval
5
- ::Hesabu::Types.as_bigdecimal(int)
6
- end
7
- end
8
- end
9
- end
@@ -1,42 +0,0 @@
1
- module Hesabu
2
- module Types
3
- Operation = Struct.new(:left, :operator, :right) do
4
- def eval
5
- op = operator.str.strip
6
- result(op, left.eval, right.eval)
7
- end
8
-
9
- private
10
-
11
- def result(op, leftval, rightval)
12
- case op
13
- when "+"
14
- leftval + rightval
15
- when "-"
16
- leftval - rightval
17
- when "*"
18
- leftval * rightval
19
- when "/"
20
- raise DivideByZeroError, "division by 0 : #{leftval}/0" if rightval.zero?
21
- leftval / rightval
22
- when ">"
23
- leftval > rightval
24
- when "<"
25
- leftval < rightval
26
- when ">="
27
- leftval >= rightval
28
- when "<="
29
- leftval <= rightval
30
- when "=", "=="
31
- leftval == rightval
32
- when "!="
33
- leftval != rightval
34
- when "AND"
35
- leftval && rightval
36
- else
37
- raise "unsupported operand : #{op} : #{left} #{operator} #{right}"
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,9 +0,0 @@
1
- module Hesabu
2
- module Types
3
- StringLit = Struct.new(:string) do
4
- def eval
5
- string
6
- end
7
- end
8
- end
9
- end