Enn3DevPlayer

tinyyaml

Feb 12th, 2021 (edited)
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.64 KB | None | 0 0
  1. -------------------------------------------------------------------------------
  2. -- tinyyaml - YAML subset parser
  3. -------------------------------------------------------------------------------
  4.  
  5. -- original author: peposso
  6.  
  7. local table = table
  8. local string = string
  9. local schar = string.char
  10. local ssub, gsub = string.sub, string.gsub
  11. local sfind, smatch = string.find, string.match
  12. local tinsert, tremove = table.insert, table.remove
  13. local setmetatable = setmetatable
  14. local pairs = pairs
  15. local type = type
  16. local tonumber = tonumber
  17. local math = math
  18. local getmetatable = getmetatable
  19. local error = error
  20.  
  21. local UNESCAPES = {
  22.   ['0'] = "\x00", z = "\x00", N    = "\x85",
  23.   a = "\x07",     b = "\x08", t    = "\x09",
  24.   n = "\x0a",     v = "\x0b", f    = "\x0c",
  25.   r = "\x0d",     e = "\x1b", ['\\'] = '\\',
  26. };
  27.  
  28. -------------------------------------------------------------------------------
  29. -- utils
  30. local function select(list, pred)
  31.   local selected = {}
  32.   for i = 0, #list do
  33.     local v = list[i]
  34.     if v and pred(v, i) then
  35.       tinsert(selected, v)
  36.     end
  37.   end
  38.   return selected
  39. end
  40.  
  41. local function startswith(haystack, needle)
  42.   return ssub(haystack, 1, #needle) == needle
  43. end
  44.  
  45. local function ltrim(str)
  46.   return smatch(str, "^%s*(.-)$")
  47. end
  48.  
  49. local function rtrim(str)
  50.   return smatch(str, "^(.-)%s*$")
  51. end
  52.  
  53. -------------------------------------------------------------------------------
  54. -- Implementation.
  55. --
  56. local class = {__meta={}}
  57. function class.__meta.__call(cls, ...)
  58.   local self = setmetatable({}, cls)
  59.   if cls.__init then
  60.     cls.__init(self, ...)
  61.   end
  62.   return self
  63. end
  64.  
  65. function class.def(base, typ, cls)
  66.   base = base or class
  67.   local mt = {__metatable=base, __index=base}
  68.   for k, v in pairs(base.__meta) do mt[k] = v end
  69.   cls = setmetatable(cls or {}, mt)
  70.   cls.__index = cls
  71.   cls.__metatable = cls
  72.   cls.__type = typ
  73.   cls.__meta = mt
  74.   return cls
  75. end
  76.  
  77.  
  78. local types = {
  79.   null = class:def('null'),
  80.   map = class:def('map'),
  81.   omap = class:def('omap'),
  82.   pairs = class:def('pairs'),
  83.   set = class:def('set'),
  84.   seq = class:def('seq'),
  85.   timestamp = class:def('timestamp'),
  86. }
  87.  
  88. local Null = types.null
  89. function Null.__tostring() return 'yaml.null' end
  90. function Null.isnull(v)
  91.   if v == nil then return true end
  92.   if type(v) == 'table' and getmetatable(v) == Null then return true end
  93.   return false
  94. end
  95. local null = Null()
  96.  
  97. function types.timestamp:__init(y, m, d, h, i, s, f, z)
  98.   self.year = tonumber(y)
  99.   self.month = tonumber(m)
  100.   self.day = tonumber(d)
  101.   self.hour = tonumber(h or 0)
  102.   self.minute = tonumber(i or 0)
  103.   self.second = tonumber(s or 0)
  104.   if type(f) == 'string' and sfind(f, '^%d+$') then
  105.     self.fraction = tonumber(f) * math.pow(10, 3 - #f)
  106.   elseif f then
  107.     self.fraction = f
  108.   else
  109.     self.fraction = 0
  110.   end
  111.   self.timezone = z
  112. end
  113.  
  114. function types.timestamp:__tostring()
  115.   return string.format(
  116.     '%04d-%02d-%02dT%02d:%02d:%02d.%03d%s',
  117.     self.year, self.month, self.day,
  118.     self.hour, self.minute, self.second, self.fraction,
  119.     self:gettz())
  120. end
  121.  
  122. function types.timestamp:gettz()
  123.   if not self.timezone then
  124.     return ''
  125.   end
  126.   if self.timezone == 0 then
  127.     return 'Z'
  128.   end
  129.   local sign = self.timezone > 0
  130.   local z = sign and self.timezone or -self.timezone
  131.   local zh = math.floor(z)
  132.   local zi = (z - zh) * 60
  133.   return string.format(
  134.     '%s%02d:%02d', sign and '+' or '-', zh, zi)
  135. end
  136.  
  137.  
  138. local function countindent(line)
  139.   local _, j = sfind(line, '^%s+')
  140.   if not j then
  141.     return 0, line
  142.   end
  143.   return j, ssub(line, j+1)
  144. end
  145.  
  146. local function parsestring(line, stopper)
  147.   stopper = stopper or ''
  148.   local q = ssub(line, 1, 1)
  149.   if q == ' ' or q == '\t' then
  150.     return parsestring(ssub(line, 2))
  151.   end
  152.   if q == "'" then
  153.     local i = sfind(line, "'", 2, true)
  154.     if not i then
  155.       return nil, line
  156.     end
  157.     return ssub(line, 2, i-1), ssub(line, i+1)
  158.   end
  159.   if q == '"' then
  160.     local i, buf = 2, ''
  161.     while i < #line do
  162.       local c = ssub(line, i, i)
  163.       if c == '\\' then
  164.         local n = ssub(line, i+1, i+1)
  165.         if UNESCAPES[n] ~= nil then
  166.           buf = buf..UNESCAPES[n]
  167.         elseif n == 'x' then
  168.           local h = ssub(i+2,i+3)
  169.           if sfind(h, '^[0-9a-fA-F]$') then
  170.             buf = buf..schar(tonumber(h, 16))
  171.             i = i + 2
  172.           else
  173.             buf = buf..'x'
  174.           end
  175.         else
  176.           buf = buf..n
  177.         end
  178.         i = i + 1
  179.       elseif c == q then
  180.         break
  181.       else
  182.         buf = buf..c
  183.       end
  184.       i = i + 1
  185.     end
  186.     return buf, ssub(line, i+1)
  187.   end
  188.   if q == '{' or q == '[' then  -- flow style
  189.     return nil, line
  190.   end
  191.   if q == '|' or q == '>' then  -- block
  192.     return nil, line
  193.   end
  194.   if q == '-' or q == ':' then
  195.     if ssub(line, 2, 2) == ' ' or #line == 1 then
  196.       return nil, line
  197.     end
  198.   end
  199.   local buf = ''
  200.   while #line > 0 do
  201.     local c = ssub(line, 1, 1)
  202.     if sfind(stopper, c, 1, true) then
  203.       break
  204.     elseif c == ':' and (ssub(line, 2, 2) == ' ' or #line == 1) then
  205.       break
  206.     elseif c == '#' and (ssub(buf, #buf, #buf) == ' ') then
  207.       break
  208.     else
  209.       buf = buf..c
  210.     end
  211.     line = ssub(line, 2)
  212.   end
  213.   return rtrim(buf), line
  214. end
  215.  
  216. local function isemptyline(line)
  217.   return line == '' or sfind(line, '^%s*$') or sfind(line, '^%s*#')
  218. end
  219.  
  220. local function equalsline(line, needle)
  221.   return startswith(line, needle) and isemptyline(ssub(line, #needle+1))
  222. end
  223.  
  224. local function checkdupekey(map, key)
  225.   if map[key] ~= nil then
  226.     -- print("found a duplicate key '"..key.."' in line: "..line)
  227.     local suffix = 1
  228.     while map[key..'_'..suffix] do
  229.       suffix = suffix + 1
  230.     end
  231.     key = key ..'_'..suffix
  232.   end
  233.   return key
  234. end
  235.  
  236. local function parseflowstyle(line, lines)
  237.   local stack = {}
  238.   while true do
  239.     if #line == 0 then
  240.       if #lines == 0 then
  241.         break
  242.       else
  243.         line = tremove(lines, 1)
  244.       end
  245.     end
  246.     local c = ssub(line, 1, 1)
  247.     if c == '#' then
  248.       line = ''
  249.     elseif c == ' ' or c == '\t' or c == '\r' or c == '\n' then
  250.       line = ssub(line, 2)
  251.     elseif c == '{' or c == '[' then
  252.       tinsert(stack, {v={},t=c})
  253.       line = ssub(line, 2)
  254.     elseif c == ':' then
  255.       local s = tremove(stack)
  256.       tinsert(stack, {v=s.v, t=':'})
  257.       line = ssub(line, 2)
  258.     elseif c == ',' then
  259.       local value = tremove(stack)
  260.       if value.t == ':' or value.t == '{' or value.t == '[' then error() end
  261.       if stack[#stack].t == ':' then
  262.         -- map
  263.         local key = tremove(stack)
  264.         key.v = checkdupekey(stack[#stack].v, key.v)
  265.         stack[#stack].v[key.v] = value.v
  266.       elseif stack[#stack].t == '{' then
  267.         -- set
  268.         stack[#stack].v[value.v] = true
  269.       elseif stack[#stack].t == '[' then
  270.         -- seq
  271.         tinsert(stack[#stack].v, value.v)
  272.       end
  273.       line = ssub(line, 2)
  274.     elseif c == '}' then
  275.       if stack[#stack].t == '{' then
  276.         if #stack == 1 then break end
  277.         stack[#stack].t = '}'
  278.         line = ssub(line, 2)
  279.       else
  280.         line = ','..line
  281.       end
  282.     elseif c == ']' then
  283.       if stack[#stack].t == '[' then
  284.         if #stack == 1 then break end
  285.         stack[#stack].t = ']'
  286.         line = ssub(line, 2)
  287.       else
  288.         line = ','..line
  289.       end
  290.     else
  291.       local s, rest = parsestring(line, ',{}[]')
  292.       if not s then
  293.         error('invalid flowstyle line: '..line)
  294.       end
  295.       tinsert(stack, {v=s, t='s'})
  296.       line = rest
  297.     end
  298.   end
  299.   return stack[1].v, line
  300. end
  301.  
  302. local function parseblockstylestring(line, lines, indent)
  303.   if #lines == 0 then
  304.     error("failed to find multi-line scalar content")
  305.   end
  306.   local s = {}
  307.   local firstindent = -1
  308.   local endline = -1
  309.   for i = 1, #lines do
  310.     local ln = lines[i]
  311.     local idt = countindent(ln)
  312.     if idt <= indent then
  313.       break
  314.     end
  315.     if ln == '' then
  316.       tinsert(s, '')
  317.     else
  318.       if firstindent == -1 then
  319.         firstindent = idt
  320.       elseif idt < firstindent then
  321.         break
  322.       end
  323.       tinsert(s, ssub(ln, firstindent + 1))
  324.     end
  325.     endline = i
  326.   end
  327.  
  328.   local striptrailing = true
  329.   local sep = '\n'
  330.   local newlineatend = true
  331.   if line == '|' then
  332.     striptrailing = true
  333.     sep = '\n'
  334.     newlineatend = true
  335.   elseif line == '|+' then
  336.     striptrailing = false
  337.     sep = '\n'
  338.     newlineatend = true
  339.   elseif line == '|-' then
  340.     striptrailing = true
  341.     sep = '\n'
  342.     newlineatend = false
  343.   elseif line == '>' then
  344.     striptrailing = true
  345.     sep = ' '
  346.     newlineatend = true
  347.   elseif line == '>+' then
  348.     striptrailing = false
  349.     sep = ' '
  350.     newlineatend = true
  351.   elseif line == '>-' then
  352.     striptrailing = true
  353.     sep = ' '
  354.     newlineatend = false
  355.   else
  356.     error('invalid blockstyle string:'..line)
  357.   end
  358.   local eonl = 0
  359.   for i = #s, 1, -1 do
  360.     if s[i] == '' then
  361.       tremove(s, i)
  362.       eonl = eonl + 1
  363.     end
  364.   end
  365.   if striptrailing then
  366.     eonl = 0
  367.   end
  368.   if newlineatend then
  369.     eonl = eonl + 1
  370.   end
  371.   for i = endline, 1, -1 do
  372.     tremove(lines, i)
  373.   end
  374.   return table.concat(s, sep)..string.rep('\n', eonl)
  375. end
  376.  
  377. local function parsetimestamp(line)
  378.   local _, p1, y, m, d = sfind(line, '^(%d%d%d%d)%-(%d%d)%-(%d%d)')
  379.   if not p1 then
  380.     return nil, line
  381.   end
  382.   if p1 == #line then
  383.     return types.timestamp(y, m, d), ''
  384.   end
  385.   local _, p2, h, i, s = sfind(line, '^[Tt ](%d+):(%d+):(%d+)', p1+1)
  386.   if not p2 then
  387.     return types.timestamp(y, m, d), ssub(line, p1+1)
  388.   end
  389.   if p2 == #line then
  390.     return types.timestamp(y, m, d, h, i, s), ''
  391.   end
  392.   local _, p3, f = sfind(line, '^%.(%d+)', p2+1)
  393.   if not p3 then
  394.     p3 = p2
  395.     f = 0
  396.   end
  397.   local zc = ssub(line, p3+1, p3+1)
  398.   local _, p4, zs, z = sfind(line, '^ ?([%+%-])(%d+)', p3+1)
  399.   if p4 then
  400.     z = tonumber(z)
  401.     local _, p5, zi = sfind(line, '^:(%d+)', p4+1)
  402.     if p5 then
  403.       z = z + tonumber(zi) / 60
  404.     end
  405.     z = zs == '-' and -tonumber(z) or tonumber(z)
  406.   elseif zc == 'Z' then
  407.     p4 = p3 + 1
  408.     z = 0
  409.   else
  410.     p4 = p3
  411.     z = false
  412.   end
  413.   return types.timestamp(y, m, d, h, i, s, f, z), ssub(line, p4+1)
  414. end
  415.  
  416. local function parsescalar(line, lines, indent)
  417.   line = ltrim(line)
  418.   line = gsub(line, '^%s*#.*$', '')  -- comment only -> ''
  419.   line = gsub(line, '^%s*', '')  -- trim head spaces
  420.  
  421.   if line == '' or line == '~' then
  422.     return null
  423.   end
  424.  
  425.   local ts, _ = parsetimestamp(line)
  426.   if ts then
  427.     return ts
  428.   end
  429.  
  430.   local s, _ = parsestring(line)
  431.   -- startswith quote ... string
  432.   -- not startswith quote ... maybe string
  433.   if s and (startswith(line, '"') or startswith(line, "'")) then
  434.     return s
  435.   end
  436.  
  437.   if startswith('!', line) then  -- unexpected tagchar
  438.     error('unsupported line: '..line)
  439.   end
  440.  
  441.   if equalsline(line, '{}') then
  442.     return {}
  443.   end
  444.   if equalsline(line, '[]') then
  445.     return {}
  446.   end
  447.  
  448.   if startswith(line, '{') or startswith(line, '[') then
  449.     return parseflowstyle(line, lines)
  450.   end
  451.  
  452.   if startswith(line, '|') or startswith(line, '>') then
  453.     return parseblockstylestring(line, lines, indent)
  454.   end
  455.  
  456.   -- Regular unquoted string
  457.   line = gsub(line, '%s*#.*$', '')  -- trim tail comment
  458.   local v = line
  459.   if v == 'null' or v == 'Null' or v == 'NULL'then
  460.     return null
  461.   elseif v == 'true' or v == 'True' or v == 'TRUE' then
  462.     return true
  463.   elseif v == 'false' or v == 'False' or v == 'FALSE' then
  464.     return false
  465.   elseif v == '.inf' or v == '.Inf' or v == '.INF' then
  466.     return math.huge
  467.   elseif v == '+.inf' or v == '+.Inf' or v == '+.INF' then
  468.     return math.huge
  469.   elseif v == '-.inf' or v == '-.Inf' or v == '-.INF' then
  470.     return -math.huge
  471.   elseif v == '.nan' or v == '.NaN' or v == '.NAN' then
  472.     return 0 / 0
  473.   elseif sfind(v, '^[%+%-]?[0-9]+$') or sfind(v, '^[%+%-]?[0-9]+%.$')then
  474.     return tonumber(v)  -- : int
  475.   elseif sfind(v, '^[%+%-]?[0-9]+%.[0-9]+$') then
  476.     return tonumber(v)
  477.   end
  478.   return s or v
  479. end
  480.  
  481. local parsemap;  -- : func
  482.  
  483. local function parseseq(line, lines, indent)
  484.   local seq = setmetatable({}, types.seq)
  485.   if line ~= '' then
  486.     error()
  487.   end
  488.   while #lines > 0 do
  489.     -- Check for a new document
  490.     line = lines[1]
  491.     if startswith(line, '---') then
  492.       while #lines > 0 and not startswith(lines, '---') do
  493.         tremove(lines, 1)
  494.       end
  495.       return seq
  496.     end
  497.  
  498.     -- Check the indent level
  499.     local level = countindent(line)
  500.     if level < indent then
  501.       return seq
  502.     elseif level > indent then
  503.       error("found bad indenting in line: ".. line)
  504.     end
  505.  
  506.     local i, j = sfind(line, '%-%s+')
  507.     if not i then
  508.       i, j = sfind(line, '%-$')
  509.       if not i then
  510.         return seq
  511.       end
  512.     end
  513.     local rest = ssub(line, j+1)
  514.  
  515.     if sfind(rest, '^[^\'\"%s]*:') then
  516.       -- Inline nested hash
  517.       local indent2 = j
  518.       lines[1] = string.rep(' ', indent2)..rest
  519.       tinsert(seq, parsemap('', lines, indent2))
  520.     elseif sfind(rest, '^%-%s+') then
  521.       -- Inline nested seq
  522.       local indent2 = j
  523.       lines[1] = string.rep(' ', indent2)..rest
  524.       tinsert(seq, parseseq('', lines, indent2))
  525.     elseif isemptyline(rest) then
  526.       tremove(lines, 1)
  527.       if #lines == 0 then
  528.         tinsert(seq, null)
  529.         return seq
  530.       end
  531.       if sfind(lines[1], '^%s*%-') then
  532.         local nextline = lines[1]
  533.         local indent2 = countindent(nextline)
  534.         if indent2 == indent then
  535.           -- Null seqay entry
  536.           tinsert(seq, null)
  537.         else
  538.           tinsert(seq, parseseq('', lines, indent2))
  539.         end
  540.       else
  541.         -- - # comment
  542.         --   key: value
  543.         local nextline = lines[1]
  544.         local indent2 = countindent(nextline)
  545.         tinsert(seq, parsemap('', lines, indent2))
  546.       end
  547.     elseif rest then
  548.       -- Array entry with a value
  549.       tremove(lines, 1)
  550.       tinsert(seq, parsescalar(rest, lines))
  551.     end
  552.   end
  553.   return seq
  554. end
  555.  
  556. local function parseset(line, lines, indent)
  557.   if not isemptyline(line) then
  558.     error('not seq line: '..line)
  559.   end
  560.   local set = setmetatable({}, types.set)
  561.   while #lines > 0 do
  562.     -- Check for a new document
  563.     line = lines[1]
  564.     if startswith(line, '---') then
  565.       while #lines > 0 and not startswith(lines, '---') do
  566.         tremove(lines, 1)
  567.       end
  568.       return set
  569.     end
  570.  
  571.     -- Check the indent level
  572.     local level = countindent(line)
  573.     if level < indent then
  574.       return set
  575.     elseif level > indent then
  576.       error("found bad indenting in line: ".. line)
  577.     end
  578.  
  579.     local i, j = sfind(line, '%?%s+')
  580.     if not i then
  581.       i, j = sfind(line, '%?$')
  582.       if not i then
  583.         return set
  584.       end
  585.     end
  586.     local rest = ssub(line, j+1)
  587.  
  588.     if sfind(rest, '^[^\'\"%s]*:') then
  589.       -- Inline nested hash
  590.       local indent2 = j
  591.       lines[1] = string.rep(' ', indent2)..rest
  592.       set[parsemap('', lines, indent2)] = true
  593.     elseif sfind(rest, '^%s+$') then
  594.       tremove(lines, 1)
  595.       if #lines == 0 then
  596.         tinsert(set, null)
  597.         return set
  598.       end
  599.       if sfind(lines[1], '^%s*%?') then
  600.         local indent2 = countindent(lines[1])
  601.         if indent2 == indent then
  602.           -- Null array entry
  603.           set[null] = true
  604.         else
  605.           set[parseseq('', lines, indent2)] = true
  606.         end
  607.       end
  608.  
  609.     elseif rest then
  610.       tremove(lines, 1)
  611.       set[parsescalar(rest, lines)] = true
  612.     else
  613.       error("failed to classify line: "..line)
  614.     end
  615.   end
  616.   return set
  617. end
  618.  
  619. function parsemap(line, lines, indent)
  620.   if not isemptyline(line) then
  621.     error('not map line: '..line)
  622.   end
  623.   local map = setmetatable({}, types.map)
  624.   while #lines > 0 do
  625.     -- Check for a new document
  626.     line = lines[1]
  627.     if startswith(line, '---') then
  628.       while #lines > 0 and not startswith(lines, '---') do
  629.         tremove(lines, 1)
  630.       end
  631.       return map
  632.     end
  633.  
  634.     -- Check the indent level
  635.     local level, _ = countindent(line)
  636.     if level < indent then
  637.       return map
  638.     elseif level > indent then
  639.       error("found bad indenting in line: ".. line)
  640.     end
  641.  
  642.     -- Find the key
  643.     local key
  644.     local s, rest = parsestring(line)
  645.  
  646.     -- Quoted keys
  647.     if s and startswith(rest, ':') then
  648.       local sc = parsescalar(s, {}, 0)
  649.       if sc and type(sc) ~= 'string' then
  650.         key = sc
  651.       else
  652.         key = s
  653.       end
  654.       line = ssub(rest, 2)
  655.     else
  656.       error("failed to classify line: "..line)
  657.     end
  658.  
  659.     key = checkdupekey(map, key)
  660.     line = ltrim(line)
  661.  
  662.     if ssub(line, 1, 1) == '!' then
  663.       -- ignore type
  664.       local rh = ltrim(ssub(line, 3))
  665.       local typename = smatch(rh, '^!?[^%s]+')
  666.       line = ltrim(ssub(rh, #typename+1))
  667.     end
  668.  
  669.     if not isemptyline(line) then
  670.       tremove(lines, 1)
  671.       line = ltrim(line)
  672.       map[key] = parsescalar(line, lines, indent)
  673.     else
  674.       -- An indent
  675.       tremove(lines, 1)
  676.       if #lines == 0 then
  677.         map[key] = null
  678.         return map;
  679.       end
  680.       if sfind(lines[1], '^%s*%-') then
  681.         local indent2 = countindent(lines[1])
  682.         map[key] = parseseq('', lines, indent2)
  683.       elseif sfind(lines[1], '^%s*%?') then
  684.         local indent2 = countindent(lines[1])
  685.         map[key] = parseset('', lines, indent2)
  686.       else
  687.         local indent2 = countindent(lines[1])
  688.         if indent >= indent2 then
  689.           -- Null hash entry
  690.           map[key] = null
  691.         else
  692.           map[key] = parsemap('', lines, indent2)
  693.         end
  694.       end
  695.     end
  696.   end
  697.   return map
  698. end
  699.  
  700.  
  701. -- : (list<str>)->dict
  702. local function parsedocuments(lines)
  703.   lines = select(lines, function(s) return not isemptyline(s) end)
  704.  
  705.   if sfind(lines[1], '^%%YAML') then tremove(lines, 1) end
  706.  
  707.   local root = {}
  708.   local in_document = false
  709.   while #lines > 0 do
  710.     local line = lines[1]
  711.     -- Do we have a document header?
  712.     local docright;
  713.     if sfind(line, '^%-%-%-') then
  714.       -- Handle scalar documents
  715.       docright = ssub(line, 4)
  716.       tremove(lines, 1)
  717.       in_document = true
  718.     end
  719.     if docright then
  720.       if (not sfind(docright, '^%s+$') and
  721.           not sfind(docright, '^%s+#')) then
  722.         tinsert(root, parsescalar(docright, lines))
  723.       end
  724.     elseif #lines == 0 or startswith(line, '---') then
  725.       -- A naked document
  726.       tinsert(root, null)
  727.       while #lines > 0 and not sfind(lines[1], '---') do
  728.         tremove(lines, 1)
  729.       end
  730.       in_document = false
  731.     -- XXX The final '-+$' is to look for -- which ends up being an
  732.     -- error later.
  733.     elseif not in_document and #root > 0 then
  734.       -- only the first document can be explicit
  735.       error('parse error: '..line)
  736.     elseif sfind(line, '^%s*%-') then
  737.       -- An array at the root
  738.       tinsert(root, parseseq('', lines, 0))
  739.     elseif sfind(line, '^%s*[^%s]') then
  740.       -- A hash at the root
  741.       local level = countindent(line)
  742.       tinsert(root, parsemap('', lines, level))
  743.     else
  744.       -- Shouldn't get here.  @lines have whitespace-only lines
  745.       -- stripped, and previous match is a line with any
  746.       -- non-whitespace.  So this clause should only be reachable via
  747.       -- a perlbug where \s is not symmetric with \S
  748.  
  749.       -- uncoverable statement
  750.       error('parse error: '..line)
  751.     end
  752.   end
  753.   if #root > 1 and Null.isnull(root[1]) then
  754.     tremove(root, 1)
  755.     return root
  756.   end
  757.   return root
  758. end
  759.  
  760. --- Parse yaml string into table.
  761. local function parse(source)
  762.   local lines = {}
  763.   for line in string.gmatch(source .. '\n', '(.-)\r?\n') do
  764.     tinsert(lines, line)
  765.   end
  766.  
  767.   local docs = parsedocuments(lines)
  768.   if #docs == 1 then
  769.     return docs[1]
  770.   end
  771.  
  772.   return docs
  773. end
  774.  
  775. return {
  776.   version = 0.1,
  777.   parse = parse,
  778. }
Add Comment
Please, Sign In to add comment