2014-01-21 22:57:04 -05:00
|
|
|
/**
|
|
|
|
* Provides system for code reuse via traits
|
|
|
|
*
|
|
|
|
* Copyright (C) 2014 Mike Gerwitz
|
|
|
|
*
|
|
|
|
* This file is part of GNU ease.js.
|
|
|
|
*
|
|
|
|
* ease.js is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
var AbstractClass = require( __dirname + '/class_abstract' ),
|
|
|
|
ClassBuilder = require( __dirname + '/ClassBuilder' );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
|
|
|
|
function Trait()
|
|
|
|
{
|
|
|
|
switch ( arguments.length )
|
|
|
|
{
|
|
|
|
case 1:
|
|
|
|
return Trait.extend.apply( this, arguments );
|
|
|
|
break;
|
2014-02-13 05:21:26 -05:00
|
|
|
|
|
|
|
case 2:
|
|
|
|
return createNamedTrait.apply( this, arguments );
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw Error( "Missing trait name or definition" );
|
2014-01-21 22:57:04 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-02-13 05:21:26 -05:00
|
|
|
/**
|
|
|
|
* Create a named trait
|
|
|
|
*
|
|
|
|
* @param {string} name trait name
|
|
|
|
* @param {Object} def trait definition
|
|
|
|
*
|
|
|
|
* @return {Function} named trait
|
|
|
|
*/
|
|
|
|
function createNamedTrait( name, dfn )
|
|
|
|
{
|
|
|
|
if ( arguments.length > 2 )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"Expecting at most two arguments for definition of named " +
|
|
|
|
"Trait " + name + "'; " + arguments.length + " given"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( typeof name !== 'string' )
|
|
|
|
{
|
|
|
|
throw Error(
|
|
|
|
"First argument of named class definition must be a string"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
dfn.__name = name;
|
|
|
|
|
|
|
|
return Trait.extend( dfn );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
Trait.extend = function( dfn )
|
|
|
|
{
|
2014-02-28 23:55:24 -05:00
|
|
|
// we may have been passed some additional metadata
|
|
|
|
var meta = this.__$$meta || {};
|
|
|
|
|
2014-02-13 05:21:26 -05:00
|
|
|
// store any provided name, since we'll be clobbering it (the definition
|
|
|
|
// object will be used to define the hidden abstract class)
|
|
|
|
var name = dfn.__name || '(Trait)';
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// we need at least one abstract member in order to declare a class as
|
|
|
|
// abstract (in this case, our trait class), so let's create a dummy one
|
|
|
|
// just in case DFN does not contain any abstract members itself
|
2014-01-23 01:15:53 -05:00
|
|
|
dfn[ 'abstract protected ___$$trait$$' ] = [];
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-01-26 03:30:52 -05:00
|
|
|
// give the abstract trait class a distinctive name for debugging
|
|
|
|
dfn.__name = '#AbstractTrait#';
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
function TraitType()
|
|
|
|
{
|
|
|
|
throw Error( "Cannot instantiate trait" );
|
|
|
|
};
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// implement interfaces if indicated
|
|
|
|
var base = AbstractClass;
|
|
|
|
if ( meta.ifaces )
|
|
|
|
{
|
|
|
|
base = base.implement.apply( null, meta.ifaces );
|
|
|
|
}
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// and here we can see that traits are quite literally abstract classes
|
2014-02-28 23:55:24 -05:00
|
|
|
var tclass = base.extend( dfn );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-02-13 05:21:26 -05:00
|
|
|
TraitType.__trait = true;
|
|
|
|
TraitType.__acls = tclass;
|
|
|
|
TraitType.__ccls = null;
|
|
|
|
TraitType.toString = function()
|
|
|
|
{
|
|
|
|
return ''+name;
|
|
|
|
};
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// traits are not permitted to define constructors
|
|
|
|
if ( tclass.___$$methods$$['public'].__construct !== undefined )
|
|
|
|
{
|
|
|
|
throw Error( "Traits may not define __construct" );
|
|
|
|
}
|
|
|
|
|
2014-03-07 01:07:46 -05:00
|
|
|
// traits have property restrictions
|
|
|
|
validateProps( tclass.___$$props$$['public'] );
|
|
|
|
validateProps( tclass.___$$props$$['protected'] );
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
// invoked to trigger mixin
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
TraitType.__mixin = function( dfn, tc, base )
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
mixin( TraitType, dfn, tc, base );
|
2014-01-23 00:34:15 -05:00
|
|
|
};
|
2014-01-21 22:57:04 -05:00
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// mixes in implemented types
|
|
|
|
TraitType.__mixinImpl = function( dest_meta )
|
|
|
|
{
|
|
|
|
mixinImpl( tclass, dest_meta );
|
|
|
|
};
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
return TraitType;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-03-07 01:07:46 -05:00
|
|
|
/**
|
|
|
|
* Throws error if non-internal property is found within PROPS
|
|
|
|
*
|
|
|
|
* For details and rationale, see the Trait/PropertyTest case.
|
|
|
|
*
|
|
|
|
* @param {Object} props properties to prohibit
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function validateProps( props )
|
|
|
|
{
|
|
|
|
for ( var f in props )
|
|
|
|
{
|
|
|
|
// ignore internal properties
|
|
|
|
if ( f.substr( 0, 3 ) === '___' )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw Error(
|
|
|
|
"Cannot define property `" + f + "'; only private " +
|
|
|
|
"properties are permitted within Trait definitions"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
Trait.implement = function()
|
|
|
|
{
|
|
|
|
var ifaces = arguments;
|
|
|
|
|
|
|
|
return {
|
|
|
|
extend: function()
|
|
|
|
{
|
|
|
|
// pass our interface metadata as the invocation context
|
|
|
|
return Trait.extend.apply(
|
|
|
|
{ __$$meta: { ifaces: ifaces } },
|
|
|
|
arguments
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
Trait.isTrait = function( trait )
|
|
|
|
{
|
|
|
|
return !!( trait || {} ).__trait;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
/**
|
|
|
|
* Create a concrete class from the abstract trait class
|
|
|
|
*
|
|
|
|
* This class is the one that will be instantiated by classes that mix in
|
|
|
|
* the trait.
|
|
|
|
*
|
|
|
|
* @param {AbstractClass} acls abstract trait class
|
|
|
|
*
|
|
|
|
* @return {Class} concrete trait class for instantiation
|
|
|
|
*/
|
|
|
|
function createConcrete( acls )
|
|
|
|
{
|
2014-01-26 03:26:15 -05:00
|
|
|
// start by providing a concrete implementation for our dummy method and
|
|
|
|
// a constructor that accepts the protected member object of the
|
|
|
|
// containing class
|
2014-01-23 00:34:15 -05:00
|
|
|
var dfn = {
|
2014-01-23 01:15:53 -05:00
|
|
|
'protected ___$$trait$$': function() {},
|
2014-01-26 03:26:15 -05:00
|
|
|
|
2014-03-04 00:19:39 -05:00
|
|
|
// protected member object (we define this as protected so that the
|
|
|
|
// parent ACLS has access to it (!), which is not prohibited since
|
|
|
|
// JS does not provide a strict typing mechanism...this is a kluge)
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
// and target supertype---that is, what __super calls should
|
|
|
|
// referene
|
2014-03-04 00:19:39 -05:00
|
|
|
'protected ___$$pmo$$': null,
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
'protected ___$$super$$': null,
|
|
|
|
__construct: function( base, pmo )
|
2014-01-26 03:26:15 -05:00
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
this.___$$super$$ = base;
|
|
|
|
this.___$$pmo$$ = pmo;
|
2014-01-26 03:26:15 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
// mainly for debugging; should really never see this.
|
|
|
|
__name: '#ConcreteTrait#',
|
2014-01-23 00:34:15 -05:00
|
|
|
};
|
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// every abstract method should be overridden with a proxy to the
|
|
|
|
// protected member object that will be passed in via the ctor
|
|
|
|
var amethods = ClassBuilder.getMeta( acls ).abstractMethods;
|
|
|
|
for ( var f in amethods )
|
|
|
|
{
|
|
|
|
// TODO: would be nice if this check could be for '___'; need to
|
|
|
|
// replace amethods.__length with something else, then
|
|
|
|
if ( !( Object.hasOwnProperty.call( amethods, f ) )
|
|
|
|
|| ( f.substr( 0, 2 ) === '__' )
|
|
|
|
)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-26 03:30:52 -05:00
|
|
|
// we know that if it's not public, then it must be protected
|
|
|
|
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
|
|
|
|
? 'public'
|
|
|
|
: 'protected';
|
|
|
|
|
|
|
|
// setting the correct visibility modified is important to prevent
|
|
|
|
// visibility de-escalation errors if a protected concrete method is
|
|
|
|
// provided
|
|
|
|
dfn[ vis + ' proxy ' + f ] = '___$$pmo$$';
|
2014-01-26 03:26:15 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-02-03 23:54:21 -05:00
|
|
|
// virtual methods need to be handled with care to ensure that we invoke
|
|
|
|
// any overrides
|
|
|
|
createVirtProxy( acls, dfn );
|
|
|
|
|
2014-01-23 00:34:15 -05:00
|
|
|
return acls.extend( dfn );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-02-03 23:54:21 -05:00
|
|
|
/**
|
|
|
|
* Create virtual method proxies for all virtual members
|
|
|
|
*
|
|
|
|
* Virtual methods are a bit of hassle with traits: we are in a situation
|
|
|
|
* where we do not know at the time that the trait is created whether or not
|
|
|
|
* the virtual method has been overridden, since the class that the trait is
|
|
|
|
* mixed into may do the overriding. Therefore, we must check if an override
|
|
|
|
* has occured *when the method is invoked*; there is room for optimization
|
|
|
|
* there (by making such a determination at the time of mixin), but we'll
|
|
|
|
* leave that for later.
|
|
|
|
*
|
|
|
|
* @param {AbstractClass} acls abstract trait class
|
|
|
|
* @param {Object} dfn destination definition object
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function createVirtProxy( acls, dfn )
|
|
|
|
{
|
|
|
|
var vmembers = ClassBuilder.getMeta( acls ).virtualMembers;
|
|
|
|
|
|
|
|
// f = `field'
|
|
|
|
for ( var f in vmembers )
|
|
|
|
{
|
|
|
|
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
|
|
|
|
? 'public'
|
|
|
|
: 'protected';
|
|
|
|
|
2014-02-19 00:26:57 -05:00
|
|
|
// this is the aforementioned proxy method; see the docblock for
|
|
|
|
// more information
|
2014-02-03 23:54:21 -05:00
|
|
|
dfn[ vis + ' virtual override ' + f ] = ( function()
|
|
|
|
{
|
|
|
|
return function()
|
|
|
|
{
|
|
|
|
var pmo = this.___$$pmo$$,
|
2014-02-19 00:26:57 -05:00
|
|
|
o = pmo[ f ];
|
|
|
|
|
|
|
|
// proxy to virtual override from the class we are mixed
|
|
|
|
// into, if found; otherwise, proxy to our supertype
|
|
|
|
return ( o )
|
|
|
|
? o.apply( pmo, arguments )
|
2014-02-03 23:54:21 -05:00
|
|
|
: this.__super.apply( this, arguments );
|
2014-02-19 00:26:57 -05:00
|
|
|
};
|
|
|
|
} )( f );
|
|
|
|
|
|
|
|
// this guy bypasses the above virtual override check, which is
|
|
|
|
// necessary in certain cases to prevent infinte recursion
|
|
|
|
dfn[ vis + ' virtual __$$' + f ] = ( function( f )
|
|
|
|
{
|
|
|
|
return function()
|
|
|
|
{
|
2014-02-20 23:17:04 -05:00
|
|
|
return this.___$$parent$$[ f ].apply( this, arguments );
|
2014-02-19 00:26:57 -05:00
|
|
|
};
|
2014-02-03 23:54:21 -05:00
|
|
|
} )( f );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
/**
|
|
|
|
* Mix trait into the given definition
|
|
|
|
*
|
2014-02-16 23:23:11 -05:00
|
|
|
* The original object DFN is modified; it is not cloned. TC should be
|
|
|
|
* initialized to an empty array; it is used to store context data for
|
|
|
|
* mixing in traits and will be encapsulated within a ctor closure (and thus
|
|
|
|
* will remain in memory).
|
2014-01-21 22:57:04 -05:00
|
|
|
*
|
|
|
|
* @param {Trait} trait trait to mix in
|
|
|
|
* @param {Object} dfn definition object to merge into
|
2014-02-16 23:23:11 -05:00
|
|
|
* @param {Array} tc trait class context
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Class} base target supertyep
|
2014-01-21 22:57:04 -05:00
|
|
|
*
|
|
|
|
* @return {Object} dfn
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function mixin( trait, dfn, tc, base )
|
2014-01-21 22:57:04 -05:00
|
|
|
{
|
2014-01-23 00:34:15 -05:00
|
|
|
// the abstract class hidden within the trait
|
2014-02-28 23:55:24 -05:00
|
|
|
var acls = trait.__acls;
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// retrieve the private member name that will contain this trait object
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
var iname = addTraitInst( trait, dfn, tc, base );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// recursively mix in trait's underlying abstract class (ensuring that
|
|
|
|
// anything that the trait inherits from is also properly mixed in)
|
|
|
|
mixinCls( acls, dfn, iname );
|
|
|
|
return dfn;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively mix in class methods
|
|
|
|
*
|
|
|
|
* If CLS extends another class, its methods will be recursively processed
|
|
|
|
* to ensure that the entire prototype chain is properly proxied.
|
|
|
|
*
|
|
|
|
* For an explanation of the iname parameter, see the mixin function.
|
|
|
|
*
|
|
|
|
* @param {Class} cls class to mix in
|
|
|
|
* @param {Object} dfn definition object to merge into
|
|
|
|
* @param {string} iname trait object private member instance name
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function mixinCls( cls, dfn, iname )
|
|
|
|
{
|
|
|
|
var methods = cls.___$$methods$$;
|
|
|
|
|
2014-01-23 01:15:53 -05:00
|
|
|
mixMethods( methods['public'], dfn, 'public', iname );
|
|
|
|
mixMethods( methods['protected'], dfn, 'protected', iname );
|
|
|
|
|
2014-02-28 23:55:24 -05:00
|
|
|
// if this class inherits from another class that is *not* the base
|
|
|
|
// class, recursively process its methods; otherwise, we will have
|
|
|
|
// incompletely proxied the prototype chain
|
|
|
|
var parent = methods['public'].___$$parent$$;
|
|
|
|
if ( parent && ( parent.constructor !== ClassBuilder.ClassBase ) )
|
|
|
|
{
|
|
|
|
mixinCls( parent.constructor, dfn, iname );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mix implemented types into destination object
|
|
|
|
*
|
|
|
|
* The provided destination object will ideally be the `implemented' array
|
|
|
|
* of the destination class's meta object.
|
|
|
|
*
|
|
|
|
* @param {Class} cls source class
|
|
|
|
* @param {Object} dest_meta destination object to copy into
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function mixinImpl( cls, dest_meta )
|
|
|
|
{
|
|
|
|
var impl = ClassBuilder.getMeta( cls ).implemented || [],
|
|
|
|
i = impl.length;
|
|
|
|
|
|
|
|
while ( i-- )
|
|
|
|
{
|
|
|
|
// TODO: this could potentially result in duplicates
|
|
|
|
dest_meta.push( impl[ i ] );
|
|
|
|
}
|
2014-01-23 01:15:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mix methods from SRC into DEST using proxies
|
|
|
|
*
|
|
|
|
* @param {Object} src visibility object to scavenge from
|
|
|
|
* @param {Object} dest destination definition object
|
|
|
|
* @param {string} vis visibility modifier
|
2014-02-19 00:26:57 -05:00
|
|
|
* @param {string} iname proxy destination (trait instance)
|
2014-01-23 01:15:53 -05:00
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
|
|
|
function mixMethods( src, dest, vis, iname )
|
|
|
|
{
|
|
|
|
for ( var f in src )
|
2014-01-21 22:57:04 -05:00
|
|
|
{
|
2014-01-23 01:15:53 -05:00
|
|
|
if ( !( Object.hasOwnProperty.call( src, f ) ) )
|
2014-01-22 01:11:17 -05:00
|
|
|
{
|
2014-01-23 00:34:15 -05:00
|
|
|
continue;
|
2014-01-22 01:11:17 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// TODO: this is a kluge; we'll use proper reflection eventually,
|
2014-01-23 01:15:53 -05:00
|
|
|
// but for now, this is how we determine if this is an actual method
|
|
|
|
// vs. something that just happens to be on the visibility object
|
|
|
|
if ( !( src[ f ].___$$keywords$$ ) || f === '___$$trait$$' )
|
2014-01-22 01:11:17 -05:00
|
|
|
{
|
2014-01-23 00:34:15 -05:00
|
|
|
continue;
|
2014-01-22 01:11:17 -05:00
|
|
|
}
|
|
|
|
|
2014-02-01 23:15:40 -05:00
|
|
|
var keywords = src[ f ].___$$keywords$$,
|
|
|
|
vis = keywords['protected'] ? 'protected' : 'public';
|
2014-01-26 03:30:52 -05:00
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// if abstract, then we are expected to provide the implementation;
|
|
|
|
// otherwise, we proxy to the trait's implementation
|
2014-03-04 00:19:39 -05:00
|
|
|
if ( keywords[ 'abstract' ] && !( keywords[ 'override' ] ) )
|
2014-01-23 01:00:16 -05:00
|
|
|
{
|
2014-01-26 03:26:15 -05:00
|
|
|
// copy the abstract definition (N.B. this does not copy the
|
2014-01-26 03:30:52 -05:00
|
|
|
// param names, since that is not [yet] important); the
|
|
|
|
// visibility modified is important to prevent de-escalation
|
|
|
|
// errors on override
|
|
|
|
dest[ vis + ' weak abstract ' + f ] = src[ f ].definition;
|
2014-01-26 03:26:15 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-03-04 00:19:39 -05:00
|
|
|
var vk = keywords['virtual'],
|
2014-03-06 00:57:24 -05:00
|
|
|
virt = vk ? 'virtual ' : '',
|
2014-03-04 00:19:39 -05:00
|
|
|
ovr = ( keywords['override'] ) ? 'override ' : '',
|
|
|
|
pname = ( vk ? '' : 'proxy ' ) + virt + ovr + vis + ' ' + f;
|
2014-01-26 03:26:15 -05:00
|
|
|
|
|
|
|
// if we have already set up a proxy for a field of this name,
|
|
|
|
// then multiple traits have defined the same concrete member
|
|
|
|
if ( dest[ pname ] !== undefined )
|
|
|
|
{
|
|
|
|
// TODO: between what traits?
|
|
|
|
throw Error( "Trait member conflict: `" + f + "'" );
|
|
|
|
}
|
|
|
|
|
2014-02-19 00:26:57 -05:00
|
|
|
// if non-virtual, a normal proxy should do
|
2014-03-04 00:19:39 -05:00
|
|
|
if ( !( keywords[ 'virtual' ] ) )
|
2014-02-19 00:26:57 -05:00
|
|
|
{
|
|
|
|
dest[ pname ] = iname;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-26 03:26:15 -05:00
|
|
|
// proxy this method to what will be the encapsulated trait
|
2014-02-19 00:26:57 -05:00
|
|
|
// object (note that we do not use the proxy keyword here
|
|
|
|
// beacuse we are not proxying to a method of the same name)
|
|
|
|
dest[ pname ] = ( function( f )
|
|
|
|
{
|
|
|
|
return function()
|
|
|
|
{
|
|
|
|
var pdest = this[ iname ];
|
|
|
|
|
|
|
|
// invoke the direct method on the trait instance; this
|
|
|
|
// bypasses the virtual override check on the trait
|
|
|
|
// method to ensure that it is invoked without
|
|
|
|
// additional overhead or confusion
|
|
|
|
var ret = pdest[ '__$$' + f ].apply( pdest, arguments );
|
|
|
|
|
|
|
|
// if the trait returns itself, return us instead
|
2014-02-20 23:17:04 -05:00
|
|
|
return ( ret === pdest )
|
2014-02-19 00:26:57 -05:00
|
|
|
? this
|
|
|
|
: ret;
|
|
|
|
};
|
|
|
|
} )( f );
|
2014-01-23 01:00:16 -05:00
|
|
|
}
|
2014-01-21 22:57:04 -05:00
|
|
|
}
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add concrete trait class to a class instantion list
|
|
|
|
*
|
|
|
|
* This list---which will be created if it does not already exist---will be
|
|
|
|
* used upon instantiation of the class consuming DFN to instantiate the
|
|
|
|
* concrete trait classes.
|
|
|
|
*
|
|
|
|
* Here, `tc' and `to' are understood to be, respectively, ``trait class''
|
|
|
|
* and ``trait object''.
|
|
|
|
*
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Class} T trait
|
|
|
|
* @param {Object} dfn definition object of class being mixed into
|
|
|
|
* @param {Array} tc trait class object
|
|
|
|
* @param {Class} base target supertyep
|
2014-01-23 00:34:15 -05:00
|
|
|
*
|
|
|
|
* @return {string} private member into which C instance shall be stored
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function addTraitInst( T, dfn, tc, base )
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
var base_cid = base.__cid;
|
|
|
|
|
|
|
|
// creates a property of the form ___$to$N$M to hold the trait object
|
|
|
|
// reference; M is required because of the private member restrictions
|
|
|
|
// imposed to be consistent with pre-ES5 fallback
|
|
|
|
var iname = '___$to$' + T.__acls.__cid + '$' + base_cid;
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// the trait object array will contain two values: the destination field
|
2014-01-23 22:24:27 -05:00
|
|
|
// and the trait to instantiate
|
|
|
|
tc.push( [ iname, T ] );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
|
|
|
// we must also add the private field to the definition object to
|
|
|
|
// support the object assignment indicated by TC
|
|
|
|
dfn[ 'private ' + iname ] = null;
|
|
|
|
|
|
|
|
// create internal trait ctor if not available
|
|
|
|
if ( dfn.___$$tctor$$ === undefined )
|
|
|
|
{
|
2014-02-10 00:37:25 -05:00
|
|
|
// TODO: let's check for inheritance or something to avoid this weak
|
|
|
|
// definition (this prevents warnings if there is not a supertype
|
|
|
|
// that defines the trait ctor)
|
|
|
|
dfn[ 'weak virtual ___$$tctor$$' ] = function() {};
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc, base );
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return iname;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trait initialization constructor
|
|
|
|
*
|
|
|
|
* May be used to initialize all traits mixed into the class that invokes
|
|
|
|
* this function. All concrete trait classes are instantiated and their
|
|
|
|
* resulting objects assigned to their rsepective pre-determined field
|
|
|
|
* names.
|
|
|
|
*
|
2014-01-23 22:24:27 -05:00
|
|
|
* This will lazily create the concrete trait class if it does not already
|
|
|
|
* exist, which saves work if the trait is never used.
|
|
|
|
*
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Object} tc trait class list
|
|
|
|
* @param {Class} base target supertype
|
|
|
|
*
|
2014-01-23 00:34:15 -05:00
|
|
|
* @return {undefined}
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function tctor( tc, base )
|
2014-01-23 00:34:15 -05:00
|
|
|
{
|
|
|
|
// instantiate all traits and assign the object to their
|
|
|
|
// respective fields
|
|
|
|
for ( var t in tc )
|
|
|
|
{
|
|
|
|
var f = tc[ t ][ 0 ],
|
2014-01-23 22:24:27 -05:00
|
|
|
T = tc[ t ][ 1 ],
|
|
|
|
C = T.__ccls || ( T.__ccls = createConcrete( T.__acls ) );
|
2014-01-23 00:34:15 -05:00
|
|
|
|
2014-01-26 03:28:36 -05:00
|
|
|
// instantiate the trait, providing it with our protected visibility
|
|
|
|
// object so that it has access to our public and protected members
|
|
|
|
// (but not private); in return, we will use its own protected
|
|
|
|
// visibility object to gain access to its protected members...quite
|
|
|
|
// the intimate relationship
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
this[ f ] = C( base, this.___$$vis$$ ).___$$vis$$;
|
2014-01-23 00:34:15 -05:00
|
|
|
}
|
2014-02-10 00:37:25 -05:00
|
|
|
|
|
|
|
// if we are a subtype, be sure to initialize our parent's traits
|
|
|
|
this.__super && this.__super();
|
2014-01-21 22:57:04 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-02-10 00:37:25 -05:00
|
|
|
/**
|
|
|
|
* Create trait constructor
|
|
|
|
*
|
|
|
|
* This binds the generic trait constructor to a reference to the provided
|
|
|
|
* trait class list.
|
|
|
|
*
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
* @param {Object} tc trait class list
|
|
|
|
* @param {Class} base target supertype
|
2014-02-10 00:37:25 -05:00
|
|
|
*
|
|
|
|
* @return {function()} trait constructor
|
|
|
|
*/
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
function createTctor( tc, base )
|
2014-02-10 00:37:25 -05:00
|
|
|
{
|
|
|
|
return function()
|
|
|
|
{
|
Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.
This differs slightly from Scala. For example, consider this ease.js mixin:
C.use( T ).use( T )()
This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:
- C' = C.use( T );
- new C'.use( T );
That is, it each call to `use' creates another class with T mixed in.
Scala, on the other hand, complains in this situation:
new C with T with T
will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:
class Ca extends T
new Ca with T
In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:
new C.use( T ).extend( {} ).use( T )
Just keep that in mind.
2014-03-06 01:51:49 -05:00
|
|
|
return tctor.call( this, tc, base );
|
2014-02-10 00:37:25 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-21 22:57:04 -05:00
|
|
|
module.exports = Trait;
|
2014-03-04 00:19:39 -05:00
|
|
|
|