HoopLang Docs
/
Playground
Referencia del lenguaje

Expresiones

Una expresión produce un valor con tipo (ver Tipos). Las expresiones aparecen en el valor de const, shape y param, en las propiedades (nombre: valor), en los argumentos, en las condiciones y en las cabeceras de for. No tienen efectos: el compilador las evalúa por completo al verificar el archivo, incluida la geometría de las formas.

Gramática#

ebnf
expr        = and_expr { "or" and_expr } ;
and_expr    = not_expr { "and" not_expr } ;
not_expr    = "not" not_expr | comparación ;
comparación = suma [ ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) suma ] ;
suma        = término { ( "+" | "-" ) término } ;
término     = postfijo { ( "*" | "/" | "&" | "mod" ) postfijo } ;
postfijo    = unario { "." identificador "(" args ")" } ;
unario      = "-" unario | primario ;
primario    = número | color | texto
            | nombre
            | identificador "(" args ")"
            | "path" "{" { segmento } "}"
            | "if" expr "{" expr "}" "else" ( "{" expr "}" | si_valor )
            | "(" expr ")"
            | "(" expr "," expr ")"
            | "[" { expr [ "," ] } "]" ;
nombre      = identificador { "." identificador } ;   (* sin "(" detrás *)
args        = [ arg { "," arg } [ "," ] ] ;
arg         = [ identificador ":" ] expr ;

Precedencia#

De menor a mayor. Los operadores de un mismo nivel asocian a la izquierda, salvo la comparación, que no asocia.

NivelOperadoresAsociatividad
1orizquierda
2andizquierda
3not (prefijo)—
4== != < <= > >=no asocia
5+ -izquierda
6* / & modizquierda
7.método(…) (postfijo)izquierda
8- (prefijo)—

Consecuencias:

  • & (intersección) liga más fuerte que + y -: a + b & c es a + (b & c).
  • mod tiene la precedencia de *: i mod 2 == 1 es (i mod 2) == 1.
  • not a == b es not (a == b).
  • Una comparación no se encadena: 1 < 2 < 3 es un error de sintaxis (E000); escriba 1 < 2 and 2 < 3.
  • El - prefijo liga más fuerte que un método: -s.move(1mm, 0mm) aplica el método a -s (lo que es un error, porque una forma no se niega). Use paréntesis: -(…).

Literales y nombres#

Los literales (números con unidad, textos, colores) están en Léxico. Un nombre se resuelve así, en orden:

  1. Si es un nombre calificado base.miembro, no está declarado tal cual y base es una forma, es un miembro de la forma.
  2. true y false son Bool.
  3. Una constante, shape, param, variable de for o nombre importado con ese nombre: su valor.
  4. Un hilo con ese nombre: un valor Thread.
  5. Un valor de enumerado conocido (fuente, tela, ancla, bastidor, underlay, left/right, auto, satin, fill, none): un valor Name; ver Tipos § Name.
  6. Cualquier otro nombre es E007: unknown name x(con `did you mean` si hay un nombre parecido), o x is used before it is declared (line N) si se declara más abajo. Si la declaración de `x` falló, no se repite el error. En las propiedades que toman un enumerado (`font`, `fabric`, `underlay`, `align`, `style`, `anchor`, `origin`), un nombre suelto llega tal cual y la propiedad dice si vale ( Scrip is not valid for font; did you mean Script? ``).

Operadores unarios#

  • -x: negación de Length, Angle, Ratio o Int. Con otro tipo, E001: cannot negate a String.
  • not x: negación de un Bool. Con otro tipo, E001: not takes a Bool, not Int.

Operadores binarios#

Aritmética, operaciones de formas, comparaciones y lógica, con la tabla completa de qué tipos admite cada operador, están en Tipos § Operaciones entre tipos. En resumen:

  • + - * / entre magnitudes, con unidades coherentes (Length / Length da Ratio; Length * Int da Length; Int / Int no existe).
  • mod entre Int.
  • + (unión), - (diferencia) y & (intersección) entre formas o caminos cerrados.
  • + - entre Point.
  • == != entre valores del mismo tipo; < <= > >= entre números del mismo tipo.
  • and, or entre Bool. Ambos operandos se evalúan siempre (no hay cortocircuito).
HoopLang
const paso = 60mm / 4
const par: Bool = 6 mod 2 == 0
shape anillo = circle(center: (50mm, 50mm), r: 20mm) - circle(center: (50mm, 50mm), r: 15mm)
shape cuarto = anillo & rect(at: (50mm, 50mm), size: (30mm, 30mm))

Puntos#

ebnf
punto = "(" expr "," expr ")" ;

