"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; }; } };