Um Hello World para o Ruby em Ragel 6.0

Um Hello World para o Ruby em Ragel 6.0

Tradução de: http://www.devchix.com/2008/01/13/a-hello-world-for-ruby-on-ragel-60/

Essa é uma versão atualizada desse tutorial. Essa versão atualizada é compatível com Ruby 1.8 e Ruby 1.9, e Ragel 6.0.

No fim desse post, você será capaz de transformar uma simples string “h” em uma string muito mais interessante e maior, “hello world!”, usando a magia do Ragel, tudo com o conforto de Ruby. Ragel é um poderoso compilador de máquinas de estados e gerador de parser, que é o coração de softwares como Mongrel e Hpricot. É capaz de gerar código C, C++, Objective-C, D, Java ou código Ruby.

Ragel tem documentação excelente fornecida pelo autor. Meu alvo aqui é somente fornecer algum contexto para que a documentação faça ainda mais sentido quando você a ler, e fornecer um exemplo que você pode modificar conforme explora a funcionalidade do Ragel. Se você quer ir direto ao ponto, o exemplo completo está aqui.

O primeiro passo, é claro, a instalação do Ragel. A home page do Ragel tem uma seção de downloads que lista versões para várias plataformas. Se você já tem o Ragel instalado, verifique se a versão é 6.0 ou maior. Você pode também compilar e instalar o Ragel pelo fonte. Mesmo se você não quer instalar a partir do código fonte, é interessante ter uma cópia dele, pois ela contém alguns exemplos que você pode testar. O repositório subversion do ragel está localizado aqui:

svn://mambo.cs.queensu.ca/ragel/trunk/

Como de praxe, o diretório test/ é seu amigo, veja também o diretório examples/. Conforme essa thread, tente procurar por “LANG: ruby”.

Ao escrever código Ragel, você pode criar um arquivo com uma extensão .rl. O arquivo .rl é escrito na linguagen "hospedeira", nesse caso, Ruby, e a especificação da máquina de estados Ragel é embutida dentro do código Ruby usando delimitadores especiais. Não é obrigatório especificar uma máquina de estados, então um arquivo .rl perfeitamente válido pode ser:

puts "hello world"

Não se preocupe, eu farei um Hello World melhor que esse, mas esse é um bom lugar pra começar. Para converter esse arquivo .rl em um executável .rb file, use o comando "ragel" com o argumento -R para indicar que você quer código Ruby.

ragel -R hello_world.rl

Isso irá criar um arquivo chamado hello_world.rb com o seguinte conteúdo:

# line 1 "hello_world.rl"
puts "hello world"

Eu vou deixar a execução desse arquivo como um exercício para o estudante diligente.

O Ragel na verdade faz essa conversão em dois estágios. Primeiro, ele criar um arquivo XML, então converte o XML para Ruby. Se você quer ver o XML intermediários então você pode especificar o argumento -x em adição ao argumento -R.

ragel -R -x simple_state_machine.rl > simple_state_machine.xml

Agora, vamos escrever algum código Ragel pra valer. Inicie um novo arquivo .rl ou baixe o exemplo para acompanhar. Nós vamos criar uma máquina de estados que imprime a string "hello world!" quando recebe a string "h". Para indicar ao compilar ragel que você está escrevendo instruções para ele, e não código Ruby, precisamos colocar nosso código Ragel dentro de dois sinais de porcento seguidos de chaves, %%{ e }%% , ou você pode simplesmente entrar uma instrução de uma única linha apenas digitando %%. (Veja a página 6 do Guia de Usuário.) Aqui está nossa especificação da máquina de estados:

%%{
machine hello;
expr = "h";
main := expr @ { puts "hello world!" } ;
}%%

Uma olhada rápida no que está acontecendo. O nome da máquina de estados é "hello" (o Ragel nos obrigada a nomeá-la). Ele reconhece um único token, a string "h". Quando ele encontra esse token, ele executa (em Ruby) a ação:

puts "hello world"

Agora, se você executar o ragel nesse arquivo ele compilará, mas você vai terminar com um arquivo em Ruby em branco. Nós apenas especificamos a máquina, nós também temos de dizer ao Ragel como traduzir essa máquina em código Ruby usando as declarações de escrita do Ragel. A primeira declaração de escrita que precisamos adicionar é

%% write data;

Se você adicionar essa linha depois do bloco de definição da máquina de estados, ela irá compilar, contando que você lembre-se de adicionar uma linha em branco depois. (Depois que você trabalhar com parsers por um tempo, você vai apreciar quebras de linha de uma nova forma.) Após adicionar essa linha e compilar, você deve ter um arquivo Ruby significante, com várias declarações de class << self, todas geradas pelo Ragel. Você não precisa estudar esse código, pelo menos não agora. Ele é muito idiota e feio. E se você executá-lo, você não verá nenhuma saída.

Existem mais duas declarações write para adicionar, e por conveniência, vamos colocá-los dentro de um método ruby. O argumento para esse método será a string que queremos parsear. O Ragel expera encontrar um variável chamada "data" contendo um array de códigos ASCII, então precisaremos converter nossa string em um array. Isso é feito muito facilmente em Ruby usando o método unpack.

def run_machine(data)
  data = data.unpack("c*") if data.is_a?(String)
  %% write init;
  %% write exec;
end

O write init diz ao Ragel que nós queremos gerar código de inicialização para a máquina de estados. O código Ragel gerado aqui é:

begin
  p ||= 0
  pe ||= data.length
  cs = hello_start
end

A variável p mantém o registro de qual caracter na string data nós estamos parseando agora, iniciando em 0. pe é um limite superior para p. cs mantém o estado atual da máquina de estados, e aqui é inicializado o estado de início da máquina de estados. Essas variáveis são discutidas no Guia de Usuário.

write exec diz ao Ragel para escrever o recheio do parser (finalmente!). O código gerado aqui irá realmente pegar uma entrada (o argumento data) e determinar qual deverá ser o estado do sistema baseado nessa entrada, executando quaisquer ações que possam ser disparadas pelo caminho. Vamos adicionar algumas declarações puts para que possamos seguir a execução do código.

def run_machine(data)
  data = data.unpack("c*") if data.is_a?(String)
  puts "Running the state machine with input #{data}..."

  %% write init;
  %% write exec;

  puts "Finished. The state of the machine is: #{cs}"
  puts "p: #{p} pe: #{pe}"
end

Apenas adicione mais duas linhas ao fim da chamada run_machine com vários argumentos e então finalmente poderemos compilar e executar nossa máquina de estados.

run_machine "h"
run_machine "x"

E lá vamos nós…

Running the state machine with input 104...
hello world!
Finished. The state of the machine is: 2
p: 1 pe: 1
Running the state machine with input 120...
Finished. The state of the machine is: 0
p: 0 pe: 1

Funcionou! Agora, para nos ajudar a interpretar os valores de p, pe e cs vamos dar uma olhada em um gráfico de estados dessa máquina de estados. O Ragel tem suporte embutido a Graphviz para criar gráficos de estados. Precisamos usar o argumento -V ao invés do -R.

ragel -V simple_state_machine.rl > simple_state_machine.dot

Se você renderizer o arquivo simple_state_machine.dot resultante no Graphviz, você deve obter algo semelhante a:

State Chart for Simple State Machine

Nós podemos ver que essa máquina tem apenas uma transição possível, do estado 1 para o estado 2. Quando passamos "h" como parâmetro para run_machine nós terminaremos com a variável cs (current state) igual a 2 no fim de nossa execução. Quando "x" foi passado, nós terminamos com cs = 0. 0 é o estado de erro, indicando que um erro ocorreu na máquina de estados. (Você pode dizer que 0 é o estado de erro lendo algumas das atribuições de variáveis geradas pelo write data, o código que eu disse que era burro e feio.)

No label 104/4:18 sobre a seta na transição do estado 1 para o estado 2, o label 104 corresponde ao código ASCII para a letra "h". (Digite “?h” no irb.) A / indica que uma ação está sendo executada, e 4:18 nos diz que a ação inicia na linha 4, coluna 18 do arquivo .rl. Se nós tivéssemos dado um nome a nossa ação, isso teria aparecido no lugar da posição no arquivo.

A propósito, aqui está o shell script (específico do textmate) que eu usei para executar esses passos rapidamente:

ragel -R simple_state_machine.rl
ragel -V simple_state_machine.rl > simple_state_machine.dot
dot -Tpng simple_state_machine.dot > simple_state_machine.png
open simple_state_machine.png
ruby simple_state_machine.rb
mate simple_state_machine.out

Agora, tente executar esse código:

run_machine "hh"

Você deve obter:

Running the state machine with input 104104...
hello world!
Finished. The state of the machine is: 0
p: 1 pe: 2

Você não obtém "hello world!" duas vezes. Desculpe. Nossa máquina de estados está somente procurando o primeiro caractere que passamos. Ela sabe que passamos dois caracteres, demonstrado pela variável pe = 2, mas após avaliar o primeiro caractere, ela está no estado final. Não existe seta saindo do círculo no estado 2. Assim, a passagem de inputs adicionais resulta no sistema entrando no estado de erro. Se queremos que toda a string data seja executada, precisamos fazer uma pequena mudança na especificação de nossa máquina de estados.

main := expr+ @ { puts "hello world!" } ;
Endless Simple State Machine

(Tente expr* ao invés de expr+ e veja como o gráfico de estados é diferente.)

Agora, tente executar essa nova máquina de, com as entradas "hhh" e "hxh":

Running the state machine with input 104104104...
hello world!
hello world!
hello world!
Finished. The state of the machine is: 2
p: 3 pe: 3
Running the state machine with input 104120104...
hello world!
Finished. The state of the machine is: 0
p: 1 pe: 3

Quando passamos "hhh", nós obtivemos um "hello world!" para cada "h". Quando passamos "hxh", nós obtivemos o primeiro "hello world!", mas quando chegamos ao "x" entramos no estado de erro, então o último "h" não foi avaliado.

Aqui está mais um exemplo <http://dev.agent.ie/svn/ragel/examples/multiple_state_machine.rl>, dessa vez sem definir um método run_machine:

%%{
  machine hello_and_welcome;
  main := ( 'h' @ { puts "hello world!" }
          | 'w' @ { puts "welcome" }
          )*;
}%%
  data = 'whwwwwhw'
  %% write data;
  %% write init;
  %% write exec;
Hello and Welcome State Machine
welcome
hello world!
welcome
welcome
welcome
welcome
hello world!
welcome

Então, é isso. Horas de entretenimento o aguardam. Tivemos uma visão muito pequena das característica do Ragel aqui, mas você deve ser capaz de acompanhar o Guia do Usuário sem muito problema. Se você precisa de uma razão melhor que "divertimento" para brincar com o Ragel, tenha em mente que parsers são uma grande ferramenta para construir Domain Specific Languages (DSLs), e máquinas de estados são máquinas mágicas de encolhimento de código para situações onde você precisa seguir o estado de algo e controlar as mudanças entre os estados(i.e. lógica de negócio). Eu recomendaria que todos lessem esse artigo sobre o Ragel que me inspirou a estudá-lo. Se você usa Rails, então veja o plugin acts_as_state_machine que pode ser mais intuitivo que o Ragel no início. Se DSLs são a sua preferência, então você pode querer dar uma olhada no ANTLR , que tem um foco e conjunto de características diferentes do Ragel.

# % if c.boo_box: DVD Ao Vivo em Brotas e S&#227;o Paulo 32&#186;  Anu&#225;rio do Clube de cria&#231;&#227;o de S&#227;o Paulo S&#227;o Paulo por Paulo Caruso: uma Vis&#227;o Bem Humorada Sobre Esta Cidade Dose Dupla: Ao Vivo em Brotas e S&#227;o Paulo DVD + CD CD S&#227;o Paulo Ska Jazz Quadruplos, S&#227;o Paulo - Paulo Fridman - Fotografia