constraint 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/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
|