Referencia
Especificación del lenguaje Ridgun
Visión general
Ridgun es un lenguaje funcional y pensado para el paralelismo desde el principio, con operadores en lenguaje natural y tipado estático fuerte. Su sintaxis se inspira en Haskell y en los lenguajes de la familia ML, e introduce construcciones nuevas para el paralelismo y para definir operadores legibles por personas.
Características principales:
- Operadores en lenguaje natural — Los operadores se leen como frases en inglés
- Paralelismo implícito — La ejecución paralela es una primitiva sintáctica, no una llamada a una librería
- Tipado estático fuerte — Todos los tipos se declaran en las firmas
- Definiciones por patrones — Las funciones y los operadores se definen mediante ecuaciones
Comentarios
Los comentarios de una línea usan --:
-- This is a comment
Cadenas
Ridgun tiene dos tipos de literales de cadena, con distinto comportamiento de interpolación.
Cadenas interpoladas (comillas dobles)
Las cadenas entre comillas dobles admiten interpolación de código en línea con {expr}. La expresión entre llaves se evalúa y su resultado se inserta en la cadena.
name = "Ridgun"
"Hello, {name}!" -- "Hello, Ridgun!"
"Sum is {sum 3 4}" -- "Sum is 7"
"Nested: {factorial 5} done" -- "Nested: 120 done"
Para incluir una llave literal dentro de "...", escápala con una barra invertida (\{ y \}) o duplícala ({{ y }}):
"Shell expansion: \$\{VAR:-default\}" -- "Shell expansion: ${VAR:-default}"
"This is {{not interpolated}}" -- "This is {not interpolated}"
"Use \{ \} or {{}} for literal braces" -- "Use { } or {} for literal braces"
Cadenas en crudo (comillas simples)
Las cadenas entre comillas simples van en crudo: no se realiza ninguna interpolación. Todos los caracteres se tratan como texto literal, incluidas las llaves.
'Hello, {name}!' -- "Hello, {name}!"
'{this is just pure chars}' -- "{this is just pure chars}"
'No {interpolation} here' -- "No {interpolation} here"
Resumen
| Sintaxis | Interpolación | Caso de uso |
|---|---|---|
"..." | Sí — {expr} (escapa las llaves con \{/\} o {{/}}) | Cadenas dinámicas con expresiones incrustadas |
'...' | No — todo literal | Texto estático, plantillas, patrones de expresiones regulares |
Estructura de un programa
Todo el código ejecutable de Ridgun debe estar dentro de una función o de un operador. La función main es el punto de entrada obligatorio para ejecutar el programa.
fn :: main => ()
main = echo "Hello, Ridgun!"
Un archivo fuente de Ridgun solo puede contener:
- sentencias
import - definiciones
type - declaraciones y definiciones
fnyop - una función
maincomo punto de entrada
No se permiten expresiones sueltas en el nivel superior. Todo lo que se ejecuta debe llamarse desde main.
Uso de la línea de comandos
ridgunc <command> [options] <file.rg>
| Comando | Función |
|---|---|
run | Compila y ejecuta un archivo fuente |
check | Comprueba los tipos y analiza sin ejecutar |
compile | Genera un archivo de bytecode .rgc |
build | Crea un ejecutable nativo; una biblioteca con --lib o --so |
version | Muestra la versión del compilador |
help | Muestra la ayuda |
El formato, las pruebas y los proyectos corresponden a stable.
Funciones y operadores
Las funciones y los operadores infijos o en lenguaje natural se declaran con una firma de tipos que termina en => <return_type>, seguida de una o más ecuaciones (cases). Los prefijos iniciales fn :: y op :: son opcionales:
- Forma canónica sin prefijo (empieza en la columna 0):
list len [A] => Int
list len [] = 0
list len (_:xs) = 1 + (list len xs)
swap A with B => (B, A)
swap x with y = (y, x)
- Forma explícita con prefijo (
fn ::/op ::, empieza en la columna 0, con las cláusulas alineadas en la columna 6):
fn :: sum Int Int => Int
sum a b = a + b
fn :: format app err AppErr => String
format app err (IoError msg) = "IO:{msg}"
- Espacios naturales en
fn ::, funciones nativas y nombres de archivo (FR-COMP-005): - Las declaraciones y llamadas
fn ::admiten nombres de varias palabras separadas por espacios (fn :: format app err AppErr => String,fn :: project name => String), igual queop ::y las declaraciones sin prefijo. - Las funciones nativas del runtime y de tests se pueden llamar con espacios (
assert equals a b,str len s,read file p,cmm live blocks), y las formas con_siguen siendo compatibles. ridguncystableadmiten de forma nativa las referencias de primera clase a funciones de varias palabras (map double val xs,||map double val xs,filter is even xs,fold add pair 0 xs) y los nombres de archivo.rgcon espacios (src/test/ridgun/test core language.rg).
Variables de tipo polimórficas (A..Z) y palabras clave contextuales (type, with)
Para garantizar que no haya ninguna ambigüedad léxica entre las palabras de un operador y los parámetros de tipo, tanto en las firmas con prefijo (op ::) como en las firmas sin prefijo:
- Todo token de tipo en una firma empieza por una letra mayúscula (o por
[...]/(...)): - Las letras mayúsculas sueltas (
A..Z) denotan variables de tipo polimórficas (por ejemplo,[A],(A, B),(A => B),swap A with B => (B, A)). Las apariciones repetidas de una variable de tipo entre los parámetros se unifican en cada punto de llamada y se sustituyen en el tipo de retorno. - Los identificadores de varios caracteres con inicial mayúscula (
Int,String,Bool,RExpr,Tensor) denotan tipos concretos o definidos por el usuario. - Todo token en minúsculas (o símbolo de operador
+,-,*,/,%) en una firma de nivel superior forma parte del nombre de la función o del operador mixfix. - Palabras clave contextuales
typeywith:typesolo introduce una declaración de tipo en el nivel superior cuando le sigueUpperName =. En cualquier otro lugar (firmasfn ::yop ::, palabras de operador, patrones de parámetros, variables locales y nombres de campos de registro),typeywithson identificadores o palabras válidos, sin ambigüedad. - Una firma de nivel superior con un identificador inicial en minúsculas seguido solo de tipos antes de
=>es una función prefija (fn); cualquier firma con dos o más palabras en minúsculas o símbolos de operador intercalados con tipos antes de=>es un operador mixfix, infijo o postfijo (op).
La declaración y la definición están separadas:
- Declaración (
fn ::) — indica el nombre, los tipos de los parámetros y el tipo de retorno - Definición — vincula los nombres de los parámetros y aporta la implementación
Operadores
Los operadores son el rasgo distintivo de Ridgun. Se nombran con lenguaje natural y se definen con op ::. A diferencia de los operadores tradicionales (+, -), los operadores de Ridgun son expresiones de varias palabras que se leen como frases.
Sintaxis
op :: <natural language signature with types> -> <return_type>
<natural language pattern with params> = <body>
Ejemplo básico
-- Declaration: "add [Int] to [Int]" returns an Int
op :: add Int to Int => Int
add a to b = a + b
Su uso se leería así: add 3 to 5 → 8
Las palabras entre los parámetros con tipo (to, and, in, etc.) son palabras clave del operador: forman parte de la identidad del operador y deben aparecer en los puntos de llamada.
Operadores paralelos (||)
Los operadores pueden expresar la ejecución paralela como un concepto de primera clase mediante ||:
-- Run two configs in parallel with named DAG bindings (|| expr -> var)
op :: run Conf and Conf in parallel => Res, Res
run a and b in parallel = res_a, res_b
|| run my own process a -> res_a
|| run my own process b -> res_b
Elementos clave:
- Varios valores de retorno —
=> Res, Resdeclara dos valores de retorno - Bloque
|| expr -> var— Vincula subcálculos a nombres de resultado (wherese ha eliminado;||unifica todas las ramas paralelas) ->— Asigna el resultado de un subcálculo paralelo a una variable (o a una porción de tensor-> out[i, j])- Las ramas
||independientes se ejecutan de forma concurrente como un DAG paralelo, según las dependencias de datos
Bloque directo de tubería paralela (|| expr)
Cuando una expresión o función devuelve directamente una tupla de cálculos paralelos sin nombres de variable intermedios, las líneas || aparecen justo después de =:
-- Run two processes in parallel and return their results as a tuple
op :: another way to run two processes Conf Conf in parallel => Res, Res
another way to run two processes a b in parallel =
|| run my own process a
|| run my own process b
Cada línea || se ejecuta como una tarea paralela independiente. Los resultados se recogen en el orden de declaración.
Sobrecarga de operadores
Los operadores existentes (incluidos los nativos como +) se pueden sobrecargar para tipos nuevos:
-- Overload + for Complex numbers
op :: Complex + Complex => Complex
a + b = (real a) + (real b) + complex_i * ((imaginary a) + (imaginary b))
Precedencia
Los operadores simbólicos tienen la precedencia más alta: primero las matemáticas.
Cuando aparecen operadores simbólicos (como +, *) dentro del cuerpo de un operador en lenguaje natural, se vinculan con más fuerza que las palabras clave del operador que los rodea. Así, las expresiones aritméticas se evalúan como se espera, sin paréntesis de más.
| Precedencia | Categoría | Ejemplos |
|---|---|---|
| Máxima | Operadores simbólicos | *, /, +, -, % |
| Aplicación de función | f x | |
| Comparación | ==, !=, <, > | |
| Lógicos | and, or, xor, not | |
| Mínima | Operadores en lenguaje natural | add a to b, run a and b in parallel |
Encadenamiento de operadores y tuberías idiomáticas de izquierda a derecha (->) (FR-COMP-014)
Los operadores y las funciones que transforman valores o secuencias se pueden encadenar de izquierda a derecha, de modo que los datos fluyan con naturalidad del origen al destino, sin paréntesis anidados de derecha a izquierda (h (g (f x))) ni variables intermedias desechables (st1, st2, st3).
1. El estilo de tubería idiomático recomendado en Ridgun (2b)
Ridgun unifica en el mismo AST la invocación infija con el operando a la izquierda y las etapas explícitas de tubería con ->. Aunque las cuatro formas siguientes son válidas y equivalentes, la forma 2b (empezar de forma natural con un sujeto y una operación a la izquierda, encadenar las transformaciones siguientes con -> y vincular el resultado final con -> target) es el estilo idiomático recomendado en Ridgun:
-- Recommended idiomatic style (2b):
xs map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results
-- Equivalent unified forms (all normalize to the exact same AST):
xs -> map (x => x * 2) -> filter (x => x > 6) -> compact limit 14 -> results -- (2) explicit -> from subject
xs map (x => x * 2) filter (x => x > 6) compact limit 14 -> results -- (1) contiguous infix chain
map (x => x * 2) xs -> filter (x => x > 6) -> compact limit 14 -> results -- (3) prefix start + -> chain
Las tuberías de varias etapas también se pueden escribir en líneas de continuación sangradas que empiezan por ->:
raw_nums map (x => x * 3)
-> filter (x => x >= 12)
-> fold 0 (acc x => acc + x)
-> total_sum
2. Regla universal del primer argumento con -> y combinadores de colecciones
- Regla universal para funciones y operadores: cualquier valor que venga de la izquierda de
->(o de la izquierda de una llamada infijax op a b) entra como primer argumento de la función o del operador de la derecha: x -> f a b$\equiv$x f a b$\equiv$f x a b- Funciona igual para las declaraciones
fn ::y para las declaraciones mixfixop ::, sin duplicar firmas. - Si una función posterior necesita el valor de la tubería en un parámetro que no es el primero, un marcador explícito
_coloca el valor de la tubería en_(payload -> write_file output_path _). - Combinadores de colecciones (
map,||map,filter,||filter,fold): aceptan la colección en la posición prefija tradicional (map f xs,filter g xs,fold f init xs) o por la izquierda de una etapa infija o de tubería (xs map f,xs -> map f,xs filter g,xs -> filter g,xs fold init f,xs -> fold init f). - Etapas de agente (
-> send to,-> ask to): al pasar por-> send to ago-> ask to ag, el valor de la izquierda se envía como mensaje ([5, 10, 15] -> sum int list -> send to worker_ag). - Vinculación final a una variable (
-> var): cuando una sentencia de tubería en un bloque secuencial termina en-> var(dondevares el nombre de una variable local y no una función declarada), vinculavar(var = <pipeline_result>) durante el resto del bloque.
3. Máscaras de secuencia en tuberías (-> (+ _ _ +) ->)
Cuando una etapa devuelve una secuencia de varios valores o una tupla, una etapa de máscara de secuencia (-> (+ _ _ +) ->) filtra sus elementos (+ conserva una posición, _ la descarta) y desempaqueta la tupla seleccionada directamente en una función posterior de varios argumentos:
parse header packet "v0.9.9" -> (+ _ _ +) -> combine code msg -> status_line
4. Sentencias con efectos secundarios sin _ = y descarte explícito de unit (-> ())
En los bloques secuenciales, las sentencias con efectos secundarios (echo, write_file, write_bytes, shell_exec, log info, print_error_lines, sys_mkdir, etc.) se escriben directamente, sin la coletilla _ =:
verify strict if enabled strict_mode input_path
s_errs -> format_type_errors -> print_error_lines
echo "[ridgunc --strict] {input_path}: {err_c} error(s) found."
sys_exit 1
Cuando la expresión final de una función => () llama a una función auxiliar que devuelve un valor distinto de () (como shell_exec : String => Int), añade -> () para evaluar la expresión, descartar su valor de retorno y producir ():
fn :: exec built run Int => ()
exec built run 0 = shell_exec "./target/app" -> ()
exec built run _ = sys_exit 1
5. Encadenamiento de operadores de secuencia sin puntos (point-free)
Los operadores que devuelven secuencias también se pueden encadenar sin puntos:
op :: Int Int switch -> Int Int
a b switch = b a
op :: Int Int invariant -> Int Int
a b invariant = a b switch switch
Nota: en las cabeceras
fn ::/op ::se aceptan tanto->como=>como separadores del tipo de retorno. En las expresiones,->es el operador de tubería y vinculación de izquierda a derecha, mientras que=>introduce clausuras léxicas(x => expr).
Sistema de tipos
Firmas de tipos
Las firmas de tipos aparecen en las declaraciones, después de fn :: o op :: (o sin prefijo):
fn :: <name> <type1> <type2> ... => <return_type1> <return_type2> ...
op :: <words and types mixed> => <return_type1> <return_type2> ...
- Los parámetros son posicionales; los tipos aparecen donde se esperan los parámetros.
- Sintaxis unificada de tipos de tupla y de secuencia (
FR-COMP-008): - En Ridgun, una lista de argumentos
T1 T2 ... Tny una tupla(T1, T2, ..., Tn)representan el mismo tipo compuesto subyacente: una secuencia ordenada de tamaño fijo de valores con tipo (seq). - Por eso, los tipos separados por espacios y los tipos de tupla separados por comas son 100% intercambiables en los tipos de retorno, en los corchetes de lista y en las tuplas entre paréntesis:
- Tipos de retorno después de
=>/->:
fn :: parse header String => Int Int [String String] String
-- 100% equivalent to:
fn :: parse header String => (Int, Int, [(String, String)], String)
- Listas de tuplas (
[T1 T2 ... Tn]):
[String String] -- equivalent to [(String, String)]
[Int String Bool] -- equivalent to [(Int, String, Bool)]
- Tuplas entre paréntesis (
(T1 T2 ... Tn)):
fn :: fst (A B) => A
fst (a, _) = a
- Por qué esto no crea ninguna ambigüedad sintáctica: a diferencia de Haskell o ML (donde la yuxtaposición
Maybe Intdenota aplicación de tipos), los tipos parametrizados de Ridgun usan paréntesis explícitos (Tensor(Float, 3, 3)) y las listas usan corchetes ([T]). Además, todo token de tipo empieza por una letra mayúscula (A..Z,Int,String), por[o por(, mientras que las palabras de funciones y operadores van en minúsculas. Así,T1 T2nunca entra en conflicto con constructores de tipos ni con palabras clave de operadores. - La flecha
=>(o->) separa los tipos de entrada del tipo de retorno.
Tipos nativos
| Tipo | Descripción |
|---|---|
Int | Entero |
Real | Número real |
Bool | Booleano |
Char | Un único carácter Unicode (UTF-8) |
String | Cadena de texto (UTF-8) |
Complex | Número complejo |
Tensor | Tensor mutable de N dimensiones (elementos Real, CPU) |
Tensor_GPU | Tensor mutable de N dimensiones (elementos Real, GPU) |
[!NOTE] Todo el texto en Ridgun está codificado en UTF-8 por defecto.
Charrepresenta un único punto de código Unicode yStringes una secuencia deChar.
Literales de Bool
Bool tiene dos valores primitivos:
True -- boolean true
False -- boolean false
True y False son constructores: se pueden usar en expresiones, pasar como argumentos y usar en pattern matching:
fn :: is_valid Int => Bool
is_valid n =
| n > 0 = True
| otherwise = False
Operadores lógicos
Ridgun usa operadores lógicos con palabras en minúsculas en lugar de símbolos:
| Operador | Significado | Ejemplo |
|---|---|---|
and | Conjunción lógica | a and b |
or | Disyunción lógica | a or b |
xor | Disyunción exclusiva | a xor b |
not | Negación lógica | not a |
result = True and False -- False
result = True or False -- True
result = True xor True -- False
result = not False -- True
Son operadores infijos de palabra clave con la semántica booleana estándar.
Tipos de colección nativos
Listas
Las listas son un tipo recursivo nativo e inmutable con sintaxis propia. No hace falta definir ningún ADT a mano. Las listas son adecuadas para el procesamiento funcional de datos, el cálculo simbólico y los algoritmos recursivos. Para el cálculo numérico mutable, consulta Tensores más abajo.
Sintaxis de tipo: [type] — una lista de elementos del tipo indicado.
fn :: sum_list [Int] => Int
fn :: reverse [Int] => [Int]
Sintaxis de literal: [elem1, elem2, ...] — un literal de lista. [] es la lista vacía.
fn :: my_list => [Int]
my_list = [1, 2, 3, 4, 5]
Pattern matching con cons: (x:xs) — descompone una lista en su cabeza x y su cola xs. [] coincide con la lista vacía. Usa _ como comodín que coincide pero no vincula.
fn :: sum_list [Int] => Int
sum_list [] = 0
sum_list (x:xs) = x + sum_list xs
fn :: list_length [Int] => Int
list_length [] = 0
list_length (_:xs) = 1 + list_length xs
Concatenación: :: une dos listas (o secuencias).
fn :: append [Int] [Int] => [Int]
append a b = a :: b
Tensores
Los tensores son el tipo numérico nativo mutable de N dimensiones de Ridgun. Generalizan vectores, matrices y arrays de más dimensiones en un solo tipo. Los elementos son Real por defecto.
Sintaxis de literal: Tensor seguido de corchetes anidados. La profundidad de anidamiento determina la dimensionalidad.
v = Tensor [1, 2, 3] -- 1D (vector), shape: (3)
m = Tensor [[1, 2], [5, 3]] -- 2D (matrix), shape: (2, 2)
t = Tensor [[[1, 2], [3, 4]], -- 3D tensor, shape: (2, 2, 2)
[[5, 6], [7, 8]]]
Indexación: se admite tanto la indexación encadenada como la separada por comas.
val = m[0][1] -- chained indexing: row 0, col 1
val = m[0, 1] -- comma indexing: equivalent
row = m[0] -- slice: entire first row
Mutación (in-place): los tensores son mutables: los elementos y las porciones se pueden modificar in-place (al estilo de PyTorch y NumPy).
m[0, 1] = 5.0 -- set element
m[0] = Tensor [10, 20] -- set entire row
Contracción de tensores (@): el operador @ realiza la suma de Einstein. Los subíndices de cada operando definen la contracción: los índices compartidos por todos los operandos se suman; los índices únicos sobreviven en el resultado.
-- A has shape (s, d, f), B has shape (n, d, f)
-- Shared indices: d, f → contracted (summed)
-- Result T has shape (s, n)
T = A_sdf @ B_ndf
-- Equivalent to: np.einsum('sdf,ndf->sn', A, B)
La contracción funciona con varios tensores en una sola expresión:
-- A(a,b,c), B(a,b,c), C(a,b,c,d)
-- Shared: a, b, c → contracted. Result R has shape (d)
R = A_abc @ B_abc @ C_abcd
-- Equivalent to: np.einsum('abc,abc,abcd->d', A, B, C)
Operaciones de Hadamard (+, -, *, /): los operadores aritméticos con subíndices realizan operaciones elemento a elemento. Los índices que coinciden alinean las dimensiones; los que no coinciden se expanden por broadcasting.
-- Element-wise multiply: same shape, same indices
H = A_ij * B_ij -- shape (i, j)
-- Broadcasting: B has fewer dimensions
S = A_ij * B_j -- B broadcasts along i
-- Element-wise add
C = A_ij + B_ij
-- Element-wise divide
D = A_ij / B_j -- B broadcasts along i
Esto elimina la necesidad de unsqueeze, expand_dims o de reorganizar la forma a mano: los índices son las reglas de broadcasting.
Variante para GPU: Tensor_GPU coloca el tensor en la memoria de la GPU para acelerar el cálculo.
g = Tensor_GPU [[1, 2], [3, 4]]
result = g_ij @ g_jk -- GPU-accelerated matrix multiply
Secuencias (seq)
Las secuencias son el tipo compuesto central de Ridgun. Una seq es una colección ordenada de valores de tamaño fijo; en esencia, una tupla con tipo. Las listas de parámetros de funciones y operadores son secuencias de forma implícita.
-- These parameter types form implicit sequences:
fn :: sum Int Int => Int -- params: seq(Int, Int)
fn :: process Int Real => Bool -- params: seq(Int, Real)
Vinculación de secuencia ((name)) — poner el nombre de un parámetro entre paréntesis vincula todos los parámetros como una sola variable de secuencia:
fn :: first_of_seq Int Int => Int
first_of_seq (s) = s(+ _)
-- Equivalent without seq binding:
fn :: first_of Int Int => Int
first_of s _ => s
Selector de máscara (s(+ _)) — extrae elementos mediante una máscara, donde + selecciona una posición y _ la omite:
s(+ _) -- select 1st, skip 2nd
s(_ +) -- skip 1st, select 2nd
s(+ _ _ _) -- select 1st, skip 2nd-4th
s(_ _ _ +) -- skip 1st-3rd, select 4th
Selector de índices (s(0 3)) — extrae elementos por sus posiciones, contadas desde cero:
fn :: extreme_points Int Int Int Int => Int Int
extreme_points (s) = s(0 3)
Unión plana (:) — concatena dos secuencias en una sola secuencia plana:
seq1 = a, b -- seq(a, b)
seq2 = c, d -- seq(c, d)
seq1:seq2 -- seq(a, b, c, d)
Anidamiento (,) — compone secuencias como elementos distintos (secuencia de secuencias):
seq1, seq2 -- seq(seq(a, b), seq(c, d))
Tipos de datos algebraicos (ADT)
Ridgun usa tipos de datos algebraicos para los tipos definidos por el usuario. No hay clases, métodos ni bloques impl: las funciones y los operadores reciben los tipos como parámetros.
Tipos producto (registros) y sintaxis de actualización de registros (with)
Un tipo con campos con nombre:
type Conf = host: String, port: Int, tls: Bool
type Point = x: Real, y: Real
A los campos se accede por su nombre, como funciones (host c, port c), y se actualizan de forma funcional con with y dos puntos ::
fn :: get_host Conf => String
get_host c = host c
-- Functional record update (creates a new Conf with updated fields;
-- mutates in place via Arc::try_unwrap when the record is uniquely owned):
fn :: enable_tls Conf => Conf
enable_tls c = c with tls = True, port = 443
Tipos suma (variantes)
Un tipo con varios constructores, separados por |:
type Result = Ok Int | Err String
type Option = Some Int | None
type Color = Red | Green | Blue
ADT suma genéricos y registros producto genéricos (FR-COMP-013)
Los ADT suma y los registros producto definidos por el usuario pueden declarar uno o más parámetros de tipo en mayúsculas (A..Z o nombres de variable de tipo con inicial mayúscula) entre el nombre del tipo y =:
type Result T E = Ok T | Err E
type Option A = Some A | None
type Box A = Box A
type Page T = items: [T], total: Int
type Pair A B = fst: A, snd: B
- Las aplicaciones de tipos paramétricos (
Result Int String,Page User,[Box Int]) se pueden usar en firmasfn ::/op ::, en campos de registro anidados y en los datos de los constructores de ADT. - Los constructores (
Ok 42,Ok "hello",Page [1, 2] 2), el pattern matching, los accesores de campo (items p,total p) y las actualizaciones funcionales de registros (p with total = 10) instancian variables de tipo nuevas en cada punto de llamada. type Option A = Some A | Noneestá preregistrado como ADT polimórfico nativo cuando el usuario no lo ha declarado ya, de modo quetry receive from agdevuelveSome voNonesin necesidad de declaraciones repetitivas (FR-COMP-013).
Funciones anónimas y clausuras léxicas ((x => expr), (a b => expr) — FR-COMP-013)
Las funciones anónimas (lambdas) se escriben entre paréntesis, con los parámetros separados por espacios, seguidos de => y la expresión del cuerpo ((x => expr) o (a b => expr); por ejemplo, map (x => x + offset) xs, fold (acc x => acc + x) 0 xs), y pueden capturar variables locales o parámetros de su ámbito léxico envolvente:
fn :: add offset Int [Int] => [Int]
add offset k xs = map (x => x + k) xs
Cinco reglas canónicas rigen las funciones anónimas:
- Paréntesis obligatorios: toda lambda debe ir entre paréntesis
(... => ...). Escribirx => exprsin paréntesis (comof = x => x + 1) es un error de compilación (lambda expression must be enclosed in parentheses: '(x => expr)'). - Lambdas sin parámetros (
(=> expr)): Ridgun prohíbe()antes de=>en las firmas de funciones (fn :: main => ()). De forma coherente, una lambda sin parámetros se escribe(=> expr)(se invoca comothunk ()), y su tipo en las firmas es(=> Type). Escribir(() => expr)o(() => Type)es un error de compilación. - Varios parámetros frente a desestructuración de tupla (
(a b => expr)frente a((a b) => expr)):(a b => expr)es una lambda de 2 parámetros (comofold (acc x => acc + x) 0 xs), mientras que((a b) => expr)es una lambda de 1 parámetro que desestructura una tupla (map ((a b) => a + b) [(1, 2), (3, 4)]). Si los confundes, obtienes un error de tipos específico que explica la diferencia. - Cuerpos de lambda de varias líneas: cuando
=>está al final de la línea, el bloque sangrado de debajo forma un cuerpo de varias sentencias (o de guardas|) que se cierra con):
map (x =>
doubled = x * 2
doubled + offset
) [1, 2, 3]
- Tipos en mayúsculas frente a valores en minúsculas:
(Conn => Conn)es un tipo de función y(conn => conn)es un valor lambda. Escribir tipos en mayúsculas(Conn => Conn)en una expresión o parámetros en minúsculas(conn => conn)en una firmafn ::se rechaza con un error de compilación claro.
Las vinculaciones capturadas se elevan automáticamente a clausuras de aplicación parcial gestionadas por RMM1/RMM2.
Tipos recursivos
Los tipos pueden hacer referencia a sí mismos:
type List = Empty | Cons Int List
type Tree = Leaf Int | Node Tree Tree
Nombres de constructor compartidos entre tipos (FR-COMP-005)
Varias declaraciones type pueden reutilizar los mismos nombres de constructor de variante (como Error, Var, Lit, None), con la misma aridad o con otra distinta. El verificador de tipos distingue los patrones de constructor por el tipo de parámetro esperado en la columna, y resuelve las llamadas a constructores por la aridad y los tipos de los argumentos y por los tipos de retorno de los ADT de unión:
type ParseOut = Parsed Int | Error String
type DbOut = Rows Int | Error Int String
type MiniExpr = Var String | Lit Int
type MiniPat = Var String | Lit Int | Wild
Extensiones abiertas de tipos (expand type) y fusión automática de cláusulas fn :: (FR-COMP-005)
Los ADT suma y los registros producto existentes se pueden extender con expand type UpperName = ...:
- Extender un ADT suma: añade nuevos constructores de variante al tipo existente.
- Extender un registro producto: añade nuevos campos con nombre (
field: Type) y genera automáticamente sus funciones de acceso. - Variantes con nombre de tipo sin nombres de constructor redundantes: en las declaraciones suma
typeyexpand type, una variante cuyo nombre es un tipo nativo sin más (String,Int,Real/Float,Bool,Char), sin más argumentos de tipo detrás, representa directamente los valores de ese tipo (sin escribirInt IntniString String). Quien llama puede pasar valores directos (describe "hej",describe 42) o llamadas explícitas al constructor (describe (String "hej"),describe (Int 42)), y las ecuaciones pueden coincidir tanto con literales ("admin",0) como con patrones tipados ((String s),(Int n)). - Fusión automática de cláusulas
fn ::/op ::(sinexpand fn): volver a declarar unfn ::oop ::existente con cláusulas de patrón nuevas las fusiona automáticamente con la definición existente de la función, y coloca las cláusulas con patrones concretos de constructor o de literal antes de cualquier cláusula de respaldo que lo capture todo (_o variable):
type Something = String | Int | MyStruct Float Int
expand type Something = Bool
fn :: describe something Something => String
describe something "admin" = "special_admin"
describe something 0 = "zero_int"
describe something (String s) = "str:{s}"
describe something (Int n) = "int:{n}"
describe something (Bool b) = "bool:{b}"
describe something (MyStruct ratio cnt) = "struct:{ratio}:{cnt}"
type AppErr = IoError String | Timeout Int
fn :: format app err AppErr => String
format app err (IoError msg) = "IO:{msg}"
format app err (Timeout ms) = "TO:{ms}"
format app err _ = "UNKNOWN"
expand type AppErr = AuthError String | DbError Int String
fn :: format app err AppErr => String
format app err (AuthError user) = "AUTH:{user}"
format app err (DbError code db) = "DB:{code}:{db}"
type AppConfig = host: String, port: Int
expand type AppConfig = tls: Bool
Espacios de nombres de módulo, acceso cualificado y visibilidad estricta de importaciones por módulo (FR-COMP-012)
Cada archivo .rg o paquete .rglib define un espacio de nombres de módulo (derivado del nombre del archivo sin extensión o de una cabecera explícita module :: <name>) y puede importar otros módulos mediante import <mod> o import <mod> as <alias>.
- Módulos de varios archivos (
module :: <name>):
- Varios archivos que declaran la misma cabecera
module :: <name>(por ejemplo,orm.rgyorm_changeset.rg, que empiezan ambos pormodule :: orm) comparten un único espacio de nombres lógico de móduloormy ven directamente los tipos, constructores, campos de registro y funciones de los demás.
- Acceso cualificado (
mod.Type,mod.Constructor,mod.field,mod.fn):
- Los tipos exportados, los constructores de ADT suma, los accesores de campo de registros producto, las actualizaciones de registros (
with) y las funciones se pueden referenciar con un prefijo<mod_or_alias>.(http.Config,db.Status,http.Ok 200,http.port cfg,http.check status cfg).
- Resolución estricta de nombres sin cualificar por módulo, en 3 niveles:
- Cada módulo
M(incluido el módulo raíz de la aplicación) resuelve los tipos, constructores, campos de registro, funciones y llamadas de varias palabras sin cualificar estrictamente según el grafo de importaciones del propioM, en tres niveles de prioridad: - Nivel 1 — El propio módulo (
M): las declaraciones del propioM(en todos los archivos que compartenmodule :: M) siempre tienen la máxima prioridad. - Nivel 2 — Importaciones directas de
M: siMno define el nombre,ridguncbusca en los módulos queMimporta directamente (import D). Si una única importación directaDdefine el nombre, este se resuelve sin ambigüedad a la declaración deD, aunque otros módulos de la unidad de compilación (dependencias transitivas, módulos hermanos o la aplicación raíz) también definan ese nombre. Si dos o más importaciones directas deMdefinen el mismo nombre yMno lo define, las referencias sin cualificar se rechazan por ambiguas (__ambiguous__.<name>) y deben cualificarse (d1.<name>frente ad2.<name>). - Nivel 3 — Importaciones transitivas de
M: solo se consultan cuando ni el nivel 1 ni el nivel 2 definen el nombre. Por tanto, una importación directa enM(import json) oculta limpiamente una función auxiliar transitiva (codec.json_get_str, quesolsticeimporta internamente) sin obligar asolsticeni aMa renombrar o cualificar sus llamadas. - Aislamiento de los módulos hermanos no importados y de la aplicación raíz: las declaraciones definidas solo en módulos que
Mno ha importado (ni directa ni transitivamente), incluidas las librerías hermanas importadas por la aplicación raíz o las declaraciones de la propia aplicación raíz al compilar una librería importadaM, son invisibles para la búsqueda sin cualificar dentro deM(__unimported__.<name>).
Gramática unificada de = ((none), |, ||) y variables locales (FR-COMP-003)
Toda definición o vinculación en Ridgun, ya sea una cláusula de función (fn ::), una cláusula de operador (op ::), una variable local inmutable (x =), una variable local mutable (mut x =) o una rama de guarda (| cond = / | otherwise =), termina en = y acepta a la derecha exactamente las mismas cuatro formas de bloque o de expresión:
Prefijo después de = | Semántica de ejecución | Significado |
|---|---|---|
<expr> en línea | Una sola expresión (con líneas || expr -> var opcionales debajo) | Evalúa <expr> directamente (resolviendo las vinculaciones del DAG paralelo || que tenga debajo). |
(none) (líneas sangradas) | Secuencial (de arriba abajo) | Evalúa las sentencias en orden (mut, asignaciones, efectos secundarios); la última expresión es el resultado. |
| <cond> = ... | Condicional (guardas y árboles de decisión anidados) | Evalúa las condiciones booleanas de arriba abajo hasta la primera rama True (o | otherwise =). |
|| <expr> | Paralelo (DAG de green threads o tupla) | Evalúa las ramas || de forma concurrente (|| expr -> var para nodos de DAG con nombre, || expr para resultados paralelos en tupla). |
1. Las guardas (|) siempre van detrás de = (funciones, operadores, variables locales y árboles de decisión anidados)
Los bloques de guardas (| cond = ... y | otherwise = ...) siempre aparecen detrás de un =, lo que unifica las cláusulas de función, las cláusulas de operador, las definiciones de variables locales y los árboles condicionales anidados:
Cláusulas de función y de operador con guardas
fn :: abs Int => Int
abs n =
| n < 0 = 0 - n
| otherwise = n
op :: clamp Int between Int and Int => Int
clamp x between lo and hi =
| x < lo = lo
| x > hi = hi
| otherwise = x
Asignación de variables locales con guardas
Como | es válido después de cualquier =, las variables locales se pueden vincular de forma condicional sin necesitar una palabra clave if/else ni una función auxiliar:
discount =
| is_vip = 20
| total > 100 = 10
| otherwise = 0
Bloques secuenciales de varias líneas, árboles de decisión profundos y cierres anidados (|. / ||.)
Cada rama de guarda termina en =, así que su cuerpo puede ser a su vez un bloque secuencial ((none)), un bloque paralelo (||) u otro bloque de guardas | anidado, lo que permite árboles de decisión arbitrarios (de 3 o 4 niveles de profundidad, o más) dentro de | cond = y | otherwise =.
- Cerrar niveles anidados (
|.y||.): - En los bloques simples de 1 nivel o al final de una función o árbol, no hace falta ningún token de cierre.
- Cuando un subbloque anidado
|(o||) termina y la ejecución vuelve a un nivel exterior con más ramas o sentencias debajo,|.(para|) o||.(para||) cierra explícitamente ese nivel anidado. - Son válidas tanto la colocación en línea al final de la última línea (
| otherwise = val |., el estilo compacto canónico) como la colocación en la línea siguiente (|.):
fn :: evaluate_payment Account Customer Payment => String
evaluate_payment account customer payment =
| is_locked account = "REJECTED_ACCOUNT_LOCKED"
| amount payment <= 0 = "ERROR_INVALID_AMOUNT"
| otherwise =
| tier customer == "ENTERPRISE" =
| amount payment > 50000 =
| has_dual_signature payment = "APPROVED_ENTERPRISE_DUAL_SIG"
| tenure customer > 10 = "RISK_COMMITTEE_REVIEW"
| otherwise = "REJECTED_ENTERPRISE_LIMIT" |.
| country payment != "US" =
| is_whitelisted (country payment) = "APPROVED_ENTERPRISE_INTL"
| otherwise = "HELD_COMPLIANCE_INTL" |.
| otherwise = "APPROVED_ENTERPRISE_STANDARD" |.
| tier customer == "VIP" =
| amount payment > 10000 =
| has_2fa payment = "APPROVED_VIP_2FA"
| otherwise = "REQUEST_VIP_SMS" |.
| otherwise = "APPROVED_VIP_DIRECT" |.
| otherwise =
| amount payment > 1000 =
| has_2fa payment and risk payment < 30 = "APPROVED_RETAIL_2FA"
| otherwise = "REJECTED_RETAIL_RISK" |.
| otherwise = "APPROVED_RETAIL_LOW_AMOUNT"
fn :: charge_order Payment => Int
charge_order payment =
|| fetch_base_fee payment -> base_fee
|| compute_risk_score payment -> risk_score ||.
surcharge =
| risk_score > 80 = 50
| otherwise = 0 |.
base_fee + surcharge
2. Inmutable por defecto (x = ...) frente a variables mutables explícitas (mut x = ...)
Dentro de los bloques secuenciales, las variables locales siguen la opción 1:
- Vinculación inmutable (
x = <rhs>): - Declara una nueva variable local inmutable
x. - Intentar reasignar
x = ...más adelante en el mismo ámbito sin una declaración previamut x = ...es un error de compilación. - Declaración mutable (
mut x = <rhs>) y reasignación (x = <new_rhs>): mut x = <rhs>declaraxcomo mutable en el ámbito actual y la inicializa.- Las reasignaciones posteriores a
xen el mismo ámbito se escriben simplemente comox = <new_rhs>(sin repetirmut). - Con
RMM2, reasignarx = <new_rhs>incrementa la estructura nueva (+1) antes de decrementar la antigua (-1, en cascada a sus hijos si su contador llega a0).
fn :: accumulate Int => Int
accumulate n =
factor = 2
mut acc = n * factor
acc = acc + 10
acc =
| acc > 100 = 100
| otherwise = acc
acc
3. Pattern matching sobre literales y constructores de ADT
fn :: factorial Int => Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
fn :: unwrap Result => Int
unwrap (Ok n) = n
unwrap (Err msg) = 0
fn :: length List => Int
length Empty = 0
length (Cons _ rest) = 1 + length rest
fn :: sum_tree Tree => Int
sum_tree (Leaf n) = n
sum_tree (Node left right) = sum_tree left + sum_tree right
Primitivas de paralelismo (||)
Ridgun unifica toda la ejecución paralela bajo || (where se ha eliminado):
Vinculaciones con nombre en un DAG paralelo (|| expr -> var)
fn :: compute_total Int Int => Int
compute_total a b = x + y + z
|| heavy_calc a -> x
|| heavy_calc b -> y
|| combine x y -> z
- Todas las líneas
|| expr -> vardebajo de una expresión forman un grafo acíclico dirigido (DAG) paralelo. - El compilador analiza las dependencias de variables entre las líneas
||(xeyno tienen dependencias y se ejecutan en paralelo en la oleada 1;zdepende dexy deyy se ejecuta en la oleada 2, antes de evaluarx + y + z). - También se admiten escrituras paralelas en porciones de tensor (
|| expr -> out[i, j]).
Bloques directos de tubería paralela (|| expr)
op :: run Conf and Conf in parallel => Res, Res
run a and b in parallel =
|| run_process a
|| run_process b
Cada línea || se ejecuta como un green thread paralelo independiente, y los resultados se devuelven como una tupla en el orden de declaración (o se unen cuando el bloque está dentro de un bloque secuencial).
Canales con tipo
Para flujos de datos y topologías productor-consumidor entre green threads, Ridgun ofrece canales MPMC con tipo (Channel):
| Función u operador nativo | Firma | Propósito |
|---|---|---|
new_channel | () => Channel | Crea un canal con tipo sin límite |
new_bounded_channel | Int => Channel | Crea un canal limitado con capacidad de contrapresión |
send_through | T Channel => () | Envía un valor a un canal (se bloquea si está limitado y lleno) |
receive_from | Channel => T | Recibe el siguiente valor de un canal |
try_receive_from | Channel => Option | Sondeo no bloqueante que devuelve Some v o None |
Primitivas de E/S
Ridgun ofrece funciones nativas para la salida por terminal.
echo
Imprime en la terminal un número arbitrario de cadenas, separadas por espacios, seguidas de un salto de línea.
fn :: echo String... => ()
El sufijo ... en el último tipo de parámetro lo marca como variádico: acepta uno o más argumentos de ese tipo.
echo "hello" -- prints: hello
echo "hello" "world" "!" -- prints: hello world !
echo "result: {result}" -- prints: result: 42
out
Un operador que escribe una cadena directamente en la terminal y la devuelve (lo que permite encadenar).
op :: String out => String
"hello" out -- prints: hello
"step 1 done" out -- prints: step 1 done
-- Chaining: out returns the string, so it can be passed onward
fn :: log_and_process String => String
log_and_process s = process (s out) and report
Resumen
| Función | Tipo | Propósito |
|---|---|---|
echo | String... => () | Imprime cadenas con espacios y un salto de línea |
out | String => String | Imprime una cadena y la devuelve para encadenar |
Librería estándar
Las librerías se importan con import:
import test
Tests unitarios (lib/test.rg)
Una librería de tests sencilla que usa echo para la salida por terminal.
Aserciones:
| Operador | Propósito |
|---|---|
assert Int equals Int | Igualdad de enteros |
assert Real equals Real | Igualdad de reales |
assert String equals String | Igualdad de cadenas |
assert Bool is true | El booleano es verdadero |
assert Bool is false | El booleano es falso |
Estructura de un test:
import test
fn :: main => ()
main =
describe "Arithmetic" does
test "sum adds correctly" does
assert (sum 3 4) equals 7
test "factorial base case" does
assert (factorial 0) equals 1
Salida por terminal:
═══ Arithmetic ═══
● sum adds correctly
✓ PASS
● factorial base case
✓ PASS
Resumen de elementos sintácticos
| Elemento | Sintaxis | Propósito |
|---|---|---|
| Comentario | -- text | Comentario de una línea |
| Declaración de función | fn :: name types => return | Declara la firma de tipos de una función |
| Definición de función | name params = body | Define la implementación de una función |
| Declaración de operador | op :: words types words => return | Declara un operador en lenguaje natural |
| Definición de operador | words params words = body | Define la implementación de un operador |
| Varios retornos | => type, type | Declara varios valores de retorno |
| Parámetros variádicos | Type... | Acepta uno o más argumentos de un tipo |
| Literal de tensor | Tensor [[1, 2], [3, 4]] | Crea un tensor de N dimensiones |
| Indexación de tensor | t[0, 1] o t[0][1] | Accede a los elementos de un tensor |
| Mutación de tensor | t[0, 1] = 5.0 | Modifica elementos de un tensor in-place |
| Contracción de Einstein | A_ij @ B_jk | Contracción de tensores sobre índices compartidos |
| Operación de Hadamard | A_ij * B_ij | Aritmética elemento a elemento con alineación de índices |
echo | echo "a" "b" "c" | Imprime cadenas en la terminal |
out | "text" out | Escribe una cadena en la terminal |
| Vinculación local inmutable | x = expr | Vincula una variable local inmutable (= admite (none), |, ||) |
| Vinculación local mutable | mut x = expr | Declara una variable local mutable (se reasigna con x = new_expr) |
| Bloque de guardas | | cond = expr | Rama de guarda condicional después de = (admite | anidado) |
| Bloque de DAG paralelo | || expr -> var | Subcálculos paralelos con resultados de DAG con nombre |
| Bloque de tubería paralela | || expr | Ejecución paralela que devuelve una tupla |
| Sobrecarga de operadores | op :: type + type => type | Sobrecarga operadores existentes para tipos nuevos |
| Literales de Bool | True, False | Valores booleanos primitivos |
| Operadores lógicos | and, or, xor, not | Operadores lógicos con palabras en minúsculas |
| Punto de entrada | fn :: main => () | Punto de entrada obligatorio del programa |
Formato canónico del código fuente (stable format y herramientas de editor)
Solo estética, no es un requisito del compilador (
FR-TOOL-001): el lexer y el parser de Ridgun no son sensibles a la alineación horizontal por columnas. Pararidgunc, los archivos fuente.rgsin alinear y alineados por columnas son 100% equivalentes. Las reglas siguientes definen la maquetación estética canónica que aplicanstable formaty elridgun-modede GNU Emacs (C-c C-f).
- Grupo de alineación por firma (
fn ::/op ::):
- Cada declaración
fn ::uop ::inicia un grupo de alineación que abarca su firma, sus cláusulas de pattern matching y sus vinculaciones|| expr -> var, y que termina en la siguiente declaración de nivel superior (fn,op,type,import,global,context,shared,hardware,@).
- Alineación por columnas de los tokens del lado izquierdo ($C_0 = 6$):
fn ::yop ::ocupan 6 caracteres, así que el token $0$ del lado izquierdo de la firma empieza en la columna $C_0 = 6$.- Cada hueco de token $i \in [0, N-1]$ antes de
=>/->(tratando"...",'...',[...]y(...)como tokens únicos) tiene un ancho $W_i = \max(\text{len}(\text{sig_tok}i), \max{\text{clause} : \text{len}(\text{clause_tok}_i) \le 18} \text{len}(\text{clause_tok}_i))$ y empieza en la columna $C_i = C_{i-1} + W_{i-1} + 1$. Los patrones de cláusula de más de18caracteres se tratan como valores atípicos: no amplían $W_i$ para el resto del grupo y simplemente se desbordan en su propia línea con 1 espacio. - Las cláusulas de definición correspondientes se sangran hasta la columna $6$ ($C_0$), de modo que el nombre de la función o las palabras del operador y cada patrón de argumento queden alineados verticalmente debajo de sus huecos de la firma.
- Alineación vertical de
=>,=y->($C_{\text{arrow}}$):
- Sean $C_{\text{arrow}} = \max\big(C_{N-1} + W_{N-1} + 1,; \max_{\text{pipe_bind}}(8 + 3 + \text{len}(\text{pipe_lhs}) + 1)\big)$ y $C_{\text{ret}} = C_{\text{arrow}} + \text{len}(\text{arrow}) + 1$.
- En la firma,
=>se coloca en $C_{\text{arrow}}$ y el tipo de retorno en $C_{\text{ret}}$. - En las cláusulas de una línea (
<lhs> = <rhs>), en las cláusulas de bloque de varias líneas (<lhs> =) y en las cláusulas encabezadas por guardas (<lhs> =seguido de| cond = ...),=se coloca en $C_{\text{arrow}}$ (alineado justo debajo de=>); en las cláusulas de una línea,<rhs>empieza en $C_{\text{ret}}$. - En las vinculaciones de DAG paralelo (
|| <lhs> -> <rhs>),||empieza en la columna $8$ (6 + 2),->se coloca en $C_{\text{arrow}}$ y<rhs>empieza en $C_{\text{ret}}$. - Alternativa compacta (
> 4parámetros o> 80columnas): si una declaración tiene más de4huecos de parámetro o su línea de firma alineada superaría los80caracteres, se desactiva el relleno horizontal por columnas para ese grupo y los tokens se separan con un solo espacio (" "), manteniendo el sangrado de las cláusulas en la columna 6.
- Sangrado estructural (desplazamiento de 2 espacios):
- Declaraciones de nivel superior (
fn,op,type,import,global,context,shared,hardware,@): columna0. - Cláusulas de función u operador bajo una firma: columna
6. - Guardas (
| cond = expr) y tuberías paralelas (|| expr/|| expr -> var):prev_indent + 2(columna8dentro de una cláusula, oindent + 2dentro de una vinculación local o de una guarda anidada). - Líneas del cuerpo de un bloque secuencial después de una línea que termina en
=:prev_indent + 2.
Operaciones de colección de orden superior, secuenciales y paralelas (FR-COMP-001, FR-COMP-014)
Ridgun ofrece los combinadores de colecciones secuenciales habituales (map, filter, fold) junto con combinadores paralelos multicore explícitos con el prefijo || (||map, ||filter). Los cinco combinadores admiten tanto la sintaxis idiomática de tubería o infija de izquierda a derecha (xs map f -> filter g -> fold 0 h -> res) como la sintaxis prefija tradicional (map f xs):
| Nativa | Formas prefija y de tubería | Modelo de ejecución |
|---|---|---|
map | xs map f / xs -> map f / map f xs | Secuencial: evalúa f x estrictamente de izquierda a derecha en el hilo que hace la llamada. |
| ` | map` | |
filter | xs filter p / xs -> filter p / filter p xs | Secuencial: evalúa el predicado p x estrictamente de izquierda a derecha en el hilo que hace la llamada. |
| ` | filter` | |
fold | xs fold init f / xs -> fold init f / fold f init xs | Plegado secuencial por la izquierda: acumula acc = f acc x de izquierda a derecha a partir de init. |
E/S de archivos sin alteraciones, flujos de bytes y funciones POSIX nativas (FR-VM-001, FR-VM-002)
- E/S de archivos y flujos de bytes sin alteraciones (
FR-VM-001):
read_file : String => Stringlee el contenido de un archivo tal cual (conserva\r\ny la presencia o ausencia exacta del salto de línea final, y nunca ejecuta el preprocesador deimportde.rgdel compilador sobre archivos de datos).load_rg_source : String => Stringla usan exclusivamente las herramientas del compilador al cargar archivos fuente.rgcon inserción deimport.read_bytes : String => [Int]ywrite_bytes : String [Int] => ()leen y escriben flujos de bytes binarios en crudo (0..255, incluidos0x00NUL y0x80..0xFF).
- Funciones nativas POSIX de sistema de archivos, procesos y seguridad (
FR-VM-002):
- Sistema de archivos:
list_dir,file_lstat,sha256_file,sys_mkdir,sys_chmod. - Procesos y entorno:
sys_exec,sys_chdir,sys_getcwd,sys_getenv,sys_setenv,sys_exit. - Seguridad y separación de privilegios:
sys_getuid,sys_getgid,sys_username,sys_user_groups,sys_lookup_uid_gid,sys_setresgid,sys_setgroups,sys_setresuid,sys_execve,sys_read_pass.
- E/S PAL multiplataforma, sondeo, primitivas de procesos, ADT de destino y cuotas declarativas
with timeout(FR-COMP-007):
- ADT de plataforma de destino predeclarados:
type SysOs = Linux | MacOS | Windows(sys os => SysOs, abierto a extensiones conexpand type SysOs = ...)type SysArch = X86_64 | Arm64(sys arch => SysArch, abierto a extensiones conexpand type SysArch = ...)- Literales con unidad de duración: los literales enteros aceptan los sufijos
_ms(* 1),_s(* 1000) y_min(* 60000) (por ejemplo,500_ms,5_s,2_min), que complementan a los sufijos de unidad de bytes (_KB,_MB,_GB). - Capa 1 — Primitivas PAL multiplataforma para las librerías de sistema
.rglib(posix.rglib/io.rglib): sys os => SysOsysys arch => SysArch(ADT suma del sistema operativo y la arquitectura de destino en tiempo de compilación)sys open mode String String => Int(path, modo portable"r","w","a","rw","w+","a+"-> descriptor o handle>= 0o-errno, traducido para cada sistema operativo de destino)sys open String Int Int => Int(path,flagsnormalizados de la PAL de Ridgun (0= lectura,1= escritura,2= rdwr,64= crear,512= truncar,1024= añadir al final,2048= no bloqueante),mode->fd >= 0o-errno)sys close Int => Int(fd->0o-errno)sys read fd Int Int => String(fd,max_bytes-> cadena leída defd)sys write fd Int String => Int(fd,data-> bytes escritos o-errno)sys poll Int Int Int => Int(fd,eventsnormalizados de la PAL de Ridgun (1=POLLIN/lectura,4=POLLOUT/escritura),timeout_ms->> 0con los revents cuando está listo,0si se agota el tiempo,< 0con-errno; usasys remaining mscuandotimeout_ms < 0dentro dewith timeout)sys terminate Int Bool => Int(pid,force-> terminación de procesos portable, ordenada o forzada) ysys kill Int Int => Int(pid, señal normalizada ->0o-errno)sys call Int Int Int Int Int Int Int => Int(nr,a1..a6-> vía de escape de bajo nivel a las syscalls según la ABI del sistema operativo de destino)sys time ms => Intysys sleep ms Int => ()(reloj monotónico en milisegundos y espera)sys deadline ms => Intysys remaining ms => Int(plazo léxico activo y milisegundos restantes, o-1cuando no hay límite)- Capa 2 — Cuota de ejecución declarativa
with timeout <duration> do: - Igual que
with region 64_KB doestablece una cuota léxica de bytes,with timeout 500_ms do(o la forma combinadawith region 64_KB, timeout 500_ms do) establece un plazo léxico de ejecución (sys_time_ms + duration_ms) en el contexto del hilo actual y restaura el plazo anterior al salir del bloque:
with timeout 500_ms do
rem = sys remaining ms
rc = sys poll fd 1 rem
rc
- Consulta
docs/lang/pal-and-system-libraries.mdpara ver la especificación completa de la arquitectura de la PAL y de las librerías de sistema (.rglib).
- Compilación de librerías reutilizables (
.rgliby.soELF64) ystableextensible (FR-COMP-006,FR-TOOL-003):
ridgunc build --lib <file.rg> -o <out.rglib>yridgunc build --so <file.rg> -o <lib.so>(ostable built lib/stable new --lib <name>) compilan módulos sin necesidad defn :: main => ()y conservan todas las funciones, operadores y tipos exportados.stabledescubre comandos personalizadosfn :: task <words...> => Stringy extensionesfn :: plugins => [String](plugins/*.rg,.stable/plugins/*.rg), que se listan constable tasksystable plugins.
Gestión de memoria de Ridgun (RMM): RMM1 (CMM) y RMM2 (FR-COMP-002)
Ridgun gestiona la memoria del heap (String, [a], (a, b), ADT, Tensor) de forma determinista, sin un recolector de basura de rastreo, mediante Ridgun Memory Management (RMM):
- Política
RMM1— Gestión de memoria compilada (CMM lineal + condicional):
- Se aplica cuando el flujo de control y las funciones llamadas se conocen estáticamente en tiempo de compilación sobre vinculaciones no anidadas:
- CMM lineal (
rg_cmm_move/rg_cmm_drop): el compilador determina en tiempo de compilación la sentencia exacta del último uso de cada variable local, mueve las variables devueltas (rg_cmm_move(&mut lv_x)) y emite una liberación incondicional (rg_cmm_drop(&mut lv_x)) justo después del último uso de una variable cuando no queda vivo ningún posible alias. - CMM condicional (
rg_cmm_drop_unless/ptr(a) != ptr(c)): cuando una llamada con ramificación estática (c = choose_list cond a b) devuelve una de varias entradas candidatas y una o más candidatas siguen vivas junto ac, el compilador construye la tabla de posibles alias de ancestros (RCmmAlias) y emite una guarda de identidad de punteros de 1 ciclo en la sentencia estática del último uso de cada variable:
a = make_a () -- Allocates M1
b = make_b () -- Allocates M2
c = choose_list cond a b -- b dies here while c is live -> if ptr(b) != ptr(c) { free(b) }
print_list a -- a dies here while c is live -> if ptr(a) != ptr(c) { free(a) }
consume_list c -- c dies here (no live peers) -> free(c) (unconditional)
- Política
RMM2— Conteo por asignación y cascada estructural (política activa):
- Unifica la gestión de memoria entre funciones, retornos con ramificación (
choose_list), contenedores anidados (xs = [a, b, ..., z],xs[3] = a), variables locales mutables (mut x = ...) y la ejecución multihilo: - Escalares primitivos (
Int,Real,Bool,()): nunca se cuentan; se copian por valor en la pila o en los registros. - Argumentos de entrada de la función (
pats): nunca se tocan (+0al entrar,-0al salir). - Variables locales internas (
x = .../mut x = ...): reciben+1en la asignación (x = expr,xs[3] = a). En la reasignación mutable (x = new_expr),new_exprrecibe+1antes de que la antiguaxreciba-1. - Fin de la función o salida del ámbito:
- Las variables locales internas no devueltas reciben primero
-1: sirc == 0, se libera el bloque padre y se propaga-1en cascada, uno por uno, a cada estructura hija directa (liberando de forma recursiva cualquier hija cuyorcllegue a0). - Una variable local interna devuelta (
return a) recibe-1(o se mueve medianterg_cmm_move), y aunque surcsea0en tránsito, se libra, de modo que la asignación de quien llama (c = make_pair ()) suma+1(0 + 1 = 1). Cuando se devuelve un argumento de entrada (choose_list cond a b), no se ha tocado dentro de la función (rc = 1), así quec = choose_list cond a ben quien llama suma+1(1 + 1 = 2, compartido entreayc).
- Política
RMM3(reservada):
- Reservada para una futura política de optimización estática agresiva.
- Consulta
docs/etc/cmm_architecture.md(en el repositorio de Ridgun) para ver la especificación completa deRMM1yRMM2.