Esta documentação descreve a sintaxe, semântica e limitações atuais da linguagem "Obsidian" e como usar o compilador disponível neste repositório.
Obsidian é uma linguagem imperativa e tipada (estaticamente em notação), desenvolvida como um projeto educacional para demonstrar fases clássicas de compilador: lexer, parser, geração de AST e geração de LLVM IR. A implementação atual gera código LLVM usando llvmlite e pode executar programas via JIT.
Observações gerais:
- Blocos usam chaves
{ ... }. - Instruções terminam em
;quando necessário. - Comentários ainda não formalizados (considere usar apenas código limpo nos exemplos).
Forma:
let <ident>$<tipo> -> <expressão>;
Exemplos:
let a$int -> 5;
let x$float -> 3.14;
Notas:
- O tipo é escrito após
$(por exemploa$int). - A declaração aloca um espaço para a variável e armazena o valor inicial.
Forma:
<ident> -> <expressão>;
Exemplo:
a -> 10;
Assinatura:
fun <nome>(<params>): <tipo_retorno> { ... }
Exemplo básico:
fun main(): int {
let a$int -> 4;
a -> a * 2;
return a;
}
Observações:
- O parser consome a lista de parâmetros, mas ainda não há suporte completo para tipos e nomes de parâmetros no gerador de código (implementação futura).
- Literais: inteiros (ex.:
10), floats (ex.:3.14). - Identificadores: nomes alfanuméricos.
- Operadores aritméticos:
+,-,*,/,%. - Operador de potência:
**(sintaxe suportada pelo parser, sem implementação completa no backend). - Comparações:
<,<=,>,>=,==,!=. - Operadores booleanos ainda limitados — comparação produz
bool(representado internamente comoi1em LLVM).
Precedência de operadores é tratada no parser (ver Parser.py).
Forma:
if <condicao> { <consequencia> } else { <alternativa> }
Exemplo:
if a == 5 {
a -> 10;
} else {
a -> 20;
}
Semântica atual:
ifavalia a condição (uma expressão booleana) e executa o bloco consequente quando verdadeira.elseé opcional; quando presente, produz um ramo alternativo.- O compilador traduz
if/elsepara blocos condicionais LLVM; mudanças recentes corrigiram bugs na detecção/consumo do tokenelsee na geração do IR correspondente.
Usado dentro de funções para retornar um valor:
return <expressao>;
int— inteiro de 32 bits (LLVMi32).float— ponto flutuante (LLVMfloat).bool— valor booleano representado comoi1em LLVM.
Observações: tipagem estática explícita (o tipo aparece na declaração let e em assinaturas de função) mas o sistema atual ainda não faz checagem completa de tipos em todos os caminhos.
Lexer.py: tokenização.Parser.py: construção da AST (utiliza funções de prefix/infix parsing e mapa de precedência).AST.py: modelos de nós da AST.Compiler.py: geração de LLVM IR usandollvmlitee umEnvironmentpara variáveis.Environment.py: tabela de símbolos, mapeando nomes para ponteiros LLVM e tipos.main.py: orquestra leitura do arquivo de entrada, parsing, compilação e execução JIT.
- Operador de potência
**não está implementado no backend. - Suporte a parâmetros de função está parcial (parâmetros são consumidos no parser, mas tipos/uso em código ainda não completos).
- Inferência de tipos, checagem de tipos mais completa e tratamento de erros semânticos ainda a implementar.
- Sistema de módulos/import ainda não existe.
Pré-requisitos:
- Python 3.8+
llvmliteinstalado (pip install llvmlite)- LLVM adequado ao seu ambiente (para JIT nativo)
Executar (por padrão main.py lê tests/test_if.obs):
python main.pyFlags de depuração em main.py:
LEXER_DEBUG = True— imprime tokens do lexer.PARSER_DEBUG = True— salva AST emdebug/ast.json.COMPILER_DEBUG = True— salva IR emdebug/ir.obs.
Arquivo tests/test_if.obs:
fun main(): int {
let a$int -> 5;
if a == 5 {
a -> 10;
} else {
a -> 20;
}
return a;
}
Esperado: o if altera a para 10 quando a == 5, caso contrário para 20. O runtime JIT retorna o inteiro final da função main.
- Abra issues para bugs/evoluções.
- Pull requests são bem-vindos; foque em testes de unidade e exemplos em
tests/.
- Corrigido parsing de
if/elseemParser.pypara consumir corretamenteelsee blocos. - Corrigida geração de
if/elseemCompiler.pypara emitir corretamente ramostheneelse.
Arquivo principal de documentação: README.md
O Obsidian Compiler é um compilador completo para a linguagem de programação Obsidian, uma linguagem simples e imperativa projetada para fins educacionais e de demonstração. O compilador traduz código fonte escrito em Obsidian para código intermediário LLVM (IR), que pode ser executado diretamente usando o runtime LLVM. O projeto é implementado em Python e utiliza a biblioteca llvmlite para geração de código LLVM, permitindo a compilação just-in-time (JIT) e execução eficiente.
O objetivo principal do projeto é demonstrar os conceitos fundamentais de compiladores, incluindo análise léxica, parsing, geração de código intermediário e execução. A linguagem Obsidian suporta variáveis, expressões aritméticas, funções, controle de fluxo básico e tipos simples como inteiros e floats.
Obsidian é uma linguagem imperativa com sintaxe inspirada em linguagens como C e Python, mas simplificada. Ela suporta:
- Declaração e atribuição de variáveis.
- Expressões aritméticas com operadores básicos.
- Definição de funções.
- Estruturas de controle como blocos e retornos.
O compilador processa o código fonte em várias fases:
- Análise Léxica (Lexer): Converte o código fonte em tokens.
- Análise Sintática (Parser): Constrói uma Árvore de Sintaxe Abstrata (AST) a partir dos tokens.
- Compilação (Compiler): Gera código LLVM IR a partir da AST.
- Execução (Opcional): Usa LLVM para executar o código compilado.
let nome$tipo -> valor;
Exemplo:
let a$int -> 10;
let b$float -> 3.14;
nome -> expressão;
Exemplo:
a -> a * 2;
fun nome(parametros): tipo_retorno {
// corpo da função
return expressão;
}
Exemplo:
fun main(): int {
let a$int -> 4;
a -> a * 2;
return a;
}
- Literais:
10,3.14 - Identificadores:
a,b - Operadores aritméticos:
+,-,*,/,%,**(potência, não implementado) - Agrupamento:
(expressão)
int: Inteiro de 32 bits.float: Ponto flutuante.
O compilador é dividido em módulos principais:
- Lexer.py: Responsável pela tokenização. Lê o código fonte e gera uma sequência de tokens.
- Parser.py: Constrói a AST a partir dos tokens, usando precedência de operadores.
- AST.py: Define as estruturas de dados para a árvore de sintaxe.
- Compiler.py: Gera código LLVM IR a partir da AST. Usa um ambiente (Environment) para gerenciar variáveis.
- Environment.py: Gerencia o escopo de variáveis, armazenando ponteiros LLVM.
- Token.py: Define os tipos de tokens.
- main.py: Ponto de entrada, coordena as fases de compilação e execução opcional.
Durante a compilação, variáveis são alocadas na pilha usando alloca, e operações são traduzidas para instruções LLVM correspondentes.
O projeto utiliza as seguintes bibliotecas Python:
-
llvmlite: Biblioteca para geração e manipulação de código LLVM IR. É usada porque LLVM é um backend robusto e eficiente para compilação, permitindo otimização e execução JIT.
llvmlitefornece uma interface Python para LLVM, facilitando a geração de código sem precisar de C++. -
ctypes: Biblioteca padrão do Python para chamar funções C. É usada para executar o código compilado, criando ponteiros de função a partir do endereço gerado pelo LLVM JIT. Permite a integração direta com código nativo.
-
json: Biblioteca padrão para manipulação de JSON. É usada para depuração, salvando a AST em formato JSON no arquivo
debug/ast.json. -
time: Biblioteca padrão para medição de tempo. É usada para medir o tempo de execução do código compilado, fornecendo feedback de performance.
Nenhuma outra dependência externa é necessária, mantendo o projeto leve e fácil de instalar.
- Python 3.8+
- LLVM instalado (para execução JIT)
Clone o repositório e instale as dependências:
pip install llvmlite
- Escreva código Obsidian em um arquivo, e.g.,
tests/test_fun.obs. - Execute o compilador:
python main.py- O código em
tests/test_fun.obsserá compilado. - Se
RUN_CODE = Trueemmain.py, o código será executado e o resultado impresso. - Se
COMPILER_DEBUG = True, o IR LLVM será salvo emdebug/ir.obs.
- O código em
Veja os arquivos em tests/:
test.obs: Exemplo simples.test_fun.obs: Função main com operações.
LEXER_DEBUG: Imprime tokens.PARSER_DEBUG: Salva AST em JSON.COMPILER_DEBUG: Salva IR LLVM.
Contribuições são bem-vindas! Abra issues para bugs ou sugestões, e pull requests para melhorias.
- Parser (
Parser.py): Corrigido__parse_if_expressionpara detectar e consumir corretamente o tokenelsee os blocos{}associados, evitando que tokens de chave sejam deixados comocurrent_tokensem uma função de prefixo. - Parser (
Parser.py): Melhorias no parsing de declarações de função (__parse_function_statement) — agora consumimos corretamente os parâmetros (mesmo quando não implementados) e o tipo de retorno, evitando dessincronização do parser. - Compiler (
Compiler.py): Corrigida a lógica de__visit_if_statementque estava invertida — agora gera umif/elsequando há alternativa, ou umifsimples quando não há. - Vários pequenos ajustes de consistência e comentários para clarificar o fluxo de parsing/compilação.
Próximos passos recomendados:
- Rodar os testes em
tests/para validar os casos de controle de fluxo. - Verificar
debug/ast.jsonpara inspecionar AST gerada paraif/else.