Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Clean scoping mechanism, keeping proper linear parent chains.

  • Loading branch information...
commit 37dfc72d48e51ee570100dcf5153ba8d0f09178b 1 parent 8373eb2
Bernard Lambeau authored
42 lib/wlang/scope.rb
View
@@ -4,38 +4,35 @@ class Scope
attr_reader :subject
attr_reader :parent
- def initialize(subject, parent)
- @subject, @parent = subject, parent
+ def initialize(subject)
+ @subject = subject
+ @parent = nil
end
def self.null
@null ||= NullScope.new
end
- def self.coerce(arg, parent = nil)
- return arg if Scope===arg && parent.nil?
- clazz = case arg
- when Binding then BindingScope
- when Scope then ProxyScope
- when Proc then ProcScope
+ def self.coerce(arg)
+ case arg
+ when Binding then BindingScope.new(arg)
+ when Scope then arg
+ when Proc then ProcScope.new(arg)
else
- ObjectScope
+ ObjectScope.new(arg)
end
- clazz.new(arg, parent)
end
def self.chain(scopes)
- scopes.compact.inject(nil){|parent,child|
- Scope.coerce(child, parent)
- }
+ scopes.compact.inject(Scope.null){|p,c| p.push(c)}
end
def root
- parent.nil? ? self : parent.root
+ parent ? parent.root : self
end
def push(x)
- Scope.coerce(x, self)
+ append(Scope.coerce(x))
end
def pop
@@ -61,6 +58,20 @@ def evaluate(expr, *default)
protected
+ def append(x)
+ x.prepend(self)
+ end
+
+ def prepend(x)
+ newp = parent ? parent.prepend(x) : x
+ dup.with_parent!(newp)
+ end
+
+ def with_parent!(p)
+ @parent = p
+ self
+ end
+
def safe_parent
parent || Scope.null
end
@@ -68,7 +79,6 @@ def safe_parent
end # class Scope
end # module WLang
require 'wlang/scope/null_scope'
-require 'wlang/scope/proxy_scope'
require 'wlang/scope/object_scope'
require 'wlang/scope/binding_scope'
require 'wlang/scope/proc_scope'
20 lib/wlang/scope/null_scope.rb
View
@@ -3,15 +3,7 @@ class Scope
class NullScope < Scope
def initialize
- super(nil,nil)
- end
-
- def push(x)
- Scope.coerce(x)
- end
-
- def pop
- nil
+ super(nil)
end
def fetch(key)
@@ -23,6 +15,16 @@ def inspect
end
alias :to_s :inspect
+ protected
+
+ def append(x)
+ x
+ end
+
+ def prepend(x)
+ x
+ end
+
end # class NullScope
end # class Scope
end # module WLang
18 lib/wlang/scope/proxy_scope.rb
View
@@ -1,18 +0,0 @@
-module WLang
- class Scope
- class ProxyScope < Scope
-
- def fetch(key, &blk)
- subject.fetch(key) do
- safe_parent.fetch(key, &blk)
- end
- end
-
- def inspect
- subject.inspect
- end
- alias :to_s :inspect
-
- end # class ProxyScope
- end # class Scope
-end # module WLang
4 lib/wlang/template.rb
View
@@ -44,8 +44,8 @@ def to_ast
compiler.to_ast(template_content)
end
- def call(locs = {}, buffer = '')
- scope = WLang::Scope.chain([locals, locs])
+ def call(locals = {}, buffer = '')
+ scope = WLang::Scope.chain([self.locals, locals])
dialect_instance.dup.render compiled, scope, buffer
end
alias :render :call
4 spec/integration/scoping/hello.wlang
View
@@ -0,0 +1,4 @@
+---
+who: world
+---
+Hello ${who}!
16 spec/integration/scoping/test_scoping_rules.rb
View
@@ -0,0 +1,16 @@
+require 'spec_helper'
+module WLang
+ describe "WLang scoping rules" do
+
+ let(:template){ Template.new(Path.dir / 'hello.wlang') }
+
+ specify 'template locals are used by default' do
+ template.render.should eq("Hello world!")
+ end
+
+ specify 'template locals are overriden by render locals' do
+ template.render(:who => "wlang").should eq("Hello wlang!")
+ end
+
+ end
+end
2  spec/unit/scope/test_binding_scope.rb
View
@@ -10,7 +10,7 @@ class Scope
end
it 'delegates fetch to its parent when not found' do
- scope = Scope.coerce(binding, Scope.coerce(:who => "World"))
+ scope = Scope.coerce(:who => "World").push(binding)
scope.fetch(:who).should eq("World")
end
4 spec/unit/scope/test_chain.rb
View
@@ -1,8 +1,8 @@
module WLang
describe Scope, '.chain' do
- it 'returns nil on empty chain' do
- Scope.chain([]).should be_nil
+ it 'returns the NullScope on empty chain' do
+ Scope.chain([]).should eq(Scope.null)
end
it 'returns a single scope on singleton' do
4 spec/unit/scope/test_coerce.rb
View
@@ -24,9 +24,9 @@ module WLang
Scope.coerce(s).should eq(s)
end
- it 'builds ProxyScope on Scopes' do
+ it 'returns the Scope on a Scope' do
s = Scope.coerce({})
- Scope.coerce(s, Scope.null).should be_a(Scope::ProxyScope)
+ Scope.coerce(s).should eq(s)
end
end # describe Scope
2  spec/unit/scope/test_null_scope.rb
View
@@ -14,7 +14,7 @@ class Scope
end
it 'returns pushed scope on push' do
- pushed = ObjectScope.new(12, nil)
+ pushed = ObjectScope.new(12)
scope.push(pushed).should eq(pushed)
end
2  spec/unit/scope/test_object_scope.rb
View
@@ -24,7 +24,7 @@ def x.[](k); "World"; end
end
it 'delegates fetch to its parent when not found' do
- scope = Scope.coerce(12, Scope.coerce({:who => "World"}))
+ scope = Scope.coerce({:who => "World"}).push(12)
scope.fetch(:who).should eq("World")
end
2  spec/unit/scope/test_proc_scope.rb
View
@@ -9,7 +9,7 @@ class Scope
end
it 'delegates fetch to its parent when not found' do
- scope = Scope.coerce(lambda{ nil }, Scope.coerce({:who => "World"}))
+ scope = Scope.coerce({:who => "World"}).push(lambda{ nil })
scope.fetch(:who).should eq("World")
end
22 spec/unit/scope/test_proxy_scope.rb
View
@@ -1,22 +0,0 @@
-require 'spec_helper'
-module WLang
- class Scope
- describe ProxyScope do
-
- it 'delegates fetch to its subject' do
- proxy = Scope.coerce(Scope.coerce(:who => "World"))
- proxy.fetch(:who).should eq("World")
- end
-
- it 'delegates fetch to its parent when not found' do
- proxy = Scope.coerce(Scope.null, Scope.coerce(:who => "World"))
- proxy.fetch(:who).should eq("World")
- end
-
- it 'fetches `self` correctly' do
- Scope.coerce(Scope.coerce(12)).fetch(:self).should eq(12)
- end
-
- end # describe ProxyScope
- end # class Scope
-end # module WLang
61 spec/unit/scope/test_push.rb
View
@@ -0,0 +1,61 @@
+require 'spec_helper'
+module WLang
+ describe Scope, 'push' do
+
+ let(:x_scope){ Scope.coerce(:x) }
+
+ subject{ x_scope.push(pushed) }
+
+ before do
+ subject.should be_a(Scope)
+ end
+
+ after do
+ x_scope.parent.should be_nil
+ x_scope.subject.should eq(:x)
+ x_scope.root.should eq(x_scope)
+ end
+
+ context 'when pushing a simple value' do
+ let(:pushed){ :y }
+
+ it 'returns the scope with correct subject' do
+ subject.subject.should eq(:y)
+ end
+
+ it 'sets the parent correctly on created scope' do
+ subject.parent.should eq(x_scope)
+ end
+
+ it 'returns the correct root scope' do
+ subject.root.should eq(x_scope)
+ end
+ end
+
+ context 'when pushing another scope' do
+ let(:pushed){ Scope.coerce(:y).push(:z) }
+
+ it 'returns the scope with most specific subject' do
+ subject.subject.should eq(:z)
+ end
+
+ it 'rechains parents correctly' do
+ subject.parent.subject.should eq(:y)
+ subject.parent.parent.subject.should eq(:x)
+ subject.parent.parent.parent.should be_nil
+ end
+
+ it 'returns the correct root scope' do
+ subject.root.should eq(x_scope)
+ end
+
+ it 'does not touch the original scope' do
+ pushed.subject.should eq(:z)
+ pushed.parent.subject.should eq(:y)
+ pushed.parent.parent.should be_nil
+ pushed.root.should eq(pushed.parent)
+ end
+ end
+
+ end
+end
7 spec/unit/test_scope.rb
View
@@ -2,12 +2,11 @@
module WLang
describe Scope do
- let(:scope){ Scope.coerce({:who => "World"}, Scope.null) }
+ let(:scope){ Scope.coerce({:who => "World"}) }
it 'acts like a stack' do
s = scope
s.evaluate(:who).should eq("World")
- lambda{ s.pop.evaluate(:who) }.should throw_symbol(:fail)
s = scope.push(:who => "World2")
s.evaluate(:who).should eq("World2")
s = s.pop
@@ -29,9 +28,9 @@ module WLang
end
it 'gives access to the root' do
- scope.root.should eq(Scope.null)
+ scope.root.should eq(scope)
scope.with(:other => "World2") do |s|
- s.root.should eq(Scope.null)
+ s.root.should eq(scope)
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.