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
package/src/runtime/compiler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../syntax/ast';
|
|
2
2
|
import type { Value } from './values';
|
|
3
3
|
|
|
4
|
-
export type DecisionLabel = 'si' | 'pero_si' | 'mientras' | 'hasta_que' | 'para';
|
|
4
|
+
export type DecisionLabel = 'si' | 'pero_si' | 'mientras' | 'hasta_que' | 'para' | 'repetir' | 'para_cada';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Instrucciones de la máquina de Codexivo. Cada una conserva el nodo del AST que la originó, así la
|
|
@@ -10,17 +10,36 @@ export type DecisionLabel = 'si' | 'pero_si' | 'mientras' | 'hasta_que' | 'para'
|
|
|
10
10
|
export type Instruction =
|
|
11
11
|
/** Inicio de una sentencia: punto natural para pausar. */
|
|
12
12
|
| { op: 'step'; node: ast.Statement }
|
|
13
|
-
| { op: 'const'; value: Value; node: ast.
|
|
13
|
+
| { op: 'const'; value: Value; node: ast.Node }
|
|
14
14
|
| { op: 'list'; count: number; node: ast.ListLiteral }
|
|
15
|
+
/** Saca `count` pares clave–valor de la pila y crea un diccionario. */
|
|
16
|
+
| { op: 'dict'; count: number; node: ast.DictLiteral }
|
|
15
17
|
| { op: 'load'; name: string; node: ast.Identifier; forUpdate?: boolean }
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Saca un valor de la pila y crea la variable (o constante) `target` en el alcance actual. `node` es la
|
|
20
|
+
* sentencia que la declara: una declaración, un procedimiento con nombre o la variable de un `para_cada`.
|
|
21
|
+
*/
|
|
22
|
+
| {
|
|
23
|
+
op: 'declare';
|
|
24
|
+
name: string;
|
|
25
|
+
constant: boolean;
|
|
26
|
+
target: ast.Identifier;
|
|
27
|
+
node: ast.Node;
|
|
28
|
+
/** La variable de un `para … desde … hasta`: avanza sola y no se puede cambiar en el cuerpo. */
|
|
29
|
+
loop?: boolean;
|
|
30
|
+
}
|
|
18
31
|
/** Saca un valor de la pila y lo guarda en una variable existente. */
|
|
19
|
-
| { op: 'store'; name: string; node: ast.Assignment }
|
|
32
|
+
| { op: 'store'; name: string; target: ast.Identifier; node: ast.Assignment | ast.Increment }
|
|
33
|
+
/** Saca valor, índice (o clave) y lista (o diccionario), en ese orden, y cambia el elemento. */
|
|
34
|
+
| { op: 'setIndex'; target: ast.Index; node: ast.Assignment | ast.Increment }
|
|
35
|
+
/** Copia los `count` valores de la cima de la pila. */
|
|
36
|
+
| { op: 'dup'; count: number }
|
|
37
|
+
/** Saca `count` valores de la pila (los valores ocultos de un bucle al salir con `romper`). */
|
|
38
|
+
| { op: 'pop'; count: number }
|
|
20
39
|
/** Saca un valor de la pila; es el valor de una sentencia de expresión. */
|
|
21
40
|
| { op: 'result'; node: ast.ExpressionStatement }
|
|
22
41
|
| { op: 'unary'; operator: ast.UnaryOperator; node: ast.Unary }
|
|
23
|
-
| { op: 'binary'; operator: ast.BinaryOperator; node: ast.Binary | ast.Assignment }
|
|
42
|
+
| { op: 'binary'; operator: ast.BinaryOperator; node: ast.Binary | ast.Assignment | ast.Increment }
|
|
24
43
|
| { op: 'index'; node: ast.Index }
|
|
25
44
|
| { op: 'closure'; chunk: Chunk; node: ast.FunctionLiteral }
|
|
26
45
|
| { op: 'call'; argc: number; node: ast.Call }
|
|
@@ -35,10 +54,35 @@ export type Instruction =
|
|
|
35
54
|
node: ast.Expression;
|
|
36
55
|
owner: ast.Statement;
|
|
37
56
|
}
|
|
57
|
+
/** Revisa que la cantidad de `repetir` (en la cima de la pila) sea un entero ≥ 0; queda como contador oculto. */
|
|
58
|
+
| { op: 'repeatStart'; node: ast.Expression; owner: ast.Repeat }
|
|
59
|
+
/** Si al contador le quedan vueltas, lo reduce y sigue; si no, lo saca de la pila y salta a `target`. */
|
|
60
|
+
| { op: 'repeatNext'; target: number; node: ast.Expression; owner: ast.Repeat }
|
|
61
|
+
/**
|
|
62
|
+
* Revisa lo que se recorre y deja tres valores ocultos en la pila: lo recorrido, su longitud (o, en un
|
|
63
|
+
* diccionario, la lista de sus claves al empezar) y la posición 0.
|
|
64
|
+
*/
|
|
65
|
+
| { op: 'forEachStart'; node: ast.Expression; owner: ast.ForEach }
|
|
66
|
+
/**
|
|
67
|
+
* Saca inicio, final y paso de la pila, los revisa y deja cuatro valores ocultos: el inicio, el paso, la
|
|
68
|
+
* cantidad de vueltas y la vuelta actual.
|
|
69
|
+
*/
|
|
70
|
+
| { op: 'rangeStart'; explicitStep: boolean; owner: ast.ForRange }
|
|
71
|
+
/** Si quedan vueltas, pone el siguiente valor en la pila; si no, saca los valores ocultos y salta a `target`. */
|
|
72
|
+
| { op: 'rangeNext'; target: number; node: ast.Expression; owner: ast.ForRange }
|
|
73
|
+
/**
|
|
74
|
+
* Si quedan elementos, pone el siguiente en la pila (en un diccionario, la clave; o el valor y luego la
|
|
75
|
+
* clave si hay dos variables); si no, saca los valores ocultos y salta a `target`.
|
|
76
|
+
*/
|
|
77
|
+
| { op: 'forEachNext'; target: number; node: ast.Expression; owner: ast.ForEach }
|
|
38
78
|
/** Cortocircuito: revisa el lado izquierdo de `y`/`o`; si ya decide el resultado, lo deja y salta. */
|
|
39
79
|
| { op: 'shortCircuit'; target: number; node: ast.Logical }
|
|
40
80
|
/** Revisa que el lado derecho de `y`/`o` sea lógico. */
|
|
41
81
|
| { op: 'checkLogical'; node: ast.Logical }
|
|
82
|
+
/** Abre el alcance de un `dominio`: ahí se declaran sus miembros. */
|
|
83
|
+
| { op: 'domainStart' }
|
|
84
|
+
/** Cierra el alcance del dominio, lo convierte en un valor y lo declara (como constante) afuera. */
|
|
85
|
+
| { op: 'domainEnd'; name: string; writable: string[]; node: ast.Domain }
|
|
42
86
|
| { op: 'enterScope' }
|
|
43
87
|
| { op: 'exitScope' }
|
|
44
88
|
| { op: 'halt' };
|
|
@@ -49,32 +93,67 @@ export interface Chunk {
|
|
|
49
93
|
params: string[];
|
|
50
94
|
code: Instruction[];
|
|
51
95
|
node: ast.Program | ast.FunctionLiteral;
|
|
96
|
+
/**
|
|
97
|
+
* Código de la biblioteca escrito en Codexivo (`prelude.ts`). Sus pasos no pausan, no emiten eventos ni
|
|
98
|
+
* cuentan operaciones: la llamada se cobra según la tabla de costos, y sus errores se ubican en la llamada.
|
|
99
|
+
*/
|
|
100
|
+
hidden?: boolean;
|
|
101
|
+
/** Parámetros obligatorios, si son menos que `params` (los demás llegan como `nulo`). Solo en la biblioteca. */
|
|
102
|
+
minParams?: number;
|
|
52
103
|
}
|
|
53
104
|
|
|
54
105
|
/** Traduce el AST a instrucciones. El AST debe venir sin errores de sintaxis. */
|
|
55
106
|
export const compile = (program: ast.Program): Chunk => {
|
|
56
|
-
const compiler = new Compiler({ name: 'programa', params: [], code: [], node: program });
|
|
57
|
-
|
|
107
|
+
const compiler = new Compiler({ name: 'programa', params: [], code: [], node: program }, false);
|
|
108
|
+
compiler.statements(program.statements);
|
|
58
109
|
compiler.emit({ op: 'halt' });
|
|
59
110
|
return compiler.chunk;
|
|
60
111
|
};
|
|
61
112
|
|
|
62
|
-
|
|
63
|
-
|
|
113
|
+
/** Compila un procedimiento. `hidden` marca el código de la biblioteca (ver `Chunk.hidden`). */
|
|
114
|
+
export const compileProcedure = (node: ast.FunctionLiteral, hidden = false): Chunk => {
|
|
115
|
+
const chunk: Chunk = {
|
|
64
116
|
name: node.name ?? 'procedimiento',
|
|
65
117
|
params: node.params.map(param => param.name),
|
|
66
118
|
code: [],
|
|
67
119
|
node,
|
|
68
|
-
}
|
|
120
|
+
};
|
|
121
|
+
if (hidden) chunk.hidden = true;
|
|
122
|
+
const compiler = new Compiler(chunk, hidden);
|
|
69
123
|
// El cuerpo comparte alcance con los parámetros: así `variable x` dentro de `procedimiento(x)` es un error.
|
|
70
|
-
|
|
124
|
+
compiler.statements(node.body.statements);
|
|
71
125
|
compiler.emit({ op: 'const', value: null, node });
|
|
72
126
|
compiler.emit({ op: 'return', hasValue: true, node: { kind: 'Return', id: node.id, span: node.body.span } });
|
|
73
127
|
return compiler.chunk;
|
|
74
128
|
};
|
|
75
129
|
|
|
130
|
+
/** Operador que aplica cada asignación compuesta. */
|
|
131
|
+
const COMPOUND_OPERATORS: Record<Exclude<ast.AssignmentOperator, '='>, ast.BinaryOperator> = {
|
|
132
|
+
'+=': '+',
|
|
133
|
+
'-=': '-',
|
|
134
|
+
'*=': '*',
|
|
135
|
+
'/=': '/',
|
|
136
|
+
'%=': 'mod',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** Un bucle abierto: a dónde saltan `romper` y `continuar`, y qué hay que limpiar antes. */
|
|
140
|
+
interface Loop {
|
|
141
|
+
breaks: number[];
|
|
142
|
+
continues: number[];
|
|
143
|
+
/** Alcances abiertos en el punto al que llegan los saltos. */
|
|
144
|
+
scopeDepth: number;
|
|
145
|
+
/** Valores ocultos del bucle en la pila (el contador de `repetir`, el estado de `para_cada`). */
|
|
146
|
+
slots: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
76
149
|
class Compiler {
|
|
77
|
-
|
|
150
|
+
private scopeDepth = 0;
|
|
151
|
+
private readonly loops: Loop[] = [];
|
|
152
|
+
|
|
153
|
+
constructor(
|
|
154
|
+
readonly chunk: Chunk,
|
|
155
|
+
private readonly hidden: boolean,
|
|
156
|
+
) {}
|
|
78
157
|
|
|
79
158
|
emit(instruction: Instruction): number {
|
|
80
159
|
this.chunk.code.push(instruction);
|
|
@@ -90,22 +169,56 @@ class Compiler {
|
|
|
90
169
|
if (instruction && 'target' in instruction) instruction.target = target;
|
|
91
170
|
}
|
|
92
171
|
|
|
172
|
+
private enterScope(): void {
|
|
173
|
+
this.emit({ op: 'enterScope' });
|
|
174
|
+
this.scopeDepth += 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private exitScope(): void {
|
|
178
|
+
this.emit({ op: 'exitScope' });
|
|
179
|
+
this.scopeDepth -= 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Las sentencias de un bloque. Los procedimientos con nombre se crean antes que todo lo demás. */
|
|
183
|
+
statements(statements: ast.Statement[]): void {
|
|
184
|
+
for (const statement of statements) {
|
|
185
|
+
if (statement.kind !== 'FunctionDeclaration') continue;
|
|
186
|
+
this.emit({ op: 'closure', chunk: compileProcedure(statement.function, this.hidden), node: statement.function });
|
|
187
|
+
this.emit({
|
|
188
|
+
op: 'declare',
|
|
189
|
+
name: statement.name.name,
|
|
190
|
+
constant: false,
|
|
191
|
+
target: statement.name,
|
|
192
|
+
node: statement,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
for (const statement of statements) this.statement(statement);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Abre un bucle, compila su cuerpo y regresa los saltos de `romper` y `continuar` para conectarlos. */
|
|
199
|
+
private loop(slots: number, body: () => void): Loop {
|
|
200
|
+
const loop: Loop = { breaks: [], continues: [], scopeDepth: this.scopeDepth, slots };
|
|
201
|
+
this.loops.push(loop);
|
|
202
|
+
body();
|
|
203
|
+
this.loops.pop();
|
|
204
|
+
return loop;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private connect(jumps: number[], target: number): void {
|
|
208
|
+
for (const jump of jumps) this.patch(jump, target);
|
|
209
|
+
}
|
|
210
|
+
|
|
93
211
|
statement(node: ast.Statement): void {
|
|
212
|
+
if (node.kind === 'FunctionDeclaration') return; // ya se declaró al entrar al bloque
|
|
94
213
|
if (node.kind !== 'Block') this.emit({ op: 'step', node });
|
|
95
214
|
switch (node.kind) {
|
|
96
215
|
case 'VariableDeclaration':
|
|
97
216
|
this.expression(node.value);
|
|
98
|
-
this.emit({ op: 'declare', name: node.name.name, node });
|
|
217
|
+
this.emit({ op: 'declare', name: node.name.name, constant: node.constant, target: node.name, node });
|
|
99
218
|
return;
|
|
100
219
|
case 'Assignment':
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
this.expression(node.value);
|
|
105
|
-
if (node.operator !== '=') {
|
|
106
|
-
this.emit({ op: 'binary', operator: node.operator[0] as ast.BinaryOperator, node });
|
|
107
|
-
}
|
|
108
|
-
this.emit({ op: 'store', name: node.target.name, node });
|
|
220
|
+
case 'Increment':
|
|
221
|
+
this.assignment(node);
|
|
109
222
|
return;
|
|
110
223
|
case 'ExpressionStatement':
|
|
111
224
|
this.expression(node.expression);
|
|
@@ -118,6 +231,33 @@ class Compiler {
|
|
|
118
231
|
case 'Block':
|
|
119
232
|
this.block(node);
|
|
120
233
|
return;
|
|
234
|
+
case 'Domain':
|
|
235
|
+
this.emit({ op: 'domainStart' });
|
|
236
|
+
this.scopeDepth += 1;
|
|
237
|
+
this.statements(node.body.statements);
|
|
238
|
+
this.scopeDepth -= 1;
|
|
239
|
+
this.emit({
|
|
240
|
+
op: 'domainEnd',
|
|
241
|
+
name: node.name.name,
|
|
242
|
+
writable: node.body.statements.flatMap(member =>
|
|
243
|
+
member.kind === 'VariableDeclaration' && !member.constant ? [member.name.name] : [],
|
|
244
|
+
),
|
|
245
|
+
node,
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
case 'Break':
|
|
249
|
+
case 'Continue': {
|
|
250
|
+
const loop = this.loops[this.loops.length - 1]!;
|
|
251
|
+
// El código que sigue nunca se ejecuta, así que la cuenta de alcances no cambia.
|
|
252
|
+
for (let depth = this.scopeDepth; depth > loop.scopeDepth; depth--) this.emit({ op: 'exitScope' });
|
|
253
|
+
if (node.kind === 'Break') {
|
|
254
|
+
if (loop.slots > 0) this.emit({ op: 'pop', count: loop.slots });
|
|
255
|
+
loop.breaks.push(this.emit({ op: 'jump', target: -1 }));
|
|
256
|
+
} else {
|
|
257
|
+
loop.continues.push(this.emit({ op: 'jump', target: -1 }));
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
121
261
|
case 'If': {
|
|
122
262
|
const exits: number[] = [];
|
|
123
263
|
node.branches.forEach((branch, index) => {
|
|
@@ -149,14 +289,17 @@ class Compiler {
|
|
|
149
289
|
node: node.condition,
|
|
150
290
|
owner: node,
|
|
151
291
|
});
|
|
152
|
-
this.block(node.body);
|
|
292
|
+
const loop = this.loop(0, () => this.block(node.body));
|
|
153
293
|
this.emit({ op: 'jump', target: start });
|
|
154
294
|
this.patch(exit, this.here);
|
|
295
|
+
this.connect(loop.continues, start);
|
|
296
|
+
this.connect(loop.breaks, this.here);
|
|
155
297
|
return;
|
|
156
298
|
}
|
|
157
299
|
case 'DoUntil': {
|
|
158
300
|
const start = this.here;
|
|
159
|
-
this.block(node.body);
|
|
301
|
+
const loop = this.loop(0, () => this.block(node.body));
|
|
302
|
+
this.connect(loop.continues, this.here);
|
|
160
303
|
this.expression(node.condition);
|
|
161
304
|
this.emit({
|
|
162
305
|
op: 'branch',
|
|
@@ -166,10 +309,11 @@ class Compiler {
|
|
|
166
309
|
node: node.condition,
|
|
167
310
|
owner: node,
|
|
168
311
|
});
|
|
312
|
+
this.connect(loop.breaks, this.here);
|
|
169
313
|
return;
|
|
170
314
|
}
|
|
171
315
|
case 'For': {
|
|
172
|
-
this.
|
|
316
|
+
this.enterScope();
|
|
173
317
|
if (node.init) this.statement(node.init);
|
|
174
318
|
const start = this.here;
|
|
175
319
|
let exit = -1;
|
|
@@ -184,20 +328,116 @@ class Compiler {
|
|
|
184
328
|
owner: node,
|
|
185
329
|
});
|
|
186
330
|
}
|
|
187
|
-
this.block(node.body);
|
|
331
|
+
const loop = this.loop(0, () => this.block(node.body));
|
|
332
|
+
this.connect(loop.continues, this.here);
|
|
188
333
|
if (node.update) this.statement(node.update);
|
|
189
334
|
this.emit({ op: 'jump', target: start });
|
|
190
335
|
if (exit >= 0) this.patch(exit, this.here);
|
|
191
|
-
this.
|
|
336
|
+
this.connect(loop.breaks, this.here);
|
|
337
|
+
this.exitScope();
|
|
192
338
|
return;
|
|
193
339
|
}
|
|
340
|
+
case 'Repeat': {
|
|
341
|
+
this.expression(node.count);
|
|
342
|
+
this.emit({ op: 'repeatStart', node: node.count, owner: node });
|
|
343
|
+
const start = this.here;
|
|
344
|
+
const exit = this.emit({ op: 'repeatNext', target: -1, node: node.count, owner: node });
|
|
345
|
+
const loop = this.loop(1, () => this.block(node.body));
|
|
346
|
+
this.emit({ op: 'jump', target: start });
|
|
347
|
+
this.patch(exit, this.here);
|
|
348
|
+
this.connect(loop.continues, start);
|
|
349
|
+
this.connect(loop.breaks, this.here);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
case 'ForRange': {
|
|
353
|
+
this.expression(node.from);
|
|
354
|
+
this.expression(node.to);
|
|
355
|
+
if (node.step) this.expression(node.step);
|
|
356
|
+
else this.emit({ op: 'const', value: 1, node });
|
|
357
|
+
this.emit({ op: 'rangeStart', explicitStep: Boolean(node.step), owner: node });
|
|
358
|
+
const start = this.here;
|
|
359
|
+
const exit = this.emit({ op: 'rangeNext', target: -1, node: node.to, owner: node });
|
|
360
|
+
const loop = this.loop(4, () => {
|
|
361
|
+
// Como en `para_cada`, la variable es nueva en cada vuelta y comparte alcance con el cuerpo.
|
|
362
|
+
this.enterScope();
|
|
363
|
+
this.emit({
|
|
364
|
+
op: 'declare',
|
|
365
|
+
name: node.variable.name,
|
|
366
|
+
constant: false,
|
|
367
|
+
loop: true,
|
|
368
|
+
target: node.variable,
|
|
369
|
+
node: node.variable,
|
|
370
|
+
});
|
|
371
|
+
this.statements(node.body.statements);
|
|
372
|
+
this.exitScope();
|
|
373
|
+
});
|
|
374
|
+
this.emit({ op: 'jump', target: start });
|
|
375
|
+
this.patch(exit, this.here);
|
|
376
|
+
this.connect(loop.continues, start);
|
|
377
|
+
this.connect(loop.breaks, this.here);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
case 'ForEach': {
|
|
381
|
+
this.expression(node.iterable);
|
|
382
|
+
this.emit({ op: 'forEachStart', node: node.iterable, owner: node });
|
|
383
|
+
const start = this.here;
|
|
384
|
+
const exit = this.emit({ op: 'forEachNext', target: -1, node: node.iterable, owner: node });
|
|
385
|
+
const loop = this.loop(3, () => {
|
|
386
|
+
// La variable del bucle es nueva en cada vuelta y comparte alcance con el cuerpo.
|
|
387
|
+
this.enterScope();
|
|
388
|
+
for (const variable of [node.variable, ...(node.value ? [node.value] : [])]) {
|
|
389
|
+
this.emit({ op: 'declare', name: variable.name, constant: false, target: variable, node: variable });
|
|
390
|
+
}
|
|
391
|
+
this.statements(node.body.statements);
|
|
392
|
+
this.exitScope();
|
|
393
|
+
});
|
|
394
|
+
this.emit({ op: 'jump', target: start });
|
|
395
|
+
this.patch(exit, this.here);
|
|
396
|
+
this.connect(loop.continues, start);
|
|
397
|
+
this.connect(loop.breaks, this.here);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private assignment(node: ast.Assignment | ast.Increment): void {
|
|
404
|
+
const operator =
|
|
405
|
+
node.kind === 'Increment'
|
|
406
|
+
? node.operator === '++'
|
|
407
|
+
? '+'
|
|
408
|
+
: '-'
|
|
409
|
+
: node.operator === '='
|
|
410
|
+
? undefined
|
|
411
|
+
: COMPOUND_OPERATORS[node.operator];
|
|
412
|
+
const value = () => {
|
|
413
|
+
if (node.kind === 'Increment') this.emit({ op: 'const', value: 1, node: node.target });
|
|
414
|
+
else this.expression(node.value);
|
|
415
|
+
};
|
|
416
|
+
const { target } = node;
|
|
417
|
+
|
|
418
|
+
if (target.kind === 'Identifier') {
|
|
419
|
+
if (operator) this.emit({ op: 'load', name: target.name, node: target, forUpdate: true });
|
|
420
|
+
value();
|
|
421
|
+
if (operator) this.emit({ op: 'binary', operator, node });
|
|
422
|
+
this.emit({ op: 'store', name: target.name, target, node });
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
this.expression(target.object);
|
|
427
|
+
this.expression(target.index);
|
|
428
|
+
if (operator) {
|
|
429
|
+
this.emit({ op: 'dup', count: 2 });
|
|
430
|
+
this.emit({ op: 'index', node: target });
|
|
194
431
|
}
|
|
432
|
+
value();
|
|
433
|
+
if (operator) this.emit({ op: 'binary', operator, node });
|
|
434
|
+
this.emit({ op: 'setIndex', target, node });
|
|
195
435
|
}
|
|
196
436
|
|
|
197
437
|
private block(node: ast.Block): void {
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
this.
|
|
438
|
+
this.enterScope();
|
|
439
|
+
this.statements(node.statements);
|
|
440
|
+
this.exitScope();
|
|
201
441
|
}
|
|
202
442
|
|
|
203
443
|
private expression(node: ast.Expression): void {
|
|
@@ -214,6 +454,13 @@ class Compiler {
|
|
|
214
454
|
for (const element of node.elements) this.expression(element);
|
|
215
455
|
this.emit({ op: 'list', count: node.elements.length, node });
|
|
216
456
|
return;
|
|
457
|
+
case 'DictLiteral':
|
|
458
|
+
for (const entry of node.entries) {
|
|
459
|
+
this.expression(entry.key);
|
|
460
|
+
this.expression(entry.value);
|
|
461
|
+
}
|
|
462
|
+
this.emit({ op: 'dict', count: node.entries.length, node });
|
|
463
|
+
return;
|
|
217
464
|
case 'Identifier':
|
|
218
465
|
this.emit({ op: 'load', name: node.name, node });
|
|
219
466
|
return;
|
|
@@ -245,7 +492,7 @@ class Compiler {
|
|
|
245
492
|
this.emit({ op: 'index', node });
|
|
246
493
|
return;
|
|
247
494
|
case 'FunctionLiteral':
|
|
248
|
-
this.emit({ op: 'closure', chunk:
|
|
495
|
+
this.emit({ op: 'closure', chunk: compileProcedure(node, this.hidden), node });
|
|
249
496
|
return;
|
|
250
497
|
}
|
|
251
498
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isDict, isList } from './values';
|
|
2
|
+
import type { Value } from './values';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cuántas operaciones cuesta cada procedimiento de la biblioteca (§10). Es la única tabla de costos: la
|
|
6
|
+
* usan los nativos de `builtins.ts` y los procedimientos de `prelude.ts`, y los retos del sitio comparan
|
|
7
|
+
* soluciones con estas cifras. Un procedimiento que no aparece aquí cuesta 1, como cualquier operación.
|
|
8
|
+
*
|
|
9
|
+
* `n` es el tamaño (caracteres, elementos o claves) del primer argumento.
|
|
10
|
+
*/
|
|
11
|
+
export type Cost = (args: Value[]) => number;
|
|
12
|
+
|
|
13
|
+
/** Tamaño de un texto, lista o diccionario; 1 para lo demás. */
|
|
14
|
+
export const sizeOf = (value: Value | undefined): number => {
|
|
15
|
+
if (typeof value === 'string') return value.length;
|
|
16
|
+
if (value !== undefined && isList(value)) return value.items.length;
|
|
17
|
+
if (value !== undefined && isDict(value)) return value.entries.size;
|
|
18
|
+
return 1;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** `n`: recorre su primer argumento una vez. */
|
|
22
|
+
const linear: Cost = args => Math.max(1, sizeOf(args[0]));
|
|
23
|
+
|
|
24
|
+
/** `n · ⌈log₂ n⌉`: lo que cuesta ordenar `n` elementos. */
|
|
25
|
+
const nLogN: Cost = args => {
|
|
26
|
+
const n = sizeOf(args[0]);
|
|
27
|
+
return Math.max(1, n * Math.ceil(Math.log2(Math.max(n, 1))));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Cuesta tantas casillas como crea. */
|
|
31
|
+
const creates =
|
|
32
|
+
(count: (args: Value[]) => number): Cost =>
|
|
33
|
+
args =>
|
|
34
|
+
Math.max(1, Math.ceil(count(args)));
|
|
35
|
+
|
|
36
|
+
/** `n` de `máximo(…)` y `mínimo(…)`, que reciben varios números o una lista. */
|
|
37
|
+
const values: Cost = args => (args.length === 1 ? linear(args) : Math.max(1, args.length));
|
|
38
|
+
|
|
39
|
+
/** Largo del texto o lista que se busca (segundo argumento). */
|
|
40
|
+
const searched: Cost = args => Math.max(1, sizeOf(args[1]));
|
|
41
|
+
|
|
42
|
+
const slice: Cost = args =>
|
|
43
|
+
typeof args[1] === 'number' && typeof args[2] === 'number' ? Math.max(1, Math.abs(args[2] - args[1])) : 1;
|
|
44
|
+
|
|
45
|
+
const range: Cost = args => {
|
|
46
|
+
const [start, end, step = 1] = args;
|
|
47
|
+
if (typeof start !== 'number' || typeof end !== 'number' || typeof step !== 'number' || step === 0) return 1;
|
|
48
|
+
return Math.max(1, Math.floor((end - start) / step) + 1);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const COSTS: Readonly<Partial<Record<string, Cost>>> = {
|
|
52
|
+
// matemáticas
|
|
53
|
+
máximo: values,
|
|
54
|
+
mínimo: values,
|
|
55
|
+
|
|
56
|
+
// texto
|
|
57
|
+
mayúsculas: linear,
|
|
58
|
+
minúsculas: linear,
|
|
59
|
+
recortar: linear,
|
|
60
|
+
dividir: linear,
|
|
61
|
+
unir: linear,
|
|
62
|
+
reemplazar: linear,
|
|
63
|
+
contiene: linear,
|
|
64
|
+
posición_de: linear,
|
|
65
|
+
empieza_con: searched,
|
|
66
|
+
termina_con: searched,
|
|
67
|
+
subtexto: slice,
|
|
68
|
+
caracteres: linear,
|
|
69
|
+
invertir: linear,
|
|
70
|
+
|
|
71
|
+
// listas
|
|
72
|
+
insertar: linear,
|
|
73
|
+
quitar: linear,
|
|
74
|
+
quitar_primero: linear,
|
|
75
|
+
desencolar: linear,
|
|
76
|
+
rebanar: slice,
|
|
77
|
+
copiar: linear,
|
|
78
|
+
sumar: linear,
|
|
79
|
+
ordenar: nLogN,
|
|
80
|
+
rango: range,
|
|
81
|
+
índices: linear,
|
|
82
|
+
lista_de: creates(args => (typeof args[0] === 'number' ? args[0] * sizeOf(args[1]) : 1)),
|
|
83
|
+
matriz: creates(args => (typeof args[0] === 'number' && typeof args[1] === 'number' ? args[0] * args[1] : 1)),
|
|
84
|
+
|
|
85
|
+
// diccionarios
|
|
86
|
+
claves: linear,
|
|
87
|
+
valores: linear,
|
|
88
|
+
|
|
89
|
+
// procedimientos como valores: además, cada llamada a `f` cuesta lo que cueste su cuerpo
|
|
90
|
+
mapear: linear,
|
|
91
|
+
filtrar: linear,
|
|
92
|
+
reducir: linear,
|
|
93
|
+
alguno: linear,
|
|
94
|
+
todos: linear,
|
|
95
|
+
buscar: linear,
|
|
96
|
+
};
|