shunting_yard 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|