ReplPair.java
package bjc.everge;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.function.UnaryOperator;
import bjc.everge.ControlledString.Control;
/**
* String pairs for replacements.
*
* @author Ben Culkin
*/
public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> {
// Line number we read this pair from
private int lno;
// Stage this pair is in
private int stage;
// Status of this pair with regards to doing staging stuff
private StageStatus stat = StageStatus.BOTH;
/**
* The priority for this replacement.
*/
public int priority;
/**
* The name of this replacement.
*
* Defaults to the 'find' string.
*/
public String name;
/**
* The string to look for.
*/
public String find;
/**
* The string to replace it with.
*/
public String replace;
/**
* Create a new blank replacement pair.
*/
public ReplPair() {
this("", "", 1, null);
}
/**
* Create a new replacement pair with a priority of 1.
*
* @param f
* The string to find.
* @param r
* The string to replace.
*/
public ReplPair(String f, String r) {
this(f, r, 1);
}
/**
* Create a new named replacement pair with a priority of 1.
*
* @param f
* The string to find.
* @param r
* The string to replace.
* @param n
* The name of the replacement pair.
*/
public ReplPair(String f, String r, String n) {
this(f, r, 1, n);
}
/**
* Create a new replacement pair with a set priority.
*
* @param f
* The string to find.
* @param r
* The string to replace.
* @param p
* The priority for the replacement.
*/
public ReplPair(String f, String r, int p) {
this(f, r, p, f);
}
/**
* Create a new replacement pair with a set priority and name.
*
* @param f
* The string to find.
* @param r
* The string to replace.
* @param n
* The name of the replacement pair.
* @param p
* The priority for the replacement.
*/
public ReplPair(String f, String r, int p, String n) {
find = f;
replace = r;
name = n;
priority = p;
}
/**
* Read a list of replacement pairs from an input source.
*
* @param scn
* The source to read the replacements from.
* @return
* The list of replacements.
*/
public static List<ReplPair> readList(Scanner scn) {
List<ReplPair> lst = new ArrayList<>();
return readList(lst, scn);
}
/**
* Read a list of replacement pairs from an input source, adding them to
* an existing list.
*
* @param detals
* The list to add the replacements to.
* @param scn
* The source to read the replacements from.
* @return
* The list of replacements.
*/
public static List<ReplPair> readList(List<ReplPair> detals, Scanner scn) {
List<ReplError> errList = new ArrayList<>();
List<ReplPair> rplPar = readList(detals, scn, errList);
if (errList.size() != 0) {
throw new ReplParseException("", errList);
}
return rplPar;
}
/**
* Read a list of replacement pairs from an input source, adding them to
* an existing list.
*
* @param detals
* The list to add the replacements to.
* @param scn
* The source to read the replacements from.
* @param errs
* The list to stick errors in.
* @return
* The list of replacements.
*/
public static List<ReplPair> readList(List<ReplPair> detals, Scanner scn, List<ReplError> errs) {
return readList(detals, scn, errs, new ReplOpts());
}
/**
* Read a list of replacement pairs from an input source, adding them to
* an existing list.
*
* @param detals
* The list to add the replacements to.
* @param scn
* The source to read the replacements from.
* @param errs
* The list to stick errors in.
* @param ropts
* The options to use when reading the pairs.
* @return
* The list of replacements.
*/
public static List<ReplPair> readList(List<ReplPair> detals, Scanner scn,
List<ReplError> errs, ReplOpts ropts) {
IntHolder lno = new IntHolder();
IntHolder pno = new IntHolder();
List<List<ReplPair>> stages = new ArrayList<>();
stages.add(new ArrayList<ReplPair>());
// For every line in the source...
while (scn.hasNextLine()) {
String name = scn.nextLine().trim();
lno.incr();
// If its commented or blank, skip it
if (name.equals("")) continue;
if (name.startsWith("#")) continue;
// Global control. Process it.
if (name.startsWith("|//")) {
readGlobal(name, scn, errs, ropts, lno, pno);
continue;
}
ReplPair rp = new ReplPair();
rp.priority = ropts.defPrior;
rp.stat = ropts.defStatus;
rp.lno = lno.get();
rp.stage = ropts.defStage;
boolean isMulti = ropts.defMulti;
{
String tmpName = readName(name, scn, errs, rp, ropts, lno, pno);
if (tmpName == null) continue;
name = tmpName;
}
rp.find = name;
if (rp.name == null) rp.name = name;
// We started to process the pair, mark it as being
// started
pno.incr();
String body = null;
// Read in the next uncommented line
do {
if (!scn.hasNextLine()) break;
body = scn.nextLine().trim();
lno.incr();
} while (body.startsWith("#"));
if (body == null) {
String msg =
"Ran out of input looking for replacement body for raw name '" + name + "'";
errs.add(new ReplError(lno, pno, msg, null));
break;
}
isMulti = ropts.defMulti;
// Body has attached controls, process them.
if (body.startsWith("//")) {
body = body.substring(2);
String[] bodyBits = StringUtils.escapeSplit("|", "//", body);
if (bodyBits.length < 2) {
String msg = "Did not find control terminator (//) in body where it should be";
errs.add(new ReplError(lno, pno, msg, body));
continue;
}
String contBody = bodyBits[0];
String actBody = bodyBits[1];
// Split out each control
String[] bits = StringUtils.escapeSplit("|", ";", actBody);
for (String bit : bits) {
String bitHead = bit.toUpperCase();
String bitBody = bit;
String[] bots = StringUtils.escapeSplit("|", "/", bit);
if (bots.length > 1) {
bitHead = bots[0].toUpperCase();
bitBody = bots[1];
}
switch (bitHead) {
case "MULTITRUE":
case "MULTIT":
case "MT":
isMulti = true;
break;
case "MULTIFALSE":
case "MULTIF":
case "MF":
isMulti = false;
break;
case "MULTI":
case "M":
isMulti = Boolean.parseBoolean(bitBody);
break;
default:
errs.add(new ReplError(lno, pno, String.format("Invalid control name '%s'", bitHead), body));
break;
}
}
body = actBody;
}
if (isMulti) {
String tmp = readMultiLine(body, scn, ropts, errs, "body", lno);
if (tmp == null) continue;
body = tmp;
}
rp.replace = body;
List<ReplPair> stageList = null;
if (rp.stage == 0 || stages.size() < (rp.stage - 1)) {
stageList = stages.get(rp.stage);
if (stageList == null) {
stageList = new ArrayList<>();
stages.add(rp.stage, stageList);
}
} else {
for (int i = stages.size(); i <= rp.stage; i++) {
stages.add(new ArrayList<>());
}
stageList = stages.get(rp.stage);
}
if (ropts.isTrace) {
ropts.errStream.printf("\t[DEBUG] Stage %d: Added %s\n\t\tContents: %s\n",
rp.stage, rp, stageList);
}
stageList.add(rp);
}
// Special-case one-stage processing.
if (stages.size() == 1) {
if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Executing single-stage bypass\n");
for (ReplPair rp : stages.iterator().next()) {
if (rp.stat == StageStatus.INTERNAL) {
if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluding internal RP %s\n", rp);
continue;
}
detals.add(rp);
}
detals.sort(null);
return detals;
}
// Handle stages
List<ReplPair> tmpList = new ArrayList<>();
tmpList.addAll(detals);
if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Stages: %s\n", stages);
int procStg = 0;
for (List<ReplPair> stageList : stages) {
procStg += 1;
List<ReplPair> curStage = new ArrayList<>();
if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Staging stage %d of %d: %s\n",
procStg, stageList.size(), stageList);
for (ReplPair rp : stageList) {
// Process through every pair in the previous
// stages
for (ReplPair curPar : tmpList) {
String tmp = rp.replace.replaceAll(curPar.find, curPar.replace);
if (ropts.isTrace && !rp.replace.equals(tmp)) {
ropts.errStream.printf("\t[DEBUG] Staged '%s' -> '%s'\t%s\n",
rp.replace, tmp, curPar);
}
rp.replace = tmp;
}
// If we're external; add straight to the output
if (rp.stat == StageStatus.EXTERNAL) {
if (ropts.isTrace) {
ropts.errStream.printf("\t[DEBUG] Skipped external for staging: %s\n",
rp);
}
detals.add(rp);
} else {
if (ropts.isTrace) {
ropts.errStream.printf("\t[DEBUG] Added to stage %d: %s\n\t\tContents: %s\n",
procStg, rp, curStage);
}
curStage.add(rp);
}
}
tmpList.addAll(curStage);
tmpList.sort(null);
}
// Copy over to output, excluding internals
for (ReplPair rp : tmpList) {
if (rp.stat == StageStatus.INTERNAL) {
if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluded internal: %s\n", rp);
continue;
}
detals.add(rp);
}
detals.sort(null);
if (ropts.isTrace) {
ropts.errStream.printf("\t[DEBUG] Final output: %s\n", detals);
}
return detals;
}
private static String readMultiLine(String lead, Scanner src, ReplOpts ropts,
List<ReplError> errs, String typ, IntHolder lno) {
String tmp = lead;
if (ropts.isTrace && tmp.endsWith("\\"))
ropts.errStream.printf("\t[TRACE] Starting multi-line parse for %s '%s'\n", typ, tmp);
boolean didMulti = tmp.endsWith("\\");
while (tmp.endsWith("\\")) {
boolean incNL = tmp.endsWith("|\\");
if (!src.hasNextLine()) break;
String nxt = src.nextLine().trim();
lno.incr();
if (nxt.startsWith("#")) continue;
String nlStr = incNL ? "\n" : "";
if (tmp.endsWith("\\")) {
if (incNL) {
tmp = tmp.substring(0, tmp.length() - 2);
} else {
tmp = tmp.substring(0, tmp.length() - 1);
}
}
tmp = String.format("%s%s%s", tmp, nlStr, nxt);
}
if (ropts.isTrace && didMulti)
ropts.errStream.printf("\t[TRACE] Finished multi-line parse for %s:\n%s\n.\n",
typ, tmp);
return tmp;
}
@Override
public String apply(String inp) {
return inp.replaceAll(find, replace);
}
@Override
public String toString() {
String nameStr = "";
if (!find.equals(name)) nameStr = String.format("(%s)", name);
return String.format("%ss/%s/%s/p(%d)", nameStr, find, replace, priority);
}
@Override
public int compareTo(ReplPair rp) {
if (this.priority == rp.priority) return this.lno - rp.lno;
return rp.priority - this.priority;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (!getClass().equals(o.getClass())) return false;
ReplPair ro = (ReplPair)o;
if (!find.equals(ro.find)) return false;
// lno is not a field we consider for equality
if (!name.equals(ro.name)) return false;
if (priority != ro.priority) return false;
if (!replace.equals(ro.name)) return false;
// stat is not a field we consider for equality
return true;
}
private static String readName(String nam, Scanner scn, List<ReplError> errs,
ReplPair rp, ReplOpts ropts, IntHolder lno, IntHolder pno) {
String name = nam;
boolean isMulti = ropts.defMulti;
// Name has attached controls, process them.
if (name.startsWith("//")) {
name = name.substring(2);
String[] nameBits = StringUtils.escapeSplit("|", "//", name);
if (nameBits.length < 2) {
String msg = "Did not find control terminator (//) in name where it should be";
errs.add(new ReplError(lno, pno, msg, name));
return null;
}
String contName = nameBits[0];
String actName = nameBits[1];
// Split out each control
String[] bits = StringUtils.escapeSplit("|", ";", contName);
for (String bit : bits) {
String bitHead = bit.toUpperCase();
String bitBody = bit;
String[] bots = StringUtils.escapeSplit("|", "/", bit);
if (bots.length > 1) {
bitHead = bots[0].toUpperCase();
bitBody = bots[1];
}
switch (bitHead) {
case "NAME":
case "N":
rp.name = bitBody;
break;
case "PRIORITY":
case "PRIOR":
case "P":
try {
rp.priority = Integer.parseInt(bitBody);
} catch (NumberFormatException nfex) {
String errMsg = String.format("'%s' is not a valid priority (must be an integer)", bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
}
break;
case "STAGE":
case "S":
try {
int tmpStage = Integer.parseInt(bitBody);
if (tmpStage < 0) {
String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
break;
}
rp.stage = tmpStage;
} catch (NumberFormatException nfex) {
String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)", bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
}
break;
case "MULTITRUE":
case "MULTIT":
case "MT":
isMulti = true;
break;
case "MULTIFALSE":
case "MULTIF":
case "MF":
isMulti = false;
break;
case "MULTI":
case "M":
isMulti = Boolean.parseBoolean(bitBody);
break;
case "INTERNAL":
case "INT":
case "I":
rp.stat = StageStatus.INTERNAL;
break;
case "EXTERNAL":
case "EXT":
case "E":
rp.stat = StageStatus.EXTERNAL;
break;
case "BOTH":
case "B":
rp.stat = StageStatus.BOTH;
break;
default:
{
ReplError erd = new ReplError(lno, pno,
String.format("Unknown control name '%s' for name '%s'",
bitHead, name), name);
errs.add(erd);
}
break;
}
name = actName;
}
// Multi-line name with a trailer
if (isMulti) {
String tmp = readMultiLine(name, scn, ropts, errs, "name", lno);
if (tmp == null) return null;
name = tmp;
}
}
return name;
}
private static void readGlobal(String nam, Scanner scn, List<ReplError> errs,
ReplOpts ropts, IntHolder lno, IntHolder pno) {
String name = nam.substring(3);
// Split out each control
String[] bits = StringUtils.escapeSplit("|", ";", name);
if (ropts.isTrace) {
ropts.errStream.printf("\t[TRACE] Split control bits are: \n");
for (String bit : bits) {
ropts.errStream.printf("%s, ", bit);
}
ropts.errStream.println();
}
for (String bit : bits) {
String bitHead = bit.toUpperCase();
String bitBody = bit;
String[] bots = StringUtils.escapeSplit("|", "/", bit);
if (bots.length > 1) {
bitHead = bots[0];
bitBody = bots[1];
}
switch (bitHead) {
case "PRIORITY":
case "PRIOR":
case "P":
try {
int tmp = Integer.parseInt(bitBody);
ropts.defPrior = tmp;
} catch (NumberFormatException nfex) {
String errMsg = String.format("'%s' is not a valid priority (must be an integer)",
bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
}
break;
case "STAGE":
case "S":
try {
int tmpStage = Integer.parseInt(bitBody);
if (tmpStage < 0) {
String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)",
bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
break;
}
ropts.defStage = tmpStage;
} catch (NumberFormatException nfex) {
String errMsg = String.format("'%s' is not a valid stage (must be a positive integer)",
bitBody);
errs.add(new ReplError(lno, pno, errMsg, name));
}
break;
case "MULTITRUE":
case "MULTIT":
case "MT":
ropts.defMulti = true;
break;
case "MULTIFALSE":
case "MULTIF":
case "MF":
ropts.defMulti = false;
break;
case "MULTI":
case "M":
ropts.defMulti = Boolean.parseBoolean(bitBody);
break;
case "INTERNAL":
case "INT":
case "I":
ropts.defStatus = StageStatus.INTERNAL;
break;
case "EXTERNAL":
case "EXT":
case "E":
ropts.defStatus = StageStatus.EXTERNAL;
break;
case "BOTH":
case "B":
ropts.defStatus = StageStatus.BOTH;
break;
case "DEBUGTRUE":
case "DEBUGT":
case "DT":
ropts.isDebug = true;
break;
case "DEBUGFALSE":
case "DEBUGF":
case "DF":
ropts.isDebug = false;
break;
case "DEBUG":
case "D":
ropts.isDebug = Boolean.parseBoolean(bitBody);
break;
case "TRACETRUE":
case "TRACET":
case "TT":
ropts.isTrace = true;
break;
case "TRACEFALSE":
case "TRACEF":
case "TF":
ropts.isTrace = false;
break;
case "TRACE":
case "T":
ropts.isTrace = Boolean.parseBoolean(bitBody);
break;
case "PERFTRUE":
case "PERFT":
case "PRFT":
ropts.isPerf = true;
break;
case "PERFFALSE":
case "PERFF":
case "PRFF":
ropts.isPerf = false;
break;
case "PERF":
case "PRF":
ropts.isPerf = Boolean.parseBoolean(bitBody);
break;
default:
{
String msg = String.format("Invalid global control name '%s'", bitHead);
ReplError err = new ReplError(lno, pno, msg, name);
errs.add(err);
}
break;
}
if (ropts.isTrace)
ropts.errStream.printf("\t[TRACE] Processed global control '%s':'%s'\n",
bitHead, bitBody);
}
return;
}
private static ControlledString getControls(String lne, List<ReplError> errs,
ReplOpts ropts, IntHolder lno, IntHolder pno, String type) {
if (!lne.startsWith("//")) {
return new ControlledString(lne);
}
String tmp = lne.substring(2);
String[] bits = StringUtils.escapeSplit("|", "//", lne);
if (bits.length < 2) {
String msg = "Did not find control terminator (//) in %s where it should be";
msg = String.format(msg, type);
ReplError re = new ReplError(lno, pno, msg, lne);
errs.add(re);
return null;
}
ControlledString cs = new ControlledString(bits[0]);
bits = StringUtils.escapeSplit("|", ";", bits[1]);
cs.controls = new Control[bits.length];
for (int i = 0; i < bits.length; i++) {
String bit = bits[i];
String[] bots = StringUtils.escapeSplit("|", "/", bit);
Control cont = new Control(bots[0]);
if (cont.name.length() > 1) {
cont.name = cont.name.toUpperCase();
}
if (bots.length > 1) {
cont.args = new String[bots.length - 1];
for (int j = 1; j < bots.length; j++) {
cont.args[j - 1] = bots[j];
}
}
cs.controls[i] = cont;
}
return cs;
}
}