hesabu 0.1.5 → 0.1.6

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 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