minad-evaluator 0.1.1 → 0.1.2

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