diff --git a/lua/pl/pretty.lua b/lua/pl/pretty.lua index 775a2d7f..3b2e0cae 100644 --- a/lua/pl/pretty.lua +++ b/lua/pl/pretty.lua @@ -9,8 +9,25 @@ local append = table.insert local concat = table.concat local utils = require 'pl.utils' local lexer = require 'pl.lexer' +local quote_string = require'pl.stringx'.quote_string local assert_arg = utils.assert_arg +--AAS +--Perhaps this could be evolved into part of a "Compat5.3" library some day. +--I didn't think that it was time for that, however. +local tostring = tostring +if _VERSION == "Lua 5.3" then + local _tostring = tostring + tostring = function(s) + if type(s) == "number" then + return ("%.f"):format(s) + else + return _tostring(s) + end + end + +end + local pretty = {} local function save_string_index () @@ -93,7 +110,8 @@ end local function quote_if_necessary (v) if not v then return '' else - if v:find ' ' then v = '"'..v..'"' end + --AAS + if v:find ' ' then v = quote_string(v) end end return v end @@ -108,12 +126,17 @@ local function quote (s) if type(s) == 'table' then return pretty.write(s,'') else - return ('%q'):format(tostring(s)) + --AAS + return quote_string(s)-- ('%q'):format(tostring(s)) end end local function index (numkey,key) - if not numkey then key = quote(key) end + --AAS + if not numkey then + key = quote(key) + key = key:find("^%[") and (" " .. key .. " ") or key + end return '['..key..']' end @@ -178,11 +201,13 @@ function pretty.write (tbl,space,not_clever) if tp ~= 'string' and tp ~= 'table' then putln(quote_if_necessary(tostring(t))..',') elseif tp == 'string' then - if t:find('\n') then - putln('[[\n'..t..']],') - else - putln(quote(t)..',') - end + -- if t:find('\n') then + -- putln('[[\n'..t..']],') + -- else + -- putln(quote(t)..',') + -- end + --AAS + putln(quote_string(t) ..",") elseif tp == 'table' then if tables[t] then putln(',') @@ -245,7 +270,9 @@ local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'} local comma function comma (val) local thou = math.floor(val/1000) - if thou > 0 then return comma(thou)..','..(val % 1000) + --AAS + if thou > 0 then return comma(tostring(thou))..','.. tostring(val % 1000) + -- if thou > 0 then return comma(thou)..','..(val % 1000) else return tostring(val) end end diff --git a/lua/pl/stringx.lua b/lua/pl/stringx.lua index 15d88fa7..9bae615e 100644 --- a/lua/pl/stringx.lua +++ b/lua/pl/stringx.lua @@ -454,6 +454,54 @@ function stringx.shorten(self,sz,tail) return self end +--- Utility function that finds any patterns that match a long string's an open or close. +-- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. +-- Right now, it simply returns the greatest number of them found. +-- @param s The string +-- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. +local function has_lquote(s) + local lstring_pat = '([%[%]])(=*)%1' + local start, finish, bracket, equals, next_equals = nil, 0, nil, nil, nil + -- print("checking lquote for", s) + repeat + start, finish, bracket, next_equals = s:find(lstring_pat, finish + 1) + if start then + -- print("found start", start, finish, bracket, next_equals) + --length of captured =. Ex: [==[ is 2, ]] is 0. + next_equals = #next_equals + equals = next_equals >= (equals or 0) and next_equals or equals + end + until not start + --next_equals will be nil if there was no match. + return equals +end + +--- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. +-- @param s The string to be quoted. +-- @return The quoted string. +function stringx.quote_string(s) + --find out if there are any embedded long-quote + --sequences that may cause issues. + --This is important when strings are embedded within strings, like when serializing. + local equal_signs = has_lquote(s) + if s:find("\n") or equal_signs then + -- print("going with long string:", s) + equal_signs = ("="):rep((equal_signs or -1) + 1) + --long strings strip out leading \n. We want to retain that, when quoting. + if s:find("^\n") then s = "\n" .. s end + --if there is an embedded sequence that matches a long quote, then + --find the one with the maximum number of = signs and add one to that number + local lbracket, rbracket = + "[" .. equal_signs .. "[", + "]" .. equal_signs .. "]" + s = lbracket .. s .. rbracket + else + --Escape funny stuff. + s = ("%q"):format(s) + end + return s +end + function stringx.import(dont_overload) utils.import(stringx,string) end diff --git a/tests/test-stringx.lua b/tests/test-stringx.lua index a648de2d..0bc01a1c 100644 --- a/tests/test-stringx.lua +++ b/tests/test-stringx.lua @@ -270,3 +270,63 @@ asserteq(stringx.strip(' hello '),'hello') asserteq(stringx.strip('--[hello] -- - ','-[] '),'hello') asserteq(stringx.rstrip('--[hello] -- - ','-[] '),'--[hello') +-- + + +local assert_str_round_trip = function(s) + + local qs = stringx.quote_string(s) + local compiled, err = load("return "..qs) + + if not compiled then + print( + ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): + format(s, qs, err) + ) + error() + else + compiled = compiled() + end + + if compiled ~= s then + print("strinx.quote_string assert Failed: String compiled but did not round trip.") + print("input string:\t\t",s, #s) + print("compiled string:\t", compiled, #compiled) + print("output string:\t\t",qs, #qs) + error() + else + -- print("input string:\t\t",s) + -- print("compiled string:\t", compiled) + -- print("output string:\t\t",qs) + end +end + +assert_str_round_trip( "normal string with nothing weird.") +assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") + +assert_str_round_trip( "Unescapped quote \" in the middle") +assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") +assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) +assert_str_round_trip( "[[Completely normal\n long quote. ]]") +assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") +assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') +assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") +assert_str_round_trip( "This\tincludes\ttabs.") +assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") +assert_str_round_trip( "The \z + escape does not trigger a control pattern, however.") + +assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") +assert_str_round_trip('"A quoted string looks like what?"') +assert_str_round_trip( "'I think that it should be quoted, anyway.'") +assert_str_round_trip( "[[Even if they're long quoted.]]") + +assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "") +assert_str_round_trip( " ") +assert_str_round_trip( "\n") --tricky. +assert_str_round_trip( "[[") +assert_str_round_trip( "''") +assert_str_round_trip( '""') \ No newline at end of file