tins 0.13.2 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +1 -13
- data/VERSION +1 -1
- data/examples/add_one.png +0 -0
- data/examples/add_one.stm +13 -0
- data/examples/bb3.png +0 -0
- data/examples/bb3.stm +26 -0
- data/examples/bb3_19.stm +26 -0
- data/examples/concatenate_compare.mtm +31 -0
- data/examples/concatenate_compare.png +0 -0
- data/examples/concatenate_compare_19.mtm +31 -0
- data/examples/length_difference.mtm +17 -0
- data/examples/length_difference.png +0 -0
- data/examples/length_difference_19.mtm +17 -0
- data/examples/let.rb +62 -0
- data/examples/mail.rb +73 -0
- data/examples/minsky.rb +145 -0
- data/examples/multiply.reg +42 -0
- data/examples/null_pattern.rb +52 -0
- data/examples/ones_difference-mtm.png +0 -0
- data/examples/ones_difference-stm.png +0 -0
- data/examples/ones_difference.mtm +12 -0
- data/examples/ones_difference.stm +25 -0
- data/examples/ones_difference_19.mtm +12 -0
- data/examples/ones_difference_19.stm +25 -0
- data/examples/prefix-equals-suffix-reversed-with-infix.png +0 -0
- data/examples/prefix-equals-suffix-reversed-with-infix.stm +38 -0
- data/examples/prefix-equals-suffix-reversed-with-infix_19.stm +38 -0
- data/examples/recipe.rb +81 -0
- data/examples/recipe2.rb +82 -0
- data/examples/recipe_common.rb +97 -0
- data/examples/subtract.reg +9 -0
- data/examples/turing-graph.rb +17 -0
- data/examples/turing.rb +310 -0
- data/lib/dslkit.rb +2 -0
- data/lib/dslkit/polite.rb +1 -0
- data/lib/dslkit/rude.rb +1 -0
- data/lib/tins.rb +1 -0
- data/lib/tins/dslkit.rb +662 -0
- data/lib/tins/thread_local.rb +52 -0
- data/lib/tins/version.rb +1 -1
- data/lib/tins/xt.rb +1 -0
- data/lib/tins/xt/dslkit.rb +23 -0
- data/tests/concern_test.rb +24 -25
- data/tests/dslkit_test.rb +308 -0
- data/tests/dynamic_scope_test.rb +31 -0
- data/tests/from_module_test.rb +61 -0
- data/tests/scope_test.rb +31 -0
- data/tests/test_helper.rb +0 -1
- data/tins.gemspec +10 -10
- metadata +68 -17
@@ -0,0 +1,9 @@
|
|
1
|
+
# vim: set filetype=ruby:
|
2
|
+
|
3
|
+
# Subtraction A = A - B, A >= 0 and A >= B
|
4
|
+
register.A = 10 # register names have to be uppercase
|
5
|
+
register.B = 3
|
6
|
+
|
7
|
+
label(Sub_B) { decrement B, Stop, Sub_A } # labels have to be uppercase as well
|
8
|
+
label(Sub_A) { decrement A, Stop, Sub_B }
|
9
|
+
label(Stop) { halt }
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
load File.join(File.dirname(__FILE__), 'turing.rb')
|
4
|
+
include Turing
|
5
|
+
|
6
|
+
filename, *tapes = ARGV
|
7
|
+
machine_type =
|
8
|
+
case ext = File.extname(filename)
|
9
|
+
when '.stm'
|
10
|
+
SingleTapeMachine
|
11
|
+
when '.mtm'
|
12
|
+
MultiTapeMachine
|
13
|
+
else
|
14
|
+
raise "unknown turing machine suffix: #{ext}, use .stm or .mtm"
|
15
|
+
end
|
16
|
+
tm = machine_type.new(File.read(filename))
|
17
|
+
print tm.to_graphviz
|
data/examples/turing.rb
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
require 'tins'
|
3
|
+
|
4
|
+
module Turing
|
5
|
+
class Tape
|
6
|
+
def initialize(*initials)
|
7
|
+
@left = []
|
8
|
+
@head = 'B'
|
9
|
+
@right = []
|
10
|
+
c = 0
|
11
|
+
first = true
|
12
|
+
for initial in initials
|
13
|
+
if first
|
14
|
+
c += 1
|
15
|
+
first = false
|
16
|
+
else
|
17
|
+
@left.push 'B'
|
18
|
+
c += 1
|
19
|
+
end
|
20
|
+
for s in initial.split(//)
|
21
|
+
@left.push s
|
22
|
+
c += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
c.times { left }
|
26
|
+
end
|
27
|
+
|
28
|
+
def read
|
29
|
+
@head
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(symbol)
|
33
|
+
@head = symbol
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def left
|
38
|
+
@right.push @head
|
39
|
+
@head = @left.pop || 'B'
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def right
|
44
|
+
@left.push @head
|
45
|
+
@head = @right.pop || 'B'
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear
|
50
|
+
@left.clear
|
51
|
+
@right.clear
|
52
|
+
@head = 'B'
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
"#{@left.join}#{Term::ANSIColor.red(@head)}#{@right.join.reverse}"
|
58
|
+
end
|
59
|
+
|
60
|
+
alias inspect to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
module States
|
64
|
+
class State
|
65
|
+
attr_accessor :tape
|
66
|
+
end
|
67
|
+
|
68
|
+
class Cond < State
|
69
|
+
def initialize(opts = {})
|
70
|
+
@if, @then, @else = opts.values_at :if, :then, :else
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute
|
74
|
+
tape.read == @if ? @then : @else
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
"if #@if then #@then else #@else"
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_graphviz(stateno, tapeno = nil)
|
82
|
+
%{#{stateno} [ shape=diamond label="#{tapeno && "#{tapeno}: "}#@if" ];
|
83
|
+
#{stateno} -> #@then [ taillabel="+" ];
|
84
|
+
#{stateno} -> #@else [ taillabel="-" ];
|
85
|
+
#{stateno} -> #{stateno} [ label="#{stateno}" weight=4.0 color=transparent ];}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Left < State
|
90
|
+
def initialize(opts = {})
|
91
|
+
@goto = opts[:goto]
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute
|
95
|
+
tape.left
|
96
|
+
@goto
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
"left, goto #@goto"
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_graphviz(stateno, tapeno = nil)
|
104
|
+
%{#{stateno} [ shape=rect label="#{tapeno && "#{tapeno}: "}L" ];
|
105
|
+
#{stateno} -> #@goto;
|
106
|
+
#{stateno} -> #{stateno} [ label="#{stateno}" weight=4.0 color=transparent ];}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Right < State
|
111
|
+
def initialize(opts = {})
|
112
|
+
@goto = opts[:goto]
|
113
|
+
end
|
114
|
+
|
115
|
+
def execute
|
116
|
+
tape.right
|
117
|
+
@goto
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_s
|
121
|
+
"right, goto #@goto"
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_graphviz(stateno, tapeno = nil)
|
125
|
+
%{#{stateno} [ shape=rect label="#{tapeno && "#{tapeno}: "}R" ];
|
126
|
+
#{stateno} -> #@goto;
|
127
|
+
#{stateno} -> #{stateno} [ label="#{stateno}" weight=4.0 color=transparent ];}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Write < State
|
132
|
+
def initialize(opts = {})
|
133
|
+
@symbol, @goto = opts.values_at :symbol, :goto
|
134
|
+
end
|
135
|
+
|
136
|
+
def execute
|
137
|
+
tape.write @symbol
|
138
|
+
@goto
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_s
|
142
|
+
"write #@symbol, goto #@goto"
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_graphviz(stateno, tapeno = nil)
|
146
|
+
%{#{stateno} [ shape=rect label="#{tapeno && "#{tapeno}: "}#@symbol" ];
|
147
|
+
#{stateno} -> #@goto;
|
148
|
+
#{stateno} -> #{stateno} [ label="#{stateno}" weight=4.0 color=transparent ];}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Halt < State
|
153
|
+
def initialize(opts = {})
|
154
|
+
end
|
155
|
+
|
156
|
+
def execute
|
157
|
+
-1
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_s
|
161
|
+
'halt'
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_graphviz(stateno, tapeno = nil)
|
165
|
+
%{#{stateno} [ shape=rect label="HALT" ];
|
166
|
+
#{stateno} -> #{stateno} [ label="#{stateno}" weight=4.0 color=transparent ];}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class BaseMachine
|
172
|
+
def initialize(program = nil, &block)
|
173
|
+
@states = []
|
174
|
+
if program
|
175
|
+
block_given? and raise "use either program source string or a block"
|
176
|
+
interpret program
|
177
|
+
else
|
178
|
+
instance_eval(&block)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def step(*tapes)
|
183
|
+
@stepping = true
|
184
|
+
run(*tapes)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class SingleTapeMachine < BaseMachine
|
189
|
+
include Tins::Deflect
|
190
|
+
include Tins::Interpreter
|
191
|
+
|
192
|
+
def initialize(program = nil)
|
193
|
+
deflector = Deflector.new do |number, id, name, *args|
|
194
|
+
opts = Hash === args.last ? args.pop : {}
|
195
|
+
state = States.const_get(name.to_s.capitalize).new(opts)
|
196
|
+
@states[number] = state
|
197
|
+
end
|
198
|
+
deflect_start(Integer, :method_missing, deflector)
|
199
|
+
super
|
200
|
+
ensure
|
201
|
+
deflect_stop(Integer, :method_missing) if deflect?(Integer, :method_missing)
|
202
|
+
end
|
203
|
+
|
204
|
+
def run(*tape)
|
205
|
+
@tape = Tape.new(*tape)
|
206
|
+
@states.each { |s| s and s.tape = @tape }
|
207
|
+
goto_state = -1
|
208
|
+
@states.any? { |s| goto_state += 1; s }
|
209
|
+
begin
|
210
|
+
printf "%3u: %s", goto_state, @tape
|
211
|
+
@stepping ? STDIN.gets : puts
|
212
|
+
goto_state = @states[goto_state].execute
|
213
|
+
end until goto_state < 0
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_s
|
217
|
+
result = ''
|
218
|
+
@states.each_with_index do |state, i|
|
219
|
+
result << "%3u. %s\n" % [ i, state ]
|
220
|
+
end
|
221
|
+
result
|
222
|
+
end
|
223
|
+
|
224
|
+
def to_graphviz
|
225
|
+
result = "digraph {\n"
|
226
|
+
start_edge = false
|
227
|
+
@states.each_with_index do |state, stateno|
|
228
|
+
state or next
|
229
|
+
unless start_edge
|
230
|
+
result << "start [ fontcolor=transparent color=transparent ];"
|
231
|
+
result << "start -> #{stateno};"
|
232
|
+
start_edge = true
|
233
|
+
end
|
234
|
+
result << state.to_graphviz(stateno) << "\n"
|
235
|
+
end
|
236
|
+
result << "}\n"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class MultiTapeMachine < BaseMachine
|
241
|
+
include Tins::Deflect
|
242
|
+
include Tins::Interpreter
|
243
|
+
|
244
|
+
def initialize(program = nil)
|
245
|
+
deflector = Deflector.new do |number, id, name, *args|
|
246
|
+
opts = Hash === args.last ? args.pop : {}
|
247
|
+
tape, = *args
|
248
|
+
state = States.const_get(name.to_s.capitalize).new(opts)
|
249
|
+
@states[number] = [ tape, state ]
|
250
|
+
end
|
251
|
+
deflect_start(Integer, :method_missing, deflector)
|
252
|
+
super
|
253
|
+
ensure
|
254
|
+
deflect_stop(Integer, :method_missing) if deflect?(Integer, :method_missing)
|
255
|
+
end
|
256
|
+
|
257
|
+
def run(*tapes)
|
258
|
+
tapes.unshift ''
|
259
|
+
@tapes = tapes.map { |tape| Tape.new(tape) }
|
260
|
+
goto_state = -1
|
261
|
+
@states.any? { |s| goto_state += 1; s }
|
262
|
+
begin
|
263
|
+
printf "%3u: %s", goto_state, @tapes * ' '
|
264
|
+
@stepping ? STDIN.gets : puts
|
265
|
+
tape, state = @states[goto_state]
|
266
|
+
state.tape = tape ? @tapes[tape] : nil
|
267
|
+
goto_state = state.execute
|
268
|
+
end until goto_state < 0
|
269
|
+
end
|
270
|
+
|
271
|
+
def to_s
|
272
|
+
result = ''
|
273
|
+
@states.each_with_index do |(tape, state), i|
|
274
|
+
result << "%3u. %1u: %s\n" % [ i, tape, state ]
|
275
|
+
end
|
276
|
+
result
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_graphviz
|
280
|
+
result = "digraph {\n"
|
281
|
+
start_edge = false
|
282
|
+
@states.each_with_index do |(tapeno,state), stateno|
|
283
|
+
state or next
|
284
|
+
unless start_edge
|
285
|
+
result << "start [ fontcolor=transparent color=transparent ];"
|
286
|
+
result << "start -> #{stateno};"
|
287
|
+
start_edge = true
|
288
|
+
end
|
289
|
+
result << state.to_graphviz(stateno, tapeno) << "\n"
|
290
|
+
end
|
291
|
+
result << "}\n"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
if $0 == __FILE__ and ARGV.any?
|
297
|
+
include Turing
|
298
|
+
filename, *tapes = ARGV
|
299
|
+
machine_type =
|
300
|
+
case ext = File.extname(filename)
|
301
|
+
when '.stm'
|
302
|
+
SingleTapeMachine
|
303
|
+
when '.mtm'
|
304
|
+
MultiTapeMachine
|
305
|
+
else
|
306
|
+
raise "unknown turing machine suffix: #{ext}, use .stm or .mtm"
|
307
|
+
end
|
308
|
+
tm = machine_type.new(File.read(filename))
|
309
|
+
$DEBUG ? tm.step(*tapes) : tm.run(*tapes)
|
310
|
+
end
|
data/lib/dslkit.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'tins/dslkit'
|
data/lib/dslkit/rude.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'tins/xt/dslkit'
|
data/lib/tins.rb
CHANGED
data/lib/tins/dslkit.rb
ADDED
@@ -0,0 +1,662 @@
|
|
1
|
+
require 'tins'
|
2
|
+
require 'thread'
|
3
|
+
require 'sync'
|
4
|
+
|
5
|
+
require 'tins/thread_local'
|
6
|
+
|
7
|
+
module Tins
|
8
|
+
# This module contains some handy methods to deal with eigenclasses. Those
|
9
|
+
# are also known as virtual classes, singleton classes, metaclasses, plus all
|
10
|
+
# the other names Matz doesn't like enough to actually accept one of the
|
11
|
+
# names.
|
12
|
+
#
|
13
|
+
# The module can be included into other modules/classes to make the methods available.
|
14
|
+
module Eigenclass
|
15
|
+
if Object.respond_to?(:singleton_class)
|
16
|
+
alias eigenclass singleton_class
|
17
|
+
else
|
18
|
+
# Returns the eigenclass of this object.
|
19
|
+
def eigenclass
|
20
|
+
class << self; self; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Evaluates the _block_ in context of the eigenclass of this object.
|
25
|
+
def eigenclass_eval(&block)
|
26
|
+
eigenclass.instance_eval(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethod
|
31
|
+
include Eigenclass
|
32
|
+
|
33
|
+
# Define a class method named _name_ using _block_. To be able to take
|
34
|
+
# blocks as arguments in the given _block_ Ruby 1.9 is required.
|
35
|
+
def class_define_method(name, &block)
|
36
|
+
eigenclass_eval { define_method(name, &block) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Define reader and writer attribute methods for all <i>*ids</i>.
|
40
|
+
def class_attr_accessor(*ids)
|
41
|
+
eigenclass_eval { attr_accessor(*ids) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define reader attribute methods for all <i>*ids</i>.
|
45
|
+
def class_attr_reader(*ids)
|
46
|
+
eigenclass_eval { attr_reader(*ids) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Define writer attribute methods for all <i>*ids</i>.
|
50
|
+
def class_attr_writer(*ids)
|
51
|
+
eigenclass_eval { attr_writer(*ids) }
|
52
|
+
end
|
53
|
+
|
54
|
+
# I boycott attr!
|
55
|
+
end
|
56
|
+
|
57
|
+
module ThreadGlobal
|
58
|
+
# Define a thread global variable named _name_ in this module/class. If the
|
59
|
+
# value _value_ is given, it is used to initialize the variable.
|
60
|
+
def thread_global(name, default_value = nil)
|
61
|
+
is_a?(Module) or raise TypeError, "receiver has to be a Module"
|
62
|
+
|
63
|
+
name = name.to_s
|
64
|
+
var_name = "@__#{name}_#{__id__.abs}__"
|
65
|
+
|
66
|
+
lock = Mutex.new
|
67
|
+
modul = self
|
68
|
+
|
69
|
+
define_method(name) do
|
70
|
+
lock.synchronize { modul.instance_variable_get var_name }
|
71
|
+
end
|
72
|
+
|
73
|
+
define_method(name + "=") do |value|
|
74
|
+
lock.synchronize { modul.instance_variable_set var_name, value }
|
75
|
+
end
|
76
|
+
|
77
|
+
modul.instance_variable_set var_name, default_value if default_value
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define a thread global variable for the current instance with name
|
82
|
+
# _name_. If the value _value_ is given, it is used to initialize the
|
83
|
+
# variable.
|
84
|
+
def instance_thread_global(name, value = nil)
|
85
|
+
sc = class << self
|
86
|
+
extend Tins::ThreadGlobal
|
87
|
+
self
|
88
|
+
end
|
89
|
+
sc.thread_global name, value
|
90
|
+
self
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module InstanceExec
|
95
|
+
unless (Object.instance_method(:instance_exec) rescue nil)
|
96
|
+
class << self
|
97
|
+
attr_accessor :pool
|
98
|
+
attr_accessor :count
|
99
|
+
end
|
100
|
+
self.count = 0
|
101
|
+
self.pool = []
|
102
|
+
|
103
|
+
# This is a pure ruby implementation of Ruby 1.9's instance_exec method. It
|
104
|
+
# executes _block_ in the context of this object while parsing <i>*args</i> into
|
105
|
+
# the block.
|
106
|
+
def instance_exec(*args, &block)
|
107
|
+
instance = self
|
108
|
+
id = instance_exec_fetch_symbol
|
109
|
+
InstanceExec.module_eval do
|
110
|
+
begin
|
111
|
+
define_method id, block
|
112
|
+
instance.__send__ id, *args
|
113
|
+
ensure
|
114
|
+
remove_method id if method_defined?(id)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
ensure
|
118
|
+
InstanceExec.pool << id
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
@@mutex = Mutex.new
|
124
|
+
|
125
|
+
# Fetch a symbol from a pool in thread save way. If no more symbols are
|
126
|
+
# available create a new one, that will be pushed into the pool later.
|
127
|
+
def instance_exec_fetch_symbol
|
128
|
+
@@mutex.synchronize do
|
129
|
+
if InstanceExec.pool.empty?
|
130
|
+
InstanceExec.count += 1
|
131
|
+
symbol = :"__instance_exec_#{InstanceExec.count}__"
|
132
|
+
else
|
133
|
+
symbol = InstanceExec.pool.shift
|
134
|
+
end
|
135
|
+
return symbol
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module Interpreter
|
142
|
+
include InstanceExec
|
143
|
+
|
144
|
+
# Interpret the string _source_ as a body of a block, while passing
|
145
|
+
# <i>*args</i> into the block.
|
146
|
+
#
|
147
|
+
# A small example explains how the method is supposed to be used and how
|
148
|
+
# the <i>*args</i> can be fetched:
|
149
|
+
#
|
150
|
+
# class A
|
151
|
+
# include Tins::Interpreter
|
152
|
+
# def c
|
153
|
+
# 3
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# A.new.interpret('|a,b| a + b + c', 1, 2) # => 6
|
158
|
+
#
|
159
|
+
# To use a specified binding see #interpret_with_binding.
|
160
|
+
def interpret(source, *args)
|
161
|
+
interpret_with_binding(source, binding, *args)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Interpret the string _source_ as a body of a block, while passing
|
165
|
+
# <i>*args</i> into the block and using _my_binding_ for evaluation.
|
166
|
+
#
|
167
|
+
# A small example:
|
168
|
+
#
|
169
|
+
# class A
|
170
|
+
# include Tins::Interpreter
|
171
|
+
# def c
|
172
|
+
# 3
|
173
|
+
# end
|
174
|
+
# def foo
|
175
|
+
# b = 2
|
176
|
+
# interpret_with_binding('|a| a + b + c', binding, 1) # => 6
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
# A.new.foo # => 6
|
180
|
+
#
|
181
|
+
# See also #interpret.
|
182
|
+
def interpret_with_binding(source, my_binding, *args)
|
183
|
+
path = '(interpret)'
|
184
|
+
if source.respond_to? :to_io
|
185
|
+
path = source.path if source.respond_to? :path
|
186
|
+
source = source.to_io.read
|
187
|
+
end
|
188
|
+
block = lambda { |*a| eval("lambda { #{source} }", my_binding, path).call(*a) }
|
189
|
+
instance_exec(*args, &block)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# This module contains the _constant_ method. For small example of its usage
|
194
|
+
# see the documentation of the DSLAccessor module.
|
195
|
+
module Constant
|
196
|
+
# Create a constant named _name_, that refers to value _value_. _value is
|
197
|
+
# frozen, if this is possible. If you want to modify/exchange a value use
|
198
|
+
# DSLAccessor#dsl_reader/DSLAccessor#dsl_accessor instead.
|
199
|
+
def constant(name, value = name)
|
200
|
+
value = value.freeze rescue value
|
201
|
+
define_method(name) { value }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# The DSLAccessor module contains some methods, that can be used to make
|
206
|
+
# simple accessors for a DSL.
|
207
|
+
#
|
208
|
+
#
|
209
|
+
# class CoffeeMaker
|
210
|
+
# extend Tins::Constant
|
211
|
+
#
|
212
|
+
# constant :on
|
213
|
+
# constant :off
|
214
|
+
#
|
215
|
+
# extend Tins::DSLAccessor
|
216
|
+
#
|
217
|
+
# dsl_accessor(:state) { off } # Note: the off constant from above is used
|
218
|
+
#
|
219
|
+
# dsl_accessor :allowed_states, :on, :off
|
220
|
+
#
|
221
|
+
# def process
|
222
|
+
# allowed_states.include?(state) or fail "Explode!!!"
|
223
|
+
# if state == on
|
224
|
+
# puts "Make coffee."
|
225
|
+
# else
|
226
|
+
# puts "Idle..."
|
227
|
+
# end
|
228
|
+
# end
|
229
|
+
# end
|
230
|
+
#
|
231
|
+
# cm = CoffeeMaker.new
|
232
|
+
# cm.instance_eval do
|
233
|
+
# state # => :off
|
234
|
+
# state on
|
235
|
+
# state # => :on
|
236
|
+
# process # => outputs "Make coffee."
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# Note that Tins::SymbolMaker is an alternative for Tins::Constant in
|
240
|
+
# this example. On the other hand SymbolMaker can make debugging more
|
241
|
+
# difficult.
|
242
|
+
module DSLAccessor
|
243
|
+
# This method creates a dsl accessor named _name_. If nothing else is given
|
244
|
+
# as argument it defaults to nil. If <i>*default</i> is given as a single
|
245
|
+
# value it is used as a default value, if more than one value is given the
|
246
|
+
# _default_ array is used as the default value. If no default value but a
|
247
|
+
# block _block_ is given as an argument, the block is executed everytime
|
248
|
+
# the accessor is read <b>in the context of the current instance</b>.
|
249
|
+
#
|
250
|
+
# After setting up the accessor, the set or default value can be retrieved
|
251
|
+
# by calling the method +name+. To set a value one can call <code>name
|
252
|
+
# :foo</code> to set the attribute value to <code>:foo</code> or
|
253
|
+
# <code>name(:foo, :bar)</code> to set it to <code>[ :foo, :bar ]</code>.
|
254
|
+
def dsl_accessor(name, *default, &block)
|
255
|
+
variable = "@#{name}"
|
256
|
+
define_method(name) do |*args|
|
257
|
+
if args.empty?
|
258
|
+
result = instance_variable_get(variable)
|
259
|
+
if result.nil?
|
260
|
+
result = if default.empty?
|
261
|
+
block && instance_eval(&block)
|
262
|
+
elsif default.size == 1
|
263
|
+
default.first
|
264
|
+
else
|
265
|
+
default
|
266
|
+
end
|
267
|
+
instance_variable_set(variable, result)
|
268
|
+
result
|
269
|
+
else
|
270
|
+
result
|
271
|
+
end
|
272
|
+
else
|
273
|
+
instance_variable_set(variable, args.size == 1 ? args.first : args)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# This method creates a dsl reader accessor, that behaves exactly like a
|
279
|
+
# #dsl_accessor but can only be read not set.
|
280
|
+
def dsl_reader(name, *default, &block)
|
281
|
+
variable = "@#{name}"
|
282
|
+
define_method(name) do |*args|
|
283
|
+
if args.empty?
|
284
|
+
result = instance_variable_get(variable)
|
285
|
+
if result.nil?
|
286
|
+
if default.empty?
|
287
|
+
block && instance_eval(&block)
|
288
|
+
elsif default.size == 1
|
289
|
+
default.first
|
290
|
+
else
|
291
|
+
default
|
292
|
+
end
|
293
|
+
else
|
294
|
+
result
|
295
|
+
end
|
296
|
+
else
|
297
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# This module can be included in another module/class. It generates a symbol
|
304
|
+
# for every missing method that was called in the context of this
|
305
|
+
# module/class.
|
306
|
+
module SymbolMaker
|
307
|
+
# Returns a symbol (_id_) for every missing method named _id_.
|
308
|
+
def method_missing(id, *args)
|
309
|
+
if args.empty?
|
310
|
+
id
|
311
|
+
else
|
312
|
+
super
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# This module can be used to extend another module/class. It generates
|
318
|
+
# symbols for every missing constant under the namespace of this
|
319
|
+
# module/class.
|
320
|
+
module ConstantMaker
|
321
|
+
# Returns a symbol (_id_) for every missing constant named _id_.
|
322
|
+
def const_missing(id)
|
323
|
+
id
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
module BlankSlate
|
328
|
+
# Creates an anonymous blank slate class, that only responds to the methods
|
329
|
+
# <i>*ids</i>. ids can be Symbols, Strings, and Regexps that have to match
|
330
|
+
# the method name with #===.
|
331
|
+
def self.with(*ids)
|
332
|
+
opts = Hash === ids.last ? ids.pop : {}
|
333
|
+
ids = ids.map { |id| Regexp === id ? id : id.to_s }
|
334
|
+
klass = opts[:superclass] ? Class.new(opts[:superclass]) : Class.new
|
335
|
+
klass.instance_eval do
|
336
|
+
instance_methods.each do |m|
|
337
|
+
m = m.to_s
|
338
|
+
undef_method m unless m =~ /^(__|object_id)/ or ids.any? { |i| i === m }
|
339
|
+
end
|
340
|
+
end
|
341
|
+
klass
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# See examples/recipe.rb and examples/recipe2.rb how this works at the
|
346
|
+
# moment.
|
347
|
+
module Deflect
|
348
|
+
# The basic Deflect exception
|
349
|
+
class DeflectError < StandardError; end
|
350
|
+
|
351
|
+
class << self
|
352
|
+
extend Tins::ThreadLocal
|
353
|
+
|
354
|
+
# A thread local variable, that holds a DeflectorCollection instance for
|
355
|
+
# the current thread.
|
356
|
+
thread_local :deflecting
|
357
|
+
end
|
358
|
+
|
359
|
+
# A deflector is called with a _class_, a method _id_, and its
|
360
|
+
# <i>*args</i>.
|
361
|
+
class Deflector < Proc; end
|
362
|
+
|
363
|
+
# This class implements a collection of deflectors, to make them available
|
364
|
+
# by emulating Ruby's message dispatch.
|
365
|
+
class DeflectorCollection
|
366
|
+
def initialize
|
367
|
+
@classes = {}
|
368
|
+
end
|
369
|
+
|
370
|
+
# Add a new deflector _deflector_ for class _klass_ and method name _id_,
|
371
|
+
# and return self.
|
372
|
+
#
|
373
|
+
def add(klass, id, deflector)
|
374
|
+
k = @classes[klass]
|
375
|
+
k = @classes[klass] = {} unless k
|
376
|
+
k[id.to_s] = deflector
|
377
|
+
self
|
378
|
+
end
|
379
|
+
|
380
|
+
# Return true if messages are deflected for class _klass_ and method name
|
381
|
+
# _id_, otherwise return false.
|
382
|
+
def member?(klass, id)
|
383
|
+
!!(k = @classes[klass] and k.key?(id.to_s))
|
384
|
+
end
|
385
|
+
|
386
|
+
# Delete the deflecotor class _klass_ and method name _id_. Returns the
|
387
|
+
# deflector if any was found, otherwise returns true.
|
388
|
+
def delete(klass, id)
|
389
|
+
if k = @classes[klass]
|
390
|
+
d = k.delete id.to_s
|
391
|
+
@classes.delete klass if k.empty?
|
392
|
+
d
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Try to find a deflector for class _klass_ and method _id_ and return
|
397
|
+
# it. If none was found, return nil instead.
|
398
|
+
def find(klass, id)
|
399
|
+
klass.ancestors.find do |k|
|
400
|
+
if d = @classes[k] and d = d[id.to_s]
|
401
|
+
return d
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
@@sync = Sync.new
|
408
|
+
|
409
|
+
# Start deflecting method calls named _id_ to the _from_ class using the
|
410
|
+
# Deflector instance deflector.
|
411
|
+
def deflect_start(from, id, deflector)
|
412
|
+
@@sync.synchronize do
|
413
|
+
Deflect.deflecting ||= DeflectorCollection.new
|
414
|
+
Deflect.deflecting.member?(from, id) and
|
415
|
+
raise DeflectError, "#{from}##{id} is already deflected"
|
416
|
+
Deflect.deflecting.add(from, id, deflector)
|
417
|
+
from.class_eval do
|
418
|
+
define_method(id) do |*args|
|
419
|
+
if Deflect.deflecting and d = Deflect.deflecting.find(self.class, id)
|
420
|
+
d.call(self, id, *args)
|
421
|
+
else
|
422
|
+
super(*args)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Return true if method _id_ is deflected from class _from_, otherwise
|
430
|
+
# return false.
|
431
|
+
def self.deflect?(from, id)
|
432
|
+
Deflect.deflecting && Deflect.deflecting.member?(from, id)
|
433
|
+
end
|
434
|
+
|
435
|
+
# Return true if method _id_ is deflected from class _from_, otherwise
|
436
|
+
# return false.
|
437
|
+
def deflect?(from, id)
|
438
|
+
Deflect.deflect?(from, id)
|
439
|
+
end
|
440
|
+
|
441
|
+
# Start deflecting method calls named _id_ to the _from_ class using the
|
442
|
+
# Deflector instance deflector. After that yield to the given block and
|
443
|
+
# stop deflecting again.
|
444
|
+
def deflect(from, id, deflector)
|
445
|
+
@@sync.synchronize do
|
446
|
+
begin
|
447
|
+
deflect_start(from, id, deflector)
|
448
|
+
yield
|
449
|
+
ensure
|
450
|
+
deflect_stop(from, id)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# Stop deflection method calls named _id_ to class _from_.
|
456
|
+
def deflect_stop(from, id)
|
457
|
+
@@sync.synchronize do
|
458
|
+
Deflect.deflecting.delete(from, id) or
|
459
|
+
raise DeflectError, "#{from}##{id} is not deflected from"
|
460
|
+
from.instance_eval { remove_method id }
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# This module can be included into modules/classes to make the delegate
|
466
|
+
# method available.
|
467
|
+
module Delegate
|
468
|
+
# A method to easily delegate methods to an object, stored in an
|
469
|
+
# instance variable or returned by a method call.
|
470
|
+
#
|
471
|
+
# It's used like this:
|
472
|
+
# class A
|
473
|
+
# delegate :method_here, :@obj, :method_there
|
474
|
+
# end
|
475
|
+
# or:
|
476
|
+
# class A
|
477
|
+
# delegate :method_here, :method_call, :method_there
|
478
|
+
# end
|
479
|
+
#
|
480
|
+
# _other_method_name_ defaults to method_name, if it wasn't given.
|
481
|
+
def delegate(method_name, obj, other_method_name = method_name)
|
482
|
+
raise ArgumentError, "obj wasn't defined" unless obj
|
483
|
+
=begin
|
484
|
+
1.9 only:
|
485
|
+
define_method(method_name) do |*args, &block|
|
486
|
+
instance_variable_get(obj).__send__(other_method_name, *args, &block)
|
487
|
+
end
|
488
|
+
=end
|
489
|
+
obj = obj.to_s
|
490
|
+
if obj[0] == ?@
|
491
|
+
class_eval <<-EOS
|
492
|
+
def #{method_name}(*args, &block)
|
493
|
+
instance_variable_get('#{obj}').__send__(
|
494
|
+
'#{other_method_name}', *args, &block)
|
495
|
+
end
|
496
|
+
EOS
|
497
|
+
else
|
498
|
+
class_eval <<-EOS
|
499
|
+
def #{method_name}(*args, &block)
|
500
|
+
__send__('#{obj}').__send__(
|
501
|
+
'#{other_method_name}', *args, &block)
|
502
|
+
end
|
503
|
+
EOS
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# This module includes the block_self module_function.
|
509
|
+
module BlockSelf
|
510
|
+
module_function
|
511
|
+
|
512
|
+
# This method returns the receiver _self_ of the context in which _block_
|
513
|
+
# was created.
|
514
|
+
def block_self(&block)
|
515
|
+
eval 'self', block.__send__(:binding)
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# This module contains a configurable method missing delegator and can be
|
520
|
+
# mixed into a module/class.
|
521
|
+
module MethodMissingDelegator
|
522
|
+
|
523
|
+
# Including this module in your classes makes an _initialize_ method
|
524
|
+
# available, whose first argument is used as method_missing_delegator
|
525
|
+
# attribute. If a superior _initialize_ method was defined it is called
|
526
|
+
# with all arguments but the first.
|
527
|
+
module DelegatorModule
|
528
|
+
include Tins::MethodMissingDelegator
|
529
|
+
|
530
|
+
def initialize(delegator, *a, &b)
|
531
|
+
self.method_missing_delegator = delegator
|
532
|
+
super(*a, &b) if defined? super
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
# This class includes DelegatorModule and can be used as a superclass
|
537
|
+
# instead of including DelegatorModule.
|
538
|
+
class DelegatorClass
|
539
|
+
include DelegatorModule
|
540
|
+
end
|
541
|
+
|
542
|
+
# This object will be the receiver of all missing method calls, if it has a
|
543
|
+
# value other than nil.
|
544
|
+
attr_accessor :method_missing_delegator
|
545
|
+
|
546
|
+
# Delegates all missing method calls to _method_missing_delegator_ if this
|
547
|
+
# attribute has been set. Otherwise it will call super.
|
548
|
+
def method_missing(id, *a, &b)
|
549
|
+
unless method_missing_delegator.nil?
|
550
|
+
method_missing_delegator.__send__(id, *a, &b)
|
551
|
+
else
|
552
|
+
super
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
module ParameterizedModule
|
558
|
+
# Pass _args_ and _block_ to configure the module and then return it after
|
559
|
+
# calling the parameterize method has been called with these arguments. The
|
560
|
+
# _parameterize_ method should return a configured module.
|
561
|
+
def parameterize_for(*args, &block)
|
562
|
+
respond_to?(:parameterize) ? parameterize(*args, &block) : self
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
module FromModule
|
567
|
+
include ParameterizedModule
|
568
|
+
|
569
|
+
alias from parameterize_for
|
570
|
+
|
571
|
+
def parameterize(opts = {})
|
572
|
+
modul = opts[:module] or raise ArgumentError, 'option :module is required'
|
573
|
+
import_methods = Array(opts[:methods])
|
574
|
+
result = modul.dup
|
575
|
+
remove_methods = modul.instance_methods.map(&:to_sym) - import_methods.map(&:to_sym)
|
576
|
+
remove_methods.each do |m|
|
577
|
+
begin
|
578
|
+
result.__send__ :remove_method, m
|
579
|
+
rescue NameError
|
580
|
+
end
|
581
|
+
end
|
582
|
+
result
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
module Scope
|
587
|
+
def scope_push(scope_frame, name = :default)
|
588
|
+
scope_get(name).push scope_frame
|
589
|
+
self
|
590
|
+
end
|
591
|
+
|
592
|
+
def scope_pop(name = :default)
|
593
|
+
scope_get(name).pop
|
594
|
+
scope_get(name).empty? and Thread.current[name] = nil
|
595
|
+
self
|
596
|
+
end
|
597
|
+
|
598
|
+
def scope_top(name = :default)
|
599
|
+
scope_get(name).last
|
600
|
+
end
|
601
|
+
|
602
|
+
def scope_reverse(name = :default, &block)
|
603
|
+
scope_get(name).reverse_each(&block)
|
604
|
+
end
|
605
|
+
|
606
|
+
def scope_block(scope_frame, name = :default)
|
607
|
+
scope_push(scope_frame, name)
|
608
|
+
yield
|
609
|
+
self
|
610
|
+
ensure
|
611
|
+
scope_pop(name)
|
612
|
+
end
|
613
|
+
|
614
|
+
def scope_get(name = :default)
|
615
|
+
Thread.current[name] ||= []
|
616
|
+
end
|
617
|
+
|
618
|
+
def scope(name = :default)
|
619
|
+
scope_get(name).dup
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
module DynamicScope
|
624
|
+
class Context < Hash
|
625
|
+
def [](name)
|
626
|
+
super name.to_sym
|
627
|
+
end
|
628
|
+
|
629
|
+
def []=(name, value)
|
630
|
+
super name.to_sym, value
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
include Scope
|
635
|
+
|
636
|
+
attr_accessor :dynamic_scope_name
|
637
|
+
|
638
|
+
def dynamic_defined?(id)
|
639
|
+
self.dynamic_scope_name ||= :variables
|
640
|
+
scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return true }
|
641
|
+
false
|
642
|
+
end
|
643
|
+
|
644
|
+
def dynamic_scope(&block)
|
645
|
+
self.dynamic_scope_name ||= :variables
|
646
|
+
scope_block(Context.new, dynamic_scope_name, &block)
|
647
|
+
end
|
648
|
+
|
649
|
+
def method_missing(id, *args)
|
650
|
+
self.dynamic_scope_name ||= :variables
|
651
|
+
if args.empty? and scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return c[id] }
|
652
|
+
super
|
653
|
+
elsif args.size == 1 and id.to_s =~ /(.*?)=\Z/
|
654
|
+
c = scope_top(dynamic_scope_name) or super
|
655
|
+
c[$1] = args.first
|
656
|
+
else
|
657
|
+
super
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
DSLKit = Tins
|