配置项一多,INI 很快就会遇到层级和数组不好表达的问题;JSON 虽然通用,但手写时对引号、逗号要求比较严格。我整理这个 YAML_v1 库,就是想给 AHK v1 留一个更适合读取结构化配置的选择。

这个版本基于 HotKeyIt/Yaml 和 thqby 的实现整理,并移植为 AutoHotkey v1 可用写法。它可以在 YAML 或 JSON 文本与 AHK 对象之间转换,对数字、数组、嵌套对象的处理比较直接,也补上了 truefalsenull 类型的保留方式。

这个库适合做什么

我更建议把它用在脚本配置、任务清单、接口返回数据、规则表和小型项目数据文件上。比如一个工具同时有服务器地址、开关、用户列表和多组参数,用 YAML 写出来比把所有内容挤进 INI 更容易维护。

它也能直接解析以 { 或 [ 开头的 JSON 文本,所以同一套调用方式可以兼顾 YAML 和 JSON。库本身不依赖额外 DLL,内部针对 32 位和 64 位 AHK 都准备了相应处理,不过它仍然是面向实际脚本配置的解析器,不应把它当成完整覆盖全部 YAML 规范的验证器。

两个主要入口

YAML.parse(text, mergedarray := 0, keepbooltype := false) 用来把文本解析成 AHK 对象。普通场景第二个参数保持为 0 即可;需要处理多文档内容时,可以传入数组收集结果。第三个参数设为 true 后,布尔值和 null 不会简单折叠成 1、0、空字符串,而是可以通过 YAML.trueYAML.falseYAML.null 判断。

YAML.stringify(obj, expandlevel := "", space := " ", unicode_escaped := false) 用来把 AHK 对象重新生成文本。默认输出适合阅读的多行 YAML;space 可以调整缩进字符串,最后一个参数可以控制是否把非 ASCII 字符转成 Unicode 转义。

布尔值和 null 为什么要单独说明

AHK v1 本身没有和 YAML 完全对应的布尔、null 类型。不开启类型保留时,truefalsenull 会分别得到 1、0、空字符串,这对多数开关判断已经够用,但重新序列化时可能分不清原值到底是布尔值还是普通数字。

如果配置需要往返读取和写回,我建议解析时把 keepbooltype 设为 true。判断时不要写成普通真假判断,而是和 YAML.trueYAML.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 配置文件来说,这个库的价值在于调用入口简单,并且能把常见的嵌套对象、数组、数字和布尔配置留在一套流程里。

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。