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 creating an output string utilizing and highlighting those patterns. Thus it's
a primarily a debugging tool, not a speedy serialization tool. 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. Sometimes just aligning elements are enough to easy the burden on the eyes.
Contrast the two following pieces of code: 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 pretty printers, inspired me to create `pretty`. See the bottom of the page for
other pretty printers. 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 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 ## Features
@ -46,7 +56,7 @@ attempting to avoid reliance on outside documentation.
* Keys-value pairs are [properly](http://www.davekoelle.com/alphanum.html) * Keys-value pairs are [properly](http://www.davekoelle.com/alphanum.html)
sorted by key type and thereafter alphabetically. sorted by key type and thereafter alphabetically.
* The format and structure of output changes depending upon the input. * 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. with short strings to short lists.
* Uses the standard `debug` library to gain information about functions * Uses the standard `debug` library to gain information about functions
and other advanced structures. and other advanced structures.
@ -72,15 +82,16 @@ I'm looking into implementing following features:
- Nice formatting for `cdata` datatype in LuaJIT. - Nice formatting for `cdata` datatype in LuaJIT.
- Add possibility of comments in output, for stuff like `__tostring` methods, - Add possibility of comments in output, for stuff like `__tostring` methods,
and global namespaces like `io` or `math`. and global namespaces like `io` or `math`.
- Better support upvalues in functions. Complete support is impossible without - Better support upvalues for in functions. Complete support is impossible
traversing the original code or inspecting the intermediate representation, without traversing the original code or inspecting the intermediate
due to lexical scoping. (Pluto does it, but it's written in C.) representation, due to lexical scoping. (Pluto does it, but it's written in
C.)
- Look more into `string.dump` in the core library. - Look more into `string.dump` in the core library.
- Look into using concat operation to improve appearance of overly long - Look into using concat operation to improve appearance of overly long
non-breaking strings. Maybe even attempt to break near whitespace. non-breaking strings. Maybe even attempt to break near whitespace.
- Attempt to fit output within a predefined width limit. Default to 80(?) - Attempt to fit output within a predefined width limit. Default to 80(?)
- Find a better name than `pretty`. - 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 ## 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 = {}
local function library_function ( func_def ) 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 -- Constants
local MAXIMUM_INT = 2^53 -- The maximum double for where all integers can be represented exactly. 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 -- 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) local function calculate_fraction (n)
-- Returns x and y such that x/y = n. If none could be found, returns nil. -- Returns x and y such that x/y = n. If none could be found, returns nil.
local a, b = 1, n % 1 local a, b = 1, n % 1
@ -59,89 +24,68 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local format_num
local SPECIAL_NUMBER = { local SPECIAL_NUMBER = {
-- x = ∞ -- x = ∞
{ est = function (...) return ... end, { est = function (a) return math.huge end,
real = function (a) return math.huge end, real = function (a) return math.huge end,
repr = function (a) return '1/0' end, repr = function (a) return '1/0' end,
short = function (a) return '' 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 -- x = a/b
{ est = calculate_fraction, { est = calculate_fraction,
real = function (a, b) return b ~= 1 and (a/b) end, real = function (a, b) return b ~= 1 and (a/b) end,
repr = function (a, b) return 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 -- x = 2^a
{ est = function (n) return math.log(n)/math.log(2) end, { est = function (n) return math.log(n)/math.log(2) end,
real = function (a) return 2^a end, real = function (a) return 2^a end,
repr = 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 -- x = 10^a
{ est = function (n) return math.log(n)/math.log(10) end, { est = function (n) return math.log(n)/math.log(10) end,
real = function (a) return 10^a end, real = function (a) return 10^a end,
repr = 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 -- x = 1/√a
{ est = function (n) return 1/(n^2) end, { est = function (n) return 1/(n^2) end,
real = function (a) return 1/math.sqrt(a) end, real = function (a) return a >= 0 and 1/math.sqrt(a) end,
repr = function (a) return ('1/math.sqrt(%i)'):format(a) end, repr = function (a) return ('1/math.sqrt(%.0f)'):format(a) end,
short = function (a) return '1/√'..utf8.overline(a) end,
}, },
-- x = lg a -- x = lg a
{ est = function (n) return math.exp(n) end, { est = function (n) return math.exp(n) end,
real = function (a) return math.log(a) end, real = function (a) return a >= 0 and math.log(a) end,
repr = function (a) return ('math.log(%i)'):format(a) end, repr = function (a) return ('math.log(%.0f)'):format(a) end,
short = function (a) return ('lg(%i)'):format(a) end,
}, },
-- x = ^a -- x = ^a
{ est = function (n) return math.log(n) end, { est = function (n) return math.log(n) end,
real = function (a) return math.exp(a) end, real = function (a) return math.exp(a) end,
repr = function (a) return ('math.exp(%i)'):format(a) end, repr = function (a) return ('math.exp(%.0f)'):format(a) end,
short = function (a) return a == 1 and '' or ''..utf8.superscript(a)end,
}, },
-- x = aπ -- x = aπ
{ est = function (n) return n/math.pi end, { est = function (n) return n/math.pi end,
real = function (a) return a*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, 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) -- x = sqrt(a)
{ est = function (n) return n^2 end, { est = function (n) return n^2 end,
real = function (a) return math.sqrt(a) end, real = function (a) return a >= 0 and math.sqrt(a) end,
repr = function (a) return ('math.sqrt(%i)'):format(a) end, repr = function (a) return ('math.sqrt(%.0f)'):format(a) end,
short = function (a) return ''..utf8.overline(a) end,
}, },
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function format_num (n, shorthand, soft_numbers) local function format_soft_num (n)
if n ~= n then return shorthand and 'NaN' or '0/0' assert(type(n) == 'number')
elseif n < 0 then return '-' .. format_num(-n, shorthand)
if n ~= n then return '0/0'
elseif n == 0 then return '0'
elseif n < 0 then return '-' .. format_soft_num(-n)
end end
-- Finding the shortest -- Finding the shortest
local shortest, length = nil, math.huge local shortest, length = nil, math.huge
local function alternative_repr (repr) local function alternative_repr (repr)
local repr_len = utf8.width(repr) if #repr < length then shortest, length = repr, #repr end
if repr_len < length then shortest, length = repr, repr_len end
end end
-- Maybe it's a "special" number? -- 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 for i = 1, #a do a[i] = math.floor(a[i] + 0.5) end
local num = special_number_tests.real(unpack(a)) local num = special_number_tests.real(unpack(a))
if num == n then if num == n then
alternative_repr( special_number_tests[shorthand and 'short' or 'repr'](unpack(a)) ) alternative_repr( special_number_tests.repr(unpack(a)) )
elseif num and soft_numbers then elseif num then
local repr = special_number_tests[shorthand and 'short' or 'repr'](unpack(a)) local repr = special_number_tests.repr(unpack(a))
local native_precise = tonumber(('%'..utf8.width(repr)..'f'):format(n)) local native_precise = tonumber(('%'..#repr..'f'):format(n))
if math.abs(num - n) <= math.abs( native_precise - n ) then if math.abs(num - n) <= math.abs( native_precise - n ) then
alternative_repr( repr ) alternative_repr( repr )
end end
@ -163,14 +107,54 @@ function format_num (n, shorthand, soft_numbers)
end end
-- Maybe it's a decimal number? -- 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! -- Well, this is not a pretty number!
return shortest return shortest
end 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) 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(value) == 'number')
assert(type(depth) == 'number' and type(l) == 'table') 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 end

View File

@ -471,8 +471,6 @@ local KNOWN_OPTIONS = {
cut_strings = { type = 'boolean', default = false }, cut_strings = { type = 'boolean', default = false },
include_closure = { type = 'boolean', default = false }, include_closure = { type = 'boolean', default = false },
indent = { type = 'string', default = ' ' }, 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 }, max_depth = { type = 'number', default = math.huge },
embed_loaded_funcs = { type = 'boolean', default = false }, embed_loaded_funcs = { type = 'boolean', default = false },
more_function_info = { type = 'boolean', default = false }, more_function_info = { type = 'boolean', default = false },

View File

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

View File

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