Log In  


This is some code I wrote to nicely stringify tables. This code started life as a printh() replacement that output directly to the console, but I rewrote it as seen below so it can be used in other contexts.

The strngfy() function can produce either indented, multi-line code, or single-line compact output. Both scalar values and tables are supported as both keys and values.

The rptsp() and tcnt() functions are support functions used by strngfy(), but they also have utility as stand-alone functions.

The code is probably not particularly elegant, but it works for the scenarios I tested. There are some comments in the code to help users get started. All lines fit within the line-width of Pico-8's built-in editor.

The code is 238 tokens.

I'm new to both Pico-8 and Lua, so if I did something the hard way, or missed some tricks, or if you find bugs, let me know.

Updates

  • 04/25/2023
    • Updated function to format function types in table
  • 04/30/2023
    • Same thing for booleans. Code is now 246 tokens.
    • Shaved a few tokens by combining assignments. Code is now 238 tokens.
  • 05/02/2023
    • Turns out nils exist. Code no longer chokes if you pass it a nil type.
  • 7/26/2024
    • After all this time, I just noticed that I had a mistake in the code
      comments. The compact output argument is co, not cm.
  • 7/28/2024
    • Fixed a goofy mistake in the example table which resulted in a
      nil value where I intended for there to be data.
    • Added a simple array to the table used in the examples, below.

The code:

-- t=table
-- cm=compact output. if true,
--  no newlines or indentation
--  are added.
-- ind=indent size
-- l=indent level
-- lst=last item in table?
-- in practice, you only need
-- to provide the first
-- argument. the others are for
-- tweaking output spacing
-- (ind and l) and, in the case
-- of lst, for internal use by
-- the function .
-- does not currently handle
-- recursive tables, so be
-- careful not to create
-- infinite loops in any tables
-- you send to this function.
function strngfy(t,co,ind,l,lst)
 -- make sure ind, l, lst, and
 -- co have sane values. prep
 -- some other variables for
 -- use. we're shadowing ind,
 -- but i don't think it should
 -- matter under any reasonable
 -- set of assumptions.
 local ind,l,lst,co,tp,st
  =max(ind or 2,ind),l and l or
  0,lst or false,co or false
  ,type(t),""
 --------
 l=co and 0 or l+1
 local sp=rptsp(l*ind)
 if tp=='table' then
  local cnt=tcnt(t)
  st..="{"..(co and "" or "\n")
  -- order is not guaranteed, so
  -- two executions of this loop
  -- on the same table might
  -- produce two different
  -- orders of the keys in the
  -- output.
  -- ipairs() does guarantee
  -- order, but only iterates
  -- over consecutive numerical
  -- indices, and is therefore
  -- useless if we want to deal
  -- with sparsely-keyed tables,
  -- or tables which can have
  -- other tables for keys.
  for k,v in next,t do
   st..=sp.."["..strngfy(k,co,
    ind,l,cnt>1).."] => "..
    strngfy(v,co,ind,l,cnt>1)
    ..(cnt>1 and ","..(co and ""
    or "\n") or "")
   cnt-=1
  end
  l-=1
  local sp=rptsp(l*ind)
  st..=(co and "" or "\n")..sp
   .."}"..(cnt > 1 and ","
   or "")
 else 
  st..=tp=="function" and
  	"<function>" or tp=="boolean"
  	and t==true and "true" or
  	t==false and "false" or
  	tp=="nil" and "nil" or t
 end

 return st
end

-- a special case of rptchr()
-- that only produces spaces.
function rptsp(c)
 c=c>0 and c or 0
 s=""
 for i=1,c do
  s..=" "
 end

 return s
end

-- insanity, but as far as i can
-- tell, this is the only way to
-- get the number of entries in
-- a table that doesn't have
-- contiguous numerical keys.
function tcnt(t)
  if type(t) ~= "table" then
   -- to remove ambiguity
   -- between this and a table
   -- with no entries.
   return -1
  end
  local cnt=0
  for _ in pairs(t) do
   cnt+=1
  end

  return cnt
end

Example usage:

