Synsemadocsv0.6.xENES

Referencia

Errores y exit codes

Tocá Run para ver un error capturado; editalo (quitá el try/recover) para ver un Runtime error: … sin capturar. Los tests de abajo fijan los mensajes de los errores que más probablemente te cruces.

errors.syn
-- Doc example: how errors surface — raise, try/recover, and a clean message.
-- Edit this and press Run: remove the try/recover to see an uncaught
-- "Runtime error: x must be >= 0 …" instead.
intent: "doc example: errors"

task risky(x)
    when x < 0
        raise("x must be >= 0, got " + text(x))
    give x * 2

task fails()
    give risky(-1)

-- The message of the error `f` raises, or nothing when it does not raise.
task error_of(f)
    try
        f()
    recover err
        give err
    give nothing

task set_out_of_range()
    let xs be [1]
    set xs[10] to 1

-- run shows the recovery in action:
try
    print("risky(5) = " + text(risky(5)))
    print("risky(-3) = " + text(risky(-3)))      -- this one raises
recover err
    print("caught → " + err)

test "raise fails; try/recover catches the message"
    assert_eq(risky(5), 10)
    let caught be ""
    try
        let bad be risky(-1)
    recover err
        set caught to err
    assert(contains(caught, "must be >= 0"))
    assert_error(fails)
    assert_eq(error_of(() => risky(1)), nothing)

test "Python reflexes get the Synsema form in the message"
    let xs be [1]
    assert(contains(error_of(() => None), "in Synsema: nothing"))
    assert(contains(error_of(() => len(xs)), "in Synsema: length(x)"))
    assert(contains(error_of(() => xs.append(2)), "Synsema has no methods: set xs to append(xs, item)"))
    assert(contains(error_of(() => round(1.234, 2)), "round_to(x, n)"))
    assert(contains(error_of(() => "%d" % 5), "there is no %-formatting"))

test "calls: arity and named arguments"
    assert(contains(error_of(() => risky()), "task 'risky' is missing argument 'x'"))
    assert(contains(error_of(() => risky(1, 2)), "task 'risky' takes 1 argument, got 2"))
    assert(contains(error_of(() => upper("a", "b")), "upper() takes at most 1 argument, got 2"))
    assert(contains(error_of(() => upper(s = "a")), "does not accept named arguments"))

test "type: values do not convert behind your back"
    assert(contains(error_of(() => "id=" + nothing), "Cannot add text and nothing — convert it on purpose"))
    assert(contains(error_of(() => fmt("{a}", {})), "fmt: no value for {a}"))
    assert(contains(error_of(() => int("ff", 16)), "int(x, base = 16)"))
    assert(contains(error_of(() => int("ff", 2.0)), "int(x, base = 2)"))
    assert(contains(error_of(() => int(1.5)), "not a whole number"))
    assert(contains(error_of(() => number("123456789012345678901")), "Use int(x)"))
    assert(contains(error_of(() => bytes(33)), "int_to_bytes(33, size)"))
    assert(contains(error_of(() => 1d == 1.0), "cannot mix decimal and float"))
    assert(contains(error_of(() => length(array(5))), "use size(a)"))
    assert(contains(error_of(() => json_encode({"f": (x) => x})), "the value[\"f\"] is a task, not data"))

test "data: missing keys, indexes out of range or not integers, invalid JSON"
    assert(contains(error_of(() => {"a": 1}["b"]), "Map has no key 'b'"))
    assert(contains(error_of(() => [1][5]), "Index 5 out of bounds (list length 1)"))
    assert(contains(error_of(set_out_of_range), "Index 10 out of bounds (list length 1)"))
    assert(contains(error_of(() => [1, 2][1.5]), "index must be an integer, got 1.5"))
    assert(contains(error_of(() => json_decode("{")), "invalid JSON"))

test "total forms return a fallback instead of raising"
    assert_eq(json_decode("{", nothing), nothing)
    assert_eq(int("abc", fallback = nothing), nothing)
    assert_eq(number("abc", nothing), nothing)
    assert_eq(number("2.5", nothing), 2.5)

Lanzar y capturar§

raise("message")            -- fail deliberately (or re-propagate inside recover)

try
    risky()
recover err
    log "failed: " + err    -- err is the message (text)
    raise(err)              -- RE-PROPAGATE; without it, recover swallows the error

give y stop no son errores — pasan por try/recover. (fail(...) arma una respuesta HTTP, no lanza.) Testeá que algo lanza con assert_error(task). La forma sentencia raise "msg" / raise err (sin paréntesis) también funciona, y un raise solo es error de parseo con el fix en el mensaje. ⚠️ En el v0.5.1 publicado y anteriores, raise "msg" sin paréntesis eran dos expresiones inertes — no-op silencioso; en esos binarios siempre raise("msg").

