toml.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. local TOML = {
  2. -- denotes the current supported TOML version
  3. version = 0.40,
  4. -- sets whether the parser should follow the TOML spec strictly
  5. -- currently, no errors are thrown for the following rules if strictness is turned off:
  6. -- tables having mixed keys
  7. -- redefining a table
  8. -- redefining a key within a table
  9. strict = true,
  10. }
  11. local date_metatable = {
  12. __tostring = function( t )
  13. local rep = ''
  14. if t.year then
  15. rep = rep .. string.format("%04d-%02d-%02d", t.year, t.month, t.day)
  16. end
  17. if t.hour then
  18. if t.year then
  19. rep = rep .. ' '
  20. end
  21. rep = rep .. string.format("%02d:%02d:", t.hour, t.min)
  22. local sec, frac = math.modf( t.sec )
  23. rep = rep .. string.format("%02d", sec)
  24. if frac > 0 then
  25. rep = rep .. tostring(frac):gsub("0(.-)0*$","%1")
  26. end
  27. end
  28. if t.zone then
  29. if t.zone >= 0 then
  30. rep = rep .. '+' .. string.format("%02d:00", t.zone)
  31. elseif t.zone < 0 then
  32. rep = rep .. '-' .. string.format("%02d:00", -t.zone)
  33. end
  34. end
  35. return rep
  36. end,
  37. }
  38. local setmetatable, getmetatable = setmetatable, getmetatable
  39. TOML.datefy = function( tab )
  40. -- TODO : VALIDATE !
  41. return setmetatable(tab, date_metatable)
  42. end
  43. TOML.isdate = function( tab )
  44. return getmetatable( tab ) == date_metatable
  45. end
  46. -- converts TOML data into a lua table
  47. TOML.multistep_parser = function (options)
  48. options = options or {}
  49. local strict = (options.strict ~= nil and options.strict or TOML.strict)
  50. local toml = ''
  51. -- the output table
  52. local out = {}
  53. local ERR = {}
  54. -- the current table to write to
  55. local obj = out
  56. -- stores text data
  57. local buffer = ""
  58. -- the current location within the string to parse
  59. local cursor = 1
  60. -- remember that the last chunk was already read
  61. local stream_ended = false
  62. local nl_count = 1
  63. local function result_or_error()
  64. if #ERR > 0 then return nil, table.concat(ERR) end
  65. return out
  66. end
  67. -- produce a parsing error message
  68. -- the error contains the line number of the current position
  69. local function err(message, strictOnly)
  70. if not strictOnly or (strictOnly and strict) then
  71. local line = 1
  72. local c = 0
  73. local msg = "At TOML line " .. nl_count .. ': ' .. message .. "."
  74. if not ERR[msg] then
  75. ERR[1+#ERR] = msg
  76. ERR[msg] = true
  77. end
  78. end
  79. end
  80. -- read n characters (at least) or chunk terminator (nil)
  81. local function getNewData(n)
  82. while not stream_ended do
  83. if cursor + (n or 0) < #toml then break end
  84. local new_data = coroutine.yield(result_or_error())
  85. if new_data == nil then
  86. stream_ended = true
  87. break
  88. end
  89. toml = toml:sub(cursor)
  90. cursor = 1
  91. toml = toml .. new_data
  92. end
  93. end
  94. -- TODO : use 1-based indexing ?
  95. -- returns the next n characters from the current position
  96. local function getData( a, b )
  97. getNewData(b)
  98. a = a or 0
  99. b = b or (toml:len() - cursor)
  100. return toml:sub( cursor + a, cursor + b )
  101. end
  102. -- count how many new lines are in the next n chars
  103. local function count_source_line(n)
  104. local count = 0
  105. for _ in getData(0, n-1):gmatch('\n') do
  106. count = count + 1
  107. end
  108. return count
  109. end
  110. -- moves the current position forward n (default: 1) characters
  111. local function step(n)
  112. n = n or 1
  113. nl_count = nl_count + count_source_line(n)
  114. cursor = cursor + n
  115. end
  116. -- prevent infinite loops by checking whether the cursor is
  117. -- at the end of the document or not
  118. local function bounds()
  119. if cursor <= toml:len() then return true end
  120. getNewData(1)
  121. return cursor <= toml:len()
  122. end
  123. -- Check if we are at end of the data
  124. local function dataEnd()
  125. return cursor >= toml:len()
  126. end
  127. -- returns the next n characters from the current position
  128. local function char(n)
  129. n = n or 0
  130. return getData(n, n)
  131. end
  132. -- Match official TOML definition of whitespace
  133. local function matchWs(n)
  134. n = n or 0
  135. return getData(n,n):match("[\009\032]")
  136. end
  137. -- Match the official TOML definition of newline
  138. local function matchnl(n)
  139. n = n or 0
  140. local c = getData(n,n)
  141. if c == '\10' then return '\10' end
  142. return getData(n,n+1):match("^\13\10")
  143. end
  144. -- move forward until the next non-whitespace character
  145. local function skipWhitespace()
  146. while(matchWs()) do
  147. step()
  148. end
  149. end
  150. -- remove the (Lua) whitespace at the beginning and end of a string
  151. local function trim(str)
  152. return str:gsub("^%s*(.-)%s*$", "%1")
  153. end
  154. -- divide a string into a table around a delimiter
  155. local function split(str, delim)
  156. if str == "" then return {} end
  157. local result = {}
  158. local append = delim
  159. if delim:match("%%") then
  160. append = delim:gsub("%%", "")
  161. end
  162. for match in (str .. append):gmatch("(.-)" .. delim) do
  163. table.insert(result, match)
  164. end
  165. return result
  166. end
  167. local function parseString()
  168. local quoteType = char() -- should be single or double quote
  169. -- this is a multiline string if the next 2 characters match
  170. local multiline = (char(1) == char(2) and char(1) == char())
  171. -- buffer to hold the string
  172. local str = ""
  173. -- skip the quotes
  174. step(multiline and 3 or 1)
  175. while(bounds()) do
  176. if multiline and matchnl() and str == "" then
  177. -- skip line break line at the beginning of multiline string
  178. step()
  179. end
  180. -- keep going until we encounter the quote character again
  181. if char() == quoteType then
  182. if multiline then
  183. if char(1) == char(2) and char(1) == quoteType then
  184. step(3)
  185. break
  186. end
  187. else
  188. step()
  189. break
  190. end
  191. end
  192. if matchnl() and not multiline then
  193. err("Single-line string cannot contain line break")
  194. end
  195. -- if we're in a double-quoted string, watch for escape characters!
  196. if quoteType == '"' and char() == "\\" then
  197. if multiline and matchnl(1) then
  198. -- skip until first non-whitespace character
  199. step(1) -- go past the line break
  200. while(bounds()) do
  201. if not matchWs() and not matchnl() then
  202. break
  203. end
  204. step()
  205. end
  206. else
  207. -- all available escape characters
  208. local escape = {
  209. b = "\b",
  210. t = "\t",
  211. n = "\n",
  212. f = "\f",
  213. r = "\r",
  214. ['"'] = '"',
  215. ["\\"] = "\\",
  216. }
  217. -- utf function from http://stackoverflow.com/a/26071044
  218. -- converts \uXXX into actual unicode
  219. local function utf(char)
  220. local bytemarkers = {{0x7ff, 192}, {0xffff, 224}, {0x1fffff, 240}}
  221. if char < 128 then return string.char(char) end
  222. local charbytes = {}
  223. for bytes, vals in pairs(bytemarkers) do
  224. if char <= vals[1] then
  225. for b = bytes + 1, 2, -1 do
  226. local mod = char % 64
  227. char = (char - mod) / 64
  228. charbytes[b] = string.char(128 + mod)
  229. end
  230. charbytes[1] = string.char(vals[2] + char)
  231. break
  232. end
  233. end
  234. return table.concat(charbytes)
  235. end
  236. if escape[char(1)] then
  237. -- normal escape
  238. str = str .. escape[char(1)]
  239. step(2) -- go past backslash and the character
  240. elseif char(1) == "u" then
  241. -- utf-16
  242. step()
  243. local uni = char(1) .. char(2) .. char(3) .. char(4)
  244. step(5)
  245. uni = tonumber(uni, 16)
  246. if not uni then
  247. err("Unicode escape is not a Unicode scalar")
  248. elseif (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
  249. str = str .. utf(uni)
  250. else
  251. err("Unicode escape is not a Unicode scalar")
  252. end
  253. elseif char(1) == "U" then
  254. -- utf-32
  255. step()
  256. local uni = char(1) .. char(2) .. char(3) .. char(4) .. char(5) .. char(6) .. char(7) .. char(8)
  257. step(9)
  258. uni = tonumber(uni, 16)
  259. if (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
  260. str = str .. utf(uni)
  261. else
  262. err("Unicode escape is not a Unicode scalar")
  263. end
  264. else
  265. err("Invalid escape")
  266. step()
  267. end
  268. end
  269. else
  270. -- if we're not in a double-quoted string, just append it to our buffer raw and keep going
  271. str = str .. char()
  272. step()
  273. end
  274. end
  275. return {value = str, type = "string"}
  276. end
  277. local function matchDate()
  278. local year, month, day, n =
  279. getData(0, 10):match('^(%d%d%d%d)%-([0-1][0-9])%-([0-3][0-9])()')
  280. if not year then return nil end
  281. step(n-1)
  282. return year, month, day
  283. end
  284. local function matchTime()
  285. local hour, minute, second, n =
  286. getData(0, 19):match('^([0-2][0-9])%:([0-6][0-9])%:(%d+%.?%d*)()')
  287. if not hour then return nil end
  288. step(n-1)
  289. return hour, minute, second
  290. end
  291. local function matchTimezone()
  292. local eastwest, offset, zero, n =
  293. getData(0, 6):match('^([%+%-])([0-9][0-9])%:([0-9][0-9])()')
  294. if not eastwest then return nil end
  295. step(n-1)
  296. return eastwest .. offset
  297. end
  298. local function parseDate()
  299. local year, month, day = matchDate()
  300. if not year then err("Invalid date") end
  301. local hour, minute, second = '', '', '', ''
  302. local date_time_separator = false
  303. if char():match('[T ]') then
  304. step(1)
  305. date_time_separator = true
  306. end
  307. local n
  308. if date_time_separator then
  309. hour, minute, second, n = matchTime()
  310. if not hour then err("Invalid date") end
  311. end
  312. local zone
  313. if char():match('Z') then
  314. step(1)
  315. zone = 0
  316. else
  317. local timezone = matchTimezone()
  318. if timezone then
  319. zone = tonumber(timezone)
  320. end
  321. end
  322. local value = {
  323. year = tonumber(year),
  324. month = tonumber(month),
  325. day = tonumber(day),
  326. hour = tonumber(hour),
  327. min = tonumber(minute),
  328. sec = tonumber(second),
  329. zone = zone,
  330. }
  331. local e
  332. value, e = TOML.datefy(value)
  333. if not value then
  334. err(e)
  335. end
  336. return {
  337. type = "date",
  338. value = value,
  339. }
  340. end
  341. local function parseTime()
  342. local hour, minute, second, n = matchTime()
  343. if not hour then err("Invalid date") end
  344. local value = {
  345. hour = tonumber(hour),
  346. min = tonumber(minute),
  347. sec = tonumber(second),
  348. }
  349. local value, e = TOML.datefy(value)
  350. if not value then err(e) end
  351. return {
  352. type = "date",
  353. value = value,
  354. }
  355. end
  356. local function parseNumber()
  357. local num = ""
  358. local exp
  359. local date = false
  360. local dotfound = false
  361. local prev_underscore = false
  362. while(bounds()) do
  363. if char():match("[%+%-%.eE_0-9]") then
  364. if char():match'%.' then dotfound = true end
  365. if not exp then
  366. if char():lower() == "e" then
  367. -- as soon as we reach e or E, start appending to exponent buffer instead of
  368. -- number buffer
  369. exp = ""
  370. elseif char() ~= "_" then
  371. num = num .. char()
  372. end
  373. elseif char():match("[%+%-_0-9]") then
  374. if char() ~= "_" then
  375. exp = exp .. char()
  376. end
  377. else
  378. err("Invalid exponent")
  379. end
  380. elseif matchWs() or char() == "#" or matchnl() or char() == "," or char() == "]" or char() == "}" then
  381. break
  382. else
  383. err("Invalid number")
  384. end
  385. if char() == '_' and num:sub(#num) == '.' then
  386. err('Undescore after decimal point')
  387. end
  388. if char() == '_' and char(1) == '.' then
  389. err('Undescore before decimal point')
  390. end
  391. if char() == '_' and prev_underscore then
  392. err('Double underscore in number')
  393. end
  394. if char() == "_" then
  395. prev_underscore = true
  396. else
  397. prev_underscore = false
  398. end
  399. step()
  400. end
  401. if prev_underscore then
  402. err("Invalid undescore at end of number")
  403. end
  404. if date then
  405. return {value = num, type = "date"}
  406. end
  407. if num:match('^[%+%-]?0[0-9]') then
  408. err('Leading zero found in number')
  409. end
  410. if dotfound then
  411. if num:match('%.$') then
  412. err('No trailing zero found in float')
  413. end
  414. end
  415. exp = exp and tonumber(exp) or 0
  416. if exp > 0 then
  417. exp = math.floor(10 ^ exp)
  418. elseif exp < 0 then
  419. exp = 10 ^ exp
  420. elseif exp == 0 then
  421. exp = 1
  422. end
  423. num = tonumber(num) * exp
  424. if exp < 0 or dotfound then
  425. return {value = num, type = "float"}
  426. end
  427. return {value = num, type = "integer"}
  428. end
  429. local parseArray, getValue
  430. function parseArray()
  431. step() -- skip [
  432. skipWhitespace()
  433. local arrayType
  434. local array = {}
  435. while(bounds()) do
  436. if char() == "]" then
  437. break
  438. elseif matchnl() then
  439. -- skip
  440. step()
  441. skipWhitespace()
  442. elseif char() == "#" then
  443. while(bounds() and not matchnl()) do
  444. step()
  445. end
  446. else
  447. -- get the next object in the array
  448. local v = getValue()
  449. if not v then break end
  450. -- set the type if it hasn't been set before
  451. if arrayType == nil then
  452. arrayType = v.type
  453. elseif arrayType ~= v.type then
  454. err("Mixed types in array", true)
  455. end
  456. array = array or {}
  457. table.insert(array, v.value)
  458. if char() == "," then
  459. step()
  460. end
  461. skipWhitespace()
  462. end
  463. end
  464. step()
  465. return {value = array, type = "array"}
  466. end
  467. local function parseInlineTable()
  468. step() -- skip opening brace
  469. local buffer = ""
  470. local quoted = false
  471. local tbl = {}
  472. while bounds() do
  473. if char() == "}" then
  474. break
  475. elseif char() == "'" or char() == '"' then
  476. buffer = parseString().value
  477. quoted = true
  478. skipWhitespace()
  479. elseif char() == "=" then
  480. if not quoted then
  481. buffer = trim(buffer)
  482. end
  483. step() -- skip =
  484. skipWhitespace()
  485. if matchnl() then
  486. err("Newline in inline table")
  487. end
  488. local v = getValue().value
  489. tbl[buffer] = v
  490. skipWhitespace()
  491. if char() == "," then
  492. step()
  493. elseif matchnl() then
  494. err("Newline in inline table")
  495. end
  496. quoted = false
  497. buffer = ""
  498. else
  499. if quoted then
  500. if not matchWs() then
  501. err("Unexpected character after the key")
  502. end
  503. else
  504. buffer = buffer .. char()
  505. end
  506. step()
  507. end
  508. end
  509. step() -- skip closing brace
  510. return {value = tbl, type = "array"}
  511. end
  512. local function parseBoolean()
  513. local v
  514. if getData(0, 3) == "true" then
  515. step(4)
  516. v = {value = true, type = "boolean"}
  517. elseif getData(0, 4) == "false" then
  518. step(5)
  519. v = {value = false, type = "boolean"}
  520. else
  521. err("Invalid primitive")
  522. end
  523. skipWhitespace()
  524. if char() == "#" then
  525. while(not matchnl()) do
  526. step()
  527. end
  528. end
  529. return v
  530. end
  531. -- figure out the type and get the next value in the document
  532. function getValue()
  533. if char() == '"' or char() == "'" then
  534. return parseString()
  535. elseif getData(0,5):match("^%d%d%d%d%-%d") then
  536. return parseDate()
  537. elseif getData(0,3):match("^%d%d%:%d") then
  538. return parseTime()
  539. elseif char():match("[%+%-0-9]") then
  540. return parseNumber()
  541. elseif char() == "[" then
  542. return parseArray()
  543. elseif char() == "{" then
  544. return parseInlineTable()
  545. else
  546. return parseBoolean()
  547. end
  548. -- date regex (for possible future support):
  549. -- %d%d%d%d%-[0-1][0-9]%-[0-3][0-9]T[0-2][0-9]%:[0-6][0-9]%:[0-6][0-9][Z%:%+%-%.0-9]*
  550. end
  551. local function parse()
  552. local function check_key()
  553. if buffer == "" then
  554. err("Empty key")
  555. end
  556. if buffer:match("[%s%c%%%(%)%*%+%.%?%[%]!\"#$&',/:;<=>@`\\^{|}~]") and not quotedKey then
  557. err('Invalid character in key')
  558. end
  559. end
  560. -- avoid double table definition
  561. local defined_table = setmetatable({},{__mode='kv'})
  562. -- keep track of container type i.e. table vs array
  563. local container_type = setmetatable({},{__mode='kv'})
  564. local function processKey(isLast, tableArray)
  565. if isLast and obj[buffer] and not tableArray and #obj[buffer] > 0 then
  566. err("Cannot redefine table", true)
  567. end
  568. -- set obj to the appropriate table so we can start
  569. -- filling it with values!
  570. if tableArray then
  571. -- push onto cache
  572. local current = obj[buffer]
  573. -- crete as needed + identify table vs array
  574. local isArray = false
  575. if current then
  576. isArray = (container_type[current] == 'array')
  577. else
  578. current = {}
  579. obj[buffer] = current
  580. if isLast then
  581. isArray = true
  582. container_type[current] = 'array'
  583. else
  584. isArray = false
  585. container_type[current] = 'hash'
  586. end
  587. end
  588. if isLast and not isArray then
  589. err('The selected key contains a table, not an array', true)
  590. end
  591. -- update current object
  592. if not isLast then obj = current end
  593. if isArray then
  594. if isLast then table.insert(current, {}) end
  595. obj = current[#current]
  596. end
  597. else
  598. local newObj = obj[buffer] or {}
  599. obj[buffer] = newObj
  600. if #newObj > 0 then
  601. if type(newObj) ~= 'table' then
  602. err('Duplicate field')
  603. else
  604. -- an array is already in progress for this key, so modify its
  605. -- last element, instead of the array itself
  606. obj = newObj[#newObj]
  607. end
  608. else
  609. obj = newObj
  610. end
  611. end
  612. if isLast then
  613. if defined_table[obj] then
  614. err('Duplicated table definition')
  615. end
  616. defined_table[obj] = true
  617. end
  618. end
  619. -- track whether the current key was quoted or not
  620. local quotedKey = false
  621. -- parse the document!
  622. while(bounds()) do
  623. -- skip comments and whitespace
  624. if char() == "#" then
  625. while(not matchnl()) do
  626. step()
  627. end
  628. end
  629. if matchnl() then
  630. if trim(buffer) ~= '' then
  631. err('Invalid key')
  632. end
  633. end
  634. if char() == "=" then
  635. step()
  636. skipWhitespace()
  637. -- trim key name
  638. buffer = trim(buffer)
  639. if not quotedKey then check_key() end
  640. if buffer:match("^[0-9]+$") and not quotedKey then
  641. buffer = tonumber(buffer)
  642. end
  643. if buffer == "" and not quotedKey then
  644. err("Empty key name")
  645. end
  646. local v = getValue()
  647. if v then
  648. -- if the key already exists in the current object, throw an error
  649. if obj[buffer] ~= nil then
  650. err('Cannot redefine key "' .. buffer .. '"', true)
  651. end
  652. obj[buffer] = v.value
  653. end
  654. -- clear the buffer
  655. buffer = ""
  656. quotedKey = false
  657. -- skip whitespace and comments
  658. skipWhitespace()
  659. if char() == "#" then
  660. while(bounds() and not matchnl()) do
  661. step()
  662. end
  663. end
  664. -- if there is anything left on this line after parsing a key and its value,
  665. -- throw an error
  666. if not dataEnd() and not matchnl() then
  667. err("Invalid primitive")
  668. end
  669. elseif char() == "[" then
  670. if trim(buffer) ~= '' then
  671. err("Invalid key")
  672. end
  673. buffer = ""
  674. step()
  675. local tableArray = false
  676. -- if there are two brackets in a row, it's a table array!
  677. if char() == "[" then
  678. tableArray = true
  679. step()
  680. end
  681. obj = out
  682. while(bounds()) do
  683. if char() == "]" then
  684. break
  685. elseif char() == '"' or char() == "'" then
  686. buffer = parseString().value
  687. quotedKey = true
  688. elseif char() == "." then
  689. step() -- skip period
  690. buffer = trim(buffer)
  691. if not quotedKey then check_key() end
  692. processKey(false, tableArray, quotedKey)
  693. buffer = ""
  694. elseif char() == "[" then
  695. err('Invalid character in key')
  696. step()
  697. else
  698. buffer = buffer .. char()
  699. step()
  700. end
  701. end
  702. if tableArray then
  703. if char(1) ~= "]" then
  704. err("Mismatching brackets")
  705. else
  706. step() -- skip inside bracket
  707. end
  708. end
  709. step() -- skip outside bracket
  710. buffer = trim(buffer)
  711. if not quotedKey then check_key() end
  712. processKey(true, tableArray, quotedKey)
  713. buffer = ""
  714. buffer = ""
  715. quotedKey = false
  716. skipWhitespace()
  717. if bounds() and (not char():match('#') and not matchnl()) then
  718. err("Something found on the same line of a table definition")
  719. end
  720. elseif (char() == '"' or char() == "'") then
  721. -- quoted key
  722. buffer = parseString().value
  723. quotedKey = true
  724. else
  725. buffer = buffer .. (matchnl() and "" or char())
  726. step()
  727. end
  728. end
  729. return result_or_error()
  730. end
  731. local coparse = coroutine.wrap(parse)
  732. coparse()
  733. return coparse
  734. end
  735. TOML.parse = function(data, options)
  736. local cp = TOML.multistep_parser(options)
  737. cp(data)
  738. return cp()
  739. end
  740. TOML.encode = function(tbl)
  741. local toml = ""
  742. local cache = {}
  743. local function parse(tbl)
  744. for k, v in pairs(tbl) do
  745. if type(v) == "boolean" then
  746. toml = toml .. k .. " = " .. tostring(v) .. "\n"
  747. elseif type(v) == "number" then
  748. toml = toml .. k .. " = " .. tostring(v) .. "\n"
  749. elseif type(v) == "string" then
  750. local quote = '"'
  751. v = v:gsub("\\", "\\\\")
  752. -- if the string has any line breaks, make it multiline
  753. if v:match("^\n(.*)$") then
  754. quote = quote:rep(3)
  755. v = "\\n" .. v
  756. elseif v:match("\n") then
  757. quote = quote:rep(3)
  758. end
  759. v = v:gsub("\b", "\\b")
  760. v = v:gsub("\t", "\\t")
  761. v = v:gsub("\f", "\\f")
  762. v = v:gsub("\r", "\\r")
  763. v = v:gsub('"', '\\"')
  764. v = v:gsub("/", "\\/")
  765. toml = toml .. k .. " = " .. quote .. v .. quote .. "\n"
  766. elseif type(v) == "table" and getmetatable(v) == date_metatable then
  767. toml = toml .. k .. " = " .. tostring(v) .. "\n"
  768. elseif type(v) == "table" then
  769. local array, arrayTable = true, true
  770. local first = {}
  771. for kk, vv in pairs(v) do
  772. if type(kk) ~= "number" then array = false end
  773. if type(vv) ~= "table" then
  774. v[kk] = nil
  775. first[kk] = vv
  776. arrayTable = false
  777. end
  778. end
  779. if array then
  780. if arrayTable then
  781. -- double bracket syntax go!
  782. table.insert(cache, k)
  783. for kk, vv in pairs(v) do
  784. toml = toml .. "[[" .. table.concat(cache, ".") .. "]]\n"
  785. for k3, v3 in pairs(vv) do
  786. if type(v3) ~= "table" then
  787. vv[k3] = nil
  788. first[k3] = v3
  789. end
  790. end
  791. parse(first)
  792. parse(vv)
  793. end
  794. table.remove(cache)
  795. else
  796. -- plain ol boring array
  797. toml = toml .. k .. " = [\n"
  798. local quote = '"'
  799. for kk, vv in pairs(first) do
  800. if type(vv) == "string" then
  801. toml = toml .. quote .. tostring(vv) .. quote .. ",\n"
  802. else
  803. toml = toml .. tostring(vv) .. ",\n"
  804. end
  805. end
  806. toml = toml .. "]\n"
  807. end
  808. else
  809. -- just a key/value table, folks
  810. table.insert(cache, k)
  811. toml = toml .. "[" .. table.concat(cache, ".") .. "]\n"
  812. parse(first)
  813. parse(v)
  814. table.remove(cache)
  815. end
  816. end
  817. end
  818. end
  819. parse(tbl)
  820. return toml:sub(1, -2)
  821. end
  822. return TOML