Initial cut of the "define" API with blocks

This has taken a lot of experimenting and research to get to, and it has to
resort to clever method_missing hacks because it appears to be absolutely
impossible to take a binding from a block and mutate it.

Original appraoch looked something like this:

    define :log, message: String do with
        # ... logic
    end

The `with` method couldn't access the right caller's binding (using clever
hacks: <http://rubychallenger.blogspot.com/2011/07/caller-binding.html>)

A follow up approach:

    define :log, message: String do with binding
        # ... logic
    end

Wouldn't work because the binding object ID inside the block would change as
soon as the with method would mutata it.

Thus the current approach, which is probably not going to perform worth a shit
This commit is contained in:
R. Tyler Croy 2014-10-24 01:00:14 -07:00
parent 5d005c2c7f
commit eb4df666fc
7 changed files with 154 additions and 1 deletions

1
.rspec Normal file
View File

@ -0,0 +1 @@
--order random --fail-fast --color --format doc

View File

@ -2,3 +2,12 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in typedeaf.gemspec
gemspec
gem 'facets'
group :development do
gem 'rake'
gem 'rspec'
gem 'pry'
gem 'debugger-pry', :platform => :mri
end

View File

@ -1,2 +1,7 @@
require "bundler/gem_tasks"
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
task :default => [:spec, :build]

View File

@ -1,5 +1,71 @@
require 'typedeaf/errors'
require "typedeaf/version"
require 'continuation'
require 'facets/binding'
module Typedeaf
# Your code goes here...
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
def __typedeaf_varstack__
if @__typedeaf_varstack__.nil?
@__typedeaf_varstack__ = []
end
return @__typedeaf_varstack__
end
def method_missing(sym, *args)
# We only want to peek at the stack if we have no args (i.e. not trying
# to make a method call
if args.empty? && !(__typedeaf_varstack__.empty?)
params, values = __typedeaf_varstack__.last
# The top of our stack contains something that we want
return values[sym] if params[sym]
end
return super
end
end
module ClassMethods
def define(method_sym, params={}, &block)
if block.nil?
raise MissingMethodException,
"You must provide a block for the #{method_sym} body"
end
define_method(method_sym) do |*args|
param_indices = {}
params.each.with_index do |(key, value), index|
param_indices[key] = args[index]
end
__typedeaf_varstack__ << [params, param_indices]
begin
instance_exec(*args, &block)
ensure
__typedeaf_varstack__.pop
end
end
return self
end
end
# Install the Typedeaf methods onto Class to be used everywhere
def self.global_install
Class.class_eval do
include Typedeaf
alias_method :old_inherited, :inherited
def inherited(subclass)
subclass.class_eval do
include Typedeaf
end
return old_inherited(subclass)
end
end
end
end

4
lib/typedeaf/errors.rb Normal file
View File

@ -0,0 +1,4 @@
module Typedeaf
class MissingMethodException < StandardError; end
end

6
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,6 @@
require 'rubygems'
require 'typedeaf'
unless RUBY_PLATFORM == 'java'
require 'debugger/pry'
end

62
spec/typedeaf_spec.rb Normal file
View File

@ -0,0 +1,62 @@
require 'spec_helper'
describe Typedeaf do
subject(:klass) do
Class.new do
include Typedeaf
end
end
context 'with a new class' do
it { should be_kind_of Class }
it { should respond_to :define }
end
context 'defining typedeaf instance methods' do
subject(:instance) { klass.new }
context 'defining a method with an invalid block' do
it 'should raise a MissingMethodException' do
expect {
klass.class_eval do
define :log
end
}.to raise_error(Typedeaf::MissingMethodException)
end
end
#context 'defining a method with no arguments' do
# before :each do
# klass.class_eval do
# tdef :log do
# 'hello rspec'
# end
# end
# end
# it { should respond_to :log }
# it 'should return the right value when invoked' do
# expect(instance.log).to eql('hello rspec')
# end
#end
context 'defining a method with arguments' do
before :each do
klass.class_eval do
define :log, message: String do
"hello #{message}"
end
end
end
it { should respond_to :log }
it 'should use the arguments to generate a result' do
puts "instance: #{instance.object_id}"
expect(instance.log('world')).to eql('hello world')
end
end
end
end