Class: Reduxco::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/reduxco/context.rb,
lib/reduxco/context/callstack.rb,
lib/reduxco/context/callable_table.rb

Overview

Context is the client facing object for Reduxco.

Typically, one instantiates a Context with one or more maps of callables by name, and then calls Contxt#reduce to calculate all dependent nodes and return a result.

Maps may be any object that, when iterated with each, gives name/callable pairs. Names may be any object that can serve as a hash key. Callables can be any object that responds to the call method.

Overview

Context orchestrates the reduction calculation. It is primarily used by callables invoked during computation to get access to their environment.

Instantiators of a Context typically only use the Context#reduce method.

Users of Reduxco should use Reduxco::Reduxer rather than directly consume Context directly.

Callable Helper Functions

Callables (objects that respond to call) are the meat of the Context. When their call method is invoked, it is passed a reference to the Context. Callables can use this reference to access a range of methods, including the following:

Context#call

Given a refname, run the associated callable and returns its value. Usually invoked as Context#[]

Context#include?

Introspects if a refname is available.

Context#completed?

Instrospects if a callable has been called and returned.

Context#after

Given a refname and a block, runs the contents of the block after the given refname, but returns the value of the callable accociated with the refname.

Context#inside

Given a refname and a block, runs the callable associated with the refname, giving it access to running the block inside of it and getting its value.

Defined Under Namespace

Classes: AssertError, CallableTable, Callstack, CyclicalError, LocalJumpError, NameError, NotCallableError

Instance Method Summary collapse

Constructor Details

#initialize(*callable_maps) ⇒ Context

Instantiate a Context with the one or more callalbe maps (e.g. hashes whose keys are names and values are callable) for calculations.

The further to the right in the arguments that a map is, the higher the precedence of itsdefinition.



65
66
67
68
69
70
71
72
73
74
# File 'lib/reduxco/context.rb', line 65

def initialize(*callable_maps)
  @callable_maps = callable_maps
  @calltable = CallableTable.new(@callable_maps)

  @callstack = Callstack.new
  @cache = {}

  @block_association_cache = {}
  @yield_frame_depth = 0
end

Instance Method Details

#after(refname) ⇒ Object

Runs the passed block after calling the passed refname. Returns the value of the call to refname.



197
198
199
200
201
# File 'lib/reduxco/context.rb', line 197

def after(refname)
  result = call(refname)
  yield if block_given?
  result
end

#assert_completed(refname) ⇒ Object

Raises an exception if completed? is false. Useful for asserting weak dependencies (those which you do not need the return value of) have been met.

Raises:



184
185
186
# File 'lib/reduxco/context.rb', line 184

def assert_completed(refname)
  raise AssertError, "Assertion that #{refname} has completed failed.", caller unless completed?(refname)
end

#before(refname) ⇒ Object

Runs the passed block before calling the passed refname. Returns the value of the call to refname.



190
191
192
193
# File 'lib/reduxco/context.rb', line 190

def before(refname)
  yield if block_given?
  call(refname)
end

#call(refname = :app, &block) ⇒ Object Also known as: reduce, [], inside

Given a refname, call it for this context and return the result.

This can also take CallableRef instances directly, however if you find yourself passing in static references, this is likely because of design flaw in your callable map hierarchy.

Call results are cached so that their values can be re-used. If callables have side-effects their side-effects are only invoked the first time they are run.

Given a block, Context#yield may be used by the callable to invoke the block. Depending on the purpose of the block, Context#inside may be the preferrable alias.



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/reduxco/context.rb', line 89

def call(refname=:app, &block)
  # First, we resolve the callref and invoke it.
  frame, callable = @calltable.resolve( CallableRef.new(refname) )

  # If the ref is nil then we couldn't resolve, otherwise invoke.
  if( frame.nil? )
    raise NameError, "No reference for name #{refname.inspect}", caller
  else
    invoke(frame, callable, &block)
  end
end

#callstackObject

Returns a copy of the current callstack.



150
151
152
# File 'lib/reduxco/context.rb', line 150

def callstack
  @callstack.dup
end

#completed?(refname) ⇒ Boolean

Returns a true value if the given refname has been computed.

If the given CallableRef, it returns a true if the reference has already been computed.

Returns:

  • (Boolean)


175
176
177
178
179
# File 'lib/reduxco/context.rb', line 175

def completed?(refname)
  callref = CallableRef.new(refname)
  key = callref.dynamic? ? @calltable.resolve(callref).first : callref
  @cache.include?(key)
end

#current_frameObject

Returns the top frame of the callstack.



155
156
157
# File 'lib/reduxco/context.rb', line 155

def current_frame
  @callstack.top
end

#dupObject

Duplication of Contexts are dangerous because of all the deeply nested structures. That being said, it is very tempting to try to use a well-constructed Context rather than save and reuse the callable maps used for instantiation.

To remedy this concern, dup acts as a copy constructor, making a new Context instance with the same callable maps, but is otherwise freshly constructed.



211
212
213
# File 'lib/reduxco/context.rb', line 211

def dup
  self.class.new(*@callable_maps)
end

#include?(refname) ⇒ Boolean

Returns a true value if the given refname is defined in this context.

If given a CallableRef, it returns a true value if the reference is resolvable.

Returns:

  • (Boolean)


167
168
169
# File 'lib/reduxco/context.rb', line 167

def include?(refname)
  @calltable.resolve( CallableRef.new(refname) ) != CallableTable::RESOLUTION_FAILURE
end

#super(&block) ⇒ Object

When invoked, finds the next callable in the CallableTable up the chain from the current frame, calls it, and returns the result.

This is primarily used to reference shadowed callables in their overrides.

Like Context#call, it may take a block that is yielded to with Context#yield. If no block is given but the current scope has a block, its block will be automatically forwarded.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/reduxco/context.rb', line 118

def super(&block)
  # First, we resolve the super ref.
  frame, callable = @calltable.resolve_super( current_frame )

  # If the ref is nil then we couldn't resolve, otherwise invoke.
  if( frame.nil? )
    raise NameError, "No super found for #{current_frame}", caller
  else
    block = block_for_frame(current_frame) if block.nil?
    invoke(frame, callable, &block)
  end
end

#yield(*args) ⇒ Object

Yields to the block given to a #Context.call



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/reduxco/context.rb', line 132

def yield(*args)
  block = block_for_frame(yield_frame)
  if( block.nil? )
    raise LocalJumpError, "No block given to yield to.", caller
  else
    begin
      # If the block call has a context yield inside, resolve that one frame up.
      # This turns out to be what we usually want as we are forwarding a
      # yielded value in the call.
      @yield_frame_depth += 1
      block.call(*args)
    ensure
      @yield_frame_depth -= 1
    end
  end
end

#yield_frameObject



159
160
161
# File 'lib/reduxco/context.rb', line 159

def yield_frame
  @callstack.peek(@yield_frame_depth)
end