Class: Cheri::Builder::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/cheri/builder/context.rb

Defined Under Namespace

Classes: ConnectionMinder

Instance Method Summary collapse

Constructor Details

#initialize(ictx, client) ⇒ Context

call-seq:

Context.new(instance_context,client) -> aContext

– Key to the cryptic instance variable names:

@a - any? hash - active builders (on the stack) for which any? -> true
@c - the client (normally instance of class that included Cheri builder modules)
@f - reference to the factories in the class's Config instance
@g - reference to the class's Config instance (all included Cheri builder modules)
@p - configuration properties hash, used for various purposes
@i - reference to the InstanceContext that created this Context
@l - reference to aliases from InstanceConfig
@m - the ConnectionMinder for this context, tracks pending (prepared) connections
@n - reference to the connecters in the class's Config instance
@r - array of ConstantResolver instances, lazily initialized
@s - the stack, holds active builders and builder frames
@u - auto-factories array, lazily initialized
@x - flag indicating whether instance_exec is supported, lazily initialized

++



140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/cheri/builder/context.rb', line 140

def initialize(ictx,client)
  @i = ictx
  cfg = @g = ictx.cfg
  @f = cfg.factories
  @n = cfg.connecters
  if (u = ictx.auto)
    @u = u.dup
  end
  @l = ictx.aliases
#    @c = client
  @m = ConnectionMinder.new
  @s = [] # stack
  @p = {} # configuration properties
end

Instance Method Details

#[](k) ⇒ Object

call-seq:

ctx[key] -> value


582
583
584
# File 'lib/cheri/builder/context.rb', line 582

def [](k)
  @p[k]
end

#[]=(k, v) ⇒ Object

call-seq:

ctx[key] = value -> value


589
590
591
# File 'lib/cheri/builder/context.rb', line 589

def []=(k,v)
  @p[k]=v  
end

#active?Boolean

call-seq:

ctx.active? -> true/false

Returns true if the context stack is not empty.

Returns:

  • (Boolean)


403
404
405
# File 'lib/cheri/builder/context.rb', line 403

def active?
  !@s.empty?
end

#alsObject Also known as: aliases

call-seq:

ctx.aliases -> aliases hash
ctx.als -> aliases hash


597
598
599
# File 'lib/cheri/builder/context.rb', line 597

def als
  @l  
end

#autoObject

def client

  @c
end


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/cheri/builder/context.rb', line 163

def auto
  # same mods or both nil
  if (u = @u) == (iu = @i.auto)
    return u    
  end
  # TODO: save ref to iu to compare? otherwise we'll do this every time if
  # thread-only auto is set for one mod and instance auto is set for another.
  if iu
    if u
      u.concat(iu - u)
    else
      u = @u = iu.dup
    end    
  end
  u
end

#auto!(m) ⇒ Object

Raises:

  • (BuilderException)


180
181
182
183
# File 'lib/cheri/builder/context.rb', line 180

def auto!(m)
  raise BuilderException,"not an included builder module: #{m}" unless @g.include?(m)
  (@u ||= []) << m unless @u && @u.include?(m)
end

#bld(*r, &k) ⇒ Object Also known as: builder

call-seq:

ctx.builder(symbol, *args [, &block]) -> aBuilder or nil
ctx.bld(symbol, *args [, &block]) -> aBuilder or nil

Searches the stack for a builder/frame whose associated factory can supply a builder for the specified symbol (usually originating in the client’s method_missing method). If no stack frame can supply a builder, any auto-enabled builder modules are searched in the order they were enabled. Returns nil if no builder is found.



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/cheri/builder/context.rb', line 429

def bld(*r,&k)
  #puts "bld args: #{[r.join(',')]}"
  queried = nil # lazily-allocated array to hold factories we've already queried
  b = nil # builder
  atf = @f
  # search stack for a frame whose module's factory can supply a matching builder
  @s.reverse_each do |s|
    if (f = atf[s.mod])
      unless queried && queried.include?(f)
        return b if (b = f.builder(self,*r,&k))
        (queried ||= []) << f
      end
    end
  end
  
  # try auto-enabled builders
  if (u = auto)
    u.each do |m|
      if (f = atf[m])
        unless queried && queried.include?(f)
          return b if (b = f.builder(self,*r,&k))
          (queried ||= []) << f
        end
      end
    end
  end
  
  # no matches
  nil
