// savetable module by RabidToaster // usage: // savetable.WriteTable(table) - turns a table into a string // savetable.ReadTable(string) - turns the string back into a table // savetable.RegisterType(type name, to string, to value) - Registers functions for converting a value back and forth. savetable = {} local pcall = pcall local print = print local ErrorNoHalt = ErrorNoHalt local string = string local pairs = pairs local type = type local tostring = tostring local table = table local unpack = unpack local print = print local tostring = tostring local tonumber = tonumber local Vector = Vector local Angle = Angle local toString, toValue = {}, {} function savetable.RegisterType( typeName, stringFunc, valueFunc ) toString[ typeName ] = stringFunc or toString[ typeName ] toValue[ typeName ] = valueFunc or toValue[ typeName ] end function savetable.ToString( typename, value ) if ( toString[ typename ] ) then local ok, err = pcall( toString[ typename ], value ) if ( ok ) then return err else ErrorNoHalt( "Error converting type '" .. typename .. "' to string:" ) ErrorNoHalt( err ) end else ErrorNoHalt( "No value->string convertion function found for type '" .. typename .. "'" ) end end function savetable.ToValue( typename, value ) if ( toValue[ typename ] ) then local ok, err = pcall( toValue[ typename ], value ) if ( ok ) then return err else ErrorNoHalt( "Error converting type '" .. typename .. "' from string:" ) ErrorNoHalt( err ) end else ErrorNoHalt( "No string->value convertion function found for type '" .. typename .. "'" ) end end // We're not using this as, being a regular expression, it fucks up when stuff isn't escaped, which makes stuff hard. local function Explode( sep, str ) local t = {} for piece in string.gmatch(str, "[^" .. sep .. "]+") do t[#t + 1] = piece end return t end // These could be optimised better, but we need to stop regexs messing up. local function Explode(sep, str) local pos, t = 1, {} while (true) do local st, en = string.find(str, sep, pos) if !st then t[#t + 1] = string.sub(str, pos) break else t[#t + 1] = string.sub(str, pos, st - 1) pos = en + 1 end end return t end local function Replace(str, rep, with) local done = table.concat(Explode(rep, str), with) return done end local function Trim(str) return string.gsub(str, "^%s*(.-)%s*$", "%1") end local function Sanitise( str ) str = Replace( str, "\\", "\\\\" ) str = Replace( str, " | ", " \\| " ) str = Replace( str, "\n", "\\n" ) return str end local function DeSanitise( str ) str = Replace( str, "\\n", "\n" ) str = Replace( str, " \\| ", " | " ) str = Replace( str, "\\\\", "\\" ) return str end local function GetTypes( t, passUp ) passUp = passUp or {} for k, v in pairs( t ) do passUp[ type( k ) ] = true passUp[ type( v ) ] = true if ( type( v ) == "table" ) then GetTypes( v, passUp ) end end return passUp end function savetable.WriteTable( t, tabs, types ) local str = "" tabs = tabs or 0 if ( !types ) then // Compress typenames down. types = {} for typeName, _ in pairs( GetTypes( t ) ) do local shortBase = string.sub( typeName, 1, 1 ) local i, short = 2, shortBase while ( types[ short ] ) do short = shortBase .. i i = i + 1 end types[ typeName ] = short str = str .. Sanitise( Sanitise( short ) .. " | " .. Sanitise( typeName ) ) .. " | " end str = string.sub( str, 1, str:len() - 3 ) .. "\n" end local tabstr = string.rep( "\t", tabs ) for key, val in pairs( t ) do local keyStr = savetable.ToString( type( key ), key ) if ( keyStr ) then local keyType = type( key ) keyType = types[ keyType ] or keyType local valType = type( val ) valType = types[ valType ] or valType if ( type( val ) != "table" ) then local valStr = savetable.ToString( type( val ), val ) if ( valStr ) then // This is to allow us to have |s and newlines in the text. valStr = Sanitise( valStr ) str = str .. tabstr .. keyType .. " | " .. keyStr .. " | " .. valType .. " | " .. valStr .. "\n" else ErrorNoHalt( "Unknown type '" .. type( val ) .. "'" ) end else str = str .. tabstr .. keyType .. " | " .. keyStr .. " | " .. valType .. "\n" str = str .. savetable.WriteTable( val, tabs + 1, types ) end else ErrorNoHalt( "Unknown type '" .. type( key ) .. "'" ) end end // Strip the last newline. str = string.sub( str, 1, str:len() - 1 ) return str end local function NumTabs( str ) local num = 0 while ( string.sub( str, 1, num + 1 ) == string.rep( "\t", num + 1 ) ) do num = num + 1 end return num end local function ReadLine( line ) return unpack( Explode( " | ", line ) ) end function savetable.ReadTable( str, startLine, tabs ) local lines = Explode( "\n", str ) local t = {} tabs = tabs or 0 // Read shortened typenames here. local types = {} for _, typeDesc in pairs( Explode( " | ", lines[ 1 ] ) ) do if ( Trim( typeDesc ) != "" ) then typeDesc = DeSanitise( typeDesc ) local split = Explode( " \| ", typeDesc ) types[ DeSanitise( split[ 1 ] ) ] = DeSanitise( split[ 2 ] ) end end table.remove( lines, 1 ) local i = startLine or 1 while ( i <= #lines ) do local line = lines[ i ] // Check if we've reached the end of the current table. local lineTabs = NumTabs( line ) if ( lineTabs < tabs ) then break end // Strip the tabs from the start of the line. line = string.sub( line, tabs + 1 ) local keytype, key, valtype, value = ReadLine( line ) keytype = types[ keytype ] or keytype key = savetable.ToValue( keytype, key ) if ( key ) then valtype = types[ valtype ] or valtype if ( valtype != "table" ) then // This is to allow us to have |s and newlines in the text. value = DeSanitise( value ) local var = savetable.ToValue( valtype, value ) t[ key ] = var else local var var, i = savetable.ReadTable( str, i + 1, tabs + 1 ) i = i or #lines t[ key ] = var end end i = i + 1 end if ( i < #lines ) then return t, i - 1 else return t end end // Type name, to string, to value. savetable.RegisterType( "string", function( v ) return v end, function( s ) return s end ) savetable.RegisterType( "number", function( v ) return tostring( v ) end, function( s ) return tonumber( s ) end ) savetable.RegisterType( "boolean", function( v ) if ( v ) then return "true" end return "false" end, function( s ) return s == "true" end ) savetable.RegisterType( "Vector", function( v ) return tostring( v ) end, function( s ) return Vector( unpack( Explode( " ", s ) ) ) end ) savetable.RegisterType( "Angle", function( v ) return tostring( v ) end, function( s ) return Angle( unpack( Explode( " ", s ) ) ) end )