Moby 0.5.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/CalculateShortestDistance.rb +65 -0
- data/Examples/Dijkstra/calculate_shortest_path.rb +352 -0
- data/Examples/Dijkstra/data.rb +210 -0
- data/Examples/Dijkstra/dijkstra.rb +84 -0
- data/Examples/MoneyTransfer.rb +61 -0
- data/Examples/greeter.rb +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/Moby.gemspec +28 -0
- data/README.md +41 -0
- data/Rakefile +1 -0
- data/lib/Moby.rb +358 -0
- data/lib/Moby/kernel.rb +5 -0
- data/lib/Moby/version.rb +3 -0
- metadata +158 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require '../../Lib/Moby.rb'
|
3
|
+
require './data.rb'
|
4
|
+
require './CalculateShortestDistance.rb'
|
5
|
+
require './Calculate_Shortest_Path.rb'
|
6
|
+
#!/usr/bin/env ruby
|
7
|
+
# Example in Ruby -- Dijkstra's algorithm in DCI
|
8
|
+
# Modified and simplified for a Manhattan geometry with 8 roles
|
9
|
+
#
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# Demonstrates an example where:
|
13
|
+
#
|
14
|
+
# - objects of class Node play several roles simultaneously
|
15
|
+
# (albeit spread across Contexts: a Node can
|
16
|
+
# play the CurrentIntersection in one Context and an Eastern or
|
17
|
+
# Southern Neighbor in another)
|
18
|
+
# - stacked Contexts (to implement recursion)
|
19
|
+
# - mixed access of objects of Node through different
|
20
|
+
# paths of role elaboration (the root is just a node,
|
21
|
+
# whereas others play roles)
|
22
|
+
# - there is a significant pre-existing data structure called
|
23
|
+
# a Geometry (plays the Map role) which contains the objects
|
24
|
+
# of instance. Where DCI comes in is to ascribe roles to those
|
25
|
+
# objects and let them interact with each other to evaluate the
|
26
|
+
# minimal path through the network
|
27
|
+
# - true to core DCI we are almost always concerned about
|
28
|
+
# what happens between the objects (paths and distance)
|
29
|
+
# rather than in the objects themselves (which have
|
30
|
+
# relatively uninteresting properties like "name")
|
31
|
+
# - equality of nodes is not identity, and several
|
32
|
+
# nodes compare equal with each other by standard
|
33
|
+
# equality (eql?)
|
34
|
+
# - returns references to the original data objects
|
35
|
+
# in a vector, to describe the resulting path
|
36
|
+
#
|
37
|
+
# There are some curiosities
|
38
|
+
#
|
39
|
+
# - east_neighbor and south_neighbor were typographically equivalent,
|
40
|
+
# so I folded them into a single role: Neighbor. That type still
|
41
|
+
# serves the two original roles
|
42
|
+
# - Roles are truly scoped to the use case context
|
43
|
+
# - The Map and Distance_labeled_graph_node roles have to be
|
44
|
+
# duplicated in two Contexts. blah blah blah
|
45
|
+
# - Node inheritance is replaced by injecting two roles
|
46
|
+
# into the object
|
47
|
+
# - Injecting roles no longer adds new fields to existing
|
48
|
+
# data objects.
|
49
|
+
# - There is an intentional call to distance_between while the
|
50
|
+
# Context is still extant, but outside the scope of the
|
51
|
+
# Context itself. Should that be legal?
|
52
|
+
# - I have added a tentative_distance_values array to the Context
|
53
|
+
# to support the algorithm. Its data are shared across the
|
54
|
+
# roles of the CalculateShortestPath Context
|
55
|
+
# - nearest_unvisited_node_to_target is now a feature of Map,
|
56
|
+
# which seems to reflect better coupling than in the old
|
57
|
+
# design
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
# --- Main Program: test driver
|
62
|
+
#
|
63
|
+
geometries = Geometry_1.new
|
64
|
+
path = CalculateShortestPath.new(geometries.root, geometries.destination, geometries)
|
65
|
+
print 'Path is: '
|
66
|
+
path.each { |node| print "#{node.name} " }
|
67
|
+
print "\n"
|
68
|
+
puts "distance is #{CalculateShortestDistance.new(geometries.root, geometries).distance}"
|
69
|
+
|
70
|
+
puts ''
|
71
|
+
|
72
|
+
geometries = ManhattanGeometry2.new
|
73
|
+
path = CalculateShortestPath.new(geometries.root, geometries.destination, geometries)
|
74
|
+
print 'Path is: '
|
75
|
+
last_node = nil
|
76
|
+
path.each do |node|
|
77
|
+
if last_node != nil; print " - #{geometries.distances[Edge.new(node, last_node)]} - " end
|
78
|
+
print "#{node.name}"
|
79
|
+
last_node = node
|
80
|
+
end
|
81
|
+
print "\n"
|
82
|
+
|
83
|
+
geometries = ManhattanGeometry2.new
|
84
|
+
puts "distance is #{CalculateShortestDistance.new(geometries.root, geometries).distance }"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require '../lib/Moby.rb'
|
2
|
+
|
3
|
+
Context::define :MoneyTransfer do
|
4
|
+
role :source do
|
5
|
+
withdraw do |amount|
|
6
|
+
source.movement(amount)
|
7
|
+
source.log "withdrawal #{amount}"
|
8
|
+
end
|
9
|
+
log do |message|
|
10
|
+
p "#{@source} source #{message}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
role :destination do
|
15
|
+
deposit do |amount|
|
16
|
+
@destination.movement(amount)
|
17
|
+
@destination.log "deposit #{amount}"
|
18
|
+
end
|
19
|
+
logger do |message|
|
20
|
+
p "#{@source} destination #{message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
role :amount do end
|
25
|
+
|
26
|
+
transfer do
|
27
|
+
source.withdraw -amount
|
28
|
+
destination.deposit amount
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MoneyTransfer
|
33
|
+
def initialize(source, destination, amount)
|
34
|
+
@source = source
|
35
|
+
@destination = destination
|
36
|
+
@amount = amount
|
37
|
+
end
|
38
|
+
end
|
39
|
+
class Account
|
40
|
+
def initialize (amount, id)
|
41
|
+
@balance = amount
|
42
|
+
@account_id = id
|
43
|
+
end
|
44
|
+
|
45
|
+
def movement(amount)
|
46
|
+
log "Amount #{amount}"
|
47
|
+
@balance+=amount
|
48
|
+
end
|
49
|
+
|
50
|
+
def log(message)
|
51
|
+
(p s = "instance #{message}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"balance of #{@account_id}: #{@balance}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
account = Account.new 1000, "source"
|
60
|
+
ctx = MoneyTransfer.new account, account, 100
|
61
|
+
ctx.transfer
|
data/Examples/greeter.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require '../lib/Moby.rb'
|
2
|
+
require '../lib/Moby/kernel.rb'
|
3
|
+
|
4
|
+
context :Greeter do
|
5
|
+
role :who do
|
6
|
+
say do
|
7
|
+
self
|
8
|
+
end
|
9
|
+
talk do
|
10
|
+
self.say
|
11
|
+
end
|
12
|
+
end
|
13
|
+
greeting do
|
14
|
+
p "Hello #{who.talk}!"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Greeter
|
19
|
+
def initialize(player)
|
20
|
+
@who = player
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Greeter.new('world').greeting #Will print "Hello world!"
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Moby.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'Moby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'Moby'
|
8
|
+
gem.version = Moby::VERSION
|
9
|
+
gem.authors = ['Rune Funch Søltoft']
|
10
|
+
gem.email = %w(funchsoltoft@gmail.com)
|
11
|
+
gem.description = %q{Moby makes DCI a DSL for Ruby it's mainly based on the work gone into Marvin,
|
12
|
+
the first language to support injectionless DCI.
|
13
|
+
|
14
|
+
The performance of code written using Moby is on par with code using regular method invocation.
|
15
|
+
|
16
|
+
For examples on how to use Moby look at the examples}
|
17
|
+
gem.summary = %q{Moby}
|
18
|
+
gem.homepage = 'https://github.com/runefs/Moby'
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($/)
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
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'
|
28
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Moby
|
2
|
+
|
3
|
+
A module to make pure DCI available in Ruby
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'Moby'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install Moby
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
See the examples for detailed information on how to use Moby.
|
22
|
+
|
23
|
+
Essentially you can define a context by using
|
24
|
+
|
25
|
+
Context::define :context_name do
|
26
|
+
role :role_name do
|
27
|
+
print_self do |x| #notice no symbol
|
28
|
+
p "#{role_name} #use role_name to refer to the role of said name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
1. Fork it
|
37
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
38
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
39
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
40
|
+
5. Create new Pull Request
|
41
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/Moby.rb
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'live_ast'
|
3
|
+
require 'live_ast/to_ruby'
|
4
|
+
|
5
|
+
##
|
6
|
+
# The Context class is used to define a DCI context with roles and their role methods
|
7
|
+
# to define a context call define with the name of the context (this name will become the name of the class that defines the context)
|
8
|
+
# the name should be a symbol and since it's going to be used as a class name, use class naming conventions
|
9
|
+
# follow the name with a block. With in this block you can define roles and interactions
|
10
|
+
# and interaction is defined by write the name of the interaction (hello in the below example) followed by a block
|
11
|
+
# the block will become the method body
|
12
|
+
# a role can be defined much like a context. instead of calling the define method call the role method followed by the role name (as a symbol)
|
13
|
+
# the role will be used for a private instance variable and the naming convention should match this
|
14
|
+
# With in the block supplied to the role method you can define role methods the same way as you define interactions. See the method who
|
15
|
+
# in the below example
|
16
|
+
# = Example
|
17
|
+
# Context::define :Greeter do
|
18
|
+
# role :who do
|
19
|
+
# say do
|
20
|
+
# @who #could be self as well to refer to the current role player of the 'who' role
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# greeting do
|
24
|
+
# p "Hello #{who.say}!"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class Greeter
|
29
|
+
# def initialize(player)
|
30
|
+
# #@who = player
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Greeter.new('world').greeting #Will print "Hello world!"
|
35
|
+
#Moby is base on Marvin which was the first injectionless language for DCI
|
36
|
+
#being injectionless there's no runtime extend or anything else impacting the performance. There' only regular method invocation even when using role methods
|
37
|
+
#Author:: Rune Funch Søltoft (funchsoltoft@gmail.com)
|
38
|
+
#License:: Same as for Ruby
|
39
|
+
##
|
40
|
+
class Context
|
41
|
+
@roles
|
42
|
+
@interactions
|
43
|
+
@defining_role
|
44
|
+
@role_alias
|
45
|
+
@alias_list
|
46
|
+
@cached_roles_and_alias_list
|
47
|
+
|
48
|
+
#define is the only exposed method and can be used to define a context (class)
|
49
|
+
#if Moby/kernel is required calling context of Context::define are equivalent
|
50
|
+
#params
|
51
|
+
#name:: the name of the context. Since this is used as the name of a class, class naming convetions should be used
|
52
|
+
#block:: the body of the context. Can include definitions of roles (through the role method) or definitions of interactions
|
53
|
+
#by simply calling a method with the name of the interaction and passing a block as the body of the interaction
|
54
|
+
def self.define(name, &block)
|
55
|
+
ctx = Context.new
|
56
|
+
ctx.instance_eval &block
|
57
|
+
return ctx.send(:finalize, name)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
##
|
62
|
+
#Defines a role with the given name
|
63
|
+
#role methods can be defined inside a block passed to this method
|
64
|
+
# = Example
|
65
|
+
# role :who do
|
66
|
+
# say do
|
67
|
+
# p @who
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#The above code defines a role called 'who' with a role method called say
|
71
|
+
##
|
72
|
+
def role(role_name)
|
73
|
+
raise 'Argument role_name must be a symbol' unless role_name.instance_of? Symbol
|
74
|
+
|
75
|
+
@defining_role = role_name
|
76
|
+
@roles[role_name] = Hash.new
|
77
|
+
yield if block_given?
|
78
|
+
@defining_role = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize
|
82
|
+
@roles = Hash.new
|
83
|
+
@interactions = Hash.new
|
84
|
+
@role_alias = Array.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def role_aliases
|
88
|
+
@alias_list if @alias_list
|
89
|
+
@alias_list = Hash.new
|
90
|
+
@role_alias.each {|aliases|
|
91
|
+
aliases.each {|k,v|
|
92
|
+
@alias_list[k] = v
|
93
|
+
}
|
94
|
+
}
|
95
|
+
@alias_list
|
96
|
+
end
|
97
|
+
|
98
|
+
def roles
|
99
|
+
@cached_roles_and_alias_list if @cached_roles_and_alias_list
|
100
|
+
@roles unless @role_alias and @role_alias.length
|
101
|
+
@cached_roles_and_alias_list = Hash.new
|
102
|
+
@roles.each {|k,v|
|
103
|
+
@cached_roles_and_alias_list[k] = v
|
104
|
+
}
|
105
|
+
role_aliases.each {|k,v|
|
106
|
+
@cached_roles_and_alias_list[k] = @roles[v]
|
107
|
+
}
|
108
|
+
@cached_roles_and_alias_list
|
109
|
+
end
|
110
|
+
|
111
|
+
def methods
|
112
|
+
(@defining_role ? @roles[@defining_role] : @interactions)
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_alias (a,role_name)
|
116
|
+
@cached_roles_and_alias_list,@alias_list = nil
|
117
|
+
@role_alias.last()[a] = role_name
|
118
|
+
end
|
119
|
+
|
120
|
+
def finalize(name)
|
121
|
+
c = Class.new
|
122
|
+
Kernel.const_set name, c
|
123
|
+
code = ''
|
124
|
+
fields = ''
|
125
|
+
getters = ''
|
126
|
+
impl = ''
|
127
|
+
interactions = ''
|
128
|
+
@interactions.each do |method_name, method_source|
|
129
|
+
@defining_role = nil
|
130
|
+
interactions << " #{lambda2method(method_name, method_source)}"
|
131
|
+
end
|
132
|
+
@roles.each do |role, methods|
|
133
|
+
fields << "@#{role}\n"
|
134
|
+
getters << "def #{role};@#{role} end\n"
|
135
|
+
|
136
|
+
methods.each do |method_name, method_source|
|
137
|
+
@defining_role = role
|
138
|
+
rewritten_method_name = "self_#{role}_#{method_name}"
|
139
|
+
definition = lambda2method rewritten_method_name, method_source
|
140
|
+
impl << " #{definition}" if definition
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
code << "#{interactions}\n#{fields}\n private\n#{getters}\n#{impl}\n"
|
145
|
+
|
146
|
+
complete = "class #{name}\r\n#{code}\r\nend"
|
147
|
+
#File.open("#{name}_generate.rb", 'w') { |f| f.write(complete) }
|
148
|
+
return c.class_eval(code),complete
|
149
|
+
end
|
150
|
+
|
151
|
+
def role_or_interaction_method(method_name, &b)
|
152
|
+
raise "method with out block #{method_name}" unless b
|
153
|
+
|
154
|
+
args, block = block2source b.to_ruby, method_name
|
155
|
+
args = "|#{args}|" if args
|
156
|
+
source = "(proc do #{args}\n #{block}\nend)"
|
157
|
+
methods[method_name] = source
|
158
|
+
end
|
159
|
+
|
160
|
+
alias method_missing role_or_interaction_method
|
161
|
+
|
162
|
+
def role_method_call(ast, method)
|
163
|
+
is_call_expression = ast && ast[0] == :call
|
164
|
+
self_is_instance_expression = is_call_expression && (!ast[1]) #implicit self
|
165
|
+
is_in_block = ast && ast[0] == :lvar
|
166
|
+
role_name_index = self_is_instance_expression ? 2 : 1
|
167
|
+
role = (self_is_instance_expression || is_in_block) ? roles[ast[role_name_index]] : nil #is it a call to a role getter
|
168
|
+
is_role_method = role && role.has_key?(method)
|
169
|
+
role_name = is_in_block ? role_aliases[ast[1]] : (ast[2] if self_is_instance_expression)
|
170
|
+
role_name if is_role_method #return role name
|
171
|
+
end
|
172
|
+
|
173
|
+
def lambda2method (method_name, method_source)
|
174
|
+
evaluated = ast_eval method_source, binding
|
175
|
+
ast = evaluated.to_ast
|
176
|
+
transform_ast ast
|
177
|
+
p "#{ast}"
|
178
|
+
args, block = block2source LiveAST.parser::Unparser.unparse(ast), method_name
|
179
|
+
args = "(#{args})" if args
|
180
|
+
"\ndef #{method_name} #{args}\n#{block} end\n"
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
#Test if there's a block that needs to potentially be transformed
|
185
|
+
##
|
186
|
+
def transform_block(exp)
|
187
|
+
if exp && exp[0] == :iter
|
188
|
+
(exp.length-1).times do |i|
|
189
|
+
expr = exp[i+1]
|
190
|
+
#find the block
|
191
|
+
if expr && expr.length && expr[0] == :block
|
192
|
+
transform_ast exp if rewrite_bind? expr,expr[1]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
#Calls rewrite_block if needed and will return true if the AST was changed otherwise false
|
200
|
+
##
|
201
|
+
def rewrite_bind?(block, expr)
|
202
|
+
#check if the first call is a bind call
|
203
|
+
if expr && expr.length && (expr[0] == :call && expr[1] == nil && expr[2] == :bind)
|
204
|
+
arglist = expr[3]
|
205
|
+
if arglist && arglist[0] == :arglist
|
206
|
+
arguments = arglist[1]
|
207
|
+
if arguments && arguments[0] == :hash
|
208
|
+
block.delete_at 1
|
209
|
+
count = (arguments.length-1) / 2
|
210
|
+
(1..count).each do |j|
|
211
|
+
temp = j * 2
|
212
|
+
local = arguments[temp-1][1]
|
213
|
+
if local.instance_of? Sexp
|
214
|
+
local = local[1]
|
215
|
+
end
|
216
|
+
raise 'invalid value for role alias' unless local.instance_of? Symbol
|
217
|
+
#find the name of the role being bound to
|
218
|
+
aliased_role = arguments[temp][1]
|
219
|
+
if aliased_role.instance_of? Sexp
|
220
|
+
aliased_role = aliased_role[1]
|
221
|
+
end
|
222
|
+
raise "#{aliased_role} used in binding is an unknown role #{roles}" unless aliased_role.instance_of? Symbol and @roles.has_key? aliased_role
|
223
|
+
add_alias local, aliased_role
|
224
|
+
#replace bind call with assignment of iteration variable to role field
|
225
|
+
rewrite_bind(aliased_role, local, block)
|
226
|
+
return true
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
false
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
#removes call to bind in a block
|
236
|
+
#and replaces it with assignment to the proper role player local variables
|
237
|
+
#in the end of the block the local variables have their original values reassigned
|
238
|
+
def rewrite_bind(aliased_role, local, block)
|
239
|
+
raise 'aliased_role must be a Symbol' unless aliased_role.instance_of? Symbol
|
240
|
+
raise 'local must be a Symbol' unless local.instance_of? Symbol
|
241
|
+
assignment = Sexp.new
|
242
|
+
assignment[0] = :iasgn
|
243
|
+
assignment[1] = aliased_role
|
244
|
+
load_arg = Sexp.new
|
245
|
+
load_arg[0] = :lvar
|
246
|
+
load_arg[1] = local
|
247
|
+
assignment[2] = load_arg
|
248
|
+
block.insert 1, assignment
|
249
|
+
|
250
|
+
# assign role player to temp
|
251
|
+
temp_symbol = "temp____#{aliased_role}".to_sym
|
252
|
+
assignment = Sexp.new
|
253
|
+
assignment[0] = :lasgn
|
254
|
+
assignment[1] = temp_symbol
|
255
|
+
load_field = Sexp.new
|
256
|
+
load_field[0] = :ivar
|
257
|
+
load_field[1] = aliased_role
|
258
|
+
assignment[2] = load_field
|
259
|
+
block.insert 1, assignment
|
260
|
+
|
261
|
+
# reassign original player
|
262
|
+
assignment = Sexp.new
|
263
|
+
assignment[0] = :iasgn
|
264
|
+
assignment[1] = aliased_role
|
265
|
+
load_temp = Sexp.new
|
266
|
+
load_temp[0] = :lvar
|
267
|
+
load_temp[1] = temp_symbol
|
268
|
+
assignment[2] = load_temp
|
269
|
+
block[block.length] = assignment
|
270
|
+
end
|
271
|
+
|
272
|
+
# rewrites a call to self in a role method to a call to the role player accessor
|
273
|
+
# which is subsequently rewritten to a call to the instance variable itself
|
274
|
+
# in the case where no role method is called on the role player
|
275
|
+
# It's rewritten to an instance call on the context object if a role method is called
|
276
|
+
def rewrite_self (ast)
|
277
|
+
ast.length.times do |i|
|
278
|
+
raise 'Invalid argument. must be an expression' unless ast.instance_of? Sexp
|
279
|
+
exp = ast[i]
|
280
|
+
if exp == :self
|
281
|
+
ast[0] = :call
|
282
|
+
ast[1] = nil
|
283
|
+
ast[2] = @defining_role
|
284
|
+
arglist = Sexp.new
|
285
|
+
ast[3] = arglist
|
286
|
+
arglist[0] = :arglist
|
287
|
+
elsif exp.instance_of? Sexp
|
288
|
+
rewrite_self exp
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
#rewrites the ast so that role method calls are rewritten to a method invocation on the context object rather than the role player
|
294
|
+
#also does rewriting of binds in blocks
|
295
|
+
def transform_ast(ast)
|
296
|
+
if ast
|
297
|
+
if @defining_role
|
298
|
+
rewrite_self ast
|
299
|
+
end
|
300
|
+
ast.length.times do |k|
|
301
|
+
exp = ast[k]
|
302
|
+
if exp
|
303
|
+
method_name = exp[2]
|
304
|
+
role = role_method_call exp[1], exp[2]
|
305
|
+
if exp[0] == :iter
|
306
|
+
@role_alias.push Hash.new
|
307
|
+
transform_block exp
|
308
|
+
@role_alias.pop()
|
309
|
+
end
|
310
|
+
if exp[0] == :call && role
|
311
|
+
exp[1] = nil #remove call to attribute
|
312
|
+
exp[2] = "self_#{role}_#{method_name}".to_sym
|
313
|
+
end
|
314
|
+
if exp.instance_of? Sexp
|
315
|
+
transform_ast exp
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
#cleans up the string for further processing and separates arguments from body
|
323
|
+
def block2source(b, method_name)
|
324
|
+
args = nil
|
325
|
+
block = b.strip
|
326
|
+
block = block[method_name.length..-1].strip if block.start_with? method_name.to_s
|
327
|
+
block = cleanup_head_and_tail(block)
|
328
|
+
if block.start_with? '|'
|
329
|
+
args = block.scan(/\|([\w\d,\s]*)\|/)
|
330
|
+
if args.length && args[0]
|
331
|
+
args = args[0][0]
|
332
|
+
else
|
333
|
+
args = nil
|
334
|
+
end
|
335
|
+
block = block[(2 + (block[1..-1].index '|'))..-1].strip
|
336
|
+
end
|
337
|
+
return args, block
|
338
|
+
end
|
339
|
+
|
340
|
+
# removes proc do/{ at start and } or end at the end of the string
|
341
|
+
def cleanup_head_and_tail(block)
|
342
|
+
if /^proc\s/.match(block)
|
343
|
+
block = block['proc'.length..-1].strip
|
344
|
+
end
|
345
|
+
if /^do\s/.match(block)
|
346
|
+
block = block[2..-1].strip
|
347
|
+
elsif block.start_with? '{'
|
348
|
+
block = block[1..-1].strip
|
349
|
+
end
|
350
|
+
|
351
|
+
if /end$/.match(block)
|
352
|
+
block = block[0..-4]
|
353
|
+
elsif /\}$/.match(block)
|
354
|
+
block = block[0..-2]
|
355
|
+
end
|
356
|
+
block
|
357
|
+
end
|
358
|
+
end
|