end

#bottomObject



362
363
364
# File 'lib/cheri/builder/context.rb', line 362

def bottom
  @s.first
end

#call(b, *r, &k) ⇒ Object

call-seq:

ctx.call(builder, *args, &block) -> result

Pushes builder onto the stack, then calls the block/proc, passing the builder’s object and any other arguments supplied: block.call(builder.object,*args) Then pops the builder from the stack and returns the result of the call.

Context#call ensures that the stack is always popped, even if an exception is raised in the called block. If you choose to call the block directly, be sure to do the same, otherwise failures and/or (possibly subtle) bugs will ensue.



292
293
294
295
296
297
# File 'lib/cheri/builder/context.rb', line 292

def call(b,*r,&k)
  push(b)
  k.call(b.object,*r)    
 ensure
  pop
end

#cfgObject



571
572
573
# File 'lib/cheri/builder/context.rb', line 571

def cfg
  @g  
end

#crz(mod, c, a) ⇒ Object Also known as: resolve_ctor

call-seq:

ctx.resolve_ctor(clazz, args) -> resolved?
ctx.crz(clazz, args) -> resolved?

Attempts to resolve any :Constant symbols found in args for the specified clazz. Note that args must be passed as an array, not as *args, as substitutions will be made directly in the array.



511
512
513
514
515
516
517
# File 'lib/cheri/builder/context.rb', line 511

def crz(mod,c,a)
  #puts "resolving: m=#{mod}, c=#{c}, a=[#{a.join(',')}]"
  if (z = @g.resolvers[mod])
    return true if z.resolve_ctor(c,a)
  end
  false
end

#csm(*r, &k) ⇒ Object Also known as: consume

call-seq:

ctx.consume(sym, *args, &block) -> consumed?, result
ctx.csm(sym, *args, &block) -> consumed?, result

Attempts to consume the specified sym, usually as a method on a built object (though not necessarily; the Java on_xxx handlers are implemented through a consumer). This is normally called when no builder is found matching sym (which normally originates in the client’s method_missing method). Only the topmost stack frame is eligible to consume.

This method is primarily intended for internal use by Context.



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/cheri/builder/context.rb', line 471

def csm(*r,&k)
  # methods are not passed up the stack; if it can't be consumed
  # by the last builder/frame, it's an error
  return false,nil unless (b = @s.last) # TODO: error if !b

  # offer the builder/object the opportunity to consume the method directly
  #puts "csm b=#{b}, b.o=#{b.object}, b.mod=#{b.mod}"
  if b.respond_to?(:consume)
    return b.consume(*r,&k)
  # must have object for standard consumers
  elsif b.object && (c = @g.consumers[b.mod])
    return c.consume(self,b,*r,&k)
  else
    return false, nil
  end
end

#eachObject Also known as: each_frame

:yields: frame



366
367
368
# File 'lib/cheri/builder/context.rb', line 366

def each #:yields: frame
  @s.reverse_each {|v| yield v } if block_given?
end

#empty?Boolean Also known as: stack_empty?

call-seq:

ctx.stack_empty? -> true/false
ctx.empty? -> true/false

Returns true if the context stack is empty.

Returns:

  • (Boolean)


394
395
396
# File 'lib/cheri/builder/context.rb', line 394

def empty?
  @s.empty?  
end

#eval(i, b, &k) ⇒ Object

call-seq:

ctx.eval(instance, builder, &block) -> result

Pushes builder onto the stack, then calls instance.instance_eval(&block). Then pops the builder from the stack and returns the result of the instance_eval.

Context#eval ensures that the stack is always popped, even if an exception is raised in the executed block. If you choose to do your own instance_eval, be sure to do the same, otherwise failures and/or (possibly subtle) bugs will ensue.



308
309
310
311
312
313
# File 'lib/cheri/builder/context.rb', line 308

def eval(i,b,&k)
  push(b)
  i.instance_eval(&k)    
 ensure
  pop
end

#exec(i, b, *r, &k) ⇒ Object

call-seq:

ctx.exec(instance, builder, *args, &block) -> result

Pushes builder onto the stack, then calls instance.instance_exec(builder.object,*args,&block). Then pops the builder from the stack and returns the result of the instance_exec.

