Loom and mock-as3

6 views
Skip to first unread message

maxim.porges

unread,
Feb 19, 2009, 8:45:26 AM2/19/09
to loom-as3
Starting a thread here to copy in my discussions with Drew so far so
we can continue them here.

=========

From: Drew
Subject: Loom and mock-as3
Date: February 17, 2009 4:04:02 PM EST
To: Maxim

Hi Maxim,

Brian LeGros suggested I email you about Loom and integrating it with
a mock object library.

I have been hoping for someone to release a bytecode generator for
months/years, and gave up waiting just a few weeks ago. I figured if
no-one else is doing it and I want it then I'll have to build it. At
the moment I have a working SWF disassembler and ABC disassembler that
I was going to use to verify generated bytecode. I was about to start
on the generation side when I saw Loom referenced on the FlexCamp
Miami site.

Are you interested in collaborating on getting Loom working with mock-
as3?

Also I have been considering forking / renaming mock-as3 to be more
distinct (and clean up of the API) and this would be a prime
opportunity to do so by releasing it with this new functionality.

cheers,
Drew

maxim.porges

unread,
Feb 19, 2009, 8:48:42 AM2/19/09
to loom-as3
On Wed, Feb 18, 2009 at 9:47 AM, Maxim Porges <maxim....@gmail.com>
wrote:

Hey Drew,

Thanks for touching base, Brian had told me to expect your email.

>> I have been hoping for someone to release a bytecode generator
>> for months/years, and gave up waiting just a few weeks ago.

I was in the same boat, which led me to start on Loom at the end of
2008.
I've been working on it seriously for a little over a month.


>> At the moment I have a working SWF disassembler and ABC disassembler that I
>> was going to use to verify generated bytecode.

Yep, I've got most of that working in Loom at the moment. I'm working
on the
next generation of what I have, which is a reflection API that anybody
can
use without much knowledge of the ABC format; I hope to be done with
the
reflection API in a week or so. I don't have a SWF disassembler yet,
but
there are several reference implementations out there so I figured
this
would not be too hard. I'd be interested in incorporating any work you
have
done in this area if you are interested in contributing it.
I was not planning on writing a bytecode verifier specifically, since
the
unit test suite that I have for the ABC assembler validates that the
various
objects create valid ABC bytecode in accordance with the AVM2 spec.
The AVM
verifies the ABC files on the way in, so I figured that I would just
keep
the unit test suite up to date as bugs are found and that should
suffice.
Once again, if you have any work in this area, I'd love to take a look
at
integrating it.


>> Are you interested in collaborating on getting Loom working with mock-as3?

Absolutely. I have a few people interested in using Loom at the
moment, and
I plan to support everybody to foster adoption with their libraries.
If you
have some time, we can chat about your needs on IM, and then I'll get
you a
pre-alpha release after I clean it up. The code is a bit of a mess at
the
moment since it's in transition, but I am rapidly cleaning it up as I
develop the reflection API.
Here's my IM details. I'm usually on all day 10-6 EST and sometimes
later in
the day. What time zone are you on?

[IM details]

Thanks,

- max

maxim.porges

unread,
Feb 19, 2009, 8:50:02 AM2/19/09
to loom-as3
On Feb 17, 2009, at 7:14 PM, Drew Bourne wrote:

Hi Max,

I'm in +10 (+11 at the moment for daylight savings time) , my
gtalk/gmail address is [email]

I've written a couple of variations on reflection APIs and would be
glad to contribute code and ideas.

Talk to you soon,

Cheers,
Drew

On Feb 19, 8:48 am, "maxim.porges" <maxim.por...@gmail.com> wrote:
> On Wed, Feb 18, 2009 at 9:47 AM, Maxim Porges <maxim.por...@gmail.com>

maxim.porges

unread,
Feb 19, 2009, 8:51:38 AM2/19/09
to loom-as3
On Wed, Feb 18, 2009 at 3:08 PM, Maxim Porges <maxim....@gmail.com>
wrote:

Thanks Drew, I have added you to my chat list.

What are some of the things you would need Loom to do to support
your
mocking use cases? Let me know if the dynamic class/closure mechanism
I
describe below will suit your needs.

