v0.10
Lenguaje Módulo juego Tutorial

Manual de Lince

Los 55 ejemplos de esta página no están escritos a mano: son archivos .lince del repositorio que se ejecutan al generar el manual, y lo que ves bajo cada uno es su salida real. Si el lenguaje cambia de comportamiento, la página deja de cuadrar y la integración continua lo detecta — así la documentación no puede quedarse mintiendo.

Nada coincide con esa búsqueda.

Primeros pasos

Hola, mundo

escribir imprime y añade un salto de línea.

010_hola.lince

Funciones que ya vienen

Sin importar nada: escribir y leer, a_numero, a_texto y a_logico para pasar de un tipo a otro, rango para contar, y mapear, filtrar y reducir para trabajar con listas (y con generadores, como rango()). argumentos() da lo que se escribió detrás del programa en la terminal.

escribir(a_numero("3.5") * 2)
escribir(a_texto(42) + "!")
escribir("{a_logico(0)} {a_logico("hola")}")

para cada numero n en rango(1, 10, 3) { escribir(n) }

sea dobles = mapear([1, 2, 3], funcion(val numero x): numero { devolver x * 2 })
sea pares = filtrar(rango(10), funcion(val numero x): logico { devolver x % 2 == 0 })
sea suma = reducir([1, 2, 3, 4], 0, funcion(val numero a, val numero x): numero { devolver a + x })
escribir("{dobles} {pares} {suma}")
salida verificada
7
42!
falso verdadero
1
4
7
[2, 4, 6] [0, 2, 4, 6, 8] 10
015_integradas.lince

Variables y constantes

sea crea algo que puede cambiar; fijo, algo que no.

sea nombre = "Ana"
fijo PI = 3.14159

nombre = "Blanca"
escribir(nombre)
escribir(PI)
salida verificada
Blanca
3.14159
020_variables.lince

Operar y guardar: +=, -=, ++, --

x += 5 es x = x + 5 (también -=, *=, /= y %=), y x++ suma uno (x-- resta uno). Valen para variables, elementos de listas y diccionarios, y campos (mi.vidas--).

sea puntos = 10
puntos += 5
puntos *= 2
escribir(puntos)

sea nombre = "Lin"
nombre += "ce"
escribir(nombre)

sea vidas = {"Ana": 3, "Luis": 3}
vidas["Luis"] -= 1
escribir(vidas)

sea marcador = [3, 3]
marcador[1]--
marcador[0]++
escribir(marcador)
salida verificada
30
Lince
{"Ana": 3, "Luis": 2}
[4, 2]
025_operar_y_guardar.lince

Cada nombre se define una sola vez

Definir dos veces en el mismo ámbito es un error, no un descarte silencioso. Para cambiar el valor, asigna.

sea contador = 1
contador = 2
escribir(contador)

intentar {
    sea contador = 3
} capturar (error e) {
    escribir(e.mensaje)
}
salida verificada
2
030_una_vez.lince

Tapar un nombre en un ámbito interior

Esto no es redefinir: es otro ámbito. El de fuera queda intacto.

sea visible = "fuera"

funcion dentro(): texto {
    sea visible = "dentro"
    devolver visible
}

escribir(dentro())
escribir(visible)
salida verificada
dentro
fuera
040_tapar.lince

Dónde se declaran los tipos

En parámetros y retorno de función son obligatorios. En sea son opcionales y sólo para lista y diccionario.

sea lista notas = [9, 7, 10]
sea diccionario ficha = {"nombre": "Ana"}

funcion presentar(val texto quien, val numero anios): texto {
    devolver quien + " tiene " + anios + " años"
}

escribir(presentar(ficha["nombre"], notas.longitud()))
salida verificada
Ana tiene 3 años
050_tipos.lince

Varios nombres de una vez

Con corchetes, de una lista (tantos nombres como elementos); con llaves, de un diccionario por clave. Sin sea, asigna.

sea [ancho, alto] = [320, 180]
escribir(ancho * alto)

sea enemigo = {"vida": 50, "nombre": "orco", "fuerza": 7}
sea {nombre, vida} = enemigo
escribir("{nombre} con {vida} de vida")

sea a = 1
sea b = 2
[a, b] = [b, a]
escribir("{a} {b}")
salida verificada
57600
orco con 50 de vida
2 1
060_varios_nombres.lince

Control de flujo

Condicionales

si, sino si, sino. Los operadores lógicos son y, o, no.

sea nota = 7

si nota >= 9 {
    escribir("sobresaliente")
} sino si nota >= 5 {
    escribir("aprobado")
} sino {
    escribir("suspenso")
}

si nota > 5 y no (nota == 10) {
    escribir("entre 6 y 9")
}
salida verificada
aprobado
entre 6 y 9
110_condicionales.lince

