@semacode/cli 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/geracaoCore.d.ts +2 -0
- package/dist/index.part07.js +21 -0
- package/docs/testing.md +21 -0
- package/exemplos/python_verificacao.sema +68 -0
- package/node_modules/@sema/gerador-python/dist/expectativas.d.ts +3 -0
- package/node_modules/@sema/gerador-python/dist/expectativas.js +37 -0
- package/node_modules/@sema/gerador-python/dist/index.part01.d.ts +1 -1
- package/node_modules/@sema/gerador-python/dist/index.part01.js +11 -42
- package/node_modules/@sema/gerador-python/dist/index.part02.js +12 -5
- package/node_modules/@sema/gerador-python/dist/runtime.d.ts +1 -0
- package/node_modules/@sema/gerador-python/dist/runtime.js +70 -0
- package/node_modules/@sema/gerador-python/dist/valores.d.ts +4 -0
- package/node_modules/@sema/gerador-python/dist/valores.js +72 -0
- package/node_modules/@sema/gerador-typescript/dist/index.part01.d.ts +2 -2
- package/node_modules/@sema/gerador-typescript/dist/index.part01.js +118 -16
- package/node_modules/@sema/gerador-typescript/dist/index.part02.d.ts +1 -1
- package/node_modules/@sema/gerador-typescript/dist/index.part02.js +10 -3
- package/node_modules/@sema/nucleo/dist/semantico/analisador.part08.js +2 -0
- package/node_modules/@sema/nucleo/dist/semantico/testesTipos.d.ts +3 -0
- package/node_modules/@sema/nucleo/dist/semantico/testesTipos.js +35 -0
- package/package.json +1 -1
package/dist/geracaoCore.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface ResumoAlvoVerificacao {
|
|
|
18
18
|
estrutura: EstruturaSaida;
|
|
19
19
|
testesExecutados: boolean;
|
|
20
20
|
origem?: "executado" | "cache";
|
|
21
|
+
tipoEvidencia?: "andaime_demonstrativo";
|
|
22
|
+
implementacaoExternaExecutada?: boolean;
|
|
21
23
|
}
|
|
22
24
|
export interface ResumoModuloVerificacao {
|
|
23
25
|
modulo: string;
|
package/dist/index.part07.js
CHANGED
|
@@ -226,6 +226,11 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
|
|
|
226
226
|
const alvos = opcoes.alvo
|
|
227
227
|
? [opcoes.alvo]
|
|
228
228
|
: resolverAlvosVerificacao(contextoProjeto.configCarregada);
|
|
229
|
+
const testesNaoExecutaveis = diagnosticos.filter(item => item.codigo === "SEM125");
|
|
230
|
+
if (alvos.includes("python") && testesNaoExecutaveis.length > 0) {
|
|
231
|
+
console.error(formatarDiagnosticos(testesNaoExecutaveis));
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
229
234
|
const avisoEscopo = avisoEscopoStackVerificacao(contextoProjeto.configCarregada?.config.fontesLegado, alvos);
|
|
230
235
|
if (avisoEscopo) {
|
|
231
236
|
console.warn(avisoEscopo);
|
|
@@ -279,6 +284,7 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
|
|
|
279
284
|
estrutura,
|
|
280
285
|
testesExecutados: false,
|
|
281
286
|
origem: "cache",
|
|
287
|
+
...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
|
|
282
288
|
});
|
|
283
289
|
if (!emCache.sucesso) {
|
|
284
290
|
imprimirResumoVerificacao([...resumos, resumoModulo]);
|
|
@@ -313,6 +319,7 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
|
|
|
313
319
|
estrutura,
|
|
314
320
|
testesExecutados,
|
|
315
321
|
origem: "executado",
|
|
322
|
+
...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
|
|
316
323
|
});
|
|
317
324
|
if (execucao.codigoSaida !== 0) {
|
|
318
325
|
imprimirResumoVerificacao([...resumos, resumoModulo]);
|
|
@@ -347,6 +354,18 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
|
|
|
347
354
|
const alvos = opcoes.alvo
|
|
348
355
|
? [opcoes.alvo]
|
|
349
356
|
: resolverAlvosVerificacao(contextoProjeto.configCarregada);
|
|
357
|
+
const testesNaoExecutaveis = diagnosticos.filter(item => item.codigo === "SEM125");
|
|
358
|
+
if (alvos.includes("python") && testesNaoExecutaveis.length > 0) {
|
|
359
|
+
console.log(JSON.stringify({
|
|
360
|
+
comando: "verificar",
|
|
361
|
+
sucesso: false,
|
|
362
|
+
erro: "Casos de teste não executáveis em Python: substitua texto descritivo por entidades estruturadas.",
|
|
363
|
+
diagnosticos: testesNaoExecutaveis,
|
|
364
|
+
modulos: [],
|
|
365
|
+
totais: { modulos: 0, alvos: 0, arquivos: 0, testes: 0 },
|
|
366
|
+
}, null, 2));
|
|
367
|
+
return 1;
|
|
368
|
+
}
|
|
350
369
|
const avisoEscopo = avisoEscopoStackVerificacao(contextoProjeto.configCarregada?.config.fontesLegado, alvos);
|
|
351
370
|
const configuracoesAlvo = alvos.map((alvo) => ({
|
|
352
371
|
alvo,
|
|
@@ -411,6 +430,7 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
|
|
|
411
430
|
estrutura,
|
|
412
431
|
testesExecutados: false,
|
|
413
432
|
origem: "cache",
|
|
433
|
+
...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
|
|
414
434
|
});
|
|
415
435
|
resumoModulo.saidaTestes.push({ alvo, stdout: "", stderr: `resultado reutilizado do cache de verificacao (chave ${chave.slice(0, 12)})` });
|
|
416
436
|
if (!emCache.sucesso) {
|
|
@@ -444,6 +464,7 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
|
|
|
444
464
|
estrutura,
|
|
445
465
|
testesExecutados,
|
|
446
466
|
origem: "executado",
|
|
467
|
+
...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
|
|
447
468
|
});
|
|
448
469
|
resumoModulo.saidaTestes.push({ alvo, stdout: execucao.saidaPadrao, stderr: execucao.saidaErro });
|
|
449
470
|
if (execucao.codigoSaida !== 0) {
|
package/docs/testing.md
CHANGED
|
@@ -148,3 +148,24 @@ Native generator evidence is mandatory when changing C#/.NET or C++ support.
|
|
|
148
148
|
The `dotnet` smoke must compile and execute through the local .NET SDK. The
|
|
149
149
|
`cpp` smoke must compile and execute through GCC, Clang, or MSVC; checking only
|
|
150
150
|
for generated files is not sufficient.
|
|
151
|
+
|
|
152
|
+
Python verification must execute the generated code and inspect every declared
|
|
153
|
+
output expectation, including nested fields. A mismatching `expect` must fail;
|
|
154
|
+
the generator must never initialize an output from that expectation. Numeric
|
|
155
|
+
demonstration values respect comparison operators and simultaneous bounds.
|
|
156
|
+
Contradictory guarantees fail explicitly instead of being removed. Nested
|
|
157
|
+
paths preserve every segment and work with both JSON dictionaries and entities.
|
|
158
|
+
|
|
159
|
+
`SEM125` identifies descriptive scalar fixtures used where an entity or
|
|
160
|
+
structured type is declared. Contract analysis emits a compatibility warning
|
|
161
|
+
for historical declarative examples; executable Python generation rejects
|
|
162
|
+
them before running tests. Replace the scalar with a structured `given` block.
|
|
163
|
+
The regression suite is `testes/integracao/gerador-python-verificacao.test.ts`.
|
|
164
|
+
|
|
165
|
+
Generated Python functions are demonstration scaffolds. Their metadata declares
|
|
166
|
+
`origem_execucao: andaime_demonstrativo` and
|
|
167
|
+
`implementacao_externa_executada: false`. A linked `impl` is not executed by
|
|
168
|
+
this scaffold: its tests and guarantees cannot establish production behavior.
|
|
169
|
+
Use the application's real tests and drift evidence for that claim. Generated
|
|
170
|
+
failure cases currently retain the explicit synthetic error context and are
|
|
171
|
+
not evidence that the real implementation produces those errors.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module exemplos.python_verificacao {
|
|
2
|
+
docs {
|
|
3
|
+
resumo: "Regressao executavel Python para garantias numericas, formulario tipado, status e metadados aninhados."
|
|
4
|
+
}
|
|
5
|
+
entity FormularioEntrega {
|
|
6
|
+
fields {
|
|
7
|
+
tipo: Texto
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
entity MetaTemplate {
|
|
11
|
+
fields {
|
|
12
|
+
original_campaign_id: Texto
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
entity Template {
|
|
16
|
+
fields {
|
|
17
|
+
_template_meta: MetaTemplate
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
entity Status {
|
|
21
|
+
fields {
|
|
22
|
+
desired_action: Texto
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
task preparar {
|
|
26
|
+
input {
|
|
27
|
+
formulario: FormularioEntrega required
|
|
28
|
+
}
|
|
29
|
+
output {
|
|
30
|
+
poolSize: Inteiro
|
|
31
|
+
workersAplicados: Inteiro
|
|
32
|
+
template: Template
|
|
33
|
+
status: Status
|
|
34
|
+
}
|
|
35
|
+
rules {
|
|
36
|
+
formulario.tipo existe
|
|
37
|
+
}
|
|
38
|
+
guarantees {
|
|
39
|
+
poolSize > 0
|
|
40
|
+
workersAplicados >= 2
|
|
41
|
+
template._template_meta existe
|
|
42
|
+
template._template_meta.original_campaign_id em ["campanha_1"]
|
|
43
|
+
status.desired_action em ["iniciar", "parar"]
|
|
44
|
+
}
|
|
45
|
+
tests {
|
|
46
|
+
caso "verifica cada saida" {
|
|
47
|
+
given {
|
|
48
|
+
formulario {
|
|
49
|
+
tipo: "entrega"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
expect {
|
|
53
|
+
sucesso: verdadeiro
|
|
54
|
+
poolSize: 1
|
|
55
|
+
workersAplicados: 2
|
|
56
|
+
template {
|
|
57
|
+
_template_meta {
|
|
58
|
+
original_campaign_id: "campanha_1"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
status {
|
|
62
|
+
desired_action: "iniciar"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { IrBlocoDeclarativo, IrTask } from "@sema/nucleo";
|
|
2
|
+
export declare function validarEntradasTestePython(bloco: IrBlocoDeclarativo, campos: Map<string, string>, tipos: Map<string, Map<string, string>>, contexto: string): void;
|
|
3
|
+
export declare function gerarExpectativasPython(task: IrTask, bloco: IrBlocoDeclarativo, tipos: Map<string, Map<string, string>>, prefixo?: string): string[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { formatarLiteralTestePython } from "./index.part01.js";
|
|
2
|
+
import { acessoPython } from "./valores.js";
|
|
3
|
+
export function validarEntradasTestePython(bloco, campos, tipos, contexto) {
|
|
4
|
+
for (const campo of bloco.campos) {
|
|
5
|
+
const tipo = campos.get(campo.nome);
|
|
6
|
+
if (tipo && tipos.has(tipo) && campo.tipo !== "nulo") {
|
|
7
|
+
throw new Error(`SEM125: ${contexto}.${campo.nome} exige ${tipo}; forneça bloco estruturado, não texto descritivo.`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
for (const filho of bloco.blocos) {
|
|
11
|
+
const tipo = campos.get(filho.nome);
|
|
12
|
+
if (tipo && tipos.has(tipo))
|
|
13
|
+
validarEntradasTestePython(filho.conteudo, tipos.get(tipo), tipos, `${contexto}.${filho.nome}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function gerarExpectativasPython(task, bloco, tipos, prefixo = "") {
|
|
17
|
+
const linhas = [];
|
|
18
|
+
for (const campo of bloco.campos) {
|
|
19
|
+
const caminho = prefixo ? `${prefixo}.${campo.nome}` : campo.nome;
|
|
20
|
+
const raiz = caminho.split(".")[0];
|
|
21
|
+
const saida = task.output.find(item => item.nome === raiz);
|
|
22
|
+
if (!saida && caminho === "sucesso") {
|
|
23
|
+
linhas.push(` assert (resultado is not None) == ${formatarLiteralTestePython(campo.tipo, "Booleano")}, "Expectativa de sucesso divergente"`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!saida)
|
|
27
|
+
throw new Error(`Expectativa "${caminho}" da task "${task.nome}" não corresponde a campo do output.`);
|
|
28
|
+
let tipo = saida.tipo;
|
|
29
|
+
for (const filho of caminho.split(".").slice(1))
|
|
30
|
+
tipo = tipos.get(tipo)?.get(filho) ?? "Json";
|
|
31
|
+
linhas.push(` assert ${acessoPython("resultado", caminho)} == ${formatarLiteralTestePython(campo.tipo, tipo)}, ${JSON.stringify(`Expectativa divergente: ${caminho}`)}`);
|
|
32
|
+
}
|
|
33
|
+
for (const filho of bloco.blocos)
|
|
34
|
+
linhas.push(...gerarExpectativasPython(task, filho.conteudo, tipos, prefixo ? `${prefixo}.${filho.nome}` : filho.nome));
|
|
35
|
+
return linhas;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=expectativas.js.map
|
|
@@ -25,4 +25,4 @@ export declare function gerarMapaLiteralPython(campos: Array<{
|
|
|
25
25
|
export declare function coletarTiposCompostos(modulo: IrModulo): Map<string, Map<string, string>>;
|
|
26
26
|
export declare function gerarLiteralBlocoTestePython(bloco: IrBlocoDeclarativo, tiposCompostos?: Map<string, Map<string, string>>, tiposDeclarados?: Map<string, string>, tipoAtual?: string): string;
|
|
27
27
|
export declare function paraPascalCase(valor: string): string;
|
|
28
|
-
export declare function gerarPreparacaoSaida(task: IrTask): string;
|
|
28
|
+
export declare function gerarPreparacaoSaida(task: IrTask, tipos?: Map<string, Map<string, string>>): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SEMA-GOVERNED: sema.geradores_codigo_governado
|
|
2
2
|
// Descricao: modulo particionado; consulte contratos/sema/geradores_codigo_governado.sema antes de editar.
|
|
3
|
+
import { acessoPython, prepararSaidaPython } from "./valores.js";
|
|
3
4
|
import { extrairTiposNomeados, mapearTipoParaPython, } from "@sema/padroes";
|
|
4
5
|
export function mapearCampoParaPython(campo) {
|
|
5
6
|
let anotacao;
|
|
@@ -79,7 +80,7 @@ export function gerarMapaErrosPython(erros) {
|
|
|
79
80
|
return `{\n${entradas.map(([nome, mensagem]) => ` ${JSON.stringify(nome)}: ${JSON.stringify(mensagem)},`).join("\n")}\n}`;
|
|
80
81
|
}
|
|
81
82
|
export function formatarValorPython(valor, camposConhecidos, variavel) {
|
|
82
|
-
const texto = valor.trim();
|
|
83
|
+
const texto = valor.trim().replace(/^-\s+(?=\d)/u, "-").replace(/^(-?\d+)\s*\.\s*(\d+)$/u, "$1.$2");
|
|
83
84
|
if (/^-?\d+(?:\.\d+)?$/.test(texto)) {
|
|
84
85
|
return texto;
|
|
85
86
|
}
|
|
@@ -93,7 +94,7 @@ export function formatarValorPython(valor, camposConhecidos, variavel) {
|
|
|
93
94
|
return "None";
|
|
94
95
|
}
|
|
95
96
|
if (camposConhecidos.has(texto.split(".")[0] ?? texto)) {
|
|
96
|
-
return
|
|
97
|
+
return acessoPython(variavel, texto);
|
|
97
98
|
}
|
|
98
99
|
return JSON.stringify(texto);
|
|
99
100
|
}
|
|
@@ -106,23 +107,20 @@ export function extrairReferenciaCampoConhecido(valor, camposConhecidos) {
|
|
|
106
107
|
return camposConhecidos.has(partes[0] ?? "") ? partes : null;
|
|
107
108
|
}
|
|
108
109
|
export function gerarInicializacaoReferenciaSaida(partes) {
|
|
109
|
-
|
|
110
|
-
if (!raiz || !filho) {
|
|
110
|
+
if (partes.length === 0)
|
|
111
111
|
return [];
|
|
112
|
-
}
|
|
113
112
|
return [
|
|
114
|
-
` if saida
|
|
115
|
-
` if getattr(saida.${raiz}, "${filho}", None) is None:\n saida.${raiz}.${filho} = "valor_garantido"`,
|
|
113
|
+
` if ${acessoPython("saida", partes.join("."))} is None:\n sema_set(saida, ${JSON.stringify(partes)}, "valor_garantido")`,
|
|
116
114
|
];
|
|
117
115
|
}
|
|
118
116
|
export function resolverExpressaoPython(expressao, camposConhecidos, variavel) {
|
|
119
117
|
switch (expressao.tipo) {
|
|
120
118
|
case "existe":
|
|
121
|
-
return `${variavel
|
|
119
|
+
return `${acessoPython(variavel, expressao.alvo)} is not None`;
|
|
122
120
|
case "comparacao":
|
|
123
|
-
return `${variavel
|
|
121
|
+
return `${acessoPython(variavel, expressao.alvo)} ${expressao.operador} ${formatarValorPython(expressao.valor, camposConhecidos, variavel)}`;
|
|
124
122
|
case "pertencimento":
|
|
125
|
-
return `${variavel
|
|
123
|
+
return `${acessoPython(variavel, expressao.alvo)} in [${(expressao.valores ?? []).map((valor) => formatarValorPython(valor, camposConhecidos, variavel)).join(", ")}]`;
|
|
126
124
|
case "predicado":
|
|
127
125
|
return "True";
|
|
128
126
|
case "composta":
|
|
@@ -253,7 +251,7 @@ export function resolverTipoItemTeste(tipoDeclarado) {
|
|
|
253
251
|
return undefined;
|
|
254
252
|
}
|
|
255
253
|
export function formatarLiteralTestePython(valor, tipoDeclarado) {
|
|
256
|
-
const bruto = valor.trim();
|
|
254
|
+
const bruto = valor.trim().replace(/^-\s+(?=\d)/u, "-").replace(/^(-?\d+)\s*\.\s*(\d+)$/u, "$1.$2");
|
|
257
255
|
if (bruto.startsWith("[") && bruto.endsWith("]")) {
|
|
258
256
|
const interior = bruto.slice(1, -1).trim();
|
|
259
257
|
const tipoItem = resolverTipoItemTeste(tipoDeclarado);
|
|
@@ -348,36 +346,7 @@ export function paraPascalCase(valor) {
|
|
|
348
346
|
.map((parte) => parte[0].toUpperCase() + parte.slice(1))
|
|
349
347
|
.join("");
|
|
350
348
|
}
|
|
351
|
-
export function gerarPreparacaoSaida(task) {
|
|
352
|
-
|
|
353
|
-
const argumentos = task.output.map((campo) => `${campo.nome}=${valorPadraoPython(campo)}`).join(", ");
|
|
354
|
-
const ajustes = [];
|
|
355
|
-
for (const garantia of task.garantiasEstruturadas) {
|
|
356
|
-
if (garantia.tipo === "pertencimento" && garantia.valores && camposSaida.has(garantia.alvo)) {
|
|
357
|
-
ajustes.push(` saida.${garantia.alvo} = ${formatarValorPython(garantia.valores[0] ?? "", camposSaida, "saida")}`);
|
|
358
|
-
}
|
|
359
|
-
if (garantia.tipo === "comparacao" && garantia.valor && camposSaida.has(garantia.alvo.split(".")[0] ?? garantia.alvo) && !garantia.alvo.includes(".")) {
|
|
360
|
-
ajustes.push(` saida.${garantia.alvo} = ${formatarValorPython(garantia.valor, camposSaida, "saida")}`);
|
|
361
|
-
}
|
|
362
|
-
if (garantia.tipo === "comparacao" && garantia.valor && garantia.alvo.includes(".")) {
|
|
363
|
-
const [raiz, filho] = garantia.alvo.split(".", 2);
|
|
364
|
-
if (raiz && filho && camposSaida.has(raiz)) {
|
|
365
|
-
const referenciaValor = extrairReferenciaCampoConhecido(garantia.valor, camposSaida);
|
|
366
|
-
if (referenciaValor) {
|
|
367
|
-
ajustes.push(...gerarInicializacaoReferenciaSaida(referenciaValor));
|
|
368
|
-
}
|
|
369
|
-
ajustes.push(` if saida.${raiz} is None:\n saida.${raiz} = SimpleNamespace()`);
|
|
370
|
-
ajustes.push(` saida.${raiz}.${filho} = ${formatarValorPython(garantia.valor, camposSaida, "saida")}`);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (garantia.tipo === "existe" && garantia.alvo.includes(".")) {
|
|
374
|
-
const [raiz, filho] = garantia.alvo.split(".", 2);
|
|
375
|
-
if (raiz && filho && camposSaida.has(raiz)) {
|
|
376
|
-
ajustes.push(` if saida.${raiz} is None:\n saida.${raiz} = SimpleNamespace()`);
|
|
377
|
-
ajustes.push(` if getattr(saida.${raiz}, "${filho}", None) is None:\n saida.${raiz}.${filho} = "valor_garantido"`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return ` saida = ${task.nome}Saida(${argumentos})\n${ajustes.join("\n")}`;
|
|
349
|
+
export function gerarPreparacaoSaida(task, tipos = new Map()) {
|
|
350
|
+
return prepararSaidaPython(task, tipos);
|
|
382
351
|
}
|
|
383
352
|
//# sourceMappingURL=index.part01.js.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// SEMA-GOVERNED: sema.geradores_codigo_governado
|
|
2
2
|
// Descricao: modulo particionado; consulte contratos/sema/geradores_codigo_governado.sema antes de editar.
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { prepararSaidaPython } from "./valores.js";
|
|
5
|
+
import { RUNTIME_PYTHON } from "./runtime.js";
|
|
6
|
+
import { gerarExpectativasPython, validarEntradasTestePython } from "./expectativas.js";
|
|
4
7
|
import { descreverEstruturaModulo, normalizarNomeModulo, normalizarNomeParaSimbolo, } from "@sema/padroes";
|
|
5
|
-
import { coletarTiposCompostos, coletarTiposExternos, formatarLiteralTestePython, gerarComentarioInvariantesPython, gerarDataclass, gerarListaCamposPython, gerarLiteralBlocoTestePython, gerarMapaErrosPython,
|
|
8
|
+
import { coletarTiposCompostos, coletarTiposExternos, formatarLiteralTestePython, gerarComentarioInvariantesPython, gerarDataclass, gerarListaCamposPython, gerarLiteralBlocoTestePython, gerarMapaErrosPython, mapearCampoParaPython, paraPascalCase, resolverExpressaoPython } from "./index.part01.js";
|
|
6
9
|
export function finalizarBlocoPython(linhas) {
|
|
7
10
|
if (linhas.length === 0) {
|
|
8
11
|
return " pass";
|
|
@@ -48,6 +51,8 @@ export function gerarMetadadosTask(task) {
|
|
|
48
51
|
"impl": ${implementacoes},
|
|
49
52
|
"errors": ${gerarMapaErrosPython(task.errors)},
|
|
50
53
|
"guarantees": ${JSON.stringify(task.guarantees, null, 2)},
|
|
54
|
+
"origem_execucao": "andaime_demonstrativo",
|
|
55
|
+
"implementacao_externa_executada": False,
|
|
51
56
|
}
|
|
52
57
|
`;
|
|
53
58
|
}
|
|
@@ -191,7 +196,7 @@ ${erroAutorizacao ? ` if contexto_execucao["erro_esperado"] == "${erroAutoriz
|
|
|
191
196
|
${task.stateContract ? ` # Vinculo de estado: ${task.stateContract.nomeEstado ?? "nao_definido"}\n # Transicoes declaradas pela task: ${task.stateContract.transicoes.map((transicao) => `${transicao.origem}->${transicao.destino}`).join(", ") || "nenhuma"}` : ""}
|
|
192
197
|
${implementacoes}
|
|
193
198
|
${efeitos}
|
|
194
|
-
${
|
|
199
|
+
${prepararSaidaPython(task, tiposCompostos)}
|
|
195
200
|
${garantias}
|
|
196
201
|
`;
|
|
197
202
|
}
|
|
@@ -202,6 +207,7 @@ export function gerarTestes(modulo) {
|
|
|
202
207
|
const nomeFuncao = `executar_${normalizarNomeParaSimbolo(task.nome)}`;
|
|
203
208
|
const tiposEntrada = new Map(task.input.map((campo) => [campo.nome, campo.tipo]));
|
|
204
209
|
for (const caso of task.tests) {
|
|
210
|
+
validarEntradasTestePython(caso.given, tiposEntrada, tiposCompostos, `given da task ${task.nome}, caso ${caso.nome}`);
|
|
205
211
|
const argumentos = [
|
|
206
212
|
...caso.given.campos
|
|
207
213
|
.filter((campo) => tiposEntrada.has(campo.nome))
|
|
@@ -222,14 +228,15 @@ export function gerarTestes(modulo) {
|
|
|
222
228
|
linhas.push(`def test_${normalizarNomeParaSimbolo(task.nome)}_${normalizarNomeParaSimbolo(caso.nome)}() -> None:\n entrada = ${task.nome}Entrada(${argumentos})\n contexto = { ${contextoLinhas.join(", ")} }\n with pytest.raises(${task.nome}_${tipoErro}Erro):\n ${nomeFuncao}(entrada, contexto)\n`);
|
|
223
229
|
continue;
|
|
224
230
|
}
|
|
225
|
-
|
|
231
|
+
const expectativas = gerarExpectativasPython(task, caso.expect, tiposCompostos);
|
|
232
|
+
linhas.push(`def test_${normalizarNomeParaSimbolo(task.nome)}_${normalizarNomeParaSimbolo(caso.nome)}() -> None:\n entrada = ${task.nome}Entrada(${argumentos})\n resultado = ${nomeFuncao}(entrada)\n${expectativas.length ? expectativas.join("\n") : " assert resultado is not None"}\n`);
|
|
226
233
|
}
|
|
227
234
|
}
|
|
228
235
|
return linhas.join("\n");
|
|
229
236
|
}
|
|
230
237
|
export function gerarCabecalhoSemaPython(modulo, tipo = "contrato") {
|
|
231
238
|
const descricoes = {
|
|
232
|
-
contrato: "
|
|
239
|
+
contrato: "andaime Python demonstrativo para revisar regras; não constitui prova de execução da implementação externa.",
|
|
233
240
|
teste: "testes Python gerados a partir dos casos do contrato Sema.",
|
|
234
241
|
schemas: "schemas FastAPI derivados do contrato Sema para entrada e saída públicas.",
|
|
235
242
|
service: "service FastAPI que conecta o scaffold do framework às tasks governadas pelo contrato Sema.",
|
|
@@ -262,7 +269,7 @@ export function gerarPythonBase(modulo) {
|
|
|
262
269
|
const tiposCompostos = coletarTiposCompostos(modulo);
|
|
263
270
|
const tasks = modulo.tasks.map((task) => gerarTask(task, tiposCompostos)).join("\n");
|
|
264
271
|
const contratosPublicos = gerarRotas(modulo);
|
|
265
|
-
const codigo = `${gerarCabecalhoSemaPython(modulo)}from __future__ import annotations\n${interoperabilidades ? `${interoperabilidades}\n` : ""}\nfrom dataclasses import dataclass\nfrom types import SimpleNamespace\n\n${tiposExternos}\n${tipos}\n${enums}\n${entidades}\n${states}\n${flows}\n${routes}\n${tasks}\n${contratosPublicos}\n`;
|
|
272
|
+
const codigo = `${gerarCabecalhoSemaPython(modulo)}from __future__ import annotations\n${interoperabilidades ? `${interoperabilidades}\n` : ""}\nfrom dataclasses import dataclass\nfrom types import SimpleNamespace\n${RUNTIME_PYTHON}\n${tiposExternos}\n${tipos}\n${enums}\n${entidades}\n${states}\n${flows}\n${routes}\n${tasks}\n${contratosPublicos}\n`;
|
|
266
273
|
const testes = `${gerarCabecalhoSemaPython(modulo, "teste")}${gerarTestes(modulo)}`;
|
|
267
274
|
return [
|
|
268
275
|
{ caminhoRelativo: `${nomeBase}.py`, conteudo: codigo },
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const RUNTIME_PYTHON = "\nimport math\n\ndef sema_get(objeto, caminho):\n for campo in caminho:\n if objeto is None:\n return None\n objeto = objeto.get(campo) if isinstance(objeto, dict) else getattr(objeto, campo, None)\n return objeto\n\ndef sema_set(objeto, caminho, valor):\n for campo in caminho[:-1]:\n filho = sema_get(objeto, [campo])\n if filho is None:\n filho = {} if isinstance(objeto, dict) else SimpleNamespace()\n if isinstance(objeto, dict):\n objeto[campo] = filho\n else:\n setattr(objeto, campo, filho)\n if not isinstance(filho, dict) and not hasattr(filho, \"__dict__\"):\n raise ValueError(f\"Caminho de objeto incompat\u00EDvel: {campo}\")\n objeto = filho\n if isinstance(objeto, dict):\n objeto[caminho[-1]] = valor\n else:\n setattr(objeto, caminho[-1], valor)\n\ndef sema_sintetizar(atual, restricoes, inteiro=False):\n # Apenas valores demonstrativos: expect nunca participa desta s\u00EDntese.\n def aceita(valor):\n for operador, limite in restricoes:\n if operador == \"existe\" and valor is None:\n return False\n if operador == \"in\" and valor not in limite:\n return False\n try:\n if operador == \"==\" and valor != limite: return False\n if operador == \"!=\" and valor == limite: return False\n if operador == \">\" and not valor > limite: return False\n if operador == \">=\" and not valor >= limite: return False\n if operador == \"<\" and not valor < limite: return False\n if operador == \"<=\" and not valor <= limite: return False\n except TypeError:\n return False\n return True\n\n candidatos = [atual]\n numeros = []\n for operador, limite in restricoes:\n if operador == \"in\":\n candidatos.extend(limite)\n elif operador != \"existe\":\n candidatos.append(limite)\n if isinstance(limite, (int, float)) and not isinstance(limite, bool):\n numeros.append(limite)\n candidatos.extend([limite - 1, limite + 1, math.floor(limite), math.ceil(limite)])\n numeros.sort()\n candidatos.extend((a + b) / 2 for a, b in zip(numeros, numeros[1:]))\n candidatos.extend([1, 0, -1, True, False, \"valor_garantido\", \"outro_valor\"])\n for candidato in candidatos:\n if inteiro and (not isinstance(candidato, int) or isinstance(candidato, bool)):\n continue\n if aceita(candidato):\n return candidato\n raise ValueError(\"N\u00E3o foi poss\u00EDvel sintetizar sa\u00EDda demonstrativa compat\u00EDvel com as garantias; forne\u00E7a implementa\u00E7\u00E3o real.\")\n";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SEMA-GOVERNED: sema.produto.gerador_python_verificacao
|
|
2
|
+
// Contrato: contratos/sema/gerador_python_verificacao.sema
|
|
3
|
+
// Descrição: acesso uniforme a mapas e entidades e síntese demonstrativa de garantias.
|
|
4
|
+
export const RUNTIME_PYTHON = `
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
def sema_get(objeto, caminho):
|
|
8
|
+
for campo in caminho:
|
|
9
|
+
if objeto is None:
|
|
10
|
+
return None
|
|
11
|
+
objeto = objeto.get(campo) if isinstance(objeto, dict) else getattr(objeto, campo, None)
|
|
12
|
+
return objeto
|
|
13
|
+
|
|
14
|
+
def sema_set(objeto, caminho, valor):
|
|
15
|
+
for campo in caminho[:-1]:
|
|
16
|
+
filho = sema_get(objeto, [campo])
|
|
17
|
+
if filho is None:
|
|
18
|
+
filho = {} if isinstance(objeto, dict) else SimpleNamespace()
|
|
19
|
+
if isinstance(objeto, dict):
|
|
20
|
+
objeto[campo] = filho
|
|
21
|
+
else:
|
|
22
|
+
setattr(objeto, campo, filho)
|
|
23
|
+
if not isinstance(filho, dict) and not hasattr(filho, "__dict__"):
|
|
24
|
+
raise ValueError(f"Caminho de objeto incompatível: {campo}")
|
|
25
|
+
objeto = filho
|
|
26
|
+
if isinstance(objeto, dict):
|
|
27
|
+
objeto[caminho[-1]] = valor
|
|
28
|
+
else:
|
|
29
|
+
setattr(objeto, caminho[-1], valor)
|
|
30
|
+
|
|
31
|
+
def sema_sintetizar(atual, restricoes, inteiro=False):
|
|
32
|
+
# Apenas valores demonstrativos: expect nunca participa desta síntese.
|
|
33
|
+
def aceita(valor):
|
|
34
|
+
for operador, limite in restricoes:
|
|
35
|
+
if operador == "existe" and valor is None:
|
|
36
|
+
return False
|
|
37
|
+
if operador == "in" and valor not in limite:
|
|
38
|
+
return False
|
|
39
|
+
try:
|
|
40
|
+
if operador == "==" and valor != limite: return False
|
|
41
|
+
if operador == "!=" and valor == limite: return False
|
|
42
|
+
if operador == ">" and not valor > limite: return False
|
|
43
|
+
if operador == ">=" and not valor >= limite: return False
|
|
44
|
+
if operador == "<" and not valor < limite: return False
|
|
45
|
+
if operador == "<=" and not valor <= limite: return False
|
|
46
|
+
except TypeError:
|
|
47
|
+
return False
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
candidatos = [atual]
|
|
51
|
+
numeros = []
|
|
52
|
+
for operador, limite in restricoes:
|
|
53
|
+
if operador == "in":
|
|
54
|
+
candidatos.extend(limite)
|
|
55
|
+
elif operador != "existe":
|
|
56
|
+
candidatos.append(limite)
|
|
57
|
+
if isinstance(limite, (int, float)) and not isinstance(limite, bool):
|
|
58
|
+
numeros.append(limite)
|
|
59
|
+
candidatos.extend([limite - 1, limite + 1, math.floor(limite), math.ceil(limite)])
|
|
60
|
+
numeros.sort()
|
|
61
|
+
candidatos.extend((a + b) / 2 for a, b in zip(numeros, numeros[1:]))
|
|
62
|
+
candidatos.extend([1, 0, -1, True, False, "valor_garantido", "outro_valor"])
|
|
63
|
+
for candidato in candidatos:
|
|
64
|
+
if inteiro and (not isinstance(candidato, int) or isinstance(candidato, bool)):
|
|
65
|
+
continue
|
|
66
|
+
if aceita(candidato):
|
|
67
|
+
return candidato
|
|
68
|
+
raise ValueError("Não foi possível sintetizar saída demonstrativa compatível com as garantias; forneça implementação real.")
|
|
69
|
+
`;
|
|
70
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IrTask } from "@sema/nucleo";
|
|
2
|
+
export declare function acessoPython(variavel: string, caminho: string): string;
|
|
3
|
+
export declare function valorCompostoPython(tipo: string, tipos: Map<string, Map<string, string>>, vistos?: Set<string>): string;
|
|
4
|
+
export declare function prepararSaidaPython(task: IrTask, tipos: Map<string, Map<string, string>>): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { formatarValorPython } from "./index.part01.js";
|
|
2
|
+
export function acessoPython(variavel, caminho) {
|
|
3
|
+
return `sema_get(${variavel}, ${JSON.stringify(caminho.split("."))})`;
|
|
4
|
+
}
|
|
5
|
+
export function valorCompostoPython(tipo, tipos, vistos = new Set()) {
|
|
6
|
+
if (/^(Lista|Mapa)</u.test(tipo) || /\[\]$/u.test(tipo))
|
|
7
|
+
return tipo.startsWith("Mapa") ? "{}" : "[]";
|
|
8
|
+
if (tipo.startsWith("Opcional<") || tipo.endsWith("?"))
|
|
9
|
+
return "None";
|
|
10
|
+
if (["Numero", "Inteiro", "Decimal"].includes(tipo))
|
|
11
|
+
return "1";
|
|
12
|
+
if (tipo === "Booleano")
|
|
13
|
+
return "False";
|
|
14
|
+
if (["Json", "Objeto"].includes(tipo))
|
|
15
|
+
return "{}";
|
|
16
|
+
if (!tipos.has(tipo))
|
|
17
|
+
return ["Texto", "Id", "Email", "Url", "Data", "DataHora", "Timestamp"].includes(tipo) ? '"valor_garantido"' : "SimpleNamespace()";
|
|
18
|
+
if (vistos.has(tipo))
|
|
19
|
+
return "None";
|
|
20
|
+
const proximos = new Set(vistos).add(tipo);
|
|
21
|
+
return `${tipo}(${[...tipos.get(tipo)].map(([nome, filho]) => `${nome}=${valorCompostoPython(filho, tipos, proximos)}`).join(", ")})`;
|
|
22
|
+
}
|
|
23
|
+
function termosConjuntos(expressao) {
|
|
24
|
+
return expressao.tipo === "composta" && expressao.operadorLogico === "e"
|
|
25
|
+
? expressao.termos.flatMap(termosConjuntos) : [expressao];
|
|
26
|
+
}
|
|
27
|
+
export function prepararSaidaPython(task, tipos) {
|
|
28
|
+
const campos = new Set(task.output.map(campo => campo.nome));
|
|
29
|
+
const grupos = new Map();
|
|
30
|
+
for (const regra of task.garantiasEstruturadas.flatMap(termosConjuntos)) {
|
|
31
|
+
if (!("alvo" in regra) || !campos.has(regra.alvo.split(".")[0]))
|
|
32
|
+
continue;
|
|
33
|
+
if (!["comparacao", "pertencimento", "existe"].includes(regra.tipo))
|
|
34
|
+
continue;
|
|
35
|
+
grupos.set(regra.alvo, [...(grupos.get(regra.alvo) ?? []), regra]);
|
|
36
|
+
}
|
|
37
|
+
const argumentos = task.output.map(campo => `${campo.nome}=${campo.opcional ? "None" : valorCompostoPython(campo.tipo, tipos)}`);
|
|
38
|
+
const linhas = [` # Andaime demonstrativo; não executa a implementação externa vinculada.`, ` saida = ${task.nome}Saida(${argumentos.join(", ")})`];
|
|
39
|
+
// Inicialize folhas antes de suas referências e preserve nós que são pais.
|
|
40
|
+
const referencias = new Set();
|
|
41
|
+
for (const regras of grupos.values())
|
|
42
|
+
for (const regra of regras) {
|
|
43
|
+
if (regra.tipo === "comparacao" && campos.has(regra.valor.split(".")[0]))
|
|
44
|
+
referencias.add(regra.valor);
|
|
45
|
+
}
|
|
46
|
+
for (const referencia of referencias) {
|
|
47
|
+
if (!grupos.has(referencia))
|
|
48
|
+
grupos.set(referencia, [{ tipo: "existe", alvo: referencia, textoOriginal: `${referencia} existe` }]);
|
|
49
|
+
}
|
|
50
|
+
const caminhos = [...grupos.keys()].sort((a, b) => Number(referencias.has(b)) - Number(referencias.has(a)) || a.split(".").length - b.split(".").length);
|
|
51
|
+
for (const caminho of caminhos) {
|
|
52
|
+
const regras = grupos.get(caminho);
|
|
53
|
+
let tipo = task.output.find(campo => campo.nome === caminho.split(".")[0])?.tipo ?? "Json";
|
|
54
|
+
for (const segmento of caminho.split(".").slice(1))
|
|
55
|
+
tipo = tipos.get(tipo)?.get(segmento) ?? "Json";
|
|
56
|
+
const restricoes = regras.map(regra => {
|
|
57
|
+
if (regra.tipo === "comparacao")
|
|
58
|
+
return `(${JSON.stringify(regra.operador)}, ${formatarValorPython(regra.valor, campos, "saida")})`;
|
|
59
|
+
if (regra.tipo === "pertencimento")
|
|
60
|
+
return `("in", [${(regra.valores ?? []).map(valor => formatarValorPython(valor, campos, "saida")).join(", ")}])`;
|
|
61
|
+
return '("existe", None)';
|
|
62
|
+
});
|
|
63
|
+
if (regras.every(regra => regra.tipo === "existe") && caminhos.some(outro => outro.startsWith(`${caminho}.`))) {
|
|
64
|
+
linhas.push(` if ${acessoPython("saida", caminho)} is None:`, ` sema_set(saida, ${JSON.stringify(caminho.split("."))}, ${valorCompostoPython(tipo, tipos) === "None" ? "SimpleNamespace()" : valorCompostoPython(tipo, tipos)})`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
linhas.push(` sema_set(saida, ${JSON.stringify(caminho.split("."))}, sema_sintetizar(${acessoPython("saida", caminho)}, [${restricoes.join(", ")}], inteiro=${tipo === "Inteiro" ? "True" : "False"}))`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return linhas.join("\n");
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=valores.js.map
|
|
@@ -8,7 +8,7 @@ export declare function gerarComentarioInvariantesTypeScript(invariantes: Expres
|
|
|
8
8
|
export declare function gerarLiteralCamposTypeScript(campos: IrCampo[]): string;
|
|
9
9
|
export declare function coletarTiposExternos(modulo: IrModulo): string[];
|
|
10
10
|
export declare function gerarLiteralErrosTypeScript(erros: Record<string, string>): string;
|
|
11
|
-
export declare function formatarValorTypeScript(valor: string, camposConhecidos: Set<string>, variavel: string): string;
|
|
11
|
+
export declare function formatarValorTypeScript(valor: string, camposConhecidos: Set<string>, variavel: string, literal?: boolean): string;
|
|
12
12
|
export declare function resolverReferenciaTypeScript(referencia: string, variavel: string): string;
|
|
13
13
|
export declare function gerarExpressaoTypeScript(expressao: ExpressaoSemantica, camposConhecidos: Set<string>, variavel: string): string;
|
|
14
14
|
export declare function valorPadraoTypeScript(tipo: string, nomeCampo: string): string;
|
|
@@ -17,7 +17,7 @@ export declare function dividirLiteralNoNivelRaiz(valor: string, separador: ","
|
|
|
17
17
|
export declare function resolverTipoItemTeste(tipoDeclarado?: string): string | undefined;
|
|
18
18
|
export declare function formatarLiteralTesteTypeScript(valor: string, tipoDeclarado?: string): unknown;
|
|
19
19
|
export declare function converterBlocoTesteParaValorTypeScript(bloco: IrBlocoDeclarativo, tiposDeclarados?: Map<string, string>): Record<string, unknown>;
|
|
20
|
-
export declare function gerarPreparacaoSaida(task: IrTask): string;
|
|
20
|
+
export declare function gerarPreparacaoSaida(task: IrTask, tiposDeclarados?: Map<string, Map<string, string>>): string;
|
|
21
21
|
export declare function gerarValidacoes(task: IrTask): string;
|
|
22
22
|
export declare function gerarGarantias(task: IrTask): string;
|
|
23
23
|
export declare function gerarFuncaoGarantias(task: IrTask): string;
|
|
@@ -48,7 +48,7 @@ export function gerarLiteralErrosTypeScript(erros) {
|
|
|
48
48
|
}
|
|
49
49
|
return `{\n${entradas.map(([nome, mensagem]) => ` ${JSON.stringify(nome)}: ${JSON.stringify(mensagem)},`).join("\n")}\n}`;
|
|
50
50
|
}
|
|
51
|
-
export function formatarValorTypeScript(valor, camposConhecidos, variavel) {
|
|
51
|
+
export function formatarValorTypeScript(valor, camposConhecidos, variavel, literal = false) {
|
|
52
52
|
const texto = valor.trim();
|
|
53
53
|
if (/^-?\d+(?:\.\d+)?$/.test(texto)) {
|
|
54
54
|
return texto;
|
|
@@ -62,7 +62,7 @@ export function formatarValorTypeScript(valor, camposConhecidos, variavel) {
|
|
|
62
62
|
if (texto === "nulo") {
|
|
63
63
|
return "null";
|
|
64
64
|
}
|
|
65
|
-
if (camposConhecidos.has(texto.split(".")[0] ?? texto)) {
|
|
65
|
+
if (!literal && camposConhecidos.has(texto.split(".")[0] ?? texto)) {
|
|
66
66
|
return `${variavel}.${texto}`;
|
|
67
67
|
}
|
|
68
68
|
return JSON.stringify(texto);
|
|
@@ -260,29 +260,131 @@ export function converterBlocoTesteParaValorTypeScript(bloco, tiposDeclarados) {
|
|
|
260
260
|
}
|
|
261
261
|
return literal;
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
function achatarGarantiasConjuntivasTypeScript(expressao) {
|
|
264
|
+
return expressao.tipo === "composta" && expressao.operadorLogico === "e"
|
|
265
|
+
? expressao.termos.flatMap(achatarGarantiasConjuntivasTypeScript)
|
|
266
|
+
: [expressao];
|
|
267
|
+
}
|
|
268
|
+
function sugerirValoresTypeScript(tipo, tiposDeclarados) {
|
|
269
|
+
if (["Numero", "Inteiro", "Decimal"].includes(tipo))
|
|
270
|
+
return "[1, 0, -1]";
|
|
271
|
+
if (tipo === "Booleano")
|
|
272
|
+
return "[false, true]";
|
|
273
|
+
if (["Texto", "Id", "Email", "Url", "Data", "DataHora", "Timestamp"].includes(tipo)) {
|
|
274
|
+
return '["valor_garantido", "outro_valor"]';
|
|
275
|
+
}
|
|
276
|
+
if (/^Lista</u.test(tipo) || /\[\]$/u.test(tipo))
|
|
277
|
+
return "[[]]";
|
|
278
|
+
if (/^Mapa</u.test(tipo) || ["Json", "Objeto"].includes(tipo) || tiposDeclarados.has(tipo))
|
|
279
|
+
return "[{}]";
|
|
280
|
+
return "[1, 0, -1, true, false, \"valor_garantido\", \"outro_valor\", {}]";
|
|
281
|
+
}
|
|
282
|
+
export function gerarPreparacaoSaida(task, tiposDeclarados = new Map()) {
|
|
264
283
|
const camposSaida = new Set(task.output.map((campo) => campo.nome));
|
|
265
284
|
const linhas = [];
|
|
266
285
|
for (const campo of task.output) {
|
|
267
286
|
linhas.push(` ${campo.nome}: ${valorPadraoTypeScript(campo.tipo, campo.nome)},`);
|
|
268
287
|
}
|
|
269
|
-
const
|
|
270
|
-
for (const garantia of task.garantiasEstruturadas) {
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
ajustes.push(` ${resolverReferenciaTypeScript(garantia.alvo, "saida")} = ${formatarValorTypeScript(garantia.valor, camposSaida, "saida")} as any;`);
|
|
288
|
+
const grupos = new Map();
|
|
289
|
+
for (const garantia of task.garantiasEstruturadas.flatMap(achatarGarantiasConjuntivasTypeScript)) {
|
|
290
|
+
if (!("alvo" in garantia) || !camposSaida.has(garantia.alvo.split(".")[0] ?? garantia.alvo))
|
|
291
|
+
continue;
|
|
292
|
+
if (garantia.tipo === "comparacao" || garantia.tipo === "pertencimento" || garantia.tipo === "existe") {
|
|
293
|
+
grupos.set(garantia.alvo, [...(grupos.get(garantia.alvo) ?? []), garantia]);
|
|
276
294
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
295
|
+
}
|
|
296
|
+
const referencias = new Set();
|
|
297
|
+
for (const regras of grupos.values()) {
|
|
298
|
+
for (const regra of regras) {
|
|
299
|
+
if (regra.tipo === "comparacao" && !regra.valorLiteral && camposSaida.has(regra.valor.split(".")[0] ?? regra.valor)) {
|
|
300
|
+
referencias.add(regra.valor);
|
|
282
301
|
}
|
|
283
302
|
}
|
|
284
303
|
}
|
|
285
|
-
|
|
304
|
+
for (const referencia of referencias) {
|
|
305
|
+
if (!grupos.has(referencia)) {
|
|
306
|
+
grupos.set(referencia, [{ tipo: "existe", alvo: referencia, textoOriginal: `${referencia} existe` }]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const caminhos = [...grupos.keys()].sort((a, b) => Number(referencias.has(b)) - Number(referencias.has(a)) || a.split(".").length - b.split(".").length);
|
|
310
|
+
const ajustes = [];
|
|
311
|
+
for (const caminho of caminhos) {
|
|
312
|
+
const regras = grupos.get(caminho);
|
|
313
|
+
let tipo = task.output.find((campo) => campo.nome === caminho.split(".")[0])?.tipo ?? "Json";
|
|
314
|
+
for (const segmento of caminho.split(".").slice(1)) {
|
|
315
|
+
tipo = tiposDeclarados.get(tipo)?.get(segmento) ?? "Json";
|
|
316
|
+
}
|
|
317
|
+
const restricoes = regras.map((regra) => {
|
|
318
|
+
if (regra.tipo === "comparacao") {
|
|
319
|
+
return `[${JSON.stringify(regra.operador)}, ${formatarValorTypeScript(regra.valor, camposSaida, "saida", regra.valorLiteral)}]`;
|
|
320
|
+
}
|
|
321
|
+
if (regra.tipo === "pertencimento") {
|
|
322
|
+
const valores = (regra.valores ?? []).map((valor) => formatarValorTypeScript(valor, camposSaida, "saida", true));
|
|
323
|
+
return `["in", [${valores.join(", ")}]]`;
|
|
324
|
+
}
|
|
325
|
+
return '["existe", null]';
|
|
326
|
+
});
|
|
327
|
+
const caminhoLiteral = JSON.stringify(caminho.split("."));
|
|
328
|
+
const somenteExistencia = regras.every((regra) => regra.tipo === "existe");
|
|
329
|
+
const temDescendente = caminhos.some((outro) => outro.startsWith(`${caminho}.`));
|
|
330
|
+
if (somenteExistencia && temDescendente) {
|
|
331
|
+
ajustes.push(` if (semaLerSaida(saida, ${caminhoLiteral}) == null) semaDefinirSaida(saida, ${caminhoLiteral}, {});`);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const inteiro = tipo === "Inteiro" ? "true" : "false";
|
|
335
|
+
ajustes.push(` semaDefinirSaida(saida, ${caminhoLiteral}, semaSintetizarSaida(semaLerSaida(saida, ${caminhoLiteral}), [${restricoes.join(", ")}], ${inteiro}, ${sugerirValoresTypeScript(tipo, tiposDeclarados)}));`);
|
|
336
|
+
}
|
|
337
|
+
const helpers = grupos.size === 0 ? "" : `
|
|
338
|
+
const semaLerSaida = (objeto: any, caminho: string[]): any => caminho.reduce((atual, campo) => atual == null ? undefined : atual[campo], objeto);
|
|
339
|
+
const semaDefinirSaida = (objeto: any, caminho: string[], valor: any): void => {
|
|
340
|
+
let atual = objeto;
|
|
341
|
+
for (const campo of caminho.slice(0, -1)) {
|
|
342
|
+
if (atual[campo] === null || typeof atual[campo] !== "object") atual[campo] = {};
|
|
343
|
+
atual = atual[campo];
|
|
344
|
+
}
|
|
345
|
+
const ultimo = caminho[caminho.length - 1];
|
|
346
|
+
if (ultimo !== undefined) atual[ultimo] = valor;
|
|
347
|
+
};
|
|
348
|
+
const semaSintetizarSaida = (atual: any, restricoes: Array<[string, any]>, inteiro: boolean, sementes: any[]): any => {
|
|
349
|
+
const aceita = (valor: any): boolean => restricoes.every(([operador, limite]) => {
|
|
350
|
+
if (operador === "existe") return valor !== undefined && valor !== null;
|
|
351
|
+
if (operador === "in") return Array.isArray(limite) && limite.some((item: any) => Object.is(item, valor));
|
|
352
|
+
if (operador === "==") return valor === limite;
|
|
353
|
+
if (operador === "!=") return valor !== limite;
|
|
354
|
+
const esquerda: any = valor;
|
|
355
|
+
const direita: any = limite;
|
|
356
|
+
if (operador === ">") return esquerda > direita;
|
|
357
|
+
if (operador === ">=") return esquerda >= direita;
|
|
358
|
+
if (operador === "<") return esquerda < direita;
|
|
359
|
+
if (operador === "<=") return esquerda <= direita;
|
|
360
|
+
return false;
|
|
361
|
+
});
|
|
362
|
+
const candidatos: any[] = [atual, ...sementes];
|
|
363
|
+
const numeros: number[] = [];
|
|
364
|
+
for (const [operador, limite] of restricoes) {
|
|
365
|
+
if (operador === "in" && Array.isArray(limite)) candidatos.push(...limite);
|
|
366
|
+
else if (operador !== "existe") {
|
|
367
|
+
candidatos.push(limite);
|
|
368
|
+
if (typeof limite === "number" && Number.isFinite(limite)) {
|
|
369
|
+
numeros.push(limite);
|
|
370
|
+
candidatos.push(limite - 1, limite + 1, Math.floor(limite), Math.ceil(limite));
|
|
371
|
+
} else if (typeof limite === "string") {
|
|
372
|
+
candidatos.push(limite + "\\u0000", limite.slice(0, -1), "");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
numeros.sort((a, b) => a - b);
|
|
377
|
+
for (let indice = 0; indice < numeros.length - 1; indice += 1) {
|
|
378
|
+
candidatos.push((numeros[indice]! + numeros[indice + 1]!) / 2);
|
|
379
|
+
}
|
|
380
|
+
for (const candidato of candidatos) {
|
|
381
|
+
if (inteiro && (typeof candidato !== "number" || !Number.isInteger(candidato))) continue;
|
|
382
|
+
if (aceita(candidato)) return candidato;
|
|
383
|
+
}
|
|
384
|
+
throw new Error("Nao foi possivel sintetizar uma saida demonstrativa compativel com as garantias.");
|
|
385
|
+
};
|
|
386
|
+
`;
|
|
387
|
+
return ` const saida = {\n${linhas.join("\n")}\n } as ${task.nome}Saida;${helpers}\n${ajustes.join("\n")}`;
|
|
286
388
|
}
|
|
287
389
|
export function gerarValidacoes(task) {
|
|
288
390
|
const linhas = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IrModulo, IrTask } from "@sema/nucleo";
|
|
2
2
|
import { type ArquivoGerado } from "@sema/padroes";
|
|
3
3
|
export declare function gerarRotas(modulo: IrModulo): string;
|
|
4
|
-
export declare function gerarTask(task: IrTask): string;
|
|
4
|
+
export declare function gerarTask(task: IrTask, tiposDeclarados?: Map<string, Map<string, string>>): string;
|
|
5
5
|
export declare function gerarTestes(modulo: IrModulo): string;
|
|
6
6
|
export type TipoCabecalhoSemaTypeScript = "contrato" | "teste" | "dto" | "service" | "controller" | "spec";
|
|
7
7
|
export declare function gerarCabecalhoSemaTypeScript(modulo: IrModulo, tipo?: TipoCabecalhoSemaTypeScript): string;
|
|
@@ -68,7 +68,7 @@ ${verificacoesErro || " throw erro;"}
|
|
|
68
68
|
`;
|
|
69
69
|
}).join("\n");
|
|
70
70
|
}
|
|
71
|
-
export function gerarTask(task) {
|
|
71
|
+
export function gerarTask(task, tiposDeclarados = new Map()) {
|
|
72
72
|
const nomeSimbolo = normalizarNomeParaSimbolo(task.nome);
|
|
73
73
|
const entradaNome = `${nomeSimbolo}_entrada`;
|
|
74
74
|
const saidaNome = `${nomeSimbolo}_saida`;
|
|
@@ -130,7 +130,7 @@ ${task.stateContract ? ` // Vinculo de estado: ${task.stateContract.nomeEstado
|
|
|
130
130
|
${task.implementacoesExternas.length > 0 ? ` // Implementacoes externas vinculadas:\n${task.implementacoesExternas.map((impl) => ` // - ${impl.origem}: ${impl.caminho} [${impl.statusImpl ?? "nao_verificado"}]`).join("\n")}` : ""}
|
|
131
131
|
// Efeitos declarados:
|
|
132
132
|
${task.efeitosEstruturados.map((efeito) => ` // - categoria=${efeito.categoria} alvo=${efeito.alvo}${efeito.detalhe ? ` detalhe=${efeito.detalhe}` : ""}${efeito.criticidade ? ` criticidade=${efeito.criticidade}` : ""}`).join("\n") || task.effects.map((efeito) => ` // - ${efeito}`).join("\n") || " // - Nenhum efeito declarado."}
|
|
133
|
-
${gerarPreparacaoSaida(task)}
|
|
133
|
+
${gerarPreparacaoSaida(task, tiposDeclarados)}
|
|
134
134
|
${gerarGarantias(task)}
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -211,6 +211,13 @@ export function gerarCabecalhoSemaTypeScript(modulo, tipo = "contrato") {
|
|
|
211
211
|
}
|
|
212
212
|
export function gerarTypeScriptBase(modulo) {
|
|
213
213
|
const nomeBase = normalizarNomeModulo(modulo.nome).replace(/\./g, "_");
|
|
214
|
+
const mapaTiposDeclarados = new Map();
|
|
215
|
+
for (const entidade of modulo.entities) {
|
|
216
|
+
mapaTiposDeclarados.set(entidade.nome, new Map(entidade.campos.map((campo) => [campo.nome, campo.tipo])));
|
|
217
|
+
}
|
|
218
|
+
for (const tipo of modulo.types) {
|
|
219
|
+
mapaTiposDeclarados.set(tipo.nome, new Map(tipo.definicao.campos.map((campo) => [campo.nome, campo.tipo])));
|
|
220
|
+
}
|
|
214
221
|
const interoperabilidades = modulo.interoperabilidades
|
|
215
222
|
.map((interop) => `// Interop externo ${interop.origem}: ${interop.caminho}`)
|
|
216
223
|
.join("\n");
|
|
@@ -235,7 +242,7 @@ export function gerarTypeScriptBase(modulo) {
|
|
|
235
242
|
const routes = modulo.routes
|
|
236
243
|
.map((route) => `// Route ${route.nome}: metodo=${route.metodo ?? "nao_definido"} caminho=${route.caminho ?? "nao_definido"} task=${route.task ?? "nao_definida"} input_publico=${route.inputPublico.map((campo) => campo.nome).join(", ") || "padrao_task"} output_publico=${route.outputPublico.map((campo) => campo.nome).join(", ") || "padrao_task"} erros_publicos=${route.errosPublicos.map((erro) => erro.nome).join(", ") || "padrao_task"} effects_publicos=${route.efeitosPublicos.map((efeito) => `${efeito.categoria}:${efeito.alvo}`).join(", ") || "nenhum"} garantias_publicas=${route.garantiasPublicasMinimas.length}`)
|
|
237
244
|
.join("\n");
|
|
238
|
-
const tasks = modulo.tasks.map(gerarTask).join("\n");
|
|
245
|
+
const tasks = modulo.tasks.map((task) => gerarTask(task, mapaTiposDeclarados)).join("\n");
|
|
239
246
|
const contratosPublicos = gerarRotas(modulo);
|
|
240
247
|
const codigo = `${gerarCabecalhoSemaTypeScript(modulo)}${interoperabilidades ? `${interoperabilidades}\n` : ""}\n${tiposExternos}\n${tipos}\n${entidades}\n${enums}\n${states}\n${flows}\n${routes}\n${tasks}\n${contratosPublicos}\n`;
|
|
241
248
|
const testes = `${gerarCabecalhoSemaTypeScript(modulo, "teste")}${gerarTestes(modulo)}`;
|
|
@@ -8,6 +8,7 @@ import { validarFlow, validarInvariantesDeCampos, validarState } from "./analisa
|
|
|
8
8
|
import { validarStatusTextoComState, validarTask } from "./analisador.part07.js";
|
|
9
9
|
import { validarGuardrailsSeguranca, validarRoute } from "./analisador.part06.js";
|
|
10
10
|
import { validarDatabase } from "./analisador.part02.js";
|
|
11
|
+
import { validarTiposEstruturadosTestes } from "./testesTipos.js";
|
|
11
12
|
export function analisarSemantica(modulo, opcoes = {}) {
|
|
12
13
|
const diagnosticos = [];
|
|
13
14
|
const simbolos = new Map();
|
|
@@ -177,6 +178,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
|
|
|
177
178
|
for (const task of modulo.tasks) {
|
|
178
179
|
validarTask(task, tiposConhecidos, statesConhecidos, diagnosticos);
|
|
179
180
|
}
|
|
181
|
+
validarTiposEstruturadosTestes(modulo, diagnosticos);
|
|
180
182
|
for (const flow of modulo.flows) {
|
|
181
183
|
validarFlow(flow, tasksConhecidas, tarefasDetalhadas, diagnosticos);
|
|
182
184
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { criarDiagnostico } from "../diagnosticos/index.js";
|
|
2
|
+
export function validarTiposEstruturadosTestes(modulo, diagnosticos) {
|
|
3
|
+
const tipos = new Map();
|
|
4
|
+
for (const item of [...modulo.types, ...modulo.entities]) {
|
|
5
|
+
const fields = item.corpo.blocos.find(bloco => bloco.tipo === "bloco_generico" && bloco.palavraChave === "fields");
|
|
6
|
+
tipos.set(item.nome, fields?.tipo === "bloco_generico" ? fields.campos : item.corpo.campos);
|
|
7
|
+
}
|
|
8
|
+
function conferir(bloco, campos, contexto) {
|
|
9
|
+
for (const valor of bloco.campos) {
|
|
10
|
+
const declarado = campos.find(campo => campo.nome === valor.nome);
|
|
11
|
+
if (!declarado || !tipos.has(declarado.valor))
|
|
12
|
+
continue;
|
|
13
|
+
if (valor.valor === "nulo" && !declarado.modificadores.includes("required"))
|
|
14
|
+
continue;
|
|
15
|
+
diagnosticos.push(criarDiagnostico("SEM125", `${contexto}: "${valor.nome}" exige ${declarado.valor}, mas recebeu literal escalar; caso não executável.`, "aviso", valor.intervalo, `Use ${valor.nome} { ... } com os campos de ${declarado.valor}; texto descritivo não é uma entidade.`));
|
|
16
|
+
}
|
|
17
|
+
for (const filho of bloco.blocos) {
|
|
18
|
+
if (filho.tipo !== "bloco_generico")
|
|
19
|
+
continue;
|
|
20
|
+
const tipo = campos.find(campo => campo.nome === filho.nome)?.valor;
|
|
21
|
+
if (tipo && tipos.has(tipo))
|
|
22
|
+
conferir(filho, tipos.get(tipo), `${contexto}.${filho.nome}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const task of modulo.tasks)
|
|
26
|
+
for (const caso of task.tests?.blocos ?? []) {
|
|
27
|
+
if (caso.tipo !== "caso_teste")
|
|
28
|
+
continue;
|
|
29
|
+
if (caso.given)
|
|
30
|
+
conferir(caso.given, task.input?.campos ?? [], `given do caso "${caso.nome}" da task "${task.nome}"`);
|
|
31
|
+
if (caso.expect)
|
|
32
|
+
conferir(caso.expect, task.output?.campos ?? [], `expect do caso "${caso.nome}" da task "${task.nome}"`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=testesTipos.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semacode/cli",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "Local-first semantic governance CLI for AI agents, proven with Claude, Codex, zCode (GLM) and Kimi: contracts, drift, impact, docs gates, and code generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "logo.png",
|