maroon 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,4 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require 'live_ast'
3
- require 'live_ast/to_ruby'
4
2
  #
5
3
  # Consider street corners on a Manhattan grid. We want to find the
6
4
  # minimal path from the most northeast city to the most
@@ -352,5 +350,5 @@ class CalculateShortestPath
352
350
  end
353
351
  end
354
352
 
355
- File.open('CalculateShortestPath_generated.rb', 'w') {|f| f.write(source) }
353
+ File.open('CalculateShortestPath_generated.rb', 'w') {|f| f.write(source) }
356
354
 
@@ -1,8 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require '../../Lib/maroon.rb'
3
- require './data.rb'
4
- require './CalculateShortestDistance.rb'
5
- require './Calculate_Shortest_Path.rb'
2
+ require './Lib/maroon.rb'
3
+ require './Examples/Dijkstra/data.rb'
4
+ require './Examples/Dijkstra/CalculateShortestDistance.rb'
5
+ require './Examples/Dijkstra/Calculate_Shortest_Path.rb'
6
6
  #!/usr/bin/env ruby
7
7
  # Example in Ruby -- Dijkstra's algorithm in DCI
8
8
  # Modified and simplified for a Manhattan geometry with 8 roles
@@ -1,4 +1,4 @@
1
- require '../lib/maroon.rb'
1
+ require './lib/maroon.rb'
2
2
 
3
3
  Context::define :MoneyTransfer do
4
4
  role :source do
@@ -1,12 +1,91 @@
1
- require "test/unit"
2
- require '../lib/maroon.rb'
3
- require '../lib/maroon/kernel.rb'
1
+ require 'test/unit'
2
+ require './lib/maroon.rb'
3
+ require './lib/maroon/kernel.rb'
4
+ require 'ripper'
5
+ require './Test/source_assertions.rb'
6
+
7
+
8
+ class BasicTests < Test::Unit::TestCase
9
+ include Source_assertions
10
+
11
+ def test_define_context
12
+ name = :MyContext
13
+ ctx, source = Context::define name do
14
+ end
15
+ assert_equal(ctx.name, "Kernel::#{name}")
16
+ assert_equal(source, "class #{name}\r\n\n\n private\n\n\n\r\nend")
17
+ end
18
+
19
+ def test_base_class
20
+ name = :MyDerivedContext
21
+ ctx,source = context name, Person do
22
+ end
23
+ obj = MyDerivedContext.new
24
+ obj.name = name
25
+ assert_equal(ctx.name, "Kernel::#{name}")
26
+ assert_not_nil(obj.name)
27
+ assert((obj.class < Person), 'Object is not a Person')
28
+ assert_equal(name, obj.name)
29
+ end
30
+
31
+ def test_define_role
32
+ name, role_name = :MyContextWithRole, :my_role
33
+ ctx, source = Context::define name do
34
+ role role_name do
35
+ role_go_do do
36
+
37
+ end
38
+ end
39
+ end
40
+ assert_not_nil(ctx)
41
+ assert_equal(ctx.name, "Kernel::#{name}")
42
+ assert_source_equal("class #{name}\r\n\n@#{role_name}\n\n private\ndef #{role_name};@#{role_name} end\n\n \ndef self_#{role_name}_role_go_do \n end\n\n\r\nend", source)
43
+ end
44
+
45
+ def test_args_on_role_method
46
+ name, role_name = :MyContextWithRoleAndArgs, :my_role
47
+ ctx, source = Context::define name do
48
+ role role_name do
49
+ role_go_do do |x,y|
50
+
51
+ end
52
+ role_go do |x|
53
+
54
+ end
55
+ end
56
+ end
57
+ assert_not_nil(ctx)
58
+ assert_equal(ctx.name, "Kernel::#{name}")
59
+ assert_source_equal("class #{name}\r\n\n@#{role_name}\n\n private\ndef #{role_name};@#{role_name} end\n\n \ndef self_#{role_name}_role_go_do(x,y) \n end\n\ndef self_#{role_name}_role_go(x) \n end\n\n\r\nend", source)
60
+ end
61
+
62
+ def test_bind
63
+ name, other_name = :MyContextUsingBind, :other_role
64
+ ctx, source = Context::define name do
65
+ role other_name do
66
+ plus_one do
67
+ (self + 1)
68
+ end
69
+ end
70
+ go_do do
71
+ a = Array.new
72
+ [1, 2].each do |e|
73
+ bind e => :other_role
74
+ a << e.plus_one
75
+ end
76
+ a
77
+ end
78
+ end
79
+ arr = MyContextUsingBind.new.go_do
80
+ assert_not_nil(ctx)
81
+ assert_equal(ctx.name, "Kernel::#{name}")
82
+ expected = "class MyContextUsingBind\r\n \ndef go_do \na = Array.new\n [1, 2].each do |e|\n temp____other_role = @other_role\n @other_role = e\n (a << self_other_role_plus_one)\n @other_role = temp____other_role\n end\n a\n end\n\n@other_role\n\n private\ndef other_role;@other_role end\n\n \ndef self_other_role_plus_one \n(other_role + 1) end\n\n\r\nend"
83
+ assert_source_equal(expected, source)
84
+ assert_equal(2, arr[0])
85
+ assert_equal(3, arr[1])
86
+ end
87
+ end
4
88
 
