patternMinor
Parsing Valve Key-Value files
Viewed 0 times
valuevalvefilesparsingkey
Problem
Introduction
Valve recently launched the Dota 2 Workshop Tools, which allows players to create their own maps with custom gamemodes, similar to Warcraft 3 in capabilities.
Ability and unit definitions are stored in txt files under a specific format, very similar to JSON:
It's essentialy JSON with strings only, no punctuation and comments.
I've been taking my time into writing a serializer/deserializer for such files in Lua, so that fellow map creators can use it to save arbitrary data into files. I feel like it's a bit clunky, and not the most readable solution, seeing that I'm new with Lua, this is why I've joined this site. I'd like to see which points can be improved on the code, ither with the use of convenience functions, or plainly logic changes. Even style changes are welcome, since I'm completely unaware of Lua's coding standards.
The code
Everything is contained in a 150-line Lua file, available at GitHub. Below is the mentioned file. I've wrapped everything into the
```
if KV == nil then
local function contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
end
return false
end
local chars = {
indent = "\t",
escape = "\\",
delim = '"',
commentStart = "/",
commentEnd = {"\n", "\r"},
blockStart = "{",
blockEnd = "}"
}
-- iterate_chars doc
-- receives string to operate and a iterator func
-- iterator is called for each character in the string (unless skip)
-- iterator is called with current, previous and next chars, and also
-- the remaining uniterated string
-- iterator can return false to abort iteration, or return a number
Valve recently launched the Dota 2 Workshop Tools, which allows players to create their own maps with custom gamemodes, similar to Warcraft 3 in capabilities.
Ability and unit definitions are stored in txt files under a specific format, very similar to JSON:
{
"key" "value"
"otherKey" { //C-style comments
"moreKey" "moreValue"
} //no block comments!
}It's essentialy JSON with strings only, no punctuation and comments.
I've been taking my time into writing a serializer/deserializer for such files in Lua, so that fellow map creators can use it to save arbitrary data into files. I feel like it's a bit clunky, and not the most readable solution, seeing that I'm new with Lua, this is why I've joined this site. I'd like to see which points can be improved on the code, ither with the use of convenience functions, or plainly logic changes. Even style changes are welcome, since I'm completely unaware of Lua's coding standards.
The code
Everything is contained in a 150-line Lua file, available at GitHub. Below is the mentioned file. I've wrapped everything into the
KV == nil condition so I can have safely local variables and functions, essentialy creating a closure.```
if KV == nil then
local function contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
end
return false
end
local chars = {
indent = "\t",
escape = "\\",
delim = '"',
commentStart = "/",
commentEnd = {"\n", "\r"},
blockStart = "{",
blockEnd = "}"
}
-- iterate_chars doc
-- receives string to operate and a iterator func
-- iterator is called for each character in the string (unless skip)
-- iterator is called with current, previous and next chars, and also
-- the remaining uniterated string
-- iterator can return false to abort iteration, or return a number
Solution
Essentially, the data format is more similar to the lua-table syntax than JSON. It'll be far easier to parse the text string (or file, which can be loaded as a string variable) to be changed to a table instead. Here's the little code I wrote (and tried).
Now, to execute (or test), you pass your string to the function
You can use any pretty printers for printing this returned lua-table back. Do comment if you don't understand any of the steps in my program.
local function comment(w)
local r, m = w, w:match '(.+)//'
local t, _ = m:gsub( '"', '' )
if _ % 2 == 1 then return r else return m end
end
local function parser(x)
local r = x:gsub( "(.-)[\n\r]+",
function(w)
if w:match '//' then w = comment(w) end
w = w:gsub( '("[^"]+") {', '[%1] = {' )
w = w:gsub( '("[^"]+") ("[^"]+")', '[%1] = %2,' )
return w..'\n'
end
)
return loadstring( "return "..r:gsub('}', '},'):gsub(',
Now, to execute (or test), you pass your string to the function parser, which'll return the key-value pairs as a table. For my testing purposes, I used the data string as follows:
{
"key" "value"
"otherKey" { //C-style "comments" "to break?"
"moreKey" "moreValue"
{"let's see" "if // breaks" } //should it?}
} //no block comments!
"let's see" "if // breaks"
}
You can use any pretty printers for printing this returned lua-table back. Do comment if you don't understand any of the steps in my program., '') )()
endNow, to execute (or test), you pass your string to the function
parser, which'll return the key-value pairs as a table. For my testing purposes, I used the data string as follows:{
"key" "value"
"otherKey" { //C-style "comments" "to break?"
"moreKey" "moreValue"
{"let's see" "if // breaks" } //should it?}
} //no block comments!
"let's see" "if // breaks"
}
You can use any pretty printers for printing this returned lua-table back. Do comment if you don't understand any of the steps in my program.
Code Snippets
local function comment(w)
local r, m = w, w:match '(.+)//'
local t, _ = m:gsub( '"', '' )
if _ % 2 == 1 then return r else return m end
end
local function parser(x)
local r = x:gsub( "(.-)[\n\r]+",
function(w)
if w:match '//' then w = comment(w) end
w = w:gsub( '("[^"]+") {', '[%1] = {' )
w = w:gsub( '("[^"]+") ("[^"]+")', '[%1] = %2,' )
return w..'\n'
end
)
return loadstring( "return "..r:gsub('}', '},'):gsub(',$', '') )()
endContext
StackExchange Code Review Q#61135, answer score: 4
Revisions (0)
No revisions yet.