Referencia del lenguaje

Altair Language Reference v1.7vB

Documentación generada a partir del compilador real altairc.

Guía completa del lenguaje Altair

Basada en el funcionamiento interno real del compilador (altairc, escrito en C: lexer → parser → sema → codegen → C → gcc → binario nativo) y del runtime (altair_rt.c). No incluye async ni safe block, que todavía no están implementadas. El bloque data sí está implementado (con una sintaxis distinta a la propuesta original) — ver §37.

Índice

  1. Visión general
  2. Estructura de un programa
  3. Variables y almacenamiento
  4. Tipos de datos
  5. Operadores y expresiones
  6. Control de flujo
  7. Funciones
  8. Clases y objetos
  9. Listas
  10. Manejo de errores
  11. Snapshots
  12. Choose (aleatoriedad ponderada)
  13. Introspección
  14. Variables token
  15. Orbit y prefer
  16. Servidor HTTP
  17. Rutas y handlers
  18. Middleware
  19. Límite de tasa (rate limiting)
  20. Health checks
  21. Métricas
  22. Apagado ordenado (graceful shutdown)
  23. Sesiones
  24. Configuración y variables de entorno
  25. Pool de base de datos
  26. Planificador de tareas (job scheduler)
  27. Gráficos (raylib)
  28. Consultas de sistema integradas
  29. Referencia de la CLI altairc
  30. Códigos de error
  31. char, file, p#Tipo y operadores a nivel de bit
  32. E/S de ficheros y ejecución de procesos
  33. Punteros crudos
  34. Argumentos de línea de comandos
  35. Variables persistentes con archivo propio
  36. El compilador auto-hospedado (Altair-Core)
  37. Bloque data — contenedores de variables agrupadas

1. Visión general

Altair es un lenguaje compilado estáticamente y orientado a expresiones que se traduce a C mediante altairc. Cada programa termina siendo un único binario nativo, sin dependencias de runtime externas.

Pipeline del compilador:

código .at → lexer → parser → sema → codegen → código .c → gcc → binario

Propiedades clave: - Cada variable tiene un nivel de almacenamiento declarado (ram/disk/cache/temp). - Las variables pueden migrar entre niveles de almacenamiento en tiempo de ejecución mediante orbit. - Texto, listas, objetos y tokens son valores de primera clase. - Toda la memoria la gestiona el runtime (no hay que hacer malloc/free manual, salvo con los punteros crudos nuevos, ver §33). - Los servidores HTTP se declaran con listen; las rutas con route.


2. Estructura de un programa

Todo programa Altair tiene una cabecera opcional y un cuerpo. La cabecera da metadatos del programa; el cuerpo contiene todas las sentencias.

altair.doc;
    name = MyApp
    version = 1.0.0
    author = "Jane Smith"
create altair.doc

/ Esto es un comentario (línea que empieza por /)

define text greeting = "Hello, world!"
log greeting

Campos de cabecera: name, version, author.

El parser lee la cabecera desde altair.doc; hasta create altair.doc. Todo lo demás es el cuerpo del programa.

Comentarios: un / al inicio de una línea (o tras espacios en blanco) seguido de un espacio o una letra empieza un comentario de una línea.


3. Variables y almacenamiento

Declaración

Hay dos formas equivalentes, ambas soportadas por el parser:

define <tipo> <nombre> [almacenamiento] [cualificadores] [= expresión]
<tipo> <nombre> [= expresión] [almacenamiento] [cualificadores]

La palabra define es opcional; el compilador reconoce igualmente una declaración que empieza directamente por el tipo.

Ejemplos:

define numeric count = 0
numeric count2 = 0

define text name = "world"
define bool ready = true
define list items
define object user

Niveles de almacenamiento

Palabra clave Significado
ram Memoria del proceso (por defecto). Rápido; se pierde al salir.
disk Almacenamiento persistente en fichero. Sobrevive a reinicios.
cache Persistente con TTL opcional. Se auto-expira.
temp Se pone a cero al liberarse; para datos sensibles.
auto El runtime elige el mejor nivel.
define text username disk
define text session_token temp
define text api_response cache expire=30m

Cualificadores

Cualificador Ejemplo Efecto
const define numeric pi const = 3.14159 No se puede reasignar
expire=<dur> expire=5m Se auto-expira tras un tiempo
weight=<n> weight=5 Pista de prioridad (0–100)

Duraciones: 30s (segundos), 5m (minutos), 2h (horas).

Asignación

name = "Alice"
count = count + 1
count += 10
count -= 3
count *= 2
count /= 4
count %= 7

Liberar (release)

Libera explícitamente una variable y su memoria:

define text buffer ram = "large data"
/ ... usar buffer ...
release buffer

release es seguro de llamar sobre variables que ya no existen (no hace nada).


4. Tipos de datos

numeric

Un número de coma flotante de 64 bits (double en C internamente).

define numeric x = 42
define numeric pi = 3.14159

text

Una cadena UTF-8.

define text greeting = "Hello!"
define text multi = "Línea 1\nLínea 2"

La concatenación usa +:

define text full = "Hola, " + name + "!"

bool

define bool active = true
define bool done = false

list

Colección ordenada de tamaño dinámico.

define list scores = [10, 20, 30]
scores.append(40)
scores.remove(0)      / elimina el índice 0
scores.clear()        / vacía la lista
define numeric n = scores.length()
define numeric v = scores[1]
scores[0] = 99

.length funciona con o sin paréntesis.

object

Ver §8 Clases y objetos.

token

Un valor de un solo uso: se consume la primera vez que se lee, y una segunda lectura lanza ALT0004.

define token invite_code = "XXXX-YYYY"
define text used = invite_code    / consume el token
define text again = invite_code   / ERROR: ALT0004, token ya consumido

char — texto de un carácter (añadido para self-hosting, §31)

file — manejador de fichero (añadido para self-hosting, §31–32)

p#Tipo — puntero crudo (añadido para self-hosting, §31, §33)


5. Operadores y expresiones

Aritméticos

Op Descripción Ejemplo
+ Suma / concatena texto 1 + 2, "a" + "b"
- Resta 10 - 3
* Multiplica 4 * 5
/ Divide 10 / 3
% Módulo 10 % 3
-x Negación -count

Comparación

==, !=, <, >, <=, >= — funcionan sobre numeric y text (lexicográfica para texto).

Lógicos

&& (y), || (o), ! (no). (En la fuente del propio compilador solo existen estas formas simbólicas — no hay palabras "and"/"or"/"not"; esas palabras sí las usa, por su cuenta, el lenguaje "Altair-Core" del compilador auto-hospedado, ver §36).

if x > 0 && x < 100;
    log "en rango"
break

A nivel de bit (añadido, §31.5)

& | ^ ~ << >> — solo válidos sobre numeric (internamente se convierten a entero de 64 bits, se opera, y se vuelve a double).

Precedencia (de mayor a menor)

  1. Unarios: !, -, ~
  2. *, /, %
  3. +, -
  4. <<, >>
  5. &
  6. ^
  7. |
  8. <, >, <=, >=
  9. ==, !=
  10. &&
  11. ||

6. Control de flujo

Casi todos los bloques en Altair terminan con la palabra break (no usan llaves {}).

if / elif / else

if score > 90;
    log "Excelente"
break
elif score > 70;
    log "Bien"
break
else;
    log "Necesita mejorar"
break

while

define numeric n = 1
while n <= 10;
    log n
    n += 1
break

repeat

repeat 5 times;
    log "hola"
break

/ o con una expresión:
repeat count;
    log "tick"
break

forever

forever;
    log "corriendo..."
    wait 1s
break

foreach

define list fruits = ["manzana", "plátano", "cereza"]
foreach fruit in fruits;
    log fruit
break

exit

Sale del bucle actual, o termina el programa si no hay bucle:

while true;
    if done == true;
        exit        / sale del while
    break
break

exit        / termina el programa (fuera de un bucle)

wait

wait 2s     / espera 2 segundos
wait 500ms  / (usar 0.5s para fracciones de segundo)

7. Funciones

fun greet name text;
    define text msg = "Hola, " + name + "!"
    log msg
    return msg
break

Con tipo de retorno:

fun add -> numeric numeric x, numeric y;
    return x + y
break

Llamada:

greet("Alice")
define numeric result = add(10, 20)

Las funciones pueden leer y escribir variables globales, y sus parámetros se liberan correctamente al terminar (sin fugas de memoria).

Funciones recursivas:

fun factorial -> numeric numeric n;
    if n <= 1;
        return 1
    break
    return n * factorial(n - 1)
break

8. Clases y objetos

class Point;
    define numeric x = 0
    define numeric y = 0

    fun distance;
        define numeric dx = x * x
        define numeric dy = y * y
        return dx + dy
    break
break

create object Point as p
p.x = 3
p.y = 4
log p.distance()
log p.x

Llamadas a métodos:

define text result = myObj.methodName(arg1, arg2)

Los objetos usan arrays dinámicos de campos/métodos internamente (muy poco overhead por instancia), así que crear muchos objetos es viable.


9. Listas

define list items = [1, 2, 3]

/ Añadir
items.append(4)
items.append("hola")

/ Acceso por índice (base 0)
define numeric first = items[0]

