CompactaEVE e semântica de correspondência

O CompactaEVE é a forma compilada e concisa da linguagem EVE, e a entrada que o compilador de padrões realmente analisa. Cada construção do ExpandaEVE é reduzida a um símbolo do CompactaEVE antes de correr. Esta página documenta os símbolos compactos, o que o compilador aceita e como um padrão compilado decide se um candidato corresponde.

Não tem de escrever CompactaEVE à mão: o tradutor do ExpandaEVE produ-lo automaticamente. Mas lê-lo é útil para compreender exatamente como um padrão se comporta, e um documento pode ser criado diretamente no dialeto compacto.

As formas de instrução compactas

As diretivas ao nível do documento mantêm os mesmos nomes em minúsculas:

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 ...

Os membros de classe e os braços de grupo são separados pela barra | em vez de OR. Uma classe é referenciada por &name. O resto desta página abrange o corpo do padrão de uma regra difusa, que é onde vivem os símbolos compactos.

Marcadores de raiz

Exatamente um marcador de raiz separa o radical da raiz do resto do padrão:

Símbolo Do ExpandaEVE Significado
[^^] (WHOLE) A raiz corresponde como palavra inteira. O percurso tem de chegar a uma fronteira de palavra.
[*^] (ROOT) A raiz é um radical sobre o qual partes posteriores se constroem.

O texto antes do marcador é o radical da raiz. ni[*^] tem radical ni; spic[^^] tem radical spic e requer uma correspondência de palavra inteira.

Partes: as peças de um fragmento

Um fragmento (uma raiz, um braço de grupo, um sufixo) é analisado numa lista ordenada de partes. O compilador reconhece estes tokens; tudo o resto acumula-se como uma sequência literal.

Token Tipo Corresponde a
texto simples Literal Os caracteres exatos, por ordem.
@ Any Exatamente um carácter de material de palavra.
(#*) Gap Uma sequência de quaisquer caracteres, letras ou não. Não determinista: pode terminar em qualquer posição alcançável.
($^) Gap (letras) Uma sequência apenas de letras (material de palavra).
[swap:...] Swap Qualquer um de vários braços alternativos com regras de fronteira.

Um literal corresponde exatamente. @ consome um byte de palavra. Uma lacuna é a tolerância do motor para caracteres inseridos: pode saltar zero ou mais posições, e o correspondente tenta cada fim alcançável.

Material de palavra

Material de palavra é uma letra ASCII ou qualquer byte não ASCII. Um glifo de vários bytes, como um emoji, é uma sequência de bytes de palavra, por isso as sequências de glifos são tokenizadas e delimitadas como palavras. Nenhum byte ASCII é tratado como byte de símbolo, por isso o texto comum não é alterado.

Raízes e alternativas de raiz

A raiz pode assumir três formas:

  • Um radical simples: masturb[*^] compila para uma raiz.
  • Uma referência de classe: &name... expande-se para que cada membro da classe se torne uma alternativa de raiz, com qualquer texto final mantido.
  • Uma raiz segmentada [[..][..]]: consulte Raízes segmentadas.

Os parênteses de substituição iniciais e os produtos de prefixo geram mais alternativas de raiz:

  • [text;] antes da raiz substitui o literal inicial da raiz por text, produzindo uma grafia alternativa. É isto para que ALIAS se reduz. Uma substituição [text^^;] ou [text*^;] também define a fronteira dessa alternativa.
  • [+[a|b]] antes da raiz é um produto cruzado de prefixo: cada prefixo listado é anteposto a cada raiz, multiplicando as alternativas. É isto que ALSO utiliza quando fica antes da raiz, e para que uma referência de classe dentro de [+[...]] se expande.

As alternativas de raiz são ordenadas primeiro pelo literal mais longo, por isso uma grafia mais específica é tentada antes de uma mais curta. Uma alternativa de raiz é descartada cedo quando um literal que tem de consumir está ausente do candidato.

Raízes segmentadas

Uma raiz segmentada escreve-se [[seg1][seg2]...] onde um segmento que termina em * é um ponto de entrada. Gera alternativas que começam em cada segmento marcado com asterisco, opcionalmente prefixadas pelos segmentos anteriores. Isto permite que uma raiz como re[tard] seja alcançada tanto como a palavra inteira como a partir do segmento flexionável. Uma raiz segmentada sem ponto de entrada marcado com asterisco é um erro.

Sufixos e terminações (finais)

As terminações vivem na lista de sufixos finais. No CompactaEVE são adicionadas com |[a|b|...] após a raiz, que é para que suffix(...) se reduz. Um percurso corresponde apenas quando o candidato termina num dos sufixos finais e chega ao fim da palavra.

Um braço vazio (NONE) torna o sufixo opcional.

Sufixos de reescrita

Um braço final escrito from>to é uma reescrita. Corresponde a um candidato que termina em to retirando to, acrescentando from e testando o resultado contra o padrão central. y>ies permite que uma raiz que termina em y também corresponda a uma palavra que termina em ies, por isso uma raiz alcança tanto baby como babies. O marcador > é o único sítio onde uma reescrita é interpretada, por isso todos os leitores de sufixos finais concordam sobre o seu significado.

Grupos

Entre a raiz e os sufixos finais o padrão é uma sequência de grupos. Um grupo é um conjunto de braços alternativos, qualquer um dos quais pode satisfazê-lo. Um grupo tem um tipo:

Tipo CompactaEVE Comportamento
Normal `[a b]`
Plus (flutuante) `[+[a b]]`
Hard (ancorado) uma referência de classe hard Uma alternativa ancorada correspondida a partir do início.

Um braço vazio torna o seu grupo opcional. Um grupo escrito como uma lacuna simples, (#*) ou ($^), é um grupo de lacuna opcional, que é como uma GAP ou LETTERS autónoma entre partes se reduz.

Referências de classe como grupos

&name na posição de grupo expande a classe num grupo de braços, um por membro. &!name torna o grupo opcional (é adicionado um braço vazio extra), que é como MAYBE FUNCTION se reduz. Uma referência pode carregar [^^] ou [*^] para forçar a correspondência hard ou soft; caso contrário herda a dureza declarada da classe. Uma referência de classe hard torna-se um grupo ancorado hard.

Grupos plus (flutuantes)

Um grupo plus [+[...]] não fica em ordem fixa. Depois de a raiz e os grupos normais serem consumidos, o correspondente calcula todas as posições alcançáveis e deixa o grupo plus anexar-se em qualquer uma delas, correndo até ao fim da palavra. É assim que as funções ADDITIVE e ALSO flutuam em relação ao radical.

Swaps

[swap:a|b] corresponde a qualquer um dos braços numa posição. Cada braço carrega flags de fronteira decididas pelos seus asteriscos:

  • Um único * inicial liberta a fronteira de início.
  • Um único * final liberta a fronteira de fim.
  • Sem asterisco, ou um duplo ** inicial, ancora ambos os lados.

Um lado ancorado requer uma fronteira de palavra aí: o início tem de seguir um byte não de palavra (ou ser o início da string), e o fim tem de preceder um byte não de palavra (ou ser o fim da string). Um swap pode ser escrito inline num fragmento ou como o seu próprio grupo.

Exclusões (veto)

[-[a|b]] é uma lista de veto, que é para que NEVER se reduz. Cada frase listada é um teste de subcadeia simples contra a linha inteira, em minúsculas. Se alguma frase estiver presente, o padrão não dispara, independentemente do resto da correspondência.

Verificação prévia e condicionamento posterior

Duas construções condicionam um padrão pela linha circundante:

  • {pre:a|b} é uma verificação prévia, de BEFORE. O padrão permanece inerte a menos que uma das frases seja uma subcadeia da linha, em minúsculas.
  • {post:a|b} é uma condição posterior, de AFTER. É inserida no marcador de raiz como um grupo inicial opcional de terminações permitidas.

Um padrão condicionado (um com verificação prévia) dispara apenas quando uma frase de verificação prévia está presente na linha, ou quando o candidato satisfaz a condição posterior por conta própria: começa com o radical da raiz e termina com uma das terminações posteriores, com um s plural final tolerado. Os factos de verificação prévia e de veto dependem apenas da linha, não do candidato, por isso são calculados uma vez por linha e reutilizados entre candidatos.

Esqueletos

{skel:...} é a lista branca de esqueleto, de SKELETON. É expandida em tempo de compilação num conjunto de palavras concretas. Um candidato exatamente igual a uma dessas palavras corresponde imediatamente, antes de o correspondente central correr.

Dentro de um esqueleto:

  • [a,b,c] é uma lista de escolha inline: uma posição que pode ser qualquer membro separado por vírgula. Os membros podem ter vários caracteres. Os espaços em branco à volta de um membro são retirados.
  • &name expande-se para os membros dessa classe. Um nome de classe numérico expande-se dígito a dígito; uma classe nomeada expande-se como letras.

A expansão é o produto cruzado completo de cada escolha, em minúsculas.

Invólucros de flag

{name:y?...} e {name:n?...} envolvem um padrão numa condição de flag, de FLAG. A forma y requer a flag ligada, a forma n requer-a desligada. Vários invólucros podem aninhar-se. O nome da flag tem de ser declarado com define, ou o documento não carrega. As flags declaradas são o que o filtro reporta como categoria.

Comentários

{#c:...} é um comentário inline, de NOTE. É retirado antes da compilação, incluindo chavetas aninhadas. Os comentários de linha inteira utilizam um # inicial.

Como um padrão difuso corresponde

Quando uma palavra candidata é testada contra um padrão difuso compilado, o motor executa estas verificações por ordem:

  1. Condicionamento. Se o padrão tem uma verificação prévia e nem uma frase de verificação prévia está presente na linha nem a condição posterior é satisfeita, não corresponde. Se alguma frase de veto está presente na linha, não corresponde.
  2. Esqueleto. Se o candidato for exatamente igual a uma palavra de esqueleto, corresponde.
  3. Correspondência central. O motor principal corre (abaixo).
  4. Recurso de reescrita. Para cada sufixo de reescrita, se o candidato terminar no lado to, o lado from é restaurado e a correspondência central é repetida na palavra restaurada.

A correspondência central

O correspondente central decide se todo o candidato é explicado por um percurso:

  1. Uma rejeição rápida descarta o candidato quando lhe falta todos os literais que um percurso central tem de conter.
  2. Os grupos ancorados hard são tentados a partir do início; um percurso que chega a uma terminação permitida corresponde.
  3. Cada alternativa de raiz é executada a partir do início. Uma raiz cujos literais necessários estão ausentes é descartada. Os fins alcançáveis alimentam a sequência de grupos normais.
  4. Os grupos normais são enfiados por ordem a partir dos fins da raiz. Um percurso corresponde quando chega a um sufixo final permitido que corre até ao fim da palavra. A menos que o percurso seja puramente literal, pelo menos um braço não vazio tem de ter disparado.
  5. Os grupos plus flutuantes são tentados em cada posição alcançável, e um grupo plus corresponde quando um dos seus braços corre de uma âncora alcançável até ao fim da palavra.

As fronteiras de palavra vêm do material de palavra: existe uma fronteira nos fins da string e onde quer que um byte não de palavra encontre um byte de palavra. As lacunas saltam sequências de caracteres de forma não determinista, por isso o correspondente explora todas as posições que uma lacuna pode alcançar.

Âncoras e o prefiltro de varrimento

Para cada regra difusa o compilador calcula um conjunto de âncoras literais necessárias: literais tais que um candidato que não contenha nenhum deles não pode corresponder. Se uma regra puder corresponder sem qualquer literal garantido (por exemplo um percurso puramente de lacuna ou condicionado), o seu conjunto de âncoras está vazio e a regra é sempre executada. As âncoras de cada regra constroem um prefiltro Aho-Corasick que, numa única passagem sobre uma mensagem, marca que regras poderiam eventualmente corresponder, para que as regras cujos literais estão ausentes sejam saltadas. Isto é apenas uma otimização: nunca salta uma regra que poderia corresponder.

Regras estritas (MATCH)

Uma regra MATCH utiliza a linguagem estrita em vez do motor difuso. Um padrão estrito é ancorado: tem êxito apenas quando os seus termos consomem toda a entrada do princípio ao fim. É utilizado para entradas estruturadas como números e endereços, e pode capturar peças nomeadas.

Primitivas estritas

Palavra-chave Corresponde a
digit Exatamente um dígito.
digits Uma sequência de dígitos.
letter Exatamente uma letra.
letters Uma sequência de letras.
word Uma sequência de letras, dígitos ou underscore.
number Uma sequência de dígitos.
ip Uma sequência de dígitos e pontos.
space Exatamente um carácter de espaço em branco.
spaces Uma sequência de espaços em branco.
anything Qualquer extensão de texto.

A correspondência de palavras-chave ignora as maiúsculas. Um token "..." é um literal, com escapes de barra invertida respeitados.

Quantificadores e estrutura estritos

Construção Efeito
any x Zero ou mais de x, greedy.
many x Um ou mais de x, greedy.
maybe x Zero ou um de x.
m to n x Entre m e n de x.
n digit (etc.) Exatamente n de uma classe, por exemplo 3 digit.
( ... ) Um grupo correspondido como subsequência.
x or y Alternância: qualquer ramo pode satisfazer a posição.
then Cola legível opcional entre termos; ignorada.
x as name Captura o texto correspondido de x sob name.

Um quantificador aplicado a uma classe plural estreita-a à sua forma de um único carácter para que se repita um carácter de cada vez. A repetição é apenas greedy; não há modo lazy, e uma repetição vazia não pode fazer ciclo.

Referências de lista e busca difusa em estrito

&name num padrão estrito expande-se para uma alternância dos membros da classe como literais. Um termo com plicas find `pattern` incorpora um padrão EVE difuso: o correspondente estrito entrega uma extensão sem espaços ao padrão difuso compilado e aceita a extensão quando corresponde. Ambos precisam do registo, que está disponível quando um documento é compilado.

Regras anywhere e sequence

ANYWHERE e SEQUENCE são tipos de regra separados. ANYWHERE dispara quando cada grupo (separado por !! na forma compacta, WITH no ExpandaEVE) tem um membro encontrado algures na mensagem; cada membro é compilado como um correspondente difuso de palavra inteira, e a regra precisa de pelo menos dois grupos. SEQUENCE corresponde a uma sequência ordenada de glifos e é utilizada pelo quadro de símbolos, que está documentado separadamente; um prefixo mirror também aceita a ordem inversa, e a regra precisa de pelo menos dois glifos.