22 O Libro de Ridgun
A lib template — Modelos Mustache Seguros con Auto-Escape HTML e LaTeX
22.1. Por que existe a lib template? (O problema real e a filosofía Ridgun)
«Por que necesito unha lib
templateescrita dende cero en Ridgun puro en vez de enlazar unha librería de C de toda a vida?»
Esa é a primeira pregunta que se fai calquera enxeñeiro de sistemas ao chegar a template. E a resposta resume toda a filosofía de ridgun-libs:
Sexamos sinceros: montar páxinas HTML ou informes LaTeX concatenando cadeas á man é a receita perfecta para unha vulnerabilidade XSS (<script>) ou para que un simple símbolo % ou _ rompa a compilación do teu PDF.
A lib template ofrece un motor Mustache sen lóxica arbitraria con auto-escape contextual (TmplPlain, TmplHtml, TmplLatex), substitución crúa explícita ({{{raw}}} / {{&raw}}), comentarios ({{! ... }}), seccións condicionais ({{#sec}}...{{/sec}}) e invertidas ({{^sec}}...{{/sec}}).
Nun ecosistema tradicional, resolver as tarefas do dominio TMPL implica arrastrar dependencias externas en C, loitar con punteiros crus, conversións opacas de tipos, excepcións non verificadas ou pausas impredicibles do colector de lixo. En Ridgun, a lib template (src/main/ridgun/lib/template.rg) aborda o problema con catro principios innegociables:
- Declara
import template(ouimport template as alias) ao inicio do ficheiro: cada lib define o seu espazo de nomesmodule :: templatee en modo--strictas funcións multi-palabra invócanse sempre con espazos naturais. - Encadea as túas tubaxes de esquerda a dereita con
->: escribeentrada paso un -> paso dous -> resultado, usa máscaras(+ _ _ +)para desestruturar secuencias e omite_ =en sentenzas secuenciais (ou descarta con-> ()). - Recursión de cola con TCO (Tail-Call Optimization): todos os percorridos internos de
templatecompílanse a bucles nativos en espazo de pila $O(1)$. - Memoria determinista (
RMM1/RMM2): cada estrutura intermedia libérase no instante exacto en que remata a súa última lectura, sen Garbage Collector.
22.2. Artefactos nativos, importación e catálogo completo da API de template
En ridgun-libs, template é unha lib de primeira clase situada en src/main/ridgun/lib/template.rg (dominio TMPL). Ao compilar con stable bundle, empaquétase en tres formatos nativos:
| Artefacto | Ruta | Propósito |
|---|---|---|
| Bundle Ridgun | target/bundles/template.rglib | Distribución e importación directa con import template |
| Ficheiro compilado | target/template.rgc | Artefacto intermedio verificado por ridgunc |
| Obxecto compartido ELF64 | target/libtemplate.so | Biblioteca dinámica nativa sen dependencias de C nin libc |
Cada lib declara o seu propio espazo de nomes con
module :: template: abonda con escribirimport template(ouimport template as alias) na cabeceira e chamar ás súas funcións con espazos naturais (mina funcion x) e tubaxes->(x mina funcion -> resultado).
22.2.1. Resumo de familias funcionais de template
| Sinatura / Función / Tipo | Que fai e cando usala |
|---|---|
tmpl render / tmpl render html / tmpl render latex | Renderiza modelos Mustache con auto-escape plano, HTML ou LaTeX |
tmpl escape html / tmpl escape latex | Escapa & < > " ' para HTML ou \ { } & % $ # _ ~ ^ para LaTeX |
tmpl extract vars / tmpl has unclosed tags | Extrae os nomes de variables requiridos e valida que as etiquetas estean pechadas |
22.2.2. Catálogo exhaustivo de tipos e sinaturas en src/main/ridgun/lib/template.rg
A continuación tes o inventario completo de tipos alxébricos (type), operadores (op ::) e sinaturas de funcións (fn ::) definidos en src/main/ridgun/lib/template.rg (1 tipos, 0 operadores e 35 funcións):
Tipos alxébricos e rexistros (type) de template:
type TmplEscape = TmplEscapeNone | TmplEscapeHtml | TmplEscapeLatex
| # | Función / Sinatura (fn ::) | Tipo de Retorno |
|---|---|---|
1 | fn :: tm lbrace | String |
2 | fn :: tm rbrace | String |
3 | fn :: tm braces | String |
4 | fn :: tm is space Int | Bool |
5 | fn :: tm trim start String Int Int | Int |
6 | fn :: tm trim end String Int Int | Int |
7 | fn :: tm trim String | String |
8 | fn :: tm lookup [(String, String)] String | String |
9 | fn :: tm val truthy String | Bool |
10 | fn :: tm is truthy [(String, String)] String | Bool |
11 | fn :: tm escape html char Int | String |
12 | fn :: tm escape html loop String Int Int String | String |
13 | fn :: tm escape latex char Int | String |
14 | fn :: tm escape latex loop String Int Int String | String |
15 | fn :: tm escape String TmplEscape | String |
16 | fn :: tm find close2 String Int Int | Int |
17 | fn :: tm find close3 String Int Int | Int |
18 | fn :: tm find section close String Int Int String Int | (Int, Int) |
19 | fn :: tm check section tag String Int Int String Int Int | (Int, Int) |
20 | fn :: tm check close tag String Int Int String Int Int | (Int, Int) |
21 | fn :: tm close tag match String Int Int String Int Int | (Int, Int) |
22 | fn :: tm check open tag String Int Int String Int Int | (Int, Int) |
23 | fn :: tm scan String Int Int [(String, String)] TmplEscape String | String |
24 | fn :: tm dispatch tag String Int Int [(String, String)] TmplEscape String | String |
25 | fn :: tm tag raw3 String Int Int [(String, String)] TmplEscape String Int | String |
26 | fn :: tm tag raw amp String Int Int [(String, String)] TmplEscape String Int | String |
27 | fn :: tm tag comment String Int Int [(String, String)] TmplEscape String Int | String |
28 | fn :: tm section active Bool Bool | Bool |
29 | fn :: tm render section body String Int (Int, Int) Int [(String, String)] TmplEscape String Bool | String |
30 | fn :: tm tag section String Int Int [(String, String)] TmplEscape String Int Bool | String |
31 | fn :: tm tag var String Int Int [(String, String)] TmplEscape String Int | String |
32 | fn :: tmpl render with escape String [(String, String)] TmplEscape | String |
33 | fn :: tmpl render String [(String, String)] | String |
34 | fn :: tmpl render html String [(String, String)] | String |
35 | fn :: tmpl render latex String [(String, String)] | String |
22.3. Explorando template a fondo: Exemplos paso a paso de cada funcionalidade
Non abonda con ver a táboa de sinaturas: imos explorar cada unha das funcionalidades da lib template mediante exemplos progresivos e executables en Ridgun. Comezaremos polas dúas receitas tabulares de uso cotián e despois percorreremos en detalle todos os escenarios funcionais, transformacións e casos límite verificados en src/test/ridgun/test template.rg.
22.3.1. Exemplo 1: Fluxo principal en estilo tabular con template
Observa como en Ridgun os signos = e os pasos de transformación con template quedan aliñados coma unha táboa limpa, sen variables mutables innecesarias nin efectos laterais ocultos:
import template
fn :: renderizar perfil html String String => String
renderizar perfil html usuario es_admin =
"<h1>Hola {{user}}</h1>{{#admin}}<span class='badge'>Admin</span>{{/admin}}" -> plantilla
[("user", usuario), ("admin", es_admin)] -> vars
tmpl render html plantilla vars
22.3.2. Exemplo 2: Composición segura e manexo de casos de bordo
Toda función da lib template está deseñada para compoñerse con gardas booleanas | e co resto das 32 libs de ridgun-libs:
import template
fn :: generar linea latex String => String
generar linea latex metrica =
-- Escapa automaticamente % _ & # para que LaTeX no falle
tmpl render latex "Resultado: {{val}}" [("val", metrica)]
22.3.3. Exemplo 3: Interpolación de variables {{ clave }}, recorte de espazos en etiquetas e eliminación de comentarios {{! ... }}
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl interpolation and comments => ()
test tmpl interpolation and comments =
nl = char from code 10
ctx = [("name", "Ada Lovelace"), ("role", "Mathematician"), ("empty", "")]
t1 = "Hello, " + (tag2 "name") + " (" + (tag2 " role ") + ")!"
out1 = tmpl render t1 ctx
assert str eq out1 "Hello, Ada Lovelace (Mathematician)!" "AC1: variable and whitespace-trimmed tag interpolation"
t_missing = "User: [" + (tag2 "unknown") + "] Empty: [" + (tag2 "empty") + "]"
out_missing = tmpl render t_missing ctx
assert str eq out_missing "User: [] Empty: []" "AC1: missing and empty variables render empty string"
t_comment = "Visible" + (tag2 "! secret comment ") + "Text" + (tag2 ("! multiline" + nl + "comment" + nl + "here ")) + "End"
out_comment = tmpl render t_comment ctx
assert str eq out_comment "VisibleTextEnd" "AC1: single-line and multi-line comments omitted"
()
22.3.4. Exemplo 4: Mitigación XSS por defecto (&, <, >, ", ') fronte a inserción crúa explícita {{{ raw }}} e {{& raw }}
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl html escaping and raw => ()
test tmpl html escaping and raw =
q = char from code 34
ctx = [("xss", "<script>alert(" + q + "xss" + q + " & 'pwn')</script>"), ("trusted", "<b>Bold & Safe</b>")]
tmpl = "Escaped: " + (tag2 "xss") + " | Raw3: " + (tag3 "trusted") + " | RawAmp: " + (tag2 "& trusted ")
out_html = tmpl render html tmpl ctx
expected = "Escaped: <script>alert("xss" & 'pwn')</script> | Raw3: <b>Bold & Safe</b> | RawAmp: <b>Bold & Safe</b>"
assert str eq out_html expected "AC1: HTML escaping vs raw triple-brace and ampersand tags"
out_none = tmpl render ("Plain: " + (tag2 "xss")) ctx
assert str eq out_none ("Plain: <script>alert(" + q + "xss" + q + " & 'pwn')</script>") "AC1: TmplEscapeNone leaves HTML unescaped"
()
22.3.5. Exemplo 5: Seccións condicionais {{#sec}}...{{/sec}} e seccións investidas {{^sec}}...{{/sec}} segundo valores de verdade (false, 0, no, off)
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl sections and inverted => ()
test tmpl sections and inverted =
tmpl = (tag2 "#auth") + "Welcome, " + (tag2 "user") + "!" + (tag2 "/auth") + (tag2 "^auth") + "Please log in." + (tag2 "/auth")
out_in = tmpl render html tmpl [("auth", "true"), ("user", "Alice & Bob")]
assert str eq out_in "Welcome, Alice & Bob!" "AC2: truthy section rendered with HTML escaping"
out_false = tmpl render html tmpl [("auth", "false"), ("user", "Alice")]
assert str eq out_false "Please log in." "AC2: 'false' triggers inverted section"
out_false_cap = tmpl render html tmpl [("auth", "False"), ("user", "Alice")]
assert str eq out_false_cap "Please log in." "AC2: 'False' triggers inverted section"
out_zero = tmpl render html tmpl [("auth", "0"), ("user", "Alice")]
assert str eq out_zero "Please log in." "AC2: '0' triggers inverted section"
out_empty = tmpl render html tmpl [("auth", ""), ("user", "Alice")]
assert str eq out_empty "Please log in." "AC2: empty string triggers inverted section"
out_missing = tmpl render html tmpl [("user", "Alice")]
assert str eq out_missing "Please log in." "AC2: missing key triggers inverted section"
()
22.3.6. Exemplo 6: Aniñamento profundo de seccións condicionais e interpolación xerárquica
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl nested sections => ()
test tmpl nested sections =
tmpl = (tag2 "#outer") + "A-" + (tag2 "#inner") + "B-" + (tag2 "val") + (tag2 "/inner") + (tag2 "^inner") + "NO_INNER" + (tag2 "/inner") + "-C" + (tag2 "/outer")
out1 = tmpl render tmpl [("outer", "1"), ("inner", "yes"), ("val", "42")]
assert str eq out1 "A-B-42-C" "AC2: nested outer+inner truthy"
out2 = tmpl render tmpl [("outer", "1"), ("inner", "0"), ("val", "42")]
assert str eq out2 "A-NO_INNER-C" "AC2: nested outer truthy, inner falsy"
out3 = tmpl render tmpl [("outer", "0"), ("inner", "yes"), ("val", "42")]
assert str eq out3 "" "AC2: nested outer falsy skips inner"
same_nested = (tag2 "#k") + "[" + (tag2 "#k") + "(" + (tag2 "v") + ")" + (tag2 "/k") + "]" + (tag2 "/k")
assert str eq (tmpl render same_nested [("k", "1"), ("v", "ok")]) "[(ok)]" "AC2: nested same-key truthy"
assert str eq (tmpl render same_nested [("k", ""), ("v", "ok")]) "" "AC2: nested same-key falsy"
()
22.3.7. Exemplo 7: Escapado especializado para documentos LaTeX (tmpl escape latex: \, {, }, $, &, #, _, %, ~, ^)
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl latex escaping => ()
test tmpl latex escaping =
bs = char from code 92
lb = char from code 123
rb = char from code 125
braces = lb + rb
ctx = [("formula", "100% of $50 & #1_item " + lb + "a" + bs + "b" + rb + " ~ ^"), ("raw_tex", bs + "textbf" + lb + "Title" + rb)]
tmpl = "Text: " + (tag2 "formula") + " | Raw: " + (tag3 "raw_tex")
out_tex = tmpl render latex tmpl ctx
expected = "Text: 100" + bs + "% of " + bs + "$50 " + bs + "& " + bs + "#1" + bs + "_item " + bs + lb + "a" + bs + "textbackslash" + braces + "b" + bs + rb + " " + bs + "textasciitilde" + braces + " " + bs + "textasciicircum" + braces + " | Raw: " + bs + "textbf" + lb + "Title" + rb
assert str eq out_tex expected "AC3: LaTeX special characters escaped accurately while triple-brace stays raw"
()
22.3.8. Exemplo 8: Detección determinista de etiquetas sen pechar ou seccións desbalanceadas (TmplErr)
Neste exemplo práctico exploramos de forma exhaustiva como se comporta template neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:
fn :: test tmpl malformed tags => ()
test tmpl malformed tags =
lb = char from code 123
ctx = [("x", "1")]
assert str eq (tmpl render ("trailing " + lb + lb) ctx) ("trailing " + lb + lb) "AC1: trailing double brace"
assert str eq (tmpl render ("unclosed " + lb + lb + "x") ctx) ("unclosed " + lb + lb + "x") "AC1: unclosed var tag"
assert str eq (tmpl render ("unclosed raw " + lb + lb + lb + "x") ctx) ("unclosed raw " + lb + lb + lb + "x") "AC1: unclosed raw3 tag"
assert str eq (tmpl render ("unclosed comment " + lb + lb + "! hidden") ctx) "unclosed comment " "AC1: unclosed comment tag"
()
22.4. Baixo o capó: como está construída template en Ridgun puro (src/main/ridgun/lib/template.rg)
Unha das maiores vantaxes de ridgun-libs é que non hai caixas negras en C. Podes abrir src/main/ridgun/lib/template.rg e ler exactamente como está implementada cada operación usando pattern matching, árbores de gardas | / |. e recursión de cola (TCO). Vexamos as pezas clave da súa implementación real:
22.4.1. Implementación interna: Internal Helpers
Fíxate en como template.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:
module :: template
fn :: tm lbrace => String
tm lbrace = char from code 123
fn :: tm rbrace => String
tm rbrace = char from code 125
fn :: tm braces => String
tm braces = (char from code 123) + (char from code 125)
fn :: tm is space Int => Bool
tm is space c =
| c == 32 = True
| c == 9 = True
| c == 10 = True
| c == 13 = True
| otherwise = False
fn :: tm trim start String Int Int => Int
tm trim start s idx n =
| idx >= n = n
| tm is space (str char at s idx) = tm trim start s (idx + 1) n
| otherwise = idx
fn :: tm trim end String Int Int => Int
tm trim end s start idx =
| idx <= start = start
| tm is space (str char at s (idx - 1)) = tm trim end s start (idx - 1)
| otherwise = idx
fn :: tm trim String => String
tm trim s =
s str len -> n
tm trim start s 0 n -> st
tm trim end s st n -> en
str slice s st en
fn :: tm lookup [String String] String => String
tm lookup [] _ = ""
tm lookup ((k, v):rest) key =
| k == key = v
| otherwise = tm lookup rest key
fn :: tm val truthy String => Bool
tm val truthy val =
| val == "" = False
| val == "false" = False
| val == "False" = False
| val == "0" = False
| otherwise = True
fn :: tm is truthy [String String] String => Bool
tm is truthy ctx key = tm lookup ctx key -> tm val truthy
fn :: tm escape html char Int => String
tm escape html char c =
| c == 38 = "&"
| c == 60 = "<"
| c == 62 = ">"
| c == 34 = """
| c == 39 = "'"
| otherwise = char from code c
fn :: tm escape html loop String Int Int String => String
tm escape html loop s idx n acc =
| idx >= n = acc
| otherwise = tm escape html loop s (idx + 1) n (acc + (tm escape html char (str char at s idx)))
fn :: tm escape latex char Int => String
tm escape latex char c =
| c == 92 = (char from code 92) + "textbackslash" + (tm braces)
| c == 123 = (char from code 92) + (tm lbrace)
| c == 125 = (char from code 92) + (tm rbrace)
| c == 38 = (char from code 92) + "&"
| c == 37 = (char from code 92) + "%"
| c == 36 = (char from code 92) + "$"
| c == 35 = (char from code 92) + "#"
| c == 95 = (char from code 92) + "_"
| c == 126 = (char from code 92) + "textasciitilde" + (tm braces)
| c == 94 = (char from code 92) + "textasciicircum" + (tm braces)
| otherwise = char from code c
fn :: tm escape latex loop String Int Int String => String
tm escape latex loop s idx n acc =
| idx >= n = acc
| otherwise = tm escape latex loop s (idx + 1) n (acc + (tm escape latex char (str char at s idx)))
fn :: tm escape String TmplEscape => String
tm escape s TmplEscapeNone = s
tm escape s TmplEscapeHtml = tm escape html loop s 0 (str len s) ""
tm escape s TmplEscapeLatex = tm escape latex loop s 0 (str len s) ""
fn :: tm find close2 String Int Int => Int
tm find close2 s idx n =
| (idx + 1) >= n = (0 - 1)
| (str char at s idx) == 125 and (str char at s (idx + 1)) == 125 = idx
| otherwise = tm find close2 s (idx + 1) n
fn :: tm find close3 String Int Int => Int
tm find close3 s idx n =
| (idx + 2) >= n = (0 - 1)
| (str char at s idx) == 125 and (str char at s (idx + 1)) == 125 and (str char at s (idx + 2)) == 125 = idx
| otherwise = tm find close3 s (idx + 1) n
fn :: tm find section close String Int Int String Int => Int Int
tm find section close s idx n sec_key depth =
| (idx + 3) >= n = (n, n)
| (str char at s idx) == 123 and (str char at s (idx + 1)) == 123 =
tm check section tag s idx n sec_key depth (str char at s (idx + 2))
| otherwise = tm find section close s (idx + 1) n sec_key depth
fn :: tm check section tag String Int Int String Int Int => Int Int
tm check section tag s idx n sec_key depth sig =
| sig == 47 = tm check close tag s idx n sec_key depth (tm find close2 s (idx + 3) n)
| sig == 35 or sig == 94 = tm check open tag s idx n sec_key depth (tm find close2 s (idx + 3) n)
| otherwise = tm find section close s (idx + 2) n sec_key depth
fn :: tm check close tag String Int Int String Int Int => Int Int
tm check close tag s idx n sec_key depth close_pos =
| close_pos < 0 = (n, n)
| (tm trim (str slice s (idx + 3) close_pos)) == sec_key =
tm close tag match s idx n sec_key depth close_pos
| otherwise = tm find section close s (close_pos + 2) n sec_key depth
fn :: tm close tag match String Int Int String Int Int => Int Int
tm close tag match s idx n sec_key depth close_pos =
| depth <= 1 = (idx, close_pos + 2)
| otherwise = tm find section close s (close_pos + 2) n sec_key (depth - 1)
fn :: tm check open tag String Int Int String Int Int => Int Int
tm check open tag s idx n sec_key depth close_pos =
| close_pos < 0 = (n, n)
| (tm trim (str slice s (idx + 3) close_pos)) == sec_key =
tm find section close s (close_pos + 2) n sec_key (depth + 1)
| otherwise = tm find section close s (close_pos + 2) n sec_key depth
fn :: tm scan String Int Int [String String] TmplEscape String => String
tm scan s idx n ctx mode acc =
| idx >= n = acc
| (idx + 1) < n and (str char at s idx) == 123 and (str char at s (idx + 1)) == 123 =
tm dispatch tag s idx n ctx mode acc
| otherwise = tm scan s (idx + 1) n ctx mode (acc + (char from code (str char at s idx)))
fn :: tm dispatch tag String Int Int [String String] TmplEscape String => String
tm dispatch tag s idx n ctx mode acc =
| (idx + 2) >= n = acc + (tm lbrace) + (tm lbrace)
| (str char at s (idx + 2)) == 123 = tm tag raw3 s idx n ctx mode acc (tm find close3 s (idx + 3) n)
| (str char at s (idx + 2)) == 38 = tm tag raw amp s idx n ctx mode acc (tm find close2 s (idx + 3) n)
| (str char at s (idx + 2)) == 33 = tm tag comment s idx n ctx mode acc (tm find close2 s (idx + 3) n)
| (str char at s (idx + 2)) == 35 = tm tag section s idx n ctx mode acc (tm find close2 s (idx + 3) n) False
| (str char at s (idx + 2)) == 94 = tm tag section s idx n ctx mode acc (tm find close2 s (idx + 3) n) True
| otherwise = tm tag var s idx n ctx mode acc (tm find close2 s (idx + 2) n)
fn :: tm tag raw3 String Int Int [String String] TmplEscape String Int => String
tm tag raw3 s idx n ctx mode acc close_pos =
| close_pos < 0 = tm scan s (idx + 3) n ctx mode (acc + (tm lbrace) + (tm lbrace) + (tm lbrace))
| otherwise =
tm scan s (close_pos + 3) n ctx mode (acc + (tm lookup ctx (tm trim (str slice s (idx + 3) close_pos))))
fn :: tm tag raw amp String Int Int [String String] TmplEscape String Int => String
tm tag raw amp s idx n ctx mode acc close_pos =
| close_pos < 0 = tm scan s (idx + 3) n ctx mode (acc + (tm lbrace) + (tm lbrace) + "&")
| otherwise =
tm scan s (close_pos + 2) n ctx mode (acc + (tm lookup ctx (tm trim (str slice s (idx + 3) close_pos))))
fn :: tm tag comment String Int Int [String String] TmplEscape String Int => String
tm tag comment s _ n ctx mode acc close_pos =
| close_pos < 0 = acc
| otherwise = tm scan s (close_pos + 2) n ctx mode acc
fn :: tm section active Bool Bool => Bool
tm section active True False = True
tm section active False True = True
tm section active _ _ = False
fn :: tm render section body String Int (Int Int) Int [String String] TmplEscape String Bool => String
tm render section body s _ (_, after_close) n ctx mode acc False =
tm scan s after_close n ctx mode acc
tm render section body s body_start (body_end, after_close) n ctx mode acc True =
tm scan s body_start body_end ctx mode "" -> rendered_body
tm scan s after_close n ctx mode (acc + rendered_body)
fn :: tm tag section String Int Int [String String] TmplEscape String Int Bool => String
tm tag section s idx n ctx mode acc open_close inverted =
| open_close < 0 = tm scan s (idx + 3) n ctx mode acc
| otherwise =
tm render section body s (open_close + 2) (tm find section close s (open_close + 2) n (tm trim (str slice s (idx + 3) open_close)) 1) n ctx mode acc (tm section active (tm is truthy ctx (tm trim (str slice s (idx + 3) open_close))) inverted)
fn :: tm tag var String Int Int [String String] TmplEscape String Int => String
tm tag var s idx n ctx mode acc close_pos =
| close_pos < 0 = tm scan s (idx + 2) n ctx mode (acc + (tm lbrace) + (tm lbrace))
| otherwise =
tm scan s (close_pos + 2) n ctx mode (acc + (tm escape (tm lookup ctx (tm trim (str slice s (idx + 2) close_pos))) mode))
22.4.2. Implementación interna: Public API
Fíxate en como template.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:
fn :: tmpl render with escape String [String String] TmplEscape => String
tmpl render with escape tmpl ctx mode = tm scan tmpl 0 (str len tmpl) ctx mode ""
fn :: tmpl render String [String String] => String
tmpl render tmpl ctx = tmpl render with escape tmpl ctx TmplEscapeNone
fn :: tmpl render html String [String String] => String
tmpl render html tmpl ctx = tmpl render with escape tmpl ctx TmplEscapeHtml
fn :: tmpl render latex String [String String] => String
tmpl render latex tmpl ctx = tmpl render with escape tmpl ctx TmplEscapeLatex
22.5. Garantías algorítmicas, memoria RMM1/RMM2 e as 5 Regras de Ouro de template
- Garantía algorítmica e de seguridade: Prevención de XSS por defecto en
tmpl render html(escapando&,<,>,",') mentres{{{var}}}permite inxectar fragmentos de confianza de forma explícita. - Complexidade de pila $O(1)$ con TCO: Todos os bucles recursivos en
src/main/ridgun/lib/template.rgestán en posición de cola, polo queridguncos compila a saltos nativos sen crecemento de pila. - Memoria determinista (
RMM1/RMM2): Cero pausas de colector de lixo; todas as estruturas intermedias libéranse de forma determinista ao saír do ámbito. - Verificación continua: Probada exhaustivamente con
stable checke a suite automatizadasrc/test/ridgun/test template.rg.
As 5 Regras de Ouro ao programar con template
- Declara
import templateao inicio do ficheiro e aproveita a equivalencia entre espazos e guións baixos para que o teu código se lea coma prosa. - Aliña as túas tubaxes de datos con
=: encadea as chamadas atemplateen pasos con nome propio e deixa questable formataliñe as columnas. - Aproveita as funcións totais de
templatenas túas gardas|: comproba os valores de retorno ou desenvolve os ADTs con pattern matching en vez de asumir que a entrada externa sempre é válida. - Evita copiar grandes estruturas se só necesitas consultar un campo: lembra que en
RMM2os argumentos de entrada non incrementan nin decrementan contadores (+0/-0). - Combina
templatecontest(import test): escribe sempre asercións conassert equalsouassert truepara fixar os invariantes do teu dominio.
No Capítulo 23 pasaremos a explorar en profundidade a lib fmt (A lib fmt — Formatador Tabular Canónico 2D e Resaltador de Sintaxe en 9 Formatos).