summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/bjc/inflexion/InflectionML.java7
-rw-r--r--src/main/java/bjc/inflexion/InflectionString.java884
-rw-r--r--src/test/java/bjc/inflexion/InflectPair.java30
-rw-r--r--src/test/java/bjc/inflexion/InflectionMLTest.java117
4 files changed, 1003 insertions, 35 deletions
diff --git a/src/main/java/bjc/inflexion/InflectionML.java b/src/main/java/bjc/inflexion/InflectionML.java
index fc89b4e..be1fe34 100644
--- a/src/main/java/bjc/inflexion/InflectionML.java
+++ b/src/main/java/bjc/inflexion/InflectionML.java
@@ -31,8 +31,11 @@ import bjc.inflexion.nouns.Prepositions;
/*
* @TODO 10/11/17 Ben Culkin :InflectionML
- * Complete the implementation of this from the documentation for
- * Lingua::EN:Inflexion
+ *
+ * Complete the implementation of this from the documentation for Lingua::EN:Inflexion.
+ *
+ * ADDENDA 10/25/18
+ * Everything that doesn't require doing verbs is done.
*/
/**
* Implementation of a simple format language for inflections.
diff --git a/src/main/java/bjc/inflexion/InflectionString.java b/src/main/java/bjc/inflexion/InflectionString.java
new file mode 100644
index 0000000..b9eb764
--- /dev/null
+++ b/src/main/java/bjc/inflexion/InflectionString.java
@@ -0,0 +1,884 @@
+/**
+ *
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import bjc.inflexion.nouns.Noun;
+import bjc.inflexion.nouns.Nouns;
+import bjc.inflexion.nouns.Prepositions;
+
+import static bjc.inflexion.InflectionString.InflectionDirective.*;
+
+/**
+ * A compiled inflection markup string
+ *
+ * @author bjculkin
+ *
+ */
+public class InflectionString {
+ /**
+ * 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
+ }
+
+ /**
+ * Options for a numeric directive.
+ *
+ * @author bjculkin
+ *
+ */
+ public static class NumericOptions {
+ /**
+ * 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;
+
+ // Emit error message
+ private static String error(int curPos, int i, String msg, Object... props) {
+ return String.format("%s (at position %d in # directive starting at %d)",
+ String.format(msg, props), curPos + i, curPos);
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ *
+ */
+ 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));
+ }
+ }
+ }
+
+ /**
+ * Options for a noun directive.
+ *
+ * @author bjculkin
+ *
+ */
+ public static class NounOptions {
+ /**
+ * 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));
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public NounOptions() {
+ }
+
+ // Emit error message
+ private static String error(int curPos, int i, String msg, Object... props) {
+ return String.format("%s (at position %d in N directive starting at %d)",
+ String.format(msg, props), curPos + i, 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 numeric directive.
+ */
+ public NumericOptions numOpts = new NumericOptions();
+
+ /**
+ * The options for a noun directive.
+ */
+ public NounOptions nounOpts = new NounOptions();
+
+ /**
+ * 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;
+
+ 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;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unhandled or wrong arguments (1 number) 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);
+ }
+
+ /**
+ * Set the numeric options for this directive.
+ *
+ * @param numOpts
+ * The numeric options of the directive.
+ * @return The directive.
+ */
+ public InflectionDirective options(NumericOptions numOpts) {
+ this.numOpts = 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) {
+ this.nounOpts = nounOpts;
+
+ return this;
+ }
+ }
+
+ // Marker for finding articles to replace
+ private static Pattern AN_MARKER = Pattern.compile("\\{an(\\d+)\\}");
+
+ // Noun storage
+ private static Nouns nounDB;
+
+ /* Load DBs from files. */
+ static {
+ final Prepositions prepositionDB = new Prepositions();
+ prepositionDB.loadFromStream(InflectionML.class.getResourceAsStream("/prepositions.txt"));
+
+ nounDB = new Nouns(prepositionDB);
+ nounDB.loadFromStream(InflectionML.class.getResourceAsStream("/nouns.txt"));
+ }
+
+ /*
+ * The directives that make up the string.
+ */
+ private List<InflectionDirective> dirs;
+
+ /*
+ * String we were formed from.
+ */
+ private String rawString;
+
+ /**
+ * Create a new empty inflection string.
+ */
+ public InflectionString() {
+ dirs = new ArrayList<>();
+ }
+
+ /**
+ * Create a new compiled inflection string.
+ *
+ * @param inp
+ * The string to compile.
+ */
+ public InflectionString(String inp) {
+ this();
+
+ rawString = inp;
+
+ int curPos = 0;
+
+ List<String> parseErrors = new ArrayList<>();
+
+ // Split input on spaces, preserving the delimiters
+ for (String strang : inp.split("(?<=\\s+)|(?=\\s+)")) {
+ InflectionDirective dir = literal("<ERRROR>");
+
+ // Variables start with $
+ if (strang.startsWith("$")) {
+ dir = variable(strang.substring(1));
+ dir.isVRef = true;
+ // A string starting with $ can be escaped
+ } else if (strang.startsWith("\\$")) {
+ dir = literal(strang.substring(2));
+ } else if (strang.startsWith("<") && strang.endsWith(">")) {
+ String dirBody = strang.substring(2, strang.length() - 1);
+
+ int idx = dirBody.indexOf(":");
+ if (idx == -1) parseErrors.add(error(strang, curPos, "Missing body for %c directive",
+ strang.charAt(1)));
+
+ String options = dirBody.substring(0, idx);
+ dirBody = dirBody.substring(idx + 1);
+
+ switch (strang.charAt(1)) {
+ case '#': {
+ NumericOptions numOpts = new NumericOptions(options, curPos, parseErrors);
+
+ if (dirBody.startsWith("$")) {
+ dir = numeric(dirBody.substring(1));
+ dir.isVRef = true;
+ } else {
+ try {
+ dir = numeric(Integer.parseInt(dirBody));
+ } catch (NumberFormatException nfex) {
+ parseErrors.add(error(strang, curPos,
+ "Non-integer parameter '%s' to # directive",
+ dirBody));
+ }
+ }
+
+ dir.options(numOpts);
+ }
+ break;
+ case 'N': {
+ NounOptions nounOpts = new NounOptions(options, curPos, parseErrors);
+
+ if (dirBody.startsWith("$")) {
+ dir = noun(dirBody.substring(1));
+ dir.isVRef = true;
+ } else {
+ dir = noun(dirBody);
+ }
+
+ dir.options(nounOpts);
+ }
+ break;
+ default:
+ parseErrors.add(error(strang, curPos, "Unhandled directive type %c",
+ strang.charAt(1)));
+ }
+ } else if (strang.startsWith("\\<")) {
+ dir = literal(strang.substring(2));
+ } else {
+ dir = literal(strang);
+ }
+
+ if (dir != null) dirs.add(dir);
+
+ // Bump forward position.
+ curPos += strang.length();
+ }
+
+ }
+
+ // Emit an error message
+ private static String error(String substr, int curPos, String msg, Object... props) {
+ return String.format("%s (starting at position %d inside part %s)", String.format(msg, props), curPos,
+ substr);
+ }
+
+ /**
+ * Execute inflection of the string.
+ *
+ * @param vars
+ * The variables to insert into the string.
+ *
+ * @return The inflected form of the string.
+ */
+ public String inflect(Object... vars) {
+ Map<String, Object> mep = new HashMap<>();
+
+ int i = 0;
+ for (Object var : vars) {
+ mep.put(Integer.toString(++i), var);
+ }
+
+ return inflect(mep);
+ }
+
+ /**
+ * Execute inflection of the string.
+ *
+ * @param vars
+ * The variables to insert into the string.
+ *
+ * @return The inflected form of the string.
+ */
+ public String inflect(Map<String, Object> vars) {
+ StringBuilder sb = new StringBuilder();
+
+ int curNum = 0;
+ int anNum = 0;
+
+ boolean inflectSingular = false;
+ boolean pendingAn = false;
+
+ List<String> anVals = new ArrayList<>();
+
+ for (InflectionDirective dir : dirs) {
+ switch (dir.type) {
+ case LITERAL:
+ sb.append(dir.litString);
+ break;
+ case VARIABLE: {
+ String vName = dir.litString;
+
+ if (vars.containsKey(vName)) {
+ sb.append(vars.get(vName));
+ } else {
+ throw new IllegalArgumentException("Unbound variable " + vName);
+ }
+
+ break;
+ }
+ case NUMERIC: {
+ int actNum;
+
+ if (dir.isVRef) {
+ Object val = vars.get(dir.litString);
+
+ if (!(val instanceof Integer))
+ throw new IllegalArgumentException("Non-numeric variable "
+ + dir.litString + " passed to # directive");
+
+ actNum = (Integer) val;
+ } else {
+ actNum = dir.numNumber;
+ }
+
+ curNum = actNum;
+ {
+ NumericOptions opts = dir.numOpts;
+ String rep = Integer.toString(curNum);
+
+ if (opts.increment) curNum += opts.incrementAmt;
+ if (curNum == 1) {
+ inflectSingular = true;
+ } else if (curNum == 0 && opts.singular) {
+ inflectSingular = true;
+ } else {
+ inflectSingular = false;
+ }
+
+ if (opts.zeroNo && curNum == 0) rep = "no";
+
+ if (opts.article && curNum == 1) {
+ anNum += 1;
+ rep = "{an" + anNum + "}";
+
+ pendingAn = true;
+ }
+
+ if (opts.nonPrint) break;
+
+ boolean override = true;
+ if (rep.equals("no") || rep.matches("\\{an\\d+\\}")) {
+ override = false;
+ }
+
+ if (override) {
+ if (opts.cardinal) {
+ rep = NumberUtils.toCardinal(curNum, opts.cardinalThresh);
+ }
+
+ if (opts.ordinal) {
+ if (opts.cardinal) {
+ rep = NumberUtils.toOrdinal(curNum, opts.ordinalThresh,
+ curNum < opts.cardinalThresh);
+ } else {
+ rep = NumberUtils.toOrdinal(curNum, opts.ordinalThresh,
+ false);
+ }
+
+ if (curNum < opts.ordinalThresh) {
+ // Respect english usage of ordinals
+ curNum = 1;
+
+ inflectSingular = true;
+ }
+ }
+
+ if (opts.summarize) {
+ rep = NumberUtils.summarizeNumber(curNum, opts.atEnd);
+ }
+ }
+
+ sb.append(rep);
+ }
+
+ break;
+ }
+ case NOUN: {
+ String actNoun;
+
+ if (dir.isVRef) {
+ Object val = vars.get(dir.litString);
+
+ if (!(val instanceof String))
+ throw new IllegalArgumentException("Non-numeric variable "
+ + dir.litString + " passed to # directive");
+
+ actNoun = (String) val;
+ } else {
+ actNoun = dir.litString;
+ }
+
+ final Noun noun = nounDB.getNoun(actNoun);
+
+ String nounVal;
+
+ if (dir.nounOpts.plural || !inflectSingular) {
+ if (dir.nounOpts.classical) {
+ nounVal = noun.classicalPlural();
+ } else {
+ nounVal = noun.plural();
+ }
+ } else {
+ nounVal = noun.singular();
+ }
+
+ sb.append(nounVal);
+ if (pendingAn) {
+ anVals.add(EnglishUtils.pickIndefinite(nounVal));
+
+ pendingAn = false;
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unhandled directive type " + dir.type);
+ }
+ }
+
+ String res = sb.toString();
+
+ StringBuffer work = new StringBuffer();
+
+ Matcher anMat = AN_MARKER.matcher(res);
+
+ Iterator<String> anItr = anVals.iterator();
+ while (anMat.find()) {
+ anMat.appendReplacement(work, anItr.next());
+ }
+ anMat.appendTail(work);
+
+ return work.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (rawString != null) return rawString;
+ else return super.toString();
+ }
+}
diff --git a/src/test/java/bjc/inflexion/InflectPair.java b/src/test/java/bjc/inflexion/InflectPair.java
deleted file mode 100644
index d2f076a..0000000
--- a/src/test/java/bjc/inflexion/InflectPair.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * (C) Copyright 2018 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;
-
-class InflectPair {
- public String exp;
- public Object[] pars;
-
- public InflectPair(String str, Object... pars) {
- this.exp = str;
- this.pars = pars;
- }
-
- public static InflectPair pair(String str, Object... pars) {
- return new InflectPair(str, pars);
- }
-} \ 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 d94407f..f40070d 100644
--- a/src/test/java/bjc/inflexion/InflectionMLTest.java
+++ b/src/test/java/bjc/inflexion/InflectionMLTest.java
@@ -3,8 +3,8 @@ package bjc.inflexion;
import org.junit.Test;
import static org.junit.Assert.*;
-import static bjc.inflexion.InflectPair.pair;
+import static bjc.inflexion.InflectionMLTest.InflectPair.pair;
import static bjc.inflexion.InflectionML.inflect;
/**
@@ -99,8 +99,97 @@ public class InflectionMLTest {
pair("7 brethren found", 7, "brother"));
}
- private static void assertInflects(String exp, String real) {
- assertEquals(exp, inflect(real));
+ /**
+ * Test compiled inflection markup.
+ */
+ @Test
+ public void testCompiledML() {
+ assertCInflects("test literal string", pair("test literal string"));
+
+ assertCInflects("test $1 string", pair("test literal string", "literal"),
+ pair("test variable string", "variable"));
+ //////////////////////
+ // Check # command //
+ //////////////////////
+
+ // Check general inflection
+ assertCInflects("<#:$1> <N:indexes> $2 found", pair("0 indexes were found", 0, "were"),
+ pair("1 index was found", 1, "was"), pair("99 indexes were found", 99, "were"));
+
+ // Check fancier inflection
+ assertCInflects("<#wnc:$1> <Noun:indexes> $2 found", pair("no indexes were found", 0, "were"),
+ pair("one index was found", 1, "was"), pair("99 indexes were found", 99, "were"));
+
+ // Check count inflection
+ assertCInflects("<#w20:$1> <N:indexes> were found", pair("six indexes were found", 6),
+ pair("nineteen indexes were found", 19), pair("20 indexes were found", 20));
+
+ // Check 'n' option
+ assertCInflects("<#n:$1> <N:results>", pair("no results", 0), pair("7 results", 7));
+
+ // FIXME
+ //
+ // Adjust this to use <V> for were/was when it is implemented
+ // Check general inflection
+ //
+ // For now, compiled inflection strings don't support variable options
+ assertCInflects("<#n:$1> <N:item> $2 found", pair("no items were found", 0, "were"));
+ assertCInflects("<#ns:$1> <N:item> $2 found", pair("no item was found", 0, "was"));
+
+ // Check article picking
+ assertCInflects("<#a:$1> <N:$2>", pair("a result", 1, "results"), pair("3 results", 3, "results"),
+ pair("an outcome", 1, "outcomes"), pair("7 outcomes", 7, "outcomes"));
+
+ // Check 'w' option
+ assertCInflects("<#w:$1> <N:results>", pair("six results", 6), pair("ten results", 10),
+ pair("11 results", 11));
+ // Check 'o' option
+ assertCInflects("<#o:$1> <N:results>", pair("6th result", 6), pair("11th result", 11),
+ pair("22nd result", 22));
+ assertCInflects("<#ow:$1> <N:results>", pair("first result", 1), pair("sixth result", 6),
+ pair("22nd result", 22));
+ assertCInflects("<#o15:$1> <N:results>", pair("6th result", 6), pair("11th result", 11),
+ pair("22 results", 22));
+
+ // Check 'f' option
+ assertCInflects("Found <#f:$1> <N:matches>", pair("Found no matches", 0), pair("Found one match", 1),
+ pair("Found a couple of matches", 2), pair("Found a few matches", 4),
+ pair("Found several matches", 8), pair("Found many matches", 11));
+
+ // FIXME Don't require spaces to mark out directives.
+ // - Ben Culkin, 10/28/18
+ 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),
+ pair("Searching for items ....found a few", 4),
+ pair("Searching for items ....found several", 8),
+ pair("Searching for items ....found many", 11));
+
+ assertCInflects("Found <#fns:$1> <N:matches>", pair("Found no match", 0),
+ pair("Found several matches", 7));
+ assertCInflects("Found <#fa:$1> <N:matches>", pair("Found a match", 1),
+ pair("Found several matches", 7));
+
+ // Check 'e' option
+ assertCInflects("Found <#e:$1> <N:matches>", pair("Found no match", 0), pair("Found a match", 1),
+ pair("Found ten matches", 10), pair("Found 12 matches", 12));
+
+ // Check 'd' option
+ assertCInflects("<#d:$1> <N:Match> found", pair(" Match found", 1), pair(" Matches found", 2));
+
+ ////////////////////////
+ // Check N command //
+ ///////////////////////
+
+ // Check 'c' option
+ assertCInflects("<#:$1> <N:$2> found", pair("7 maximums found", 7, "maximum"),
+ pair("7 formulas found", 7, "formula"), pair("7 corpuses found", 7, "corpuses"),
+ pair("7 brothers found", 7, "brothers"));
+
+ 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"));
}
private static void assertInflects(String real, InflectPair... pairs) {
@@ -108,4 +197,26 @@ public class InflectionMLTest {
assertEquals(pair.exp, inflect(String.format(real, pair.pars)));
}
}
+
+ private static void assertCInflects(String real, InflectPair... pairs) {
+ InflectionString strang = new InflectionString(real);
+
+ for (InflectPair pair : pairs) {
+ assertEquals(pair.exp, strang.inflect(pair.pars));
+ }
+ }
+
+ static class InflectPair {
+ public String exp;
+ public Object[] pars;
+
+ public InflectPair(String str, Object... pars) {
+ this.exp = str;
+ this.pars = pars;
+ }
+
+ public static InflectPair pair(String str, Object... pars) {
+ return new InflectPair(str, pars);
+ }
+ }
}