Corporate Speak: Endereçável

October 29th, 2009 § 2 comments § permalink

en•de•re•çá•vel
adjetivo
1. Algo que pode ser endereçado
2. Aquilo que foi ou será endereçado

— Dicionário Corporativo, Superfície Reflexiva

en•de•re•ça•do
adjetivo
Tratado; corrigido; mitigado; repassado; contornado; delegado; ignorado

— Dicionário Corporativo, Superfície Reflexiva

en•de•re•çar
verbo transitivo
1. Tratar; corrigir; mitigar; repassar; contornar; delegar; ignorar
2. Ato de tornar algo endereçável
3. (corruptela do inglês to address) Começar a pensar ou lidar com algo

— Dicionário Corporativo, Superfície Reflexiva

Sinergia Arquitetural

October 27th, 2009 § 3 comments § permalink

syn•er•gy |ˈsinərjē|
noun
the interaction or cooperation of two or more organizations, substances, or other agents to produce a combined effect greater than the sum of their separate effects

— Apple’s Mac OS X Dictionary

A definição acima não é muito padrão mas expressa bem o conceito em que a palavra é entendida hoje em dia. Sinergia vem do grego sunergos, que literalmente significa trabalhar em conjunto.

Em inglês, a palavra data de cerca de 1650, e tinha um sentido bastante medicinal, podendo indicar tanto a ação cooperativa entre dois ou mais músculos do corpo para a realização de um esforço ou a ação combinada de medicamentos ou drogas para criar um estímulo maior no paciente.

O Wikitionary define sinergia também com o “comportamento de um sistema que não pode ser previsto pelo comportamento de suas partes”, o que é uma definição bastante interessante e diversa do sentido necessariamente positivo que a palavra ganhou nas últimas décadas.

De fato, sinergia hoje em dia é considerada uma buzzword, uma palavra sem significado para indicar a vontade de que algum esforço conjunto qualquer resulte em mais lucro, para qualquer valor de lucro, do que um esforço individual das partes envolvidas–a palavra-chave na mudança de definição aqui sendo “vontade” versus o efeito real.

No meu trabalho com arquitetura de software, entretanto, sinergia é algo que faz sentido naturalmente no desenho de sistemas. De fato, sistemas de sistemas tender a exibir isso de uma forma quase que óbvio já que não é possível desenhar os mesmos de outra forma. Não é sem motivo que a página na Wikipedia que explica o conceito usa sinergismo, uma variação de sinergia para explicar a motivação primária desse objetivo. O Unix é um exemplo bem óbvio disso e algumas pessoas chegam a se referir a isso como o Tao do Unix pelo fato de que as peças se encaixam com tal perfeição que é impossível pensar em trabalhar de outra forma.

Sistemas de sistemas fazem sentido em praticamente qualquer situação em que a complexidade das partes individuais torne o sistema final potencialmente (e exponencialmente) difícil de ser descrito de maneira consistente.

Essa é uma das razões, inclusive, pela qual as pessoas se decepcionam tanto com frameworks como Rails ou Django quando precisam de expandir aplicações já existentes para níveis maiores de complementaridade ou escalabilidade. Frameworks fechados simplesmente não exibem esse tipo de sinergia integral necessária para garantir que os princípios aplicáveis em aplicações menores sejam os mesmos de aplicações maiores. O resultado é que, essencialmente, todos esses princípios são violados à medida que o sistema precisa crescer resultando em um entendimento completamente diferente do que se desejava a princípio. Funciona, mas sem elegância ou beleza.

A despeito do significado amortecido, uma reflexão cuidadosa sobre sinergia e sua aplicabilidade ao desenho de sistemas faria bem a qualquer desenvolvedor ou arquiteto de sistemas. Sapir-Whorf sendo fraca ou não, um entendimento real é impossível se o vocabulário natural é desprezado.

Balanço cultural de setembro

October 26th, 2009 § 0 comments § permalink

Setembro melhorou um pouco em relação a agosto mas ainda estou bem aquém do que gostaria. O resultado do mês foi o seguinte:

  • 2 livros
  • 1 filme

