lamep 0.1
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 +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
|