Interrompendo a gramática Raku na EOS (fim da string)

9

No processo de escrever um tradutor de uma linguagem musical para outra (ABC para Alda) como uma desculpa para aprender a capacidade DSL do Raku, notei que não parece haver uma maneira de encerrar a .parse! Aqui está o meu código de demonstração abreviado:

#!/home/hsmyers/rakudo741/bin/perl6
use v6d;

# use Grammar::Debugger;
use Grammar::Tracer;

my $test-n01 = q:to/EOS/;
a b c d e f g
A B C D E F G
EOS

grammar test {
  token TOP { <score>+ }
  token score {
      <.ws>?
      [
          | <uc>
          | <lc>
      ]+
      <.ws>?
  }
  token uc { <[A..G]> }
  token lc { <[a..g]> }
}

test.parse($test-n01).say;

E é a última parte da tela Grammer :: Tracer que demonstra meu problema.

|  score
|  |  uc
|  |  * MATCH "G"
|  * MATCH "G\n"
|  score
|  * FAIL
* MATCH "a b c d e f g\nA B C D E F G\n"
「a b c d e f g
A B C D E F G
」

Na penúltima linha, a palavra FAIL diz que a execução .parse não tem como sair. Gostaria de saber se isso está correto? O .say mostra tudo como deve ser, então não tenho certeza de quão real é o FAIL? A pergunta permanece: "Como escrevo corretamente uma gramática que analisa várias linhas sem erro?"

hsmyers
fonte
Não quero interferir no seu processo de aprendizado, mas, caso você não saiba, existe um módulo ABC .
raiph
11
Bem, pelo menos não escolhemos as mesmas músicas para testar!
hsmyers

Respostas:

10

Quando você usa o depurador de gramática, ele permite que você veja exatamente como o mecanismo está analisando a sequência - as falhas são normais e esperadas. Considerado, por exemplo, correspondência a+b*com a sequência aab. Você deve obter duas correspondências para 'a', seguidas por uma falha (porque bnão é a), mas, em seguida, ele tentará novamente be corresponderá com êxito.

Isso pode ser visto com mais facilidade se você alternar com ||(que impõe ordem). Se você tem

token TOP   { I have a <fruit> }
token fruit { apple || orange || kiwi }

e você analisa a frase "eu tenho um kiwi", você verá a primeira correspondência "eu tenho um", seguida de duas falhas com "maçã" e "laranja" e, finalmente, uma correspondência com "kiwi".

Agora vamos ver o seu caso:

TOP                  # Trying to match top (need >1 match of score)
|  score             #   Trying to match score (need >1 match of lc/uc)
|  |  lc             #     Trying to match lc
|  |  * MATCH "a"    #     lc had a successful match! ("a")
|  * MATCH "a "      #   and as a result so did score! ("a ")
|  score             #   Trying to match score again (because <score>+)
|  |  lc             #     Trying to match lc 
|  |  * MATCH "b"    #     lc had a successful match! ("b")
|  * MATCH "b "      #   and as a result so did score! ("b ")
……………                #     …so forth and so on until…
|  score             #   Trying to match score again (because <score>+)
|  |  uc             #     Trying to match uc
|  |  * MATCH "G"    #     uc had a successful match! ("G")
|  * MATCH "G\n"     #   and as a result, so did score! ("G\n")
|  score             #   Trying to match *score* again (because <score>+)
|  * FAIL            #   failed to match score, because no lc/uc.
|
|  # <--------------   At this point, the question is, did TOP match?
|  #                     Remember, TOP is <score>+, so we match TOP if there 
|  #                     was at least one <score> token that matched, there was so...
|
* MATCH "a b c d e f g\nA B C D E F G\n" # this is the TOP match

A falha aqui é normal: em algum momento, ficaremos sem <score>fichas, portanto, uma falha é inevitável. Quando isso acontece, o mecanismo de gramática pode passar para o que vier depois da <score>+gramática. Como não há nada, essa falha realmente resulta em uma correspondência de toda a cadeia de caracteres (porque TOPcorresponde ao implícito /^…$/).

Além disso, você pode reescrever sua gramática com uma regra que insere <.ws> * automaticamente (a menos que seja importante que seja apenas um espaço):

grammar test {
  rule TOP { <score>+ }
  token score {
      [
          | <uc>
          | <lc>
      ]+
  }
  token uc { <[A..G]> }
  token lc { <[a..g]> }
}

Além disso, IME, você também pode querer adicionar um token proto para o uc / lc, porque quando o tiver [ <foo> | <bar> ], sempre haverá um deles indefinido, o que pode tornar um pouco irritante o processamento deles em uma classe de ações. Você poderia tentar:

grammar test {
  rule  TOP   { <score>  + }
  token score { <letter> + }

  proto token letter    {     *    }
        token letter:uc { <[A..G]> }
        token letter:lc { <[a..g]> }
}

$<letter> será sempre definido dessa maneira.

user0721090601
fonte
Isso explica o fato de o objeto de correspondência ter retornado 'so' como verdadeiro mesmo com o 'FAIL'. Eu pensei que poderia ser o caso; Eu vou voltar para a adição de fichas necessárias para o projeto real;)
hsmyers
A gramática real não parece gostar de inserir <.ws> * automaticamente; provavelmente devido a camadas adicionais envolvidas além de <score>. Sua sugestão para uso proto parece bom assim que eu sou capaz de envolver minha cabeça em torno da técnica ...
hsmyers
Eu odeio ter código que não preciso - mais para depurar, e depois há a estética de tudo! O problema real é que o ABC não dá a mínima para os espaços. Existem algumas exceções, mas em geral elas podem ocorrer em quase qualquer lugar. O caso 'use' é uma questão de legibilidade, semelhante a vírgulas em grandes cadeias de dígitos. Vou revisitar o problema conforme necessário até entender o problema e reduzi-lo ao mínimo.
hsmyers
11
hsmyers: felizmente, entender protonão é muito difícil e, quando você pega o jeito, torna sua vida muito mais fácil.
user0721090601