UNPKG

jison-gho

Version:

A parser generator with a Bison/YACC-similar API (derived off zaach/jison repo)

1,124 lines (974 loc) 70.6 kB
function parse(input, parseParams) { var self = this; var stack = new Array(128); // token stack: stores token which leads to state at the same index (column storage) var sstack = new Array(128); // state stack: stores states (column storage) var tstack = []; // token stack (only used when `%options token_stack` support has been enabled) var vstack = new Array(128); // semantic value stack var lstack = new Array(128); // location stack var table = this.table; var sp = 0; // 'stack pointer': index into the stacks var yyloc; var yytext; var yylineno; var yyleng; var symbol = 0; var preErrorSymbol = 0; var lastEofErrorStateDepth = Infinity; var recoveringErrorInfo = null; var recovering = 0; // (only used when the grammar contains error recovery rules) var TERROR = this.TERROR; var EOF = this.EOF; var ERROR_RECOVERY_TOKEN_DISCARD_COUNT = (this.options.errorRecoveryTokenDiscardCount | 0) || 3; var NO_ACTION = [0, YY_ERROR_RECOVERY_COMBINE_ID /* === table.length :: ensures that anyone using this new state will fail dramatically! */]; var lexer; if (this.__lexer__) { lexer = this.__lexer__; } else { lexer = this.__lexer__ = Object.create(this.lexer); } var sharedState_yy = { parseError: undefined, quoteName: undefined, lexer: undefined, parser: undefined, pre_parse: undefined, post_parse: undefined, pre_lex: undefined, post_lex: undefined, parseParamsAsMembers: parseParamsAsMembers // WARNING: must be written this way for the code expanders to work correctly in both ES5 and ES6 modes! }; var ASSERT; if (typeof assert !== 'function') { ASSERT = function JisonAssert(cond, msg) { if (!cond) { throw new Error('assertion failed: ' + (msg || '***')); } }; } else { ASSERT = assert; } this.yyGetSharedState = function yyGetSharedState() { return sharedState_yy; }; //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules this.yyGetErrorInfoTrack = function yyGetErrorInfoTrack() { return recoveringErrorInfo; }; //_handle_error_no_recovery: // run this code when the grammar does not include any error recovery rules //_handle_error_end_of_section: // this concludes the error recovery / no error recovery code section choice above // shallow clone objects, straight copy of simple `src` values // e.g. `lexer.yytext` MAY be a complex value object, // rather than a simple string/value. function shallow_copy(src) { if (typeof src === 'object') { var dst = {}; for (var k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) { dst[k] = src[k]; } } return dst; } return src; } function shallow_copy_noclobber(dst, src) { for (var k in src) { if (typeof dst[k] === 'undefined' && Object.prototype.hasOwnProperty.call(src, k)) { dst[k] = src[k]; } } } function copy_yylloc(loc) { var rv = shallow_copy(loc); if (rv && rv.range) { rv.range = rv.range.slice(0); } return rv; } // copy state shallow_copy_noclobber(sharedState_yy, this.yy); sharedState_yy.lexer = lexer; sharedState_yy.parser = this; var yydebug = false; if (this.options.debug) { yydebug = function yydebug_impl(msg, obj) { var ref_list; var ref_names; function deepClone(from, sub) { if (sub == null) { ref_list = []; ref_names = []; sub = 'root'; } if (typeof from === 'function') return '[Function]'; if (from == null || typeof from !== 'object') return from; if (from.constructor !== Object && from.constructor !== Array) { return from; } for (var i = 0, len = ref_list.length; i < len; i++) { if (ref_list[i] === from) { return '[Circular/Xref:' + ref_names[i] + ']'; // circular or cross reference } } ref_list.push(from); ref_names.push(sub); var to = new from.constructor(); for (var name in from) { if (name === 'parser') continue; if (name === 'lexer') continue; to[name] = deepClone(from[name], name); } return to; } obj = obj || {}; if (obj.symbol) { obj.local_yytext = yytext; obj.lexer_yytext = lexer.yytext; obj.lexer_yylloc = lexer.yylloc; obj.lexer_yyllineno = lexer.yyllineno; } // warning: here we fetch from closure (stack et al) obj.symbol_stack = stack; obj.state_stack = sstack; obj.value_stack = vstack; obj.location_stack = lstack; obj.stack_pointer = sp; // ready the object for printing: obj = deepClone(obj); // wrap try/catch in a function to help the V8 JIT compiler... function yydebug_cvt(obj) { var js; try { var re1; if (typeof XRegExp === 'undefined') { re1 = / \"([a-z_][a-z_0-9. ]*)\": /ig; } else { re1 = new XRegExp(' \"([\\p{Alphabetic}_][\\p{Alphabetic}\\p{Number}_. ]*)\": ', 'g'); } js = JSON.stringify(obj, null, 2) .replace(re1, ' $1: ') .replace(/[\n\s]+/g, ' ') // shorten yylloc object dumps too: .replace(/\{ first_line: (\d+), first_column: (\d+), last_line: (\d+), last_column: (\d+)/g, '{L/C: ($1,$2)..($3,$4)'); } catch (ex) { js = String(obj); } return js; } self.trace(msg, yydebug_cvt(obj), '\n'); }; } // disable debugging at run-time ANYWAY when you've *explicitly* set "yy.yydebug = false": if (sharedState_yy.yydebug === false) { yydebug = undefined; } // *Always* setup `yyError`, `YYRECOVERING`, `yyErrOk` and `yyClearIn` functions as it is paramount // to have *their* closure match ours -- if we only set them up once, // any subsequent `parse()` runs will fail in very obscure ways when // these functions are invoked in the user action code block(s) as // their closure will still refer to the `parse()` instance which set // them up. Hence we MUST set them up at the start of every `parse()` run! if (this.yyError) { this.yyError = function yyError(str /*, ...args */) { if (yydebug) yydebug('yyerror: ', { message: str, args: arguments, symbol: symbol, state: state, newState: newState, recovering: recovering, action: action }); //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules var error_rule_depth = (this.options.parserErrorsAreRecoverable ? locateNearestErrorRecoveryRule(state) : -1); var expected = this.collect_expected_token_set(state); var hash = this.constructParseErrorInfo(str, null, expected, (error_rule_depth >= 0)); // append to the old one? if (recoveringErrorInfo) { var esp = recoveringErrorInfo.info_stack_pointer; recoveringErrorInfo.symbol_stack[esp] = symbol; var v = this.shallowCopyErrorInfo(hash); v.yyError = true; v.errorRuleDepth = error_rule_depth; v.recovering = recovering; // v.stackSampleLength = error_rule_depth + EXTRA_STACK_SAMPLE_DEPTH; recoveringErrorInfo.value_stack[esp] = v; recoveringErrorInfo.location_stack[esp] = copy_yylloc(lexer.yylloc); recoveringErrorInfo.state_stack[esp] = newState || NO_ACTION[1]; ++esp; recoveringErrorInfo.info_stack_pointer = esp; } else { recoveringErrorInfo = this.shallowCopyErrorInfo(hash); recoveringErrorInfo.yyError = true; recoveringErrorInfo.errorRuleDepth = error_rule_depth; recoveringErrorInfo.recovering = recovering; } //_handle_error_no_recovery: // run this code when the grammar does not include any error recovery rules var expected = this.collect_expected_token_set(state); var hash = this.constructParseErrorInfo(str, null, expected, false); //_handle_error_end_of_section: // this concludes the error recovery / no error recovery code section choice above // Add any extra args to the hash under the name `extra_error_attributes`: var args = Array.prototype.slice.call(arguments, 1); if (args.length) { hash.extra_error_attributes = args; } return this.parseError(str, hash, this.JisonParserError); }; } //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules if (this.yyRecovering) { this.yyRecovering = function yyRecovering() { if (yydebug) yydebug('yyrecovering: ', { symbol: symbol, state: state, newState: newState, recovering: recovering, action: action }); return recovering; }; } if (this.yyErrOk) { this.yyErrOk = function yyErrOk() { if (yydebug) yydebug('yyerrok: ', { symbol: symbol, state: state, newState: newState, recovering: recovering, action: action }); recovering = 0; // DO NOT reset/cleanup `recoveringErrorInfo` yet: userland code // MAY invoke this API before the error is actually fully // recovered, in which case the parser recovery code won't be able // to append the skipped tokens to this info object. // // The rest of the kernel code is safe enough that it won't inadvertedly // re-use an old `recoveringErrorInfo` chunk so we'ld better wait // with destruction/cleanup until the end of the parse or until another // fresh parse error rears its ugly head... // // if (recoveringErrorInfo && typeof recoveringErrorInfo.destroy === 'function') { // recoveringErrorInfo.destroy(); // recoveringErrorInfo = undefined; // } }; } if (this.yyClearIn) { this.yyClearIn = function yyClearIn() { if (yydebug) yydebug('yyclearin: ', { symbol: symbol, newState: newState, recovering: recovering, action: action, preErrorSymbol: preErrorSymbol }); if (symbol === TERROR) { symbol = 0; yytext = null; yyleng = 0; yyloc = undefined; } preErrorSymbol = 0; }; } //_handle_error_no_recovery: // run this code when the grammar does not include any error recovery rules //_handle_error_end_of_section: // this concludes the error recovery / no error recovery code section choice above // Does the shared state override the default `parseError` that already comes with this instance? if (typeof sharedState_yy.parseError === 'function') { this.parseError = function parseErrorAlt(str, hash, ExceptionClass) { if (!ExceptionClass) { ExceptionClass = this.JisonParserError; } return sharedState_yy.parseError.call(this, str, hash, ExceptionClass); }; } else { this.parseError = this.originalParseError; } // Does the shared state override the default `quoteName` that already comes with this instance? if (typeof sharedState_yy.quoteName === 'function') { this.quoteName = function quoteNameAlt(id_str) { return sharedState_yy.quoteName.call(this, id_str); }; } else { this.quoteName = this.originalQuoteName; } // set up the cleanup function; make it an API so that external code can re-use this one in case of // calamities or when the `%options no-try-catch` option has been specified for the grammar, in which // case this parse() API method doesn't come with a `finally { ... }` block any more! // // NOTE: as this API uses parse() as a closure, it MUST be set again on every parse() invocation, // or else your `sharedState`, etc. references will be *wrong*! this.cleanupAfterParse = function parser_cleanupAfterParse(resultValue, invoke_post_methods, do_not_nuke_errorinfos) { var rv; if (invoke_post_methods) { var hash; if (sharedState_yy.post_parse || this.post_parse) { // create an error hash info instance: we re-use this API in a **non-error situation** // as this one delivers all parser internals ready for access by userland code. hash = this.constructParseErrorInfo(null /* no error! */, null /* no exception! */, null, false); } if (sharedState_yy.post_parse) { rv = sharedState_yy.post_parse.call(this, sharedState_yy, resultValue, hash); if (typeof rv !== 'undefined') resultValue = rv; } if (this.post_parse) { rv = this.post_parse.call(this, sharedState_yy, resultValue, hash); if (typeof rv !== 'undefined') resultValue = rv; } // cleanup: if (hash && hash.destroy) { hash.destroy(); } } if (this.__reentrant_call_depth > 1) return resultValue; // do not (yet) kill the sharedState when this is a reentrant run. // clean up the lingering lexer structures as well: if (lexer.cleanupAfterLex) { lexer.cleanupAfterLex(do_not_nuke_errorinfos); } // prevent lingering circular references from causing memory leaks: if (sharedState_yy) { sharedState_yy.lexer = undefined; sharedState_yy.parser = undefined; if (lexer.yy === sharedState_yy) { lexer.yy = undefined; } } sharedState_yy = undefined; this.parseError = this.originalParseError; this.quoteName = this.originalQuoteName; // nuke the vstack[] array at least as that one will still reference obsoleted user values. // To be safe, we nuke the other internal stack columns as well... stack.length = 0; // fastest way to nuke an array without overly bothering the GC sstack.length = 0; lstack.length = 0; vstack.length = 0; sp = 0; // nuke the error hash info instances created during this run. // Userland code must COPY any data/references // in the error hash instance(s) it is more permanently interested in. if (!do_not_nuke_errorinfos) { for (var i = this.__error_infos.length - 1; i >= 0; i--) { var el = this.__error_infos[i]; if (el && typeof el.destroy === 'function') { el.destroy(); } } this.__error_infos.length = 0; //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules for (var i = this.__error_recovery_infos.length - 1; i >= 0; i--) { var el = this.__error_recovery_infos[i]; if (el && typeof el.destroy === 'function') { el.destroy(); } } this.__error_recovery_infos.length = 0; // `recoveringErrorInfo` is also part of the `__error_recovery_infos` array, // hence has been destroyed already: no need to do that *twice*. if (recoveringErrorInfo) { recoveringErrorInfo = undefined; } //_handle_error_no_recovery: // run this code when the grammar does not include any error recovery rules //_handle_error_end_of_section: // this concludes the error recovery / no error recovery code section choice above } return resultValue; }; // merge yylloc info into a new yylloc instance. // // `first_index` and `last_index` MAY be UNDEFINED/NULL or these are indexes into the `lstack[]` location stack array. // // `first_yylloc` and `last_yylloc` MAY be UNDEFINED/NULL or explicit (custom or regular) `yylloc` instances, in which // case these override the corresponding first/last indexes. // // `dont_look_back` is an optional flag (default: FALSE), which instructs this merge operation NOT to search // through the parse location stack for a location, which would otherwise be used to construct the new (epsilon!) // yylloc info. // // Note: epsilon rule's yylloc situation is detected by passing both `first_index` and `first_yylloc` as UNDEFINED/NULL. this.yyMergeLocationInfo = function parser_yyMergeLocationInfo(first_index, last_index, first_yylloc, last_yylloc, dont_look_back) { var i1 = first_index | 0, i2 = last_index | 0; var l1 = first_yylloc, l2 = last_yylloc; var rv; // rules: // - first/last yylloc entries override first/last indexes if (!l1) { if (first_index != null) { for (var i = i1; i <= i2; i++) { l1 = lstack[i]; if (l1) { break; } } } } if (!l2) { if (last_index != null) { for (var i = i2; i >= i1; i--) { l2 = lstack[i]; if (l2) { break; } } } } // - detect if an epsilon rule is being processed and act accordingly: if (!l1 && first_index == null) { // epsilon rule span merger. With optional look-ahead in l2. if (!dont_look_back) { for (var i = (i1 || sp) - 1; i >= 0; i--) { l1 = lstack[i]; if (l1) { break; } } } if (!l1) { if (!l2) { // when we still don't have any valid yylloc info, we're looking at an epsilon rule // without look-ahead and no preceding terms and/or `dont_look_back` set: // in that case we ca do nothing but return NULL/UNDEFINED: return undefined; } else { // shallow-copy L2: after all, we MAY be looking // at unconventional yylloc info objects... rv = shallow_copy(l2); if (rv.range) { // shallow copy the yylloc ranges info to prevent us from modifying the original arguments' entries: rv.range = rv.range.slice(0); } return rv; } } else { // shallow-copy L1, then adjust first col/row 1 column past the end. rv = shallow_copy(l1); rv.first_line = rv.last_line; rv.first_column = rv.last_column; if (rv.range) { // shallow copy the yylloc ranges info to prevent us from modifying the original arguments' entries: rv.range = rv.range.slice(0); rv.range[0] = rv.range[1]; } if (l2) { // shallow-mixin L2, then adjust last col/row accordingly. shallow_copy_noclobber(rv, l2); rv.last_line = l2.last_line; rv.last_column = l2.last_column; if (rv.range && l2.range) { rv.range[1] = l2.range[1]; } } return rv; } } if (!l1) { l1 = l2; l2 = null; } if (!l1) { return undefined; } // shallow-copy L1|L2, before we try to adjust the yylloc values: after all, we MAY be looking // at unconventional yylloc info objects... rv = shallow_copy(l1); // first_line: ..., // first_column: ..., // last_line: ..., // last_column: ..., if (rv.range) { // shallow copy the yylloc ranges info to prevent us from modifying the original arguments' entries: rv.range = rv.range.slice(0); } if (l2) { shallow_copy_noclobber(rv, l2); rv.last_line = l2.last_line; rv.last_column = l2.last_column; if (rv.range && l2.range) { rv.range[1] = l2.range[1]; } } return rv; }; // NOTE: as this API uses parse() as a closure, it MUST be set again on every parse() invocation, // or else your `lexer`, `sharedState`, etc. references will be *wrong*! this.constructParseErrorInfo = function parser_constructParseErrorInfo(msg, ex, expected, recoverable) { var pei = { errStr: msg, exception: ex, text: lexer.match, value: lexer.yytext, token: this.describeSymbol(symbol) || symbol, token_id: symbol, line: lexer.yylineno, loc: copy_yylloc(lexer.yylloc), expected: expected, recoverable: recoverable, state: state, action: action, new_state: newState, symbol_stack: stack, state_stack: sstack, value_stack: vstack, location_stack: lstack, stack_pointer: sp, yy: sharedState_yy, lexer: lexer, parser: this, // and make sure the error info doesn't stay due to potential // ref cycle via userland code manipulations. // These would otherwise all be memory leak opportunities! // // Note that only array and object references are nuked as those // constitute the set of elements which can produce a cyclic ref. // The rest of the members is kept intact as they are harmless. destroy: function destructParseErrorInfo() { // remove cyclic references added to error info: // info.yy = null; // info.lexer = null; // info.value = null; // info.value_stack = null; // ... var rec = !!this.recoverable; for (var key in this) { if (this.hasOwnProperty(key) && typeof key === 'object') { this[key] = undefined; } } this.recoverable = rec; } }; // track this instance so we can `destroy()` it once we deem it superfluous and ready for garbage collection! this.__error_infos.push(pei); return pei; }; // clone some parts of the (possibly enhanced!) errorInfo object // to give them some persistence. this.shallowCopyErrorInfo = function parser_shallowCopyErrorInfo(p) { var rv = shallow_copy(p); // remove the large parts which can only cause cyclic references // and are otherwise available from the parser kernel anyway. delete rv.sharedState_yy; delete rv.parser; delete rv.lexer; // lexer.yytext MAY be a complex value object, rather than a simple string/value: rv.value = shallow_copy(rv.value); // yylloc info: rv.loc = copy_yylloc(rv.loc); // the 'expected' set won't be modified, so no need to clone it: //rv.expected = rv.expected.slice(0); //symbol stack is a simple array: rv.symbol_stack = rv.symbol_stack.slice(0); // ditto for state stack: rv.state_stack = rv.state_stack.slice(0); // clone the yylloc's in the location stack?: rv.location_stack = rv.location_stack.map(copy_yylloc); // and the value stack may carry both simple and complex values: // shallow-copy the latter. rv.value_stack = rv.value_stack.map(shallow_copy); // and we don't bother with the sharedState_yy reference: //delete rv.yy; // now we prepare for tracking the COMBINE actions // in the error recovery code path: // // as we want to keep the maximum error info context, we // *scan* the state stack to find the first *empty* slot. // This position will surely be AT OR ABOVE the current // stack pointer, but we want to keep the 'used but discarded' // part of the parse stacks *intact* as those slots carry // error context that may be useful when you want to produce // very detailed error diagnostic reports. // // ### Purpose of each stack pointer: // // - stack_pointer: points at the top of the parse stack // **as it existed at the time of the error // occurrence, i.e. at the time the stack // snapshot was taken and copied into the // errorInfo object.** // - base_pointer: the bottom of the **empty part** of the // stack, i.e. **the start of the rest of // the stack space /above/ the existing // parse stack. This section will be filled // by the error recovery process as it // travels the parse state machine to // arrive at the resolving error recovery rule.** // - info_stack_pointer: // this stack pointer points to the **top of // the error ecovery tracking stack space**, i.e. // this stack pointer takes up the role of // the `stack_pointer` for the error recovery // process. Any mutations in the **parse stack** // are **copy-appended** to this part of the // stack space, keeping the bottom part of the // stack (the 'snapshot' part where the parse // state at the time of error occurrence was kept) // intact. // - root_failure_pointer: // copy of the `stack_pointer`... // for (var i = rv.stack_pointer; typeof rv.state_stack[i] !== 'undefined'; i++) { // empty } rv.base_pointer = i; rv.info_stack_pointer = i; rv.root_failure_pointer = rv.stack_pointer; // track this instance so we can `destroy()` it once we deem it superfluous and ready for garbage collection! this.__error_recovery_infos.push(rv); return rv; }; function getNonTerminalFromCode(symbol) { var tokenName = self.getSymbolName(symbol); if (!tokenName) { tokenName = symbol; } return tokenName; } //_lexer_without_token_stack: function stdLex() { var token = lexer.lex(); // if token isn't its numeric value, convert if (typeof token !== 'number') { token = self.symbols_[token] || token; } return token || EOF; } function fastLex() { var token = lexer.fastLex(); // if token isn't its numeric value, convert if (typeof token !== 'number') { token = self.symbols_[token] || token; } return token || EOF; } var lex = stdLex; //_lexer_with_token_stack: // lex function that supports token stacks function tokenStackLex() { var token; token = tstack.pop() || lexer.lex() || EOF; // if token isn't its numeric value, convert if (typeof token !== 'number') { if (token instanceof Array) { tstack = token; token = tstack.pop(); } // if token isn't its numeric value, convert if (typeof token !== 'number') { token = self.symbols_[token] || token; } } return token || EOF; } //_lexer_with_token_stack_end: var state, action, r, t; var yyval = { $: true, _$: undefined, yy: sharedState_yy }; var p; var yyrulelen; var this_production; var newState; var retval = false; //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules // Return the rule stack depth where the nearest error rule can be found. // Return -1 when no error recovery rule was found. function locateNearestErrorRecoveryRule(state) { var stack_probe = sp - 1; var depth = 0; // try to recover from error while (stack_probe >= 0) { // check for error recovery rule in this state if (yydebug) yydebug('locateNearestErrorRecoveryRule #test#: ', { symbol: symbol, state: state, depth: depth, stackidx: sp - 1 - depth, lastidx: lastEofErrorStateDepth }); var t = table[state][TERROR] || NO_ACTION; if (t[0]) { // We need to make sure we're not cycling forever: // once we hit EOF, even when we `yyerrok()` an error, we must // prevent the core from running forever, // e.g. when parent rules are still expecting certain input to // follow after this, for example when you handle an error inside a set // of braces which are matched by a parent rule in your grammar. // // Hence we require that every error handling/recovery attempt // *after we've hit EOF* has a diminishing state stack: this means // we will ultimately have unwound the state stack entirely and thus // terminate the parse in a controlled fashion even when we have // very complex error/recovery code interplay in the core + user // action code blocks: if (yydebug) yydebug('locateNearestErrorRecoveryRule #found#: ', { symbol: symbol, state: state, depth: depth, stackidx: sp - 1 - depth, lastidx: lastEofErrorStateDepth }); if (symbol === EOF) { if (lastEofErrorStateDepth > sp - 1 - depth) { lastEofErrorStateDepth = sp - 1 - depth; } else { if (yydebug) yydebug('locateNearestErrorRecoveryRule #skip#: ', { symbol: symbol, state: state, depth: depth, stackidx: sp - 1 - depth, lastidx: lastEofErrorStateDepth }); --stack_probe; // popStack(1): [symbol, action] state = sstack[stack_probe]; ++depth; continue; } } return depth; } if (state === 0 /* $accept rule */ || stack_probe < 1) { if (yydebug) yydebug('locateNearestErrorRecoveryRule #end=NIL#: ', { symbol: symbol, state: state, depth: depth, stackidx: sp - 1 - depth, lastidx: lastEofErrorStateDepth }); return -1; // No suitable error recovery rule available. } --stack_probe; // popStack(1): [symbol, action] state = sstack[stack_probe]; ++depth; } if (yydebug) yydebug('locateNearestErrorRecoveryRule #EMPTY#: ', { symbol: symbol, state: state, depth: depth, stackidx: sp - 1 - depth, lastidx: lastEofErrorStateDepth }); return -1; // No suitable error recovery rule available. } //_handle_error_no_recovery: // run this code when the grammar does not include any error recovery rules //_handle_error_end_of_section: // this concludes the error recovery / no error recovery code section choice above try { this.__reentrant_call_depth++; lexer.setInput(input, sharedState_yy); // NOTE: we *assume* no lexer pre/post handlers are set up *after* // this initial `setInput()` call: hence we can now check and decide // whether we'll go with the standard, slower, lex() API or the // `fast_lex()` one: if (typeof lexer.canIUse === 'function') { var lexerInfo = lexer.canIUse(); if (lexerInfo.fastLex && typeof fastLex === 'function') { lex = fastLex; } } yyloc = lexer.yylloc; lstack[sp] = yyloc; vstack[sp] = null; sstack[sp] = 0; stack[sp] = 0; ++sp; yytext = lexer.yytext; yylineno = lexer.yylineno; yyleng = lexer.yyleng; if (this.pre_parse) { this.pre_parse.call(this, sharedState_yy); } if (sharedState_yy.pre_parse) { sharedState_yy.pre_parse.call(this, sharedState_yy); } newState = sstack[sp - 1]; for (;;) { // retrieve state number from top of stack state = newState; // sstack[sp - 1]; // use default actions if available if (this.defaultActions[state]) { action = 2; newState = this.defaultActions[state]; } else { // The single `==` condition below covers both these `===` comparisons in a single // operation: // // if (symbol === null || typeof symbol === 'undefined') ... if (!symbol) { symbol = lex(); } // read action for current state and first input t = (table[state] && table[state][symbol]) || NO_ACTION; newState = t[1]; action = t[0]; if (yydebug) yydebug('after FETCH/LEX: ', { symbol: symbol, symbolID: this.terminals_ && this.terminals_[symbol], state: state, newState: newState, recovering: recovering, action: action }); //_handle_error_with_recovery: // run this code when the grammar includes error recovery rules // handle parse error if (!action) { // first see if there's any chance at hitting an error recovery rule: var error_rule_depth = locateNearestErrorRecoveryRule(state); var errStr = null; var errSymbolDescr = (this.describeSymbol(symbol) || symbol); var expected = this.collect_expected_token_set(state); if (!recovering) { // Report error if (typeof lexer.yylineno === 'number') { errStr = 'Parse error on line ' + (lexer.yylineno + 1) + ': '; } else { errStr = 'Parse error: '; } if (typeof lexer.showPosition === 'function') { errStr += '\n' + lexer.showPosition(79 - 10, 10) + '\n'; } if (expected.length) { errStr += 'Expecting ' + expected.join(', ') + ', got unexpected ' + errSymbolDescr; } else { errStr += 'Unexpected ' + errSymbolDescr; } p = this.constructParseErrorInfo(errStr, null, expected, (error_rule_depth >= 0)); // DO NOT cleanup the old one before we start the new error info track: // the old one will *linger* on the error stack and stay alive until we // invoke the parser's cleanup API! recoveringErrorInfo = this.shallowCopyErrorInfo(p); if (yydebug) yydebug('error recovery rule detected: ', { error_rule_depth: error_rule_depth, error: p.errStr, error_hash: p }); r = this.parseError(p.errStr, p, this.JisonParserError); if (typeof r !== 'undefined') { retval = r; break; } // Protect against overly blunt userland `parseError` code which *sets* // the `recoverable` flag without properly checking first: // we always terminate the parse when there's no recovery rule available anyhow! if (!p.recoverable || error_rule_depth < 0) { break; } else { // TODO: allow parseError callback to edit symbol and or state at the start of the error recovery process... } } if (yydebug) yydebug('after ERROR DETECT: ', { error_rule_depth: error_rule_depth, error: p.errStr, error_hash: p }); var esp = recoveringErrorInfo.info_stack_pointer; // just recovered from another error if (recovering === ERROR_RECOVERY_TOKEN_DISCARD_COUNT && error_rule_depth >= 0) { // SHIFT current lookahead and grab another recoveringErrorInfo.symbol_stack[esp] = symbol; recoveringErrorInfo.value_stack[esp] = shallow_copy(lexer.yytext); recoveringErrorInfo.location_stack[esp] = copy_yylloc(lexer.yylloc); recoveringErrorInfo.state_stack[esp] = newState; // push state ++esp; // Pick up the lexer details for the current symbol as that one is not 'look-ahead' any more: yyleng = lexer.yyleng; yytext = lexer.yytext; yylineno = lexer.yylineno; yyloc = lexer.yylloc; preErrorSymbol = 0; symbol = lex(); if (yydebug) yydebug('after ERROR RECOVERY-3: ', { symbol: symbol, symbolID: this.terminals_ && this.terminals_[symbol] }); } // try to recover from error if (error_rule_depth < 0) { ASSERT(recovering > 0, "line 897"); recoveringErrorInfo.info_stack_pointer = esp; // barf a fatal hairball when we're out of look-ahead symbols and none hit a match // while we are still busy recovering from another error: var po = this.__error_infos[this.__error_infos.length - 1]; // Report error if (typeof lexer.yylineno === 'number') { errStr = 'Parsing halted on line ' + (lexer.yylineno + 1) + ' while starting to recover from another error'; } else { errStr = 'Parsing halted while starting to recover from another error'; } if (po) { errStr += ' -- previous error which resulted in this fatal result: ' + po.errStr; } else { errStr += ': '; } if (typeof lexer.showPosition === 'function') { errStr += '\n' + lexer.showPosition(79 - 10, 10) + '\n'; } if (expected.length) { errStr += 'Expecting ' + expected.join(', ') + ', got unexpected ' + errSymbolDescr; } else { errStr += 'Unexpected ' + errSymbolDescr; } p = this.constructParseErrorInfo(errStr, null, expected, false); if (po) { p.extra_error_attributes = po; } r = this.parseError(p.errStr, p, this.JisonParserError); if (typeof r !== 'undefined') { retval = r; } break; } preErrorSymbol = (symbol === TERROR ? 0 : symbol); // save the lookahead token symbol = TERROR; // insert generic error symbol as new lookahead const EXTRA_STACK_SAMPLE_DEPTH = 3; // REDUCE/COMBINE the pushed terms/tokens to a new ERROR token: recoveringErrorInfo.symbol_stack[esp] = preErrorSymbol; if (errStr) { recoveringErrorInfo.value_stack[esp] = { yytext: shallow_copy(lexer.yytext), errorRuleDepth: error_rule_depth, errStr: errStr, errorSymbolDescr: errSymbolDescr, expectedStr: expected, stackSampleLength: error_rule_depth + EXTRA_STACK_SAMPLE_DEPTH }; if (yydebug) yydebug('Error recovery process: pushed error info item on the info stack: ', { item: vstack[sp], sp, esp, vstack, stack, sstack, combineState: NO_ACTION[1] }); } else { recoveringErrorInfo.value_stack[esp] = { yytext: shallow_copy(lexer.yytext), errorRuleDepth: error_rule_depth, stackSampleLength: error_rule_depth + EXTRA_STACK_SAMPLE_DEPTH }; } recoveringErrorInfo.location_stack[esp] = copy_yylloc(lexer.yylloc); recoveringErrorInfo.state_stack[esp] = newState || NO_ACTION[1]; ++esp; recoveringErrorInfo.info_stack_pointer = esp; yyval.$ = recoveringErrorInfo; yyval._$ = undefined; yyrulelen = error_rule_depth; if (yydebug) yydebug('Error recovery process: performAction: COMBINE: ', { yyval, yytext, sp, pop_size: yyrulelen, vstack, stack, sstack, combineState: NO_ACTION[1] }); r = this.performAction.call(yyval, yytext, yyleng, yylineno, yyloc, NO_ACTION[1], sp - 1, yyrulelen, vstack, lstack, stack, sstack); if (typeof r !== 'undefined') { retval = r; break; } // pop off stack sp -= yyrulelen; // and move the top entries + discarded part of the parse stacks onto the error info stack: for (var idx = sp - EXTRA_STACK_SAMPLE_DEPTH, top = idx + yyrulelen; idx < top; idx++, esp++) { recoveringErrorInfo.symbol_stack[esp] = stack[idx]; recoveringErrorInfo.value_stack[esp] = shallow_copy(vstack[idx]); recoveringErrorInfo.location_stack[esp] = copy_yylloc(lstack[idx]); recoveringErrorInfo.state_stack[esp] = sstack[idx]; } recoveringErrorInfo.symbol_stack[esp] = TERROR; recoveringErrorInfo.value_stack[esp] = shallow_copy(yyval.$); recoveringErrorInfo.location_stack[esp] = copy_yylloc(yyval._$); // goto new state = table[STATE][NONTERMINAL] newState = sstack[sp - 1]; if (this.defaultActions[newState]) { recoveringErrorInfo.state_stack[esp] = this.defaultActions[newState]; } else { t = (table[newState] && table[newState][symbol]) || NO_ACTION; recoveringErrorInfo.state_stack[esp] = t[1]; } ++esp; recoveringErrorInfo.info_stack_pointer = esp; // allow N (default: 3) real symbols to be shifted before reporting a new error recovering = ERROR_RECOVERY_TOKEN_DISCARD_COUNT; if (yydebug) yydebug('after ERROR POP: ', { error_rule_depth: error_rule_depth, symbol: symbol, preErrorSymbol: preErrorSymbol }); // Now duplicate the standard parse machine here, at least its initial // couple of rounds until the TERROR symbol is **pushed onto the parse stack**, // as we wish to push something special then! // // Run the state machine in this copy of the parser state machine // until we *either* consume the error symbol (and its related information) // *or* we run into another error while recovering from this one // *or* we execute a `reduce` action which outputs a final parse // result (yes, that MAY happen!). // // We stay in this secondary parse loop until we have completed // the *error recovery phase* as the main parse loop (further below) // is optimized for regular parse operation and DOES NOT cope with // error recovery *at all*. // // We call the secondary parse loop just below the "slow parse loop", // while the main parse loop, which is an almost-duplicate of this one, // yet optimized for regular parse operation, is called the "fast // parse loop". // // Compare this to `bison` & (vanilla) `jison`, both of which have // only a single parse loop, which handles everything. Our goal is // to eke out every drop of performance in the main parse loop... ASSERT(recoveringErrorInfo, "line 1049"); ASSERT(symbol === TERROR, "line 1050"); ASSERT(!action, "line 1051"); var errorSymbolFromParser = true; for (;;) { // retrieve state number from top of stack state = newState; // sstack[sp - 1]; // use default actions if available if (this.defaultActions[state]) { action = 2; newState = this.defaultActions[state]; } else { // The single `==` condition below covers both these `===` comparisons in a single // operation: // // if (symbol === null || typeof symbol === 'undefined') ... if (!symbol) { symbol = lex(); // **Warning: Edge Case**: the *lexer* may produce // TERROR tokens of its own volition: *those* TERROR // tokens should be treated like *regular tokens* // i.e. tokens which have a lexer-provided `yyvalue` // and `yylloc`: errorSymbolFromParser = false; } // read action for current state and first input t = (table[state] && table[state][symbol]) || NO_ACTION; newState = t[1]; action = t[0]; if (yydebug) yydebug('after FETCH/LEX: ', { symbol: symbol, symbolID: this.terminals_ && this.terminals_[symbol], state: state, newState: newState, recovering: recovering, action: action }); // encountered another parse error? If so, break out to main loop // and take it from there! if (!action) {