codexivo 0.2.0 → 1.0.0
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/CHANGELOG.md +37 -1
- package/README.md +26 -25
- package/dist/index.js +153 -9
- package/dist/index.js.map +22 -12
- package/dist/worker.js +155 -0
- package/dist/worker.js.map +25 -0
- package/docs/README.md +2 -0
- package/docs/arquitectura.md +86 -4
- package/docs/especificacion-v1.md +211 -100
- package/docs/guia-aprender-desde-el-codigo.md +5 -5
- package/docs/integracion.md +104 -0
- package/docs/lenguaje.md +449 -60
- package/docs/modo-mundo.md +128 -0
- package/docs/recetario.md +63 -63
- package/package.json +11 -20
- package/src/catalog.ts +257 -0
- package/src/challenge.ts +138 -0
- package/src/engine.ts +7 -0
- package/src/error-codes.ts +432 -0
- package/src/flow.ts +385 -0
- package/src/index.ts +53 -1
- package/src/printer.ts +133 -0
- package/src/runtime/builtins.ts +1402 -65
- package/src/runtime/compiler.ts +278 -31
- package/src/runtime/costs.ts +96 -0
- package/src/runtime/machine.ts +722 -48
- package/src/runtime/prelude.ts +230 -0
- package/src/runtime/values.ts +120 -18
- package/src/sandbox-host.ts +310 -0
- package/src/sandbox.ts +95 -0
- package/src/syntax/ast.ts +120 -6
- package/src/syntax/lexer.ts +39 -5
- package/src/syntax/parser.ts +496 -72
- package/src/syntax/token.ts +49 -1
- package/src/worker.ts +18 -0
- package/src/world.ts +518 -0
- package/bin/codexivo.exe +0 -1
- package/postinstall.mjs +0 -38
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { parse } from '../syntax/parser';
|
|
2
|
+
import { compileProcedure } from './compiler';
|
|
3
|
+
import { COSTS } from './costs';
|
|
4
|
+
import type { Chunk } from './compiler';
|
|
5
|
+
import { Scope } from './values';
|
|
6
|
+
import type { ProcedureDoc, ProcedureValue } from './values';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Los procedimientos de la biblioteca que reciben procedimientos están escritos en Codexivo. Así, cuando
|
|
10
|
+
* llaman a `f`, la llamada pasa por la máquina como cualquier otra: se puede pausar dentro de `f`, cuenta
|
|
11
|
+
* para el límite de llamadas, emite sus eventos y sus operaciones se cuentan igual que siempre.
|
|
12
|
+
*
|
|
13
|
+
* Su propio código se compila como oculto (`Chunk.hidden`): no pausa ni emite pasos, y en lugar de contar
|
|
14
|
+
* cada instrucción se cobra lo que dice `costs.ts`. Los `revisar_*` y `ordenar_natural` son nativos internos
|
|
15
|
+
* que solo se ven desde aquí (`internals` en `builtins.ts`).
|
|
16
|
+
*/
|
|
17
|
+
const SOURCE = `
|
|
18
|
+
procedimiento mapear(lista, f) {
|
|
19
|
+
revisar_lista("mapear", lista);
|
|
20
|
+
revisar_procedimiento("mapear", f);
|
|
21
|
+
variable resultado = [];
|
|
22
|
+
para_cada elemento en lista {
|
|
23
|
+
agregar(resultado, f(elemento));
|
|
24
|
+
}
|
|
25
|
+
regresa resultado;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
procedimiento filtrar(lista, f) {
|
|
29
|
+
revisar_lista("filtrar", lista);
|
|
30
|
+
revisar_procedimiento("filtrar", f);
|
|
31
|
+
variable resultado = [];
|
|
32
|
+
para_cada elemento en lista {
|
|
33
|
+
si (revisar_logico("filtrar", f(elemento))) {
|
|
34
|
+
agregar(resultado, elemento);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
regresa resultado;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
procedimiento reducir(lista, f, inicial) {
|
|
41
|
+
revisar_lista("reducir", lista);
|
|
42
|
+
revisar_procedimiento("reducir", f);
|
|
43
|
+
variable acumulado = inicial;
|
|
44
|
+
para_cada elemento en lista {
|
|
45
|
+
acumulado = f(acumulado, elemento);
|
|
46
|
+
}
|
|
47
|
+
regresa acumulado;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
procedimiento alguno(lista, f) {
|
|
51
|
+
revisar_lista("alguno", lista);
|
|
52
|
+
revisar_procedimiento("alguno", f);
|
|
53
|
+
para_cada elemento en lista {
|
|
54
|
+
si (revisar_logico("alguno", f(elemento))) {
|
|
55
|
+
regresa verdadero;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
regresa falso;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
procedimiento todos(lista, f) {
|
|
62
|
+
revisar_lista("todos", lista);
|
|
63
|
+
revisar_procedimiento("todos", f);
|
|
64
|
+
para_cada elemento en lista {
|
|
65
|
+
si (no revisar_logico("todos", f(elemento))) {
|
|
66
|
+
regresa falso;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
regresa verdadero;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
procedimiento buscar(lista, f) {
|
|
73
|
+
revisar_lista("buscar", lista);
|
|
74
|
+
revisar_procedimiento("buscar", f);
|
|
75
|
+
para_cada elemento en lista {
|
|
76
|
+
si (revisar_logico("buscar", f(elemento))) {
|
|
77
|
+
regresa elemento;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
regresa nulo;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
procedimiento ordenar(lista, va_antes) {
|
|
84
|
+
revisar_lista("ordenar", lista);
|
|
85
|
+
si (va_antes == nulo) {
|
|
86
|
+
regresa ordenar_natural(lista);
|
|
87
|
+
}
|
|
88
|
+
revisar_procedimiento("ordenar", va_antes);
|
|
89
|
+
|
|
90
|
+
// Ordenamiento por mezcla: estable, porque solo adelanta un elemento de la derecha si va antes.
|
|
91
|
+
procedimiento mezclar(parte) {
|
|
92
|
+
si (longitud(parte) <= 1) {
|
|
93
|
+
regresa copiar(parte);
|
|
94
|
+
}
|
|
95
|
+
variable medio = longitud(parte) div 2;
|
|
96
|
+
variable izquierda = mezclar(rebanar(parte, 0, medio));
|
|
97
|
+
variable derecha = mezclar(rebanar(parte, medio, longitud(parte)));
|
|
98
|
+
variable resultado = [];
|
|
99
|
+
variable i = 0;
|
|
100
|
+
variable j = 0;
|
|
101
|
+
mientras (i < longitud(izquierda) y j < longitud(derecha)) {
|
|
102
|
+
si (revisar_logico("ordenar", va_antes(derecha[j], izquierda[i]))) {
|
|
103
|
+
agregar(resultado, derecha[j]);
|
|
104
|
+
j++;
|
|
105
|
+
} si_no {
|
|
106
|
+
agregar(resultado, izquierda[i]);
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
mientras (i < longitud(izquierda)) {
|
|
111
|
+
agregar(resultado, izquierda[i]);
|
|
112
|
+
i++;
|
|
113
|
+
}
|
|
114
|
+
mientras (j < longitud(derecha)) {
|
|
115
|
+
agregar(resultado, derecha[j]);
|
|
116
|
+
j++;
|
|
117
|
+
}
|
|
118
|
+
regresa resultado;
|
|
119
|
+
}
|
|
120
|
+
regresa mezclar(lista);
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const each = (): ProcedureDoc['params'] => [
|
|
125
|
+
{ name: 'lista', type: 'lista' },
|
|
126
|
+
{ name: 'f', type: 'procedimiento' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
/** Documentación de cada procedimiento de `SOURCE`, para el catálogo. */
|
|
130
|
+
export const libraryDocs: Readonly<Record<string, ProcedureDoc>> = {
|
|
131
|
+
mapear: {
|
|
132
|
+
level: 'avanzado',
|
|
133
|
+
category: 'procedimientos',
|
|
134
|
+
params: each(),
|
|
135
|
+
returns: 'lista',
|
|
136
|
+
description: 'Una lista nueva con el resultado de `f(x)` para cada elemento.',
|
|
137
|
+
examples: [{ code: 'mapear([1, 2, 3], procedimiento(x) { regresa x * 2 })', result: '[2, 4, 6]' }],
|
|
138
|
+
},
|
|
139
|
+
filtrar: {
|
|
140
|
+
level: 'avanzado',
|
|
141
|
+
category: 'procedimientos',
|
|
142
|
+
params: each(),
|
|
143
|
+
returns: 'lista',
|
|
144
|
+
description: 'Una lista nueva con los elementos para los que `f(x)` es `verdadero`.',
|
|
145
|
+
examples: [{ code: 'filtrar([1, 2, 3, 4], procedimiento(x) { regresa x mod 2 == 0 })', result: '[2, 4]' }],
|
|
146
|
+
},
|
|
147
|
+
reducir: {
|
|
148
|
+
level: 'avanzado',
|
|
149
|
+
category: 'procedimientos',
|
|
150
|
+
params: [...each(), { name: 'inicial', type: 'cualquiera' }],
|
|
151
|
+
returns: 'cualquiera',
|
|
152
|
+
description: 'Junta la lista en un solo valor: empieza con `inicial` y en cada elemento hace `f(acumulado, x)`.',
|
|
153
|
+
examples: [{ code: 'reducir([1, 2, 3], procedimiento(total, x) { regresa total + x }, 0)', result: '6' }],
|
|
154
|
+
},
|
|
155
|
+
alguno: {
|
|
156
|
+
level: 'avanzado',
|
|
157
|
+
category: 'procedimientos',
|
|
158
|
+
params: each(),
|
|
159
|
+
returns: 'lógico',
|
|
160
|
+
description: '`verdadero` si `f(x)` es `verdadero` para al menos un elemento.',
|
|
161
|
+
examples: [{ code: 'alguno([1, 5], procedimiento(x) { regresa x > 3 })', result: 'verdadero' }],
|
|
162
|
+
},
|
|
163
|
+
todos: {
|
|
164
|
+
level: 'avanzado',
|
|
165
|
+
category: 'procedimientos',
|
|
166
|
+
params: each(),
|
|
167
|
+
returns: 'lógico',
|
|
168
|
+
description: '`verdadero` si `f(x)` es `verdadero` para todos los elementos.',
|
|
169
|
+
examples: [{ code: 'todos([1, 5], procedimiento(x) { regresa x > 3 })', result: 'falso' }],
|
|
170
|
+
},
|
|
171
|
+
buscar: {
|
|
172
|
+
level: 'avanzado',
|
|
173
|
+
category: 'procedimientos',
|
|
174
|
+
params: each(),
|
|
175
|
+
returns: 'cualquiera',
|
|
176
|
+
description: 'El primer elemento para el que `f(x)` es `verdadero`, o `nulo` si no hay.',
|
|
177
|
+
examples: [{ code: 'buscar([1, 5, 7], procedimiento(x) { regresa x > 3 })', result: '5' }],
|
|
178
|
+
},
|
|
179
|
+
ordenar: {
|
|
180
|
+
level: 'intermedio',
|
|
181
|
+
category: 'listas',
|
|
182
|
+
params: [
|
|
183
|
+
{ name: 'lista', type: 'lista' },
|
|
184
|
+
{ name: 'va_antes', type: 'procedimiento', optional: true },
|
|
185
|
+
],
|
|
186
|
+
returns: 'lista',
|
|
187
|
+
description:
|
|
188
|
+
'Una lista nueva ordenada. Sin comparador, todos tienen que ser números o todos textos; `va_antes(a, b)` responde «¿va `a` antes que `b`?». Es estable.',
|
|
189
|
+
examples: [
|
|
190
|
+
{ code: 'ordenar([3, 1, 2])', result: '[1, 2, 3]' },
|
|
191
|
+
{ code: 'ordenar([3, 1, 2], procedimiento(a, b) { regresa a > b })', result: '[3, 2, 1]' },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** Parámetros que se pueden omitir (llegan como `nulo`). */
|
|
197
|
+
const OPTIONAL: Readonly<Record<string, number>> = { ordenar: 1 };
|
|
198
|
+
|
|
199
|
+
const compileLibrary = (): Map<string, Chunk> => {
|
|
200
|
+
const { program, errors } = parse(SOURCE);
|
|
201
|
+
if (errors.length > 0) throw new Error(`La biblioteca en Codexivo tiene errores: ${errors[0]!.message}`);
|
|
202
|
+
const chunks = new Map<string, Chunk>();
|
|
203
|
+
for (const statement of program.statements) {
|
|
204
|
+
if (statement.kind !== 'FunctionDeclaration') continue;
|
|
205
|
+
const chunk = compileProcedure(statement.function, true);
|
|
206
|
+
const optional = OPTIONAL[statement.name.name];
|
|
207
|
+
if (optional !== undefined) chunk.minParams = optional;
|
|
208
|
+
chunks.set(statement.name.name, chunk);
|
|
209
|
+
}
|
|
210
|
+
return chunks;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Los procedimientos de la biblioteca escritos en Codexivo, por nombre. Son inmutables, así que todas las
|
|
215
|
+
* máquinas los comparten. Sus `id` son negativos para no mover los de los valores del programa.
|
|
216
|
+
*/
|
|
217
|
+
export const libraryProcedures: Readonly<Record<string, ProcedureValue>> = (() => {
|
|
218
|
+
const scope = new Scope();
|
|
219
|
+
const table: Record<string, ProcedureValue> = {};
|
|
220
|
+
let id = -1;
|
|
221
|
+
for (const [name, chunk] of compileLibrary()) {
|
|
222
|
+
const procedure: ProcedureValue = { kind: 'procedimiento', id: id--, name, chunk, closure: scope };
|
|
223
|
+
scope.variables.set(name, procedure);
|
|
224
|
+
table[name] = procedure;
|
|
225
|
+
}
|
|
226
|
+
return table;
|
|
227
|
+
})();
|
|
228
|
+
|
|
229
|
+
/** Costo de llamar a un procedimiento de la biblioteca escrito en Codexivo. */
|
|
230
|
+
export const libraryCost = (name: string) => COSTS[name];
|
package/src/runtime/values.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CodexivoError } from '../errors';
|
|
2
|
+
import type { RobotEvent } from '../world';
|
|
2
3
|
import type { Chunk } from './compiler';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -6,7 +7,8 @@ import type { Chunk } from './compiler';
|
|
|
6
7
|
* listas y procedimientos son objetos con un `id` para poder dibujar cuándo dos variables
|
|
7
8
|
* comparten el mismo valor.
|
|
8
9
|
*/
|
|
9
|
-
export type Value =
|
|
10
|
+
export type Value =
|
|
11
|
+
number | string | boolean | null | ListValue | DictValue | DomainValue | ProcedureValue | NativeValue;
|
|
10
12
|
|
|
11
13
|
export interface ListValue {
|
|
12
14
|
kind: 'lista';
|
|
@@ -14,6 +16,28 @@ export interface ListValue {
|
|
|
14
16
|
items: Value[];
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/** Clave de un diccionario: un texto o un número. */
|
|
20
|
+
export type DictKey = string | number;
|
|
21
|
+
|
|
22
|
+
/** Diccionario: conserva el orden en que se agregaron las claves. */
|
|
23
|
+
export interface DictValue {
|
|
24
|
+
kind: 'diccionario';
|
|
25
|
+
id: number;
|
|
26
|
+
entries: Map<DictKey, Value>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Un `dominio`: un espacio de nombres. Sus miembros son las variables de `scope`; desde fuera se usan con `.` y
|
|
31
|
+
* solo se pueden cambiar los que se declararon con `variable` (`writable`).
|
|
32
|
+
*/
|
|
33
|
+
export interface DomainValue {
|
|
34
|
+
kind: 'dominio';
|
|
35
|
+
id: number;
|
|
36
|
+
name: string;
|
|
37
|
+
scope: Scope;
|
|
38
|
+
writable: ReadonlySet<string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
export interface ProcedureValue {
|
|
18
42
|
kind: 'procedimiento';
|
|
19
43
|
id: number;
|
|
@@ -28,21 +52,70 @@ export interface NativeContext {
|
|
|
28
52
|
/** Siguiente línea de entrada, `null` si ya no hay, o `WAIT_FOR_INPUT` si hay que pedirla. */
|
|
29
53
|
readLine(): string | null | typeof WAIT_FOR_INPUT;
|
|
30
54
|
newList(items: Value[]): ListValue;
|
|
55
|
+
newDict(entries: Map<DictKey, Value>): DictValue;
|
|
56
|
+
/** Número pseudoaleatorio en [0, 1) del generador con semilla de la máquina. */
|
|
57
|
+
random(): number;
|
|
31
58
|
/** Error de ejecución ubicado en la llamada actual. */
|
|
32
59
|
fail(code: string, message: string, hint?: string): CodexivoError;
|
|
60
|
+
/** Emite un evento propio del procedimiento (el modo mundo emite `robot`), ubicado en la llamada actual. */
|
|
61
|
+
emit(event: RobotEvent): void;
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
export const WAIT_FOR_INPUT: unique symbol = Symbol('esperar-entrada');
|
|
36
65
|
|
|
66
|
+
export type ProcedureLevel = 'principiante' | 'intermedio' | 'avanzado';
|
|
67
|
+
|
|
68
|
+
export type ProcedureCategory =
|
|
69
|
+
| 'entrada y salida'
|
|
70
|
+
| 'tipos'
|
|
71
|
+
| 'verificación'
|
|
72
|
+
| 'matemáticas'
|
|
73
|
+
| 'texto'
|
|
74
|
+
| 'listas'
|
|
75
|
+
| 'diccionarios'
|
|
76
|
+
| 'procedimientos'
|
|
77
|
+
| 'estructuras'
|
|
78
|
+
| 'mundo';
|
|
79
|
+
|
|
80
|
+
/** Ejemplo ejecutable de un procedimiento: `result` es el valor final como se escribiría en código. */
|
|
81
|
+
export interface ProcedureExample {
|
|
82
|
+
code: string;
|
|
83
|
+
result?: string;
|
|
84
|
+
output?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Documentación de un procedimiento de la biblioteca; de aquí sale el catálogo (`catalog.ts`). */
|
|
88
|
+
export interface ProcedureDoc {
|
|
89
|
+
level: ProcedureLevel;
|
|
90
|
+
category: ProcedureCategory;
|
|
91
|
+
/** Tipos con los nombres de `tipo(x)`, `cualquiera`, o varios separados por ` | `. */
|
|
92
|
+
params: Array<{ name: string; type: string; optional?: boolean }>;
|
|
93
|
+
/** `true` si el último parámetro se puede repetir (`imprimir(a, b, …)`). */
|
|
94
|
+
variadic?: boolean;
|
|
95
|
+
/** Lo que regresa; `nulo` si no regresa nada útil. */
|
|
96
|
+
returns: string;
|
|
97
|
+
/** `true` si cambia su primer argumento. */
|
|
98
|
+
mutates?: boolean;
|
|
99
|
+
description: string;
|
|
100
|
+
examples: ProcedureExample[];
|
|
101
|
+
}
|
|
102
|
+
|
|
37
103
|
export interface NativeValue {
|
|
38
104
|
kind: 'nativo';
|
|
39
105
|
name: string;
|
|
106
|
+
/** Documentación para el catálogo. Los nativos internos de la biblioteca no la tienen. */
|
|
107
|
+
doc?: ProcedureDoc;
|
|
40
108
|
/** Cantidad de argumentos: exacta, o `[mínimo, máximo]` (`máximo` puede ser `Infinity`). */
|
|
41
109
|
arity: number | [number, number];
|
|
42
110
|
call(args: Value[], context: NativeContext): Value | typeof WAIT_FOR_INPUT;
|
|
111
|
+
/**
|
|
112
|
+
* Cuántas operaciones debería costar una llamada con estos argumentos (§10). Sin `cost`, cuesta 1.
|
|
113
|
+
* Se registra aquí para que la tabla de costos viva junto a cada procedimiento.
|
|
114
|
+
*/
|
|
115
|
+
cost?: (args: Value[]) => number;
|
|
43
116
|
}
|
|
44
117
|
|
|
45
|
-
export type TypeName = 'número' | 'texto' | 'lógico' | 'nulo' | 'lista' | 'procedimiento';
|
|
118
|
+
export type TypeName = 'número' | 'texto' | 'lógico' | 'nulo' | 'lista' | 'diccionario' | 'dominio' | 'procedimiento';
|
|
46
119
|
|
|
47
120
|
export const typeOf = (value: Value): TypeName => {
|
|
48
121
|
if (value === null) return 'nulo';
|
|
@@ -54,7 +127,9 @@ export const typeOf = (value: Value): TypeName => {
|
|
|
54
127
|
case 'boolean':
|
|
55
128
|
return 'lógico';
|
|
56
129
|
default:
|
|
57
|
-
return value.kind === 'lista'
|
|
130
|
+
return value.kind === 'lista' || value.kind === 'diccionario' || value.kind === 'dominio'
|
|
131
|
+
? value.kind
|
|
132
|
+
: 'procedimiento';
|
|
58
133
|
}
|
|
59
134
|
};
|
|
60
135
|
|
|
@@ -73,6 +148,12 @@ export const describeType = (type: TypeName): string => {
|
|
|
73
148
|
export const isList = (value: Value): value is ListValue =>
|
|
74
149
|
typeof value === 'object' && value !== null && value.kind === 'lista';
|
|
75
150
|
|
|
151
|
+
export const isDomain = (value: Value): value is DomainValue =>
|
|
152
|
+
typeof value === 'object' && value !== null && value.kind === 'dominio';
|
|
153
|
+
|
|
154
|
+
export const isDict = (value: Value): value is DictValue =>
|
|
155
|
+
typeof value === 'object' && value !== null && value.kind === 'diccionario';
|
|
156
|
+
|
|
76
157
|
export const isCallable = (value: Value): value is ProcedureValue | NativeValue =>
|
|
77
158
|
typeof value === 'object' && value !== null && (value.kind === 'procedimiento' || value.kind === 'nativo');
|
|
78
159
|
|
|
@@ -84,7 +165,7 @@ const formatNumber = (value: number): string => {
|
|
|
84
165
|
};
|
|
85
166
|
|
|
86
167
|
/** Representación como se escribiría en código: los textos van entre comillas. */
|
|
87
|
-
export const repr = (value: Value, seen: Set<ListValue> = new Set()): string => {
|
|
168
|
+
export const repr = (value: Value, seen: Set<ListValue | DictValue> = new Set()): string => {
|
|
88
169
|
if (value === null) return 'nulo';
|
|
89
170
|
switch (typeof value) {
|
|
90
171
|
case 'number':
|
|
@@ -94,12 +175,16 @@ export const repr = (value: Value, seen: Set<ListValue> = new Set()): string =>
|
|
|
94
175
|
case 'boolean':
|
|
95
176
|
return value ? 'verdadero' : 'falso';
|
|
96
177
|
}
|
|
97
|
-
if (value.kind === '
|
|
98
|
-
|
|
178
|
+
if (value.kind === 'dominio') return `dominio ${value.name}`;
|
|
179
|
+
if (value.kind === 'lista' || value.kind === 'diccionario') {
|
|
180
|
+
if (seen.has(value)) return value.kind === 'lista' ? '[…]' : '{…}';
|
|
99
181
|
seen.add(value);
|
|
100
|
-
const inner =
|
|
182
|
+
const inner =
|
|
183
|
+
value.kind === 'lista'
|
|
184
|
+
? value.items.map(item => repr(item, seen))
|
|
185
|
+
: [...value.entries].map(([key, item]) => `${repr(key)}: ${repr(item, seen)}`);
|
|
101
186
|
seen.delete(value);
|
|
102
|
-
return `[${inner}]`;
|
|
187
|
+
return value.kind === 'lista' ? `[${inner.join(', ')}]` : `{${inner.join(', ')}}`;
|
|
103
188
|
}
|
|
104
189
|
return value.name ? `procedimiento ${value.name}` : 'procedimiento';
|
|
105
190
|
};
|
|
@@ -107,18 +192,31 @@ export const repr = (value: Value, seen: Set<ListValue> = new Set()): string =>
|
|
|
107
192
|
/** Como lo muestra `imprimir`: los textos sin comillas. */
|
|
108
193
|
export const show = (value: Value): string => (typeof value === 'string' ? value : repr(value));
|
|
109
194
|
|
|
110
|
-
|
|
111
|
-
|
|
195
|
+
type Container = ListValue | DictValue;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Igualdad por valor: dos listas son iguales si tienen los mismos elementos en el mismo orden, y dos
|
|
199
|
+
* diccionarios si tienen las mismas claves con los mismos valores, en cualquier orden.
|
|
200
|
+
*/
|
|
201
|
+
export const equals = (a: Value, b: Value, seen: Array<[Container, Container]> = []): boolean => {
|
|
112
202
|
if (a === b) return true;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
203
|
+
const bothLists = isList(a) && isList(b);
|
|
204
|
+
const bothDicts = isDict(a) && isDict(b);
|
|
205
|
+
if (!bothLists && !bothDicts) return false;
|
|
206
|
+
if (seen.some(([x, y]) => x === a && y === b)) return true;
|
|
207
|
+
seen.push([a as Container, b as Container]);
|
|
208
|
+
let result: boolean;
|
|
209
|
+
if (bothLists) {
|
|
210
|
+
const [x, y] = [a as ListValue, b as ListValue];
|
|
211
|
+
result = x.items.length === y.items.length && x.items.every((item, index) => equals(item, y.items[index]!, seen));
|
|
212
|
+
} else {
|
|
213
|
+
const [x, y] = [a as DictValue, b as DictValue];
|
|
214
|
+
result =
|
|
215
|
+
x.entries.size === y.entries.size &&
|
|
216
|
+
[...x.entries].every(([key, item]) => y.entries.has(key) && equals(item, y.entries.get(key)!, seen));
|
|
120
217
|
}
|
|
121
|
-
|
|
218
|
+
seen.pop();
|
|
219
|
+
return result;
|
|
122
220
|
};
|
|
123
221
|
|
|
124
222
|
/**
|
|
@@ -126,6 +224,10 @@ export const equals = (a: Value, b: Value, seen: Array<[ListValue, ListValue]> =
|
|
|
126
224
|
*/
|
|
127
225
|
export class Scope {
|
|
128
226
|
readonly variables = new Map<string, Value>();
|
|
227
|
+
/** Nombres de este alcance declarados con `constante`. */
|
|
228
|
+
readonly constants = new Set<string>();
|
|
229
|
+
/** Variables de `para … desde … hasta` en este alcance: avanzan solas. */
|
|
230
|
+
readonly loopVariables = new Set<string>();
|
|
129
231
|
|
|
130
232
|
constructor(readonly parent?: Scope) {}
|
|
131
233
|
|