Especificación del lenguaje
Cómo funciona Caju: sintaxis, tipos, sentencias, funciones, errores y módulos. Los ejemplos se compilaron con la versión 0.5.0 y muestran la salida real.
01Visión general
Caju es un lenguaje de programación compilado, de tipado estático con inferencia. El compilador genera código máquina x86-64: ejecutables ELF para Linux y PE para Windows. También genera módulos WebAssembly, que se ejecutan en Node y en el navegador. El compilador y el runtime están escritos en el propio Caju.
El lenguaje incluye todo lo necesario para trabajar: compilador, ejecutor de pruebas (caju test) y gestor de paquetes (caju pkg), en un único comando caju.
Un programa es uno o más archivos .caju. Las sentencias del nivel superior del archivo se ejecutan en orden, de arriba abajo: no hace falta declarar una función main.
fn greet(name: str) -> str { return "Hello, " + name + "!"} print(greet("world"))02Elementos léxicos
Comentarios. Empiezan con # o // y llegan hasta el final de la línea.
Fin de sentencia. Un salto de línea o un ; termina la sentencia. Una expresión no continúa en la línea siguiente después de un operador: divídela en variables.
Identificadores. Letras (incluidas las acentuadas del alfabeto latino, como é y ç), dígitos y _, sin empezar por un dígito.
Palabras reservadas. No se pueden usar como nombres:
letconstifelsewhilefortoandornottruefalseimportfromexportfnreturnexterndllexportstructinterfacenilswitchcasedefaultbreakcontinuefallthroughdefergoselectchanmaprangeLiterales.
a := 1_000; b := 0xFF; c := 0b1010; d := 0o17print(a, b, c, d, 1.5e3)print("tab\tquote\" \u00e9 \x41", `raw\n`, 'a')03Tipos
Los tipos son opcionales en las declaraciones: sin anotación, el compilador los infiere. Con anotación, un valor de otro tipo es un error de compilación.
Alias. byte = uint8, rune = int32, string = str y float64 = num.
Valor cero. Una variable declarada sin valor empieza con el cero de su tipo: 0, "", false, nil, o una struct con todos los campos a cero.
04Variables y constantes
x := 10x = "ten"05Expresiones y operadores
+ también concatena textos. No existe ~: para invertir los bits, usa x ^ -1.
Las conversiones se escriben como llamadas: int(x), num(x), str(x), uint8(x), float32(x), byte(x), rune(x) y las demás.
print(2 ** 10, 7 % 3, 6 &^ 3, 1 << 4, -8 >> 1)print(true and not false, !true && false)print(str(3.5), int(3.9), num(2))06Control de flujo
if acepta una sentencia antes de la condición, separada por ;. No hay paréntesis alrededor de la condición.
Bucles. Hay cuatro formas de for y el while:
break y continue aceptan una etiqueta para salir de un bucle externo o continuar con él.
switch compara con cada case, que puede listar varios valores. Los casos no pasan al siguiente, salvo que terminen con fallthrough.
for j = 1 to 2 { print("j", j) } outer: for p := 0; p < 3; p++ { for q := 0; q < 3; q++ { if q == 1 { continue outer } if p == 2 { break outer } print(p, q) }} i := 3switch i {case 1, 2: print("small")case 3: print("three") fallthroughdefault: print("default")}07Funciones
Las funciones se declaran con fn en el nivel superior del archivo. Cada parámetro tiene un tipo. El resultado va después de -> y, si se omite, se infiere del return.
Una función puede devolver varios valores, -> (int, error), y los resultados pueden tener nombre: un return solo devuelve sus valores actuales.
Dentro de otra función, crea funciones como valores con fn(...) { }. Capturan las variables externas (closures).
fn split(n: int) -> (q: int, r: int) { q = n / 10 r = n % 10 return}print(split(47)) add := fn(a: int, b: int) -> int { return a + b }print(add(2, 3))... sirve para expandir una lista en una llamada, como en append(a, b...).08Structs, métodos e interfaces
Las structs agrupan campos con tipo. El literal puede dar los valores en orden, Point{1, 2}, o por nombre, Point{y: 2}. Los campos no indicados quedan con el valor cero.
Los métodos son funciones con un receptor: fn (p: Point) dist() recibe una copia, y fn (p: *Point) move() recibe el original y puede modificarlo.
Embeber una struct en otra le da a la externa los campos y métodos de la interna.
Las interfaces se satisfacen implícitamente: un tipo satisface una interfaz si tiene sus métodos. Una interfaz puede embeber otra. x.(T) recupera el tipo guardado y, con v, ok := x.(T), no provoca un panic.
interface Named { name() -> str }interface Greeter { Named; greet() -> str } struct P { n: str }fn (p: P) name() -> str { return p.n }fn (p: P) greet() -> str { return "hi " + p.n } gs := []Greeter{P{"ana"}}print(gs[0].greet(), gs[0].name())09Errores, defer y panic
Los errores son valores del tipo error. Crea uno con errors.New("mensaje") o con cualquier tipo que tenga el método Error() -> str. Las funciones que pueden fallar devuelven el error como último resultado.
defer programa una llamada para cuando termine la función; las llamadas programadas se ejecutan de la última a la primera. panic interrumpe la ejecución y deshace las llamadas; recover, dentro de un defer, detiene el panic. Los errores de ejecución, como un índice fuera de la lista, también provocan un panic.
struct NotFound { name: str }fn (e: *NotFound) Error() -> str { return e.name + " not found" } fn find(name: str) -> error { if name != "caju" { return &NotFound{name} } return nil} err := find("mango")if err != nil { print(err.Error()) }10Funciones integradas
11Módulos y paquetes
Cada archivo es un módulo. Solo los nombres declarados con export pueden usarse desde otros archivos.
# mods/geo.cajuexport struct Point { x: int, y: int }export fn origin() -> Point { return Point{} }fn hidden() -> int { return 1 }import "./mods/geo" print(geo.origin())print(geo.hidden())12Gestor de paquetes
Caju incluye un gestor de paquetes, caju pkg. No hay servidor central: un paquete es un repositorio de GitHub con un archivo caju.toml en la raíz. El nombre del paquete es la ruta del repositorio, y ese es el nombre que importan los programas.
[package]name = "github.com/acme/strutil"version = "1.1.0"description = "Text helpers"license = "MIT" [dependencies]"github.com/acme/other" = "1.0.0"Versiones. Una versión publicada es una etiqueta de git con el formato vX.Y.Z, con el mismo número que el version de caju.toml. Si no coinciden, el gestor rechaza el paquete.
Elección de versiones. Cada dependencia indica la versión mínima que necesita. Cuando dos paquetes piden versiones distintas del mismo paquete, se usa la mayor de las mínimas. Así, el resultado solo cambia cuando un caju.toml pide una versión mayor, nunca porque alguien haya publicado una versión nueva. Las versiones con distinto MAJOR, como 1.x y 2.x, no son compatibles.
Bloqueo de versiones. caju.lock guarda, para cada paquete usado directa o indirectamente, la versión, el commit y un hash SHA-256 de los archivos. Haz commit de él: así todos compilan exactamente los mismos archivos.
Verificación. Antes de compilar, caju build y caju run comprueban que caju.lock esté al día con caju.toml y que los archivos de cada paquete coincidan con el hash fijado. Cualquier diferencia impide la compilación:
import "github.com/acme/strutil" print(strutil.shout("caju"))Dónde se guardan. Los paquetes se descargan con git (la etiqueta de la versión) a la caché en ~/.caju/pkg. add, remove y update solo editan la sección [dependencies] de caju.toml, sin tocar el resto del archivo.
import "github.com/acme/strutil/extra", y un paquete solo debe importar lo que está en su [dependencies].13Llamadas al sistema en Windows
extern declara una función de una DLL de Windows, con la convención stdcall (por defecto) o cdecl. Los parámetros usan tipos de interfaz: i32, u32, i64, u64, f32, f64, ptr, bool, str y wstr, y el resultado puede ser void. Estas llamadas solo existen en programas compilados con --target windows.
extern "user32.dll" fn MessageBoxA(hwnd: ptr, text: str, title: str, flags: u32) -> i32 MessageBoxA(0, "Hello from Caju", "caju", 0)14Compilación y herramientas
15En desarrollo
- Goroutines, canales y
selecten el compilador nativo. Las palabrasgo,chanyselectya están reservadas. - En Windows:
alloc,peek,poke,readstr,dllexporty DLL nativa. - Mapas con clave struct, array o
any.