codexivo 0.2.0 → 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.
@@ -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.Expression }
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
- /** Saca un valor de la pila y crea la variable en el alcance actual. */
17
- | { op: 'declare'; name: string; node: ast.VariableDeclaration }
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
- for (const statement of program.statements) compiler.statement(statement);
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
- const compileFunction = (node: ast.FunctionLiteral): Chunk => {
63
- const compiler = new Compiler({
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
- for (const statement of node.body.statements) compiler.statement(statement);
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
- constructor(readonly chunk: Chunk) {}
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
- if (node.operator !== '=') {
102
- this.emit({ op: 'load', name: node.target.name, node: node.target, forUpdate: true });
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.emit({ op: 'enterScope' });
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.emit({ op: 'exitScope' });
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.emit({ op: 'enterScope' });
199
- for (const statement of node.statements) this.statement(statement);
200
- this.emit({ op: 'exitScope' });
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: compileFunction(node), node });
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
+ };