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.
- package/CHANGELOG.md +1 -1
- package/README.md +26 -25
- package/dist/index.js +151 -9
- package/dist/index.js.map +22 -12
- package/dist/worker.js +153 -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 +192 -86
- package/docs/guia-aprender-desde-el-codigo.md +5 -5
- package/docs/integracion.md +104 -0
- package/docs/lenguaje.md +435 -60
- package/docs/modo-mundo.md +128 -0
- package/docs/recetario.md +63 -63
- package/package.json +11 -20
- package/src/catalog.ts +256 -0
- package/src/challenge.ts +138 -0
- package/src/engine.ts +7 -0
- package/src/error-codes.ts +427 -0
- package/src/flow.ts +385 -0
- package/src/index.ts +53 -1
- package/src/printer.ts +133 -0
- package/src/runtime/builtins.ts +1358 -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 +119 -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,128 @@
|
|
|
1
|
+
# Modo mundo
|
|
2
|
+
|
|
3
|
+
Codexivo nació de querer algo como el robot Karel. El **modo mundo** es el mismo lenguaje con procedimientos extra para
|
|
4
|
+
mover un robot en una cuadrícula. Este repositorio define el mundo, sus reglas y sus eventos; el sitio lo dibuja.
|
|
5
|
+
|
|
6
|
+
## El mundo
|
|
7
|
+
|
|
8
|
+
Un mundo se describe en JSON (formato versión 1):
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"version": 1,
|
|
13
|
+
"width": 5,
|
|
14
|
+
"height": 1,
|
|
15
|
+
"walls": [],
|
|
16
|
+
"beepers": [
|
|
17
|
+
{ "x": 2, "y": 1, "count": 1 },
|
|
18
|
+
{ "x": 4, "y": 1, "count": 3 }
|
|
19
|
+
],
|
|
20
|
+
"robot": { "x": 1, "y": 1, "facing": "este", "beepers": 0 }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Campo | Qué es |
|
|
25
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `version` | Siempre `1` |
|
|
27
|
+
| `width`, `height` | Casillas de oeste a este y de sur a norte (de 1 a 100). La casilla suroeste es `(1, 1)` |
|
|
28
|
+
| `walls` | Paredes: `{ "x", "y", "side" }` es la pared del lado `side` (`"norte"`, `"este"`, `"sur"`, `"oeste"`) de esa casilla |
|
|
29
|
+
| `beepers` | Zumbadores por casilla: `{ "x", "y", "count" }`, con `count` un entero o `"infinito"` |
|
|
30
|
+
| `robot` | Posición, orientación (`facing`) y zumbadores en la mochila (`beepers`, entero o `"infinito"`) |
|
|
31
|
+
|
|
32
|
+
Los bordes de la cuadrícula siempre son paredes. `validateWorld(json)` revisa un mundo y regresa los problemas con su
|
|
33
|
+
ubicación (`robot.x`, `walls[2].side`…) para mostrarlos en el editor.
|
|
34
|
+
|
|
35
|
+
## Los procedimientos
|
|
36
|
+
|
|
37
|
+
Solo existen cuando el programa corre en un mundo.
|
|
38
|
+
|
|
39
|
+
| Acción | Qué hace |
|
|
40
|
+
| -------------------- | ---------------------------------------------- |
|
|
41
|
+
| `avanzar()` | Avanza una casilla hacia donde mira |
|
|
42
|
+
| `girar_izquierda()` | Gira 90° a la izquierda |
|
|
43
|
+
| `recoger_zumbador()` | Toma un zumbador de la casilla y lo guarda |
|
|
44
|
+
| `dejar_zumbador()` | Deja un zumbador de la mochila en la casilla |
|
|
45
|
+
| `apagar()` | Apaga el robot; después ya no puede hacer nada |
|
|
46
|
+
|
|
47
|
+
| Pregunta | Responde `verdadero` si… |
|
|
48
|
+
| ----------------------------------------------------------------------------------- | -------------------------------------- |
|
|
49
|
+
| `frente_libre()`, `izquierda_libre()`, `derecha_libre()` | No hay pared en esa dirección |
|
|
50
|
+
| `hay_zumbador()` | Hay al menos un zumbador en la casilla |
|
|
51
|
+
| `tengo_zumbadores()` | Hay al menos un zumbador en la mochila |
|
|
52
|
+
| `mirando_al_norte()`, `mirando_al_sur()`, `mirando_al_este()`, `mirando_al_oeste()` | El robot mira hacia ese lado |
|
|
53
|
+
|
|
54
|
+
No hay `girar_derecha()`: construirlo es el primer ejercicio.
|
|
55
|
+
|
|
56
|
+
## Un ejemplo
|
|
57
|
+
|
|
58
|
+
Recoger todos los zumbadores de la fila y volver al inicio:
|
|
59
|
+
|
|
60
|
+
```codexivo
|
|
61
|
+
procedimiento girar_derecha() {
|
|
62
|
+
repetir 3 veces {
|
|
63
|
+
girar_izquierda()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
procedimiento recoger_todos() {
|
|
68
|
+
mientras (hay_zumbador()) {
|
|
69
|
+
recoger_zumbador()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
mientras (frente_libre()) {
|
|
74
|
+
avanzar()
|
|
75
|
+
recoger_todos()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// media vuelta y de regreso
|
|
79
|
+
girar_derecha()
|
|
80
|
+
girar_derecha()
|
|
81
|
+
mientras (frente_libre()) {
|
|
82
|
+
avanzar()
|
|
83
|
+
}
|
|
84
|
+
apagar()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Al terminar, el robot está en `(1, 1)` mirando al oeste, apagado y con los 4 zumbadores en la mochila.
|
|
88
|
+
|
|
89
|
+
## Errores
|
|
90
|
+
|
|
91
|
+
Son errores de ejecución que dicen dónde está y hacia dónde mira el robot:
|
|
92
|
+
|
|
93
|
+
| Código | Cuándo |
|
|
94
|
+
| ------------------ | ----------------------------------------------- |
|
|
95
|
+
| `CHOQUE_CON_PARED` | `avanzar()` contra una pared o el borde |
|
|
96
|
+
| `SIN_ZUMBADORES` | `recoger_zumbador()` donde no hay |
|
|
97
|
+
| `MOCHILA_VACÍA` | `dejar_zumbador()` sin zumbadores en la mochila |
|
|
98
|
+
| `ROBOT_APAGADO` | Cualquier acción después de `apagar()` |
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Error de ejecución en la línea 3, columna 1: El robot chocó con una pared. El robot está en (2, 1) mirando al este.
|
|
102
|
+
Pista: Pregunta antes con `frente_libre()`.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Desde JavaScript
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { checkGoal, createWorld, run } from 'codexivo';
|
|
109
|
+
|
|
110
|
+
const world = createWorld(definición);
|
|
111
|
+
const result = run(código, { world, trace: true });
|
|
112
|
+
|
|
113
|
+
result.world; // estado final: el mismo formato del mundo, más `robot.on`
|
|
114
|
+
result.events?.filter(event => event.type === 'robot'); // una por acción: before, after y la casilla
|
|
115
|
+
|
|
116
|
+
checkGoal(result.world!, {
|
|
117
|
+
robot: { x: 1, y: 1, on: false },
|
|
118
|
+
beepers: [],
|
|
119
|
+
onlyListedBeepers: true, // las casillas no listadas tienen que quedar vacías
|
|
120
|
+
});
|
|
121
|
+
// → { ok: true, differences: [] }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- Cada acción emite un evento `robot` con `action`, el robot `before` y `after` (posición, orientación, mochila y si
|
|
125
|
+
está encendido) y, al recoger o dejar, los zumbadores que quedaron en la casilla (`cell`).
|
|
126
|
+
- `machine.snapshot().world` trae el estado del mundo en cualquier pausa.
|
|
127
|
+
- `checkGoal` compara el mundo final con un objetivo y solo revisa lo que el objetivo indica; cada diferencia trae un
|
|
128
|
+
mensaje para mostrarlo en el reto.
|
package/docs/recetario.md
CHANGED
|
@@ -7,10 +7,10 @@ archivo `.codexivo`.
|
|
|
7
7
|
|
|
8
8
|
```codexivo
|
|
9
9
|
variable saludar = procedimiento(nombre) {
|
|
10
|
-
regresa "Hola " + nombre + "!"
|
|
11
|
-
}
|
|
10
|
+
regresa "Hola " + nombre + "!"
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
-
saludar("Codexivo")
|
|
13
|
+
saludar("Codexivo")
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Demuestra cómo declarar un procedimiento, concatenar cadenas y retornar un valor.
|
|
@@ -19,16 +19,16 @@ Demuestra cómo declarar un procedimiento, concatenar cadenas y retornar un valo
|
|
|
19
19
|
|
|
20
20
|
```codexivo
|
|
21
21
|
variable clasificar = procedimiento(edad) {
|
|
22
|
-
si (edad
|
|
23
|
-
regresa "adulto"
|
|
24
|
-
} pero_si (edad
|
|
25
|
-
regresa "adolescente"
|
|
22
|
+
si (edad es_mayor_o_igual_a 18) {
|
|
23
|
+
regresa "adulto"
|
|
24
|
+
} pero_si (edad es_mayor_o_igual_a 13) {
|
|
25
|
+
regresa "adolescente"
|
|
26
26
|
} si_no {
|
|
27
|
-
regresa "niño"
|
|
27
|
+
regresa "niño"
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
clasificar(16)
|
|
31
|
+
clasificar(16)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Ilustra el encadenamiento `si`/`pero_si`/`si_no` y el uso de operadores relacionales.
|
|
@@ -37,11 +37,11 @@ Ilustra el encadenamiento `si`/`pero_si`/`si_no` y el uso de operadores relacion
|
|
|
37
37
|
|
|
38
38
|
```codexivo
|
|
39
39
|
variable promedio = procedimiento(a, b, c) {
|
|
40
|
-
variable total = a + b + c
|
|
41
|
-
regresa total / 3
|
|
42
|
-
}
|
|
40
|
+
variable total = a + b + c
|
|
41
|
+
regresa total / 3
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
promedio(8, 7.5, 9)
|
|
44
|
+
promedio(8, 7.5, 9)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
Combina números enteros y decimales con operaciones aritméticas.
|
|
@@ -50,30 +50,30 @@ Combina números enteros y decimales con operaciones aritméticas.
|
|
|
50
50
|
|
|
51
51
|
```codexivo
|
|
52
52
|
variable valorAbsoluto = procedimiento(n) {
|
|
53
|
-
si (n
|
|
54
|
-
regresa -n
|
|
53
|
+
si (n es_menor_que 0) {
|
|
54
|
+
regresa -n
|
|
55
55
|
} si_no {
|
|
56
|
-
regresa n
|
|
56
|
+
regresa n
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|
|
59
59
|
|
|
60
|
-
valorAbsoluto(-42)
|
|
60
|
+
valorAbsoluto(-42)
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
Ejemplo de uso del operador prefijo
|
|
63
|
+
Ejemplo de uso del operador prefijo `-`.
|
|
64
64
|
|
|
65
65
|
## 5. Contador recursivo
|
|
66
66
|
|
|
67
67
|
```codexivo
|
|
68
68
|
variable cuentaRegresiva = procedimiento(n) {
|
|
69
|
-
si (n
|
|
70
|
-
regresa "¡Despegue!"
|
|
69
|
+
si (n es_igual_a 0) {
|
|
70
|
+
regresa "¡Despegue!"
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
regresa cuentaRegresiva(n - 1)
|
|
74
|
-
}
|
|
73
|
+
regresa cuentaRegresiva(n - 1)
|
|
74
|
+
}
|
|
75
75
|
|
|
76
|
-
cuentaRegresiva(3)
|
|
76
|
+
cuentaRegresiva(3)
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
Muestra cómo las funciones en Codexivo soportan recursión gracias a que conservan su entorno de definición.
|
|
@@ -82,48 +82,48 @@ Muestra cómo las funciones en Codexivo soportan recursión gracias a que conser
|
|
|
82
82
|
|
|
83
83
|
```codexivo
|
|
84
84
|
variable esNombreValido = procedimiento(nombre) {
|
|
85
|
-
variable tamaño = longitud(nombre)
|
|
86
|
-
si (tamaño
|
|
87
|
-
regresa verdadero
|
|
85
|
+
variable tamaño = longitud(nombre)
|
|
86
|
+
si (tamaño es_mayor_o_igual_a 3 y tamaño es_menor_o_igual_a 15) {
|
|
87
|
+
regresa verdadero
|
|
88
88
|
}
|
|
89
|
-
regresa falso
|
|
90
|
-
}
|
|
89
|
+
regresa falso
|
|
90
|
+
}
|
|
91
91
|
|
|
92
|
-
esNombreValido("Ana")
|
|
92
|
+
esNombreValido("Ana")
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
Ejemplo de uso del builtin `longitud` junto con operadores de comparación y booleanos.
|
|
96
96
|
|
|
97
|
-
## 7. Suma con bucle
|
|
97
|
+
## 7. Suma con un bucle que cuenta
|
|
98
98
|
|
|
99
99
|
```codexivo
|
|
100
|
-
|
|
101
|
-
variable suma = 0
|
|
102
|
-
para
|
|
103
|
-
suma
|
|
100
|
+
procedimiento sumar_hasta(n) {
|
|
101
|
+
variable suma = 0
|
|
102
|
+
para i desde 1 hasta n {
|
|
103
|
+
suma = suma + i
|
|
104
104
|
}
|
|
105
|
-
regresa suma
|
|
106
|
-
}
|
|
105
|
+
regresa suma
|
|
106
|
+
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
sumar_hasta(10) // Suma de 1 a 10 = 55
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Demuestra
|
|
111
|
+
Demuestra `para … desde … hasta`, que incluye los dos extremos.
|
|
112
112
|
|
|
113
113
|
## 8. Búsqueda en arreglo
|
|
114
114
|
|
|
115
115
|
```codexivo
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
si (
|
|
119
|
-
regresa i
|
|
116
|
+
procedimiento buscar_posición(lista, elemento) {
|
|
117
|
+
para_cada i en índices(lista) {
|
|
118
|
+
si (lista[i] es_igual_a elemento) {
|
|
119
|
+
regresa i
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
regresa -1
|
|
123
|
-
}
|
|
122
|
+
regresa -1
|
|
123
|
+
}
|
|
124
124
|
|
|
125
|
-
variable
|
|
126
|
-
|
|
125
|
+
variable números = [10, 20, 30, 40]
|
|
126
|
+
buscar_posición(números, 30) // Regresa 2
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
Combina bucles, acceso a arreglos, condicionales y funciones built-in.
|
|
@@ -132,25 +132,25 @@ Combina bucles, acceso a arreglos, condicionales y funciones built-in.
|
|
|
132
132
|
|
|
133
133
|
```codexivo
|
|
134
134
|
variable estadisticas = procedimiento(datos) {
|
|
135
|
-
variable suma = 0
|
|
136
|
-
variable mayor = primero(datos)
|
|
137
|
-
variable menor = primero(datos)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
suma
|
|
141
|
-
si (
|
|
142
|
-
mayor =
|
|
135
|
+
variable suma = 0
|
|
136
|
+
variable mayor = primero(datos)
|
|
137
|
+
variable menor = primero(datos)
|
|
138
|
+
|
|
139
|
+
para_cada dato en datos {
|
|
140
|
+
suma = suma + dato
|
|
141
|
+
si (dato es_mayor_que mayor) {
|
|
142
|
+
mayor = dato
|
|
143
143
|
}
|
|
144
|
-
si (
|
|
145
|
-
menor =
|
|
144
|
+
si (dato es_menor_que menor) {
|
|
145
|
+
menor = dato
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
variable promedio = suma / longitud(datos)
|
|
149
|
-
|
|
150
|
-
regresa [redondear(promedio), mayor, menor];
|
|
151
|
-
};
|
|
148
|
+
variable promedio = suma / longitud(datos)
|
|
152
149
|
|
|
153
|
-
|
|
150
|
+
regresa [redondear(promedio), mayor, menor]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
estadisticas([5, 2, 8, 1, 9]) // [5, 9, 1]
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Muestra el uso de las funciones matemáticas built-in y manipulación de arreglos.
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexivo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Lenguaje de programación en español para practicar lógica y algoritmos, que se ejecuta en el navegador.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "src/index.ts",
|
|
9
9
|
"files": [
|
|
10
|
-
"bin/codexivo.exe",
|
|
11
|
-
"postinstall.mjs",
|
|
12
10
|
"dist/index.js",
|
|
13
11
|
"dist/index.js.map",
|
|
12
|
+
"dist/worker.js",
|
|
13
|
+
"dist/worker.js.map",
|
|
14
14
|
"src",
|
|
15
15
|
"!src/cli.ts",
|
|
16
16
|
"docs",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
".": {
|
|
21
21
|
"types": "./src/index.ts",
|
|
22
22
|
"import": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./worker": {
|
|
25
|
+
"types": "./src/worker.ts",
|
|
26
|
+
"import": "./dist/worker.js"
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"scripts": {
|
|
@@ -27,16 +31,16 @@
|
|
|
27
31
|
"check": "bun check",
|
|
28
32
|
"repl": "bun run src/cli.ts",
|
|
29
33
|
"build": "bun run build:lib && bun run build:binary",
|
|
30
|
-
"build:lib": "bun build ./src/index.ts --target browser --format esm --outdir=dist --minify --sourcemap=linked",
|
|
34
|
+
"build:lib": "bun build ./src/index.ts ./src/worker.ts --target browser --format esm --outdir=dist --minify --sourcemap=linked",
|
|
31
35
|
"build:binary": "bun run scripts/build-binaries.ts",
|
|
32
36
|
"build:binaries": "bun run scripts/build-binaries.ts --all",
|
|
33
37
|
"test:package": "bun run scripts/test-package.ts",
|
|
38
|
+
"docs:biblioteca": "bun run scripts/docs-biblioteca.ts",
|
|
34
39
|
"prepack": "bun run build:lib",
|
|
35
40
|
"version": "bun run scripts/sync-native-versions.ts && git add package.json bun.lock packages/*/package.json",
|
|
36
41
|
"publish:native": "bun run scripts/publish-native.ts",
|
|
37
42
|
"format": "bun fmt",
|
|
38
|
-
"format:check": "bun fmt --check"
|
|
39
|
-
"postinstall": "node ./postinstall.mjs"
|
|
43
|
+
"format:check": "bun fmt --check"
|
|
40
44
|
},
|
|
41
45
|
"keywords": [
|
|
42
46
|
"lenguaje de programación",
|
|
@@ -60,18 +64,5 @@
|
|
|
60
64
|
},
|
|
61
65
|
"workspaces": [
|
|
62
66
|
"packages/*"
|
|
63
|
-
]
|
|
64
|
-
"optionalDependencies": {
|
|
65
|
-
"codexivo-linux-x64": "0.2.0",
|
|
66
|
-
"codexivo-linux-x64-musl": "0.2.0",
|
|
67
|
-
"codexivo-linux-arm64": "0.2.0",
|
|
68
|
-
"codexivo-linux-arm64-musl": "0.2.0",
|
|
69
|
-
"codexivo-darwin-x64": "0.2.0",
|
|
70
|
-
"codexivo-darwin-arm64": "0.2.0",
|
|
71
|
-
"codexivo-windows-x64": "0.2.0",
|
|
72
|
-
"codexivo-windows-arm64": "0.2.0"
|
|
73
|
-
},
|
|
74
|
-
"bin": {
|
|
75
|
-
"codexivo": "./bin/codexivo.exe"
|
|
76
|
-
}
|
|
67
|
+
]
|
|
77
68
|
}
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { ERROR_CODES } from './error-codes';
|
|
2
|
+
import type { ErrorKind } from './errors';
|
|
3
|
+
import { constantDocs, constants, natives, withoutAccents } from './runtime/builtins';
|
|
4
|
+
import { libraryDocs } from './runtime/prelude';
|
|
5
|
+
import type { ProcedureCategory, ProcedureDoc, ProcedureLevel } from './runtime/values';
|
|
6
|
+
import { KEYWORDS, TokenType } from './syntax/token';
|
|
7
|
+
import { EXAMPLE_WORLD, worldDocs } from './world';
|
|
8
|
+
import type { WorldDefinition } from './world';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* El lenguaje y su biblioteca como datos, para que el resaltado, el autocompletado, la documentación y los
|
|
12
|
+
* avisos del editor del sitio salgan de una sola fuente. Todo es serializable: no hay funciones.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export type KeywordGroup = 'declaración' | 'control' | 'lógicas' | 'literales' | 'operadores';
|
|
16
|
+
|
|
17
|
+
export interface OperatorEntry {
|
|
18
|
+
symbol?: string;
|
|
19
|
+
/** Forma en palabras (`es_igual_a`, `mod`), si la tiene. */
|
|
20
|
+
word?: string;
|
|
21
|
+
/** Nivel de precedencia de la §4.1: 1 es el que agrupa al final, 9 el primero. */
|
|
22
|
+
precedence: number;
|
|
23
|
+
associativity: 'izquierda' | 'derecha' | 'prefijo' | 'sin encadenar';
|
|
24
|
+
description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProcedureEntry extends ProcedureDoc {
|
|
28
|
+
/** Nombre canónico, con acento si lo lleva. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Otras formas aceptadas (el nombre sin acento). */
|
|
31
|
+
aliases: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConstantEntry {
|
|
35
|
+
name: string;
|
|
36
|
+
value: number;
|
|
37
|
+
level: ProcedureLevel;
|
|
38
|
+
description: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ErrorEntry {
|
|
42
|
+
code: string;
|
|
43
|
+
kind: ErrorKind;
|
|
44
|
+
explanation: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Catalog {
|
|
48
|
+
version: 1;
|
|
49
|
+
keywords: Array<{ group: KeywordGroup; words: string[] }>;
|
|
50
|
+
operators: OperatorEntry[];
|
|
51
|
+
/** Procedimientos de la biblioteca, incluidos los del modo mundo (categoría `mundo`). */
|
|
52
|
+
library: ProcedureEntry[];
|
|
53
|
+
constants: ConstantEntry[];
|
|
54
|
+
errors: ErrorEntry[];
|
|
55
|
+
categories: ProcedureCategory[];
|
|
56
|
+
/** Mundo con el que se ejecutan los ejemplos de la categoría `mundo`. */
|
|
57
|
+
exampleWorld: WorldDefinition;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const KEYWORD_GROUPS: Readonly<Record<string, KeywordGroup>> = {
|
|
61
|
+
[TokenType.LET]: 'declaración',
|
|
62
|
+
[TokenType.CONST]: 'declaración',
|
|
63
|
+
[TokenType.DOMAIN]: 'declaración',
|
|
64
|
+
[TokenType.FUNCTION]: 'declaración',
|
|
65
|
+
[TokenType.RETURN]: 'declaración',
|
|
66
|
+
[TokenType.IF]: 'control',
|
|
67
|
+
[TokenType.ELSEIF]: 'control',
|
|
68
|
+
[TokenType.ELSE]: 'control',
|
|
69
|
+
[TokenType.WHILE]: 'control',
|
|
70
|
+
[TokenType.DO]: 'control',
|
|
71
|
+
[TokenType.UNTIL]: 'control',
|
|
72
|
+
[TokenType.FOR]: 'control',
|
|
73
|
+
[TokenType.FOR_EACH]: 'control',
|
|
74
|
+
[TokenType.FROM]: 'control',
|
|
75
|
+
[TokenType.TO]: 'control',
|
|
76
|
+
[TokenType.STEP]: 'control',
|
|
77
|
+
[TokenType.IN]: 'control',
|
|
78
|
+
[TokenType.REPEAT]: 'control',
|
|
79
|
+
[TokenType.TIMES]: 'control',
|
|
80
|
+
[TokenType.BREAK]: 'control',
|
|
81
|
+
[TokenType.CONTINUE]: 'control',
|
|
82
|
+
[TokenType.AND]: 'lógicas',
|
|
83
|
+
[TokenType.OR]: 'lógicas',
|
|
84
|
+
[TokenType.NOT]: 'lógicas',
|
|
85
|
+
[TokenType.TRUE]: 'literales',
|
|
86
|
+
[TokenType.FALSE]: 'literales',
|
|
87
|
+
[TokenType.NULL]: 'literales',
|
|
88
|
+
[TokenType.MOD]: 'operadores',
|
|
89
|
+
[TokenType.DIV]: 'operadores',
|
|
90
|
+
[TokenType.EQ]: 'operadores',
|
|
91
|
+
[TokenType.NEQ]: 'operadores',
|
|
92
|
+
[TokenType.LT]: 'operadores',
|
|
93
|
+
[TokenType.GT]: 'operadores',
|
|
94
|
+
[TokenType.LTE]: 'operadores',
|
|
95
|
+
[TokenType.GTE]: 'operadores',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const GROUP_ORDER: KeywordGroup[] = ['declaración', 'control', 'lógicas', 'literales', 'operadores'];
|
|
99
|
+
|
|
100
|
+
/** Grupo de cada palabra reservada; una palabra sin grupo es un error del catálogo (lo revisa una prueba). */
|
|
101
|
+
export const keywordGroup = (word: string): KeywordGroup | undefined => KEYWORD_GROUPS[KEYWORDS[word] ?? ''];
|
|
102
|
+
|
|
103
|
+
const keywords = GROUP_ORDER.map(group => ({
|
|
104
|
+
group,
|
|
105
|
+
words: Object.keys(KEYWORDS).filter(word => keywordGroup(word) === group),
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
const operators: OperatorEntry[] = [
|
|
109
|
+
{
|
|
110
|
+
word: 'o',
|
|
111
|
+
precedence: 1,
|
|
112
|
+
associativity: 'izquierda',
|
|
113
|
+
description: 'Verdadero si alguno de los dos lo es. Si el izquierdo es verdadero, no evalúa el derecho.',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
word: 'y',
|
|
117
|
+
precedence: 2,
|
|
118
|
+
associativity: 'izquierda',
|
|
119
|
+
description: 'Verdadero si los dos lo son. Si el izquierdo es falso, no evalúa el derecho.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
symbol: '!',
|
|
123
|
+
word: 'no',
|
|
124
|
+
precedence: 3,
|
|
125
|
+
associativity: 'prefijo',
|
|
126
|
+
description: 'Cambia verdadero por falso y al revés.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
symbol: '==',
|
|
130
|
+
word: 'es_igual_a',
|
|
131
|
+
precedence: 4,
|
|
132
|
+
associativity: 'sin encadenar',
|
|
133
|
+
description: 'Igualdad por valor, también de listas y diccionarios.',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
symbol: '!=',
|
|
137
|
+
word: 'es_distinto_de',
|
|
138
|
+
precedence: 4,
|
|
139
|
+
associativity: 'sin encadenar',
|
|
140
|
+
description: 'Lo contrario de `==`.',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
symbol: '<',
|
|
144
|
+
word: 'es_menor_que',
|
|
145
|
+
precedence: 4,
|
|
146
|
+
associativity: 'sin encadenar',
|
|
147
|
+
description: 'Compara dos números o dos textos.',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
symbol: '>',
|
|
151
|
+
word: 'es_mayor_que',
|
|
152
|
+
precedence: 4,
|
|
153
|
+
associativity: 'sin encadenar',
|
|
154
|
+
description: 'Compara dos números o dos textos.',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
symbol: '<=',
|
|
158
|
+
word: 'es_menor_o_igual_a',
|
|
159
|
+
precedence: 4,
|
|
160
|
+
associativity: 'sin encadenar',
|
|
161
|
+
description: 'Compara dos números o dos textos.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
symbol: '>=',
|
|
165
|
+
word: 'es_mayor_o_igual_a',
|
|
166
|
+
precedence: 4,
|
|
167
|
+
associativity: 'sin encadenar',
|
|
168
|
+
description: 'Compara dos números o dos textos.',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
symbol: '+',
|
|
172
|
+
precedence: 5,
|
|
173
|
+
associativity: 'izquierda',
|
|
174
|
+
description: 'Suma dos números o une dos textos; no los mezcla.',
|
|
175
|
+
},
|
|
176
|
+
{ symbol: '-', precedence: 5, associativity: 'izquierda', description: 'Resta.' },
|
|
177
|
+
{ symbol: '*', precedence: 6, associativity: 'izquierda', description: 'Multiplica.' },
|
|
178
|
+
{ symbol: '/', precedence: 6, associativity: 'izquierda', description: 'División real: `7 / 2` es `3.5`.' },
|
|
179
|
+
{
|
|
180
|
+
word: 'div',
|
|
181
|
+
precedence: 6,
|
|
182
|
+
associativity: 'izquierda',
|
|
183
|
+
description: 'División entera hacia abajo: `-7 div 2` es `-4`.',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
symbol: '%',
|
|
187
|
+
word: 'mod',
|
|
188
|
+
precedence: 6,
|
|
189
|
+
associativity: 'izquierda',
|
|
190
|
+
description: 'Residuo consistente con `div`: `-7 mod 3` es `2`.',
|
|
191
|
+
},
|
|
192
|
+
{ symbol: '-', precedence: 7, associativity: 'prefijo', description: 'Cambia el signo.' },
|
|
193
|
+
{
|
|
194
|
+
symbol: '^',
|
|
195
|
+
precedence: 8,
|
|
196
|
+
associativity: 'derecha',
|
|
197
|
+
description: 'Potencia: `2 ^ 3 ^ 2` es `2 ^ 9`, y `-2 ^ 2` es `-4`.',
|
|
198
|
+
},
|
|
199
|
+
{ symbol: '( )', precedence: 9, associativity: 'izquierda', description: 'Llama a un procedimiento: `f(x)`.' },
|
|
200
|
+
{
|
|
201
|
+
symbol: '[ ]',
|
|
202
|
+
precedence: 9,
|
|
203
|
+
associativity: 'izquierda',
|
|
204
|
+
description: 'Elemento de una lista o texto, o clave de un diccionario: `l[0]`, `d["a"]`.',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
symbol: '.',
|
|
208
|
+
precedence: 9,
|
|
209
|
+
associativity: 'izquierda',
|
|
210
|
+
description: 'Campo de un diccionario: `persona.edad` es `persona["edad"]`.',
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
const entry = (name: string, doc: ProcedureDoc): ProcedureEntry => {
|
|
215
|
+
const alias = withoutAccents(name);
|
|
216
|
+
return { name, aliases: alias === name ? [] : [alias], ...doc };
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const library: ProcedureEntry[] = [
|
|
220
|
+
...Object.values(natives)
|
|
221
|
+
.filter((native, index, all) => all.indexOf(native) === index)
|
|
222
|
+
.map(native => entry(native.name, native.doc!)),
|
|
223
|
+
...Object.entries(libraryDocs).map(([name, doc]) => entry(name, doc)),
|
|
224
|
+
...Object.entries(worldDocs).map(([name, doc]) => entry(name, doc)),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const CATEGORIES: ProcedureCategory[] = [
|
|
228
|
+
'entrada y salida',
|
|
229
|
+
'tipos',
|
|
230
|
+
'matemáticas',
|
|
231
|
+
'texto',
|
|
232
|
+
'listas',
|
|
233
|
+
'diccionarios',
|
|
234
|
+
'procedimientos',
|
|
235
|
+
'estructuras',
|
|
236
|
+
'mundo',
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
export const catalog: Catalog = {
|
|
240
|
+
version: 1,
|
|
241
|
+
keywords,
|
|
242
|
+
operators,
|
|
243
|
+
library: library.sort(
|
|
244
|
+
(a, b) => CATEGORIES.indexOf(a.category) - CATEGORIES.indexOf(b.category) || a.name.localeCompare(b.name, 'es'),
|
|
245
|
+
),
|
|
246
|
+
constants: Object.entries(constantDocs).map(([name, doc]) => ({
|
|
247
|
+
name,
|
|
248
|
+
value: constants[name] as number,
|
|
249
|
+
...doc,
|
|
250
|
+
})),
|
|
251
|
+
errors: Object.entries(ERROR_CODES)
|
|
252
|
+
.map(([code, info]) => ({ code, ...info }))
|
|
253
|
+
.sort((a, b) => a.code.localeCompare(b.code, 'es')),
|
|
254
|
+
categories: CATEGORIES,
|
|
255
|
+
exampleWorld: EXAMPLE_WORLD,
|
|
256
|
+
};
|