Especificación de Sintaxis — Fitz¶
Estado: BORRADOR v0.7 (actualización 2026-05-17, post Fase 9.z entera). La mayoría del diseño original ya está implementado; lo pendiente queda señalado abajo.
Este documento describe el diseño completo del lenguaje. Para ver solo lo que el intérprete ejecuta hoy, con ejemplos que corren, leé docs/guide.md.
Matriz rápida de estado¶
Implementado y estable: - Variables, primitivos, strings con interpolación, operadores (cap 3-6 de la guía). - Control de flujo:
if/while/for/loop/match(caps 7-10). - Funciones, closures, higher-order (cap 11). - Tipos custom (type), structs, field access, defaults, nullables (cap 12-13). - Listas, mapas, rangos, métodos built-in (push,map,filter,find,get, etc.) — cap 9, 13. -Result<T>+Ok/Err+?+matchexhaustivo (cap 14). - Módulos:import foo,from foo import bar as baz— cap 16. - Type checker estático:fitz checkvalida tipos en todo el programa (Fase 5a).fitz runcorre en modo strict por default. - HTTP nativo (cap 17):@get/@post/@put/@delete, path params tipados, body JSON, query params, status codes custom (return <int> { ... }),@server(port, host, docs=Bool, api_version="X"),@header(name="X", into="alias"),@middleware(fn | cors(...)). - OpenAPI 3.1 autogenerado + UI Scalar en/docs(cap 18). Subcomandofitz openapi archivo.fitz. Status codes custom reflejados enresponsesdel schema. - Async nativo (cap 19):async fn,.await,Future<T>como tipo built-in, builtinsleep(ms). - Paralelismo HTTP real (post-F17): el server (tantofitz runcomo el binario producido porfitz build) corre tokiort-multi-threadcon N workers según cores. Containers de Value y EnvRef migrados aArc<Mutex<>>(Send + Sync). Bridge HTTPmpsc/oneshoteliminado. 5 requests concurrentes a un handlersleep(1000).awaitresponden en ~1.2s (no ~5s como pre-F17). - Codegen a binario nativo viafitz build(cap 20). - Middleware + CORS con preflight automático y echo del Origin recibido (cors({"allow_origin": ["a.com", "b.com"]})). - Interop con Python (from python import sqlalchemy) — Fase 8 entera (caps 21.1 → 21.12). - LSP (autocomplete + hover + go-to-def + diagnostics) + extensión VSCode — Fase 9.x entera (cap 22). Distribución multi-plataforma. - Package manager:fitz new/init/add/remove/update, manifestfitz.toml, deps path/git con lockfile — Fase 9.y.1 → 9.y.4. Registry (9.y.5) diferido. - Formatterfitz fmt(cero config, preserva comments) — Fase 9.z.1 (cap 23). - Test runnerfitz testcon@test+ 4 assertion builtins (assert,assert_eq,assert_ne,assert_throws) — Fase 9.z.2 (cap 24). -fitz dev(hot reload con file watcher + kill/respawn) — Fase 9.z.3 (cap 25). -fitz repl(REPL interactivo con env compartido, multi-line, comandos:type/:env/:load, history persistente) — Fase 9.z.4 (cap 26). -fitz lint(4 lints:unused_variable,unused_import,useless_match,string_concat; supresión por// @allow(<lint>)) — Fase 9.z.5 (cap 27). Cierra Fase 9.z entera.Diseñado pero no implementado: -
@benchpara benchmarks (post-MVP de 9.z.2). - Test fixtures (@before_all,@before_each, etc.) — post-MVP. - Auto-fixfitz lint --fix(post-MVP de 9.z.5). - Lints adicionales (redundant_clonecuando aterrice análisis de movimientos). -fitz lint(linter de patrones más allá de tipos) — Fase 9.z.5. - Registry público (fitz publish+fitz add foo@1.2.3) — Fase 9.y.5, diferido. - Bundle Scalar offline embebido (hoy CDN) — deuda menor (Q.5, postergada por trade-off de tamaño). - Doc-strings sobre handlers retenidos por el parser — refactor invasivo lexer/parser/AST; pendiente. - CLI builder nativo (@command/@arg/@flag) — Fase 13 del roadmap. - Frontend en.fitz(SFC + SSR) — Fase 11+ del roadmap.Cuando esta especificación y la guía discrepan, gana la guía (que solo documenta lo implementado).
Comentarios¶
Variables¶
// sin tipo — inferido. `let` es opcional, ambas formas conviven.
name = "Fitz"
let count = 42
active = true
// con tipo explícito
name: Str = "Fitz"
let count: Int = 42
score: Float = 3.14
// nullable — el ? indica que puede ser null
email: Str? = null
age: Int? = 25
let x = 1yx = 1son equivalentes — el parser acepta ambas sin distinción semántica. Mutabilidad por defecto.
Tipos primitivos¶
| Tipo | Descripción | Ejemplo |
|---|---|---|
Int |
entero 64-bit | 42 |
Float |
punto flotante 64-bit | 3.14 |
Str |
string UTF-8 | "hola" |
Bool |
booleano | true, false |
Null |
ausencia de valor | null |
Strings¶
name = "Fitz"
// interpolación nativa con {}
greeting = "Hola, {name}!"
result = "La respuesta es {40 + 2}"
// multilínea
text = """
Hola
mundo
"""
Tipos compuestos¶
// listas
numbers: List<Int> = [1, 2, 3]
names = ["Fitz", "Rust", "Python"] // inferido
// mapas
config: Map<Str, Any> = {
"host": "localhost",
"port": 3000
}
// tuplas
point = (10, 20)
coords: (Int, Int) = (x, y)
Structs / Tipos custom¶
type User {
id: Int
name: Str
email: Str?
active: Bool = true // valor por defecto
}
// instanciar
user = User {
id: 1,
name: "Fitz",
email: "fitz@example.com"
}
// acceder
print(user.name)
Funciones¶
// función básica
fn greet(name: Str) -> Str {
return "Hola, {name}"
}
// función con tipos inferidos
fn add(a, b) {
return a + b
}
// función flecha (una expresión)
fn double(n: Int) -> Int => n * 2
// función async
async fn fetch_user(id: Int) -> User {
let user = db.find(id).await
return user
}
// función sin retorno
fn log(msg: Str) {
print(msg)
}
Control de flujo¶
// if / else
if age >= 18 {
print("mayor")
} else if age >= 13 {
print("adolescente")
} else {
print("niño")
}
// if como expresión
status = if active { "activo" } else { "inactivo" }
// for
for item in items {
print(item)
}
for i in 0..10 {
print(i)
}
// while
while running {
tick()
}
// loop infinito con break
loop {
let input = read_line()
if input == "quit" { break }
process(input)
}
Match¶
// match básico
match status {
"active" => print("activo")
"inactive" => print("inactivo")
_ => print("desconocido")
}
// match con Result (manejo de errores)
match db.find(id).await {
Ok(user) => return user
Err(e) => return 404 { message: e }
}
// match con binding
match user.age {
0..12 => print("niño")
13..17 => print("adolescente")
18.. => print("adulto")
}
Manejo de errores¶
// Result es el tipo de retorno para operaciones que pueden fallar
fn divide(a: Float, b: Float) -> Result<Float> {
if b == 0.0 {
return Err("División por cero")
}
return Ok(a / b)
}
// ? propaga el error automáticamente (como en Rust)
async fn get_user_name(id: Int) -> Result<Str> {
let user = db.find(id).await?
return Ok(user.name)
}
// match para manejar el error
match divide(10.0, 0.0) {
Ok(result) => print("Resultado: {result}")
Err(e) => print("Error: {e}")
}
Async — async fn y await¶
Fitz soporta concurrencia cooperativa con async/await al
estilo Rust. Una función marcada async fn devuelve un
Future<T> cuando se la llama; await extrae el T.
async fn fetch_data(url: Str) -> Result<Str> {
let body = http_get(url).await?
return Ok(body)
}
async fn main() {
let data = fetch_data("https://example.com").await
print(data)
}
Reglas¶
awaites postfix: se escribeexpr.await, noawait expr. Encaja naturalmente en method chains:db.find(id).await?.awaitsólo es legal adentro deasync fn. Usarlo en una fn sync es un error de tipos.- Llamar a una
async fnsin.awaitdevuelve unFuture<T>— útil para guardar el future en una variable o pasarlo como argumento. - Sync y async conviven libremente. Una
async fnpuede llamar a una sync fn (sin.await); una sync fn puede recibir unFuture<T>pero no puede await-earlo.
Future<T> como tipo¶
Future<T> es un generic built-in con la misma forma que
List<T>, Map<K, V>, Result<T> o Nullable<T> — válido en
anotaciones, parámetros, returns, y campos de type.
HTTP async¶
Cualquier handler HTTP puede ser async fn. El runtime tokio
existente ejecuta los handlers async sin trabajo extra del
usuario. Sync sigue siendo válido para handlers triviales:
@get("/users/{id}")
async fn get_user(id: Int) -> Result<User> {
let user = db.find(id).await?
return Ok(user)
}
@get("/health")
fn health() -> Str => "ok" // sync — sin await
Implementado en Fase 6. Hasta entonces,
async fnse parsea pero el evaluator lo trata como sync (los handlers HTTP corren con bridge sync/async via mpsc). El operador.awaitse introduce en 6.1 y comienza a funcionar en 6.3.
HTTP — Core del lenguaje¶
// GET
@get("/")
async fn index() -> Str {
return "Hola desde Fitz 🏔️"
}
// GET con parámetro de ruta
@get("/users/{id}")
async fn get_user(id: Int) -> User {
return db.find(id).await?
}
// POST con body tipado
@post("/users")
async fn create_user(body: UserInput) -> User {
return db.save(body).await?
}
// PUT
@put("/users/{id}")
async fn update_user(id: Int, body: UserInput) -> User {
return db.update(id, body).await?
}
// DELETE
@delete("/users/{id}")
async fn delete_user(id: Int) -> Str {
db.delete(id).await?
return "eliminado"
}
// respuestas con status code explícito
@get("/protected")
async fn protected() -> Response {
return 401 { message: "No autorizado" }
}
HTTP avanzado — auth, websockets, jobs (futuro, Fase 9.w)¶
Decoradores adicionales que extienden @get/@post/... al resto
del stack web típico. No implementado todavía — sintaxis
tentativa, sujeta a revisión al arrancar Fase 9.w.
// auth con decoradores apilables sobre handlers
@authenticated
@get("/me")
async fn me(user: User) -> User {
return user
}
@admin
@delete("/users/{id}")
async fn delete_user(id: Int) -> Str { ... }
// el provider del auth lo define el usuario (una vez por proyecto)
@auth_provider
fn check_token(headers: Map<Str, Str>) -> Result<User> {
let token = headers.get("authorization")?
// ... validación, lookup en DB
return Ok(user)
}
// websockets tipados — decorator paralelo a @get
type ChatMsg { user: Str, text: Str }
@ws("/chat")
async fn chat_handler(conn: WsConn<ChatMsg>) {
loop {
match conn.recv().await {
Ok(msg) => conn.broadcast(msg).await,
Err(_) => break,
}
}
}
// frames binarios raw — `WsConn<Bytes>` cambia el wire de
// text JSON a `Message::Binary` opaco. Útil para protocolos
// custom (protobuf, MessagePack), streaming audio/video, etc.
@ws("/raw")
async fn raw(conn: WsConn<Bytes>) {
loop {
match conn.recv().await {
Ok(buf) => conn.send(buf).await,
Err(_) => break,
}
}
}
// cron jobs
@cron("0 0 * * *") // cada medianoche
async fn cleanup_sessions() { ... }
// background tasks fire-and-forget
@background
async fn send_email(to: Str, body: Str) { ... }
@post("/users")
async fn create(input: UserInput) -> User {
let u = save(input)
spawn send_email(u.email, "welcome")
return u
}
Módulos e imports¶
// importar módulo del proyecto
import utils
import utils.format
// importar específico
from utils import format_date, slugify
// interop Python
from python import numpy as np
from python import pandas as pd
// importar paquete de fitz registry (futuro)
import fitz/http
import fitz/db
Entry point¶
// Si hay rutas HTTP definidas, el servidor arranca automáticamente
// en puerto 3000 por defecto.
// Configuración opcional:
@server(port: 8080, host: "0.0.0.0")
// Para programas CLI, el entry point es main:
fn main() {
print("Hola mundo")
}
Testing (Fase 9.z.2 — IMPLEMENTADO)¶
Test runner built-in con decorator @test. Cerrado entero el
2026-05-17. Para detalle de uso ver
cap 24 de la guía.
@test fn suma_funciona() {
assert_eq(2 + 2, 4)
}
@test fn nullable_funciona() {
let u = User { id: 1, name: "Ada" }
assert(u.email == null)
}
@test async fn pausa_y_compara() {
let r = sleep(0).await
assert_eq(r, null)
}
// benchmarks (futuro post-MVP de @test, no implementado)
@bench fn fib_es_rapido() {
fib(20) // medido por iteración
}
Builtins de aserción: assert(cond, msg?), assert_eq(a, b),
assert_ne(a, b), assert_throws(fn).
Discovery: fitz test descubre todos los @test del proyecto
(tests/*.fitz top-level + [lib].entry para tests inline solo-lib).
CLI builder (futuro, Fase 13)¶
Construcción de CLIs con decoradores + autogeneración de --help.
No implementado todavía — sintaxis tentativa, Fase 13 del
roadmap.
@command("greet")
@arg("name", help="A quién saludar")
@flag("loud", short="l", help="MAYÚSCULAS")
fn greet(name: Str, loud: Bool = false) {
let msg = if (loud) { "HOLA, {name}!" } else { "Hola, {name}" }
print(msg)
}
@command("server", help="Inicia el server HTTP")
@arg("port", help="Puerto", default=3000)
fn run_server(port: Int) {
// arranca el server
}
Sin imports — typer/click/clap built-in.
Ejemplo completo¶
// api.fitz
type User {
id: Int
name: Str
email: Str?
}
type UserInput {
name: Str
email: Str
}
// base de datos en memoria (para el ejemplo)
let users: List<User> = []
@get("/users")
async fn list_users() -> List<User> {
return users
}
@get("/users/{id}")
async fn get_user(id: Int) -> User {
let user = users.find(fn(u) => u.id == id)
match user {
Ok(u) => return u
Err(_) => return 404 { message: "Usuario no encontrado" }
}
}
@post("/users")
async fn create_user(body: UserInput) -> User {
let user = User {
id: users.len() + 1,
name: body.name,
email: body.email
}
users.push(user)
return 201 user
}