5
- ##
6
- # General comment
7
- # it's hopeless to test for the actual source. It should be the syntax tree rather than the source
8
- # it's the semantics that are important not the formatted source!!
9
- ##
10
89
 
11
90
  context :Greet_Someone, :greet do
12
91
  role :greeter do
@@ -23,6 +102,23 @@ context :Greet_Someone, :greet do
23
102
  end
24
103
  end
25
104
 
105
+ context :Greet_Someone2, :greet do
106
+ role :greeter do
107
+ welcome do
108
+ self.greeting
109
+ end
110
+ end
111
+
112
+ role :greeted do
113
+ end
114
+
115
+ greet do |msg|
116
+ a = "#{greeter.name}: \"#{greeter.welcome}, #{greeted.name}!\" #{msg}"
117
+ p a
118
+ a
119
+ end
120
+ end
121
+
26
122
  class Person
27
123
  attr_accessor :name
28
124
  attr_accessor :greeting
@@ -36,73 +132,59 @@ class Greet_Someone
36
132
  end
37
133
  end
38
134
 
39
- class BasicTests < Test::Unit::TestCase
40
-
41
- def test_define_context
42
- name = :MyContext
43
- ctx,source = Context::define name do end
44
- assert_equal(ctx.name, "Kernel::#{name}")
45
- assert_equal(source,"class #{name}\r\n\n\n private\n\n\n\r\nend")
46
- end
47
-
48
- def test_define_role
49
- name,role_name = :MyContextWithRole,:my_role
50
- ctx,source = Context::define name do
51
- role role_name do
52
- role_go_do do
53
-
54
- end
55
- end
56
- end
57
- assert_not_nil(ctx)
58
- assert_equal(ctx.name, "Kernel::#{name}")
59
- assert_equal("class #{name}\r\n\n@#{role_name}\n\n private\ndef #{role_name};@#{role_name} end\n\n \ndef self_#{role_name}_role_go_do \n end\n\n\r\nend",source)
135
+ class Greet_Someone2
136
+ def initialize(greeter, greeted)
137
+ @greeter = greeter
138
+ @greeted = greeted
60
139
  end
140
+ end
61
141
 
62
- def test_bind
63
- name,role_name,other_name = :MyContextUsingBind,:my_role, :other_role
64
- ctx,source = Context::define name do
65
- role other_name do
66
- plus_one do
67
- (self + 1)
68
- end
69
- end
70
- go_do do
71
- a = Array.new
72
- [1,2].each do |e|
73
- bind e => :other_role
74
- a << e.plus_one
75
- end
76
- a
77
- end
78
- end
79
- arr = MyContextUsingBind.new.go_do
80
- assert_not_nil(ctx)
81
- assert_equal(ctx.name, "Kernel::#{name}")
82
- assert_equal("class MyContextUsingBind\r\n \ndef go_do \na = Array.new\n [1, 2].each do |e|\n temp____other_role = @other_role\n @other_role = e\n (a << self_other_role_plus_one)\n @other_role = temp____other_role\n end\n a\n end\n\n@other_role\n\n private\ndef other_role;@other_role end\n\n \ndef self_other_role_plus_one \n(other_role + 1) end\n\n\r\nend",source)
83
- assert_equal(2,arr[0])
84
- assert_equal(3,arr[1])
142
+ class TestExamples < Test::Unit::TestCase
143
+ def test_greeter
144
+ p1 = Person.new
145
+ p1.name = 'Bob'
146
+ p1.greeting = 'Hello'
147
+
148
+ p2 = Person.new
149
+ p2.name = 'World!'
150
+ p2.greeting = 'Greetings'
151
+
152
+ #Execute is automagically created for the default interaction (specified by the second argument in context :Greet_Someone, :greet do)
153
+ #Executes constructs a context object and calls the default interaction on this object
154
+ res1 = Greet_Someone.call p1, p2
155
+ res2 = Greet_Someone.new(p2, p1).greet
156
+ res3 = Greet_Someone.new(p2, p1).call
157
+ assert_equal(res1, "#{p1.name}: \"#{p1.greeting}, #{p2.name}!\"")
158
+ assert_equal(res1, Greet_Someone.new(p1, p2).greet) #verifies default action
159
+ #constructs a Greet_Someone context object and executes greet.
160
+ assert_equal(res2, "#{p2.name}: \"#{p2.greeting}, #{p1.name}!\"")
161
+ assert_equal(res2, res3)
85
162
  end