The idea is that Loom will have a custom SWFLoader API that can
load
a SWF, crack it, and add dynamic class definitions to the end of the
ABC
block based on whatever means makes sense (class metadata tags a la
@Configurable in Spring, or classes registered for proxying with the
custom
loader, etc.), and then pass that SWF to the Flash Player as if it
were the
original. Another option would be to use describeType() to introspect
structure and weave dynamic definitions whenever you want to at
runtime.
After Loom has created a dynamic class definition, you'll be able to
get a
reference to it by using the original class as a key against a Loom
utility.
You can then "new" the dynamic class reference, and use your newly-
created
dynamic instance in place of the original type wherever you want to.

All dynamic instances will maintain a structure of Functions
(probably a Dictionary) that map to the original method declarations.
You
can set new Functions dynamically in place of the original methods by
passing a closure in to the dynamic instance; the method that makes
this
possible will be weaved in to the class def by Loom. When you invoke a
method on any of the class def's types, the dynamic class will check
the
internal Function registry to see if it has a closure registered; if
it
does, it will invoke that, and if not it will call the superclass
method.
The neat part about using closures instead of weaving straight
bytecode is
that you can add different advice to different instances of the same
dynamic
type as you see fit, and also do method replacement at runtime on any
dynamic instance; you'll just have to maintain the method signature in
the
closure signature.

As far as creating class definitions on the fly: ideally, I'd
like to
set up the Loom API so that people unfamiliar with the AVM2 spec can
just
make a new ClassDefinition and add Methods, Fields, and Properties to
it
since those terms should be familiar. When it comes to weaving opcodes
on
the fly, they'll obviously have to get under the hood, but the idea
would be
that Loom would provide some AS3 tools for dumping class definitions
(basically a more detailed version of abcdump) to help them figure it
out.
They can write regular old code, compile with the Flex tools, and then
look
at it to see what they need to pass in as OpCodes to make their method
do
whatever they want. Loom would apply all the appropriate rules at
serialization time to make sure the method flags were set properly
based
upon the opcodes used, etc. I'd also like to make it smart enough to
determine the method stack/scope depth dynamically and deal with
opcode
injection around existing code.


>> I've written a couple of variations on reflection APIs and would be
>> glad to contribute code and ideas.

I'm not doing anything terribly exciting in Loom - basically just
grocking
the bytecode in a deserializer and building out a ClassDefinition
containing
all the objects in the AVM spec.

- ConstantPool (gets thrown away after deserialization, since
the ClassDefinition doesn't need it once it's been filled)
- Creating instances of Namespace/NamespaceSet/Multinames
(QualifiedName, RuntimeQualifiedName/L, Multiname/L) for the pool
- Method (combining the MethodInfo signature, MethodTraits, and
MethodBody opcodes)
- Metadata
- Field
- Property
- ClassDefinition (combining all the class/instance info and
traits, plus info on the superclass, etc., and the methods
representing the init script/constructor/static init)

When serializing, the ClassDefinition gets run through a
serializer
class that fills a fresh ConstantPool at the appropriate points to
keep it
all in check while building out the blocks that the AVM2 spec defines.
Once
all the structures are built it just flushes them to the ABC file.

Let me know your thoughts on all this, and if you have any ideas
or
code that might assist. Nothing is really set in stone yet; I'm just
following the path of least resistance to get basic AOP with closures
working as a start.

Thanks,

- max

maxim.porges

unread,
Feb 19, 2009, 8:54:58 AM2/19/09
to loom-as3
From: Drew
Subject: Re: Loom and mock-as3
Date: February 18, 2009 7:39:07 AM EST
To: Maxim

Hi Max,

The idea about using closures to replace existing methods is much more
accessible as a solution then what I was thinking to do. I'll include
an example of how I was thinking to go later in this email.

I figured the best way to understand how I would use Loom for mocks
was to create a couple of use cases. The three scenarios I came up
with were:

- simple AOP: around, before, after;
- stubbing / partial mocking: replacing a method in the class under
test;
- mocking: using loom internally to create mock objects;

Have a look at the attached loom-use-cases.as file for an example of
each scenario. I use an RSpec-inspired testing framework, and have
made assumptions about the Loom API. In case its not clear the
beforeAll functions are run once per describe block, hopefully
everything else in there makes sense.