x e y

y y o son operadores solo entre dos valores. En cualquier otro sitio son nombres normales, así que x e y sirven para las coordenadas, como en cualquier juego.

sea x = 3
sea y = 8
si x > 0 y y > 0 {
    y = y - 1
    escribir("arriba a la derecha, en ({x}, {y})")
}
salida verificada
arriba a la derecha, en (3, 7)
112_x_e_y.lince

Un si que da un valor

Una expresión en cada rama y sino obligatorio. Solo se evalúa la rama que toca.

sea velocidad = -4
sea lado = si velocidad < 0 { "izquierda" } sino { "derecha" }
escribir(lado)

funcion nota(val numero n): texto {
    devolver si n >= 9 { "sobresaliente" } sino si n >= 5 { "aprobado" } sino { "suspenso" }
}
escribir(nota(7))

sea puntos = 1
escribir("{puntos} {si puntos == 1 { "punto" } sino { "puntos" }}")
salida verificada
izquierda
aprobado
1 punto
115_si_con_valor.lince

Las cuatro formas de repetir

para clásico, mientras, hacer/mientras y para cada.

para (i = 0; i < 3; i++) {
    escribir("para " + i)
}

sea n = 0
mientras n < 2 {
    escribir("mientras " + n)
    n = n + 1
}

sea m = 0
hacer {
    escribir("hacer " + m)
    m = m + 1
} mientras (m < 2)

para cada numero x en [10, 20] {
    escribir("cada " + x)
}
salida verificada
para 0
para 1
para 2
mientras 0
mientras 1
hacer 0
hacer 1
cada 10
cada 20
120_bucles.lince

Romper y continuar

romper sale del bucle; continuar pasa a la vuelta siguiente. Con bucles uno dentro de otro, afectan al de dentro.

sea enemigos = [{"vida": 0}, {"vida": 5}, {"vida": 8}, {"vida": 2}]

para cada diccionario e en enemigos {
    si e["vida"] == 0 { continuar }     # los muertos no cuentan
    escribir("vivo con " + e["vida"])
    si e["vida"] > 6 {
        escribir("¡uno fuerte! dejo de mirar")
        romper
    }
}

para (i = 0; i < 20; i += 5) {
    escribir(i)
}
salida verificada
vivo con 5
vivo con 8
¡uno fuerte! dejo de mirar
0
5
10
15
125_romper.lince

Elegir / caso

otro es el caso por defecto.

sea animal = "lince"

elegir animal {
    caso "gato"  { escribir("miau") }
    caso "lince" { escribir("es un felino ibérico") }
    otro         { escribir("no sé qué hace") }
}
salida verificada
es un felino ibérico
130_elegir.lince

Funciones

Declarar y llamar

Los parámetros llevan val (por valor) o ref (por referencia), tipo y nombre.

funcion area(val numero base, val numero altura): numero {
    devolver base * altura / 2
}

escribir(area(10, 4))
salida verificada
20
210_funciones.lince

Valores por defecto

Los parámetros con valor por defecto van al final.

funcion saludo(val texto nombre, val texto trato = "Hola"): texto {
    devolver trato + ", " + nombre
}

escribir(saludo("Ana"))
escribir(saludo("Ana", "Buenas"))
salida verificada
Hola, Ana
Buenas, Ana
220_defecto.lince

Lambdas y funciones como argumento

Una función sin nombre es un valor como cualquier otro.

sea doble = funcion(val numero x): numero { devolver x * 2 }
escribir(doble(21))

sea nums = [1, 2, 3, 4]
escribir(mapear(nums, doble))
escribir(filtrar(nums, funcion(val numero x): logico { devolver x % 2 == 0 }))
escribir(reducir(nums, 0, funcion(val numero a, val numero x): numero { devolver a + x }))
salida verificada
42
[2, 4, 6, 8]
[2, 4]
10
230_lambdas.lince

Llamar a cualquier cosa que dé una función

