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 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: []