One of possible pain points that I can see are that when using a
Loom-augmented class it would need to be typed as the target class for
use and not as the augmented type as the combined type will not be
available at compile time. Though I would be happy with the situation
below as it keeps the concerns (real use vs. augmentation) separate.

// we have to type to EventDispatcher to use it anywhere
var eventDispatcher:EventDispatcher = new
loomAugmentedEventDispatcherClass();
// but we cannot access any additional methods unless we type it to an
interface provided by Loom
var loomAugmented:LoomAugmented = eventDispatcher as LoomAugmented;
loomAugmented.around("dispatchEvent", function(fn:Function,
args:Array):Boolean {
// ...
});
// nor could we cast it like this, because
LoomAugmentedEventDispatcher would not exist as a type at compile
var loomAugmentedEventDispatcher:LoomAugmentedEventDispatcher =
eventDispatcher as LoomAugmentedEventDispatcher;

The asynchronous nature of generating and loading the bytes is easily
worked with too. Most developers are used to events and callbacks by
now so either style of async API is cool.

Can you explain a bit more about the use case you describe at the
start of your email? Is it so you can replace an existing class with
an augmented version? Eg replace class EchoService with class
EchoService implements LoomAugmentation, so none of the references to
that class have to point to the generated version?

Implementing the snippet in abc-dsl.as is what I was toying with, its
goal was to create the class definition and bytecode that is currently
used by mock-as3. The point of using closures for each block is to
enable the setup of context and capture of values for the constant
pool, etc.

cheers,
Drew


======= abc-dsl.as =========
var abc:ABC = __abc(function():void {
__package("example.mock", function():void {
// class name, class to extend, interfaces to implement
__class("WafflesMock", null, [Waffles], function():void {
__var("mock", Mock, NS.PUBLIC));

// iterate over methods we are going to implement
classInfo(Waffles).publicMethods.forEach(function
(method:Method):void {
__method(method.name, method.parameters, method.returnType,
function():void {
__getproperty("mock");
__callmethod(method.name, method.parameters.length);
if (method.returnType) {
__returnvalue();
} else {
__returnvoid();
}
});
});

// add an extra method because we can
__method("ifexample", function():void {
__ifeq(__getproperty("lhs"), __getproperty("rhs"), function
():void {
__getproperty("other");
__callmethod("example");
});
__returnvoid();
}));
}); // end class
}); // end package
}); // end abc




=========== loom-use-cases.as ===============
// AOP

describe('aop', function():void {
var loom:Loom;
var eventDispatcherClass:Class;
var eventDispatcher:EventDispatcher;

beforeAll(function():void {
loom = new Loom();
loom.weave(EventDispatcher, async(function(klass:Class):void {
eventDispatcherClass = klass;
}));
});

before(function():void {
eventDispatcher = new eventDispatcherClass();
});

describe('around', function():void {
it('receives a closure to execute the original method, and any
arguments', function():void {
loom.around(eventDispatcher, 'dispatchEvent', function
(fn:Function, args:Array):Boolean {
trace('eventDispatcher dispatchEvent around before', event);
var result:Boolean = fn.apply(null, args);
trace('eventDispatcher dispatchEvent around after', event);
return result;
});
});
});

describe('before', function():void {
it('receives arguments only', function():void {
loom.before(eventDispatcher, 'dispatchEvent', function
(args:Array):void {
trace('eventDispatcher dispatchEvent before', args);
});
});

});
describe('after', function():void {
it('receives arguments and result', function(args:Array,
result:Boolean):void {
loom.after(eventDispatcher, 'dispatchEvent', function
(args:Array, result:Boolean):void {
trace('eventDispatcher dispatchEvent after', args, result);
});
});
});
});

// stubbing / partial mock

describe('using a stub', function():void {
var swfReaderClass:Class;
var swfReader:SWFReader
var loom:Loom;

beforeAll(function():void {
loom = new Loom();
loom.weave(SWFReader, async(function(klass:Class):void {
swfReaderClass = klass;
}));
});

before(function():void {
swfReader = new swfReaderClass();
});

it('should allow partial mocking', function():void {
loom.replace(swfReader, 'readU30', function():int {
return 0xF430;
});

assertThat(swfReader.readU30(), equalTo(0xF430));
});
});