Un paréntesis con dos expresiones separadas por coma es un Point; ambas deben ser Length (E001: a Point is (Length, Length), not (Int, Int)). Con una sola expresión, el paréntesis solo agrupa.

HoopLang
const p = (12mm, 4.5mm)
const q: Point = p + (1mm, 0mm)

Listas#

ebnf
lista = "[" { expr [ "," ] } "]" ;

Una lista puede estar vacía y sus elementos pueden ser de cualquier tipo; la coma entre elementos es opcional. Si un elemento falla, la lista entera falla.

HoopLang
const nombres = ["ANA", "LUIS", "SOFÍA"]
const centros = [(10mm, 10mm) (30mm, 10mm)]
const n: Int = len(nombres)

Expresión if#

ebnf
si_valor = "if" expr "{" expr "}" "else" ( "{" expr "}" | si_valor ) ;

Elige un valor entre dos. El else es obligatorio (E000: an if that gives a value needs else: if c { a } else { b }) y puede encadenarse con else if.

  • La condición debe ser Bool (E001: a condition is a Bool (a comparison like len(name) > 8), not Int).
  • Se evalúan ambas ramas (sus errores se informan aunque no se elijan), y deben tener el mismo tipo (E001: both branches of an if give the same type, not Length and Int).
HoopLang
param nombre = "MARÍA"
const alto = if len(nombre) > 8 { 6mm } else if len(nombre) > 4 { 8mm } else { 10mm }

El if como contenido de una capa (que elige primitivas, no valores) se describe en Control § if.

Miembros de una forma#

ebnf
miembro = identificador "." identificador ;

Un nombre calificado s.m, donde s es el nombre de una forma (una shape, una constante de tipo Shape, canvas, una variable de for que vale una forma), da un punto o una medida de su caja envolvente (la caja alineada a los ejes que contiene los contornos exteriores):

MiembroTipoValor
centerPointcentro de la caja
top_leftPointesquina superior izquierda
top_center, topPointcentro del borde superior
top_rightPointesquina superior derecha
center_left, leftPointcentro del borde izquierdo
center_right, rightPointcentro del borde derecho
bottom_leftPointesquina inferior izquierda
bottom_center, bottomPointcentro del borde inferior
bottom_rightPointesquina inferior derecha
widthLengthancho de la caja
heightLengthalto de la caja

Reglas:

  • Un miembro desconocido es E007: a shape has no .widht; did you mean .width?.
  • Cualquier expresión que dé una forma tiene miembros, no sólo un nombre: rect(size: (10mm, 5mm)).width, canvas.inset(5mm).center.
  • Los puntos no tienen miembros: p.x es E007: p is a Point: only a shape has members (…).
  • Si s.m es un nombre importado (import club from …, ver Módulos), el nombre importado tiene prioridad.
HoopLang
shape escudo = rect(at: (20mm, 10mm), size: (60mm, 70mm))
const medio: Point = escudo.center
const ancho: Length = escudo.width
const pie: Point = canvas.bottom

Métodos de forma#

ebnf
método  = postfijo "." identificador "(" args ")" ;
miembro = postfijo "." identificador ;

Un método se aplica a cualquier valor que sea una forma (o un Path cerrado, que se convierte) y devuelve una forma nueva. Los argumentos se toman por posición; si se les pone nombre, el nombre se ignora.

MétodoArgumentosResultado
.move(dx, dy)Length, Lengthtraslada
.rotate(a)Anglegira alrededor del centro de la caja de la forma
.rotate(a, c)Angle, Pointgira alrededor de c
.scale(r)Ratio (> 0)escala respecto del centro de la caja
.scale(r, c)Ratio (> 0), Pointescala respecto de c
.inset(d)Lengthcontrae el contorno d hacia adentro
.outset(d)Lengthexpande el contorno d hacia afuera
  • El ángulo es en grados, positivo en el sentido de x hacia y (con y hacia abajo, se ve horario en pantalla).
  • Un método desconocido, o argumentos de otro tipo o en otra cantidad, es E001: unknown method or invalid arguments: .spin(…) (inset(Length), outset(Length), move(Length, Length), rotate(Angle[, center]), scale(Ratio[, center])). .scale(0%) (o negativo) es E002: scale must be greater than 0%, y un entero sin unidad, E001: .inset needs a unit (e.g. 2mm).
  • Aplicar un método a algo que no es forma es E001: expected a Shape, not Point.
  • Los métodos se encadenan: s.inset(2mm).rotate(10deg).
HoopLang
shape base = rect(at: (40mm, 10mm), size: (20mm, 10mm))
shape punta = base.rotate(45deg, (50mm, 50mm))
shape marco = base.outset(2mm) - base
shape chica = base.scale(50%).move(0mm, 30mm)

