From b46d49cbc31303e5677d0c4d3ec598f699b413e0 Mon Sep 17 00:00:00 2001 From: Duologic Date: Thu, 4 Apr 2024 22:35:21 +0200 Subject: [PATCH 1/3] fix(ascii): implement isStringNumeric according to JSON specification --- ascii.libsonnet | 62 +++++++++++++++++++++++++++++++++++++++-- docs/ascii.md | 2 +- test/ascii_test.jsonnet | 50 ++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 4 deletions(-) diff --git a/ascii.libsonnet b/ascii.libsonnet index f7962d0..fabb867 100644 --- a/ascii.libsonnet +++ b/ascii.libsonnet @@ -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), + ]), } diff --git a/docs/ascii.md b/docs/ascii.md index 1ad7288..e0a2abd 100644 --- a/docs/ascii.md +++ b/docs/ascii.md @@ -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 diff --git a/test/ascii_test.jsonnet b/test/ascii_test.jsonnet index 077b350..d351bfb 100644 --- a/test/ascii_test.jsonnet +++ b/test/ascii_test.jsonnet @@ -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', + ], + {}, +) From 6369921d8427f838d6dbce5f05ef71dd4a9c2918 Mon Sep 17 00:00:00 2001 From: Duologic Date: Thu, 4 Apr 2024 22:59:24 +0200 Subject: [PATCH 2/3] docs --- ascii.libsonnet | 2 +- docs/ascii.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ascii.libsonnet b/ascii.libsonnet index fabb867..9deb17f 100644 --- a/ascii.libsonnet +++ b/ascii.libsonnet @@ -28,7 +28,7 @@ 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` is a number as defined by [JSON](https://www.json.org/json-en.html) but without the leading minus.', + '`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html).', [d.arg('str', d.T.string)] ), isStringNumeric(str): diff --git a/docs/ascii.md b/docs/ascii.md index e0a2abd..759ff27 100644 --- a/docs/ascii.md +++ b/docs/ascii.md @@ -41,7 +41,7 @@ isNumber(c) isStringNumeric(str) ``` -`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html) but without the leading minus. +`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html). ### fn isUpper From eda3e3f08ab21102d1fd020c09f790d2485e20c5 Mon Sep 17 00:00:00 2001 From: Duologic Date: Thu, 4 Apr 2024 23:01:12 +0200 Subject: [PATCH 3/3] fix: implement as new function --- ascii.libsonnet | 10 ++++++++-- docs/ascii.md | 11 ++++++++++- test/ascii_test.jsonnet | 6 +++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/ascii.libsonnet b/ascii.libsonnet index 9deb17f..f431376 100644 --- a/ascii.libsonnet +++ b/ascii.libsonnet @@ -28,10 +28,16 @@ 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` is a number as defined by [JSON](https://www.json.org/json-en.html).', + '`isStringNumeric` reports whether string `s` consists only of numeric characters.', [d.arg('str', d.T.string)] ), - isStringNumeric(str): + isStringNumeric(str): std.all(std.map(self.isNumber, std.stringChars(str))), + + '#isStringJSONNumeric':: d.fn( + '`isStringJSONNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html).', + [d.arg('str', d.T.string)] + ), + isStringJSONNumeric(str): // "1" "9" local onenine(c) = (cp(c) >= 49 && cp(c) <= 57); diff --git a/docs/ascii.md b/docs/ascii.md index 759ff27..bee1eb3 100644 --- a/docs/ascii.md +++ b/docs/ascii.md @@ -14,6 +14,7 @@ local ascii = import "github.com/jsonnet-libs/xtd/ascii.libsonnet" * [`fn isLower(c)`](#fn-islower) * [`fn isNumber(c)`](#fn-isnumber) +* [`fn isStringJSONNumeric(str)`](#fn-isstringjsonnumeric) * [`fn isStringNumeric(str)`](#fn-isstringnumeric) * [`fn isUpper(c)`](#fn-isupper) @@ -35,13 +36,21 @@ isNumber(c) `isNumber` reports whether character `c` is a number. +### fn isStringJSONNumeric + +```ts +isStringJSONNumeric(str) +``` + +`isStringJSONNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html). + ### fn isStringNumeric ```ts isStringNumeric(str) ``` -`isStringNumeric` reports whether string `s` is a number as defined by [JSON](https://www.json.org/json-en.html). +`isStringNumeric` reports whether string `s` consists only of numeric characters. ### fn isUpper diff --git a/test/ascii_test.jsonnet b/test/ascii_test.jsonnet index d351bfb..f8b3f6b 100644 --- a/test/ascii_test.jsonnet +++ b/test/ascii_test.jsonnet @@ -39,7 +39,7 @@ test.new(std.thisFile) name='empty', test=test.expect.eq( actual=ascii.isStringNumeric(''), - expected=false, + expected=true, ) ) @@ -49,7 +49,7 @@ test.new(std.thisFile) + test.case.new( name='valid: ' + str, test=test.expect.eq( - actual=ascii.isStringNumeric(str), + actual=ascii.isStringJSONNumeric(str), expected=true, ) ), @@ -72,7 +72,7 @@ test.new(std.thisFile) + test.case.new( name='invalid: ' + str, test=test.expect.eq( - actual=ascii.isStringNumeric(str), + actual=ascii.isStringJSONNumeric(str), expected=false, ) ),