├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── init.lua └── pprint-0-0.rockspec /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.sw[op] 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things—for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use Github pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the Software Grant and Corporate Contributor License Agreement. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2014, Google Inc. All rights reserved. 3 | Copyright (c) 2013 Enrique García Cota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pprint 2 | 3 | Pretty Printing for Torch and Lua. 4 | 5 | `pprint` will print a human readable printout of Lua tables and torch tensors. 6 | 7 | ## Features 8 | 9 | The visual improvements offered by pprint are the following: 10 | 11 | * Keys are printed alphabetically 12 | * Nested tables are indented 13 | * Loops are detected 14 | * Metatable info is printed 15 | * Torch tensors are printed if they contain less than 20 elements, 16 | otherwise just the dimensions are printed 17 | 18 | ## Differences with Penlight.pretty 19 | 20 | All the mentioned features, except for loop detection, 21 | are not included in `Penlight.pretty`. In addition `Penlight.pretty` 22 | substitutes loops by a `` tag, making it impossible to see 23 | the table causing the loop. 24 | 25 | Apart from this, `Penlight.pretty` introduces new lines per table element, 26 | whereas pprint only creates them based on non-numerical indices of the table. 27 | 28 | ## Usage 29 | 30 | There are different ways in which pprint can be used: 31 | 32 | ### General (torch and lua) usages 33 | 34 | * Using `pprint(data)` directly, as seen in the example below: 35 | 36 | ```lua 37 | require 'pprint' 38 | a = {1, 2} 39 | a[3] = a 40 | a[4] = torch.zeros(21) 41 | pprint(a) 42 | ``` 43 | 44 | Outputs: 45 | 46 | ```lua 47 | { 1, 2, { 1, 2, { 1, 2, { 1, 2, {...}, [torch.DoubleTensor of dimension 21] }, 48 | [torch.DoubleTensor of dimension 21] }, [torch.DoubleTensor of dimension 21] }, 49 | [torch.DoubleTensor of dimension 21] } 50 | ``` 51 | 52 | * Calling `pprint.pretty_string(data)`, which differs from 53 | `pprint(data)` in that the method returns the generated string instead 54 | of printing it. 55 | 56 | * Calling `pprint.string(data)`, which is the inline result 57 | version of `pprint.pretty_string(data)`. 58 | 59 | * Using `pprint.printer()` to create a printer that returns a the 60 | concatenated `pretty_string` version of an arbitrary amount of parameters. 61 | 62 | * When printing a table, the default depth is 4. This depth can be modified: 63 | 64 | ```lua 65 | require 'pprint' 66 | a = {1, {2, {3, {4, {5}}}}} 67 | pprint(a, 2) 68 | print(pprint.pretty_string(a, 3)) 69 | pprint(a) 70 | ``` 71 | 72 | Outputs: 73 | 74 | ```lua 75 | { 1, { 2, {...} } } 76 | { 1, { 2, { 3, {...} } } } 77 | { 1, { 2, { 3, { 4, {...} } } } } 78 | ``` 79 | 80 | ### Tensor specific usage 81 | 82 | * `pprint.dims(tensor)` returns a string containing the dimensions of the 83 | `tensor` passed as an argument. 84 | 85 | * `pprint.info(data)` returns a string containing information of the tensors 86 | (dimensions, minimum value, mean value, maximum value, type) found in `data`. 87 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --[[ Pretty Printing for Torch and Lua, main module. 2 | 3 | Code adapted from inspect.lua, here (https://github.com/kikito/inspect.lua). 4 | * changed some formatting, removed numbering of elements 5 | * added support for tensors 6 | * changed various function names 7 | ]] 8 | 9 | require 'torch' 10 | 11 | -- Wraps quoted strings in apostraphes 12 | local function smart_quote(str) 13 | if string.match( string.gsub(str, "[^'\"]", ""), '^"+$' ) then 14 | return "'" .. str .. "'" 15 | end 16 | return string.format("%q", str) 17 | end 18 | 19 | 20 | local control_chars_translation = { 21 | ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", 22 | ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\" 23 | } 24 | 25 | 26 | local function unescape_char(c) 27 | return control_chars_translation[c] 28 | end 29 | 30 | 31 | local function unescape(str) 32 | local result, _ = string.gsub( str, "(%c)", unescape_char ) 33 | return result 34 | end 35 | 36 | 37 | local function is_identifier(str) 38 | return string.match( str, "^[_%a][_%a%d]*$" ) 39 | end 40 | 41 | 42 | local function is_array_key(k, length) 43 | return type(k)=='number' and 1 <= k and k <= length 44 | end 45 | 46 | 47 | local function is_dictionary_key(k, length) 48 | return not is_array_key(k, length) 49 | end 50 | 51 | 52 | local sort_orders_by_type = { 53 | ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, 54 | ['tensor'] = 5, ['function'] = 6, ['userdata'] = 7, ['thread'] = 8 55 | } 56 | 57 | 58 | local function sort_keys(a,b) 59 | local ta, tb = type(a), type(b) 60 | if ta ~= tb then return sort_orders_by_type[ta] < sort_orders_by_type[tb] end 61 | if ta == 'string' or ta == 'number' then return a < b end 62 | return false 63 | end 64 | 65 | 66 | local function get_dictionary_keys(t) 67 | local length = #t 68 | local keys = {} 69 | for k, _ in pairs(t) do 70 | if is_dictionary_key(k, length) then table.insert(keys, k) end 71 | end 72 | table.sort(keys, sort_keys) 73 | return keys 74 | end 75 | 76 | 77 | local function get_to_string_result_safely(t, mt) 78 | local __tostring = type(mt) == 'table' and mt.__tostring 79 | local string, status 80 | if type(__tostring) == 'function' then 81 | status, string = pcall(__tostring, t) 82 | string = status and string or 'error: ' .. tostring(string) 83 | end 84 | return string 85 | end 86 | 87 | 88 | local Printer = {} 89 | 90 | function Printer:init(depth, inline) 91 | local pprintor = { 92 | buffer = {}, 93 | depth = depth or 0, 94 | inline = inline or false, 95 | level = 0, 96 | counters = { 97 | ['function'] = 0, 98 | ['userdata'] = 0, 99 | ['thread'] = 0, 100 | ['table'] = 0 101 | }, 102 | pools = { 103 | ['function'] = setmetatable({}, {__mode = "kv"}), 104 | ['userdata'] = setmetatable({}, {__mode = "kv"}), 105 | ['thread'] = setmetatable({}, {__mode = "kv"}), 106 | ['table'] = setmetatable({}, {__mode = "kv"}) 107 | } 108 | } 109 | 110 | setmetatable(pprintor, { 111 | __index = Printer, 112 | __tostring = function(instance) return table.concat(instance.buffer) end 113 | } ) 114 | return pprintor 115 | end 116 | 117 | 118 | function Printer:new(v, depth, inline) 119 | local p = self:init(depth, inline) 120 | return p:put_value(v) 121 | end 122 | 123 | 124 | function Printer:puts(...) 125 | local args = {...} 126 | for i = 1, #args do 127 | table.insert(self.buffer, tostring(args[i])) 128 | end 129 | return self 130 | end 131 | 132 | 133 | function Printer:tabify() 134 | if self.inline then 135 | self:puts(" ") 136 | else 137 | self:puts("\n", string.rep(" ", self.level)) 138 | end 139 | return self 140 | end 141 | 142 | 143 | function Printer:up() 144 | self.level = self.level - 1 145 | end 146 | 147 | 148 | function Printer:down() 149 | self.level = self.level + 1 150 | end 151 | 152 | 153 | function Printer:put_comma(comma) 154 | if comma then self:puts(',') end 155 | return true 156 | end 157 | 158 | 159 | function Printer:put_table(t) 160 | if self:already_seen(t) then 161 | self:puts('') 162 | elseif self.level >= self.depth then 163 | self:puts('{...}') 164 | else 165 | self:down() 166 | 167 | local length = #t 168 | local mt = getmetatable(t) 169 | self:puts('{') 170 | 171 | local string = get_to_string_result_safely(t, mt) 172 | if type(string) == 'string' and #string > 0 then 173 | self:puts(' -- ', unescape(string)) 174 | if length >= 1 then self:tabify() end -- tabify the array values 175 | end 176 | 177 | local comma = false 178 | for i = 1, length do 179 | comma = self:put_comma(comma) 180 | self:puts(' '):put_value(t[i]) 181 | end 182 | 183 | local dict_keys = get_dictionary_keys(t) 184 | 185 | for _, k in ipairs(dict_keys) do 186 | comma = self:put_comma(comma) 187 | self:tabify():put_key(k):puts(' = '):put_value(t[k]) 188 | end 189 | 190 | if mt then 191 | comma = self:put_comma(comma) 192 | self:tabify():puts(' = '):put_value(mt) 193 | end 194 | self:up() 195 | 196 | if #dict_keys > 0 or mt then -- dictionary table. Justify closing } 197 | self:tabify() 198 | elseif length > 0 then -- array tables have one extra space before closing } 199 | self:puts(' ') 200 | end 201 | self:puts('}') 202 | end 203 | return self 204 | end 205 | 206 | 207 | function Printer:put_tensor(t) 208 | local size = t:nElement() 209 | if size <= 20 then 210 | self:puts(tostring(t)) 211 | else 212 | self:puts('[' .. torch.typename(t) .. ' of dimension ') 213 | self:put_tensor_dims(t) 214 | self:puts(']') 215 | end 216 | end 217 | 218 | function Printer:put_storage(t) 219 | local size = t:size() 220 | if size <= 20 then 221 | self:puts(tostring(t)) 222 | else 223 | self:puts('[' .. torch.typename(t) .. ' of size ') 224 | self:puts(size) 225 | self:puts(']') 226 | end 227 | end 228 | 229 | function Printer:put_tensor_dims(t) 230 | local n_dims = t:dim() 231 | local dim_sizes = t:size() 232 | if n_dims == 0 then 233 | self:puts('[0-dimensional tensor]') 234 | else 235 | for i = 1, n_dims-1 do 236 | self:puts(dim_sizes[i]):puts('x') 237 | end 238 | self:puts(dim_sizes[n_dims]) 239 | end 240 | end 241 | 242 | 243 | function Printer:already_seen(v) 244 | local tv = type(v) 245 | return self.pools[tv][v] ~= nil 246 | end 247 | 248 | 249 | function Printer:get_or_create_counter(v) 250 | local tv = type(v) 251 | local current = self.pools[tv][v] 252 | if not current then 253 | current = self.counters[tv] + 1 254 | self.counters[tv] = current 255 | self.pools[tv][v] = current 256 | end 257 | return current 258 | end 259 | 260 | 261 | function Printer:put_value(v) 262 | local tv = type(v) 263 | 264 | if tv == 'string' then 265 | self:puts(smart_quote(unescape(v))) 266 | elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then 267 | if v == math.huge then 268 | self:puts('math.huge') 269 | elseif v == -math.huge then 270 | self:puts('-math.huge') 271 | else 272 | self:puts(tostring(v)) 273 | end 274 | elseif tv == 'table' then 275 | self:put_table(v) 276 | elseif torch.isTensor(v) then 277 | self:put_tensor(v) 278 | elseif torch.isStorage(v) then 279 | self:put_storage(v) 280 | else 281 | self:puts('<',tv,'>') 282 | end 283 | return self 284 | end 285 | 286 | 287 | function Printer:put_key(k) 288 | if type(k) == "string" and is_identifier(k) then 289 | return self:puts(k) 290 | end 291 | return self:puts( "[" ):put_value(k):puts("]") 292 | end 293 | 294 | 295 | local function print_tensor_dimensions(t) 296 | local p = Printer.init() 297 | p:put_tensor_dims(t) 298 | return tostring(p) 299 | end 300 | 301 | 302 | local function print_tensor_info(t) 303 | local p = Printer.init() 304 | 305 | -- if we find a table, recursively call info on its elements 306 | if type(t) == 'table' then 307 | local newline = ' ' 308 | if #t > 1 then newline = '\n' end 309 | p:puts('{' .. newline) 310 | for i = 1, #t do 311 | p:puts(print_tensor_info(t[i])) 312 | p:puts(newline) 313 | end 314 | p:puts('}') 315 | else 316 | if t == nil then p:puts("[nil]") end 317 | p:puts('['):put_tensor_dims(t) 318 | if t.min then p:puts(', min: '):put_value(t:min()) end 319 | if t.mean then p:puts(', mean: '):put_value(t:mean()) end 320 | if t.max then p:puts(', max: '):put_value(t:max()) end 321 | if t.type then p:puts(', type: '):put_value(t:type()) end 322 | p:puts(']') 323 | end 324 | return tostring(p) 325 | end 326 | 327 | 328 | local function pretty_string(t, depth, inline) 329 | depth = depth or 4 330 | inline = inline or false 331 | return tostring(Printer:new(t, depth, inline)) 332 | end 333 | 334 | -- Returns an inline string. 335 | local function string(t, depth) 336 | return pretty_string(t, depth, true) 337 | end 338 | 339 | local function pprint_pprint(self, data, depth) 340 | print(pretty_string(data, depth)) 341 | end 342 | 343 | pprint = { 344 | pretty_string = pretty_string, -- multi-line string 345 | string = string, -- inline string 346 | dims = print_tensor_dimensions, 347 | info = print_tensor_info, 348 | __call = pprint_pprint, 349 | printer = function(depth) 350 | depth = depth or 4 351 | local function pretty_string(...) 352 | local str_table = {} 353 | for i = 1, select('#', ...) do 354 | local obj = select(i, ...) 355 | table.insert(str_table, tostring(Printer:new(obj, depth))) 356 | end 357 | return table.concat(str_table, ' ') 358 | end 359 | return pretty_string 360 | end 361 | } 362 | 363 | 364 | setmetatable(pprint, pprint) 365 | 366 | 367 | return pprint 368 | -------------------------------------------------------------------------------- /pprint-0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = 'pprint' 2 | version = '0-0' 3 | 4 | source = { 5 | url = 'git://github.com/deepmind/lua-pprint.git', 6 | branch = 'master' 7 | } 8 | 9 | description = { 10 | summary = "A pretty print library for Torch and lua.", 11 | homepage = "https://github.com/deepmind/lua-pprint.git" 12 | } 13 | 14 | dependencies = {'torch >= 7.0'} 15 | build = { 16 | type = 'builtin', 17 | modules = { 18 | ['pprint.init'] = 'init.lua' 19 | } 20 | } 21 | --------------------------------------------------------------------------------