Você está na página 1de 49

Criando sua própria

linguagem de programação
Dev In Sampa
Sã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ção
CÓDIGO FONTE SAÍDA

PARSING

ANÁLISE LÉXICA COMPILADOR


INTERPRETADOR
TRADUTOR
TOKENS

ANÁLISE SINTÁTICA AST


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 3
push 2
send +
load Fib
send of:
send transcribeAndBreak
pop
A gramática
Blocos básicos
grammar 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
end
end

class Statement
def initialize(expression)
@expression = expression
end
end
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)
end
end

class Message
def initialize(target, selector, *arguments)
@target = target
@selector = selector
@arguments = arguments
end
end
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 dispatch
class 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
ACCOUNT

BALANCE 0

DEPOSIT: BLOCK CONTEXT

WITHDRAW: BLOCK CONTEXT

USER USER
Universe & World
UNIVERSE WORLD

WORLD MIRRORINTO:

ERROR SET: TO:


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 Virtual
class VM

def initialize(instructions)
@instructions = instructions
end

def run
reset_instruction_pointer
while has_instructions?
execute(next_instruction)
end
end

# ...

end
Máquina Virtual
class 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_block
exit_block_true = function.create_block
exit_block_false = function.create_block

builder = entry_block.builder
cmp = builder.icmp_sgt(-1.llvm, 1.llvm)
builder.cond_br(cmp, exit_block_true, exit_block_false)
LLVM: Uso

builder = exit_block_true.builder
builder.return(1.llvm)

builder = exit_block_false.builder
builder.return(0.llvm)

ExecutionEngine.get(module)
ExecutionEngine.run_autoconvert(function)
Questões?

@rferraz
http://logbr.reflectivesurface.com