86
163
  end
87
164
 
165
+
88
166
  class TestExamples < Test::Unit::TestCase
89
- def test_greeter
90
- p1 = Person.new
91
- p1.name = 'Bob'
92
- p1.greeting = 'Hello'
93
-
94
- p2 = Person.new
95
- p2.name = 'World!'
96
- p2.greeting = 'Greetings'
97
-
98
- #Execute is automagically created for the default interaction (specified by the second argument in context :Greet_Someone, :greet do)
99
- #Executes construc a context object and calls the default interaction on this object
100
- res1 = Greet_Someone.execute p1, p2
101
- res2 = Greet_Someone.new(p2, p1).greet
102
- assert_equal(res1,"#{p1.name}: \"#{p1.greeting}, #{p2.name}!\"")
103
- assert_equal(res1,Greet_Someone.new(p1, p2).greet) #verifies default action
104
- #constructs a Greet_Someone context object and executes greet.
105
- assert_equal(res2,"#{p2.name}: \"#{p2.greeting}, #{p1.name}!\"")
106
- end
167
+ def test_greeter
168
+ p1 = Person.new
169
+ p1.name = 'Bob'
170
+ p1.greeting = 'Hello'
171
+
172
+ p2 = Person.new
173
+ p2.name = 'World!'
174
+ p2.greeting = 'Greetings'
175
+
176
+ message = ' Nice weather, don\'t you think?'
177
+ res1 = Greet_Someone2.call p1, p2, message
178
+ res2 = Greet_Someone2.new(p2, p1).greet message
179
+ res3 = Greet_Someone2.new(p2, p1).call message
180
+ assert_equal(res1, "#{p1.name}: \"#{p1.greeting}, #{p2.name}!\" #{message}")
181
+ assert_equal(res1, Greet_Someone2.new(p1, p2).greet(message)) #verifies default action
182
+ #constructs a Greet_Someone context object and executes greet.
183
+ assert_equal(res2, "#{p2.name}: \"#{p2.greeting}, #{p1.name}!\" #{message}")
184
+ assert_equal(res2, res3)
185
+ end
107
186
  end
108
187
 
188
+
189
+
190
+
@@ -0,0 +1,40 @@
1
+ module Source_assertions
2
+ def assert_source_equal(expected, actual)
3
+ expected_sexp = Ripper::sexp expected
4
+ actual_sexp = Ripper::sexp actual
5
+ assert_sexp_with_ident(expected_sexp, actual_sexp, "Expected #{expected} but got #{actual}")
6
+ assert_equal(1,1) #just getting the correct assertion count
7
+ end
8
+
9
+ def is_terminal(sexp)
10
+ sexp == :@ident || sexp == :@int || sexp == :@ivar
11
+ end
12
+
13
+ def assert_sexp_with_ident(expected, actual, message)
14
+ if is_terminal expected[0]
15
+ if expected[-1].instance_of? Array
16
+ if actual[-1].instance_of? Array
17
+ if actual[-1].length == 2
18
+ if expected[-1].length == 2
19
+ return assert_sexp_with_ident(expected[1..-2], actual[1..-2], message)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ expected.each_index do |i|
27
+ if expected[i].instance_of? Array
28
+ if actual[i].instance_of? Array
29
+ assert_sexp_with_ident(expected[i], actual[i], message)
30
+ else
31
+ assert_fail(message || "the arrays differ at index #{i}. Actual was an element but an array was expected")
32
+ end
33
+ else
34
+ if expected[i] != actual[i]
35
+ assert_fail (message || "the arrays differ at index #{i}")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ require 'sourcify'
2
+ require 'sorcerer'
3
+
4
+ module Source_cleaner
5
+ private
6
+
7
+ #cleans up the string for further processing and separates arguments from body
8
+ def block2source(method_name, &block)
9
+ source = block.to_sexp
10
+ raise 'unknown format' unless source[0] == :iter or source.length != 4
11
+ args = get_args source[2]
12
+ body = source[3]
13
+ return args, body
14
+ end
15
+
16
+ def get_args(sexp)
17
+ return nil unless sexp
18
+ return sexp[1] if sexp[0] == :lasgn
19
+ sexp = sexp[1][1..-1] # array or arguments
20
+ args = []
21
+ sexp.each { |e|
22
+ args << e[1]
23
+ }
24
+ args.join(',')
25
+ end
26
+
27
+ def lambda2method (method_name, method)
28
+ arguments, body = method.arguments, method.body
29
+ transform_ast body
30
+ block = Ruby2Ruby.new.process(body)
31
+ args = "(#{arguments})" if arguments
32
+ "\ndef #{method_name} #{args}\n#{block} end\n"
33
+ end
34
+ end
@@ -1,6 +1,16 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require 'live_ast'
3
- require 'live_ast/to_ruby'
2
+ require './lib/Source_cleaner.rb'
3
+ require './lib/rewriter.rb'
4
+
5
+
6
+ class Method_info
7
+ def initialize(arguments,body)
8
+ @arguments = arguments
9
+ @body = body
10
+ end
11
+ attr_reader :arguments
12
+ attr_reader :body
13
+ end
4
14
 
