%PDF- %PDF-
| Direktori : /home/qgbqkvz/www/wp-content/plugins/wp-scss/scssphp/src/Value/ |
| Current File : /home/qgbqkvz/www/wp-content/plugins/wp-scss/scssphp/src/Value/SassCalculation.php |
<?php
/**
* SCSSPHP
*
* @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Value;
use ScssPhp\ScssPhp\Exception\SassScriptException;
use ScssPhp\ScssPhp\Util\Equatable;
use ScssPhp\ScssPhp\Util\NumberUtil;
use ScssPhp\ScssPhp\Visitor\ValueVisitor;
/**
* A SassScript calculation.
*
* Although calculations can in principle have any name or any number of
* arguments, this class only exposes the specific calculations that are
* supported by the Sass spec. This ensures that all calculations that the user
* works with are always fully simplified.
*/
final class SassCalculation extends Value
{
/**
* The calculation's name, such as `"calc"`.
*
* @var string
* @readonly
*/
private $name;
/**
* The calculation's arguments.
*
* Each argument is either a {@see SassNumber}, a {@see SassCalculation}, an unquoted
* {@see SassString}, a {@see CalculationOperation}, or a {@see CalculationInterpolation}.
*
* @var list<object>
* @readonly
*/
private $arguments;
/**
* Creates a new calculation with the given [name] and [arguments]
* that will not be simplified.
*
* @param string $name
* @param list<object> $arguments
*
* @return Value
*
* @internal
*/
public static function unsimplified(string $name, array $arguments): Value
{
return new SassCalculation($name, $arguments);
}
/**
* Creates a `calc()` calculation with the given $argument.
*
* The $argument must be either a {@see SassNumber}, a {@see SassCalculation}, an
* unquoted {@see SassString}, a {@see CalculationOperation}, or a
* {@see CalculationInterpolation}.
*
* This automatically simplifies the calculation, so it may return a
* {@see SassNumber} rather than a {@see SassCalculation}. It throws an exception if it
* can determine that the calculation will definitely produce invalid CSS.
*
* @param object $argument
*
* @return Value
*
* @throws SassScriptException
*/
public static function calc(object $argument): Value
{
$argument = self::simplify($argument);
if ($argument instanceof SassNumber) {
return $argument;
}
if ($argument instanceof SassCalculation) {
return $argument;
}
return new SassCalculation('calc', [$argument]);
}
/**
* Creates a `min()` calculation with the given $arguments.
*
* Each argument must be either a {@see SassNumber}, a {@see SassCalculation}, an
* unquoted {@see SassString}, a {@see CalculationOperation}, or a
* {@see CalculationInterpolation}. It must be passed at least one argument.
*
* This automatically simplifies the calculation, so it may return a
* {@see SassNumber} rather than a {@see SassCalculation}. It throws an exception if it
* can determine that the calculation will definitely produce invalid CSS.
*
* @param list<object> $arguments
*
* @return Value
*
* @throws SassScriptException
*/
public static function min(array $arguments): Value
{
$args = self::simplifyArguments($arguments);
if (!$args) {
throw new \InvalidArgumentException('min() must have at least one argument.');
}
/** @var SassNumber|null $minimum */
$minimum = null;
foreach ($args as $arg) {
if (!$arg instanceof SassNumber || $minimum !== null && !$minimum->isComparableTo($arg)) {
$minimum = null;
break;
}
if ($minimum === null || $minimum->greaterThan($arg)->isTruthy()) {
$minimum = $arg;
}
}
if ($minimum !== null) {
return $minimum;
}
self::verifyCompatibleNumbers($args);
return new SassCalculation('min', $args);
}
/**
* Creates a `max()` calculation with the given $arguments.
*
* Each argument must be either a {@see SassNumber}, a {@see SassCalculation}, an
* unquoted {@see SassString}, a {@see CalculationOperation}, or a
* {@see CalculationInterpolation}. It must be passed at least one argument.
*
* This automatically simplifies the calculation, so it may return a
* {@see SassNumber} rather than a {@see SassCalculation}. It throws an exception if it
* can determine that the calculation will definitely produce invalid CSS.
*
* @param list<object> $arguments
*
* @return Value
*
* @throws SassScriptException
*/
public static function max(array $arguments): Value
{
$args = self::simplifyArguments($arguments);
if (!$args) {
throw new \InvalidArgumentException('max() must have at least one argument.');
}
/** @var SassNumber|null $maximum */
$maximum = null;
foreach ($args as $arg) {
if (!$arg instanceof SassNumber || $maximum !== null && !$maximum->isComparableTo($arg)) {
$maximum = null;
break;
}
if ($maximum === null || $maximum->lessThan($arg)->isTruthy()) {
$maximum = $arg;
}
}
if ($maximum !== null) {
return $maximum;
}
self::verifyCompatibleNumbers($args);
return new SassCalculation('max', $args);
}
/**
* Creates a `clamp()` calculation with the given $min, $value, and $max.
*
* Each argument must be either a {@see SassNumber}, a {@see SassCalculation}, an
* unquoted {@see SassString}, a {@see CalculationOperation}, or a
* {@see CalculationInterpolation}.
*
* This automatically simplifies the calculation, so it may return a
* {@see SassNumber} rather than a {@see SassCalculation}. It throws an exception if it
* can determine that the calculation will definitely produce invalid CSS.
*
* This may be passed fewer than three arguments, but only if one of the
* arguments is an unquoted `var()` string.
*
* @param object $min
* @param object|null $value
* @param object|null $max
*
* @return Value
*
* @throws SassScriptException
*/
public static function clamp(object $min, object $value = null, object $max = null): Value
{
if ($value === null && $max !== null) {
throw new \InvalidArgumentException('If value is null, max must also be null.');
}
$min = self::simplify($min);
if ($value !== null) {
$value = self::simplify($value);
}
if ($max !== null) {
$max = self::simplify($max);
}
if ($min instanceof SassNumber && $value instanceof SassNumber && $max instanceof SassNumber && $min->hasCompatibleUnits($value) && $min->hasCompatibleUnits($max)) {
if ($value->lessThanOrEquals($min)->isTruthy()) {
return $min;
}
if ($value->greaterThanOrEquals($max)->isTruthy()) {
return $max;
}
return $value;
}
$args = array_filter([$min, $value, $max]);
self::verifyCompatibleNumbers($args);
self::verifyLength($args, 3);
return new SassCalculation('clamp', $args);
}
/**
* Creates and simplifies a {@see CalculationOperation} with the given $operator,
* $left, and $right.
*
* This automatically simplifies the operation, so it may return a
* {@see SassNumber} rather than a {@see CalculationOperation}.
*
* Each of $left and $right must be either a {@see SassNumber}, a
* {@see SassCalculation}, an unquoted {@see SassString}, a {@see CalculationOperation}, or
* a {@see CalculationInterpolation}.
*
* @param string $operator
* @param object $left
* @param object $right
*
* @return object
*
* @phpstan-param CalculationOperator::* $operator
*
* @throws SassScriptException
*/
public static function operate(string $operator, object $left, object $right): object
{
return self::operateInternal($operator, $left, $right, false, true);
}
/**
* Like {@see operate}, but with the internal-only $inMinMax parameter.
*
* If $inMinMax is `true`, this allows unitless numbers to be added and
* subtracted with numbers with units, for backwards-compatibility with the
* old global `min()` and `max()` functions.
*
* If $simplify is `false`, no simplification will be done.
*
* @param string $operator
* @param object $left
* @param object $right
* @param bool $inMinMax
* @param bool $simplify
*
* @return object
*
* @throws SassScriptException
*
* @phpstan-param CalculationOperator::* $operator
*
* @internal
*/
public static function operateInternal(string $operator, object $left, object $right, bool $inMinMax, bool $simplify): object
{
if (!$simplify) {
return new CalculationOperation($operator, $left, $right);
}
$left = self::simplify($left);
$right = self::simplify($right);
if ($operator === CalculationOperator::PLUS || $operator === CalculationOperator::MINUS) {
if ($left instanceof SassNumber && $right instanceof SassNumber && ($inMinMax ? $left->isComparableTo($right) : $left->hasCompatibleUnits($right))) {
return $operator === CalculationOperator::PLUS ? $left->plus($right) : $left->minus($right);
}
self::verifyCompatibleNumbers([$left, $right]);
if ($right instanceof SassNumber && NumberUtil::fuzzyLessThan($right->getValue(), 0)) {
$right = $right->times(SassNumber::create(-1));
$operator = $operator === CalculationOperator::PLUS ? CalculationOperator::MINUS : CalculationOperator::PLUS;
}
return new CalculationOperation($operator, $left, $right);
}
if ($left instanceof SassNumber && $right instanceof SassNumber) {
return $operator === CalculationOperator::TIMES ? $left->times($right) : $left->dividedBy($right);
}
return new CalculationOperation($operator, $left, $right);
}
/**
* An internal constructor that doesn't perform any validation or
* simplification.
*
* @param string $name
* @param list<object> $arguments
*/
private function __construct(string $name, array $arguments)
{
$this->name = $name;
$this->arguments = $arguments;
}
public function getName(): string
{
return $this->name;
}
public function isSpecialNumber(): bool
{
return true;
}
/**
* @return list<object>
*/
public function getArguments(): array
{
return $this->arguments;
}
public function accept(ValueVisitor $visitor)
{
return $visitor->visitCalculation($this);
}
public function assertCalculation(?string $name = null): SassCalculation
{
return $this;
}
public function plus(Value $other): Value
{
if ($other instanceof SassString) {
return parent::plus($other);
}
throw new SassScriptException("Undefined operation \"$this + $other\".");
}
public function minus(Value $other): Value
{
throw new SassScriptException("Undefined operation \"$this - $other\".");
}
public function unaryPlus(): Value
{
throw new SassScriptException("Undefined operation \"+$this\".");
}
public function unaryMinus(): Value
{
throw new SassScriptException("Undefined operation \"-$this\".");
}
public function equals(object $other): bool
{
if (!$other instanceof SassCalculation || $this->name !== $other->name) {
return false;
}
if (\count($this->arguments) !== \count($other->arguments)) {
return false;
}
foreach ($this->arguments as $i => $argument) {
assert($argument instanceof Equatable);
$otherArgument = $other->arguments[$i];
if (!$argument->equals($otherArgument)) {
return false;
}
}
return true;
}
/**
* @param list<object> $args
*
* @return list<object>
*
* @throws SassScriptException
*/
private static function simplifyArguments(array $args): array
{
return array_map([self::class, 'simplify'], $args);
}
/**
* @throws SassScriptException
*/
private static function simplify(object $arg): object
{
if ($arg instanceof SassNumber || $arg instanceof CalculationInterpolation || $arg instanceof CalculationOperation) {
return $arg;
}
if ($arg instanceof SassString) {
if (!$arg->hasQuotes()) {
return $arg;
}
throw new SassScriptException("Quoted string $arg can't be used in a calculation.");
}
if ($arg instanceof SassCalculation) {
return $arg->getName() === 'calc' ? $arg->getArguments()[0] : $arg;
}
if ($arg instanceof Value) {
throw new SassScriptException("Value $arg can't be used in a calculation.");
}
throw new \InvalidArgumentException(sprintf('Unexpected calculation argument %s.', get_class($arg)));
}
/**
* Verifies that all the numbers in $args aren't known to be incompatible
* with one another, and that they don't have units that are too complex for
* calculations.
*
* @param list<object> $args
*
* @throws SassScriptException
*/
private static function verifyCompatibleNumbers(array $args): void
{
foreach ($args as $arg) {
if (!$arg instanceof SassNumber) {
continue;
}
if (\count($arg->getNumeratorUnits()) > 1 || \count($arg->getDenominatorUnits())) {
throw new SassScriptException("Number $arg isn't compatible with CSS calculations.");
}
}
for ($i = 0; $i < \count($args); $i++) {
$number1 = $args[$i];
if (!$number1 instanceof SassNumber) {
continue;
}
for ($j = $i + 1; $j < \count($args); $j++) {
$number2 = $args[$j];
if (!$number2 instanceof SassNumber) {
continue;
}
if ($number1->hasPossiblyCompatibleUnits($number2)) {
continue;
}
throw new SassScriptException("$number1 and $number2 are incompatible.");
}
}
}
/**
* Throws a {@see SassScriptException} if $args isn't $expectedLength *and*
* doesn't contain either a {@see SassString} or a {@see CalculationInterpolation}.
*
* @param list<object> $args
* @param int $expectedLength
*
* @throws SassScriptException
*/
private static function verifyLength(array $args, int $expectedLength): void
{
if (\count($args) === $expectedLength) {
return;
}
foreach ($args as $arg) {
if ($arg instanceof SassString || $arg instanceof CalculationInterpolation) {
return;
}
}
$length = \count($args);
$verb = $length === 1 ? 'was' : 'were';
throw new SassScriptException("$expectedLength arguments required, but only $length $verb passed.");
}
}