Arquitetura da linguagem Joss
Índice ·
Antes: status ·
Depois: contribuir
Pipeline real
fuentes .joss
→ lexer (`pkg/parser/lexer.go`)
→ parser Pratt (`pkg/parser`)
→ AST (`pkg/parser/ast*.go`)
→ análisis semántico (`pkg/analyzer`)
→ diagnósticos (`pkg/diagnostics`)
→ intérprete AST (`pkg/core/evaluator*.go`, `executor.go`)
→ runtime integrado (`pkg/core`, `pkg/server`)
flowchart LR
S[Fuente .joss] --> L[Lexer]
L --> T[Tokens]
T --> P[Parser Pratt]
P --> A[AST]
A --> N[Analyzer]
N -->|sin errores| E[Intérprete core]
N --> D[Diagnósticos]
E --> R[Runtime y servicios]
A --> B[JOSSBC2Z para build]
B --> E
joss analyze preserva cada arquivo como analyzer.SourceUnit ; não concatena ASTs perdendo a origem. Primeiro registra declarações globais de funções e classes e depois analisa cada método com um escopo léxico independente. As classes nativas vêm de Runtime.RegisterNativeClasses ; Os plug-ins fornecem seus índices de símbolos JP v2.
Responsabilidades
| Pacote | Responsabilidade |
|---|---|
pkg/parser |
Tokens, lexer, precedências, analisador e nós AST. |
|
| pkg/typesystem | Nomes canônicos, inferência, coerção explícita e compatibilidade de atribuição.
|
| pkg/analyzer | Unidades de origem, escopos, símbolos, inferência de expressões, assinaturas e fluxo alcançável. Não depende do tempo de execução.
|
| pkg/diagnostics | Modelo comum: código, gravidade, mensagem, arquivo, intervalo, explicação e dica.
|
| pkg/core | Adaptação de catálogos reais ao analisador, interpretador e primitivas integradas.
|
| pkg/runtime/errors | Erro de tempo de execução estruturado e Joss stack frames, sem dependências de framework.
|
| pkg/runtime/value | Semântica de valor independente do avaliador, incluindo indexação Unicode.
|
| pkg/runtime/plan , pkg/runtime/frame | Planos resgatáveis, slots e representação etiquetada usados para acelerar a resolução local; eles não formam bytecode portátil.
|
| pkg/pluginruntime , pkg/pluginpkg | Carregamento isolado, verificação e resolução de símbolos de plugins JP v2.
|
| pkg/bytecode | Serialização compactada do AST. Não é código de máquina ou LLVM IR.
|
| pkg/vm | VM/compilador experimental independente. A CLI e pkg/core não os utilizam como rota padrão.
|
| cmd/joss | CLI, análise, execução, construção e administração de projetos.
|
| vscode-joss | LSP/editora. Consome o catálogo gerado do kernel.
|
Fontes da verdade
- Palavras-chave e símbolos:
pkg/parser/token.go;parser.KeywordNames()eparser.SymbolDefinitions()são as projeções para lexer, formatador e geradores. - Tipos e compatibilidade:
pkg/typesystem, incluindo classificações semânticas comoType.IsNumeric(). - Métodos primitivos: nome e retorno em
pkg/typesystem/primitive_methods.go.pkg/analyzerprojeta esses metadados epkg/core/primitives.gopreserva apenas a implementação do tempo de execução. Qualquer definição deve ser abrangida porTestPrimitiveMethodCatalogHasRuntimeImplementations. - Integrados globais:
pkg/core/builtins.godeclara nome, domínio do despachante e retorna uma vez. O tempo de execução é despachado diretamente por esse descritor e rejeita nomes fora do catálogo. - Classes/métodos nativos: chamadas para
registerNativedentro deRuntime.RegisterNativeClasses(); seus retornos são digitadospkg/core/native_signatures.go. - Símbolos de plug-in:
pluginpkg.SymbolIndexincluídos em cada.jp. - Diagnóstico:
pkg/diagnostics.Diagnostice códigos emitidos porpkg/analyzer. - Catálogo de códigos VS:
vscode-joss/src/server/generated/languageCatalog.json, gerado usandogo run ./tools/cataloggen.
CI executa go run ./tools/cataloggen --check ; editar manualmente o catálogo gerado não é válido.
Escopos e símbolos
- Cada função, método,
Inite fechamento tem seu próprio escopo. – Os parâmetros pertencem exclusivamente ao seu callable. - Os blocos de controle usam o escopo do chamável para refletir o tempo de execução atual.
- A ligação
foreachpode ser reutilizada em outro loop; o tempo de execução trata isso como uma atribuição. – Classes e funções de nível superior são resolvidas no nível do projeto. - Globais nativos e símbolos de plug-in são injetados usando
analyzer.Environment. - Uma função nomeada não herda variáveis de origem do chamador ou variáveis de nível superior: ela recebe parâmetros, locais,
thise ligações do host/plugin. Um fechamento captura lexicamente. - Os parâmetros requerem tipo explícito;
mixednunca é apresentado silenciosamente. – Um parâmetrorefrecebe um alias temporário para a ligação mutável do chamador. O analisador e o tempo de execução requerem marcação bilateral, valor l, não constância e tipo exatamente invariante. - A visibilidade não tem padrão: analisador, analisador e tempo de execução preservam e validam
public,privateeprotected.
Construa e execute
O modo de desenvolvimento interpreta o AST. Cada invocação de callable cria um quadro lexical independente; não há escopo dinâmico entre chamador e receptor. Isso evita que uma chamada recursiva leia ou substitua as localidades do chamador. O tempo de execução limita a profundidade a 1.024 quadros por padrão e os encerramentos gravam apenas no ambiente capturado. Os tipos de retorno anotados são validados no analisador/tempo de execução e o analisador requer uma conclusão exaustiva e comprovável.
Antes de executar uma chamada, pkg/runtime/plan pode atribuir slots a
e parâmetros locais. frame_runtime.go usa esses slots e mantém um substituto para ligações
que não se enquadram no plano. Os metadados de classe, acesso e escopo são armazenados em cache; qualquer alteração semântica
deve comparar a rota rápida com a rota geral. Os controles de loop
buscam saltos diretos no AST planejado e hoje não percorrem todos os ternários/correspondências
, limite registrado na auditoria.
As referências seguras não expõem ponteiros Go: core.VariableReference preserva a ligação valor/tipo/const durante uma chamada e é automaticamente desreferenciada pelo avaliador. Uma referência não é um valor Joss armazenável nem ultrapassa os limites assíncronos/plugin.
pkg/bytecode codifica o AST com gob e compactação sob o único cabeçalho aceito JOSSBC2Z . Native compila um pacote que codifica byte junto com o executor Go; Atualmente não há back-end LLVM/Cranelift ou tradução AOT do programa Joss para código de máquina. O compilador do plugin possui um JPBC IR separado; Não deve ser confundido com o pipeline do idioma host.
A árvore pkg/vm contém opcodes e uma VM experimental. Seus erros aritméticos e
não definem a semântica publicada, desde que não esteja conectado ao pipeline
anterior. Da mesma forma, o JPBC define apenas a execução do plugin. Ao documentar
“compilação” indica qual das três representações está sendo usada.
Regra de dependência
Camadas de linguagem ( parser , typesystem , diagnostics , analyzer ) não importam core . core adapta seus registros ao analisador. Este endereço evita que o verificador de tipo dependa dos efeitos colaterais do servidor ou do banco de dados.
O servidor mantém seus adaptadores HTTP fora do interpretador: request_data.go traduz net/http para o mapa estável consumido por Joss e rate_limiter.go encapsula o estado de limitação. handler.go continua como orquestrador e não deve absorver novamente essas responsabilidades.
Limites internos do avaliador
As chamadas mantêm uma única rota avaliada. call_arguments.go transforma expressões de origem e aplica ligação posicional/nomeada/padrão/ref; call_method.go instala parâmetros, gerencia o quadro, recursão, adia e retorna contrato; callable_dispatch.go adapta fechamentos, métodos vinculados, plug-ins e funções Go para esse caminho; evaluator_call.go resolve apenas uma chamada de origem e o domínio integrado. Os pontos de entrada públicos históricos delegam, não reimplementam regras.
Os infixos são coordenados em evaluator_infix.go porque a ordem observável – coalescência, pipeline, curto-circuito, entrada, avaliação correta e saída – deve permanecer explícita. Seus comportamentos vivem de dominância em evaluator_control.go , evaluator_pipeline.go , evaluator_numeric.go , evaluator_stream.go e evaluator_update.go . Um operador não deve ser adicionado diretamente ao coordenador, a menos que isso afete a ordem de avaliação.
No analisador, infer_nominal.go estende typesystem.Assignable com classes/interfaces do projeto; infer_narrowing.go cria escopos refinados por is e comparações com nulo. Essas regras permanecem separadas do tempo de execução: elas compartilham tipos e metadados, não execução ou estado.
Terceira fase da arquitetura — setembro de 2026
O analisador possui um pipeline semântico explícito: coleta de declarações, escopo do projeto, contratos nominais, órgãos/solicitáveis e diagnósticos. As responsabilidades são separadas em arquivos no mesmo pacote para preservar o encapsulamento e evitar APIs públicas artificiais. Funções de projeto call_resolution.go e member_resolution.go , métodos, nativos, primitivos e plug-ins para a assinatura semântica Callable ; a execução continua em core .
Antes de mudar a infraestrutura, foram adicionadas caracterizações de tempo de execução (fork/reset/reuse), HTTP (503, CORS, sessões, CSRF e resposta) e publicação (ZIP, traversal, lockfile e registro). Esses testes já encontraram e corrigiram um caso real de estado residual em Runtime.Free . O manipulador e a CLI ainda são grandes coordenadores: a extração está condicionada à conclusão do WebSocket, mapeamento de resposta e mais casos de registro.
As regras negativas são mantidas: o analisador não importa o núcleo, o analisador não conhece o tempo de execução, o servidor não redefine a semântica, o formatador não descarta curiosidades para reutilizar o lexer e a VM não define a semântica publicada. @json e NativeMethodDefinition continuam como dívidas P1 até que uma representação comum sustentável esteja disponível.
Quarta fase da arquitetura — setembro de 2026
O ciclo de vida do tempo de execução reside em runtime_lifecycle.go : construção, pool, aquisição, vinculações de host e redefinição. Runtime.Free remove todos os estados e caches por solicitação/por execução; não fecha DB , porque o pool SQL é um recurso compartilhado externo cujo proprietário é o aplicativo. Fork copia mapas mutáveis, redefine cursores/caches e compartilha apenas AST/planos/configuração imutável, plugin de registro e recursos externos. Os testes simultâneos e de isolamento protegem essas regras.
MainHandler adquire o fork e registra imediatamente uma limpeza única. A adaptação dos resultados reside em response_writer.go ; a decodificação da solicitação continua em request_data.go , a limitação de taxa em rate_limiter.go e a sessão/CSRF permanece no manipulador até que seus back-ends sejam concluídos. A borda publicada reconhece string, JSON, RAW, FILE, STREAM e REDIRECT; outros valores continuam no arquivo/404 substituto.
NativeMethodDefinition são metadados semânticos, não tempo de execução de reflexão. Publica apenas nome, retorno e parâmetros confiáveis, distinguindo aridade desconhecida. Stack, Queue e Math são a migração inicial; as outras classes usam o adaptador legado. Todos são finalmente projetados para analyzer.Callable e gerados catálogos.
pkg/viewtemplate possui a sintaxe de diretiva compartilhada mínima. O scanner reconhece intervalos, aspas e parênteses aninhados; runtime e linter compartilham a interpretação de @json . As políticas de renderização, acesso a arquivos e lint permanecem em seus domínios.
A autoridade é explicitamente dividida: verdade de sintaxe no analisador, verdade de tipo em sistema de tipos/analisador, verdade de tempo de execução em intérprete/núcleo e ferramentas como projeção. A VM participa apenas de um corpus diferencial declarado para recursos suportados; nunca define a semântica publicada.
Quinta fase da arquitetura — setembro de 2026
A quinta fase consolida propriedade, simultaneidade segura, contratos de sessão e o ciclo de vida de plug-ins e WebSockets:
- Propriedade de plugins e drivers nativos: O registro de plugins compartilha catálogos e ASTs tratados como imutáveis; cada
RuntimeimplementaPluginAwareHoste mantém mecanismos AST e namespaces vinculados à sua execução. Os drivers dinâmicos usam propriedade contada: o runtime que carrega possui o handle, cadaFork()o retém e cadaFree()o libera. O último proprietário executaFreeLibrary/dlclose;Unload()rejeita uma descarga explícita enquanto existirem mutuários e é serializado com as chamadas ativas. - Contratos de sessão e flash nas respostas:
memory,fileeredissão os únicos backends aceitos; um nome desconhecido falha explicitamente. Cada requisição recebe um snapshot que clona recursivamente mapas e slices JSON para impedir aliasing antes desaveSession. Qualquer falha ao persistir o flash cancelaLocatione produz HTTP 500. - Isolamento e callbacks WebSocket: Atualmente, a conexão executa
onMessageeonClose; o handler recebe o socket seguido dos parâmetros posicionais da rota.onConnect,onErrore uma variável$paramsainda não fazem parte do contrato implementado. O servidor fornece um runtime bifurcado por requisição, enquanto os testes protegem a limpeza, o isolamento de panics e as mensagens concorrentes. - Defesa do pool de runtimes:
Runtime.Free()usaatomic.Bool.CompareAndSwappara que apenas um chamador possa limpar e devolver uma instância async.Pool. A inicialização global dos assets é serializada separadamente para impedir que aquisições simultâneas gravem no singleton ao mesmo tempo. - Expansão dos metadados nativos: Dez classes usam
NativeMethodDefinition(Stack,Queue,Math,JSON,Markdown,Str,UUID,Lang,Console,Zip). A projeção publica nomes e retornos confiáveis; sua aridade permanece explicitamente desconhecida até que existam contratos comprovados. - Posicionamento consciente de runas em templates de views: O scanner e o linter de diretivas (
pkg/viewtemplate) calculam colunas exatas contando runas UTF-8, garantindo consistência absoluta nos diagnósticos diante de caracteres multibyte.