5
15
  ##
6
16
  # The Context class is used to define a DCI context with roles and their role methods
@@ -38,6 +48,7 @@ require 'live_ast/to_ruby'
38
48
  #License:: Same as for Ruby
39
49
  ##
40
50
  class Context
51
+ include Rewriter,Source_cleaner
41
52
  @roles
42
53
  @interactions
43
54
  @defining_role
@@ -54,7 +65,8 @@ class Context
54
65
  def self.define(*args, &block)
55
66
  name,base_class,default_interaction = *args
56
67
  #if there's two arguments and the second is not a class it must be an interaction
57
- base_class,default_interaction = default_interaction, base_class if base_class and !default_interaction and base_class.instance_of? Symbol
68
+ if default_interaction && (!base_class.instance_of? Class) then base_class = eval(base_class.to_s) end
69
+ base_class,default_interaction = default_interaction, base_class if base_class and !default_interaction and !base_class.instance_of? Class
58
70
  ctx = Context.new
59
71
  ctx.instance_eval &block
60
72
  return ctx.send(:finalize, name,base_class,default_interaction)
@@ -87,39 +99,10 @@ class Context
87
99
  @role_alias = Array.new
88
100
  end
89
101
 
90
- def role_aliases
91
- @alias_list if @alias_list
92
- @alias_list = Hash.new
93
- @role_alias.each {|aliases|
94
- aliases.each {|k,v|
95
- @alias_list[k] = v
96
- }
97
- }
98
- @alias_list
99
- end
100
-
101
- def roles
102
- @cached_roles_and_alias_list if @cached_roles_and_alias_list
103
- @roles unless @role_alias and @role_alias.length
104
- @cached_roles_and_alias_list = Hash.new
105
- @roles.each {|k,v|
106
- @cached_roles_and_alias_list[k] = v
107
- }
108
- role_aliases.each {|k,v|
109
- @cached_roles_and_alias_list[k] = @roles[v]
110
- }
111
- @cached_roles_and_alias_list
112
- end
113
-
114
102
  def methods
115
103
  (@defining_role ? @roles[@defining_role] : @interactions)
116
104
  end
117
105
 
118
- def add_alias (a,role_name)
119
- @cached_roles_and_alias_list,@alias_list = nil
120
- @role_alias.last()[a] = role_name
121
- end
122
-
123
106
  def finalize(name, base_class, default)
124
107
  c = base_class ? (Class.new base_class) : Class.new
125
108
  Kernel.const_set name, c
@@ -128,12 +111,27 @@ class Context
128
111
  getters = ''
129
112
  impl = ''
130
113
  interactions = ''
131
- @interactions.each do |method_name, method_source|
114
+ @interactions.each do |method_name, method|
132
115
  @defining_role = nil
133
- interactions << " #{lambda2method(method_name, method_source)}"
116
+ interactions << " #{lambda2method(method_name, method)}"
134
117
  end
135
118
  if default
136
- interactions <<"\ndef self.execute(*args);#{name}.new(*args).#{default}; end\n"
119
+ interactions <<"
120
+ def self.call(*args)
121
+ arity =#{name}.method(:new).arity
122
+ newArgs = args[0..arity-1]
123
+ p \"new \#{newArgs}\"
124
+ obj = #{name}.new *newArgs
125
+ if arity < args.length
126
+ methodArgs = args[arity..-1]
127
+ p \"method \#{methodArgs}\"
128
+ obj.#{default} *methodArgs
129
+ else
130
+ obj.#{default}
131
+ end
132
+ end
133
+ "
134
+ interactions <<"\ndef call(*args);#{default} *args; end\n"
137
135
  end
138
136
 
139
137
  @roles.each do |role, methods|
@@ -151,6 +149,7 @@ class Context
151
149
  code << "#{interactions}\n#{fields}\n private\n#{getters}\n#{impl}\n"
152
150
 
153
151
  complete = "class #{name}\r\n#{code}\r\nend"
152
+ #File.open("#{name}_generated.rb", 'w') {|f| f.write(complete) }
154
153
  temp = c.class_eval(code)
155
154
  return (temp ||c),complete
156
155
  end
@@ -158,208 +157,9 @@ class Context
158
157
  def role_or_interaction_method(method_name,*args, &b)