// mocking

describe('using a mock', function():void {
var urlLoaderClass:Class;
var urlLoader:URLLoader;
var swfReader:SWFReader;
var mockery:Mockery;

beforeAll(function():void {
mockery = new Mockery();
mockery.addEventListener('ready', async(done));
mockery.bake(URLLoader);
});

before(function():void {
urlLoaderMock = mockery.make(URLLoader);
swfReader = new SWFReader();
});

it('should have a target of the mocked/weaved class', function
():void {
urlLoaderMock.method('load').withArgs(URLRequest).dispatchesEvent
(new Event('complete'));
swfReader.urlLoader = urlLoaderMock.target as URLLoader;
swfReader.load(new URLRequest('path/to/swf'));
});
});

maxim.porges

unread,
Feb 19, 2009, 9:25:38 AM2/19/09
to loom-as3
Drew,

Thanks for the code samples, this definitely gives me a better
picture.

> // we have to type to EventDispatcher to use it anywhere
> var eventDispatcher:EventDispatcher = new loomAugmentedEventDispatcherClass();

Hmm... I had not planned on making everything implement
EventDispatcher, but this is certainly do-able. Since the AOP would be
introduced as closures, and the closure will get a reference to the
original MethodInvocation, you would be able to dispatch an event
explicitly on the original class instance if you wanted to (something
like invocation.target.dispatchEvent(...)). In this case, it would
already have EventDispatcher in its inheritance hierarchy or would
have implemented IEventDispatcher.

What was the use case you had in mind where you would want
EventDispatcher for everything? If this is needed, Loom can sniff for
EventDispatcher when enhancing a class, and add EventDispatcher as the
superclass if the object does not extend EventDispatcher already, or
weave in IEventDispatcher and associated boilerplate methods and
properties to support event dispatching if a non-Object superclass is
already present. We could also make weaving in of IEventDispatcher an
optional weave-time flag on a per-class basis.