/ Modificar por índice
items[0] = 99

/ Eliminar por índice
items.remove(2)

/ Vaciar
items.clear()

/ Longitud (con o sin paréntesis)
define numeric n = items.length
define numeric m = items.length()

/ Iterar
foreach item in items;
    log item
break

/ Concatenar dos listas
define list all = listA + listB

10. Manejo de errores

try;
    / código que puede fallar
    define numeric result = 10 / 0
break
catch as err;
    log err.code
    log err.message
    log err.line
break

El runtime lanza errores en: división por cero, índice fuera de rango, acceso a objeto nulo, doble consumo de token, reasignación de const.

Anidar try/catch:

try;
    try;
        / interno
    break
    catch as inner_err;
        log inner_err.message
    break
break
catch as outer_err;
    log outer_err.message
break

11. Snapshots

Los snapshots guardan todas las variables registradas en disco de forma atómica (con checksum CRC32).

/ Guardar el estado actual
snapshot create "checkpoint1"

/ Restaurar un estado anterior
snapshot restore "checkpoint1"

/ Borrar un snapshot
snapshot delete "checkpoint1"

Se guardan en ~/.altair/<nombre_programa>/snap/.


12. Choose (aleatoriedad ponderada)

choose outcome;
    "win"  70
    "draw" 20
    "lose" 10
break

log outcome

Los pesos son proporcionales — no hace falta que sumen 100.


13. Introspección

Consultas de metadatos del runtime y del compilador, mediante la sintaxis namespace@clave:

/ Consultas de sistema
log system@time        / timestamp Unix
log system@random      / float 0.0–1.0 aleatorio
log system@pid         / PID del proceso
log system@hostname    / nombre de la máquina
log system@username    / usuario actual
log system@os          / "linux", "macos" o "windows"
log system@memory      / RAM usada (bytes)
log system@diskfree    / espacio libre en disco (bytes)

/ Introspección de variables
define disk numeric counter
log system@storage(counter)  / "disk"
log system@weight(counter)   / 0
log system@type(counter)     / "numeric"
log system@size(counter)     / tamaño serializado

/ Info del compilador
log compiler@version
log compiler@name        / "altairc"
log compiler@build       / fecha de compilación
log compiler@architecture  / "x86_64" o "arm64"

/ Metadatos del programa (de la cabecera altair.doc)
log program@name
log program@version
log program@author

14. Variables token

Un token solo se puede consumir una vez. Leerlo una segunda vez lanza ALT0004.

define token api_key temp = "secret-api-key-abc123"

/ La primera lectura consume el token
define text key = api_key
log "Clave usada: " + key

/ La segunda lectura lanza error
define text key2 = api_key   / ERROR: Token already consumed

Útil para contraseñas de un solo uso, códigos de invitación, tokens CSRF, etc.


15. Orbit y prefer

orbit — migración entre varios niveles

Declara una variable con estados de almacenamiento nombrados entre los que puede migrar:

define numeric data orbit 1 "hot" ram, 2 "warm" disk, 3 "cold" cache expire=1h = 0

/ Mover a otro estado
migrate data as "warm"
migrate data as 3

prefer — almacenamiento por orden de preferencia

Prueba niveles de almacenamiento en un orden de preferencia:

define text session prefer ram, cache expire=30m, disk = ""

El runtime usa el primer nivel disponible.


16. Servidor HTTP

Arranca un servidor HTTP en un puerto. El bloque listen contiene declaraciones de rutas, middleware, health, métricas y apagado.

listen 8080;
    route "GET" "/hello";
        respond.text("Hello, World!")
    break
break

El servidor corre hasta que se termina. Las señales (SIGTERM, Ctrl+C) disparan un apagado ordenado.

Cómo funciona: el servidor HTTP integrado de Altair usa sockets TCP POSIX (sin librerías externas). Es de un solo hilo pero usa un modelo de conexión por petición.


17. Rutas y handlers

route "METHOD" "/path";
    / cuerpo del handler
break

Métodos soportados: GET, POST, PUT, DELETE, PATCH, * (cualquiera)

Parámetros de ruta

Sintaxis :param en las rutas:

route "GET" "/users/:id";
    define text user_id = param("id")
    respond.json("found user " + user_id)
break

Acceso a la petición

Dentro de un handler:

define text body_data = body()                / cuerpo crudo de la petición
define text auth = header("Authorization")     / cabecera de la petición
define text id = param("id")                   / parámetro de ruta

Respuesta

respond.text("respuesta en texto plano")
respond.json("cadena o valor json")
respond.status(201)
respond.status(404)
respond.status(500)

Ejemplo:

route "POST" "/users";
    define text payload = body()
    if payload == "";
        respond.status(400)
        respond.json("missing body")
    break
    respond.status(201)
    respond.json("user created")
break

18. Middleware

El middleware se ejecuta antes de cada handler de ruta. Si el middleware llama a stop, se detiene la cadena de la petición.

middleware auth;
    define text token = header("Authorization")
    if token == "";
        respond.status(401)
        respond.json("unauthorized")
        stop
    break
break

Varios middleware se encadenan en orden:

listen 8080;
    middleware logger;
        log "petición recibida"
    break
    middleware auth;
        define text tok = header("X-API-Key")
        if tok != "secret";
            respond.status(401)
            respond.json("bad key")
            stop
        break
    break
    route "GET" "/data";
        respond.json("protected data")
    break
break

19. Límite de tasa (rate limiting)

Se puede añadir a cualquier ruta:

route "POST" "/login" rate_limit 10 per_minute;
    respond.json("ok")
break

Al superar el límite, el runtime devuelve automáticamente HTTP 429 con {"error":"rate limit exceeded"}.

Sintaxis:

route "METHOD" "/path" rate_limit N per_minute;

o

rate_limit N per_second

20. Health checks

La declaración health registra una ruta GET que responde con estado JSON:

health "/health";
    check "database" -> true
    check "cache"    -> true
break

Formato de respuesta:

{"status":"ok","checks":{"database":"ok","cache":"ok"}}

Si algún check devuelve false, el estado HTTP es 503 Service Unavailable. El lado derecho de -> es una expresión (numérica o booleana); cualquier valor distinto de cero se considera saludable.


21. Métricas

Registra un endpoint de métricas compatible con Prometheus:

metrics "/metrics";

Expone un GET /metrics en formato Prometheus de texto plano.

El endpoint produce algo como:

# TYPE events_total counter
events_total 42

22. Apagado ordenado (graceful shutdown)

Registra lógica de limpieza que se ejecuta al recibir SIGTERM o al llamar a altair_server_stop():

on_shutdown;
    log "Servidor apagándose, guardando datos..."
    snapshot create "shutdown_checkpoint"
break

Ejemplo con ciclo de vida completo:

listen 8080;
    route "GET" "/";
        respond.text("running")
    break
    on_shutdown;
        log "adiós!"
    break
break

23. Sesiones

Declara variables de sesión con TTL opcional:

session user_token expires 30m;

Las sesiones se guardan en memoria del proceso en un almacén clave-valor indexado por un ID de sesión. Usa header("X-Session-ID") para recuperar el ID de sesión del cliente.

Dentro de handlers de ruta:

route "GET" "/profile";
    define text sid = header("X-Session-ID")
    define text username = session_get(sid, "username")
    if username == "";
        respond.status(401)
        respond.json("not logged in")
    break
    respond.json("Hello " + username)
break

route "POST" "/login";
    define text user = body()
    session_set("my-session-id", "username", user, 1800)
    respond.json("logged in")
break

24. Configuración y variables de entorno

Usa el bloque config para declarar configuración respaldada por variables de entorno:

config;
    env("DATABASE_URL") default "postgres://localhost/mydb"
    env("PORT") default "8080"
    env("API_SECRET") required
break

Acceder a los valores:

La declaración env(...) crea variables en el ámbito, nombradas según la clave:

config;
    env("PORT") default "8080"
break

define numeric port = PORT
listen port;
    route "GET" "/";
        respond.text("ok")
    break
break

25. Pool de base de datos

Declara un pool de conexiones para una URL de base de datos:

db_pool db = connect("postgres://user:pass@localhost/mydb") max 20

Esto crea una variable db que guarda la cadena de conexión. Hay que conectar tu propia librería cliente de base de datos en el C generado, o usar el stub para prototipar. max fija el tamaño del pool (10 por defecto).

Altair da la sintaxis de declaración y el stub de pooling; el soporte completo de consultas SQL requiere enlazar tu propia librería cliente (PostgreSQL/MySQL/SQLite) en el C generado.


26. Planificador de tareas (job scheduler)

Registra tareas periódicas en segundo plano con job:

job cleanup every 5m;
    log "ejecutando tarea de limpieza"
break

Sintaxis:

job <nombre> every <duración>;
    / cuerpo de la tarea
break

schedule es un alias de job:

schedule heartbeat every 30s;
    log "heartbeat"
break

Cómo funciona: las tareas se comprueban en cada petición (altair_jobs_tick() se llama por conexión). En escenarios de poco tráfico, usa wait dentro de un forever para temporización precisa en su lugar.


27. Gráficos (raylib)

Altair puede producir programas gráficos con ventana sobre raylib. Requiere tener raylib disponible en tiempo de compilación (empaquetado en libs/raylib/<os>/ o instalado en el sistema).

