summaryrefslogtreecommitdiff
path: root/BJC-Utils2
diff options
context:
space:
mode:
Diffstat (limited to 'BJC-Utils2')
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/CLFormatter.java192
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/CLParameters.java109
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/NumberUtils.java405
3 files changed, 704 insertions, 2 deletions
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLFormatter.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLFormatter.java
index a119c9b..f9ae49a 100644
--- a/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLFormatter.java
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLFormatter.java
@@ -1,14 +1,202 @@
package bjc.utils.ioutils;
+import java.util.HashMap;
+import java.util.IllegalFormatConversionException;
+import java.util.Map;
+import java.util.UnknownFormatConversionException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import bjc.utils.PropertyDB;
+import bjc.utils.esodata.Tape;
+import bjc.utils.esodata.SingleTape;
import static bjc.utils.PropertyDB.applyFormat;
import static bjc.utils.PropertyDB.getCompiledRegex;
import static bjc.utils.PropertyDB.getRegex;
-import java.util.regex.Pattern;
-
public class CLFormatter {
+ @FunctionalInterface
+ public interface Directive {
+ /*
+ * @TODO fill in parameters
+ */
+ public void format();
+ }
+
private static final String prefixParam = getRegex("clFormatPrefix");
private static final Pattern pPrefixParam = Pattern.compile(prefixParam);
+
+ private static final String formatMod = getRegex("clFormatModifier");
+
+ private static final String prefixList = applyFormat("delimSeparatedList", prefixParam, ",");
+
+ private static final String formatDirective = applyFormat("clFormatDirective", prefixList, formatMod);
+ private static final Pattern pFormatDirective = Pattern.compile(formatDirective);
+
+ private Map<String, Directive> extraDirectives;
+
+ public CLFormatter() {
+ extraDirectives = new HashMap<>();
+ }
+
+ public String formatString(String format, Object... params) {
+ StringBuffer sb = new StringBuffer();
+ Matcher dirMatcher = pFormatDirective.matcher(format);
+
+ /*
+ * Put the parameters where we can easily handle them.
+ */
+ Tape<Object> tParams = new SingleTape(params);
+
+ while(dirMatcher.find()) {
+ dirMatcher.appendReplacement(sb, "");
+
+ String dirName = dirMatcher.group("name");
+ String dirMods = dirMatcher.group("modifiers");
+ String dirParams = dirMatcher.group("params");
+
+ CLParameters arrParams = CLParameters.fromDirective(dirParams.split("(?<!'),"), tParams);
+
+ boolean atMod = false;
+ boolean colonMod = false;
+ if(dirMods != null) {
+ atMod = dirMods.contains("@");
+ colonMod = dirMods.contains(":");
+ }
+
+ switch(dirName) {
+ case "C":
+ handleCDirective(sb, tParams.item(), atMod, colonMod);
+ tParams.right();
+ break;
+ case "&":
+ handleFreshlineDirective(sb, arrParams);
+ break;
+ case "%":
+ handleLiteralDirective(sb, arrParams, "\n", '%');
+ break;
+ case "|":
+ handleLiteralDirective(sb, arrParams, "\f", '|');
+ break;
+ case "~":
+ handleLiteralDirective(sb, arrParams, "~", '~');
+ break;
+ case "R":
+ handleRadixDirective(sb, atMod, colonMod, arrParams, tParams.item());
+ tParams.right();
+ break;
+ default:
+ String msg = String.format("Unknown format directive '%s'", dirName);
+ throw new UnknownFormatConversionException(msg);
+ }
+ }
+
+ dirMatcher.appendTail(sb);
+
+ return sb.toString();
+ }
+
+ private void handleCDirective(StringBuffer buff, Object parm, boolean atMod, boolean colonMod) {
+ if(!(parm instanceof Character)) {
+ throw new IllegalFormatConversionException('C', parm.getClass());
+ }
+
+ char ch = (Character) parm;
+ int codepoint = (int) ch;
+
+ if(colonMod) {
+ /*
+ * Colon mod means print Unicode character name.
+ */
+ buff.append(Character.getName(codepoint));
+ } else {
+ buff.append(ch);
+ }
+ }
+
+ private void handleFreshlineDirective(StringBuffer buff, CLParameters params) {
+ int nTimes = 1;
+
+ if(params.length() > 1) {
+ nTimes = params.getInt(0, "occurance count", '&');
+ }
+
+ if(buff.charAt(buff.length() - 1) == '\n') nTimes -= 1;
+
+ for(int i = 0; i < nTimes; i++) {
+ buff.append("\n");
+ }
+ }
+
+ private void handleLiteralDirective(StringBuffer buff, CLParameters params, String lit, char directive) {
+ int nTimes = 1;
+
+ if(params.length() > 1) {
+ nTimes = params.getInt(0, "occurance count", directive);
+ }
+
+ for(int i = 0; i < nTimes; i++) {
+ buff.append(lit);
+ }
+ }
+
+ private void handleRadixDirective(StringBuffer buff, boolean atMod, boolean colonMod, CLParameters params, Object arg) {
+ 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(atMod) {
+ buff.append(NumberUtils.toRoman((Long)val, colonMod));
+ } else if(colonMod) {
+ buff.append(NumberUtils.toOrdinal(val));
+ } else {
+ buff.append(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');
+
+ /*
+ * 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() > 2) {
+ mincol = params.getIntDefault(1, "minimum column count", 'R', 0);
+ }
+ if(params.length() > 3) {
+ padchar = params.getCharDefault(2, "padding character", 'R', ' ');
+ }
+
+ if(colonMod) {
+ /*
+ * We're doing commas, so check if the two
+ * comma-related parameters were supplied.
+ */
+ int commaInterval = 0;
+ char commaChar = ',';
+ if(params.length() > 3) {
+ commaChar = params.getCharDefault(3, "comma character", 'R', ' ');
+ }
+ if(params.length() > 4) {
+ commaInterval = params.getIntDefault(4, "comma interval", 'R', 0);
+ }
+
+ NumberUtils.toCommaString(val, mincol, padchar, commaInterval, commaChar, atMod, radix);
+ } else {
+ NumberUtils.toNormalString(val, mincol, padchar, atMod, radix);
+ }
+ }
+ }
}
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLParameters.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLParameters.java
new file mode 100644
index 0000000..e4bb6fb
--- /dev/null
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/CLParameters.java
@@ -0,0 +1,109 @@
+package bjc.utils.ioutils;
+
+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;
+
+ public CLParameters(String[] params) {
+ this.params = params;
+ }
+
+ 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[] params, Tape<Object> dirParams) {
+ List<String> parameters = new ArrayList<>();
+
+ for(String param : params) {
+ if(param.equalsIgnoreCase("V")) {
+ Object par = dirParams.item();
+ boolean succ = dirParams.right();
+
+ 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.position()));
+ } else {
+ parameters.add(param);
+ }
+ }
+
+ return new CLParameters(parameters.toArray(new String[0]));
+ }
+
+ public char getCharDefault(int idx, String paramName, char directive, char def) {
+ if(!params[idx].equals("")) {
+ return getChar(idx, paramName, directive);
+ }
+
+ return def;
+ }
+
+ public char getChar(int idx, String paramName, char directive) {
+ String param = params[idx];
+
+ if(!param.startsWith("'")) {
+ throw new IllegalArgumentException(String.format("Invalid %s %s to %c directive", paramName, param, directive));
+ }
+
+ return param.charAt(1);
+ }
+
+ public int getIntDefault(int idx, String paramName, char directive, int def) {
+ if(!params[idx].equals("")) {
+
+ }
+
+ return def;
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/NumberUtils.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/NumberUtils.java
new file mode 100644
index 0000000..1b754e2
--- /dev/null
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/NumberUtils.java
@@ -0,0 +1,405 @@
+package bjc.utils.ioutils;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.LongPredicate;
+
+import static java.util.Map.Entry;
+
+public class NumberUtils {
+ /*
+ * @TODO Use U+305 for large roman numerals, as well as excels 'concise'
+ * numerals (as implemented by roman()).
+ */
+ public static String toRoman(long number, boolean classic) {
+ StringBuilder work = new StringBuilder();
+
+ long currNumber = number;
+
+ if(currNumber == 0) {
+ return "N";
+ }
+
+ if(currNumber < 0) {
+ currNumber *= -1;
+
+ work.append("-");
+ }
+
+ if(currNumber >= 1000) {
+ int numM = (int)(currNumber / 1000);
+ currNumber = currNumber % 1000;
+
+ for(int i = 0; i < numM; i++) {
+ work.append("M");
+ }
+ }
+
+ if(currNumber >= 900 && !classic) {
+ currNumber = currNumber % 900;
+
+ work.append("CM");
+ }
+
+ if(currNumber >= 500) {
+ currNumber = currNumber % 500;
+
+ work.append("D");
+ }
+
+ if(currNumber >= 400 && !classic) {
+ currNumber = currNumber % 400;
+
+ work.append("CD");
+ }
+
+ if(currNumber >= 100) {
+ int numC = (int)(currNumber / 100);
+ currNumber = currNumber % 100;
+
+ for(int i = 0; i < numC; i++) {
+ work.append("C");
+ }
+ }
+
+ if(currNumber >= 90 && !classic) {
+ currNumber = currNumber % 90;
+
+ work.append("XC");
+ }
+
+ if(currNumber >= 50) {
+ currNumber = currNumber % 50;
+
+ work.append("L");
+ }
+
+ if(currNumber >= 40 && !classic) {
+ currNumber = currNumber % 40;
+
+ work.append("XL");
+ }
+
+ if(currNumber >= 10) {
+ int numX = (int)(currNumber / 10);
+ currNumber = currNumber % 10;
+
+ for(int i = 0; i < numX; i++) {
+ work.append("X");
+ }
+ }
+
+ if(currNumber >= 9 && !classic) {
+ currNumber = currNumber % 9;
+
+ work.append("IX");
+ }
+
+ if(currNumber >= 5) {
+ currNumber = currNumber % 5;
+
+ work.append("V");
+ }
+
+ if(currNumber >= 4 && !classic) {
+ currNumber = currNumber % 4;
+
+ work.append("IV");
+ }
+
+ if(currNumber >= 1) {
+ int numI = (int)(currNumber / 1);
+ currNumber = currNumber % 1;
+
+ for(int i = 0; i < numI; i++) {
+ work.append("I");
+ }
+ }
+
+ return work.toString();
+ }
+
+ public static String toCardinal(long number) {
+ return toCardinal(number, null);
+ }
+
+ private static String[] cardinals = new String[] {
+ "zero", "one", "two", "three", "four", "five", "six", "seven",
+ "eight", "nine", "ten", "eleven", "twelve", "thirteen",
+ "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
+ "nineteen", "twenty",
+ };
+
+ public static class CardinalState {
+ public final Map<Long, String> customNumbers;
+ public final Map<LongPredicate, BiFunction<Long, CardinalState, String>> customScales;
+
+ public CardinalState(Map<Long, String> customNumbers, Map<LongPredicate, BiFunction<Long, CardinalState, String>> customScales) {
+ this.customNumbers = customNumbers;
+ this.customScales = customScales;
+ }
+
+ public String handleCustom(long number) {
+ if(customNumbers.containsKey(number)) {
+ return customNumbers.get(number);
+ }
+
+ for(Entry<LongPredicate, BiFunction<Long, CardinalState, String>> ent : customScales.entrySet()) {
+ if(ent.getKey().test(number)) {
+ return ent.getValue().apply(number, this);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ public static String toCardinal(long number, CardinalState custom) {
+ if(custom != null) {
+ String res = custom.handleCustom(number);
+
+ if(res != null) return res;
+ }
+
+ if(number < 0) return "negative " + toCardinal(number * -1, custom);
+
+ if(number <= 20) return cardinals[(int)number];
+
+ if(number < 100) {
+ if(number % 10 == 0) {
+ switch((int)number) {
+ case 30:
+ return "thirty";
+ case 40:
+ return "forty";
+ case 50:
+ return "fifty";
+ case 60:
+ return "sixty";
+ case 70:
+ return "seventy";
+ case 80:
+ return "eighty";
+ case 90:
+ return "ninety";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert(false);
+ }
+ }
+
+ long numTens = (long)(number / 10);
+ long numOnes = number % 10;
+
+ return toCardinal(numTens, custom) + "-" + toCardinal(numOnes, custom);
+ }
+
+ if(number < 1000) {
+ long numHundreds = (long)(number / 100);
+ long rest = number % 100;
+
+ return toCardinal(numHundreds, custom) + " hundred and " + toCardinal(rest, custom);
+ }
+
+ long MILLION = (long)(Math.pow(10, 6));
+ if(number < MILLION) {
+ long numThousands = (long)(number / 1000);
+ long rest = number % 1000;
+
+ return toCardinal(numThousands, custom) + " thousand, " + toCardinal(rest, custom);
+ }
+
+ long BILLION = (long)(Math.pow(10, 9));
+ if(number < BILLION) {
+ long numMillions = (long)(number / MILLION);
+ long rest = number % MILLION;
+
+ return toCardinal(numMillions, custom) + " million, " + toCardinal(rest, custom);
+ }
+
+ long TRILLION = (long)(Math.pow(10, 12));
+ if(number < TRILLION) {
+ long numBillions = (long)(number / BILLION);
+ long rest = number % BILLION;
+
+ return toCardinal(numBillions, custom) + " billion, " + toCardinal(rest, custom);
+ }
+
+ throw new IllegalArgumentException("Numbers greater than or equal to 1 trillion are not supported yet.");
+ }
+
+ public static String toOrdinal(long number) {
+ if(number < 0) {
+ return "minus " + toOrdinal(number);
+ }
+
+ if(number < 20) {
+ switch((int)number) {
+ case 0:
+ return "zeroth";
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ case 13:
+ return "thirteenth";
+ case 14:
+ return "fourteenth";
+ case 15:
+ return "fifteenth";
+ case 16:
+ return "sixteenth";
+ case 17:
+ return "seventeenth";
+ case 18:
+ return "eighteenth";
+ case 19:
+ return "nineteenth";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert(false);
+ }
+ }
+
+ if(number < 100) {
+ if(number % 10 == 0) {
+ switch((int)number) {
+ case 20:
+ return "twentieth";
+ case 30:
+ return "thirtieth";
+ case 40:
+ return "fortieth";
+ case 50:
+ return "fiftieth";
+ case 60:
+ return "sixtieth";
+ case 70:
+ return "seventieth";
+ case 80:
+ return "eightieth";
+ case 90:
+ return "ninetieth";
+ }
+ }
+
+ long numPostfix = number % 10;
+ return toCardinal(number - numPostfix) + "-" + toOrdinal(numPostfix);
+ }
+
+ long procNum = number % 100;
+ long tens = (long)(procNum / 10);
+ long ones = procNum % 10;
+
+ if(tens == 1) {
+ return Long.toString(number) + "th";
+ }
+
+ switch((int)ones) {
+ case 1:
+ return Long.toString(number) + "st";
+ case 2:
+ return Long.toString(number) + "nd";
+ case 3:
+ return Long.toString(number) + "rd";
+ default:
+ return Long.toString(number) + "th";
+ }
+ }
+
+ private static char[] radixChars = new char[62];
+ static {
+ int idx = 0;
+
+ for(char i = 0; i < 10; i++) {
+ radixChars[idx] = (char)('0' + i);
+
+ idx += 1;
+ }
+
+ for(char i = 0; i < 26; i++) {
+ radixChars[idx] = (char)('A' + i);
+
+ idx += 1;
+ }
+
+ for(char i = 0; i < 26; i++) {
+ radixChars[idx] = (char)('a' + i);
+
+ idx += 1;
+ }
+ }
+
+ public static String toCommaString(long val, int mincols, char padchar, int commaInterval, char commaChar, boolean signed, int radix) {
+ if(radix > radixChars.length) {
+ throw new IllegalArgumentException(String.format("Radix %d is larger than largest supported radix %d", radix, radixChars.length));
+ }
+
+ StringBuilder work = new StringBuilder();
+
+ boolean isNeg = false;
+ long currVal = val;
+ if(currVal < 0) {
+ isNeg = true;
+ currVal *= -1;
+ }
+
+ if(currVal == 0) {
+ work.append(radixChars[0]);
+ } else {
+ int valCounter = 0;
+
+ while(currVal != 0) {
+ valCounter += 1;
+
+ int radDigit = (int)(currVal % radix);
+ work.append(radixChars[radDigit]);
+ currVal = (long)(currVal / radix);
+
+ if(commaInterval != 0 && valCounter % commaInterval == 0) work.append(commaChar);
+ }
+ }
+
+ if(isNeg) work.append("-");
+ else if(signed) work.append("+");
+
+ work.reverse();
+
+ /* @TODO Should we have some way to specify how to pad? */
+ if(work.length() < mincols) {
+ for(int i = work.length(); i < mincols; i++) {
+ work.append(padchar);
+ }
+ }
+
+ return work.toString();
+ }
+
+ public static String toNormalString(long val, int mincols, char padchar, boolean signed, int radix) {
+ return toCommaString(val, mincols, padchar, 0, ',', signed, radix);
+ }
+}