minad-evaluator 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/evaluator.rb +103 -80
  2. data/test/test_evaluator.rb +27 -1
  3. metadata +1 -1
data/lib/evaluator.rb CHANGED
@@ -4,64 +4,84 @@ module Evaluator
4
4
  def self.infix(priority, unary = nil, &block) [false, priority, lambda(&block), unary] end
5
5
  def self.prefix(&block) [true, 1e5, lambda(&block)] end
6
6
 
7
- VERSION = "0.1.1"
7
+ VERSION = "0.1.2"
8
8
  OPERATOR = {
9
- '||' => infix(0) {|a,b| a || b },
10
- 'or' => infix(0) {|a,b| a || b },
11
- '&&' => infix(1) {|a,b| a && b },
12
- 'and' => infix(1) {|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(2) {|a,b| a < b },
18
- '>' => infix(2) {|a,b| a > b },
19
- '+' => infix(3, 'plus') {|a,b| a + b },
20
- '-' => infix(3, 'minus') {|a,b| a - b },
21
- '>>' => infix(4) {|a,b| a >> b },
22
- '<<' => infix(4) {|a,b| a << b },
23
- '&' => infix(5) {|a,b| a & b },
24
- '|' => infix(5) {|a,b| a | b },
25
- '^' => infix(5) {|a,b| a ^ b },
26
- '*' => infix(6) {|a,b| a * b },
27
- '/' => infix(6) {|a,b| a / b },
28
- '%' => infix(6) {|a,b| a % b },
29
- 'div' => infix(6) {|a,b| a.div b },
30
- 'mod' => infix(6) {|a,b| a % b },
31
- '**' => infix(7) {|a,b| a ** b },
32
- 'sin' => prefix {|x| Math.sin(x) },
33
- 'cos' => prefix {|x| Math.cos(x) },
34
- 'tan' => prefix {|x| Math.tan(x) },
35
- 'sinh' => prefix {|x| Math.sinh(x) },
36
- 'cosh' => prefix {|x| Math.cosh(x) },
37
- 'tanh' => prefix {|x| Math.tanh(x) },
38
- 'asin' => prefix {|x| Math.asin(x) },
39
- 'acos' => prefix {|x| Math.acos(x) },
40
- 'atan' => prefix {|x| Math.atan(x) },
41
- 'asinh' => prefix {|x| Math.asinh(x) },
42
- 'atanh' => prefix {|x| Math.atanh(x) },
43
- 'sqrt' => prefix {|x| Math.sqrt(x) },
44
- 'log' => prefix {|x| Math.log(x) },
45
- 'ln' => prefix {|x| Math.log(x) },
46
- 'log10' => prefix {|x| Math.log10(x) },
47
- 'log2' => prefix {|x| Math.log(x)/Math.log(2) },
48
- 'exp' => prefix {|x| Math.exp(x) },
49
- 'floor' => prefix {|x| x.floor },
50
- 'ceil' => prefix {|x| x.ceil },
51
- 'string' => prefix {|x| x.to_s },
52
- 'int' => prefix {|x| x.to_i },
53
- 'float' => prefix {|x| x.to_f },
54
- 'rand' => prefix {|| rand },
55
- 'conj' => prefix {|x| x.conj },
56
- 'im' => prefix {|x| x.imag },
57
- 're' => prefix {|x| x.real },
58
- 'round' => prefix {|x| x.round },
59
- 'abs' => prefix {|x| x.abs },
60
- 'minus' => prefix {|x| -x },
61
- 'plus' => prefix {|x| x },
62
- '!' => prefix {|x| !x },
63
- 'substr' => prefix {|x,a,b| x.slice(a,b) },
64
- 'len' => prefix {|x| x.length }
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 },
29
+ 'gcd' => prefix {|x,y| x.gcd(y) },
30
+ 'lcm' => prefix {|x,y| x.lcm(y) },
31
+ 'sin' => prefix {|x| Math.sin(x) },
32
+ 'cos' => prefix {|x| Math.cos(x) },
33
+ 'tan' => prefix {|x| Math.tan(x) },
34
+ 'sinh' => prefix {|x| Math.sinh(x) },
35
+ 'cosh' => prefix {|x| Math.cosh(x) },
36
+ 'tanh' => prefix {|x| Math.tanh(x) },
37
+ 'asin' => prefix {|x| Math.asin(x) },
38
+ 'acos' => prefix {|x| Math.acos(x) },
39
+ 'atan' => prefix {|x| Math.atan(x) },
40
+ 'asinh' => prefix {|x| Math.asinh(x) },
41
+ 'atanh' => prefix {|x| Math.atanh(x) },
42
+ 'sqrt' => prefix {|x| Math.sqrt(x) },
43
+ 'log' => prefix {|x| Math.log(x) },
44
+ 'log10' => prefix {|x| Math.log10(x) },
45
+ 'log2' => prefix {|x| Math.log(x)/Math.log(2) },
46
+ 'exp' => prefix {|x| Math.exp(x) },
47
+ 'erf' => prefix {|x| Math.erf(x) },
48
+ 'erfc' => prefix {|x| Math.erfc(x) },
49
+ 'floor' => prefix {|x| x.floor },
50
+ 'ceil' => prefix {|x| x.ceil },
51
+ 'string' => prefix {|x| x.to_s },
52
+ 'int' => prefix {|x| x.to_i },
53
+ 'float' => prefix {|x| x.to_f },
54
+ 'rand' => prefix {|| rand },
55
+ 'conj' => prefix {|x| x.conj },
56
+ 'im' => prefix {|x| x.imag },
57
+ 're' => prefix {|x| x.real },
58
+ 'round' => prefix {|x| x.round },
59
+ 'abs' => prefix {|x| x.abs },
60
+ 'minus' => prefix {|x| -x },
61
+ 'plus' => prefix {|x| x },
62
+ '!' => prefix {|x| !x },
63
+ '~' => prefix {|x| ~x },
64
+ 'substr' => prefix {|x,a,b| x.slice(a,b) },
65
+ 'len' => prefix {|x| x.length },
66
+ 'tolower' => prefix {|x| x.downcase },
67
+ 'toupper' => prefix {|x| x.upcase },
68
+ 'strip' => prefix {|x| x.strip },
69
+ 'reverse' => prefix {|x| x.reverse },
70
+ 'index' => prefix {|x,y| x.index(y) },
71
+ 'rindex' => prefix {|x,y| x.rindex(y) },
72
+ 'or' => '||',
73
+ 'and' => '&&',
74
+ 'mod' => '%',
75
+ 'ln' => 'log',
76
+ 'imag' => 'im',
77
+ 'real' => 're',
78
+ 'count' => 'len',
79
+ 'size' => 'len',
80
+ 'length' => 'len',
81
+ 'trim' => 'strip',
82
+ 'downcase' => 'tolower',
83
+ 'upcase' => 'toupper',
84
+ 'slice' => 'substr',
65
85
  }
