Synsemadocsv0.6.xENES

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).
syntax.syn
-- 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§

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

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

de texto, una subsecuencia de bytes. El texto necesita texto: 1 in "a1" es error (escribí text(1) in "a1").

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

map o bytes es error — convertí a propósito con text(x), o usá un template con backticks.

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§

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

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.

"abc"[-1] → "c" (listas y bytes también). Un índice tiene que ser entero (xs[1.5] es error; xs[1.0] anda).

Números§

1_000_000, 1_000.5 (1__0 o 1_ es error).

_ puede seguir al prefijo (0x_ff).

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.