Skip to content

strict mode for parsing numbers #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

russaa
Copy link

@russaa russaa commented Sep 5, 2018

added a strict-mode for parsing numbers (see issue #4 ):

the basic idea is the same as parsing for "numbers only" in jednano/parse-css-font#11:
manipulating the input-string so that parseFloat().toString() would return the exact same string.

There are some special cases for which this does not work (namely e-notations; here it just makes sure that the exponent is an integer, but does not compare the complete evaluated number against the input-string) or where some more elaborate manipulation of the input-string is required (namely related to zeros at the beginning or the end), in order to successfully match a valid number against the the parseFloat() result.

This approach does somewhat favor handling the standard cases efficiently, and applies more involved processing for the special cases.

And it does not yet support numbers with leading zeros like 00056.

I would like a general feedback, before continuing to work on this ;-)

@codecov
Copy link

codecov bot commented Sep 5, 2018

Codecov Report

Merging #6 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master     #6   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           1      1           
  Lines          57     91   +34     
  Branches       11     25   +14     
=====================================
+ Hits           57     91   +34
Impacted Files Coverage Δ
src/index.ts 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 77735aa...3321734. Read the comment docs.

'23e+0.07',
];

test('throws in mode when a number with invalid e-notation is provided', (t) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean throws "in strict mode"?

if (/%$/.test(value)) {
this.type = 'percentage';
this.value = tryParseFloat(value);
this.value = tryParseNumber(value.substring(0, value.length - 1), strict);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here with the substring?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it removes the percent sign so that (potentially) only the number itself will be parsed (removal of the percent sign could be omitted in non-strict mode)

@@ -8,37 +8,46 @@ const cssResolutionUnits: string[] = require('css-resolution-units');
const cssFrequencyUnits: string[] = require('css-frequency-units');
const cssTimeUnits: string[] = require('css-time-units');

const numberPrefixPattern = /^(\+|-)?(\.)?\d/;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this so far away from the implementation? Also, I can remove the trailing \d and all the tests still pass.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, there needs to be be test added for this:
without this you could enter "NaN%" and it would falsely validate it a number

src/index.ts Outdated
dots = countDots(value);
}
if (dots > 0) {
if (!allowDot) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some weird indentation in this block.

src/index.ts Outdated
@@ -82,6 +95,69 @@ function tryParseFloat(value: string) {
return result;
}

function normalizeNumber(value: string, allowDot: boolean = true): string {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to crunch this function down to the following without breaking tests:

function normalizeNumber(value: string, allowDot = true) {
	const match = numberPrefixPattern.exec(value);
	if (!match) {
		throw new Error(`Invalid number: ${value}`);
	}
	const [, sign, dot] = match;
	if (sign === '+') {
		value = value.substr(1);
	}
	if (dot) {
		if (!allowDot) {
			throw new Error(`Invalid number (too many dots): ${value}`);
		}
		if (sign === '-') {
			value = '-0' + value.substr(1);
		} else {
			value = '0' + value;
		}
	}
	return (dot || countDots(value))
		? value.replace(/\.?0+$/, '')
		: value;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes -- the previous version did focus on computing as little as possible, e.g. run countDots() only if necessary, and also apply replace(/\.?0+$/, '') only on input where it would have an effect

but the gain of that is probably negligible

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah and it still has to do the check before doing the replace, so it probably ends up being about the same performance there.

src/index.ts Outdated
function tryParseStrict(value: string): number {
const nval = normalizeNumber(value);
const result = parseFloat(nval);
if (result.toString() !== nval) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify:

	if (result.toString() !== nval && !verifyZero(value) && !verifyENotation(value)) {
		throw new Error(`Invalid number: ${value}`);
	}
	return result;

src/index.ts Outdated
return value;
}

function tryParseStrict(value: string): number {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the return type here.

}

function verifyZero(value: string) {
return /^[-+]?0\.0+$/.test(value);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following works w/o breaking tests:

return parseFloat(value) === 0;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so, but parseFloat() will always ignore trailing non-number parts in the string -- I have not thought too hard about this (in this instance), but it may be that there is such a case that it would fail here (i.e. falsely claim a non-valid input as number)

Copy link
Owner

@jednano jednano Sep 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the discussion I was trying to spark here. Those other cases might be worth testing. You could also consider Math.abs(value) === 0.

src/index.ts Outdated
const nval = normalizeNumber(value);
const result = parseFloat(nval);
if (result.toString() !== nval) {
if (verifyZero(value) || verifyENotation(value)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I think if we just inlined Math.abs(value) === 0 here it would be pretty clear.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean Math.abs(parseFloat(value)) === 0?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I don't mean that. Did you have issues w/o the parseFloat? Because I wasn't having issues in my console.

@russaa
Copy link
Author

russaa commented Sep 6, 2018

I made those changes where did not have any questions & pushed them into the branch/PR

Also:
I thought about a different approach for parsing the number, not using parseFloat().toString(), but instead using regular expression and parsing according to the number-token grammar rule in CSS spec

I'll open another PR for this alternative approach and maybe you can say which one is more appropriate here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants