Já conheces o básico: classes de carateres como \d e [A-Z], quantificadores como {4} e +, e as palavras matches?, all-matching-subseqs e re-replace. Este exercício acrescenta as partes das expressões regulares que descrevem estrutura e contexto. Encontram-se no mesmo vocabulário regexp e escrevem-se com o mesmo literal R/ /.
As âncoras correspondem a uma posição e não a um caráter:
^ ! the start of the string
$ ! the end of the string
\b ! a word boundary
Como $ significa o fim da string, escreve \$ quando queres dizer um sinal de dólar literal.
Um limite de palavra (\b) é o ponto onde um caráter de palavra encontra algo que não é um caráter de palavra. Os carateres de palavra são letras, algarismos e o underscore; tudo o resto (espaços, pontuação e as duas extremidades da string) conta como limite. Por isso, \bcat\b só corresponde a cat quando este aparece isolado:
USING: regexp ;
"cat nap" R/ \bcat\b/ re-contains? . ! => t
"scatter" R/ \bcat\b/ re-contains? . ! => f (cat sits inside a word)
Os parênteses curvos ( ) agrupam parte de um padrão, para que um quantificador (ou um |, que significa "isto ou aquilo") se aplique ao grupo inteiro:
USING: regexp ;
"abab" R/ (ab)+/ matches? . ! => t (+ applies to "ab")
"dog" R/ cat|dog/ matches? . ! => t (either word)
"$100.50" R/ \$\d+(\.\d{2})?/ matches? . ! => t
"$100" R/ \$\d+(\.\d{2})?/ matches? . ! => t (the group is optional)
O último padrão lê-se assim: "um sinal de dólar, um ou mais algarismos e, depois, um grupo opcional formado por um ponto e dois algarismos".
Nota: em Factor, um grupo apenas estrutura o padrão. Ao contrário de algumas ferramentas de regex, não consegues recuperar o texto com que um grupo correspondeu: não há extração de grupos de captura, e referências anteriores como
\1não são suportadas. Para extrair texto em torno de um ponto de referência, usa antes o lookaround (abaixo).
Um lookaround verifica o que vem imediatamente antes ou imediatamente depois do ponto atual, sem tornar esses carateres parte da correspondência. É de "largura zero": o que testa não é incluído no resultado.
(?=...) ! lookahead: ... must follow
(?!...) ! negative lookahead: ... must NOT follow
(?<=...) ! lookbehind: ... must come before
(?<!...) ! negative lookbehind: ... must NOT come before
O lookaround é seletivo: os mesmos carateres correspondem ou não, consoante o que está ao lado deles. Um simples \d+ apanha todos os números; o lookbehind e o lookahead ficam apenas com os que têm o vizinho certo:
USING: regexp ;
"3 for $10 or 10 for $30" R/ \d+/ all-matching-subseqs .
! => { "3" "10" "10" "30" } (every number)
"3 for $10 or 10 for $30" R/ (?<=\$)\d+/ all-matching-subseqs .
! => { "10" "30" } (only the prices — the bare 3 and 10 drop)
"buy 2 at 15% off, 4 at 30% off" R/ \d+(?=%)/ all-matching-subseqs .
! => { "15" "30" } (only the discounts — the bare 2 and 4 drop)
Acrescenta uma letra depois do / de fecho para alterar o comportamento do padrão inteiro. O i faz com que não distinga maiúsculas de minúsculas:
USING: regexp ;
"REFUND" R/ refund/i matches? . ! => t
"Refund" R/ refund/i matches? . ! => t
O all-matching-subseqs entrega-te o texto correspondido. O map-matches vai um passo mais longe: executa uma quotation em cada correspondência e recolhe os resultados. A quotation recebe o índice start da correspondência, o índice end e a string completa; o subseq transforma esses três elementos no texto correspondido, que depois transformas:
map-matches ( string regexp quot: ( start end string -- obj ) -- seq )
USING: math regexp sequences ;
"a1 b22 c333" R/ \d+/ [ subseq length ] map-matches .
! => { 1 2 3 } (the length of each run of digits)
O seu primo que só produz efeitos secundários, o each-match, executa uma quotation em cada correspondência sem recolher nada.
Tens a contabilidade de uma pequena empresa e, todos os dias, chegam transações em linhas de texto desorganizadas. Vais usar expressões regulares para validar montantes, extrair valores pelo símbolo que os marca e sinalizar linhas que precisam de uma análise mais atenta.
Um montante bem formado é um $, um ou mais algarismos e uma parte de
cêntimos opcional, composta por um ponto seguido de exatamente dois
algarismos: $100 ou $100.50, mas não $100.5. Define valid-amount?,
que devolve t quando a string inteira é um montante bem formado.
"$100.50" valid-amount? . ! => t
"$100" valid-amount? . ! => t
"$100.5" valid-amount? . ! => f
Define dollar-amounts, que devolve todos os números que aparecem
imediatamente a seguir a um $ na linha (apenas os algarismos, sem o
$), por ordem.
"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }
Define percentages, que devolve todos os números que são
imediatamente seguidos por um %.
"up 5% then down 12%" percentages .
! => { "5" "12" }
Define flagged?, que devolve t quando a linha menciona refund ou
chargeback em qualquer combinação de maiúsculas e minúsculas.
"Issued a REFUND today" flagged? . ! => t
"a normal sale" flagged? . ! => f
Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.