UNPKG

@glimmer/syntax

Version:
1,899 lines (1,868 loc) 353 kB
'use strict'; const Char = { NBSP: 0xa0, QUOT: 0x22, LT: 0x3c, GT: 0x3e, AMP: 0x26 }; // \x26 is ampersand, \xa0 is non-breaking space const ATTR_VALUE_REGEX_TEST = /["\x26\xa0]/u; const ATTR_VALUE_REGEX_REPLACE = new RegExp(ATTR_VALUE_REGEX_TEST.source, 'gu'); const TEXT_REGEX_TEST = /[&<>\xa0]/u; const TEXT_REGEX_REPLACE = new RegExp(TEXT_REGEX_TEST.source, 'gu'); function attrValueReplacer(char) { switch(char.charCodeAt(0)){ case Char.NBSP: return '&nbsp;'; case Char.QUOT: return '&quot;'; case Char.AMP: return '&amp;'; default: return char; } } function textReplacer(char) { switch(char.charCodeAt(0)){ case Char.NBSP: return '&nbsp;'; case Char.AMP: return '&amp;'; case Char.LT: return '&lt;'; case Char.GT: return '&gt;'; default: return char; } } function escapeAttrValue(attrValue) { if (ATTR_VALUE_REGEX_TEST.test(attrValue)) { return attrValue.replace(ATTR_VALUE_REGEX_REPLACE, attrValueReplacer); } return attrValue; } function escapeText(text) { if (TEXT_REGEX_TEST.test(text)) { return text.replace(TEXT_REGEX_REPLACE, textReplacer); } return text; } function sortByLoc(a, b) { // If either is invisible, don't try to order them if (a.loc.isInvisible || b.loc.isInvisible) { return 0; } if (a.loc.startPosition.line < b.loc.startPosition.line) { return -1; } if (a.loc.startPosition.line === b.loc.startPosition.line && a.loc.startPosition.column < b.loc.startPosition.column) { return -1; } if (a.loc.startPosition.line === b.loc.startPosition.line && a.loc.startPosition.column === b.loc.startPosition.column) { return 0; } return 1; } const voidMap = new Set([ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]); function getVoidTags() { return [ ...voidMap ]; } const NON_WHITESPACE = /^\S/u; /** * Examples when true: * - link * - liNK * * Examples when false: * - Link (component) */ function isVoidTag(tag) { return voidMap.has(tag.toLowerCase()) && tag[0]?.toLowerCase() === tag[0]; } class Printer { constructor(options){ this.buffer = ''; this.options = options; } /* This is used by _all_ methods on this Printer class that add to `this.buffer`, it allows consumers of the printer to use alternate string representations for a given node. The primary use case for this are things like source -> source codemod utilities. For example, ember-template-recast attempts to always preserve the original string formatting in each AST node if no modifications are made to it. */ handledByOverride(node, ensureLeadingWhitespace = false) { if (this.options.override !== undefined) { let result = this.options.override(node, this.options); if (typeof result === 'string') { if (ensureLeadingWhitespace && NON_WHITESPACE.test(result)) { result = ` ${result}`; } this.buffer += result; return true; } } return false; } Node(node) { switch(node.type){ case 'MustacheStatement': case 'BlockStatement': case 'MustacheCommentStatement': case 'CommentStatement': case 'TextNode': case 'ElementNode': case 'AttrNode': case 'Block': case 'Template': return this.TopLevelStatement(node); case 'StringLiteral': case 'BooleanLiteral': case 'NumberLiteral': case 'UndefinedLiteral': case 'NullLiteral': case 'PathExpression': case 'SubExpression': return this.Expression(node); case 'ConcatStatement': // should have an AttrNode parent return this.ConcatStatement(node); case 'Hash': return this.Hash(node); case 'HashPair': return this.HashPair(node); case 'ElementModifierStatement': return this.ElementModifierStatement(node); } } Expression(expression) { switch(expression.type){ case 'StringLiteral': case 'BooleanLiteral': case 'NumberLiteral': case 'UndefinedLiteral': case 'NullLiteral': return this.Literal(expression); case 'PathExpression': return this.PathExpression(expression); case 'SubExpression': return this.SubExpression(expression); } } Literal(literal) { switch(literal.type){ case 'StringLiteral': return this.StringLiteral(literal); case 'BooleanLiteral': return this.BooleanLiteral(literal); case 'NumberLiteral': return this.NumberLiteral(literal); case 'UndefinedLiteral': return this.UndefinedLiteral(literal); case 'NullLiteral': return this.NullLiteral(literal); } } TopLevelStatement(statement) { switch(statement.type){ case 'MustacheStatement': return this.MustacheStatement(statement); case 'BlockStatement': return this.BlockStatement(statement); case 'MustacheCommentStatement': return this.MustacheCommentStatement(statement); case 'CommentStatement': return this.CommentStatement(statement); case 'TextNode': return this.TextNode(statement); case 'ElementNode': return this.ElementNode(statement); case 'Block': return this.Block(statement); case 'Template': return this.Template(statement); case 'AttrNode': // should have element return this.AttrNode(statement); } } Template(template) { this.TopLevelStatements(template.body); } Block(block) { /* When processing a template like: ```hbs {{#if whatever}} whatever {{else if somethingElse}} something else {{else}} fallback {{/if}} ``` The AST still _effectively_ looks like: ```hbs {{#if whatever}} whatever {{else}}{{#if somethingElse}} something else {{else}} fallback {{/if}}{{/if}} ``` The only way we can tell if that is the case is by checking for `block.chained`, but unfortunately when the actual statements are processed the `block.body[0]` node (which will always be a `BlockStatement`) has no clue that its ancestor `Block` node was chained. This "forwards" the `chained` setting so that we can check it later when processing the `BlockStatement`. */ if (block.chained) { let firstChild = block.body[0]; firstChild.chained = true; } if (this.handledByOverride(block)) { return; } this.TopLevelStatements(block.body); } TopLevelStatements(statements) { statements.forEach((statement)=>this.TopLevelStatement(statement)); } ElementNode(el) { if (this.handledByOverride(el)) { return; } this.OpenElementNode(el); this.TopLevelStatements(el.children); this.CloseElementNode(el); } OpenElementNode(el) { this.buffer += `<${el.tag}`; const parts = [ ...el.attributes, ...el.modifiers, ...el.comments ].sort(sortByLoc); for (const part of parts){ this.buffer += ' '; switch(part.type){ case 'AttrNode': this.AttrNode(part); break; case 'ElementModifierStatement': this.ElementModifierStatement(part); break; case 'MustacheCommentStatement': this.MustacheCommentStatement(part); break; } } if (el.blockParams.length) { this.BlockParams(el.blockParams); } if (el.selfClosing) { this.buffer += ' /'; } this.buffer += '>'; } CloseElementNode(el) { if (el.selfClosing || isVoidTag(el.tag)) { return; } this.buffer += `</${el.tag}>`; } AttrNode(attr) { if (this.handledByOverride(attr)) { return; } let { name, value } = attr; this.buffer += name; const isAttribute = !name.startsWith('@'); const shouldElideValue = isAttribute && value.type == 'TextNode' && value.chars.length === 0; if (!shouldElideValue) { this.buffer += '='; this.AttrNodeValue(value); } } AttrNodeValue(value) { if (value.type === 'TextNode') { let quote = '"'; if (this.options.entityEncoding === 'raw') { if (value.chars.includes('"') && !value.chars.includes("'")) { quote = "'"; } } this.buffer += quote; this.TextNode(value, quote); this.buffer += quote; } else { this.Node(value); } } TextNode(text, isInAttr) { if (this.handledByOverride(text)) { return; } if (this.options.entityEncoding === 'raw') { if (isInAttr && text.chars.includes(isInAttr)) { this.buffer += escapeAttrValue(text.chars); } else { this.buffer += text.chars; } } else if (isInAttr) { this.buffer += escapeAttrValue(text.chars); } else { this.buffer += escapeText(text.chars); } } MustacheStatement(mustache) { if (this.handledByOverride(mustache)) { return; } this.buffer += mustache.trusting ? '{{{' : '{{'; if (mustache.strip.open) { this.buffer += '~'; } this.Expression(mustache.path); this.Params(mustache.params); this.Hash(mustache.hash); if (mustache.strip.close) { this.buffer += '~'; } this.buffer += mustache.trusting ? '}}}' : '}}'; } BlockStatement(block) { if (this.handledByOverride(block)) { return; } if (block.chained) { this.buffer += block.inverseStrip.open ? '{{~' : '{{'; this.buffer += 'else '; } else { this.buffer += block.openStrip.open ? '{{~#' : '{{#'; } this.Expression(block.path); this.Params(block.params); this.Hash(block.hash); if (block.program.blockParams.length) { this.BlockParams(block.program.blockParams); } if (block.chained) { this.buffer += block.inverseStrip.close ? '~}}' : '}}'; } else { this.buffer += block.openStrip.close ? '~}}' : '}}'; } this.Block(block.program); if (block.inverse) { if (!block.inverse.chained) { this.buffer += block.inverseStrip.open ? '{{~' : '{{'; this.buffer += 'else'; this.buffer += block.inverseStrip.close ? '~}}' : '}}'; } this.Block(block.inverse); } if (!block.chained) { this.buffer += block.closeStrip.open ? '{{~/' : '{{/'; this.Expression(block.path); this.buffer += block.closeStrip.close ? '~}}' : '}}'; } } BlockParams(blockParams) { this.buffer += ` as |${blockParams.join(' ')}|`; } ConcatStatement(concat) { if (this.handledByOverride(concat)) { return; } this.buffer += '"'; concat.parts.forEach((part)=>{ if (part.type === 'TextNode') { this.TextNode(part, '"'); } else { this.Node(part); } }); this.buffer += '"'; } MustacheCommentStatement(comment) { if (this.handledByOverride(comment)) { return; } this.buffer += `{{!--${comment.value}--}}`; } ElementModifierStatement(mod) { if (this.handledByOverride(mod)) { return; } this.buffer += '{{'; this.Expression(mod.path); this.Params(mod.params); this.Hash(mod.hash); this.buffer += '}}'; } CommentStatement(comment) { if (this.handledByOverride(comment)) { return; } this.buffer += `<!--${comment.value}-->`; } PathExpression(path) { if (this.handledByOverride(path)) { return; } this.buffer += path.original; } SubExpression(sexp) { if (this.handledByOverride(sexp)) { return; } this.buffer += '('; this.Expression(sexp.path); this.Params(sexp.params); this.Hash(sexp.hash); this.buffer += ')'; } Params(params) { // TODO: implement a top level Params AST node (just like the Hash object) // so that this can also be overridden if (params.length) { params.forEach((param)=>{ this.buffer += ' '; this.Expression(param); }); } } Hash(hash) { if (this.handledByOverride(hash, true)) { return; } hash.pairs.forEach((pair)=>{ this.buffer += ' '; this.HashPair(pair); }); } HashPair(pair) { if (this.handledByOverride(pair)) { return; } this.buffer += pair.key; this.buffer += '='; this.Node(pair.value); } StringLiteral(str) { if (this.handledByOverride(str)) { return; } this.buffer += JSON.stringify(str.value); } BooleanLiteral(bool) { if (this.handledByOverride(bool)) { return; } this.buffer += String(bool.value); } NumberLiteral(number) { if (this.handledByOverride(number)) { return; } this.buffer += String(number.value); } UndefinedLiteral(node) { if (this.handledByOverride(node)) { return; } this.buffer += 'undefined'; } NullLiteral(node) { if (this.handledByOverride(node)) { return; } this.buffer += 'null'; } print(node) { let { options } = this; if (options.override) { let result = options.override(node, options); if (result !== undefined) { return result; } } this.buffer = ''; this.Node(node); return this.buffer; } } function build(ast, options = { entityEncoding: 'transformed' }) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS users if (!ast) { return ''; } let printer = new Printer(options); return printer.print(ast); } function isKeyword(word, type) { if (word in KEYWORDS_TYPES) { if (type === undefined) { return true; } else { let types = KEYWORDS_TYPES[word]; // This seems like a TypeScript bug – it inferred types as never[]? return types.includes(type); } } else { return false; } } /** * This includes the full list of keywords currently in use in the template * language, and where their valid usages are. */ const KEYWORDS_TYPES = { action: [ 'Call', 'Modifier' ], component: [ 'Call', 'Append', 'Block' ], debugger: [ 'Append' ], 'each-in': [ 'Block' ], each: [ 'Block' ], 'has-block-params': [ 'Call', 'Append' ], 'has-block': [ 'Call', 'Append' ], helper: [ 'Call', 'Append' ], if: [ 'Call', 'Append', 'Block' ], 'in-element': [ 'Block' ], let: [ 'Block' ], log: [ 'Call', 'Append' ], modifier: [ 'Call', 'Modifier' ], mount: [ 'Append' ], mut: [ 'Call', 'Append' ], outlet: [ 'Append' ], readonly: [ 'Call', 'Append' ], unbound: [ 'Call', 'Append' ], unless: [ 'Call', 'Append', 'Block' ], yield: [ 'Append' ] }; // import Logger from './logger'; function assert(test, msg) { } function setLocalDebugType(type, ...brand) { } function unwrap(val) { return val; } function expect(val, message) { return val; } function isPresentArray(list) { return list ? list.length > 0 : false; } function asPresentArray(list, message = `unexpected empty list`) { return list; } function getLast(list) { return list.length === 0 ? undefined : list[list.length - 1]; } function getFirst(list) { return list.length === 0 ? undefined : list[0]; } function dict() { return Object.create(null); } const assign = Object.assign; /** * This constant exists to make it easier to differentiate normal logs from * errant console.logs. LOGGER can be used outside of LOCAL_TRACE_LOGGING checks, * and is meant to be used in the rare situation where a console.* call is * actually appropriate. */ const LOGGER = console; function assertNever(value, desc = 'unexpected unreachable branch') { LOGGER.log('unreachable', value); LOGGER.log(`${desc} :: ${JSON.stringify(value)} (${value})`); throw new Error(`code reached unreachable`); } var errorProps = [ 'description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack' ]; function Exception(message, node) { var loc = node && node.loc, line, endLineNumber, column, endColumn; if (loc) { line = loc.start.line; endLineNumber = loc.end.line; column = loc.start.column; endColumn = loc.end.column; message += ' - ' + line + ':' + column; } var tmp = Error.prototype.constructor.call(this, message); // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. for(var idx = 0; idx < errorProps.length; idx++){ this[errorProps[idx]] = tmp[errorProps[idx]]; } /* istanbul ignore else */ if (Error.captureStackTrace) { Error.captureStackTrace(this, Exception); } try { if (loc) { this.lineNumber = line; this.endLineNumber = endLineNumber; // Work around issue under safari where we can't directly set the column value /* istanbul ignore next */ if (Object.defineProperty) { Object.defineProperty(this, 'column', { value: column, enumerable: true }); Object.defineProperty(this, 'endColumn', { value: endColumn, enumerable: true }); } else { this.column = column; this.endColumn = endColumn; } } } catch (nop) { /* Ignore if the browser is very particular */ } } Exception.prototype = new Error(); function Visitor() { this.parents = []; } Visitor.prototype = { constructor: Visitor, mutating: false, // Visits a given value. If mutating, will replace the value if necessary. acceptKey: function(node, name) { var value = this.accept(node[name]); if (this.mutating) { // Hacky sanity check: This may have a few false positives for type for the helper // methods but will generally do the right thing without a lot of overhead. if (value && !Visitor.prototype[value.type]) { throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); } node[name] = value; } }, // Performs an accept operation with added sanity check to ensure // required keys are not removed. acceptRequired: function(node, name) { this.acceptKey(node, name); if (!node[name]) { throw new Exception(node.type + ' requires ' + name); } }, // Traverses a given array. If mutating, empty respnses will be removed // for child elements. acceptArray: function(array) { for(var i = 0, l = array.length; i < l; i++){ this.acceptKey(array, i); if (!array[i]) { array.splice(i, 1); i--; l--; } } }, accept: function(object) { if (!object) { return; } /* istanbul ignore next: Sanity code */ if (!this[object.type]) { throw new Exception('Unknown type: ' + object.type, object); } if (this.current) { this.parents.unshift(this.current); } this.current = object; var ret = this[object.type](object); this.current = this.parents.shift(); if (!this.mutating || ret) { return ret; } else if (ret !== false) { return object; } }, Program: function(program) { this.acceptArray(program.body); }, MustacheStatement: visitSubExpression, Decorator: visitSubExpression, BlockStatement: visitBlock, DecoratorBlock: visitBlock, PartialStatement: visitPartial, PartialBlockStatement: function(partial) { visitPartial.call(this, partial); this.acceptKey(partial, 'program'); }, ContentStatement: function() {}, CommentStatement: function() {}, SubExpression: visitSubExpression, PathExpression: function() {}, StringLiteral: function() {}, NumberLiteral: function() {}, BooleanLiteral: function() {}, UndefinedLiteral: function() {}, NullLiteral: function() {}, Hash: function(hash) { this.acceptArray(hash.pairs); }, HashPair: function(pair) { this.acceptRequired(pair, 'value'); } }; function visitSubExpression(mustache) { this.acceptRequired(mustache, 'path'); this.acceptArray(mustache.params); this.acceptKey(mustache, 'hash'); } function visitBlock(block) { visitSubExpression.call(this, block); this.acceptKey(block, 'program'); this.acceptKey(block, 'inverse'); } function visitPartial(partial) { this.acceptRequired(partial, 'name'); this.acceptArray(partial.params); this.acceptKey(partial, 'hash'); } function WhitespaceControl(options) { if (options === void 0) { options = {}; } this.options = options; } WhitespaceControl.prototype = new Visitor(); WhitespaceControl.prototype.Program = function(program) { var doStandalone = !this.options.ignoreStandalone; var isRoot = !this.isRootSeen; this.isRootSeen = true; var body = program.body; for(var i = 0, l = body.length; i < l; i++){ var current = body[i], strip = this.accept(current); if (!strip) { continue; } var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), _isNextWhitespace = isNextWhitespace(body, i, isRoot), openStandalone = strip.openStandalone && _isPrevWhitespace, closeStandalone = strip.closeStandalone && _isNextWhitespace, inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; if (strip.close) { omitRight(body, i, true); } if (strip.open) { omitLeft(body, i, true); } if (doStandalone && inlineStandalone) { omitRight(body, i); if (omitLeft(body, i)) { // If we are on a standalone node, save the indent info for partials if (current.type === 'PartialStatement') { // Pull out the whitespace from the final line current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; } } } if (doStandalone && openStandalone) { omitRight((current.program || current.inverse).body); // Strip out the previous content node if it's whitespace only omitLeft(body, i); } if (doStandalone && closeStandalone) { // Always strip the next node omitRight(body, i); omitLeft((current.inverse || current.program).body); } } return program; }; WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(block) { this.accept(block.program); this.accept(block.inverse); // Find the inverse program that is involed with whitespace stripping. var program = block.program || block.inverse, inverse = block.program && block.inverse, firstInverse = inverse, lastInverse = inverse; if (inverse && inverse.chained) { firstInverse = inverse.body[0].program; // Walk the inverse chain to find the last inverse that is actually in the chain. while(lastInverse.chained){ lastInverse = lastInverse.body[lastInverse.body.length - 1].program; } } var strip = { open: block.openStrip.open, close: block.closeStrip.close, // Determine the standalone candiacy. Basically flag our content as being possibly standalone // so our parent can determine if we actually are standalone openStandalone: isNextWhitespace(program.body), closeStandalone: isPrevWhitespace((firstInverse || program).body) }; if (block.openStrip.close) { omitRight(program.body, null, true); } if (inverse) { var inverseStrip = block.inverseStrip; if (inverseStrip.open) { omitLeft(program.body, null, true); } if (inverseStrip.close) { omitRight(firstInverse.body, null, true); } if (block.closeStrip.open) { omitLeft(lastInverse.body, null, true); } // Find standalone else statments if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { omitLeft(program.body); omitRight(firstInverse.body); } } else if (block.closeStrip.open) { omitLeft(program.body, null, true); } return strip; }; WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(mustache) { return mustache.strip; }; WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(node) { /* istanbul ignore next */ var strip = node.strip || {}; return { inlineStandalone: true, open: strip.open, close: strip.close }; }; function isPrevWhitespace(body, i, isRoot) { if (i === undefined) { i = body.length; } // Nodes that end with newlines are considered whitespace (but are special // cased for strip operations) var prev = body[i - 1], sibling = body[i - 2]; if (!prev) { return isRoot; } if (prev.type === 'ContentStatement') { return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); } } function isNextWhitespace(body, i, isRoot) { if (i === undefined) { i = -1; } var next = body[i + 1], sibling = body[i + 2]; if (!next) { return isRoot; } if (next.type === 'ContentStatement') { return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); } } // Marks the node to the right of the position as omitted. // I.e. {{foo}}' ' will mark the ' ' node as omitted. // // If i is undefined, then the first child will be marked as such. // // If multiple is truthy then all whitespace will be stripped out until non-whitespace // content is met. function omitRight(body, i, multiple) { var current = body[i == null ? 0 : i + 1]; if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { return; } var original = current.value; current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); current.rightStripped = current.value !== original; } // Marks the node to the left of the position as omitted. // I.e. ' '{{foo}} will mark the ' ' node as omitted. // // If i is undefined then the last child will be marked as such. // // If multiple is truthy then all whitespace will be stripped out until non-whitespace // content is met. function omitLeft(body, i, multiple) { var current = body[i == null ? body.length - 1 : i - 1]; if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { return; } // We omit the last node if it's whitespace only and not preceded by a non-content node. var original = current.value; current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); current.leftStripped = current.value !== original; return current.leftStripped; } /* parser generated by jison 0.4.18 */ /* Returns a Parser object of the following structure: Parser: { yy: {} } Parser.prototype: { yy: {}, trace: function(), symbols_: {associative list: name ==> number}, terminals_: {associative list: number ==> name}, productions_: [...], performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), table: [...], defaultActions: {...}, parseError: function(str, hash), parse: function(input), lexer: { EOF: 1, parseError: function(str, hash), setInput: function(input), input: function(), unput: function(str), more: function(), less: function(n), pastInput: function(), upcomingInput: function(), showPosition: function(), test_match: function(regex_match_array, rule_index), next: function(), lex: function(), begin: function(condition), popState: function(), _currentRules: function(), topState: function(), pushState: function(condition), options: { ranges: boolean (optional: true ==> token location info will include a .range[] member) flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) }, performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), rules: [...], conditions: {associative list: name ==> set}, } } token location info (@$, _$, etc.): { first_line: n, last_line: n, first_column: n, last_column: n, range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) } the parseError function receives a 'hash' object with these members for lexer and parser errors: { text: (matched text) token: (the produced terminal token, if any) line: (yylineno) } while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { loc: (yylloc) expected: (string describing the set of expected tokens) recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) } */ var parser = function() { var o = function(k, v, o, l) { for(o = o || {}, l = k.length; l--; o[k[l]] = v); return o; }, $V0 = [ 2, 44 ], $V1 = [ 1, 20 ], $V2 = [ 5, 14, 15, 19, 29, 34, 39, 44, 47, 48, 52, 56, 60 ], $V3 = [ 1, 35 ], $V4 = [ 1, 38 ], $V5 = [ 1, 30 ], $V6 = [ 1, 31 ], $V7 = [ 1, 32 ], $V8 = [ 1, 33 ], $V9 = [ 1, 34 ], $Va = [ 1, 37 ], $Vb = [ 14, 15, 19, 29, 34, 39, 44, 47, 48, 52, 56, 60 ], $Vc = [ 14, 15, 19, 29, 34, 44, 47, 48, 52, 56, 60 ], $Vd = [ 15, 18 ], $Ve = [ 14, 15, 19, 29, 34, 47, 48, 52, 56, 60 ], $Vf = [ 33, 64, 71, 79, 80, 81, 82, 83, 84 ], $Vg = [ 23, 33, 55, 64, 67, 71, 74, 79, 80, 81, 82, 83, 84 ], $Vh = [ 1, 51 ], $Vi = [ 23, 33, 55, 64, 67, 71, 74, 79, 80, 81, 82, 83, 84, 86 ], $Vj = [ 2, 43 ], $Vk = [ 55, 64, 71, 79, 80, 81, 82, 83, 84 ], $Vl = [ 1, 58 ], $Vm = [ 1, 59 ], $Vn = [ 1, 66 ], $Vo = [ 33, 64, 71, 74, 79, 80, 81, 82, 83, 84 ], $Vp = [ 23, 64, 71, 79, 80, 81, 82, 83, 84 ], $Vq = [ 1, 76 ], $Vr = [ 64, 67, 71, 79, 80, 81, 82, 83, 84 ], $Vs = [ 33, 74 ], $Vt = [ 23, 33, 55, 67, 71, 74 ], $Vu = [ 1, 106 ], $Vv = [ 1, 118 ], $Vw = [ 71, 76 ]; var parser = { trace: function trace() {}, yy: {}, symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "expr": 49, "mustache_repetition0": 50, "mustache_option0": 51, "OPEN_UNESCAPED": 52, "mustache_repetition1": 53, "mustache_option1": 54, "CLOSE_UNESCAPED": 55, "OPEN_PARTIAL": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "sexpr": 63, "OPEN_SEXPR": 64, "sexpr_repetition0": 65, "sexpr_option0": 66, "CLOSE_SEXPR": 67, "hash": 68, "hash_repetition_plus0": 69, "hashSegment": 70, "ID": 71, "EQUALS": 72, "blockParams": 73, "OPEN_BLOCK_PARAMS": 74, "blockParams_repetition_plus0": 75, "CLOSE_BLOCK_PARAMS": 76, "path": 77, "dataName": 78, "STRING": 79, "NUMBER": 80, "BOOLEAN": 81, "UNDEFINED": 82, "NULL": 83, "DATA": 84, "pathSegments": 85, "SEP": 86, "$accept": 0, "$end": 1 }, terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 52: "OPEN_UNESCAPED", 55: "CLOSE_UNESCAPED", 56: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 64: "OPEN_SEXPR", 67: "CLOSE_SEXPR", 71: "ID", 72: "EQUALS", 74: "OPEN_BLOCK_PARAMS", 76: "CLOSE_BLOCK_PARAMS", 79: "STRING", 80: "NUMBER", 81: "BOOLEAN", 82: "UNDEFINED", 83: "NULL", 84: "DATA", 86: "SEP" }, productions_: [ 0, [ 3, 2 ], [ 4, 1 ], [ 7, 1 ], [ 7, 1 ], [ 7, 1 ], [ 7, 1 ], [ 7, 1 ], [ 7, 1 ], [ 7, 1 ], [ 13, 1 ], [ 10, 3 ], [ 16, 5 ], [ 9, 4 ], [ 9, 4 ], [ 24, 6 ], [ 27, 6 ], [ 38, 6 ], [ 43, 2 ], [ 45, 3 ], [ 45, 1 ], [ 26, 3 ], [ 8, 5 ], [ 8, 5 ], [ 11, 5 ], [ 12, 3 ], [ 59, 5 ], [ 49, 1 ], [ 49, 1 ], [ 63, 5 ], [ 68, 1 ], [ 70, 3 ], [ 73, 3 ], [ 20, 1 ], [ 20, 1 ], [ 20, 1 ], [ 20, 1 ], [ 20, 1 ], [ 20, 1 ], [ 20, 1 ], [ 78, 2 ], [ 77, 1 ], [ 85, 3 ], [ 85, 1 ], [ 6, 0 ], [ 6, 2 ], [ 17, 0 ], [ 17, 2 ], [ 21, 0 ], [ 21, 2 ], [ 22, 0 ], [ 22, 1 ], [ 25, 0 ], [ 25, 1 ], [ 28, 0 ], [ 28, 1 ], [ 30, 0 ], [ 30, 2 ], [ 31, 0 ], [ 31, 1 ], [ 32, 0 ], [ 32, 1 ], [ 35, 0 ], [ 35, 2 ], [ 36, 0 ], [ 36, 1 ], [ 37, 0 ], [ 37, 1 ], [ 40, 0 ], [ 40, 2 ], [ 41, 0 ], [ 41, 1 ], [ 42, 0 ], [ 42, 1 ], [ 46, 0 ], [ 46, 1 ], [ 50, 0 ], [ 50, 2 ], [ 51, 0 ], [ 51, 1 ], [ 53, 0 ], [ 53, 2 ], [ 54, 0 ], [ 54, 1 ], [ 57, 0 ], [ 57, 2 ], [ 58, 0 ], [ 58, 1 ], [ 61, 0 ], [ 61, 2 ], [ 62, 0 ], [ 62, 1 ], [ 65, 0 ], [ 65, 2 ], [ 66, 0 ], [ 66, 1 ], [ 69, 1 ], [ 69, 2 ], [ 75, 1 ], [ 75, 2 ] ], performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */ , $$ /* vstack */ , _$ /* lstack */ ) { /* this == yyval */ var $0 = $$.length - 1; switch(yystate){ case 1: return $$[$0 - 1]; case 2: this.$ = yy.prepareProgram($$[$0]); break; case 3: case 4: case 5: case 6: case 7: case 8: case 20: case 27: case 28: case 33: case 34: this.$ = $$[$0]; break; case 9: this.$ = { type: 'CommentStatement', value: yy.stripComment($$[$0]), strip: yy.stripFlags($$[$0], $$[$0]), loc: yy.locInfo(this._$) }; break; case 10: this.$ = { type: 'ContentStatement', original: $$[$0], value: $$[$0], loc: yy.locInfo(this._$) }; break; case 11: this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); break; case 12: this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; break; case 13: this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); break; case 14: this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); break; case 15: this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; break; case 16: case 17: this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; break; case 18: this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] }; break; case 19: var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$), program = yy.prepareProgram([ inverse ], $$[$0 - 1].loc); program.chained = true; this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true }; break; case 21: this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) }; break; case 22: case 23: this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); break; case 24: this.$ = { type: 'PartialStatement', name: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], indent: '', strip: yy.stripFlags($$[$0 - 4], $$[$0]),