UNPKG

buffer-apg-js

Version:

JavaScript APG, an ABNF Parser Generator

307 lines (301 loc) 9.58 kB
/* eslint-disable guard-for-in */ /* eslint-disable no-restricted-syntax */ /* ************************************************************************************* * copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved * license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause) * ********************************************************************************* */ // This module implements the `exec()` function. 'use strict;'; const funcs = require('./result'); /* turns on or off the read-only attribute of the `last result` properties of the object */ const setProperties = function seProperties(p, readonly) { const exp = p.thisThis; const prop = { writable: readonly, enumerable: false, configurable: true, }; Object.defineProperty(exp, 'input', prop); Object.defineProperty(exp, 'leftContext', prop); Object.defineProperty(exp, 'lastMatch', prop); Object.defineProperty(exp, 'rightContext', prop); Object.defineProperty(exp, '$_', prop); Object.defineProperty(exp, '$`', prop); Object.defineProperty(exp, '$&', prop); Object.defineProperty(exp, "$'", prop); prop.enumerable = true; Object.defineProperty(exp, 'rules', prop); if (!exp.rules) { exp.rules = []; } for (const name in exp.rules) { const des = `\${${name}}`; Object.defineProperty(exp, des, prop); Object.defineProperty(exp.rules, name, prop); } }; /* generate the results object for JavaScript strings */ const sResult = function sResult(p) { const ret = { index: p.result.index, length: p.result.length, input: p.charsToString(p.chars, 0), treeDepth: p.result.treeDepth, nodeHits: p.result.nodeHits, rules: [], toText() { return funcs.s.resultToText(this); }, toHtml() { return funcs.s.resultToHtml(this); }, toHtmlPage() { return funcs.s.resultToHtmlPage(this); }, }; ret[0] = p.charsToString(p.chars, p.result.index, p.result.length); /* each rule is either 'undefined' or an array of phrases */ for (const name in p.result.rules) { const rule = p.result.rules[name]; if (rule) { ret.rules[name] = []; for (let i = 0; i < rule.length; i += 1) { ret.rules[name][i] = { index: rule[i].index, phrase: p.charsToString(p.chars, rule[i].index, rule[i].length), }; } } else { ret.rules[name] = undefined; } } return ret; }; /* generate the results object for integer arrays of character codes */ const uResult = function uResult(p) { const { chars } = p; const { result } = p; let beg; let end; const ret = { index: result.index, length: result.length, input: chars.slice(0), treeDepth: result.treeDepth, nodeHits: result.nodeHits, rules: [], toText(mode) { return funcs.u.resultToText(this, mode); }, toHtml(mode) { return funcs.u.resultToHtml(this, mode); }, toHtmlPage(mode) { return funcs.u.resultToHtmlPage(this, mode); }, }; beg = result.index; end = beg + result.length; ret[0] = chars.slice(beg, end); /* each rule is either 'undefined' or an array of phrases */ for (const name in result.rules) { const rule = result.rules[name]; if (rule) { ret.rules[name] = []; for (let i = 0; i < rule.length; i += 1) { beg = rule[i].index; end = beg + rule[i].length; ret.rules[name][i] = { index: beg, phrase: chars.slice(beg, end), }; } } else { ret.rules[name] = undefined; } } return ret; }; /* generate the apg-exp properties or "last match" object for JavaScript strings */ const sLastMatch = function sLastMatch(p, result) { const exp = p.thisThis; let temp; // eslint-disable-next-line prefer-destructuring exp.lastMatch = result[0]; temp = p.chars.slice(0, result.index); exp.leftContext = p.charsToString(temp); temp = p.chars.slice(result.index + result.length); exp.rightContext = p.charsToString(temp); exp.input = result.input.slice(0); // eslint-disable-next-line no-underscore-dangle exp.$_ = exp.input; exp['$&'] = exp.lastMatch; exp['$`'] = exp.leftContext; exp["$'"] = exp.rightContext; exp.rules = {}; for (const name in result.rules) { const rule = result.rules[name]; if (rule) { exp.rules[name] = rule[rule.length - 1].phrase; } else { exp.rules[name] = undefined; } exp[`\${${name}}`] = exp.rules[name]; } }; /* generate the apg-exp properties or "last match" object for integer arrays of character codes */ const uLastMatch = function uLastMatch(p, result) { const exp = p.thisThis; const { chars } = p; let beg; beg = 0; const end = beg + result.index; exp.leftContext = chars.slice(beg, end); exp.lastMatch = result[0].slice(0); beg = result.index + result.length; exp.rightContext = chars.slice(beg); exp.input = result.input.slice(0); // eslint-disable-next-line no-underscore-dangle exp.$_ = exp.input; exp['$&'] = exp.lastMatch; exp['$`'] = exp.leftContext; exp["$'"] = exp.rightContext; exp.rules = {}; for (const name in result.rules) { const rule = result.rules[name]; if (rule) { exp.rules[name] = rule[rule.length - 1].phrase; } else { exp.rules[name] = undefined; } exp[`\${${name}}`] = exp.rules[name]; } }; /* set the returned result properties, and the `last result` properties of the object */ const setResult = function setResult(p, parserResult) { let result; p.result = { index: parserResult.index, length: parserResult.length, treeDepth: parserResult.treeDepth, nodeHits: parserResult.nodeHits, rules: [], }; /* set result in APG phrases {phraseIndex, phraseLength} */ /* p.ruleNames are all names in the grammar */ /* p.thisThis.ast.callbacks[name] only defined for 'included' rule names */ const obj = p.parser.ast.phrases(); for (const name in p.thisThis.ast.callbacks) { // const cap = p.ruleNames[name]; if (p.thisThis.ast.callbacks[name]) { const cap = p.ruleNames[name]; if (Array.isArray(obj[cap])) { p.result.rules[cap] = obj[cap]; } else { p.result.rules[cap] = undefined; } } } /* p.result now has everything we need to know about the result of exec() */ /* generate the Unicode or JavaScript string version of the result & last match objects */ setProperties(p, true); if (p.thisThis.unicode) { result = uResult(p); uLastMatch(p, result); } else { result = sResult(p); sLastMatch(p, result); } setProperties(p, false); return result; }; /* create an unsuccessful parser result object */ const resultInit = function resultInit() { return { success: false, }; }; /* create a successful parser result object */ const resultSuccess = function resultSuccess(index, parserResult) { return { success: true, index, length: parserResult.matched, treeDepth: parserResult.maxTreeDepth, nodeHits: parserResult.nodeHits, }; }; /* search forward from `lastIndex` until a match is found or the end of string is reached */ const forward = function forward(p) { let result = resultInit(); for (let i = p.thisThis.lastIndex; i < p.chars.length; i += 1) { const re = p.parser.parseSubstring(p.grammarObject, 0, p.chars, i, p.chars.length - i); if (p.match(re.state)) { result = resultSuccess(i, re); break; } } return result; }; /* reset lastIndex after a search */ const setLastIndex = function setLastIndex(lastIndex, flag, parserResult) { if (flag) { if (parserResult.success) { let ret = parserResult.index; /* bump-along mode - increment is never zero */ ret += parserResult.length > 0 ? parserResult.length : 1; return ret; } return 0; } return lastIndex; }; /* attempt a match at lastIndex only - does look further if a match is not found */ const anchor = function anchor(p) { let result = resultInit(); if (p.thisThis.lastIndex < p.chars.length) { const re = p.parser.parseSubstring( p.grammarObject, 0, p.chars, p.thisThis.lastIndex, p.chars.length - p.thisThis.lastIndex ); if (p.match(re.state)) { result = resultSuccess(p.thisThis.lastIndex, re); } } return result; }; /* called by exec() for a forward search */ exports.execForward = function execForward(p) { const parserResult = forward(p); let result = null; if (parserResult.success) { result = setResult(p, parserResult); } p.thisThis.lastIndex = setLastIndex(p.thisThis.lastIndex, p.thisThis.global, parserResult); return result; }; /* called by exec() for an anchored search */ exports.execAnchor = function execAnchor(p) { const parserResult = anchor(p); let result = null; if (parserResult.success) { result = setResult(p, parserResult); } p.thisThis.lastIndex = setLastIndex(p.thisThis.lastIndex, p.thisThis.sticky, parserResult); return result; }; /* search forward from lastIndex looking for a match */ exports.testForward = function testForward(p) { const parserResult = forward(p); p.thisThis.lastIndex = setLastIndex(p.thisThis.lastIndex, p.thisThis.global, parserResult); return parserResult.success; }; /* test for a match at lastIndex only, do not look further if no match is found */ exports.testAnchor = function testAnchor(p) { const parserResult = anchor(p); p.thisThis.lastIndex = setLastIndex(p.thisThis.lastIndex, p.thisThis.sticky, parserResult); return parserResult.success; };