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#
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.
| Nivel | Operadores | Asociatividad |
|---|---|---|
| 1 | or | izquierda |
| 2 | and | izquierda |
| 3 | not (prefijo) | — |
| 4 | == != < <= > >= | no asocia |
| 5 | + - | izquierda |
| 6 | * / & mod | izquierda |
| 7 | .método(…) (postfijo) | izquierda |
| 8 | - (prefijo) | — |
Consecuencias:
&(intersección) liga más fuerte que+y-:a + b & cesa + (b & c).modtiene la precedencia de*:i mod 2 == 1es(i mod 2) == 1.not a == besnot (a == b).- Una comparación no se encadena:
1 < 2 < 3es un error de sintaxis (E000); escriba1 < 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:
- Si es un nombre calificado
base.miembro, no está declarado tal cual ybasees una forma, es un miembro de la forma. trueyfalsesonBool.- Una constante,
shape,param, variable deforo nombre importado con ese nombre: su valor. - Un hilo con ese nombre: un valor
Thread. - Un valor de enumerado conocido (fuente, tela, ancla, bastidor,
underlay,left/right,auto,satin,fill,none): un valorName; ver Tipos §Name. - Cualquier otro nombre es
E007: unknown namex(con `did you mean` si hay un nombre parecido), oxis 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 (Scripis not valid for font; did you meanScript? ``).
Operadores unarios#
-x: negación deLength,Angle,RatiooInt. Con otro tipo,E001: cannot negate a String.not x: negación de unBool. 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 / LengthdaRatio;Length * IntdaLength;Int / Intno existe).modentreInt.+(unión),-(diferencia) y&(intersección) entre formas o caminos cerrados.+ -entrePoint.== !=entre valores del mismo tipo;< <= > >=entre números del mismo tipo.and,orentreBool. Ambos operandos se evalúan siempre (no hay cortocircuito).
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#
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.
const p = (12mm, 4.5mm)
const q: Point = p + (1mm, 0mm)Listas#
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.
const nombres = ["ANA", "LUIS", "SOFÍA"]
const centros = [(10mm, 10mm) (30mm, 10mm)]
const n: Int = len(nombres)Expresión if#
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).
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#
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):
| Miembro | Tipo | Valor |
|---|---|---|
center | Point | centro de la caja |
top_left | Point | esquina superior izquierda |
top_center, top | Point | centro del borde superior |
top_right | Point | esquina superior derecha |
center_left, left | Point | centro del borde izquierdo |
center_right, right | Point | centro del borde derecho |
bottom_left | Point | esquina inferior izquierda |
bottom_center, bottom | Point | centro del borde inferior |
bottom_right | Point | esquina inferior derecha |
width | Length | ancho de la caja |
height | Length | alto 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.xesE007:pis a Point: only a shape has members (…). - Si
s.mes un nombre importado (import club from …, ver Módulos), el nombre importado tiene prioridad.
shape escudo = rect(at: (20mm, 10mm), size: (60mm, 70mm))
const medio: Point = escudo.center
const ancho: Length = escudo.width
const pie: Point = canvas.bottomMétodos de forma#
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étodo | Argumentos | Resultado |
|---|---|---|
.move(dx, dy) | Length, Length | traslada |
.rotate(a) | Angle | gira alrededor del centro de la caja de la forma |
.rotate(a, c) | Angle, Point | gira alrededor de c |
.scale(r) | Ratio (> 0) | escala respecto del centro de la caja |
.scale(r, c) | Ratio (> 0), Point | escala respecto de c |
.inset(d) | Length | contrae el contorno d hacia adentro |
.outset(d) | Length | expande el contorno d hacia afuera |
- El ángulo es en grados, positivo en el sentido de
xhaciay(conyhacia 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) esE002:scalemust be greater than 0%, y un entero sin unidad,E001:.insetneeds 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).
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#
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#
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 conat/sizeque no sonPoint:E001: rect(at: Point, size: Point, radius: Length). - Ancho o alto ≤ 0:
E004: rect with zero or negative size. - Un
radiusque no esLengthse ignora (el rectángulo queda con esquinas rectas).
circle#
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#
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#
grid(i: Int, cols: Int, step: Point, start: Point = (0mm, 0mm)) -> PointEl 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).
colsfalta o no es unInt> 0, o faltaiostep:E001: grid(i, cols: Int, step: (Length, Length), start: Point).
len#
len(x: String | List) -> IntNú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).
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#
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).
| Segmento | Significado |
|---|---|
from p | punto inicial; solo como primer segmento |
line p | recta hasta p |
curve c1 c2 p | Bé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) |
close | cierra el camino; debe ser el último segmento |
Errores (E004):
- No empieza con
from:a path starts with from (x, y). fromdespué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:arcdoes 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).
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.