159
158
  raise "method with out block #{method_name}" unless b
160
159
 
161
- args, block = block2source b.to_ruby, method_name
162
- args = "|#{args}|" if args
163
- source = "(proc do #{args}\n #{block}\nend)"
164
- methods[method_name] = source
160
+ args, body = block2source method_name, &b
161
+ methods[method_name] = Method_info.new args,body
165
162
  end
166
163
 
167
164
  alias method_missing role_or_interaction_method
168
-
169
- def role_method_call(ast, method)
170
- is_call_expression = ast && ast[0] == :call
171
- self_is_instance_expression = is_call_expression && (!ast[1]) #implicit self
172
- is_in_block = ast && ast[0] == :lvar
173
- role_name_index = self_is_instance_expression ? 2 : 1
174
- role = (self_is_instance_expression || is_in_block) ? roles[ast[role_name_index]] : nil #is it a call to a role getter
175
- is_role_method = role && role.has_key?(method)
176
- role_name = is_in_block ? role_aliases[ast[1]] : (ast[2] if self_is_instance_expression)
177
- role_name if is_role_method #return role name
178
- end
179
-
180
- def lambda2method (method_name, method_source)
181
- evaluated = ast_eval method_source, binding
182
- ast = evaluated.to_ast
183
- transform_ast ast
184
- args, block = block2source LiveAST.parser::Unparser.unparse(ast), method_name
185
- args = "(#{args})" if args
186
- "\ndef #{method_name} #{args}\n#{block} end\n"
187
- end
188
-
189
- ##
190
- #Test if there's a block that needs to potentially be transformed
191
- ##
192
- def transform_block(exp)
193
- if exp && exp[0] == :iter
194
- (exp.length-1).times do |i|
195
- expr = exp[i+1]
196
- #find the block
197
- if expr && expr.length && expr[0] == :block
198
- transform_ast exp if rewrite_bind? expr,expr[1]
199
- end
200
- end
201
- end
202
- end
203
-
204
- ##
205
- #Calls rewrite_block if needed and will return true if the AST was changed otherwise false
206
- ##
207
- def rewrite_bind?(block, expr)
208
- #check if the first call is a bind call
209
- if expr && expr.length && (expr[0] == :call && expr[1] == nil && expr[2] == :bind)
210
- arglist = expr[3]
211
- if arglist && arglist[0] == :arglist
212
- arguments = arglist[1]
213
- if arguments && arguments[0] == :hash
214
- block.delete_at 1
215
- count = (arguments.length-1) / 2
216
- (1..count).each do |j|
217
- temp = j * 2
218
- local = arguments[temp-1][1]
219
- if local.instance_of? Sexp
220
- local = local[1]
221
- end
222
- raise 'invalid value for role alias' unless local.instance_of? Symbol
223
- #find the name of the role being bound to
224
- aliased_role = arguments[temp][1]
225
- if aliased_role.instance_of? Sexp
226
- aliased_role = aliased_role[1]
227
- end
228
- raise "#{aliased_role} used in binding is an unknown role #{roles}" unless aliased_role.instance_of? Symbol and @roles.has_key? aliased_role
229
- add_alias local, aliased_role
230
- #replace bind call with assignment of iteration variable to role field
231
- rewrite_bind(aliased_role, local, block)
232
- return true
233
- end
234
- end
235
- end
236
- end
237
- false
238
- end
239
-
240
- ##
241
- #removes call to bind in a block
242
- #and replaces it with assignment to the proper role player local variables
243
- #in the end of the block the local variables have their original values reassigned
244
- def rewrite_bind(aliased_role, local, block)
245
- raise 'aliased_role must be a Symbol' unless aliased_role.instance_of? Symbol
246
- raise 'local must be a Symbol' unless local.instance_of? Symbol
247
- aliased_field = "@#{aliased_role}".to_sym
248
- assignment = Sexp.new
249
- assignment[0] = :iasgn
250
- assignment[1] = aliased_field
251
- load_arg = Sexp.new
252
- load_arg[0] = :lvar
253
- load_arg[1] = local
254
- assignment[2] = load_arg
255
- block.insert 1, assignment
256
-
257
- # assign role player to temp
258
- temp_symbol = "temp____#{aliased_role}".to_sym
259
- assignment = Sexp.new
260
- assignment[0] = :lasgn
261
- assignment[1] = temp_symbol
262
- load_field = Sexp.new
263
- load_field[0] = :ivar
264
- load_field[1] = aliased_field
265
- assignment[2] = load_field
266
- block.insert 1, assignment
267
-
268
- # reassign original player
269
- assignment = Sexp.new
270
- assignment[0] = :iasgn
271
- assignment[1] = aliased_field
272
- load_temp = Sexp.new
273
- load_temp[0] = :lvar
274
- load_temp[1] = temp_symbol
275
- assignment[2] = load_temp
276
- block[block.length] = assignment
277
- end
278
-
279
- # rewrites a call to self in a role method to a call to the role player accessor
280
- # which is subsequently rewritten to a call to the instance variable itself
281
- # in the case where no role method is called on the role player
282
- # It's rewritten to an instance call on the context object if a role method is called
283
- def rewrite_self (ast)
284
- ast.length.times do |i|
285
- raise 'Invalid argument. must be an expression' unless ast.instance_of? Sexp
286
- exp = ast[i]
287
- if exp == :self
288
- ast[0] = :call
289
- ast[1] = nil
290
- ast[2] = @defining_role
291
- arglist = Sexp.new
292
- ast[3] = arglist
293
- arglist[0] = :arglist
294
- elsif exp.instance_of? Sexp
295
- rewrite_self exp
296
- end
297
- end
298
- end
299
-
300
- #rewrites the ast so that role method calls are rewritten to a method invocation on the context object rather than the role player
301
- #also does rewriting of binds in blocks
302
- def transform_ast(ast)
303
- if ast
304
- if @defining_role
305
- rewrite_self ast
306
- end
307
- ast.length.times do |k|
308
- exp = ast[k]
309
- if exp
310
- method_name = exp[2]
311
- role = role_method_call exp[1], exp[2]
312
- if exp[0] == :iter
313
- @role_alias.push Hash.new
314
- transform_block exp
315
- @role_alias.pop()
316
- end
317
- if exp[0] == :call && role
318
- exp[1] = nil #remove call to attribute
319
- exp[2] = "self_#{role}_#{method_name}".to_sym
320
- end
321
- if exp.instance_of? Sexp
322
- transform_ast exp
323
- end
324
- end
325
- end
326
- end
327
- end
328
-
329
- #cleans up the string for further processing and separates arguments from body
330
- def block2source(b, method_name)
331
- args = nil
332
- block = b.strip
333
- block = block[method_name.length..-1].strip if block.start_with? method_name.to_s
334
- block = cleanup_head_and_tail(block)
335
- if block.start_with? '|'
336
- args = block.scan(/\|([\w\d,\s]*)\|/)
337
- if args.length && args[0]
338
- args = args[0][0]
339
- else
340
- args = nil
341
- end
342
- block = block[(2 + (block[1..-1].index '|'))..-1].strip
343
- end
344
- return args, block
345
- end
346
-
347
- # removes proc do/{ at start and } or end at the end of the string
348
- def cleanup_head_and_tail(block)
349
- if /^proc\s/.match(block)
350
- block = block['proc'.length..-1].strip
351
- end
352
- if /^do\s/.match(block)
353
- block = block[2..-1].strip
354
- elsif block.start_with? '{'
355
- block = block[1..-1].strip
356
- end
357
-
358
- if /end$/.match(block)
359
- block = block[0..-4]
360
- elsif /\}$/.match(block)
361
- block = block[0..-2]
362
- end
363
- block
364
- end
365
165
  end
