1+ local toml = {}
2+
3+ -- serialization
4+ type SerializerState = {
5+ buf : buffer ,
6+ cursor : number ,
7+ }
8+
9+ local function serializeValue (value : string | number )
10+ if typeof (value ) == "string" then
11+ value = string.gsub (value , '\\ ' , '\\\\ ' )
12+ value = string.gsub (value , '\n ' , '\\ n' )
13+ value = string.gsub (value , '\t ' , '\\ t' )
14+ return "\" " .. value .. "\" "
15+ elseif value == math.huge then
16+ return "inf"
17+ elseif value == - math.huge then
18+ return "-inf"
19+ elseif value ~= value then
20+ return "nan"
21+ else
22+ return tostring (value )
23+ end
24+ end
25+
26+ local function hasNestedTables (tbl : {})
27+ for _ , v in tbl do
28+ if typeof (v ) == "table" and next (v ) ~= nil then
29+ return true
30+ end
31+ end
32+ return false
33+ end
34+
35+ local function tableToToml (tbl : {}, parent : string )
36+ local result = ""
37+ local subTables = {}
38+ local hasDirectValues = false
39+
40+ for k , v in tbl do
41+ if typeof (v ) == "table" and next (v ) ~= nil then
42+ if # v > 0 then
43+ for _ , entry in v do
44+ result ..= "\n [[" .. (parent and parent .. "." or "" ) .. k .. "]]\n "
45+ result ..= tableToToml (entry , nil )
46+ end
47+ else
48+ subTables [k ] = v
49+ end
50+ else
51+ hasDirectValues = true
52+ result ..= k .. " = " .. serializeValue (v ) .. "\n "
53+ end
54+ end
55+
56+ for k , v in subTables do
57+ local key = parent and (parent .. "." .. k ) or k
58+
59+ if hasNestedTables (v ) == true then
60+ result ..= tableToToml (v , key )
61+ continue
62+ end
63+
64+ if hasDirectValues or parent then
65+ result ..= "\n [" .. key .. "]\n "
66+ end
67+
68+ result ..= tableToToml (v , key )
69+ end
70+
71+ return result
72+ end
73+
74+
75+ local function serialize (tbl : {}): string
76+ return tableToToml (tbl , nil )
77+ end
78+
79+ -- deserialization
80+ type DeserializerState = {
81+ buf : string ,
82+ cursor : number ,
83+ }
84+
85+ local function skipWhitespace (state : DeserializerState )
86+ local pos = state .cursor
87+ while pos <= string.len (state .buf ) and string.match (string.sub (state .buf , pos , pos ), "%s" ) do
88+ pos += 1
89+ end
90+ state .cursor = pos
91+ end
92+
93+ local function readLine (state : DeserializerState )
94+ local nextLine = string.find (state .buf , "\n " , state .cursor ) or string.len (state .buf ) + 1
95+ local line = string.sub (state .buf , state .cursor , nextLine - 1 )
96+ state .cursor = nextLine + 1
97+ return line
98+ end
99+
100+ local function deserialize (input : string )
101+ local state : DeserializerState = {
102+ buf = input ,
103+ cursor = 1 ,
104+ }
105+ local result = {}
106+ local currentTable = result
107+ local arrayTables = {}
108+
109+ while state .cursor <= string.len (state .buf ) do
110+ skipWhitespace (state )
111+ local line = readLine (state )
112+
113+ if line == "" or string.sub (line , 1 , 1 ) == "#" then
114+ continue
115+ end
116+
117+ if string.match (line , "^%[%[(.-)%]%]$" ) then
118+ local tableName = string.match (line , "^%[%[(.-)%]%]$" )
119+ arrayTables [tableName ] = arrayTables [tableName ] or {}
120+
121+ local newEntry = {}
122+ table.insert (arrayTables [tableName ], newEntry )
123+
124+ result [tableName ] = arrayTables [tableName ]
125+ currentTable = newEntry
126+ elseif string.match (line , "^%[(.-)%]$" ) then
127+ local tablePath = string.match (line , "^%[(.-)%]$" )
128+ local parent = result
129+
130+ for section in string.gmatch (tablePath , "([^.]+)" ) do
131+ if not parent [section ] then parent [section ] = {} end
132+ parent = parent [section ]
133+ end
134+
135+ currentTable = parent
136+ elseif string.match (line , "^(.-)%s*=%s*(.-)$" ) then
137+ local key , value = string.match (line , "^(.-)%s*=%s*(.-)$" )
138+ key = string.match (key , "^%s*(.-)%s*$" )
139+ value = string.match (value , "^%s*(.-)%s*$" )
140+
141+ if string.match (value , '^"(.*)"$' ) or string.match (value , "^'(.*)'$" ) then
142+ value = string.sub (value , 2 , - 2 )
143+ value = string.gsub (value , '\\\\ ' , '\\ ' )
144+ value = string.gsub (value , '\\ n' , '\n ' )
145+ value = string.gsub (value , '\\ t' , '\t ' )
146+ elseif tonumber (value ) then
147+ value = tonumber (value )
148+ elseif value == "true" then
149+ value = true
150+ elseif value == "false" then
151+ value = false
152+ elseif value == "inf" or value == "+inf" then
153+ value = math.huge
154+ elseif value == "-inf" then
155+ value = - math.huge
156+ elseif value == "nan" then
157+ value = 0 / 0
158+ end
159+
160+ currentTable [key ] = value
161+ end
162+ end
163+
164+ return result
165+ end
166+
167+ -- user-facing
168+ toml .serialize = serialize
169+ toml .deserialize = deserialize
170+
171+ return toml
0 commit comments