Skip to content

Commit

Permalink
fix(ascii): implement isStringNumeric according to JSON specification
Browse files Browse the repository at this point in the history
  • Loading branch information
Duologic committed Apr 4, 2024
1 parent fc2e57a commit b46d49c
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 4 deletions.
62 changes: 60 additions & 2 deletions ascii.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,66 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
isNumber(c): std.isNumber(c) || (cp(c) >= 48 && cp(c) < 58),

'#isStringNumeric':: d.fn(
'`isStringNumeric` reports whether string `s` consists only of numeric characters.',
'`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html) but without the leading minus.',
[d.arg('str', d.T.string)]
),
isStringNumeric(str): std.all(std.map(self.isNumber, std.stringChars(str))),
isStringNumeric(str):
// "1" "9"
local onenine(c) = (cp(c) >= 49 && cp(c) <= 57);

// "0"
local digit(c) = (cp(c) == 48 || onenine(c));

local digits(str) =
std.length(str) > 0
&& std.all(
std.foldl(
function(acc, c)
acc + [digit(c)],
std.stringChars(str),
[],
)
);

local fraction(str) = str == '' || (str[0] == '.' && digits(str[1:]));

local sign(c) = (c == '-' || c == '+');

local exponent(str) =
str == ''
|| (str[0] == 'E' && digits(str[1:]))
|| (str[0] == 'e' && digits(str[1:]))
|| (std.length(str) > 1 && str[0] == 'E' && sign(str[1]) && digits(str[2:]))
|| (std.length(str) > 1 && str[0] == 'e' && sign(str[1]) && digits(str[2:]));


local integer(str) =
(std.length(str) == 1 && digit(str[0]))
|| (std.length(str) > 0 && onenine(str[0]) && digits(str[1:]))
|| (std.length(str) > 1 && str[0] == '-' && digit(str[1]))
|| (std.length(str) > 1 && str[0] == '-' && onenine(str[1]) && digits(str[2:]));

local expectInteger =
if std.member(str, '.')
then std.split(str, '.')[0]
else if std.member(str, 'e')
then std.split(str, 'e')[0]
else if std.member(str, 'E')
then std.split(str, 'E')[0]
else str;

local expectFraction =
if std.member(str, 'e')
then std.split(str[std.length(expectInteger):], 'e')[0]
else if std.member(str, 'E')
then std.split(str[std.length(expectInteger):], 'E')[0]
else str[std.length(expectInteger):];

local expectExponent = str[std.length(expectInteger) + std.length(expectFraction):];

std.all([
integer(expectInteger),
fraction(expectFraction),
exponent(expectExponent),
]),
}
2 changes: 1 addition & 1 deletion docs/ascii.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ isNumber(c)
isStringNumeric(str)
```

`isStringNumeric` reports whether string `s` consists only of numeric characters.
`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html) but without the leading minus.

### fn isUpper

Expand Down
50 changes: 49 additions & 1 deletion test/ascii_test.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,54 @@ test.new(std.thisFile)
name='empty',
test=test.expect.eq(
actual=ascii.isStringNumeric(''),
expected=true,
expected=false,
)
)

+ std.foldl(
function(acc, str)
acc
+ test.case.new(
name='valid: ' + str,
test=test.expect.eq(
actual=ascii.isStringNumeric(str),
expected=true,
)
),
[
'15',
'1.5',
'-1.5',
'1e5',
'1E5',
'1.5e5',
'1.5E5',
'1.5e-5',
'1.5E+5',
],
{},
)
+ std.foldl(
function(acc, str)
acc
+ test.case.new(
name='invalid: ' + str,
test=test.expect.eq(
actual=ascii.isStringNumeric(str),
expected=false,
)
),
[
'15e',
'1.',
'+',
'+1E5',
'.5',
'E5',
'e5',
'15e5garbage',
'1garbag5e5garbage',
'garbage15e5garbage',
],
{},
)

0 comments on commit b46d49c

Please sign in to comment.