Nos livros, comecei o mês lendo Thunderer, do Felix Gilman. O nome e a capa do livro mais blurbs mal escritos me deixaram meio ressabiado mas as resenhas geralmente positivas e as duas indicações a prêmios do gênero finalmente me convenceram a dar uma chance à estória.

Esse primeiro trabalho de Gilman me lembrou fortemente os livros do China Miéville, que estão entre os meus favoritos do New Weird (e de fantasia em geral, na verdade). De uma certa forma, parece que Gilman leu os livros de Miéville e decidiu que consegui escrever algo similar. E para crédito do autor, acho que ele conseguiu uma boa variação sobre o mesmo tema.

Essencialmente, a estória é sobre um peregrino, Arjun, que chega na enorme e multi-facetada cidade de Ararat em busca de uma divindade perdida por sua ordem de músicos e cantores. Arjun, estranho aos costumes da cidade, já chega em meio a um evento significativo: a transmutação de um navio pertencente a uma das casas que dominam a região perto do porto da cidade–cujo nome é o mesmo do título do livro–em uma fortaleza voadora usando os poderes deixados pela esteira de uma divindade que há muito não visitava a metrópole. O responsável por isso é um cientista chamado Holbach que está convencido poder controlar os sinais e símbolos que regem os poderes sobrenaturais da cidade. Como tudo em Ararat, entretanto, cidade de milhares de deuses, a passagem desse deus muda mais do que somente o navio e Arjun logo se vê envolvido na conseqüência desses eventos.

Como é fácil perceber, Gilman realmente bebeu na mesma fonte que Miéville e sua estória segue muitos dos passados traçados por esse último em Perdido Street Station. Arjun é muito parecido com Yagharek e Holbach com Isaac Dan der Grimnebulin. Os dois primeiros estão a procura de algo que perderam e os outros dois em busca de quebrar as barreiras da magia por meio de uma incipiente ciência taumatúrgica. Todos, movidos por suas necessidades, trazem às suas respectivas vidas mais do que conseguem controlar e precisam lidar com o que não previam para tentar restaurar uma aparência de ordem em seus mundos. E, da mesma forma, os sucessos nessa empresa são parciais e insatisfatórios.

Com isso não quero dizer também que Gilman plagiou descaradamente Miéville. Ararat, como New Crobuzon, é uma cidade-personagem mas Ararat é muito mais complexa do que a cidade imaginada por Miéville. De fato, como fica logo aparente, Ararat é muito mais expressiva: suas ruas resistem mapeamento e mudam de momento a momento, é possível passar semanas tentando atravessar certas áreas da cidade sem sucesso, há uma montanha ao longe que nunca pode ser alcançada e infinitas versões passadas e futuras da cidade convivem em planos que podem ser alcançados por uso de passagens entre as vielas e casas.

O resultado disso tudo é um livro que não fica nada a dever dos outros do gênero e que acrescenta originalidade ao que já existe. Um pequena falha que é a multidão de personagens que não é plenamente desenvolvida marca um pouco o livro mas não chega a atrapalhar tanto. Uma seqüência para o livro também já existe e está na minha fila.

O segundo livro que li no mês foi The Last Theorem, uma colaboração entre Arthur C. Clarke e Frederik Pohl, dois dos maiores mestres da era de ouro da ficção científica. O livro foi a última coisa que Clarke fez. Essencialmente, ele forneceu notas sobre uma estória que posteriormente foi escrita por Pohl.

O livro conta a estória de um jovem matemático nascido no Sri Lanka que consegue encontrar uma prova rápida, de apenas cinco páginas, para o último teorema de Fermat. A estória segue toda a sua vida, da infância até sua velhice dentro do backdrop da ignorância da Terra em relação a uma frota alienígena que está a caminho para erradicar os humanos sob ordens de uma civilização galáctica maior. As duas estórias se encontram quase que por acaso mais para o meio do livro.

A estória é interessante, e Pohl continua escrevendo bem, mas achei que as premissas eram um pouco fracas demais. A resolução da ameaça é um deus ex machina que me deixou bastante frustrado. Foi bem divertido acompanhar a vida de Ranjit Subramanian e as noções irônicas de Pohl sobre as civilizações galácticas ao redor da Terra mas o livro nunca passa muito disso. Destaque para discussões sobre razão, vida e inteligência mas nem essas compensam a fraqueza do livro.