Funciones incorporadas#

ebnf
llamada = identificador "(" args ")" ;

No hay funciones de usuario. Las incorporadas son rect, circle, polygon, grid y len; cualquier otro nombre es E007: unknown function hole (rect, circle, polygon, grid, len). Los argumentos con nombre se pasan como nombre: valor; los nombres que una función no usa se ignoran, y si un nombre se repite vale el último. Entre argumentos la coma es obligatoria (E000: expected ,or)``).

rect#

Salida del compilador
rect(at: Point = (0mm, 0mm), size: Point, radius: Length)

Rectángulo con su esquina superior izquierda en at y tamaño size (ancho, alto). Con radius > 0, las esquinas se redondean con ese radio. at, size y radius son argumentos con nombre.

  • Sin size, o con at/size que no son Point: E001: rect(at: Point, size: Point, radius: Length).
  • Ancho o alto ≤ 0: E004: rect with zero or negative size.
  • Un radius que no es Length se ignora (el rectángulo queda con esquinas rectas).

circle#

Salida del compilador
circle(center: Point, r: Length)

Círculo de centro center y radio r (ambos obligatorios, con nombre). Se aproxima con un polígono de entre 24 y 360 lados (unos 12 por milímetro de radio).

  • Falta un argumento o tiene otro tipo: E001: circle(center: Point, r: Length).
  • r ≤ 0: E004: circle with zero or negative radius.

polygon#

Salida del compilador
polygon(p1, p2, p3, …)

Polígono cerrado por los puntos dados, en orden y por posición. Si hay menos de tres puntos, o algún argumento no es Point: E004: polygon needs at least 3 Points.

grid#

Salida del compilador
grid(i: Int, cols: Int, step: Point, start: Point = (0mm, 0mm)) -> Point

El punto de la celda i de una grilla que se llena de izquierda a derecha y de arriba abajo: columna i mod cols, fila i / cols (divisiones enteras euclidianas); el resultado es start + (columna · step.x, fila · step.y). i es posicional; cols, step y start van con nombre. Sirve para ubicar copias con un for (ver Control).

  • cols falta o no es un Int > 0, o falta i o step: E001: grid(i, cols: Int, step: (Length, Length), start: Point).

len#

Salida del compilador
len(x: String | List) -> Int

Número de caracteres de un texto (puntos de código, no bytes) o de elementos de una lista. Con otro argumento: E001: len(String or List).

HoopLang
shape tarjeta = rect(at: (10mm, 10mm), size: (80mm, 50mm), radius: 4mm)
shape ojo = circle(center: (50mm, 35mm), r: 6mm)
shape flecha = polygon((10mm, 90mm), (30mm, 80mm), (10mm, 70mm))
const celda: Point = grid(4, cols: 3, step: (30mm, 20mm), start: (5mm, 5mm))
const letras: Int = len("ñandú")

custom(ancho, alto) no es una función general: solo se acepta como valor de la propiedad frame (ver Declaraciones § design y Bastidor).

path#

ebnf
path     = "path" "{" { segmento } "}" ;
segmento = "from" expr
         | "line" expr
         | "curve" expr expr expr
         | "close" ;

Construye un Path. Cada expr de un segmento es un Point (E001: a path takes Points (x, y), not Length).

SegmentoSignificado
from ppunto inicial; solo como primer segmento
line precta hasta p
curve c1 c2 pBézier cúbica desde el punto actual hasta p, con controles c1 y c2; se aplana en tramos de unos 0.25 mm (entre 4 y 400)
closecierra el camino; debe ser el último segmento

Errores (E004):

  • No empieza con from: a path starts with from (x, y).
  • from después del inicio: from goes only at the start of the path.
  • Algo después de close: nothing can follow close.
  • Menos de dos puntos: a path needs at least 2 points.
  • Otra palabra como segmento (por ejemplo arc) es un error de sintaxis: E000: arc does not belong in a path (from, line, curve or close).

Un camino abierto sirve para run, triple, rieles y along de un satín; uno cerrado se convierte en forma donde se espera una (ver Tipos § Conversiones).

HoopLang
const trazo = path { from (10mm, 50mm) line (50mm, 50mm) curve (60mm, 50mm) (70mm, 40mm) (70mm, 30mm) }
shape gota = path {
  from (50mm, 10mm)
  curve (70mm, 40mm) (60mm, 60mm) (50mm, 60mm)
  curve (40mm, 60mm) (30mm, 40mm) (50mm, 10mm)
  close
}

Rangos#

a..b no es una expresión: solo existe en la cabecera de un for (for i in 0..4). Ver Control § for.