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