diff options
Diffstat (limited to 'clformat/src/main/java/bjc/utils/ioutils/format')
19 files changed, 1764 insertions, 0 deletions
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 & 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); + } + } + } + } +} |
