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,499 @@
1
+ import type * as ast from '../syntax/ast';
2
+ import type { Value } from './values';
3
+
4
+ export type DecisionLabel = 'si' | 'pero_si' | 'mientras' | 'hasta_que' | 'para' | 'repetir' | 'para_cada';
5
+
6
+ /**
7
+ * Instrucciones de la máquina de Codexivo. Cada una conserva el nodo del AST que la originó, así la
8
+ * máquina puede decir qué parte del código está ejecutando y dónde ocurrió un error.
9
+ */
10
+ export type Instruction =
11
+ /** Inicio de una sentencia: punto natural para pausar. */
12
+ | { op: 'step'; node: ast.Statement }
13
+ | { op: 'const'; value: Value; node: ast.Node }
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 }
17
+ | { op: 'load'; name: string; node: ast.Identifier; forUpdate?: boolean }
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
+ }
31
+ /** Saca un valor de la pila y lo guarda en una variable existente. */
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 }
39
+ /** Saca un valor de la pila; es el valor de una sentencia de expresión. */
40
+ | { op: 'result'; node: ast.ExpressionStatement }
41
+ | { op: 'unary'; operator: ast.UnaryOperator; node: ast.Unary }
42
+ | { op: 'binary'; operator: ast.BinaryOperator; node: ast.Binary | ast.Assignment | ast.Increment }
43
+ | { op: 'index'; node: ast.Index }
44
+ | { op: 'closure'; chunk: Chunk; node: ast.FunctionLiteral }
45
+ | { op: 'call'; argc: number; node: ast.Call }
46
+ | { op: 'return'; hasValue: boolean; node: ast.Return }
47
+ | { op: 'jump'; target: number }
48
+ /** Saca la condición (debe ser lógica) y salta si vale `jumpWhen`. */
49
+ | {
50
+ op: 'branch';
51
+ target: number;
52
+ jumpWhen: boolean;
53
+ label: DecisionLabel;
54
+ node: ast.Expression;
55
+ owner: ast.Statement;
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 }
78
+ /** Cortocircuito: revisa el lado izquierdo de `y`/`o`; si ya decide el resultado, lo deja y salta. */
79
+ | { op: 'shortCircuit'; target: number; node: ast.Logical }
80
+ /** Revisa que el lado derecho de `y`/`o` sea lógico. */
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 }
86
+ | { op: 'enterScope' }
87
+ | { op: 'exitScope' }
88
+ | { op: 'halt' };
89
+
90
+ /** Código de un procedimiento o del programa principal. */
91
+ export interface Chunk {
92
+ name: string;
93
+ params: string[];
94
+ code: Instruction[];
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;
103
+ }
104
+
105
+ /** Traduce el AST a instrucciones. El AST debe venir sin errores de sintaxis. */
106
+ export const compile = (program: ast.Program): Chunk => {
107
+ const compiler = new Compiler({ name: 'programa', params: [], code: [], node: program }, false);
108
+ compiler.statements(program.statements);
109
+ compiler.emit({ op: 'halt' });
110
+ return compiler.chunk;
111
+ };
112
+
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 = {
116
+ name: node.name ?? 'procedimiento',
117
+ params: node.params.map(param => param.name),
118
+ code: [],
119
+ node,
120
+ };
121
+ if (hidden) chunk.hidden = true;
122
+ const compiler = new Compiler(chunk, hidden);
123
+ // El cuerpo comparte alcance con los parámetros: así `variable x` dentro de `procedimiento(x)` es un error.
124
+ compiler.statements(node.body.statements);
125
+ compiler.emit({ op: 'const', value: null, node });
126
+ compiler.emit({ op: 'return', hasValue: true, node: { kind: 'Return', id: node.id, span: node.body.span } });
127
+ return compiler.chunk;
128
+ };
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
+
149
+ class Compiler {
150
+ private scopeDepth = 0;
151
+ private readonly loops: Loop[] = [];
152
+
153
+ constructor(
154
+ readonly chunk: Chunk,
155
+ private readonly hidden: boolean,
156
+ ) {}
157
+
158
+ emit(instruction: Instruction): number {
159
+ this.chunk.code.push(instruction);
160
+ return this.chunk.code.length - 1;
161
+ }
162
+
163
+ private get here(): number {
164
+ return this.chunk.code.length;
165
+ }
166
+
167
+ private patch(at: number, target: number): void {
168
+ const instruction = this.chunk.code[at];
169
+ if (instruction && 'target' in instruction) instruction.target = target;
170
+ }
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
+
211
+ statement(node: ast.Statement): void {
212
+ if (node.kind === 'FunctionDeclaration') return; // ya se declaró al entrar al bloque
213
+ if (node.kind !== 'Block') this.emit({ op: 'step', node });
214
+ switch (node.kind) {
215
+ case 'VariableDeclaration':
216
+ this.expression(node.value);
217
+ this.emit({ op: 'declare', name: node.name.name, constant: node.constant, target: node.name, node });
218
+ return;
219
+ case 'Assignment':
220
+ case 'Increment':
221
+ this.assignment(node);
222
+ return;
223
+ case 'ExpressionStatement':
224
+ this.expression(node.expression);
225
+ this.emit({ op: 'result', node });
226
+ return;
227
+ case 'Return':
228
+ if (node.value) this.expression(node.value);
229
+ this.emit({ op: 'return', hasValue: Boolean(node.value), node });
230
+ return;
231
+ case 'Block':
232
+ this.block(node);
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
+ }
261
+ case 'If': {
262
+ const exits: number[] = [];
263
+ node.branches.forEach((branch, index) => {
264
+ this.expression(branch.condition);
265
+ const skip = this.emit({
266
+ op: 'branch',
267
+ target: -1,
268
+ jumpWhen: false,
269
+ label: index === 0 ? 'si' : 'pero_si',
270
+ node: branch.condition,
271
+ owner: node,
272
+ });
273
+ this.block(branch.body);
274
+ exits.push(this.emit({ op: 'jump', target: -1 }));
275
+ this.patch(skip, this.here);
276
+ });
277
+ if (node.alternative) this.block(node.alternative);
278
+ for (const exit of exits) this.patch(exit, this.here);
279
+ return;
280
+ }
281
+ case 'While': {
282
+ const start = this.here;
283
+ this.expression(node.condition);
284
+ const exit = this.emit({
285
+ op: 'branch',
286
+ target: -1,
287
+ jumpWhen: false,
288
+ label: 'mientras',
289
+ node: node.condition,
290
+ owner: node,
291
+ });
292
+ const loop = this.loop(0, () => this.block(node.body));
293
+ this.emit({ op: 'jump', target: start });
294
+ this.patch(exit, this.here);
295
+ this.connect(loop.continues, start);
296
+ this.connect(loop.breaks, this.here);
297
+ return;
298
+ }
299
+ case 'DoUntil': {
300
+ const start = this.here;
301
+ const loop = this.loop(0, () => this.block(node.body));
302
+ this.connect(loop.continues, this.here);
303
+ this.expression(node.condition);
304
+ this.emit({
305
+ op: 'branch',
306
+ target: start,
307
+ jumpWhen: false,
308
+ label: 'hasta_que',
309
+ node: node.condition,
310
+ owner: node,
311
+ });
312
+ this.connect(loop.breaks, this.here);
313
+ return;
314
+ }
315
+ case 'For': {
316
+ this.enterScope();
317
+ if (node.init) this.statement(node.init);
318
+ const start = this.here;
319
+ let exit = -1;
320
+ if (node.condition) {
321
+ this.expression(node.condition);
322
+ exit = this.emit({
323
+ op: 'branch',
324
+ target: -1,
325
+ jumpWhen: false,
326
+ label: 'para',
327
+ node: node.condition,
328
+ owner: node,
329
+ });
330
+ }
331
+ const loop = this.loop(0, () => this.block(node.body));
332
+ this.connect(loop.continues, this.here);
333
+ if (node.update) this.statement(node.update);
334
+ this.emit({ op: 'jump', target: start });
335
+ if (exit >= 0) this.patch(exit, this.here);
336
+ this.connect(loop.breaks, this.here);
337
+ this.exitScope();
338
+ return;
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 });
431
+ }
432
+ value();
433
+ if (operator) this.emit({ op: 'binary', operator, node });
434
+ this.emit({ op: 'setIndex', target, node });
435
+ }
436
+
437
+ private block(node: ast.Block): void {
438
+ this.enterScope();
439
+ this.statements(node.statements);
440
+ this.exitScope();
441
+ }
442
+
443
+ private expression(node: ast.Expression): void {
444
+ switch (node.kind) {
445
+ case 'NumberLiteral':
446
+ case 'StringLiteral':
447
+ case 'BooleanLiteral':
448
+ this.emit({ op: 'const', value: node.value, node });
449
+ return;
450
+ case 'NullLiteral':
451
+ this.emit({ op: 'const', value: null, node });
452
+ return;
453
+ case 'ListLiteral':
454
+ for (const element of node.elements) this.expression(element);
455
+ this.emit({ op: 'list', count: node.elements.length, node });
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;
464
+ case 'Identifier':
465
+ this.emit({ op: 'load', name: node.name, node });
466
+ return;
467
+ case 'Unary':
468
+ this.expression(node.operand);
469
+ this.emit({ op: 'unary', operator: node.operator, node });
470
+ return;
471
+ case 'Binary':
472
+ this.expression(node.left);
473
+ this.expression(node.right);
474
+ this.emit({ op: 'binary', operator: node.operator, node });
475
+ return;
476
+ case 'Logical': {
477
+ this.expression(node.left);
478
+ const skip = this.emit({ op: 'shortCircuit', target: -1, node });
479
+ this.expression(node.right);
480
+ this.emit({ op: 'checkLogical', node });
481
+ this.patch(skip, this.here);
482
+ return;
483
+ }
484
+ case 'Call':
485
+ this.expression(node.callee);
486
+ for (const arg of node.args) this.expression(arg);
487
+ this.emit({ op: 'call', argc: node.args.length, node });
488
+ return;
489
+ case 'Index':
490
+ this.expression(node.object);
491
+ this.expression(node.index);
492
+ this.emit({ op: 'index', node });
493
+ return;
494
+ case 'FunctionLiteral':
495
+ this.emit({ op: 'closure', chunk: compileProcedure(node, this.hidden), node });
496
+ return;
497
+ }
498
+ }
499
+ }
@@ -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
+ };