Lenguaje
Sintaxis
Synsema se lee como prosa. Los bloques se definen por indentación (4 espacios o 1 tab) — no hay llaves ni dos puntos.
¿Venís de Python? Leé primero Python → Synsema — la tabla de traducción — mapea los reflejos de Python a estas formas y marca las trampas semánticas (doctesteadas).
-- Doc example: core syntax. Universal code; English comments.
intent: "doc example: variables, operators, pipe, inline conditional, strings, numbers"
task double(n)
give n * 2
task add(a, b)
give a + b
print("5 |> double = " + text(5 |> double)) -- run shows: 5 |> double = 10
test "let / set / arithmetic"
let x be 10
set x to x + 5
assert_eq(x, 15)
assert_eq(10 % 3, 1)
assert_eq(10 / 4, 2.5)
assert_eq(10 / 2, 5.0)
assert_eq(10 // 3, 3)
assert_eq(-7 // 2, -4)
assert_eq(-7 % 2, 1)
assert_eq(2 ** 10, 1024)
assert_eq(7 // 0.1, 69.0)
test "** binds tighter than unary minus and groups to the right"
assert_eq(-2 ** 2, -4)
assert_eq((-2) ** 2, 4)
assert_eq(2 ** 3 ** 2, 512)
assert_eq(2 ** -1, 0.5)
test "comparison + boolean logic"
assert(1 < 2 and 2 <= 2)
assert(true or false)
assert(not false)
assert_eq(1 == 1.0, true)
assert_eq(2 ** 53 + 1 == 9007199254740992.0, false)
test "chained comparisons"
let x be 5
assert(1 < x <= 10)
assert_eq(1 < x < 3, false)
assert(0 < 1 < 2 < 3)
test "membership: in / not in"
assert(2 in [1, 2])
assert(3 not in [1, 2])
assert("a" in {"a": 1})
assert("ell" in "hello")
assert(bytes([2]) in bytes([1, 2]))
assert_error(() => 1 in "a1")
test "set writes along a path that starts from a variable"
let m be {"a": {}}
set m["a"]["b"] to 1
assert_eq(m, {"a": {"b": 1}})
let s be {"items": [1]}
set s.items to append(s.items, 2)
assert_eq(s.items, [1, 2])
let xs be [1, 2]
set xs[0] to 9
assert_eq(xs, [9, 2])
test "and/or short-circuit and give a bool"
let m be {"a": 1}
assert_eq("k" in m and m["k"] == 1, false)
assert_eq("a" in m or m["zz"] == 1, true)
assert_eq(1 or 0, true)
test "pipe: lowest precedence; a call step gets the value as its first argument"
assert_eq(5 |> double, 10)
assert_eq(5 |> double |> double, 20)
assert_eq(1 + 2 |> double, 6)
assert_eq(5 |> add(1), 6)
assert_eq([3, 1, 2] |> sort_by((v) => v), [1, 2, 3])
assert_eq(5 |> ((v) => add(v, 10)), 15)
test "inline conditional is an expression"
let label be when 80 >= 50 then "pass" otherwise "fail"
assert_eq(label, "pass")
let n be -3
assert_eq(when n > 0 then "pos" otherwise when n < 0 then "neg" otherwise "zero", "neg")
test "strings: escapes, \u escapes, backtick templates"
assert_eq(length("a\nb"), 3)
assert_eq(length("a\\b"), 3)
assert_eq("\u00e9", "é")
assert_eq("\u{1F600}", "😀")
assert_eq(length("\uD83D\uDE00"), 1)
assert_eq(length("\\u00e9"), 6)
assert_eq(length("C:\users"), 8)
assert_eq(length("C:\build\x64"), 12)
let name be "Ada"
assert_eq(`Hello {name}, {1 + 1}`, "Hello Ada, 2")
assert_eq(`xs={[1, "a"]} n={nothing}`, "xs=[1, \"a\"] n=nothing")
assert_eq("{name}", "{" + "name}")
assert_eq('single', "single")
assert_eq('it\'s', "it's")
assert_eq(length("say \"hi\""), 8)
assert_eq(`\u00e9`, "é")
let a be 1
assert_eq(`x\u{a}y`, "x\\u1y")
assert_eq(text(["1", 1]), "[\"1\", 1]")
test "text indexing and negative indexes"
assert_eq("abc"[0], "a")
assert_eq("abc"[-1], "c")
assert_eq([10, 20, 30][-2], 20)
assert_error(() => [1, 2][1.5])
test "text + number joins; text + nothing does not"
assert_eq("n=" + 1, "n=1")
assert_eq("ok=" + true, "ok=true")
assert_error(() => "n=" + nothing)
assert_error(() => "xs=" + [1])
test "number literals"
assert_eq(1_000_000, 1000000)
assert_eq(0x1f18, 7960)
assert_eq(0o17, 15)
assert_eq(0b101, 5)
assert_eq(0x_ff, 255)
assert_eq(1e3, 1000.0)
assert_eq(is_integer(1e3), false)
assert_eq(1.5e-3, 0.0015)
assert(is_integer(10 ** 18))
test "a trailing comma is fine in calls, lists and maps"
assert_eq(add(1, 2,), 3)
assert_eq([1, 2,], [1, 2])
assert_eq({"a": 1,}, {"a": 1})
test "comments: -- to the end of the line, glued or not"
let x be 5 -- a note
print(x)--note
assert_eq(x, 5)
assert_eq(5 - -1, 6)
test "after a dot any word is a member name"
let tx be {"to": "0xabc", "type": 2}
assert_eq(tx.to, "0xabc")
assert_eq(tx.type, 2)Variables§
let name be value introduce una variable; set name to value la reasigna. set también escribe dentro de una lista o un map, por un camino que arranca en una variable: set m["a"]["b"] to v, set xs[0] to v, set s.items to append(s.items, x). x = 1 no es una asignación — el error dice que escribas let/set — y no hay +=.
Operadores§
- Aritmética:
+-///%*.
- / es siempre float (10 / 4 → 2.5, 10 / 2 → 5.0). - // es división entera hacia abajo, como en Python: exacta para enteros de cualquier tamaño (10 // 3 → 3, -7 // 2 → -4); con floats, exactamente la respuesta de CPython (7 // 0.1 → 69.0, porque 0.1 se guarda como algo más que 0.1). % redondea igual (-7 % 2 → 1). - liga más fuerte que el menos unario y agrupa a la derecha: -2 2 → -4, (-2) 2 → 4, 2 3 2 → 512, 2 -1 → 0.5.
- Comparación:
==!=<><=>=. Se encadenan como en Python:1 < x <= 10
significa 1 < x and x <= 10, evaluando cada operando una vez. Comparar un entero con un float es exacto (2 ** 53 + 1 == 9007199254740992.0 → false).
- Pertenencia:
x in coll/x not in coll— un item de lista, una clave de map, un pedazo
de texto, una subsecuencia de bytes. El texto necesita texto: 1 in "a1" es error (escribí text(1) in "a1").
- Lógica:
andornot.and/orcortocircuitan:"k" in m and m["k"] == 1es un
guard válido — el índice no corre cuando falta la clave, y or saltea el lado derecho cuando el izquierdo ya es verdadero. El resultado siempre es un bool, nunca el operando: x or "default" es true/false, no el default (usá when, o get(m, k, default)).
+une texto con un número o un bool ("n=" + 1→"n=1"); texto +nothing, una lista, un
map o bytes es error — convertí a propósito con text(x), o usá un template con backticks.
- El pipe
|>pasa un valor:x |> fesf(x), y un paso que es una llamada recibe el valor
como primer argumento: 5 |> add(1) es add(5, 1), xs |> sort_by(f) es sort_by(xs, f). Los pasos se encadenan (data |> clean |> validate), y una lambda sirve de paso: x |> ((v) => f(v, extra)).
Precedencia, de la más floja a la más fuerte: |> · or · and · not · comparaciones e in · + - · / // % · - unario · *. Así, 1 + 2 |> double es double(3).
Condicional inline§
when cond then a otherwise b es una expresión (usable dentro de let, un map o una llamada), distinta del when en bloque. Se encadena: when n > 0 then "pos" otherwise when n < 0 then "neg" otherwise "zero".
Strings§
"..."/'...'— una línea, con escapes:\n,\t,\\,\",\', y Unicode:\u00e9
(exactamente 4 dígitos hex) → é, \u{1F600} (1 a 6 dígitos hex) → 😀, y un par sustituto \uD83D\uDE00 es un solo carácter. Un salto de línea literal adentro es error. Lo que no es un escape completo queda como está ("C:\users"); \x no es un escape ("C:\build\x64" no cambia). Para una barra invertida + u literal, escribí \\u.
- `
...— multilínea + interpolación:Hola {name}evalúa{expr}`. Un hueco acepta
cualquier valor, como un f-string de Python (` xs={xs} n={nothing} → xs=[1, "a"] n=nothing). Los escapes también andan, pero solo el \u de 4 dígitos (las llaves de \u{…}` interpolan). Ideal para SQL/HTML.
- Una string con comillas nunca interpola:
"{name}"son esos seis caracteres. - El texto se indexa por carácter:
s[0], y los índices negativos cuentan desde el final:
"abc"[-1] → "c" (listas y bytes también). Un índice tiene que ser entero (xs[1.5] es error; xs[1.0] anda).
- Dentro de una lista o un map, el texto se muestra entre comillas:
print(["1", 1])→["1", 1].
Números§
- Los enteros no tienen límite de tamaño:
10 ** 18es exacto._va solo entre dígitos:
1_000_000, 1_000.5 (1__0 o 1_ es error).
- Los literales hex, octales y binarios son enteros:
0x1f18(7960),0o17(15),0b101(5), y
_ puede seguir al prefijo (0x_ff).
- Un exponente da un float, como en Python:
1e3→1000.0,1.5e-3→0.0015.1e18es
un float, no un entero exacto — escribí 10 ** 18.
Llamadas§
Los argumentos van entre paréntesis, separados por comas; una coma final vale (f(1, 2,)), como en listas y maps. Una llamada con un argumento de menos o de más es error — ver Tasks y lambdas. Después de un ., cualquier palabra es un nombre de miembro (tx.to, ev.type), incluso una keyword.
Comentarios§
-- un comentario va hasta el fin de línea, pegado al código o no (print(x)--note). Dos casos son error en vez de un comentario silencioso: -- pegado a un valor y seguido de un número o de ( (5--1 — escribí 5 - -1 o 5 -- nota), y x --1 dentro de paréntesis cuyo ) está en la misma línea (el comentario se lo comería). // es división entera, así que un // nota estilo C falla con la pista `comments in Synsema start with -- ; #` es un carácter inesperado.