maroon 0.6.1 → 0.6.5
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 +7 -0
- data/Examples/Dijkstra/CalculateShortestDistance.rb +16 -12
- data/Examples/Dijkstra/calculate_shortest_path.rb +47 -27
- data/Examples/Dijkstra/data.rb +41 -14
- data/Examples/Dijkstra/dijkstra.rb +4 -3
- data/Examples/MoneyTransfer.rb +61 -60
- data/Examples/greeter.rb +8 -7
- data/Examples/meter.rb +35 -29
- data/Gemfile +9 -4
- data/LICENSE.txt +21 -21
- data/README.md +13 -0
- data/Rakefile +41 -1
- data/Test/Generate/method_info_test.rb +12 -0
- data/Test/{Greeter_test.rb → Greeter_test_disabled.rb} +24 -20
- data/Test/ImmutableQueue_test.rb +18 -0
- data/Test/MethodInfo_test.rb +65 -0
- data/Test/alltests.rb +1 -0
- data/Test/{source_assertions.rb → assertions.rb} +15 -7
- data/Test/bind_test.rb +13 -0
- data/Test/expression_test.rb +105 -0
- data/Test/method_call_test.rb +83 -0
- data/Test/self_test.rb +46 -0
- data/Test/stack_test.rb +17 -0
- data/Test/test_helper.rb +14 -0
- data/base/ImmutableStack.rb +32 -0
- data/base/MethodDefinition.rb +124 -0
- data/base/bind_rewriter.rb +58 -0
- data/base/immutable_queue.rb +50 -0
- data/base/maroon_base.rb +267 -0
- data/base/method_call.rb +78 -0
- data/base/method_info.rb +86 -0
- data/base/self.rb +60 -0
- data/generated/build.rb +13 -0
- data/generated/interpretation_context.rb +29 -0
- data/lib/Bind.rb +65 -0
- data/lib/Context.rb +187 -0
- data/lib/ImmutableQueue.rb +50 -0
- data/lib/ImmutableStack.rb +38 -0
- data/lib/MethodCall.rb +91 -0
- data/lib/MethodDefinition.rb +114 -0
- data/lib/MethodInfo.rb +78 -0
- data/lib/Self.rb +71 -0
- data/lib/build.rb +14 -0
- data/lib/interpretation_context.rb +30 -0
- data/lib/maroon/contracts.rb +43 -0
- data/lib/maroon/kernel.rb +2 -0
- data/lib/maroon/version.rb +3 -3
- data/maroon.gemspec +26 -26
- metadata +49 -31
- data/lib/Source_cleaner.rb +0 -34
- data/lib/maroon.rb +0 -165
- data/lib/rewriter.rb +0 -185
@@ -0,0 +1,58 @@
|
|
1
|
+
context :Bind, :execute do
|
2
|
+
role :block do
|
3
|
+
end
|
4
|
+
role :local do
|
5
|
+
end
|
6
|
+
role :aliased_role do
|
7
|
+
end
|
8
|
+
initialize do |local, aliased_role, block|
|
9
|
+
@local = local
|
10
|
+
@aliased_role = aliased_role
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
##
|
14
|
+
#removes call to bind in a block
|
15
|
+
#and replaces it with assignment to the proper role player local variables
|
16
|
+
#in the end of the block the local variables have their original values reassigned
|
17
|
+
execute do
|
18
|
+
must_b_sym = 'aliased_role must be a Symbol'.to_sym
|
19
|
+
local_must_b_sym = 'local must be a Symbol'.to_sym
|
20
|
+
raise must_b_sym unless aliased_role.instance_of? Symbol
|
21
|
+
raise local_must_b_sym unless local.instance_of? Symbol
|
22
|
+
# assigning role player to role field
|
23
|
+
#notice that this will be executed after the next block
|
24
|
+
aliased_field = ('@' + aliased_role.to_s).to_sym
|
25
|
+
temp_symbol = ('temp____' + aliased_role.to_s).to_sym
|
26
|
+
|
27
|
+
assignment = Sexp.new
|
28
|
+
assignment[0] = :iasgn
|
29
|
+
assignment[1] = aliased_field
|
30
|
+
load_arg = Sexp.new
|
31
|
+
load_arg[0] = :lvar
|
32
|
+
load_arg[1] = local
|
33
|
+
assignment[2] = load_arg
|
34
|
+
block.insert 1, assignment
|
35
|
+
|
36
|
+
# assign role player to temp
|
37
|
+
# notice this is prepended Ie. inserted in front of the role player to role field
|
38
|
+
|
39
|
+
assignment = Sexp.new
|
40
|
+
assignment[0] = :lasgn
|
41
|
+
assignment[1] = temp_symbol
|
42
|
+
load_field = Sexp.new
|
43
|
+
load_field[0] = :ivar
|
44
|
+
load_field[1] = aliased_field
|
45
|
+
assignment[2] = load_field
|
46
|
+
block.insert 1, assignment
|
47
|
+
|
48
|
+
# reassign original player
|
49
|
+
assignment = Sexp.new
|
50
|
+
assignment[0] = :iasgn
|
51
|
+
assignment[1] = aliased_field
|
52
|
+
load_temp = Sexp.new
|
53
|
+
load_temp[0] = :lvar
|
54
|
+
load_temp[1] = temp_symbol
|
55
|
+
assignment[2] = load_temp
|
56
|
+
block[block.length] = assignment
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
context :ImmutableQueue do
|
2
|
+
role :front do
|
3
|
+
end
|
4
|
+
role :back do
|
5
|
+
end
|
6
|
+
push do |element|
|
7
|
+
b = back || ImmutableStack::empty
|
8
|
+
ImmutableQueue.new(front, b.push(element))
|
9
|
+
end
|
10
|
+
|
11
|
+
pop do
|
12
|
+
f, b = front, back
|
13
|
+
if f == ImmutableStack::empty
|
14
|
+
#reverse the back stack to be able to pop from the front in correct order
|
15
|
+
until b == ImmutableStack::empty
|
16
|
+
e, b = b.pop
|
17
|
+
f = f.push(e)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
head, f = f.pop
|
21
|
+
if f == b #can only happen if they are both ImmutableStack::empty
|
22
|
+
[head, ImmutableQueue::empty]
|
23
|
+
else
|
24
|
+
[head, ImmutableQueue.new(f, b)]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
empty true do
|
29
|
+
@@empty ||= ImmutableQueue.new(ImmutableStack::empty, ImmutableStack::empty)
|
30
|
+
end
|
31
|
+
|
32
|
+
push_array do |arr|
|
33
|
+
q = self
|
34
|
+
if arr
|
35
|
+
arr.each do |i|
|
36
|
+
q = q.push i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
q
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
initialize do |front, back|
|
45
|
+
@front = front || ImmutableStack::empty
|
46
|
+
@back = back || ImmutableStack::empty
|
47
|
+
self.freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/base/maroon_base.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
##
|
4
|
+
# The Context class is used to define a DCI context with roles and their role methods
|
5
|
+
# to define a context call define with the name of the context (this name will become the name of the class that defines the context)
|
6
|
+
# the name should be a symbol and since it's going to be used as a class name, use class naming conventions
|
7
|
+
# follow the name with a block. With in this block you can define roles and interactions
|
8
|
+
# and interaction is defined by write the name of the interaction (hello in the below example) followed by a block
|
9
|
+
# the block will become the method body
|
10
|
+
# a role can be defined much like a context. instead of calling the define method call the role method followed by the role name (as a symbol)
|
11
|
+
# the role will be used for a private instance variable and the naming convention should match this
|
12
|
+
# With in the block supplied to the role method you can define role methods the same way as you define interactions. See the method who
|
13
|
+
# in the below example
|
14
|
+
# = Example
|
15
|
+
# Context::define :Greeter do
|
16
|
+
# role :who do
|
17
|
+
# say do
|
18
|
+
# @who #could be self as well to refer to the current role player of the 'who' role
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# greeting do
|
22
|
+
# p "Hello #{who.say}!"
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class Greeter
|
27
|
+
# def initialize(player)
|
28
|
+
# #@who = player
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Greeter.new('world').greeting #Will print "Hello world!"
|
33
|
+
#maroon is base on Marvin which was the first injectionless language for DCI
|
34
|
+
#being injectionless there's no runtime extend or anything else impacting the performance. There' only regular method invocation even when using role methods
|
35
|
+
#Author:: Rune Funch Søltoft (funchsoltoft@gmail.com)
|
36
|
+
#License:: Same as for Ruby
|
37
|
+
##
|
38
|
+
Context::generate_files_in 'generated'
|
39
|
+
context :Context do
|
40
|
+
role :roles do
|
41
|
+
end
|
42
|
+
role :interactions do
|
43
|
+
end
|
44
|
+
role :defining_role do
|
45
|
+
end
|
46
|
+
role :role_alias do
|
47
|
+
end
|
48
|
+
role :alias_list do
|
49
|
+
end
|
50
|
+
role :cached_roles_and_alias_list do
|
51
|
+
end
|
52
|
+
|
53
|
+
#define is the only exposed method and can be used to define a context (class)
|
54
|
+
#if maroon/kernel is required calling context of Context::define are equivalent
|
55
|
+
#params
|
56
|
+
#name:: the name of the context. Since this is used as the name of a class, class naming convetions should be used
|
57
|
+
#block:: the body of the context. Can include definitions of roles (through the role method) or definitions of interactions
|
58
|
+
#by simply calling a method with the name of the interaction and passing a block as the body of the interaction
|
59
|
+
define :block => :block, :self => self do |*args|
|
60
|
+
@@with_contracts ||= nil
|
61
|
+
@@generate_file_path ||= nil
|
62
|
+
alias method_missing role_or_interaction_method
|
63
|
+
base_class, ctx, default_interaction, name = self.send(:create_context_factory, args, block)
|
64
|
+
ctx.generate_files_in(args.last()) if args.last().instance_of? FalseClass or args.last().instance_of? TrueClass
|
65
|
+
return ctx.send(:finalize, name, base_class, default_interaction)
|
66
|
+
end
|
67
|
+
|
68
|
+
generate_files_in :block => :b,:self => self do |*args|
|
69
|
+
return role_or_interaction_method(:generate_files_in, *args, &b) if block_given?
|
70
|
+
|
71
|
+
@@generate_file_path = args[0]
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
with_contracts self do |*args|
|
76
|
+
return @@with_contracts if args.length == 0
|
77
|
+
value = args[0]
|
78
|
+
raise 'make up your mind! disabling contracts during execution will result in undefined behavior' if @@with_contracts && !value
|
79
|
+
@@with_contracts = value
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
#Defines a role with the given name
|
84
|
+
#role methods can be defined inside a block passed to this method
|
85
|
+
# = Example
|
86
|
+
# role :who do
|
87
|
+
# say do
|
88
|
+
# p @who
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#The above code defines a role called 'who' with a role method called say
|
92
|
+
##
|
93
|
+
role :block => :b do |*args|
|
94
|
+
role_name = args[0]
|
95
|
+
return role_or_interaction_method(:role, *args, &b) if args.length != 1 or not (role_name.instance_of? Symbol)
|
96
|
+
|
97
|
+
@defining_role = role_name
|
98
|
+
@roles[role_name] = Hash.new
|
99
|
+
yield if block_given?
|
100
|
+
@defining_role = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
initialize :block => :b do |*args|
|
104
|
+
if block_given?
|
105
|
+
role_or_interaction_method(:initialize, *args, &b)
|
106
|
+
else
|
107
|
+
@roles = Hash.new
|
108
|
+
@interactions = Hash.new
|
109
|
+
@role_alias = Hash.new
|
110
|
+
@contracts = Hash.new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
role_or_interaction_method(:private) do
|
115
|
+
@private = true
|
116
|
+
end
|
117
|
+
|
118
|
+
current_interpretation_context :block => :b do |*args|
|
119
|
+
return role_or_interaction_method(:current_interpretation_context, *args, &b) if block_given?
|
120
|
+
InterpretationContext.new(roles, contracts, role_alias, nil)
|
121
|
+
end
|
122
|
+
|
123
|
+
get_methods :block => :b do |*args|
|
124
|
+
return role_or_interaction_method(:get_methods, *args, &b) if block_given?
|
125
|
+
name = args[0]
|
126
|
+
sources = (@defining_role ? (@roles[@defining_role]) : (@interactions))[name]
|
127
|
+
if @defining_role && !sources
|
128
|
+
@roles[@defining_role][name] = []
|
129
|
+
else
|
130
|
+
(@interactions)[name] = []
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
role :contracts do
|
135
|
+
end
|
136
|
+
|
137
|
+
add_method :block => :b do |*args|
|
138
|
+
return role_or_interaction_method(:add_method, *args, &b) if block_given?
|
139
|
+
name, method = *args
|
140
|
+
sources = get_methods(name)
|
141
|
+
sources << method
|
142
|
+
end
|
143
|
+
|
144
|
+
finalize :block => :b do |*args|
|
145
|
+
return role_or_interaction_method(:finalize, *args, &b) if block_given?
|
146
|
+
name, base_class, default = *args
|
147
|
+
|
148
|
+
code = generate_context_code(default, name)
|
149
|
+
|
150
|
+
if @@generate_file_path
|
151
|
+
name = name.to_s
|
152
|
+
complete = 'class ' + name + (base_class ? '<< ' + base_class.name : '') + '
|
153
|
+
' + code.to_s + '
|
154
|
+
end'
|
155
|
+
File.open('./' + @@generate_file_path.to_s + '/' + name + '.rb', 'w') { |f| f.write(complete) }
|
156
|
+
complete
|
157
|
+
else
|
158
|
+
|
159
|
+
c = base_class ? (Class.new base_class) : Class.new
|
160
|
+
if @@with_contracts
|
161
|
+
c.class_eval('def self.assert_that(obj)
|
162
|
+
ContextAsserter.new(self.contracts,obj)
|
163
|
+
end
|
164
|
+
def self.refute_that(obj)
|
165
|
+
ContextAsserter.new(self.contracts,obj,false)
|
166
|
+
end
|
167
|
+
def self.contracts
|
168
|
+
@@contracts
|
169
|
+
end
|
170
|
+
def self.contracts=(value)
|
171
|
+
raise \'Contracts must be supplied\' unless value
|
172
|
+
@@contracts = value
|
173
|
+
end')
|
174
|
+
c.contracts=contracts
|
175
|
+
end
|
176
|
+
Kernel.const_set name, c
|
177
|
+
temp = c.class_eval(code)
|
178
|
+
(temp || c)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
create_context_factory self do |args, block|
|
183
|
+
name, base_class, default_interaction = *args
|
184
|
+
#if there's two arguments and the second is not a class it must be an interaction
|
185
|
+
if default_interaction && (!base_class.instance_of? Class) then
|
186
|
+
base_class = eval(base_class.to_s)
|
187
|
+
end
|
188
|
+
base_class, default_interaction = default_interaction, base_class if base_class and !default_interaction and !base_class.instance_of? Class
|
189
|
+
ctx = Context.new
|
190
|
+
ctx.instance_eval &block
|
191
|
+
return base_class, ctx, default_interaction, name
|
192
|
+
end
|
193
|
+
|
194
|
+
generate_context_code :block => :b do |*args|
|
195
|
+
return role_or_interaction_method(:generate_context_code, *args, &b) if block_given?
|
196
|
+
default, name = args
|
197
|
+
|
198
|
+
getters = ''
|
199
|
+
impl = ''
|
200
|
+
interactions = ''
|
201
|
+
@interactions.each do |method_name, methods|
|
202
|
+
methods.each do |method|
|
203
|
+
@defining_role = nil
|
204
|
+
code = ' ' + (method.build_as_context_method method_name, current_interpretation_context)
|
205
|
+
if method.is_private
|
206
|
+
getters << code
|
207
|
+
else
|
208
|
+
interactions << code
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
if default
|
214
|
+
interactions << '
|
215
|
+
def self.call(*args)
|
216
|
+
arity = ' + name.to_s + '.method(:new).arity
|
217
|
+
newArgs = args[0..arity-1]
|
218
|
+
obj = ' + name.to_s + '.new *newArgs
|
219
|
+
if arity < args.length
|
220
|
+
methodArgs = args[arity..-1]
|
221
|
+
obj.' + default.to_s + ' *methodArgs
|
222
|
+
else
|
223
|
+
obj.' + default.to_s + '
|
224
|
+
end
|
225
|
+
end
|
226
|
+
'
|
227
|
+
interactions << '
|
228
|
+
def call(*args);' + default.to_s + ' *args; end
|
229
|
+
'
|
230
|
+
end
|
231
|
+
|
232
|
+
@roles.each do |role, methods|
|
233
|
+
getters << 'attr_reader :' + role.to_s + '
|
234
|
+
'
|
235
|
+
|
236
|
+
methods.each do |method_name, method_sources|
|
237
|
+
raise 'Duplicate definition of ' + method_name.to_s unless method_sources.length < 2
|
238
|
+
raise 'No source for ' + method_name.to_s unless method_sources.length > 0
|
239
|
+
|
240
|
+
method_source = method_sources[0]
|
241
|
+
@defining_role = role
|
242
|
+
rewritten_method_name = 'self_' + role.to_s + '_' + method_name.to_s
|
243
|
+
definition = method_source.build_as_context_method rewritten_method_name, current_interpretation_context
|
244
|
+
(impl << ' ' + definition.to_s) if definition
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
interactions + '
|
250
|
+
private
|
251
|
+
' + getters + '
|
252
|
+
' + impl + '
|
253
|
+
'
|
254
|
+
end
|
255
|
+
|
256
|
+
role_or_interaction_method({:block => :b}) do |*arguments|
|
257
|
+
method_name, on_self = *arguments
|
258
|
+
unless method_name.instance_of? Symbol
|
259
|
+
on_self = method_name
|
260
|
+
method_name = :role_or_interaction_method
|
261
|
+
end
|
262
|
+
|
263
|
+
raise 'Method with out block ' + method_name.to_s unless block_given?
|
264
|
+
|
265
|
+
add_method(method_name, MethodInfo.new(on_self, b.to_sexp, @private))
|
266
|
+
end
|
267
|
+
end
|
data/base/method_call.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative '../lib/maroon/kernel'
|
2
|
+
context :MethodCall, :rewrite_call? do
|
3
|
+
role :interpretation_context do
|
4
|
+
contracts {
|
5
|
+
@interpretation_context.contracts || {}
|
6
|
+
}
|
7
|
+
roles {
|
8
|
+
@interpretation_context.roles || {}
|
9
|
+
}
|
10
|
+
role_aliases {
|
11
|
+
@interpretation_context.role_aliases || {}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
role :method do
|
16
|
+
call_in_block? {
|
17
|
+
@in_block unless @in_block == nil
|
18
|
+
(@in_block = (method && method[0] == :lvar))
|
19
|
+
}
|
20
|
+
|
21
|
+
get_role_definition {
|
22
|
+
is_call_expression = method && method[0] == :call
|
23
|
+
self_is_instance_expression = (is_call_expression && !method[1])
|
24
|
+
role_name = nil
|
25
|
+
role_name = method[2] if self_is_instance_expression
|
26
|
+
if (not self_is_instance_expression) and method[1]
|
27
|
+
role_name = method[1][2] if method[1][1] == nil and method[1][0] == :call #call role field is instance
|
28
|
+
role_name = interpretation_context.role_aliases[method[1][1]] if method[1][0] == :lvar #local var potentially bound
|
29
|
+
end
|
30
|
+
role = role_name ? interpretation_context.roles[role_name] : nil
|
31
|
+
[role, (role ? role_name : nil)]
|
32
|
+
}
|
33
|
+
|
34
|
+
role_method_call? { |method_name|
|
35
|
+
|
36
|
+
return nil, nil unless method
|
37
|
+
|
38
|
+
role, role_name = method.get_role_definition #is it a call to a role getter
|
39
|
+
|
40
|
+
#in_block = method.call_in_block?
|
41
|
+
#role_name = interpretation_context.role_aliases[role_name] if in_block
|
42
|
+
is_role_method = role && role.has_key?(method_name)
|
43
|
+
|
44
|
+
return role_name, is_role_method
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rewrite_call? do
|
49
|
+
method_name = method[2]
|
50
|
+
if method[0] == :call
|
51
|
+
if method[1] == nil && method.length < 5 && method[3] && method[3].length == 1 && method[3][0] == :arglist
|
52
|
+
#accessing a role field?
|
53
|
+
is_role = interpretation_context.roles.has_key? method[3]
|
54
|
+
method[3] = ':@' + method[3].to_sym if is_role
|
55
|
+
else
|
56
|
+
role_name, is_role_method = method.role_method_call? method_name
|
57
|
+
if is_role_method #role_name only returned if it's a role method call
|
58
|
+
method[1] = nil #remove call to attribute
|
59
|
+
method[2] = ('self_' + role_name.to_s + '_' + method_name.to_s).to_sym
|
60
|
+
else # it's an instance method invocation
|
61
|
+
if interpretation_context.roles.has_key? role_name
|
62
|
+
unless method.length == 3 && method[1] == nil && method[2] == role_name
|
63
|
+
contract_methods = (interpretation_context.contracts[role_name] ||= {})
|
64
|
+
contract_methods[method_name] ||= 0
|
65
|
+
contract_methods[method_name] += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
initialize do |method, interpretation_context|
|
73
|
+
raise 'No method supplied' unless method
|
74
|
+
|
75
|
+
@method = method
|
76
|
+
@interpretation_context = interpretation_context
|
77
|
+
end
|
78
|
+
end
|