mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
Merge pull request #3900 from ZyX-I/inf-nan-string
Make it possible to eval() all floating-point values dumped by string()
This commit is contained in:
commit
0c2ba7554f
@ -206,6 +206,12 @@ if(MSVC)
|
||||
else()
|
||||
add_definitions(-Wall -Wextra -pedantic -Wno-unused-parameter
|
||||
-Wstrict-prototypes -std=gnu99)
|
||||
|
||||
# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
|
||||
# 3.4.1 used there.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
add_definitions(-Wno-c11-extensions)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
|
@ -395,7 +395,8 @@ endfunction
|
||||
""
|
||||
" Dump floating-point value.
|
||||
function s:msgpack_dump_float(v) abort
|
||||
return string(type(a:v) == type({}) ? a:v._VAL : a:v)
|
||||
return substitute(string(type(a:v) == type({}) ? a:v._VAL : a:v),
|
||||
\'\V\^\(-\)\?str2float(''\(inf\|nan\)'')\$', '\1\2', '')
|
||||
endfunction
|
||||
|
||||
""
|
||||
|
@ -6238,12 +6238,22 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
|
||||
{expr} type result ~
|
||||
String 'string'
|
||||
Number 123
|
||||
Float 123.123456 or 1.123456e8
|
||||
Funcref function('name')
|
||||
Float 123.123456 or 1.123456e8 or
|
||||
`str2float('inf')`
|
||||
Funcref `function('name')`
|
||||
List [item, item]
|
||||
Dictionary {key: value, key: value}
|
||||
Note that in String values the ' character is doubled.
|
||||
Also see |strtrans()|.
|
||||
Note 2: Output format is mostly compatible with YAML, except
|
||||
for infinite and NaN floating-point values representations
|
||||
which use |str2float()|. Strings are also dumped literally,
|
||||
only single quote is escaped, which does not allow using YAML
|
||||
for parsing back binary strings (including text when
|
||||
'encoding' is not UTF-8). |eval()| should always work for
|
||||
strings and floats though and this is the only official
|
||||
method, use |msgpackdump()| or |json_encode()| if you need to
|
||||
share data with other application.
|
||||
|
||||
*strlen()*
|
||||
strlen({expr}) The result is a Number, which is the length of the String
|
||||
|
@ -99,6 +99,8 @@ are always available and may be used simultaneously in separate plugins. The
|
||||
Same thing applies to |string()| (though it uses construct like
|
||||
"{E724@level}"), but this is not reliable because |string()| continues to
|
||||
error out.
|
||||
4. Stringifyed infinite and NaN values now use |str2float()| and can be evaled
|
||||
back.
|
||||
|
||||
Viminfo text files were replaced with binary (messagepack) ShaDa files.
|
||||
Additional differences:
|
||||
|
@ -4010,12 +4010,22 @@ eval6 (
|
||||
* When either side is a float the result is a float.
|
||||
*/
|
||||
if (use_float) {
|
||||
if (op == '*')
|
||||
if (op == '*') {
|
||||
f1 = f1 * f2;
|
||||
else if (op == '/') {
|
||||
/* We rely on the floating point library to handle divide
|
||||
* by zero to result in "inf" and not a crash. */
|
||||
f1 = f2 != 0 ? f1 / f2 : INFINITY;
|
||||
} else if (op == '/') {
|
||||
// Division by zero triggers error from AddressSanitizer
|
||||
f1 = (f2 == 0
|
||||
? (
|
||||
#ifdef NAN
|
||||
f1 == 0
|
||||
? NAN
|
||||
:
|
||||
#endif
|
||||
(f1 > 0
|
||||
? INFINITY
|
||||
: -INFINITY)
|
||||
)
|
||||
: f1 / f2);
|
||||
} else {
|
||||
EMSG(_("E804: Cannot use '%' with Float"));
|
||||
return FAIL;
|
||||
@ -6848,9 +6858,25 @@ vim_to_msgpack_error_ret: \
|
||||
|
||||
#define CONV_FLOAT(flt) \
|
||||
do { \
|
||||
char numbuf[NUMBUFLEN]; \
|
||||
vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", (flt)); \
|
||||
ga_concat(gap, (char_u *) numbuf); \
|
||||
const float_T flt_ = (flt); \
|
||||
switch (fpclassify(flt_)) { \
|
||||
case FP_NAN: { \
|
||||
ga_concat(gap, (char_u *) "str2float('nan')"); \
|
||||
break; \
|
||||
} \
|
||||
case FP_INFINITE: { \
|
||||
if (flt_ < 0) { \
|
||||
ga_append(gap, '-'); \
|
||||
} \
|
||||
ga_concat(gap, (char_u *) "str2float('inf')"); \
|
||||
break; \
|
||||
} \
|
||||
default: { \
|
||||
char numbuf[NUMBUFLEN]; \
|
||||
vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", flt_); \
|
||||
ga_concat(gap, (char_u *) numbuf); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CONV_FUNC(fun) \
|
||||
|
28
test/functional/eval/operators_spec.lua
Normal file
28
test/functional/eval/operators_spec.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local eq = helpers.eq
|
||||
local eval = helpers.eval
|
||||
local clear = helpers.clear
|
||||
|
||||
describe('Division operator', function()
|
||||
before_each(clear)
|
||||
|
||||
it('returns infinity on {positive}/0.0', function()
|
||||
eq('str2float(\'inf\')', eval('string(1.0/0.0)'))
|
||||
eq('str2float(\'inf\')', eval('string(1.0e-100/0.0)'))
|
||||
eq('str2float(\'inf\')', eval('string(1.0e+100/0.0)'))
|
||||
eq('str2float(\'inf\')', eval('string((1.0/0.0)/0.0)'))
|
||||
end)
|
||||
|
||||
it('returns -infinity on {negative}/0.0', function()
|
||||
eq('-str2float(\'inf\')', eval('string((-1.0)/0.0)'))
|
||||
eq('-str2float(\'inf\')', eval('string((-1.0e-100)/0.0)'))
|
||||
eq('-str2float(\'inf\')', eval('string((-1.0e+100)/0.0)'))
|
||||
eq('-str2float(\'inf\')', eval('string((-1.0/0.0)/0.0)'))
|
||||
end)
|
||||
|
||||
it('returns NaN on 0.0/0.0', function()
|
||||
eq('str2float(\'nan\')', eval('string(0.0/0.0)'))
|
||||
eq('str2float(\'nan\')', eval('string(-(0.0/0.0))'))
|
||||
eq('str2float(\'nan\')', eval('string((-0.0)/0.0)'))
|
||||
end)
|
||||
end)
|
175
test/functional/eval/string_spec.lua
Normal file
175
test/functional/eval/string_spec.lua
Normal file
@ -0,0 +1,175 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local clear = helpers.clear
|
||||
local eq = helpers.eq
|
||||
local command = helpers.command
|
||||
local meths = helpers.meths
|
||||
local eval = helpers.eval
|
||||
local exc_exec = helpers.exc_exec
|
||||
local redir_exec = helpers.redir_exec
|
||||
local funcs = helpers.funcs
|
||||
local write_file = helpers.write_file
|
||||
|
||||
describe('string() function', function()
|
||||
before_each(clear)
|
||||
|
||||
describe('used to represent floating-point values', function()
|
||||
it('dumps NaN values', function()
|
||||
eq('str2float(\'nan\')', eval('string(str2float(\'nan\'))'))
|
||||
end)
|
||||
|
||||
it('dumps infinite values', function()
|
||||
eq('str2float(\'inf\')', eval('string(str2float(\'inf\'))'))
|
||||
eq('-str2float(\'inf\')', eval('string(str2float(\'-inf\'))'))
|
||||
end)
|
||||
|
||||
it('dumps regular values', function()
|
||||
eq('1.5', funcs.string(1.5))
|
||||
eq('1.56e-20', funcs.string(1.56000e-020))
|
||||
eq('0.0', eval('string(0.0)'))
|
||||
end)
|
||||
|
||||
it('dumps values with at most six digits after the decimal point',
|
||||
function()
|
||||
eq('1.234568e-20', funcs.string(1.23456789123456789123456789e-020))
|
||||
eq('1.234568', funcs.string(1.23456789123456789123456789))
|
||||
end)
|
||||
|
||||
it('dumps values with at most seven digits before the decimal point',
|
||||
function()
|
||||
eq('1234567.891235', funcs.string(1234567.89123456789123456789))
|
||||
eq('1.234568e7', funcs.string(12345678.9123456789123456789))
|
||||
end)
|
||||
|
||||
it('dumps negative values', function()
|
||||
eq('-1.5', funcs.string(-1.5))
|
||||
eq('-1.56e-20', funcs.string(-1.56000e-020))
|
||||
eq('-1.234568e-20', funcs.string(-1.23456789123456789123456789e-020))
|
||||
eq('-1.234568', funcs.string(-1.23456789123456789123456789))
|
||||
eq('-1234567.891235', funcs.string(-1234567.89123456789123456789))
|
||||
eq('-1.234568e7', funcs.string(-12345678.9123456789123456789))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('used to represent numbers', function()
|
||||
it('dumps regular values', function()
|
||||
eq('0', funcs.string(0))
|
||||
eq('-1', funcs.string(-1))
|
||||
eq('1', funcs.string(1))
|
||||
end)
|
||||
|
||||
it('dumps large values', function()
|
||||
eq('2147483647', funcs.string(2^31-1))
|
||||
eq('-2147483648', funcs.string(-2^31))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('used to represent strings', function()
|
||||
it('dumps regular strings', function()
|
||||
eq('\'test\'', funcs.string('test'))
|
||||
end)
|
||||
|
||||
it('dumps empty strings', function()
|
||||
eq('\'\'', funcs.string(''))
|
||||
end)
|
||||
|
||||
it('dumps strings with \' inside', function()
|
||||
eq('\'\'\'\'\'\'\'\'', funcs.string('\'\'\''))
|
||||
eq('\'a\'\'b\'\'\'\'\'', funcs.string('a\'b\'\''))
|
||||
eq('\'\'\'b\'\'\'\'d\'', funcs.string('\'b\'\'d'))
|
||||
eq('\'a\'\'b\'\'c\'\'d\'', funcs.string('a\'b\'c\'d'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('used to represent funcrefs', function()
|
||||
local fname = 'Xtest-functional-eval-string_spec-fref-script.vim'
|
||||
|
||||
before_each(function()
|
||||
write_file(fname, [[
|
||||
function Test1()
|
||||
endfunction
|
||||
|
||||
function s:Test2()
|
||||
endfunction
|
||||
|
||||
function g:Test3()
|
||||
endfunction
|
||||
|
||||
let g:Test2_f = function('s:Test2')
|
||||
]])
|
||||
command('source ' .. fname)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(fname)
|
||||
end)
|
||||
|
||||
it('dumps references to built-in functions', function()
|
||||
eq('function(\'function\')', eval('string(function("function"))'))
|
||||
end)
|
||||
|
||||
it('dumps references to user functions', function()
|
||||
eq('function(\'Test1\')', eval('string(function("Test1"))'))
|
||||
eq('function(\'g:Test3\')', eval('string(function("g:Test3"))'))
|
||||
end)
|
||||
|
||||
it('dumps references to script functions', function()
|
||||
eq('function(\'<SNR>1_Test2\')', eval('string(Test2_f)'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('used to represent lists', function()
|
||||
it('dumps empty list', function()
|
||||
eq('[]', funcs.string({}))
|
||||
end)
|
||||
|
||||
it('dumps nested lists', function()
|
||||
eq('[[[[[]]]]]', funcs.string({{{{{}}}}}))
|
||||
end)
|
||||
|
||||
it('dumps nested non-empty lists', function()
|
||||
eq('[1, [[3, [[5], 4]], 2]]', funcs.string({1, {{3, {{5}, 4}}, 2}}))
|
||||
end)
|
||||
|
||||
it('errors when dumping recursive lists', function()
|
||||
meths.set_var('l', {})
|
||||
eval('add(l, l)')
|
||||
eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container',
|
||||
exc_exec('echo string(l)'))
|
||||
end)
|
||||
|
||||
it('dumps recursive lists despite the error', function()
|
||||
meths.set_var('l', {})
|
||||
eval('add(l, l)')
|
||||
eq('\nE724: unable to correctly dump variable with self-referencing container\n[{E724@0}]',
|
||||
redir_exec('echo string(l)'))
|
||||
eq('\nE724: unable to correctly dump variable with self-referencing container\n[[{E724@1}]]',
|
||||
redir_exec('echo string([l])'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('used to represent dictionaries', function()
|
||||
it('dumps empty dictionary', function()
|
||||
eq('{}', eval('string({})'))
|
||||
end)
|
||||
|
||||
it('dumps non-empty dictionary', function()
|
||||
eq('{\'t\'\'est\': 1}', funcs.string({['t\'est']=1}))
|
||||
end)
|
||||
|
||||
it('errors when dumping recursive dictionaries', function()
|
||||
meths.set_var('d', {d=1})
|
||||
eval('extend(d, {"d": d})')
|
||||
eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container',
|
||||
exc_exec('echo string(d)'))
|
||||
end)
|
||||
|
||||
it('dumps recursive dictionaries despite the error', function()
|
||||
meths.set_var('d', {d=1})
|
||||
eval('extend(d, {"d": d})')
|
||||
eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'d\': {E724@0}}',
|
||||
redir_exec('echo string(d)'))
|
||||
eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'out\': {\'d\': {E724@1}}}',
|
||||
redir_exec('echo string({"out": d})'))
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user