1
0
pretty/pstring.lua

143 lines
4.1 KiB
Lua

-- pretty.string
-- The string formatting module for pretty.
--[=[ Thoughts on displaying strings in the useful ways.
TODO
--]=]
--------------------------------------------------------------------------------
-- Constants
local NR_CHARS_IN_LONG_STRING = 40
local SHORT_STR_DELIMITER = '\''
local STRING_CONT_INDICATOR = '...'
local CHAR_TO_STR_REPR = {}
do
for i = 00, 031 do CHAR_TO_STR_REPR[i] = ('\\%03i'):format(i) end
for i = 32, 255 do CHAR_TO_STR_REPR[i] = string.char(i) end
CHAR_TO_STR_REPR[7] = '\\a'
CHAR_TO_STR_REPR[8] = '\\b'
CHAR_TO_STR_REPR[9] = '\\t'
CHAR_TO_STR_REPR[10] = '\\n'
CHAR_TO_STR_REPR[11] = '\\v'
CHAR_TO_STR_REPR[12] = '\\f'
CHAR_TO_STR_REPR[13] = '\\r'
CHAR_TO_STR_REPR[92] = '\\\\'
CHAR_TO_STR_REPR[127] = '\\127'
end
local CHARACTERS_THAT_REQUIRE_ESCAPE_SEQ = '[%z\001-\008\011-\031\127]'
--------------------------------------------------------------------------------
-- Util
local function requires_weird_escape_seq (str)
return not not str:find(CHARACTERS_THAT_REQUIRE_ESCAPE_SEQ)
end
local function escape_string (str)
-- Attempts to escape the string, to a format that is both a valid Lua
-- constant, and ledible unicode.
-- TODO: Escape invalid unicode sequences.
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local l = {}
for i = 1, #str do l[#l+1] = CHAR_TO_STR_REPR[str:byte(i)] end
return table.concat(l, '')
end
local function smallest_secure_longform_string_level (str)
-- Determines the level a longform string needs to use, to avoid code
-- injection. For example, if we want to use longform on the string
-- 'Hello ]] World', we cannot use level-0 as this would result in
-- '[[Hello ]] World]]', which could be an issue in certain applications.
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local levels = { [1] = 1 }
str:gsub('%]=*%]', function (m) levels[m:len()] = true end)
return #levels - 1
end
--------------------------------------------------------------------------------
local function format_shortform_string (str, depth, l)
l[#l+1] = SHORT_STR_DELIMITER
l[#l+1] = escape_string(str):gsub(SHORT_STR_DELIMITER, '\\'..SHORT_STR_DELIMITER)
l[#l+1] = SHORT_STR_DELIMITER
end
local function format_cut_string (str, depth, l)
-- Calculate string
local str = escape_string(str)
:gsub(SHORT_STR_DELIMITER, '\\'..SHORT_STR_DELIMITER)
:sub(1, NR_CHARS_IN_LONG_STRING - #STRING_CONT_INDICATOR)
-- Search for the number of backslashes just before the send of the string.
-- If that number is even, it's a sequence of backslashes, if not it's a
-- broken escape string.
local start_of_backslashes, start_of_digits = str:match '()\\*()%d?%d?$'
local nr_backslashes_before_end = start_of_digits - start_of_backslashes
if nr_backslashes_before_end % 2 == 1 then str = str:sub(1, start_of_backslashes - 1) end
-- Format
l[#l+1] = SHORT_STR_DELIMITER
l[#l+1] = str
l[#l+1] = SHORT_STR_DELIMITER
l[#l+1] = STRING_CONT_INDICATOR
end
local function format_concatted_string (str, depth, l)
error '[pretty.string/internal]: format_concatted_string not implemented yet!'
end
local function format_longform_string (str, depth, l)
-- Error checking
assert( type(str) == 'string' )
assert(type(depth) == 'number' and type(l) == 'table')
-- Calculate
local level_required = smallest_secure_longform_string_level(str)
-- Format
l[#l+1] = '['..string.rep('=', level_required)..'['
l[#l+1] = '\n'
l[#l+1] = str
l[#l+1] = ']'..string.rep('=', level_required)..']'
end
return function (str, depth, l)
-- pretty.format_string
-- Error checking
assert( type(str) == 'string' )
assert(type(depth) == 'number' and type(l) == 'table')
-- Do work
if #str < NR_CHARS_IN_LONG_STRING then
return format_shortform_string(str, depth, l)
elseif depth > 0 then
return format_cut_string (str, depth, l)
elseif requires_weird_escape_seq (str) then
return format_concatted_string(str, depth, l)
else
return format_longform_string(str, depth, l)
end
end