lamep 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +2 -0
- data/lamep.gemspec +23 -0
- data/lib/lamep/Expressions/and.rb +8 -0
- data/lib/lamep/Expressions/arity1_operators.rb +16 -0
- data/lib/lamep/Expressions/arity2_operators.rb +17 -0
- data/lib/lamep/Expressions/equal.rb +8 -0
- data/lib/lamep/Expressions/greater_than.rb +8 -0
- data/lib/lamep/Expressions/greater_than_equal.rb +8 -0
- data/lib/lamep/Expressions/less_than.rb +8 -0
- data/lib/lamep/Expressions/less_than_equal.rb +8 -0
- data/lib/lamep/Expressions/not_equal.rb +8 -0
- data/lib/lamep/Expressions/operator.rb +30 -0
- data/lib/lamep/Expressions/or.rb +7 -0
- data/lib/lamep/Expressions/unary_minus.rb +10 -0
- data/lib/lamep/Expressions/value_expression.rb +11 -0
- data/lib/lamep/abstract_syntax_tree_builder.rb +32 -0
- data/lib/lamep/shunting_yard.rb +49 -0
- data/lib/lamep/token_parser.rb +22 -0
- data/spec/lib/Expressions/operators_spec.rb +78 -0
- data/spec/lib/abstract_syntax_tree_builder_spec.rb +67 -0
- data/spec/lib/shunting_yard_spec.rb +53 -0
- data/spec/lib/token_parser_spec.rb +37 -0
- data/spec/spec_helper.rb +90 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fc0ce5e8f408a519a1c060670e476287be1d9f67
|
4
|
+
data.tar.gz: c2930b9fbdcdc8c53df84b860f2d331adf53d911
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fbf0d01e871e567f1e9fb466512a581c17b10631845611b09e02e1374d338a5c6edfd53a7acd480186b8a7df5e14c23dfc025cd26929441eed5863a4087dd466
|
7
|
+
data.tar.gz: 1e0f8b49ed2b77cadc2cb0aa7b10fbebe9a7d21865647a34e25af1e6a47bcb8769a87cf6c5420e44bf86683cbee3f1f89157e68f582a2fa80e6f5d60e36b60f9
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Miroslav Csonka
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# LAMEP
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'lamep'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install lamep
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
# Basic Usage
|
22
|
+
|
23
|
+
You can use lamep for:
|
24
|
+
|
25
|
+
1. Parsing expression into array of tokens:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Splits expression into single tokens
|
29
|
+
tokens = TokenParser.new.parse('material = wood && (price > 180 || price <= 250)')
|
30
|
+
```
|
31
|
+
Variable `tokens` will be array of tokens:
|
32
|
+
```ruby
|
33
|
+
%w{material = wood && ( price > 180 || price <= 250 )}
|
34
|
+
```
|
35
|
+
|
36
|
+
2. Converting tokens into postfix notation:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# Converts tokens into postfix notation (also known as RPN)
|
40
|
+
postfix = ShuntingYard.new(tokens).posfix
|
41
|
+
```
|
42
|
+
Variable `postfix` will be array of tokens in postfix notation:
|
43
|
+
```ruby
|
44
|
+
%w{material wood = price 180 > price 250 <= || &&}
|
45
|
+
```
|
46
|
+
|
47
|
+
3. Building Abstract Syntax Tree:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Builds tree from postfix
|
51
|
+
ast = AbstractSyntaxTreeBuilder.new(postfix).build_tree
|
52
|
+
```
|
53
|
+
Variable `ast` will be containing such structure:
|
54
|
+
```ruby
|
55
|
+
And.new(
|
56
|
+
Equal.new('material', 'wood'),
|
57
|
+
Or.new(
|
58
|
+
GreaterThan.new('price', '180'),
|
59
|
+
LessThanEqual.new('price', '250')
|
60
|
+
)
|
61
|
+
)
|
62
|
+
```
|
63
|
+
4. Building SQL WHERE clause:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# Builds SQL WHERE clause
|
67
|
+
sql_where = ast.to_sql
|
68
|
+
```
|
69
|
+
Variable `sql_where` will be string:
|
70
|
+
```ruby
|
71
|
+
'((material = wood) AND ((price > 180) OR (price <= 250)))'
|
72
|
+
```
|
73
|
+
|
74
|
+
# Supported operators
|
75
|
+
|
76
|
+
At this point we support following list of operators and their SQL conversion:
|
77
|
+
|
78
|
+
operator | description
|
79
|
+
---------|--------------
|
80
|
+
**=** | *Equal*
|
81
|
+
**!=** | *Not Equal*
|
82
|
+
**>** | *Greater Than*
|
83
|
+
**>=** | *Greater Than Equal*
|
84
|
+
**<** | *Less Than*
|
85
|
+
**<=** | *Less Than Equal*
|
86
|
+
**&&** | *Logical AND*
|
87
|
+
**\|\|** | *Logical OR*
|
88
|
+
**-** | *Unary Minus*
|
89
|
+
|
90
|
+
The rest of passed expression is considered as an operand i.e. either variable or a number.
|
91
|
+
|
92
|
+
# Adding your own operators
|
93
|
+
TODO:
|
94
|
+
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it ( https://github.com/forex-kaiz/lamep/fork )
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lamep.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'lamep'
|
7
|
+
spec.version = 0.1
|
8
|
+
spec.authors = ['Martin Svoboda', 'Miroslav Csonka']
|
9
|
+
spec.email = ['miroslav.csonka@gmail.com']
|
10
|
+
spec.summary = %q{Logical and mathematical expression parser}
|
11
|
+
spec.description = %q{Loads logical or mathematical expression (in string) into Abstract Syntax Tree for later evaluation}
|
12
|
+
spec.homepage = ''
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
21
|
+
spec.add_development_dependency 'rake'
|
22
|
+
spec.add_development_dependency 'rspec'
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Arity1Operators < Operator
|
2
|
+
|
3
|
+
attr_reader :operand
|
4
|
+
ARITY = 1
|
5
|
+
|
6
|
+
def ==(other)
|
7
|
+
other.class == self.class && other.operand == @operand
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(operand)
|
11
|
+
@operand = (operand.is_a?(Operator)) ? operand : ValueExpression.new(operand)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :equal?, :==
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Arity2Operators < Operator
|
2
|
+
|
3
|
+
attr_reader :left, :right
|
4
|
+
ARITY = 2
|
5
|
+
|
6
|
+
def initialize(left, right)
|
7
|
+
@left = (left.is_a?(Operator)) ? left : ValueExpression.new(left)
|
8
|
+
@right = (right.is_a?(Operator)) ? right : ValueExpression.new(right)
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
other.class == self.class && other.left == @left && other.right == @right
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :equal?, :==
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Operator
|
2
|
+
|
3
|
+
OPERATORS = {}
|
4
|
+
|
5
|
+
def self.factory!(operator_symbol)
|
6
|
+
operator(operator_symbol)[:class_name]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.precedence!(operator_symbol)
|
10
|
+
operator(operator_symbol)[:precedence]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.operator(operator_symbol)
|
14
|
+
fail "Unregistered operator: #{operator_symbol}" unless exists?(operator_symbol)
|
15
|
+
OPERATORS[operator_symbol]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.exists?(operator_symbol)
|
19
|
+
OPERATORS.key?(operator_symbol)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.register(operator_symbol, class_name, precedence)
|
23
|
+
OPERATORS[operator_symbol] = { class_name: class_name, precedence: precedence }
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_sql
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class AbstractSyntaxTreeBuilder
|
2
|
+
|
3
|
+
def initialize(postfix)
|
4
|
+
@postfix = postfix
|
5
|
+
end
|
6
|
+
|
7
|
+
def build_tree
|
8
|
+
until @postfix.length == 1
|
9
|
+
operator_index = first_operator_index
|
10
|
+
fail 'Not enough operators' if operator_index.nil?
|
11
|
+
exp = Operator.factory!(@postfix.slice!(operator_index))
|
12
|
+
most_left_child = operator_index - exp::ARITY
|
13
|
+
fail "Not enough operands for operator #{exp}" if most_left_child < 0
|
14
|
+
children = @postfix.slice!(most_left_child, exp::ARITY)
|
15
|
+
@postfix.insert(most_left_child, exp.new(*children))
|
16
|
+
end
|
17
|
+
@postfix.first
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def first_operator_index
|
23
|
+
@postfix.find_index { |token|
|
24
|
+
begin
|
25
|
+
Operator.factory!(token)
|
26
|
+
true
|
27
|
+
rescue
|
28
|
+
false
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class ShuntingYard
|
2
|
+
|
3
|
+
def initialize(tokens)
|
4
|
+
fail ArgumentError.new("Expected array: Got #{tokens.class}") unless tokens.is_a?(Array)
|
5
|
+
@tokens = tokens
|
6
|
+
end
|
7
|
+
|
8
|
+
def postfix
|
9
|
+
@output = []
|
10
|
+
@stack = []
|
11
|
+
bracket_sum = 0
|
12
|
+
@tokens.each do |token|
|
13
|
+
case token
|
14
|
+
when '('
|
15
|
+
bracket_sum += 1
|
16
|
+
@stack << token
|
17
|
+
when ')'
|
18
|
+
bracket_sum -= 1
|
19
|
+
fail('Right parentheses mismatch') if bracket_sum < 0
|
20
|
+
burn_stack_to_parentheses
|
21
|
+
else
|
22
|
+
if Operator.exists?(token)
|
23
|
+
burn_stack_to_higher_precedence(token)
|
24
|
+
@stack << token
|
25
|
+
else
|
26
|
+
@output << token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
fail('Left parentheses mismatch') if bracket_sum > 0
|
31
|
+
@output += @stack.reverse
|
32
|
+
@output
|
33
|
+
end
|
34
|
+
|
35
|
+
def burn_stack_to_higher_precedence(token)
|
36
|
+
until @stack.empty? || @stack.last == '(' || Operator.precedence!(token) < Operator.precedence!(@stack.last)
|
37
|
+
@output << @stack.pop
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private def burn_stack_to_parentheses
|
42
|
+
until @stack.empty?
|
43
|
+
popped = @stack.pop
|
44
|
+
break if popped == '('
|
45
|
+
@output << popped
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class TokenParser
|
2
|
+
|
3
|
+
def parse(input)
|
4
|
+
input.scan(/^<
|
5
|
+
|(>=)|
|
6
|
+
(<=)|
|
7
|
+
(!=)|
|
8
|
+
([a-zA-Záäéëěíóöôúůüýčďňřšťžĺľ]+)|
|
9
|
+
(=)|
|
10
|
+
(>)|
|
11
|
+
(<)|
|
12
|
+
(\d+)|
|
13
|
+
(\()|
|
14
|
+
(\))|
|
15
|
+
(&&)|
|
16
|
+
(\|\|)|
|
17
|
+
>+$/x)
|
18
|
+
.flatten
|
19
|
+
.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe Operator do
|
4
|
+
|
5
|
+
describe 'Generating sql statements' do
|
6
|
+
describe UnaryMinus do
|
7
|
+
it 'unary minus on int to SQL' do
|
8
|
+
expect(UnaryMinus.new('180').to_sql).to be(-180)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'unary minus on string to SQL' do
|
12
|
+
expect { UnaryMinus.new('ahoj').to_sql }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Equal do
|
17
|
+
it 'simple case' do
|
18
|
+
expect(Equal.new('price', '180').to_sql).to eq '(price = 180)'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe GreaterThan do
|
23
|
+
it 'simple case' do
|
24
|
+
expect(GreaterThan.new('price', '180').to_sql).to eq '(price > 180)'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe GreaterThanEqual do
|
29
|
+
it 'simple case' do
|
30
|
+
expect(GreaterThanEqual.new('price', '180').to_sql).to eq '(price >= 180)'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe LessThan do
|
35
|
+
it 'simple case' do
|
36
|
+
expect(LessThan.new('price', '180').to_sql).to eq '(price < 180)'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe LessThanEqual do
|
41
|
+
it 'simple case' do
|
42
|
+
expect(LessThan.new('price', '180').to_sql).to eq '(price < 180)'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe NotEqual do
|
47
|
+
it 'simple case' do
|
48
|
+
expect(NotEqual.new('price', '180').to_sql).to eq '(price != 180)'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe Or do
|
53
|
+
it 'Nested case' do
|
54
|
+
expression = Or.new(Equal.new('price', UnaryMinus.new('180')), Equal.new('Group', 'Tabák'))
|
55
|
+
expect(expression.to_sql).to eq '((price = -180) OR (Group = Tabák))'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe And do
|
60
|
+
it 'Nested case' do
|
61
|
+
expression = And.new(Equal.new('price', UnaryMinus.new('180')), Equal.new('Group', 'Tabák'))
|
62
|
+
expect(expression.to_sql).to eq '((price = -180) AND (Group = Tabák))'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'Composite case' do
|
67
|
+
expect(And.new(
|
68
|
+
Equal.new('materiál', 'dřevo'),
|
69
|
+
Or.new(
|
70
|
+
GreaterThan.new('cena', '180'),
|
71
|
+
GreaterThanEqual.new('cena', '250')
|
72
|
+
)
|
73
|
+
).to_sql).to eq '((materiál = dřevo) AND ((cena > 180) OR (cena >= 250)))'
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe AbstractSyntaxTreeBuilder do
|
4
|
+
def returns(postfix, tree)
|
5
|
+
expect(AbstractSyntaxTreeBuilder.new(postfix).build_tree).to be(tree)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'equal case' do
|
9
|
+
returns %w(materiál dřevo =), Equal.new('materiál', 'dřevo')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'gt case' do
|
13
|
+
returns %w(materiál 180 >), GreaterThan.new('materiál', '180')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'lt case' do
|
17
|
+
returns %w(materiál 180 <), LessThan.new('materiál', '180')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'gte case' do
|
21
|
+
returns %w(materiál 180 >=), GreaterThanEqual.new('materiál', '180')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'lte case' do
|
25
|
+
returns %w(materiál 180 <=), LessThanEqual.new('materiál', '180')
|
26
|
+
end
|
27
|
+
|
28
|
+
it '&& case' do
|
29
|
+
returns %w(materiál 180 &&), And.new('materiál', '180')
|
30
|
+
end
|
31
|
+
|
32
|
+
it '|| case' do
|
33
|
+
returns %w(materiál 180 ||), Or.new('materiál', '180')
|
34
|
+
end
|
35
|
+
|
36
|
+
it '!= case' do
|
37
|
+
returns %w(materiál 180 !=), NotEqual.new('materiál', '180')
|
38
|
+
end
|
39
|
+
|
40
|
+
it '- case' do
|
41
|
+
returns %w(180 -), UnaryMinus.new('180')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'composite cases' do
|
45
|
+
returns %w(materiál dřevo = cena 180 > &&),
|
46
|
+
And.new(Equal.new('materiál', 'dřevo'), GreaterThan.new('cena', '180'))
|
47
|
+
|
48
|
+
returns %w{materiál dřevo = cena 180 > cena 250 >= || &&},
|
49
|
+
And.new(
|
50
|
+
Equal.new('materiál', 'dřevo'),
|
51
|
+
Or.new(
|
52
|
+
GreaterThan.new('cena', '180'),
|
53
|
+
GreaterThanEqual.new('cena', '250')
|
54
|
+
)
|
55
|
+
)
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'only operands' do
|
60
|
+
expect {AbstractSyntaxTreeBuilder.new(%w(materiál cena)).build_tree }.to raise_error(RuntimeError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'only operators' do
|
64
|
+
expect {AbstractSyntaxTreeBuilder.new(%w(&& -)).build_tree }.to raise_error(RuntimeError)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe ShuntingYard do
|
4
|
+
def returns(tokens, postfix)
|
5
|
+
expect(ShuntingYard.new(tokens).postfix).to eq(postfix)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'empty input' do
|
9
|
+
returns [], []
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'single operand' do
|
13
|
+
returns ['drevo'], ['drevo']
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'wrong input' do
|
17
|
+
expect { ShuntingYard.new({}).postfix }.to raise_error(ArgumentError)
|
18
|
+
expect { ShuntingYard.new(5).postfix }.to raise_error(ArgumentError)
|
19
|
+
expect { ShuntingYard.new(nil).postfix }.to raise_error(ArgumentError)
|
20
|
+
expect { ShuntingYard.new('ahoj').postfix }.to raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'private constant' do
|
24
|
+
expect { ShuntingYard::OPERATORS }.to raise_error(NameError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'simple eq' do
|
28
|
+
returns %w(material = drevo), %w(material drevo =)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'empty brackets' do
|
32
|
+
returns %w(( )), []
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'brackets' do
|
36
|
+
returns %w(( cena > 180 )), %w(cena 180 >)
|
37
|
+
returns %w(( ( cena ) > ( 180 ) )), %w(cena 180 >)
|
38
|
+
expect { ShuntingYard.new(%w{( cena ) > 180 )}).postfix }.to raise_error(RuntimeError)
|
39
|
+
expect { ShuntingYard.new(%w{((( cena ) > 180 )}).postfix }.to raise_error(RuntimeError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'difficult cases' do
|
43
|
+
returns %w{material = drevo && ( cena > 180 || cena >= 250 )},
|
44
|
+
%w{material drevo = cena 180 > cena 250 >= || &&}
|
45
|
+
returns %w{( A || - B ) && C}, %w{A B - || C &&}
|
46
|
+
returns %w{( ) ( ( cena = - 150 ) ) ( )}, %w{cena 150 - =}
|
47
|
+
returns %w{- A = 150 || B && C}, %w{A - 150 = B C && ||}
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'czech operands' do
|
51
|
+
returns %w{dřevo < polička}, %w{dřevo polička <}
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe TokenParser do
|
4
|
+
let(:parser) { TokenParser.new }
|
5
|
+
|
6
|
+
def returns(string, tokens)
|
7
|
+
expect(parser.parse(string)).to eq tokens
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'empty input' do
|
11
|
+
returns '', []
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'single token' do
|
15
|
+
returns 'attribute', %w(attribute)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'simple assertions' do
|
19
|
+
returns 'weight > 0', %w(weight > 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'simple OR operator' do
|
23
|
+
returns ' true || false', %w(true || false)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'simple AND operator' do
|
27
|
+
returns 'true && false', %w(true && false)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'complex case' do
|
31
|
+
returns 'material = dřevo && (cena > 180 || cena >= 250)', %w(material = dřevo && ( cena > 180 || cena >= 250 ))
|
32
|
+
returns '(material != ocel || cena = 30 && ( šlahounů >= 2 && šlahounů <= 5 ) ',
|
33
|
+
%w{( material != ocel || cena = 30 && ( šlahounů >= 2 && šlahounů <= 5 )}
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require './lib/lamep/Expressions/operator'
|
2
|
+
require './lib/lamep/Expressions/arity1_operators'
|
3
|
+
require './lib/lamep/Expressions/arity2_operators'
|
4
|
+
require './lib/lamep/Expressions/value_expression'
|
5
|
+
require './lib/lamep/token_parser'
|
6
|
+
require './lib/lamep/shunting_yard'
|
7
|
+
require './lib/lamep/Expressions/equal'
|
8
|
+
require './lib/lamep/Expressions/greater_than'
|
9
|
+
require './lib/lamep/Expressions/less_than'
|
10
|
+
require './lib/lamep/Expressions/greater_than_equal'
|
11
|
+
require './lib/lamep/Expressions/less_than_equal'
|
12
|
+
require './lib/lamep/Expressions/and'
|
13
|
+
require './lib/lamep/Expressions/or'
|
14
|
+
require './lib/lamep/Expressions/unary_minus'
|
15
|
+
require './lib/lamep/Expressions/not_equal'
|
16
|
+
require './lib/lamep/abstract_syntax_tree_builder'
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
# rspec-expectations config goes here. You can use an alternate
|
20
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
21
|
+
# assertions if you prefer.
|
22
|
+
config.expect_with :rspec do |expectations|
|
23
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
24
|
+
# and `failure_message` of custom matchers include text for helper methods
|
25
|
+
# defined using `chain`, e.g.:
|
26
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
27
|
+
# # => "be bigger than 2 and smaller than 4"
|
28
|
+
# ...rather than:
|
29
|
+
# # => "be bigger than 2"
|
30
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
31
|
+
end
|
32
|
+
|
33
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
34
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
35
|
+
config.mock_with :rspec do |mocks|
|
36
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
37
|
+
# a real object. This is generally recommended, and will default to
|
38
|
+
# `true` in RSpec 4.
|
39
|
+
mocks.verify_partial_doubles = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# The settings below are suggested to provide a good initial experience
|
43
|
+
# with RSpec, but feel free to customize to your heart's content.
|
44
|
+
=begin
|
45
|
+
# These two settings work together to allow you to limit a spec run
|
46
|
+
# to individual examples or groups you care about by tagging them with
|
47
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
48
|
+
# get run.
|
49
|
+
config.filter_run :focus
|
50
|
+
config.run_all_when_everything_filtered = true
|
51
|
+
|
52
|
+
# Limits the available syntax to the non-monkey patched syntax that is recommended.
|
53
|
+
# For more details, see:
|
54
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
55
|
+
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
56
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
57
|
+
config.disable_monkey_patching!
|
58
|
+
|
59
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
60
|
+
# be too noisy due to issues in dependencies.
|
61
|
+
config.warnings = true
|
62
|
+
|
63
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
64
|
+
# file, and it's useful to allow more verbose output when running an
|
65
|
+
# individual spec file.
|
66
|
+
if config.files_to_run.one?
|
67
|
+
# Use the documentation formatter for detailed output,
|
68
|
+
# unless a formatter has already been configured
|
69
|
+
# (e.g. via a command-line flag).
|
70
|
+
config.default_formatter = 'doc'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Print the 10 slowest examples and example groups at the
|
74
|
+
# end of the spec run, to help surface which specs are running
|
75
|
+
# particularly slow.
|
76
|
+
config.profile_examples = 10
|
77
|
+
|
78
|
+
# Run specs in random order to surface order dependencies. If you find an
|
79
|
+
# order dependency and want to debug it, you can fix the order by providing
|
80
|
+
# the seed, which is printed after each run.
|
81
|
+
# --seed 1234
|
82
|
+
config.order = :random
|
83
|
+
|
84
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
85
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
86
|
+
# test failures related to randomization by passing the same `--seed` value
|
87
|
+
# as the one that triggered the failure.
|
88
|
+
Kernel.srand config.seed
|
89
|
+
=end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lamep
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Svoboda
|
8
|
+
- Miroslav Csonka
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: Loads logical or mathematical expression (in string) into Abstract Syntax
|
57
|
+
Tree for later evaluation
|
58
|
+
email:
|
59
|
+
- miroslav.csonka@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- lamep.gemspec
|
70
|
+
- lib/lamep/Expressions/and.rb
|
71
|
+
- lib/lamep/Expressions/arity1_operators.rb
|
72
|
+
- lib/lamep/Expressions/arity2_operators.rb
|
73
|
+
- lib/lamep/Expressions/equal.rb
|
74
|
+
- lib/lamep/Expressions/greater_than.rb
|
75
|
+
- lib/lamep/Expressions/greater_than_equal.rb
|
76
|
+
- lib/lamep/Expressions/less_than.rb
|
77
|
+
- lib/lamep/Expressions/less_than_equal.rb
|
78
|
+
- lib/lamep/Expressions/not_equal.rb
|
79
|
+
- lib/lamep/Expressions/operator.rb
|
80
|
+
- lib/lamep/Expressions/or.rb
|
81
|
+
- lib/lamep/Expressions/unary_minus.rb
|
82
|
+
- lib/lamep/Expressions/value_expression.rb
|
83
|
+
- lib/lamep/abstract_syntax_tree_builder.rb
|
84
|
+
- lib/lamep/shunting_yard.rb
|
85
|
+
- lib/lamep/token_parser.rb
|
86
|
+
- spec/lib/Expressions/operators_spec.rb
|
87
|
+
- spec/lib/abstract_syntax_tree_builder_spec.rb
|
88
|
+
- spec/lib/shunting_yard_spec.rb
|
89
|
+
- spec/lib/token_parser_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
homepage: ''
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.2.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Logical and mathematical expression parser
|
115
|
+
test_files:
|
116
|
+
- spec/lib/Expressions/operators_spec.rb
|
117
|
+
- spec/lib/abstract_syntax_tree_builder_spec.rb
|
118
|
+
- spec/lib/shunting_yard_spec.rb
|
119
|
+
- spec/lib/token_parser_spec.rb
|
120
|
+
- spec/spec_helper.rb
|