Created
April 21, 2009 23:06
-
-
Save rkh/99457 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "cheap_proc" | |
file = "/tmp/stored_proc" | |
unless File.exists? file | |
foo = 42 | |
cheap = lambda { puts foo }.to_cheap_proc | |
File.open(file, "w") { |f| f.write(cheap.dump) } | |
else | |
cheap = File.read(file).to_cheap_proc | |
end | |
cheap.call |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# | |
# This library contains an alternative closure implementation that is both more lightweight | |
# than ruby's build-in Proc class and can be stored in a string (read: a file, can be sent | |
# over network, etc.) | |
require "rubygems" | |
require "sexp_processor" | |
require "parse_tree" | |
require "ruby2ruby" | |
TOPLEVEL_OWNER = self | |
# A CheapProc is a Proc replacement using less memory than Proc. Besides this, it can be | |
# stored in a string (and of course can be restored from a string) and still keep the original | |
# scope. This is accomplished by a pure-ruby upval implementation (simmilar to what tinyrb/lua does). | |
class CheapProc | |
class BlockParser < SexpProcessor # :nodoc: | |
attr_accessor :scope | |
def lvars | |
@lvars ||= eval("local_variables", scope) | |
end | |
def upvals | |
@upvals ||= {} | |
end | |
def process_call(sexp) | |
upvals[sexp[1]] = eval(sexp[1], scope) if sexp.first.nil? and lvars.include? sexp[1] | |
result = sexp.dup | |
sexp.clear | |
result | |
end | |
end | |
def self.from_proc(block) | |
new.tap do |cheap| | |
scope = block.binding | |
owner = eval("self", scope) | |
unifier = Unifier.new.tap { |u| u.processors.each { |p| p.unsupported.delete :cfunc } } | |
parser = BlockParser.new | |
parser.scope = scope | |
cheap.block = block | |
cheap.sexp = parser.process unifier.process(ParseTree.new.parse_tree_for_proc(block)) | |
cheap.upvals = parser.upvals | |
cheap.block_owner = owner unless owner == TOPLEVEL_OWNER | |
cheap.freeze | |
end | |
end | |
def self.from_string(string) | |
new.tap do |cheap| | |
Marshal.load(string).each { |k, v| cheap.send("#{k}=", v) } | |
cheap.freeze | |
end | |
end | |
attr_accessor :sexp, :upvals, :block, :block_owner | |
# for really using less space than a proc, one has to call this after creating a cheap proc form a proc | |
def clean_up | |
@block, @fixed_sexp = nil, nil | |
GC.start | |
end | |
def frozen? | |
!!@frozen | |
end | |
def freeze | |
@frozen = true | |
upvals.freeze | |
self.sexp = sexp.to_a.freeze | |
class << self | |
%w(sexp upvals block block_owner).each do |attr| | |
define_method("#{attr}=") { raise TypeError, "can't modify frozen CheapProc" } | |
end | |
end | |
self | |
end | |
def to_cheap_proc | |
self | |
end | |
def dump | |
Marshal.dump "upvals" => upvals, "block_owner" => block_owner, "sexp" => sexp | |
end | |
def to_ruby | |
Ruby2Ruby.new.process to_sexp | |
end | |
def to_sexp | |
@fixed_sexp ||= sexp.dup.tap do |fixed| | |
fixed[3] = upvals.inject(s(:block)) { |sx, (k, v)| sx << s(:lasgn, k.to_sym, s(:lit, v)) } << fixed[3] | |
end | |
end | |
def to_proc | |
@block ||= block_owner ? block_owner.instance_eval(to_ruby) : eval(to_ruby, TOPLEVEL_BINDING) | |
end | |
def method_missing(name, *a, &b) | |
return super unless Proc.instance_methods.include? name.to_s | |
to_proc.send(name, *a, &b) | |
end | |
end | |
class Object | |
unless respond_to? :tap | |
def tap | |
yield(self) | |
self | |
end | |
end | |
end | |
class Proc | |
def self.===(other) | |
super or CheapProc === other | |
end | |
def to_cheap_proc | |
CheapProc.from_proc(self) | |
end | |
end | |
class String | |
def to_cheap_proc | |
CheapProc.from_string(self) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment