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:
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!" } ;
(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;
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.