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.
- data/Examples/Dijkstra/calculate_shortest_path.rb +1 -3
- data/Examples/Dijkstra/dijkstra.rb +4 -4
- data/Examples/MoneyTransfer.rb +1 -1
- data/Test/Greeter_test.rb +152 -70
- data/Test/source_assertions.rb +40 -0
- data/lib/Source_cleaner.rb +34 -0
- data/lib/maroon.rb +36 -236
- data/lib/maroon/version.rb +1 -1
- data/lib/rewriter.rb +184 -0
- data/maroon.gemspec +2 -4
- metadata +13 -60
@@ -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
|
-
|
353
|
+
File.open('CalculateShortestPath_generated.rb', 'w') {|f| f.write(source) }
|
356
354
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require '
|
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
|
data/Examples/MoneyTransfer.rb
CHANGED
data/Test/Greeter_test.rb
CHANGED
@@ -1,12 +1,91 @@
|
|
1
|
-
require
|
2
|
-
require '
|
3
|
-
require '
|
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
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
assert_equal(
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
data/lib/maroon.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require '
|
3
|
-
require '
|
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
|
-
|
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,
|
114
|
+
@interactions.each do |method_name, method|
|
132
115
|
@defining_role = nil
|
133
|
-
interactions << " #{lambda2method(method_name,
|
116
|
+
interactions << " #{lambda2method(method_name, method)}"
|
134
117
|
end
|
135
118
|
if default
|
136
|
-
interactions <<"
|
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,
|
162
|
-
|
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
|
data/lib/maroon/version.rb
CHANGED
data/lib/rewriter.rb
ADDED
@@ -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
|
data/maroon.gemspec
CHANGED
@@ -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 '
|
25
|
-
gem.add_runtime_dependency '
|
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.
|
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-
|
12
|
+
date: 2013-02-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
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
|
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:
|
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: '
|
32
|
+
version: '0.3'
|
77
33
|
- - ! '>='
|
78
34
|
- !ruby/object:Gem::Version
|
79
|
-
version:
|
35
|
+
version: 0.3.10
|
80
36
|
- !ruby/object:Gem::Dependency
|
81
|
-
name:
|
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:
|
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:
|
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: []
|