codexivo 0.0.0-stage → 0.2.3

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.
@@ -0,0 +1,159 @@
1
+ # Recetario de programas Codexivo
2
+
3
+ Esta colección de programas ilustra patrones útiles del lenguaje. Cada ejemplo está listo para copiarse en el REPL o en un
4
+ archivo `.codexivo`.
5
+
6
+ ## 1. Hola mundo personalizado
7
+
8
+ ```codexivo
9
+ variable saludar = procedimiento(nombre) {
10
+ regresa "Hola " + nombre + "!"
11
+ }
12
+
13
+ saludar("Codexivo")
14
+ ```
15
+
16
+ Demuestra cómo declarar un procedimiento, concatenar cadenas y retornar un valor.
17
+
18
+ ## 2. Clasificador por edades
19
+
20
+ ```codexivo
21
+ variable clasificar = procedimiento(edad) {
22
+ si (edad es_mayor_o_igual_a 18) {
23
+ regresa "adulto"
24
+ } pero_si (edad es_mayor_o_igual_a 13) {
25
+ regresa "adolescente"
26
+ } si_no {
27
+ regresa "niño"
28
+ }
29
+ }
30
+
31
+ clasificar(16)
32
+ ```
33
+
34
+ Ilustra el encadenamiento `si`/`pero_si`/`si_no` y el uso de operadores relacionales.
35
+
36
+ ## 3. Promedio simple
37
+
38
+ ```codexivo
39
+ variable promedio = procedimiento(a, b, c) {
40
+ variable total = a + b + c
41
+ regresa total / 3
42
+ }
43
+
44
+ promedio(8, 7.5, 9)
45
+ ```
46
+
47
+ Combina números enteros y decimales con operaciones aritméticas.
48
+
49
+ ## 4. Valor absoluto
50
+
51
+ ```codexivo
52
+ variable valorAbsoluto = procedimiento(n) {
53
+ si (n es_menor_que 0) {
54
+ regresa -n
55
+ } si_no {
56
+ regresa n
57
+ }
58
+ }
59
+
60
+ valorAbsoluto(-42)
61
+ ```
62
+
63
+ Ejemplo de uso del operador prefijo `-`.
64
+
65
+ ## 5. Contador recursivo
66
+
67
+ ```codexivo
68
+ variable cuentaRegresiva = procedimiento(n) {
69
+ si (n es_igual_a 0) {
70
+ regresa "¡Despegue!"
71
+ }
72
+
73
+ regresa cuentaRegresiva(n - 1)
74
+ }
75
+
76
+ cuentaRegresiva(3)
77
+ ```
78
+
79
+ Muestra cómo las funciones en Codexivo soportan recursión gracias a que conservan su entorno de definición.
80
+
81
+ ## 6. Validación de longitud
82
+
83
+ ```codexivo
84
+ variable esNombreValido = procedimiento(nombre) {
85
+ variable tamaño = longitud(nombre)
86
+ si (tamaño es_mayor_o_igual_a 3 y tamaño es_menor_o_igual_a 15) {
87
+ regresa verdadero
88
+ }
89
+ regresa falso
90
+ }
91
+
92
+ esNombreValido("Ana")
93
+ ```
94
+
95
+ Ejemplo de uso del builtin `longitud` junto con operadores de comparación y booleanos.
96
+
97
+ ## 7. Suma con un bucle que cuenta
98
+
99
+ ```codexivo
100
+ procedimiento sumar_hasta(n) {
101
+ variable suma = 0
102
+ para i desde 1 hasta n {
103
+ suma = suma + i
104
+ }
105
+ regresa suma
106
+ }
107
+
108
+ sumar_hasta(10) // Suma de 1 a 10 = 55
109
+ ```
110
+
111
+ Demuestra `para … desde … hasta`, que incluye los dos extremos.
112
+
113
+ ## 8. Búsqueda en arreglo
114
+
115
+ ```codexivo
116
+ procedimiento buscar_posición(lista, elemento) {
117
+ para_cada i en índices(lista) {
118
+ si (lista[i] es_igual_a elemento) {
119
+ regresa i
120
+ }
121
+ }
122
+ regresa -1
123
+ }
124
+
125
+ variable números = [10, 20, 30, 40]
126
+ buscar_posición(números, 30) // Regresa 2
127
+ ```
128
+
129
+ Combina bucles, acceso a arreglos, condicionales y funciones built-in.
130
+
131
+ ## 9. Trabajo con funciones matemáticas
132
+
133
+ ```codexivo
134
+ variable estadisticas = procedimiento(datos) {
135
+ variable suma = 0
136
+ variable mayor = primero(datos)
137
+ variable menor = primero(datos)
138
+
139
+ para_cada dato en datos {
140
+ suma = suma + dato
141
+ si (dato es_mayor_que mayor) {
142
+ mayor = dato
143
+ }
144
+ si (dato es_menor_que menor) {
145
+ menor = dato
146
+ }
147
+ }
148
+ variable promedio = suma / longitud(datos)
149
+
150
+ regresa [redondear(promedio), mayor, menor]
151
+ }
152
+
153
+ estadisticas([5, 2, 8, 1, 9]) // [5, 9, 1]
154
+ ```
155
+
156
+ Muestra el uso de las funciones matemáticas built-in y manipulación de arreglos.
157
+
158
+ Todos los ejemplos de este recetario se ejecutan con la versión actual del intérprete. Consulta [qué funciona hoy](./lenguaje.md)
159
+ y [hacia dónde va el lenguaje](./especificacion-v1.md).
package/docs/repl.md ADDED
@@ -0,0 +1,64 @@
1
+ # REPL y ejecución desde la terminal
2
+
3
+ Instala el principal con `npm install -g codexivo` o `bun add -g --trust codexivo`. El gestor selecciona el paquete nativo
4
+ compatible mediante `os`, `cpu` y `libc`; `postinstall` verifica la versión y coloca el ejecutable en la ruta `bin` del
5
+ principal. Una vez instalado, `codexivo` abre el REPL y `codexivo programa.cdx` ejecuta archivos sin necesitar Node.js ni Bun.
6
+
7
+ Si omites scripts, la biblioteca sigue disponible; para preparar el CLI tras una instalación local, ejecuta
8
+ `node node_modules/codexivo/postinstall.mjs` una vez. Los ejecutables de
9
+ [Releases](https://github.com/UrielCuriel/codexivo/releases) funcionan directamente sin gestores ni runtimes externos.
10
+ En Linux/macOS, aplica `chmod +x` al ejecutable descargado. Se admiten x64 y ARM64 en Linux, macOS y Windows; para Alpine
11
+ Linux elige la variante `musl`.
12
+
13
+ ## REPL
14
+
15
+ ```sh
16
+ bun install
17
+ bun run repl
18
+ ```
19
+
20
+ Escribe código y presiona Enter. Las variables se conservan entre entradas. Si una línea deja un bloque `{` abierto, el REPL
21
+ sigue leyendo (con el prompt `..`) hasta que lo cierres. Para salir escribe `salir`.
22
+
23
+ ```
24
+ >> variable saludar = procedimiento(nombre) { regresa "Hola, " + nombre; };
25
+ >> saludar("Codexivo")
26
+ "Hola, Codexivo"
27
+ >> si (verdadero) {
28
+ .. imprimir("dentro del bloque")
29
+ .. }
30
+ dentro del bloque
31
+ >> salir
32
+ ¡Adiós!
33
+ ```
34
+
35
+ En el REPL se puede volver a declarar una variable (`variable x = …` dos veces) para experimentar; en un programa normal es un
36
+ error.
37
+
38
+ ## Ejecutar un archivo
39
+
40
+ ```sh
41
+ bun run src/cli.ts programa.cdx
42
+ ```
43
+
44
+ Lo que llegue por la entrada estándar alimenta a `leer()` y `leer_número()`:
45
+
46
+ ```sh
47
+ echo 21 | bun run src/cli.ts doble.cdx
48
+ ```
49
+
50
+ ## Ver la ejecución desde código
51
+
52
+ ```ts
53
+ import { prepare, run } from 'codexivo';
54
+
55
+ // Todos los eventos de una ejecución
56
+ const { events } = run('variable x = 1; x += 1;', { trace: true });
57
+
58
+ // Paso a paso
59
+ const { machine } = prepare('variable x = 1;\nx += 1;');
60
+ while (machine && !machine.done) {
61
+ machine.step();
62
+ console.log(machine.snapshot().span?.start.line, machine.snapshot().frames[0]?.scopes);
63
+ }
64
+ ```
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "codexivo",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.3",
4
+ "description": "Lenguaje de programación en español para practicar lógica y algoritmos, que se ejecuta en el navegador.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "src/index.ts",
9
+ "files": [
10
+ "dist/index.js",
11
+ "dist/index.js.map",
12
+ "dist/worker.js",
13
+ "dist/worker.js.map",
14
+ "src",
15
+ "!src/cli.ts",
16
+ "docs",
17
+ "CHANGELOG.md"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./src/index.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./worker": {
25
+ "types": "./src/worker.ts",
26
+ "import": "./dist/worker.js"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "test": "bun test",
31
+ "check": "bun check",
32
+ "repl": "bun run src/cli.ts",
33
+ "build": "bun run build:lib && bun run build:binary",
34
+ "build:lib": "bun build ./src/index.ts ./src/worker.ts --target browser --format esm --outdir=dist --minify --sourcemap=linked",
35
+ "build:binary": "bun run scripts/build-binaries.ts",
36
+ "build:binaries": "bun run scripts/build-binaries.ts --all",
37
+ "test:package": "bun run scripts/test-package.ts",
38
+ "docs:biblioteca": "bun run scripts/docs-biblioteca.ts",
39
+ "prepack": "bun run build:lib",
40
+ "version": "bun run scripts/sync-native-versions.ts && git add package.json bun.lock packages/*/package.json",
41
+ "publish:native": "bun run scripts/publish-native.ts",
42
+ "format": "bun fmt",
43
+ "format:check": "bun fmt --check"
44
+ },
45
+ "keywords": [
46
+ "lenguaje de programación",
47
+ "español",
48
+ "educación",
49
+ "intérprete"
50
+ ],
51
+ "author": "Uriel Curiel",
52
+ "license": "ISC",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/UrielCuriel/codexivo.git"
56
+ },
57
+ "devDependencies": {
58
+ "@types/bun": "^1.4.3"
59
+ },
60
+ "packageManager": "bun@1.4.4",
61
+ "sideEffects": false,
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "workspaces": [
66
+ "packages/*"
67
+ ]
68
+ }
package/src/catalog.ts ADDED
@@ -0,0 +1,256 @@
1
+ import { ERROR_CODES } from './error-codes';
2
+ import type { ErrorKind } from './errors';
3
+ import { constantDocs, constants, natives, withoutAccents } from './runtime/builtins';
4
+ import { libraryDocs } from './runtime/prelude';
5
+ import type { ProcedureCategory, ProcedureDoc, ProcedureLevel } from './runtime/values';
6
+ import { KEYWORDS, TokenType } from './syntax/token';
7
+ import { EXAMPLE_WORLD, worldDocs } from './world';
8
+ import type { WorldDefinition } from './world';
9
+
10
+ /**
11
+ * El lenguaje y su biblioteca como datos, para que el resaltado, el autocompletado, la documentación y los
12
+ * avisos del editor del sitio salgan de una sola fuente. Todo es serializable: no hay funciones.
13
+ */
14
+
15
+ export type KeywordGroup = 'declaración' | 'control' | 'lógicas' | 'literales' | 'operadores';
16
+
17
+ export interface OperatorEntry {
18
+ symbol?: string;
19
+ /** Forma en palabras (`es_igual_a`, `mod`), si la tiene. */
20
+ word?: string;
21
+ /** Nivel de precedencia de la §4.1: 1 es el que agrupa al final, 9 el primero. */
22
+ precedence: number;
23
+ associativity: 'izquierda' | 'derecha' | 'prefijo' | 'sin encadenar';
24
+ description: string;
25
+ }
26
+
27
+ export interface ProcedureEntry extends ProcedureDoc {
28
+ /** Nombre canónico, con acento si lo lleva. */
29
+ name: string;
30
+ /** Otras formas aceptadas (el nombre sin acento). */
31
+ aliases: string[];
32
+ }
33
+
34
+ export interface ConstantEntry {
35
+ name: string;
36
+ value: number;
37
+ level: ProcedureLevel;
38
+ description: string;
39
+ }
40
+
41
+ export interface ErrorEntry {
42
+ code: string;
43
+ kind: ErrorKind;
44
+ explanation: string;
45
+ }
46
+
47
+ export interface Catalog {
48
+ version: 1;
49
+ keywords: Array<{ group: KeywordGroup; words: string[] }>;
50
+ operators: OperatorEntry[];
51
+ /** Procedimientos de la biblioteca, incluidos los del modo mundo (categoría `mundo`). */
52
+ library: ProcedureEntry[];
53
+ constants: ConstantEntry[];
54
+ errors: ErrorEntry[];
55
+ categories: ProcedureCategory[];
56
+ /** Mundo con el que se ejecutan los ejemplos de la categoría `mundo`. */
57
+ exampleWorld: WorldDefinition;
58
+ }
59
+
60
+ const KEYWORD_GROUPS: Readonly<Record<string, KeywordGroup>> = {
61
+ [TokenType.LET]: 'declaración',
62
+ [TokenType.CONST]: 'declaración',
63
+ [TokenType.DOMAIN]: 'declaración',
64
+ [TokenType.FUNCTION]: 'declaración',
65
+ [TokenType.RETURN]: 'declaración',
66
+ [TokenType.IF]: 'control',
67
+ [TokenType.ELSEIF]: 'control',
68
+ [TokenType.ELSE]: 'control',
69
+ [TokenType.WHILE]: 'control',
70
+ [TokenType.DO]: 'control',
71
+ [TokenType.UNTIL]: 'control',
72
+ [TokenType.FOR]: 'control',
73
+ [TokenType.FOR_EACH]: 'control',
74
+ [TokenType.FROM]: 'control',
75
+ [TokenType.TO]: 'control',
76
+ [TokenType.STEP]: 'control',
77
+ [TokenType.IN]: 'control',
78
+ [TokenType.REPEAT]: 'control',
79
+ [TokenType.TIMES]: 'control',
80
+ [TokenType.BREAK]: 'control',
81
+ [TokenType.CONTINUE]: 'control',
82
+ [TokenType.AND]: 'lógicas',
83
+ [TokenType.OR]: 'lógicas',
84
+ [TokenType.NOT]: 'lógicas',
85
+ [TokenType.TRUE]: 'literales',
86
+ [TokenType.FALSE]: 'literales',
87
+ [TokenType.NULL]: 'literales',
88
+ [TokenType.MOD]: 'operadores',
89
+ [TokenType.DIV]: 'operadores',
90
+ [TokenType.EQ]: 'operadores',
91
+ [TokenType.NEQ]: 'operadores',
92
+ [TokenType.LT]: 'operadores',
93
+ [TokenType.GT]: 'operadores',
94
+ [TokenType.LTE]: 'operadores',
95
+ [TokenType.GTE]: 'operadores',
96
+ };
97
+
98
+ const GROUP_ORDER: KeywordGroup[] = ['declaración', 'control', 'lógicas', 'literales', 'operadores'];
99
+
100
+ /** Grupo de cada palabra reservada; una palabra sin grupo es un error del catálogo (lo revisa una prueba). */
101
+ export const keywordGroup = (word: string): KeywordGroup | undefined => KEYWORD_GROUPS[KEYWORDS[word] ?? ''];
102
+
103
+ const keywords = GROUP_ORDER.map(group => ({
104
+ group,
105
+ words: Object.keys(KEYWORDS).filter(word => keywordGroup(word) === group),
106
+ }));
107
+
108
+ const operators: OperatorEntry[] = [
109
+ {
110
+ word: 'o',
111
+ precedence: 1,
112
+ associativity: 'izquierda',
113
+ description: 'Verdadero si alguno de los dos lo es. Si el izquierdo es verdadero, no evalúa el derecho.',
114
+ },
115
+ {
116
+ word: 'y',
117
+ precedence: 2,
118
+ associativity: 'izquierda',
119
+ description: 'Verdadero si los dos lo son. Si el izquierdo es falso, no evalúa el derecho.',
120
+ },
121
+ {
122
+ symbol: '!',
123
+ word: 'no',
124
+ precedence: 3,
125
+ associativity: 'prefijo',
126
+ description: 'Cambia verdadero por falso y al revés.',
127
+ },
128
+ {
129
+ symbol: '==',
130
+ word: 'es_igual_a',
131
+ precedence: 4,
132
+ associativity: 'sin encadenar',
133
+ description: 'Igualdad por valor, también de listas y diccionarios.',
134
+ },
135
+ {
136
+ symbol: '!=',
137
+ word: 'es_distinto_de',
138
+ precedence: 4,
139
+ associativity: 'sin encadenar',
140
+ description: 'Lo contrario de `==`.',
141
+ },
142
+ {
143
+ symbol: '<',
144
+ word: 'es_menor_que',
145
+ precedence: 4,
146
+ associativity: 'sin encadenar',
147
+ description: 'Compara dos números o dos textos.',
148
+ },
149
+ {
150
+ symbol: '>',
151
+ word: 'es_mayor_que',
152
+ precedence: 4,
153
+ associativity: 'sin encadenar',
154
+ description: 'Compara dos números o dos textos.',
155
+ },
156
+ {
157
+ symbol: '<=',
158
+ word: 'es_menor_o_igual_a',
159
+ precedence: 4,
160
+ associativity: 'sin encadenar',
161
+ description: 'Compara dos números o dos textos.',
162
+ },
163
+ {
164
+ symbol: '>=',
165
+ word: 'es_mayor_o_igual_a',
166
+ precedence: 4,
167
+ associativity: 'sin encadenar',
168
+ description: 'Compara dos números o dos textos.',
169
+ },
170
+ {
171
+ symbol: '+',
172
+ precedence: 5,
173
+ associativity: 'izquierda',
174
+ description: 'Suma dos números o une dos textos; no los mezcla.',
175
+ },
176
+ { symbol: '-', precedence: 5, associativity: 'izquierda', description: 'Resta.' },
177
+ { symbol: '*', precedence: 6, associativity: 'izquierda', description: 'Multiplica.' },
178
+ { symbol: '/', precedence: 6, associativity: 'izquierda', description: 'División real: `7 / 2` es `3.5`.' },
179
+ {
180
+ word: 'div',
181
+ precedence: 6,
182
+ associativity: 'izquierda',
183
+ description: 'División entera hacia abajo: `-7 div 2` es `-4`.',
184
+ },
185
+ {
186
+ symbol: '%',
187
+ word: 'mod',
188
+ precedence: 6,
189
+ associativity: 'izquierda',
190
+ description: 'Residuo consistente con `div`: `-7 mod 3` es `2`.',
191
+ },
192
+ { symbol: '-', precedence: 7, associativity: 'prefijo', description: 'Cambia el signo.' },
193
+ {
194
+ symbol: '^',
195
+ precedence: 8,
196
+ associativity: 'derecha',
197
+ description: 'Potencia: `2 ^ 3 ^ 2` es `2 ^ 9`, y `-2 ^ 2` es `-4`.',
198
+ },
199
+ { symbol: '( )', precedence: 9, associativity: 'izquierda', description: 'Llama a un procedimiento: `f(x)`.' },
200
+ {
201
+ symbol: '[ ]',
202
+ precedence: 9,
203
+ associativity: 'izquierda',
204
+ description: 'Elemento de una lista o texto, o clave de un diccionario: `l[0]`, `d["a"]`.',
205
+ },
206
+ {
207
+ symbol: '.',
208
+ precedence: 9,
209
+ associativity: 'izquierda',
210
+ description: 'Campo de un diccionario: `persona.edad` es `persona["edad"]`.',
211
+ },
212
+ ];
213
+
214
+ const entry = (name: string, doc: ProcedureDoc): ProcedureEntry => {
215
+ const alias = withoutAccents(name);
216
+ return { name, aliases: alias === name ? [] : [alias], ...doc };
217
+ };
218
+
219
+ const library: ProcedureEntry[] = [
220
+ ...Object.values(natives)
221
+ .filter((native, index, all) => all.indexOf(native) === index)
222
+ .map(native => entry(native.name, native.doc!)),
223
+ ...Object.entries(libraryDocs).map(([name, doc]) => entry(name, doc)),
224
+ ...Object.entries(worldDocs).map(([name, doc]) => entry(name, doc)),
225
+ ];
226
+
227
+ const CATEGORIES: ProcedureCategory[] = [
228
+ 'entrada y salida',
229
+ 'tipos',
230
+ 'matemáticas',
231
+ 'texto',
232
+ 'listas',
233
+ 'diccionarios',
234
+ 'procedimientos',
235
+ 'estructuras',
236
+ 'mundo',
237
+ ];
238
+
239
+ export const catalog: Catalog = {
240
+ version: 1,
241
+ keywords,
242
+ operators,
243
+ library: library.sort(
244
+ (a, b) => CATEGORIES.indexOf(a.category) - CATEGORIES.indexOf(b.category) || a.name.localeCompare(b.name, 'es'),
245
+ ),
246
+ constants: Object.entries(constantDocs).map(([name, doc]) => ({
247
+ name,
248
+ value: constants[name] as number,
249
+ ...doc,
250
+ })),
251
+ errors: Object.entries(ERROR_CODES)
252
+ .map(([code, info]) => ({ code, ...info }))
253
+ .sort((a, b) => a.code.localeCompare(b.code, 'es')),
254
+ categories: CATEGORIES,
255
+ exampleWorld: EXAMPLE_WORLD,
256
+ };
@@ -0,0 +1,138 @@
1
+ import { run } from './engine';
2
+ import type { ErrorInfo } from './errors';
3
+ import { equals, repr } from './runtime/values';
4
+ import type { DictKey, Value } from './runtime/values';
5
+
6
+ /**
7
+ * Retos: un conjunto de casos que se ejecutan con su propia entrada y su propio límite de operaciones. Corre
8
+ * igual en el worker del sitio y en Bun, así los retos se prueban en CI.
9
+ *
10
+ * - Estilo `salida` (como HackerRank): el programa lee con `leer()` y se compara lo que imprime.
11
+ * - Estilo `regresa` (como LeetCode): se llama a `resolver(…)` (u otro procedimiento) con argumentos y se compara
12
+ * lo que regresa, por valor.
13
+ */
14
+
15
+ /** Un valor de JSON: los arreglos se vuelven listas y los objetos, diccionarios. */
16
+ export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
17
+
18
+ export interface ChallengeCase {
19
+ name?: string;
20
+ /** Entrada estándar: un texto (una línea por renglón) o una lista de líneas. */
21
+ input?: string | string[];
22
+ /** Estilo `salida`: lo que el programa tiene que imprimir. */
23
+ expectedOutput?: string;
24
+ /** Estilo `regresa`: los argumentos para el procedimiento. */
25
+ args?: JsonValue[];
26
+ /** Estilo `regresa`: lo que tiene que regresar. */
27
+ expected?: JsonValue;
28
+ /** Límite de operaciones de este caso; si no, el del reto. */
29
+ maxOperations?: number;
30
+ }
31
+
32
+ export interface Challenge {
33
+ style: 'salida' | 'regresa';
34
+ cases: ChallengeCase[];
35
+ /** Estilo `regresa`: el procedimiento que se llama. Por defecto `resolver`. */
36
+ procedure?: string;
37
+ /** Límite de operaciones por caso. Por defecto 1 000 000. */
38
+ maxOperations?: number;
39
+ /**
40
+ * Con `true`, la salida tiene que ser idéntica. Si no, se ignoran los espacios al final de cada línea y las
41
+ * líneas vacías del final.
42
+ */
43
+ exactOutput?: boolean;
44
+ }
45
+
46
+ export interface CaseResult {
47
+ name: string;
48
+ passed: boolean;
49
+ output: string;
50
+ expectedOutput?: string;
51
+ /** Estilo `regresa`: lo que regresó y lo que se esperaba, como se escribirían en código. */
52
+ result?: string;
53
+ expected?: string;
54
+ operations: number;
55
+ error?: ErrorInfo;
56
+ }
57
+
58
+ export interface ChallengeResult {
59
+ passed: boolean;
60
+ cases: CaseResult[];
61
+ }
62
+
63
+ const DEFAULT_MAX_OPERATIONS = 1_000_000;
64
+
65
+ /** Un valor de JSON escrito como literal de Codexivo. */
66
+ const literal = (value: JsonValue): string => {
67
+ if (value === null) return 'nulo';
68
+ if (typeof value === 'boolean') return value ? 'verdadero' : 'falso';
69
+ if (typeof value === 'number') {
70
+ if (!Number.isFinite(value)) throw new Error(`El reto tiene un número que no es finito: ${value}.`);
71
+ return value < 0 ? `(${value})` : String(value);
72
+ }
73
+ if (typeof value === 'string') return JSON.stringify(value);
74
+ if (Array.isArray(value)) return `[${value.map(literal).join(', ')}]`;
75
+ return `{ ${Object.entries(value)
76
+ .map(([key, item]) => `${JSON.stringify(key)}: ${literal(item)}`)
77
+ .join(', ')} }`;
78
+ };
79
+
80
+ /** Un valor de JSON como valor de Codexivo, para comparar por valor con `equals`. */
81
+ const toValue = (value: JsonValue): Value => {
82
+ if (value === null || typeof value !== 'object') return value;
83
+ if (Array.isArray(value)) return { kind: 'lista', id: 0, items: value.map(toValue) };
84
+ return {
85
+ kind: 'diccionario',
86
+ id: 0,
87
+ entries: new Map<DictKey, Value>(Object.entries(value).map(([key, item]) => [key, toValue(item)])),
88
+ };
89
+ };
90
+
91
+ const normalize = (text: string): string =>
92
+ text
93
+ .replace(/\r\n/g, '\n')
94
+ .split('\n')
95
+ .map(line => line.replace(/[ \t]+$/, ''))
96
+ .join('\n')
97
+ .replace(/\n+$/, '');
98
+
99
+ /** Ejecuta cada caso del reto con el código de quien lo resuelve. */
100
+ export const evaluarReto = (source: string, challenge: Challenge): ChallengeResult => {
101
+ const procedure = challenge.procedure ?? 'resolver';
102
+ const cases = challenge.cases.map((testCase, index): CaseResult => {
103
+ const name = testCase.name ?? `Caso ${index + 1}`;
104
+ const maxOperations = testCase.maxOperations ?? challenge.maxOperations ?? DEFAULT_MAX_OPERATIONS;
105
+ const input = testCase.input ?? [];
106
+
107
+ if (challenge.style === 'salida') {
108
+ const result = run(source, { input, maxOperations });
109
+ const expectedOutput = testCase.expectedOutput ?? '';
110
+ const same = challenge.exactOutput
111
+ ? result.output === expectedOutput
112
+ : normalize(result.output) === normalize(expectedOutput);
113
+ return {
114
+ name,
115
+ passed: result.ok && same,
116
+ output: result.output,
117
+ expectedOutput,
118
+ operations: result.operations,
119
+ ...(result.errors[0] ? { error: result.errors[0] } : {}),
120
+ };
121
+ }
122
+
123
+ // La llamada va al final, en su propia línea, para que los errores del programa conserven su lugar.
124
+ const call = `\n;\nregresa ${procedure}(${(testCase.args ?? []).map(literal).join(', ')});\n`;
125
+ const result = run(source + call, { input, maxOperations });
126
+ const expected = toValue(testCase.expected ?? null);
127
+ return {
128
+ name,
129
+ passed: result.ok && equals(result.result, expected),
130
+ output: result.output,
131
+ result: result.resultText,
132
+ expected: repr(expected),
133
+ operations: result.operations,
134
+ ...(result.errors[0] ? { error: result.errors[0] } : {}),
135
+ };
136
+ });
137
+ return { passed: cases.every(testCase => testCase.passed), cases };
138
+ };