Un builtin que puede fallar con entrada no confiable suele tener una forma total que devuelve un fallback en vez de lanzar: json_decode(text, nothing), int(text, fallback = nothing), number(text, nothing).

Errores que nombran el arreglo§

Un mensaje dice qué escribir, no solo qué salió mal. Los reflejos de otros lenguajes se reconocen al cargar (así el programa nunca arranca a medias mal) y se contestan con la forma Synsema:

EscribisteEl error dice
x = 5`declare with let x be …, change it with set x to … `
x += 1`Synsema has no compound assignment (+=): write set x to x + 1`
return v / def f(x):`return is not a Synsema statement: give <value> returns from a task` / def is not a Synsema statement: a function is task name(params) …``
if / elif / else`if is not a Synsema statement: a condition is when <condition> …`, chain with otherwise when <condition> , the fallback branch is otherwise `
when x > 0:Synsema blocks have no colon
break / continue / pass`leave a loop with stop / skip to the next item by wrapping the rest of the body in when / an empty block is not needed`
except / finally`after the try block write recover err / there is no finally: put the cleanup after the try/recover block`
import json / class P:`import a local module with use "./file.syn" as name / a record type is type Name with typed fields`
a if c else b / c ? a : b`the inline form is when c then a otherwise b `
&& / ;`the logical operators are and / or / one statement per line (no ;)`
f"…" / lambda y: y / [x for x in xs] / xs[1:3]el template con backticks / a lambda is (y) => y / no comprehensions: apply(…) … where(…) / no slice syntax: slice(xs, start, end?)
// nota (un comentario estilo C)`comments in Synsema start with -- (//` es división entera)
5--1` -- right after a value: … write 5 - -1, or 5 -- comment `

Y en runtime: None, len, str, null, dropna… → `Undefined variable: 'None' — in Synsema: nothing; xs.append(2) → Synsema has no methods: set xs to append(xs, item); d.get("a") → … get(m, key, default); round(x, 2) → … round_to(x, n); "%d" % x → there is no %-formatting: use a template`.

Desde el motor v0.6.46: log(x) → `Undefined variable: 'log' — in Synsema: ln(x) for the natural log, log10(x), log2(x) or log_base(x, b); log "message" (no parentheses) writes a log line (synsema check también lo avisa); in_units(90, "minutes") → in_units() takes a duration, got number — make one first: in_units(duration(seconds = 90), "minutes"); un argumento con nombre que no existe → unknown parameter 'limt' — did you mean 'limit'?; the parameters are: limit, offset (tasks, lambdas y builtins; se detecta cuando corre la llamada, no en check — antes, un nombre mal escrito en una task decía que faltaba el correcto); enumerate(array(5)) → enumerate() of a 0-d array: it is a scalar and has no rows — use to_list(a) or the value itself; parquet_scan(bytes) → parquet_scan: expected a path, got bytes — for bytes already in memory, parquet_read(bytes)`.

synsema check (motor v0.6.45+) atrapa la llamada antes de que corra: una llamada a un nombre que no existe en ningún lado del archivo — no es builtin, no está definido, no se usa como valor — es el aviso `p.syn:3: warning: len(...) — there is no task, variable or builtin named 'len' in this file; it fails when that line runs — did you mean length(x)? (without(xs, k) sin un task without` recibe el mismo aviso sin la pista). Un aviso no cambia el código de salida. Los otros avisos están en CLI.

Las palabras LLM reason/decide/analyze/generate y las demás keywords (to, type, match…) no pueden nombrar lo que bindeás: task f(reason) → 'reason' is a reserved word in Synsema; choose another name for the parameter name. Renombrá (resolve, why). Después de un . cualquier palabra es un nombre de miembro, así que m.decide y tx.to andan.

Exit codes§

ExitCuándo
0éxito
1error de parseo, error de runtime, o algún agente spawneado terminó en ERROR
2error de uso — un argumento faltante, synsema openapi sin serve, o un --flag desconocido (rechazado, no ignorado, en run/test/conform)

El run plano imprime una línea estable: Runtime error: <file>:<line>:<col>: <msg>. ¿Lo medís en una shell? Redirigí (… >/dev/null; echo $?) — no pipees antes de echo $?, o leés el code del pipe.

internal error in the engine (a bug, not your program — please report it): … significa que falló el engine mismo; no hay nada que cambiar en tu código — reportalo con el programa que lo dispara.

Diagnósticos ricos (--explain)§

synsema run --explain program.syn                # legible, en stderr
synsema run --explain --format json program.syn  # estructurado, para tools/agentes

El reporte agrega: contexto de fuente, call stack, variables visibles, el intent del programa, una clasificación (data / io / logic / capability / type), si es recuperable, y sugerencias de fix. El exit code no cambia en ningún caso.

Formas comunes de error§