tablekey={thisisakey="asdf",thisisanotherkey="fdsa"}
tinternaltable={tintkey="tintvalue"}
itable={thisis="an internal table", witha=tinternaltable}
tablekey[itable]={yetanother="table"}
tablekey[itable][tinternaltable] = "final"
table={}
table[tablekey]="some value"
table.stringkey="stringkey value"
omt={onemore="table",withsome="values",includingint=128,float=2.24}
omtv={okthis="is the last one", i={pinky="promise"}}
arr={"this", "is", "an", "array"}
table.stringkeyb={
  stkb="a value in a table",
  stkc=32,
  stkd={},
  stke={}
}
table.contains_array=arr
table.stringkeyb.stke[omt]=omtv

printh(strngfyplus(table,false,2,nil,nil,nil,nil,nil))

Output:

{
  [stringkeyb] => {
    [stkb] => a value in a table,
    [stke] => {
      [{
        [float] => 2.24,
        [onemore] => table,
        [includingint] => 128,
        [withsome] => values
      }] => {
        [i] => {
          [pinky] => promise
        },
        [okthis] => is the last one
      }
    },
    [stkd] => {

    },
    [stkc] => 32
  },
  [{
    [thisisakey] => asdf,
    [thisisanotherkey] => fdsa,
    [{
      [thisis] => an internal table,
      [witha] => {
        [tintkey] => tintvalue
      }
    }] => {
      [{
        [tintkey] => tintvalue
      }] => final,
      [yetanother] => table
    }
  }] => some value,
  [stringkey] => stringkey value,
  [contains_array] => {
    [1] => this,
    [2] => is,
    [3] => an,
    [4] => array
  }
}

This code is released under the "Do whatever you want with it, I don't care" license, although I'd like to hear about it if you find it useful.

2


I've created an alternate function called strngfyplus() which serves the same purpose as strngfy(), but allows more customization of the output. The new function arguments are lb, rb, lc, rc, and ptr. You can see a brief description of these in the function comments, and you can see them in action below.

This code is 237 tokens, but its primary usefulness is in outputting to the console for debug purposes. Most of the time you won't include it in a finished cart. This function relies on the tcnt() and rptsp() functions included in the original strngfy() code above.

I am debating adding a srt parameter to trigger sorting of strngfy()d tables by their keys, but I'm not sure if that is worthwhile. The user can always sort the tables before passing them to strngfy().

-- This is the same as strngfy(),
-- but it allows you to
-- customize the output syntax.
--
-- lb, rb, lc, rc are left
-- bracket, right bracket, left
-- curly, and right curly. Pass
-- in the characters or strings
-- you want to use in place of
-- those default characters.
-- ptr is the => that separates
-- keys from values.
function strngfyplus(
 t,co,ind,lb,rb,lc,rc
 ,ptr,l,lst
)
 local ind,lb,rb,lc,rc
   ,ptr,l,lst,co,tp,st
   =max(ind or 2,ind)
    ,lb and lb or "["
    ,rb and rb or "]"
    ,lc and lc or "{"
    ,rc and rc or "}"
    ,ptr and ptr or " => "
    ,l and l or 0,lst or
    false,co or false
    ,type(t),""
 --------
 l=co and 0 or l+1
 local sp=rptsp(l*ind)
 if tp=="table" then
   local cnt=tcnt(t)
   st..=lc..(co and "" or "\n")
   -- order is not guaranteed, so
   -- two executions of this loop
   -- on the same table might
   -- produce two different
   -- orders of the keys in the
   -- output.
   -- ipairs() does guarantee
   -- order, but only iterates
   -- over consecutive numerical
   -- indices, and is therefore
   -- useless if we want to deal
   -- with sparsely-keyed tables,
   -- or tables which can have
   -- other tables for keys.
   for k,v in next,t do
   st..=sp..lb..strngfyplus(k,co,
     ind,lb,rb,lc,rc,ptr,l,cnt>1)..rb..ptr..
     strngfyplus(v,co,ind,lb,rb,lc,rc,ptr,l,cnt>1)
     ..(cnt>1 and ","..(co and ""
     or "\n") or "")
   cnt-=1
   end
   l-=1
   local sp=rptsp(l*ind)
   st..=(co and "" or "\n")..sp
   ..rc..(cnt > 1 and ","
   or "")
 else 
   st..=tp=="function" and
       "<function>" or tp=="boolean"
       and t==true and "true" or
       t==false and "false" or
       tp=="nil" and "nil" or t
 end

 return st
