|
A alguns dias atras li alguns artigos (esse, e esse outro) sobre DSL em ruby e fiquei altamente instigado para fazer uma DSL.
Não quero entrar em detalhes do que é ou não é uma DSL pois esse assunto é muito subjetivo. Ideial par se levar para mesa de um bar. (dependendo da compainha logico!) =)
O que você tem que saber é que se estou escrevendo essa linguagem para um fim e se você a usa com essa finalidade isso é uma DSL. Agora se você consegue usar, usando alguma esperteza, para outros fins isso não é uma DSL para você.
Vamos escrever uma DSL para meu irmão (pudim, o pelidiozinho! hehehe) fazer programas para controlar o winamp com as musicas dele (swinguera da bahia!). Já fiz algo parecido em java (aqui) mas concerteza vai ficar muito dificil para ele usar. Então vamos fazer uma DSL para ele =)
Primeiro precisamos escrever como seria uma DSL facil para ele usar. Pensei em algo do tipo:
Bem acho que meu irmão consegue entender isso! Mas perai… temos um problema! Precisamos fazer isso ficar com a cara de ruby do jeito que tá a sintaxe não encaixa. Vamos ajeitar para:
Bem agora isso ta com cara de ruby! Então vamos colocar isso em um arquivo para que nossa DSL possa parsear isso.
winamp.scr
Agora vamos começar a fazer nossa DSL. Vamos pirmeiro nos preucupar com o parse e criar metodos burros que apenas imprimem o que irão fazer.
dsl.rb
load ‘winamp.src’
Ao rodar isso vamos ter como saida:
vou rodar os comandos de proc: #<Proc:0×027f59fc@./winamp.src:1>
Agora vamos implementar a classe que vai se comunicar com o winamp:
dsl.rb
#O winamp tem que esta aberto!
class Winamp
def initialize
#faz uma chamada a api do windows para procurar pelo winamp aberto
findWindow = Win32API.new(‘user32’,‘FindWindow’, ‘PI’, ‘I’)
#guarda a referencia do winamp aberto
handle = findWindow.call('Winamp v1.x', 0)
end
def play
if @handle
#faz uma chamada a api do windows para enviar uma mensagem ao winamp
sendMessage = Win32API.new("user32", "SendMessage", ['L'] * 4, 'L')
#envia a mensagem com os codigos para da play no winamp
#esses codigos podem ser conseguidos na pagina do Winamp na area de desenvolvimento (SDK)
sendMessage.call(handle, 0×0111, 40045, 0)
end
end
def stop
if handle
#faz uma chamada a api do windows para enviar uma mensagem ao winamp
sendMessage = Win32API.new("user32", "SendMessage", ['L'] * 4, 'L')
#envia a mensagem com os codigos para da play no winamp
#esses codigos podem ser conseguidos na pagina do Winamp na area de desenvolvimento (SDK)
sendMessage.call(handle, 0×0111, 40047, 0)
end
end
end
def com_winamp(&proc)
#roda os comandos na instancia do objeto
Winamp.new.instance_eval &proc
end
load ‘winamp.src’
Agora fazemos no prompt de comando:
ruby dsl.rb
Opa! já vemos nossa DSL em acão!
Agora vamos ser um pouco DRY e refatorar um pouco. Você deve ter percebido que repitimos muito essas chamdas de api do windows. Vamos enxugar isso e criar metodos privados para realizar essas chamadas.
dsl.rb
#O winamp tem que esta aberto!
class Winamp
def initialize
#guarda a referencia do winamp aberto
handle = find_window('Winamp v1.x', 0)
end
def play
if @handle
send_message(handle, 0×0111, 40045, 0)
end
end
def stop
if handle
send_message(handle, 0×0111, 40047, 0)
end
end
private
#faz uma chamada a api do windows para enviar mensagens ao winamp
def send_message(args)
#envia a mensagem com os codigos para da play no winamp
#esses codigos podem ser conseguidos na pagina do Winamp na area de desenvolvimento (SDK)
Win32API.new(“user32”, “SendMessage”, [‘L’] * 4, ‘L’).call *args
end
#faz uma chamada a api do windows para procurar pelo winamp aberto
def find_window(args)
Win32API.new(‘user32’,‘FindWindow’, ‘PI’, ‘I’).call *args
end
end
def com_winamp(&proc)
#roda os comandos na instancia do objeto
Winamp.new.instance_eval &proc
end
load ‘winamp.src’
Legal! Mas ainda não é suficiente estamos repitindo muito o “if” que verifica se o handle não é nulo em cada metodo da classe. Vamos mudar isso tambem.
dsl.rb
#O winamp tem que esta aberto!
class Winamp
class << self
# cria um metodo na classe Winamp para definicao de metodos com verificação de handle
def novo_metodo(metodo, &proc)
define_method(metodo) { instance_eval &proc if handle }
end
end
def initialize
#guarda a referencia do winamp aberto
@handle = find_window('Winamp v1.x', 0)
end
novo_metodo :play do
send_message(handle, 0×0111, 40045, 0)
end
novo_metodo :stop do
send_message(@handle, 0×0111, 40047, 0)
end
private
#faz uma chamada a api do windows para enviar mensagens ao winamp
def send_message(args)
#envia a mensagem com os codigos para da play no winamp
#esses codigos podem ser conseguidos na pagina do Winamp na area de desenvolvimento (SDK)
Win32API.new(“user32”, “SendMessage”, [‘L’] * 4, ‘L’).call *args
end
#faz uma chamada a api do windows para procurar pelo winamp aberto
def find_window(args)
Win32API.new(‘user32’,‘FindWindow’, ‘PI’, ‘I’).call *args
end
end
def com_winamp(&proc)
#roda os comandos na instancia do objeto
Winamp.new.instance_eval &proc
end
load ‘winamp.src’
Agora podemos retirar aquela chamada estatica ao “winamp.src” e passar isso por paramentro na linha de comando tambem podemos colocar constantes nos codigos de mensagens do winamp.
E nosso resultado final é algo do tipo:
dsl.rb
#O winamp tem que esta aberto!
class Winamp
WM_COMMAND = 0×0111
WA_NOTHING = 0
WA_PLAY = 40045
WA_STOP = 40047;
WA_PAUSE = 40046;
WA_PREVTRACK = 40044;
WA_NEXTTRACK = 40048;
WA_PLAYLISTLEN = 124;
WA_SETVOLUME = 122 ;
WA_SETPLAYLISTPOS = 121;
WA_CLEARPLAYLIST = 101;
WA_RESTART = 135;
WA_GETSTATUS = 104;
WA_REFRESHPLCACHE = 247;
WA_SETSHUFFLESTATUS = 252;
WA_SETREPEATSTATUS = 253;
WA_VOLUMEUP = 40058;
WA_VOLUMEDOWN = 40059;
class << self
# cria um metodo na classe Winamp para definicao de metodos com verificação de handle
def novo_metodo(metodo, &proc)
define_method(metodo) { instance_eval &proc if handle }
end
end
def initialize
#guarda a referencia do winamp aberto
@handle = find_window('Winamp v1.x', 0)
end
novo_metodo :play do
send_message(handle, WM_COMMAND, WA_PLAY, WA_NOTHING)
end
novo_metodo :stop do
send_message(@handle, WM_COMMAND, WA_STOP, WA_NOTHING)
end
novo_metodo :pause do
send_message(@handle, WM_COMMAND, WA_PAUSE, WA_NOTHING)
end
novo_metodo :faixa_anterior do
send_message(@handle, WM_COMMAND, WA_PREVTRACK, WA_NOTHING)
end
novo_metodo :faixa_seguinte do
send_message(@handle, WM_COMMAND, WA_NEXTTRACK, WA_NOTHING)
end
private
#faz uma chamada a api do windows para enviar uma mensagem ao winamp
def send_message(*args)
#envia a mensagem com os codigos para da play no winamp
#esses codigos podem ser conseguidos na pagina do Winamp na area de desenvolvimento (SDK)
Win32API.new(“user32”, “SendMessage”, [‘L’] * 4, ‘L’).call *args
end
#faz uma chamada a api do windows para procurar pelo winamp aberto
def find_window(*args)
Win32API.new(‘user32’,‘FindWindow’, ‘PI’, ‘I’).call *args
end
end
def com_winamp(&proc)
#roda os comandos na instancia do objeto
Winamp.new.instance_eval &proc
end
load ARGV.shift
winamp.src
para funcionar:
ruby dls.rb winamp.src
ps: só lembrando o winamp tem que ta aberto =)
=*
|