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:

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.

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):

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:

- 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

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:

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
##