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
         |