Shunt 0.1 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/changes.txt +8 -0
- data/lib/shunt.rb +1 -1
- data/lib/shunt/algorithm.rb +16 -14
- data/lib/shunt/{numeric.rb → arithmetic.rb} +1 -1
- data/lib/shunt/context.rb +57 -23
- data/rakefile.rb +1 -1
- data/readme.txt +11 -12
- data/spec/context_spec.rb +34 -24
- metadata +4 -3
data/changes.txt
ADDED
data/lib/shunt.rb
CHANGED
data/lib/shunt/algorithm.rb
CHANGED
@@ -1,31 +1,29 @@
|
|
1
1
|
module Shunt
|
2
2
|
|
3
|
-
def self.convert(infix_expression, context =
|
3
|
+
def self.convert(infix_expression, context = ArithmeticContext.instance)
|
4
4
|
output, stack = [], []
|
5
5
|
|
6
6
|
infix_expression.each do |token|
|
7
|
-
if context.
|
7
|
+
if context.opening?(token) || context.function?(token)
|
8
8
|
stack << token
|
9
|
-
elsif context.
|
10
|
-
until context.
|
9
|
+
elsif context.closing?(token)
|
10
|
+
until context.opening?(stack.last) || stack.empty?
|
11
11
|
output << stack.pop
|
12
12
|
end
|
13
|
-
if context.
|
13
|
+
if context.opening?(stack.last)
|
14
14
|
stack.pop
|
15
15
|
else
|
16
|
-
raise ConversionError, "missing
|
16
|
+
raise ConversionError, "missing opening symbol"
|
17
17
|
end
|
18
18
|
if !stack.empty? && context.function?(stack.last)
|
19
19
|
output << stack.pop
|
20
20
|
end
|
21
|
-
elsif context.
|
22
|
-
until context.
|
21
|
+
elsif context.seperating?(token)
|
22
|
+
until context.opening?(stack.last) || stack.empty?
|
23
23
|
output << stack.pop
|
24
24
|
end
|
25
|
-
unless context.
|
26
|
-
raise ConversionError, "missing
|
27
|
-
context.opening_bracket, context.function_seperator
|
28
|
-
]
|
25
|
+
unless context.opening?(stack.last)
|
26
|
+
raise ConversionError, "missing opening symbol or misplaced seperating symbol"
|
29
27
|
end
|
30
28
|
elsif context.operator?(token)
|
31
29
|
unless stack.empty?
|
@@ -46,8 +44,8 @@ module Shunt
|
|
46
44
|
return output + stack.reverse
|
47
45
|
end
|
48
46
|
|
49
|
-
def self.
|
50
|
-
|
47
|
+
def self.calculate(postfix_expression, context = ArithmeticContext.instance)
|
48
|
+
postfix_expression.inject([]) do |stack, token|
|
51
49
|
if context.operator?(token)
|
52
50
|
context.call_operator(token, stack)
|
53
51
|
elsif context.function?(token)
|
@@ -57,5 +55,9 @@ module Shunt
|
|
57
55
|
end
|
58
56
|
end.first
|
59
57
|
end
|
58
|
+
|
59
|
+
def self.eval(infix_expression, context = ArithmeticContext.instance)
|
60
|
+
calculate convert(infix_expression, context), context
|
61
|
+
end
|
60
62
|
|
61
63
|
end
|
data/lib/shunt/context.rb
CHANGED
@@ -3,34 +3,51 @@ module Shunt
|
|
3
3
|
|
4
4
|
include Singleton
|
5
5
|
|
6
|
+
# Methods for declaring expected expression tokens: #
|
7
|
+
|
6
8
|
class <<self
|
7
|
-
|
8
|
-
|
9
|
+
|
10
|
+
# Declare "opening bracket" tokens.
|
11
|
+
def open_with(*tokens)
|
12
|
+
@openers = tokens
|
9
13
|
end
|
10
|
-
|
11
|
-
|
14
|
+
|
15
|
+
# Declare "closing bracket" tokens.
|
16
|
+
def close_with(*tokens)
|
17
|
+
@closers = tokens
|
12
18
|
end
|
13
|
-
|
14
|
-
|
19
|
+
|
20
|
+
# Declare function argument seperator tokens.
|
21
|
+
def seperate_with(*tokens)
|
22
|
+
@seperators = tokens
|
15
23
|
end
|
24
|
+
|
25
|
+
# Declare a binary operator token.
|
16
26
|
def operator(name, equivalent = nil)
|
17
27
|
operators << name
|
18
28
|
operator_equivalents << (equivalent || name)
|
19
29
|
end
|
30
|
+
|
31
|
+
# Delegate functions to another object (given as last argument).
|
32
|
+
def delegate(*functions)
|
33
|
+
receiver = functions.pop
|
34
|
+
functions.each { |f| delegations[f] = receiver }
|
35
|
+
end
|
36
|
+
|
20
37
|
end
|
21
38
|
|
22
|
-
#
|
39
|
+
# Methods used internally by the algorithm (duck-type these): #
|
23
40
|
|
24
|
-
def
|
25
|
-
self.class.
|
41
|
+
def opening?(token)
|
42
|
+
self.class.openers.include? token
|
26
43
|
end
|
27
44
|
|
28
|
-
def
|
29
|
-
self.class.
|
45
|
+
def closing?(token)
|
46
|
+
self.class.closers.include? token
|
30
47
|
end
|
31
48
|
|
32
|
-
def
|
33
|
-
self.class.
|
49
|
+
def seperating?(token)
|
50
|
+
self.class.seperators.include? token
|
34
51
|
end
|
35
52
|
|
36
53
|
def operator?(token)
|
@@ -38,25 +55,30 @@ module Shunt
|
|
38
55
|
end
|
39
56
|
|
40
57
|
def function?(token)
|
41
|
-
token.is_a?(String)
|
58
|
+
unless token.is_a?(String) || token.is_a?(Symbol)
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
respond_to?(token) || self.class.delegations.has_key?(token)
|
42
62
|
end
|
43
63
|
|
44
|
-
def precedence(
|
45
|
-
self.class.operators.size - self.class.operators.index(
|
64
|
+
def precedence(token)
|
65
|
+
self.class.operators.size - self.class.operators.index(token)
|
46
66
|
end
|
47
67
|
|
48
|
-
def call_operator(
|
49
|
-
stack << stack.delete_at(-2).send(ruby_equivalent_of(
|
68
|
+
def call_operator(token, stack)
|
69
|
+
stack << stack.delete_at(-2).send(ruby_equivalent_of(token), stack.pop)
|
50
70
|
end
|
51
71
|
|
52
|
-
def call_function(
|
53
|
-
|
72
|
+
def call_function(token, stack)
|
73
|
+
receiver = self.class.delegations[token] || self
|
74
|
+
|
75
|
+
stack << case arity = receiver.method(token).arity
|
54
76
|
when 0
|
55
|
-
send
|
77
|
+
receiver.send token
|
56
78
|
when -1
|
57
|
-
send
|
79
|
+
receiver.send token, *stack.last!(stack.size)
|
58
80
|
else
|
59
|
-
send
|
81
|
+
receiver.send token, *stack.last!(arity)
|
60
82
|
end
|
61
83
|
end
|
62
84
|
|
@@ -64,12 +86,24 @@ module Shunt
|
|
64
86
|
private
|
65
87
|
|
66
88
|
class <<self
|
89
|
+
def openers
|
90
|
+
@openers ||= ['(']
|
91
|
+
end
|
92
|
+
def closers
|
93
|
+
@closers ||= [')']
|
94
|
+
end
|
95
|
+
def seperators
|
96
|
+
@seperators ||= [',']
|
97
|
+
end
|
67
98
|
def operators
|
68
99
|
@operators ||= []
|
69
100
|
end
|
70
101
|
def operator_equivalents
|
71
102
|
@operator_equivalents ||= []
|
72
103
|
end
|
104
|
+
def delegations
|
105
|
+
@delegations ||= {}
|
106
|
+
end
|
73
107
|
end
|
74
108
|
|
75
109
|
def ruby_equivalent_of(operator_token)
|
data/rakefile.rb
CHANGED
@@ -9,7 +9,7 @@ require 'spec/rake/spectask'
|
|
9
9
|
|
10
10
|
gemspec = Gem::Specification.new do |s|
|
11
11
|
s.name = 'Shunt'
|
12
|
-
s.version = '0.
|
12
|
+
s.version = '0.2'
|
13
13
|
s.summary = "A Ruby implementation of Dijkstra's Shunting Yard Algorithm"
|
14
14
|
s.files = FileList['{lib,spec}/**/*.rb', '*.txt', 'rakefile.rb']
|
15
15
|
s.require_path = 'lib'
|
data/readme.txt
CHANGED
@@ -23,28 +23,23 @@ Remember to require 'mathn' if you want precise answers.
|
|
23
23
|
|
24
24
|
== Contexts
|
25
25
|
|
26
|
-
The default context (used in the examples above) is
|
27
|
-
array of Numeric objects, brackets/parentheses
|
28
|
-
(as Strings), and function symbols (as Strings or Symbols).
|
26
|
+
The default context (used in the examples above) is arithmetic i.e. it expects an
|
27
|
+
array of Numeric objects, brackets/parentheses, function symbols and binary operators.
|
29
28
|
|
30
29
|
Other contexts can be defined by subclassing Shunt::Context, e.g.
|
31
30
|
|
32
31
|
class MyContext < Shunt::Context
|
33
32
|
|
34
|
-
# Declare
|
35
|
-
# with a natural equivalent Ruby method:
|
33
|
+
# Declare binary operators:
|
36
34
|
|
37
|
-
operator '&'
|
35
|
+
operator '&' # assumes equivalent method in Ruby
|
38
36
|
|
39
|
-
#
|
40
|
-
# with :| as the equivalent Ruby method:
|
37
|
+
operator 'or', :| # explicitly specifies the equivalent Ruby method
|
41
38
|
|
42
|
-
operator 'or', :|
|
43
|
-
|
44
39
|
# Operators are given precedence in the order that they are declared,
|
45
40
|
# so in this example '&' has higher precedence than 'or'.
|
46
41
|
|
47
|
-
#
|
42
|
+
# Declare functions as ordinary Ruby methods:
|
48
43
|
|
49
44
|
def foo; end
|
50
45
|
|
@@ -52,9 +47,13 @@ Other contexts can be defined by subclassing Shunt::Context, e.g.
|
|
52
47
|
|
53
48
|
def blah(*roles); end
|
54
49
|
|
50
|
+
# Delegate functions to other objects:
|
51
|
+
|
52
|
+
delegate 'sin', 'cos', 'tan', Math
|
53
|
+
|
55
54
|
end
|
56
55
|
|
57
|
-
This can then be used to evaluate
|
56
|
+
This context can then be used to evaluate appropriate expressions, e.g.
|
58
57
|
|
59
58
|
permissions = Shunt.eval([User,'&','(',Admin,'or',Moderator,')'], MyContext.instance)
|
60
59
|
|
data/spec/context_spec.rb
CHANGED
@@ -9,64 +9,74 @@ context 'A (Shunt) Context' do
|
|
9
9
|
@instance = @class.instance
|
10
10
|
end
|
11
11
|
|
12
|
-
specify 'should allow
|
13
|
-
@instance.
|
14
|
-
lambda { @class.
|
15
|
-
@instance.
|
12
|
+
specify 'should allow alternative function seperators' do
|
13
|
+
@instance.should_be_seperating(',')
|
14
|
+
lambda { @class.seperate_with ';' }.should_not_raise
|
15
|
+
@instance.should_not_be_seperating(',')
|
16
|
+
@instance.should_be_seperating(';')
|
16
17
|
end
|
17
18
|
|
18
|
-
specify 'should allow
|
19
|
-
@instance.
|
20
|
-
lambda { @class.
|
21
|
-
@instance.
|
19
|
+
specify 'should allow alternative opening symbols' do
|
20
|
+
@instance.should_be_opening('(')
|
21
|
+
lambda { @class.open_with '[' }.should_not_raise
|
22
|
+
@instance.should_not_be_opening('(')
|
23
|
+
@instance.should_be_opening('[')
|
22
24
|
end
|
23
25
|
|
24
|
-
specify 'should allow
|
25
|
-
@instance.
|
26
|
-
lambda { @class.
|
27
|
-
@instance.
|
26
|
+
specify 'should allow alternative closing symbols' do
|
27
|
+
@instance.should_be_closing(')')
|
28
|
+
lambda { @class.close_with ']' }.should_not_raise
|
29
|
+
@instance.should_not_be_closing(')')
|
30
|
+
@instance.should_be_closing(']')
|
28
31
|
end
|
29
32
|
|
30
33
|
specify 'should allow operators that are also ruby methods' do
|
31
|
-
@instance.
|
34
|
+
@instance.should_not_be_operator('+')
|
32
35
|
lambda { @class.operator '+' }.should_not_raise
|
33
|
-
@instance.
|
36
|
+
@instance.should_be_operator('+')
|
34
37
|
@instance.call_operator('+', [1, 2]).should_equal [3]
|
35
38
|
end
|
36
39
|
|
37
40
|
specify 'should allow operators that are aliases of ruby methods' do
|
38
|
-
@instance.
|
41
|
+
@instance.should_not_be_operator('plus')
|
39
42
|
lambda { @class.operator 'plus', :+ }.should_not_raise
|
40
|
-
@instance.
|
43
|
+
@instance.should_be_operator('plus')
|
41
44
|
@instance.call_operator('plus', [1, 2]).should_equal [3]
|
42
45
|
end
|
43
46
|
|
44
47
|
specify 'should allow definition and call of a function with arity 0' do
|
45
|
-
@instance.
|
48
|
+
@instance.should_not_be_function('ans')
|
46
49
|
lambda { @class.class_eval { define_method(:ans) { 42 } } }.should_not_raise
|
50
|
+
@instance.should_be_function('ans')
|
47
51
|
@instance.call_function('ans', []).should_equal [42]
|
48
|
-
@instance.function?('ans').should_be true
|
49
52
|
end
|
50
53
|
|
51
54
|
specify 'should allow definition and call of a function with arity 1' do
|
52
|
-
@instance.
|
55
|
+
@instance.should_not_be_function('inc')
|
53
56
|
lambda { @class.class_eval { define_method(:inc) { |x| x + 1 } } }.should_not_raise
|
57
|
+
@instance.should_be_function('inc')
|
54
58
|
@instance.call_function('inc', [0]).should_equal [1]
|
55
|
-
@instance.function?('inc').should_be true
|
56
59
|
end
|
57
60
|
|
58
61
|
specify 'should allow definition and call of a function with arity 2' do
|
59
|
-
@instance.
|
62
|
+
@instance.should_not_be_function('add')
|
60
63
|
lambda { @class.class_eval { define_method(:add) { |one, two| one + two } } }.should_not_raise
|
64
|
+
@instance.should_be_function('add')
|
61
65
|
@instance.call_function('add', [1, 2]).should_equal [3]
|
62
|
-
@instance.function?('add').should_be true
|
63
66
|
end
|
64
67
|
|
65
68
|
specify 'should allow definition and call of a function with arity -1' do
|
66
|
-
@instance.
|
69
|
+
@instance.should_not_be_function('min')
|
67
70
|
lambda { @class.class_eval { define_method(:min) { |*args| args.min } } }.should_not_raise
|
71
|
+
@instance.should_be_function('min')
|
68
72
|
@instance.call_function('min', [10, 1, 2]).should_equal [1]
|
69
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
specify 'should allow delegation of functions to other objects' do
|
76
|
+
@instance.should_not_be_function('sin')
|
77
|
+
lambda { @class.class_eval { delegate 'sin', Math } }.should_not_raise
|
78
|
+
@instance.should_be_function('sin')
|
79
|
+
@instance.call_function('sin', [Math::PI / 2]).should_equal [1]
|
70
80
|
end
|
71
81
|
|
72
82
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: Shunt
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "0.
|
7
|
-
date: 2006-
|
6
|
+
version: "0.2"
|
7
|
+
date: 2006-06-11 00:00:00 +01:00
|
8
8
|
summary: A Ruby implementation of Dijkstra's Shunting Yard Algorithm
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -30,10 +30,11 @@ authors:
|
|
30
30
|
files:
|
31
31
|
- lib/shunt.rb
|
32
32
|
- lib/shunt/algorithm.rb
|
33
|
+
- lib/shunt/arithmetic.rb
|
33
34
|
- lib/shunt/context.rb
|
34
|
-
- lib/shunt/numeric.rb
|
35
35
|
- spec/algorithm_spec.rb
|
36
36
|
- spec/context_spec.rb
|
37
|
+
- changes.txt
|
37
38
|
- readme.txt
|
38
39
|
- rakefile.rb
|
39
40
|
test_files: []
|