> // but we cannot access any additional methods unless we type it to an interface provided by Loom
> var loomAugmented:LoomAugmented = eventDispatcher as LoomAugmented;
> loomAugmented.around("dispatchEvent", function(fn:Function, args:Array):Boolean {
> // ...});

This is a really good point. I had planned on just having the method
invocations for adding advice be called dynamically (probably by a
factory in Loom), but adding a special interface to make things 100%
typesafe is a much better idea, and is trivial from an implementation
standpoint in Loom. It will also give people a really clean way to say
"Is this thing enhanced?" when dealing with an interface, since they
can just "is" an object instance against LoomAugmented.


> ======= abc-dsl.as =========
> var abc:ABC = __abc(function():void {
> __package("example.mock", function():void { ...

I think I get where you were going in abc-dsl.as. The style you are
using looks terse but might be unfamiliar territory for mainstram AS
developers; it's certainly a little unusual to me. In contrast, the
intended API for Loom will look something like this. There's no reason
why we couldn't wrap this all in a functional style for those who want
to go that route.

<pre>
var classDefinion : ClassDefinition = new ClassDefinition
(classNamespace, superclassNamespace);
var method : Method = classDefinition.addMethod(methodName,
returnType);
var argumentType : Multiname = new QualifiedName("String", new
Namespace(NamespaceKind.NAMESPACE), MultinameKind.QNAME);
method.addArgument(new Argument(argumentType));
method.setBody([array of bytecodes built by helper class/function]);

... [call to utility class to generate classDefinition bytecode as in-
memory SWF]
</pre>


> // add an extra method because we can
> __method("ifexample", function():void {
> __ifeq(__getproperty("lhs"), __getproperty("rhs"), function():void {
> __getproperty("other");
> __callmethod("example");
> });
> __returnvoid();
> }));

I'm planning on making helper methods/classes for dealing with the
bytecode creation. I'm not sure how I would implement an API similar
to the code snippet above - are all the __functionName functions
static imports? If so, I think the class creation would have to be run
in some kind of state machine due to the way the constant pool has to
be constructed.

Thanks,

- max

Chris Scott

unread,
Feb 19, 2009, 9:59:18 AM2/19/09
to loom...@googlegroups.com
Hey Maxim,

I think the structure of having an internal closure map for methods you are replacing should work well. I have a fully functioning AOP implementation completed in Swiz, except for the typed proxy class. If I pass in my ProxyMethod's execute() to your method map, this should work very well. The only weird thing is that there is a bit of duplicate functionality occuring. My ProxyMethod has a reference to an AdvisorChain, and first checks for a match on the method, then either builds a MethodInvocation, with all the Advisors all set up, or executes a command method on the original method.

Here's kind of a breakdown of what I am doing:

- method call is captured, in ProxyMethod.execute(... args)
- Method object is build, storing target object, method name, and arguments
- Method is passed to AdvisorChain to build a MethodInvocation
- If MethodInvocation is not null, proceed() is called, else execute() is called on the Method
- MethodInvocation contains all the interceptors, to properly execute Before, AfterReturning, Around advice types
- when Method.execute() is finally called, it retreives the target method on the original object, and calls apply(null, args) on it

What I have also build is an AdvisorFactory, which greatly simplifies the configuration of Advice. Instead of configuring ProxyFactories, Advisors, Pointcuts, and Advice, I am doing something very similar to Spring 2.5's annotations. You write one as class, add an Aspect annotation, then use annotations on each method that represents a Before, AfterReturning, or Around advice method. The pointcut is defined in those annotations. Swiz will pass these classes into the AdvisorFactory, which will build the Advisors containing Pointcuts and MethodInterceptors for each advice method.

It's crazy simple to work with, Swiz will do all the auto-proxying for you. The only thing I think I may want to really play around with is how to work my ProxyMethod with the very similar method you are building into proxies, which is probably pretty much like ColdSpring's callMethod().

Looking forward to seeing this stuff really execute!

-C

Maxim Porges

unread,
Feb 19, 2009, 12:08:50 PM2/19/09
to loom...@googlegroups.com
Chris,

Sounds like you have some good stuff going. 

- method call is captured, in ProxyMethod.execute(... args)
- Method object is build, storing target object, method name, and arguments
- Method is passed to AdvisorChain to build a MethodInvocation
- If MethodInvocation is not null, proceed() is called, else execute() is called on the Method
- MethodInvocation contains all the interceptors, to properly execute Before, AfterReturning, Around advice types
- when Method.execute() is finally called, it retreives the target method on the original object, and calls apply(null, args) on it

This is the same thing Loom will be doing under the hood to implement AOP, except that instead of ProxyMethod.execute() happening, the actual dynamically overriden method will be invoked. The dynamically overriden method will call a private method in the class (weaved at creation time) that will accept the method name and arguments.

This private method will do all the work of figuring out if advice is registered, creating the MethodInvocation, etc. If nothing is registered, it will just invoke the method in super with the arguments.

To support the weaving, there will be a "freeze-dried" template class that contains all the code, properties, and class references to inject the proxying mechanism in to every dynamic subclass at load/weave time. I have not mapped out the AOP side of Loom yet, and it sounds like Loom might just be able to implement your classes as the freeze-dried bytecode.

Below is a stupid-simple template class for the freeze-dried version, which I wrote so that I could look at the opcodes and determine if what I had in mind made any sense. Clearly, it is not completely implemented, but should give you an idea.

Imagine the following base class:

====================
package loom.template
{
public class BaseClass
{
public function BaseClass()
{
}

public function operationOne() : void
{
trace("^^^ operationOne ^^^");
}

public function operationTwo(argument1 : String, argument2 : int, argument3 : Object) : void
{
trace("^^^ operationTwo(" + argument1 + ", " + argument2 + ", " + argument3 + ") ^^^");
}
}
}
====================


This would be an analog for the runtime subclass generated by Loom:

====================
package loom.template
{
import flash.utils.Dictionary;
public class DynamicFunctionProxy extends BaseClass
{
        private var dictionary : Dictionary;

public function DynamicFunctionProxy()
{
super();
dictionary = new Dictionary();
}
public function setMappedFunctionFor(functionName : String, mappedFunction : Function) : void
{
dictionary[functionName] = mappedFunction;
}
public function getMappedFunctionFor(methodName : String) : Function
{
return dictionary[methodName];
}
public function invokeMethod(methodName : String, arguments : Array) : *
{
var mappedFunction : Function = getMappedFunctionFor(methodName);
if (mappedFunction)
{
mappedFunction.apply(this, arguments);
}
else
{
super[methodName].apply(super, arguments);
}
}
override public function operationOne() : void
{
invokeMethod("operationOne", []);
}
override public function operationTwo(argument1 : String, argument2 : int, argument3 : Object) : void
{
invokeMethod("operationTwo", [argument1, argument2, argument3]);
}
}
}
====================

The opcodes for both look like this:

====================
class loom.template::BaseClass extends Object
{

  function loom.template::BaseClass():* /* disp_id -1*/
  {
    // local_count=1 max_scope=1 max_stack=1 code_len=6
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       constructsuper (0)
    5       returnvoid    
  }

  function operationTwo(String,int,Object):void /* disp_id 0*/
  {
    // local_count=4 max_scope=1 max_stack=3 code_len=26
    0       getlocal0    
    1       pushscope    
    2       findpropstrict trace
    4       pushstring     "^^^ operationTwo("
    6       getlocal1    
    7       add          
    8       pushstring     ", "
    10      add          
    11      getlocal2    
    12      add          
    13      pushstring     ", "
    15      add          
    16      getlocal3    
    17      add          
    18      pushstring     ") ^^^"
    20      add          
    21      callproperty   trace (1)
    24      pop          
    25      returnvoid    
  }

  function operationOne():void /* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=2 code_len=11
    0       getlocal0    
    1       pushscope    
    2       findpropstrict trace
    4       pushstring     "^^^ operationOne ^^^"
    6       callproperty   trace (1)
    9       pop          
    10      returnvoid    
  }

  static function loom.template::BaseClass$cinit():* /* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=1 code_len=3
    0       getlocal0    
    1       pushscope    
    2       returnvoid    
  }
}

function script0$init():* /* disp_id 0*/
{
  // local_count=1 max_scope=2 max_stack=2 code_len=19
  0         getlocal0    
  1         pushscope    
  2         getscopeobject 0
  4         findpropstrict Object
  6         getproperty   Object
  8         pushscope    
  9         findpropstrict Object
  11        getproperty   Object
  13        newclass       loom.template::BaseClass
  15        popscope      
  16        initproperty   loom.template::BaseClass
  18        returnvoid    
}
====================

and

====================
class loom.template::DynamicFunctionProxy extends loom.template::BaseClass
{

  function loom.template::DynamicFunctionProxy():* /* disp_id -1*/
  {
    // local_count=1 max_scope=1 max_stack=2 code_len=14
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       constructsuper (0)
    5       getlocal0    
    6       findpropstrict Dictionary
    8       constructprop Dictionary (0)
    11      initproperty   dictionary
    13      returnvoid    
  }

  function operationTwo(String,int,Object):void /* disp_id 0*/
  {
    // local_count=4 max_scope=1 max_stack=5 code_len=15
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       pushstring     "operationTwo"
    5       getlocal1    
    6       getlocal2    
    7       getlocal3    
    8       newarray       [3]
    10      callproperty   invokeMethod (2)
    13      pop          
    14      returnvoid    
  }
  var private::dictionary:flash.utils::Dictionary /* slot_id 0 */

  function getMappedFunctionFor(String):Function /* disp_id 0*/
  {
    // local_count=2 max_scope=1 max_stack=2 code_len=9
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       getproperty   dictionary
    5       getlocal1    
    6       getproperty   null
    8       returnvalue  
  }

  function invokeMethod(String,Array):* /* disp_id 0*/
  {
    // local_count=4 max_scope=1 max_stack=3 code_len=38
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       getlocal1    
    4       callproperty   getMappedFunctionFor (1)
    7       coerce         Function
    9       setlocal3    
    10      getlocal3    
    11      convert_b    
    12      iffalse       L1

    16      getlocal3    
    17      getlocal0    
    18      getlocal2    
    19      callproperty   apply (2)
    22      pop          
    23      jump           L2
    
    L1: 
    27      getlocal0    
    28      getlocal1    
    29      getsuper       null
    31      getlocal0    
    32      getlocal2    
    33      callproperty   apply (2)
    36      pop          
    
    L2: 
    37      returnvoid    
  }

  function setMappedFunctionFor(String,Function):void /* disp_id 0*/
  {
    // local_count=3 max_scope=1 max_stack=3 code_len=10
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       getproperty   dictionary
    5       getlocal1    
    6       getlocal2    
    7       setproperty   null
    9       returnvoid    
  }

  function operationOne():void /* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=3 code_len=12
    0       getlocal0    
    1       pushscope    
    2       getlocal0    
    3       pushstring     "operationOne"
    5       newarray       [0]
    7       callproperty   invokeMethod (2)
    10      pop          
    11      returnvoid    
  }

  static function loom.template::DynamicFunctionProxy$cinit():* /* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=1 code_len=3
    0       getlocal0    
    1       pushscope    
    2       returnvoid    
  }
}

function script0$init():* /* disp_id 0*/
{
  // local_count=1 max_scope=3 max_stack=2 code_len=25
  0         getlocal0    
  1         pushscope    
  2         getscopeobject 0
  4         findpropstrict Object
  6         getproperty   Object
  8         pushscope    
  9         findpropstrict loom.template::BaseClass
  11        getproperty   loom.template::BaseClass
  13        pushscope    
  14        findpropstrict BaseClass
  16        getproperty   BaseClass
  18        newclass       loom.template::DynamicFunctionProxy
  20        popscope      
  21        popscope      
  22        initproperty   loom.template::DynamicFunctionProxy
  24        returnvoid    
}
====================

getMappedFunctionFor, setMappedFunctionFor, and invokeMethod would all basically look the same all the time. Note that depending on the method signature, the stack has to be adjusted when filling the array of arguments. In the opcode samples below (taken form above), the first operation fills the stack and invokes the opcode "newarray" differently than the second operation, since there are different numbers of args. Even so, this is a pretty trivial change, and since everything else would be identical in terms of arguments (since we have only the method name and args to pass to invokeMethod) the implementation should be pretty simple.

  function operationOne():void /* disp_id 0*/
  {
    // local_count=1 max_scope=1 max_stack=3 code_len=12
    0       getlocal0     
    1       pushscope     
    2       getlocal0     
    3       pushstring     "operationOne"
    5       newarray       [0]
    7       callproperty   invokeMethod (2)
    10      pop           
    11      returnvoid    
  }

  function operationTwo(String,int,Object):void /* disp_id 0*/
  {
    // local_count=4 max_scope=1 max_stack=5 code_len=15
    0       getlocal0     
    1       pushscope     
    2       getlocal0     
    3       pushstring     "operationTwo"
    5       getlocal1     
    6       getlocal2     
    7       getlocal3     
    8       newarray       [3]
    10      callproperty   invokeMethod (2)
    13      pop           
    14      returnvoid    
  }

- max


>  // ....});

Chris Scott

unread,
Feb 19, 2009, 1:36:40 PM2/19/09
to loom...@googlegroups.com
Yea, that's pretty much what I figured your extended class would look like. I could easily just call setMappedFunctionFor("doFoo", aProxyMethod.execute) and that will work totally fine for my needs. I don't need anything else from Loom (other than whatever call creates the proxy instance...) to have Swiz's AOP framework 100%, or at least as powerful as ColdSpring's.

Since I have decoupled all the interceptors / pointcuts / aspects from the proxy, from my point of view, no other logic needs to be handled from within Loom's Proxy.

-C

Drew Bourne

unread,
Feb 19, 2009, 4:36:28 PM2/19/09
to loom...@googlegroups.com
Hi Max,

No reason for everything to implement EventDispatcher, it was simply
the first class I thought of for use an example.

The DSL would be implemented behind the scenes much as your own
example demonstrates. I prefer DSLs (or as near as we can make them in
AS3) so I can see the what without having to deal with the how so
much. All the __functions would be package level functions because AS3
does not support static imports like java.

I have implemented this style of API and runner in Spectacular, the
framework I used for the loom use cases.
http://github.com/drewbourne/spectacular/tree/master

cheers,
Drew

Maxim Porges

unread,
Feb 19, 2009, 5:32:51 PM2/19/09
to loom...@googlegroups.com
Thanks Drew, that answers my questions.

- max
Reply all
Reply to author
Forward
0 new messages