diff --git a/lib/ctor/ErrorCtor.js b/lib/ctor/ErrorCtor.js
new file mode 100644
index 0000000..9ddf3f9
--- /dev/null
+++ b/lib/ctor/ErrorCtor.js
@@ -0,0 +1,292 @@
+/**
+ * Handles the stupid-complicated error subtyping situation in JavaScript
+ *
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * 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 .
+ *
+ * Before you wonder why this is so stupid-complicated and question this
+ * effort: ease.js supports ECMAScript 3 and later environments.
+ *
+ * Unless you continue to question because this is JavaScript and,
+ * regardless of ECMAScript version, it's still stupid. Then you'd be
+ * right.
+ *
+ * See test case for comprehensive examples.
+ */
+
+/**
+ * Constructor generator for Error subtypes
+ *
+ * BASE should be the supertype of all error prototypes in the environment;
+ * this is usually `Error'. BASE is used to determine what features are
+ * available in the particular environment (e.g. `Error.captureStackTrace').
+ *
+ * The goal is to generate error constructors that will produce errors as
+ * close to the form of the environment of BASE as possible: this is _not_
+ * an attempt to provide a unified Error interface across all environments;
+ * even if we know about certain data line line numbers, if an error from
+ * BASE would not normally produce them, then neither will we.
+ *
+ * @param {Function} base supertype of all error prototypes
+ *
+ * @return {ErrorCtor}
+ */
+function ErrorCtor( base )
+{
+ if ( !( this instanceof ErrorCtor ) )
+ {
+ return new ErrorCtor( base );
+ }
+
+ if ( typeof base !== 'function' )
+ {
+ throw TypeError( "Expected constructor for error base" );
+ }
+
+ this._base = base;
+ this._initDataSupport( base );
+};
+
+
+ErrorCtor.prototype = {
+ /**
+ * Stack parser-guesser
+ *
+ * This recognizes Mozilla- and V8-style stack traces containing our
+ * unique identifier; other formats might work by chance, but their
+ * support is not intentional.
+ *
+ * There are four match groups, as noted in the regex itself below:
+ * 1. The entire stripped stack (if recognized);
+ * 2. Filename;
+ * 3. Line number; and
+ * 4. Column number (might not exist).
+ *
+ * @type {RegExp}
+ */
+ _stackre: new RegExp(
+ '^' +
+ '(?:' +
+ '.+?\\n\\s+at ' + // V8-style 'at' on second line
+ ')?' +
+
+ '.*?__\\$\\$ector\\$\\$__' + // our unique identifier
+ '.*(?:\\n|$)' + // ignore rest of line
+
+ '(' + // (stripped stack)
+ '(?:' +
+ '.*?[@(]' + // skip Mozilla/V8 frame name
+ '(.*?)' + // (filename)
+ ':(\\d+)' + // (line)
+ '(?::(\\d+))?' + // (column)
+ '.*?\\n' + // ignore rest of line
+ ')?' +
+ '(?:.|\\n)*' + // include rest of stack
+ ')?' +
+ '$'
+ ),
+
+ /**
+ * Base error constructor (usually Error)
+ * @type {Function}
+ */
+ _base: {},
+
+
+ /**
+ * Create error constructor
+ *
+ * Note that, as this is intended for use as a constructor for ease.js
+ * classes, this will _not_ set up the prototype as a subtype of
+ * SUPERTYPE---the caller is expected to do so.
+ *
+ * @param {Function} supertype parent error constructor
+ * @param {string} name error subtype name
+ *
+ * @return {function(string)} error constructor
+ */
+ createCtor: function( supertype, name )
+ {
+ if ( typeof supertype !== 'function' )
+ {
+ throw TypeError( "Expected constructor for supertype" );
+ }
+
+ var _self = this;
+
+ // yes, this name is important, as we use it as an identifier for
+ // stack stripping (see `#_parseStack')
+ function __$$ector$$__( message )
+ {
+ this.message = message;
+ _self._setStackTrace( this, _self._base, supertype );
+ }
+
+ // it's important to let the name fall through if not provided
+ if ( name !== undefined )
+ {
+ __$$ector$$__.prototype.name = name;
+ }
+
+ return __$$ector$$__;
+ },
+
+
+ /**
+ * Create stack trace using appropriate method for environment
+ *
+ * If BASE has a `captureStackStrace' method, then it will be used with
+ * DEST as the destination and SUPERTYPE as the relative object for the
+ * stack frames. Otherwise, `DEST.stack' will be overwritten with the
+ * `stack' produces by instantiating SUPERTYPE, which is the
+ * conventional approach.
+ *
+ * @param {Object} dest destination object for values
+ * @param {Function} base supertype of all errors
+ * @param {Function} supertype supertype of new error
+ *
+ * @return {undefined}
+ */
+ _setStackTrace: function( dest, base, supertype )
+ {
+ if ( typeof base.captureStackTrace === 'function' )
+ {
+ base.captureStackTrace( dest, dest.constructor );
+ return;
+ }
+
+ var super_inst = new supertype(),
+ stack_data = this._parseStack( super_inst.stack );
+
+ dest.stack = stack_data.stripped;
+
+ if ( this._lineSupport )
+ {
+ dest.lineNumber = stack_data.line;
+ }
+
+ if ( this._columnSupport )
+ {
+ dest.columnNumber = stack_data.column;
+ }
+
+ if ( this._filenameSupport )
+ {
+ dest.fileName = stack_data.filename;
+ }
+ },
+
+
+ /**
+ * Attempt to extract stack frames below self, as well as the line and
+ * column numbers (if available)
+ *
+ * The provided string STACK should be the full stack trace. It will be
+ * parsed to ensure that the first stack frame matches a unique
+ * identifier for the error constructor, and then return the following:
+ *
+ * `full': original STACK;
+ * `stripped': stack trace with first frame stripped, if matching;
+ * `filename': filename from the first non-error frame, if matching;
+ * `line': line number from first non-error frame, if matching;
+ * `column': column number from first non-error frame, if matching.
+ *
+ * @param {string} stack full stack trace
+ *
+ * @return {Object} full, stripped, line, column
+ */
+ _parseStack: function( stack )
+ {
+ var match = ( typeof stack === 'string' )
+ ? stack.match( this._stackre )
+ : null;
+
+ if ( match )
+ {
+ // these undefined defaults deal with older environments
+ // (e.g. IE<9) returning an empty string rather than undefined
+ // for non-matches (note that !!"0"===true, so we're okay)
+ return {
+ full: stack,
+ stripped: match[ 1 ] || '',
+ filename: match[ 2 ] || undefined,
+ line: match[ 3 ] || undefined,
+ column: match[ 4 ] || undefined
+ };
+ }
+
+ return {
+ full: stack,
+ stripped: stack
+ };
+ },
+
+
+ /**
+ * Initialize with whether line, column, and/or filenames are supported
+ * by the environment (of BASE)
+ *
+ * Some environments (e.g. GNU IceCat) support line and column
+ * numbers. Others (like older versions of a certain proprietary
+ * browser) only support line numbers. Others support neither.
+ *
+ * The reason for this very specific distinction is strict consistency:
+ * we want to produce errors of the exact same form as those created by
+ * BASE.
+ *
+ * Below, we check for the value on the prototype chain first and, upon
+ * failing to find anything, then check to see if the field exists at
+ * all on an instance of BASE.
+ *
+ * This method sets `_{line,column,filename}Support`.
+ *
+ * @param {Function} base supertype of all errors
+ *
+ * @return {undefined}
+ */
+ _initDataSupport: function( base )
+ {
+ var chk = new base(),
+ hasOwn = Object.hasOwnProperty;
+
+ this._lineSupport = ( chk.lineNumber !== undefined )
+ || hasOwn.call( chk, 'lineNumber' );
+
+ this._columnSupport = ( chk.columnNumber !== undefined )
+ || hasOwn.call( chk, 'columnNumber' );
+
+ this._filenameSupport = ( chk.fileName !== undefined )
+ || hasOwn.call( chk, 'fileName' );
+ },
+
+
+ /**
+ * Whether the given TYPE is our base error constructor or a subtype
+ *
+ * @param {Function} type constructor to check against our base
+ *
+ * @return {boolean} whether TYPE is our base constructor or a subtype
+ */
+ isError: function( type )
+ {
+ return ( type === this._base )
+ || ( type.prototype instanceof this._base );
+ },
+};
+
+
+module.exports = ErrorCtor;
diff --git a/test/ctor/ErrorCtorTest.js b/test/ctor/ErrorCtorTest.js
new file mode 100644
index 0000000..751185a
--- /dev/null
+++ b/test/ctor/ErrorCtorTest.js
@@ -0,0 +1,510 @@
+/**
+ * Tests error constructor generation
+ *
+ * Copyright (C) 2016 Free Software Foundation, Inc.
+ *
+ * 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 .
+ */
+
+function DummyError() {};
+
+function SubDummyError() {};
+SubDummyError.prototype = new DummyError();
+SubDummyError.prototype.name = 'sub dummy name';
+
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'ctor/ErrorCtor' );
+
+ // no need to be comprehensive
+ this.bases = [ '', {} ];
+
+ // space is intentional, since in IceCat (for example) you get
+ // '@debugger eval code' if you get an error in the console
+ this.frames = [
+ // average case, multiple frames, Mozilla-style
+ {
+ frames: [
+ "__$$ector$$__@foo.js:1:2",
+ "bar@first-frame.js:1:2",
+ "baz@second other frame:2:2",
+ ],
+
+ strip: 1,
+ fileName: 'first-frame.js',
+ lineNumber: 1,
+ columnNumber: 2,
+ },
+ // average case, multiple frames, Mozilla-style, no-match
+ {
+ frames: [
+ "__$$no$$__@foo.js:1:2",
+ "bar@first-frame2.js:1:2",
+ "baz@second other frame:2:2",
+ ],
+
+ strip: 0,
+ fileName: undefined,
+ lineNumber: undefined,
+ columnNumber: undefined,
+ },
+ // average case, Mozilla-style, no column number
+ {
+ frames: [
+ "__$$ector$$__@foo.js:1",
+ "bar@first-frame2.js:2",
+ "baz@second other frame:3",
+ ],
+
+ strip: 1,
+ fileName: 'first-frame2.js',
+ lineNumber: 2,
+ columnNumber: undefined,
+ },
+ // average case, Mozilla-style, no frame name
+ {
+ frames: [
+ "__$$ector$$__@foo.js:1:1",
+ "@first-frame3.js:2:2",
+ "baz@second other frame:3:3",
+ ],
+
+ strip: 1,
+ fileName: 'first-frame3.js',
+ lineNumber: 2,
+ columnNumber: 2,
+ },
+ // no other frames (but notice the trailing
+ // newline), Mozilla-style
+ {
+ frames: [
+ "__$$ector$$__@foo.js:1:2\n",
+ ],
+
+ strip: 1,
+ fileName: undefined,
+ lineNumber: undefined,
+ columnNumber: undefined,
+ },
+
+ // average case, multiple frames, V8-style
+ {
+ frames: [
+ "SomeError",
+ " at __$$ector$$__ (foo.js:1:2)",
+ " at bar (first-frame.js:1:2)",
+ " at baz (second other frame:2:2)",
+ ],
+
+ strip: 2,
+ fileName: 'first-frame.js',
+ lineNumber: 1,
+ columnNumber: 2,
+ },
+ // average case, V8-style, multiple frames, no-match
+ {
+ frames: [
+ "SomeError",
+ " at __$$nomatch$$__ (foo.js:1:2)",
+ " at bar (first-frame.js:1:2)",
+ " at baz (second other frame:2:2)",
+ ],
+
+ strip: 0,
+ fileName: undefined,
+ lineNumber: undefined,
+ columnNumber: undefined,
+ },
+ // average case, V8-style, no column number
+ {
+ frames: [
+ "SomeError",
+ " at __$$ector$$__ (foo.js:1)",
+ " at bar (first-frame2.js:1)",
+ " at baz (second other frame:2)",
+ ],
+
+ strip: 2,
+ fileName: 'first-frame2.js',
+ lineNumber: 1,
+ columnNumber: undefined,
+ },
+ // average case, V8-style, no frame name
+ {
+ frames: [
+ "SomeError",
+ " at __$$ector$$__ (foo.js:1:2)",
+ " at (first-frame.js:1:2)",
+ " at (second other frame:2:2)",
+ ],
+
+ strip: 2,
+ fileName: 'first-frame.js',
+ lineNumber: 1,
+ columnNumber: 2,
+ },
+ // no other frames, V8-style
+ {
+ frames: [
+ "SomeError",
+ " at __$$ector$$__ (foo.js:1:2)"
+ ],
+
+ strip: 2,
+ fileName: 'first-frame.js',
+ lineNumber: undefined,
+ columnNumber: undefined,
+ },
+ ];
+
+ // whether line, column, or filename are available in environment
+ this.linecolf = [
+ { lineNumber: false, columnNumber: false, fileName: true },
+ { lineNumber: true, columnNumber: false, fileName: true },
+ { lineNumber: false, columnNumber: true, fileName: true },
+ { lineNumber: true , columnNumber: true, fileName: true },
+ { lineNumber: false, columnNumber: false, fileName: false },
+ { lineNumber: true, columnNumber: false, fileName: false },
+ { lineNumber: false, columnNumber: true, fileName: false },
+ { lineNumber: true , columnNumber: true, fileName: false },
+ ];
+ },
+
+
+ '@each(bases) Throws error if base is not a function': function( obj )
+ {
+ this.assertThrows( function()
+ {
+ this.Sut( obj );
+ }, TypeError );
+ },
+
+
+ '@each(bases) Throws error if supertype is not a function': function( obj )
+ {
+ this.assertThrows( function()
+ {
+ this.Sut( DummyError, obj );
+ }, TypeError );
+ },
+
+
+ /**
+ * Error messages are set by a `message' property.
+ */
+ 'Sets message via constructor': function()
+ {
+ var expected = 'foo message';
+
+ this.assertEqual(
+ new ( this.Sut( DummyError ).createCtor( DummyError ) )
+ ( expected ).message,
+ expected
+ );
+ },
+
+
+ /**
+ * The build-in ECMAScript Error constructors don't cast MESSAGE to a
+ * string, so we shouldn't either.
+ */
+ 'Does not cast message to string': function()
+ {
+ var expected = {};
+
+ this.assertStrictEqual(
+ new ( this.Sut( DummyError ).createCtor( DummyError ) )
+ ( expected ).message,
+ expected
+ );
+ },
+
+
+ /**
+ * The name of the error is derived from the `name' property.
+ */
+ 'Sets name to class name': function( Type )
+ {
+ var expected = 'MyError';
+
+ this.assertEqual(
+ new (
+ this.Sut( DummyError ).createCtor( DummyError, expected )
+ )().name,
+ expected
+ );
+ },
+
+
+ /**
+ * ...unless one is not provided, in which case we should retain the
+ * parent's.
+ *
+ * Since the constructor generator doesn't set up the constructor's
+ * supertype, this amounts to seeing if it'll fall through if we set the
+ * supertype. We don't want to just check whether `name' is or is not
+ * defined on the prototype, since we only care that it works, not how
+ * it's done.
+ */
+ 'Defaults name to supertype': function( Type )
+ {
+ var ctor = this.Sut( DummyError )
+ .createCtor( SubDummyError );
+
+ ctor.prototype = new SubDummyError();
+
+ this.assertEqual(
+ new ctor().name,
+ new SubDummyError().name
+ );
+ },
+
+
+ /**
+ * JavaScript doesn't make extending Error pleasent---we need to take
+ * care of our own stack trace, and that trace isn't going to be
+ * entirely correct (because we have an extra stack frame, being in the
+ * Error itself). Furthermore, not all environments support stack.
+ *
+ * To make matters worse, the proper method of obtaining or overwriting
+ * a stack trace also varies. So, let's emulate some environments.
+ */
+
+
+ /**
+ * Certain browsers (like Chromium) support `Error.captureStackTrace',
+ * which sets the `stack' property on the given object to either a
+ * complete stack trace, or a stack trace below a reference to a given
+ * object. The `stack' property is also defined as a getter, which
+ * makes for confusing and frustrating development when you are
+ * wondering why setting it does nothing. (Personal experience
+ * perhaps?)
+ *
+ * Note that the previous tests will implicitly test that
+ * `captureStackTrace' is _not_ called when unavailable, because they
+ * will fail to call an undefined function.
+ */
+ 'Uses Error.captureStackTrace when available': function()
+ {
+ var _self = this,
+ expected = 'as expected',
+ capture_args;
+
+ function DummyErrorCapture() {};
+ DummyErrorCapture.captureStackTrace = function()
+ {
+ capture_args = arguments;
+ };
+
+ var given_ctor = this.Sut( DummyErrorCapture )
+ .createCtor( DummyError );
+
+ // if the stack trace were generated now, then that would be bad (as
+ // it would be incorrect when the error is actually instantiated)
+ this.assertEqual( undefined, capture_args );
+
+ var inst = new given_ctor();
+
+ // destination for `stack' property set
+ this.assertStrictEqual( capture_args[ 0 ], inst );
+
+ // relative stack frame
+ this.assertStrictEqual( capture_args[ 1 ], given_ctor );
+ },
+
+
+ /**
+ * If `Error.captureStackTrace' is _not_ available, we fall back to the
+ * good-ol'-fashion overwrite-stack-with-a-super-instance-stack
+ * approach, which is conventional.
+ */
+ 'Overwrites `stack\' property if no `captureStackTrace\'': function()
+ {
+ var expected = 'as expected',
+ allow = false;
+
+ // just something that we can mock the stack on
+ function SubDummyError()
+ {
+ if ( !allow ) return;
+
+ this.stack = expected;
+ }
+
+ // this is why stack traces are traditionally a problem unless you
+ // remember to explicitly set it; ease.js does it for you
+ SubDummyError.prototype = new DummyError();
+ SubDummyError.prototype.stack = 'stack not set';
+
+ var given = this.Sut( DummyError )
+ .createCtor( SubDummyError );
+
+ // ensures that this stack is actually from a new object, not the
+ // stack that was set on the prototype
+ allow = true;
+ var result = new given().stack;
+
+ this.assertEqual( result, expected );
+ },
+
+
+ /**
+ * If `Error.captureStackTrace' is available and used, then the error
+ * constructor itself will not appear in the stack trace. If we have to
+ * set it, however, then it will---this is a consequence of
+ * instantiating the supertype within the error constructor in order to
+ * get a proper stack trace.
+ *
+ * We will attempt to strip ourselves from the string if the stack trace
+ * string meets certain critiera.
+ *
+ * Also make sure we don't strip if there is a non-match. Generally
+ * speaking, this won't often (if ever) be the case in practice, but
+ * let's never make assumptions.
+ */
+ '@each(frames) Strips self from stack if no `captureStackTrace\'':
+ function( framedata )
+ {
+ var lines = Array.prototype.slice.call( framedata.frames );
+
+ function SubDummyError()
+ {
+ this.stack = lines.join( '\n' );
+ }
+
+ var ctor = this.Sut( DummyError )
+ .createCtor( SubDummyError );
+
+ var result = new ctor().stack;
+
+ this.assertEqual(
+ lines.slice( framedata.strip ).join( '\n' ),
+ result
+ );
+ },
+
+
+ /**
+ * Certain browsers (like GNU IceCat) support `{line,column}Number` and
+ * `fileName`; if those are defined, we will propagate them.
+ *
+ * ...but there's a caveat: the values set on the supertype's error
+ * object aren't going to be correct, because the first frame is not our
+ * own. That means we have to do some string parsing on the second
+ * frame; this will only happen if stack stripping was successful, since
+ * we otherwise have no idea if the second frame is actually what we
+ * want.
+ *
+ * Even if we do happen to know the values, if the environment in which
+ * we are running does not normally provide those data, then neither
+ * will we (for strict consistency).
+ */
+ '@each(linecolf) Sets line, column, and filename data if available':
+ function( linecolf )
+ {
+ var expected = {
+ lineNumber: 5,
+ columnNumber: 3,
+ fileName: 'foofile.js',
+ }
+
+ var lines = [
+ "@__$$ector$$__ foo:1:1",
+ "@" + expected.fileName + ":" + expected.lineNumber
+ + ":" + expected.columnNumber,
+ "@second other frame:2:2"
+ ];
+
+ function LineColDummyError()
+ {
+ this.stack = lines.join( '\n' );
+
+ for ( var prop in linecolf )
+ {
+ if ( !linecolf[ prop ] ) continue;
+
+ // purposefully not an integer; should apply if the key
+ // exists at all
+ this[ prop ] = undefined;
+ }
+ }
+
+ var ctor = this.Sut( LineColDummyError )
+ .createCtor( LineColDummyError );
+
+ var errobj = new ctor();
+
+ for ( var prop in linecolf )
+ {
+ if ( linecolf[ prop ] )
+ {
+ this.assertEqual( expected[ prop ], errobj[ prop ] );
+ }
+ else
+ {
+ this.assertOk(
+ !Object.hasOwnProperty.call( errobj, prop )
+ );
+ }
+ }
+ },
+
+
+ /**
+ * This tests various situations with regards to the data available in
+ * stack traces; see `this.frames` for those cases.
+ */
+ '@each(frames) Recognizes line, column, and filename when available':
+ function( framedata )
+ {
+ function LineColDummyError()
+ {
+ this.stack = framedata.frames.join( '\n' );
+ this.lineNumber = undefined;
+ this.columnNumber = undefined;
+ this.fileName = undefined;
+ }
+
+ var ctor = this.Sut( LineColDummyError )
+ .createCtor( LineColDummyError );
+
+ var errobj = new ctor();
+
+ this.assertEqual( framedata.lineNumber, errobj.lineNumber );
+ this.assertEqual( framedata.columnNumber, errobj.columnNumber );
+ },
+
+
+ /**
+ * A predicate is provided to allow callers to determine if the given
+ * object is our base constructor or a subtype thereof.
+ */
+ 'Provides predicate to recognize base match': function()
+ {
+ var sut = this.Sut( DummyError );
+
+ this.assertOk( sut.isError( DummyError ) );
+ this.assertOk( !sut.isError( new DummyError() ) );
+
+ this.assertOk( sut.isError( SubDummyError ) );
+ this.assertOk( !sut.isError( new SubDummyError() ) );
+
+ this.assertOk( !sut.isError( function() {} ) );
+ },
+} );