Zum Inhalt

ExpandaEVE Authoring Reference

ExpandaEVE is the readable form of the EVE language. It is what you write in a .eve filter file. Every construct here is translated into CompactaEVE and then compiled. This page documents each construct with real examples drawn from the shipping filter files.

For the document-level directives (DEFINE, LET, REALM, the rule statements) see The EVE Language. This page is about the body of a fuzzy rule: the pattern itself.

The shape of a rule body

A fuzzy rule body always has a root and may add any number of constructs around it. The root is the stem word the rule is built on. Everything else refines it: suffixes, plural handling, exclusions, context, and so on.

HEAR rule
AS
  fuck(WHOLE)
  suffix(NONE OR er OR ers OR ing OR ed OR s)
  FLAG(pf YES)

Here fuck(WHOLE) is the root, suffix(...) adds allowed endings, and FLAG(pf YES) tags the category. The constructs may appear in any order that reads naturally; the translator recognizes each by keyword.

The root and its boundary markers

The root is the anchor of a rule. Exactly one root marker sets how strictly the match is bounded at the root:

Marker Meaning
(WHOLE) The root must match as a whole word. The candidate begins with the root stem and the whole route runs to a word boundary. Written [^^] in CompactaEVE.
(ROOT) The root is a stem that suffixes and groups build on. It need not be a whole word by itself. Written [*^] in CompactaEVE.

Use (WHOLE) when the bare word is itself the offence:

  spic(WHOLE)
  PLURAL

Use (ROOT) when you will attach endings or trailing groups:

  ni(ROOT)
  suffix(g OR ga OR gga OR gger OR gl LETTER t)
  PLURAL

Every fuzzy rule needs a root marker

A rule body with no (WHOLE) or (ROOT) marker cannot compile. The marker is what separates the root stem from the rest of the pattern.

Fuzzy matching is automatic

A fuzzy root is not a literal string test. The matcher already tolerates common evasion: vowels are interchangeable through the vowel set, letters may be stretched, and gaps (see GAP) let inserted characters through where you allow them. You do not enable this; it is how fuzzy rules match. To catch a wider spread of deliberate misspellings, add aliases and skeletons.

suffix: allowed endings

suffix(...) lists endings the word may take after the root. The arms are separated by OR. Only one suffix group is needed, but you may stack several; each adds an optional segment in order.

  bitch(WHOLE)
  suffix(NONE OR es OR ed OR ing OR y OR ier OR iest OR fest OR fests)

NONE means the ending is optional: the word may end right at the root. Without a NONE arm the suffix is required.

Suffix groups compose left to right, so several in a row build a chain of optional segments:

  fa(ROOT)
  suffix(g OR gg OR NONE)
  suffix(ggot OR NONE)
  suffix(ggie OR NONE)

A suffix arm may itself contain GAP or LETTER (see GAP and LETTER):

  suffix(g OR ga OR gga OR gger OR gl LETTER t)
  suffix(my GAP OR NONE)

PLURAL

PLURAL adds a rich set of plural and inflection endings in one word. It expands to the built-in s class of endings, so a rule tagged PLURAL also catches -s, -z, -es, -ing, -ed, -er, -y, and the other members declared in the s function.

  dyke(WHOLE)
  PLURAL

PLURAL reads the class named s. The shipping English filter declares it as:

LET function(s) BE s OR z OR es OR ez OR ies OR iez OR y>ies OR y>iez OR ing OR ings OR ed OR er OR ers OR y OR ys

An arm written from>to is a rewrite ending: it matches a word that ends in to by testing the root with the from ending instead. y>ies catches a word ending in ies as though it ended in y, which is how baby and babies are both reached from one root. See Rewrite suffixes for the mechanics.

NEVER: exclusions

NEVER(...) lists phrases that must not appear in the surrounding line. If any listed phrase is present, the rule does not fire, even if the pattern otherwise matches. This is how an aggressive root is kept from catching innocent words.

  wop(WHOLE)
  PLURAL
  NEVER(doowop OR doo wop OR doowap OR doo wap OR dowap OR do wap OR do wop)
  gook(WHOLE)
  PLURAL
  NEVER(600k OR 900k OR 60ok OR 90ok OR 6ook OR 9ook OR 600ks OR 900ks)

A NEVER check is a plain substring test against the whole line, lowercased. If the phrase is anywhere in the message, the rule is vetoed.

ALIAS: alternate spellings

