CompactaEVE e semantica del matching

CompactaEVE è la forma compilata concisa del linguaggio EVE, e l'input che il compilatore di pattern effettivamente analizza. Ogni costrutto ExpandaEVE viene ridotto a un simbolo CompactaEVE prima di essere eseguito. Questa pagina documenta i simboli compatti, cosa accetta il compilatore e come un pattern compilato decide se un candidato corrisponde.

Non devi scrivere CompactaEVE a mano: il traduttore ExpandaEVE lo produce. Ma leggerlo è utile per capire esattamente come si comporta un pattern, e un documento può essere scritto direttamente nel dialetto compatto.

Le forme compatte delle istruzioni

Le direttive a livello di documento mantengono gli stessi nomi in minuscolo:

ExpandaEVE CompactaEVE
DEFINE name AS id define name as id
LET function(name) BE a OR b let &name be a|b
REALM x realm x
HEAR n AS body / FIND n AS body hear n as body
MATCH n AS body match n as body
ANYWHERE n AS ... anywhere n as g1 !! g2
SEQUENCE n AS ... sequence n as ...

I membri delle classi e le opzioni dei gruppi sono separati dalla barra | anziché da OR. Una classe viene referenziata con &name. Il resto di questa pagina copre il corpo del pattern di una regola fuzzy, che è dove risiedono i simboli compatti.

Marcatori di radice

Esattamente un marcatore di radice separa lo stem della radice dal resto del pattern:

Simbolo Da ExpandaEVE Significato
[^^] (WHOLE) La radice corrisponde come parola intera. Il percorso deve arrivare fino a un confine di parola.
[*^] (ROOT) La radice è uno stem su cui le parti successive si costruiscono.

Il testo prima del marcatore è lo stem della radice. ni[*^] ha stem ni; spic[^^] ha stem spic e richiede una corrispondenza a parola intera.

Parti: i pezzi di un frammento