Activar el modo gráfico

link graphics raylib

Debe aparecer una vez, antes de cualquier sentencia de ventana/loop/draw. Indica al compilador que enlace -lraylib (más las librerías de ventaneo de la plataforma) en el binario final.

Ventana

window
    title = "Mi Ventana"
    width = 800
    height = 600
    fps = 60
create window

Bucle principal

loop
    clear skyblue
    / sentencias en cada frame
break

clear <color> limpia el frame. break cierra la definición del bloque loop (no sale del programa — el while generado corre hasta que se cierra la ventana).

draw

draw <tipo>
    x = 100
    y = 100
    ...
create draw
tipo props requeridas notas
text content/text, x, y, size, color
rect / rectangle x, y, width/w, height/h, color
circle x, y, radius/r, color
line x, y, x2, y2, color
line_thick / thick_line añade thick/thickness
triangle x,y,x2,y2,x3,y3,color
pixel x, y, color
image / texture image/src (variable de textura), x, y, color

Los colores aceptan nombres de raylib (red, gold, skyblue, white, ...) o una declaración de bloque color.

Ejemplo completo

link graphics raylib

window
    title = "Paint Test"
    width = 800
    height = 600
    fps = 60
create window

loop
    clear skyblue
    draw text
        content = "Altair Graphics"
        x = 10
        y = 10
        size = 24
        color = white
    create draw
    draw rect
        x = 100
        y = 100
        width = 200
        height = 120
        color = red
    create draw
    draw circle
        x = 500
        y = 200
        radius = 60
        color = gold
    create draw
break

Compilar y ejecutar:

altairc test/paint.at -o paint
./paint

28. Consultas de sistema integradas

Consulta Devuelve
system@time Timestamp Unix (numeric)
system@random Aleatorio 0.0–1.0 (numeric)
system@pid PID del proceso (numeric)
system@hostname Nombre de la máquina (text)
system@username Usuario del SO (text)
system@os "linux" / "macos" / "windows" (text)
system@memory RAM usada en bytes (numeric)
system@diskfree Bytes libres en disco (numeric)
compiler@version Versión del compilador
compiler@name "altairc"
compiler@build Fecha de compilación
compiler@architecture "x86_64" / "arm64"
program@name De la cabecera altair.doc
program@version De la cabecera altair.doc
program@author De la cabecera altair.doc

29. Referencia de la CLI altairc

altairc <source.at> [opciones]   Compila un programa Altair
altairc guide                    Escribe ALTAIR_GUIDE.md en el directorio actual
altairc guide --stdout           Imprime la guía por stdout
altairc --version                Imprime la versión del compilador
altairc --help                   Muestra la ayuda

Opciones:

Flag Descripción
-o <file> Nombre del binario de salida (por defecto: a.out)
--emit-c Imprime el código C generado por stdout
--emit-ast Imprime un resumen de nodos del AST
--no-sema Salta el análisis semántico (compila más rápido)
-v Número de versión

Ejemplos:

# Compilar un programa
altairc hello.at -o hello

# Compilar un servidor
altairc server.at -o myserver

# Depurar: ver qué C se genera
altairc server.at --emit-c | head -200

# Generar la guía del lenguaje
altairc guide

30. Códigos de error

Código Descripción
ALT0001 Variable desconocida o acceso a objeto nulo
ALT0002 Tipos incompatibles en aritmética o comparación
ALT0003 Error de parseo / sintaxis
ALT0004 Token ya consumido
ALT0005 Error de snapshot (crear/restaurar/borrar)
ALT0006 Bloque prefer sin entradas de almacenamiento
ALT0007 Asignación a una variable const
ALT0008 weight debe ser un entero no negativo
ALT0009 orbit tiene números de estado duplicados
ALT0010 División o módulo por cero
ALT0011 Clave o namespace de introspección desconocido
ALT0012 Estado de orbit no encontrado / variable sin orbit
ALT0013 Índice de lista (o de texto) fuera de rango
ALT0014 Campo de objeto no encontrado
ALT0015 Variable de entorno requerida no definida

31. char, file, p#Tipo y operadores a nivel de bit

Añadidos para dar soporte a self-hosting (que un compilador de Altair pueda algún día escribirse en el propio Altair). Tocan las cuatro etapas del compilador: lexer.c/h (tokens nuevos), parser.c (gramática nueva), ast.h (VTYPE_FILE, VTYPE_POINTER), codegen.c (nuevos casos de emisión) y altair_rt.c/h (nuevas etiquetas ALT_FILE/ALT_POINTER y nuevas funciones C integradas).

31.1 char — texto de un carácter

char es un alias de tipo, no un tipo de runtime nuevo. El parser mapea el token char directamente a VTYPE_TEXT (tok_to_vtype() en parser.c), así que una variable char es, a nivel de C/runtime, un valor ALT_TEXT normal que contiene un solo carácter. Por eso toda operación de texto (+, comparaciones, length()) ya funciona sobre char sin necesitar codegen nuevo.

text code = "hola"
char c = code[0]
log c

31.2 Indexar texto: text[índice]

Antes de este cambio, el indexado [i] (ND_INDEX_ACCESS) solo funcionaba sobre ALT_LIST. La función de runtime altair_list_get() ahora también maneja ALT_TEXT: comprueba límites contra strlen() y devuelve una nueva cadena de un carácter. Acceder fuera de rango lanza ALT0013, el mismo código que usan las listas.

text s = "abc"
log s[0]      / "a"
log length(s) / 3   (length() ahora también acepta text, no solo listas)

31.3 file — un tipo manejador de fichero

file es un tipo de valor genuinamente nuevo: VTYPE_FILE en el AST mapea a ALT_FILE en runtime. AltairVal recibió un campo void *ptr (union) compartido entre ALT_FILE (guarda un FILE* de C) y ALT_POINTER (ver más abajo). Copiar un valor file (altair_val_copy) es una copia superficial: el FILE* subyacente se comparte, igual que al asignar un puntero en C.

file f = open("data.txt")
text contents = read(f)
close(f)

31.4 p#Tipo — punteros crudos (sintaxis actualizada)

Esta sintaxis sustituye a la antigua point@Tipo, que ya no existe. La palabra clave completa point se abrevió a p, y el namespace pasó de @ a # (para diferenciarlo visualmente de system@/compiler@, que son namespaces de solo lectura, mientras que p# opera sobre memoria mutable). p queda reservada como palabra clave en todo el lenguaje: ya no se puede usar como nombre de variable o función.

Declarar y reservar memoria (en un solo paso):

p#Tipo nombre = alloc(n)

p#Tipo declara una variable de VTYPE_POINTER (ALT_POINTER en runtime). El Tipo tras la # se guarda solo a efectos de documentación — el valor subyacente es un bloque de memoria cruda de n bytes, así que Altair no obliga a que el puntero apunte siempre a valores de Tipo.

Namespace p# — operaciones sobre un puntero ya creado:

Operación Descripción
p#write(nombre, offset, valor) Escribe valor (numeric) en el slot offset
numeric v = p#read(nombre, offset) Lee el slot offset
p#bytes(nombre) Bytes totales reservados por alloc(n)
p#null(nombre) true si el puntero es nulo o ya fue liberado
p#free(nombre) Libera la memoria

