minad-evaluator 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,8 +12,24 @@ Usage
12
12
 
13
13
  See the test cases for more examples.
14
14
 
15
+ Calculator
16
+ ----------
17
+
18
+ A small calculator program (calc.rb) is provided with this library. You can use
19
+ it as follows:
20
+
21
+ $ ./calc.rb
22
+ > number := 10
23
+ 10
24
+ > number * 3
25
+ 30
26
+ > 1 [joule] in [MeV]
27
+ 6241509647120.42 MeV
28
+
29
+ The calculator loads a few natural constants at startup (calc.startup). For unit support
30
+ my minad-units library is used. Units are denoted in brackets e.g. [meter], [kV] etc
31
+
15
32
  Authors
16
33
  -------
17
34
 
18
35
  Daniel Mendler
19
-
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'hoe'
3
3
  $:.unshift 'lib'
4
4
  require 'evaluator'
5
5
 
6
- Hoe.new 'evaluator', Evaluator::VERSION do |latex|
7
- latex.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
6
+ Hoe.new 'evaluator', Evaluator::VERSION do |evaluator|
7
+ evaluator.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
8
8
  end
9
9
 
@@ -1,31 +1,39 @@
1
1
  require 'complex'
2
+ begin
3
+ require 'units'
4
+ rescue LoadError
5
+ end
2
6
 
3
7
  module Evaluator
4
8
  def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
5
9
  def self.prefix(&block) [true, 1e5, lambda(&block)] end
6
10
 
7
- VERSION = "0.1.3"
11
+ VERSION = '0.1.5'
8
12
  OPERATOR = {
9
- '||' => infix(0) {|a,b| a || b },
10
- '&&' => infix(1) {|a,b| a && b },
11
- '==' => infix(2) {|a,b| a == b },
12
- '!=' => infix(2) {|a,b| a != b },
13
- '<=' => infix(2) {|a,b| a <= b },
14
- '>=' => infix(2) {|a,b| a >= b },
15
- '<' => infix(2) {|a,b| a < b },
16
- '>' => infix(2) {|a,b| a > b },
17
- '+' => infix(3, 'plus') {|a,b| a + b },
18
- '-' => infix(3, 'minus') {|a,b| a - b },
19
- '>>' => infix(4) {|a,b| a >> b },
20
- '<<' => infix(4) {|a,b| a << b },
21
- '&' => infix(5) {|a,b| a & b },
22
- '|' => infix(5) {|a,b| a | b },
23
- '^' => infix(5) {|a,b| a ^ b },
24
- '*' => infix(6) {|a,b| a * b },
25
- '/' => infix(6) {|a,b| a / b },
26
- '%' => infix(6) {|a,b| a % b },
27
- 'div' => infix(6) {|a,b| a.div b },
28
- '**' => infix(7) {|a,b| a ** b },
13
+ 'in' => infix(0) do |a,b|
14
+ raise(RuntimeError, 'Unit support not available') if !a.respond_to? :in
15
+ a.in(b)
16
+ end,
17
+ '||' => infix(1) {|a,b| a || b },
18
+ '&&' => infix(2) {|a,b| a && b },
19
+ '==' => infix(3) {|a,b| a == b },
20
+ '!=' => infix(3) {|a,b| a != b },
21
+ '<=' => infix(3) {|a,b| a <= b },
22
+ '>=' => infix(3) {|a,b| a >= b },
23
+ '<' => infix(3) {|a,b| a < b },
24
+ '>' => infix(3) {|a,b| a > b },
25
+ '+' => infix(4, 'plus') {|a,b| a + b },
26
+ '-' => infix(4, 'minus') {|a,b| a - b },
27
+ '>>' => infix(5) {|a,b| a >> b },
28
+ '<<' => infix(5) {|a,b| a << b },
29
+ '&' => infix(6) {|a,b| a & b },
30
+ '|' => infix(6) {|a,b| a | b },
31
+ '^' => infix(6) {|a,b| a ^ b },
32
+ '*' => infix(7) {|a,b| a * b },
33
+ '/' => infix(7) {|a,b| a / b },
34
+ '%' => infix(7) {|a,b| a % b },
35
+ 'div' => infix(7) {|a,b| a.div b },
36
+ '**' => infix(8) {|a,b| a ** b },
29
37
  'gcd' => prefix {|x,y| x.gcd(y) },
30
38
  'lcm' => prefix {|x,y| x.lcm(y) },
31
39
  'sin' => prefix {|x| Math.sin(x) },
@@ -69,6 +77,7 @@ module Evaluator
69
77
  'reverse' => prefix {|x| x.reverse },
70
78
  'index' => prefix {|x,y| x.index(y) },
71
79
  'rindex' => prefix {|x,y| x.rindex(y) },
80
+ '=' => '==',
72
81
  'or' => '||',
73
82
  'and' => '&&',
74
83
  'mod' => '%',
@@ -89,7 +98,7 @@ module Evaluator
89
98
  'nil' => nil,
90
99
  'e' => Math::E,
91
100
  'pi' => Math::PI,
92
- 'i' => Complex::I
101
+ 'i' => Complex::I,
93
102
  }
