Changed project priorities, and removed lots of redundant features from number.lua
This commit is contained in:
parent
2cc9301c58
commit
288d9e4de8
31
README.md
31
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
-- TODO: Make lazy? It imports a lot of packages tbh.
|
||||
|
||||
local function_library = {}
|
||||
|
||||
local function library_function ( func_def )
|
||||
|
|
142
number.lua
142
number.lua
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 = '7π',
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user