-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmetaprogramming.rb
98 lines (82 loc) · 2.34 KB
/
metaprogramming.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class PreconditionError < StandardError
def initialize(method_name, precondition_index)
super("Precondition #{precondition_index} failed for #{method_name}")
end
end
class PostconditionError < StandardError
def initialize(method_name, postcondition_index)
super("Postcondition #{postcondition_index} failed for #{method_name}")
end
end
class Module
def pre(&block)
@current_preconditions ||= []
@current_preconditions << block
end
def post(&block)
@current_postconditions ||= []
@current_postconditions << block
end
def method_added(method_name)
return unless @current_preconditions&.any? || @current_postconditions&.any?
preconditions = @current_preconditions
@current_preconditions = []
postconditions = @current_postconditions
@current_postconditions = []
original_method = instance_method(method_name)
define_method(method_name) do |*args, &method_block|
puts "Executing preconditions for #{method_name}"
preconditions.each_with_index do |pc, index|
raise PreconditionError.new(method_name, index + 1) unless instance_exec(*args, &pc)
end
puts "Executing method #{method_name}"
ret = original_method.bind(self).call(*args, &method_block)
puts "Executing postconditions for #{method_name}"
postconditions.each_with_index do |pc, index|
raise PostconditionError.new(method_name, index + 1) unless instance_exec(*args, &pc)
end
ret
end
end
end
# Sample usage class
class Stack
attr_accessor :current_node, :capacity
pre { |capacity_param| capacity_param > 0 }
post { empty? }
def initialize(capacity)
@capacity = capacity
@current_node = nil
puts "Initialized stack with capacity: #{@capacity}"
end
pre { !full? }
def push(element)
puts "Pushing element: #{element}"
@current_node = Node.new(element, current_node)
end
pre { !empty? }
def pop
element = top
@current_node = current_node.next_node
puts "Popped element: #{element}"
element
end
pre { !empty? }
def top
current_node.element
end
def height
empty? ? 0 : current_node.chain_size
end
def empty?
current_node.nil?
end
def full?
height == capacity
end
Node = Struct.new(:element, :next_node) do
def chain_size
next_node.nil? ? 1 : 1 + next_node.chain_size
end
end
end