Context#exec ensures that the stack is always popped, even if an exception is raised in the executed block. If you choose to do your own instance_exec, be sure to do the same, otherwise failures and/or (possibly subtle) bugs will ensue.

If instance_exec is not supported on a system, the call is redirected to Context#eval (instance_eval). Note that parameters will not be passed to blocks in that case. – TODO: need a good default instance_exec, (the one in active_supportcore_extobjectextending.rb?) ++



331
332
333
334
335
336
337
# File 'lib/cheri/builder/context.rb', line 331

def exec(i,b,*r,&k)
  return eval(i,b,&k) unless @x ||= (defined?instance_exec)
  push(b)
  i.instance_exec(b.object,*r,&k)    
 ensure
  pop
end

#ictxObject



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

def ictx
  @i  
end

#inspectObject

Overrides the default Object#inspect to prevent mind-boggling circular displays in IRB.



629
630
631
# File 'lib/cheri/builder/context.rb', line 629

def inspect
  to_s 
end

#mrz(mod, o, y, a) ⇒ Object Also known as: resolve_meth

call-seq:

ctx.resolve_meth(object, method_name, args) -> resolved?
ctx.mrz(object, method_name, args) -> resolved?

Attempts to resolve any :Constant symbols found in args for the specified object and method_name. Note that args must be passed as an array, not as *args, as substitutions will be made directly in the array.



496
497
498
499
500
501
# File 'lib/cheri/builder/context.rb', line 496

def mrz(mod,o,y,a)
  if (z = @g.resolvers[mod])
    return true if z.resolve_meth(o,y,a)
  end
  false
end

#msend(m, sym, *r, &k) ⇒ Object

call-seq:

ctx.msend(builder_module, sym, *args, &block) -> result

Version of send intended for use by proxy objects. The supplied module’s factory must be able to resolve the symbol; otherwise, a NoMethodError will be raised.

Raises:

  • (ArgumentError)


235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/cheri/builder/context.rb', line 235

def msend(m,sym,*r,&k)
  # ok for stack to be empty for this call, so no check
  raise Cheri.type_error(sym,Symbol) unless sym.instance_of?(Symbol)
  raise ArgumentError,"not a valid builder module: #{m}" unless (f = @f[m])
  # substitute sym for alias, if any
  sym = @l[sym] if @l[sym]
  # check args against pending connections (see note at ConnectionMinder)
  @m.ck(sym,*r)
  # the factory _must_ be able to return a matching builder
  unless (b = f.builder(self,sym,*r,&k))
    raise NoMethodError,"undefined method '#{sym}' for #{m}"
  end
  # run it
  res = b.run
  # connect it
  ctc(b)
  res
end

#nsend(m, ns, sym, *r, &k) ⇒ Object

call-seq:

ctx.nsend(builder_module, namespace_sym, sym, *args, &block) -> result

Version of send intended for use by namespace objects. The supplied module’s factory must be able to resolve the symbol; otherwise, a NoMethodError will be raised. The resulting builder must supply a ns= method; otherwise a TypeError will be raised.

Raises:

  • (ArgumentError)


260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/cheri/builder/context.rb', line 260

def nsend(m,ns,sym,*r,&k)
  # ok for stack to be empty for this call, so no check
  raise Cheri.type_error(ns,Symbol) unless Symbol === ns
  raise Cheri.type_error(sym,Symbol) unless Symbol === sym
  raise ArgumentError,"not a valid builder module: #{m}" unless (f = @f[m])
  # substitute sym for alias, if any
  sym = @l[sym] if @l[sym]
  # check args against pending connections (see note at ConnectionMinder)
  @m.ck(sym,*r)
  # the factory _must_ be able to return a matching builder
  unless (b = f.builder(self,sym,*r,&k))
    raise NoMethodError,"undefined method '#{sym}' for #{m}"
  end
  raise Cheri.type_error(b,Markup) unless b.respond_to?(:ns=)
  b.ns = ns
  # run it
  res = b.run
  # connect it
  ctc(b)
  res
end

#popObject Also known as: pop_frame



349
350
351
352
353
354
# File 'lib/cheri/builder/context.rb', line 349

def pop
  f = @s.pop
  @m.po f
  @a.pop if f.any?
  f
end

