"use strict";
/** @fileOverview Implementation of the *page* object-oriented parser generator system.
* @author Axel T. Schreiner <ats@cs.rit.edu>
* @version 1.5.1
*/
/** Object-oriented parser generator system supporting BNF and EBNF grammar
* representations and tokenizer, LL(1), and naive LR(1) parser generation.
* @namespace
*/
var page = {
/** Used for type-checking and other assertion checking.
* Template method, could be overwritten in any baseclass.
*
* @param {string} _marker to identify position across a re-throw.
* @param {boolean} [_condition] should be true.
*
* @throws {Error} if a condition is not present or not met.
*
* @see page.baseclass
*/
assert: function (_marker, _condition) {
if (!_condition) throw new Error('assert: ' + _marker);
},
/** Prefixes message with `error:` and delegates to {@link page.message}.
* Increments `this.errors` if it exists.
* Template method, could be overwritten in any baseclass.
*
* @param {string} arguments message strings, will be joined with blanks.
*
* @example
* aBNF.error('example', 'message')
*
* @see page.baseclass
*/
error: function () {
if (this && 'errors' in this) ++ this.errors;
page.message('error: ' + [].slice.call(arguments).join(' '));
},
/** Prints a message.
* Template method, could be overwritten in any baseclass.
*
* @param {string} arguments message strings, will be joined with blanks.
*
* @see page.baseclass
*/
message: function () {
page.assert('__WHERE__', arguments.length > 0);
print([].slice.call(arguments).join(' '));
},
/** Provides `assert`, `error`, `message`, and `subclass` methods and `className` property for a new class.
*
* @param {Function} _constructor new class' constructor function.
* @param {string} _className new class' name.
*
* @example
* page.baseclass(page.BNF, 'page.BNF');
*
* @see page.assert
* @see page.error
* @see page.message
* @see page.subclass
*/
baseclass: function (_constructor, _className) {
page.assert('__WHERE__', typeof _constructor == 'function');
page.assert('__WHERE__', typeof _className == 'string');
_constructor.prototype.assert = page.assert;
_constructor.prototype.error = page.error;
_constructor.prototype.message = page.message;
_constructor.prototype.subclass = page.subclass;
_constructor.prototype.className = _className;
},
/** Connects subclass to superclass.
* Arranges for inheritance, `className`, and `instanceof`.
* Template method, could be overwritten in any baseclass.
* Taken from [here](http://www.golimojo.com/etc/js-subclass.html).
*
* Note that there can't be a method similar to `super` in Java
* because `super` is implemented with a lexical search above the
* current class — it can't be applied to `this`.
*
* @param {Function} _constructor subclass' constructor function.
* @param {string} _className new subclass' name.
* @param {Function} _superclass superclass' constructor function.
*
* @example
* page.subclass(page.BNF.Lit, 'page.BNF.Lit', page.BNF.T);
*/
subclass: function (_constructor, _className, _superclass) {
page.assert('__WHERE__', typeof _constructor == 'function');
page.assert('__WHERE__', typeof _className == 'string');
function surrogateConstructor () { } // placeholder for _superclass
surrogateConstructor.prototype = _superclass.prototype; // inherit from _superclass
var prototypeObject = new surrogateConstructor(); // replacement for subclass' prototype
prototypeObject.constructor = _constructor; // attach subclass' constructor
prototypeObject.className = _className; // attach subclass' class name
_constructor.prototype = prototypeObject; // replace subclass' prototype
},
/** Escapes non-ASCII and invisible characters using backslash.
*
* @param {string} _s string to escape.
*
* @returns {string} single-quoted, escaped string.
*/
escape: function (_s) {
if (_s == null) return '';
page.assert('__WHERE__', typeof _s == 'string');
var result = '\'';
for (var i = 0; i < _s.length; ++ i) {
var c = _s.charAt(i);
var cc = '\b\f\n\r\t\v\\\''.indexOf(c);
if (cc >= 0)
result += '\\' + 'bfnrtv\\\''.charAt(cc);
else if (c >= ' ' && c <= '~')
result += c;
else if ((cc = _s.charCodeAt(i)) < 16)
result += '\\x0' + cc.toString(16);
else if (cc < 256)
result += '\\x' + cc.toString(16);
else if (cc < 16 * 256)
result += '\\u0' + cc.toString(16);
else
result += '\\u' + cc.toString(16);
}
return result + '\'';
},
/** Unescapes the result of {@link page.escape} and removes leading and trailing delimiter character.
*
* @param {string} _s string to unescape.
*
* @returns {string} unquoted, unescaped string.
*/
unescape: function (_s) {
page.assert('__WHERE__', typeof _s == 'string');
page.assert('__WHERE__', _s.length >= 2);
page.assert('__WHERE__', _s.charAt(0) == _s.charAt(_s.length-1));
var result = '';
var c;
for (var i = 1; i < _s.length-1; )
if ((c = _s.charAt(i++)) != '\\')
result += c;
else if (i >= _s.length-1)
result += '\\'; // trailing backslash in literal
else if ((c = 'bfnrtv\\\''.indexOf(_s.charAt(i++))) >= 0)
result += '\b\f\n\r\t\v\\\''.charAt(c);
else switch (c = _s.charAt(i-1)) {
case 'x':
if (i + 1 < _s.length-1 &&
'0123456789abcdef'.indexOf(_s.charAt(i)) >= 0 &&
'0123456789abcdef'.indexOf(_s.charAt(i+1)) >= 0) {
result += String.fromCharCode(parseInt(_s.substr(i, 2), 16));
i += 2;
} else
result += 'x'; // bad \x
break;
case 'u':
if (i + 3 < _s.length-1 &&
'0123456789abcdef'.indexOf(_s.charAt(i)) >= 0 &&
'0123456789abcdef'.indexOf(_s.charAt(i+1)) >= 0 &&
'0123456789abcdef'.indexOf(_s.charAt(i+2)) >= 0 &&
'0123456789abcdef'.indexOf(_s.charAt(i+3)) >= 0) {
result += String.fromCharCode(parseInt(_s.substr(i, 4), 16));
i += 4;
} else
result += 'u'; // bad \u
break;
default: // bad \
result += c;
}
return result;
},
/** Rescursively displays a recursive `Array` or `object`
* aggregate containing boolean, number, or string values as leaves.
* This is similar to `JSON.stringify` but perhaps prettier.
*
* @param {object} _o object to display.
* @param {string} [_indent] indentation prefix.
*
* @returns {string}
*
* @example
* // the operation can be reversed
* eval('obj = '+page.uneval(obj))
*/
uneval: function (_o, _indent) {
page.assert('__WHERE__', _o instanceof Array || typeof _o == 'object');
page.assert('__WHERE__', !_indent || typeof _indent == 'string');
if (!_indent) _indent = '';
var delim = ' ';
if (_o instanceof Array) {
var s = _indent + '[';
for (var i in _o)
switch (typeof _o[i]) {
case 'boolean':
case 'number':
s += delim + _o[i];
delim = ',\n ' + _indent;
continue;
case 'string':
s += delim + page.escape(_o[i]);
delim = ',\n ' + _indent;
continue;
case 'object':
if (delim.length > 1) s += ',';
s += '\n' + page.uneval(_o[i], _indent + ' ');
delim = ',\n ' + _indent;
}
return s + ' ]';
}
var s = _indent + '{';
for (var i in _o)
switch (typeof _o[i]) {
case 'boolean':
case 'number':
s += delim + i + ': ' + _o[i];
delim = ',\n ' + _indent;
continue;
case 'string':
s += delim + i + ': ' + page.escape(_o[i]);
delim = ',\n ' + _indent;
continue;
case 'object':
s += delim + i + ':\n' + page.uneval(_o[i], _indent + ' ');
delim = ',\n ' + _indent;
continue;
}
return s + ' }';
},
/** Hashtable with strings or numbers as keys.
*
* @typedef Map
* @type object
*
* @see page.overlap
*/
/** Compares two maps, i.e., checks if two objects contain properties with the same key.
*
* @param {Map} _a to check.
* @param {Map} _b to check.
*
* @returns {boolean} `true` if there are common property keys.
*/
overlap: function (_a, _b) {
for (var a in _a) if (a in _b) return true;
return false;
},
/** Adds one map to another.
*
* @param {Map} _a to add to.
* @param {Map} _b to add.
*
* @returns {boolean} `true` if one or more keys were added.
*/
merge: function (_a, _b) {
var result = false;
for (var b in _b)
if (b in _a) {} else {
result = true;
_a[b] = _b[b];
}
return result;
},
/** Creates a function which can observe any parsing function and
* display the messages after it passes them through to another
* observer, if any. It will return the value from the observer,
* the information for an `error` action, or `null`.
* The function is serially reusable; when
* first accessed it will print a line to label the output columns.
*
* @param {Observer} [_observe] next observer function to delegate to, if any.
* @param {RegExp} [_match] if specified, selects the actions to be printed.
*
* @returns {Observer} a function which can observe a parsing algorithm.
*
* @see page.LL1#parse
* @see page.LL1#parser
* @see page.SLR1#parse
*/
trace: function (_observe, _match) {
if (!_match && _observe instanceof RegExp) {
_match = _observe;
_observe = null;
} else {
!_observe || page.assert('__WHERE__', typeof _observe == 'function');
!_match || page.assert('__WHERE__', _match instanceof RegExp);
}
var first = true;
// closure
return function (_at, _tuple, _action, _info, _sender) {
page.assert('__WHERE__', typeof _action == 'string');
!_info || page.assert('__WHERE__', typeof _info == 'string' || typeof _info == 'number'
|| _info instanceof page.BNF.Rule || _info instanceof page.BNF.T);
if (first) {
print('\nAT TUPLE ACTION RETURN');
first = false;
}
var result = _observe ? _observe(_at, _tuple, _action, _info, _sender) : (_action == 'error' ? _info : null);
if (!_match || _match.test(_action)) {
var w = (_at + ' ') .substr(0, 4);
var t = (_tuple + ' ') .substr(0, 15);
var a = (_action + ' ') .substr(0, 6);
var i = (String(_info).replace(/\n.*/, '') + ' ').substr(0, 23);
var r = (result == null ? 'null' :
result instanceof Array ? '[ ] ' + result :
(typeof result).substr(0, 3) +' '+ result).replace(/\n.*/, '');
if (r.length > 23) r = r.substr(0, 23);
print(w + t +' '+ a +' '+ i +' '+ r);
}
return result;
};
}
};