summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbculkin2442 <bjculkin@mix.wvu.edu>2018-10-14 20:15:17 -0400
committerbculkin2442 <bjculkin@mix.wvu.edu>2018-10-14 20:15:17 -0400
commit004bc5de7618bc44079e6cdd21a50d6814c76c50 (patch)
tree3cbcc77bf04c7dae4c8ad3d745d181d4bfca77fb
parentb09885e13d8829ee59e10ec0a957f6209c3e4aeb (diff)
General update
Testing, plus some reorganization
-rw-r--r--.settings/org.eclipse.jdt.core.prefs98
-rw-r--r--src/main/java/bjc/inflexion/CardinalState.java78
-rw-r--r--src/main/java/bjc/inflexion/EnglishUtils.java52
-rw-r--r--src/main/java/bjc/inflexion/InflectionML.java116
-rw-r--r--src/main/java/bjc/inflexion/NumberUtils.java570
-rw-r--r--src/main/java/bjc/inflexion/nouns/Noun.java23
-rw-r--r--src/test/java/bjc/inflexion/InflectPair.java30
-rw-r--r--src/test/java/bjc/inflexion/InflectionMLTest.java101
8 files changed, 977 insertions, 91 deletions
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index d59e09c..55d9710 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,5 +1,103 @@
eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=info
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=info
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=info
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=info
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=info
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=info
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=info
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=info
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=info
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=info
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=info
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=info
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=info
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=enabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=info
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=info
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=info
+org.eclipse.jdt.core.compiler.problem.unusedParameter=info
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=info
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.8
diff --git a/src/main/java/bjc/inflexion/CardinalState.java b/src/main/java/bjc/inflexion/CardinalState.java
new file mode 100644
index 0000000..b9867f7
--- /dev/null
+++ b/src/main/java/bjc/inflexion/CardinalState.java
@@ -0,0 +1,78 @@
+package bjc.inflexion;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.BiFunction;
+import java.util.function.LongPredicate;
+
+/*
+ * @TODO 2/12/18 Ben Culkin :AdditionalCardinals
+ *
+ * Add some built-in implementations for various things.
+ *
+ * By this, I mean for various unit scales, like custom and metric weights
+ */
+/**
+ * Customizations for number cardinalization.
+ *
+ * @author EVE
+ *
+ */
+public class CardinalState {
+ /**
+ * Alias type for converting numbers to cardinals.
+ *
+ * @author EVE
+ *
+ */
+ @FunctionalInterface
+ public interface Cardinalizer extends BiFunction<Long, CardinalState, String> {
+ /*
+ * Alias
+ */
+ }
+
+ /**
+ * Custom cardinals for numbers.
+ */
+ public final Map<Long, String> customNumbers;
+
+ /**
+ * Custom functions to apply to certain scales.
+ */
+ public final Map<LongPredicate, Cardinalizer> customScales;
+
+ /**
+ * Create a new set of cardinalization customizations.
+ *
+ * @param customNumbers
+ * The custom numbers to use.
+ * @param customScales
+ * The custom scales to use.
+ */
+ public CardinalState(Map<Long, String> customNumbers, Map<LongPredicate, Cardinalizer> customScales) {
+ this.customNumbers = customNumbers;
+ this.customScales = customScales;
+ }
+
+ /**
+ * Handle a custom cardinal number
+ *
+ * @param number
+ * The number to handle
+ * @return The number as a cardinal, or null if we don't handle it.
+ */
+ public String handleCustom(long number) {
+ if(customNumbers.containsKey(number)) {
+ return customNumbers.get(number);
+ }
+
+ for(Entry<LongPredicate, Cardinalizer> ent : customScales.entrySet()) {
+ if(ent.getKey().test(number)) {
+ return ent.getValue().apply(number, this);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/bjc/inflexion/EnglishUtils.java b/src/main/java/bjc/inflexion/EnglishUtils.java
index 1a399be..cee804d 100644
--- a/src/main/java/bjc/inflexion/EnglishUtils.java
+++ b/src/main/java/bjc/inflexion/EnglishUtils.java
@@ -22,58 +22,6 @@ import java.util.regex.Pattern;
* @author student
*/
public class EnglishUtils {
- private static String[] smallNums = new String[] {
- "zero", "one", "two", "three", "four", "five", "six", "seven",
- "eight", "nine", "ten" };
-
- private static String[] summaryNums = new String[] { "no", "one", "a couple of", "a few", "several" };
-
- private static int[] summaryMap = new int[] {
- /* no */
- 0,
- /* one */
- 1,
- /* a couple of */
- 2,
- /* a few */
- 3, 3, 3,
- /* several */
- 4, 4, 4, 4
- };
-
- /**
- * Convert small integers to words.
- *
- * @param num
- * The number to convert.
- *
- * @return
- * The word for the number, if it's less than ten.
- */
- public static String smallIntToWord(final int num) {
- if (num >= 0 && num <= 10) return smallNums[num];
-
- return Integer.toString(num);
- }
-
- /**
- * Summarize an integer.
- *
- * @param num
- * The number to summarize.
- *
- * @param atEnd
- * Whether or not the integer is at the end of a string.
- *
- * @return
- * A string summarizing the integer.
- */
- public static String intSummarize(final int num, final boolean atEnd) {
- if (num >= 0 && num < 10) return summaryNums[summaryMap[num]];
-
- return "many";
- }
-
/**
* Pick an indefinite article ('a' or 'an') for a phrase.
* @param phrase The phrase to pick an article for.
diff --git a/src/main/java/bjc/inflexion/InflectionML.java b/src/main/java/bjc/inflexion/InflectionML.java
index 46cbc59..fc89b4e 100644
--- a/src/main/java/bjc/inflexion/InflectionML.java
+++ b/src/main/java/bjc/inflexion/InflectionML.java
@@ -15,8 +15,10 @@ package bjc.inflexion;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Matcher;
@@ -33,7 +35,7 @@ import bjc.inflexion.nouns.Prepositions;
* Lingua::EN:Inflexion
*/
/**
- * Implementation of a simple format language for inflections.
+ * Implementation of a simple format language for inflections.
*
* @author student
*/
@@ -42,8 +44,7 @@ public class InflectionML {
private static final List<String> ESUB_OPT = Arrays.asList("a", "s", "w");
/* The regex that marks an inflection form. */
- private static Pattern FORM_MARKER =
- Pattern.compile("<(?<command>[#N])(?<options>[^:]*):(?<text>[^>]*)>");
+ private static Pattern FORM_MARKER = Pattern.compile("<(?<command>[#N])(?<options>[^:]*):(?<text>[^>]*)>");
private static Pattern AN_MARKER = Pattern.compile("\\{an(\\d+)\\}");
@@ -63,10 +64,9 @@ public class InflectionML {
* Apply inflection to marked forms in the string.
*
* @param form
- * The string to inflect.
+ * The string to inflect.
*
- * @return
- * The inflected string.
+ * @return The inflected string.
*/
public static String inflect(String form) {
Matcher formMatcher = FORM_MARKER.matcher(form);
@@ -84,38 +84,76 @@ public class InflectionML {
while (formMatcher.find()) {
final String command = formMatcher.group("command");
final String options = formMatcher.group("options");
- final String text = formMatcher.group("text");
+ final String text = formMatcher.group("text");
final Set<String> optionSet = new HashSet<>();
boolean doCaseFold = false;
+ final Map<Character, Integer> numOpts = new HashMap<>();
+ numOpts.put('w', 11);
+ numOpts.put('o', Integer.MAX_VALUE);
+ numOpts.put('f', 0);
+
if (!options.equals("")) {
if (options.matches("(?:[a-z]*[A-Z]+[a-z])+")) {
doCaseFold = true;
}
+ char prevOption = ' ';
+
+ StringBuilder currNum = new StringBuilder();
+
for (int i = 0; i < options.length(); i++) {
- char ci = options.charAt(i);
+ char ci = options.charAt(i);
+
+ if (Character.isDigit(ci)) {
+ currNum.append(ci);
+ continue;
+ }
+
+ if (currNum.length() > 0) {
+ numOpts.put(prevOption, Integer.parseInt(currNum.toString()));
+
+ currNum = new StringBuilder();
+ }
+
String opt = Character.toString(ci);
+ // @TODO Ben Culkin 10/14/18
+ //
+ // There is some weird bug that I think is related to case folding,
+ // where having options with a capitalized letter followed by more
+ // than 1 lowercase letter gets ignored.
if (doCaseFold) {
- if (Character.isUpperCase(ci))
+ if (Character.isUpperCase(ci)) {
+ System.err.printf("Case-folding '%c'\n", ci);
+
opt = opt.toLowerCase();
- else
+ } else {
+
+ System.err.printf("Ignoring '%c' due to case folding\n", ci);
continue;
+ }
}
+ prevOption = ci;
optionSet.add(opt);
}
+
+ if (currNum.length() > 0) {
+ numOpts.put(prevOption, Integer.parseInt(currNum.toString()));
+
+ currNum = new StringBuilder();
+ }
}
switch (command) {
case "#":
- /* @NOTE
- * These should maybe be moved into their
- * own function. This will also allow the
- * use of custom inflection forms.
+ /*
+ * @NOTE These should maybe be moved into their
+ * own function. This will also allow the use of
+ * custom inflection forms.
*/
try {
if (optionSet.contains("e")) {
@@ -164,24 +202,51 @@ public class InflectionML {
/* Break out of switch. */
if (optionSet.contains("d")) {
- formMatcher.appendReplacement(formBuffer, rep);
+ formMatcher.appendReplacement(formBuffer, "");
break;
}
-
- final boolean shouldOverride = !(rep.equals("no") || rep.matches("\\{an\\d+\\}"));
+
+ boolean shouldOverride = true;
+ if (rep.equals("no") || rep.matches("\\{an\\d+\\}")) {
+ shouldOverride = false;
+ }
if (optionSet.contains("w") && shouldOverride) {
- rep = EnglishUtils.smallIntToWord(curCount);
+ rep = NumberUtils.toCardinal(curCount, numOpts.get('w'));
+
+ }
+
+ if (optionSet.contains("o") && shouldOverride) {
+ if (optionSet.contains("w")) {
+ if (curCount < numOpts.get('w'))
+ rep = NumberUtils.toOrdinal(curCount, numOpts.get('o'),
+ true);
+ else
+ rep = NumberUtils.toOrdinal(curCount, numOpts.get('o'),
+ false);
+ } else {
+ rep = NumberUtils.toOrdinal(curCount, numOpts.get('o'), false);
+ }
+
+ if (curCount < numOpts.get('o')) {
+ // Respect english usage of ordinals
+ curCount = 1;
+ inflectSingular = true;
+ }
}
if (optionSet.contains("f") && shouldOverride) {
- rep = EnglishUtils.intSummarize(curCount, false);
+ rep = NumberUtils.summarizeNumber(curCount, numOpts.get('f') != 0);
}
+ numOpts.put('o', Integer.MAX_VALUE);
+ numOpts.put('w', 11);
+ numOpts.put('f', 0);
+
formMatcher.appendReplacement(formBuffer, rep);
} catch (final NumberFormatException nfex) {
throw new InflectionException("Count setter must take a number as a parameter",
- nfex);
+ nfex);
}
break;
case "N":
@@ -200,7 +265,7 @@ public class InflectionML {
}
formMatcher.appendReplacement(formBuffer, nounVal);
- if(pendingAN) {
+ if (pendingAN) {
anVals.add(EnglishUtils.pickIndefinite(nounVal));
pendingAN = false;
@@ -222,7 +287,7 @@ public class InflectionML {
Matcher anMat = AN_MARKER.matcher(res);
Iterator<String> anItr = anVals.iterator();
- while(anMat.find()) {
+ while (anMat.find()) {
anMat.appendReplacement(formBuffer, anItr.next());
}
anMat.appendTail(formBuffer);
@@ -234,13 +299,12 @@ public class InflectionML {
* Alias method to format a string, then inflect it.
*
* @param format
- * The combined format/inflection string.
+ * The combined format/inflection string.
*
* @param objects
- * The parameters for the format string.
+ * The parameters for the format string.
*
- * @return
- * The string, formatted &amp; inflected.
+ * @return The string, formatted &amp; inflected.
*/
public static String iprintf(final String format, final Object... objects) {
return inflect(String.format(format, objects));
diff --git a/src/main/java/bjc/inflexion/NumberUtils.java b/src/main/java/bjc/inflexion/NumberUtils.java
new file mode 100644
index 0000000..3a0200d
--- /dev/null
+++ b/src/main/java/bjc/inflexion/NumberUtils.java
@@ -0,0 +1,570 @@
+package bjc.inflexion;
+
+/**
+ * A variety of functions for doing useful stuff with numbers.
+ *
+ * @author EVE
+ *
+ */
+public class NumberUtils {
+ /*
+ * @TODO 2/12/18 Ben Culkin :RomanExpansion
+ *
+ * Use U+305 for large roman numerals, as well as excels 'concise'
+ * numerals (as implemented by roman()).
+ */
+
+ /**
+ * Convert a number into a roman numeral.
+ *
+ * @param number
+ * The number to convert.
+ * @param classic
+ * Whether to use classic roman numerals (use IIII
+ * instead of IV, and such).
+ * @return The number as a roman numeral.
+ */
+ public static String toRoman(long number, boolean classic) {
+ StringBuilder work = new StringBuilder();
+
+ long currNumber = number;
+
+ if (currNumber == 0) { return "N"; }
+
+ if (currNumber < 0) {
+ currNumber *= -1;
+
+ work.append("-");
+ }
+
+ if (currNumber >= 1000) {
+ int numM = (int) (currNumber / 1000);
+ currNumber = currNumber % 1000;
+
+ for (int i = 0; i < numM; i++) {
+ work.append("M");
+ }
+ }
+
+ if (currNumber >= 900 && !classic) {
+ currNumber = currNumber % 900;
+
+ work.append("CM");
+ }
+
+ if (currNumber >= 500) {
+ currNumber = currNumber % 500;
+
+ work.append("D");
+ }
+
+ if (currNumber >= 400 && !classic) {
+ currNumber = currNumber % 400;
+
+ work.append("CD");
+ }
+
+ if (currNumber >= 100) {
+ int numC = (int) (currNumber / 100);
+ currNumber = currNumber % 100;
+
+ for (int i = 0; i < numC; i++) {
+ work.append("C");
+ }
+ }
+
+ if (currNumber >= 90 && !classic) {
+ currNumber = currNumber % 90;
+
+ work.append("XC");
+ }
+
+ if (currNumber >= 50) {
+ currNumber = currNumber % 50;
+
+ work.append("L");
+ }
+
+ if (currNumber >= 40 && !classic) {
+ currNumber = currNumber % 40;
+
+ work.append("XL");
+ }
+
+ if (currNumber >= 10) {
+ int numX = (int) (currNumber / 10);
+ currNumber = currNumber % 10;
+
+ for (int i = 0; i < numX; i++) {
+ work.append("X");
+ }
+ }
+
+ if (currNumber >= 9 && !classic) {
+ currNumber = currNumber % 9;
+
+ work.append("IX");
+ }
+
+ if (currNumber >= 5) {
+ currNumber = currNumber % 5;
+
+ work.append("V");
+ }
+
+ if (currNumber >= 4 && !classic) {
+ currNumber = currNumber % 4;
+
+ work.append("IV");
+ }
+
+ if (currNumber >= 1) {
+ int numI = (int) (currNumber / 1);
+ currNumber = currNumber % 1;
+
+ for (int i = 0; i < numI; i++) {
+ work.append("I");
+ }
+ }
+
+ return work.toString();
+ }
+
+ private static String[] summaryNums = new String[] { "no", "one", "a couple of", "a few", "several" };
+
+ private static String[] summaryNumsEnd = new String[] { "none", "one", "a couple", "a few", "several" };
+
+ private static int[] summaryMap = new int[] {
+ /* no */
+ 0,
+ /* one */
+ 1,
+ /* a couple of */
+ 2,
+ /* a few */
+ 3, 3, 3,
+ /* several */
+ 4, 4, 4, 4 };
+
+ /**
+ * Summarize an integer.
+ *
+ * @param num
+ * The number to summarize.
+ *
+ * @param atEnd
+ * Whether or not the integer is at the end of a string.
+ *
+ * @return A string summarizing the integer.
+ */
+ public static String summarizeNumber(final long num, final boolean atEnd) {
+ if (num >= 0 && num < 10) {
+ if (atEnd) return summaryNumsEnd[summaryMap[(int) num]];
+
+ return summaryNums[summaryMap[(int) num]];
+ }
+
+ return "many";
+ }
+
+ /**
+ * Convert a number into a cardinal number, up to a threshold.
+ *
+ * @param number
+ * The number to convert
+ * @param thresh
+ * The threshold to stop at.
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number, long thresh) {
+ if (number < thresh) return toCardinal(number, null);
+
+ return Long.toString(number);
+ }
+
+ /**
+ * Convert a number into a cardinal number.
+ *
+ * @param number
+ * The number to convert
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number) {
+ return toCardinal(number, null);
+ }
+
+ private static String[] cardinals = new String[] { "zero", "one", "two", "three", "four", "five", "six",
+ "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
+ "sixteen", "seventeen", "eighteen", "nineteen", "twenty", };
+
+ /**
+ * Convert a number into a cardinal number.
+ *
+ * @param number
+ * The number to convert to a cardinal.
+ * @param custom
+ * The customizations to use.
+ * @return The number as a cardinal.
+ */
+ public static String toCardinal(long number, CardinalState custom) {
+ if (custom != null) {
+ String res = custom.handleCustom(number);
+
+ if (res != null) return res;
+ }
+
+ if (number < 0) return "negative " + toCardinal(number * -1, custom);
+
+ if (number <= 20) return cardinals[(int) number];
+
+ if (number < 100) {
+ if (number % 10 == 0) {
+ switch ((int) number) {
+ case 30:
+ return "thirty";
+ case 40:
+ return "forty";
+ case 50:
+ return "fifty";
+ case 60:
+ return "sixty";
+ case 70:
+ return "seventy";
+ case 80:
+ return "eighty";
+ case 90:
+ return "ninety";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert (false);
+ }
+ }
+
+ long numTens = number / 10;
+ long numOnes = number % 10;
+
+ return toCardinal(numTens, custom) + "-" + toCardinal(numOnes, custom);
+ }
+
+ if (number < 1000) {
+ long numHundreds = number / 100;
+ long rest = number % 100;
+
+ return toCardinal(numHundreds, custom) + " hundred and " + toCardinal(rest, custom);
+ }
+
+ long MILLION = (long) (Math.pow(10, 6));
+ if (number < MILLION) {
+ long numThousands = number / 1000;
+ long rest = number % 1000;
+
+ return toCardinal(numThousands, custom) + " thousand, " + toCardinal(rest, custom);
+ }
+
+ long BILLION = (long) (Math.pow(10, 9));
+ if (number < BILLION) {
+ long numMillions = number / MILLION;
+ long rest = number % MILLION;
+
+ return toCardinal(numMillions, custom) + " million, " + toCardinal(rest, custom);
+ }
+
+ long TRILLION = (long) (Math.pow(10, 12));
+ if (number < TRILLION) {
+ long numBillions = number / BILLION;
+ long rest = number % BILLION;
+
+ return toCardinal(numBillions, custom) + " billion, " + toCardinal(rest, custom);
+ }
+
+ throw new IllegalArgumentException(
+ "Numbers greater than or equal to 1 trillion are not supported yet.");
+ }
+
+ /**
+ * Convert a number into an ordinal, up to a certain value.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param thresh
+ * The threshold value to stop converting at.
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, long thresh) {
+ return toOrdinal(number, thresh, true);
+ }
+
+ /**
+ * Convert a number into an ordinal, up to a certain value.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param thresh
+ * The threshold value to stop converting at.
+ * @param longForm
+ * Whether or not to use long-form ordinals (zeroth,
+ * first, etc.) instead of (0th, 1st, etc.)
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, long thresh, boolean longForm) {
+ if (number < thresh) return toOrdinal(number, longForm);
+
+ return Long.toString(number);
+ }
+
+ /**
+ * Convert a number into an ordinal.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number) {
+ return toOrdinal(number, true);
+ }
+
+ /**
+ * Convert a number into an ordinal.
+ *
+ * @param number
+ * The number to convert to an ordinal.
+ * @param longForm
+ * Whether or not to use long-form ordinals (zeroth,
+ * first, etc.) instead of (0th, 1st, etc.)
+ * @return The number as an ordinal.
+ */
+ public static String toOrdinal(long number, boolean longForm) {
+ if (number < 0) { return "minus " + toOrdinal(number, longForm); }
+
+ if (longForm) {
+ if (number < 20) {
+ switch ((int) number) {
+ case 0:
+ return "zeroth";
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ case 13:
+ return "thirteenth";
+ case 14:
+ return "fourteenth";
+ case 15:
+ return "fifteenth";
+ case 16:
+ return "sixteenth";
+ case 17:
+ return "seventeenth";
+ case 18:
+ return "eighteenth";
+ case 19:
+ return "nineteenth";
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ assert (false);
+ }
+ }
+
+ if (number < 100) {
+ if (number % 10 == 0) {
+ switch ((int) number) {
+ case 20:
+ return "twentieth";
+ case 30:
+ return "thirtieth";
+ case 40:
+ return "fortieth";
+ case 50:
+ return "fiftieth";
+ case 60:
+ return "sixtieth";
+ case 70:
+ return "seventieth";
+ case 80:
+ return "eightieth";
+ case 90:
+ return "ninetieth";
+ default:
+ throw new IllegalArgumentException(
+ String.format("Illegal number %d", number));
+ }
+ }
+
+ long numPostfix = number % 10;
+ return toCardinal(number - numPostfix) + "-" + toOrdinal(numPostfix, longForm);
+ }
+ }
+
+ long procNum = number % 100;
+ long tens = procNum / 10;
+ long ones = procNum % 10;
+
+ if (tens == 1) { return Long.toString(number) + "th"; }
+
+ switch ((int) ones) {
+ case 1:
+ return Long.toString(number) + "st";
+ case 2:
+ return Long.toString(number) + "nd";
+ case 3:
+ return Long.toString(number) + "rd";
+ default:
+ return Long.toString(number) + "th";
+ }
+ }
+
+ private static char[] radixChars = new char[62];
+ static {
+ int idx = 0;
+
+ for (char i = 0; i < 10; i++) {
+ radixChars[idx] = (char) ('0' + i);
+
+ idx += 1;
+ }
+
+ for (char i = 0; i < 26; i++) {
+ radixChars[idx] = (char) ('A' + i);
+
+ idx += 1;
+ }
+
+ for (char i = 0; i < 26; i++) {
+ radixChars[idx] = (char) ('a' + i);
+
+ idx += 1;
+ }
+ }
+
+ /**
+ * Convert a number into a commafied string.
+ *
+ * @param val
+ * The number to convert.
+ * @param mincols
+ * The minimum number of columns to use.
+ * @param padchar
+ * The padding char to use.
+ * @param commaInterval
+ * The interval to place commas at.
+ * @param commaChar
+ * The character to use as a comma
+ * @param signed
+ * Whether or not to always display a sign
+ * @param radix
+ * The radix to use
+ * @return The number as a commafied string.
+ */
+ public static String toCommaString(long val, int mincols, char padchar, int commaInterval, char commaChar,
+ boolean signed, int radix) {
+ if (radix > radixChars.length) { throw new IllegalArgumentException(String.format(
+ "Radix %d is larger than largest supported radix %d", radix, radixChars.length)); }
+
+ StringBuilder work = new StringBuilder();
+
+ boolean isNeg = false;
+ long currVal = val;
+ if (currVal < 0) {
+ isNeg = true;
+ currVal *= -1;
+ }
+
+ if (currVal == 0) {
+ work.append(radixChars[0]);
+ } else {
+ int valCounter = 0;
+
+ while (currVal != 0) {
+ valCounter += 1;
+
+ int radDigit = (int) (currVal % radix);
+ work.append(radixChars[radDigit]);
+ currVal = currVal / radix;
+
+ if (commaInterval != 0 && valCounter % commaInterval == 0 && currVal != 0)
+ work.append(commaChar);
+ }
+ }
+
+ if (isNeg)
+ work.append("-");
+ else if (signed) work.append("+");
+
+ work.reverse();
+
+ /*
+ * @TODO
+ *
+ * Should we have some way to specify how to pad?
+ *
+ * By this, I mean specify padding direction (left, right,
+ * balanced...)
+ */
+ StringBuilder pad = new StringBuilder();
+
+ if (work.length() < mincols) {
+ @SuppressWarnings("unused")
+ int padCount = 0;
+ for (int i = work.length(); i < mincols; i++) {
+ // @NOTE 9/6/18 :CommaPad
+ //
+ // I have no idea if this is the intended
+ // behavior, or if something is wrong with the
+ // example case in the menu
+ // if (commaInterval != 0 && padCount != 0) {
+ // if (Character.isDigit(padchar) && padCount % commaInterval == 0)
+ // pad.append(commaChar);
+ // else
+ pad.append(padchar);
+ // }
+
+ padCount++;
+ }
+ }
+
+ return pad.toString() + work.toString();
+ }
+
+ /**
+ * Convert a number to a normal commafied string.
+ *
+ * @param val
+ * The value to convert.
+ * @param mincols
+ * The minimum number of columns.
+ * @param padchar
+ * The padding char to use.
+ * @param signed
+ * Whether or not to display the sign.
+ * @param radix
+ * The radix to use.
+ * @return The number as a normal commafied string.
+ */
+ public static String toNormalString(long val, int mincols, char padchar, boolean signed, int radix) {
+ return toCommaString(val, mincols, padchar, 0, ',', signed, radix);
+ }
+}
diff --git a/src/main/java/bjc/inflexion/nouns/Noun.java b/src/main/java/bjc/inflexion/nouns/Noun.java
index 25cd04a..bfb7bb9 100644
--- a/src/main/java/bjc/inflexion/nouns/Noun.java
+++ b/src/main/java/bjc/inflexion/nouns/Noun.java
@@ -20,8 +20,6 @@ package bjc.inflexion.nouns;
* @author EVE
*/
public class Noun {
- /* Format string for toString. */
- private static final String TOSTRING_FMT = "Noun [word=%s, inflection=%s]";
/* The word itself. */
private final String word;
/* Its inflection. */
@@ -82,6 +80,25 @@ public class Noun {
}
/**
+ * Check whether or not this noun is uninflected (does not change in singular/plural).
+ * @return Whether or not the noun is uninflected.
+ */
+ public boolean isUninflected() {
+ String singlar = singular();
+
+ if (singlar.equals(modernPlural()) || singlar.equals(classicalPlural())) return true;
+
+ return false;
+ }
+
+ /**
+ * Check if this noun has differing modern/classical plural forms.
+ * @return Whether this noun has differing plural forms.
+ */
+ public boolean isDifferingPlural() {
+ return modernPlural().equals(classicalPlural());
+ }
+ /**
* Get the singular form of this noun.
*
* @return
@@ -107,7 +124,7 @@ public class Noun {
@Override
public String toString() {
- return String.format(TOSTRING_FMT, word, inflection);
+ return String.format("Noun [word=%s, inflection=%s]", word, inflection);
}
/**
diff --git a/src/test/java/bjc/inflexion/InflectPair.java b/src/test/java/bjc/inflexion/InflectPair.java
new file mode 100644
index 0000000..d2f076a
--- /dev/null
+++ b/src/test/java/bjc/inflexion/InflectPair.java
@@ -0,0 +1,30 @@
+/**
+ * (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 8c44881..d94407f 100644
--- a/src/test/java/bjc/inflexion/InflectionMLTest.java
+++ b/src/test/java/bjc/inflexion/InflectionMLTest.java
@@ -3,28 +3,109 @@ package bjc.inflexion;
import org.junit.Test;
import static org.junit.Assert.*;
+import static bjc.inflexion.InflectPair.pair;
import static bjc.inflexion.InflectionML.inflect;
+/**
+ * Tests for inflection markup.
+ *
+ * @author bjculkin
+ *
+ */
public class InflectionMLTest {
+ /**
+ * Test inflection markup.
+ */
@Test
public void testML() {
- assertEquals("no results", inflect("<#n:0> <N:results>"));
- assertEquals("7 results", inflect("<#n:7> <N:results>"));
+ //////////////////////
+ // Check # command //
+ //////////////////////
+
+ // Check general inflection
+ assertInflects("<#:%s> <N:indexes> %s 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
+ assertInflects("<#wnc:%d> <Noun:indexes> %s 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
+ assertInflects("<#w20:%d> <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
+ assertInflects("<#n:%d> <N:results>", pair("no results", 0), pair("7 results", 7));
// FIXME
//
// Adjust this to use <V> for were/was when it is implemented
- assertEquals("no items were found", inflect("<#n:0> <N:item> were found"));
- assertEquals("no item was found", inflect("<#s:0> <N:item> was found"));
+ // Check general inflection
+ assertInflects("<#%s:%d> <N:item> %s found", pair("no items were found", "n", 0, "were"),
+ pair("no item was found", "s", 0, "was"));
+
+ // Check article picking
+ assertInflects("<#a:%d> <N:%s>", pair("a result", 1, "results"), pair("3 results", 3, "results"),
+ pair("an outcome", 1, "outcomes"), pair("7 outcomes", 7, "outcomes"));
+
+ // Check 'w' option
+ assertInflects("<#w:%d> <N:results>", pair("six results", 6), pair("ten results", 10),
+ pair("11 results", 11));
+ // Check 'o' option
+ assertInflects("<#o:%d> <N:results>", pair("6th result", 6), pair("11th result", 11),
+ pair("22nd result", 22));
+ assertInflects("<#ow:%d> <N:results>", pair("first result", 1), pair("sixth result", 6),
+ pair("22nd result", 22));
+ assertInflects("<#o15:%d> <N:results>", pair("6th result", 6), pair("11th result", 11),
+ pair("22 results", 22));
+
+ // Check 'f' option
+ assertInflects("Found <#f:%d> <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));
+
+ 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),
+ pair("Searching for items....found a few", 4),
+ pair("Searching for items....found several", 8),
+ pair("Searching for items....found many", 11));
- assertEquals("a result", inflect("<#a:1> <N:results>"));
- assertEquals("3 results", inflect("<#a:3> <N:results>"));
+ assertInflects("Found <#fs:%d> <N:matches>", pair("Found no match", 0),
+ pair("Found several matches", 7));
+ assertInflects("Found <#fa:%d> <N:matches>", pair("Found a match", 1),
+ pair("Found several matches", 7));
- assertEquals("an outcome", inflect("<#a:1> <N:outcomes>"));
- assertEquals("3 outcomes", inflect("<#a:3> <N:outcomes>"));
+ // Check 'e' option
+ assertInflects("Found <#e:%d> <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
+ assertInflects("<#d:%d><N:Match> found", pair("Match found", 1), pair("Matches found", 2));
+
+ ////////////////////////
+ // Check N command //
+ ///////////////////////
+
+ // Check 'c' option
+ assertInflects("<#:%d> <N:%s> 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"));
+
+ 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"));
+ }
+
+ private static void assertInflects(String exp, String real) {
+ assertEquals(exp, inflect(real));
+ }
- assertEquals("5 things", inflect("<#a:5> <Noun:things>"));
- assertEquals("a thing", inflect("<#An:1> <Noun:things>"));
+ private static void assertInflects(String real, InflectPair... pairs) {
+ for (InflectPair pair : pairs) {
+ assertEquals(pair.exp, inflect(String.format(real, pair.pars)));
+ }
}
}