shunting_yard 0.1.0
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 +14 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +6 -0
- data/lib/shunting_yard.rb +10 -0
- data/lib/shunting_yard/errors.rb +45 -0
- data/lib/shunting_yard/interpreter.rb +132 -0
- data/lib/shunting_yard/lexer.rb +58 -0
- data/lib/shunting_yard/parser.rb +28 -0
- data/lib/shunting_yard/reverse_polish_notation.rb +7 -0
- data/lib/shunting_yard/structs.rb +10 -0
- data/lib/shunting_yard/version.rb +3 -0
- data/shunting_yard.gemspec +34 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 825936faa9f1c872f460b25d5e988e418abe6b98a2bd167af2c83fe13b25bf03
|
4
|
+
data.tar.gz: 2c91cb879600cc2dc260475ae03d3f1d763d0173f77a9060f43f1dd7609a9029
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4484b313d0106bb9279290fab1f86413701a673d0e4d2f43521f8f1cb95bb5fb62feb773f1d6e2abc921fc0711939ebca3ae8b9e6f2225c0cbdfe4d9114c504
|
7
|
+
data.tar.gz: ad729d6a29a484d3c3c77ba36e9080a3bf90c7f64d4ef8c021112d7817efd3732f0ce7d264431187ea73528b5f035d3820962d3f50867575043febea69fc7743
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
shunting_yard (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (11.1.3)
|
10
|
+
coderay (1.1.2)
|
11
|
+
debase (0.2.4.1)
|
12
|
+
debase-ruby_core_source (>= 0.10.2)
|
13
|
+
debase-ruby_core_source (0.10.9)
|
14
|
+
diff-lcs (1.3)
|
15
|
+
method_source (1.0.0)
|
16
|
+
pry (0.13.1)
|
17
|
+
coderay (~> 1.1)
|
18
|
+
method_source (~> 1.0)
|
19
|
+
pry-byebug (3.9.0)
|
20
|
+
byebug (~> 11.0)
|
21
|
+
pry (~> 0.13.0)
|
22
|
+
rake (12.3.2)
|
23
|
+
rspec (3.9.0)
|
24
|
+
rspec-core (~> 3.9.0)
|
25
|
+
rspec-expectations (~> 3.9.0)
|
26
|
+
rspec-mocks (~> 3.9.0)
|
27
|
+
rspec-core (3.9.2)
|
28
|
+
rspec-support (~> 3.9.3)
|
29
|
+
rspec-expectations (3.9.2)
|
30
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
+
rspec-support (~> 3.9.0)
|
32
|
+
rspec-mocks (3.9.1)
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
+
rspec-support (~> 3.9.0)
|
35
|
+
rspec-support (3.9.3)
|
36
|
+
ruby-debug-ide (0.7.2)
|
37
|
+
rake (>= 0.8.1)
|
38
|
+
|
39
|
+
PLATFORMS
|
40
|
+
ruby
|
41
|
+
|
42
|
+
DEPENDENCIES
|
43
|
+
debase
|
44
|
+
pry
|
45
|
+
pry-byebug
|
46
|
+
rake (~> 12.0)
|
47
|
+
rspec (~> 3.0)
|
48
|
+
ruby-debug-ide
|
49
|
+
shunting_yard!
|
50
|
+
|
51
|
+
BUNDLED WITH
|
52
|
+
2.1.2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Artem Rashev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# ShuntingYard
|
2
|
+
|
3
|
+
## TL;DR Usage
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
parser = ShuntingYard::Parser.new
|
7
|
+
|
8
|
+
parser.add_pattern :space, /\s+/, -> (_) { nil }
|
9
|
+
parser.add_pattern :argument_separator, /\,/
|
10
|
+
parser.add_pattern :operator, /[\+\-\*\/\^]/
|
11
|
+
parser.add_pattern :parenthesis, /[\(\)]/
|
12
|
+
parser.add_pattern :function, /(?:min|max)/
|
13
|
+
parser.add_pattern :operand, /d+/, -> (lexeme) { Integer(lexeme) }
|
14
|
+
|
15
|
+
parser.add_operator "+", 0, :left, -> (left, right) { left + right }
|
16
|
+
parser.add_operator "-", 0, :left, -> (left, right) { left - right }
|
17
|
+
parser.add_operator "*", 1, :left, -> (left, right) { left * right }
|
18
|
+
parser.add_operator "/", 1, :left, -> (left, right) { left / right }
|
19
|
+
parser.add_operator "^", 2, :right, -> (left, right) { left ** right }
|
20
|
+
|
21
|
+
parser.add_function "min", -> (left, right) { [left, right].min }
|
22
|
+
parser.add_function "max", -> (left, right) { [left, right].max }
|
23
|
+
|
24
|
+
input = "min(max(3, 4 / 2) * 2 ^ 3, 25)"
|
25
|
+
|
26
|
+
parser.to_rpn(input).to_s #=> 3 4 2 / max 2 3 ^ * 25 min
|
27
|
+
parser.evaluate(input) #=> 24
|
28
|
+
```
|
29
|
+
|
30
|
+
## Lexer
|
31
|
+
|
32
|
+
`ShuntingYard::Lexer` is responsible for splitting source string into tokens.
|
33
|
+
To recognize each possible token it needs to know corresponding patterns represented as regular expressions.
|
34
|
+
|
35
|
+
If substring is matched to multiple patterns, **the longest match** wil be used.
|
36
|
+
|
37
|
+
After matching a token, its lexeme is evaluated with provided function. If function is not provided - it returns the lexeme itself.
|
38
|
+
|
39
|
+
Tokens values evaluated to `nil` will not be included into output sequence.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
lexer = ShuntingYard::Lexer.new
|
43
|
+
|
44
|
+
lexer.add_pattern :operator, /\+/
|
45
|
+
lexer.add_pattern :space, /\s+/, -> (_) { nil }
|
46
|
+
lexer.add_pattern :operand, /\d+/, -> (lexeme) { Integer(lexeme) }
|
47
|
+
|
48
|
+
puts lexer.tokenize("3 + 5").inspect
|
49
|
+
```
|
50
|
+
|
51
|
+
```
|
52
|
+
[
|
53
|
+
#<struct ShuntingYard::Token name=:operand, lexeme="3", value=3>,
|
54
|
+
#<struct ShuntingYard::Token name=:operator, lexeme="+", value="+">,
|
55
|
+
#<struct ShuntingYard::Token name=:operand, lexeme="5", value=5>
|
56
|
+
]
|
57
|
+
```
|
58
|
+
|
59
|
+
First argument in `#add_pattern` is a token name. It can have any value since `Lexer` doesn't make assumptions about names.
|
60
|
+
|
61
|
+
## Interpterer
|
62
|
+
|
63
|
+
Once we have token list, it needs to be converted to [Revese Polish Notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation) before evalutation and here Shunting Yard algorithm comes in.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
...
|
67
|
+
|
68
|
+
interpreter = ShuntingYard::Interpreter.new
|
69
|
+
interpreter.add_operator "+", 0, :left, -> (left, right) { left + right }
|
70
|
+
|
71
|
+
tokens = lexer.tokenize("3 + 5")
|
72
|
+
puts interpreter.to_rpn(tokens).inspect
|
73
|
+
```
|
74
|
+
|
75
|
+
```
|
76
|
+
[
|
77
|
+
#<struct ShuntingYard::Operand value=3>,
|
78
|
+
#<struct ShuntingYard::Operand value=5>,
|
79
|
+
#<struct ShuntingYard::Operator value="+", precedence=0, associativity=:left, evaluator=#<Proc:...>>
|
80
|
+
]
|
81
|
+
```
|
82
|
+
|
83
|
+
Interpreter defines **strict names list that are accepted in tokens**:
|
84
|
+
|
85
|
+
* `:operand` - arbitrary value, passed as argument operators and functions
|
86
|
+
* `:parenthesis` - currently interpreter accepts only "(" and ")" parentheses
|
87
|
+
* `:operator` - token value must match to one of registered in interpreter operators
|
88
|
+
* `:function` - token value must match to one of registered in interpreter functions
|
89
|
+
* `:argument_separator` - pattern that defines argument separation in functions (usually comma)
|
90
|
+
|
91
|
+
All other token types will not be recognized and interpreter throws `ShuntingYard::UnknownTokenError`.
|
92
|
+
|
93
|
+
### Registering functions
|
94
|
+
|
95
|
+
`#add_function(name, evaluator)`
|
96
|
+
|
97
|
+
* `name` - function name that must match to corresponding token value
|
98
|
+
* `evaluator` - a function that accepts fixed number of arguments and returns single value
|
99
|
+
|
100
|
+
### Registering operations
|
101
|
+
|
102
|
+
`#add_operator(name, precedence, associativity, evaluator)`
|
103
|
+
|
104
|
+
* `name` - function name that must match to corresponding token value
|
105
|
+
* `precedence` - operator precedence, can be any integer value
|
106
|
+
* `associativity` - [operator associativity](https://en.wikipedia.org/wiki/Operator_associativity), can be either `:left` or `:right`
|
107
|
+
* `evaluator` - a function that accepts fixed number of arguments and returns single value
|
108
|
+
|
109
|
+
|
110
|
+
### Evaluators
|
111
|
+
|
112
|
+
Evaluators for operators and functions can be defined as `proc` or `lambda` with at least one argument.
|
113
|
+
**Default arguments are not allowed** because interpreter uses `#arity` method to get number of operands required by function / operator.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# Expected format
|
117
|
+
interpreter.add_operator "%", 0, :left, -> (left, right) { left % right }
|
118
|
+
interpreter.add_operator "~", 0, :left, proc { |left, right| left % right }
|
119
|
+
|
120
|
+
# Will not work
|
121
|
+
interpreter.add_operator "%", 0, :left, -> (left, right = 5) { left % right }
|
122
|
+
interpreter.add_function "max", -> (*args) { args.max }
|
123
|
+
```
|
124
|
+
|
125
|
+
## Parser
|
126
|
+
|
127
|
+
`ShuntingYard::Parser` is a proxy class for Lexer and Interpreter.
|
128
|
+
|
129
|
+
When you need to recognize and evaluate an expression in one go, Parser object is a single entry point for all actions.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
parser = ShuntingYard::Parser.new
|
133
|
+
|
134
|
+
# Add patterns to parser's lexer
|
135
|
+
parser.add_pattern :space, /\s+/, -> (_) { nil }
|
136
|
+
parser.add_pattern :operator, /[\+\-]/
|
137
|
+
parser.add_pattern :operand, /d+/, -> (lexeme) { Integer(lexeme) }
|
138
|
+
|
139
|
+
# Add operators to parser's interpreter
|
140
|
+
parser.add_operator "+", 0, :left, -> (left, right) { left + right }
|
141
|
+
parser.add_operator "-", 0, :left, -> (left, right) { left - right }
|
142
|
+
|
143
|
+
input = "5 + 3 - 2"
|
144
|
+
|
145
|
+
# Tokenize, convert to RPN and evaluate the expression
|
146
|
+
parser.evaluate(input) #=> 6
|
147
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "shunting_yard/structs"
|
2
|
+
require "shunting_yard/errors"
|
3
|
+
require "shunting_yard/reverse_polish_notation"
|
4
|
+
require "shunting_yard/interpreter"
|
5
|
+
require "shunting_yard/lexer"
|
6
|
+
require "shunting_yard/parser"
|
7
|
+
require "shunting_yard/version"
|
8
|
+
|
9
|
+
module ShuntingYard
|
10
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ShuntingYard
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class InvalidArgumentsCountError < Error
|
5
|
+
def initialize
|
6
|
+
super "Invalid arguments count passed to one of functions or operators"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MismatchedParenthesesError < Error
|
11
|
+
def initialize
|
12
|
+
super "Mismatched parentheses"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UnknownTokenError < Error
|
17
|
+
def initialize(token, position)
|
18
|
+
super "Unknown token '#{token}' at position #{position}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownOperatorError < Error
|
23
|
+
def initialize(token)
|
24
|
+
super "Unknown operator '#{token.lexeme}'"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class UnknownFunctionError < Error
|
29
|
+
def initialize(token)
|
30
|
+
super "Unknown function '#{token}'"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class UnknownParenthesisError < Error
|
35
|
+
def initialize(token)
|
36
|
+
super "Token '#{token}' is not a parenthesis"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class UnknownTokenTypeError < Error
|
41
|
+
def initialize(name)
|
42
|
+
super "Token '#{name}' is not defined"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module ShuntingYard
|
2
|
+
class Interpreter
|
3
|
+
attr_accessor :functions
|
4
|
+
attr_accessor :operators
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@functions = []
|
8
|
+
@operators = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_function(*args)
|
12
|
+
functions << Function.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_operator(*args)
|
16
|
+
operators << Operator.new(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_rpn(source_tokens)
|
20
|
+
tokens = source_tokens.dup
|
21
|
+
output = ReversePolishNotation.new
|
22
|
+
op_stack = []
|
23
|
+
|
24
|
+
while tokens.any?
|
25
|
+
current = match_token(tokens.shift)
|
26
|
+
|
27
|
+
case current
|
28
|
+
when ArgumentSeparator
|
29
|
+
while op_stack.any? &&
|
30
|
+
op_stack.last.class != Parenthesis
|
31
|
+
output << op_stack.pop
|
32
|
+
end
|
33
|
+
when Function
|
34
|
+
op_stack << current
|
35
|
+
when Parenthesis
|
36
|
+
case current.side
|
37
|
+
when :left
|
38
|
+
op_stack << current
|
39
|
+
when :right
|
40
|
+
while op_stack.last.class != Parenthesis
|
41
|
+
raise MismatchedParenthesesError if op_stack.empty?
|
42
|
+
|
43
|
+
output << op_stack.pop
|
44
|
+
end
|
45
|
+
|
46
|
+
op_stack.pop
|
47
|
+
end
|
48
|
+
when Operand
|
49
|
+
output << current
|
50
|
+
when Operator
|
51
|
+
while op_stack.any? &&
|
52
|
+
op_stack.last.class != Parenthesis &&
|
53
|
+
(op_stack.last.class == Function ||
|
54
|
+
op_stack.last.precedence > current.precedence ||
|
55
|
+
op_stack.last.precedence == current.precedence && current.associativity == :left)
|
56
|
+
output << op_stack.pop
|
57
|
+
end
|
58
|
+
|
59
|
+
op_stack << current
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
while op_stack.any?
|
64
|
+
current = op_stack.pop
|
65
|
+
raise MismatchedParenthesesError if current.class == Parenthesis
|
66
|
+
|
67
|
+
output << current
|
68
|
+
end
|
69
|
+
|
70
|
+
output
|
71
|
+
end
|
72
|
+
|
73
|
+
def evaluate(rpn_tokens)
|
74
|
+
rpn = rpn_tokens.dup
|
75
|
+
stack = []
|
76
|
+
|
77
|
+
while rpn.any?
|
78
|
+
current = rpn.shift
|
79
|
+
|
80
|
+
case current
|
81
|
+
when Function, Operator
|
82
|
+
arity = current.evaluator.arity
|
83
|
+
raise InvalidArgumentsCountError if stack.size < arity
|
84
|
+
|
85
|
+
operands = stack.pop(arity)
|
86
|
+
stack << current.evaluator.(*operands)
|
87
|
+
when Operand
|
88
|
+
stack << current.value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
raise InvalidArgumentsCountError if stack.size > 1
|
93
|
+
|
94
|
+
stack[0]
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def match_token(token)
|
100
|
+
matched =
|
101
|
+
case token.name
|
102
|
+
when :argument_separator
|
103
|
+
ArgumentSeparator.new(token.value)
|
104
|
+
when :function
|
105
|
+
function = functions.find { |f| f.value == token.value }
|
106
|
+
raise UnknownFunctionError, token.lexeme unless function
|
107
|
+
|
108
|
+
function
|
109
|
+
when :parenthesis
|
110
|
+
case token.value
|
111
|
+
when "("
|
112
|
+
Parenthesis.new(:left)
|
113
|
+
when ")"
|
114
|
+
Parenthesis.new(:right)
|
115
|
+
else
|
116
|
+
raise UnknownParenthesisError, token.lexeme
|
117
|
+
end
|
118
|
+
when :operand
|
119
|
+
Operand.new(token.value)
|
120
|
+
when :operator
|
121
|
+
operator = operators.find { |op| binding.pry if op.kind_of?(Array); op.value == token.value }
|
122
|
+
raise UnknownOperatorError, token unless operator
|
123
|
+
|
124
|
+
operator
|
125
|
+
else
|
126
|
+
raise UnknownTokenTypeError, token.name
|
127
|
+
end
|
128
|
+
|
129
|
+
matched
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "strscan"
|
2
|
+
|
3
|
+
module ShuntingYard
|
4
|
+
class Lexer
|
5
|
+
SPACE_OR_EOL = /(\s|$)/.freeze
|
6
|
+
|
7
|
+
attr_accessor :patterns
|
8
|
+
attr_accessor :separator_pattern
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@patterns = []
|
12
|
+
@separator_pattern = SPACE_OR_EOL
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_pattern(name, regex, evaluator = -> (lexeme) { lexeme })
|
16
|
+
@patterns << [name, regex, evaluator]
|
17
|
+
end
|
18
|
+
|
19
|
+
def tokenize(input)
|
20
|
+
sc = StringScanner.new(input)
|
21
|
+
matches = []
|
22
|
+
|
23
|
+
until sc.eos?
|
24
|
+
match = nil
|
25
|
+
last_match = nil
|
26
|
+
longest_match_size = 0
|
27
|
+
|
28
|
+
@patterns.each do |name, regex, evaluator|
|
29
|
+
match = sc.check(regex)
|
30
|
+
next if match.nil?
|
31
|
+
|
32
|
+
longest_match_size = [longest_match_size, match.size].max
|
33
|
+
|
34
|
+
value = evaluator.(match)
|
35
|
+
next if value.nil?
|
36
|
+
|
37
|
+
last_match = [name, match, value] if last_match.nil? || last_match[1].size < match.size
|
38
|
+
end
|
39
|
+
|
40
|
+
if longest_match_size == 0
|
41
|
+
unknown_token = sc.check_until(separator_pattern).sub(separator_pattern, "")
|
42
|
+
raise UnknownTokenError.new(unknown_token, sc.pos + 1)
|
43
|
+
end
|
44
|
+
|
45
|
+
sc.pos += longest_match_size
|
46
|
+
matches << build_token(last_match) unless last_match.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
matches
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_token(args)
|
55
|
+
Token.new(*args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module ShuntingYard
|
4
|
+
class Parser
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :lexer
|
8
|
+
attr_accessor :interpreter
|
9
|
+
|
10
|
+
def initialize(lexer: nil, interpreter: nil)
|
11
|
+
@lexer = lexer || Lexer.new
|
12
|
+
@interpreter = interpreter || Interpreter.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :@lexer, :add_pattern, :separator_pattern, :separator_pattern=, :tokenize
|
16
|
+
def_delegators :@interpreter, :add_function, :add_operator
|
17
|
+
|
18
|
+
def evaluate(input)
|
19
|
+
rpn_tokens = to_rpn(input)
|
20
|
+
interpreter.evaluate(rpn_tokens)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_rpn(input)
|
24
|
+
tokens = tokenize(input)
|
25
|
+
interpreter.to_rpn(tokens)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ShuntingYard
|
2
|
+
Token = Struct.new(:name, :lexeme, :value)
|
3
|
+
|
4
|
+
# Matched tokens
|
5
|
+
ArgumentSeparator = Struct.new(:value)
|
6
|
+
Function = Struct.new(:value, :evaluator)
|
7
|
+
Operand = Struct.new(:value)
|
8
|
+
Operator = Struct.new(:value, :precedence, :associativity, :evaluator)
|
9
|
+
Parenthesis = Struct.new(:side)
|
10
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'lib/shunting_yard/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "shunting_yard"
|
5
|
+
spec.version = ShuntingYard::VERSION
|
6
|
+
spec.authors = ["Donkey Kong"]
|
7
|
+
spec.email = ["584951-highaf@users.noreply.gitlab.com"]
|
8
|
+
|
9
|
+
spec.summary = "ShuntingYard algorithm implementation"
|
10
|
+
spec.description = ""
|
11
|
+
spec.homepage = "https://gitlab.com/hodlhodl-public/shunting_yard"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://gitlab.com/hodlhodl-public/shunting_yard"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "debase"
|
31
|
+
spec.add_development_dependency "pry"
|
32
|
+
spec.add_development_dependency "pry-byebug"
|
33
|
+
spec.add_development_dependency "ruby-debug-ide"
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shunting_yard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Donkey Kong
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: debase
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ruby-debug-ide
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: ''
|
70
|
+
email:
|
71
|
+
- 584951-highaf@users.noreply.gitlab.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/shunting_yard.rb
|
84
|
+
- lib/shunting_yard/errors.rb
|
85
|
+
- lib/shunting_yard/interpreter.rb
|
86
|
+
- lib/shunting_yard/lexer.rb
|
87
|
+
- lib/shunting_yard/parser.rb
|
88
|
+
- lib/shunting_yard/reverse_polish_notation.rb
|
89
|
+
- lib/shunting_yard/structs.rb
|
90
|
+
- lib/shunting_yard/version.rb
|
91
|
+
- shunting_yard.gemspec
|
92
|
+
homepage: https://gitlab.com/hodlhodl-public/shunting_yard
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata:
|
96
|
+
allowed_push_host: https://rubygems.org
|
97
|
+
homepage_uri: https://gitlab.com/hodlhodl-public/shunting_yard
|
98
|
+
source_code_uri: https://gitlab.com/hodlhodl-public/shunting_yard
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: 2.3.0
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubygems_version: 3.1.2
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: ShuntingYard algorithm implementation
|
118
|
+
test_files: []
|