diff options
| author | Ben Culkin <scorpress@gmail.com> | 2022-08-16 23:05:19 -0400 |
|---|---|---|
| committer | Ben Culkin <scorpress@gmail.com> | 2022-08-16 23:05:19 -0400 |
| commit | bdce1524bf06679573ed1875196bf43f4664fe91 (patch) | |
| tree | 4c270949f5fdfa984479785c2a57026fddbedecb | |
| parent | fc9a76f62faef95912d4cf4906f229ccd909d2dc (diff) | |
General cleanup
| -rw-r--r-- | src/main/java/bjc/inflexion/DirectiveIterator.java | 128 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/DirectiveType.java | 45 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/InflectionDirective.java | 291 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/InflectionFormatException.java | 83 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/InflectionML.java | 4 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/InflectionString.java | 805 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/NounOptions.java | 96 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/NumericOptions.java | 248 | ||||
| -rw-r--r-- | src/main/java/bjc/inflexion/Options.java | 26 | ||||
| -rw-r--r-- | src/test/java/bjc/inflexion/InflectionMLTest.java | 8 |
10 files changed, 936 insertions, 798 deletions
diff --git a/src/main/java/bjc/inflexion/DirectiveIterator.java b/src/main/java/bjc/inflexion/DirectiveIterator.java new file mode 100644 index 0000000..9fbed79 --- /dev/null +++ b/src/main/java/bjc/inflexion/DirectiveIterator.java @@ -0,0 +1,128 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +import java.util.Iterator; + +/** + * 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 similar here? + */ + if (level > 0) + throw new IllegalArgumentException( + "Unclosed inflection directive, starting at position " + prevPos + + " in string '" + strang + "'"); + + return strang.substring(prevPos, pos); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/DirectiveType.java b/src/main/java/bjc/inflexion/DirectiveType.java new file mode 100644 index 0000000..51be06b --- /dev/null +++ b/src/main/java/bjc/inflexion/DirectiveType.java @@ -0,0 +1,45 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +/** + * The type of the directive in the inflection string. + * + * @author bjculkin + * + */ +public 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 +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/InflectionDirective.java b/src/main/java/bjc/inflexion/InflectionDirective.java new file mode 100644 index 0000000..27183c0 --- /dev/null +++ b/src/main/java/bjc/inflexion/InflectionDirective.java @@ -0,0 +1,291 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +import static java.util.Arrays.asList; + +import java.util.List; + +/** + * Represents a directive in a inflection string. + * + * @author bjculkin + * + */ +public final class InflectionDirective { + /** + * Emit error message + * + * @param dir The directive causing the error + * @param curPos The position in the string + * @param i The current index + * @param msg The detail message + * @param props Properties for the detail message. + * + * @return A properly formatted error message + */ + public 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; + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/InflectionFormatException.java b/src/main/java/bjc/inflexion/InflectionFormatException.java new file mode 100644 index 0000000..f4b4329 --- /dev/null +++ b/src/main/java/bjc/inflexion/InflectionFormatException.java @@ -0,0 +1,83 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +import java.util.Collections; +import java.util.List; + +/** + * 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(); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/InflectionML.java b/src/main/java/bjc/inflexion/InflectionML.java index 34b534a..ae74c2e 100644 --- a/src/main/java/bjc/inflexion/InflectionML.java +++ b/src/main/java/bjc/inflexion/InflectionML.java @@ -103,7 +103,8 @@ public class InflectionML { final Set<String> optionSet = new HashSet<>(); - boolean doCaseFold = false; + char ch = command.charAt(0); + boolean doCaseFold = Character.isUpperCase(ch); final Map<Character, Integer> numOpts = new HashMap<>(); numOpts.put('w', 11); @@ -265,6 +266,7 @@ public class InflectionML { "Count setter must take a number as a parameter", nfex); } break; + case "n": case "N": final Noun noun = nounDB.getNoun(text); 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); diff --git a/src/main/java/bjc/inflexion/NounOptions.java b/src/main/java/bjc/inflexion/NounOptions.java new file mode 100644 index 0000000..273e700 --- /dev/null +++ b/src/main/java/bjc/inflexion/NounOptions.java @@ -0,0 +1,96 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +import java.util.List; + +/** + * Options for a noun directive. + * + * @author bjculkin + * + */ +public 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 startFold Whether to start with folding on + * @param parseErrors + * The current list of parsing errors. + */ + public NounOptions(String options, int curPos, boolean startFold, List<String> parseErrors) { + if (options.equals("")) + return; + + boolean doingCaseFolding = startFold; + + 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); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/NumericOptions.java b/src/main/java/bjc/inflexion/NumericOptions.java new file mode 100644 index 0000000..179c669 --- /dev/null +++ b/src/main/java/bjc/inflexion/NumericOptions.java @@ -0,0 +1,248 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +import java.util.List; + +/** + * Options for a numeric directive. + * + * @author bjculkin + * + */ +public 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 startFold Whether to start with folding on + * @param parseErrors + * The current list of parsing errors. + */ + public NumericOptions(String options, int curPos, boolean startFold, List<String> parseErrors) { + if (options.equals("")) + return; + + char prevOption = ' '; + StringBuilder currNum = new StringBuilder(); + + boolean doingCaseFolding = startFold; + + 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); + } +}
\ No newline at end of file diff --git a/src/main/java/bjc/inflexion/Options.java b/src/main/java/bjc/inflexion/Options.java new file mode 100644 index 0000000..1cbdaa7 --- /dev/null +++ b/src/main/java/bjc/inflexion/Options.java @@ -0,0 +1,26 @@ +/** + * (C) Copyright 2022 Benjamin Culkin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package bjc.inflexion; + +/** + * Empty base class for directive options. + * + * @author bjculkin + * + */ +public class Options { + // Empty Base Class +}
\ No newline at end of file diff --git a/src/test/java/bjc/inflexion/InflectionMLTest.java b/src/test/java/bjc/inflexion/InflectionMLTest.java index 54ee170..663859a 100644 --- a/src/test/java/bjc/inflexion/InflectionMLTest.java +++ b/src/test/java/bjc/inflexion/InflectionMLTest.java @@ -77,7 +77,7 @@ public class InflectionMLTest { pair("Found a few matches", 4), pair("Found several matches", 8), pair("Found many matches", 11)); - assertInflects("Searching for <Np:items>....found <#f1:%d>", + assertInflects("Searching for <np:items>....found <#f1:%d>", pair("Searching for items....found none", 0), pair("Searching for items....found one", 1), pair("Searching for items....found a couple", 2), @@ -109,7 +109,7 @@ public class InflectionMLTest { pair("7 corpuses found", 7, "corpuses"), pair("7 brothers found", 7, "brothers")); - assertInflects("<#:%d> <Nc:%s> found", pair("7 maxima found", 7, "maximum"), + assertInflects("<#:%d> <nc:%s> found", pair("7 maxima found", 7, "maximum"), pair("7 formulae found", 7, "formula"), pair("7 corpora found", 7, "corpus"), pair("7 brethren found", 7, "brother")); @@ -190,7 +190,7 @@ public class InflectionMLTest { // FIXME Don't require spaces to mark out directives. // - Ben Culkin, 10/28/18 - assertCInflects("Searching for <Np:items> ....found <#f1:$1>", + assertCInflects("Searching for <np:items> ....found <#f1:$1>", pair("Searching for items ....found none", 0), pair("Searching for items ....found one", 1), pair("Searching for items ....found a couple", 2), @@ -222,7 +222,7 @@ public class InflectionMLTest { pair("7 corpuses found", 7, "corpuses"), pair("7 brothers found", 7, "brothers")); - assertCInflects("<#:$1> <Nc:$2> found", pair("7 maxima found", 7, "maximum"), + assertCInflects("<#:$1> <nc:$2> found", pair("7 maxima found", 7, "maximum"), pair("7 formulae found", 7, "formula"), pair("7 corpora found", 7, "corpus"), pair("7 brethren found", 7, "brother")); |
