constraint 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.TXT +115 -32
- data/VERSION.TXT +1 -1
- data/examples/in_range.rb +72 -0
- data/examples/primes.rb +97 -0
- data/examples/queens.rb +108 -0
- data/examples/queens_ca.rb +83 -0
- data/examples/queens_cb.rb +82 -0
- data/examples/queens_s.rb +85 -0
- data/examples/run_queens.sh +5 -0
- data/lib/constraint.rb +294 -110
- data/test/tc_contraint.rb +88 -42
- metadata +9 -2
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# queens.rb
|
3
|
+
# @Author: Thomas Link (samul@web.de)
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 11-Jun-2005.
|
6
|
+
# @Last Change: 12-Jun-2005.
|
7
|
+
# @Revision: 0.290
|
8
|
+
|
9
|
+
require 'constraint'
|
10
|
+
|
11
|
+
QUEEN_MIN = 1
|
12
|
+
QUEEN_MAX = 5
|
13
|
+
MAX_BOARD_N = (QUEEN_MAX ** QUEEN_MAX) - 1
|
14
|
+
|
15
|
+
class QueensBoard < Constraint::CArray
|
16
|
+
attr_reader :curr_board_n
|
17
|
+
|
18
|
+
def new_constrained
|
19
|
+
@boards = []
|
20
|
+
@masks = []
|
21
|
+
@curr_board_n = 0
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_constrained_value(value)
|
26
|
+
if (@curr_board_n += 1) <= MAX_BOARD_N
|
27
|
+
return board(@curr_board_n)
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def board(pos=nil)
|
34
|
+
if pos
|
35
|
+
unless (b = @boards[pos])
|
36
|
+
b = @boards[pos] = []
|
37
|
+
(QUEEN_MAX - 1).downto(0) do |x|
|
38
|
+
y, pos = pos.divmod(QUEEN_MAX ** x)
|
39
|
+
b << y
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return b
|
43
|
+
else
|
44
|
+
return @constrained_value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def good?(x, y)
|
49
|
+
@constrained_value.each_with_index do |ox, oy|
|
50
|
+
if y == oy
|
51
|
+
next
|
52
|
+
elsif x == ox or ((y - oy).abs == (x - ox).abs)
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
|
59
|
+
or_constraint('Good') do |qb, queen|
|
60
|
+
rv = true
|
61
|
+
qb.constrained_value.each_with_index do |x, y|
|
62
|
+
if !qb.good?(x, y)
|
63
|
+
rv = false
|
64
|
+
break
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rv
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
if __FILE__ == $0
|
73
|
+
b = QueensBoard.new
|
74
|
+
|
75
|
+
puts "First solution:"
|
76
|
+
p b.next_constrained_value!.board.collect {|e| e}
|
77
|
+
|
78
|
+
puts "All Solutions:"
|
79
|
+
for board in b.collect_constrained_values_until {|e| b.curr_board_n >= MAX_BOARD_N}
|
80
|
+
p board.board.collect {|e| e}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# queens.rb
|
3
|
+
# @Author: Thomas Link (samul@web.de)
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 11-Jun-2005.
|
6
|
+
# @Last Change: 12-Jun-2005.
|
7
|
+
# @Revision: 0.388
|
8
|
+
|
9
|
+
require 'constraint'
|
10
|
+
|
11
|
+
QUEEN_MIN = 1
|
12
|
+
QUEEN_MAX = 5
|
13
|
+
MAX_BOARD_N = (QUEEN_MAX ** QUEEN_MAX) - 1
|
14
|
+
|
15
|
+
class QueensBoard < Constraint::CArray
|
16
|
+
attr_reader :curr_board_n
|
17
|
+
|
18
|
+
def new_constrained
|
19
|
+
@boards = []
|
20
|
+
@masks = []
|
21
|
+
@curr_board_n = 0
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_constrained_value(value)
|
26
|
+
if (@curr_board_n += 1) <= MAX_BOARD_N
|
27
|
+
return board(@curr_board_n)
|
28
|
+
else
|
29
|
+
super(@curr_board_n)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def board(pos=nil)
|
34
|
+
if pos
|
35
|
+
unless (@constrained_value = @boards[pos])
|
36
|
+
@constrained_value = []
|
37
|
+
(QUEEN_MAX - 1).downto(0) do |x|
|
38
|
+
y, pos = pos.divmod(QUEEN_MAX ** x)
|
39
|
+
self << [x, y]
|
40
|
+
end
|
41
|
+
@boards[pos] = @constrained_value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return @constrained_value
|
45
|
+
end
|
46
|
+
|
47
|
+
or_constraint('Good') do |qb, queen|
|
48
|
+
x, y = queen
|
49
|
+
rv = true
|
50
|
+
qb.constrained_value.each do |other_queen|
|
51
|
+
if queen.equal?(other_queen)
|
52
|
+
next
|
53
|
+
end
|
54
|
+
ox, oy = other_queen
|
55
|
+
if y == oy or x == ox or ((y - oy).abs == (x - ox).abs)
|
56
|
+
rv = false
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rv
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
if __FILE__ == $0
|
66
|
+
b = QueensBoard.new
|
67
|
+
|
68
|
+
puts "First solution:"
|
69
|
+
p b.next_constrained_value!.board.collect {|x, y| y}
|
70
|
+
|
71
|
+
puts "All Solutions:"
|
72
|
+
for board in b.collect_constrained_values_until {|e| b.curr_board_n >= MAX_BOARD_N - 1}
|
73
|
+
b = board.board
|
74
|
+
# b.sort do |q1, q2|
|
75
|
+
# x1, y1 = q1
|
76
|
+
# x2, y2 = q2
|
77
|
+
# x1 <=> x2
|
78
|
+
# end
|
79
|
+
p b.collect {|x, y| y}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# queens.rb
|
3
|
+
# @Author: Thomas Link (samul@web.de)
|
4
|
+
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
5
|
+
# @Created: 11-Jun-2005.
|
6
|
+
# @Last Change: 12-Jun-2005.
|
7
|
+
# @Revision: 0.223
|
8
|
+
|
9
|
+
require 'constraint'
|
10
|
+
|
11
|
+
QUEEN_MIN = 1
|
12
|
+
QUEEN_MAX = 5
|
13
|
+
|
14
|
+
class QueensBoard < Constraint::Shell
|
15
|
+
def new_constrained
|
16
|
+
@boards = []
|
17
|
+
@masks = []
|
18
|
+
@max_board = (QUEEN_MAX ** QUEEN_MAX) - 1
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_constrained_value
|
23
|
+
if @constrained_value
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_constrained_value(value)
|
29
|
+
if !value
|
30
|
+
return 0
|
31
|
+
elsif value < @max_board
|
32
|
+
return value + 1
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def board(pos=@constrained_value)
|
39
|
+
unless (b = @boards[pos])
|
40
|
+
b = @boards[pos] = []
|
41
|
+
QUEEN_MAX.downto(1) do |x|
|
42
|
+
xx, pos = pos.divmod(QUEEN_MAX ** (x - 1))
|
43
|
+
b << xx
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return b
|
47
|
+
end
|
48
|
+
|
49
|
+
def strike_any?(x, y, board)
|
50
|
+
board.each_with_index do |oy, ox|
|
51
|
+
if x == ox
|
52
|
+
next
|
53
|
+
elsif y == oy or ((y - oy).abs == (x - ox).abs)
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
|
60
|
+
or_constraint('Good') do |qb, board|
|
61
|
+
b = qb.board(board)
|
62
|
+
rv = true
|
63
|
+
b.each_with_index do |y, i|
|
64
|
+
if qb.strike_any?(i, y, b)
|
65
|
+
rv = false
|
66
|
+
break
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rv
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
if __FILE__ == $0
|
75
|
+
b = QueensBoard.new
|
76
|
+
|
77
|
+
puts "First solution:"
|
78
|
+
p b.next_constrained_value!.board.collect {|e| e}
|
79
|
+
|
80
|
+
puts "All Solutions:"
|
81
|
+
for board in b.collect_constrained_values_until {|e| e >= (QUEEN_MAX ** QUEEN_MAX) - 2}
|
82
|
+
p board.board.collect {|e| e}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/lib/constraint.rb
CHANGED
@@ -2,110 +2,114 @@
|
|
2
2
|
# @Author: Thomas Link (samul AT web.de)
|
3
3
|
# @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
|
4
4
|
# @Created: 23-Mai-2005.
|
5
|
-
# @Last Change:
|
6
|
-
# @Revision:
|
5
|
+
# @Last Change: 14-Jun-2005.
|
6
|
+
# @Revision: 986
|
7
7
|
|
8
|
-
|
9
|
-
class ConstraintViolation < Exception
|
10
|
-
end
|
8
|
+
require 'forwardable'
|
11
9
|
|
12
10
|
|
13
|
-
# If CONSTRAINT_DISABLE is set to true
|
11
|
+
# If CONSTRAINT_DISABLE is set to true _before_ loading this library,
|
14
12
|
# constraint checking is turned off.
|
15
13
|
CONSTRAINT_DISABLE = false unless defined?(CONSTRAINT_DISABLE)
|
16
14
|
|
17
15
|
|
18
16
|
module Constraint
|
19
|
-
VERSION = '0.
|
17
|
+
VERSION = '0.2'
|
18
|
+
|
19
|
+
|
20
|
+
class Violation < Exception
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
class NoMoreValues < Violation
|
24
|
+
end
|
25
|
+
|
26
|
+
# The base constraint class.
|
27
|
+
class SingleConstraint
|
23
28
|
# shell:: The Shell class containing this constraint
|
24
29
|
# name:: This constraint's name
|
25
30
|
# description:: Optional constraint description
|
26
31
|
# block:: The constraint that must evaluate to true in order for this constraint to succeed
|
27
32
|
def initialize(shell, name, description=nil, &block)
|
28
|
-
@
|
29
|
-
@other = shell.constraints
|
33
|
+
# @other = shell.constraints
|
30
34
|
@name = name
|
31
|
-
@description = description
|
32
35
|
@this = block
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@shell.log_constraint_exception(e)
|
41
|
-
end
|
42
|
-
begin
|
43
|
-
if @other
|
44
|
-
return evaluate_control(rv, object)
|
45
|
-
elsif rv
|
46
|
-
return object
|
47
|
-
else
|
48
|
-
return evaluate(handle_violation(object))
|
49
|
-
end
|
50
|
-
rescue ConstraintViolation => e
|
51
|
-
raise e
|
38
|
+
def evaluate(invoker, object)
|
39
|
+
if @this.call(invoker, object)
|
40
|
+
object
|
41
|
+
else
|
42
|
+
invoker.handle_constraint_violation(@name, object)
|
52
43
|
end
|
53
44
|
end
|
54
|
-
|
55
|
-
# Delegate to Shell#handle_constraint_violation.
|
56
|
-
def handle_violation(object)
|
57
|
-
return @shell.handle_constraint_violation(@name, object)
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
# Combine constraints. In the base class, always evaluate to
|
62
|
-
# true.
|
63
|
-
def evaluate_control(value, object)
|
64
|
-
raise "Subclass responsibility"
|
65
|
-
# value ? object : handle_violation(object)
|
66
|
-
end
|
67
45
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
46
|
+
|
47
|
+
# All constraints must succeed.
|
48
|
+
class AndConstraint < Array
|
49
|
+
def evaluate(invoker, object)
|
50
|
+
for a in self
|
51
|
+
object = a.evaluate(invoker, object)
|
52
|
+
end
|
53
|
+
object
|
74
54
|
end
|
75
55
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
56
|
+
|
57
|
+
# At least one constraint must succeed.
|
58
|
+
class OrConstraint < Array
|
59
|
+
def evaluate(invoker, object)
|
60
|
+
c = nil
|
61
|
+
for o in self
|
62
|
+
begin
|
63
|
+
return o.evaluate(invoker, object)
|
64
|
+
rescue Constraint::Violation => e
|
65
|
+
c = e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
raise c
|
82
69
|
end
|
83
70
|
end
|
84
|
-
|
71
|
+
|
85
72
|
module Helper
|
86
73
|
# The current class's/object's head constraint.
|
87
74
|
attr_reader :constraints
|
88
|
-
# A hash of constraint
|
89
|
-
attr_reader :
|
75
|
+
# A hash of constraint constaint_descriptions.
|
76
|
+
attr_reader :constaint_descriptions
|
77
|
+
# An array of constraint handlers
|
78
|
+
attr_reader :constaint_handlers
|
79
|
+
# An array of additional attributes that should be copied when
|
80
|
+
# replicating a shell
|
81
|
+
attr_reader :constraint_attributes
|
90
82
|
|
91
|
-
# Define an OrConstraint.
|
92
|
-
# previous constraints become obsolete.
|
83
|
+
# Define an OrConstraint. Either this or the previous constraint have to succeed.
|
93
84
|
# name:: This constraint's name
|
94
85
|
# description:: Optional constraint description
|
95
86
|
# block:: The constraint that must evaluate to true in order for this constraint to succeed
|
96
87
|
def or_constraint(name, desc=nil, &block)
|
97
88
|
describe_constraint(name, desc)
|
98
|
-
@constraints =
|
89
|
+
@constraints = @constraints.dup
|
90
|
+
constraint = SingleConstraint.new(self, name, desc, &block)
|
91
|
+
if @constraints.empty?
|
92
|
+
@constraints << constraint
|
93
|
+
else
|
94
|
+
last = @constraints.last
|
95
|
+
case last
|
96
|
+
when OrConstraint
|
97
|
+
last << constraint
|
98
|
+
else
|
99
|
+
@constraints[-1] = OrConstraint.new([last, constraint])
|
100
|
+
end
|
101
|
+
end
|
99
102
|
end
|
100
103
|
|
101
|
-
# Define an
|
104
|
+
# Define an SingleConstraint. This and all previous constraints
|
102
105
|
# have to succeed.
|
103
106
|
# name:: This constraint's name
|
104
107
|
# description:: Optional constraint description
|
105
108
|
# block:: The constraint that must evaluate to true in order for this constraint to succeed
|
106
109
|
def and_constraint(name, desc=nil, &block)
|
107
110
|
describe_constraint(name, desc)
|
108
|
-
@constraints =
|
111
|
+
@constraints = @constraints.dup
|
112
|
+
@constraints << SingleConstraint.new(self, name, desc, &block)
|
109
113
|
end
|
110
114
|
|
111
115
|
# If $VERBOSE is set, print the exception to $stderr.
|
@@ -115,50 +119,94 @@ module Constraint
|
|
115
119
|
end
|
116
120
|
end
|
117
121
|
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
122
|
+
# Define a new constraint handler. The block takes a
|
123
|
+
# continuation as its first argument. If the amendment of the
|
124
|
+
# object isn't successful, the handler must throw an
|
125
|
+
# Constraint::Violation exception.
|
126
|
+
#
|
127
|
+
# Example:
|
128
|
+
#
|
129
|
+
# enum = EvenInteger.new(2)
|
130
|
+
# enum.and_constraint('LT10') {|enum, n| n < 10}
|
131
|
+
# enum.on_constraint_violation('LT10') do |o, n|
|
132
|
+
# n > 10 ? 10 : 0
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# *names:: The constraints names
|
136
|
+
# block:: The handler (argument: object)
|
137
|
+
def on_constraint_violation(*names, &block)
|
138
|
+
@constaint_handlers = @constaint_handlers.dup
|
139
|
+
names.each do |name|
|
140
|
+
@constaint_handlers[name] ||= []
|
141
|
+
@constaint_handlers[name] << block
|
142
|
+
end
|
128
143
|
end
|
129
144
|
|
145
|
+
def constraint_attr(name, val)
|
146
|
+
(@constraint_attributes = @constraint_attributes.dup) << name
|
147
|
+
instance_eval %{
|
148
|
+
class << self
|
149
|
+
attr_accessor :#{name}
|
150
|
+
end
|
151
|
+
attr_accessor :#{name}
|
152
|
+
}
|
153
|
+
instance_variable_set("@#{name}", val)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Copy needed class/object variables.
|
157
|
+
def replicate_constraints(predecessor)
|
158
|
+
@constaint_descriptions = predecessor.constaint_descriptions
|
159
|
+
@constaint_handlers = predecessor.constaint_handlers
|
160
|
+
@constraints = predecessor.constraints
|
161
|
+
for name in (@constraint_attributes = predecessor.constraint_attributes)
|
162
|
+
nname = "@#{name}"
|
163
|
+
instance_variable_set(nname, predecessor.instance_variable_get(nname))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
130
167
|
private
|
131
168
|
# Copy constraint-related class-local variables from +klass+ to self.
|
132
|
-
def inherit_constraint(klass)
|
133
|
-
|
134
|
-
|
135
|
-
|
169
|
+
# def inherit_constraint(klass)
|
170
|
+
# @constaint_descriptions = klass.constaint_descriptions
|
171
|
+
# @constaint_handlers = klass.constaint_handlers
|
172
|
+
# @constraints = klass.constraints
|
173
|
+
# end
|
136
174
|
|
137
175
|
# Register a constraint description. If no description is
|
138
176
|
# provided, the +name+ is used.
|
139
177
|
# name:: This constraint's name
|
140
178
|
# desc:: Optional constraint description
|
141
179
|
def describe_constraint(name, desc=nil)
|
142
|
-
@
|
180
|
+
@constaint_descriptions[name] = (desc || name)
|
143
181
|
end
|
144
182
|
end
|
145
183
|
|
146
184
|
# If CONSTRAINT_DISABLE is set to true *before* loading this library,
|
147
185
|
# constraint checking is turned off.
|
148
186
|
class Shell
|
187
|
+
extend Forwardable
|
188
|
+
|
149
189
|
# The actual value that is covered by the Shell.
|
150
190
|
attr_reader :constrained_value
|
151
191
|
|
152
|
-
@
|
153
|
-
@
|
192
|
+
@constaint_descriptions = {}
|
193
|
+
@constaint_handlers = {}
|
194
|
+
# @constraints = nil
|
195
|
+
@constraints = AndConstraint.new
|
196
|
+
@constraint_attributes = []
|
154
197
|
|
155
198
|
class << self
|
156
199
|
include Helper
|
157
|
-
|
200
|
+
|
201
|
+
def delegate_to_constrained_value(meth, alias_name)
|
202
|
+
class_eval %{alias #{alias_name} #{meth}}
|
203
|
+
def_delegator(:@constrained_value, meth)
|
204
|
+
end
|
205
|
+
|
158
206
|
def inherited(subclass)
|
159
207
|
parent = self
|
160
208
|
subclass.class_eval do
|
161
|
-
|
209
|
+
replicate_constraints(parent)
|
162
210
|
end
|
163
211
|
end
|
164
212
|
|
@@ -174,12 +222,12 @@ module Constraint
|
|
174
222
|
# Wrap +methods+ so that some arguments are filtered through
|
175
223
|
# #check_constraints. This is useful in two situations:
|
176
224
|
#
|
177
|
-
#
|
225
|
+
# * if filtering the return value would be inefficient --
|
178
226
|
# e.g. with methods performing trivial Array operations
|
179
227
|
# where the return value will comply with the constraints
|
180
228
|
# if its argument do.
|
181
229
|
#
|
182
|
-
#
|
230
|
+
# * if the return value of the method call is not an
|
183
231
|
# instance of the actual value's class (only then will the
|
184
232
|
# value's integrity be checked)
|
185
233
|
#
|
@@ -187,7 +235,6 @@ module Constraint
|
|
187
235
|
#
|
188
236
|
# methods:: Method names as symbols
|
189
237
|
# block:: Select the arguments that should be filtered
|
190
|
-
|
191
238
|
def with_arguments_constrained(*methods, &block)
|
192
239
|
unless CONSTRAINT_DISABLE
|
193
240
|
methods.each do |method|
|
@@ -215,24 +262,29 @@ module Constraint
|
|
215
262
|
|
216
263
|
include Helper
|
217
264
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
@constraints = predecessor.constraints
|
222
|
-
else
|
223
|
-
@descriptions = self.class.descriptions.dup
|
224
|
-
@constraints = self.class.constraints
|
225
|
-
@constraints = @constraints.dup if @constraints
|
226
|
-
end
|
227
|
-
|
228
|
-
if core
|
229
|
-
@constrained_value = core
|
230
|
-
else
|
231
|
-
@constrained_value = new_constrained
|
232
|
-
end
|
265
|
+
delegate_to_constrained_value(:inspect, :inspect_constraint_shell)
|
266
|
+
delegate_to_constrained_value(:=~, :match_constraint_shell)
|
267
|
+
delegate_to_constrained_value(:instance_eval, :constraint_shell_eval)
|
233
268
|
|
269
|
+
# delegate_to_constrained_value(:tainted?, :tainted_constraint_shell?)
|
270
|
+
# delegate_to_constrained_value(:taint, :taint_constraint_shell)
|
271
|
+
# delegate_to_constrained_value(:untaint, :untaint_constraint_shell)
|
272
|
+
|
273
|
+
delegate_to_constrained_value(:===, :case_equal_constraint_shell)
|
274
|
+
# def ===(other)
|
275
|
+
# if other.kind_of?(Shell)
|
276
|
+
# super
|
277
|
+
# else
|
278
|
+
# @constrained_value === other
|
279
|
+
# end
|
280
|
+
# end
|
281
|
+
|
282
|
+
def initialize(core=nil, predecessor=nil, &block)
|
283
|
+
predecessor ||= self.class
|
284
|
+
replicate_constraints(predecessor)
|
285
|
+
@constrained_value = core || new_constrained
|
234
286
|
unless CONSTRAINT_DISABLE
|
235
|
-
|
287
|
+
check_constrained_value
|
236
288
|
end
|
237
289
|
end
|
238
290
|
|
@@ -244,12 +296,86 @@ module Constraint
|
|
244
296
|
replicate_constraint_shell(super)
|
245
297
|
end
|
246
298
|
|
247
|
-
# Construct a new instance.
|
299
|
+
# Construct a new instance. By default, this method calls
|
300
|
+
# generate_constrained_value(nil).
|
248
301
|
def new_constrained
|
249
|
-
|
250
|
-
raise 'Subclass responsibility'
|
302
|
+
generate_constrained_value(nil)
|
303
|
+
# raise 'Subclass responsibility'
|
304
|
+
end
|
305
|
+
|
306
|
+
# Generate the next value for #next_constrained_value.
|
307
|
+
def generate_constrained_value(value)
|
308
|
+
raise Constraint::NoMoreValues, "No more values: #{value}"
|
309
|
+
end
|
310
|
+
|
311
|
+
# "Increase" the current value to the next valid one (see
|
312
|
+
# #generate_constrained_value). Modify the object in-place.
|
313
|
+
def next_constrained_value!(value=nil)
|
314
|
+
begin
|
315
|
+
cv = @constrained_value
|
316
|
+
if value
|
317
|
+
@constrained_value = value
|
318
|
+
end
|
319
|
+
begin
|
320
|
+
while (@constrained_value = generate_constrained_value(@constrained_value))
|
321
|
+
check_constrained_value
|
322
|
+
return self
|
323
|
+
end
|
324
|
+
rescue Constraint::NoMoreValues => e
|
325
|
+
raise e
|
326
|
+
rescue Constraint::Violation => e
|
327
|
+
retry
|
328
|
+
end
|
329
|
+
raise_constraint_violation(nil, value, "No more values: #{value}")
|
330
|
+
rescue Exception => e
|
331
|
+
@constrained_value = cv
|
332
|
+
raise e
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Return an object with an increased value (see
|
337
|
+
# #generate_constrained_value).
|
338
|
+
def next_constrained_value(value=nil)
|
339
|
+
self.clone.next_constrained_value!(value)
|
340
|
+
end
|
341
|
+
|
342
|
+
# Return an array of constrained values starting from the
|
343
|
+
# current value until a value for which block is true. Requires
|
344
|
+
# #generate_constrained_value to be defined.
|
345
|
+
def collect_constrained_values_while(&block)
|
346
|
+
collect_constrained_values_control do |val, acc|
|
347
|
+
if (rv = block.call(val))
|
348
|
+
acc << self.dup
|
349
|
+
next_constrained_value!
|
350
|
+
end
|
351
|
+
rv
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Return an array of constrained values starting from the
|
356
|
+
# current value until a value for which block is true. Requires
|
357
|
+
# #generate_constrained_value to be defined.
|
358
|
+
def collect_constrained_values_until(&block)
|
359
|
+
collect_constrained_values_control do |val, acc|
|
360
|
+
if (rv = !block.call(val))
|
361
|
+
acc << self.dup
|
362
|
+
next_constrained_value!
|
363
|
+
end
|
364
|
+
rv
|
365
|
+
end
|
251
366
|
end
|
252
367
|
|
368
|
+
# Collect all values. Shell#generate_constrained_value must
|
369
|
+
# provide a means to decide when no more values should be
|
370
|
+
# generated.
|
371
|
+
def collect_constrained_values
|
372
|
+
collect_constrained_values_control do |val, acc|
|
373
|
+
acc << self.dup
|
374
|
+
next_constrained_value!
|
375
|
+
true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
253
379
|
# Filters @constrained_value through the supplied block (usually a
|
254
380
|
# call to #check_constraints) and collects the output in
|
255
381
|
# @constrained_value).
|
@@ -258,6 +384,10 @@ module Constraint
|
|
258
384
|
@constrained_value = yield @constrained_value
|
259
385
|
end
|
260
386
|
|
387
|
+
def check_constrained_value
|
388
|
+
process_constrained_value {|e| check_constraints(e)}
|
389
|
+
end
|
390
|
+
|
261
391
|
if CONSTRAINT_DISABLE
|
262
392
|
def check_constraints(object)
|
263
393
|
object
|
@@ -266,7 +396,7 @@ module Constraint
|
|
266
396
|
# Check if +object+ complies with @constraints.
|
267
397
|
def check_constraints(object)
|
268
398
|
if @constraints
|
269
|
-
return @constraints.evaluate(object)
|
399
|
+
return @constraints.evaluate(self, object)
|
270
400
|
else
|
271
401
|
return object
|
272
402
|
end
|
@@ -288,7 +418,7 @@ module Constraint
|
|
288
418
|
# Shell.with_arguments_constrained and friends.
|
289
419
|
#
|
290
420
|
# new_value:: The result from the method call
|
291
|
-
#
|
421
|
+
# block:: Optional, called @constrained_value is +new_value+
|
292
422
|
def with_constraints(new_value)
|
293
423
|
if new_value.equal?(@constrained_value)
|
294
424
|
yield if block_given?
|
@@ -300,6 +430,25 @@ module Constraint
|
|
300
430
|
end
|
301
431
|
end
|
302
432
|
|
433
|
+
# Handle constraint violations. Can be overwritten by subclasses
|
434
|
+
# in order to rescue the constraint. The return value will
|
435
|
+
# replace the original object.
|
436
|
+
# name:: The constraint's name that didn't succeed
|
437
|
+
# object:: The object that caused the violation
|
438
|
+
def handle_constraint_violation(name, object)
|
439
|
+
handlers = @constaint_handlers[name]
|
440
|
+
if handlers
|
441
|
+
for h in handlers
|
442
|
+
begin
|
443
|
+
return h.call(self, object)
|
444
|
+
rescue Constraint::Violation
|
445
|
+
next
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
raise_constraint_violation(name, object)
|
450
|
+
end
|
451
|
+
|
303
452
|
if CONSTRAINT_DISABLE
|
304
453
|
def method_missing(method, *args, &block)
|
305
454
|
with_constraints(@constrained_value.send(method, *args, &block))
|
@@ -313,7 +462,7 @@ module Constraint
|
|
313
462
|
def method_missing(method, *args, &block)
|
314
463
|
# $stderr.puts "Constraint::Shell: Delegate method: #{method}" if $DEBUG
|
315
464
|
with_constraints(@constrained_value.send(method, *args, &block)) do
|
316
|
-
|
465
|
+
check_constrained_value
|
317
466
|
end
|
318
467
|
end
|
319
468
|
end
|
@@ -324,12 +473,47 @@ module Constraint
|
|
324
473
|
end
|
325
474
|
|
326
475
|
private
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
-
|
476
|
+
# Eval block while it's yielding true. Maintain
|
477
|
+
# @constrained_value in case of an exception.
|
478
|
+
def collect_constrained_values_control(&block)
|
479
|
+
begin
|
480
|
+
acc = []
|
481
|
+
cv = @constrained_value
|
482
|
+
begin
|
483
|
+
while block.call(@constrained_value, acc)
|
484
|
+
end
|
485
|
+
rescue Constraint::Violation
|
486
|
+
end
|
487
|
+
return acc
|
488
|
+
ensure
|
331
489
|
@constrained_value = cv
|
332
490
|
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Raise a Constraint::Violation.
|
494
|
+
def raise_constraint_violation(name, object, msg=nil)
|
495
|
+
i = 0
|
496
|
+
caller.each_with_index do |e,l|
|
497
|
+
if e =~ /^(\w+:)?([^:].*?[\\\/])+?constraint.rb:\d+:/
|
498
|
+
i = l
|
499
|
+
end
|
500
|
+
end
|
501
|
+
msg ||= "#{object.class} didn't meet constraint '#{name}': #{object.inspect}"
|
502
|
+
raise Constraint::Violation, msg, caller[i+1..-1]
|
503
|
+
end
|
504
|
+
|
505
|
+
# Some kind of semi-deep copy. If @constrained_value can't be
|
506
|
+
# cloned (e.g., a Fixnum), the new_instance will be returned as
|
507
|
+
# such.
|
508
|
+
# new_instance:: A duplicate of Shell
|
509
|
+
def replicate_constraint_shell(new_instance)
|
510
|
+
begin
|
511
|
+
cv = @constrained_value.clone
|
512
|
+
new_instance.constraint_shell_eval do
|
513
|
+
@constrained_value = cv
|
514
|
+
end
|
515
|
+
rescue TypeError
|
516
|
+
end
|
333
517
|
new_instance
|
334
518
|
end
|
335
519
|
end
|