end

Here are some sample usages and outputs using the table from the strngfy() example.

First, proof that strngfy() and strngfyplus() generate the same output when no customization is applied.

Code:

strng=strngfy(f)
strngplus=strngfyplus(f)
assert(strng == strngplus)
printh("If you can read this, it worked.")

Output:

If you can read this, it worked.

 

 
Using the compact output option:

strng=strngfy(f,true)
strngplus=strngfyplus(f,true)
assert(strng == strngplus)
printh("If you can read this, it worked.")

Output:

If you can read this, it worked.

 

 
Maybe you believe in pipe symbols.

printh(strngfyplus(table,false,2,"|","|"))

Output:

{
  |stringkeyb| => {
    |stkc| => 32,
    |stke| => {
      |{
        |onemore| => table,
        |float| => 2.24,
        |withsome| => values,
        |includingint| => 128
      }| => {
        |i| => {
          |pinky| => promise
        },
        |okthis| => is the last one
      }
    },
    |stkb| => a value in a table,
    |stkd| => {

    }
  },
  |{
    |thisisakey| => asdf,
    |thisisanotherkey| => fdsa,
    |{
      |thisis| => an internal table,
      |witha| => {
        |tintkey| => tintvalue
      }
    }| => {
      |yetanother| => table,
      |{
        |tintkey| => tintvalue
      }| => final
    }
  }| => some value,
  |stringkey| => stringkey value,
  |contains_array| => {
    |1| => this,
    |2| => is,
    |3| => an,
    |4| => array
  }
}

 

 
Maybe you believe in ::.

printh(strngfyplus(table,false,2,nil,nil,nil,nil, " :: "))

Output:

{
  [contains_array] :: {
    [1] :: this,
    [2] :: is,
    [3] :: an,
    [4] :: array
  },
  [stringkey] :: stringkey value,
  [{
    [{
      [witha] :: {
        [tintkey] :: tintvalue
      },
      [thisis] :: an internal table
    }] :: {
      [{
        [tintkey] :: tintvalue
      }] :: final,
      [yetanother] :: table
    },
    [thisisanotherkey] :: fdsa,
    [thisisakey] :: asdf
  }] :: some value,
  [stringkeyb] :: {
    [stke] :: {
      [{
        [withsome] :: values,
        [onemore] :: table,
        [float] :: 2.24,
        [includingint] :: 128
      }] :: {
        [i] :: {
          [pinky] :: promise
        },
        [okthis] :: is the last one
      }
    },
    [stkc] :: 32,
    [stkb] :: a value in a table,
    [stkd] :: {

    }
  }
}

 

 
Myabe you believe in rococo output.

printh(strngfyplus(table,false,2,"<--","-->","@{", "}@", " :: "))

Output:

@{
  <--stringkey--> :: stringkey value,
  <--stringkeyb--> :: @{
    <--stkb--> :: a value in a table,
    <--stkd--> :: @{

    }@,
    <--stke--> :: @{
      <--@{
        <--float--> :: 2.24,
        <--withsome--> :: values,
        <--onemore--> :: table,
        <--includingint--> :: 128
      }@--> :: @{
        <--i--> :: @{
          <--pinky--> :: promise
        }@,
        <--okthis--> :: is the last one
      }@
    }@,
    <--stkc--> :: 32
  }@,
  <--@{
    <--thisisakey--> :: asdf,
    <--@{
      <--witha--> :: @{
        <--tintkey--> :: tintvalue
      }@,
      <--thisis--> :: an internal table
    }@--> :: @{
      <--@{
        <--tintkey--> :: tintvalue
      }@--> :: final,
      <--yetanother--> :: table
    }@,
    <--thisisanotherkey--> :: fdsa
  }@--> :: some value,
  <--contains_array--> :: @{
    <--1--> :: this,
    <--2--> :: is,
    <--3--> :: an,
    <--4--> :: array
  }@
}@

 

 
Maybe you believe in chaos.

printh(strngfyplus(table,true,0,"","","","",""))

Output:

Too horrific for human eyes.


[Please log in to post a comment]