94
103
  STRING = /^(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*")$/
95
104
  REAL = /^(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
@@ -97,48 +106,64 @@ module Evaluator
97
106
  OCT = /^0[0-7]+$/
98
107
  DEC = /^\d+$/
99
108
  SYMBOL = /^[a-zA-Z_][\w_]*$/
100
- VALUE_TOKENS = [STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
109
+ UNIT = /^\[[^\]]+\]$/
110
+ VALUE_TOKENS = [UNIT, STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
101
111
  OPERATOR_TOKENS = OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |x| Regexp.quote(x) }
102
112
  TOKENIZER = Regexp.new((VALUE_TOKENS + OPERATOR_TOKENS + ['\\(', '\\)', ',']).join('|'))
103
113
 
104
114
  def self.eval(expr, vars = {})
105
- vars = Hash[*vars.map {|k,v| [k.to_s.downcase, v] }.flatten].merge(CONSTANTS)
115
+ vars = Hash[*vars.merge(CONSTANTS).map {|k,v| [k.to_s.downcase, v] }.flatten]
106
116
  stack, result, unary = [], [], true
107
117
  expr.to_s.scan(TOKENIZER).each do |tok|
108
118
  if tok == '('
109
119
  stack << '('
120
+ unary = true
110
121
  elsif tok == ')'
111
122
  exec(result, stack.pop) while !stack.empty? && stack.last != '('
112
- raise(SyntaxError, "Unexpected token )") if stack.empty?
123
+ raise(SyntaxError, 'Unexpected token )') if stack.empty?
113
124
  stack.pop
125
+ unary = false
114
126
  elsif tok == ','
115
127
  exec(result, stack.pop) while !stack.empty? && stack.last != '('
128
+ unary = true
116
129
  elsif operator = OPERATOR[tok.downcase]
130
+ # Check for alias
117
131
  tok = String === operator ? operator : tok.downcase
132
+ operator = OPERATOR[tok]
118
133
  if operator[0]
134
+ # Prefix operator
119
135
  stack << tok
120
136
  elsif unary && operator[3]
137
+ # Alternative prefix operator
121
138
  stack << operator[3]
122
139
  else
140
+ # Infix operator
123
141
  exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= operator[1]
124
142
  stack << tok
125
143
  end
144
+ unary = true
126
145
  else
127
- result << case tok
128
- when STRING then tok[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'")
129
- when REAL then tok.to_f
130
- when HEX then tok.to_i(16)
131
- when OCT then tok.to_i(8)
132
- when DEC then tok.to_i(10)
133
- when SYMBOL
134
- tok.downcase!
135
- raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
136
- vars[tok]
137
- end
146
+ val = case tok
147
+ when UNIT
148
+ if tok.respond_to? :to_unit
149
+ tok[1..-2].to_unit
150
+ else
151
+ raise(RuntimeError, 'Unit support not available')
152
+ end
153
+ when STRING then tok[1..-2].gsub(/\\"/, '"').gsub(/\\'/, "'")
154
+ when REAL then tok.to_f
155
+ when HEX then tok.to_i(16)
156
+ when OCT then tok.to_i(8)
157
+ when DEC then tok.to_i(10)
158
+ when SYMBOL
159
+ tok.downcase!
160
+ raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
161
+ vars[tok]
162
+ end
163
+ stack << '*' if !unary
164
+ result << val
138
165
  unary = false
139
- next
140
166
  end
141
- unary = true
142
167
  end
143
168
  exec(result, stack.pop) while !stack.empty?
144
169
  result.last
@@ -146,9 +171,9 @@ module Evaluator
146
171
 
147
172
  def self.exec(result, op)
148
173
  raise(SyntaxError, "Unexpected token #{op}") if !OPERATOR.include?(op)
149
- op = OPERATOR[op][2]
150
- raise(SyntaxError, "Not enough operands for #{op}") if result.size < op.arity
151
- result << op[*result.slice!(-op.arity, op.arity)]
174
+ fn = OPERATOR[op][2]
175
+ raise(SyntaxError, "Not enough operands for #{op}") if result.size < fn.arity
176
+ result << fn[*result.slice!(-fn.arity, fn.arity)]
152
177
  end
153
178
 
154
179
  private_class_method :infix, :prefix, :exec
@@ -30,6 +30,8 @@ class TestEvaluator < Test::Unit::TestCase
30
30
 
31
31
  assert_equal 3, Evaluator('1+2')
32
32
  assert_equal 'xyz', Evaluator('"x"+"yz"')
33
+ assert_equal 'a1b', Evaluator("'a'+string 1+'b'")
34
+ assert_equal 'a1b', Evaluator("'a'+string(1)+'b'")
33
35
 
34
36
  assert_equal 3, Evaluator('5-2')
35
37
  assert_equal(-1, Evaluator('3-4'))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minad-evaluator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Mendler