1
0
Fork 0
Commit Graph

491 Commits (e934338b41518ecb5cb898cec25e0be44b739bdb)

Author SHA1 Message Date
Mike Gerwitz e934338b41 Subtype ctor guarantees with parent __mixin or __construct
A solution for this problem took a disproportionally large amount of time,
attempting many different approaches, and arriving still at a kluge; this is
indicative of a larger issue---we've long since breached the comfort of the
original design, and drastic refactoring is needed.

I have ideas for this, and have already started in another branch, but I
cannot but this implementation off any longer while waiting for it.

Sorry for anyone waiting on the next release: this is what held it up, in
combination with my attention being directed elsewhere during that time (see
the sparse commit timestamps). Including this ordering guarantee is very
important for a stable, well-designed [trait] system.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 90a32a104f __construct and __mixin ordering guarantees
See test cases for rationale.

I'm not satisfied with this implementation, but the current state of ease.js
makes this kind of thing awkward. Will revisit at some point.
2014-07-27 01:54:30 -04:00
Mike Gerwitz f3cb815baa Sibling traits will each have __mixin called distinctly 2014-07-27 01:54:30 -04:00
Mike Gerwitz 2204ff6d28 Parameterized traits may now be mixed in without configuration
Rationale is available in the test case.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 3fc0f90e01 Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:

  1. Expose setters for credentials
  2. Trait closure
  3. Extend the trait (not yet supported)

The first option seems like a simple solution:

```javascript
  Transport.use( HttpPlainAuth )()
    .setUser( 'username', 'password' )
    .send( ... );
```

But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.

More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!

The second option is to store configuration data outside of the Trait acting
as a closure:

```javascript
  var _user, _pass;
  function setCredentials( user, pass ) { _user = user; _pass = pass; }
  Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```

There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.

The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.

To recover from memory leaks, you could instead create a trait generator:

```javascript
  function createHttpPlainAuth( user, pass )
  {
      return Trait( { /* ... */ } );
  }
```

This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.

The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.

Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:

```javascript
  Transport.use( HttpPlainAuth( user, pass ) )()
    .send( ... );
```

Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.

It further allows for a convenient means of providing useful, reusable
exports:

```javascript
  module.exports = {
      ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
      ServerBarAuth: HttpPlainAuth( userbar, passbar ),

      ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
      // ...
  };

  var module = require( 'foo' );

  // dynamic auth
  Transport.use( foo.ServerFooAuth )().send( ... );

  // or predefined classes
  module.ServerFooTransport().send( ... );
```

Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.

So how do you create parameterized traits? You need only define a `__mixin`
method:

  Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );

The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.

A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-07-27 01:54:30 -04:00
Mike Gerwitz d400a4cbb1 FallbackSymbol now returns instance of self
Existing functionality is maintained using toString. This is necessary to
support type checking, and to be more consistent with the native Symbol
implementation.

