summaryrefslogtreecommitdiff
path: root/clformat/src/main/java
diff options
context:
space:
mode:
authorBenjamin J. Culkin <bjculkin@mix.wvu.edu>2018-09-16 21:28:42 -0300
committerBenjamin J. Culkin <bjculkin@mix.wvu.edu>2018-09-16 21:31:19 -0300
commit14c444db5bc8dac003cd31283de6484264377004 (patch)
treee277e4e781dd93f6d8a29cd2f30b38fc5a43385e /clformat/src/main/java
parent2f96f49c2d2c8679841c790e9dd7d9f1b6f3fed1 (diff)
Move CL format to submodule
Diffstat (limited to 'clformat/src/main/java')
-rw-r--r--clformat/src/main/java/bjc/App.java13
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/CLFormatter.java282
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/CLModifiers.java66
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/CLParameters.java224
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/EscapeException.java33
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/AestheticDirective.java74
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/CaseDirective.java117
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/CharacterDirective.java45
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/ConditionalDirective.java193
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/Directive.java72
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/EscapeDirective.java59
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/FreshlineDirective.java34
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/GeneralNumberDirective.java55
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/GotoDirective.java47
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/IterationDirective.java171
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/LiteralDirective.java51
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/NumberDirective.java57
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/RadixDirective.java55
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/RecursiveDirective.java49
-rw-r--r--clformat/src/main/java/bjc/utils/ioutils/format/directives/TabulateDirective.java80
20 files changed, 1777 insertions, 0 deletions
diff --git a/clformat/src/main/java/bjc/App.java b/clformat/src/main/java/bjc/App.java
new file mode 100644
index 0000000..ff1c4f9
--- /dev/null
+++ b/clformat/src/main/java/bjc/App.java
@@ -0,0 +1,13 @@
+package bjc;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/CLFormatter.java b/clformat/src/main/java/bjc/utils/ioutils/format/CLFormatter.java
new file mode 100644
index 0000000..9e01ca6
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/CLFormatter.java
@@ -0,0 +1,282 @@
+package bjc.utils.ioutils.format;
+
+import bjc.utils.esodata.SingleTape;
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.directives.*;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UnknownFormatConversionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static bjc.utils.misc.PropertyDB.applyFormat;
+import static bjc.utils.misc.PropertyDB.getRegex;
+/**
+ * An implementation of CL's FORMAT.
+ *
+ * @author EVE
+ *
+ */
+public class CLFormatter {
+ private static final String prefixParam = getRegex("clFormatPrefix");
+ private static final String formatMod = getRegex("clFormatModifier");
+ private static final String directiveName = getRegex("clFormatName");
+
+ private static final String prefixList = applyFormat("delimSeparatedList", prefixParam, ",");
+ private static final String formatDirective = applyFormat("clFormatDirective", prefixList, formatMod, directiveName);
+
+ private static final Pattern pFormatDirective = Pattern.compile(formatDirective);
+
+ private static Map<String, Directive> builtinDirectives;
+ private Map<String, Directive> extraDirectives;
+
+ static {
+ builtinDirectives = new HashMap<>();
+
+ builtinDirectives.put("A", new AestheticDirective());
+ // @NOTE 9/6/18
+ //
+ // This is just an alias, not the actual S directive
+ builtinDirectives.put("S", new AestheticDirective());
+
+ builtinDirectives.put("C", new CharacterDirective());
+
+ builtinDirectives.put("B", new NumberDirective(-1, 2, 'B'));
+ builtinDirectives.put("O", new NumberDirective(-1, 8, 'O'));
+ builtinDirectives.put("D", new NumberDirective(-1, 10, 'D'));
+ builtinDirectives.put("X", new NumberDirective(-1, 16, 'X'));
+
+ builtinDirectives.put("R", new RadixDirective());
+
+ builtinDirectives.put("&", new FreshlineDirective());
+
+ builtinDirectives.put("%", new LiteralDirective("\n", '%'));
+ builtinDirectives.put("|", new LiteralDirective("\f", '|'));
+ builtinDirectives.put("~", new LiteralDirective("~", '~'));
+ builtinDirectives.put("?", new RecursiveDirective());
+
+ builtinDirectives.put("*", new GotoDirective());
+
+ builtinDirectives.put("^", new EscapeDirective());
+ builtinDirectives.put("[", new ConditionalDirective());
+ builtinDirectives.put("{", new IterationDirective());
+ builtinDirectives.put("(", new CaseDirective());
+
+ builtinDirectives.put("T", new TabulateDirective());
+ }
+
+ /**
+ * Create a new CL formatter.
+ */
+ public CLFormatter() {
+ extraDirectives = new HashMap<>();
+ }
+
+ /**
+ * Check that an item is valid for a directive.
+ *
+ * @param itm
+ * The item to check.
+ * @param directive
+ * The directive to check for.
+ */
+ public static void checkItem(Object itm, char directive) {
+ if(itm == null) throw new IllegalArgumentException(
+ String.format("No argument provided for %c directive", directive));
+ }
+
+ /**
+ * Format a string in the style of CL's FORMAT.
+ *
+ * @param format
+ * The format string to use.
+ * @param params
+ * The parameters for the string.
+ * @return The formatted string.
+ */
+ public String formatString(String format, Object... params) throws IOException {
+ ReportWriter rw = new ReportWriter(new StringWriter());
+ /* Put the parameters where we can easily handle them. */
+ Tape<Object> tParams = new SingleTape<>(params);
+
+ doFormatString(format, rw, tParams, true);
+
+ return rw.toString();
+ }
+
+ /**
+ * Format a string in the style of CL's FORMAT.
+ *
+ * @param format
+ * The format string to use.
+ * @param params
+ * The parameters for the string.
+ * @return The formatted string.
+ */
+ public String formatString(String format, Iterable<Object> params) throws IOException {
+ ReportWriter rw = new ReportWriter(new StringWriter());
+
+ /* Put the parameters where we can easily handle them. */
+ Tape<Object> tParams = new SingleTape<>(params);
+
+ doFormatString(format, rw, tParams, true);
+
+ return rw.toString();
+ }
+
+ /**
+ * Format a string in the style of CL's FORMAT.
+ *
+ * @param format
+ * The format string to use.
+ * @param params
+ * The parameters for the string.
+ */
+ public void formatString(Writer target, String format, Object... params) throws IOException {
+ ReportWriter rw = new ReportWriter(target);
+ /* Put the parameters where we can easily handle them. */
+ Tape<Object> tParams = new SingleTape<>(params);
+
+ doFormatString(format, rw, tParams, true);
+ }
+
+ /**
+ * Format a string in the style of CL's FORMAT.
+ *
+ * @param format
+ * The format string to use.
+ * @param params
+ * The parameters for the string.
+ * @return The formatted string.
+ */
+ public void formatString(Writer target, String format, Iterable<Object> params) throws IOException {
+ ReportWriter rw = new ReportWriter(target);
+
+ /* Put the parameters where we can easily handle them. */
+ Tape<Object> tParams = new SingleTape<>(params);
+
+ doFormatString(format, rw, tParams, true);
+ }
+
+ /**
+ * Fill in a partially started format string.
+ *
+ * Used mostly for directives that require formatting again with a
+ * different string.
+ *
+ * @param format
+ * The format to use.
+ * @param sb
+ * The buffer to file output into.
+ * @param tParams
+ * The parameters to use.
+ */
+ public void doFormatString(String format, ReportWriter rw, Tape<Object> tParams, boolean isToplevel) throws IOException {
+ Matcher dirMatcher = pFormatDirective.matcher(format);
+
+ // We need this StringBuffer to use appendReplacement and stuff
+ // from Matcher. The fact that for some reason, StringBuffer is
+ // final prevents us from using our own dummy StringBuffer that
+ // auto-flushes to our output stream, so we have to do it
+ // ourselves.
+ StringBuffer sb = new StringBuffer();
+
+ boolean doTail = true;
+ try {
+ while(dirMatcher.find()) {
+ dirMatcher.appendReplacement(sb, "");
+ rw.writeBuffer(sb);
+
+ String dirName = dirMatcher.group("name");
+ String dirFunc = dirMatcher.group("funcname");
+ String dirMods = dirMatcher.group("modifiers");
+ String dirParams = dirMatcher.group("params");
+
+ if(dirMods == null) dirMods = "";
+ if(dirParams == null) dirParams = "";
+
+ CLParameters arrParams = CLParameters.fromDirective(dirParams, tParams);
+
+ CLModifiers mods = CLModifiers.fromString(dirMods);
+
+ Object item = tParams.item();
+
+ if(dirName == null && dirFunc != null) {
+ /*
+ * @TODO implement user-called functions.
+ */
+ continue;
+ }
+
+ if(extraDirectives.containsKey(dirName)) {
+ extraDirectives.get(dirName).format(rw, item, mods, arrParams, tParams, dirMatcher,
+ this);
+
+ continue;
+ }
+
+ if(builtinDirectives.containsKey(dirName)) {
+ // System.err.printf("Executing directive %s (%s) (%d to %d) from string %s\n", dirName, dirMatcher.group(), dirMatcher.start(), dirMatcher.end(), format);
+
+ builtinDirectives.get(dirName).format(rw,
+ item, mods, arrParams, tParams, dirMatcher, this);
+
+ continue;
+ }
+
+ if(dirName == null) dirName = "<null>";
+
+ switch(dirName) {
+ case "]":
+ throw new IllegalArgumentException("Found conditional-end outside of conditional.");
+ case ";":
+ throw new IllegalArgumentException(
+ "Found seperator outside of block.");
+ case "}":
+ throw new IllegalArgumentException("Found iteration-end outside of iteration");
+ case "<":
+ case ">":
+ throw new IllegalArgumentException("Layout-control directives aren't implemented yet.");
+ case "F":
+ case "E":
+ case "G":
+ case "$":
+ /* @TODO implement floating point directives. */
+ throw new IllegalArgumentException("Floating-point directives aren't implemented yet.");
+ case "W":
+ /*
+ * @TODO figure out if we want to implement
+ * someting for these directives instead of
+ * punting.
+ */
+ throw new IllegalArgumentException("S and W aren't implemented. Use A instead");
+ case "P":
+ throw new IllegalArgumentException("These directives aren't implemented yet");
+ case ")":
+ throw new IllegalArgumentException("Case-conversion end outside of case conversion");
+ case "\n":
+ /*
+ * Ignored newline.
+ */
+ break;
+ default:
+ String msg = String.format("Unknown format directive '%s'", dirName);
+ throw new IllegalArgumentException(msg);
+ }
+ }
+ } catch (EscapeException eex) {
+ if (!isToplevel) throw eex;
+
+ doTail = false;
+ }
+
+ if (doTail) dirMatcher.appendTail(sb);
+ rw.writeBuffer(sb);
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/CLModifiers.java b/clformat/src/main/java/bjc/utils/ioutils/format/CLModifiers.java
new file mode 100644
index 0000000..68127b6
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/CLModifiers.java
@@ -0,0 +1,66 @@
+package bjc.utils.ioutils.format;
+
+/**
+ * A collection of the modifiers attached to a CL format directive.
+ *
+ * @author EVE
+ *
+ */
+public class CLModifiers {
+ /**
+ * Whether the at mod is on.
+ */
+ public final boolean atMod;
+ /**
+ * Whether the colon mod is on.
+ */
+ public final boolean colonMod;
+ /**
+ * Whether the dollar mod is on.
+ */
+ public final boolean dollarMod;
+ /**
+ * Whether the star mod is on.
+ */
+ public final boolean starMod;
+
+ /**
+ * Create a new set of CL modifiers.
+ *
+ * @param at
+ * The state of the at mod.
+ * @param colon
+ * The state of the colon mod.
+ * @param dollar
+ * The state of the dollar mod.
+ */
+ public CLModifiers(boolean at, boolean colon, boolean dollar, boolean star) {
+ atMod = at;
+ colonMod = colon;
+ dollarMod = dollar;
+ starMod = star;
+ }
+
+ /**
+ * Create a set of modifiers from a modifier string.
+ *
+ * @param modString
+ * The string to parse modifiers from.
+ * @return A set of modifiers matching the string.
+ */
+ public static CLModifiers fromString(String modString) {
+ boolean atMod = false;
+ boolean colonMod = false;
+ boolean dollarMod = false;
+ boolean starMod = false;
+
+ if(modString != null) {
+ atMod = modString.contains("@");
+ colonMod = modString.contains(":");
+ dollarMod = modString.contains("$");
+ starMod = modString.contains("*");
+ }
+
+ return new CLModifiers(atMod, colonMod, dollarMod, starMod);
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/CLParameters.java b/clformat/src/main/java/bjc/utils/ioutils/format/CLParameters.java
new file mode 100644
index 0000000..bde7a7d
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/CLParameters.java
@@ -0,0 +1,224 @@
+package bjc.utils.ioutils.format;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import bjc.utils.esodata.Tape;
+
+/**
+ * Represents a set of parameters to a CL format directive.
+ *
+ * @author Benjamin Culkin
+ */
+public class CLParameters {
+ private String[] params;
+
+ /**
+ * Create a new set of CL format parameters.
+ *
+ * @param params
+ * The CL format parameters to use.
+ */
+ public CLParameters(String[] params) {
+ this.params = params;
+ }
+
+ /**
+ * Get the length of the parameter list.
+ *
+ * @return The length of the parameters.
+ */
+ public int length() {
+ return params.length;
+ }
+
+ /**
+ * Creates a set of parameters from an array of parameters.
+ *
+ * Mostly, this just fills in V and # parameters.
+ *
+ * @param params
+ * The parameters of the directive.
+ * @param dirParams
+ * The parameters of the format string.
+ *
+ * @return A set of CL parameters.
+ */
+ public static CLParameters fromDirective(String unsplit, Tape<Object> dirParams) {
+ List<String> lParams = new ArrayList<>();
+ StringBuilder currParm = new StringBuilder();
+
+ char prevChar = ' ';
+
+ for (int i = 0; i < unsplit.length(); i++) {
+ char c = unsplit.charAt(i);
+
+ if (c == ',' && prevChar != '\'') {
+ lParams.add(currParm.toString());
+
+ currParm = new StringBuilder();
+ } else {
+ currParm.append(c);
+ }
+
+ prevChar = c;
+ }
+ lParams.add(currParm.toString());
+
+ List<String> parameters = new ArrayList<>();
+
+ if (lParams.size() == 1 && lParams.get(0).equals(""))
+ return new CLParameters(parameters.toArray(new String[0]));
+
+ for(String param : lParams) {
+ if(param.equalsIgnoreCase("V")) {
+ Object par = dirParams.item();
+ boolean succ = dirParams.right();
+
+ if(!succ) {
+ throw new IllegalStateException("Couldn't advance tape for parameter");
+ }
+
+ if(par == null) {
+ throw new IllegalArgumentException(
+ "Expected a format parameter for V inline parameter");
+ }
+
+ if(par instanceof Number) {
+ int val = ((Number) par).intValue();
+
+ parameters.add(Integer.toString(val));
+ } else if(par instanceof Character) {
+ char ch = ((Character) par);
+
+ parameters.add(Character.toString(ch));
+ } else {
+ throw new IllegalArgumentException(
+ "Incorrect type of parameter for V inline parameter");
+ }
+ } else if (param.equals("#")) {
+ parameters.add(Integer.toString(dirParams.size() - dirParams.position()));
+ } else if (param.equals("%")) {
+ parameters.add(Integer.toString(dirParams.position()));
+ } else {
+ parameters.add(param);
+ }
+ }
+
+ return new CLParameters(parameters.toArray(new String[0]));
+ }
+
+ /**
+ * Get an optional character parameter with a default value.
+ *
+ * @param idx
+ * The index the parameter is at.
+ * @param paramName
+ * The name of the parameter.
+ * @param directive
+ * The directive this parameter belongs to.
+ * @param def
+ * The default value for the parameter.
+ * @return The value of the parameter if it exists, or the default
+ * otherwise.
+ */
+ public char getCharDefault(int idx, String paramName, char directive, char def) {
+ if(!params[idx].equals("")) {
+ return getChar(idx, paramName, directive);
+ }
+
+ return def;
+ }
+
+ /**
+ * Get a mandatory character parameter.
+ *
+ * @param idx
+ * The index the parameter is at.
+ * @param paramName
+ * The name of the parameter.
+ * @param directive
+ * The directive this parameter belongs to.
+ * @return The value for the parameter.
+ */
+ public char getChar(int idx, String paramName, char directive) {
+ String param = params[idx];
+
+ if (param.length() == 1) {
+ // Punt in the case we have a slightly malformed issue
+ return param.charAt(0);
+ }
+
+ if(!param.startsWith("'")) {
+ throw new IllegalArgumentException(
+ String.format("Invalid %s \"%s\" to %c directive", paramName, param, directive));
+ }
+
+ return param.charAt(1);
+ }
+
+ /**
+ * Get an optional integer parameter with a default value.
+ *
+ * @param idx
+ * The index the parameter is at.
+ * @param paramName
+ * The name of the parameter.
+ * @param directive
+ * The directive this parameter belongs to.
+ * @param def
+ * The default value for the parameter.
+ * @return The value of the parameter if it exists, or the default
+ * otherwise.
+ */
+ public int getIntDefault(int idx, String paramName, char directive, int def) {
+ if(!params[idx].equals("")) {
+ return getInt(idx, paramName, directive);
+ }
+
+ return def;
+ }
+
+ /**
+ * Get a mandatory integer parameter.
+ *
+ * @param idx
+ * The index the parameter is at.
+ * @param paramName
+ * The name of the parameter.
+ * @param directive
+ * The directive this parameter belongs to.
+ * @return The value for the parameter.
+ */
+ public int getInt(int idx, String paramName, char directive) {
+ String param = params[idx];
+
+ try {
+ return Integer.parseInt(param);
+ } catch(NumberFormatException nfex) {
+ String msg = String.format("Invalid %s \"%s\" to %c directive", paramName, param, directive);
+
+ IllegalArgumentException iaex = new IllegalArgumentException(msg);
+ iaex.initCause(nfex);
+
+ throw iaex;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("[");
+
+ for (int i = 0; i < params.length; i++) {
+ if (i != 0) sb.append(", ");
+
+ sb.append("\"");
+ sb.append(params[i]);
+ sb.append("\"");
+ }
+
+ sb.append("]");
+
+ return sb.toString();
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/EscapeException.java b/clformat/src/main/java/bjc/utils/ioutils/format/EscapeException.java
new file mode 100644
index 0000000..086f1cd
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/EscapeException.java
@@ -0,0 +1,33 @@
+package bjc.utils.ioutils.format;
+
+/**
+ * An exception thrown to escape CL iteration directives.
+ *
+ * @author EVE
+ *
+ */
+public class EscapeException extends RuntimeException {
+ private static final long serialVersionUID = -4552821131068559005L;
+
+ /**
+ * Whether or not this exception should end iteration.
+ */
+ public final boolean endIteration;
+
+ /**
+ * Create a new escape exception.
+ */
+ public EscapeException() {
+ endIteration = false;
+ }
+
+ /**
+ * Create a new escape exception.
+ *
+ * @param end
+ * Whether or not to end the iteration.
+ */
+ public EscapeException(boolean end) {
+ endIteration = end;
+ }
+} \ No newline at end of file
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/AestheticDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/AestheticDirective.java
new file mode 100644
index 0000000..9bad6d7
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/AestheticDirective.java
@@ -0,0 +1,74 @@
+package bjc.utils.ioutils.format.directives;
+
+import java.io.IOException;
+
+import java.util.regex.Matcher;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+/**
+ * Implementation of the A directive.
+ *
+ * @author student
+ *
+ */
+public class AestheticDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ // System.err.printf("Aesthetic directive with item \"%s\" and params: %s\n", item, tParams);
+ CLFormatter.checkItem(item, 'A');
+
+ int mincol = 0, colinc = 1, minpad = 0;
+ char padchar = ' ';
+
+ if (params.length() == 0) {
+ // Zero parameters, use all defaults
+ } else if (params.length() == 1) {
+ mincol = params.getIntDefault(0, "minimum column count", 'A', 0);
+ } else if (params.length() < 4) {
+ throw new IllegalArgumentException("Must provide either zero, one or four arguments to A directive");
+ } else {
+ colinc = params.getIntDefault(1, "padding increment", 'A', 1);
+ minpad = params.getIntDefault(2, "minimum amount of padding", 'A', 0);
+ padchar = params.getCharDefault(3, "padding character", 'A', ' ');
+ }
+
+ StringBuilder work = new StringBuilder();
+
+ if (mods.atMod) {
+ for (int i = 0; i < minpad; i++) {
+ work.append(padchar);
+ }
+
+ for (int i = work.length(); i < mincol; i++) {
+ for (int k = 0; k < colinc; k++) {
+ work.append(padchar);
+ }
+ }
+ }
+
+ work.append(item.toString());
+
+ if (!mods.atMod) {
+ for (int i = 0; i < minpad; i++) {
+ work.append(padchar);
+ }
+
+ for (int i = work.length(); i < mincol; i++) {
+ for (int k = 0; k < colinc; k++) {
+ work.append(padchar);
+ }
+ }
+ }
+
+ rw.write(work.toString());
+
+ tParams.right();
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/CaseDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/CaseDirective.java
new file mode 100644
index 0000000..728bb43
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/CaseDirective.java
@@ -0,0 +1,117 @@
+
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.*;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CaseDirective implements Directive {
+ private static final Pattern wordPattern = Pattern.compile("(\\w+)(\\b*)");
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ StringBuffer condBody = new StringBuffer();
+
+ int nestLevel = 1;
+
+ while (dirMatcher.find()) {
+ /* Process a list of clauses. */
+ String dirName = dirMatcher.group("name");
+
+ if (dirName != null) {
+ /* Append everything up to this directive. */
+ dirMatcher.appendReplacement(condBody, "");
+
+ if (dirName.equals("(")) {
+ if (nestLevel > 0) {
+ condBody.append(dirMatcher.group());
+ }
+
+ nestLevel += 1;
+ } else if (Directive.isOpening(dirName)) {
+ nestLevel += 1;
+
+ condBody.append(dirMatcher.group());
+ } else if (dirName.equals(")")) {
+ nestLevel = Math.max(0, nestLevel - 1);
+
+ /* End the iteration. */
+ if (nestLevel == 0) break;
+ } else if (Directive.isClosing(dirName)) {
+ nestLevel = Math.max(0, nestLevel - 1);
+ } else {
+ /* Not a special directive. */
+ condBody.append(dirMatcher.group());
+ }
+ }
+ }
+
+ String frmt = condBody.toString();
+
+ ReportWriter nrw = rw.duplicate(new StringWriter());
+
+ fmt.doFormatString(frmt, nrw, tParams, false);
+
+ String strang = nrw.toString();
+
+ if (mods.colonMod && mods.atMod) {
+ strang = strang.toUpperCase();
+ } else if (mods.colonMod) {
+ Matcher mat = wordPattern.matcher(strang);
+
+ StringBuffer sb = new StringBuffer();
+ while(!mat.find()) {
+ mat.appendReplacement(sb, "");
+
+ String word = mat.group(1);
+
+ word = word.substring(0, 1).toUpperCase() + word.substring(1);
+
+ sb.append(word);
+ sb.append(mat.group(2));
+ }
+
+ mat.appendTail(sb);
+
+ strang = sb.toString();
+ } else if (mods.atMod) {
+ Matcher mat = wordPattern.matcher(strang);
+
+ StringBuffer sb = new StringBuffer();
+ boolean doCap = true;
+ while(!mat.find()) {
+ mat.appendReplacement(sb, "");
+
+ String word = mat.group(1);
+
+ if (doCap) {
+ doCap = false;
+
+ word = word.substring(0, 1).toUpperCase() + word.substring(1);
+ }
+
+ sb.append(word);
+ sb.append(mat.group(2));
+ }
+
+ mat.appendTail(sb);
+
+ strang = sb.toString();
+
+ } else {
+ strang = strang.toLowerCase();
+ }
+
+ rw.write(strang);
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/CharacterDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/CharacterDirective.java
new file mode 100644
index 0000000..899e8e8
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/CharacterDirective.java
@@ -0,0 +1,45 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.util.IllegalFormatConversionException;
+import java.util.regex.Matcher;
+
+/**
+ * Implements the C directive.
+ *
+ * @author student
+ *
+ */
+public class CharacterDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object parm, CLModifiers mods, CLParameters arrParams, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ CLFormatter.checkItem(parm, 'C');
+
+ if (!(parm instanceof Character)) {
+ throw new IllegalFormatConversionException('C', parm.getClass());
+ }
+
+ char ch = (Character) parm;
+ int codepoint = ch;
+
+ if (mods.colonMod) {
+ /*
+ * Colon mod means print Unicode character name.
+ */
+ rw.write(Character.getName(codepoint));
+ } else {
+ rw.write(ch);
+ }
+
+ tParams.right();
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/ConditionalDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/ConditionalDirective.java
new file mode 100644
index 0000000..ed0b39b
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/ConditionalDirective.java
@@ -0,0 +1,193 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.*;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+
+/**
+ * Implements the [ directive.
+ *
+ * @author student
+ *
+ */
+public class ConditionalDirective implements Directive {
+ private static Logger LOG = Logger.getLogger(ConditionalDirective.class.getName());
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters arrParams,
+ Tape<Object> formatParams, Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ StringBuffer condBody = new StringBuffer();
+
+ List<String> clauses = new ArrayList<>();
+
+ String defClause = null;
+ boolean isDefault = false;
+
+ int nestLevel = 1;
+
+ while (dirMatcher.find()) {
+ /* Process a list of clauses. */
+ String dirName = dirMatcher.group("name");
+ String dirMods = dirMatcher.group("modifiers");
+
+ //System.err.printf("Found conditional directive %s with %s mods and level %d\n", dirName, dirMods, nestLevel);
+ if (dirName != null) {
+ /* Append everything up to this directive. */
+ dirMatcher.appendReplacement(condBody, "");
+
+ if (dirName.equals("[")) {
+ if (nestLevel > 0) {
+ condBody.append(dirMatcher.group());
+ }
+ nestLevel += 1;
+ } else if (Directive.isOpening(dirName)) {
+ nestLevel += 1;
+
+ condBody.append(dirMatcher.group());
+ } else if (dirName.equals("]")) {
+ nestLevel = Math.max(0, nestLevel - 1);
+
+ if (nestLevel == 0) {
+ /* End the conditional. */
+ String clause = condBody.toString();
+ // System.err.printf("Found clause \"%s]\"\n", clause);
+ condBody = new StringBuffer();
+
+ if (isDefault) {
+ defClause = clause;
+ }
+ clauses.add(clause);
+
+ break;
+ } else {
+ /* Not a special directive. */
+ condBody.append(dirMatcher.group());
+ }
+ } else if (Directive.isClosing(dirName)) {
+ nestLevel = Math.max(0, nestLevel - 1);
+
+ condBody.append(dirMatcher.group());
+ } else if (dirName.equals(";")) {
+ if (nestLevel == 1) {
+ /* End the clause. */
+ String clause = condBody.toString();
+ // System.err.printf("Found clause \"%s;\"\n", clause);
+ condBody = new StringBuffer();
+
+ if (isDefault) {
+ defClause = clause;
+ }
+ clauses.add(clause);
+
+ /*
+ * Mark the next clause as the default.
+ */
+ if (dirMods.contains(":")) {
+ isDefault = true;
+ }
+ } else {
+ /* Not a special directive. */
+ condBody.append(dirMatcher.group());
+ }
+ } else {
+ /* Not a special directive. */
+ condBody.append(dirMatcher.group());
+ }
+ }
+ }
+
+ if (mods.starMod && clauses.size() > 0) defClause = clauses.get(0);
+
+ try {
+ if (mods.colonMod) {
+ formatParams.right();
+
+ boolean res = false;
+ if (item == null) {
+ //throw new IllegalArgumentException("No parameter provided for [ directive.");
+ } else if (!(item instanceof Boolean)) {
+ throw new IllegalFormatConversionException('[', item.getClass());
+ } else {
+ res = (Boolean) item;
+ }
+
+ String frmt;
+ if (res)
+ frmt = clauses.get(1);
+ else
+ frmt = clauses.get(0);
+
+ fmt.doFormatString(frmt, rw, formatParams, false);
+ } else if (mods.atMod) {
+ boolean res = false;
+ if (item == null) {
+ // throw new IllegalArgumentException("No parameter provided for [ directive.");
+ } else if (item instanceof Integer) {
+ if ((Integer)item != 0) res = true;
+ } else if (item instanceof Boolean) {
+ res = (Boolean) item;
+ } else {
+ throw new IllegalFormatConversionException('[', item.getClass());
+ }
+
+ if (res) {
+ fmt.doFormatString(clauses.get(0), rw, formatParams, false);
+ } else {
+ formatParams.right();
+ }
+ } else {
+ int res;
+ if (arrParams.length() >= 1) {
+ res = arrParams.getInt(0, "conditional choice", '[');
+ } else {
+ if (item == null) {
+ throw new IllegalArgumentException("No parameter provided for [ directive.");
+ } else if (!(item instanceof Number)) {
+ throw new IllegalFormatConversionException('[', item.getClass());
+ }
+ res = ((Number) item).intValue();
+
+ formatParams.right();
+ }
+
+ if (mods.dollarMod) res -= 1;
+
+ // System.err.printf("Attempting selection of clause %d of %d (%s) (default %s)\n",
+ // res, clauses.size(), clauses, defClause);
+ if (clauses.size() == 0 || res < 0 || res >= clauses.size()) {
+ // System.err.printf("Selecting default clause (res %d, max %d): %s\n", res, clauses.size(), defClause);
+ // int clauseNo = 0;
+ // for (String clause : clauses) {
+ // System.err.printf("... clause %d: %s\n", ++clauseNo, clause);
+ // }
+
+ if (defClause != null) fmt.doFormatString(defClause, rw, formatParams, false);
+ } else {
+ String frmt = clauses.get(res);
+
+ // System.out.printf("Selecting clause %d of %d (params %s): %s\n", res, clauses.size(), formatParams, frmt);
+ fmt.doFormatString(frmt, rw, formatParams, false);
+ }
+ }
+ } catch (EscapeException eex) {
+ // @NOTE 9/5/18
+ //
+ // I am not sure if it is valid to error here. I'm not
+ // even sure that we need to handle this here, but I
+ // dunno
+ //if (eex.endIteration)
+ // throw new UnsupportedOperationException("Colon mod not allowed on escape marker without colon mod on iteration");
+ throw eex;
+ }
+
+ return;
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/Directive.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/Directive.java
new file mode 100644
index 0000000..61abfc1
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/Directive.java
@@ -0,0 +1,72 @@
+package bjc.utils.ioutils.format.directives;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+/**
+ * A CL format directive.
+ *
+ * @author EVE
+ *
+ */
+@FunctionalInterface
+public interface Directive {
+ /**
+ * Execute this format directive.
+ *
+ * @param sb
+ * The buffer the string is being output to.
+ * @param item
+ * The current parameter being passed
+ * @param mods
+ * The directive modifiers
+ * @param arrParams
+ * The prefix parameters to the directive
+ * @param tParams
+ * All of the provided format parameters
+ * @param dirMatcher
+ * The matcher for format directives
+ * @param fmt
+ * The formatter itself.
+ */
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters arrParams, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException;
+
+ public static boolean isOpening(String str) {
+ return isOpening(str.charAt(0));
+ }
+
+ public static boolean isOpening(char dir) {
+ switch(dir) {
+ case '(':
+ case '<':
+ case '[':
+ case '{':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isClosing(String str) {
+ return isClosing(str.charAt(0));
+ }
+
+ public static boolean isClosing(char dir) {
+ switch(dir) {
+ case ')':
+ case '>':
+ case ']':
+ case '}':
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/EscapeDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/EscapeDirective.java
new file mode 100644
index 0000000..74488ed
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/EscapeDirective.java
@@ -0,0 +1,59 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.format.EscapeException;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.util.regex.Matcher;
+
+/**
+ * Implementation for the ^ directive.
+ * @author student
+ *
+ */
+public class EscapeDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params,
+ Tape<Object> formatParams, Matcher dirMatcher, CLFormatter fmt) {
+ boolean shouldExit;
+
+ if (mods.dollarMod) formatParams.right();
+
+ switch(params.length()) {
+ case 0:
+ shouldExit = formatParams.atEnd();
+ break;
+ case 1:
+ int num = params.getInt(0, "condition count", '^');
+
+ shouldExit = num == 0;
+ break;
+ case 2:
+ int left = params.getInt(0, "left-hand condition", '^');
+ int right = params.getInt(1, "right-hand condition", '^');
+
+ shouldExit = left == right;
+ break;
+ case 3:
+ default:
+ int low = params.getInt(0, "lower-bound condition", '^');
+ int mid = params.getInt(1, "interval condition", '^');
+ int high = params.getInt(2, "upper-bound condition", '^');
+
+ shouldExit = (low <= mid) && (mid <= high);
+ break;
+ }
+
+ if (mods.dollarMod) formatParams.left();
+
+ /* At negates it. */
+ if(mods.atMod) shouldExit = !shouldExit;
+
+ if(shouldExit) throw new EscapeException(mods.colonMod);
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/FreshlineDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/FreshlineDirective.java
new file mode 100644
index 0000000..3c02bfc
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/FreshlineDirective.java
@@ -0,0 +1,34 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+/**
+ * Implement the &amp; directive.
+ * @author student
+ *
+ */
+public class FreshlineDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ int nTimes = 1;
+
+ if(params.length() >= 1) {
+ nTimes = params.getInt(0, "occurance count", '&');
+ }
+
+ if(rw.isLastCharNL()) nTimes -= 1;
+
+ for(int i = 0; i < nTimes; i++) {
+ rw.write("\n");
+ }
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/GeneralNumberDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/GeneralNumberDirective.java
new file mode 100644
index 0000000..3eb741e
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/GeneralNumberDirective.java
@@ -0,0 +1,55 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+import bjc.utils.math.NumberUtils;
+
+import java.io.IOException;
+
+/**
+ * Implementation skeleton for number directives.
+ *
+ * @author student
+ *
+ */
+public abstract class GeneralNumberDirective implements Directive {
+ protected static void handleNumberDirective(ReportWriter rw, CLModifiers mods, CLParameters params, int argidx,
+ long val, int radix) throws IOException {
+ /*
+ * Initialize the two padding related parameters, and then fill them in from the
+ * directive parameters if they are present.
+ */
+ int mincol = 0;
+ char padchar = ' ';
+ if (params.length() >= (argidx + 2)) {
+ mincol = params.getIntDefault(argidx + 1, "minimum column count", 'R', 0);
+ }
+ if (params.length() >= (argidx + 3)) {
+ padchar = params.getCharDefault(argidx + 2, "padding character", 'R', ' ');
+ }
+
+ String res;
+
+ if (mods.colonMod) {
+ /*
+ * We're doing commas, so check if the two comma-related parameters were
+ * supplied.
+ */
+ int commaInterval = 0;
+ char commaChar = ',';
+ if (params.length() >= (argidx + 4)) {
+ commaChar = params.getCharDefault((argidx + 3), "comma character", 'R', ',');
+ }
+ if (params.length() >= (argidx + 5)) {
+ commaInterval = params.getIntDefault((argidx + 4), "comma interval", 'R', 0);
+ }
+
+ res = NumberUtils.toCommaString(val, mincol, padchar, commaInterval, commaChar, mods.atMod, radix);
+ } else {
+ res = NumberUtils.toNormalString(val, mincol, padchar, mods.atMod, radix);
+ }
+
+ rw.write(res);
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/GotoDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/GotoDirective.java
new file mode 100644
index 0000000..7e30eab
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/GotoDirective.java
@@ -0,0 +1,47 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.util.regex.Matcher;
+
+/**
+ * Implement the * directive.
+ *
+ * @author student
+ *
+ */
+public class GotoDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> formatParams,
+ Matcher dirMatcher, CLFormatter fmt) {
+ if (mods.colonMod) {
+ int num = 1;
+ if (params.length() >= 1) {
+ num = params.getIntDefault(0, "number of arguments backward", '*', 1);
+ }
+
+ formatParams.left(num);
+ } else if (mods.atMod) {
+ int num = 0;
+ if (params.length() >= 1) {
+ num = params.getIntDefault(0, "argument index", '*', 0);
+ }
+
+ formatParams.first();
+ formatParams.right(num);
+ } else {
+ int num = 1;
+ if (params.length() >= 1) {
+ num = params.getIntDefault(0, "number of arguments forward", '*', 1);
+ }
+
+ formatParams.right(num);
+ }
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/IterationDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/IterationDirective.java
new file mode 100644
index 0000000..2ce6309
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/IterationDirective.java
@@ -0,0 +1,171 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.SingleTape;
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.*;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import java.util.IllegalFormatConversionException;
+import java.util.regex.Matcher;
+
+/**
+ * Implements the { directive.
+ *
+ * @author student
+ *
+ */
+public class IterationDirective implements Directive {
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters arrParams, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ CLFormatter.checkItem(item, '{');
+
+ StringBuffer condBody = new StringBuffer();
+
+ while (dirMatcher.find()) {
+ /* Process a list of clauses. */
+ String dirName = dirMatcher.group("name");
+
+ if (dirName != null) {
+ /* Append everything up to this directive. */
+ dirMatcher.appendReplacement(condBody, "");
+
+ if (dirName.equals("}")) {
+ break;
+ } else {
+ /* Not a special directive. */
+ condBody.append(dirMatcher.group());
+ }
+ }
+ }
+
+ String frmt = condBody.toString();
+ Object iter = item;
+
+ // System.err.printf("Iteration format \"%s\" (iter %s)\n", frmt, item);
+ if (frmt.equals("")) {
+ /* Grab an argument. */
+ if (!(item instanceof String)) {
+ throw new IllegalFormatConversionException('{', String.class);
+ }
+
+ frmt = (String) item;
+
+ if (!tParams.right()) {
+ throw new IllegalArgumentException("Not enough parameters to '{' directive");
+ }
+
+ iter = tParams.item();
+ }
+
+ int maxItr = Integer.MAX_VALUE;
+
+ if (arrParams.length() > 0) {
+ maxItr = arrParams.getInt(0, "maximum iterations", '{');
+ }
+
+ int numItr = 0;
+
+ if (mods.atMod && mods.colonMod) {
+ try {
+ do {
+ if (numItr > maxItr) break;
+ numItr += 1;
+
+ if (!(iter instanceof Iterable<?>)) {
+ throw new IllegalFormatConversionException('{', iter.getClass());
+ }
+
+ @SuppressWarnings("unchecked")
+ Iterable<Object> nitr = (Iterable<Object>) iter;
+ Tape<Object> nParams = new SingleTape<>(nitr);
+
+ try {
+ fmt.doFormatString(frmt, rw, nParams, false);
+ } catch (EscapeException eex) {
+ if (eex.endIteration) {
+ if (tParams.atEnd()) {
+ throw eex;
+ }
+ }
+ }
+
+ tParams.right();
+ iter = tParams.item();
+ } while (tParams.position() < tParams.size());
+ } catch (EscapeException eex) {
+ }
+ } else if (mods.atMod) {
+ try {
+ while (!tParams.atEnd()) {
+ // System.err.printf("Iterating with format \"%s\"\n", frmt);
+ if (numItr > maxItr) break;
+ numItr += 1;
+
+ fmt.doFormatString(frmt, rw, tParams, false);
+ }
+ } catch (EscapeException eex) {
+ if (eex.endIteration)
+ throw new UnsupportedOperationException("Colon mod not allowed on escape marker without colon mod on iteration");
+ }
+ } else if (mods.colonMod) {
+ if (!(item instanceof Iterable<?>)) {
+ throw new IllegalFormatConversionException('{', item.getClass());
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Iterable<Object> itb = (Iterable<Object>) item;
+ Iterator<Object> itr = itb.iterator();
+ while (itr.hasNext()) {
+ Object obj = itr.next();
+
+ if (numItr > maxItr) break;
+ numItr += 1;
+
+ if (!(obj instanceof Iterable<?>)) {
+ throw new IllegalFormatConversionException('{', obj.getClass());
+ }
+
+ @SuppressWarnings("unchecked")
+ Iterable<Object> nitr = (Iterable<Object>) obj;
+ Tape<Object> nParams = new SingleTape<>(nitr);
+
+ try {
+ fmt.doFormatString(frmt, rw, nParams, false);
+ } catch (EscapeException eex) {
+ if(eex.endIteration && !itr.hasNext()) throw eex;
+ }
+ }
+ } catch (EscapeException eex) {
+ }
+ } else {
+ if (!(item instanceof Iterable<?>)) {
+ throw new IllegalFormatConversionException('{', item.getClass());
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ Iterable<Object> itr = (Iterable<Object>) item;
+ Tape<Object> nParams = new SingleTape<>(itr);
+
+ while (!nParams.atEnd()) {
+ if (numItr > maxItr) break;
+ numItr += 1;
+
+ fmt.doFormatString(frmt, rw, nParams, false);
+ }
+ } catch (EscapeException eex) {
+ if (eex.endIteration)
+ throw new UnsupportedOperationException("Colon mod not allowed on escape marker without colon mod on iteration");
+ }
+ }
+
+ tParams.right();
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/LiteralDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/LiteralDirective.java
new file mode 100644
index 0000000..d833654
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/LiteralDirective.java
@@ -0,0 +1,51 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+/**
+ * Implements directives that create a literal string.
+ *
+ * @author student
+ *
+ */
+public class LiteralDirective implements Directive {
+
+ private char directive;
+ private String lit;
+
+ /**
+ * Create a new literal directive.
+ *
+ * @param lit
+ * The string for the directive.
+ * @param directive
+ * The character for the directive.
+ */
+ public LiteralDirective(String lit, char directive) {
+ this.directive = directive;
+ this.lit = lit;
+ }
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ int nTimes = 1;
+
+ if (params.length() >= 1) {
+ nTimes = params.getInt(0, "occurance count", directive);
+ }
+
+ for (int i = 0; i < nTimes; i++) {
+ rw.write(lit);
+ }
+
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/NumberDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/NumberDirective.java
new file mode 100644
index 0000000..88b3e7e
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/NumberDirective.java
@@ -0,0 +1,57 @@
+package bjc.utils.ioutils.format.directives;
+
+import java.io.IOException;
+import java.util.IllegalFormatConversionException;
+import java.util.regex.Matcher;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+
+/**
+ * Implements radix based numbers.
+ *
+ * @author student
+ *
+ */
+public class NumberDirective extends GeneralNumberDirective {
+
+ /**
+ * Create a new radix based number directive.
+ *
+ * @param argidx
+ * The argument offset to use.
+ * @param radix
+ * The radix of the number to use.
+ */
+ public NumberDirective(int argidx, int radix, char directive) {
+ this.argidx = argidx;
+ this.radix = radix;
+
+ this.directive = directive;
+ }
+
+ private int argidx;
+ private int radix;
+
+ private char directive;
+
+ @Override
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ CLFormatter.checkItem(item, directive);
+
+ if (!(item instanceof Number)) {
+ throw new IllegalFormatConversionException(directive, item.getClass());
+ }
+
+ long val = ((Number) item).longValue();
+
+ handleNumberDirective(rw, mods, params, argidx, val, radix);
+
+ tParams.right();
+ }
+
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/RadixDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/RadixDirective.java
new file mode 100644
index 0000000..e8dd4b0
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/RadixDirective.java
@@ -0,0 +1,55 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.CLFormatter;
+import bjc.utils.ioutils.format.CLModifiers;
+import bjc.utils.ioutils.format.CLParameters;
+import bjc.utils.ioutils.ReportWriter;
+import bjc.utils.math.NumberUtils;
+
+import java.io.IOException;
+import java.util.IllegalFormatConversionException;
+import java.util.regex.Matcher;
+
+/**
+ * Generalized radix directive.
+ *
+ * @author student
+ *
+ */
+public class RadixDirective extends GeneralNumberDirective {
+
+ @Override
+ public void format(ReportWriter rw, Object arg, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ CLFormatter.checkItem(arg, 'R');
+
+ if (!(arg instanceof Number)) {
+ throw new IllegalFormatConversionException('R', arg.getClass());
+ }
+
+ /*
+ * @TODO see if this is the way we want to do this.
+ */
+ long val = ((Number) arg).longValue();
+
+ if (params.length() == 0) {
+ if (mods.atMod) {
+ rw.write(NumberUtils.toRoman(val, mods.colonMod));
+ } else if (mods.colonMod) {
+ rw.write(NumberUtils.toOrdinal(val));
+ } else {
+ rw.write(NumberUtils.toCardinal(val));
+ }
+ } else {
+ if (params.length() < 1)
+ throw new IllegalArgumentException("R directive requires at least one parameter, the radix");
+
+ int radix = params.getInt(0, "radix", 'R');
+
+ handleNumberDirective(rw, mods, params, 0, val, radix);
+ }
+
+ tParams.right();
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/RecursiveDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/RecursiveDirective.java
new file mode 100644
index 0000000..44a25ad
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/RecursiveDirective.java
@@ -0,0 +1,49 @@
+package bjc.utils.ioutils.format.directives;
+
+import bjc.utils.esodata.SingleTape;
+import bjc.utils.esodata.Tape;
+import bjc.utils.ioutils.format.*;
+import bjc.utils.ioutils.ReportWriter;
+import java.util.IllegalFormatConversionException;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+public class RecursiveDirective implements Directive {
+ public void format(ReportWriter rw, Object arg, CLModifiers mods, CLParameters params, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ tParams.right();
+
+ CLFormatter.checkItem(arg, '?');
+
+ if (mods.atMod) {
+ if (!(arg instanceof String))
+ throw new IllegalFormatConversionException('?', arg.getClass());
+
+ try {
+ fmt.doFormatString((String)arg, rw, tParams, true);
+ } catch (EscapeException eex) {
+ if (eex.endIteration)
+ throw new UnsupportedOperationException("Colon mod not allowed on escape marker without colon mod on iteration");
+ }
+ } else {
+ if (tParams.atEnd())
+ throw new IllegalArgumentException("? directive requires two format parameters");
+
+ Object o = tParams.item();
+ tParams.right();
+
+ if (!(o instanceof Iterable))
+ throw new IllegalFormatConversionException('?', o.getClass());
+
+ Iterable<Object> itb = (Iterable<Object>)o;
+ Tape<Object> newParams = new SingleTape<>(itb);
+
+ try {
+ fmt.doFormatString((String)arg, rw, newParams, true);
+ } catch (EscapeException eex) {
+ throw new UnsupportedOperationException("Colon mod not allowed on escape marker without colon mod on iteration");
+ }
+ }
+ }
+}
diff --git a/clformat/src/main/java/bjc/utils/ioutils/format/directives/TabulateDirective.java b/clformat/src/main/java/bjc/utils/ioutils/format/directives/TabulateDirective.java
new file mode 100644
index 0000000..d9136f2
--- /dev/null
+++ b/clformat/src/main/java/bjc/utils/ioutils/format/directives/TabulateDirective.java
@@ -0,0 +1,80 @@
+package bjc.utils.ioutils.format.directives;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+import bjc.utils.esodata.Tape;
+
+import bjc.utils.ioutils.ReportWriter;
+import bjc.utils.ioutils.format.*;
+
+public class TabulateDirective implements Directive {
+ public void format(ReportWriter rw, Object item, CLModifiers mods, CLParameters arrParams, Tape<Object> tParams,
+ Matcher dirMatcher, CLFormatter fmt) throws IOException {
+ // Unsupported feature.
+ //
+ // I can't really make out what this is supposed to do from the
+ // documentation, but I suspect that it depends on font glyph
+ // size, not character positions
+ if (mods.colonMod) {
+ throw new UnsupportedOperationException("Colon mod is not supported for T directive");
+ }
+
+ // Support for a possible future feature
+ char padchar = ' ';
+
+ if (mods.atMod) {
+ int colrel = 1, colinc = 1;
+
+ if (arrParams.length() > 2) {
+ colinc = arrParams.getIntDefault(1, "column increment", 'T', 1);
+ }
+
+ if (arrParams.length() > 1) {
+ colrel = arrParams.getIntDefault(0, "relative column number", 'T', 1);
+ }
+
+ for (int i = 0; i < colrel; i++) {
+ rw.write(padchar);
+ }
+
+ int currCol = rw.getLinePos();
+
+ int nSpaces = 0;
+
+ while ((currCol + nSpaces) % colinc != 0) nSpaces++;
+
+ for (int i = 0; i < nSpaces; i++) {
+ rw.write(padchar);
+ }
+ } else {
+ int colnum = 1, colinc = 1;
+
+ if (arrParams.length() > 2) {
+ colinc = arrParams.getIntDefault(1, "column increment", 'T', 1);
+ }
+
+ if (arrParams.length() > 1) {
+ colnum = arrParams.getIntDefault(0, "column number", 'T', 1);
+ }
+
+ int currCol = rw.getLinePos();
+
+ if (currCol < colnum) {
+ for (int i = currCol; i < colnum; i++) {
+ rw.write(padchar);
+ }
+ } else {
+ if (colinc == 0) return;
+
+ int k = 0;
+
+ while (colnum > (currCol + (k * colinc))) k++;
+
+ for (int i = currCol; i < colnum; i++) {
+ rw.write(padchar);
+ }
+ }
+ }
+ }
+}