Cópia estática
Copiado de docs/llm/project-structure.md em https://github.com/poppy-team/aipo-lang (MIT). Fixado na revisão ce47c4e070404fc41722ccd8e2b7b808b99062a8, blob 0033b36fc71ea59803a0953607b2818b09eaa990. O repositório de origem permanece canônico; esta cópia é atualizada por um pull request de sincronização, não em tempo real.
Este documento define os padrões canônicos para organização de código, diretórios, manifestos aipo.toml e estratégias de teste em Aipo V1.
.aipo é um módulo: Não existe palavra-chave module ou package dentro do código. A hierarquia de pastas reflete diretamente os caminhos de importação..aipo. Apenas itens explicitamente listados em export são acessíveis via import.A importa B e B importa A) geram erro de compilação.aipo run e aipo build nunca acessam a rede. Dependências externas devem estar travadas no manifesto aipo.toml.Aplicações são projetos que compilam para um executável binário final ou script executável autônomo com um ponto de entrada main.aipo.
todo-cli) todo-cli/
├── aipo.toml
├── README.md
├── src/
│ ├── main.aipo # Ponto de entrada (CLI parser e ciclo principal)
│ ├── domain.aipo # Structs de tarefas, listas e regras de negócio
│ ├── storage.aipo # Persistência em disco e formato JSON/texto
│ └── format.aipo # Formatação visual de saída para o terminal
└── tests/
└── integration_test.aipo # Testes de ponta a ponta do executávelaipo.toml da Aplicação [package]
name = "apps.todo_cli"
version = "0.1.0"
entry = "src/main.aipo"
authors = ["Desenvolvedor <dev@exemplo.com>"]
description = "Gerenciador de tarefas minimalista em linha de comando"
license = "MIT"
targets = ["native"]
[dependencies]
# Dependências externas exigem SHA de 40 caracteres para reprodutibilidade estrita
# "aipo.terminal" = { git = "https://github.com/aipo-lang/terminal.git", rev = "3a8f9c1b2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a" }src/domain.aipo (Regras e entidades puras de domínio):
export Tarefa, criar_tarefa
struct Tarefa {
id: Int
descricao: String
var concluida: Bool = false
}
Tarefa:invariant {
self.id > 0
len(self.descricao) > 0
}
Tarefa:marcar_concluida(var self) {
self.concluida = true
}
fn criar_tarefa(id: Int, descricao: String) -> Tarefa {
if len(descricao) == 0 {
fail "descrição não pode ser vazia"
}
return Tarefa{ id: id, descricao: descricao, concluida: false }
}
#!test("criação de tarefa com sucesso")
fn test_criar_tarefa() {
let t = criar_tarefa(1, "Comprar café")
if t.concluida {
fail "tarefa deveria nascer pendente"
}
}src/storage.aipo (Persistência com journal e transação):
import domain: Tarefa
export salvar_tarefas, carregar_tarefas
fn salvar_tarefas(caminho: String, tarefas: List) {
attempt {
var buffer = ""
each t in tarefas {
let status = if t.concluida then "1" else "0"
buffer += f"{t.id}|{status}|{t.descricao}\n"
}
# Operação de I/O de escrita atômica
io.write_file(caminho, buffer)
} failed err {
fail f"falha ao persistir tarefas no disco: {err.message}"
}
}
fn carregar_tarefas(caminho: String) -> List {
let conteudo = io.read_file(caminho) or_else ""
var lista = []
# Processa linhas e reconstrói estado
return lista
}src/main.aipo (Ponto de entrada):
import domain: Tarefa, criar_tarefa
import storage: salvar_tarefas, carregar_tarefas
# Função principal de inicialização
fn main() {
let arquivo = "tarefas.txt"
var lista = carregar_tarefas(arquivo)
let args = sys.args()
if len(args) < 2 {
io.println("Uso: todo-cli <add|list|done> [argumentos]")
return
}
let comando = args[1]
match comando {
when "add" {
let desc = args[2] or_else "Sem descrição"
let nova = criar_tarefa(len(lista) + 1, desc)
lista.push(nova)
salvar_tarefas(arquivo, lista)
io.println(f"Tarefa #{nova.id} adicionada.")
}
when "list" {
each t in lista {
let check = if t.concluida then "[X]" else "[ ]"
io.println(f"{t.id}. {check} {t.descricao}")
}
}
else {
io.println(f"Comando desconhecido: {comando}")
}
}
}user-service) user-service/
├── aipo.toml
├── src/
│ ├── main.aipo # Inicialização do servidor HTTP e injeção de dependências
│ ├── config.aipo # Leitura de variáveis de ambiente e portas
│ ├── routes/
│ │ ├── auth.aipo # Endpoints de login e token
│ │ └── users.aipo # Endpoints CRUD de usuários
│ ├── services/
│ │ └── user_service.aipo # Lógica de negócio e regras de cadastro
│ └── models/
│ └── user.aipo # Structs de usuário, senhas hash, invariantes
└── tests/
└── api_test.aipo # Testes de integração de endpointsaipo.toml da API [package]
name = "services.user_api"
version = "1.0.0"
entry = "src/main.aipo"
description = "Microserviço de autenticação e usuários"
license = "Proprietary"
targets = ["native", "js"]
[dependencies]
"aipo.http" = { path = "../../packages/aipo-http" }Bibliotecas e módulos reutilizáveis não possuem função main(). O ponto de entrada padrão é src/lib.aipo, atuando como a fachada pública (façade) que expõe uma API limpa e oculta os detalhes internos.
aipo-cache) aipo-cache/
├── aipo.toml
├── README.md
├── src/
│ ├── lib.aipo # Fachada pública: reexporta tipos e funções estáveis
│ ├── memory_cache.aipo # Implementação concreta de cache LRU em memória
│ ├── contract.aipo # Interfaces abstratas do cache (CacheStore)
│ └── internal/
│ ├── hasher.aipo # Utilitário interno privado de hashing
│ └── linked_list.aipo # Lista duplamente encadeada para controle de LRU
└── tests/
├── cache_test.aipo # Testes de conformidade da API pública
└── stress_test.aipo # Testes de carga e política de desalocaçãoaipo.toml da Biblioteca [package]
name = "aipo.cache"
version = "0.2.0"
entry = "src/lib.aipo"
authors = ["Equipe de Performance <perf@aipo-lang.org>"]
description = "Cache LRU transacional determinístico de alta performance para Aipo"
license = "MIT"
targets = ["native", "js"]src/contract.aipo (Interfaces contratuais puras):
export CacheStore
# Interface estrutural para qualquer mecanismo de armazenamento de cache
interface CacheStore {
obter(chave: String) -> String?
inserir(var self, chave: String, valor: String, ttl_ms: Int)
remover(var self, chave: String) -> Bool
limpar(var self)
}src/memory_cache.aipo (Implementação concreta com satisfação estrutural):
import contract: CacheStore
import internal.hasher: hash_string
export MemoryCache, novo_memory_cache
#!satisfies CacheStore
struct MemoryCache {
capacidade: Int
var dados: Dict = {}
var tamanho_atual: Int = 0
}
MemoryCache:invariant {
self.capacidade > 0
self.tamanho_atual >= 0
self.tamanho_atual <= self.capacidade
}
fn novo_memory_cache(capacidade: Int = 1000) -> MemoryCache {
if capacidade <= 0 {
fail "capacidade deve ser positiva"
}
return MemoryCache{ capacidade: capacidade }
}
MemoryCache:obter(chave: String) -> String? {
let k = hash_string(chave)
return self.dados[k]
}
MemoryCache:inserir(var self, chave: String, valor: String, ttl_ms: Int) {
let k = hash_string(chave)
if not (k in self.dados) and self.tamanho_atual >= self.capacidade {
# Desaloca o item mais antigo (política simplificada)
self.remover_mais_antigo()
}
self.dados[k] = valor
self.tamanho_atual = len(self.dados)
}
MemoryCache:remover_mais_antigo(var self) {
# Lógica interna de desalocação
}
MemoryCache:remover(var self, chave: String) -> Bool {
let k = hash_string(chave)
if k in self.dados {
delete self.dados[k]
self.tamanho_atual -= 1
return true
}
return false
}
MemoryCache:limpar(var self) {
self.dados = {}
self.tamanho_atual = 0
}src/lib.aipo (Fachada da biblioteca):
# A fachada importa e reexporta os tipos públicos, blindando o consumidor dos módulos internos
import contract: CacheStore
import memory_cache: MemoryCache, novo_memory_cache
export CacheStore, MemoryCache, novo_memory_cachetests/ Aipo oferece duas formas oficiais para escrever testes:
#!test Ideal para testar funções privadas, invariantes locais e casos de borda imediatos.
aipo test).fn somar(a, b) { return a + b }
#!test("soma números positivos")
fn test_soma_positivos() {
if somar(2, 3) != 5 {
fail "soma incorreta"
}
}tests/*_test.aipo) Ideal para testes de integração, testes end-to-end e testes de API pública da biblioteca.
test_*.aipo ou *_test.aipo.import, garantindo que apenas símbolos exportados com export sejam testados.tests/cache_test.aipo:import aipo.cache: novo_memory_cache
#!test("respeita a capacidade máxima do cache")
fn test_capacidade_cache() {
var c = novo_memory_cache(2)
c.inserir("k1", "v1", 1000)
c.inserir("k2", "v2", 1000)
c.inserir("k3", "v3", 1000)
# Verifica que k1 foi desalojado
if c.obter("k1") != none {
fail "item k1 deveria ter sido removido por excesso de capacidade"
}
}| Aspecto | Aplicação (app) | Biblioteca (lib / pkg) |
|---|---|---|
Ponto de entrada (entry) | src/main.aipo (com fn main()) | src/lib.aipo (com re-exports) |
| Visibilidade pública | Raramente usa export no main.aipo | Uso extensivo de export na fachada lib.aipo |
| Testes principais | Integração em tests/*_test.aipo | Unitários #!test co-locados + contrato em tests/ |
| Dependências | Podem usar caminhos relativos ou pacotes externos | Mínima superfície de dependências externas |
| Alvo padrão | native | ["native", "js"] para portabilidade máxima |