Hoppa till innehållet
Ridgun
Svenska

16 Boken om Ridgun

Lib:en json — Rekursiv RFC 8259 AST-tolk, Pretty-Printer och Sökvägsfrågor

16.1. Varför finns lib:en json? (Det verkliga problemet och Ridguns filosofi)

”Varför behöver jag en json-lib skriven från grunden i ren Ridgun i stället för att bara länka in ett gammalt C-bibliotek?”

Det är den första fråga varje systemingenjör ställer sig när man möter json. Och svaret sammanfattar hela filosofin bakom ridgun-libs:

Låt oss vara ärliga: att navigera i djupt nästlad JSON i många statiskt typade språk kräver sidor av boilerplate-kod eller tio kedjade .and_then() bara för att läsa ett portnummer.

Lib:en json erbjuder en komplett rekursiv RFC 8259-tolk till den algebraiska datatypen JsonVal (JnNull, JnBool, JnInt, JnStr, JnArr, JnObj, JnErr), kompakt och indenterad serialisering (json pretty) samt punktseparerade sökvägsfrågor ("server.http.port", "users.0.name").

I ett traditionellt ekosystem innebär uppgifter inom domänen JSON ofta att man drar in externa C-beroenden, brottas med råa pekare, ogenomskinliga typkonverteringar, okontrollerade undantag eller oförutsägbara skräpsamlarpauser. I Ridgun löser lib:en json (src/main/ridgun/lib/json.rg) problemet genom fyra kompromisslösa principer:

  1. Deklarera import json (eller import json as alias) överst i filen: varje lib definierar sin module :: json-namnrymd och i --strict-läge anropas flerordsfunktioner alltid med naturliga mellanslag.
  2. Kedja dina datapipelines från vänster till höger med ->: skriv indata steg ett -> steg två -> resultat, använd (+ _ _ +)-sekvensmasker för att packa upp tupler och utelämna _ = vid sekventiella satser (eller kasta med -> ()).
  3. Svansrekursion med TCO (Tail-Call Optimization): alla interna genomlöpningar i json kompileras till inbyggda hårdvaruloopar med $O(1)$ stackutrymme.
  4. Deterministiskt minne (RMM1 / RMM2): varje mellanliggande struktur frigörs i exakt det ögonblick dess sista läsning avslutas, helt utan Garbage Collector.

16.2. Inbyggda artefakter, import och komplett API-katalog för json

I ridgun-libs är json en förstklassig lib placerad i src/main/ridgun/lib/json.rg (domän JSON). När du bygger med stable bundle paketeras den i tre inbyggda format:

ArtefaktSökvägSyfte
Ridgun-bundletarget/bundles/json.rglibPortabel distribution och direkt import med import json
Kompilerat arkivtarget/json.rgcTypkontrollerad mellanartefakt verifierad av ridgunc
Delat ELF64-objekttarget/libjson.soInbyggt dynamiskt bibliotek helt utan C- eller libc-beroenden

Varje lib deklarerar sin egen namnrymd med module :: json: skriv bara import json (eller import json as alias) överst och anropa dess funktioner med naturliga mellanslag (min funktion x) och ->-pipelines (x min funktion -> resultat).

16.2.1. Översikt över funktionella familjer i json

