apg-exp
Version:
(Deprecated: use apg-js instead.) Pattern-matching alternative to RegExp. Replaces the regular expression syntax with ABNF. Adds APG parser features such as User Defined Terminals (hand-written pattern matchers) and access to the AST.
146 lines (145 loc) • 4.82 kB
JavaScript
// This module implements the `replace()` function.
"use strict;"
var errorName = "apg-exp: replace(): ";
var parseReplacementString = require("./parse-replacement.js");
/* replace special replacement patterns, `$&`, etc. */
var generateReplacementString = function(p, rstr, items) {
var exp = p._this;
if (items.length === 0) {
/* no special characters in the replacement string */
/* just return a copy of the replacement string */
return rstr;
}
var replace = rstr.slice(0);
var first, last;
items.reverse();
items.forEach(function(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. */
var 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 */
var lastObj = function(exp) {
var obj = {}
obj.ast = exp.ast;
obj.input = exp.input;
obj.leftContext = exp.leftContext;
obj.lastMatch = exp.lastMatch;
obj.rightContext = exp.rightContext;
obj["$_"] = exp.input;
obj["$`"] = exp.leftContext;
obj["$&"] = exp.lastMatch;
obj["$'"] = exp.rightContext;
obj.rules = [];
for (name in exp.rules) {
var 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 */
var singleReplaceFunction = function(p, ostr, func) {
var result = p._this.exec(ostr);
if (result === null) {
return ostr;
}
rstr = func(result, lastObj(p._this));
var ret = (p._this.leftContext).concat(rstr, p._this.rightContext);
return ret;
}
/* call the user's replacement function to replace all pattern matches */
var globalReplaceFunction = function(p, ostr, func) {
var exp = p._this;
var retstr = ostr.slice(0);
while (true) {
var result = exp.exec(retstr);
if (result === null) {
break;
}
var 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 */
var singleReplaceString = function(p, ostr, rstr) {
var exp = p._this;
var result = exp.exec(ostr);
if (result === null) {
return ostr;
}
var ritems = parseReplacementString(p, rstr);
rstr = generateReplacementString(p, rstr, ritems);
var ret = (exp.leftContext).concat(rstr, exp.rightContext);
return ret;
}
/* do a global replacement of all matches with the caller's replacement string */
var globalReplaceString = function(p, ostr, rstr) {
var exp = p._this;
var retstr = ostr.slice(0);
var ritems = null;
while (true) {
var result = exp.exec(retstr);
if (result == null) {
break;
}
if (ritems === null) {
ritems = parseReplacementString(p, rstr);
}
var 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(p, str, replacement) {
if (p._this.global || p._this.sticky) {
return globalReplaceString(p, str, replacement);
} else {
return singleReplaceString(p, str, replacement);
}
}
/* the replace() function calls this to replace the matched patterns with a function */
exports.replaceFunction = function(p, str, func) {
if (p._this.global || p._this.sticky) {
return globalReplaceFunction(p, str, func);
} else {
return singleReplaceFunction(p, str, func);
}
}