Una función es un valor más: se guarda en una lista, en un diccionario o en un campo, y se llama directamente desde ahí. También se puede llamar a lo que devuelve otra llamada. El ( va en la misma línea que lo que se llama.

sea golpes = [
    funcion(val numero f): texto { devolver "puñetazo de " + f },
    funcion(val numero f): texto { devolver "patada de " + f }
]
escribir(golpes[1](8))

sea acciones = {"saltar": funcion(): texto { devolver "¡hop!" }}
escribir(acciones["saltar"]())

funcion sumador(val numero a): funcion {
    devolver funcion(val numero b): numero { devolver a + b }
}
escribir(sumador(2)(3))
salida verificada
patada de 8
¡hop!
5
235_llamar_lo_que_sea.lince

Closures

Una función recuerda el ámbito donde se creó, así que puede llevar estado.

funcion contador_desde(val numero inicio): funcion {
    sea n = inicio
    devolver funcion(): numero {
        n = n + 1
        devolver n
    }
}

sea siguiente = contador_desde(10)
escribir(siguiente())
escribir(siguiente())
escribir(siguiente())
salida verificada
11
12
13
240_closures.lince

Listas

Crear, modificar y consultar

agregar, eliminar e insertar cambian la lista; longitud, posicion y contiene sólo la leen.

sea notas = [9, 7, 10]
notas.agregar(6)
escribir(notas)
escribir(notas.longitud())
escribir(notas.posicion(10))
escribir(notas.contiene(7))
salida verificada
[9, 7, 10, 6]
4
2
verdadero
310_listas.lince

Operaciones que devuelven una lista nueva

ordenar, invertir, copiar y + (dos listas seguidas) no tocan la original.

sea original = [3, 1, 2]
escribir(original.ordenar())
escribir(original.invertir())
escribir(original + [4, 5])
escribir(original)
salida verificada
[1, 2, 3]
[2, 1, 3]
[3, 1, 2, 4, 5]
[3, 1, 2]
320_listas_nuevas.lince

Ordenar con tu propio criterio

La función comparadora devuelve negativo, cero o positivo. La ordenación es estable.

sea nums = [3, 1, 4, 1, 5]

escribir(nums.ordenar())
escribir(nums.ordenar(funcion(val numero a, val numero b): numero {
    devolver b - a
}))
salida verificada
[1, 1, 3, 4, 5]
[5, 4, 3, 1, 1]
330_ordenar_con.lince

Diccionarios

Crear, leer y recorrer

Las claves son texto. para cada sobre un diccionario recorre las claves.

sea persona = {"nombre": "Ana", "edad": 22}
persona["ciudad"] = "Córdoba"

escribir(persona["nombre"])
escribir(persona.longitud())

para cada texto clave en persona {
    escribir(clave + " = " + persona[clave])
}
salida verificada
Ana
3
nombre = Ana
edad = 22
ciudad = Córdoba
410_diccionarios.lince

Texto

Métodos de texto

La longitud cuenta caracteres, no bytes: las tildes y la eñe cuentan como uno.

importar "texto"

sea frase = "año de la cigüeña"
escribir(frase.longitud())
escribir(frase.mayusculas())
escribir(texto.dividir("a,b,c", ","))
escribir(texto.repetir("ab", 3))
salida verificada
17
AÑO DE LA CIGÜEÑA
["a", "b", "c"]
ababab
510_texto.lince

Texto con valores dentro

Entre llaves va cualquier expresión. Para una llave tal cual, \{; unas llaves vacías {} se quedan como están.

sea nombre = "Ana"
sea vidas = 3
escribir("{nombre} tiene {vidas} vidas")
escribir("y {vidas * 100} puntos, {nombre.longitud()} letras")
escribir("lista: {[1, 2, 3]}")
escribir("\{\"vidas\": {vidas}}")
salida verificada
Ana tiene 3 vidas
y 300 puntos, 3 letras
lista: [1, 2, 3]
{"vidas": 3}
515_texto_con_valores.lince

Clases

Clase, constructor y métodos

mi es la instancia actual. El constructor se llama crear.

clase Rectangulo {
    funcion crear(val numero ancho, val numero alto): nulo {
        mi.ancho = ancho
        mi.alto  = alto
    }
    funcion area(): numero {
        devolver mi.ancho * mi.alto
    }
}

sea r = Rectangulo(4, 3)
escribir(r.area())
salida verificada
12
610_clases.lince

Herencia

extiende hereda; padre(...) llama al constructor de arriba.

clase Figura {
    funcion crear(val texto nombre): nulo { mi.nombre = nombre }
    funcion describir(): texto { devolver "soy un " + mi.nombre }
}

clase Cuadrado extiende Figura {
    funcion crear(val numero lado): nulo {
        padre("cuadrado")
        mi.lado = lado
    }
    funcion area(): numero { devolver mi.lado * mi.lado }
}

sea c = Cuadrado(5)
escribir(c.describir())
escribir(c.area())
salida verificada
soy un cuadrado
25
620_herencia.lince

Interfaces

Una interfaz lista los métodos que una clase promete tener. Si a la clase le falta alguno, implementa da error al declararla.

interfaz Forma {
    funcion area(): numero
    funcion describir(): texto
}

clase Rectangulo implementa Forma {
    funcion crear(val numero ancho, val numero alto): nulo {
        mi.ancho = ancho
        mi.alto  = alto
    }
    funcion area(): numero {
        devolver mi.ancho * mi.alto
    }
    funcion describir(): texto {
        devolver "Rectángulo " + mi.ancho + "x" + mi.alto
    }
}

clase Cuadrado implementa Forma {
    funcion crear(val numero lado): nulo {
        mi.lado = lado
    }
    funcion area(): numero {
        devolver mi.lado * mi.lado
    }
    funcion describir(): texto {
        devolver "Cuadrado de lado " + mi.lado
    }
}

sea lista formas = [Rectangulo(2, 3), Cuadrado(5)]
para cada elemento f en formas {
    escribir(f.describir() + " → " + f.area())
}
salida verificada
Rectángulo 2x3 → 6
Cuadrado de lado 5 → 25
630_interfaces.lince

Enumeraciones

Una enumeracion da nombre a un conjunto fijo de valores: los estados de un personaje, las direcciones... Se comparan con ==, encajan en elegir y se recorren con para cada. Cada uno tiene nombre, indice (su posición) y valor: el que se le dé con =, o su posición. Si a un elegir sin otro le faltan valores, Lince avisa al empezar.

enumeracion Estado { Quieto Andando Saltando }

enumeracion Direccion {
    Arriba = [0, -1]
    Abajo = [0, 1]
    Izquierda = [-1, 0]
    Derecha = [1, 0]
}

funcion animacion(val Estado e): texto {
    elegir e {
        caso Estado.Quieto   { devolver "respirar" }
        caso Estado.Andando  { devolver "andar" }
        caso Estado.Saltando { devolver "saltar" }
    }
    devolver ""
}

sea estado = Estado.Andando
escribir(estado)
escribir(animacion(estado))
escribir(estado == Estado.Andando)

para cada Direccion d en Direccion {
    escribir(d.nombre + " mueve " + d.valor)
}
escribir(Direccion.Izquierda.indice)
salida verificada
Estado.Andando
andar
verdadero
Arriba mueve [0, -1]
Abajo mueve [0, 1]
Izquierda mueve [-1, 0]
Derecha mueve [1, 0]
2
640_enumeraciones.lince

Visibilidad: `privado` y `protegido`

Lo privado sólo se ve desde dentro de la clase; lo protegido, también desde sus subclases. Saltárselo lanza un ErrorAcceso, que se captura como cualquier error.

clase Cuenta {
    privado sea saldo = 0

    funcion ingresar(val numero cantidad): nulo {
        mi.saldo = mi.saldo + cantidad
    }
    funcion consultar(): numero {
        devolver mi.saldo
    }
}

sea c = Cuenta()
c.ingresar(50)
escribir(c.consultar())

intentar {
    escribir(c.saldo)
} capturar (ErrorAcceso e) {
    escribir("No se puede: " + e.mensaje)
}
salida verificada
50
No se puede: El campo 'saldo' de 'Cuenta' es privado: no se puede leer desde aquí.
650_visibilidad.lince

Miembros estáticos

estatico hace que un campo o método pertenezca a la clase y no a cada objeto. Se usan con el nombre de la clase. Un método estático no tiene mi.

clase Pedido {
    estatico sea creados = 0
    estatico fijo IVA = 21

    funcion crear(val numero base): nulo {
        mi.base = base
        Pedido.creados = Pedido.creados + 1
    }
    estatico funcion con_iva(val numero base): numero {
        devolver base + base * Pedido.IVA / 100
    }
}

Pedido(10)
Pedido(20)
escribir(Pedido.creados)
escribir(Pedido.con_iva(100))
salida verificada
2
121
660_estaticos.lince

Clases abstractas y `sobrescribe`

Una clase abstracta no se instancia y puede exigir métodos abstracto a sus subclases. sobrescribe avisa si el padre no tiene ese método, en vez de crear uno nuevo por una errata.

abstracta clase Figura {
    abstracto funcion area(): numero

    funcion describir(): texto {
        devolver "área " + mi.area()
    }
}

clase Cuadrado extiende Figura {
    funcion crear(val numero lado): nulo {
        mi.lado = lado
    }
    sobrescribe funcion area(): numero {
        devolver mi.lado * mi.lado
    }
}

escribir(Cuadrado(3).describir())

intentar {
    Figura()
} capturar (ErrorTipo e) {
    escribir(e.mensaje)
}
salida verificada
área 9
'Figura' es una clase abstracta y no se puede instanciar.
670_abstractas.lince

Propiedades, operadores y `a_texto`

obtener y fijar se usan como un campo pero ejecutan código, así que pueden validar. Los métodos sumar, igual, menor... dan sentido a los operadores, y a_texto decide cómo se escribe el objeto.

clase Vector {
    privado sea _x = 0
    obtener x(): numero { devolver mi._x }
    fijar x(val numero v): nulo {
        si v < 0 { lanzar Error("x no puede ser negativa") }
        mi._x = v
    }
    funcion sumar(val Vector b): Vector {
        sea r = Vector()
        r.x = mi.x + b.x
        devolver r
    }
    funcion menor(val Vector b): logico { devolver mi.x < b.x }
    funcion igual(val Vector b): logico { devolver mi.x == b.x }
    funcion a_texto(): texto { devolver "Vector(" + mi.x + ")" }
}

sea a = Vector()
a.x = 2
sea b = Vector()
b.x = 5
escribir(a + b)
escribir(a < b)
escribir(a == b)

intentar {
    a.x = -1
} capturar (Error e) {
    escribir(e.mensaje)
}
salida verificada
Vector(7)
verdadero
falso
x no puede ser negativa
680_propiedades.lince

Métodos por defecto y el operador `es`

Una interfaz puede dar el cuerpo de un método: las clases que la implementan lo reciben sin repetirlo. x es Tipo comprueba la clase, la interfaz o el tipo básico de un valor.

interfaz Saludable {
    funcion nombre(): texto
    funcion saludar(): texto {
        devolver "Hola, soy " + mi.nombre()
    }
}

clase Persona implementa Saludable {
    funcion nombre(): texto { devolver "Ana" }
}
clase Robot { }

sea p = Persona()
escribir(p.saludar())
escribir(p es Saludable)
escribir(Robot() es Saludable)
escribir(p es Persona)
escribir(5 es numero)
salida verificada
Hola, soy Ana
verdadero
falso
verdadero
verdadero
690_es_y_defecto.lince

Generadores

Producir valores de uno en uno

generador en vez de funcion, y producir en vez de devolver.

generador pares(val numero hasta): numero {
    para (i = 0; i <= hasta; i++) {
        si i % 2 == 0 { producir i }
    }
}

para cada numero p en pares(8) {
    escribir(p)
}
salida verificada
0
2
4
6
8
710_generadores.lince

Uno que no acaba nunca

Cada valor se calcula cuando para cada lo pide, así que un generador puede no acabar: se sale con romper o devolver, y entonces se para (con su finalmente). Un generador se recorre una vez.

generador naturales(): numero {
    sea n = 1
    mientras verdadero {
        producir n
        n++
    }
}

para cada numero n en naturales() {
    si n * n > 50 {
        escribir("el primero con el cuadrado mayor que 50 es " + n)
        romper
    }
}
salida verificada
el primero con el cuadrado mayor que 50 es 8
715_generador_sin_fin.lince

Un generador guardado sigue por donde iba

Guardado en una variable, un generador recuerda dónde se quedó: el siguiente para cada continúa tras el romper. Los valores se calculan cuando se piden, así que rango() sirve aunque sea enorme.

generador turnos(): texto {
    mientras verdadero {
        producir "Ana"
        producir "Luis"
        producir "Marta"
    }
}

sea quien = turnos()
para cada texto t en quien {
    escribir("ronda 1: " + t)
    si t == "Luis" { romper }
}
para cada texto t en quien {
    escribir("ronda 2: " + t)
    si t == "Ana" { romper }
}
para cada numero n en rango(1000000000) {
    si n == 3 { romper }
    escribir(n)
}
salida verificada
ronda 1: Ana
ronda 1: Luis
ronda 2: Marta
ronda 2: Ana
0
1
2
717_generador_guardado.lince

Tareas

Esperar sin parar el juego

Una tarea se escribe como una función y empieza a correr al llamarla. esperar() la deja aparcada en esa línea hasta el fotograma siguiente; esperar(segundos), hasta que pase ese tiempo, y esperar(otra), hasta que otra tarea acabe (y da lo que devolvió). Mientras, todo lo demás sigue. En un juego avanzan solas en cada juego.presentar(); aquí, sin juego, avanzan mientras el programa espera.

tarea parpadear(val texto quien, val numero veces) {
    para (i = 0; i < veces; i++) {
        escribir(quien + ": brilla")
        esperar()
    }
    escribir(quien + ": desaparece")
}

tarea jefe(): texto {
    escribir("jefe: carga el golpe")
    esperar(0.2)
    escribir("jefe: ¡golpe!")
    devolver "fin del ataque"
}

parpadear("moneda", 2)
parpadear("estrella", 3)
escribir(esperar(jefe()))

sea t = parpadear("vela", 100)
esperar()
t.parar()                  # se puede parar a medias
escribir(t.terminada())
salida verificada
moneda: brilla
estrella: brilla
jefe: carga el golpe
moneda: brilla
estrella: brilla
moneda: desaparece
estrella: brilla
estrella: desaparece
jefe: ¡golpe!
fin del ataque
vela: brilla
verdadero
720_tareas.lince

Errores

Intentar, capturar, finalmente

capturar (error e) atrapa cualquier tipo; con un tipo concreto, sólo ese.

intentar {
    sea lista nums = [1, 2, 3]
    escribir(nums[99])
} capturar (ErrorRango e) {
    escribir("fuera de rango: " + e.mensaje)
} capturar (error e) {
    escribir("otro error")
} finalmente {
    escribir("esto se ejecuta siempre")
}
salida verificada
fuera de rango: Índice 99 fuera de rango. La lista tiene 3 elementos.
esto se ejecuta siempre
810_errores.lince

Lanzar errores propios

Tipos predefinidos: Error, ErrorTipo, ErrorRango, ErrorNulo, ErrorMatematico, ErrorArgumento.

funcion raiz_entera(val numero n): numero {
    si n < 0 {
        lanzar ErrorArgumento("no hay raíz real de un número negativo")
    }
    devolver n
}

intentar {
    escribir(raiz_entera(-1))
} capturar (ErrorArgumento e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}
salida verificada
[ErrorArgumento] no hay raíz real de un número negativo
820_lanzar.lince

El primer error es el que cuenta

Un error no se tapa con sus propias consecuencias: se informa de la causa, no del fallo que vino detrás.

intentar {
    escribir(noExiste())
} capturar (error e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}

intentar {
    sea cinco = 5
    escribir(cinco(1))
} capturar (ErrorTipo e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}
salida verificada
[Error] La variable 'noExiste' no está definida.
[ErrorTipo] 'cinco' no es una función, así que no se puede llamar.
830_primer_error.lince

Los fallos de uso también se pueden capturar

Un método con los argumentos mal, un índice fuera de rango, un campo que no existe… llegan como errores con su tipo y se pueden capturar. Sin capturar, el mensaje dice la línea.

sea lista notas = [9, 7]

intentar {
    notas.agregar()
} capturar (ErrorArgumento e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}

intentar {
    notas[5] = 1
} capturar (ErrorRango e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}

clase Caja {
    funcion crear(val numero v): nulo { mi.v = v }
}

intentar {
    sea c = Caja(1)
    escribir(c.contenido)
} capturar (error e) {
    escribir("[" + e.tipo + "] " + e.mensaje)
}
salida verificada
[ErrorArgumento] 'agregar' necesita exactamente 1 argumento.
[ErrorRango] Índice 5 fuera de rango. La lista tiene 2 elementos.
[Error] El objeto no tiene el campo 'contenido'.
840_errores_de_uso.lince

Módulos y librerías

Importar un módulo estándar

Hay ocho: matematica, texto, archivos, tiempo, sistema, json, pruebas y juego.

importar "matematica"
importar "json"

escribir(matematica.raiz(144))
escribir(json.texto({"vivo": verdadero}))
salida verificada
12
{"vivo": true}
910_importar.lince

Importar un archivo tuyo

La ruta se resuelve junto al archivo que importa, no desde donde lanzas el programa. Importar dos veces el mismo archivo no hace nada la segunda.

importar "./lib_geometria"

escribir(area_circulo(1.0))
salida verificada
3.14159
920_libreria.lince

Con espacio de nombres: `como`

Con como, el archivo corre en su propio ámbito y sólo se alcanza por el alias. Así dos librerías pueden tener funciones del mismo nombre.

importar "./lib_metros" como metros
importar "./lib_pies"   como pies

escribir(metros.convertir(100))
escribir(pies.convertir(100))
escribir(metros.UNIDAD)
salida verificada
100
328.084
m
930_como.lince

Clases de una librería con `como`

Las clases de la librería se instancian por el alias: figuras.Cuadrado(3).

importar "./lib_figuras" como figuras

sea c = figuras.Cuadrado(3)
escribir(c.area())
salida verificada
9
935_clases_con_como.lince

El módulo `matematica`

Redondeos, raíces, potencias, trigonometría (en radianes), máximos y mínimos, números al azar y las constantes PI, E y TAU. grados y radianes pasan de unos a otros (el módulo juego mide los ángulos en grados), y para juegos hay distancia, angulo, limitar, interpolar y más. aleatorio() da un número entre 0 y 1; aleatorio(1, 6), un entero entre los dos (un dado).

importar "matematica"

escribir("{matematica.piso(3.9)} {matematica.techo(3.1)} {matematica.redondear(2.5)}")
escribir("{matematica.raiz(144)} {matematica.potencia(2, 10)} {matematica.abs(-7)}")
escribir("{matematica.maximo(3, 9)} {matematica.minimo(3, 9)}")
escribir(matematica.redondear(matematica.seno(matematica.PI / 2)))
escribir("{matematica.factorial(5)} {matematica.combinaciones(5, 2)}")
escribir("{matematica.grados(matematica.PI)} {matematica.distancia(0, 0, 3, 4)} {matematica.limitar(15, 0, 10)}")
sea dado = matematica.aleatorio(1, 6)
escribir(dado >= 1 y dado <= 6)
salida verificada
3 4 3
12 1024 7
9 3
1
120 10
180 5 10
verdadero
940_matematica.lince

El módulo `texto`

Lo que se hace con textos y no es un método: partirlos por un separador, unir una lista, darles la vuelta y buscar dónde aparece algo (−1 si no está).

importar "texto"

sea partes = texto.dividir("rojo,verde,azul", ",")
escribir(partes)
escribir(texto.unir(partes, " y "))
escribir(texto.invertir("lince"))
escribir("{texto.posicion("perspicaz", "pica")} {texto.posicion("lince", "x")}")
salida verificada
["rojo", "verde", "azul"]
rojo y verde y azul
ecnil
4 -1
945_modulo_texto.lince

Los módulos `archivos` y `sistema`

archivos lee y escribe en disco; sistema ejecuta órdenes del sistema y lee el entorno.

importar "archivos"
importar "sistema"

sea ruta = "/tmp/lince_manual_ejemplo.txt"
archivos.escribir(ruta, "hola")
archivos.agregar(ruta, " mundo")
escribir(archivos.leer(ruta))
escribir(archivos.existe(ruta))
archivos.eliminar(ruta)
escribir(archivos.existe(ruta))

escribir(sistema.ejecutar("echo desde el sistema"))
salida verificada
hola mundo
verdadero
falso
desde el sistema
950_archivos_y_sistema.lince

El módulo `tiempo`

tiempo da la hora y permite esperar (en segundos). En un juego se usa poco: el tiempo de cada fotograma lo da juego.dt().

importar "tiempo"

sea inicio = tiempo.ahora()
tiempo.esperar(0.02)
escribir(tiempo.ahora() - inicio >= 0)
salida verificada
verdadero
960_tiempo.lince

El módulo `json`

json.parsear convierte un texto JSON en listas y diccionarios de Lince (y da un error si no es JSON válido); json.texto hace lo contrario, en una línea o, con verdadero, en varias. Escrito dentro de un texto, cada { lleva delante \ para que no se tome por un valor metido en el texto. Lo normal es leerlo de un archivo: json.parsear(archivos.leer("nivel.json")).

importar "json"

sea datos = json.parsear("\{\"nombre\": \"Ñandú\", \"puntos\": [10, 25.5], \"activo\": true}")
escribir("{datos["nombre"]} {datos["puntos"][1]} {datos["activo"]}")
escribir(json.texto({"vidas": 3, "llaves": ["roja"]}))
escribir(json.texto({"vidas": 3, "llaves": ["roja"]}, verdadero))
intentar {
    json.parsear("\{\"roto\": ")
} capturar (error e) {
    escribir(e.mensaje)
}
salida verificada
Ñandú 25.5 verdadero
{"vidas": 3, "llaves": ["roja"]}
{
  "vidas": 3,
  "llaves": [
    "roja"
  ]
}
json.parsear(): el texto no es JSON válido
970_json.lince

Los módulos `bytes` y `bits`

bytes es para datos binarios: leer y escribir archivos byte a byte, y enteros de 1, 2 o 4 bytes en una posición (leer_u16, escribir_i32...). Se usan como una lista de números de 0 a 255. bits hace las operaciones bit a bit con nombres: ambos (AND), alguno (OR), solo_uno (XOR), izquierda y derecha (desplazar), hex y binario.

importar "bytes"
importar "bits"

sea b = bytes.crear(6)
b.escribir_u16(0, 640)
b.escribir_u32(2, 4096)
escribir(b)
escribir("{b.leer_u16(0)} {b.leer_u32(2)} {b.longitud()}")

fijo SALTA = 0b0001
fijo DISPARA = 0b0010
sea estado = bits.alguno(SALTA, DISPARA)
escribir("{bits.binario(estado, 4)} {bits.ambos(estado, DISPARA) != 0}")
escribir("{bits.hex(255)} {bits.izquierda(1, 4)} {bits.de_hex("ff")}")
salida verificada
bytes[80 02 00 10 00 00]
640 4096 6
0011 verdadero
ff 16 255
975_bytes_y_bits.lince

El módulo `pruebas`

Pruebas unitarias para tus programas: afirmar comprueba una condición, igual compara valores (también listas y diccionarios), lanza comprueba que una función da error y resumen imprime el total y termina con código 1 si algo falló, ideal para la integración continua.

importar "pruebas"

funcion doble(val numero n): numero {
    devolver n * 2
}

pruebas.suite("doble")
pruebas.afirmar("doble de 4 es 8", doble(4) == 8)
pruebas.igual("una lista de dobles", [doble(1), doble(2)], [2, 4])
pruebas.lanza("dividir por cero da error", funcion(): nulo { sea x = 1 / 0 })
pruebas.resumen()
salida verificada
  ▶ doble
    ✅ doble de 4 es 8
    ✅ una lista de dobles
    ✅ dividir por cero da error

  3 pruebas: 3 correctas, 0 fallidas
980_pruebas.lince

Fechas con `tiempo`

Una fecha es una marca de tiempo: segundos desde 1970, como tiempo.ahora(). crear, formatear, partes y analizar trabajan en hora local; sus versiones _utc no dependen de la zona horaria. analizar devuelve nulo si el texto no encaja con el formato.

importar "tiempo"

sea t = tiempo.crear_utc(2024, 2, 29, 13, 5, 9)
escribir(tiempo.formatear_utc(t, "%d/%m/%Y %H:%M"))
escribir(tiempo.formatear_utc(t + 86400, "%Y-%m-%d"))

sea p = tiempo.partes_utc(t)
escribir(p["anio"])
escribir(p["dia_anio"])

escribir(tiempo.analizar_utc("2024-02-29 13:05:09", "%Y-%m-%d %H:%M:%S") == t)
escribir(tiempo.analizar_utc("no es una fecha", "%Y-%m-%d"))
salida verificada
29/02/2024 13:05
2024-03-01
2024
60
verdadero
nulo
985_fechas.lince

El módulo `juego`

El módulo para hacer juegos: ventana, dibujo, sprites y animaciones, teclado, ratón, mandos y pantalla táctil, sonido, mapas, física, menús... Tiene su propia página, con un ejemplo y su captura para cada cosa: el módulo juego. Estas funciones de ayuda no necesitan abrir ventana.

importar "juego"

# ¿se tocan el jugador y el enemigo? (x, y, ancho, alto de cada uno)
escribir(juego.colision(10, 10, 32, 32, 30, 30, 32, 32))
escribir(juego.colision(10, 10, 32, 32, 100, 100, 32, 32))

# ancho en píxeles de un texto, para centrarlo en la pantalla
sea ancho_pantalla = 800
escribir((ancho_pantalla - juego.ancho_texto("GAME OVER", 4)) / 2)
salida verificada
verdadero
falso
294
990_juego.lince

Herramientas

Ejecutar, comprobar y compilar

lince programa.lince lo ejecuta (un juego, con recarga en caliente: al guardar cambios sigue con el código nuevo). lince comprobar solo mira que esté bien escrito. lince compilar hace un ejecutable con el programa dentro, y lo que importa, que funciona sin instalar Lince. lince nuevo crea un juego pequeño y completo para empezar a cambiarlo. Para compartir un juego, y para el móvil, mira el módulo juego.

lince programa.lince                    # ejecutarlo
lince comprobar programa.lince          # solo la sintaxis
lince compilar programa.lince -o prog   # un ejecutable con todo dentro
lince nuevo mi_juego                    # una carpeta con un juego para empezar
lince version
1010_compilar.lince

Depurar paso a paso

lince depurar programa.lince lo ejecuta parándose en cada línea: s entra en la siguiente (también dentro de las funciones), n pasa a la siguiente sin entrar, c sigue hasta un punto de parada (b archivo.lince:12), p expresión enseña un valor, v las variables, bt la pila de llamadas y q sale.

lince depurar programa.lince
→ programa.lince:1   importar "juego"
(lince-dbg) b programa.lince:12
(lince-dbg) c
(lince-dbg) p jugador["vida"]
(lince-dbg) v
(lince-dbg) n
1020_depurar.lince

Probar cosas sueltas: el REPL

lince sin nada abre el REPL: se escribe una línea y se ejecuta al momento, recordando lo anterior. Sirve para probar una función o ver qué da una expresión. :ayuda dice sus órdenes.

$ lince
🐆 Lince v0.9.0 — Escribe :ayuda para ver los comandos
>> sea vidas = 3
>> vidas * 10
=> 30
1030_repl.lince

Paquetes y editores

lince instalar descarga una librería de Lince (un repositorio git) para usarla con importar "paquete:nombre". Para el editor hay una extensión de VS Code con colores, plantillas, errores y avisos al escribir, autocompletado e ir a la definición; por debajo usa lince lsp, que cualquier editor que hable LSP puede usar.

lince instalar https://github.com/usuario/paquete
lince listar
lince desinstalar paquete
code --install-extension lince-language-X.Y.Z.vsix   # viene con cada versión
1040_paquetes_y_editores.lince