criando sua própria linguagem de programação
DESCRIPTION
Uma rápida aventura pelo processo de criar uma pequena linguagem de programação para demonstrar Treetop e interpretação de código.TRANSCRIPT
Criando sua própria linguagem de programaçãoDev In SampaSão Paulo, 28 de novembro de 2009
Por quê?
Cada nova linguagem é um campo aberto para livre experimentação
Compilação é uma arena em que todas as suas habilidades são necessárias
A verdade verdadeira
“Por que escrever um programa se você pode escrever um programa para escrever um programa?”
— Autor desconhecido
A verdade verdadeira é que é divertido ;)
Na prática
Você pode usar um parser mais sofisticado para suas DSLs
Você pode resolver problemas de portar código de um domínio para outro com um tradutor
Você pode usar um interpretador para construir geradores mais sofisticados
Na pior das hipóteses...
“Se uma linguagem não é capaz de afetar o modo como você pensa sobre programação, não vale a pena aprendê-la”
— Alan Perlis
Um pouco de conceitos
Linguagem
“Uma notação para escrever programas, que são especificações para a computação de uma algoritmo”
— Wikipedia
Elementos
Sintaxe: o que é escrito, descrita em uma gramática formal
Semântica: o que isso significa, especificado em termos de formalizações de compilação e execução
Gramáticas formais
Value ← [0-9]+ / '(' Expr ')'Product ← Value (('*' / '/') Value)*Sum ← Product (('+' / '-') Product)*Expr ← Sum
Do texto à execuçãoSAÍDA
ANÁLISE SINTÁTICA
ANÁLISE LÉXICA
TOKENS
AST
COMPILADORINTERPRETADOR
TRADUTOR
CÓDIGO FONTE
PARSING
Introduzindo “Mirror”
Inspiração
Sintaxe baseada em Smalltalk e IO
Slot-based como Self
Forte e dinamicamente tipada
Interpretada, via bytecodes
Mirror
World mirrorInto: "Fib".
Fib set: "of:" to: [ n | n <= 2 ifTrue: [ 1. ] ifFalse: [ (of: n - 1) + (of: n - 2). ].].
(Fib of: 10) transcribeAndBreak.
Análise
Via Treetop, um packrat parser em Ruby
PEGs
Análise versus geração
Sem ambigüidades
Gera uma árvore sintática que é compilada para uma representação em bytecodes
AST #1
(Fib of: 2 + 3)transcribeAndBreak.
AST #2
(Fib of: 2 + 3)transcribeAndBreak.
Bytecode
push 3push 2send +load Fibsend of:send transcribeAndBreakpop
A gramática
Blocos básicosgrammar Mirror
rule statements (spaces? statement spaces? "." spaces?)* <Statements> end rule statement message_expression end rule message_expression keyword_expression / binary_expression / unary_expression end
# ...
end
Keywords
grammar Mirror
rule keyword_expression variable:binary_expression? keywords:(spaces? keyword spaces expression:binary_expression)+ <KeywordExpression> end
# Account deposit: 100 from: user. # ...
end
Expressões binárias
grammar Mirror
rule binary_expression variable:unary_expression spaces? selector:binary_selector spaces? expression:binary_expression <BinaryExpression> / unary_expression end
# 2 + 3 * (account balance). # ...
end
Expressões unárias
grammar Mirror
rule unary_expression variable:primary selectors:(spaces selector:identifier !colon)+ <UnaryExpression> / primary end
# (Account current balance) transcribeAndBreak. # ...
end
Juntando as peças
irb> MirrorParser.new.parse('2 + 3.')
SyntaxNode+Statements offset=0, "2 + 3." (build): SyntaxNode+Statements0 offset=0, "2 + 3." (statement): SyntaxNode+BinaryExpression0+BinaryExpression offset=0, "2 + 3": SyntaxNode+IntegerLiteral offset=0, "2" (build): SyntaxNode offset=0, "2" SyntaxNode+Spaces2 offset=1, " ": SyntaxNode offset=2, "+": SyntaxNode offset=2, "+" SyntaxNode+IntegerLiteral offset=4, "3" (build): SyntaxNode offset=4, "3"
Convertendo a AST
Blocos básicos# rule statements# (spaces? statement spaces? "." spaces?)* <Statements># end
module Statements def build elements.collect do |element| Ast::Statement.new(element.statement.build) end endend
class Statement def initialize(expression) @expression = expression endend
Expressões binárias# rule binary_expression# variable:unary_expression spaces?# selector:binary_selector spaces? # expression:binary_expression <BinaryExpression> /# unary_expression
module BinaryExpression def build Ast::Message.new(variable.build, selector.text_value, expression.build) endend
class Message def initialize(target, selector, *arguments) @target = target @selector = selector @arguments = arguments endend
Juntando as peças
irb> MirrorParser.new.parse('2 + 3.').build
[ #<Ast::Statement @expression = #<Ast::Message @selector = "+", @target = #<Ast::Literal @value = "2", @type = :integer>, @arguments = [#<Ast::Literal @value = "3", @type = :integer>]>>]
Geração de código
Double dispatchclass CodeGenerator def initialize(ast) @ast = ast end
def generate @ast.collect { |statement| generate_any(statement) }.flatten end
def generate_any(ast) send("generate_#{ast.class.name.demodulize.underscore}", ast) end
# ...
end
Blocos básicos
class CodeGenerator def generate_statement(ast) ([generate_any(ast.expression)] + [Bytecode::Pop.new]).flatten end
def generate_variable(ast) Bytecode::Load.new(ast.name) end
# ...
end
Mensagens
class CodeGenerator def generate_message(ast) instructions = [] ast.arguments.reverse.each do |argument| instructions += [generate_any(argument)].flatten end instructions += [generate_any(ast.target)].flatten instructions << Bytecode::Message.new(ast.selector) instructions end
# ...
end
Bytecodes
class Pop def inspect "pop" end end
class Message def initialize(selector) @selector = selector @selector_name = get_selector_name(selector) @selector_method = get_selector_method(selector) @arity = get_selector_arity(selector) end # ... end
Juntando as peças
irb> ast = MirrorParser.new.parse('2 + 3.').build
irb> CodeGenerator.new(ast).generate
[ push 3, push 2, send +, pop]
Modelo de execução
Containers & Slots
0
BLOCK CONTEXT
BLOCK CONTEXT
ACCOUNT
DEPOSIT:
BALANCE
WITHDRAW:
USER USER
Universe & WorldUNIVERSE
ERROR
WORLD
WORLD
SET: TO:
MIRRORINTO:
Detalhes
O envio de mensagens acontece em um contexto que é gerado para cada mensagem
Blocos geram contextos empilhados
O interpretador percorre os contextos até encontrar o objeto apropriado para enviar a mensagem
Máquina virtual
Máquina Virtualclass VM def initialize(instructions) @instructions = instructions end
def run reset_instruction_pointer while has_instructions? execute(next_instruction) end end # ...
end
Máquina Virtualclass VM
def execute(instruction) case instruction when Bytecode::Implicit stack_push_and_wrap(current_context) when Bytecode::Pop stack.pop when Bytecode::Push stack_push_and_wrap(instruction.value) when Bytecode::Load stack_push_and_wrap(walk_contexts(instruction.name)) # ... end end
end
Juntando as peças
irb> Interpreter.run(true, "World offload: 2 + 2.")
[4]
irb> Interpreter.run(true, "World offload: [ 2 + 2. ] value.")
[4]
Próximos passos
Próximos passos
Arrays
Inlining de mensagens comuns
Primitivas: + - * / at: at:put:
ifTrue:ifFalse et al
to:do et al
Melhor uso de blocos
LLVM
Uma estratégia de compilação
Um conjunto de instruções virtualizado
Uma infra-estrutura de compilação
Um conjunto de ferramentas
LLVM
Efetivamente uma DSL para geração de código intermediário otimizado e portável
Estático ou JIT
Usado por MacRuby, Rubinius, Unladden Swallow e outros
LLVM: Uso
Transformar slots de código em funções
Transformar closures em funções quando não fizer sentido que os mesmos sejam inline
Compilar o próprio interpretador para ser parcialmente jitted
LLVM: Uso
module = LLVM::Module.new("mirror")type = Type::function(MACHINE_WORD, [])function = module.get_or_insert_function("main", type)
entry_block = function.create_blockexit_block_true = function.create_blockexit_block_false = function.create_block
builder = entry_block.buildercmp = builder.icmp_sgt(-1.llvm, 1.llvm)builder.cond_br(cmp, exit_block_true, exit_block_false)
LLVM: Uso
builder = exit_block_true.builderbuilder.return(1.llvm)
builder = exit_block_false.builderbuilder.return(0.llvm)
ExecutionEngine.get(module)ExecutionEngine.run_autoconvert(function)
Questões?
@rferrazhttp://logbr.reflectivesurface.com