maroon 0.5.3 → 0.6.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.
@@ -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: []