@@ -1,3 +1,3 @@
1
1
  module Maroon
2
- VERSION = '0.5.3'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -0,0 +1,184 @@
1
+ require 'Ripper'
2
+
3
+ module Rewriter
4
+ private
5
+ def role_aliases
6
+ @alias_list if @alias_list
7
+ @alias_list = Hash.new
8
+ @role_alias.each { |aliases|
9
+ aliases.each { |k, v|
10
+ @alias_list[k] = v
11
+ }
12
+ }
13
+ @alias_list
14
+ end
15
+
16
+ def roles
17
+ @cached_roles_and_alias_list if @cached_roles_and_alias_list
18
+ @roles unless @role_alias and @role_alias.length
19
+ @cached_roles_and_alias_list = Hash.new
20
+ @roles.each { |k, v|
21
+ @cached_roles_and_alias_list[k] = v
22
+ }
23
+ role_aliases.each { |k, v|
24
+ @cached_roles_and_alias_list[k] = @roles[v]
25
+ }
26
+ @cached_roles_and_alias_list
27
+ end
28
+
29
+ def add_alias (a, role_name)
30
+ @cached_roles_and_alias_list, @alias_list = nil
31
+ @role_alias.last()[a] = role_name
32
+ end
33
+
34
+ def role_method_call(ast, method)
35
+ is_call_expression = ast && ast[0] == :call
36
+ self_is_instance_expression = is_call_expression && (!ast[1]) #implicit self
37
+ is_in_block = ast && ast[0] == :lvar
38
+ role_name_index = self_is_instance_expression ? 2 : 1
39
+ role = (self_is_instance_expression || is_in_block) ? roles[ast[role_name_index]] : nil #is it a call to a role getter
40
+ is_role_method = role && role.has_key?(method)
41
+ role_name = is_in_block ? role_aliases[ast[1]] : (ast[2] if self_is_instance_expression)
42
+ role_name if is_role_method #return role name
43
+ end
44
+
45
+ ##
46
+ #Test if there's a block that needs to potentially be transformed
47
+ ##
48
+ def transform_block(exp)
49
+ if exp && exp[0] == :iter
50
+ (exp.length-1).times do |i|
51
+ expr = exp[i+1]
52
+ #find the block
53
+ if expr && expr.length && expr[0] == :block
54
+ transform_ast exp if rewrite_bind? expr, expr[1]
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ ##
61
+ #Calls rewrite_block if needed and will return true if the AST was changed otherwise false
62
+ ##
63
+ def rewrite_bind?(block, expr)
64
+ #check if the first call is a bind call
65
+ if expr && expr.length && (expr[0] == :call && expr[1] == nil && expr[2] == :bind)
66
+ argument_list = expr[3]
67
+ if argument_list && argument_list[0] == :arglist
68
+ arguments = argument_list[1]
69
+ if arguments && arguments[0] == :hash
70
+ block.delete_at 1
71
+ count = (arguments.length-1) / 2
72
+ (1..count).each do |j|
73
+ temp = j * 2
74
+ local = arguments[temp-1][1]
75
+ if local.instance_of? Sexp
76
+ local = local[1]
77
+ end
78
+ raise 'invalid value for role alias' unless local.instance_of? Symbol
79
+ #find the name of the role being bound to
80
+ aliased_role = arguments[temp][1]
81
+ if aliased_role.instance_of? Sexp
82
+ aliased_role = aliased_role[1]
83
+ end
84
+ raise "#{aliased_role} used in binding is an unknown role #{roles}" unless aliased_role.instance_of? Symbol and @roles.has_key? aliased_role
85
+ add_alias local, aliased_role
86
+ #replace bind call with assignment of iteration variable to role field
87
+ rewrite_bind(aliased_role, local, block)
88
+ return true
89
+ end
90
+ end
91
+ end
92
+ end
93
+ false
94
+ end
95
+
96
+ ##
97
+ #removes call to bind in a block
98
+ #and replaces it with assignment to the proper role player local variables
99
+ #in the end of the block the local variables have their original values reassigned
100
+ def rewrite_bind(aliased_role, local, block)
101
+ raise 'aliased_role must be a Symbol' unless aliased_role.instance_of? Symbol
102
+ raise 'local must be a Symbol' unless local.instance_of? Symbol
103
+ aliased_field = "@#{aliased_role}".to_sym
104
+ assignment = Sexp.new
105
+ assignment[0] = :iasgn
106
+ assignment[1] = aliased_field
107
+ load_arg = Sexp.new
108
+ load_arg[0] = :lvar
109
+ load_arg[1] = local
110
+ assignment[2] = load_arg
111
+ block.insert 1, assignment
112
+
113
+ # assign role player to temp
114
+ temp_symbol = "temp____#{aliased_role}".to_sym
115
+ assignment = Sexp.new
116
+ assignment[0] = :lasgn
117
+ assignment[1] = temp_symbol
118
+ load_field = Sexp.new
119
+ load_field[0] = :ivar
120
+ load_field[1] = aliased_field
121
+ assignment[2] = load_field
122
+ block.insert 1, assignment
123
+
124
+ # reassign original player
125
+ assignment = Sexp.new
126
+ assignment[0] = :iasgn
127
+ assignment[1] = aliased_field
128
+ load_temp = Sexp.new
129
+ load_temp[0] = :lvar
130
+ load_temp[1] = temp_symbol
131
+ assignment[2] = load_temp
132
+ block[block.length] = assignment
133
+ end
134
+
135
+ # rewrites a call to self in a role method to a call to the role player accessor
136
+ # which is subsequently rewritten to a call to the instance variable itself
137
+ # in the case where no role method is called on the role player
138
+ # It's rewritten to an instance call on the context object if a role method is called
139
+ def rewrite_self (ast)
140
+ ast.length.times do |i|
141
+ raise 'Invalid argument. must be an expression' unless ast.instance_of? Sexp
142
+ exp = ast[i]
143
+ if exp == :self
144
+ ast[0] = :call
145
+ ast[1] = nil
146
+ ast[2] = @defining_role
147
+ arglist = Sexp.new
148
+ ast[3] = arglist
149
+ arglist[0] = :arglist
150
+ elsif exp.instance_of? Sexp
151
+ rewrite_self exp
152
+ end
153
+ end
154
+ end
155
+
156
+ #rewrites the ast so that role method calls are rewritten to a method invocation on the context object rather than the role player
157
+ #also does rewriting of binds in blocks
158
+ def transform_ast(ast)
159
+ if ast
160
+ if @defining_role
161
+ rewrite_self ast
162
+ end
163
+ ast.length.times do |k|
164
+ exp = ast[k]
165
+ if exp
166
+ method_name = exp[2]
167
+ role = role_method_call exp[1], exp[2]
168
+ if exp[0] == :iter
169
+ @role_alias.push Hash.new
170
+ transform_block exp
171
+ @role_alias.pop()
172
+ end
173
+ if exp[0] == :call && role
174
+ exp[1] = nil #remove call to attribute
175
+ exp[2] = "self_#{role}_#{method_name}".to_sym
176
+ end
177
+ if exp.instance_of? Sexp
178
+ transform_ast exp
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -21,8 +21,6 @@ For examples on how to use maroon look at the examples found at the home page}
21
21
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
22
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
23
  gem.require_paths = ["lib"]
