Skip to content

Commit

Permalink
Consistently use floating point numbers
Browse files Browse the repository at this point in the history
This is only applied to the new codebase, not to the old one, as it is
scheduled for removal.
  • Loading branch information
stof committed Oct 3, 2022
1 parent f8eb36f commit 4a40784
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 322 deletions.
15 changes: 5 additions & 10 deletions src/Ast/Sass/Expression/NumberExpression.php
Expand Up @@ -14,6 +14,7 @@

use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\SassNumber;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;

/**
Expand All @@ -24,7 +25,7 @@
final class NumberExpression implements Expression
{
/**
* @var float|int
* @var float
* @readonly
*/
private $value;
Expand All @@ -41,20 +42,14 @@ final class NumberExpression implements Expression
*/
private $unit;

/**
* @param int|float $value
*/
public function __construct($value, FileSpan $span, ?string $unit = null)
public function __construct(float $value, FileSpan $span, ?string $unit = null)
{
$this->value = $value;
$this->span = $span;
$this->unit = $unit;
}

/**
* @return float|int
*/
public function getValue()
public function getValue(): float
{
return $this->value;
}
Expand All @@ -76,6 +71,6 @@ public function accept(ExpressionVisitor $visitor)

public function __toString(): string
{
return $this->value . ($this->unit ?? '');
return (string) SassNumber::create($this->value, $this->unit);
}
}
16 changes: 7 additions & 9 deletions src/Parser/Parser.php
Expand Up @@ -337,28 +337,26 @@ protected function string(): string
}