Technically `new FallbackSymbol` is supposed to throw a TypeError, just as
`new Symbol` would, but doing so complicates the constructor unncessarily,
so I am not going to bother with that here.
2014-07-09 23:50:36 -04:00
Mike Gerwitz 07c0a974af FIXME added for possibly leaking private symbol during tctor call
This is a security-related issue: we want to ensure that the user cannot,
without debugging tools, retrieve certain internal details that may be used
to compromise an implementation.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 031489a07b Private symbol key is now non-enumerable
This will, at least in ES5 environments, prevent its general discovery and
[accidental] use. Of course, any decent debugger will still be able to see
it, so unless ES6 is used, this is not truely hidden.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 072fef2dc0 createMeta no longer copying to new_class.prototype
I do not wholly recall why this was done initially (nor do I care to
research it, since it's not necessary now), but from the looks of it, it was
likely a kluge to handle a poor implementation of some feature.

This will help clean up a little, since it's rude to pollute a prototype
unnecessarily.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 17d11c1832 Re-encapsulated ClassBuilder private symbol
Temporary method removed and symbol now provided directly to the trait ctor;
this is needed to retrieve the protected visibility object; there is
currently no API for doing so, and there may never be (providing an API for
such a thing seems questionable).

Of course, if we eventually want to decouple the trait implementation from
ClassBuilder (which would be a good idea), we'll have to figure out
something.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 13b0bd2fb3 Visibility object is now encapsulated in symbol key
Note that this patch temporarily breaks encapsulation via
ClassBuilder.___$$privsym$$ to expose necessary internal details to Trait;
this will be resolved.
2014-07-09 00:14:25 -04:00
Mike Gerwitz a35a9c6ed3 Class ___$$meta$$ moved into private symbol field 2014-07-09 00:14:25 -04:00
Mike Gerwitz e0866db313 Corrected MemberBuilderValidator warning handler in class.js 2014-07-09 00:14:25 -04:00
Mike Gerwitz 5212515d99 ClassBuilder#build now only making one getMeta call 2014-07-09 00:14:24 -04:00
Mike Gerwitz c49a6d70de util/Symbol added 2014-07-09 00:14:24 -04:00
Mike Gerwitz 2f615fbe68 FallbackSymbol added
This is the closest we will get to implementing a concept similar to symbols
in pre-ES6. The intent is that, in an ES5 environment, the caller should
ensure that the object receiving this key will mark it as non-enumerable.
Otherwise, we're out of luck.

The symbol string is pseduo-randomly generated with an attempt to reduce the
likelihood of field collisions and malicious Math.{floor,random} overwrites
(so long as they are clean at the time of loading the module).
2014-07-09 00:14:24 -04:00
Mike Gerwitz 4e3d609b01 Extracted warning handlers into their own prototypes 2014-06-11 23:42:20 -04:00
Mike Gerwitz de33a7c964 Extracted Warning into warn/Warning
Test case moved to reflect the path change.
2014-06-11 23:08:52 -04:00
Mike Gerwitz 1bba35418a Added Global prototype
This will clean up the code for providing global check and alternatives.
2014-06-11 23:08:48 -04:00
Mike Gerwitz 887d5ef0a3 GNU ease.js and test cases now compile in strict mode
During its initial development, no environments (e.g. Node.js, Chromium,
Firefox) supported strict mode; this has since changed, and node has a
--use-strict option, which is used in the test runner to ensure conformance.
2014-05-04 22:17:28 -04:00
Mike Gerwitz ff4b9c856b
Corrrected virtual non-overridden trait methods
See the introduced test cases for great detail; there was a problem with the
implementation where only the public API of the abstract trait object was
being checked, meaning that protected virtual methods were not found when
peforming the call. This was not a problem on override, because that proxies
to the protected member object (PMO), which includes protected members.
2014-05-02 21:30:27 -04:00
Mike Gerwitz 8ab183f4c8 Setting super method on override wrapper 2014-05-02 21:26:49 -04:00
Mike Gerwitz 779dbc37bc Corrected virtual non-overridden trait method invocations
What a mouthful. And nightmare, having been away from the trait
implementation for so long.
2014-05-02 21:08:41 -04:00
Mike Gerwitz 8c95932446 Trait virtual method proxies now set __length metadata
This fixes a bug that causesd virtual definitions with parameters on classes
that a trait is mixed into to fail, and prevented proper param length
validations in the reverse case.

See test case description for a less confusing description.
2014-05-02 00:19:25 -04:00
Mike Gerwitz 8b8a08b7dc Subtypes of prototype subtypes now work correctly 2014-04-29 10:47:12 -04:00
Mike Gerwitz fa177922b4 Class#asPrototype support
This is an interop feature: allows using ease.js classes as part of a
prototype chain.
2014-04-29 02:03:51 -04:00
Mike Gerwitz 34d84412e1 Prototype supertype property proxy fix
This was a nasty bug that I discovered when working on a project at work
probably over a year ago. I had worked around it, but ease.js was largely
stalled at the time; with it revitalized by GNU, it's finally getting fixed.

See test case comments for more information.
2014-04-29 02:03:40 -04:00
Mike Gerwitz 7f3e7fba35 Overriding vanilla prototype methods no longer errors
This is something that I've been aware of for quite some time, but never got
around to fixing; ease.js had stalled until it was revitalized by becoming a
GNU project.
2014-04-28 15:09:52 -04:00
Mike Gerwitz aa0003d239 ClassBuilder.isInstanceOf now defers to type
This allows separation of concerns and makes the type system extensible. If
the type does not implement the necessary API, it falls back to using
instanceof.
2014-04-28 15:09:52 -04:00
Mike Gerwitz 4605476b4f Added isCompatible method to interfaces
There is a great amount of rationale in the test case added in this commit.
2014-04-28 15:09:52 -04:00
Mike Gerwitz fd404fc69f Named trait staging object
The syntax Trait( "Name" ) now works as Class( "Name" ), permitting
implementing interfaces and extending from the staged object.
2014-04-26 00:42:29 -04:00
Mike Gerwitz bb98c9afb3
Revert "Removed unnecessary try/catch from isInstanceOf"
This reverts commit 58ee52ad4a.

As it turns out, IE9 allows use of instanceof with special object types, not
just functions. One such example is HTMLElement; its type is `object', whereas
in Firefox and Chromium it is `function'. There is no reliable way to perform
this detection.

Therefore, the try/catch here is more reliable.
2014-04-24 21:32:49 -04:00
Mike Gerwitz 8391bc007d
No longer using __dirname in requires
I feel like I originally did this because older versions of node didn't like
relative paths (unless maybe the cwd wasn't in NODE_PATH). Regardless, it works
now, and this is cleaner.

Further, I noticed that __dirname didn't seem to be working properly with
browserify. While GNU ease.js does not make use of it (ease.js uses its own
scripts), other projects may.
2014-04-20 21:11:38 -04:00
Mike Gerwitz 42b52bb692 Exposing keyword bit values and bitmasks
Available through property parser interface
2014-04-20 02:40:36 -04:00
Mike Gerwitz 004fbd24ad Added virtual kmask 2014-04-20 02:31:28 -04:00
Mike Gerwitz fd7e0cbef7 Support for implicit private members
Members with underscore prefixes are now implicitly private, which follows
common convention. See test case comments for rationale.
2014-04-20 02:28:38 -04:00
Mike Gerwitz 58ee52ad4a Removed unnecessary try/catch from isInstanceOf
Was just being lazy.
2014-04-09 20:01:33 -04:00
Mike Gerwitz d23f34da4b Removed try/catch from Interface.extend
Permits more aggressive optimization.
2014-04-09 20:01:33 -04:00
Mike Gerwitz c76178516e Various argument handling optimizations
Permits more aggressive v8 optimization.
2014-04-09 20:01:33 -04:00
Mike Gerwitz a52fcfa1d9 Removed fallback check on each defineSecureProp call
This check was originally added because IE8's implementation is broken.
However, we already perform a test in the `can_define_prop` configuration,
so this is not necessary (it seems to be a relic).
2014-04-09 20:00:54 -04:00
Mike Gerwitz 82a02c0081 [copyright] Copyright assignment to the FSF
Thanks to Donald Robertson III for his help and guidance during this
process.
2014-04-09 19:05:07 -04:00
Mike Gerwitz c835641dcb Private methods are no longer wrapped
This is an exciting performance optimization that seems to have eluded me
for a surprisingly long time, given that the realization was quite random.
ease.js accomplishes much of its work through a method wrapper---each and
every method definition (well, until now) was wrapped in a closure that
performed a number of steps, depending on the type of wrapper involved:

  1. All wrappers perform a context lookup, binding to the instance's private
     member object of the class that defined that particular method. (See
     "Implementation Details" in the manual for more information.)
  2. This context is restored upon returning from the call: if a method
     returns `this', it is instead converted back to the context in which
     the method was invoked, which prevents the private member object from
     leaking out of a public interface.
  3. In the event of an override, this.__super is set up (and torn down).

There are other details (e.g. the method wrapper used for method proxies),
but for the sake of this particular commit, those are the only ones that
really matter. There are a couple of important details to notice:

  - Private members are only ever accessible from within the context of the
    private member object, which is always the context when executing a
    method.
  - Private methods cannot be overridden, as they cannot be inherited.

Consequently:

  1. We do not need to perform a context lookup: we are already in the proper
     context.
  2. We do not need to restore the context, as we never needed to change it
     to begin with.
  3. this.__super is never applicable.

Method wrappers are therefore never necessary for private methods; they have
therefore been removed.

This has some interesting performance implications. While in most cases the
overhead of method wrapping is not a bottleneck, it can have a strong impact
in the event of frequent method calls or heavily recursive algorithms. There
was one particular problem that ease.js suffered from, which is mentioned in
the manual: recursive calls to methods in ease.js were not recommended
because it

  (a) made two function calls for each method call, effectively halving the
      remaining call stack size, and
  (b) tail call optimization could not be performed, because recursion
      invoked the wrapper, *not* the function that was wrapped.

By removing the method wrapper on private methods, we solve both of these
problems; now, heavily recursive algorithms need only use private methods
(which could always be exposed through a protected or public API) when
recursing to entirely avoid any performance penalty by using ease.js.

Running the test cases on my system (your results may vary) before and after
the patch, we have:

  BEFORE:
  0.170s (x1000 = 0.0001700000s each): Declare 1000 anonymous classes with
    private members
  0.021s (x500000 = 0.0000000420s each): Invoke private methods internally

  AFTER:
  0.151s (x1000 = 0.0001510000s each): Declare 1000 anonymous classes with
    private members
  0.004s (x500000 = 0.0000000080s each): Invoke private methods internally

This is all the more motivation to use private members, which enforces
encapsulation; keep in mind that, because use of private members is the
ideal in well-encapsulated and well-factored code, ease.js has been designed
to perform best under those circumstances.
2014-03-20 23:43:24 -04:00
Mike Gerwitz 10d653825a
Corrected version string generation with empty suffix 2014-03-15 23:57:02 -04:00
Mike Gerwitz 744696b1a7
[copyright] Copyright update 2014-03-15 23:56:47 -04:00
Mike Gerwitz 0713a9f3d0 Deleted unnecessary trait abstract method in favor of auto-abstract flag
This method was added before this flag existed.
2014-03-15 21:16:27 -04:00
Mike Gerwitz cc43f4b339 Prohibiting trait getters/setters 2014-03-15 21:16:27 -04:00
Mike Gerwitz bfadd9fce9 Added missing trait docblocks 2014-03-15 21:16:27 -04:00
Mike Gerwitz eab4dbfe4d Throwing exception on trait static member
These will be supported in future versions; this is not something that I
want to rush, nor is it something I want to hold up the first GNU release;
it is likely to be a much lesser-used feature.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 316a7dd703 Refactored Traits to use propParse hooks 2014-03-15 21:16:27 -04:00
Mike Gerwitz 3d47443046 Refactored ClassBuilder.buildMembers (dynamic prop parse context)
The parser methods are now split into their own functions. This has a number
of benefits: The most immediate is the commit that will follow. The second
benefit is that the function is no longer a closure---all context
information is passed into it, and so it can be optimized by the JavaScript
engine accordingly.
2014-03-15 21:16:27 -04:00