Signatur / Funktion / TypVad den gör och när den används
type JsonVal`JnNull
json parse / json is okTolkar JSON-text till JsonVal och kontrollerar att JnErr inte uppstått
json stringify / json prettySerialiserar JsonVal till kompakt eller flerradig indenterad JSON
json get path / json get str / json get int / json get boolNavigerar i nästlade objekt och listor via punktseparerade sökvägar ("a.b.0.c")

16.2.2. Komplett katalog över typer och signaturer i src/main/ridgun/lib/json.rg

Nedan följer hela förteckningen över algebraiska datatyper (type), mixfix-operatorer (op ::) och funktionssignaturer (fn ::) som definieras i src/main/ridgun/lib/json.rg (7 typer, 0 operatorer och 118 funktioner):

Algebraiska datatyper och poster (type) i 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
#Funktion / Signatur (fn ::)Returtyp
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. Djupdykning i json: Steg-för-steg-exempel för varje funktion

Det räcker inte med en signaturtabell: låt oss utforska varje funktionalitet i lib:en json genom progressiva, körbara Ridgun-exempel. Vi börjar med två tabellbaserade vardagsrecept och går därefter igenom alla funktionella scenarier, transformationer och gränsfall som verifieras i src/test/ridgun/test json.rg.

16.3.1. Exempel 1: Huvudflöde i tabellform med json

Lägg märke till hur =-tecknen och transformationsstegen med json linjeras som en ren tabell utan onödiga muterbara variabler eller dolda sidoeffekter:

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. Exempel 2: Säker komposition och hantering av gränsfall

Varje funktion i lib:en json är utformad för att kombineras sömlöst med booleska vakter | och med övriga 32 libs i 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. Exempel 3: AST-tolkning (json parse), kompakt serialisering (json stringify) och indenterad utskrift (json pretty)

I detta praktiska exempel undersöker vi grundligt hur json beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

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. Exempel 4: JSON-primitiver (null, bool, int, str), flyktsekvenser och tomma behållare ([], {})

I detta praktiska exempel undersöker vi grundligt hur json beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

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. Exempel 5: Djupa punktsökvägsfrågor (json query, json query str/int/bool) och oföränderlig uppdatering (json set field)

I detta praktiska exempel undersöker vi grundligt hur json beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

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. Exempel 6: Säker detektering av felformaterad JSON-indata (JsonErr) utan panik eller undantag

I detta praktiska exempel undersöker vi grundligt hur json beter sig inom detta funktionsområde, inklusive standardindata, parameterkombinationer och gränsvärden:

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. Under huven: hur json är byggd i ren Ridgun (src/main/ridgun/lib/json.rg)

En av de största fördelarna med ridgun-libs är att det inte finns några svarta lådor i C. Du kan öppna src/main/ridgun/lib/json.rg och läsa exakt hur varje operation är implementerad med mönstermatchning, | / |.-vaktträd och svansrekursion (TCO). Låt oss titta på nyckeldelarna i den faktiska implementationen:

16.4.1. Intern implementation: Whitespace & Character Helpers

Se hur json.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

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. Intern implementation: Integer Literal Parsing

Se hur json.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

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. Intern implementation: Serialization & Pretty-Printing

Se hur json.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

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. Intern implementation: Dot-Path Queries & Field Updates

Se hur json.rg deklarerar explicita fn :: ... => ...-kontrakt först och löser varje fall genom linjerade ekvationer och svansanrop:

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. Algoritmiska garantier, RMM1/RMM2-minne och de 5 Gyllene Reglerna för json

  • Algoritmisk garanti och säkerhet: Strikt avvisande av skräptecken efter rotvärdet (trailing garbage), fullständig hantering av escape-sekvenser och undantagsfri felrapportering med JnErr.
  • $O(1)$ stackkomplexitet med TCO: Alla rekursiva loopar i src/main/ridgun/lib/json.rg ligger i svansposition, så ridgunc kompilerar dem till inbyggda hopp utan stacktillväxt.
  • Deterministiskt minne (RMM1/RMM2): Noll skräpsamlarpauser; alla temporära strukturer frigörs deterministiskt när räckvidden lämnas.
  • Kontinuerlig verifiering: Statiskt kontrollerad med stable check och verifierad av den automatiserade testsviten src/test/ridgun/test json.rg.

De 5 Gyllene Reglerna när du programmerar med json

  1. Deklarera import json överst i filen och utnyttja att mellanslag och understreck är utbytbara så att koden läses som naturlig prosa.
  2. Linjera dina datapipelines med =: kedja samman json-anrop i namngivna mellansteg och låt stable format linjera kolumnerna.
  3. Utnyttja json:s totala funktioner i dina |-vakter: kontrollera returvärden eller packa upp ADT:er med mönstermatchning i stället för att anta att extern indata alltid är giltig.
  4. Undvik att kopiera stora strukturer när du bara läser ett fält: kom ihåg att i RMM2 medför inargument noll referensräkningskostnad (+0 / -0).
  5. Kombinera json med test (import test): lås alltid fast dina domäninvarianter med assert equals eller assert true.

I Kapitel 17 går vi vidare och utforskar lib:en csv på djupet (Lib:en csv — RFC 4180 CSV/TSV-Tillståndsmaskin och Kolumnindexerade Tabeller).