MouseOver Studio

MouseOver Studio header image 2

Interpretando Ruby e outras linguagens de script dentro da plataforma Java

agosto 29th, 2008 por Diego Carrion · 6 comentários

Para poder criar o Haml4j sem a necessidade de reinventar a roda teve que poder aproveitar o código Ruby já existente. Existem vários jeitos de interpretar código Ruby na JVM. Um de eles é utilizando o engine do JRuby para o BSF (Bean Script Framework), do Apache. Outro jeito é utilizando o JRuby puro, como foi mostrado aqui. O terceiro jeito, e do qual eu gostei mais, é utilizando a implementação JRuby para à especificação JSR-233, que define o uso das diferentes linguagens de script na plataforma Java.

Existem bons documentos que explicam como trabalhar com JRuby utilizando a especificação JSR-233 aqui, porém, eles estão todos em inglês e é por tal motivo que eu vou falar aqui sobre como a coisa funciona.

Antes de que me esqueça, é importante lembrar que o que ira ser mostrado aqui se aplica tanto a JRuby como a JavaScript ou qualquer outra linguagem de script, devido a que estamos trabalhando sobre a especificação e não sobre a implementação. A implementação é utilizada de forma transparente.

Quando queremos interpretar algum script, o primeiro que temos que fazer é obter uma instancia do engine para o tipo de script que queremos interpretar. A classe que nos oferece tais engines é ScriptEngineManager. Gostaria que o método para requerer certo engine fosse estático, mas não, temos que criar uma instancia dele para logo solicitar o engine, assim:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");

Reparem no seus imports que não fazemos referencia a nada do JRuby. Isso é muito legal. Caso amanha saia um interpretador do Ruby melhor que o JRuby e desejemos utilizar ele, o único que vamos ter que fazer e mudar o parâmetro do método getEngineByName. Claro, sempre e quando o interpretador implemente a especificação.

Quando mandamos nosso engine escolhido interpretar algum script, podemos indicar à ele qual contexto utilizar. Um contexto é como um espaço ou espoco onde colocamos diversos objetos Java, os quais iram ser tratados como variareis globais na hora da interpretar os scripts. Podemos ter diversos contextos. Segue um exemplo:

engine.put("language", "portuguese");
engine.eval("puts 'You choose #{$language}'");
ScriptContext newContext = new SimpleScriptContext();
newContext.setAttribute("language", "spanish", ScriptContext.ENGINE_SCOPE);
engine.eval("puts 'You choose #{$language}'", newContext);

O contexto possui um mapa de chaves e valores. Na primeira linha do exemplo colocamos o valor portuguese para a chave language no contexto default. Quando não indicamos nenhum contexto, o engine pega os valores do contexto default e por tal motivo a segunda linha ira imprimir You choose portuguse. Na terceira linha criamos um segundo contexto e na quarta linha indicamos o valor spanish para a mesma chave utilizada anteriormente: language. Reparem que o método setAttribute aceita um segundo parâmetro que pode ser ScriptContext.ENGINE_SCOPE ou ScriptContext.GLOBAL_SCOPE. Esse segundo parâmetro é algo que ainda não entendi direito. Na teoria, ENGINE_SCOPE indica que o mapeamento se aplica somente ao engine em questão, à diferença de GLOBAL_SCOPE que indicaria que o mapeamento é para todos os engines. O problema é que eu não tenho nenhum engine relacionado com tal contexto, pelo que indicar um escopo ao contexto não teria sentido. Somente para terminar esse exemplo, a ultima linha ira utilizar o contexto criado e devido a isso ira imprimir You choose spanish.

O método eval retorna o resultado da interpretação do script. No exemplo o resultado seria nil. Para termos uma idéia do valor a ser devolvido poderíamos utilizar o Jirb:

dcrec1:~ dcrec1$ jirb 
irb(main):001:0> puts "You choose portuguese"
You choose portuguese
=> nil
irb(main):002:0> 1 + 2
=> 3
irb(main):003:0> a = 5
=> 5
irb(main):004:0> a
=> 5
irb(main):005:0> require 'rubygems'
=> true
irb(main):006:0> require 'haml'
=> true

O método eval retorna uma instancia da classe Object, pelo que o resultado precisa passar por um processo de casting. Segue um exemplo tomado da primeira versão do Haml4j:

String result = (String) engine.eval("Haml::Engine.new($haml).render");

Agora imaginemos que numa dessas chamadas ao método eval o engine interpretou uma definição de função ou de uma classe com métodos. Se queremos executar uma das funções interpretadas, não precisamos chamar de novo o método eval com o nome da função e os parâmetros concatenados como uma string. Nesse caso podemos declarar um objeto invocável e invocar ele passando como parâmetro a função desejada e os parâmetros, assim:

Invocable invocable = (Invocable) engine;
String randomText = (String) invocable.invokeFunction("generate_random_text");
Object car = invocable.invokeFunction("create_a_new_car", "blue", "automatic");

Na primeira linha definimos um objeto invocável e na segunda executamos a função generate_random_text, que sabemos devolve uma string e por isso fazemos um casting. Na terceira linha executamos a função create_new_car passando como parâmetros blue e automatic. Nesse caso não fizemos nenhum casting porque o resultado é um objeto do Ruby (poderia ser de qualquer linguagem). Nosso objeto chamado car tem alguns métodos e agora vamos chamar um deles:

int speed = (Integer) invocable.invokeMethod(
      car, "max_speed", 100);

Acabamos de chamar o método max_speed (segundo parâmetro) do objeto car (primeiro parâmetro) passando como parâmetro 100. Esse terceiro parâmetro esta estranho mas imaginemos que o método calcula a velocidade máxima que o carro pode alcançar para um peso determinado.

No caso que nossas funções ou métodos invocáveis façam referencia a alguma variável global, podemos definir o valor dela chamando o metodo put:

invocable.put("country", "Brazil");

Com o mostrado aqui da tranqüilamente para começar a utilizar scripts legais em Ruby ou outra linguagem de script dentro da plataforma Java. Espero que no futuro apareçam muitos projetos brasileiros legais. Ficarei no aguardo. Aliais, ficarei tentando eu mesmo criar um :)

Tags: java · jruby · jsr · ruby · script

6 respostas ate agora ↓

Deixar um comentário