Nos filmes, o único que assisti foi The Ugly Truth, comédia romântica com Gerard Butler (de 300) e Katherine Heigl (the Grey’s Anatomy). Divertido nas situações mas absolutamente imbecil na realização do todo. O final é óbvio desde o primeiro segundo do filme e não compensa as horas gastas. O que não fazemos pelas esposas. :)

No próximo mês, talvez algo mais consistente.

The Anubis Gates

October 23rd, 2009 § 1 comment § permalink

Terminei de ler esses dias The Anubis Gates, uma fantasia de viagem ao tempo, deus egípcios e mágica por Tim Powers. O livro é de 1983 e estava na minha lista de leituras há um bom tempo por figurar em virtualmente todas as listas de clássicos de fantasia modernos–inclusive, tendo sido ganhador do Philip K. Dick Award do ano em que foi publicado.

Com esse pedigree, eu esperava muito do livro e não fiquei desapontado. Ao contrário, o livro fornece uma leitura deliciosa nos moldes das melhores fantasias urbanas dos últimos anos. Tim Powers consegue um balanço quase perfeito entre o que geralmente se esperaria de romances capa e escada–com pitadas generosas de magia jogadas no meio–e a mecânica de viagens do tempo que geralmente está mais presente em obras de ficção científica.

A estória começa quando uma cabal de mágicos tenta trazer os deuses egípcios de volta ao poder para derrotar o pode inglês no Egito e retornar à época glorioso em que a magica ainda funcionava bem sem destruir seus usuários. Uma tentativa de trazer Anubis à vida falha e resulta em uma série de fendas no tempo que permitem, anos depois, que um milionário os use para fazer viagens no tempo. Esse milionário organiza uma viagem à Londres de 1810 e contrata um professor–Brendan Doyle, o protagonista do livro–para guiar o grupo e fornecer comentários sobre a vida de Samuel Taylor Coleridge, o poeta romântico cuja palestra é o objetivo da viagem.

Obviamente, os interesses dos dois grupos se cruzam quando a viagem do grupo chama a atenção dos mágicos e o resultado é que Doyle fica preso na Londres do século 19 sem condições de retornar ao seu tempo. Forçado a mendigar e perseguido por grupos diversos–nem tudo é o que parece e há mais interesses em jogo do que somente uma mera viagem do tempo, Doyle tem que sobreviver a vários ataques e entender o mistério que cerca tudo. Isso inclui as aparições de um misterioso lobisomem capaz de trocar corpos e que possui também um interesse velado no que está acontecendo.

O resultado desse enredo aparentemente desconexo é uma série de aventuras e desventuras em que Doyle se vê jogado entre as figuras da época e precisa negociar seu caminho em meio a um mundo que ele conhece dos livros mas que é bem diferente em sua realidade. O livro consegue combinar muito bem as situações da época e os eventos bizarros que múltiplas viagens do tempo propiciam para resolver os nós que a estória levanta–ao contrário da maioria dos livros de viagem no tempo, por exemplo, Doyle quase nunca se dá bem com seu conhecimento e precisa usar sua inteligência muito mais que sua erudição e aprender com o que acontece.

Um dos pequenos problemas do livro é que, por conta do amplo número de personagens e situações, algumas coisas são resolvidas de repente, sem muito aviso e deixando um pouco de dúvida na cabeça do leitor. Mesmo assim, isso não acontece o suficiente para deixar uma marca da estória.

No geral, o livro atendeu plenamente suas expectativas e recomendo fortemente para quem gosta desse tipo de fantasia.

Fred Brooks sobre colaboração

October 22nd, 2009 § 2 comments § permalink

Ontem, cortesia da Caelum, tive oportunidade de ver Fred Brooks falar sobre desenvolvimento, arquitetura de sistemas e colaboração entre times.