ALIAS(...) adds an alternate spelling of the root. Each alias is a separate root variant that reuses the rest of the rule (its suffixes, groups, exclusions). List one alias per line to cover deliberate misspellings and letter swaps.

  ALIAS(phuck)
  ALIAS(fuk)
  ALIAS(fukk)
  ALIAS(fucc)
  ALIAS(fck)
  ALIAS(fk)
  fuck(WHOLE)
  suffix(NONE OR er OR ers OR ing OR ed OR s)

Every alias here matches with the same suffix set as fuck. An alias may carry its own (WHOLE) or (ROOT) marker; without one it inherits the root's boundary.

An alias may reference a function, in which case each member of that function becomes an alias:

  ALIAS(function(selfharmverb))
  kill(ROOT)

An alias may also be a segmented word or a multi-part fragment; it is normalized the same way the root is.

ALSO: additive fragments

ALSO(...) adds a fragment that may appear anywhere after the root rather than in fixed order. It is used when a required extra word can float relative to the stem.

  kill(ROOT)
  GAP
  suffix(yo OR ur OR your OR yor)
  suffix(self)
  ALSO(wrists OR wrist OR throat)

ALSO lowers to a floating group that attaches at any reachable position after the root and normal groups. See Plus groups for how a floating group is matched.

GAP and LETTER

These shorthands control what may sit between parts of a pattern.