/**
* Consumes and returns a natural number (that is, a non-negative integer).
* Consumes and returns a natural number (that is, a non-negative integer) as a double.
*
* Doesn't support scientific notation.
*/
protected function naturalNumber(): int
protected function naturalNumber(): float
{
$first = $this->scanner->readChar();

if (!Character::isDigit($first)) {
$this->scanner->error('Expected digit.', $this->scanner->getPosition() - 1);
}

$number = $first;
$number = (float) intval($first);

$next = $this->scanner->peekChar();

while ($next !== null && Character::isDigit($next)) {
$number .= $this->scanner->readChar();
$next = $this->scanner->peekChar();
while (Character::isDigit($this->scanner->peekChar())) {
$number *= 10;
$number += intval($this->scanner->readChar());
}

return intval($number);
return $number;
}

/**
Expand Down
69 changes: 35 additions & 34 deletions src/Parser/StylesheetParser.php
Expand Up @@ -2754,19 +2754,24 @@ private function number(): NumberExpression
{
$start = $this->scanner->getPosition();
$first = $this->scanner->peekChar();
$sign = $first === '-' ? -1 : 1;

if ($first === '+' || $first === '-') {
$this->scanner->readChar();
}

$number = $this->scanner->peekChar() === '.' ? 0 : $this->naturalNumber();
if ($this->scanner->peekChar() !== '.') {
$this->consumeNaturalNumber();
}

// Don't complain about a dot after a number unless the number starts with a
// dot. We don't allow a plain ".", but we need to allow "1." so that
// "1..." will work as a rest argument.
$number += $this->tryDecimal($this->scanner->getPosition() !== $start);
$number *= $this->tryExponent();
$this->tryDecimal($this->scanner->getPosition() !== $start);
$this->tryExponent();

// Use PHP's built-in double parsing so that we don't accumulate
// floating-point errors for numbers with lots of digits.
$number = floatval($this->scanner->substring($start));

$unit = null;
if ($this->scanner->scanChar('%')) {
Expand All @@ -2775,32 +2780,41 @@ private function number(): NumberExpression
$unit = $this->identifier(false, true);
}

return new NumberExpression($sign * $number, $this->scanner->spanFrom($start), $unit);
return new NumberExpression($number, $this->scanner->spanFrom($start), $unit);
}

/**
* Consumes a natural number (that is, a non-negative integer).
*
* Doesn't support scientific notation.
*/
private function consumeNaturalNumber(): void
{
if (!Character::isDigit($this->scanner->readChar())) {
$this->scanner->error('Expected digit.', $this->scanner->getPosition() - 1);
}

while (Character::isDigit($this->scanner->peekChar())) {
$this->scanner->readChar();
}
}

/**
* Consumes the decimal component of a number and returns its value, or 0 if
* there is no decimal component.
* Consumes the decimal component of a number if it exists.
*
* If $allowTrailingDot is `false`, this will throw an error if there's a
* dot without any numbers following it. Otherwise, it will ignore the dot
* without consuming it.
*
* @param bool $allowTrailingDot
*
* @return int|float
*/
private function tryDecimal(bool $allowTrailingDot = false)
private function tryDecimal(bool $allowTrailingDot = false): void
{
$start = $this->scanner->getPosition();

if ($this->scanner->peekChar() !== '.') {
return 0;
return;
}

if (!Character::isDigit($this->scanner->peekChar(1))) {
if ($allowTrailingDot) {
return 0;
return;
}

$this->scanner->error('Expected digit.', $this->scanner->getPosition() + 1);
Expand All @@ -2810,34 +2824,26 @@ private function tryDecimal(bool $allowTrailingDot = false)
while (Character::isDigit($this->scanner->peekChar())) {
$this->scanner->readChar();
}

// Use PHP's built-in float parsing so that we don't accumulate
// floating-point errors for numbers with lots of digits.
return floatval($this->scanner->substring($start));
}

/**
* Consumes the exponent component of a number and returns its value, or 1 if
* there is no exponent component.
*
* @return int|float
* Consumes the exponent component of a number if it exists.
*/
private function tryExponent()
private function tryExponent(): void
{
$first = $this->scanner->peekChar();

if ($first !== 'e' && $first !== 'E') {
return 1;
return;
}

$next = $this->scanner->peekChar(1);

if (!Character::isDigit($next) && $next !== '-' && $next !== '+') {
return 1;
return;
}

$this->scanner->readChar();
$exponentSign = $next === '-' ? -1 : 1;
if ($next === '+' || $next === '-') {
$this->scanner->readChar();
}
Expand All @@ -2846,14 +2852,9 @@ private function tryExponent()
$this->scanner->error('Expected digit.');
}

$exponent = 0.0;

while (Character::isDigit($this->scanner->peekChar())) {
$exponent *= 10;
$exponent += \ord($this->scanner->readChar()) - \ord('0');
$this->scanner->readChar();
}

return pow(10, $exponentSign * $exponent);
}

/**
Expand Down
6 changes: 2 additions & 4 deletions src/Serializer/SerializeVisitor.php
Expand Up @@ -13,13 +13,11 @@
namespace ScssPhp\ScssPhp\Serializer;

use ScssPhp\ScssPhp\Ast\AstNode;
use ScssPhp\ScssPhp\Ast\Css\CssAtRule;
use ScssPhp\ScssPhp\Ast\Css\CssComment;
use ScssPhp\ScssPhp\Ast\Css\CssDeclaration;
use ScssPhp\ScssPhp\Ast\Css\CssMediaQuery;
use ScssPhp\ScssPhp\Ast\Css\CssNode;
use ScssPhp\ScssPhp\Ast\Css\CssParentNode;
use ScssPhp\ScssPhp\Ast\Css\CssStyleRule;
use ScssPhp\ScssPhp\Ast\Css\CssValue;
use ScssPhp\ScssPhp\Ast\Selector\AttributeSelector;
use ScssPhp\ScssPhp\Ast\Selector\ClassSelector;
Expand Down Expand Up @@ -934,9 +932,9 @@ public function visitNumber(SassNumber $value)
* Writes $number without exponent notation and with at most
* {@see SassNumber::PRECISION} digits after the decimal point.
*
* @param int|float $number
* @param float $number
*/
private function writeNumber($number): void
private function writeNumber(float $number): void
{
if (is_nan($number)) {
$this->buffer->write('NaN');
Expand Down

0 comments on commit 4a40784

Please sign in to comment.