O evento, lançamento da tradução brasileira de seu trabalho mais famoso–O Mítico Homem-Mês–contou com várias outras palestras e uma sessão de Q&A com Brooks mas, infelizmente, só pude participar dos quarenta ou cinqüenta minutos que ele passou discorrendo sobre o assunto. Mesmo assim, foi muito bom ver o velho professor–ele está com quase 80 anos atualmente–falando sobre assuntos que eram relevantes em 1975, quando o livro foi impresso pela primeira vez, e que ainda são relevantes hoje, quase 40 anos depois.

O Mítico Homem-Mês é um livro que qualquer pessoa que desenvolve ou gerencia algum processo de desenvolvimento de software deveria ler. Apesar da ironia do próprio Brooks ao dizer que seu livro é como uma Bíblia de Engenharia de Software, porque “todo mundo lê mas ninguém faz nada sobre o assunto”, o livro permanece uma influência seminal entre todos os trabalhos que foram feitos no campo nos últimos 30 anos, especialmente entre os responsáveis por grande parte das metodologias de desenvolvimento que são usadas atualmente.

Um dos pontos interessantes da palestra–e algo que que só tem um conhecimento superficial do livro não notaria–é que Brooks possui uma visão particular de desenvolvimento que vai muito contra o que se fala hoje em termos de desenvolvimento “ágil”. Um dos exemplos engraçados disso foi quando um dos participantes, ao final da palestra, perguntou o que ele pensava sobre o papel da arquitetura e design colaborativos dentro de um time ágil–um time de Scrum, digamos. Brooks declarou enfaticamente, em resposta à insistência do participante, que, sem um arquiteto-chefe, nada que fosse feito teria a consistência–a integridade conceitual, por assim dizer–necessária para o bom desenvolvimento do software.

Falando sobre colaboração, Brooks apontou o papel do trabalho solo de grandes artistas e realizadores como essencial para a criação de obras duradouras. Mesmo no contexto de colaboração, ele diz, as maiores equipes raramente passam de uma colaboração complementar entre duas pessoas. Mais do que isso resulta em difusão da integridade conceitual da obra em face a desafios maiores.

Inclusive, comparando com o modelo de colaboração de projetos de código livre ou aberto, Brooks aponta o fato de que, nesses projetos, os construtores e clientes geralmente são os mesmos e isso torna o desenvolvimento algo completamente diferente. Há integridade conceitual, sim, não do sistema como um todo mas de suas partes. Isso pode realmente ser comprovado de maneira fácil do desenvolvimento dos grandes projetos como o Linux onde uma pessoa retém a visão intelectual mais profunda do desenvolvimento e onde as partes são íntegras entre si por via de convenções–style sheets de desenvolvimento, como Brooks denomina o processo.

Em suma, a visão de Brooks é refrescante em tempos em que metodologias ágeis estão em voga em uma compreensão real do que elas representam. É bom ver alguém que pensou exaustivamente sobre o assunto–e que está pronto para lançar mais um livro sobre suas reflexões ao longo dos anos–mostrar o que realmente importa no desenvolvimento. Raros são os times e empresas que possuem maturidade para ver as coisas dessa forma.

Minha única tristeza na participação do evento foi não ter podido pedir a Brooks para autografar meu exemplar–que repousa em Belo Horizonte à espera de uma mudança futura para São Paulo.

District 9

October 20th, 2009 § 2 comments § permalink

Ontem consegui finalmente tirar um tempo para ir ao cinema e assistir District 9.

O filme era um dos mais esperados lançamentos do ano–tanto em ficção científica como no meio mainstream–e, pessoalmente, evitei ler praticamente qualquer coisa sobre o mesmo para ter uma visão mais pura sobre a obra, especialmente considerando os paralelos óbvios com o apartheid.

Sou um pouco suspeito para dizer, considerando minha relação com ficção científica, mas acho que o filme chega muito próximo de um clássico nos moldes de Blade Runner e Soylent Green, principalmente pela qualidade visceral da sua narrativa–com perdão do trocadilho. De uma forma bem prática, o filme consegue evitar os problemas que Sunshine apresentou, misturando uma narrativa essencialmente ficcional com um objetivo secundário de pregar uma mensagem específica.