Cada offset es un slot de 8 bytes (un numeric/double), no un byte crudo — así alloc(64) da 8 slots utilizables, del 0 al 7. Leer o escribir fuera de esos límites falla de forma segura (p#write devuelve false, p#read devuelve 0), sin volcar memoria ajena.

p#node n = alloc(64)

p#write(n, 0, 42)
p#write(n, 1, 100)

numeric a = p#read(n, 0)
numeric b = p#read(n, 1)

log a               / 42
log b               / 100
log p#bytes(n)      / 64
log p#null(n)       / false

p#free(n)
log p#null(n)       / true

Por qué antes no servía para nada: en la primera versión (point@Tipo, con ptr_alloc/ptr_free/ptr_is_null), el puntero solo reservaba y liberaba memoria — no existía forma de leer ni escribir dentro de ese bloque, así que era inútil para construir listas enlazadas o nodos de AST. p#write/p#read son justo lo que faltaba para que sea funcional de verdad.

Detalle interno importante: p#free y p#write reciben el puntero por referencia (no una copia), para que la mutación (liberar memoria, poner el puntero a NULL) se refleje también en la variable original. El resto de llamadas a función en Altair sí reciben copias — este es un caso especial en el codegen (ND_FUNC_CALL con fun_name igual a p_free/p_write usa cg_expr_receiver en vez de cg_expr para su primer argumento).

31.5 Operadores a nivel de bit

Tokens nuevos & | ^ ~ << >>, distinguidos correctamente de &&/|| (el lexer comprueba primero las formas de dos caracteres). Se insertaron nuevos niveles de precedencia en la gramática de expresiones, del más flojo al más fuerte, igual que en C:

comparación  ( == != < > <= >= )
      |
   bitor    ( | )

      |
   bitxor   ( ^ )
      |
   bitand   ( & )
      |
   shift    ( << >> )
      |
   suma/resta ( + - )

Cada operador compila a un pequeño helper de runtime (altair_band, altair_bor, altair_bxor, altair_bnot, altair_shl, altair_shr en altair_rt.c) que exige que ambos operandos sean numeric — los números de Altair son double internamente, así que las operaciones a nivel de bit convierten a long long antes de aplicar el operador de C y vuelven a double al devolver. Pasar un operando que no sea numérico lanza ALT0002.

numeric flags = 6 & 3   / 2
numeric merged = 6 | 3  / 7
numeric x = 1 << 4      / 16
numeric inv = ~0        / -1

32. E/S de ficheros y ejecución de procesos

Son llamadas a función normales (ND_FUNC_CALL) — no hizo falta gramática nueva, porque el compilador ya tenía un mecanismo genérico: cualquier llamada nombre(args) que no sea un método conocido de lista/texto compila directo a una llamada en C _fn_nombre(args). Cada builtin de abajo es simplemente una función de C llamada _fn_<nombre> implementada en altair_rt.c.

Función Firma Descripción
open(path) file Abre para lectura (modo "r").
open_write(path) file Abre para escritura, truncando (modo "w").
open_append(path) file Abre para añadir (modo "a").
read(f) text Lee todo el contenido restante de f.
read_line(f) text Lee una línea (sin el \n/\r final).
write(f, texto) bool Escribe texto en f.
close(f) bool Cierra f. Se puede llamar dos veces sin problema.
create_file(path) bool Crea un fichero vacío si no existe.
delete_file(path) bool Borra un fichero.
mkdir(path) bool Crea un directorio (éxito si ya existe).
file_exists(path) bool Comprueba existencia vía stat().
list_dir(path) list Lista los nombres de un directorio (excluye ./..).
exec(cmd) numeric Ejecuta cmd con system(); devuelve el código de salida.
exec_capture(cmd) text Ejecuta cmd con popen(); devuelve el stdout capturado.
create_file("build/out.txt")
file f = open_write("build/out.txt")
write(f, "gcc invoked here")
close(f)

numeric rc = exec("gcc build/main.c -o programa.exe")
if rc == 0;
    log "build ok"
break

Resolución de rutas: las rutas se pasan tal cual a la librería estándar de C (fopen, stat, opendir...) relativas al directorio de trabajo del proceso — no hay ninguna resolución automática hacia una carpeta de proyecto para file/open/etc. (Esa carpeta de "carpeta propia por proyecto" descrita para las variables persistentes sí se implementó, ver §35, pero es un mecanismo aparte, no parte de file/open.)


33. Punteros crudos: referencia completa de p#

Sintaxis Firma Descripción
p#Tipo nombre = alloc(n) declaración Reserva n bytes y crea el puntero nombre
p#write(nombre, offset, valor) bool Escribe valor en el slot offset (8 bytes)
numeric v = p#read(nombre, offset) numeric Lee el slot offset
p#bytes(nombre) numeric Bytes totales reservados
p#null(nombre) bool true si es nulo o ya liberado
p#free(nombre) bool Libera la memoria

Internamente, alloc(n) reserva n bytes más una cabecera oculta de sizeof(size_t) bytes justo antes de la dirección que se le entrega a Altair — esa cabecera guarda n, y es lo que consulta p#bytes. p#free libera desde el inicio real del bloque (puntero - sizeof(size_t)), no desde donde apunta la variable Altair.

Los punteros no tienen recolector de basura y no se liberan automáticamente al salir de ámbito — es gestión manual de memoria intencionadamente cruda, al estilo de C. Usar un puntero después de p#free no está protegido salvo que compruebes p#null tú mismo antes.

p#byte buf = alloc(256)
p#write(buf, 0, 1)
log p#read(buf, 0)
p#free(buf)

34. Argumentos de línea de comandos

Antes de este cambio, un binario Altair compilado no tenía forma de leer su propio argv. Como un compilador auto-hospedado necesita aceptar una ruta de fichero fuente por línea de comandos, el main(int argc, char **argv) generado ahora llama a altair_set_args(argc, argv) antes de ejecutar el cuerpo del programa.

Función Firma Descripción
argc() numeric Número de argumentos de línea de comandos (incluye argv[0]).
arg(i) text El argumento i-ésimo (base 0; arg(0) es la ruta del binario).
numeric n = argc()
if n < 2;
    log "uso: programa <archivo>"
else;
    log "archivo: " + arg(1)
break

35. Variables persistentes con archivo propio

Sintaxis: tras la declaración normal de una variable, un identificador punteado a modo de "nombre de fichero" (por ejemplo player.coins):

numeric coins = 120 player.coins
text playerName = "Nombre" player.name

Cómo funciona internamente:

altair.doc;
    name = "PersistTest"
    version = "1.0"
    author = "a"
create altair.doc

numeric coins = 120 player.coins
text playerName = "Nombre" player.name

log "coins=" + coins
coins = coins + 10
log "coins tras sumar=" + coins

Primera ejecución → imprime coins=120, luego coins tras sumar=130, y dentro de variables/ quedan player.coins (N / 130) y player.name (T / Nombre). Segunda ejecución → el 120 inicial es ignorado porque ya existe variables/player.coins; arranca directamente con coins=130.

Limitación conocida: de momento solo numeric, text y bool se serializan; declarar una variable list con nombre punteado no falla, pero su contenido no se persiste entre ejecuciones.


36. El compilador auto-hospedado (Altair-Core)

selfhost/altair.at es un compilador escrito enteramente en Altair (usa file, indexado de texto, char, y el control de flujo / funciones que Altair ya tenía) que compila un subconjunto deliberadamente más pequeño del lenguaje, llamado Altair-Core, directamente a C.

                 (bootstrap, una sola vez)
 altair.at  ──────────────────────────▶  altairc-selfhost
 (Altair real)      altairc (en C)        (binario nativo)

                 (repetible, sin necesitar inventar un compilador C)
 programa.atc ─────────────────────────▶ programa.gen.c ──▶ gcc ──▶ binario
              altairc-selfhost

Compilarlo:

./altairc selfhost/altair.at -o selfhost/altairc-selfhost

Usarlo:

./altairc-selfhost fib.atc fib.gen.c
gcc fib.gen.c -o fib_bin -lm   # -lm hace falta: numeric % compila a fmod()
./fib_bin

36.1 Gramática de Altair-Core

Altair-Core conserva la forma if/elif/else...break, while...break y fun nombre -> tipo ... break de Altair, pero no tiene análisis semántico propio — los errores de tipos aparecen como errores de gcc sobre el C generado, no como errores de Altair. Los ; tras las cabeceras son opcionales: el lexer de Altair-Core los trata igual que un espacio en blanco.

programa  := stmt*
stmt      := vardecl | assign | log | return | if | while | fun-call
vardecl   := ("numeric"|"text"|"bool") IDENT "=" expr
assign    := IDENT "=" expr
log       := "log" expr
if        := "if" expr stmt* ("elif" expr stmt*)* ("else" stmt*)? "break"
while     := "while" expr stmt* "break"
fun       := "fun" IDENT ("->" tipo)? (tipo IDENT ("," tipo IDENT)*)? stmt* "break"
return    := "return" expr?

Y expresiones con precedencia clásica (orandnot → comparación → suma/resta → mult/div/mod → unario → primario), donde primario incluye número, cadena, true/false, identificador, llamada nombre(args) y (expr).

36.2 Mapeo de tipos (Altair-Core → C)

Altair-Core C generado
numeric double
text char*
bool int
% fmod(a, b) (el % nativo de C no aplica a double)
+ + nativo — solo aritmético, no concatena texto

36.3 Runtime integrado (se emite una vez por fichero de salida)

Función Propósito
concat(a, b) Concatenación de texto (no hay + para texto).
streq(a, b) Igualdad de texto (==/!= sobre char* compararía punteros, no contenido).
numstr(n) numeric → text, para usar con log.
fun fib -> numeric numeric n;
    if n < 2;
        return n
    break
    return fib(n - 1) + fib(n - 2)
break

numeric i = 0
while i < 10;
    log numstr(fib(i))
    i = i + 1
break

log concat("hola, ", "mundo")

36.4 Qué significa y qué no significa "self-hosting" aquí

altairc-selfhost compila programas Altair-Core, pero todavía no puede compilar su propio código fuente (altair.at), porque altair.at usa funcionalidades que Altair-Core no soporta: list, file/open/read/ close, indexado de texto, y declaraciones con más de un parámetro tipado. Cerrar ese círculo — reescribir altair.at usando solo Altair-Core, tras ampliar Altair-Core con listas y E/S de ficheros — es el siguiente paso natural y no está hecho todavía.

Lo que está probado de punta a punta: - altairc (en C) compila con éxito altair.at (Altair real) → altairc-selfhost. - altairc-selfhost compila con éxito programas Altair-Core (recursión, if/elif/else, while, concatenación de texto, %, comparación de texto) → C. - El C generado compila limpio con gcc y produce el resultado correcto.


37. Bloque data — contenedores de variables agrupadas

Agrupa varias variables bajo un nombre común y un "lugar" de almacenamiento compartido. data, p y # son ahora también palabras clave reservadas.

data Nombre lugar;
    tipo var1 = valor1
    tipo var2 = valor2
data create
data PlayerSave disk;
    numeric coins = 120
    text name = "Nombre"
data create

log coins
log name

coins = coins + 5

Esto crea variables/PlayerSave.coins.disk y variables/PlayerSave.name.disk, y cada asignación posterior a coins o name los mantiene actualizados (igual que en §35).

Migrar todo el bloque de golpe

data Nombre migrate lugar

Busca todos los ficheros variables/Nombre.*. y los renombra al nuevo lugar. Es una operación de fichero real (rename()), no un stub.

Limitación conocida — importante: el fichero al que cada variable lee y escribe queda fijado en tiempo de compilación (el nombre se hornea en el C generado). migrate renombra el fichero en disco, pero si el programa sigue corriendo tras el migrate y esa variable vuelve a asignarse, o el programa simplemente termina (el volcado final de cierre también usa el nombre original), el fichero antiguo se vuelve a crear con el nombre de antes. Es decir: migrate sirve como operación puntual de reetiquetado / exportación, pero no convierte el programa en curso en un consumidor dinámico de la nueva ubicación — no es un cambio de nivel de almacenamiento en caliente que el propio programa recuerde y respete después.

data PlayerSave disk;
    numeric coins = 120
data create

coins = coins + 5
data PlayerSave migrate cache
/ en este punto variables/PlayerSave.coins.cache existe con valor 125,
/ pero si el programa reasigna coins otra vez, o simplemente termina,
/ variables/PlayerSave.coins.disk se vuelve a crear también.

Guía generada a partir de la implementación real del compilador y el runtime de Altair. Versión del compilador: 1.7. No cubre async ni safe block: no están implementadas todavía. El bloque data (§37) sí está implementado.

Altair Language Reference - v1.7

altairc guide - complete language reference for the Altair programming language. Generated by: altairc guide Does not cover async or safe block, which are not implemented yet. The data block is implemented (with syntax different from the original proposal) — see §37.


Table of Contents

  1. Overview
  2. Program Structure
  3. Variables & Storage
  4. Types
  5. Operators & Expressions
  6. Control Flow
  7. Functions
  8. Classes & Objects
  9. Lists
  10. Error Handling
  11. Snapshots
  12. Choose (Weighted Random)
  13. Introspection
  14. Token Variables
  15. Orbit & Prefer Storage
  16. HTTP Server (v1.6.5vB)
  17. Routes & Handlers (v1.6.5vB)
  18. Middleware (v1.6.5vB)
  19. Rate Limiting (v1.6.5vB)
  20. Health Checks (v1.6.5vB)
  21. Metrics (v1.6.5vB)
  22. Graceful Shutdown (v1.6.5vB)
  23. Session Management (v1.6.5vB)
  24. Configuration & Env (v1.6.5vB)
  25. Database Pool (v1.6.5vB)
  26. Job Scheduler (v1.6.5vB)
  27. Graphics (raylib)
  28. Built-in System Queries
  29. altairc CLI Reference
  30. Error Codes
  31. Self-Hosting Extensions: char, file, point, bitwise
  32. File System & Process Builtins
  33. Raw Pointers
  34. CLI Arguments in Compiled Programs
  35. The Self-Hosted Compiler (Altair-Core)
  36. Complete Server Example
  37. data Block — grouped variable containers

1. Overview

Altair is a statically compiled, expression-oriented language that transpiles to C via altairc. Every program compiles to a single native binary with no runtime dependencies. v1.6.5vB adds a complete HTTP server layer, job scheduling, session management, and configuration management built directly into the language syntax.

Compiler pipeline:

.at source → lexer → parser → sema → codegen → .c → gcc → binary

Key properties: - Every variable has a declared storage tier (ram/disk/cache/temp) - Variables can migrate between storage tiers at runtime via orbit - Strings, lists, objects, and tokens are first-class values - All memory is managed by the runtime (no manual malloc/free) - HTTP servers are declared with listen; routes are declared with route


2. Program Structure

Every Altair program has an optional header and a body. The header provides program metadata; the body contains all statements.

altair.doc;
name = MyApp
version = 1.0.0
author = "Jane Smith"
create altair.doc

/ This is a comment (line starting with /)

define text greeting = "Hello, world!"
log greeting

Header fields: name, version, author

The header is parsed from altair.doc; to create altair.doc. Everything else is the program body.

Comments: A / at the start of a line (or after whitespace) followed by a space or letter begins a single-line comment.


3. Variables & Storage

Declaration

define <type> <name> [storage] [qualifiers] [= expression]

Examples:

define numeric count = 0
define text    name  = "world"
define bool    ready = true
define list    items
define object  user

Storage Tiers

Keyword Meaning
ram In-process memory (default). Fast; lost on exit.
disk Persistent file storage. Survives restarts.
cache Persistent with optional TTL. Auto-expired.
temp Zeroed on release; for sensitive data.
auto Runtime chooses best tier.
define text username disk
define text session_token temp
define text api_response cache expire=30m

Qualifiers

Qualifier Example Effect
const define numeric pi const = 3.14159 Cannot be reassigned
expire=<dur> expire=5m Auto-expires after duration
weight=<n> weight=5 Priority hint (0–100)

Durations: 30s (seconds), 5m (minutes), 2h (hours).

Assignment

name = "Alice"
count = count + 1
count += 10
count -= 3
count *= 2
count /= 4
count %= 7

Release

Explicitly release a variable and free its memory:

define text buffer ram = "large data"
/ ... use buffer ...
release buffer

v1.6.5vB: release is safe to call on variables that no longer exist - it is a no-op.


4. Types

numeric

Stores a 64-bit floating-point number.

define numeric x = 42
define numeric pi = 3.14159
define numeric big = 1000000

text

A UTF-8 string.

define text greeting = "Hello!"
define text multi = "Line 1\nLine 2"

String concatenation uses +:

define text full = "Hello, " + name + "!"

bool

define bool active = true
define bool done   = false

list

A dynamically sized ordered collection.

define list scores = [10, 20, 30]
scores.append(40)
scores.remove(0)       / remove index 0
scores.clear()         / remove all
define numeric n = scores.length()
define numeric v = scores[1]
scores[0] = 99

v1.6.5vB Bug #7 fix: list.length() with parentheses now works correctly.

object

See Section 8: Classes & Objects.

token

A single-use value - consumed once, then marked as consumed.

define token invite_code = "XXXX-YYYY"
define text  used = invite_code   / consumes the token
define text  again = invite_code  / throws ALT0004 "Token already consumed"

5. Operators & Expressions

Arithmetic

Op Description Example
+ Add / concatenate strings 1 + 2, "a" + "b"
- Subtract 10 - 3
* Multiply 4 * 5
/ Divide 10 / 3
% Modulo 10 % 3
-x Negate -count

v1.6.5vB Bug #3 fix: String concatenation no longer O(n²). Long repeated concat in loops no longer causes OOM.

Comparison

==, !=, <, >, <=, >= - work on numerics and text (lexicographic for text).

Logical

and, or, !

if x > 0 and x < 100;
    log "in range"
break

Precedence (high → low)

  1. Unary: !, -
  2. *, /, %
  3. +, -
  4. <, >, <=, >=
  5. ==, !=
  6. and
  7. or

6. Control Flow

if / elif / else

if score > 90;
    log "Excellent"
break
elif score > 70;
    log "Good"
break
else;
    log "Needs improvement"
break

while

define numeric n = 1
while n <= 10;
    log n
    n += 1
break

repeat

repeat 5 times;
    log "hello"
break

/ Or with an expression:
repeat count;
    log "tick"
break

forever

forever;
    log "running..."
    wait 1s
break

foreach

define list fruits = ["apple", "banana", "cherry"]
foreach fruit in fruits;
    log fruit
break

exit

Exits the current loop or terminates the program:

while true;
    if done == true;
        exit        / exits the while loop
    break
break

exit        / terminates the program (outside a loop)

wait

wait 2s     / wait 2 seconds
wait 500ms  / (use 0.5s for sub-second waits)

7. Functions

fun greet name text;
    define text msg = "Hello, " + name + "!"
    log msg
    return msg
break

Return type annotation:

fun add numeric x numeric, y numeric;
    return x + y
break

Calling functions:

greet("Alice")
define numeric result = add(10, 20)

v1.6.5vB Bug #4 fix: Functions can now read and write global variables. Previously, globals were inaccessible inside function bodies.

v1.6.5vB Bug #5 fix: Function parameters are properly freed when the function returns, eliminating a per-call memory leak.

Recursive functions work correctly:

fun factorial numeric n numeric;
    if n <= 1;
        return 1
    break
    return n * factorial(n - 1)
break

8. Classes & Objects

class Point;
    define numeric x = 0
    define numeric y = 0

    fun distance;
        define numeric dx = x * x
        define numeric dy = y * y
        return dx + dy
    break
break

create object Point as p
p.x = 3
p.y = 4
log p.distance()
log p.x

Method calls:

define text result = myObj.methodName(arg1, arg2)

v1.6.5vB Bug #6 fix: Objects now use dynamic field/method arrays (~50 bytes/instance vs ~10 KB previously). This makes creating large numbers of objects viable.


9. Lists

define list items = [1, 2, 3]

/ Append
items.append(4)
items.append("hello")

/ Access by index (0-based)
define numeric first = items[0]

/ Modify by index
items[0] = 99

/ Remove by index
items.remove(2)

/ Clear
items.clear()

/ Length (with or without parentheses)
define numeric n = items.length
define numeric m = items.length()   / v1.6.5vB: both forms work

/ Iterate
foreach item in items;
    log item
break

/ Concatenate two lists
define list all = listA + listB

10. Error Handling

try;
    / code that might fail
    define numeric result = 10 / 0
break
catch as err;
    log err.code
    log err.message
    log err.line
break

Throwing errors: (from user code, not directly, but via runtime operations)

The runtime throws on: division by zero, index out of bounds, null object access, token double-consume, const reassignment.

v1.6.5vB Bug #1 fix: Sequential try/catch blocks no longer crash after ~34 uses. The try-depth counter is now always restored correctly - both on success and on error paths.

Nesting try/catch:

try;
    try;
        / inner
    break
    catch as inner_err;
        log inner_err.message
    break
break
catch as outer_err;
    log outer_err.message
break

11. Snapshots

Snapshots save all registered variables to disk atomically (with CRC32 checksum).

/ Save current state
snapshot create "checkpoint1"

/ Restore previous state
snapshot restore "checkpoint1"

/ Delete a snapshot
snapshot delete "checkpoint1"

Snapshots are stored in ~/.altair/<program_name>/snap/.


12. Choose (Weighted Random)

choose outcome;
    "win"  70
    "draw" 20
    "lose" 10
break

log outcome

Weights are proportional - they don't need to add to 100.


13. Introspection

Query runtime and compiler metadata:

/ System queries
log system@time        / Unix timestamp
log system@random      / 0.0–1.0 random float
log system@pid         / Process ID
log system@hostname    / Machine hostname
log system@username    / Current user
log system@os          / "linux", "macos", or "windows"
log system@memory      / RAM used (bytes)
log system@diskfree    / Free disk space (bytes)

/ Variable introspection
define disk numeric counter
log system@storage(counter)  / "disk"
log system@weight(counter)   / 0
log system@type(counter)     / "numeric"
log system@size(counter)     / serialized size

/ Compiler info
log compiler@version     / "1.6.5vB"
log compiler@name        / "altairc"
log compiler@build       / build date
log compiler@architecture  / "x86_64" or "arm64"

/ Program metadata (from altair.doc header)
log program@name
log program@version
log program@author

14. Token Variables

A token can only be consumed once. Attempting to read it a second time throws ALT0004.

define token api_key temp = "secret-api-key-abc123"

/ First read consumes the token
define text key = api_key
log "Key used: " + key

/ Second read throws
define text key2 = api_key   / ERROR: Token already consumed

Use tokens for one-time passwords, invite codes, CSRF tokens, etc.


15. Orbit & Prefer Storage

orbit - multi-tier migration

Declare a variable with named storage states it can migrate between:

define numeric data orbit 1 "hot" ram, 2 "warm" disk, 3 "cold" cache expire=1h = 0

/ Move to a different state
migrate data as "warm"
migrate data as 3

prefer - priority-ordered storage

Try storage tiers in preference order:

define text session prefer ram, cache expire=30m, disk = ""

The runtime uses the first available tier.


16. HTTP Server (v1.6.5vB)

Start an HTTP server on a port. The listen block contains route, middleware, health, metrics, and shutdown declarations.

listen 8080;
    route "GET" "/hello";
        respond.text("Hello, World!")
    break
break

The server runs until terminated. Signals (SIGTERM, Ctrl+C) trigger graceful shutdown.

How it works: Altair's built-in HTTP server uses POSIX TCP sockets (no external libraries required). The server is single-threaded but uses a connection-per-request model.


17. Routes & Handlers (v1.6.5vB)

route "METHOD" "/path";
    / handler body
break

Supported methods: GET, POST, PUT, DELETE, PATCH, * (any)

Path Parameters

Use :param syntax in paths:

route "GET" "/users/:id";
    define text user_id = param("id")
    respond.json("found user " + user_id)
break

Request Access

Inside a route handler:

define text body_data = body()                / raw request body
define text auth = header("Authorization")   / request header
define text id   = param("id")               / path parameter

Response

respond.text("plain text response")
respond.json("json string or value")
respond.status(201)          / set status code
respond.status(404)
respond.status(500)

Example:

route "POST" "/users";
    define text payload = body()
    if payload == "";
        respond.status(400)
        respond.json("missing body")
    break
    respond.status(201)
    respond.json("user created")
break

18. Middleware (v1.6.5vB)

Middleware runs before every route handler. If the middleware calls stop, the request chain is halted.

middleware auth;
    define text token = header("Authorization")
    if token == "";
        respond.status(401)
        respond.json("unauthorized")
        stop
    break
break

Multiple middleware declarations are chained in order:

listen 8080;
    middleware logger;
        log "request received"
    break
    middleware auth;
        define text tok = header("X-API-Key")
        if tok != "secret";
            respond.status(401)
            respond.json("bad key")
            stop
        break
    break
    route "GET" "/data";
        respond.json("protected data")
    break
break

19. Rate Limiting (v1.6.5vB)

Attach a rate limit to any route:

route "POST" "/login" rate_limit 10 per_minute;
    / handle login
    respond.json("ok")
break

When the limit is exceeded the runtime automatically returns HTTP 429 with {"error":"rate limit exceeded"}.

Syntax:

route "METHOD" "/path" rate_limit N per_minute;

or

rate_limit N per_second

20. Health Checks (v1.6.5vB)

The health declaration registers a GET route that responds with JSON status:

health "/health";
    check "database" -> true
    check "cache"    -> true
break

Response format:

{"status":"ok","checks":{"database":"ok","cache":"ok"}}

If any check returns false, the HTTP status is 503 Service Unavailable.

The right-hand side of -> is an expression (numeric or bool). Any non-zero value is considered healthy.


21. Metrics (v1.6.5vB)

Register a Prometheus-compatible metrics endpoint:

metrics "/metrics";

This exposes a GET /metrics endpoint in plain-text Prometheus format.

Incrementing metrics from code:

Use the built-in metric functions (callable from route handlers via expressions):

route "POST" "/events";
    / metric_inc("events_total") - emitted in generated C
    respond.json("recorded")
break

The metrics endpoint will output:

# TYPE events_total counter
events_total 42

22. Graceful Shutdown (v1.6.5vB)

Register cleanup logic to run when the server receives SIGTERM or calls altair_server_stop():

on_shutdown;
    log "Server shutting down, flushing data..."
    snapshot create "shutdown_checkpoint"
break

Example with full lifecycle:

listen 8080;
    route "GET" "/";
        respond.text("running")
    break
    on_shutdown;
        log "bye!"
    break
break

23. Session Management (v1.6.5vB)

Declare session variables with optional TTL:

session user_token expires 30m;

This declares a session variable. Sessions are stored in-process in a key-value store keyed by a session ID. Use header("X-Session-ID") to retrieve the session ID from the client.

Inside route handlers:

route "GET" "/profile";
    define text sid = header("X-Session-ID")
    define text username = session_get(sid, "username")
    if username == "";
        respond.status(401)
        respond.json("not logged in")
    break
    respond.json("Hello " + username)
break

route "POST" "/login";
    define text user = body()
    / store username in session for 30 minutes
    session_set("my-session-id", "username", user, 1800)
    respond.json("logged in")
break

24. Configuration & Env (v1.6.5vB)

Use the config block to declare environment-variable-backed configuration:

config;
    env("DATABASE_URL") default "postgres://localhost/mydb"
    env("PORT") default "8080"
    env("API_SECRET") required
break

Accessing env values:

The env(...) declaration creates variables in scope named after the key:

config;
    env("PORT") default "8080"
break

define numeric port = PORT
listen port;
    route "GET" "/";
        respond.text("ok")
    break
break

25. Database Pool (v1.6.5vB)

Declare a connection pool for a database URL:

db_pool db = connect("postgres://user:pass@localhost/mydb") max 20

This creates a variable db that holds the connection string. Connect your own database client library in the generated C code, or use the stub for prototyping.

The max qualifier sets the pool size (default 10).

Note: Altair v1.6.5vB provides the declaration syntax and connection pooling stub. Full SQL query support requires linking your PostgreSQL/MySQL/SQLite client library in the generated C.


26. Job Scheduler (v1.6.5vB)

Register recurring background jobs using job:

job cleanup every 5m;
    log "running cleanup job"
    / ... cleanup logic ...
break

Syntax:

job <name> every <duration>;
    / job body
break

schedule is an alias for job:

schedule heartbeat every 30s;
    log "heartbeat"
break

How it works: Jobs are checked on every request (altair_jobs_tick() is called per connection). In low-traffic scenarios, use wait inside a forever loop for precise timing instead.


27. Graphics (raylib)

Altair can produce windowed graphical programs on top of raylib. This requires raylib to be available at compile time (vendored in libs/raylib/<os>/ or installed system-wide — see libs/raylib/README.md).

Enabling graphics mode

link graphics raylib

This must appear once, before any window/loop/draw statements. It tells the compiler to link -lraylib (plus the platform windowing libraries) into the final binary.

Window

window
    title = "My Window"
    width = 800
    height = 600
    fps = 60
create window

Main loop

loop
    clear skyblue
    / statements each frame
break

clear <color> clears the frame. break ends the loop block (it does not exit the program — it closes the frame loop definition itself; the generated while runs until the window is closed).

draw

draw <kind>
    x = 100
    y = 100
    ...
create draw
kind required props notes
text content/text, x, y, size, color
rect / rectangle x, y, width/w, height/h, color
circle x, y, radius/r, color
line x, y, x2, y2, color
line_thick / thick_line adds thick/thickness
triangle x,y,x2,y2,x3,y3,color
pixel x, y, color
image / texture image/src (a texture variable), x, y, color

Colors accept raylib color names (red, gold, skyblue, white, ...) or a color block declaration.

Full example

link graphics raylib

window
    title = "Paint Test"
    width = 800
    height = 600
    fps = 60
create window

loop
    clear skyblue
    draw text
        content = "Altair Graphics"
        x = 10
        y = 10
        size = 24
        color = white
    create draw
    draw rect
        x = 100
        y = 100
        width = 200
        height = 120
        color = red
    create draw
    draw circle
        x = 500
        y = 200
        radius = 60
        color = gold
    create draw
break

Compile and run:

altairc test/paint.at -o paint
./paint

28. Built-in System Queries

Query Returns
system@time Unix timestamp (numeric)
system@random Random 0.0–1.0 (numeric)
system@pid Process ID (numeric)
system@hostname Machine hostname (text)
system@username OS username (text)
system@os "linux" / "macos" / "windows" (text)
system@memory RAM used in bytes (numeric)
system@diskfree Free disk bytes (numeric)
compiler@version "1.6.5vB"
compiler@name "altairc"
compiler@build Build date
compiler@architecture "x86_64" / "arm64"
program@name From altair.doc header
program@version From altair.doc header
program@author From altair.doc header

29. altairc CLI Reference

altairc <source.at> [options]   Compile an Altair program
altairc guide                   Write ALTAIR_GUIDE.md to the current directory
altairc guide --stdout          Print guide to stdout
altairc --version               Print compiler version
altairc --help                  Show usage

Options:

Flag Description
-o <file> Output binary name (default: a.out)
--emit-c Print the generated C source to stdout
--emit-ast Print AST node summary
--no-sema Skip semantic analysis (faster compile)
-v Version number

Examples:

# Compile a program
altairc hello.at -o hello

# Compile a server
altairc server.at -o myserver

# Debug: see what C is generated
altairc server.at --emit-c | head -200

# Generate the language guide
altairc guide

30. Error Codes

Code Description
ALT0001 Unknown variable or null object access
ALT0002 Type mismatch in arithmetic or comparison
ALT0003 Parse / syntax error
ALT0004 Token already consumed
ALT0005 Snapshot error (create/restore/delete)
ALT0006 Prefer block has no storage entries
ALT0007 Assignment to a const variable
ALT0008 Weight must be a non-negative integer
ALT0009 Orbit has duplicate state numbers
ALT0010 Division or modulo by zero
ALT0011 Unknown introspection key or namespace
ALT0012 Orbit state not found / variable has no orbit
ALT0013 List index out of bounds
ALT0014 Object field not found
ALT0015 Required env var not set

31. Self-Hosting Extensions: char, file, point, bitwise

Added to support self-hosting — i.e. so a compiler for Altair can eventually be written in Altair itself. These are low-level, "systems" features layered on top of the existing type system. Internally they touch every stage of the pipeline: lexer.c/h (new tokens), parser.c (new grammar), ast.h (VTYPE_FILE, VTYPE_POINTER), codegen.c (new emission cases), and altair_rt.c/h (new AltVType tags ALT_FILE/ALT_POINTER and new builtin C functions).

31.1 char — single-character text

char is a type alias, not a new runtime type. The parser maps the char token straight onto VTYPE_TEXT (see tok_to_vtype() in parser.c), so a char variable is, at the C/runtime level, an ordinary ALT_TEXT value that happens to hold one character. This means every text operation (+, comparisons, length()) already works on char for free — no new codegen was needed.

text code = "hola"
char c = code[0]
log c

31.2 Indexing text: text[index]

Before this change, [i] indexing (ND_INDEX_ACCESS) only worked on ALT_LIST. The runtime function altair_list_get() now also handles ALT_TEXT: it bounds-checks against strlen() and returns a freshly allocated one-character string. Out-of-range access raises ALT0013, the same error code used for list bounds.

text s = "abc"
log s[0]      / "a"
log length(s) / 3  (length() now also accepts text, not just lists)

31.3 file — a file handle type

file is a genuinely new value type: VTYPE_FILE in the AST maps to ALT_FILE at runtime. Internally, AltairVal gained a void *ptr union member shared between ALT_FILE (holds a C FILE*) and ALT_POINTER (see below). Copying a file value (altair_val_copy) is a shallow copy — the underlying FILE* is shared, exactly like assigning a pointer in C.

file f = open("data.txt")
text contents = read(f)
close(f)

31.4 p#Type — raw pointers (updated syntax)

This syntax replaces the old point@Type, which no longer exists. The full keyword point was shortened to p, and the namespace changed from @ to # (to visually distinguish it from system@/compiler@, which are read-only namespaces, while p# operates on mutable memory). p is now a reserved keyword throughout the language: it can no longer be used as a variable or function name.

Declare and allocate memory (in one step):

p#Type name = alloc(n)

p#Type declares a variable of VTYPE_POINTER (ALT_POINTER at runtime). The Type after # is stored only for documentation purposes — the underlying value is a raw memory block of n bytes, so Altair does not enforce that the pointer only ever points at values of Type.

Namespace p# — operations on an existing pointer:

Operation Description
p#write(name, offset, value) Writes value (numeric) into slot offset
numeric v = p#read(name, offset) Reads slot offset
p#bytes(name) Total bytes allocated by alloc(n)
p#null(name) true if the pointer is null or already freed
p#free(name) Frees the memory

Each offset is an 8-byte slot (a numeric/double), not a raw byte — so alloc(64) gives 8 usable slots, 0 through 7. Reading or writing outside those bounds fails safely (p#write returns false, p#read returns 0), without touching foreign memory.

p#node n = alloc(64)
p#write(n, 0, 42)
p#write(n, 1, 100)

numeric a = p#read(n, 0)
numeric b = p#read(n, 1)

log a               / 42
log b               / 100
log p#bytes(n)      / 64
log p#null(n)       / false

p#free(n)
log p#null(n)       / true

Why the old version was useless: in the first version (point@Type, with ptr_alloc/ptr_free/ptr_is_null), the pointer could only allocate and free memory — there was no way to read or write inside that block, so it was useless for building linked lists or AST nodes. p#write/p#read are exactly what was missing to make it actually functional.

Important internal detail: p#free and p#write receive the pointer by reference (not a copy), so the mutation (freeing memory, setting the pointer to NULL) is also reflected in the original variable. Every other function call in Altair receives copies — this is a special case in codegen (ND_FUNC_CALL with fun_name equal to p_free/p_write uses cg_expr_receiver instead of cg_expr for its first argument).

31.5 Bitwise operators

New tokens & | ^ ~ << >>, lexed distinctly from &&/|| (the lexer checks the two-character forms first). New precedence levels were inserted into the expression grammar, from loosest to tightest, matching C's convention:

comparison  ( == != < > <= >= )
      |
   bitor   ( | )
      |
   bitxor  ( ^ )
      |
   bitand  ( & )
      |
   shift   ( << >> )
      |
   add/sub ( + - )

Each operator compiles to a small runtime helper (altair_band, altair_bor, altair_bxor, altair_bnot, altair_shl, altair_shr in altair_rt.c) that requires both operands to be numeric — Altair numbers are double internally, so bitwise ops cast to long long before applying the C operator and cast back to double on return. Passing a non-numeric operand raises ALT0002 (type mismatch).

numeric flags = 6 & 3   / 2
numeric merged = 6 | 3  / 7
numeric x = 1 << 4      / 16
numeric inv = ~0        / -1

32. File System & Process Builtins

These are ordinary function calls (ND_FUNC_CALL) — no new grammar was needed, because the compiler already had a generic fallback: any call name(args) that isn't a known list/string method compiles straight to a C call _fn_name(args). Each builtin below is simply a C function named _fn_<name> implemented in altair_rt.c.

Function Signature Description
open(path) file Open for reading ("r" mode).
open_write(path) file Open for writing, truncating ("w" mode).
open_append(path) file Open for appending ("a" mode).
read(f) text Read the entire remaining contents of f.
read_line(f) text Read one line (strips the trailing \n/\r).
write(f, text) bool Write text to f. Always returns true unless f is invalid.
close(f) bool Close f. Safe to call twice.
create_file(path) bool Create an empty file if it doesn't already exist.
delete_file(path) bool Delete a file.
mkdir(path) bool Create a directory (succeeds if it already exists).
file_exists(path) bool Check for existence via stat().
list_dir(path) list List entry names in a directory (excludes ./..).
exec(cmd) numeric Run cmd via system(); returns the exit code.
exec_capture(cmd) text Run cmd via popen(); returns captured stdout.
create_file("build/out.txt")
file f = open_write("build/out.txt")
write(f, "gcc invoked here")
close(f)

numeric rc = exec("gcc build/main.c -o programa.exe")
if rc == 0;
    log "build ok"
break

Path resolution: paths are passed straight to the C standard library (fopen, stat, opendir, ...) relative to the process's current working directory — there is no automatic "resolve into a project folder" behavior for file/open/etc.

Not yet implemented: the variables/ auto-persistence folder, data ... save data blocks, async/safe block, and persistent variables bound to a file (e.g. numeric coins = 120 player.coins) described in the original self-hosting proposal are not part of this build. Everything in sections 31–35 covers only what was actually implemented: file I/O, char, raw pointers, bitwise ops, exec, CLI args, and the bootstrap self-hosted compiler.


33. Raw Pointers: complete p# reference

Syntax Signature Description
p#Type name = alloc(n) declaration Allocates n bytes and creates the pointer name
p#write(name, offset, value) bool Writes value into slot offset (8 bytes)
numeric v = p#read(name, offset) numeric Reads slot offset
p#bytes(name) numeric Total bytes allocated
p#null(name) bool true if null or already freed
p#free(name) bool Frees the memory

Internally, alloc(n) reserves n bytes plus a hidden header of sizeof(size_t) bytes right before the address handed to Altair — that header stores n, which is what p#bytes looks up. p#free frees from the real start of the block (pointer - sizeof(size_t)), not from where the Altair variable points.

Pointers are not garbage collected and are not automatically freed on scope exit — this is intentionally raw, C-style manual memory management. Using a pointer after p#free is not protected unless you check p#null yourself first.

p#byte buf = alloc(256)
p#write(buf, 0, 1)
log p#read(buf, 0)
p#free(buf)

34. CLI Arguments in Compiled Programs

Previously, a compiled Altair binary had no way to read its own argv. Since a self-hosted compiler needs to accept a source file path on the command line, the generated main(int argc, char **argv) now calls altair_set_args(argc, argv) before running the program body.

Function Signature Description
argc() numeric Number of command-line arguments (argv[0] included).
arg(i) text The i-th argument (0-indexed; arg(0) is the binary path).
numeric n = argc()
if n < 2;
    log "uso: programa <archivo>"
else;
    log "archivo: " + arg(1)
break

35. The Self-Hosted Compiler (Altair-Core)

selfhost/altair.at is a compiler written entirely in Altair (using the features from §31–34: file, text indexing, char, and the ordinary control flow / functions Altair already had). It compiles a deliberately smaller subset of the language — called Altair-Core — directly to C.

                 (bootstrap, one-time)
 altair.at  ──────────────────────────▶  altairc-selfhost
 (real Altair)      altairc (C)          (native binary)

                 (repeatable, no C compiler needed to invent)
 program.atc ─────────────────────────▶ program.gen.c ──▶ gcc ──▶ binary
              altairc-selfhost

Build it:

./altairc selfhost/altair.at -o selfhost/altairc-selfhost

Use it:

./altairc-selfhost fib.atc fib.gen.c
gcc fib.gen.c -o fib_bin -lm   # -lm needed: numeric % compiles to fmod()
./fib_bin

35.1 Altair-Core grammar

Altair-Core keeps Altair's if/elif/else...break, while...break, and fun name -> type ... break shape, but is untyped at the compiler level (no semantic analysis pass — type errors surface as gcc errors on the generated C, not as Altair errors). Semicolons after headers are optional: the Altair-Core lexer treats ; exactly like whitespace.

program   := stmt*
stmt      := vardecl | assign | log | return | if | while | fun-call
vardecl   := ("numeric"|"text"|"bool") IDENT "=" expr
assign    := IDENT "=" expr
log       := "log" expr
if        := "if" expr stmt* ("elif" expr stmt*)* ("else" stmt*)? "break"
while     := "while" expr stmt* "break"
fun       := "fun" IDENT ("->" type)? (type IDENT ("," type IDENT)*)? stmt* "break"
return    := "return" expr?
expr      := or ; or := and ("or" and)* ; and := not ("and" not)*
not       := "not" not | cmp
cmp       := add (("=="|"!="|"<"|">"|"<="|">=") add)?
add       := mul (("+"|"-") mul)*
mul       := unary (("*"|"/"|"%") unary)*
unary     := "-" unary | primary
primary   := NUMBER | STRING | "true" | "false" | IDENT | IDENT"("args")" | "("expr")"

35.2 Type mapping (Altair-Core → C)

Altair-Core Emitted C
numeric double
text char*
bool int
% fmod(a, b) (native C % doesn't apply to double)
+ native +arithmetic only, does not concatenate text

35.3 Built-in runtime (emitted once per output file)

Function Purpose
concat(a, b) Text concatenation (there is no + overload for text).
streq(a, b) Text equality (==/!= on char* would compare pointers, not content).
numstr(n) numeric → text, for use with log.
fun fib -> numeric numeric n;
    if n < 2;
        return n
    break
    return fib(n - 1) + fib(n - 2)
break

numeric i = 0
while i < 10;
    log numstr(fib(i))
    i = i + 1
break

log concat("hola, ", "mundo")

35.4 Known limitations — what "self-hosting" does not mean yet

altairc-selfhost can compile Altair-Core programs, but cannot yet compile its own source (altair.at), because altair.at itself uses features Altair-Core doesn't support: list, file/open/read/close, text indexing, and multi-parameter-type declarations beyond simple scalars. Closing that loop — rewriting altair.at using only Altair-Core, after extending Altair-Core with lists and file I/O — is the natural next step and is not done in this build.

What is proven to work end-to-end, and was tested: - altairc (C) successfully compiles altair.at (real Altair) → altairc-selfhost. - altairc-selfhost successfully compiles Altair-Core programs (recursion, if/elif/else, while, string concatenation, %, text comparison) → C. - The generated C compiles cleanly with gcc and produces correct output.


36. Complete Server Example

altair.doc;
name = TodoAPI
version = 1.6.5vB
author = "Altair Team"
create altair.doc

/ Configuration
config;
    env("PORT") default "8080"
    env("API_SECRET") required
break

/ In-memory todo list
define list todos disk = []
define numeric next_id = 1

/ Auth middleware
middleware auth;
    define text key = header("X-API-Key")
    define text secret = API_SECRET
    if key != secret;
        respond.status(401)
        respond.json("{\"error\":\"unauthorized\"}")
        stop
    break
break

/ Jobs
job persist every 5m;
    snapshot create "todos_checkpoint"
    log "todos saved"
break

/ Health
health "/health";
    check "storage" -> 1
break

/ Metrics
metrics "/metrics";

/ Graceful shutdown
on_shutdown;
    snapshot create "shutdown"
    log "shutting down, saved state"
break

/ Routes
listen PORT;
    route "GET" "/todos";
        respond.json(todos)
    break

    route "POST" "/todos" rate_limit 60 per_minute;
        define text title = body()
        if title == "";
            respond.status(400)
            respond.json("{\"error\":\"title required\"}")
        break
        todos.append(title)
        respond.status(201)
        respond.json("{\"created\":true}")
    break

    route "DELETE" "/todos/:idx";
        define text idx_str = param("idx")
        todos.remove(0)
        respond.json("{\"deleted\":true}")
    break

    route "GET" "/";
        respond.text("TodoAPI v1.6.5vB running")
    break
break

Run:

altairc todo.at -o todo
API_SECRET=mysecret PORT=3000 ./todo

Test:

curl http://localhost:3000/
curl -H "X-API-Key: mysecret" http://localhost:3000/todos
curl -H "X-API-Key: mysecret" -X POST -d "Buy milk" http://localhost:3000/todos
curl http://localhost:3000/health
curl http://localhost:3000/metrics

37. data Block — grouped variable containers

Groups several variables under a common name and shared storage "place". data, p, and # are now also reserved keywords.

data Name place;
    type var1 = value1
    type var2 = value2
data create
data PlayerSave disk;
    numeric coins = 120
    text name = "Nombre"
data create

log coins
log name

coins = coins + 5

This creates variables/PlayerSave.coins.disk and variables/PlayerSave.name.disk, and every subsequent assignment to coins or name keeps them up to date (same as in §35).

Migrating the whole block at once

data Name migrate place

Finds all variables/Name.*. files and renames them to the new place. This is a real file operation (rename()), not a stub.

Known limitation — important: the file each variable reads from and writes to is fixed at compile time (the name is baked into the generated C). migrate renames the file on disk, but if the program keeps running after the migrate and that variable gets reassigned again, or the program simply exits (the final shutdown dump also uses the original name), the old file gets recreated under the previous name. In other words: migrate works as a one-off relabeling/export operation, but it does not turn the running program into a dynamic consumer of the new location — it's not a hot storage-tier switch that the program itself remembers and honors afterward.

data PlayerSave disk;
    numeric coins = 120
data create

coins = coins + 5
data PlayerSave migrate cache
/ at this point variables/PlayerSave.coins.cache exists with value 125,
/ but if the program reassigns coins again, or simply exits,
/ variables/PlayerSave.coins.disk gets recreated too.

Altair Language Reference - v1.7 - Generated by altairc guide Compiler: altairc 1.7 | License: MIT