Un frammento (una radice, un'opzione di gruppo, un suffisso) viene analizzato in un elenco ordinato di parti. Il compilatore riconosce questi token; tutto il resto si accumula come una sequenza letterale.

Token Tipo Corrisponde
testo semplice Letterale I caratteri esatti, in ordine.
@ Any Esattamente un carattere di materiale di parola.
(#*) Gap Una serie di caratteri qualsiasi, lettere o meno. Non deterministico: può terminare in qualsiasi posizione raggiungibile.
($^) Gap (lettere) Una serie di sole lettere (materiale di parola).
[swap:...] Swap Una qualsiasi di diverse opzioni alternative con regole di confine.

Un letterale corrisponde esattamente. @ consuma un byte di parola. Un gap è la tolleranza del motore verso i caratteri inseriti: può saltare zero o più posizioni, e il matcher prova ogni fine raggiungibile.

Materiale di parola

Il materiale di parola è una lettera ASCII o qualsiasi byte non ASCII. Un glifo multi-byte come un emoji è una serie di byte di parola, quindi le serie di glifi vengono tokenizzate e delimitate come le parole. Nessun byte ASCII è trattato come byte di simbolo, quindi il testo ordinario è invariato.

Radici e alternative di radice

La radice può assumere tre forme:

  • Uno stem semplice: masturb[*^] compila in una radice.
  • Un riferimento a una classe: &name... si espande così che ogni membro della classe diventi un'alternativa di radice, con qualsiasi testo finale mantenuto.
  • Una radice segmentata [[..][..]]: vedi Radici segmentate.

Le parentesi di override iniziali e i prodotti di prefisso generano più alternative di radice:

  • [text;] prima della radice sostituisce il letterale iniziale della radice con text, producendo una grafia alternativa. Questo è ciò a cui ALIAS si riduce. Un override [text^^;] o [text*^;] imposta anche il confine di quell'alternativa.
  • [+[a|b]] prima della radice è un prodotto incrociato di prefisso: ogni prefisso elencato viene anteposto a ogni radice, moltiplicando le alternative. Questo è ciò che ALSO usa quando si trova prima della radice, e ciò in cui si espande un riferimento a una classe dentro [+[...]].

Le alternative di radice sono ordinate prima per il letterale più lungo, così che una grafia più specifica venga provata prima di una più corta. Un'alternativa di radice viene saltata subito quando un letterale che deve consumare è assente dal candidato.

Radici segmentate

Una radice segmentata si scrive [[seg1][seg2]...] dove un segmento che termina con * è un punto di ingresso. Genera alternative che iniziano da ogni segmento con asterisco, facoltativamente precedute dai segmenti precedenti. Questo permette a una radice come re[tard] di essere raggiunta sia come parola intera sia dal segmento flessibile. Una radice segmentata senza punto di ingresso con asterisco è un errore.

Suffissi e terminazioni (finali)

Le terminazioni risiedono nell'elenco dei suffissi finali. In CompactaEVE vengono aggiunte con |[a|b|...] dopo la radice, che è ciò a cui suffix(...) si riduce. Un percorso corrisponde solo quando il candidato termina con uno dei suffissi finali e arriva alla fine della parola.

Un'opzione vuota (NONE) rende il suffisso opzionale.

Suffissi di riscrittura

Un'opzione finale scritta from>to è una riscrittura. Corrisponde a un candidato che termina con to rimuovendo to, aggiungendo from e testando il risultato contro il pattern principale. y>ies permette a una radice che termina con y di corrispondere anche a una parola che termina con ies, così una radice raggiunge sia baby sia babies. Il marcatore > è l'unico posto in cui una riscrittura viene interpretata, quindi ogni lettore dei suffissi finali concorda sul suo significato.

Gruppi

Tra la radice e i suffissi finali il pattern è una sequenza di gruppi. Un gruppo è un insieme di opzioni alternative, una qualsiasi delle quali può soddisfarlo. Un gruppo ha un tipo:

Tipo CompactaEVE Comportamento
Normale `[a b]`
Plus (fluttuante) `[+[a b]]`
Hard (ancorato) un riferimento a una classe hard Un'alternativa ancorata corrisposta dall'inizio.

Un'opzione vuota rende il suo gruppo opzionale. Un gruppo scritto come un semplice gap, (#*) o ($^), è un gruppo gap opzionale, che è il modo in cui un GAP o LETTERS autonomo tra le parti si riduce.

Riferimenti a classi come gruppi

&name nella posizione di gruppo espande la classe in un gruppo di opzioni, una per membro. &!name rende il gruppo opzionale (viene aggiunta un'opzione vuota extra), che è il modo in cui MAYBE FUNCTION si riduce. Un riferimento può portare [^^] o [*^] per forzare il matching hard o soft; altrimenti eredita la durezza dichiarata della classe. Un riferimento a una classe hard diventa un gruppo hard ancorato.

Gruppi plus (fluttuanti)

Un gruppo plus [+[...]] non sta in un ordine fisso. Dopo che la radice e i gruppi normali sono consumati, il matcher calcola ogni posizione raggiungibile e lascia che il gruppo plus si attacchi in una qualsiasi di esse, arrivando fino alla fine della parola. Questo è il modo in cui le funzioni ADDITIVE e ALSO fluttuano rispetto allo stem.

Swap

[swap:a|b] corrisponde a una qualsiasi opzione in una posizione. Ogni opzione porta con sé flag di confine decisi dai suoi asterischi:

  • Un singolo * iniziale libera il confine iniziale.
  • Un singolo * finale libera il confine finale.
  • Nessun asterisco, o un doppio ** iniziale, ancora entrambi i lati.

Un lato ancorato richiede un confine di parola in quel punto: l'inizio deve seguire un byte non di parola (o essere l'inizio della stringa), e la fine deve precedere un byte non di parola (o essere la fine della stringa). Uno swap può essere scritto inline in un frammento o come proprio gruppo.

Esclusioni (veto)

[-[a|b]] è un elenco di veto, che è ciò a cui NEVER si riduce. Ogni frase elencata è un semplice test di sottostringa contro l'intera riga, in minuscolo. Se una qualsiasi frase è presente, il pattern non si attiva, a prescindere dal resto della corrispondenza.

Precheck e post gating

Due costrutti condizionano un pattern sulla riga circostante:

  • {pre:a|b} è un precheck, da BEFORE. Il pattern rimane inattivo a meno che una delle frasi sia una sottostringa della riga, in minuscolo.
  • {post:a|b} è una post condizione, da AFTER. Viene inserita al marcatore di radice come un gruppo iniziale opzionale di terminazioni consentite.

Un pattern condizionato (uno con un precheck) si attiva solo quando una frase di precheck è presente nella riga, o quando il candidato soddisfa la post condizione da solo: inizia con lo stem della radice e termina con una delle terminazioni post, con un plurale finale s tollerato. I fatti di precheck e veto dipendono solo dalla riga, non dal candidato, quindi vengono calcolati una volta per riga e riutilizzati tra i candidati.

Skeleton

{skel:...} è la whitelist skeleton, da SKELETON. Viene espansa in fase di compilazione in un insieme di parole concrete. Un candidato esattamente uguale a una di quelle parole corrisponde immediatamente, prima che il matcher principale venga eseguito.

All'interno di uno skeleton:

  • [a,b,c] è una lista di scelta inline: una posizione che può essere un qualsiasi membro separato da virgola. I membri possono essere multi-carattere. Gli spazi bianchi attorno a un membro vengono rimossi.
  • &name si espande nei membri di quella classe. Un nome di classe numerico si espande cifra per cifra; una classe con nome si espande come lettere.

L'espansione è il prodotto incrociato completo di ogni scelta, in minuscolo.

Wrapper di flag

{name:y?...} e {name:n?...} avvolgono un pattern in una condizione di flag, da FLAG. La forma y richiede il flag attivo, la forma n richiede il flag disattivato. Più wrapper possono annidarsi. Il nome del flag deve essere dichiarato con define, altrimenti il documento non riesce a caricarsi. I flag dichiarati sono ciò che il filtro riporta come categoria.

Commenti

{#c:...} è un commento inline, da NOTE. Viene rimosso prima della compilazione, incluse le parentesi annidate. I commenti su tutta la riga usano un # iniziale.

Come corrisponde un pattern fuzzy

Quando una parola candidata viene testata contro un pattern fuzzy compilato, il motore esegue questi controlli in ordine:

  1. Gating. Se il pattern ha un precheck e né una frase di precheck è presente nella riga né la post condizione è soddisfatta, non corrisponde. Se una qualsiasi frase di veto è presente nella riga, non corrisponde.
  2. Skeleton. Se il candidato è esattamente uguale a una parola skeleton, corrisponde.
  3. Corrispondenza principale. Il motore principale viene eseguito (sotto).
  4. Fallback di riscrittura. Per ogni suffisso di riscrittura, se il candidato termina con il lato to, il lato from viene ripristinato e la corrispondenza principale viene ritentata sulla parola ripristinata.

La corrispondenza principale

Il matcher principale decide se l'intero candidato è coperto da un percorso:

  1. Un rifiuto rapido salta il candidato quando gli manca ogni letterale che un percorso principale deve contenere.
  2. I gruppi hard ancorati vengono provati dall'inizio; un percorso che raggiunge una terminazione consentita corrisponde.
  3. Ogni alternativa di radice viene eseguita dall'inizio. Una radice i cui letterali richiesti sono assenti viene saltata. Le fini raggiungibili alimentano la sequenza dei gruppi normali.
  4. I gruppi normali vengono infilati in ordine dalle fini della radice. Un percorso corrisponde quando raggiunge un suffisso finale consentito che arriva alla fine della parola. A meno che il percorso non sia puramente letterale, almeno un'opzione non vuota deve essersi attivata.
  5. I gruppi plus fluttuanti vengono provati in ogni slot raggiungibile, e un gruppo plus corrisponde quando una delle sue opzioni arriva da un ancoraggio raggiungibile alla fine della parola.

I confini di parola derivano dal materiale di parola: un confine esiste alle estremità della stringa e ovunque un byte non di parola incontri un byte di parola. I gap saltano serie di caratteri in modo non deterministico, quindi il matcher esplora ogni posizione che un gap può raggiungere.

Ancoraggi e il prefiltro di scansione

Per ogni regola fuzzy il compilatore calcola un insieme di ancoraggi letterali richiesti: letterali tali che un candidato che non ne contiene nessuno non può corrispondere. Se una regola può corrispondere senza alcun letterale garantito (per esempio un percorso puramente gap o condizionato), il suo insieme di ancoraggi è vuoto e la regola viene sempre eseguita. Gli ancoraggi di ogni regola costruiscono un prefiltro Aho-Corasick che, in un singolo passaggio su un messaggio, segna quali regole potrebbero corrispondere, così le regole i cui letterali sono assenti vengono saltate. Questa è solo un'ottimizzazione: non salta mai una regola che potrebbe corrispondere.

Regole strict (MATCH)

Una regola MATCH usa il linguaggio strict anziché il motore fuzzy. Un pattern strict è ancorato: ha successo solo quando i suoi termini consumano l'intero input dall'inizio alla fine. Viene usato per input strutturato come numeri e indirizzi, e può catturare pezzi con nome.

Primitive strict

Parola chiave Corrisponde
digit Esattamente una cifra.
digits Una serie di cifre.
letter Esattamente una lettera.
letters Una serie di lettere.
word Una serie di lettere, cifre o underscore.
number Una serie di cifre.
ip Una serie di cifre e punti.
space Esattamente un carattere di spazio bianco.
spaces Una serie di spazi bianchi.
anything Qualsiasi tratto di testo.

Il matching delle parole chiave ignora maiuscole e minuscole. Un token "..." è un letterale, con gli escape backslash rispettati.

Quantificatori e struttura strict

Costrutto Effetto
any x Zero o più di x, greedy.
many x Uno o più di x, greedy.
maybe x Zero o uno di x.
m to n x Tra m e n di x.
n digit (ecc.) Esattamente n di una classe, per esempio 3 digit.
( ... ) Un gruppo corrisposto come sottosequenza.
x or y Alternanza: entrambi i rami possono soddisfare la posizione.
then Collante leggibile opzionale tra i termini; ignorato.
x as name Cattura il testo corrisposto di x sotto name.

Un quantificatore applicato a una classe plurale la restringe alla sua forma a carattere singolo così che si ripeta un carattere alla volta. La ripetizione è solo greedy; non c'è modalità lazy, e una ripetizione vuota non può ciclare.

Riferimenti a liste e ricerca fuzzy in strict

&name in un pattern strict si espande in un'alternanza dei membri della classe come letterali. Un termine backtick find `pattern` incorpora un pattern EVE fuzzy: il matcher strict passa uno span senza spazi al pattern fuzzy compilato e accetta lo span quando corrisponde. Entrambi hanno bisogno del registry, che è disponibile quando un documento viene compilato.

Regole anywhere e sequence

ANYWHERE e SEQUENCE sono tipi di regola separati. ANYWHERE si attiva quando ogni gruppo (separato da !! nella forma compatta, WITH in ExpandaEVE) ha un membro trovato da qualche parte nel messaggio; ogni membro è compilato come matcher fuzzy a parola intera, e la regola ha bisogno di almeno due gruppi. SEQUENCE corrisponde a una serie ordinata di glifi ed è usata dal tabellone dei simboli, che è documentato separatamente; un prefisso mirror accetta anche l'ordine inverso, e la regola ha bisogno di almeno due glifi.