Isso não significa, é claro, que District 9 não tenha seu embasamento em um problema ou mensagem específica. Como mencionei anterior, a questão do apartheid está lá e é bem óbvia. Mesmo assim, acho que acaba sendo algo no fundo, motivado pelas experiência do direitor e ofuscado, com boa razão, pela visão mais primária de comportamento humano face ao desconhecido (ao mesmo tempo em que apresenta a reação face ao incômodo).

District 9 não é, também, isento de falhas. Há uma dualidade inerente entre a necessidade de ser íntimo, evidenciada pelo formato de documentário no começo, e a necessidade de impressionar, mostrada a partir do meio do filme por cenas de perseguição e o milieu comum de filmes de ação. Mesmo assim, acho que o filme apresenta um bom balanço e consegue divertir ao mesmo tempo que motiva questões mais profundas.

O tempo vai dizer quão bem District 9 sobrevive como indagação da natureza humana. A despeito das vísceras e explosões, o filme é bem uma reflexão do nosso tempo e isso pode persistir. Espero que não tenhamos seqüências–não há nada para adicionar.

O princípio da caridade

October 20th, 2009 § 0 comments § permalink

Da Wikipedia, o Princípio da Caridade:

In philosophy and rhetoric, the principle of charity requires interpreting a speaker’s statements to be rational and, in the case of any argument, considering its best, strongest possible interpretation. In its narrowest sense, the goal of this methodological principle is to avoid attributing irrationality, logical fallacies or falsehoods to the others’ statements, when a coherent, rational interpretation of the statements is available.

Esse é um princípio pelo qual tenho tentado (ênfase em tentado) viver desde que me entendo por uma pessoa capaz de discurso em qualquer nível. Esse mesmo princípio é o que me motiva geralmente a fazer o papel de Advogado do Diabo mesmo quando a outra pessoa está defendo algo em que eu acredito usando argumentos falaciosos, seja por deliberação ou não–tentando, de qualquer forma, chegar em um ponto comum onde não há necessariamente concordância mas pelo menos entendimento.

Infelizmente–e eu sei que faço isso provavelmente mais vezes do que eu gostaria de admitir–nós sempre trazemos nossas asserções para a mesa e quase nunca estamos dispostos a aceitar isso.

Enfim, algo para tentar colocar mais em prática no dia-a-dia.

Treetop: Implementando Brainfuck

October 1st, 2009 § 1 comment § permalink

Introdução

Continuando a série sobre Treetop, é hora de brincar um pouco mais com todos os conceitos envolvidos e implementar uma linguagem Turing-complete.

Para não complicar as coisas, vamos usar uma linguagem bem simples, que demonstre os conceitos e não fique presa em detalhes de sintaxes. Brainfuck, apesar do nome, é uma boa escolha. Extremamente minimalista–possui somente oito comandos usando oito símbolos–permite uma implementação rápida e ao mesmo tempo permite brincar com os vários conceitos que estamos vendo. Tudo bem, eu admito que ela é um Turing tarpit mas serve com uma base interessante para esse artigo.

Brainfuck consiste em oito comandos, como mencionado acima, que manipulam um espaço de células de memória. Um ponteiro de instrução é definido para a primeira instrução e os comandos manipulam esse ponteiro para executar as várias instruções. Como cada comando é um simples símbolo, a ambigüidade da gramática é nula. Qualquer outro símbolo dentro do programa é ignorado e pode ser usado como uma comentário.