24
- gem.add_runtime_dependency 'sexp_processor', '~>3.2', '=3.2.0'
25
- gem.add_runtime_dependency 'ruby_parser', '~>2.0', '=2.0.6'
26
- gem.add_runtime_dependency 'ruby2ruby', '~>1.3', '>=1.3.1'
27
- gem.add_runtime_dependency 'live_ast', '~>1.0', '>=1.0.2'
24
+ gem.add_runtime_dependency 'sourcify', '~>0.3', '>=0.3.10'
25
+ gem.add_runtime_dependency 'sorcerer'
28
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maroon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,63 +9,19 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-19 00:00:00.000000000 Z
12
+ date: 2013-02-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: sexp_processor
15
+ name: sourcify
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '3.2'
22
- - - '='
23
- - !ruby/object:Gem::Version
24
- version: 3.2.0
25
- type: :runtime
26
- prerelease: false
27
- version_requirements: !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
30
- - - ~>
31
- - !ruby/object:Gem::Version
32
- version: '3.2'
33
- - - '='
34
- - !ruby/object:Gem::Version
35
- version: 3.2.0
36
- - !ruby/object:Gem::Dependency
37
- name: ruby_parser
38
- requirement: !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ~>
42
- - !ruby/object:Gem::Version
43
- version: '2.0'
44
- - - '='
45
- - !ruby/object:Gem::Version
46
- version: 2.0.6
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- none: false
51
- requirements:
52
- - - ~>
53
- - !ruby/object:Gem::Version
54
- version: '2.0'
55
- - - '='
56
- - !ruby/object:Gem::Version
57
- version: 2.0.6
58
- - !ruby/object:Gem::Dependency
59
- name: ruby2ruby
60
- requirement: !ruby/object:Gem::Requirement
61
- none: false
62
- requirements:
63
- - - ~>
64
- - !ruby/object:Gem::Version
65
- version: '1.3'
21
+ version: '0.3'
66
22
  - - ! '>='
