Skip to content

Instantly share code, notes, and snippets.

@runefs
Last active December 11, 2015 15:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runefs/4621229 to your computer and use it in GitHub Desktop.
Save runefs/4621229 to your computer and use it in GitHub Desktop.
This is an example of how DCI can be done in Ruby without injecting methods into objects. It's inspired by how the Marvin compiler works, and is basically rewritting method calls on a RolePlayer into a method call on the context object when the method being cllaed is a role method. It does not work completely like marvin E.g. any access to a rol…
require 'live_ast'
require 'live_ast/to_ruby'
module Player
alias metho_missing_org method_missing
def method_missing (name, *args)
current = Context.current
raise "no current context" unless current
role_name = current.role_name
method_name = "self_#{role_name}_#{name}".to_sym
if current.class.method_defined? method_name
current.send(method_name,*args)
else
metho_missing_org name, *args
end
end
end
class Context
@@defining_role
@@current_block
@@stack = Hash.new
@role_name
@index
def initialize
@role_name ||= Array.new
end
def bind(player, role_name)
player.class.send(:include, Player) unless player.is_a? Player
create_instance_aliases player, role_name
player
end
def Context.stack
@@stack[Thread.current.object_id] ||= Array.new
end
def push
Context.stack.push(self)
end
def pop(pop_role_name)
Context.stack.pop()
pop_role() if pop_role_name == self.role_name
end
def Context.current
Context.stack.last()
end
def role_name
@role_name[@index][0]
end
def role_id
@role_name[@index][-1]
end
def push_role(name, player)
@index = if @index then (@index + 1) % 256 else 1 end
(@role_name ||= Array.new)[@index] = [name,player.object_id]
player
end
def pop_role
name = @role_name[@index]
@index = if @index != 0 then @index - 1 else 255 end
name
end
def create_instance_aliases (player,role_name)
raise StandardException "player must have a value" unless player
prefix = "self_#{role_name}_"
alias_prefix = "#{self.class.name}_aliased_"
role_methods = self.methods.select do |m|
name = m.to_s
if name.start_with? prefix
method_name = name[(prefix.length)..-1].to_sym
has_instance_method = player.class.method_defined? method_name
has_context_override_already = player.class.method_defined? "#{alias_prefix}#{name}".to_sym
has_instance_method && !has_context_override_already
end
end
role_methods = role_methods.map do |m|
m.to_s[(prefix.length)..-1]
end
role_methods.each do |method_name|
alias_name = "#{alias_prefix}#{method_name}"
role_method_name = "self_#{role_name}_#{method_name}"
role_method_override = "\r\ndef #{method_name}(*args)\r\n"+
"current = Context.current || self\r\n"+
" is_playing = current.role_id == self.object_id\r\n" +
" is_current_role = current.role_name == :#{role_name}\r\n"+
" role_method_exists = current.class.method_defined? :#{role_method_name}\r\n" +
#" p \"playing: \#{is_playing} current role: \#{current.role_name} == :#{role_name} -> \#{is_current_role} role method (#{role_method_name})exists: \#{role_method_exists}\"\r\n" +
"if is_playing && is_current_role && role_method_exists\r\n"+
#" p 'calling role method'\r\n"+
" current.#{role_method_name} *args\r\n"+
"else\r\n"+
#" p 'calling instance method'\r\n"+
" #{alias_name} *args\r\n"+
"end\r\n"+
"end\r\n"
player.class.class_eval("alias :#{alias_name} :#{method_name}\r\n" +role_method_override)
end
end
def Context.role(role_name)
name = role_name.to_s
@@defining_role = name
@@current_block = ""
#creates code similar to
#@name
#def name
# @name
#end
#def name=(player)
# @name = bind(player)
#end
#def self_name args
# content of block
#end
pre = "@#{name}\r\n"+
"def #{name}\r\n" +
"push_role :#{name},@#{name}\r\n" +
"end\r\n" +
"def #{name}=(player)\r\n"+
" @#{name} = bind player, :#{name}\r\n"+
"end\r\n" +
"private :#{name}\r\n"
yield if block_given?
code = pre + (@@current_block || "")
class_eval(code)
end
def Context.block2method(method_name,pop_role,&b)
name = method_name.to_s
code = "\r\n"
args = ""
if block_given?
block = b.to_ruby
block.strip!
index = (block.index '|') + 1
index = 0 if index < 0
block = block[index..-1]
index = (block.index '|') - 1
index = -1 if index < 0
args = block[0..index]
if /end$/.match(block)
block = block[(index+2)..-4]
elsif /\}$/.match(block)
block = block[(index+2)..-2]
end
else
block = ""
end
code += "\r\ndef #{name}_inner #{args} \r\n#{block}\r\n end\r\n"
code += "\r\ndef #{name} #{args} \r\nself.push()\r\nretval = #{name}_inner #{args}\r\nself.pop(#{pop_role})\r\nretval\r\n end\r\n"
code
end
def Context.role_method(method_name, &b)
role_method_name = "self_#{@@defining_role}_#{method_name}"
@@current_block += block2method role_method_name,true,&b
end
def Context.interaction(method_name, &b)
code = block2method method_name,false,&b
class_eval(code)
end
end
class MoneyTransfer < Context
role :source do
role_method :withdraw do |amount|
source.movement(amount)
p (source.log "withdrawal #{amount}")
end
role_method :log do |message|
p "role #{message}"
"return value"
end
end
role :destination do
role_method :deposit do |amount|
destination.movement(amount)
destination.log "deposit #{amount}"
end
end
interaction :transfer do |amount|
source.withdraw -amount
destination.deposit amount
end
def initialize(s,d)
self.source = s
self.destination = d
end
end
class Account
def initialize (amount,id)
@balance = amount
@account_id = id
end
def movement(amount)
p "Amount #{amount}"
@balance+=amount
end
def log(message)
p "instance #{message}"
end
def to_s
"balance of #{@account_id}: #{@balance}"
end
end
account = Account.new 1000, "source"
ctx = MoneyTransfer.new account, account
ctx.transfer 100
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment