Saltar ao contido
Ridgun
Galego

16 O Libro de Ridgun

A lib json — Árbore AST Recursiva RFC 8259, Pretty-Printer e Consultas por Ruta

16.1. Por que existe a lib json? (O problema real e a filosofía Ridgun)

«Por que necesito unha lib json escrita 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 json. E a resposta resume toda a filosofía de ridgun-libs:

Sexamos sinceros: navegar un JSON aniñado en moitas linguaxes estáticas implica escribir tres pantallas de boilerplate ou encadear dez .and_then() antes de ler un simple porto.

A lib json proporciona un analizador descendente recursivo completo para RFC 8259 sobre o ADT alxébrico JsonVal (JnNull, JnBool, JnInt, JnStr, JnArr, JnObj, JnErr), serialización compacta e sangrada (json pretty) e consultas directas por ruta con puntos ("server.http.port", "users.0.name").

Nun ecosistema tradicional, resolver as tarefas do dominio JSON 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 json (src/main/ridgun/lib/json.rg) aborda o problema con catro principios innegociables:

  1. Declara import json (ou import json as alias) ao inicio do ficheiro: cada lib define o seu espazo de nomes module :: json e en modo --strict as funcións multi-palabra invócanse sempre con espazos naturais.
  2. Encadea as túas tubaxes de esquerda a dereita con ->: escribe entrada paso un -> paso dous -> resultado, usa máscaras (+ _ _ +) para desestruturar secuencias e omite _ = en sentenzas secuenciais (ou descarta con -> ()).
  3. Recursión de cola con TCO (Tail-Call Optimization): todos os percorridos internos de json compílanse a bucles nativos en espazo de pila $O(1)$.
  4. Memoria determinista (RMM1 / RMM2): cada estrutura intermedia libérase no instante exacto en que remata a súa última lectura, sen Garbage Collector.

16.2. Artefactos nativos, importación e catálogo completo da API de json

En ridgun-libs, json é unha lib de primeira clase situada en src/main/ridgun/lib/json.rg (dominio JSON). Ao compilar con stable bundle, empaquétase en tres formatos nativos:

ArtefactoRutaPropósito
Bundle Ridguntarget/bundles/json.rglibDistribución e importación directa con import json
Ficheiro compiladotarget/json.rgcArtefacto intermedio verificado por ridgunc
Obxecto compartido ELF64target/libjson.soBiblioteca dinámica nativa sen dependencias de C nin libc

Cada lib declara o seu propio espazo de nomes con module :: json: abonda con escribir import json (ou import json as alias) na cabeceira e chamar ás súas funcións con espazos naturais (mina funcion x) e tubaxes -> (x mina funcion -> resultado).

16.2.1. Resumo de familias funcionais de json

Sinatura / Función / TipoQue fai e cando usala
type JsonVal`JnNull
json parse / json is okAnaliza texto JSON nunha árbore JsonVal e valida a ausencia de JnErr
json stringify / json prettySerializa JsonVal a JSON compacto ou multiliña sangrado
json get path / json get str / json get int / json get boolPercorre obxectos e listas aniñados mediante rutas con puntos ("a.b.0.c")

16.2.2. Catálogo exhaustivo de tipos e sinaturas en src/main/ridgun/lib/json.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/json.rg (7 tipos, 0 operadores e 118 funcións):

Tipos alxébricos e rexistros (type) de json:

type JsonField = JsonField String JsonVal
type JsonVal = JsonNull | JsonBool Bool | JsonInt Int | JsonStr String | JsonArr [JsonVal] | JsonObj [JsonField]
type JsonRes = JsonOk JsonVal | JsonErr String
type JsonStep = JStepOk JsonVal Int | JStepErr String
type JsonArrStep = JArrOk [JsonVal] Int | JArrErr String
type JsonObjStep = JObjOk [JsonField] Int | JObjErr String
type JsonStrStep = JStrOk String Int | JStrErr String
#Función / Sinatura (fn ::)Tipo de Retorno
1fn :: j is ws IntBool
2fn :: j is digit IntBool
3fn :: j skip ws String Int IntInt
4fn :: j spaces IntString
5fn :: j escape loop String Int Int StringString
6fn :: j escape str StringString
7fn :: j unescape char IntString
8fn :: j parse str loop String Int Int StringJsonStrStep
9fn :: j parse string String Int IntJsonStrStep
10fn :: j scan digits String Int IntInt
11fn :: j parse number String Int IntJsonStep
12fn :: j finish neg num String Int IntJsonStep
13fn :: j finish pos num String Int IntJsonStep
14fn :: j parse val String Int IntJsonStep
15fn :: j parse val at String Int IntJsonStep
16fn :: j wrap str step JsonStrStepJsonStep
17fn :: j parse arr open String Int IntJsonStep
18fn :: j parse arr first String Int IntJsonStep
19fn :: j finish arr step JsonArrStepJsonStep
20fn :: j parse arr items String Int Int [JsonVal]JsonArrStep
21fn :: j parse arr after item String Int [JsonVal] JsonStepJsonArrStep
22fn :: j parse arr delim String Int Int [JsonVal]JsonArrStep
23fn :: j parse obj open String Int IntJsonStep
24fn :: j parse obj first String Int IntJsonStep
25fn :: j finish obj step JsonObjStepJsonStep
26fn :: j parse obj fields String Int Int [JsonField]JsonObjStep
27fn :: j parse obj after key String Int [JsonField] JsonStrStepJsonObjStep
28fn :: j parse obj colon String Int Int String [JsonField]JsonObjStep
29fn :: j parse obj after val String Int String [JsonField] JsonStepJsonObjStep
30fn :: j parse obj delim String Int Int [JsonField]JsonObjStep
31fn :: j check eof String Int JsonStepJsonRes
32fn :: j check eof pos Int Int JsonValJsonRes
33fn :: json parse StringJsonRes
34fn :: j join arr [JsonVal]String
35fn :: j join obj [JsonField]String
36fn :: json stringify JsonValString
37fn :: j pretty arr [JsonVal] Int IntString
38fn :: j pretty obj [JsonField] Int IntString
39fn :: j pretty val JsonVal Int IntString
40fn :: json pretty JsonVal IntString
41fn :: j find field [JsonField] StringJsonVal
42fn :: json get field JsonVal StringJsonVal
43fn :: j nth val [JsonVal] IntJsonVal
44fn :: json get index JsonVal IntJsonVal
45fn :: j split dots loop String Int Int Int [String][String]
46fn :: j step path JsonVal StringJsonVal
47fn :: j walk path JsonVal [String]JsonVal
48fn :: json get path JsonVal StringJsonVal
49fn :: json get str JsonVal String StringString
50fn :: j unwrap str JsonVal StringString
51fn :: json get int JsonVal String IntInt
52fn :: j unwrap int JsonVal IntInt
53fn :: json get bool JsonVal String BoolBool
54fn :: j unwrap bool JsonVal BoolBool
55fn :: j put field [JsonField] String JsonVal[JsonField]
56fn :: json set field JsonVal String JsonValJsonVal

16.3. Explorando json 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 json 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 json.rg.

16.3.1. Exemplo 1: Fluxo principal en estilo tabular con json

Observa como en Ridgun os signos = e os pasos de transformación con json quedan aliñados coma unha táboa limpa, sen variables mutables innecesarias nin efectos laterais ocultos:

import json

fn :: leer config servidor JsonVal => String Int Bool
      leer config servidor doc =
        json get str doc "server.host" "127.0.0.1" -> host
        json get int doc "server.port" 8080 -> port
        json get bool doc "server.tls.enabled" False -> tls
        (host, port, tls)

16.3.2. Exemplo 2: Composición segura e manexo de casos de bordo

Toda función da lib json está deseñada para compoñerse con gardas booleanas | e co resto das 32 libs de ridgun-libs:

import json

fn :: construir manifiesto => String
      construir manifiesto =
        JsonObj [JsonField "app" (JsonStr "solstice"), JsonField "workers" (JsonInt 8)] -> nodo
        json pretty nodo 2

16.3.3. Exemplo 3: Análise sintáctica (json parse), serialización compacta (json stringify) e impresión sangrada (json pretty)

Neste exemplo práctico exploramos de forma exhaustiva como se comporta json neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:

fn :: test json parse stringify and pretty => ()
      test json parse stringify and pretty =
        lb = char from code 123
        rb = char from code 125
        raw = lb + "\"user\":" + lb + "\"name\":\"Ada \\\"Lovelace\\\"\",\"age\":36,\"active\":true,\"roles\":[\"admin\",\"math\"],\"meta\":null" + rb + rb
        res = json parse raw
        assert true (is json ok res) "AC1: json parse nested object and array"
        val = unwrap json res

        compact = json stringify val
        assert str eq compact raw "AC1: json stringify lossless round-trip"

        pretty_s = json pretty val 2
        res2 = json parse pretty_s
        assert true (is json ok res2) "AC1: json pretty output parses back cleanly"
        assert str eq (json stringify (unwrap json res2)) raw "AC1: json pretty round-trip equality"
        ()

16.3.4. Exemplo 4: Primitivas JSON (null, bool, int, str), secuencias de escape e contedores baleiros ([], {})

Neste exemplo práctico exploramos de forma exhaustiva como se comporta json neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:

fn :: test json primitives escapes and empty containers => ()
      test json primitives escapes and empty containers =
        lb = char from code 123
        rb = char from code 125
        bs = char from code 92
        nl = char from code 10
        tab = char from code 9
        assert str eq (json stringify (unwrap json (json parse (lb + rb)))) (lb + rb) "AC1: empty object {}"
        assert str eq (json stringify (unwrap json (json parse "[]"))) "[]" "AC1: empty array []"
        assert str eq (json stringify (unwrap json (json parse ("  " + lb + "  " + rb + "  ")))) (lb + rb) "AC1: whitespace-padded empty object"
        assert true (is json null (unwrap json (json parse "null"))) "AC1: root null literal"
        assert str eq (json stringify (unwrap json (json parse "true"))) "true" "AC1: root true literal"
        assert str eq (json stringify (unwrap json (json parse "false"))) "false" "AC1: root false literal"
        assert str eq (json stringify (unwrap json (json parse "0"))) "0" "AC1: root zero integer"
        assert str eq (json stringify (unwrap json (json parse "-42000"))) "-42000" "AC1: root negative integer"

        esc_json = lb + "\"msg\":\"line1" + bs + "nline2" + bs + "t" + bs + bs + "slash\"" + rb
        esc_val = unwrap json (json parse esc_json)
        assert str eq (json get str esc_val "msg" "") ("line1" + nl + "line2" + tab + bs + "slash") "AC1: unescape \\n, \\t, \\\\ in JSON strings"
        assert str eq (json stringify esc_val) esc_json "AC1: re-escape \\n, \\t, \\\\ on stringify"
        ()

16.3.5. Exemplo 5: Consultas profundas por ruta de puntos (json query, json query str/int/bool) e actualización funcional (json set field)

Neste exemplo práctico exploramos de forma exhaustiva como se comporta json neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:

fn :: test json dot path queries and updates => ()
      test json dot path queries and updates =
        lb = char from code 123
        rb = char from code 125
        raw = lb + "\"user\":" + lb + "\"name\":\"Ada\",\"age\":36,\"active\":true,\"roles\":[\"admin\",\"math\"],\"matrix\":[[10,20],[30,40]]" + rb + ",\"count\":-7,\"disabled\":false" + rb
        val = unwrap json (json parse raw)

        assert str eq (json get str val "user.name" "") "Ada" "AC2: json get str dot path"
        assert int equals (json get int val "user.age" 0) 36 "AC2: json get int dot path"
        assert int equals (json get int val "count" 0) (-7) "AC2: json get int negative"
        assert true (json get bool val "user.active" False) "AC2: json get bool true"
        assert false (json get bool val "disabled" True) "AC2: json get bool false"
        assert str eq (json get str val "user.roles.0" "") "admin" "AC2: json get str array index 0"
        assert str eq (json get str val "user.roles.1" "") "math" "AC2: json get str array index 1"
        assert int equals (json get int val "user.matrix.1.0" 0) 30 "AC2: nested 2D array index user.matrix.1.0"
        assert int equals (json get int val "user.matrix.1.1" 0) 40 "AC2: nested 2D array index user.matrix.1.1"
        assert str eq (json get str val "user.roles.99" "oob") "oob" "AC2: out-of-bounds array index fallback"
        assert str eq (json get str val "user.missing.deep" "fallback") "fallback" "AC2: missing deep dot-path fallback"

        updated = json set field val "version" (JsonInt 2)
        assert int equals (json get int updated "version" 0) 2 "AC2: json set field adds new key"
        overwritten = json set field updated "count" (JsonInt 99)
        assert int equals (json get int overwritten "count" 0) 99 "AC2: json set field overwrites existing key"
        ()

16.3.6. Exemplo 6: Detección segura de entradas JSON malformadas (JsonErr) sen pánicos nin excepcións

Neste exemplo práctico exploramos de forma exhaustiva como se comporta json neste bloque funcional, incluíndo entradas estándar, combinacións de parámetros e valores fronteira:

fn :: test json malformed inputs => ()
      test json malformed inputs =
        lb = char from code 123
        rb = char from code 125
        assert false (is json ok (json parse "")) "AC3: reject empty input"
        assert false (is json ok (json parse "   ")) "AC3: reject whitespace-only input"
        assert false (is json ok (json parse (lb + "\"unclosed\":1"))) "AC3: reject unclosed object"
        assert false (is json ok (json parse "[1, 2, 3")) "AC3: reject unclosed array"
        assert false (is json ok (json parse "\"unterminated string")) "AC3: reject unterminated string"
        assert false (is json ok (json parse (lb + "unquoted: 1" + rb))) "AC3: reject unquoted object key"
        assert false (is json ok (json parse (lb + "\"k\" 1" + rb))) "AC3: reject missing colon in object"
        assert false (is json ok (json parse "[1, 2] trailing")) "AC3: reject trailing garbage token"
        assert false (is json ok (json parse "tru")) "AC3: reject truncated boolean literal"
        assert false (is json ok (json parse "nul")) "AC3: reject truncated null literal"
        ()

16.4. Baixo o capó: como está construída json en Ridgun puro (src/main/ridgun/lib/json.rg)

Unha das maiores vantaxes de ridgun-libs é que non hai caixas negras en C. Podes abrir src/main/ridgun/lib/json.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:

16.4.1. Implementación interna: Whitespace & Character Helpers

Fíxate en como json.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:

module :: json

fn :: j is ws Int => Bool
      j is ws c   =  c == 32 OR c == 9 OR c == 10 OR c == 13

fn :: j is digit Int => Bool
      j is digit c   =  c >= 48 AND c <= 57

fn :: j skip ws String Int Int => Int
      j skip ws s      pos len
        | pos >= len = len
        | j is ws (str char at s pos) = j skip ws s (pos + 1) len
        | otherwise = pos

fn :: j spaces Int => String
      j spaces n
        | n <= 0 = ""
        | otherwise = " " + (j spaces (n - 1))

16.4.2. Implementación interna: Integer Literal Parsing

Fíxate en como json.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:

fn :: j scan digits String Int Int => Int
      j scan digits s      pos len
        | pos >= len = len
        | j is digit (str char at s pos) = j scan digits s (pos + 1) len
        | otherwise = pos

fn :: j parse number String Int Int => JsonStep
      j parse number s      pos len
        | (str char at s pos) == 45 = j finish neg num s pos (j scan digits s (pos + 1) len)
        | otherwise = j finish pos num s pos (j scan digits s pos len)

fn :: j finish neg num String Int Int     => JsonStep
      j finish neg num s      pos end_pos
        | end_pos <= (pos + 1) = JStepErr "invalid negative number"
        | otherwise = JStepOk (JsonInt (str to int (str slice s pos end_pos))) end_pos

fn :: j finish pos num String Int Int     => JsonStep
      j finish pos num s      pos end_pos
        | end_pos <= pos = JStepErr "expected number"
        | otherwise = JStepOk (JsonInt (str to int (str slice s pos end_pos))) end_pos

16.4.3. Implementación interna: Serialization & Pretty-Printing

Fíxate en como json.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:

fn :: j join arr loop [JsonVal] String String String String => String
      j join arr loop []     c0 c1 c2 c3 = j sb finish c0 c1 c2 c3
      j join arr loop (v:vs) c0 c1 c2 c3 =
        n0, n1, n2, n3 = j sb append ("," + (json stringify v)) c0 c1 c2 c3
        j join arr loop vs n0 n1 n2 n3

fn :: j join arr [JsonVal] => String
      j join arr []        =  ""
      j join arr (v:vs)    =  j join arr loop vs (json stringify v) "" "" ""

fn :: j join obj loop [JsonField] String String String String => String
      j join obj loop []                     c0 c1 c2 c3 = j sb finish c0 c1 c2 c3
      j join obj loop ((JsonField k v):rest) c0 c1 c2 c3 =
        item = "," + (j escape str k) + ":" + (json stringify v)
        n0, n1, n2, n3 = j sb append item c0 c1 c2 c3
        j join obj loop rest n0 n1 n2 n3

fn :: j join obj [JsonField] => String
      j join obj []                     =  ""
      j join obj ((JsonField k v):rest) =
        first = (j escape str k) + ":" + (json stringify v)
        j join obj loop rest first "" "" ""

fn :: j pos int to str Int String => String
      j pos int to str n acc =
        | n <= 0    = acc
        | otherwise = j pos int to str (n / 10) ((char from code (48 + (n % 10))) + acc)

fn :: j int to str Int => String
      j int to str n =
        | n == 0         = "0"
        | n > 0          = j pos int to str n ""
        | n > (0 - 10)   = "-" + (char from code (48 + (0 - n)))
        | otherwise      = "-" + (j pos int to str (0 - (n / 10)) (char from code (48 + (0 - (n % 10)))))

fn :: json stringify JsonVal          => String
      json stringify JsonNull         =  "null"
      json stringify (JsonBool True)  =  "true"
      json stringify (JsonBool False) =  "false"
      json stringify (JsonInt n)      =  j int to str n
      json stringify (JsonStr s)      =  j escape str s
      json stringify (JsonArr items)  =  "[" + (j join arr items) + "]"
      json stringify (JsonObj fields) =  (char from code 123) + (j join obj fields) + (char from code 125)

fn :: j pretty arr loop [JsonVal] Int Int String String String String => String
      j pretty arr loop []     _   _   c0 c1 c2 c3 = j sb finish c0 c1 c2 c3
      j pretty arr loop (v:vs) ind cur c0 c1 c2 c3 =
        item = "," + (char from code 10) + (j spaces (cur + ind)) + (j pretty val v ind (cur + ind))
        n0, n1, n2, n3 = j sb append item c0 c1 c2 c3
        j pretty arr loop vs ind cur n0 n1 n2 n3

fn :: j pretty arr [JsonVal] Int Int => String
      j pretty arr []     _   _   =  ""
      j pretty arr (v:vs) ind cur =
        first = (j spaces (cur + ind)) + (j pretty val v ind (cur + ind))
        j pretty arr loop vs ind cur first "" "" ""

fn :: j pretty obj loop [JsonField] Int Int String String String String => String
      j pretty obj loop []                     _   _   c0 c1 c2 c3 = j sb finish c0 c1 c2 c3
      j pretty obj loop ((JsonField k v):rest) ind cur c0 c1 c2 c3 =
        item = "," + (char from code 10) + (j spaces (cur + ind)) + (j escape str k) + ": " + (j pretty val v ind (cur + ind))
        n0, n1, n2, n3 = j sb append item c0 c1 c2 c3
        j pretty obj loop rest ind cur n0 n1 n2 n3

fn :: j pretty obj [JsonField] Int Int => String
      j pretty obj []                     _   _   =  ""
      j pretty obj ((JsonField k v):rest) ind cur =
        first = (j spaces (cur + ind)) + (j escape str k) + ": " + (j pretty val v ind (cur + ind))
        j pretty obj loop rest ind cur first "" "" ""

fn :: j pretty val JsonVal          Int Int => String
      j pretty val (JsonArr [])     _   _   =  "[]"
      j pretty val (JsonArr items)  ind cur =
        nl = char from code 10
        "[" + nl + (j pretty arr items ind cur) + nl + (j spaces cur) + "]"
      j pretty val (JsonObj [])     _   _   =  (char from code 123) + (char from code 125)
      j pretty val (JsonObj fields) ind cur =
        nl = char from code 10
        (char from code 123) + nl + (j pretty obj fields ind cur) + nl + (j spaces cur) + (char from code 125)
      j pretty val v                _   _   =  json stringify v

fn :: json pretty JsonVal Int => String
      json pretty val     ind =  j pretty val val ind 0

16.4.4. Implementación interna: Dot-Path Queries & Field Updates

Fíxate en como json.rg estrutura as sinaturas fn :: ... => ... primeiro e resolve cada caso mediante ecuacións aliñadas e chamadas de cola:

fn :: j find field [JsonField] String => JsonVal
      j find field []                   _   =  JsonNull
      j find field ((JsonField k v):fs) key =
        | k == key  = v
        | otherwise = j find field fs key

fn :: json get field JsonVal          String => JsonVal
      json get field (JsonObj fields) key    =  j find field fields key
      json get field _                _      =  JsonNull

fn :: j nth val [JsonVal] Int => JsonVal
      j nth val []     _   =  JsonNull
      j nth val (v:vs) idx =
        | idx < 0   = JsonNull
        | idx == 0  = v
        | otherwise = j nth val vs (idx - 1)

fn :: json get index JsonVal         Int => JsonVal
      json get index (JsonArr items) idx =  j nth val items idx
      json get index _               _   =  JsonNull

fn :: j split dots loop String Int Int Int [String] => [String]
      j split dots loop s pos len start acc =
        | pos >= len                = acc :: [str slice s start len]
        | (str char at s pos) == 46 = j split dots loop s (pos + 1) len (pos + 1) (acc :: [str slice s start pos])
        | otherwise                 = j split dots loop s (pos + 1) len start acc

fn :: j step path JsonVal          String => JsonVal
      j step path (JsonObj fields) seg    =  j find field fields seg
      j step path (JsonArr items)  seg    =
        | seg != "" and (j scan digits seg 0 (str len seg)) == (str len seg) and (j digits fit i64 seg False) = j nth val items (str to int seg)
        | otherwise = JsonNull
      j step path _                _      =  JsonNull

fn :: j walk path JsonVal [String]   => JsonVal
      j walk path val     []         =  val
      j walk path val     ("":rest)  =  j walk path val rest
      j walk path val     (seg:rest) =  j walk path (j step path val seg) rest

fn :: json get path JsonVal String   => JsonVal
      json get path val     ""       =  val
      json get path val     dot_path =
        j split dots loop dot_path 0 (str len dot_path) 0 [] -> segs
        j walk path val segs

fn :: json get str JsonVal String String => String
      json get str val dot_path fallback =
        json get path val dot_path -> target
        j unwrap str target fallback

fn :: j unwrap str JsonVal     String   => String
      j unwrap str (JsonStr s) _        =  s
      j unwrap str _           fallback =  fallback

fn :: json get int JsonVal String Int => Int
      json get int val dot_path fallback =
        json get path val dot_path -> target
        j unwrap int target fallback

fn :: j unwrap int JsonVal     Int      => Int
      j unwrap int (JsonInt n) _        =  n
      j unwrap int _           fallback =  fallback

fn :: json get bool JsonVal String Bool => Bool
      json get bool val dot_path fallback =
        json get path val dot_path -> target
        j unwrap bool target fallback

fn :: j unwrap bool JsonVal      Bool     => Bool
      j unwrap bool (JsonBool b) _        =  b
      j unwrap bool _            fallback =  fallback

fn :: j put field [JsonField] String JsonVal => [JsonField]
      j put field []                   key new_val = [JsonField key new_val]
      j put field ((JsonField k v):fs) key new_val =
        | k == key  = [JsonField key new_val] :: fs
        | otherwise = [JsonField k v] :: (j put field fs key new_val)

fn :: json set field JsonVal String JsonVal => JsonVal
      json set field (JsonObj fields) key new_val = JsonObj (j put field fields key new_val)
      json set field _ key new_val = JsonObj [JsonField key new_val]

16.5. Garantías algorítmicas, memoria RMM1/RMM2 e as 5 Regras de Ouro de json

  • Garantía algorítmica e de seguridade: Rexeitamento estrito de lixo sobrante tras o valor raíz (trailing garbage), manexo completo de escapes e retorno seguro de JnErr sen excepcións.
  • Complexidade de pila $O(1)$ con TCO: Todos os bucles recursivos en src/main/ridgun/lib/json.rg están en posición de cola, polo que ridgunc os 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 check e a suite automatizada src/test/ridgun/test json.rg.

As 5 Regras de Ouro ao programar con json

  1. Declara import json ao inicio do ficheiro e aproveita a equivalencia entre espazos e guións baixos para que o teu código se lea coma prosa.
  2. Aliña as túas tubaxes de datos con =: encadea as chamadas a json en pasos con nome propio e deixa que stable format aliñe as columnas.
  3. Aproveita as funcións totais de json nas 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.
  4. Evita copiar grandes estruturas se só necesitas consultar un campo: lembra que en RMM2 os argumentos de entrada non incrementan nin decrementan contadores (+0 / -0).
  5. Combina json con test (import test): escribe sempre asercións con assert equals ou assert true para fixar os invariantes do teu dominio.

No Capítulo 17 pasaremos a explorar en profundidade a lib csv (A lib csv — Analizador de Estados RFC 4180 para CSV/TSV e Táboas Indexadas).