67
23
  - !ruby/object:Gem::Version
68
- version: 1.3.1
24
+ version: 0.3.10
69
25
  type: :runtime
70
26
  prerelease: false
71
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -73,32 +29,26 @@ dependencies:
73
29
  requirements:
74
30
  - - ~>
75
31
  - !ruby/object:Gem::Version
76
- version: '1.3'
32
+ version: '0.3'
77
33
  - - ! '>='
78
34
  - !ruby/object:Gem::Version
79
- version: 1.3.1
35
+ version: 0.3.10
80
36
  - !ruby/object:Gem::Dependency
81
- name: live_ast
37
+ name: sorcerer
82
38
  requirement: !ruby/object:Gem::Requirement
83
39
  none: false
84
40
  requirements:
85
- - - ~>
86
- - !ruby/object:Gem::Version
87
- version: '1.0'
88
41
  - - ! '>='
89
42
  - !ruby/object:Gem::Version
90
- version: 1.0.2
43
+ version: '0'
91
44
  type: :runtime
92
45
  prerelease: false
93
46
  version_requirements: !ruby/object:Gem::Requirement
94
47
  none: false
95
48
  requirements:
96
- - - ~>
97
- - !ruby/object:Gem::Version
98
- version: '1.0'
99
49
  - - ! '>='
100
50
  - !ruby/object:Gem::Version
101
- version: 1.0.2
51
+ version: '0'
102
52
  description: ! 'maroon makes DCI a DSL for Ruby it''s mainly based on the work gone
103
53
  into Marvin,
104
54
 
@@ -128,9 +78,12 @@ files:
128
78
  - README.md
129
79
  - Rakefile
130
80
  - Test/Greeter_test.rb
81
+ - Test/source_assertions.rb
82
+ - lib/Source_cleaner.rb
131
83
  - lib/maroon.rb
132
84
  - lib/maroon/kernel.rb
133
85
  - lib/maroon/version.rb
86
+ - lib/rewriter.rb
134
87
  - maroon.gemspec
135
88
  homepage: https://github.com/runefs/maroon
136
89
  licenses: []