配置项一多,INI 很快就会遇到层级和数组不好表达的问题;JSON 虽然通用,但手写时对引号、逗号要求比较严格。我整理这个 YAML_v1 库,就是想给 AHK v1 留一个更适合读取结构化配置的选择。
这个版本基于 HotKeyIt/Yaml 和 thqby 的实现整理,并移植为 AutoHotkey v1 可用写法。它可以在 YAML 或 JSON 文本与 AHK 对象之间转换,对数字、数组、嵌套对象的处理比较直接,也补上了 true、false、null 类型的保留方式。
这个库适合做什么
我更建议把它用在脚本配置、任务清单、接口返回数据、规则表和小型项目数据文件上。比如一个工具同时有服务器地址、开关、用户列表和多组参数,用 YAML 写出来比把所有内容挤进 INI 更容易维护。
它也能直接解析以 { 或 [ 开头的 JSON 文本,所以同一套调用方式可以兼顾 YAML 和 JSON。库本身不依赖额外 DLL,内部针对 32 位和 64 位 AHK 都准备了相应处理,不过它仍然是面向实际脚本配置的解析器,不应把它当成完整覆盖全部 YAML 规范的验证器。
两个主要入口
YAML.parse(text, mergedarray := 0, keepbooltype := false) 用来把文本解析成 AHK 对象。普通场景第二个参数保持为 0 即可;需要处理多文档内容时,可以传入数组收集结果。第三个参数设为 true 后,布尔值和 null 不会简单折叠成 1、0、空字符串,而是可以通过 YAML.true、YAML.false、YAML.null 判断。
YAML.stringify(obj, expandlevel := "", space := " ", unicode_escaped := false) 用来把 AHK 对象重新生成文本。默认输出适合阅读的多行 YAML;space 可以调整缩进字符串,最后一个参数可以控制是否把非 ASCII 字符转成 Unicode 转义。
布尔值和 null 为什么要单独说明
AHK v1 本身没有和 YAML 完全对应的布尔、null 类型。不开启类型保留时,true、false、null 会分别得到 1、0、空字符串,这对多数开关判断已经够用,但重新序列化时可能分不清原值到底是布尔值还是普通数字。
如果配置需要往返读取和写回,我建议解析时把 keepbooltype 设为 true。判断时不要写成普通真假判断,而是和 YAML.true、YAML.false 比较,这样再次 stringify 时才能继续输出正确的 YAML 类型。
读取和保存配置文件
中文配置最常见的问题不是 YAML 语法,而是文件编码。读取和写入时都建议明确使用 UTF-8,例如 FileOpen(path, "r", "UTF-8") 和 FileOpen(path, "w", "UTF-8"),不要依赖系统默认 ANSI 编码。解析、文件创建和写入都放进 try/catch,配置缩进错误时会更容易定位。
下面先放完整库源码,再放一个从解析、修改到重新保存的完整示例。示例会在脚本目录生成 YAML_v1_生成的配置.yaml,测试前注意同名文件会被重新写入。
示例代码:YAML_v1_示例.ahk
库源码:YAML_v1.ahk
; @description: YAML/JSON格式字符串序列化和反序列化, 修改自[HotKeyIt/Yaml](https://github.com/HotKeyIt/Yaml)
; 修复了一些YAML解析的bug, 增加了对true/false/null类型的支持, 保留了数值的类型
; @author thqby, HotKeyIt (Original v2) / dbgba (Ported to v1)
; @date 2025/08/22
; @version 1.0.9
; @note Converted for AutoHotkey v1 compatibility by dbgba.
; Original source: https://github.com/thqby/ahk2_lib
; 备注:
; - 公共API: YAML.parse(text, mergedarray := 0, keepbooltype := false)
; - 公共API: YAML.stringify(obj, expandlevel := "", space := " ", unicode_escaped := false)
class YAML {
static null := ComObjParameter(1, 0)
static true := ComObjParameter(0xB, 1)
static false := ComObjParameter(0xB, 0)
static undefined := ""
parse(text, mergedarray := 0, keepbooltype := false) {
_true := keepbooltype ? YAML.true : 1
_false := keepbooltype ? YAML.false : 0
_null := keepbooltype ? YAML.null : ""
if (text = "")
return YAML._NewArray()
text := LTrim(text, " `t`n`r")
if InStr("[{", SubStr(text, 1, 1))
return YAML._PJ(text, mergedarray, _true, _false, _null)
return YAML._CY(text, mergedarray, _true, _false, _null)
}
stringify(obj, expandlevel := "", space := " ", unicode_escaped := false) {
if (expandlevel = "")
expandlevel := 100000
return Trim(YAML._CO(obj, expandlevel, 0, 0, space, unicode_escaped))
}
_CY(ByRef txt, Y := 0, _true := 1, _false := 0, _null := "") {
static undefined := ""
if (!IsObject(undefined))
undefined := YAML._Undefined()
NXTLN := YAML._NXTLN()
NewDoc := 0, _C := "", _CE := "", _S := "", _K := "", V := undefined
N := false, _L := "", _LL := "", K := "", O := "", VQ := "", h := "", VC := ""
_Q := "", _A := "", _T := "", _V := ""
txt .= Chr(0) Chr(0) Chr(0) Chr(0)
P := &txt
A := YAML._NewMap()
D := YAML._NewArray(), D.Push(0)
I := YAML._NewArray(), I.Push(0)
LS := 2
while (P) {
LP := P
P := DllCall(NXTLN, "ptr", P, "int", true, "cdecl ptr")
LF := StrGet(LP)
if (!P)
break
if (!IsObject(Y) && InStr(LF, "---") = 1)
continue
if (InStr(LF, "---") = 1) {
Y.Push("")
NEWDOC := 0
D[1] := 0
_L := _LL := O := _Q := _K := _S := _T := _V := ""
continue
}
if (InStr(LF, "...") = 1) {
NEWDOC := 1
continue
}
if (LF = "" || RegExMatch(LF, "^\s+$"))
continue
if (NEWDOC)
throw Exception("Document ended but new document not specified.", 0, LF)
if (RegExMatch(LF, "^\s*#") || InStr(LF, "``%") = 1)
continue
if (_C || (_S && SubStr(LF, 1, LL * LS) = YAML._CL(LL + 1)) || (!YAML._IsUndefined(V) && !(K && _.SEQ) && SubStr(LF, 1, LL * LS) = YAML._CL(LL + 1))) {
if (_Q && !_K) {
if (YAML._Len(D[L])) {
VC := D[L].Pop()
if IsObject(VC)
throw Exception("Malformed inline YAML string")
} else
VC := ""
_CE := LTrim(LF, "`t ")
D[L].Push(VC (VC ? (InStr(_S, "|") = 1 ? "`n" : " ") : "") _CE)
} else {
VC := D[L][K]
if IsObject(VC)
throw Exception("Malformed inline YAML string")
_CE := LTrim(LF, "`t ")
D[L][K] := VC (VC ? (InStr(_S, "|") = 1 ? "`n" : " ") : "") _CE
}
continue
} else if (_C && YAML._LastChar(_CE) != _C) {
errValue := _Q ? D[L][YAML._Len(D[L])] : D[L][K]
throw Exception("Unexpected character", 0, errValue)
} else
_C := ""
commentPat := ".*[`""" Chr(39) "].*\s\#.*[`""" Chr(39) "].*"
if ((CM := InStr(LF, " #")) && !RegExMatch(LF, commentPat))
LF := SubStr(LF, 1, CM - 1)
if (SubStr(LTrim(LF, " `t"), 1, 1) = ":")
throw Exception("Unexpected character.", 0, ":")
sq := Chr(39), dq := """"
linePat := "OS)^(?<LVL>\s+)?(?<SEQ>-\s)?(?<KEY>"
. dq ".*" dq "\s*:(\s|$)|" sq ".*" sq "\s*:(\s|$)|[^:" dq sq "\{\[]+\s*:(\s|$))?"
. "\s*(?<SCA>[\|\>][+-]?)?\s*(?<TYP>!!\w+)?\s*(?<AGET>\*[^\s\t]+)?\s*(?<ASET>&[^\s\t]+)?"
. "\s*(?<VAL>" dq ".+" dq "|" sq ".+" sq "|.+)?\s*$"
RegExMatch(LF, linePat, _)
L := LL := YAML._CS(_.LVL, LS)
Q := _.SEQ, K := _.KEY, S := _.SCA, T := SubStr(_.TYP, 3)
V := YAML._UQ(_.VAL, T != "", _true, _false, _null)
firstVal := SubStr(LTrim(_.VAL, " `t"), 1, 1)
lastVal := YAML._LastChar(RTrim(_.VAL, " `t"))
VQ := (firstVal = "'" && lastVal = "'") || (firstVal = """" && lastVal = """")
if (L > 1) {
if (LL = _LL)
L := _L
else if (LL > _LL)
I[LL] := L := _L + 1
else if (LL < _LL) {
if (!I[LL])
throw Exception("Indentation problem.", 0, LF)
else
L := I[LL] + (LL + 1 = _LL && Q != "" && YAML._Type(D[LL]) = "Map" && YAML._Type(D[_LL]) = "Array")
}
}
if (Trim(_.Value(0), " `t") = "-")
V := undefined, Q := "-"
else if (V = "" && _.VAL = "")
V := undefined
else if (!YAML._IsUndefined(V) && !Q) {
if (K = "")
K := V, V := undefined
}
if (!Q && YAML._LastChar(RTrim(K, " `t")) != ":") {
if (L > _L) {
D[_L][_K] := K
LL := _LL, L := _L, K := _K, Q := _Q, _S := ">"
continue
} else
throw Exception("Invalid key.", 0, LF)
} else if (K != "")
K := YAML._UQ(RTrim(K, ": "), true, _true, _false, _null)
if (_L = "")
loopStart := L + 1, loopEnd := YAML._Len(D)
else if (_L)
loopStart := L + 1, loopEnd := _L
else
loopStart := 1, loopEnd := 0
loopCount := loopEnd - loopStart + 1
if (loopCount > 0) {
Loop, %loopCount%
D[loopStart + A_Index - 1] := 0, I[loopStart + A_Index - 1] := 0
}
if (!VQ && _.VAL != "") {
_C := SubStr(LTrim(_.VAL, " `t"), 1, 1)
if !InStr(Chr(39) """", _C)
_C := ""
}
if (_L != L && !D[L]) {
if (L = 1) {
if IsObject(Y) {
lastDoc := Y[YAML._Len(Y)]
lastType := YAML._Type(lastDoc)
if (lastType != "String" && ((Q && lastType != "Array") || (!Q && lastType = "Array")))
throw Exception("Mapping Item and Sequence cannot be defined on the same level.", 0, LF)
else if (lastType = "String") {
root := Q ? YAML._NewArray() : YAML._NewMap()
Y[YAML._Len(Y)] := root
} else
root := lastDoc
D[L] := root
} else {
Y := YAML._NewArray()
root := Q ? YAML._NewArray() : YAML._NewMap()
Y.Push(root)
D[L] := root
}
} else if (!_Q && YAML._Type(D[L - 1][_K]) = (Q ? "Array" : "Map")) {
D[L] := D[L - 1][_K]
} else {
O := Q ? YAML._NewArray() : YAML._NewMap()
D[L] := O
if (_A)
A[_A] := O
if (_Q) {
if (N)
D[L - 1].Pop()
D[L - 1].Push(O)
} else
D[L - 1][_K] := O
O := ""
}
}
_A := ""
if (Q && K) {
O := YAML._NewMap()
D[L].Push(O)
L++
D[L] := O
Q := O := ""
LL += 1
}
if (Q && YAML._Type(D[L + 1]) = "Array" && YAML._Type(D[L]) != "Array")
L++
if ((Q && YAML._Type(D[L]) != "Array") || (!Q && YAML._Type(D[L]) = "Array")) {
if (Q && N && !D[L + 1] && YAML._Has(D[L], _K)) {
O := YAML._NewArray()
D[L][_K] := O
I[L] := LL
LL += 1
L++
D[L] := O
O := ""
} else if ((Q && YAML._Type(D[L + 1]) = "Array") || (!Q && YAML._Type(D[L + 1]) = "Map"))
L++
else
throw Exception("Mapping Item and Sequence cannot be defined on the same level,", 0, LF)
}
if (T = "binary") {
O := YAML._NewBuffer(StrLen(V) // 2), PBIN := O.Ptr
Loop, Parse, V
{
h .= A_LoopField
if (h != "" && !Mod(A_Index, 2)) {
NumPut("0x" h, PBIN + 0, A_Index / 2 - 1, "UChar")
h := ""
}
}
} else if (T = "set")
throw Exception("Tag 'set' is not supported")
else if (T = "int" || T = "float")
V := V + 0
else if (T = "str")
V := V ""
else if (T = "null")
V := _null
else if (T = "bool")
V := V = "true" ? _true : V = "false" ? _false : V
if (_.ASET)
A[_A := SubStr(_.ASET, 2)] := V
if (_.AGET)
V := A[SubStr(_.AGET, 2)]
else if YAML._IsComValue(V) {
} else if (!VQ && SubStr(LTrim(V, " `t"), 1, 1) = "{") {
O := YAML._ParseInlineMap(V, _true, _false, _null)
if (_A)
A[_A] := O
} else if (!VQ && SubStr(LTrim(V, " `t"), 1, 1) = "[") {
O := YAML._ParseInlineArray(V, _true, _false, _null)
if (_A)
A[_A] := O
}
N := false
if RegExMatch(_S, "^[>|]\+?$") {
if (YAML._Type(D[L]) = "Map")
D[L][_K] .= "`n"
else
D[L][YAML._Len(D[L])] .= "`n"
}
if (Q) {
if (!YAML._IsUndefined(V))
D[L].Push(IsObject(O) ? O : S ? "" : V)
else
N := true, D[L].Push(_null)
} else {
if IsObject(O)
setValue := O
else if YAML._Has(D[L], K)
setValue := D[L][K]
else if (S)
setValue := ""
else if YAML._IsUndefined(V)
N := true, setValue := _null
else
setValue := V
D[L][K] := setValue
}
if (!Q && !YAML._IsUndefined(V))
_L := L, _LL := LL, O := _Q := _K := _S := _T := _V := ""
else
_L := L, _LL := LL, _Q := Q, _K := K, _S := S, _T := T, _V := V, O := ""
}
if RegExMatch(_S, "^[>|]\+?$") {
if (YAML._Type(D[L]) = "Map")
D[L][K] .= "`n"
else
D[L][YAML._Len(D[L])] .= "`n"
}
if (IsObject(Y) && YAML._Type(Y[YAML._Len(Y)]) = "String")
Y.Pop()
return (SubStr(txt, 1, 3) != "---" && YAML._Len(Y) = 1) ? Y[1] : Y
}
_YO(O, P, L, NXTLN, _true, _false, _null) {
v := q := k := b := 0, key := "", val := "", lf := "", nl := ""
while (NumGet(P + 0, 0, "UShort") != 0) {
c := Chr(NumGet(P + 0, 0, "UShort"))
if (c = "`n" || (c = "`r" && 10 = NumGet(P + 2, 0, "UShort"))) {
if (q || k || (!v && !k) || SubStr(LTrim(StrGet(P + (c = "`n" ? 2 : 4)), " `t`r`n"), 1, 1) = "}") {
P += c = "`n" ? 2 : 4
continue
} else
throw Exception("Malformed inline YAML string", 0, StrGet(P - 6))
} else if (!q && (c = " " || c = A_Tab)) {
P += 2
continue
} else if (!v && (c = """" || c = "'")) {
q := c, v := 1, P += 2
continue
} else if (!v && k && (c = "[" || c = "{")) {
if (c = "[") {
child := YAML._NewArray()
O[key] := child
P := YAML._YA(child, P + 2, L, NXTLN, _true, _false, _null)
} else {
child := YAML._NewMap()
O[key] := child
P := YAML._YO(child, P + 2, L, NXTLN, _true, _false, _null)
}
key := "", k := 0
continue
} else if (v && !k && ((!q && c = ":") || (q && !b && q = c))) {
v := 0
key := q ? (InStr(key, "\") ? YAML._UC(key) : key) : Trim(key, " `t")
k := 1, q := 0, P += 2
continue
} else if (v && k && ((!q && c = ",") || (q && !b && q = c))) {
v := 0
O[key] := YAML._InlineValue(val, q, _true, _false, _null)
val := "", key := "", q := 0, k := 0, P += 2
continue
} else if (!q && c = "}") {
if (k && v)
O[key] := YAML._InlineValue(val, false, _true, _false, _null)
tp := DllCall(NXTLN, "ptr", P + 2, "int", false, "cdecl ptr")
lf := StrGet(P + 2)
if (tp && (NumGet(P + 2, 0, "UShort") = 0 || (nl := RegExMatch(lf, "^\s+?$")) || RegExMatch(lf, "^\s*[,\}\]]")))
return nl ? DllCall(NXTLN, "ptr", P + 2, "int", true, "cdecl ptr") : (lf ? P + 2 : (NumGet(P + 4, 0, "UShort") = 0 ? P + 6 : P + 4))
else if (!tp)
return NumGet(P + 4, 0, "UShort") = 0 ? P + 6 : P + 4
else
throw Exception("Malformed inline YAML string.", 0, StrGet(P))
} else if (!v && (c = "," || c = ":" || c = " " || c = "`t")) {
P += 2
continue
} else if (!v) {
if (!k)
key := c
else
val := c
b := (!b && c = "\")
v := 1, P += 2
continue
} else if (v) {
if (!k)
key .= c
else
val .= c
b := (!b && c = "\")
P += 2
continue
} else
throw Exception("Undefined")
}
return P
}
_ParseInlineMap(text, _true, _false, _null) {
O := YAML._NewMap()
text := LTrim(text, " `t") Chr(0) Chr(0) Chr(0) Chr(0)
YAML._YO(O, &text + 2, 0, YAML._NXTLN(), _true, _false, _null)
return O
}
_ParseInlineArray(text, _true, _false, _null) {
O := YAML._NewArray()
text := LTrim(text, " `t") Chr(0) Chr(0) Chr(0) Chr(0)
YAML._YA(O, &text + 2, 0, YAML._NXTLN(), _true, _false, _null)
return O
}
_YA(O, P, L, NXTLN, _true, _false, _null) {
s := "", v := q := c := b := tp := 0, lf := "", nl := "", quoted := false
while (NumGet(P + 0, 0, "UShort") != 0) {
c := Chr(NumGet(P + 0, 0, "UShort"))
if (c = "`n" || (c = "`r" && 10 = NumGet(P + 2, 0, "UShort"))) {
if (q || !v || SubStr(LTrim(StrGet(P + (c = "`n" ? 2 : 4)), " `t`r`n"), 1, 1) = "]") {
P += c = "`n" ? 2 : 4
continue
} else
throw Exception("Malformed inline YAML string.", 0, s "`n" StrGet(P - 6))
} else if (!q && (c = " " || c = A_Tab)) {
if (lf != "")
lf .= c
P += 2
continue
} else if (!v && (c = """" || c = "'")) {
q := c, quoted := true, v := 1, P += 2
continue
} else if (!v && (c = "[" || c = "{")) {
if (c = "[") {
child := YAML._NewArray()
O.Push(child)
P := YAML._YA(child, P + 2, L, NXTLN, _true, _false, _null)
} else {
child := YAML._NewMap()
O.Push(child)
P := YAML._YO(child, P + 2, L, NXTLN, _true, _false, _null)
}
lf := "", quoted := false
continue
} else if (v && !q && c = ",") {
v := 0
O.Push(YAML._InlineValue(lf, quoted, _true, _false, _null))
q := 0, quoted := false, lf := "", P += 2
continue
} else if (q && !b && c = q) {
q := "", P += 2
continue
} else if (!q && c = "]") {
if (v)
O.Push(YAML._InlineValue(lf, quoted, _true, _false, _null))
tp := DllCall(NXTLN, "ptr", P + 2, "int", false, "cdecl ptr")
lf := StrGet(P + 2)
if (tp && (NumGet(P + 2, 0, "UShort") = 0 || (nl := RegExMatch(lf, "^\s+?$")) || RegExMatch(lf, "^\s*[,\}\]]")))
return nl ? DllCall(NXTLN, "ptr", P + 2, "int", true, "cdecl ptr") : (lf ? P + 2 : (NumGet(P + 4, 0, "UShort") = 0 ? P + 6 : P + 4))
else if (!tp)
return NumGet(P + 4, 0, "UShort") = 0 ? P + 6 : P + 4
else if (RegExMatch(lf, "^\s*#")) {
P += 2 * StrLen(lf) + 2
continue
} else
throw Exception("Malformed inline YAML string.", 0, StrGet(P))
} else if (!v && InStr(", `t", c)) {
if (!q && c = ",")
O.Push(YAML.null)
P += 2
continue
} else if (!v) {
lf .= c, v := 1, quoted := false, b := c = "\", P += 2
continue
} else if (v && q != "") {
lf .= c, b := (!b && c = "\"), P += 2
continue
} else
throw Exception("Undefined")
}
return P
}
_PJ(ByRef S, Y, _true, _false, _null) {
NQ := "", LF := "", LP := 0, P := "", R := ""
A := InStr(S, "[") = 1
C := A ? YAML._NewArray() : YAML._NewMap()
D := YAML._NewArray(), D.Push(C)
S := LTrim(SubStr(S, 2), " `t`r`n"), L := 1, N := 0, V := "", K := ""
if IsObject(Y) {
Y.Push(C)
J := Y
} else {
J := YAML._NewArray()
J.Push(C)
}
Q := InStr(S, """") != 1
if (!Q)
S := LTrim(S, """")
Loop, Parse, S, `"
{
Q := NQ ? 1 : !Q
NQ := Q && RegExMatch(A_LoopField, "(^|[^\\])(\\\\)*\\$")
if (!Q) {
t := Trim(A_LoopField, " `t`r`n")
if (t = "," || (t = ":" && V := 1))
continue
else if (t && (InStr("{[]},:", SubStr(t, 1, 1)) || RegExMatch(t, "^-?\d*(\.\d*)?\s*[,\]\}]"))) {
Loop, Parse, t
{
if (N && N--)
continue
if InStr("`n`r `t", A_LoopField)
continue
else if InStr("{[", A_LoopField) {
if (!A && !V)
throw Exception("Malformed JSON - missing key.", 0, t)
C := A_LoopField = "[" ? YAML._NewArray() : YAML._NewMap()
if (A)
D[L].Push(C)
else
D[L][K] := C
L++
if (D.HasKey(L))
D[L] := C
else
D.Push(C)
V := "", A := YAML._Type(C) = "Array"
continue
} else if InStr("]}", A_LoopField) {
if (!A && V)
throw Exception("Malformed JSON - missing value.", 0, t)
else if (L = 0)
throw Exception("Malformed JSON - to many closing brackets.", 0, t)
else {
L--
C := L = 0 ? "" : D[L]
A := YAML._Type(C) = "Array"
}
} else if !(InStr(" `t`r,", A_LoopField) || (A_LoopField = ":" && V := 1)) {
if RegExMatch(SubStr(t, A_Index), "Om)^(null|false|true|-?\d+(\.\d*(e[-+]\d+)?)?)\s*[,}\]\r\n]", R) {
N := R.Len(0) - 2
RV := R.Value(1)
if (A)
C.Push(YAML._JsonAtom(RV, _true, _false, _null))
else if (V)
C[K] := YAML._JsonAtom(RV, _true, _false, _null), K := V := ""
else
throw Exception("Malformed JSON - missing key.", 0, t)
} else
throw Exception("Malformed JSON - unrecognized character-", 0, A_LoopField " in " t)
}
}
} else if InStr(t, ":") > 1
throw Exception("Malformed JSON - unrecognized character-", 0, SubStr(t, 1, 1) " in " t)
} else if (NQ) {
P .= A_LoopField """"
continue
} else if (A) {
LF := P A_LoopField
C.Push(InStr(LF, "\") ? YAML._UC(LF) : LF), P := ""
} else if (V) {
LF := P A_LoopField
C[K] := InStr(LF, "\") ? YAML._UC(LF) : LF, K := V := P := ""
} else {
LF := P A_LoopField
K := InStr(LF, "\") ? YAML._UC(LF) : LF, P := ""
}
}
return J[1]
}
_CO(O, J := 0, R := 0, Q := 0, space := " ", unicode_escaped := false) {
M1 := "{", M2 := "}", S1 := "[", S2 := "]", N := "`n", C := ",", S := "- ", E := "", K := ":"
OT := YAML._Type(O)
if (OT = "Array") {
D := (J < 1 && !R) ? S1 : ""
len := YAML._Len(O), idx := 0
for key, value in O {
idx++
VT := YAML._Type(value)
if (VT = "Buffer" && J > 0) {
VAL := "", PTR := value.Ptr
Loop, % value.size
VAL .= Format("{1:.2X}", NumGet(PTR + A_Index - 1, 0, "UChar"))
value := "!!binary " VAL, F := E
} else
F := VT = "Array" ? "S" : (VT = "Map" || VT = "Object") ? "M" : E
Z := (VT = "Array" && YAML._Len(value) = 0) ? "[]" : ((VT = "Map" || VT = "Object") && YAML._Count(value) = 0) ? "{}" : ""
if (J <= R) {
open := F = "S" ? S1 : F = "M" ? M1 : E
close := F = "S" ? S2 : F = "M" ? M2 : E
D .= (J + R < 0 ? "`n" YAML._CL2(R + 2, space) : "") (F ? (open (Z ? "" : YAML._CO(value, J, R + 1, F, space, unicode_escaped)) close) : YAML._ES(value, J, unicode_escaped, 0)) (idx = len ? E : C)
} else if (F) {
open := J <= (R + 1) ? (F = "S" ? S1 : M1) : E
close := J <= (R + 1) ? (F = "S" ? S2 : M2) : E
child := YAML._CO(value, J, R + 1, F, space, unicode_escaped)
if (F = "M")
child := LTrim(child, " `t`n")
D .= N YAML._CL2(R + 1, space) S (Z ? Z : open child close)
} else
D .= N YAML._CL2(R + 1, space) S YAML._ES(value, J, unicode_escaped, 2)
}
} else {
D := (J < 1 && !R) ? M1 : ""
cnt := YAML._Count(O), idx := 0
for key, value in O {
idx++
VT := YAML._Type(value)
if (VT = "Buffer" && J > 0) {
VAL := "", PTR := value.Ptr
Loop, % value.size
VAL .= Format("{1:.2X}", NumGet(PTR + A_Index - 1, 0, "UChar"))
value := "!!binary " VAL, F := E
} else
F := VT = "Array" ? "S" : (VT = "Map" || VT = "Object") ? "M" : E
Z := (VT = "Array" && YAML._Len(value) = 0) ? "[]" : ((VT = "Map" || VT = "Object") && YAML._Count(value) = 0) ? "{}" : ""
if (J <= R) {
open := F = "S" ? S1 : F = "M" ? M1 : E
close := F = "S" ? S2 : F = "M" ? M2 : E
D .= (J + R < 0 ? "`n" YAML._CL2(R + 2, space) : "") (Q = "S" && idx = 1 ? M1 : E) YAML._ES(key, J, unicode_escaped, 1) K (F ? (open (Z ? "" : YAML._CO(value, J, R + 1, F, space, unicode_escaped)) close) : YAML._ES(value, J, unicode_escaped, 0)) (Q = "S" && idx = cnt ? M2 : E) (J != 0 || R ? (idx = cnt ? E : C) : E)
} else if (F) {
open := J <= (R + 1) ? (F = "S" ? S1 : M1) : E
close := J <= (R + 1) ? (F = "S" ? S2 : M2) : E
D .= N YAML._CL2(R + 1, space) YAML._ES(key, J, unicode_escaped, 3) K " " (Z ? Z : open YAML._CO(value, J, R + 1, F, space, unicode_escaped) close)
} else
D .= N YAML._CL2(R + 1, space) YAML._ES(key, J, unicode_escaped, 3) K " " YAML._ES(value, J, unicode_escaped, 2)
if (J = 0 && !R)
D .= idx < cnt ? C : E
}
}
if (J < 0 && J + R < 0)
D .= "`n" YAML._CL2(R + 1, space)
if (R = 0)
D := LTrim(D, "`n") (J < 1 ? (OT = "Array" ? S2 : M2) : "")
return D
}
_ES(S, J := 1, U := false, K := -1) {
static ascii := ""
if !IsObject(ascii) {
ascii := {}
ascii["\"] := "\"
ascii["`a"] := "a"
ascii["`b"] := "b"
ascii["`t"] := "t"
ascii["`n"] := "n"
ascii["`v"] := "v"
ascii["`f"] := "f"
ascii["`r"] := "r"
ascii[""""] := """"
}
ST := YAML._Type(S)
if (ST = "Float") {
v := "", d := InStr(S, "e")
if (d)
v := SubStr(S, d), S := SubStr(S, 1, d - 1)
if ((StrLen(S) > 17) && (d := RegExMatch(S, "(99999+|00000+)\d{0,3}$")))
S := Round(S, YAML._Max(1, d - InStr(S, ".") - 1))
return S v
} else if (ST = "Integer")
return S
else if (ST = "String") {
v := ""
if (U && RegExMatch(S, "m)[\x{7F}-\x{7FFF}]")) {
Loop, Parse, S
v .= ascii.HasKey(A_LoopField) ? "\" ascii[A_LoopField] : Ord(A_LoopField) < 128 ? A_LoopField : "\u" Format("{1:.4X}", Ord(A_LoopField))
return """" v """"
} else if (J < 1) {
Loop, Parse, S
v .= ascii.HasKey(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField
return """" v """"
} else if (K < 2) {
escPat1 := "m)[\{\}\[\]`""" Chr(39) "\s:,]|^\s|\s$"
if RegExMatch(S, escPat1) {
Loop, Parse, S
v .= ascii.HasKey(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField
return """" v """"
}
} else {
escPat2 := "m)[\{\[`""" Chr(39) "\r\n]|:\s|,\s|\s#|^[\s#\-:>]|\s$"
if RegExMatch(S, escPat2) {
Loop, Parse, S
v .= ascii.HasKey(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField
return """" v """"
}
}
if (K = 2 && (YAML._IsNumber(S) || RegExMatch(S, "i)^(true|false|null)$")))
return """" S """"
return S = "" ? """""" : S
} else {
if (YAML._IsComValue(S)) {
if (S = YAML.true)
return "true"
if (S = YAML.false)
return "false"
}
return "null"
}
}
_UQ(S, K := false, _true := 1, _false := 0, _null := "") {
S := Trim(S, " `t")
first := SubStr(S, 1, 1), last := YAML._LastChar(S)
if (first = """" && last = """")
return InStr(S, "\") ? YAML._UC(SubStr(S, 2, -1)) : SubStr(S, 2, -1)
else if (first = "'" && last = "'")
return StrReplace(SubStr(S, 2, -1), "''", "'")
else if (K)
return S
else if YAML._IsNumber(S)
return S + 0
else if (S = "null")
return _null
else if (S = "true")
return _true
else if (S = "false")
return _false
return S
}
_UC(S, e := 1) {
static m := ""
if !IsObject(m) {
m := {}
m[""""] := """"
m["a"] := "`a"
m["b"] := "`b"
m["t"] := "`t"
m["n"] := "`n"
m["v"] := "`v"
m["f"] := "`f"
m["r"] := "`r"
m["e"] := Chr(0x1B)
m["N"] := Chr(0x85)
m["P"] := Chr(0x2029)
m["0"] := ""
m["L"] := Chr(0x2028)
m["_"] := Chr(0xA0)
}
v := ""
Loop, Parse, S, \
{
part := A_LoopField
if ((e := !e) && part = "")
v .= "\"
else if (!e)
v .= part
else {
c := SubStr(part, 1, 1)
if (c = "u" && RegExMatch(SubStr(part, 2, 4), "i)^[\da-f]{4}$"))
v .= Chr("0x" SubStr(part, 2, 4)) SubStr(part, 6)
else if (c = "x" && RegExMatch(SubStr(part, 2, 2), "i)^[\da-f]{2}$"))
v .= Chr("0x" SubStr(part, 2, 2)) SubStr(part, 4)
else if m.HasKey(c)
v .= m[c] SubStr(part, 2)
else
v .= "\" part
}
if (part != "")
e := !e
}
return v
}
_InlineValue(val, quoted := false, _true := 1, _false := 0, _null := "") {
if (quoted)
return InStr(val, "\") ? YAML._UC(val) : val
val := Trim(val, " `t")
if YAML._IsNumber(val)
return val + 0
if (val = "true")
return _true
if (val = "false")
return _false
if (val = "null")
return _null
return val
}
_JsonAtom(val, _true, _false, _null) {
if (val = "null")
return _null
if (val = "true")
return _true
if (val = "false")
return _false
if YAML._IsNumber(val)
return val + 0
return val
}
_NXTLN() {
static NXTLN := 0
if (NXTLN)
return NXTLN
_fun_ := A_PtrSize = 8 ? "SIXJdExED7cBZkWFwHUM60BIg8ECZkWFwHQkZkGD+Ap0IWZBg/gNRA+3QQJ142ZBg/gKdB9Ig8ECZkWFwHXjSInIw4XSdAUx0maJEUiNQQLDMcDDhdJ0BTHAZokBSI1BBMOQkJCQ" : "i0QkBIXAdEsPtxBmhdJ1CutBg8ACZoXSdDdmg/oKdCBmg/oND7dQAnXoZoP6CnQmg8ACZoXSdejzw422AAAAAItMJAiFyXQFMdJmiRCDwALD88MxwMOLTCQIhcl0BTHSZokQg8AEw5A="
_sz_ := 0
if (!DllCall("crypt32\CryptStringToBinary", "str", _fun_, "uint", 0, "uint", 1, "ptr", 0, "uint*", _sz_, "ptr", 0, "ptr", 0, "Int") || !_sz_)
throw Exception("CryptStringToBinary failed", -1)
NXTLN := DllCall("GlobalAlloc", "uint", 0, "ptr", _sz_, "ptr")
if (!NXTLN)
throw Exception("GlobalAlloc failed", -1)
if (!DllCall("crypt32\CryptStringToBinary", "str", _fun_, "uint", 0, "uint", 1, "ptr", NXTLN, "uint*", _sz_, "ptr", 0, "ptr", 0, "Int"))
throw Exception("CryptStringToBinary failed", -1)
if (!DllCall("VirtualProtect", "ptr", NXTLN, "ptr", _sz_, "uint", 0x40, "uint*", _op_ := 0, "Int"))
throw Exception("VirtualProtect failed", -1)
return NXTLN
}
_CS(s, x) {
return ((StrLen(s) * (SubStr(s, 1, 1) = A_Tab ? 2 : 1)) // x) + 1
}
_CL(i) {
s := "", count := i - 1
if (count > 0) {
Loop, %count%
s .= " "
}
return s
}
_CL2(i, space) {
s := "", count := space ? i - 1 : 0
if (count > 0) {
Loop, %count%
s .= space
}
return s
}
_LastChar(s) {
return SubStr(s, 0)
}
_IsNumber(v) {
if v is number
return true
return false
}
_Max(a, b) {
return a > b ? a : b
}
_IsComValue(v) {
if !IsObject(v)
return false
try vt := ComObjType(v)
catch
vt := ""
return vt != ""
}
_Undefined() {
static value := ""
if !IsObject(value)
value := new YAML.YAMLUndefinedMarker
return value
}
_IsUndefined(v) {
if !IsObject(v)
return false
try vt := ComObjType(v)
catch
vt := ""
if (vt != "")
return false
try cls := v.__Class
catch
cls := ""
return cls = "YAML.YAMLUndefinedMarker"
}
_Type(v) {
if IsObject(v) {
try vt := ComObjType(v)
catch
vt := ""
if (vt != "")
return "ComValue"
try cls := v.__Class
catch
cls := ""
if (cls = "YAML.YArray" || cls = "YArray")
return "Array"
if (cls = "YAML.YMap" || cls = "YMap")
return "Map"
if (cls = "YAML.Buffer" || cls = "Buffer")
return "Buffer"
if YAML._LooksArray(v)
return "Array"
return "Object"
}
if v is integer
return "Integer"
if v is float
return "Float"
return "String"
}
_LooksArray(obj) {
if !IsObject(obj)
return false
try max := obj.MaxIndex()
catch
return false
if (max = "")
return false
try count := obj.Count()
catch
count := max
if (count != max)
return false
Loop, %max%
if !obj.HasKey(A_Index)
return false
return true
}
_Len(obj) {
if !IsObject(obj)
return 0
try return obj.Length()
catch
{
try max := obj.MaxIndex()
catch
return 0
return max = "" ? 0 : max
}
}
_Count(obj) {
if !IsObject(obj)
return 0
try return obj.Count()
catch
return 0
}
_Has(obj, key) {
return IsObject(obj) && obj.HasKey(key)
}
_NewArray() {
return new YAML.YArray
}
_NewMap() {
return new YAML.YMap
}
_NewBuffer(size) {
return new YAML.Buffer(size)
}
class YArray {
Has(key) {
return this.HasKey(key)
}
}
class YMap {
Has(key) {
return this.HasKey(key)
}
}
class Buffer {
__New(size := 0) {
this.size := size
this.SetCapacity("_data", size)
this.Ptr := this.GetAddress("_data")
}
}
class YAMLUndefinedMarker {
}
}
使用时要留意的边界
YAML 对缩进很敏感,Tab、空格层级混用或同一级同时出现映射和序列,都可能直接抛出异常。对于外部下载的配置文件,先检查文件大小和来源,再交给脚本解析;解析成功也不代表里面的路径、网址或命令参数一定可信。
另外,库中明确不支持 !!set 标签。项目如果依赖复杂自定义标签、完整 Schema 验证或非常大的 YAML 文档,应选专门的 YAML 工具。对日常 AHK v1 配置文件来说,这个库的价值在于调用入口简单,并且能把常见的嵌套对象、数组、数字和布尔配置留在一套流程里。

评论(0)