UNPKG

buffer-apg-js

Version:

JavaScript APG, an ABNF Parser Generator

158 lines (155 loc) 5.65 kB
/* 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 `replace()` function. 'use strict;'; const errorName = 'apg-exp: replace(): '; const parseReplacementString = require('./parse-replacement'); /* replace special replacement patterns, `$&`, etc. */ const generateReplacementString = function generateReplacementString(p, rstr, items) { const exp = p.thisThis; if (items.length === 0) { /* no special characters in the replacement string */ /* just return a copy of the replacement string */ return rstr; } let replace = rstr.slice(0); let first; let last; let ruleName; items.reverse(); items.forEach((item) => { first = replace.slice(0, item.index); last = replace.slice(item.index + item.length); switch (item.type) { case 'escape': replace = first.concat('$', last); break; case 'prefix': replace = first.concat(exp.leftContext, last); break; case 'match': replace = first.concat(exp.lastMatch, last); break; case 'suffix': replace = first.concat(exp.rightContext, last); break; case 'name': /* If there are multiple matches to this rule name, only the last is used */ /* If this is a problem, modify the grammar and use different rule names for the different places. */ ruleName = p.ruleNames[item.name.nameString]; replace = first.concat(exp.rules[ruleName], last); break; default: throw new Error(`${errorName}generateREplacementString(): unrecognized item type: ${item.type}`); } }); return replace; }; /* creates a special object with the apg-exp object's "last match" properites */ const lastObj = function lastObj(exp) { const obj = {}; obj.ast = exp.ast; obj.input = exp.input; obj.leftContext = exp.leftContext; obj.lastMatch = exp.lastMatch; obj.rightContext = exp.rightContext; // eslint-disable-next-line no-underscore-dangle obj.$_ = exp.input; obj['$`'] = exp.leftContext; obj['$&'] = exp.lastMatch; obj["$'"] = exp.rightContext; obj.rules = []; // eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line guard-for-in for (const name in exp.rules) { const el = `\${${name}}`; obj[el] = exp[el]; obj.rules[name] = exp.rules[name]; } return obj; }; /* call the user's replacement function for a single pattern match */ const singleReplaceFunction = function singleReplaceFunction(p, ostr, func) { const result = p.thisThis.exec(ostr); if (result === null) { return ostr; } const rstr = func(result, lastObj(p.thisThis)); const ret = p.thisThis.leftContext.concat(rstr, p.thisThis.rightContext); return ret; }; /* call the user's replacement function to replace all pattern matches */ const globalReplaceFunction = function globalReplaceFunction(p, ostr, func) { const exp = p.thisThis; let retstr = ostr.slice(0); const TRUE = true; while (TRUE) { const result = exp.exec(retstr); if (result === null) { break; } const newrstr = func(result, lastObj(exp)); retstr = exp.leftContext.concat(newrstr, exp.rightContext); exp.lastIndex = exp.leftContext.length + newrstr.length; if (result[0].length === 0) { /* an empty string IS a match and is replaced */ /* but use "bump-along" mode to prevent infinite loop */ exp.lastIndex += 1; } } return retstr; }; /* do a single replacement with the caller's replacement string */ const singleReplaceString = function singleReplaceString(p, ostr, rstr) { const exp = p.thisThis; const result = exp.exec(ostr); if (result === null) { return ostr; } const ritems = parseReplacementString(p, rstr); const rep = generateReplacementString(p, rstr, ritems); const ret = exp.leftContext.concat(rep, exp.rightContext); return ret; }; /* do a global replacement of all matches with the caller's replacement string */ const globalReplaceString = function globalReplaceString(p, ostr, rstr) { const exp = p.thisThis; let retstr = ostr.slice(0); let ritems = null; const TRUE = true; while (TRUE) { const result = exp.exec(retstr); if (result == null) { break; } if (ritems === null) { ritems = parseReplacementString(p, rstr); } const newrstr = generateReplacementString(p, rstr, ritems); retstr = exp.leftContext.concat(newrstr, exp.rightContext); exp.lastIndex = exp.leftContext.length + newrstr.length; if (result[0].length === 0) { /* an empty string IS a match and is replaced */ /* but use "bump-along" mode to prevent infinite loop */ exp.lastIndex += 1; } } return retstr; }; /* the replace() function calls this to replace the matched patterns with a string */ exports.replaceString = function replaceString(p, str, replacement) { if (p.thisThis.global || p.thisThis.sticky) { return globalReplaceString(p, str, replacement); } return singleReplaceString(p, str, replacement); }; /* the replace() function calls this to replace the matched patterns with a function */ exports.replaceFunction = function replaceFunction(p, str, func) { if (p.thisThis.global || p.thisThis.sticky) { return globalReplaceFunction(p, str, func); } return singleReplaceFunction(p, str, func); };