Saltar al contenido
cajulang

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.

hello.caju
fn greet(name: str) -> str {
return "Hello, " + name + "!"
}
print(greet("world"))
$ caju run hello.caju
Hello, 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:

letconstifelsewhilefortoandornottruefalseimportfromexportfnreturnexterndllexportstructinterfacenilswitchcasedefaultbreakcontinuefallthroughdefergoselectchanmaprange

Literales.

TipoEjemplosObservaciones
Entero42 1_000 0xFF 0b1010 0o17_ separa dígitos
Punto flotante3.14 1.5e364 bits
Texto"Hola\n"UTF-8; escapes \n \t \r \0 \\ \" \', \xHH, \uHHHH, \UHHHHHHHH
Texto sin procesar`c:\carpeta\n`Entre comillas invertidas, sin escapes
Carácter'a'El código del carácter, como entero
Booleano y nulotrue false nil
literals.caju
a := 1_000; b := 0xFF; c := 0b1010; d := 0o17
print(a, b, c, d, 1.5e3)
print("tab\tquote\" \u00e9 \x41", `raw\n`, 'a')
$ caju run literals.caju
1000 255 10 15 1500
tab quote" é A raw\n 97

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.

TipoDescripción
intEntero de 64 bits con signo
int8 int16 int32 int64Enteros con signo de tamaño fijo
uint uint8 uint16 uint32 uint64Enteros sin signo
numNúmero de punto flotante de 64 bits. Acepta valores int
float32Punto flotante de 32 bits
strTexto en UTF-8. s[i] es un uint8 y len(s) cuenta bytes
booltrue o false
anyCualquier valor. Usa una aserción o un switch de tipos para recuperar el tipo
errorInterfaz con el método Error() -> str

Alias. byte = uint8, rune = int32, string = str y float64 = num.

Tipo compuestoFormaComportamiento
Array[3]intTamaño fijo; se copia en la asignación
Slice[]intVentana sobre un array; crece con append
Mapamap[str]intClave y valor; claves de tipos básicos
Puntero*T&x da la dirección, *p el valor
Funciónfn(int) -> intLas funciones son valores
Structstruct Point { x: int, y: int }Valor con campos con nombre
Interfazinterface Shape { area() -> num }Conjunto de métodos

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

FormaEfecto
x := 10Declara e infiere el tipo del valor (aquí, int)
let x: int = 10Declara con el tipo explícito
let x: intDeclara con el valor cero del tipo
let x = 0Sin tipo, let con un número infiere num, para aceptar decimales después
const N = 3Constante; requiere un valor
Las variables valen para toda la función, no solo para el bloque. No declares el mismo nombre con tipos distintos en la misma función.
types.caju
x := 10
x = "ten"
$ caju run types.caju
Type error in types.caju, line 2: "x" (line 1) is int, but got str.

05Expresiones y operadores

GrupoOperadores
Aritméticos+ - * / % ** (potencia)
Bits& | ^ &^ << >>
Comparación== != < <= > >=
Lógicos&& o and, || o or, ! o not
Asignación= := += -= *= /= %= &= |= ^= <<= >>= &^=
Incrementox++ x--
Punteros&x (dirección) y *p (valor apuntado)

+ 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.

ops.caju
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))
$ caju run ops.caju
1024 1 4 16 -4
true false
3.5 3 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:

FormaEjemplo
Clásicofor i := 0; i < n; i++ { }
Solo condiciónfor i < n { } o while i < n { }
Intervalofor i = 1 to 10 { } (incluye ambos límites)
rangefor i, v := range lista { } y for k, v := range mapa { }

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.

flow.caju
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 := 3
switch i {
case 1, 2: print("small")
case 3:
print("three")
fallthrough
default: print("default")
}
$ caju run flow.caju
j 1
j 2
0 0
1 0
three
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).

functions.caju
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))
$ caju run functions.caju
4 7
5
Las funciones declaradas no aceptan un número variable de argumentos. ... 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.

interfaces.caju
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())
$ caju run interfaces.caju
hi ana ana

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.

errors.caju
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()) }
$ caju run errors.caju
mango not found

10Funciones integradas

FunciónUso
print(a, b, ...)Imprime los valores separados por espacios y añade un salto de línea
len(x) y cap(x)Longitud y capacidad de textos, arrays, slices y mapas
append(s, v...)Añade valores a un slice y devuelve el slice
copy(dst, src)Copia elementos y devuelve cuántos copió
delete(m, k)Elimina una clave de un mapa
make(T, n, c)Crea slices y mapas
new(T)Reserva un valor cero y devuelve un puntero
panic(v) y recover()Interrumpe la ejecución y la recupera dentro de un defer
errors.New(msg)Crea un error con el mensaje

11Módulos y paquetes

Cada archivo es un módulo. Solo los nombres declarados con export pueden usarse desde otros archivos.

ImportaciónEfecto
import "std/strings"Biblioteca estándar, usada como strings.split(...)
import filepath "std/path"Importa con otro nombre
import { split, join } from "std/strings"Trae nombres para usarlos directamente
import "./mods/geo"Otro archivo del proyecto, relativo al actual
import "github.com/dueño/paquete"Paquete de terceros, instalado con caju pkg add
mods/geo.caju
# mods/geo.caju
export struct Point { x: int, y: int }
export fn origin() -> Point { return Point{} }
fn hidden() -> int { return 1 }
main.caju
import "./mods/geo"
print(geo.origin())
print(geo.hidden())
$ caju run main.caju
Link error in main.caju, line 4: "hidden" exists in ./mods/geo.caju,
but it is not exported. Write export before its declaration.

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.

caju.toml
[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:

app.caju
import "github.com/acme/strutil"
print(strutil.shout("caju"))
$ caju run app.caju # depois de alterar um arquivo do pacote
Package error: checksum mismatch for github.com/acme/strutil@1.1.0
caju.lock: sha256:aa012c96e037f3527d565bae3fbcb84c459070d694f2276e8149582ced94ee9a
files: sha256:67e6d47f83afb261e6971681c489a422c30f146d8fac2adf0732a7dba5f62756
ComandoQué hace
caju pkg init [nombre]Crea el caju.toml del proyecto
caju pkg add nombre[@versión]Añade una dependencia (por defecto: la última versión publicada)
caju pkg remove nombreElimina una dependencia
caju pkg update [nombre]Actualiza las dependencias a la última versión publicada
caju pkg installDescarga lo que falta y lo verifica todo con caju.lock
caju pkg verifyComprueba los paquetes de la caché contra los hashes del lock
caju pkg treeMuestra el árbol de dependencias
caju pkg cache list|verify|cleanLos paquetes descargados en la máquina, compartidos por todos los proyectos y comprobados por su hash en cada reutilización
caju pkg hash [dir]Calcula el hash de un directorio de paquete

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.

Dentro de un paquete, los archivos se importan por la ruta completa, como 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.

message.caju
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

ComandoQué hace
caju run archivo.cajuCompila en memoria y ejecuta
caju build -o app archivo.cajuGenera un ejecutable de Linux (ELF)
caju build --target wasm -o app.wasm archivo.cajuGenera un módulo WebAssembly (se ejecuta en Node o en el navegador)
caju build --target windows archivo.cajuGenera un .exe para Windows (PE)
caju testEjecuta los archivos _test.caju
caju pkg ...Gestor de paquetes (ver la sección anterior)
caju versionMuestra la versión

15En desarrollo

  • Goroutines, canales y select en el compilador nativo. Las palabras go, chan y select ya están reservadas.
  • En Windows: alloc, peek, poke, readstr, dllexport y DLL nativa.
  • Mapas con clave struct, array o any.