El lenguaje
Wahoo en cinco minutos
Todo lo que hay que saber de Wahoo, en una página: mundos, monedas, preguntas, bucles, tuberías, y qué significa cada error.
En este capítulo
Wahoo es un lenguaje pequeño, con tipos estáticos, tres tipos, recursión, bucles y ámbito de bloque. Sus palabras clave vienen del Reino Champiñón (y están en inglés, como en el juego), pero por debajo es un lenguaje imperativo corriente. Todos los fragmentos de esta página pueden pegarse en el playground.
Un primer programa
// Los comentarios van de // al final de la línea.
world 1-1 {
coin vidas = 3
wahoo("Mario tiene ", vidas, " vidas")
}
Un programa es una lista de mundos (world) y tuberías (pipe), en cualquier orden. Las sentencias acaban al final de la línea; no hay puntos y coma.
Mundos
world 1-1 { … } es donde empieza la ejecución. Un programa puede tener varios mundos, y se juegan en el orden de su número: 1-1, 1-2, … 2-1, sea cual sea su orden en el fichero. Cada mundo es un nivel aparte: las variables declaradas en uno no se ven en otro. Un flag sin valor termina antes el mundo actual.
Tipos y valores
| Tipo | Valores | Equivale a |
|---|---|---|
coins | enteros de −2147483648 a 2147483647; se admite 1_000 | int (32 bits, da la vuelta al desbordar) |
switch | star (verdadero) y goomba (falso) | bool |
text | "entre comillas dobles", con \n \t \" \\ | un literal de cadena inmutable |
Los textos se pueden guardar, pasar a las pipes, comparar con == e imprimir, pero no unir: para imprimir varias cosas juntas, pásalas todas a wahoo(…).
Monedas: variables
coin vidas = 3 // el tipo (coins) se infiere
coin nombre: text = "Luigi" // o se escribe
vidas = vidas + 1 // asignación: sin `coin`
power_up vidas // vidas = vidas + 1
power_up vidas by 10 // vidas = vidas + 10
damage vidas // vidas = vidas - 1
damage vidas by 2 // vidas = vidas - 2
Una coin vive hasta el final del bloque ({ … }) donde se declara. Un bloque interior puede declarar una moneda con el mismo nombre, que oculta a la exterior hasta que el bloque termina. Declarar el mismo nombre dos veces en un bloque es un error. Los nombres empiezan por una letra o _; una moneda cuyo nombre empieza por _ nunca se avisa como no usada.
Operadores
De mayor a menor fuerza de ligadura:
| Operadores | Tipos de los operandos | Resultado |
|---|---|---|
-x | coins | coins |
* / % | coins | coins (la división trunca hacia cero) |
+ - | coins | coins |
< <= > >= | coins | switch |
== != | dos valores del mismo tipo | switch |
not | switch | switch |
and | switch | switch (el lado derecho solo se evalúa si hace falta) |
or | switch | switch (el lado derecho solo se evalúa si hace falta) |
Los operadores del mismo nivel se agrupan por la izquierda (10 - 4 - 3 es 3). Las comparaciones no se pueden encadenar: escribe a < b and b < c.
Preguntas
question vidas > 3 {
wahoo("de sobra")
} else question vidas > 0 {
wahoo("con cuidado")
} else {
wahoo("game over")
}
La condición debe ser un switch; las llaves son siempre obligatorias.
Bucles
bounce vidas > 0 { // mientras se cumpla la condición
damage vidas
}
run i from 1 to 5 { // i = 1, 2, 3, 4, 5 (ambos extremos incluidos)
wahoo(i)
}
En un bucle run los dos límites se calculan una vez, antes de la primera vuelta, y el contador es una moneda nueva que solo existe dentro del bucle. Si el inicio es mayor que el final, el cuerpo no se ejecuta nunca.
Tuberías
pipe fib(n: coins) -> coins {
question n < 2 {
flag n
}
flag fib(n - 1) + fib(n - 2)
}
pipe saluda(nombre: text) { // sin `->`: no devuelve nada
wahoo("Hola, ", nombre)
}
Una pipe es una función. Hay que escribir los parámetros y el tipo devuelto; flag valor devuelve, y una pipe con tipo devuelto debe llegar a un flag por todos los caminos. Las pipes pueden llamarse antes de su definición y pueden llamarse a sí mismas. Una pipe sin tipo devuelto se llama como sentencia, en su propia línea: saluda("Peach"); una con tipo devuelto también puede llamarse así, y su valor se descarta.
Imprimir
wahoo(a, b, …) imprime sus argumentos uno tras otro, sin separadores, y luego un salto de línea. Los switches se imprimen como star y goomba. wahoo es una sentencia: no tiene valor, y ninguna pipe puede llamarse wahoo.
Errores y avisos
El compilador informa de todos los problemas que encuentra, con la línea, la columna y, a menudo, una pista. El código indica qué fase lo encontró. Con --lang es (o un $LANG en español) los mensajes salen en español.
| Código | Fase | Significado |
|---|---|---|
| E001–E005 | lexer | carácter inesperado, texto sin cerrar, escape desconocido, número demasiado grande, número mal formado |
| E101–E105 | parser | se esperaba algo que no está, comparación encadenada, una expresión solitaria, else sin question, demasiados errores |
| E201–E218 | comprobador | no hay mundo, duplicados, nombres, pipes o tipos desconocidos, tipos que no encajan, número de argumentos erróneo, falta un flag, valores de flag fuera de lugar |
| W301–W303 | comprobador | avisos: moneda sin usar, código inalcanzable, división entre cero |
Un programa con avisos pero sin errores se compila y se ejecuta. Al ejecutar, tres cosas terminan antes un nivel: la división entre cero, una división que desborda (-2147483648 / -1) y una recursión demasiado profunda. Un programa que no termina se detiene a los tres segundos en el navegador.
En la línea de órdenes
El compilador también es una herramienta de línea de órdenes, construida con el mismo código Rust:
cargo install --path crates/wahoo-cli
wahoo run examples/es/fib.wahoo # compila y ejecuta (con el intérprete wasmi)
wahoo build examples/es/fib.wahoo # escribe examples/es/fib.wasm
wahoo wat examples/es/fib.wahoo # muestra el WebAssembly en texto
wahoo tokens examples/es/fib.wahoo # lo que ve el lexer
wahoo ast examples/es/fib.wahoo # lo que construye el parser
wahoo check examples/es/errors.wahoo --lang es