; Json.ahk — Petit parseur / sérialiseur JSON autonome pour PromptKey. ; Utilisé pour usage.json, index-cache.json et l'import/export (architecture §15, §16.1). ; Types : Map -> objet, Array -> tableau, String, Integer/Float, 1/0 (les booléens ; AHK sont des entiers ; à la sérialisation, true/false sont émis comme true/false ; uniquement pour les valeurs stockées via Json.True / Json.False — sinon nombres). class Json { static Parse(text) { p := _JsonParser(text) v := p.ParseValue() p.SkipWs() return v } static Stringify(value, indent := 0) { pad := indent > 0 ? StrRepeat(" ", indent) : "" padIn := indent > 0 ? StrRepeat(" ", indent * 2) : "" nl := indent > 0 ? "`n" : "" if (value is Map) { if (value.Count = 0) return "{}" parts := [] for k, v in value { parts.Push(padIn Json.EscapeString(String(k)) ": " Json.Stringify(v, indent)) } return "{" nl Join(parts, "," nl) nl pad "}" } if (value is Array) { if (value.Length = 0) return "[]" parts := [] for v in value parts.Push(Json.Stringify(v, indent)) return "[" Join(parts, ", ") "]" } if (value is String) return Json.EscapeString(value) if (value is Integer || value is Float) return String(value) if (value = "") return '""' return Json.EscapeString(String(value)) } static EscapeString(s) { s := StrReplace(s, "\", "\\") s := StrReplace(s, '"', '\"') s := StrReplace(s, "`n", "\n") s := StrReplace(s, "`r", "\r") s := StrReplace(s, "`t", "\t") return '"' s '"' } } StrRepeat(ch, n) { out := "" loop n out .= ch return out } Join(arr, sep) { out := "" for i, v in arr { if (i > 1) out .= sep out .= v } return out } class _JsonParser { __New(text) { this.Text := text this.Pos := 1 this.Len := StrLen(text) } SkipWs() { while (this.Pos <= this.Len) { c := SubStr(this.Text, this.Pos, 1) if (c = " " || c = "`t" || c = "`n" || c = "`r") this.Pos++ else break } } Peek() { return this.Pos <= this.Len ? SubStr(this.Text, this.Pos, 1) : "" } ParseValue() { this.SkipWs() c := this.Peek() if (c = "{") return this.ParseObject() if (c = "[") return this.ParseArray() if (c = '"') return this.ParseString() if (c = "t") { this.Pos += 4 ; true return 1 } if (c = "f") { this.Pos += 5 ; false return 0 } if (c = "n") { this.Pos += 4 ; null return "" } return this.ParseNumber() } ParseObject() { obj := Map() this.Pos++ ; { this.SkipWs() if (this.Peek() = "}") { this.Pos++ return obj } loop { this.SkipWs() key := this.ParseString() this.SkipWs() this.Pos++ ; : obj[key] := this.ParseValue() this.SkipWs() c := this.Peek() this.Pos++ if (c = "}") break } return obj } ParseArray() { arr := [] this.Pos++ ; [ this.SkipWs() if (this.Peek() = "]") { this.Pos++ return arr } loop { arr.Push(this.ParseValue()) this.SkipWs() c := this.Peek() this.Pos++ if (c = "]") break } return arr } ParseString() { out := "" this.Pos++ ; guillemet ouvrant while (this.Pos <= this.Len) { c := SubStr(this.Text, this.Pos, 1) this.Pos++ if (c = '"') break if (c = "\") { e := SubStr(this.Text, this.Pos, 1) this.Pos++ switch e { case "n": out .= "`n" case "r": out .= "`r" case "t": out .= "`t" case "b": out .= Chr(8) case "f": out .= Chr(12) case "/": out .= "/" case "\": out .= "\" case '"': out .= '"' case "u": hex := SubStr(this.Text, this.Pos, 4) this.Pos += 4 out .= Chr("0x" hex) default: out .= e } } else { out .= c } } return out } ParseNumber() { start := this.Pos while (this.Pos <= this.Len && RegExMatch(SubStr(this.Text, this.Pos, 1), "[0-9eE+\-.]")) this.Pos++ raw := SubStr(this.Text, start, this.Pos - start) if (InStr(raw, ".") || InStr(raw, "e") || InStr(raw, "E")) return Float(raw) return Integer(raw) } }