66
86
  CONSTANTS = {
67
87
  'true' => true,
@@ -71,22 +91,15 @@ module Evaluator
71
91
  'pi' => Math::PI,
72
92
  'i' => Complex::I
73
93
  }
74
- TOKENS = [
75
- [ /^'(?:[^']|\\')+'|"(?:[^"]|\\")+"$/, lambda {|tok, vars| tok[1..-2] } ],
76
- [ /^(?:\d*\.\d+(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/, lambda {|tok, vars| tok.to_f } ],
77
- [ /^0[xX][\dA-Fa-f]+$/, lambda {|tok, vars| tok.to_i(16) } ],
78
- [ /^0[0-7]+$/, lambda {|tok, vars| tok.to_i(8) } ],
79
- [ /^\d+$/, lambda {|tok, vars| tok.to_i(10) } ],
80
- [ /^[a-zA-Z_][\w_]*$/, lambda {|tok, vars|
81
- tok.downcase!
82
- raise(NameError, "Symbol #{tok} is undefined") if !vars.include?(tok)
83
- vars[tok]
84
- }
85
- ]
86
- ]
87
- TOKENIZER = Regexp.new((TOKENS.map {|x| x[0].source[1..-2] } +
88
- OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |op| Regexp.quote(op) }).
89
- join('|') + '|\\(|\\)|,')
94
+ STRING = /^(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*")$/
95
+ REAL = /^(?:(?:\d*\.\d+|\d+\.\d*)(?:[eE][-+]?\d+)?|\d+[eE][-+]?\d+)$/
96
+ HEX = /^0[xX][\dA-Fa-f]+$/
97
+ OCT = /^0[0-7]+$/
98
+ DEC = /^\d+$/
99
+ SYMBOL = /^[a-zA-Z_][\w_]*$/
100
+ VALUE_TOKENS = [STRING, REAL, HEX, OCT, DEC, SYMBOL].map {|x| x.source[1..-2] }
101
+ OPERATOR_TOKENS = OPERATOR.keys.flatten.sort { |a,b| b.length <=> a.length}.map { |x| Regexp.quote(x) }
102
+ TOKENIZER = Regexp.new((VALUE_TOKENS + OPERATOR_TOKENS + ['\\(', '\\)', ',']).join('|'))
90
103
 
91
104
  def self.eval(expr, vars = {})
92
105
  vars = Hash[*vars.map {|k,v| [k.to_s.downcase, v] }.flatten].merge(CONSTANTS)
@@ -100,18 +113,28 @@ module Evaluator
100
113
  stack.pop
101
114
  elsif tok == ','
102
115
  exec(result, stack.pop) while !stack.empty? && stack.last != '('
103
- elsif OPERATOR.include?(tok.downcase)
104
- tok.downcase!
105
- if OPERATOR[tok][0]
116
+ elsif operator = OPERATOR[tok.downcase]
117
+ tok = String === operator ? operator : tok.downcase
118
+ if operator[0]
106
119
  stack << tok
107
- elsif unary && OPERATOR[tok][3]
108
- stack << OPERATOR[tok][3]
120
+ elsif unary && operator[3]
121
+ stack << operator[3]
109
122
  else
110
- exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
123
+ exec(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= operator[1]
111
124
  stack << tok
112
125
  end
113
126
  else
114
- result << TOKENS.find {|x| tok =~ x[0] }[1][tok, vars]
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
115
138
  unary = false
116
139
  next
117
140
  end
@@ -78,7 +78,10 @@ class TestEvaluator < Test::Unit::TestCase
78
78
  assert_equal nil, Evaluator('niL')
79
79
  end
80
80
 
81
- def test_functions
81
+ def test_numeric_functions
82
+ assert_equal 13, Evaluator('gcd(26, 39)')
83
+ assert_equal 78, Evaluator('lcm(26, 39)')
84
+ assert_equal Math.cos(42), Evaluator('cos 42')
82
85
  assert_equal Math.sin(42), Evaluator('sin 42')
83
86
  assert_equal Math.cos(42), Evaluator('cos 42')
84
87
  assert_equal Math.tan(42), Evaluator('tan 42')
@@ -96,6 +99,8 @@ class TestEvaluator < Test::Unit::TestCase
96
99
  assert_equal Math.log10(42) + 3, Evaluator('log10 42 + 3')
97
100
  assert_equal Math.log(42)/Math.log(2) + 3, Evaluator('log2 42 + 3')
98
101
  assert_equal 3 * Math.exp(42), Evaluator('3 * exp 42')
102
+ assert_equal Math.erf(2), Evaluator('erf 2')
103
+ assert_equal Math.erfc(2), Evaluator('erfc 2')
99
104
  assert_equal 42, Evaluator('floor 42.3')
100
105
  assert_equal 42, Evaluator('ceil 41.6')
101
106
  assert_equal 3.5, Evaluator('float("3.5")')
@@ -113,8 +118,16 @@ class TestEvaluator < Test::Unit::TestCase
113
118
  assert_equal 3, Evaluator('plus 3')
114
119
  assert_equal(-3, Evaluator('minus 3'))
115
120
  assert_equal false, Evaluator('!3')
121
+ assert_equal ~3, Evaluator('~3')
122
+ end
123
+
124
+ def test_string_functions
116
125
  assert_equal 'bcd', Evaluator('substr("abcde", 1, 3)')
117
126
  assert_equal 4, Evaluator('len("abcd")')
127
+ assert_equal 'abc', Evaluator('strip " abc "')
128
+ assert_equal 'cba', Evaluator('reverse "abc"')
129
+ assert_equal 2, Evaluator('index("abcdefg", "cde")')
130
+ assert_equal 7, Evaluator('rindex("abcdefgcdef", "cde")')
118
131
  end
119
132
 
120
133
  def test_variables
@@ -137,5 +150,18 @@ class TestEvaluator < Test::Unit::TestCase
137
150
  assert_equal 0xABCDEF123, Evaluator('0xABCDEF123')
138
151
  assert_equal 01234, Evaluator('01234')
139
152
  assert_equal 234, Evaluator('234 ')
153
+ assert_equal 0.123, Evaluator('.123')
154
+ assert_equal 0.123, Evaluator('0.123')
155
+ assert_equal 123.0, Evaluator('123.')
156
+ assert_equal 123e-42, Evaluator('123e-42')
157
+ assert_equal 0.123e-42, Evaluator('.123e-42')
158
+ assert_equal 2.123e-42, Evaluator('2.123e-42')
159
+ end
160
+
161
+ def test_strings
162
+ assert_equal "abc'a", Evaluator('"abc\'a"')
163
+ assert_equal 'abc"a', Evaluator('"abc\"a"')
164
+ assert_equal 'abc"a', Evaluator("'abc\"a'")
165
+ assert_equal "abc'a", Evaluator("'abc\\'a'")
140
166
  end
141
167
  end
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.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Mendler