Skip to content

Commit 63d430b

Browse files
authored
feat(ascii): implement f() to validate string as number according to JSON specification (#31)
* fix(ascii): implement isStringNumeric according to JSON specification * docs * fix: implement as new function
1 parent fc2e57a commit 63d430b

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed

ascii.libsonnet

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,68 @@ local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
3232
[d.arg('str', d.T.string)]
3333
),
3434
isStringNumeric(str): std.all(std.map(self.isNumber, std.stringChars(str))),
35+
36+
'#isStringJSONNumeric':: d.fn(
37+
'`isStringJSONNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html).',
38+
[d.arg('str', d.T.string)]
39+
),
40+
isStringJSONNumeric(str):
41+
// "1" "9"
42+
local onenine(c) = (cp(c) >= 49 && cp(c) <= 57);
43+
44+
// "0"
45+
local digit(c) = (cp(c) == 48 || onenine(c));
46+
47+
local digits(str) =
48+
std.length(str) > 0
49+
&& std.all(
50+
std.foldl(
51+
function(acc, c)
52+
acc + [digit(c)],
53+
std.stringChars(str),
54+
[],
55+
)
56+
);
57+
58+
local fraction(str) = str == '' || (str[0] == '.' && digits(str[1:]));
59+
60+
local sign(c) = (c == '-' || c == '+');
61+
62+
local exponent(str) =
63+
str == ''
64+
|| (str[0] == 'E' && digits(str[1:]))
65+
|| (str[0] == 'e' && digits(str[1:]))
66+
|| (std.length(str) > 1 && str[0] == 'E' && sign(str[1]) && digits(str[2:]))
67+
|| (std.length(str) > 1 && str[0] == 'e' && sign(str[1]) && digits(str[2:]));
68+
69+
70+
local integer(str) =
71+
(std.length(str) == 1 && digit(str[0]))
72+
|| (std.length(str) > 0 && onenine(str[0]) && digits(str[1:]))
73+
|| (std.length(str) > 1 && str[0] == '-' && digit(str[1]))
74+
|| (std.length(str) > 1 && str[0] == '-' && onenine(str[1]) && digits(str[2:]));
75+
76+
local expectInteger =
77+
if std.member(str, '.')
78+
then std.split(str, '.')[0]
79+
else if std.member(str, 'e')
80+
then std.split(str, 'e')[0]
81+
else if std.member(str, 'E')
82+
then std.split(str, 'E')[0]
83+
else str;
84+
85+
local expectFraction =
86+
if std.member(str, 'e')
87+
then std.split(str[std.length(expectInteger):], 'e')[0]
88+
else if std.member(str, 'E')
89+
then std.split(str[std.length(expectInteger):], 'E')[0]
90+
else str[std.length(expectInteger):];
91+
92+
local expectExponent = str[std.length(expectInteger) + std.length(expectFraction):];
93+
94+
std.all([
95+
integer(expectInteger),
96+
fraction(expectFraction),
97+
exponent(expectExponent),
98+
]),
3599
}

docs/ascii.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ local ascii = import "github.com/jsonnet-libs/xtd/ascii.libsonnet"
1414

1515
* [`fn isLower(c)`](#fn-islower)
1616
* [`fn isNumber(c)`](#fn-isnumber)
17+
* [`fn isStringJSONNumeric(str)`](#fn-isstringjsonnumeric)
1718
* [`fn isStringNumeric(str)`](#fn-isstringnumeric)
1819
* [`fn isUpper(c)`](#fn-isupper)
1920

@@ -35,6 +36,14 @@ isNumber(c)
3536

3637
`isNumber` reports whether character `c` is a number.
3738

39+
### fn isStringJSONNumeric
40+
41+
```ts
42+
isStringJSONNumeric(str)
43+
```
44+
45+
`isStringJSONNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html).
46+
3847
### fn isStringNumeric
3948

4049
```ts

test/ascii_test.jsonnet

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,51 @@ test.new(std.thisFile)
4242
expected=true,
4343
)
4444
)
45+
46+
+ std.foldl(
47+
function(acc, str)
48+
acc
49+
+ test.case.new(
50+
name='valid: ' + str,
51+
test=test.expect.eq(
52+
actual=ascii.isStringJSONNumeric(str),
53+
expected=true,
54+
)
55+
),
56+
[
57+
'15',
58+
'1.5',
59+
'-1.5',
60+
'1e5',
61+
'1E5',
62+
'1.5e5',
63+
'1.5E5',
64+
'1.5e-5',
65+
'1.5E+5',
66+
],
67+
{},
68+
)
69+
+ std.foldl(
70+
function(acc, str)
71+
acc
72+
+ test.case.new(
73+
name='invalid: ' + str,
74+
test=test.expect.eq(
75+
actual=ascii.isStringJSONNumeric(str),
76+
expected=false,
77+
)
78+
),
79+
[
80+
'15e',
81+
'1.',
82+
'+',
83+
'+1E5',
84+
'.5',
85+
'E5',
86+
'e5',
87+
'15e5garbage',
88+
'1garbag5e5garbage',
89+
'garbage15e5garbage',
90+
],
91+
{},
92+
)

0 commit comments

Comments
 (0)