01 — Sintaxis
← README · Siguiente: 02 — Tipos →
Convención editorial. Usamos type (sin traducir) cuando referimos a la entidad sintáctica del lenguaje — un type declarado por el programador, una keyword, o la categoría léxica que empieza con mayúscula. Usamos tipo (en español) cuando referimos al concepto general — "el tipo de un valor", "tipo inferido", "tipos primitivos". El criterio: si la palabra aparece junto a un identificador concreto o reemplaza a la keyword del lenguaje, "type"; si describe la noción, "tipo".
Sensibilidad a mayúsculas
Keywords del lenguaje → case insensitive. action, Action, ACTION son equivalentes.
Identificadores → case sensitive. La primera letra determina la categoría:
- Mayúscula → type:
Background,LoginError,Sprite. - Minúscula → todo lo demás: variables, instancias, acciones, parámetros.
background,my_bg,factorial,login,move_camera.
Excepción — primitivos y colecciones del núcleo: los tipos number, text, boolean y nothing, y las keywords de colección list, map y set, se escriben en minúscula aunque son tipos. Son parte del lenguaje mismo — el compilador los trata distinto a los types definidos por el programador. Todo type creado por el programador sigue la regla normal: mayúscula obligatoria.
Background # type — empieza con mayúscula (PascalCase)
has style: BackgroundStyle
end
background: Background # instancia — empieza con minúscula, naming libre
my_bg: Background
bg: Background
action move_camera with at: Point # acción — empieza con minúscula
camera.position = at
end
El compilador rechaza un type que empiece con minúscula o cualquier otro identificador que empiece con mayúscula. La distinción es estructural, no solo convención — permite que una expresión como factorial with n - 1 se lea sin ambigüedad: factorial no puede ser un type, así que necesariamente es una acción o una variable.
Un type define la estructura de un objeto — es el blueprint del que se crean las instancias.
Caracteres válidos en identificadores: letras, números y _. El punto (.) es exclusivo del acceso a miembros — nunca puede formar parte de un nombre. El primer carácter debe ser una letra — nunca _ ni un dígito. Esto mantiene la regla "la primera letra define la categoría" sin casos especiales: no existe _foo ni _Sprite.
Motor25D # válido
Motor2_5D # válido
Motor2.5D # error — punto reservado para acceso a miembros
_foo # error — el primer carácter debe ser letra
2Motor # error — el primer carácter debe ser letra
Keywords reservadas
Todas son case insensitive. El compilador las rechaza como nombres de identificadores.
# control de flujo
if then else match when
on changes for each in
where repeat times return
while break continue retain
# definición
action needs returns fails with
has does is can be
derives from end
# módulos
module expose use exposing
# lógica
and or not
# colecciones
list set map of to
add remove contains
# valores
nothing true false # nothing también funciona como tipo (text or nothing)
# tipos primitivos
number text boolean
# otros
as raw increases decreases by
Keywords contextuales — solo reservadas en posiciones específicas.
successyfailureson etiquetas válidas únicamente trasonen un bloque de handler de resultado (on success,on success as user,on failure,on failure as err).waitsolo se reserva trascanen una firma de acción (-> X can wait), para marcar acciones que pueden bloquear esperando un mensaje.parallelsolo se reserva trasinen unfor each(for each x in xs in parallel), para indicar paralelismo de datos.
Fuera de esos contextos, el compilador las acepta como identificadores normales. La razón: son nombres con valor semántico solo cuando rotulan una construcción específica; reservarlas globalmente colisionaría con nombres comunes (success, failure como variables de estado; wait, parallel como nombres de campos o acciones de un módulo).
Algunas keywords tienen más de un rol — el compilador las desambigua por contexto. La lista siguiente es una referencia rápida; cada rol específico se cubre en la sección donde aparece (acciones en el archivo 04, colecciones en 07, range y match en 05, módulos en 06):
as— casting (42 as text) y binding de resultado (on success as user)with— declara parámetros en la firma (action f with n: number) y los pasa en la llamada (f with n)of— especifica el tipo de una colección (list of number), declara parámetros de tipo genéricos (action first_of of T) y aplica una función matemática (floor of x)to— asocia clave a valor en map (map of K to V) y extremo superior de un range (1 to 10); ademásdoes add to itemsfrom— origen de un valor derivado (derives from) y delegación de protocolo (does remove from items)by— paso en un range (0 to 100 by 10) y delta exacto en una transición (when score increases by 100)or— operador lógico (a or b) y unión de tipos en firmas (User or nothing,T or fails with E)->— retorno de acción, branch de match, handler de resultado y asociación en map (ver "Principio de símbolos")
Principio de símbolos
Si el símbolo se deriva de notación matemática universal, se queda. Si es una convención arbitraria sin base matemática, se reemplaza con una palabra.
-> aparece en cuatro contextos: retorno de acción, branch de match, handler de resultado y asociación en map. En todos significa lo mismo — "esto produce aquello" — y el compilador lo desambigua por contexto.
Operadores — se quedan
A + B A - B A * B A / B
A = B A != B
A > B A < B A >= B A <= B
`=` cumple dos roles, desambiguados por contexto sintáctico:
- Asignar a un *target asignable*. Un target asignable es siempre uno de: un nombre,
obj.field, oobj[key]. Seis contextos comparten esa estructura:
- Declaración inicial de variable: n = 5, result: Outcome = play_round - Rebind de variable local: total = total + n - Campo dentro de un bloque de instanciación: position = my_point - Argumento nombrado en una llamada: move_camera with at = my_point - Campo de una instancia: camera.tilt = 90 - Slot de un map por clave: scores["Alice"] = 95
- Comparación de igualdad —
if n = 0 then ...,match score where score = 100,tier = if lives = 0 then "fin" else "sigue".
La regla es estructural, no posicional: si = aparece como la "raíz" de un statement, de una línea de un bloque de instanciación, o de un argumento dentro de un with, es asignación. Si aparece dentro de la condición de un if/match/when o del lado derecho de otra asignación, es comparación. El compilador nunca confunde los dos casos — no hay ambigüedad parseable.
Es la misma elección de Pascal, Lua y SQL. Quien viene de C/Java/Python necesita ajustar el hábito de leer ==, pero el compilador rechaza inmediatamente cualquier mal uso (intentar asignar dentro de una condición no parsea).
Operadores — se reemplazan
&& -> and
|| -> or
!A -> not A
{} -> end (solo como delimitador de bloque)
; -> nada (fin de línea es suficiente)
Las llaves {} siguen apareciendo en literales de colección — list { 1, 2, 3 }, set {}, map { "a" -> 1 } — porque ahí no delimitan un bloque sino el contenido del literal. La sustitución por end aplica únicamente al rol de delimitador de bloque.
Precedencia de operadores
Sigue la convención matemática estándar. De mayor a menor:
1. acceso a miembro obj.field (más alto — siempre agrupa primero)
2. llamadas action with arg (agrupa a la derecha del with)
3. operaciones aritméticas * / antes que + -
4. comparaciones = != > < >= <=
5. is nothing x is nothing
6. not
7. and
8. or
not a = b → not (a = b) # comparación primero, luego not
not a and b → (not a) and b # not primero, luego and
a and b or c → (a and b) or c # and antes que or
not sprite.label is nothing → not (sprite.label is nothing) # is nothing primero, luego not
Las llamadas con with agrupan a la derecha: factorial with n - 1 es factorial with (n - 1), no (factorial with n) - 1. Si el argumento es una expresión compleja, los paréntesis lo hacen explícito.
Parseo greedy de with y of
Una vez que aparece with después de un nombre de acción, se consume todo lo que sigue como lista de argumentos separados por comas, hasta encontrar uno de estos delimitadores:
- fin de línea
- paréntesis de cierre
) - corchete de cierre
]que pertenezca a una indexación exterior, sea de list o map (scores[k] = total of items with x,items[i] = total of xs with k) - coma o llave de cierre
}que pertenezca a un literal de colección exterior (list { f with x, g with y }) - una keyword de control (
then,else,where) - el operador
->(que abre el cuerpo de un handler, branch dematch, o cuerpo defor each) - un cierre de bloque (
end)
Para "sacar" parte de la expresión de la lista de argumentos, se usan paréntesis.
La misma regla greedy y los mismos delimitadores aplican a of cuando introduce el argumento de una función matemática: floor of 10 / 3 es floor(10 / 3), no floor(10) / 3. Para forzar lo segundo, paréntesis explícitos: (floor of 10) / 3.
factorial with n - 1 → factorial(n - 1)
move_camera with at, tilt + 1 → move_camera(at, tilt + 1)
total = factorial with 5 → total = factorial(5)
total = factorial with 5 + 1 → total = factorial(5 + 1) # greedy hasta fin de línea
total = (factorial with 5) + 1 → total = factorial(5) + 1 # paréntesis explícito
if n = factorial with 5 then ok → if (n = factorial(5)) then ok # `then` corta los argumentos
Estilo recomendado. Cuando un argumento contiene operadores aritméticos y la llamada está dentro de una expresión más grande (parte de una asignación con otras operaciones, condición de if, segundo operando de otro +), agrega paréntesis explícitos alrededor del argumento. La semántica no cambia, pero la intención queda inmediata para quien lee:
total = base + factorial with (n - 1) # recomendado — los paréntesis hacen explícito qué es argumento
return factorial with n - 1 # sin paréntesis está bien — el return cierra la expresión
Los paréntesis anulan la precedencia cuando el programador quiere ser explícito:
not (a and b)
(a or b) and c
Bloques
Una línea → el fin de línea la cierra. Varias líneas → end cierra el bloque. La indentación es opcional pero recomendada para legibilidad.
# una línea — sin end
Direction can be north, south, east, west
Background has style: BackgroundStyle, infinite: boolean
if n = 0 then return 1
# varias líneas — con end
Background
has style: BackgroundStyle
has infinite: boolean
end
action factorial with n: number -> number
if n = 0 then return 1
return n * factorial with n - 1
end
Comentarios
# comentario de una línea
##
comentario multilínea
útil para deshabilitar bloques o explicar secciones
##