diff options
Diffstat (limited to 'src/main/java/bjc/inflexion/InflectionString.java')
| -rw-r--r-- | src/main/java/bjc/inflexion/InflectionString.java | 805 |
1 files changed, 12 insertions, 793 deletions
diff --git a/src/main/java/bjc/inflexion/InflectionString.java b/src/main/java/bjc/inflexion/InflectionString.java index 5c87771..1d739c3 100644 --- a/src/main/java/bjc/inflexion/InflectionString.java +++ b/src/main/java/bjc/inflexion/InflectionString.java @@ -14,16 +14,11 @@ */ package bjc.inflexion; -import static bjc.inflexion.InflectionString.InflectionDirective.literal; -import static bjc.inflexion.InflectionString.InflectionDirective.noun; -import static bjc.inflexion.InflectionString.InflectionDirective.numeric; -import static bjc.inflexion.InflectionString.InflectionDirective.variable; -import static java.util.Arrays.asList; +import static bjc.inflexion.InflectionDirective.*; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -31,8 +26,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import bjc.inflexion.InflectionString.InflectionDirective.NounOptions; -import bjc.inflexion.InflectionString.InflectionDirective.NumericOptions; import bjc.inflexion.nouns.Noun; import bjc.inflexion.nouns.Nouns; import bjc.inflexion.nouns.Prepositions; @@ -44,786 +37,6 @@ import bjc.inflexion.nouns.Prepositions; * */ public class InflectionString { - /** - * Exception thrown if the string we are attempting to compile has invalid - * syntax. - * - * @author bjculkin - * - */ - public class InflectionFormatException extends RuntimeException { - private static final long serialVersionUID = -5306003088746525691L; - - /** - * The string we attempted to parse. - */ - public final String inp; - - /** - * The errors we encountered parsing the string. - */ - public final List<String> parseErrors; - - /** - * Create a new format exception. - * - * @param inp - * The string we are attempting to compile - * @param parseErrors - * The errors we encountered parsing the string. - */ - public InflectionFormatException(String inp, List<String> parseErrors) { - this.inp = inp; - // Can't modify the list of parse errors. - this.parseErrors = Collections.unmodifiableList(parseErrors); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Throwable#toString() - */ - @Override - public String toString() { - boolean doBrief = false; - - if (doBrief) - return String.format("Encountered %d errors attempting to parse string %s", - parseErrors.size(), inp); - - StringBuilder sb = new StringBuilder(parseErrors.size()); - sb.append("Encountered errors attempting to parse the following string:\n\t"); - sb.append(inp); - sb.append("\nErrors:"); - for (int i = 0; i < parseErrors.size(); i++) { - String msg = parseErrors.get(i); - sb.append("\n\t"); - sb.append(msg); - } - sb.append("\n(total of "); - sb.append(parseErrors.size()); - sb.append(" errors)"); - - return sb.toString(); - } - } - - /** - * Represents a directive in a inflection string. - * - * @author bjculkin - * - */ - public static final class InflectionDirective { - /** - * The type of the directive in the inflection string. - * - * @author bjculkin - * - */ - public static enum DirectiveType { - /** - * A literal string. Not inflected in any way. - */ - LITERAL, - /** - * A variable reference. Not inflected in any way. - */ - VARIABLE, - /** - * Sets the current number. - */ - NUMERIC, - /** - * Prints an inflected noun. - */ - NOUN, - /** - * Represents a sequence of directives. - */ - SEQ - } - - /** - * Empty base class for directive options. - * - * @author bjculkin - * - */ - public static class Options { - // Empty Base Class - } - - /** - * Options for a numeric directive. - * - * @author bjculkin - * - */ - public static class NumericOptions extends Options { - /** - * Increment the numeric value before doing anything with it. - * - * Corresponds to the 'i' option. - */ - public boolean increment; - /** - * Amount to increase the value by. - * - * Attached to the 'i' option. - */ - public int incrementAmt = 1; - - /** - * Treat zero as singular. - * - * Doesn't correspond directly to the 's' option, but splitting between - * singular zero and using 'no' for zero is useful. - */ - public boolean singular; - - /** - * Print zero as 'no'. - * - * Corresponds to 'n' option. - */ - public boolean zeroNo; - - /** - * Print 'a'/'an' for one. - * - * Corresponds to 'a' option. - */ - public boolean article; - - /** - * Don't print any text. - * - * Corresponds to 'd' option. - */ - public boolean nonPrint; - - /** - * Print the number as a cardinal. - * - * Corresponds to 'w' option. - */ - public boolean cardinal; - /** - * Threshold for when to stop printing the number as a cardinal. - * - * Attached to the 'w' and 'o' options. - */ - public int cardinalThresh = 11; - - /** - * Print the number as an ordinal. - * - * Corresponds to the 'o' option. - */ - public boolean ordinal; - /** - * Threshold for when to stop printing the number as an ordinal. - * - * If the current count is greater than 'cardinalThresh', ordinals like 1st - * and 2nd will be printed instead of first and second. - * - * Attached to the 'o' option. - */ - public int ordinalThresh = Integer.MAX_VALUE; - - /** - * Summarize a number. - * - * Corresponds to the 'f' option. - */ - public boolean summarize; - - /** - * Mark the summarization as occurring at the end of the string, regardless of - * its current position. - */ - public boolean atEnd = false; - - /** - * Create a new set of numeric options from a string. - * - * @param options - * The string to create options from. - * @param curPos - * The current position into the string. - * @param parseErrors - * The current list of parsing errors. - */ - public NumericOptions(String options, int curPos, List<String> parseErrors) { - if (options.equals("")) - return; - - char prevOption = ' '; - StringBuilder currNum = new StringBuilder(); - - boolean doingCaseFolding = false; - - for (int i = 0; i < options.length(); i++) { - char ci = options.charAt(i); - - if (Character.isDigit(ci) || ci == '-' || ci == '+') { - currNum.append(ci); - - continue; - } - - if (doingCaseFolding && Character.isLowerCase(ci)) { - continue; - } else if (Character.isUpperCase(ci)) { - doingCaseFolding = true; - - ci = Character.toLowerCase(ci); - } - - if (currNum.length() > 0) { - parseNumericParam(curPos, parseErrors, prevOption, currNum, i); - - currNum = new StringBuilder(); - } - - switch (ci) { - case 'n': - zeroNo = true; - break; - case 's': - singular = true; - break; - case 'a': - article = true; - break; - case 'w': - cardinal = true; - break; - case 'o': - ordinal = true; - break; - case 'f': - summarize = true; - break; - case 'e': - article = true; - singular = true; - zeroNo = true; - cardinal = true; - break; - case 'i': - increment = true; - break; - case 'd': - nonPrint = true; - break; - default: - parseErrors.add(error(curPos, i, "Unhandled option %c", ci)); - } - - prevOption = ci; - } - - if (currNum.length() > 0) { - parseNumericParam(curPos, parseErrors, prevOption, currNum, - options.length() - 1); - - currNum = new StringBuilder(); - } - } - - /** - * Create a blank set of numeric options. - */ - public NumericOptions() { - } - - private void parseNumericParam(int curPos, List<String> parseErrors, - char prevOption, StringBuilder currNum, int i) { - int nVal = 0; - try { - nVal = Integer.parseInt(currNum.toString()); - } catch (NumberFormatException nfex) { - parseErrors.add(error(curPos, i, - "Improperly formatted numeric parameter %s to option '%c'", - currNum.toString(), prevOption)); - } - switch (prevOption) { - case 'w': - cardinalThresh = nVal; - break; - case 'o': - ordinalThresh = nVal; - break; - case 'f': - if (nVal == 1) { - atEnd = true; - } else if (nVal == 0) { - atEnd = false; - } else { - parseErrors.add(error(curPos, i, - "'f' parameter only takes parameters of zero or one, not %d", - nVal)); - } - break; - case 'i': - incrementAmt = nVal; - break; - default: - parseErrors.add(error(curPos, i, - "Option '%c' does not take a numeric parameter (value %s)", - prevOption, currNum)); - } - } - - // Emit error message - private static String error(int curPos, int i, String msg, Object... props) { - return InflectionDirective.error("#", curPos, i, msg, props); - } - } - - /** - * Options for a noun directive. - * - * @author bjculkin - * - */ - public static class NounOptions extends Options { - /** - * Use the classical inflection for the noun. - */ - public boolean classical; - - /** - * Inflect as plural, regardless of current count. - */ - public boolean plural; - - /** - * Inflect as singular, regardless of current count. - */ - public boolean singular; - - /** - * Create a new set of noun options from a string. - * - * @param options - * The string to create options from. - * @param curPos - * The current position into the string. - * @param parseErrors - * The current list of parsing errors. - */ - public NounOptions(String options, int curPos, List<String> parseErrors) { - if (options.equals("")) - return; - - boolean doingCaseFolding = false; - - for (int i = 0; i < options.length(); i++) { - char ci = options.charAt(i); - - if (doingCaseFolding && Character.isLowerCase(ci)) { - continue; - } else if (Character.isUpperCase(ci)) { - doingCaseFolding = true; - - ci = Character.toLowerCase(ci); - } - - switch (ci) { - case 'c': - classical = true; - break; - case 'p': - plural = true; - break; - case 's': - singular = true; - break; - default: - parseErrors.add(error(curPos, i, "Unhandled option %c", ci)); - } - } - } - - /** - * Create an empty set of noun options. - */ - public NounOptions() { - } - - // Emit error message - private static String error(int curPos, int i, String msg, Object... props) { - return InflectionDirective.error("N", curPos, i, msg, props); - } - } - - // Emit error message - private static String error(String dir, int curPos, int i, String msg, - Object... props) { - return String.format( - "%s (at position %d in %s directive starting at position %d)", - String.format(msg, props), curPos + i, dir, curPos); - } - - /** - * The type of the directive. - */ - public final DirectiveType type; - - /** - * The string value of the directive. - * - * Currently, set for literals and variable references, as well as nouns. - */ - public String litString; - - /** - * The integer value of the directive. - * - * Currently set for numeric values. - */ - public int numNumber; - - /** - * Is this directives body referencing a variable instead of a literal? - */ - public boolean isVRef = false; - - /** - * The options for a directive. - */ - public Options opts; - - /** - * The directives contained in a sequence. - */ - public List<InflectionDirective> listDir; - - /** - * Create a new inflection directive. - * - * @param type - * The type of the directive. - */ - public InflectionDirective(DirectiveType type) { - this.type = type; - - switch (type) { - default: - throw new IllegalArgumentException( - "Unhandled or wrong arguments (none) for directive type " + type); - } - } - - /** - * Create a new inflection directive. - * - * @param type - * The type of the directive. - * @param strang - * The string value for the directive. - */ - public InflectionDirective(DirectiveType type, String strang) { - this.type = type; - - // Set default options. - switch (type) { - case NUMERIC: - this.opts = new NumericOptions(); - break; - case NOUN: - this.opts = new NounOptions(); - break; - default: - // No options for these types - } - - switch (type) { - case LITERAL: - case VARIABLE: - case NUMERIC: // Reference to a numeric variable - case NOUN: - this.litString = strang; - break; - default: - throw new IllegalArgumentException( - "Unhandled or wrong arguments (1 string) for directive type " - + type); - - } - } - - /** - * Create a new inflection directive. - * - * @param type - * The type of the directive. - * @param num - * The number value for the directive. - */ - public InflectionDirective(DirectiveType type, int num) { - this.type = type; - - switch (type) { - case NUMERIC: - this.numNumber = num; - this.opts = new NumericOptions(); - break; - default: - throw new IllegalArgumentException( - "Unhandled or wrong arguments (1 number) for directive type " - + type); - - } - } - - /** - * Create a new inflection directive. - * - * @param type - * The type of the directive. - * @param listDir - * The directive list value for the directive. - */ - public InflectionDirective(DirectiveType type, - List<InflectionDirective> listDir) { - this.type = type; - - switch (type) { - case SEQ: - this.listDir = listDir; - break; - default: - throw new IllegalArgumentException( - "Unhandled or wrong arguments (1 list of directives) for directive type " - + type); - - } - } - - /** - * Create a new literal directive. - * - * @param strang - * The literal string the directive represents. - * @return A literal directive for the given string. - */ - public static InflectionDirective literal(String strang) { - return new InflectionDirective(DirectiveType.LITERAL, strang); - } - - /** - * Create a new variable directive. - * - * @param strang - * The name of the variable to interpolate into the string. - * @return A directive that says to interpolate the given value. - */ - public static InflectionDirective variable(String strang) { - return new InflectionDirective(DirectiveType.VARIABLE, strang); - } - - /** - * Create a new numeric directive. - * - * @param num - * The value of the directive, - * @return A directive that sets the current number to the specific value. - */ - public static InflectionDirective numeric(int num) { - return new InflectionDirective(DirectiveType.NUMERIC, num); - } - - /** - * Create a new numeric directive. - * - * @param strang - * The name of a variable that holds the value of the directive, - * @return A directive that sets the current number to the specific value. - */ - public static InflectionDirective numeric(String strang) { - return new InflectionDirective(DirectiveType.NUMERIC, strang); - } - - /** - * Create a new noun directive. - * - * @param strang - * The noun, or the name of the variable for the noun. - * @return A directive that inflects the specified noun. - */ - public static InflectionDirective noun(String strang) { - return new InflectionDirective(DirectiveType.NOUN, strang); - } - - /** - * Create a sequenced set of directives. - * - * @param list - * The directives to sequence. - * @return A sequence directive. - */ - public static InflectionDirective seq(List<InflectionDirective> list) { - return new InflectionDirective(DirectiveType.SEQ, list); - } - - /** - * Create a sequenced set of directives. - * - * @param arr - * The directives to sequence. - * @return A sequence directive. - */ - public static InflectionDirective seq(InflectionDirective... arr) { - return new InflectionDirective(DirectiveType.SEQ, asList(arr)); - } - - /** - * Set the numeric options for this directive. - * - * @param numOpts - * The numeric options of the directive. - * @return The directive. - */ - public InflectionDirective options(NumericOptions numOpts) { - if (type != DirectiveType.NUMERIC) - throw new IllegalArgumentException( - "Directive type " + type + " does not take numeric options"); - this.opts = numOpts; - - return this; - } - - /** - * Set the noun options for this directive. - * - * @param nounOpts - * The noun options of the directive. - * @return The directive. - */ - public InflectionDirective options(NounOptions nounOpts) { - if (type != DirectiveType.NOUN) - throw new IllegalArgumentException( - "Directive type " + type + " does not take noun options"); - this.opts = nounOpts; - - return this; - } - } - - /** - * Performs the parsing of directives from a string. - * - * @author bjculkin - */ - public class DirectiveIterator implements Iterator<String> { - private String strang; - private int pos; - - /** - * Create a new directive iterator over a string. - * - * @param strang - * The string to parse directives from. - */ - public DirectiveIterator(String strang) { - this.strang = strang; - } - - @Override - public boolean hasNext() { - return pos < strang.length(); - } - - @Override - public String next() { - if (!hasNext()) - return null; - - // Directive nesting level - int level = 0; - int prevPos = pos; - - char prevChar = ' '; - boolean parsingVar = false; - - for (; pos < strang.length(); pos++) { - // Backslash escapes a character - if (prevChar == '\\') - continue; - - char c = strang.charAt(pos); - switch (c) { - case '<': - // Stop parsing at the start of a - // directive, unless the directive is - // the first thing in the string. - if (level == 0 && prevPos != pos) { - return strang.substring(prevPos, pos); - } - level += 1; - break; - case '>': - // :ErrorHandling 11/19/18 - if (level == 0) - throw new IllegalArgumentException( - "Attempted to close inflection directive without one open at position " - + prevPos + " in string '" + strang - + "', current token is '" - + strang.substring(prevPos, pos) + "'"); - // Denest a level - level = Math.max(0, level - 1); - // Stop parsing at the end of a - // directive. - if (level == 0) { - // Advance past the '>' - pos += 1; - - return strang.substring(prevPos, pos); - - } - break; - case '$': - // Ignore v-refs when inside a directive - if (level > 0) - break; - // Stop parsing if this isn't at the - // start of a string - if (prevPos != pos) - return strang.substring(prevPos, pos); - parsingVar = true; - break; - case ' ': - // If we're parsing a v-ref, this - // finishes it. - if (parsingVar) - return strang.substring(prevPos, pos); - break; - default: - // Do nothing for ordinary characters - break; - } - } - - /* - * @TODO 11/19/18 Ben Culkin :ErrorHandling Do something better than this - * exception, if possible. - * - * In the rest of the inflection string code, we use the whole 'list of - * errors/warnings' thing. Is there a way to do something similiar here? - */ - if (level > 0) - throw new IllegalArgumentException( - "Unclosed inflection directive, starting at position " + prevPos - + " in string '" + strang + "'"); - - return strang.substring(prevPos, pos); - } - } - // Create an iterable from an iterator private static Iterable<String> I(Iterator<String> itr) { return () -> itr; @@ -896,19 +109,24 @@ public class InflectionString { dir.isVRef = true; } else if (strang.startsWith("<") && strang.endsWith(">")) { String dirBody = strang.substring(2, strang.length() - 1); + char dirName = strang.charAt(1); int idx = dirBody.indexOf(":"); if (idx == -1) parseErrors.add(error(strang, curPos, "Missing body for %c directive", - strang.charAt(1))); + dirName)); String options = dirBody.substring(0, idx); dirBody = dirBody.substring(idx + 1); - switch (strang.charAt(1)) { + boolean startFold = false; + if (Character.isUpperCase(dirName)) { + startFold = true; + } + switch (dirName) { case '#': { NumericOptions numOpts - = new NumericOptions(options, curPos, parseErrors); + = new NumericOptions(options, curPos, startFold, parseErrors); if (dirBody.startsWith("$")) { dir = numeric(dirBody.substring(1)); @@ -926,8 +144,9 @@ public class InflectionString { dir.options(numOpts); } break; + case 'n': case 'N': { - NounOptions nounOpts = new NounOptions(options, curPos, parseErrors); + NounOptions nounOpts = new NounOptions(options, curPos, startFold, parseErrors); if (dirBody.startsWith("$")) { dir = noun(dirBody.substring(1)); @@ -941,7 +160,7 @@ public class InflectionString { break; default: parseErrors.add(error(strang, curPos, "Unhandled directive type %c", - strang.charAt(1))); + dirName)); } } else { dir = literal(strang); |