Construct Meaning CompactaEVE
GAP An optional run of any characters (letters or non-letters), so inserted spaces, punctuation, or filler is tolerated. (#*)
LETTERS An optional run of letters only. ($^)
LETTER Exactly one character of word material. @

GAP between a root and a suffix lets a phrase spread across words:

  moon(ROOT)
  GAP
  suffix(cricket)
  PLURAL

This catches moon cricket, mooncricket, and spellings with filler between the two parts. LETTER matches a single character, useful inside a suffix arm to allow one wildcard position:

  suffix(m LETTER GAP OR NONE)

LETTERS matches a run of letters only, so it stops at a space:

  my(ROOT)
  GAP
  LETTERS
  GAP
  suffix(hit)

What counts as a letter

Word material is an ASCII letter or any byte of a non-ASCII glyph, so an emoji or symbol run also counts as word material for boundaries and the LETTER wildcard. Plain ASCII text is unaffected.

OR and NONE

OR separates alternatives inside a construct such as suffix(...), NEVER(...), ALSO(...), BEFORE(...), and AFTER(...), and it separates class members in a LET declaration. It must stand as a whole word.

NONE is an empty alternative. In a suffix it makes the ending optional; in a function member list it lets the class contribute an empty match:

LET function(quantifier) BE NONE OR some OR any OR a OR an OR the

BEFORE and AFTER: context gating

These gate a rule on text elsewhere in the line. They are used to make an otherwise broad or ambiguous term fire only in a telling context.

Construct Meaning CompactaEVE
BEFORE(...) A precheck. The rule only activates when one of the listed phrases appears in the surrounding line. {pre:...}
AFTER(...) A post condition. Allowed endings that let the rule fire on the word itself even without a precheck phrase present. {post:...}
  BEFORE(function(seeking))
  cp
  AFTER(mega OR pack OR packs OR nz OR db OR dropbox OR folder OR vids OR pics)
  (WHOLE)

A BEFORE phrase is a plain substring check against the whole line, lowercased. When a rule has a BEFORE gate it stays inert unless one of the phrases is present, or unless the word satisfies the AFTER post condition on its own. AFTER accepts a trailing plural s on its endings. See Gating for the exact rule.

SWAP: inline substitution set

SWAP(...) matches any one of several small fragments at a position, with control over word boundaries. It is used where a short interchangeable piece sits inside a phrase.

  kill(ROOT)
  GAP
  SWAP(my* OR me*)
  suffix(self)

Each arm may carry asterisks that free a boundary:

  • A leading * frees the start (the arm need not begin at a word boundary).
  • A trailing * frees the end.
  • No asterisk, or a double leading **, anchors both sides to a word boundary.

So my* matches my with a required boundary before it but not necessarily after, letting it run into self. See Swaps for the boundary rules in full.

SKELETON: exact spellings

SKELETON(...) lists exact-word shapes the rule also matches directly, bypassing the fuzzy matcher. It is used to nail down specific obfuscated spellings using inline choice lists.

  ni(ROOT)
  suffix(g OR ga OR gga OR gger OR gl LETTER t)
  PLURAL
  NEVER(snigger OR sniggers OR sniggered)
  SKELETON(n[a,e,i,o,u]gg[a,e,i,o,u]r OR n[a,e,i,o,u]gg[a,e,i,o,u]h)

A skeleton is expanded into the full set of concrete words it can spell, and any candidate equal to one of those words matches immediately. The expansion takes the cross product of every choice, so n[a,e,i,o,u]gg[a,e,i,o,u]r produces naggar, neggar, niggar, and so on for every vowel pair.

Inline choice lists

Inside a skeleton, [a,e,i,o,u] is an inline choice list: one character position that may be any of the comma-separated members. Members may be longer than one character. Whitespace around a member is trimmed. A skeleton may also reference a function with &name, which expands to that function's members.

  SKELETON(r[e,i]t[a,e,i]rd)
  SKELETON(m[a,e,i,o,u]st[a,e,i,o,u]rb[a,e,i,o,u]t OR m[a,e,i,o,u]st[a,e,i,o,u]rb[a,e,i,o,u]t[a,e,i,o,u])

The result is lowercased. Skeletons match the exact spelling, so they are complementary to the fuzzy root, which handles the looser variants.

FUNCTION: named classes

A function is a named class declared with LET (see The EVE Language). It is referenced with function(name), and the reference expands to the class members. Functions have several roles depending on where they appear.

As a whole group of alternatives in a rule body:

  groom(ROOT)
  suffix(NONE OR ing OR ed)
  GAP
  MAYBE function(declarative)
  GAP
  function(vulnerable)

As an alias, where each member becomes an alternate root:

  ALIAS(function(genderword))
  internal
  GAP
  condom(WHOLE)

Inside a suffix, NEVER, ALSO, BEFORE, or AFTER list, where the members join the alternatives:

  suffix(function(article) OR box of OR a box of OR internal OR function(genderword) OR NONE)

Function modifiers

A function(...) reference may carry modifiers, joined with +:

Modifier Effect CompactaEVE
ADDITIVE The reference is a floating addition to the match rather than a fixed-order requirement. wraps as a plus group [+[...]]
GAP The reference matches loosely, allowing a gap after it. appends (#*)
  function(subject + ADDITIVE + GAP)
  function(desire + ADDITIVE + GAP)
  function(quantifier + ADDITIVE + GAP)
  rap(ROOT)
  suffix(e OR ist OR ed OR ing OR eable OR able)
  PLURAL

Each additive function here floats into the match wherever it fits, so the subject, desire, and quantifier words may appear in varying order before the root.

MAYBE FUNCTION: optional function

MAYBE FUNCTION(name) makes a function reference optional: the match succeeds whether or not one of its members is present.

  rap(ROOT)
  suffix(NONE OR e OR es OR ed OR ing OR eing OR in OR ist OR ists)
  GAP
  MAYBE function(declarative)
  GAP
  function(vulnerable)

Here function(declarative) (words like a, the, my) may or may not appear between the verb and the vulnerable-person word.

FLAG: conditional rules and category tagging

FLAG(name YES) tags the rule with a declared flag. When the rule fires, that flag is set, which is how the filter reports a category such as self-harm or abuse rather than an ordinary block.

  kms(WHOLE)
  FLAG(sh YES)

The value may be written YES, Y, or TRUE for on, and anything else for off. The flag name must be declared with DEFINE (see The EVE Language), or the document fails to load. Under the hood a flag wraps the pattern in a conditional gate; see Flag wrappers.

NOTE: comments

NOTE(...) attaches an inline comment. It is removed before compilation and does not affect matching, so it is a way to annotate a pattern in place. Whole-line comments still use a leading #.

ALIAS with WHOLE or ROOT markers

An alias may end in (WHOLE) or (ROOT) to set its own boundary, independent of the root's:

  ALIAS(dike)
  dyke(WHOLE)

Without a trailing marker the alias inherits the root's boundary marker.

Segmented roots

A root may be written as several bracketed and bare pieces to mark part of it as inflectable. When at least two pieces are present and at least one is bracketed, they fold into a single root with alternate entry points.

  re[tard] (WHOLE)
  suffix(NONE OR d OR ded OR ted OR t OR der OR ders OR ing OR ed OR s)

The bracketed piece marks the inflectable segment. See Segmented roots for exactly which alternatives are generated.

Putting it together

A realistic rule combines several constructs:

HEAR rule
AS
  ALIAS(masterb)
  masturb(ROOT)
  suffix(ate OR ating OR ation OR ated OR ates OR at)
  SKELETON(m[a,e,i,o,u]st[a,e,i,o,u]rb[a,e,i,o,u]t OR m[a,e,i,o,u]st[a,e,i,o,u]rb[a,e,i,o,u]t[a,e,i,o,u])

This rule has an alias for a common misspelling, a root with a fixed suffix set, and skeletons for the leetspeak spellings the fuzzy matcher might miss. Read CompactaEVE to see how each of these lowers and matches.