@dcoffey/espells
Version:
Pure JS/TS spellchecker, using Hunspell dictionaries. Based on Spylls.
156 lines • 6.68 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { iterate } from "iterare";
import { CONSTANTS as C } from "../constants.js";
import { re } from "../util.js";
/** Base class for the {@link Prefix}/{@link Suffix} classes. Won't work by itself. */
export class Affix {
/**
* @param flag - {@link Flag} that this affix will be labled with.
* @param crossproduct - Sets the {@link Affix.crossproduct} state, with
* the string "Y" meaning `true` and everything else meaning `false`.
* @param strip - What this affix should strip from a word to be applied.
* If given as the string "0", that means not to strip anything.
* @param add - What to add to a word when this affix is applied. If
* given as the string "0", that means to add nothing.
* @param aff - The {@link Aff} data to use when parsing flags.
*/
constructor(flag, crossproduct, strip, add, aff) {
let flags;
[add, flags] = add.split("/");
add = aff.ignore(add);
this.flag = flag;
this.crossproduct = crossproduct === "Y";
this.strip = strip === "0" ? "" : strip;
this.add = add === "0" ? "" : add;
this.flags = flags ? aff.parseFlags(flags) : new Set();
}
/**
* Determines if a word matches the conditions of this affix.
*
* @param word - The word to check against this affix's conditions.
*/
relevant(word) {
return this.conditionRegex.test(word);
}
/**
* Determines if a word already has this affix applied to it.
*
* @param word - The word to check for if this affix is already present.
*/
on(word) {
return this.lookupRegex.test(word);
}
/**
* Applies this affix to a word, returning the word as a transformed string.
*
* @param word - The word to apply the transformation to.
*/
apply(word) {
return word.replace(this.replaceRegex, this.strip);
}
/**
* Determines if this affix has the given flag.
*
* @param flag - The flag to check for. Can be undefined, which will return false.
*/
has(flag) {
if (flag === undefined)
return false;
return this.flags.has(flag);
}
/**
* Determines if this affix is compatible with a set of flags, meaning
* that the affix's flags are present in the flag set given.
*
* @param required - The flags this affix must have.
* @param forbidden - An optional set of flags which has the inverse
* effect, meaning that this affix's flags *cannot* be found in this set.
*/
compatible(required, forbidden) {
// jump out early if there are no flags to check against
if (required.size === 0 && (!forbidden || forbidden.size === 0))
return true;
if (forbidden) {
for (const flag of this.flags) {
if (forbidden.has(flag))
return false;
}
}
// special case of no required flags
if (required.size === 0)
return true;
// jump out early if it isn't possible for this affix to be compatible
if (this.flags.size < required.size)
return false;
for (const flag of required) {
if (!this.flags.has(flag))
return false;
}
return true;
}
}
/** An {@link Affix} that is applied to or found at the beginning of a stem. */
export class Prefix extends Affix {
/**
* @param flag - {@link Flag} that this affix will be labled with.
* @param crossproduct - Sets the {@link Affix.crossproduct} state, with
* the string "Y" meaning `true` and everything else meaning `false`.
* @param strip - What this affix should strip from a word to be applied.
* If given as the string "0", that means not to strip anything.
* @param add - What to add to a word when this affix is applied. If
* given as the string "0", that means to add nothing.
* @param condition - A `RegExp` like pattern to check against a word to
* see if this affix is relevant.
* @param aff - The {@link Aff} data to use when parsing flags.
*/
constructor(flag, crossproduct, strip, add, condition, aff) {
super(flag, crossproduct, strip, add, aff);
let parts = iterate(condition.matchAll(C.SPLIT_CONDITION_REGEX))
.map(part => part.slice(1))
.flatten()
.toArray();
if (parts.length && this.strip)
parts = parts.slice(this.strip.length);
let cond = "";
if (parts.length && !(parts.length === 1 && parts[0] === ".")) {
cond = `(?=${parts.join("")})`.replaceAll("-", "\\-");
}
this.conditionRegex = re `/^${condition.replaceAll("-", "\\-")}/`;
this.lookupRegex = re `/^${this.add}${cond}/`;
this.replaceRegex = re `/^${this.add}/`;
}
}
/** An {@link Affix} that is applied to or can be found at the end of a stem. */
export class Suffix extends Affix {
/**
* @param flag - {@link Flag} that this affix will be labled with.
* @param crossproduct - Sets the {@link Affix.crossproduct} state, with
* the string "Y" meaning `true` and everything else meaning `false`.
* @param strip - What this affix should strip from a word to be applied.
* If given as the string "0", that means not to strip anything.
* @param add - What to add to a word when this affix is applied. If
* given as the string "0", that means to add nothing.
* @param condition - A `RegExp` like pattern to check against a word to
* see if this affix is relevant.
* @param aff - The {@link Aff} data to use when parsing flags.
*/
constructor(flag, crossproduct, strip, add, condition, aff) {
super(flag, crossproduct, strip, add, aff);
let parts = iterate(condition.matchAll(C.SPLIT_CONDITION_REGEX))
.map(part => part.slice(1))
.flatten()
.toArray();
let cond = "";
if (parts.length && !(parts.length === 1 && parts[0] === ".")) {
if (this.strip)
parts = parts.slice(0, -this.strip.length);
cond = `(${parts.join("")})`.replaceAll("-", "\\-");
}
this.conditionRegex = re `/${condition.replaceAll("-", "\\-")}$/`;
this.lookupRegex = re `/${cond}${this.add}$/`;
this.replaceRegex = re `/${this.add}$/`;
}
}
//# sourceMappingURL=affix.js.map