1
0

Changed project priorities, and removed lots of redundant features from number.lua

This commit is contained in:
Jon Michael Aanes 2017-06-15 16:30:34 +02:00
parent 2cc9301c58
commit 288d9e4de8
6 changed files with 106 additions and 117 deletions

View File

@ -8,10 +8,6 @@ human readability. This is done by looking for patterns in the input data, and
creating an output string utilizing and highlighting those patterns. Thus it's
a primarily a debugging tool, not a speedy serialization tool.
Humans are flexible in their understanding of data, as long as certain
underlying structural patterns can be found. `pretty` attempts to find those
patterns and highlight them, often after performing huge analytical tasks.
Sometimes just aligning elements are enough to easy the burden on the eyes.
Contrast the two following pieces of code:
@ -33,8 +29,22 @@ printer is much simpler than `pretty`, but the enhancements compared to other
pretty printers, inspired me to create `pretty`. See the bottom of the page for
other pretty printers.
`pretty` sorts it's priorities like so:
1. Human readability.
2. Lua-compatible output.
3. Customization support.
I'd rather have good defaults than provide a ton of customization options. And
if some structure cannot be represented in Lua, I will rather extend the
syntax, than lose the info.
Another aspect where `pretty` shines is in exploratory programming, when
attempting to avoid reliance on outside documentation.
attempting to avoid reliance on outside documentation. The amount of information
`pretty` exposes varies by the data you are inspecting. If you're inspecting
a list of functions, their function signatures are visible, but if you're
inspecting a single function, documentation and source location may appear if
available.
## Features
@ -46,7 +56,7 @@ attempting to avoid reliance on outside documentation.
* Keys-value pairs are [properly](http://www.davekoelle.com/alphanum.html)
sorted by key type and thereafter alphabetically.
* The format and structure of output changes depending upon the input.
Maps are displayed differently to deeply nested tables to long sequences
Maps appear differently to deeply nested tables to long sequences
with short strings to short lists.
* Uses the standard `debug` library to gain information about functions
and other advanced structures.
@ -72,15 +82,16 @@ I'm looking into implementing following features:
- Nice formatting for `cdata` datatype in LuaJIT.
- Add possibility of comments in output, for stuff like `__tostring` methods,
and global namespaces like `io` or `math`.
- Better support upvalues in functions. Complete support is impossible without
traversing the original code or inspecting the intermediate representation,
due to lexical scoping. (Pluto does it, but it's written in C.)
- Better support upvalues for in functions. Complete support is impossible
without traversing the original code or inspecting the intermediate
representation, due to lexical scoping. (Pluto does it, but it's written in
C.)
- Look more into `string.dump` in the core library.
- Look into using concat operation to improve appearance of overly long
non-breaking strings. Maybe even attempt to break near whitespace.
- Attempt to fit output within a predefined width limit. Default to 80(?)
- Find a better name than `pretty`.
- Add options for colored output, maybe even support multiple?
- Add options for colored output, and allow custom formatting.
## Other pretty printers

View File

@ -1,4 +1,6 @@
-- TODO: Make lazy? It imports a lot of packages tbh.
local function_library = {}
local function library_function ( func_def )

View File

@ -1,10 +1,4 @@
local utf8 = require "utf8" -- FIXME: I don't really like this. Either have a local copy of the files, or remove the functionallity requiring it.
-- Make sure we imported the correct library.
local CAN_USE_SHORTHAND = not not utf8.width
if not CAN_USE_SHORTHAND then utf8.width = function(a) return #a end end
-- Constants
local MAXIMUM_INT = 2^53 -- The maximum double for where all integers can be represented exactly.
@ -13,35 +7,6 @@ local MAXIMUM_ZERO = 10^-7 -- Used when attempting to determine fraction. Anyt
--------------------------------------------------------------------------------
-- Util
local function vulgar_fraction (a, b)
-- Returns a string representing the fraction a/b, with unicode characters.
return utf8.superscript(a) .. '' .. utf8.subscript(b)
end
local function is_integer (n)
-- Predicate. Neither +inf, -inf nor nan are integer.
return n == math.floor(n) and n ~= 1/0 and n ~= -1/0
end
local function factorial (n)
-- Returns the factorial of n.
-- That is n! = n * (n-1) * (n-2) * ... * 1
local a = 1
for i = 2, n do a = a * i end
return a
end
local function inverse_factorial (n)
-- Returns the inverse factorial of n.
-- That is some m, where n = m!
local a, i = 1, 1
while a < n do
i = i + 1
a = a * i
end
return (a == n) and i or nil
end
local function calculate_fraction (n)
-- Returns x and y such that x/y = n. If none could be found, returns nil.
local a, b = 1, n % 1
@ -59,89 +24,68 @@ end
--------------------------------------------------------------------------------
local format_num
local SPECIAL_NUMBER = {
-- x = ∞
{ est = function (...) return ... end,
real = function (a) return math.huge end,
repr = function (a) return '1/0' end,
short = function (a) return '' end,
{ est = function (a) return math.huge end,
real = function (a) return math.huge end,
repr = function (a) return '1/0' end,
},
-- a = x
{ est = function (n) return n end,
real = function (a) return a end,
repr = function (a) return ('%i'):format(a) end,
short = function (a) return ('%i'):format(a) end,
},
-- x = a/b
{ est = calculate_fraction,
real = function (a, b) return b ~= 1 and (a/b) end,
repr = function (a, b) return a..'/'..b end,
short = function (a, b) return a..'/'..b end,
},
-- Factorial
{ est = function (n) return inverse_factorial(n) end,
real = function (a) return factorial(a) end,
repr = function (a) return ('fac(%i)'):format(a) end,
short = function (a) return a..'!' end,
},
-- x = 2^a
{ est = function (n) return math.log(n)/math.log(2) end,
real = function (a) return 2^a end,
repr = function (a) return '2^'..a end,
short = function (a) return '2'..utf8.superscript(a) end,
},
-- x = 10^a
{ est = function (n) return math.log(n)/math.log(10) end,
real = function (a) return 10^a end,
repr = function (a) return '10^'..a end,
short = function (a) return '10'..utf8.superscript(a) end,
},
-- x = 1/√a
{ est = function (n) return 1/(n^2) end,
real = function (a) return 1/math.sqrt(a) end,
repr = function (a) return ('1/math.sqrt(%i)'):format(a) end,
short = function (a) return '1/√'..utf8.overline(a) end,
real = function (a) return a >= 0 and 1/math.sqrt(a) end,
repr = function (a) return ('1/math.sqrt(%.0f)'):format(a) end,
},
-- x = lg a
{ est = function (n) return math.exp(n) end,
real = function (a) return math.log(a) end,
repr = function (a) return ('math.log(%i)'):format(a) end,
short = function (a) return ('lg(%i)'):format(a) end,
real = function (a) return a >= 0 and math.log(a) end,
repr = function (a) return ('math.log(%.0f)'):format(a) end,
},
-- x = ^a
{ est = function (n) return math.log(n) end,
real = function (a) return math.exp(a) end,
repr = function (a) return ('math.exp(%i)'):format(a) end,
short = function (a) return a == 1 and '' or ''..utf8.superscript(a)end,
repr = function (a) return ('math.exp(%.0f)'):format(a) end,
},
-- x = aπ
{ est = function (n) return n/math.pi end,
real = function (a) return a*math.pi end,
repr = function (a) return a == 1 and 'math.pi' or a..'*math.pi' end,
short = function (a) return (a == 1 and '' or a)..'π' end,
},
-- x = sqrt(a)
{ est = function (n) return n^2 end,
real = function (a) return math.sqrt(a) end,
repr = function (a) return ('math.sqrt(%i)'):format(a) end,
short = function (a) return ''..utf8.overline(a) end,
real = function (a) return a >= 0 and math.sqrt(a) end,
repr = function (a) return ('math.sqrt(%.0f)'):format(a) end,
},
}
--------------------------------------------------------------------------------
function format_num (n, shorthand, soft_numbers)
if n ~= n then return shorthand and 'NaN' or '0/0'
elseif n < 0 then return '-' .. format_num(-n, shorthand)
local function format_soft_num (n)
assert(type(n) == 'number')
if n ~= n then return '0/0'
elseif n == 0 then return '0'
elseif n < 0 then return '-' .. format_soft_num(-n)
end
-- Finding the shortest
local shortest, length = nil, math.huge
local function alternative_repr (repr)
local repr_len = utf8.width(repr)
if repr_len < length then shortest, length = repr, repr_len end
if #repr < length then shortest, length = repr, #repr end
end
-- Maybe it's a "special" number?
@ -151,10 +95,10 @@ function format_num (n, shorthand, soft_numbers)
for i = 1, #a do a[i] = math.floor(a[i] + 0.5) end
local num = special_number_tests.real(unpack(a))
if num == n then
alternative_repr( special_number_tests[shorthand and 'short' or 'repr'](unpack(a)) )
elseif num and soft_numbers then
local repr = special_number_tests[shorthand and 'short' or 'repr'](unpack(a))
local native_precise = tonumber(('%'..utf8.width(repr)..'f'):format(n))
alternative_repr( special_number_tests.repr(unpack(a)) )
elseif num then
local repr = special_number_tests.repr(unpack(a))
local native_precise = tonumber(('%'..#repr..'f'):format(n))
if math.abs(num - n) <= math.abs( native_precise - n ) then
alternative_repr( repr )
end
@ -163,14 +107,54 @@ function format_num (n, shorthand, soft_numbers)
end
-- Maybe it's a decimal number?
alternative_repr( tostring(n):gsub('([^e]+)e%+?(%-?)0+', function(a, b) return (a == '1' and '' or a..'*')..'10^'..b end))
alternative_repr( tostring(n):gsub('([^e]+)e%+?(%-?[^e]*)', function(a, b) return (a == '1' and '' or a..'*')..'10^'..b end))
-- Well, this is not a pretty number!
return shortest
end
local function format_hard_num (n)
assert(type(n) == 'number')
-- All the fun special cases
if n ~= n then return '0/0'
elseif n == 0 then return '0'
elseif n == math.huge then return '1/0'
elseif n == -math.huge then return '-1/0'
end
-- Now for the serious part.
for i = 0, 99 do
local repr = ('%.'..i..'f'):format(n)
local num = tonumber(repr)
if num == n then return repr end
end
assert(false)
end
return function (value, depth, l)
-- Formats the number nicely. If depth is 0 and we have some space for extra
-- info, we give some tidbits, to help investigation.
assert(type(value) == 'number')
assert(type(depth) == 'number' and type(l) == 'table')
l[#l+1] = format_num(value, CAN_USE_SHORTHAND and l.options.math_shorthand, l.options.soft_numbers)
-- First format a "soft" version. This number is not guarenteed to accurate.
-- It's purpose is to give a general idea of the value.
l[#l+1] = format_soft_num(value)
-- If we have space for it, format a "hard" value, also. This number is as
-- short as possible, while evaluating precisely to the value of the number.
if depth == 0 then
local hard_repr = format_hard_num(value)
if l[#l] ~= hard_repr then
l[#l+1] = ' -- Approx: '
l[#l+1] = hard_repr
end
-- TODO: Add extra information. I don't really know what is useful?
-- Prime factorization is fun, but useless unless people are doing
-- cryptography or general number theory.
-- Bit pattern is maybe too lowlevel.
end
end

View File

@ -471,8 +471,6 @@ local KNOWN_OPTIONS = {
cut_strings = { type = 'boolean', default = false },
include_closure = { type = 'boolean', default = false },
indent = { type = 'string', default = ' ' },
math_shorthand = { type = 'boolean', default = false },
soft_numbers = { type = 'boolean', default = true }, -- TODO: Add support for maximally precise numbers.
max_depth = { type = 'number', default = math.huge },
embed_loaded_funcs = { type = 'boolean', default = false },
more_function_info = { type = 'boolean', default = false },

View File

@ -11,11 +11,13 @@ local function format_test (t)
end
local function number_test (t)
SUITE:addTest(t.name or t.expect, function ()
assert_equal(t.expect, format(t.input, {}))
end)
SUITE:addTest(t.name and (t.name .. ' shorthand') or t.shorthand or (t.expect .. ' shorthand'), function ()
assert_equal(t.shorthand or t.expect, format(t.input, { math_shorthand = true }))
if t.single then
SUITE:addTest((t.name or t.expect)..' single', function ()
assert_equal(t.single, format(t.input, {}))
end, { line = debug.getinfo(2).currentline })
end
SUITE:addTest((t.name or t.expect) .. ' small', function ()
assert_equal('{ '..t.expect..' }', format({t.input}, {}))
end, { line = debug.getinfo(2).currentline })
end
@ -35,6 +37,7 @@ number_test {
number_test {
input = 2000,
expect = '2000',
single = '2000',
}
number_test {
@ -48,25 +51,21 @@ number_test {
number_test {
input = 10^5,
expect = '10^5',
shorthand = '10⁵',
}
number_test {
input = 10^-6,
expect = '10^-6',
shorthand = '10⁻⁶',
}
number_test {
input = 2^17,
expect = '2^17',
shorthand = '2¹⁷',
}
number_test {
input = 2^-7,
expect = '2^-7',
shorthand = '2⁻⁷',
}
--------------------------------------------------------------------------------
@ -92,6 +91,13 @@ number_test {
expect = '2.0512523',
}
number_test {
name = 'Large decimal number',
input = 60982348080952348324.42342,
expect = '6.0982348080952*10^19',
single = '6.0982348080952*10^19 -- Approx: 60982348080952344576',
}
--------------------------------------------------------------------------------
-- Fractions
@ -111,26 +117,25 @@ number_test {
number_test {
input = math.sqrt(8),
expect = 'math.sqrt(8)',
shorthand = '√̅8',
}
number_test {
input = 1/math.sqrt(8),
expect = '1/math.sqrt(8)',
shorthand = '1/√̅8',
}
number_test {
input = math.log(8),
expect = 'math.log(8)',
shorthand = 'lg(8)',
}
--[[ Factorials are fun, but they are not inheritly supported in the language.
number_test {
input = 6227020800,
expect = 'fac(13)',
shorthand = '13!',
single = '6227020800',
}
]]
--------------------------------------------------------------------------------
-- Constants, and multiples of constants
@ -138,43 +143,36 @@ number_test {
number_test {
input = math.pi,
expect = 'math.pi',
shorthand = 'π',
}
number_test {
input = 7*math.pi,
expect = '7*math.pi',
shorthand = '',
}
number_test {
input = math.exp(1),
expect = 'math.exp(1)',
shorthand = '',
}
number_test {
input = math.exp(10),
expect = 'math.exp(10)',
shorthand = 'ℯ¹⁰',
}
number_test {
input = 1/0,
expect = '1/0',
shorthand = ''
}
number_test {
input = -1/0,
expect = '-1/0',
shorthand = '-∞'
}
number_test {
input = 0/0,
expect = '0/0',
shorthand = 'NaN',
}
--------------------------------------------------------------------------------
@ -187,12 +185,6 @@ do
name = 'Approx π',
input = sum,
expect = 'math.pi',
shorthand = 'π',
}
format_test {
input = sum,
options = { soft_numbers = false },
expect = tostring(sum),
}
end
@ -208,6 +200,7 @@ number_test {
-- A half should always be represented using 1/2, never with 2⁻¹.
input = 1/2,
expect = '1/2',
single = '1/2 -- Approx: 0.5'
}
format_test {

View File

@ -3,5 +3,6 @@ package.path = package.path .. ';./test/?.lua;./src/?.lua'
local TEST_SUITE = require("TestSuite").new('pretty')
TEST_SUITE:addModules('test/test_*')
-- TEST_SUITE:generateRequireSubmodule 'require'
TEST_SUITE:setOptions(...)
TEST_SUITE:runTests()