diff options
| author | bculkin2442 <bjculkin@mix.wvu.edu> | 2019-06-18 17:44:32 -0400 |
|---|---|---|
| committer | bculkin2442 <bjculkin@mix.wvu.edu> | 2019-06-18 17:44:32 -0400 |
| commit | d86cbcbabc4b251956bd2c5bf4dfa459a00bb239 (patch) | |
| tree | 082ad1a1aa76d2519a3226bbaf2f190f6aa931a9 | |
| parent | 1e01c5df99434be2e44bcac1d6c79486082935b1 (diff) | |
Lots of frontend work
| -rw-r--r-- | src/changes/changes.xml | 3 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/Everge.java | 417 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/IntHolder.java | 66 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/ReplError.java | 30 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/ReplOpts.java | 63 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/ReplPair.java | 641 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/ReplParseException.java | 58 | ||||
| -rw-r--r-- | src/main/java/bjc/everge/StringUtils.java | 65 | ||||
| -rw-r--r-- | src/test/java/bjc/everge/EvergeTest.java | 71 | ||||
| -rw-r--r-- | src/test/java/bjc/everge/StringUtilsTest.java | 44 | ||||
| -rw-r--r-- | src/test/java/bjc/everge/TestUtils.java | 115 |
11 files changed, 1291 insertions, 282 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f5bc768..b084b81 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -9,6 +9,9 @@ <action dev="Ben Culkin" type="add"> Initial release :) </action> + <action dev="Ben Culkin" type="add"> + Added a basic CLI front-end + </action> </release> </body> </document> diff --git a/src/main/java/bjc/everge/Everge.java b/src/main/java/bjc/everge/Everge.java index 257b242..9b5a235 100644 --- a/src/main/java/bjc/everge/Everge.java +++ b/src/main/java/bjc/everge/Everge.java @@ -1,5 +1,30 @@ package bjc.everge; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; + +import java.nio.charset.Charset; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + /** * Everge front-end application. * @@ -7,12 +32,404 @@ package bjc.everge; */ public class Everge { /** + * Details how we handle our input. + */ + public static enum InputStatus { + /** + * Process the input as a single string. + */ + ALL, + /** + * Process the input line-by-line. + */ + LINE, + /** + * Process the input, splitting it around occurances of a regex. + */ + REGEX; + } + + // Options for doing repl-pairs + private ReplOpts ropts = new ReplOpts(); + + // Loaded repl-pairs + private List<ReplPair> lrp = new ArrayList<>(); + + // Input status + private InputStatus inputStat = InputStatus.ALL; + + // Are we processing CLI args? (haven't seen a -- yet) + private boolean doingArgs = true; + + // Should an NL be printed after each replace? + private boolean printNL = true; + + // Verbosity level + private int verbosity = 0; + + // The pattern to use for REGEX input mode + private String pattern; + + // The queue of arguments to process + private Deque<String> argQue = new LinkedList<>(); + + // Used to prevent inter-mixing argument alterations with input processing. + private ReadWriteLock argLock = new ReentrantReadWriteLock(); + + // Input/output streams + public PrintStream outStream = System.out; + public PrintStream errStream = System.err; + + /** * Main method for front end, * * @param args * The CLI arguments. */ public static void main(String[] args) { + Everge evg = new Everge(); + + evg.processArgs(args); + } + + /** + * Process one or more command line arguments. + * + * @param args + * The arguments to process. + * @return Whether we processed succesfully or not. + */ + public boolean processArgs(String... args) { + List<String> errs = new ArrayList<>(); + + boolean stat = processArgs(errs, args); + if (!stat) { + for (String err : errs) { + errStream.println(err); + } + } + + return stat; + } + + /** + * Process one or more command line arguments. + * + * @param args + * The arguments to process. + * @param errs + * The list to stash errors in. + * @return Whether we processed succesfully or not. + */ + public boolean processArgs(List<String> errs, String... args) { + argLock.writeLock().lock(); + + boolean retStat = true; + + try { + loadQueue(args); + + // Process CLI args + while(argQue.size() > 0) { + String arg = argQue.pop(); + + if (arg.equals("--")) { + doingArgs = false; + continue; + } + + // Process an argument + if (doingArgs && arg.startsWith("-")) { + String argName = arg; + String argBody = ""; + + // Process arguments to arguments + int idx = arg.indexOf("="); + if (idx != -1) { + argName = arg.substring(0, idx); + argBody = arg.substring(idx + 1); + } + + switch (argName) { + case "-n": + case "--newline": + printNL = true; + break; + case "-N": + case "--no-newline": + printNL = false; + break; + case "-v": + case "--verbose": + verbosity += 1; + break; + case "-q": + case "--quiet": + verbosity -= 1; + break; + case "--verbosity": + if (argQue.size() < 1) { + errs.add("[ERROR] No parameter to --verbosity"); + retStat = false; + break; + } + argBody = argQue.pop(); + break; + case "-V": + try { + verbosity = Integer.parseInt(argBody); + } catch (NumberFormatException nfex) { + String msg = String.format("[ERROR] Invalid verbosity: '%s' is not an integer", + argBody); + errs.add(msg); + retStat = false; + } + break; + case "--pattern": + if (argQue.size() < 1) { + errs.add("[ERROR] No parameter to --pattern"); + retStat = false; + break; + } + argBody = argQue.pop(); + case "-p": + try { + pattern = argBody; + + Pattern.compile(argBody); + } catch (PatternSyntaxException psex) { + String msg = String.format("[ERROR] Pattern '%s' is invalid: %s", + pattern, psex.getMessage()); + errs.add(msg); + retStat = false; + } + break; + case "--file": + if (argQue.size() < 1) { + errs.add("[ERROR] No argument to --file"); + retStat = false; + break; + } + argBody = argQue.pop(); + case "-f": + try (FileInputStream fis = new FileInputStream(argBody); + Scanner scn = new Scanner(fis)) { + List<ReplError> ferrs = new ArrayList<>(); + + lrp = ReplPair.readList(lrp, scn, ferrs, ropts); + + if (ferrs.size() > 0) { + StringBuilder sb = new StringBuilder(); + + String errString = "an error"; + if (ferrs.size() > 1) errString = String.format("%d errors"); + + { + String msg = String.format( + "[ERROR] Encountered errors parsing data file'%s'\n", + argBody); + sb.append(msg); + } + + for (ReplError err : ferrs) { + sb.append(String.format("\t%s\n", err)); + } + + errs.add(sb.toString()); + retStat = false; + } + } catch (FileNotFoundException fnfex) { + String msg = String.format("[ERROR] Could not open data file '%s' for input", + argBody); + errs.add(msg); + retStat = false; + } catch (IOException ioex) { + String msg = String.format("[ERROR] Unknown I/O error reading data file '%s': %s", + argBody, ioex.getMessage()); + errs.add(msg); + retStat = false; + } + break; + case "--arg-file": + if (argQue.size() < 1) { + errs.add("[ERROR] No argument to --arg-file"); + break; + } + argBody = argQue.pop(); + case "-F": + try (FileInputStream fis = new FileInputStream(argBody); + Scanner scn = new Scanner(fis)) { + List<String> sl = new ArrayList<>(); + + while (scn.hasNextLine()) { + String ln = scn.nextLine().trim(); + + if (ln.equals("")) continue; + if (ln.startsWith("#")) continue; + + sl.add(ln); + } + + processArgs(sl.toArray(new String[0])); + } catch (FileNotFoundException fnfex) { + String msg = String.format("[ERROR] Could not open argument file '%s' for input", argBody); + errs.add(msg); + retStat = false; + } catch (IOException ioex) { + String msg = String.format("[ERROR] Unknown I/O error reading input file '%s': %s", + argBody, ioex.getMessage()); + errs.add(msg); + retStat = false; + } + break; + default: + { + String msg = String.format("[ERROR] Unrecognised CLI argument name '%s'\n", argName); + errs.add(msg); + retStat = false; + } + } + } else { + // Strip off an escaped initial dash + if (arg.startsWith("\\-")) arg = arg.substring(1); + + processInputFile(arg); + } + } + } finally { + argLock.writeLock().unlock(); + } + + return retStat; + } + + /** + * Process a input file. + * + * @param fle + * Input file to process. + * @return Whether we processed succesfully or not. + */ + public boolean processInputFile(String fle) { + List<String> errs = new ArrayList<>(); + + boolean stat = processInputFile(errs, fle); + if (!stat) { + for (String err : errs) { + errStream.println(err); + } + } + + return stat; + } + + /** + * Process a input file. + * + * @param fle + * Input file to process. + * @param errs + * List to accumulate errors in. + * @return Whether we processed succesfully or not. + */ + public boolean processInputFile(List<String> errs, String fle) { + argLock.readLock().lock(); + + // Read in and do replacements on a file + try { + if (verbosity > 2) { + errStream.printf("[TRACE] Reading file (%s) in mode (%s)\n", fle, inputStat); + } + + if (inputStat == InputStatus.ALL) { + Path pth = Paths.get(fle); + + if (!Files.isReadable(pth)) { + String msg = String.format("[ERROR] File '%s' is not readable\n", fle); + errs.add(msg); + return false; + } else { + byte[] inp = Files.readAllBytes(pth); + + String strang = new String(inp, Charset.forName("UTF-8")); + + processString(strang); + } + } else if (inputStat == InputStatus.LINE) { + try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) { + while(scn.hasNextLine()) { + processString(scn.nextLine()); + } + } + } else if (inputStat == InputStatus.REGEX) { + try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) { + scn.useDelimiter(pattern); + + while(scn.hasNext()) { + processString(scn.next()); + } + } + } else { + String msg = String.format("[INTERNAL-ERROR] Input status '%s' is not yet implemented\n", + inputStat); + errs.add(msg); + return false; + } + } catch (IOException ioex) { + String msg = String.format("[ERROR] Unknown I/O related error for file '%s'\n\tError was %s", + fle, ioex.getMessage()); + errs.add(msg); + return false; + } finally { + argLock.readLock().unlock(); + } + + return true; + } + + /** + * Process an input string. + * + * @param inp + * The input string to process. + */ + public void processString(String inp) { + argLock.readLock().lock(); + + try { + String strang = inp; + + for (ReplPair rp : lrp) { + strang = rp.apply(strang); + } + + outStream.print(strang); + if (printNL) outStream.println(); + } finally { + argLock.readLock().unlock(); + } + } + + // Load arguments into the argument queue. + private void loadQueue(String... args) { + boolean doArgs = true; + for (String arg : args) { + if (arg.equals("--")) doArgs = false; + // Handle things like -nNv correctly + if (doArgs) { + if (arg.startsWith("-") && !arg.startsWith("--")) { + char[] car = arg.substring(1).toCharArray(); + for (char c : car) { + String argstr = String.format("-%c", c); + argQue.add(argstr); + } + } else { + argQue.add(arg); + } + } else { + argQue.add(arg); + } + } } } diff --git a/src/main/java/bjc/everge/IntHolder.java b/src/main/java/bjc/everge/IntHolder.java new file mode 100644 index 0000000..e137d61 --- /dev/null +++ b/src/main/java/bjc/everge/IntHolder.java @@ -0,0 +1,66 @@ +package bjc.everge; + +/** + * Utility class for ints by ref. + * + * @author Ben Culkin + */ +public class IntHolder { + /** + * The int value. + */ + public int val; + + /** + * Create a new int-holder set to 0. + */ + public IntHolder() { + val = 0; + } + + /** + * Create a new int-holder set to a value. + * + * @param i + * The value to set the int to. + */ + public IntHolder(int i) { + val = i; + } + + /** + * Increment the value by one, and return it. + * + * @return The value of the holder. + */ + public int incr() { + return incr(1); + } + + /** + * Increment the value by an amount and return it. + * + * @param i + * The amount to increment by. + * + * @return The value of the holder. + */ + public int incr(int i) { + val += 1; + + return val; + } + + /** + * Get the value. + * + * @return The value. + */ + public int get() { + return val; + } + + public void set(int i) { + val = i; + } +} diff --git a/src/main/java/bjc/everge/ReplError.java b/src/main/java/bjc/everge/ReplError.java index e5a4dd0..6e58539 100644 --- a/src/main/java/bjc/everge/ReplError.java +++ b/src/main/java/bjc/everge/ReplError.java @@ -36,6 +36,22 @@ public class ReplError { * @param txt * The text that caused the error. */ + public ReplError(IntHolder lne, IntHolder nPairs, String msg, String txt) { + this(lne.get(), nPairs.get(), msg, txt); + } + + /** + * Create a new ReplPair parse error. + * + * @param lne + * The line the error occured on. + * @param nPairs + * The number of pairs processed up to this point. + * @param msg + * The message detailing the error. + * @param txt + * The text that caused the error. + */ public ReplError(int lne, int nPairs, String msg, String txt) { line = lne; numPairs = nPairs; @@ -53,4 +69,18 @@ public class ReplError { return String.format("line %d, pair %d:%s\n\t%s", line, numPairs, msg, errString); } + + public String toPrintString() { + return toPrintString(""); + } + + public String toPrintString(String hdr) { + String errString; + if (txt == null) errString = "No associated line"; + else if (txt.equals("")) errString = "Text of line was empty"; + else errString = "Text of line was: " + txt; + + return String.format("[ERROR] line %d, pair %d: %s\n%s\tContext: %s", + line, numPairs, msg, hdr, errString); + } } diff --git a/src/main/java/bjc/everge/ReplOpts.java b/src/main/java/bjc/everge/ReplOpts.java index aa836b0..1ddfc79 100644 --- a/src/main/java/bjc/everge/ReplOpts.java +++ b/src/main/java/bjc/everge/ReplOpts.java @@ -1,5 +1,7 @@ package bjc.everge; +import java.io.PrintStream; + /** * Options for processing ReplPairs. * @@ -9,61 +11,60 @@ public class ReplOpts { /** * The default priority. */ - public int defPrior; + public int defPrior = 0; + /** * The default stage. */ - public int defStage; + public int defStage = 0; /** * Whether to process multi-line defns. */ - public boolean defMulti; + public boolean defMulti = false; /** * Default status. */ - public StageStatus defStatus; + public StageStatus defStatus = StageStatus.BOTH; /** * Enable debug info. */ - public boolean isDebug; + public boolean isDebug = true; /** - * Create a default set of options. + * Enable trace info. */ - public ReplOpts() { - defPrior = 0; - defStage = 0; + public boolean isTrace = false; - defMulti = false; + /** + * Enable performance info. + */ + public boolean isPerf = false; - defStatus = StageStatus.BOTH; + public PrintStream outStream = System.out; + public PrintStream errStream = System.err; - isDebug = false; - } + @Override + public boolean equals(Object o) { + if (o == null) return false; - /** - * Create a new set of repl. opts - * - * @param p - * The default priority to use - * @param s - * The default stage to use - * @param m - * Whether to process multi-line defns. - * @param t - * The default status. - */ - public ReplOpts(int p, int s, boolean m, StageStatus t, boolean d) { - defPrior = p; - defStage = s; + if (!getClass().equals(o.getClass())) return false; + + ReplOpts ro = (ReplOpts)o; + + if (isPerf != ro.isPerf) return false; + + if (isDebug != ro.isDebug) return false; + if (isTrace != ro.isTrace) return false; - defMulti = m; + if (defPrior != ro.defPrior) return false; + if (defStage != ro.defStage) return false; + if (defMulti != ro.defMulti) return false; - defStatus = t; + if (defStatus != ro.defStatus) return false; - isDebug = d; + return true; } } diff --git a/src/main/java/bjc/everge/ReplPair.java b/src/main/java/bjc/everge/ReplPair.java index 4f6b2ad..6acc6ad 100644 --- a/src/main/java/bjc/everge/ReplPair.java +++ b/src/main/java/bjc/everge/ReplPair.java @@ -13,6 +13,8 @@ import java.util.function.UnaryOperator; */ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { private int lno; + private int stage; + private StageStatus stat = StageStatus.BOTH; /** * The priority for this replacement. @@ -35,8 +37,6 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { */ public String replace; - private StageStatus stat = StageStatus.BOTH; - /** * Create a new blank replacement pair. */ @@ -136,11 +136,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { List<ReplPair> rplPar = readList(detals, scn, errList); if (errList.size() != 0) { - String errString; - if (errList.size() == 0) errString = "An error"; - else errString = "Errors"; - - throw new IllegalArgumentException(String.format("%s occured parsing replacement pairs:\n%s", errString, errList)); + throw new ReplParseException("", errList); } return rplPar; @@ -165,17 +161,8 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { public static List<ReplPair> readList(List<ReplPair> detals, Scanner scn, List<ReplError> errs, ReplOpts ropts) { - int lno = 0; - int pno = 0; - - int defPrior = ropts.defPrior; - int defStage = ropts.defStage; - - boolean defMulti = ropts.defMulti; - - StageStatus defStatus = ropts.defStatus; - - boolean isDebug = ropts.isDebug; + IntHolder lno = new IntHolder(); + IntHolder pno = new IntHolder(); List<List<ReplPair>> stages = new ArrayList<>(); stages.add(new ArrayList<ReplPair>()); @@ -183,7 +170,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // For every line in the source... while (scn.hasNextLine()) { String name = scn.nextLine().trim(); - lno += 1; + lno.incr(); // If its commented or blank, skip it if (name.equals("")) continue; @@ -191,188 +178,24 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // Global control. Process it. if (name.startsWith("|//")) { - name = name.substring(3); - - // Split out each control - String[] bits = name.split(";"); - - for (String bit : bits) { - String bitHead = bit.toUpperCase(); - String bitBody = bit; - - int idx = bit.indexOf('/'); - if (idx != -1) { - bitHead = bit.substring(0, idx).toUpperCase(); - bitBody = bit.substring(idx + 1); - } - - switch (bitHead) { - case "P": - try { - defPrior = 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 "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; - } - 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 "MT": - defMulti = true; - break; - case "MF": - defMulti = false; - break; - case "M": - defMulti = Boolean.parseBoolean(bitBody); - break; - case "I": - defStatus = StageStatus.INTERNAL; - break; - case "E": - defStatus = StageStatus.EXTERNAL; - break; - case "B": - defStatus = StageStatus.BOTH; - break; - case "DT": - isDebug = true; - break; - case "DF": - isDebug = false; - break; - case "D": - isDebug = Boolean.parseBoolean(bitBody); - break; - default: - errs.add(new ReplError(lno, pno, String.format("Invalid control name '%s'", bitHead), name)); - break; - } - } + readGlobal(name, scn, errs, ropts, lno, pno); continue; } ReplPair rp = new ReplPair(); - rp.priority = defPrior; - rp.stat = defStatus; - rp.lno = lno; - - int stage = defStage; - boolean isMulti = defMulti; - - // Name has attached controls, process them. - if (name.startsWith("//")) { - name = name.substring(2); - - int idx = name.indexOf("//"); - if (idx == -1) { - String msg = "Did not find control terminator (//) in name where it should be"; - - errs.add(new ReplError(lno, pno, msg, name)); - continue; - } + rp.priority = ropts.defPrior; + rp.stat = ropts.defStatus; + rp.lno = lno.get(); + rp.stage = ropts.defStage; - String contName = name.substring(0, idx); - String actName = name.substring(idx + 2); + boolean isMulti = ropts.defMulti; - // Split out each control - String[] bits = contName.split(";"); - - for (String bit : bits) { - String bitHead = bit.toUpperCase(); - String bitBody = bit; - - idx = bit.indexOf('/'); - if (idx != -1) { - bitHead = bit.substring(0, idx).toUpperCase(); - bitBody = bit.substring(idx + 1); - } - - switch (bitHead) { - case "N": - rp.name = bitBody; - break; - 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 "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; - } - 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 "MT": - isMulti = true; - break; - case "MF": - isMulti = false; - break; - case "M": - isMulti = Boolean.parseBoolean(bitBody); - break; - case "I": - rp.stat = StageStatus.INTERNAL; - break; - case "E": - rp.stat = StageStatus.EXTERNAL; - break; - case "B": - rp.stat = StageStatus.BOTH; - break; - default: - errs.add(new ReplError(lno, pno, String.format("Unknown control name '%s'", bitHead), name)); - break; - } - } - - // Multi-line name with a trailer - if (isMulti) { - String tmp = actName; - - while (tmp.endsWith("\\")) { - boolean incNL = tmp.endsWith("|\\"); - - if (!scn.hasNextLine()) break; - - tmp = scn.nextLine().trim(); - - if (tmp.equals("")) continue; - if (tmp.startsWith("#")) continue; - - actName = String.format("%s%s%s", actName, incNL ? "\n" : "", tmp); - } - } - - name = actName; + { + String tmpName = readName(name, scn, errs, rp, ropts, lno, pno); + if (tmpName == null) continue; + name = tmpName; } rp.find = name; @@ -380,60 +203,67 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // We started to process the pair, mark it as being // started - pno += 1; + pno.incr(); String body = null; // Read in the next uncommented line do { - if (!scn.hasNextLine()) { - String msg = - "Ran out of input looking for replacement body for raw name " + name; - - errs.add(new ReplError(lno, pno, msg, null)); - break; - } + if (!scn.hasNextLine()) break; body = scn.nextLine().trim(); - lno += 1; + lno.incr(); } while (body.startsWith("#")); - isMulti = defMulti; + 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); - int idx = body.indexOf("//"); - if (idx == -1) { + 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 = body.substring(0, idx); - String actBody = body.substring(idx + 2); + String contBody = bodyBits[0]; + String actBody = bodyBits[1]; // Split out each control - String[] bits = actBody.split(";"); + String[] bits = StringUtils.escapeSplit("|", ";", actBody); for (String bit : bits) { String bitHead = bit.toUpperCase(); String bitBody = bit; - idx = bit.indexOf('/'); - if (idx != -1) { - bitHead = bit.substring(0, idx).toUpperCase(); - bitBody = bit.substring(idx + 1); + 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; @@ -443,48 +273,37 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { } } - // Multi-line name with a trailer - if (isMulti) { - String tmp = actBody; - - while (tmp.endsWith("\\")) { - boolean incNL = tmp.endsWith("|\\"); - - if (!scn.hasNextLine()) break; - - tmp = scn.nextLine().trim(); - - if (tmp.startsWith("#")) continue; - - actBody = String.format("%s%s%s", actBody, incNL ? "\n" : "", tmp); - } - } - 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 (stage == 0 || stages.size() < (stage - 1)) { - stageList = stages.get(stage); + if (rp.stage == 0 || stages.size() < (rp.stage - 1)) { + stageList = stages.get(rp.stage); if (stageList == null) { stageList = new ArrayList<>(); - stages.add(stage, stageList); + stages.add(rp.stage, stageList); } } else { - for (int i = stages.size(); i <= stage; i++) { + for (int i = stages.size(); i <= rp.stage; i++) { stages.add(new ArrayList<>()); } - stageList = stages.get(stage); + stageList = stages.get(rp.stage); } - if (isDebug) { - System.err.printf("\t[DEBUG] Stage %d: Added %s\n\t\tContents: %s\n", - stage, rp, stageList); + if (ropts.isTrace) { + ropts.errStream.printf("\t[DEBUG] Stage %d: Added %s\n\t\tContents: %s\n", + rp.stage, rp, stageList); } stageList.add(rp); @@ -492,11 +311,11 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // Special-case one-stage processing. if (stages.size() == 1) { - if (isDebug) System.err.printf("\t[DEBUG] Executing single-stage bypass\n"); + 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 (isDebug) System.err.printf("\t[DEBUG] Excluding internal RP %s\n", rp); + if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluding internal RP %s\n", rp); continue; } @@ -513,14 +332,14 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { List<ReplPair> tmpList = new ArrayList<>(); tmpList.addAll(detals); - if (isDebug) System.err.printf("\t[DEBUG] Stages: %s\n", stages); + 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 (isDebug) System.err.printf("\t[DEBUG] Staging stage %d of %d: %s\n", + if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Staging stage %d of %d: %s\n", procStg, stageList.size(), stageList); for (ReplPair rp : stageList) { @@ -529,8 +348,8 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { for (ReplPair curPar : tmpList) { String tmp = rp.replace.replaceAll(curPar.find, curPar.replace); - if (isDebug && !rp.replace.equals(tmp)) { - System.err.printf("\t[DEBUG] Staged '%s' -> '%s'\t%s\n", + if (ropts.isTrace && !rp.replace.equals(tmp)) { + ropts.errStream.printf("\t[DEBUG] Staged '%s' -> '%s'\t%s\n", rp.replace, tmp, curPar); } @@ -539,15 +358,15 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // If we're external; add straight to the output if (rp.stat == StageStatus.EXTERNAL) { - if (isDebug) { - System.err.printf("\t[DEBUG] Skipped external for staging: %s\n", + if (ropts.isTrace) { + ropts.errStream.printf("\t[DEBUG] Skipped external for staging: %s\n", rp); } detals.add(rp); } else { - if (isDebug) { - System.err.printf("\t[DEBUG] Added to stage %d: %s\n\t\tContents: %s\n", + if (ropts.isTrace) { + ropts.errStream.printf("\t[DEBUG] Added to stage %d: %s\n\t\tContents: %s\n", procStg, rp, curStage); } @@ -562,7 +381,7 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { // Copy over to output, excluding internals for (ReplPair rp : tmpList) { if (rp.stat == StageStatus.INTERNAL) { - if (isDebug) System.err.printf("\t[DEBUG] Excluded internal: %s\n", rp); + if (ropts.isTrace) ropts.errStream.printf("\t[DEBUG] Excluded internal: %s\n", rp); continue; } @@ -572,13 +391,51 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { detals.sort(null); - if (isDebug) { - System.err.printf("\t[DEBUG] Final output: %s\n", detals); + 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); @@ -592,11 +449,293 @@ public class ReplPair implements Comparable<ReplPair>, UnaryOperator<String> { 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; + } } diff --git a/src/main/java/bjc/everge/ReplParseException.java b/src/main/java/bjc/everge/ReplParseException.java new file mode 100644 index 0000000..0091b83 --- /dev/null +++ b/src/main/java/bjc/everge/ReplParseException.java @@ -0,0 +1,58 @@ +package bjc.everge; + +import java.util.ArrayList; +import java.util.List; + +public class ReplParseException extends RuntimeException { + public List<ReplError> errs; + + public ReplParseException(String msg) { + this(msg, new ArrayList<>()); + } + + public ReplParseException(String msg, List<ReplError> errs) { + super(msg); + + this.errs = errs; + } + + @Override + public String toString() { + String errString; + if (errs.size() == 0) errString = "An error"; + else errString = "Errors"; + + return String.format("%s occured parsing replacement pairs: %s\n%s", + errString, getMessage(), errs); + } + + public String toPrintString() { + StringBuilder errString = new StringBuilder("[ERROR] "); + + if (errs.size() == 0) { + errString.append("No specific errors"); + } else if (errs.size() == 1) { + errString.append("An error"); + } else { + errString.append(errs.size()); + errString.append(" errors"); + } + + errString.append(" occured parsing replacement pairs:"); + if (!getMessage().equals("")) { + errString.append(" "); + errString.append(getMessage()); + } + + if (errs.size() > 0) { + errString.append("\n\t"); + + for (ReplError err : errs) { + errString.append(err.toPrintString("\t")); + errString.append("\n\t"); + } + } + + return errString.toString().trim(); + } +} diff --git a/src/main/java/bjc/everge/StringUtils.java b/src/main/java/bjc/everge/StringUtils.java new file mode 100644 index 0000000..3f19b8f --- /dev/null +++ b/src/main/java/bjc/everge/StringUtils.java @@ -0,0 +1,65 @@ +package bjc.everge; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for strings. + * + * @author Ben Culkin. + */ +public class StringUtils { + /** + * Split a string on every occurance of a string not preceeded by an escape. + * + * @param escape + * The escape that stops splitting. + * @param splat + * The string to split on. + * @param inp + * The string to split. + * @return The string split as specified above. + */ + public static String[] escapeSplit(String escape, String splat, String inp) { + if (escape == null || escape.equals("")) { + return inp.split(splat); + } + + List<String> ret = new ArrayList<>(); + + String wrk = inp; + int idx = wrk.indexOf(splat); + + // System.err.printf("[DEBUG] 'hard' escapeSplit: (%s) (%s) (%s) init: %d\n", + // escape, splat, inp, idx); + + while (idx != -1) { + while (idx != -1 && wrk.regionMatches(idx - 1, escape, 0, escape.length())) { + int oidx = wrk.indexOf(splat, idx + 1); + // System.err.printf("[TRACE] idx: %d, oidx: %d\n", idx, oidx); + idx = oidx; + } + + if (idx == -1) { + break; + } + + // System.err.printf("[TRACE] sliced string into (%s) and (%s) at %d\n", + // wrk.substring(0, idx), wrk.substring(idx), idx); + String tmp = wrk.substring(0, idx); + ret.add(tmp); + if (wrk.endsWith(tmp)) { + wrk = ""; + } else { + wrk = wrk.substring(idx + splat.length()); + } + + idx = wrk.indexOf(splat); + } + + if (!wrk.equals("")) ret.add(wrk); + + return ret.toArray(new String[0]); + } +} diff --git a/src/test/java/bjc/everge/EvergeTest.java b/src/test/java/bjc/everge/EvergeTest.java new file mode 100644 index 0000000..109992a --- /dev/null +++ b/src/test/java/bjc/everge/EvergeTest.java @@ -0,0 +1,71 @@ +package bjc.everge; + +import bjc.everge.TestUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import static bjc.everge.TestUtils.*; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Unit tests for Everge front-end. + * + * @author Ben Culkin. + */ +public class EvergeTest { + @Test + public void testConstruct() { + Everge evg = new Everge(); + + assertTrue(true); + } + + @Test + public void testArgs() { + Everge evg = new Everge(); + + List<String> errs = new ArrayList<>(); + + boolean stat = evg.processArgs(errs, "-v"); + if (!stat) { + for (String err : errs) { + System.err.println(err); + } + + assertTrue(false); + } + } + + @Test + public void testLoad() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + Everge evg = new Everge(); + evg.outStream = new PrintStream(baos); + + List<String> errs = new ArrayList<>(); + boolean stat = evg.processArgs(errs, "-vv", "--file", "data/test/evg-test1.rp", + "data/test/evg-test1.inp"); + if (!stat) { + System.err.println("[ERROR] Did not succesfully process args"); + for (String err : errs) { + System.err.println(err); + } + + assertTrue(false); + } + + String outp = baos.toString().trim(); + assertEquals("b\nb", outp); + } +} diff --git a/src/test/java/bjc/everge/StringUtilsTest.java b/src/test/java/bjc/everge/StringUtilsTest.java new file mode 100644 index 0000000..9edc028 --- /dev/null +++ b/src/test/java/bjc/everge/StringUtilsTest.java @@ -0,0 +1,44 @@ +package bjc.everge; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class StringUtilsTest { + @Test + public void testNullSplit() { + assertSplitsTo("a", null, " ", "a"); + assertSplitsTo("a b", null, " ", "a", "b"); + assertSplitsTo("a b cd", null, " ", "a", "b", "cd"); + } + + @Test + public void testNoEscapeSplit() { + assertSplitsTo("a", "/", " ", "a"); + assertSplitsTo("a b", "/", " ", "a", "b"); + assertSplitsTo("a b/c", "/", " ", "a", "b/c"); + } + + @Test + public void testEscapeSplit() { + assertSplitsTo("a / b/c", "/", " ", "a", "/ ", "b/c"); + } + + private void assertSplitsTo(String inp, String esc, String splat, String... right) { + try { + String[] lst = StringUtils.escapeSplit(esc, splat, inp); + + assertArrayEquals(right, lst); + } catch (Exception ex) { + System.err.println("EXCEPTION"); + ex.printStackTrace(); + System.err.println(); + + assertTrue(false); + } + } +} diff --git a/src/test/java/bjc/everge/TestUtils.java b/src/test/java/bjc/everge/TestUtils.java new file mode 100644 index 0000000..2b9dc8d --- /dev/null +++ b/src/test/java/bjc/everge/TestUtils.java @@ -0,0 +1,115 @@ +package bjc.everge; + +import java.io.FileInputStream; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import static org.junit.Assert.*; + +/** + * Utility methods for testing. + * + * @author Ben Culkin + */ +public class TestUtils { + /** + * Assert that a ReplParseException is thrown with a given message. + * + * @param msg + * The message. + * @param fle + * The file to load input from. + */ + public static void assertThrownMessage(String msg, String fle) { + assertThrownMessage(false, msg, fle); + } + + /** + * Assert that a ReplParseException is thrown with a given message. + * + * @param logMsg + * Log the exception message. + * @param msg + * The message. + * @param fle + * The file to load input from. + */ + public static void assertThrownMessage(boolean logMsg, String msg, String fle) { + try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) { + ReplPair.readList(new ArrayList<>(), scn); + + assertTrue(false); + } catch (ReplParseException rpex) { + if (logMsg) System.err.println(rpex.toPrintString()); + + assertEquals(msg, rpex.toPrintString()); + } catch (Exception ex) { + System.err.println("EXCEPTION"); + ex.printStackTrace(); + + assertTrue(false); + + return; + } + } + + public static void assertMultiReplace(String fle, String... inps) { + assertMultiReplace(false, fle, inps); + } + + public static void assertMultiReplace(boolean logRep, String fle, String... inps) { + if (inps.length < 2) throw new IllegalArgumentException("ERROR: Must provide at least two strings to assertMultiReplace"); + if (inps.length % 2 != 0) throw new IllegalArgumentException("ERROR: Odd number of strings passed to assertMultiReplace"); + + List<ReplPair> lrp = null; + + try (FileInputStream fis = new FileInputStream(fle); Scanner scn = new Scanner(fis)) { + lrp = ReplPair.readList(scn); + } catch (ReplParseException rpex) { + System.err.println(rpex.toPrintString()); + + assertTrue(false); + } catch (Exception ex) { + System.err.println("EXCEPTION"); + ex.printStackTrace(); + + assertTrue(false); + + return; + } + + for (int i = 0; i < inps.length; i += 2) { + String right = inps[i]; + String inp = inps[i + 1]; + + assertReplacesTo(logRep, right, lrp, inp); + } + } + + public static void assertReplacesFrom(String right, String inp, String fle) { + assertMultiReplace(fle, right, inp); + } + + public static void assertReplacesTo(String right, List<ReplPair> rps, String inp) { + assertReplacesTo(false, right, rps, inp); + } + + public static void assertReplacesTo(boolean logRep, String right, List<ReplPair> rps, String inp) { + if (logRep) System.err.printf("\t[LOG] Checking '%s' -> '%s'\n", inp, right); + + String tmp = inp; + + for (ReplPair rp : rps) { + String oldTmp = tmp; + + tmp = rp.apply(tmp); + + if (logRep) System.err.printf("\t[LOG] '%s' -> '%s'\t%s\n", oldTmp, tmp, rp); + } + + assertEquals(right, tmp); + } +} |
