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.
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in Moby.gemspec
4
+ gemspec
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