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 ADDED
@@ -0,0 +1,8 @@
1
+ 0.2: 11th June 2006
2
+ - Renamed NumericContext to ArithmeticContext
3
+ - Added Shunt.calculate
4
+ - Added Shunt::Context.delegate
5
+ - Changed the Class-level API e.g. renaming opening_bracket to open_with
6
+
7
+ 0.1: 29th May 2006
8
+ - Initial release
data/lib/shunt.rb CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  require 'singleton'
23
23
  require 'shunt/context'
24
- require 'shunt/numeric'
24
+ require 'shunt/arithmetic'
25
25
  require 'shunt/algorithm'
26
26
 
27
27
  class Array
@@ -1,31 +1,29 @@
1
1
  module Shunt
2
2
 
3
- def self.convert(infix_expression, context = NumericContext.instance)
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.opening_bracket == token || context.function?(token)
7
+ if context.opening?(token) || context.function?(token)
8
8
  stack << token
9
- elsif context.closing_bracket == token
10
- until context.opening_bracket == stack.last || stack.empty?
9
+ elsif context.closing?(token)
10
+ until context.opening?(stack.last) || stack.empty?
11
11
  output << stack.pop
12
12
  end
13
- if context.opening_bracket == stack.last
13
+ if context.opening?(stack.last)
14
14
  stack.pop
15
15
  else
16
- raise ConversionError, "missing %p" % context.opening_bracket
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.function_seperator == token
22
- until context.opening_bracket == stack.last || stack.empty?
21
+ elsif context.seperating?(token)
22
+ until context.opening?(stack.last) || stack.empty?
23
23
  output << stack.pop
24
24
  end
25
- unless context.opening_bracket == stack.last
26
- raise ConversionError, "missing %p or misplaced %p" % [
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.eval(infix_expression, context = NumericContext.instance)
50
- convert(infix_expression, context).inject([]) do |stack, token|
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
@@ -1,5 +1,5 @@
1
1
  module Shunt
2
- class NumericContext < Context
2
+ class ArithmeticContext < Context
3
3
 
4
4
  operator '^', :**
5
5
 
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
- def opening_bracket(token)
8
- @opening_bracket = token
9
+
10
+ # Declare "opening bracket" tokens.
11
+ def open_with(*tokens)
12
+ @openers = tokens
9
13
  end
10
- def closing_bracket(token)
11
- @closing_bracket = token
14
+
15
+ # Declare "closing bracket" tokens.
16
+ def close_with(*tokens)
17
+ @closers = tokens
12
18
  end
13
- def function_seperator(token)
14
- @function_seperator = token
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
- # Duck-type from here to private
39
+ # Methods used internally by the algorithm (duck-type these): #
23
40
 
24
- def opening_bracket
25
- self.class.instance_variable_get('@opening_bracket') || '('
41
+ def opening?(token)
42
+ self.class.openers.include? token
26
43
  end
27
44
 
28
- def closing_bracket
29
- self.class.instance_variable_get('@closing_bracket') || ')'
45
+ def closing?(token)
46
+ self.class.closers.include? token
30
47
  end
31
48
 
32
- def function_seperator
33
- self.class.instance_variable_get('@function_seperator') || ','
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) && respond_to?(token)
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(operator_token)
45
- self.class.operators.size - self.class.operators.index(operator_token)
64
+ def precedence(token)
65
+ self.class.operators.size - self.class.operators.index(token)
46
66
  end
47
67
 
48
- def call_operator(operator_token, stack)
49
- stack << stack.delete_at(-2).send(ruby_equivalent_of(operator_token), stack.pop)
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(function_token, stack)
53
- stack << case arity = method(function_token).arity
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 function_token
77
+ receiver.send token
56
78
  when -1
57
- send function_token, *stack.last!(stack.size)
79
+ receiver.send token, *stack.last!(stack.size)
58
80
  else
59
- send function_token, *stack.last!(arity)
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.1'
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 numeric i.e. it expects an
27
- array of Numeric objects, brackets/parentheses (as Strings), binary operators
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 a binary operator, '&',
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
- # Declare a binary operator, 'or',
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
- # Functions can be defined, as ordinary Ruby methods:
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 an appropriate expression, e.g.
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 overriding of function_seperator' do
13
- @instance.function_seperator.should_equal ','
14
- lambda { @class.function_seperator ';' }.should_not_raise
15
- @instance.function_seperator.should_equal ';'
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 overriding of opening_bracket' do
19
- @instance.opening_bracket.should_equal '('
20
- lambda { @class.opening_bracket '[' }.should_not_raise
21
- @instance.opening_bracket.should_equal '['
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 overriding of closing_bracket' do
25
- @instance.closing_bracket.should_equal ')'
26
- lambda { @class.closing_bracket ']' }.should_not_raise
27
- @instance.closing_bracket.should_equal ']'
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.operator?('+').should_be false
34
+ @instance.should_not_be_operator('+')
32
35
  lambda { @class.operator '+' }.should_not_raise
33
- @instance.operator?('+').should_be true
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.operator?('plus').should_be false
41
+ @instance.should_not_be_operator('plus')
39
42
  lambda { @class.operator 'plus', :+ }.should_not_raise
40
- @instance.operator?('plus').should_be true
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.function?('ans').should_be false
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.function?('inc').should_be false
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.function?('add').should_be false
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.function?('min').should_be false
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
- @instance.function?('min').should_be true
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.1"
7
- date: 2006-05-29 00:00:00 +01:00
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: []