#ppd(ctr, bldr, obj, sym, props = nil) ⇒ Object Also known as: prepared

call-seq:

prepared(connecter,builder,obj,sym,props=nil) -> true
ppd(connecter,builder,obj,sym,props=nil) -> true

Called by a Connecter (normally) from its prepare method (normally) to indicate that it is prepared to connect obj to the parent object hosted by builder. The context will later call the connecter’s connect method to perform the actual connection, provided the pending connection has not been invalidated. (A pending connection will be invalidated if obj is passed as a parameter to a method or constructor invoked via Cheri; the assumption is that the receiver will have performed any connection necessary.)

Note that if you are using TypeConnecter (highly recommended where appropriate), this all takes place behind the scenes, so you don’t need to call this method directly.



619
620
621
622
623
624
625
# File 'lib/cheri/builder/context.rb', line 619

def ppd(ctr,bldr,obj,sym,props=nil)
  unless @ax
    @m.ppd(ctr,bldr,obj,sym,props)
  else
    @m.ppda(ctr,bldr,obj,sym,props)
  end
end

#propsObject



575
576
577
# File 'lib/cheri/builder/context.rb', line 575

def props
  @p
end

#push(f) ⇒ Object Also known as: push_frame

Raises:

  • (ArgumentError)


339
340
341
342
343
344
345
346
# File 'lib/cheri/builder/context.rb', line 339

def push(f)
  # TODO: more checks of f (frame/builder)
  raise ArgumentError,"invalid argument - can't push: #{f}" unless f
  @s << f   
  @m.pu f
  (@a ||= []) << f if f.any?
  self
end

#reachObject Also known as: reverse_each, reverse_each_frame

:yields: frame



371
372
373
# File 'lib/cheri/builder/context.rb', line 371

def reach #:yields: frame
  @s.each {|v| yield v } if block_given?
end

#run(b) ⇒ Object

call-seq:

ctx.run(builder) -> result of builder.run

TODO: this description sucks, rewrite.

Runs the specified builder. This method should be called if a builder was not created through the normal Cheri factory mechanism. This method should be called, rather than calling builder.run directly, to ensure that the builder’s object gets properly connected.



193
194
195
196
197
198
199
# File 'lib/cheri/builder/context.rb', line 193

def run(b)
  # run the builder
  res = b.run
  # connect it's object
  ctc(b)
  res
end

#send(sym, *r, &k) ⇒ Object

call-seq:

ctx.send(sym, *args, &block) -> matched?, result

Find and run a builder corresponding to sym. If no match is found, try to consume sym as a method on the topmost object on the stack. No exception is raised by this method if a match is not found (contrast with #msend), but normally an exception will subsequently be raised if this method returns to method_missing (the usual caller) empty-handed.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/cheri/builder/context.rb', line 209

def send(sym,*r,&k)
  raise Cheri.type_error(sym,Symbol) unless sym.instance_of?(Symbol)
  # substitute sym for alias, if any
  sym = @l[sym] if @l[sym]
  # check args against pending connections (see note at ConnectionMinder)
  @m.ck(sym,*r)
  # first try for a builder matching sym
  if (b = bld(sym,*r,&k))
    # run it
    res = b.run
    # connect it
    ctc(b)
    return true, res
  elsif @s.empty?
    return false, nil
  else
    # no matching builder, try to consume sym as a method
    csm(sym,*r,&k)
  end
end

#sizeObject Also known as: length, stack_size

call-seq:

ctx.stack_size -> Fixnum
ctx.size -> Fixnum
ctx.length -> Fixnum

Returns the number of frames on the context stack.



383
384
385
# File 'lib/cheri/builder/context.rb', line 383

def size
  @s.length
end

#topObject Also known as: peek



357
358
359
# File 'lib/cheri/builder/context.rb', line 357

def top
  @s.last  
end

#tos(t) ⇒ Object Also known as: type_on_stack?

call-seq:

ctx.type_on_stack?(class_or_module) -> true/false
ctx.tos(class_or_module) -> true/false

Returns true if a stack frame matches the specified class_or_module, as evaluated by class_or_module === frame.



413
414
415
416
417
418
# File 'lib/cheri/builder/context.rb', line 413

def tos(t)
  @s.reverse_each do |f|
    return true if t === f
  end
  false
end