Os comandos da linguagem são:

  • >, incrementa o ponteiro de dados;
  • <, decrementa o ponteiro de dados;
  • +, incrementa o byte apontado pelo ponteiro de dados;
  • , decrementa o byte apontado pelo ponteiro de dados;
  • ., escreve o valor do byte apontado pelo ponteiro de dados;
  • ., lê um byte e armazena no endereço apontado pelo ponteiro de dados;
  • [, se o byte no endereço atual do ponteiro de dados é zero, pula para frente até o próximo comando ];
  • ], se o byte no endereço atual do ponteiro de dados não é zero, pula para trás até o próximo comando [.

Reconhecendo a linguagem

Vamos olhar então como ficaria a gramática da linguagem:

grammar Brainfuck

  rule program
    instruction*
  end
  
  rule instruction
    loop / 
    simple /
    comment
  end
  
  rule loop
    '[' instructions:instruction* ']'
  end
  
  rule simple
    '>' /
    '<' /
    '+' /
    '-' /
    ',' / 
    '.'
  end
  
  rule comment
    [^\[\]><+-,.]
  end
  
end

A descrição da gramática é simples:

  • Um programa é uma série de zero ou mais instruções;
  • Uma instruções pode ser composta--ou seja, um loop, denotado pelos comandos [ e ]--, uma instrução simples, ou um comentário (que é qualquer caractere que não seja uma instrução);
  • Dentro de um loop, podem existir zero ou mais instruções.

Podemos ver que a gramática funciona executando comandos como abaixo:

>> require "rubygems"
=> false
>> require "treetop"
=> true
>> require "brainfuck"
=> true
>> p = BrainfuckParser.new
=> #
>> p.parse("><+-[]")
=> SyntaxNode offset=0, "><+-[]":
  SyntaxNode offset=0, ">"
  SyntaxNode offset=1, "<"
  SyntaxNode offset=2, "+"
  SyntaxNode offset=3, "-"
  SyntaxNode+Loop0 offset=4, "[]":
    SyntaxNode offset=4, "["
    SyntaxNode offset=5, ""
    SyntaxNode offset=5, "]"

Note que os comentários também são marcados por nós sintáticos, já que estamos processando os mesmos. Obviamente, quando da interpretação do programa, precisaremos eliminá-los.

Criando uma árvore sintática

A partir da gramática, o nosso objetivo é criar uma árvore manipulável, eliminando comentários, agrupando instruções de loop e preparando um ambiente para a execução em si.

Podemos usar a mesma técnica que usamos nos artigos anteriores.

Primeiro, definimos uma representação sintática. O arquivo brainfuck_ast.rb ficaria assim inicialmente:

class Program
  
  attr_reader :instructions
  
  def initialize(instructions)
    @instructions = instructions
  end
  
end

class Instruction
  
  attr_reader :command
  
  def initialize(command)
    @command = command
  end
  
end

class Loop
  
  attr_reader :instructions
  
  def initialize(instructions)
    @instructions = instructions
  end
  
end

Efetivamente, precisamos somente de três tipos de nós: o programa em si, para servir como um container inicial do ambiente, instruções simples e loops.

A seguir precisamos reconhecer isso em nossa gramática. Mostrando a gramática adaptada em um único passo, teríamos algo assim:

grammar Brainfuck

  rule program
    instruction* <ProgramPredicate>
  end
  
  rule instruction
    loop / 
    simple /
    comment
  end
  
  rule loop
    '[' instructions:instruction* ']' <LoopPredicate>
  end
  
  rule simple
    '>' <InstructionPredicate> /
    '<' <InstructionPredicate> /
    '+' <InstructionPredicate> /
    '-' <InstructionPredicate> /
    ',' <InstructionPredicate> / 
    '.' <InstructionPredicate>
  end
  
  rule comment
    [^\[\]><+-,.] <CommentPredicate>
  end
  
end

E finalmente, o arquivo contendo as extensões, brainfuck_ext.rb, ficaria assim:

module Common
  
  def build_elements(elements)
    elements.
      select { |element| element.respond_to?(:build) }.
      collect { |element| element.build }
  end
  
end

module ProgramPredicate
  
  include Common
  
  def build
    Program.new(build_elements(elements))
  end
  
end

module LoopPredicate
  
  include Common
  
  def build
    Loop.new(build_elements(instructions.elements))
  end
  
end

module InstructionPredicate
  
  def build
    Instruction.new(text_value)
  end
  
end

module CommentPredicate
  
end

No código acima, primeiro definimos um módulo comum que pode ser incluído em outros e possui um método que seleciona instruções que podem ser utilizadas. Depois definimos módulos para o que precisamos processar. E finalmente ignoramos os comentários.

Interpretando

Agora precisamos converter o código acima em um interpretador. A implementação é trivial e vamos acompanhá-la passo a passo, escrevendo o arquivo brainfuck_int.rb, que conterá o interpretador:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
end

Esse método inicializa o interpretador recebendo uma série de comandos na forma de uma string e construindo uma árvore sintática final da mesma.

O próximo passo é começar a execução em si. Um interpretador Brainfuck deve inicialmente alocar um espaço de memória para execução e inicializar o ponteiro de instruções. Em seguida, deve seguir de instrução em instrução, rodando o comando em questão. O método que faz isso é o seguinte:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
    
end

Esse método inicializa o espaço de instruções e o ponteiro e em seguida chama a execução de um nó da árvore. Embora esse nó sempre seja um nó do tipo Program, o método está preparado para executar qualquer instrução.

Na seqüência, está a implementação do método execute_ast_node:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
  
  protected
  
  def execute_ast_node(ast)
    send("execute_" + ast.class.name.downcase, ast)
  end
    
end

O método é simplesmente um dispatch para o método apropriado para lidar com o tipo de nó em si.

O nó program é o que possui a implementação mais simples. Com o mesmo representa somente uma lista de instruções, a execução consiste em nada mais do que rodar essa lista, pedindo a execução de cada nó filho. A implementação fica, então, como mostrado abaixo:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
  
  protected
  
  def execute_ast_node(ast)
    send("execute_" + ast.class.name.downcase, ast)
  end
  
  def execute_program(ast)
    ast.instructions.each { |instruction| execute_ast_node(instruction) }
  end
    
end

A implementação dos nós instruction é igualmente simples:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
  
  protected
  
  def execute_ast_node(ast)
    send("execute_" + ast.class.name.downcase, ast)
  end
  
  def execute_program(ast)
    ast.instructions.each { |instruction| execute_ast_node(instruction) }
  end
  
  def execute_instruction(ast)
    case ast.command
    when '>'
      @pointer += 1
    when '<'
      @pointer -= 1      
    when '+'
      @addresses[@pointer] += 1
    when '-'
      @addresses[@pointer] -= 1      
    when ','
      @addresses[@pointer] = STDIN.getc
    when '.'
      STDOUT.print @addresses[@pointer].chr
    end
  end
    
end

Para cada instrução, executamos a instrução correspondente. As duas primeiras simplesmente movem o ponteiro de instrução--não estamos aqui, executando qualquer tipo de testes para evitar instruções inválidas. A duas instruções seguintes não movem o ponteiro de dados e simplesmente manipulam o dado que está no endereço especificado. Finalmente, as demais instruções lêem um byte da entrada padrão para o endereço atual ou emitem o byte atual para a saída padrão.

Finalmente, temos a implementação de um loop que também é trivial:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
  
  protected
  
  def execute_ast_node(ast)
    send("execute_" + ast.class.name.downcase, ast)
  end
  
  def execute_program(ast)
    ast.instructions.each { |instruction| execute_ast_node(instruction) }
  end
  
  def execute_instruction(ast)
    case ast.command
    when '>'
      @pointer += 1
    when '<'
      @pointer -= 1      
    when '+'
      @addresses[@pointer] += 1
    when '-'
      @addresses[@pointer] -= 1      
    when ','
      @addresses[@pointer] = STDIN.getc
    when '.'
      STDOUT.print @addresses[@pointer].chr
    end
  end
  
  def execute_loop(ast)
    while @addresses[@pointer] != 0
      ast.instructions.each { |instruction| execute_ast_node(instruction) }
    end
  end
  
    
end

A implementação simplesmente verifica se o endereço atual contém um valor nulo e caso contrário continua a execução das instruções aninhadas. Isso é o mesmo que pular para frente a para trás nos operadores [ e ].

Para completar o quadro e facilitar a execução, podemos incluir métodos auxiliares:

class BrainfuckInterpreter
  
  def initialize(commands)
    @ast = BrainfuckParser.new.parse(commands).build
  end
    
  def execute
    @addresses = [0] * 3000
    @pointer = 0
    execute_ast_node(@ast)
  end
  
  def self.run(commands)
    BrainfuckInterpreter.new(commands).execute
  end

  def self.run_file(file)
    BrainfuckInterpreter.new(File.readlines(file).join).execute
  end
  
  
  protected
  
  def execute_ast_node(ast)
    send("execute_" + ast.class.name.downcase, ast)
  end
  
  def execute_program(ast)
    ast.instructions.each { |instruction| execute_ast_node(instruction) }
  end
  
  def execute_instruction(ast)
    case ast.command
    when '>'
      @pointer += 1
    when '<'
      @pointer -= 1      
    when '+'
      @addresses[@pointer] += 1
    when '-'
      @addresses[@pointer] -= 1      
    when ','
      @addresses[@pointer] = STDIN.getc
    when '.'
      STDOUT.print @addresses[@pointer].chr
    end
  end
  
  def execute_loop(ast)
    while @addresses[@pointer] != 0
      ast.instructions.each { |instruction| execute_ast_node(instruction) }
    end
  end
    
end

E com isso, podemos testar um programa. Abra um novo arquivo chamado brainfuck_test.rb e introduza o seguinte código:

require "rubygems"
require "treetop"
require "brainfuck"
require "brainfuckext"
require "brainfuckast"
require "brainfuck_int"

code = <<-EOF +++ +++ +++ + initialize counter (cell #0) to 10 [ use loop to set the next four cells to 70/100/30/10 > +++ +++ + add 7 to cell #1 > +++ +++ +++ + add 10 to cell #2 > +++ add 3 to cell #3 > + add 1 to cell #4 <<< < - decrement counter (cell #0) ]
>++ . print 'H' >+. print 'e' +++ +++ +. print 'l' . print 'l' +++ . print 'o' >++ . print ' ' <<+ +++ +++ +++ +++ ++. print 'W' >. print 'o' +++ . print 'r' --- --- . print 'l' --- --- --. print 'd' >+. print '!' >. print '\n' EOF

BrainfuckInterpreter.run(code)

O código está "comentado" (lembre-se que comandos não reconhecidos são ignorados). Se você rodar o programa agora, obterá o resultado desejado:

~$ ruby brainfuck_test.rb 
Hello World!

Pronto! Você agora possui um interpretador funcional--embora seriamente lento e incrivelmente complexo de se programar--de uma linguagem computacionalmente completa, capaz de realizar qualquer tarefa que você deseje executar.

Um outro teste interessante é rodar o código que imprime a canção 99 bottles of beer on the wall, cuja versão em Brainfuck é um pesadelo. Você verá que, embora lento, o código consegue imprimir as linhas com relativa facilidade.

Comparando a velocidade de uma versão Ruby e uma versão Brainfuck da canção temos os resultados (Ruby 1.9; iMac 2.8Ghz recente rodando Snow Leopard):

~$ time 99.bf
  real  0m0.679s
  user  0m0.586s
  sys   0m0.045s
  
~$ time 99.rb
  real  0m0.016s
  user  0m0.006s
  sys   0m0.006s

Digamos que imprimir caracteres um a um não é uma forma eficiente de se trabalhar. Obviamente, double dispatching também não é uma técnica boa em todos os casos e vai adicionar um certo overhead em troca de flexibilidade. Mas, mesmo removendo o tempo de carga do Ruby, não haveria tantas melhoras no programa.

Conclusão

Como esse artigo vimos como fazer a cadeia completa de implementação, incluindo a implementação de um interpretador funcional. Mesmo que a implementação seja bem simples, as lições são as mesmas para qualquer outra linguagem e o limite está mais na sintaxe e semântica do que nas técnicas em si. Um exercício para o leitor é implementar um otimizador para Brainfuck que agrupe instruções iguais e efetue as operações em uma única passagem. Aqui, é claro, cabe uma aviso: implementações de linguagens generalizadas de programação obviamente precisam de muito mais do que isso para funcionar bem. O Treetop oferece uma conveniência quando velocidade máxima de execução não é o desejado e sim velocidade de implementação.

Com isso encerramos essa série sobre Treetop. Eventualmente, é possível que eu escreva um artigo sobre a implementação de uma linguagem menos minimalista, mas no momento não há tempo para isso. Espero que o tempo gasto lendo esses artigos tenha sido proveitoso.

Nota: Para ver a série completa, siga a categoria Treetop deste blog.

Where am I?

You are currently viewing the archives for October, 2009 at Superfície Reflexiva.