refactor: Cleanup git state - commit all staged changes
Major refactoring cleanup: - Add new controller architecture (class-controller-*.php) - Add new settings-v2 UI (views/settings-v2/) - Add new CSS architecture (agentic-sidebar.css, tokens) - Add esbuild build pipeline (scripts/build.js, package.json) - Add composer dependencies (vendor/) - Add frontend src directory (assets/js/src/index.jsx) - Add documentation files - Remove old/obsolete files (class-settings.php, old CSS) This commits all pending changes from previous refactoring efforts.
This commit is contained in:
21
vendor/parsica-php/parsica/src/Curry/Placeholder.php
vendored
Normal file
21
vendor/parsica-php/parsica/src/Curry/Placeholder.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This code is forked from https://github.com/matteosister/php-curry, which is abandoned. It could be integrated into
|
||||
* the rest of Parsica.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Curry;
|
||||
|
||||
/**
|
||||
* This class is created simply to define a special type
|
||||
* for the placeholder. As defining a constant, even
|
||||
* a random one, could collide with other values.
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Placeholder
|
||||
{
|
||||
public function __toString() : string
|
||||
{
|
||||
return '__';
|
||||
}
|
||||
}
|
||||
87
vendor/parsica-php/parsica/src/Curry/README.md
vendored
Normal file
87
vendor/parsica-php/parsica/src/Curry/README.md
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
# php-curry
|
||||
|
||||
An implementation for currying in PHP
|
||||
|
||||
Currying a function means the ability to pass a subset of arguments to a function, and receive back another function that accepts the rest of the arguments. As soon as the last one is passed it gets back the final result.
|
||||
|
||||
Like this:
|
||||
|
||||
``` php
|
||||
$adder = function ($a, $b, $c, $d) {
|
||||
return $a + $b + $c + $d;
|
||||
};
|
||||
|
||||
$firstTwo = C\curry($adder, 1, 2);
|
||||
echo $firstTwo(3, 4); // output 10
|
||||
|
||||
$firstThree = $firstTwo(3);
|
||||
echo $firstThree(14); // output 20
|
||||
```
|
||||
|
||||
Currying is a powerful (yet simple) concept, very popular in other, more purely functional languages. In haskell for example, currying is the default behavior for every function.
|
||||
|
||||
In PHP we still need to rely on a wrapper to simulate the behavior
|
||||
|
||||
### Right to left
|
||||
|
||||
It's possible to curry a function from left (default) or from right.
|
||||
|
||||
``` php
|
||||
$divider = function ($a, $b) {
|
||||
return $a / $b;
|
||||
};
|
||||
|
||||
$divide10By = C\curry($divider, 10);
|
||||
$divideBy10 = C\curry_right($divider, 10);
|
||||
|
||||
echo $divide10By(10); // output 1
|
||||
echo $divideBy10(100); // output 10
|
||||
```
|
||||
|
||||
### Optional parameters
|
||||
|
||||
Optional parameters and currying do not play very nicely together. This library excludes optional parameters by default.
|
||||
|
||||
``` php
|
||||
$haystack = "haystack";
|
||||
$searches = ['h', 'a', 'z'];
|
||||
$strpos = C\curry('strpos', $haystack); // You can pass function as string too!
|
||||
var_dump(array_map($strpos, $searches)); // output [0, 1, false]
|
||||
```
|
||||
|
||||
But strpos has an optional $offset parameter that by default has not been considered.
|
||||
|
||||
If you want to take this optional $offset parameter into account you should "fix" the curry to a given length.
|
||||
|
||||
``` php
|
||||
$haystack = "haystack";
|
||||
$searches = ['h', 'a', 'z'];
|
||||
$strpos = C\curry_fixed(3, 'strpos', $haystack);
|
||||
$finders = array_map($strpos, $searches);
|
||||
var_dump(array_map(function ($finder) {
|
||||
return $finder(2);
|
||||
}, $finders)); // output [false, 5, false]
|
||||
```
|
||||
|
||||
*curry_right* has its own fixed version named *curry_right_fixed*
|
||||
|
||||
### Placeholders
|
||||
|
||||
The function `__()` gets a special placeholder value used to specify "gaps" within curried functions, allowing partial application of any combination of arguments, regardless of their positions.
|
||||
|
||||
```php
|
||||
$add = function($x, $y)
|
||||
{
|
||||
return $x + $y;
|
||||
};
|
||||
$reduce = C\curry('array_reduce');
|
||||
$sum = $reduce(C\__(), $add);
|
||||
|
||||
echo $sum([1, 2, 3, 4], 0); // output 10
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
|
||||
- Placeholders should be used only for required arguments.
|
||||
|
||||
- When used, optional arguments must be at the end of the arguments list.
|
||||
214
vendor/parsica-php/parsica/src/Curry/functions.php
vendored
Normal file
214
vendor/parsica-php/parsica/src/Curry/functions.php
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This code is forked from https://github.com/matteosister/php-curry, which is abandoned. It could be integrated into
|
||||
* the rest of Parsica.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Curry;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
*
|
||||
* @psalm-return pure-callable
|
||||
* @throws Exception
|
||||
* @psalm-pure
|
||||
*/
|
||||
function curry(callable $callable) : callable
|
||||
{
|
||||
return _number_of_required_params($callable) === 0
|
||||
? _make_function($callable)
|
||||
: _curry_array_args($callable, _rest(func_get_args()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
*
|
||||
* @psalm-return pure-callable
|
||||
* @psalm-pure
|
||||
*/
|
||||
function curry_right(callable $callable) : callable
|
||||
{
|
||||
return _number_of_required_params($callable) < 2
|
||||
? _make_function($callable)
|
||||
: _curry_array_args($callable, _rest(func_get_args()), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
* @psalm-param array $args
|
||||
* @psalm-param bool $left
|
||||
*
|
||||
* @psalm-return pure-callable
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _curry_array_args(callable $callable, array $args, bool $left = true) : callable
|
||||
{
|
||||
return function () use ($callable, $args, $left) {
|
||||
if (_is_fullfilled($callable, $args)) {
|
||||
return _execute($callable, $args, $left);
|
||||
}
|
||||
$newArgs = array_merge($args, func_get_args());
|
||||
if (_is_fullfilled($callable, $newArgs)) {
|
||||
return _execute($callable, $newArgs, $left);
|
||||
}
|
||||
return _curry_array_args($callable, $newArgs, $left);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
* @param array<mixed> $args
|
||||
* @param mixed $left
|
||||
*
|
||||
* @return mixed
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _execute(callable $callable, array $args, bool $left = true)
|
||||
{
|
||||
if (!$left) {
|
||||
$args = array_reverse($args);
|
||||
}
|
||||
|
||||
$placeholderPositions = _placeholder_positions($args);
|
||||
if (0 < count($placeholderPositions)) {
|
||||
$reqdParams = _number_of_required_params($callable);
|
||||
if ($reqdParams <= _last($placeholderPositions)) {
|
||||
// This means that we have more placeholderPositions than needed
|
||||
// I know that throwing exceptions is not really the
|
||||
// functional way, but this case should not happen.
|
||||
throw new Exception("Argument Placeholder found on unexpected position!");
|
||||
}
|
||||
foreach ($placeholderPositions as $placeholderPosition) {
|
||||
/** @psalm-suppress MixedAssignment */
|
||||
$args[$placeholderPosition] = $args[$reqdParams];
|
||||
array_splice($args, $reqdParams, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return call_user_func_array($callable, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _rest(array $args) : array
|
||||
{
|
||||
return array_slice($args, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
* @param array $args
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _is_fullfilled(callable $callable, array $args) : bool
|
||||
{
|
||||
$nonPlaceholderArgs = array_filter(
|
||||
$args,
|
||||
fn($arg) => !($arg instanceof Placeholder)
|
||||
);
|
||||
return count($nonPlaceholderArgs) >= _number_of_required_params($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable $callable
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _number_of_required_params(callable $callable) : int
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
$refl = new ReflectionClass($callable[0]);
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
$method = $refl->getMethod($callable[1]);
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
return $method->getNumberOfRequiredParameters();
|
||||
} elseif (is_string($callable) || $callable instanceof Closure) {
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
$refl = new ReflectionFunction($callable);
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
return $refl->getNumberOfRequiredParameters();
|
||||
}
|
||||
throw new Exception("Unexpected other type of callable");
|
||||
}
|
||||
|
||||
/**
|
||||
* if the callback is an array(instance, method),
|
||||
* it returns an equivalent function for PHP 5.3 compatibility.
|
||||
*
|
||||
* @psalm-param pure-callable $callable
|
||||
*
|
||||
* @psalm-return pure-callable
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _make_function(callable $callable) : callable
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
return /** @return mixed */ fn() => call_user_func_array($callable, func_get_args());
|
||||
}
|
||||
return $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of placeholders positions in the given arguments.
|
||||
*
|
||||
* @param array $args
|
||||
*
|
||||
* @return list<int|string>
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _placeholder_positions(array $args) : array
|
||||
{
|
||||
return array_keys(
|
||||
array_filter(
|
||||
$args,
|
||||
fn($arg) : bool => $arg instanceof Placeholder
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last element in an array.
|
||||
*
|
||||
* @psalm-param array<T> $array
|
||||
*
|
||||
* @psalm-return null|T
|
||||
* @template T
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function _last(array $array)
|
||||
{
|
||||
$lastKey = array_key_last($array);
|
||||
return is_null($lastKey) ? null : $array[$lastKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a special placeholder value used to specify "gaps" within curried
|
||||
* functions, allowing partial application of any combination of arguments,
|
||||
* regardless of their positions. Should be used only for required arguments.
|
||||
* When used, optional arguments must be at the end of the argument list.
|
||||
* @psalm-pure
|
||||
*/
|
||||
function __() : Placeholder
|
||||
{
|
||||
return new Placeholder;
|
||||
}
|
||||
75
vendor/parsica-php/parsica/src/Expression/BinaryOperator.php
vendored
Normal file
75
vendor/parsica-php/parsica/src/Expression/BinaryOperator.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BinaryOperator
|
||||
{
|
||||
/**
|
||||
* @psalm-var Parser<TSymbol>
|
||||
*/
|
||||
private Parser $symbol;
|
||||
|
||||
/**
|
||||
* @psalm-var pure-callable(TExpressionAST, TExpressionAST):TExpressionAST
|
||||
*/
|
||||
private $transform;
|
||||
|
||||
private string $label;
|
||||
|
||||
/**
|
||||
* @psalm-param Parser<TSymbol> $symbol
|
||||
* @psalm-param pure-callable(TExpressionAST, TExpressionAST):TExpressionAST $transform
|
||||
* @psalm-param string $label
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(Parser $symbol, callable $transform, string $label = "")
|
||||
{
|
||||
$this->symbol = $symbol;
|
||||
$this->transform = $transform;
|
||||
$this->label = $label ?: $symbol->getLabel() . " operator";
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<TSymbol>
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
function symbol(): Parser
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return pure-callable(TExpressionAST, TExpressionAST):TExpressionAST
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
function transform(): callable
|
||||
{
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
function label(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
}
|
||||
27
vendor/parsica-php/parsica/src/Expression/ExpressionType.php
vendored
Normal file
27
vendor/parsica-php/parsica/src/Expression/ExpressionType.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
interface ExpressionType
|
||||
{
|
||||
/**
|
||||
* @psalm-param Parser<TExpressionAST> $previousPrecedenceLevel
|
||||
* @psalm-return Parser<TExpressionAST>
|
||||
*/
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser;
|
||||
}
|
||||
90
vendor/parsica-php/parsica/src/Expression/LeftAssoc.php
vendored
Normal file
90
vendor/parsica-php/parsica/src/Expression/LeftAssoc.php
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\Curry\curry;
|
||||
use function Parsica\Parsica\choice;
|
||||
use function Parsica\Parsica\collect;
|
||||
use function Parsica\Parsica\Internal\FP\flip;
|
||||
use function Parsica\Parsica\Internal\FP\foldl;
|
||||
use function Parsica\Parsica\many;
|
||||
use function Parsica\Parsica\map;
|
||||
use function Parsica\Parsica\pure;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class LeftAssoc implements ExpressionType
|
||||
{
|
||||
/** @psalm-var non-empty-list<BinaryOperator<TSymbol, TExpressionAST>> */
|
||||
private array $operators;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @psalm-param non-empty-list<BinaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(array $operators)
|
||||
{
|
||||
$this->operators = $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param Parser<TExpressionAST> $previousPrecedenceLevel
|
||||
* @psalm-return Parser<TExpressionAST>
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var list<Parser<pure-callable(Parser<TExpressionAST>):Parser<TExpressionAST>>> $operatorParsers
|
||||
*/
|
||||
$operatorParsers = [];
|
||||
// @todo use folds?
|
||||
foreach ($this->operators as $operator) {
|
||||
$operatorParsers[] =
|
||||
pure(curry(flip($operator->transform())))
|
||||
->apply($operator->symbol()->followedBy($previousPrecedenceLevel))
|
||||
->label($operator->label());
|
||||
}
|
||||
|
||||
return map(
|
||||
collect(
|
||||
$previousPrecedenceLevel,
|
||||
many(choice(...$operatorParsers))
|
||||
),
|
||||
|
||||
/**
|
||||
* @psalm-param array{0: TExpressionAST, 1: list<pure-callable(TExpressionAST):TExpressionAST>} $o
|
||||
* @psalm-return TExpressionAST
|
||||
* @psalm-pure
|
||||
*/
|
||||
fn(array $o) => foldl(
|
||||
$o[1],
|
||||
|
||||
/**
|
||||
* @psalm-param TExpressionAST $acc
|
||||
* @psalm-param pure-callable(TExpressionAST):TExpressionAST $appl
|
||||
* @psalm-return TExpressionAST
|
||||
* @psalm-pure
|
||||
*/
|
||||
fn($acc, callable $appl) => $appl($acc),
|
||||
$o[0]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
65
vendor/parsica-php/parsica/src/Expression/NonAssoc.php
vendored
Normal file
65
vendor/parsica-php/parsica/src/Expression/NonAssoc.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\choice;
|
||||
use function Parsica\Parsica\collect;
|
||||
use function Parsica\Parsica\map;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class NonAssoc implements ExpressionType
|
||||
{
|
||||
/**
|
||||
* @psalm-var BinaryOperator<TSymbol, TExpressionAST>
|
||||
*/
|
||||
private BinaryOperator $operator;
|
||||
|
||||
/**
|
||||
* @psalm-param BinaryOperator<TSymbol, TExpressionAST> $operator
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(BinaryOperator $operator)
|
||||
{
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param Parser<TExpressionAST> $previousPrecedenceLevel
|
||||
* @psalm-return Parser<TExpressionAST>
|
||||
*/
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser
|
||||
{
|
||||
return choice(
|
||||
map(
|
||||
collect(
|
||||
$previousPrecedenceLevel,
|
||||
$this->operator->symbol(),
|
||||
$previousPrecedenceLevel
|
||||
),
|
||||
|
||||
/**
|
||||
* @psalm-param array{0: TExpressionAST, 1: TSymbol, 2: TExpressionAST} $o
|
||||
* @psalm-return TExpressionAST
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
fn(array $o) => $this->operator->transform()($o[0], $o[2])),
|
||||
$previousPrecedenceLevel
|
||||
);
|
||||
}
|
||||
}
|
||||
51
vendor/parsica-php/parsica/src/Expression/Postfix.php
vendored
Normal file
51
vendor/parsica-php/parsica/src/Expression/Postfix.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\choice;
|
||||
use function Parsica\Parsica\keepFirst;
|
||||
use function Parsica\Parsica\pure;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Postfix implements ExpressionType
|
||||
{
|
||||
/** @psalm-var non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> */
|
||||
private array $operators;
|
||||
|
||||
/**
|
||||
* @psalm-param non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(array $operators)
|
||||
{
|
||||
$this->operators = $operators;
|
||||
}
|
||||
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser
|
||||
{
|
||||
$operatorParsers = [];
|
||||
foreach ($this->operators as $operator) {
|
||||
$operatorParsers[] =
|
||||
pure($operator->transform())
|
||||
->apply(keepFirst($previousPrecedenceLevel, $operator->symbol()))
|
||||
->label($operator->label());
|
||||
}
|
||||
|
||||
return choice(...$operatorParsers)->or($previousPrecedenceLevel);
|
||||
}
|
||||
}
|
||||
51
vendor/parsica-php/parsica/src/Expression/Prefix.php
vendored
Normal file
51
vendor/parsica-php/parsica/src/Expression/Prefix.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\choice;
|
||||
use function Parsica\Parsica\pure;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Prefix implements ExpressionType
|
||||
{
|
||||
/** @psalm-var non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> */
|
||||
private array $operators;
|
||||
|
||||
/**
|
||||
* @psalm-param non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(array $operators)
|
||||
{
|
||||
$this->operators = $operators;
|
||||
}
|
||||
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser
|
||||
{
|
||||
$operatorParsers = [];
|
||||
foreach ($this->operators as $operator) {
|
||||
|
||||
$operatorParsers[] =
|
||||
pure($operator->transform())
|
||||
->apply($operator->symbol()->followedBy($previousPrecedenceLevel))
|
||||
->label($operator->label());
|
||||
}
|
||||
|
||||
return choice(...$operatorParsers)->or($previousPrecedenceLevel);
|
||||
}
|
||||
}
|
||||
86
vendor/parsica-php/parsica/src/Expression/RightAssoc.php
vendored
Normal file
86
vendor/parsica-php/parsica/src/Expression/RightAssoc.php
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\Curry\curry;
|
||||
use function Parsica\Parsica\choice;
|
||||
use function Parsica\Parsica\collect;
|
||||
use function Parsica\Parsica\Internal\FP\foldr;
|
||||
use function Parsica\Parsica\keepFirst;
|
||||
use function Parsica\Parsica\many;
|
||||
use function Parsica\Parsica\map;
|
||||
use function Parsica\Parsica\pure;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class RightAssoc implements ExpressionType
|
||||
{
|
||||
/** @var non-empty-list<BinaryOperator<TSymbol, TExpressionAST>> */
|
||||
private array $operators;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @psalm-param non-empty-list<BinaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(array $operators)
|
||||
{
|
||||
$this->operators = $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param Parser<TExpressionAST> $previousPrecedenceLevel
|
||||
* @psalm-return Parser<TExpressionAST>
|
||||
*/
|
||||
public function buildPrecedenceLevel(Parser $previousPrecedenceLevel): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var list<Parser<pure-callable(Parser<TExpressionAST>):Parser<TExpressionAST>>> $operatorParsers
|
||||
*/
|
||||
$operatorParsers = [];
|
||||
foreach ($this->operators as $operator) {
|
||||
$operatorParsers[] =
|
||||
pure(curry($operator->transform()))
|
||||
->apply(keepFirst($previousPrecedenceLevel, $operator->symbol()))
|
||||
->label($operator->label());
|
||||
}
|
||||
|
||||
return map(
|
||||
collect(
|
||||
many(choice(...$operatorParsers)),
|
||||
$previousPrecedenceLevel
|
||||
),
|
||||
|
||||
/**
|
||||
* @psalm-param array{0: list<pure-callable(TExpressionAST):TExpressionAST>, 1: TExpressionAST} $o
|
||||
* @psalm-return TExpressionAST
|
||||
*/
|
||||
fn(array $o) => foldr(
|
||||
$o[0],
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable(TExpressionAST):TExpressionAST $appl
|
||||
* @psalm-param TExpressionAST $acc
|
||||
* @psalm-return TExpressionAST
|
||||
*/
|
||||
fn(callable $appl, $acc) => $appl($acc),
|
||||
$o[1]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
69
vendor/parsica-php/parsica/src/Expression/UnaryOperator.php
vendored
Normal file
69
vendor/parsica-php/parsica/src/Expression/UnaryOperator.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class UnaryOperator
|
||||
{
|
||||
/**
|
||||
* @psalm-var Parser<TSymbol>
|
||||
*/
|
||||
private Parser $symbol;
|
||||
|
||||
/**
|
||||
* @psalm-var callable(TExpressionAST):TExpressionAST
|
||||
*/
|
||||
private $transform;
|
||||
|
||||
private string $label;
|
||||
|
||||
/**
|
||||
* @psalm-param Parser<TSymbol> $symbol
|
||||
* @psalm-param callable(TExpressionAST):TExpressionAST $transform
|
||||
* @psalm-param string $label
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
function __construct(Parser $symbol, callable $transform, string $label = "")
|
||||
{
|
||||
$this->symbol = $symbol;
|
||||
$this->transform = $transform;
|
||||
$this->label = $label ?: $symbol->getLabel() . " operator";
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<TSymbol>
|
||||
*/
|
||||
function symbol(): Parser
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return callable(TExpressionAST):TExpressionAST
|
||||
*/
|
||||
function transform(): callable
|
||||
{
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
function label(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
}
|
||||
159
vendor/parsica-php/parsica/src/Expression/expression.php
vendored
Normal file
159
vendor/parsica-php/parsica/src/Expression/expression.php
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Expression;
|
||||
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\Internal\FP\foldl;
|
||||
|
||||
/**
|
||||
* Build an expression parser from a term parser and an expression table.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @template TTerm
|
||||
* @template TExpressionAST
|
||||
*
|
||||
* @psalm-param Parser<TTerm> $term
|
||||
* @psalm-param list<ExpressionType> $expressionTable
|
||||
*
|
||||
* @psalm-return Parser<TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function expression(Parser $term, array $expressionTable): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var Parser<TExpressionAST> $parser
|
||||
*/
|
||||
$parser = foldl(
|
||||
$expressionTable,
|
||||
fn(Parser $previous, ExpressionType $next) => $next->buildPrecedenceLevel($previous),
|
||||
$term
|
||||
);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* A binary operator in an expression. The operands of the expression will be passed into $transform to produce the
|
||||
* output of the expression parser.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param Parser<TSymbol> $symbol
|
||||
* @psalm-param pure-callable(TExpressionAST, TExpressionAST):TExpressionAST $transform
|
||||
* @psalm-param string $label
|
||||
*
|
||||
* @psalm-return BinaryOperator<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function binaryOperator(Parser $symbol, callable $transform, string $label = ""): BinaryOperator
|
||||
{
|
||||
return new BinaryOperator($symbol, $transform, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unary operator in an expression. The operands of the expression will be passed into $transform to produce the
|
||||
* output of the expression parser.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param Parser<TSymbol> $symbol
|
||||
* @psalm-param callable(TExpressionAST):TExpressionAST $transform
|
||||
* @psalm-param string $label
|
||||
*
|
||||
* @return UnaryOperator<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function unaryOperator(Parser $symbol, callable $transform, string $label = ""): UnaryOperator
|
||||
{
|
||||
return new UnaryOperator($symbol, $transform, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param non-empty-list<BinaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-return LeftAssoc<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function leftAssoc(BinaryOperator ...$operators): LeftAssoc
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmptyList($operators, "LeftAssoc expects at least one Operator");
|
||||
return new LeftAssoc($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param non-empty-list<BinaryOperator<TSymbol,TExpressionAST>> $operators
|
||||
* @psalm-return RightAssoc<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function rightAssoc(BinaryOperator ...$operators): RightAssoc
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmptyList($operators, "RightAssoc expects at least one Operator");
|
||||
return new RightAssoc($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param BinaryOperator<TSymbol, TExpressionAST> $operator
|
||||
* @psalm-return NonAssoc<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function nonAssoc(BinaryOperator $operator): NonAssoc
|
||||
{
|
||||
return new NonAssoc($operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
*
|
||||
* @psalm-param non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-return Prefix<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function prefix(UnaryOperator ...$operators): Prefix
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmptyList($operators, "Prefix expects at least one Operator");
|
||||
return new Prefix($operators);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
* @template TSymbol
|
||||
* @template TExpressionAST
|
||||
* @psalm-param non-empty-list<UnaryOperator<TSymbol, TExpressionAST>> $operators
|
||||
* @psalm-return Postfix<TSymbol, TExpressionAST>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function postfix(UnaryOperator ...$operators): Postfix
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmptyList($operators, "Postfix expects at least one Operator");
|
||||
return new Postfix($operators);
|
||||
}
|
||||
125
vendor/parsica-php/parsica/src/Internal/Ascii.php
vendored
Normal file
125
vendor/parsica-php/parsica/src/Internal/Ascii.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Ascii
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function printable(string $char): string
|
||||
{
|
||||
switch (mb_ord($char)) {
|
||||
case 0:
|
||||
return "<null>";
|
||||
case 1:
|
||||
return "<start of header>";
|
||||
case 2:
|
||||
return "<start of text>";
|
||||
case 3:
|
||||
return "<end of text>";
|
||||
case 4:
|
||||
return "<end of transmission>";
|
||||
case 5:
|
||||
return "<enquiry>";
|
||||
case 6:
|
||||
return "<acknowledge>";
|
||||
case 7:
|
||||
return "<bell>";
|
||||
case 8:
|
||||
return "<backspace>";
|
||||
case 9:
|
||||
return "<horizontal tab>";
|
||||
case 10:
|
||||
return "<line feed>";
|
||||
case 11:
|
||||
return "<vertical tab>";
|
||||
case 12:
|
||||
return "<form feed>";
|
||||
case 13:
|
||||
return "<carriage return>";
|
||||
case 14:
|
||||
return "<shift out>";
|
||||
case 15:
|
||||
return "<shift in>";
|
||||
case 16:
|
||||
return "<data link escape>";
|
||||
case 17:
|
||||
return "<device control 1>";
|
||||
case 18:
|
||||
return "<device control 2>";
|
||||
case 19:
|
||||
return "<device control 3>";
|
||||
case 20:
|
||||
return "<device control 4>";
|
||||
case 21:
|
||||
return "<negative acknowledge>";
|
||||
case 22:
|
||||
return "<synchronize>";
|
||||
case 23:
|
||||
return "<end of transmission block>";
|
||||
case 24:
|
||||
return "<cancel>";
|
||||
case 25:
|
||||
return "<end of medium>";
|
||||
case 26:
|
||||
return "<substitute>";
|
||||
case 27:
|
||||
return "<escape>";
|
||||
case 28:
|
||||
return "<file separator>";
|
||||
case 29:
|
||||
return "<group separator>";
|
||||
case 30:
|
||||
return "<record separator>";
|
||||
case 31:
|
||||
return "<unit separator>";
|
||||
case 32:
|
||||
return "<space>";
|
||||
case 34:
|
||||
return "<double quote>";
|
||||
case 39:
|
||||
return "<single quote>";
|
||||
case 47:
|
||||
return "<slash>";
|
||||
case 92:
|
||||
return "<backslash>";
|
||||
case 96:
|
||||
return "<accent>";
|
||||
case 127:
|
||||
return "<delete>";
|
||||
case 130:
|
||||
return "<single low-9 quotation mark>";
|
||||
case 132:
|
||||
return "<double low-9 quotation mark>";
|
||||
case 145:
|
||||
return "<left single quotation mark>";
|
||||
case 146:
|
||||
return "<right single quotation mark>";
|
||||
case 147:
|
||||
return "<left double quotation mark>";
|
||||
case 148:
|
||||
return "<right double quotation mark>";
|
||||
case 160:
|
||||
return "<non-breaking space>";
|
||||
default:
|
||||
return "'$char'";
|
||||
}
|
||||
}
|
||||
}
|
||||
111
vendor/parsica-php/parsica/src/Internal/Assert.php
vendored
Normal file
111
vendor/parsica-php/parsica/src/Internal/Assert.php
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Assert
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function nonEmpty(string $str): void
|
||||
{
|
||||
Assert::minLength($str, 1, "The string must not be empty.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-assert list $l
|
||||
* @psalm-assert !empty $l
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function nonEmptyList(array $l, string $message): void
|
||||
{
|
||||
if (empty($l)) {
|
||||
throw new InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function minLength(string $value, int $length, string $message): void
|
||||
{
|
||||
if (mb_strlen($value) < $length) {
|
||||
throw new InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param list<string> $chars
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function singleChars(array $chars): void
|
||||
{
|
||||
foreach ($chars as $char) {
|
||||
Assert::singleChar($char);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function singleChar(string $char): void
|
||||
{
|
||||
Assert::length($char, 1, "The argument must be a single character");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function length(string $value, int $length, string $message): void
|
||||
{
|
||||
if ($length !== mb_strlen($value)) {
|
||||
throw new InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param mixed $f
|
||||
* @internal
|
||||
* @param callable|mixed $f
|
||||
*/
|
||||
public static function isCallable($f, string $message) : void
|
||||
{
|
||||
if (!is_callable($f)) {
|
||||
throw new InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public static function atLeastOneArg(array $args, string $source): void
|
||||
{
|
||||
if (0 == count($args)) {
|
||||
throw new InvalidArgumentException("$source expects at least one Parser");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vendor/parsica-php/parsica/src/Internal/EndOfStream.php
vendored
Normal file
16
vendor/parsica-php/parsica/src/Internal/EndOfStream.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
final class EndOfStream extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
70
vendor/parsica-php/parsica/src/Internal/FP.php
vendored
Normal file
70
vendor/parsica-php/parsica/src/Internal/FP.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal\FP;
|
||||
|
||||
/**
|
||||
* Swaps the arguments of the callable, returning a callable.
|
||||
*
|
||||
* @internal
|
||||
* @template Ta
|
||||
* @template Tb
|
||||
* @template Tc
|
||||
* @psalm-param pure-callable(Ta, Tb):Tc $f
|
||||
* @psalm-return pure-callable(Tb, Ta):Tc
|
||||
* @psalm-pure
|
||||
*/
|
||||
function flip(callable $f): callable
|
||||
{
|
||||
/**
|
||||
* @psalm-param Ta $x
|
||||
* @psalm-param Tb $y
|
||||
* @psalm-return Tc
|
||||
*/
|
||||
return fn($x, $y) => $f($y, $x);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TA
|
||||
* @template TB
|
||||
*
|
||||
* @psalm-param list<TA> $input
|
||||
* @psalm-param callable(TB, TA):TB $function
|
||||
* @psalm-param TB $initial
|
||||
* @psalm-return TB
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function foldl(array $input, callable $function, $initial) {
|
||||
/** @psalm-suppress ImpureFunctionCall */
|
||||
return array_reduce($input, $function, $initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TA
|
||||
* @template TB
|
||||
*
|
||||
* @psalm-param list<TA> $input
|
||||
* @psalm-param pure-callable(TA, TB):TB $function
|
||||
* @psalm-param TB $initial
|
||||
* @psalm-return TB
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
function foldr(array $input, callable $function, $initial) {
|
||||
while($head = array_pop($input))
|
||||
{
|
||||
$initial = $function($head, $initial);
|
||||
}
|
||||
return $initial;
|
||||
}
|
||||
162
vendor/parsica-php/parsica/src/Internal/Fail.php
vendored
Normal file
162
vendor/parsica-php/parsica/src/Internal/Fail.php
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Parsica\Parsica\Parser;
|
||||
use Parsica\Parsica\ParseResult;
|
||||
use Parsica\Parsica\ParserHasFailed;
|
||||
use Parsica\Parsica\Stream;
|
||||
use function Parsica\Parsica\isEqual;
|
||||
use function Parsica\Parsica\notPred;
|
||||
|
||||
/**
|
||||
* The return value of a failed parser.
|
||||
*
|
||||
* @template T
|
||||
* @internal
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Fail implements ParseResult
|
||||
{
|
||||
private string $expected;
|
||||
private Stream $got;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(string $expected, Stream $got)
|
||||
{
|
||||
$this->expected = $expected;
|
||||
$this->got = $got;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
public function errorMessage(): string
|
||||
{
|
||||
try {
|
||||
$firstChar = $this->got->take1()->chunk();
|
||||
$unexpected = Ascii::printable($firstChar);
|
||||
$body = $this->got()->takeWhile(notPred(isEqual("\n")))->chunk();
|
||||
} catch (EndOfStream $e) {
|
||||
$unexpected = $body = "<EOF>";
|
||||
}
|
||||
$lineNumber = $this->got->position()->line();
|
||||
$spaceLength = str_repeat(" ", strlen((string)$lineNumber));
|
||||
$expecting = $this->expected;
|
||||
$position = $this->got->position()->pretty();
|
||||
$columnNumber = $this->got->position()->column();
|
||||
$leftDots = $columnNumber == 1 ? "" : "...";
|
||||
$leftSpace = $columnNumber == 1 ? "" : " ";
|
||||
$bodyLine = "$lineNumber | $leftDots$body";
|
||||
$bodyLine = strlen($bodyLine) > 80 ? (substr($bodyLine, 0, 77) . "...") : $bodyLine;
|
||||
|
||||
return
|
||||
"$position\n"
|
||||
. "$spaceLength |\n"
|
||||
. "$bodyLine\n"
|
||||
. "$spaceLength | $leftSpace^— column $columnNumber\n"
|
||||
. "Unexpected $unexpected\n"
|
||||
. "Expecting $expecting";
|
||||
}
|
||||
|
||||
public function got(): Stream
|
||||
{
|
||||
return $this->got;
|
||||
}
|
||||
|
||||
public function expected(): string
|
||||
{
|
||||
return $this->expected;
|
||||
}
|
||||
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isFail(): bool
|
||||
{
|
||||
return !$this->isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return T
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
throw new BadMethodCallException("Can't read the output of a failed ParseResult.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param ParseResult<T> $other
|
||||
*
|
||||
* @psalm-return ParseResult<T>
|
||||
*/
|
||||
public function append(ParseResult $other): ParseResult
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a function over the output
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param callable(T) : T2 $transform
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
*/
|
||||
public function map(callable $transform): ParseResult
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param Parser<T2> $parser
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
*/
|
||||
public function continueWith(Parser $parser): ParseResult
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function remainder(): Stream
|
||||
{
|
||||
throw new BadMethodCallException("Can't read the remainder of a failed ParseResult.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function position(): Position
|
||||
{
|
||||
return $this->got->position();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function throw() : void
|
||||
{
|
||||
throw new ParserHasFailed($this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
88
vendor/parsica-php/parsica/src/Internal/Position.php
vendored
Normal file
88
vendor/parsica-php/parsica/src/Internal/Position.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
/**
|
||||
* File, line, and column position of the parser.
|
||||
*
|
||||
* @psalm-immutable
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
final class Position
|
||||
{
|
||||
/** @psalm-readonly */
|
||||
private string $filename;
|
||||
/** @psalm-readonly */
|
||||
private int $line;
|
||||
/** @psalm-readonly */
|
||||
private int $column;
|
||||
|
||||
function __construct(string $filename, int $line, int $column)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->line = $line;
|
||||
$this->column = $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial position (line 1, column 1). The optional filename is the source of the input, and is really just a label
|
||||
* to make more useful error messages.
|
||||
*/
|
||||
public static function initial(string $filename = "<input>"): Position
|
||||
{
|
||||
return new Position($filename, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty print as "filename:line:column"
|
||||
*/
|
||||
public function pretty(): string
|
||||
{
|
||||
return $this->filename . ":" . $this->line . ":" . $this->column;
|
||||
}
|
||||
|
||||
public function advance(string $parsed): Position
|
||||
{
|
||||
$column = $this->column;
|
||||
$line = $this->line;
|
||||
foreach (mb_str_split($parsed, 1) as $char) {
|
||||
switch ($char) {
|
||||
case "\n":
|
||||
case "\r":
|
||||
$line++;
|
||||
$column = 1;
|
||||
break;
|
||||
case "\t":
|
||||
$column = $column + 4 - (($column - 1) % 4);
|
||||
break;
|
||||
default:
|
||||
$column++;
|
||||
}
|
||||
}
|
||||
|
||||
return new Position($this->filename, $line, $column);
|
||||
}
|
||||
|
||||
public function filename(): string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function line(): int
|
||||
{
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
public function column(): int
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
}
|
||||
192
vendor/parsica-php/parsica/src/Internal/Succeed.php
vendored
Normal file
192
vendor/parsica-php/parsica/src/Internal/Succeed.php
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Exception;
|
||||
use Parsica\Parsica\Parser;
|
||||
use Parsica\Parsica\ParseResult;
|
||||
use Parsica\Parsica\Stream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template T
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class Succeed implements ParseResult
|
||||
{
|
||||
/**
|
||||
* @psalm-var T
|
||||
*/
|
||||
private $output;
|
||||
|
||||
private Stream $remainder;
|
||||
|
||||
/**
|
||||
* @psalm-param T $output
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
public function __construct($output, Stream $remainder)
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->remainder = $remainder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return T
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function remainder(): Stream
|
||||
{
|
||||
return $this->remainder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isFail(): bool
|
||||
{
|
||||
return !$this->isSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function expected(): string
|
||||
{
|
||||
throw new BadMethodCallException("Can't read the expectation of a succeeded ParseResult.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function got(): Stream
|
||||
{
|
||||
throw new BadMethodCallException("Can't read the expectation of a succeeded ParseResult.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @psalm-param ParseResult<T> $other
|
||||
* @psalm-return ParseResult<T>
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function append(ParseResult $other): ParseResult
|
||||
{
|
||||
if ($other->isFail()) {
|
||||
return $other;
|
||||
} else {
|
||||
/** @psalm-suppress ArgumentTypeCoercion */
|
||||
return $this->appendSuccess($other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO This is hardcoded to only deal with certain types. We need an interface with a append() for arbitrary types.
|
||||
*/
|
||||
private function appendSuccess(Succeed $other): ParseResult
|
||||
{
|
||||
$type1isNull = is_null($this->output);
|
||||
$type2isNull = is_null($other->output);
|
||||
|
||||
// Ignore nulls
|
||||
if ($type1isNull && $type2isNull) {
|
||||
return new Succeed(null, $other->remainder);
|
||||
} elseif(!$type1isNull && $type2isNull) {
|
||||
return new Succeed($this->output, $other->remainder);
|
||||
} elseif($type1isNull) {
|
||||
return new Succeed($other->output, $other->remainder);
|
||||
}
|
||||
|
||||
if (is_string($this->output) && is_string($other->output)) {
|
||||
return new Succeed($this->output . $other->output, $other->remainder);
|
||||
} elseif (is_array($this->output) && is_array($other->output)) {
|
||||
return new Succeed(
|
||||
array_merge($this->output, $other->output),
|
||||
$other->remainder
|
||||
);
|
||||
}
|
||||
|
||||
$type1 = gettype($this->output);
|
||||
$type2 = gettype($other->output);
|
||||
|
||||
throw new Exception("Append only works for ParseResult<T> instances with the same type T, got ParseResult<$type1> and ParseResult<$type2>.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a function over the output
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param pure-callable(T):T2 $transform
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function map(callable $transform): ParseResult
|
||||
{
|
||||
return new Succeed($transform($this->output), $this->remainder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param Parser<T2> $parser
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
*/
|
||||
public function continueWith(Parser $parser): ParseResult
|
||||
{
|
||||
return $parser->run($this->remainder);
|
||||
}
|
||||
|
||||
public function errorMessage(): string
|
||||
{
|
||||
throw new BadMethodCallException("A succeeded ParseResult has no error message.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function position(): Position
|
||||
{
|
||||
return $this->remainder->position();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function throw() : void
|
||||
{
|
||||
throw new BadMethodCallException("You can't throw a successful ParseResult.");
|
||||
}
|
||||
}
|
||||
41
vendor/parsica-php/parsica/src/Internal/TakeResult.php
vendored
Normal file
41
vendor/parsica-php/parsica/src/Internal/TakeResult.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\Internal;
|
||||
|
||||
use Parsica\Parsica\Stream;
|
||||
|
||||
/**
|
||||
* The result of Stream::take*() functions
|
||||
*
|
||||
* @internal
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class TakeResult
|
||||
{
|
||||
private string $chunk;
|
||||
private Stream $stream;
|
||||
|
||||
function __construct(string $chunk, Stream $stream)
|
||||
{
|
||||
$this->chunk = $chunk;
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
function chunk(): string
|
||||
{
|
||||
return $this->chunk;
|
||||
}
|
||||
|
||||
function stream(): Stream
|
||||
{
|
||||
return $this->stream;
|
||||
}
|
||||
}
|
||||
228
vendor/parsica-php/parsica/src/JSON/JSON.php
vendored
Normal file
228
vendor/parsica-php/parsica/src/JSON/JSON.php
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\JSON;
|
||||
|
||||
use Parsica\Parsica\Parser;
|
||||
use function Parsica\Parsica\{any,
|
||||
between,
|
||||
char,
|
||||
choice,
|
||||
collect,
|
||||
float,
|
||||
hexDigitChar,
|
||||
isCharCode,
|
||||
keepFirst,
|
||||
map,
|
||||
recursive,
|
||||
repeat,
|
||||
satisfy,
|
||||
sepBy,
|
||||
string,
|
||||
takeWhile,
|
||||
zeroOrMore};
|
||||
|
||||
/**
|
||||
* JSON parser and utility parsers
|
||||
*
|
||||
* @TODO fix psalm annotations
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class JSON
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully compliant JSON parser, built entirely in Parsica. The output is compatible with PHP's native json_decode().
|
||||
*
|
||||
* It was built to illustrate the usage of Parsica on a real world format, and to benchmark Parsica against
|
||||
* json_decode(). It will probably never reach the same performance as a C extension, so it shouldn't be used for
|
||||
* typical production JSON parsing.
|
||||
*
|
||||
* It could however be useful as a basis to expand into a custom JSON parser, for example to expand JSON with custom
|
||||
* notations or comments, or to return a custom AST instead of json_decode()'s plain PHP objects & arrays.
|
||||
*
|
||||
* To understand the terminology and the structure, have a peak at {@see https://www.json.org/json-en.html}
|
||||
*
|
||||
* @api
|
||||
* @psalm-return Parser<mixed>
|
||||
*/
|
||||
public static function json(): Parser
|
||||
{
|
||||
return JSON::ws()->sequence(JSON::element());
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @psalm-return Parser<mixed>
|
||||
* @psalm-suppress DocblockTypeContradiction
|
||||
*/
|
||||
public static function element(): Parser
|
||||
{
|
||||
// Memoize $element so we can keep reusing it for recursion.
|
||||
/** @psalm-var Parser<mixed> $element */
|
||||
static $element;
|
||||
if (!isset($element)) {
|
||||
$element = recursive();
|
||||
$element->recurse(
|
||||
any(
|
||||
JSON::object(),
|
||||
JSON::array(),
|
||||
JSON::stringLiteral(),
|
||||
JSON::number(),
|
||||
JSON::true(),
|
||||
JSON::false(),
|
||||
JSON::null(),
|
||||
)
|
||||
);
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<object>
|
||||
*/
|
||||
public static function object(): Parser
|
||||
{
|
||||
return map(
|
||||
between(
|
||||
JSON::token(char('{')),
|
||||
JSON::token(char('}')),
|
||||
sepBy(
|
||||
JSON::token(char(',')),
|
||||
JSON::member()
|
||||
)
|
||||
),
|
||||
/**
|
||||
* @psalm-param list<array{string:mixed}> $members
|
||||
* @psalm-return object
|
||||
*/
|
||||
fn(array $members):object => (object)array_merge(...$members));
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<list<mixed>>
|
||||
*/
|
||||
public static function array(): Parser
|
||||
{
|
||||
return between(
|
||||
JSON::token(char('[')),
|
||||
JSON::token(char(']')),
|
||||
sepBy(
|
||||
JSON::token(char(',')), JSON::element()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<bool>
|
||||
*/
|
||||
public static function true(): Parser
|
||||
{
|
||||
return JSON::token(string('true'))->map(fn($_) => true)->label('true');
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<bool>
|
||||
*/
|
||||
public static function false(): Parser
|
||||
{
|
||||
return JSON::token(string('false'))->map(fn($_) => false)->label('false');
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<null>
|
||||
*/
|
||||
public static function null(): Parser
|
||||
{
|
||||
return JSON::token(string('null'))->map(fn($_) => null)->label('null');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitespace
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
*/
|
||||
public static function ws(): Parser
|
||||
{
|
||||
return takeWhile(isCharCode([0x20, 0x0A, 0x0D, 0x09]))->voidLeft(null)
|
||||
->label('whitespace');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply $parser and consume all the following whitespace.
|
||||
*
|
||||
* @template T
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<T>
|
||||
*/
|
||||
public static function token(Parser $parser): Parser
|
||||
{
|
||||
return keepFirst($parser, JSON::ws());
|
||||
}
|
||||
|
||||
public static function number(): Parser
|
||||
{
|
||||
return JSON::token(float())->map('floatval')->label("number");
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Parser<string>
|
||||
*/
|
||||
public static function stringLiteral(): Parser
|
||||
{
|
||||
return JSON::token(
|
||||
between(
|
||||
char('"'),
|
||||
char('"'),
|
||||
zeroOrMore(
|
||||
choice(
|
||||
satisfy(fn(string $char): bool => !in_array($char, ['"', '\\'])),
|
||||
char("\\")->followedBy(
|
||||
choice(
|
||||
char("\"")->map(fn($_) => '"'),
|
||||
char("\\")->map(fn($_) => '\\'),
|
||||
char("/")->map(fn($_) => '/'),
|
||||
char("b")->map(fn($_) => mb_chr(8)),
|
||||
char("f")->map(fn($_) => mb_chr(12)),
|
||||
char("n")->map(fn($_) => "\n"),
|
||||
char("r")->map(fn($_) => "\r"),
|
||||
char("t")->map(fn($_) => "\t"),
|
||||
char("u")->sequence(repeat(4, hexDigitChar()))->map(fn($o) => mb_chr(hexdec($o))),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)->map(fn($o): string => (string)$o) // because the empty json string returns null
|
||||
)->label("string literal");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Parser<array{string:mixed}>
|
||||
*/
|
||||
public static function member(): Parser
|
||||
{
|
||||
return map(
|
||||
collect(
|
||||
JSON::stringLiteral(),
|
||||
JSON::token(char(':')),
|
||||
JSON::token(JSON::element())
|
||||
),
|
||||
/**
|
||||
* @psalm-param array{0:string, 1:string, 2:mixed} $o
|
||||
* @psalm-return array{string:mixed}
|
||||
* @psalm-suppress MoreSpecificReturnType
|
||||
* @psalm-suppress LessSpecificReturnStatement
|
||||
*/
|
||||
fn(array $o): array => [$o[0] => $o[2]]);
|
||||
}
|
||||
}
|
||||
167
vendor/parsica-php/parsica/src/PHPUnit/ParserAssertions.php
vendored
Normal file
167
vendor/parsica-php/parsica/src/PHPUnit/ParserAssertions.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica\PHPUnit;
|
||||
|
||||
use Exception;
|
||||
use Parsica\Parsica\Parser;
|
||||
use Parsica\Parsica\StringStream;
|
||||
|
||||
/**
|
||||
* Convenience assertion methods. When writing tests for your own parsers, extend from this instead of PHPUnit's TestCase.
|
||||
*
|
||||
* @TODO move to standalone package
|
||||
* @api
|
||||
*/
|
||||
trait ParserAssertions
|
||||
{
|
||||
/**
|
||||
* @psalm-param mixed $expectedOutput
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
protected function assertParses(string $input, Parser $parser, $expectedOutput, string $message = ""): void
|
||||
{
|
||||
$input = new StringStream($input);
|
||||
$actualResult = $parser->run($input);
|
||||
if ($actualResult->isSuccess()) {
|
||||
$this->assertStrictlyEquals(
|
||||
$expectedOutput,
|
||||
$actualResult->output(),
|
||||
$message . "\n" . "The parser succeeded but the output doesn't match your expected output."
|
||||
);
|
||||
} else {
|
||||
$this->fail(
|
||||
$message . "\n"
|
||||
."The parser failed with the following error message:\n"
|
||||
.$actualResult->errorMessage()."\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaves like assertSame for primitives, behaves like assertEquals for objects of the same type, and fails
|
||||
* for everything else.
|
||||
*
|
||||
* @psalm-param mixed $expected
|
||||
* @psalm-param mixed $actual
|
||||
* @psalm-param string $message
|
||||
*
|
||||
* @throws Exception
|
||||
* @api
|
||||
*
|
||||
* @psalm-suppress MixedArgument
|
||||
* @psalm-suppress MixedAssignment
|
||||
* @psalm-suppress MixedArrayAccess
|
||||
*/
|
||||
protected function assertStrictlyEquals($expected, $actual, string $message = ''): void
|
||||
{
|
||||
if (is_null($expected) || is_scalar($expected)) {
|
||||
$this->assertSame($expected, $actual, $message);
|
||||
} elseif (is_object($expected)) {
|
||||
$this->assertEquals(get_class($expected), get_class($actual),
|
||||
"Expected type didn't match actual type");
|
||||
$this->assertEquals($expected, $actual, $message);
|
||||
} elseif (is_array($expected)) {
|
||||
foreach ($expected as $k => $v) {
|
||||
$this->assertStrictlyEquals($expected[$k], $actual[$k], "Item $k from the actual array differs from item $k in the expected array");
|
||||
}
|
||||
$this->assertSame(count($expected), count($actual), "The length of the actual array differs from the length of the expected array.");
|
||||
} else {
|
||||
throw new Exception("@todo Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
abstract public static function assertSame($expected, $actual, string $message = ''): void;
|
||||
|
||||
abstract public static function assertEquals($expected, $actual, string $message = ''): void;
|
||||
|
||||
abstract public static function fail(string $message = ''): void;
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @param Parser $parser
|
||||
* @param string $expectedRemaining
|
||||
* @param string $message
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
protected function assertRemainder(string $input, Parser $parser, string $expectedRemaining, string $message = ""): void
|
||||
{
|
||||
$input = new StringStream($input);
|
||||
$actualResult = $parser->run($input);
|
||||
if ($actualResult->isSuccess()) {
|
||||
$this->assertEquals(
|
||||
$expectedRemaining,
|
||||
$actualResult->remainder(),
|
||||
$message . "\n" . "The parser succeeded but the expected remaining input doesn't match."
|
||||
);
|
||||
} else {
|
||||
$this->fail(
|
||||
$message . "\n"
|
||||
. "The parser failed with the following error message:\n"
|
||||
.$actualResult->errorMessage()."\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @param Parser $parser
|
||||
* @param string|null $expectedFailure
|
||||
* @param string $message
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
protected function assertParseFails(string $input, Parser $parser, ?string $expectedFailure = null, string $message = ""): void
|
||||
{
|
||||
$input = new StringStream($input);
|
||||
$actualResult = $parser->run($input);
|
||||
$this->assertTrue(
|
||||
$actualResult->isFail(),
|
||||
$message . "\n" . "The parser succeeded but expected a failure."
|
||||
);
|
||||
|
||||
if (isset($expectedFailure)) {
|
||||
$this->assertEquals(
|
||||
$expectedFailure,
|
||||
$actualResult->expected(),
|
||||
$message . "\n" . "The expected failure message is not the same as the actual one."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public static function assertTrue($condition, string $message = ''): void;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
protected function assertFailOnEOF(Parser $parser, string $message = ""): void
|
||||
{
|
||||
$actualResult = $parser->run(new StringStream(""));
|
||||
$this->assertTrue(
|
||||
$actualResult->isFail(),
|
||||
$message . "\n" . "Expected the parser to fail on EOL."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
protected function assertSucceedOnEOF(Parser $parser, string $message = ""): void
|
||||
{
|
||||
$actualResult = $parser->run(new StringStream(""));
|
||||
$this->assertTrue(
|
||||
$actualResult->isSuccess(),
|
||||
$message . "\n" . "Expected the parser to succeed on EOL."
|
||||
);
|
||||
$this->assertSame("", $actualResult->output());
|
||||
}
|
||||
}
|
||||
136
vendor/parsica-php/parsica/src/ParseResult.php
vendored
Normal file
136
vendor/parsica-php/parsica/src/ParseResult.php
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Parsica\Parsica\Internal\Position;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @psalm-immutable
|
||||
*/
|
||||
interface ParseResult
|
||||
{
|
||||
/**
|
||||
* True if the parser was successful.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isSuccess(): bool;
|
||||
|
||||
/**
|
||||
* True if the parser has failed.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isFail(): bool;
|
||||
|
||||
/**
|
||||
* The output of the parser.
|
||||
*
|
||||
* @psalm-return T
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function output();
|
||||
|
||||
/**
|
||||
* The part of the input that did not get parsed.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function remainder(): Stream;
|
||||
|
||||
/**
|
||||
* A message that indicates what the failed parser expected to find at its position in the input. It contains the
|
||||
* label that was attached to the parser.
|
||||
*
|
||||
* @see Parser::label()
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function expected(): string;
|
||||
|
||||
/**
|
||||
* A message indicating the input that the failed parser got at the point where it failed. It's only informational,
|
||||
* so don't use this for processing. A future version might change this behaviour.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function got(): Stream;
|
||||
|
||||
/**
|
||||
* Append the output of two successful ParseResults. If one or both have failed, it returns the first failed
|
||||
* ParseResult.
|
||||
*
|
||||
* @psalm-param ParseResult<T> $other
|
||||
*
|
||||
* @psalm-return ParseResult<T>
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function append(ParseResult $other): ParseResult;
|
||||
|
||||
/**
|
||||
* Map a function over the output
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param pure-callable(T):T2 $transform
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function map(callable $transform): ParseResult;
|
||||
|
||||
/**
|
||||
* Use the remainder of this ParseResult as the input for a parser.
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param Parser<T2> $parser
|
||||
*
|
||||
* @psalm-return ParseResult<T2>
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function continueWith(Parser $parser): ParseResult;
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function errorMessage() : string;
|
||||
|
||||
/**
|
||||
* Get the last position of where the parser ended up when producing this result.
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function position(): Position;
|
||||
|
||||
/**
|
||||
* Throw a ParserFailure exception if the Parser failed, or complain if you're trying to throw a successful
|
||||
* ParseResult.
|
||||
*
|
||||
* @api
|
||||
* @throws ParserHasFailed
|
||||
* @throws BadMethodCallException
|
||||
*/
|
||||
public function throw() : void;
|
||||
}
|
||||
469
vendor/parsica-php/parsica/src/Parser.php
vendored
Normal file
469
vendor/parsica-php/parsica/src/Parser.php
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Exception;
|
||||
use Parsica\Parsica\Internal\Fail;
|
||||
|
||||
/**
|
||||
* A parser is any function that takes a string input and returns a {@see ParseResult}. The Parser class is a wrapper
|
||||
* around such functions. The {@see Parser::make()} static constructor takes a callable that does the actual parsing.
|
||||
* Usually you don't need to instantiate this class directly. Instead, build your parser from existing parsers and
|
||||
* combinators.
|
||||
*
|
||||
* At the moment, there is no Parser interface, and no Parser abstract class to extend from. This is intentional, but
|
||||
* will be changed if we find use cases where those would be the best solutions.
|
||||
*
|
||||
* The type is Parser<T>, where T is the type of the output that the parser will produce after completing successfully.
|
||||
*
|
||||
* @template T
|
||||
* @api
|
||||
*/
|
||||
final class Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var pure-callable(Stream) : ParseResult<T> $parserF
|
||||
*/
|
||||
private $parserFunction;
|
||||
|
||||
/** @psalm-var 'non-recursive'|'awaiting-recurse'|'recursion-was-setup' */
|
||||
private string $recursionStatus;
|
||||
|
||||
private string $label;
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable(Stream) : ParseResult<T> $parserFunction
|
||||
* @psalm-param 'non-recursive'|'awaiting-recurse'|'recursion-was-setup' $recursionStatus
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureVariable
|
||||
*/
|
||||
private function __construct(callable $parserFunction, string $recursionStatus, string $label)
|
||||
{
|
||||
$this->parserFunction = $parserFunction;
|
||||
$this->recursionStatus = $recursionStatus;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a recursive parser. Used in combination with recurse(Parser).
|
||||
*
|
||||
* @see recursive()
|
||||
*
|
||||
* @psalm-return Parser<mixed>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function recursive(): Parser
|
||||
{
|
||||
return new Parser(
|
||||
// Make a placeholder parser that will throw when you try to run it.
|
||||
static function (Stream $_): ParseResult {
|
||||
throw new Exception(
|
||||
"Can't run a recursive parser that hasn't been setup properly yet. "
|
||||
. "A parser created by recursive(), must then be called with ->recurse(Parser) "
|
||||
. "before it can be used."
|
||||
);
|
||||
}, 'awaiting-recurse', "<recursive>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new parser.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param pure-callable(Stream):ParseResult<T2> $parserFunction
|
||||
*
|
||||
* @psalm-return Parser<T2>
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function make(string $label, callable $parserFunction): Parser
|
||||
{
|
||||
return new Parser($parserFunction, 'non-recursive', $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse on a parser. Used in combination with {@see recursive()}. After calling this method, this parser behaves
|
||||
* like a regular parser.
|
||||
*
|
||||
* @psalm-param Parser<mixed> $parser
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function recurse(Parser $parser): void
|
||||
{
|
||||
switch ($this->recursionStatus) {
|
||||
case 'non-recursive':
|
||||
throw new Exception(
|
||||
"You can't recurse on a non-recursive parser. Create a recursive parser first using recursive(), "
|
||||
. "then call ->recurse() on it."
|
||||
);
|
||||
case 'recursion-was-setup':
|
||||
throw new Exception("You can only call recurse() once on a recursive parser.");
|
||||
case 'awaiting-recurse':
|
||||
// Replace the placeholder parser from recursive() with a call to the inner parser. This must be dynamic,
|
||||
// because it's possible that the inner parser is also a recursive parser that has not been set up yet.
|
||||
$this->parserFunction = fn(Stream $input): ParseResult => $parser->run($input);
|
||||
$this->recursionStatus = 'recursion-was-setup';
|
||||
$this->label = $parser->getLabel();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unexpected recursionStatus value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the parser on an input
|
||||
*
|
||||
* @psalm-return ParseResult<T>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function run(Stream $input): ParseResult
|
||||
{
|
||||
return ($this->parserFunction)($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally parse something, but still succeed if the thing is not there.
|
||||
*
|
||||
*
|
||||
* @psalm-return Parser<T|null>
|
||||
* @see optional()
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function optional(): Parser
|
||||
{
|
||||
return optional($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try the first parser, and failing that, try the second parser. Returns the first succeeding result, or the first
|
||||
* failing result.
|
||||
*
|
||||
* Caveat: The order matters!
|
||||
* string('http')->or(string('https')
|
||||
*
|
||||
* @psalm-param Parser<T> $other
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function or(Parser $other): Parser
|
||||
{
|
||||
return either($this, $other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something, then follow by something else. Ignore the result of the first parser and return the result of
|
||||
* the second parser.
|
||||
*
|
||||
* @template T2
|
||||
* @psalm-param Parser<T2> $second
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @see sequence()
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function followedBy(Parser $second): Parser
|
||||
{
|
||||
return sequence($this, $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something, then follow by something else. Ignore the result of the first parser and return the result of
|
||||
* the second parser.
|
||||
*
|
||||
* @template T2
|
||||
* @psalm-param Parser<T2> $second
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @see sequence()
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function sequence(Parser $second): Parser
|
||||
{
|
||||
return sequence($this, $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something, then follow by something else. Ignore the result of the first parser and return the result of
|
||||
* the second parser. Alias for sequence().
|
||||
*
|
||||
* @template T2
|
||||
* @psalm-param Parser<T2> $second
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @see sequence()
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function then(Parser $second): Parser
|
||||
{
|
||||
return sequence($this, $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parser that takes the output from the first parser (if successful) and feeds it to the callable. The
|
||||
* callable must return another parser. If the first parser fails, the first parser is returned.
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param pure-callable(T) : Parser<T2> $f
|
||||
*
|
||||
* @psalm-return Parser<T2>
|
||||
* @see bind()
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function bind(callable $f): Parser
|
||||
{
|
||||
return bind($this, $f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a function over the parser (which in turn maps it over the result).
|
||||
*
|
||||
* @template T2
|
||||
*
|
||||
* @psalm-param pure-callable(T) : T2 $transform
|
||||
*
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function map(callable $transform): Parser
|
||||
{
|
||||
return map($this, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the remaining input from the result and parse it.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function continueFrom(ParseResult $result): ParseResult
|
||||
{
|
||||
return $this->run($result->remainder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the parser with another parser of the same type, which will cause the results to be appended.
|
||||
*
|
||||
* @psalm-param Parser<T|null> $other
|
||||
* @psalm-return Parser<T|null>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function append(Parser $other): Parser
|
||||
{
|
||||
return append($this, $other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the parser with another parser of the same type, which will cause the results to be appended.
|
||||
*
|
||||
* @psalm-param Parser<T|null> $other
|
||||
* @psalm-return Parser<T|null>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function and(Parser $other): Parser
|
||||
{
|
||||
return append($this, $other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse a string. Alias of `try(new StringStream($string))`.
|
||||
*
|
||||
* @TODO Try should fail when it doesn't consume the whole input.
|
||||
*
|
||||
* @psalm-param string $input
|
||||
*
|
||||
* @psalm-return ParseResult<T>
|
||||
*
|
||||
* @throws ParserHasFailed
|
||||
* @api
|
||||
*/
|
||||
public function tryString(string $input): ParseResult
|
||||
{
|
||||
return $this->try(new StringStream($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the input, or throw an exception.
|
||||
*
|
||||
* @TODO Try should fail when it doesn't consume the whole input.
|
||||
*
|
||||
* @psalm-return ParseResult<T>
|
||||
*
|
||||
* @throws ParserHasFailed
|
||||
* @api
|
||||
*/
|
||||
public function try(Stream $input): ParseResult
|
||||
{
|
||||
$result = $this->run($input);
|
||||
if ($result->isFail()) {
|
||||
$result->throw();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequential application. Given a parser which outputs a callable, return a new parser that applies the callable on the
|
||||
* output of the second parser.
|
||||
*
|
||||
* The first parser must be of type Parser<callable(T1):T2>. {@see pure()} can be used to wrap a callable in a Parser.
|
||||
*
|
||||
* Callables with more than 1 argument need to be curried: pure(curry(fn($x, $y)))->apply($parser2)->apply($parser3)
|
||||
*
|
||||
* @template T2
|
||||
* @template T3
|
||||
* @psalm-param Parser<T2> $parser
|
||||
* @psalm-return Parser<T3>
|
||||
* @psalm-suppress MixedArgumentTypeCoercion
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function apply(Parser $parser): Parser
|
||||
{
|
||||
return apply($this, $parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence two parsers, and return the output of the first one, ignore the second.
|
||||
*
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function thenIgnore(Parser $other): Parser
|
||||
{
|
||||
return keepFirst($this, $other);
|
||||
}
|
||||
|
||||
/**
|
||||
* notFollowedBy only succeeds when $second fails. It never consumes any input.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* `string("print")` will also match "printXYZ"
|
||||
*
|
||||
* `string("print")->notFollowedBy(alphaNumChar()))` will match "print something" but not "printXYZ something"
|
||||
*
|
||||
* @psalm-param Parser<T2> $parser
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @see notFollowedBy()
|
||||
*
|
||||
* @template T2
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function notFollowedBy(Parser $second): Parser
|
||||
{
|
||||
return keepFirst($this, notFollowedBy($second));
|
||||
}
|
||||
|
||||
/**
|
||||
* The parser's label.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label a parser. When a parser fails, you'll see your label as the "expected" value. As a best practice, the
|
||||
* labels should make sense to the person who provides the input for your parser. That's often an end user or a
|
||||
* third party, so keep them in mind.
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function label(string $label): Parser
|
||||
{
|
||||
$parserFn = $this->parserFunction;
|
||||
|
||||
$newParserFunction = static function (Stream $input) use ($parserFn, $label) : ParseResult {
|
||||
/** @psalm-var ParseResult $result */
|
||||
$result = ($parserFn)($input);
|
||||
return ($result->isSuccess())
|
||||
? $result
|
||||
: new Fail($label, $result->got());
|
||||
};
|
||||
|
||||
return new Parser($newParserFunction, $this->recursionStatus, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the parser is successful, call the $receiver function with the output of the parser. The resulting parser
|
||||
* behaves identical to the original one. This combinator is useful for expressing side effects during the parsing
|
||||
* process. It can be hooked into existing event publishing libraries by using $receiver as an adapter for those.
|
||||
* Other use cases are logging, caching, performing an action whenever a value is matched in a long running input
|
||||
* stream, ...
|
||||
*
|
||||
* @psalm-param callable(T): void $receiver
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
*/
|
||||
public function emit(callable $receiver): Parser
|
||||
{
|
||||
return emit($this, $receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore the output of the parser and return the new output instead.
|
||||
*
|
||||
* @template T2
|
||||
* @psalm-param T2 $output
|
||||
* @psalm-return Parser<T2>
|
||||
*
|
||||
* @deprecated @TODO needs test
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function voidLeft($output): Parser
|
||||
{
|
||||
return $this->map(
|
||||
/**
|
||||
* @psalm-param T $_
|
||||
* @psalm-return T2
|
||||
*/
|
||||
fn($_) => $output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the input ends after the parser has successfully completed. The output is the output of the
|
||||
* original parser.
|
||||
*
|
||||
* Also useful in unit tests to make sure a parser doesn't consume more than you intended.
|
||||
*
|
||||
* Alias for $parser->thenIgnore(eof()).
|
||||
*
|
||||
* @api
|
||||
* @psalm-return Parser<T>
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function thenEof(): Parser
|
||||
{
|
||||
return keepFirst($this, eof());
|
||||
// aka $this->thenIgnore(eof());
|
||||
}
|
||||
}
|
||||
38
vendor/parsica-php/parsica/src/ParserHasFailed.php
vendored
Normal file
38
vendor/parsica-php/parsica/src/ParserHasFailed.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Exception;
|
||||
use Parsica\Parsica\Internal\Fail;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
final class ParserHasFailed extends Exception
|
||||
{
|
||||
private Fail $parseResult;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
function __construct(Fail $parseResult)
|
||||
{
|
||||
$this->parseResult = $parseResult;
|
||||
parent::__construct($this->parseResult->errorMessage());
|
||||
}
|
||||
|
||||
function parseResult() : Fail
|
||||
{
|
||||
return $this->parseResult;
|
||||
}
|
||||
|
||||
}
|
||||
77
vendor/parsica-php/parsica/src/Stream.php
vendored
Normal file
77
vendor/parsica-php/parsica/src/Stream.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\Position;
|
||||
use Parsica\Parsica\Internal\TakeResult;
|
||||
|
||||
/**
|
||||
* Represents an input stream. This allows us to have different types of input, each with their own optimizations.
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
interface Stream
|
||||
{
|
||||
/**
|
||||
* Extract a single token from the stream. Throw if the stream is empty.
|
||||
*
|
||||
* @throw EndOfStream
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function take1(): TakeResult;
|
||||
|
||||
/**
|
||||
* Try to extract a chunk of length $n, or if the stream is too short, the rest of the stream.
|
||||
*
|
||||
* Valid implementation should follow the rules:
|
||||
*
|
||||
* 1. If the requested length <= 0, the empty token and the original stream should be returned.
|
||||
* 2. If the requested length > 0 and the stream is empty, throw EndOfStream.
|
||||
* 3. In other cases, take a chunk of length $n (or shorter if the stream is not long enough) from the input stream
|
||||
* and return the chunk along with the rest of the stream.
|
||||
*
|
||||
* @throw EndOfStream
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function takeN(int $n): TakeResult;
|
||||
|
||||
|
||||
/**
|
||||
* Extract a chunk of the stream, by taking tokens as long as the predicate holds. Return the chunk and the rest of
|
||||
* the stream.
|
||||
*
|
||||
* @TODO This method isn't strictly necessary but let's see.
|
||||
*
|
||||
* @psalm-param pure-callable(string):bool $predicate
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function takeWhile(callable $predicate) : TakeResult;
|
||||
|
||||
/**
|
||||
* @deprecated We will need to get rid of this again at some point, we can't assume all streams will be strings
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function __toString(): string;
|
||||
|
||||
/**
|
||||
* Test if the stream is at its end.
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isEOF(): bool;
|
||||
|
||||
/**
|
||||
* The position of the parser in the stream.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function position() : Position;
|
||||
}
|
||||
133
vendor/parsica-php/parsica/src/StringStream.php
vendored
Normal file
133
vendor/parsica-php/parsica/src/StringStream.php
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\EndOfStream;
|
||||
use Parsica\Parsica\Internal\Position;
|
||||
use Parsica\Parsica\Internal\TakeResult;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class StringStream implements Stream
|
||||
{
|
||||
private string $string;
|
||||
private Position $position;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
public function __construct(string $string, ?Position $position = null)
|
||||
{
|
||||
$this->string = $string;
|
||||
$this->position = $position ?? Position::initial();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @internal
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function take1(): TakeResult
|
||||
{
|
||||
if ($this->string === '') {
|
||||
throw new EndOfStream("End of stream was reached in " . $this->position->pretty());
|
||||
}
|
||||
|
||||
$token = mb_substr($this->string, 0, 1);
|
||||
$position = $this->position->advance($token);
|
||||
|
||||
return new TakeResult(
|
||||
$token,
|
||||
new StringStream(mb_substr($this->string, 1), $position)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function isEOF(): bool
|
||||
{
|
||||
return $this->string === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function takeN(int $n): TakeResult
|
||||
{
|
||||
if ($n <= 0) {
|
||||
return new TakeResult("", $this);
|
||||
}
|
||||
|
||||
if ($this->string === '') {
|
||||
throw new EndOfStream("End of stream was reached in " . $this->position->pretty());
|
||||
}
|
||||
|
||||
$chunk = mb_substr($this->string, 0, $n);
|
||||
return new TakeResult(
|
||||
$chunk,
|
||||
new StringStream(
|
||||
mb_substr($this->string, $n),
|
||||
$this->position->advance($chunk)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
* @psalm-mutation-free
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function takeWhile(callable $predicate): TakeResult
|
||||
{
|
||||
if ($this->string === '') {
|
||||
return new TakeResult("", $this);
|
||||
}
|
||||
|
||||
$remaining = $this->string;
|
||||
$nextToken = mb_substr($remaining, 0, 1);
|
||||
$chunk = "";
|
||||
while ($predicate($nextToken)) {
|
||||
$chunk .= $nextToken;
|
||||
$remaining = mb_substr($remaining, 1);
|
||||
if ($remaining !== '') {
|
||||
$nextToken = mb_substr($remaining, 0, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new TakeResult(
|
||||
$chunk,
|
||||
new StringStream($remaining, $this->position->advance($chunk))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function position(): Position
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
}
|
||||
190
vendor/parsica-php/parsica/src/characters.php
vendored
Normal file
190
vendor/parsica-php/parsica/src/characters.php
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
|
||||
/**
|
||||
* Parse a single character.
|
||||
*
|
||||
* @psalm-param string $c A single character
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @see charI()
|
||||
* @psalm-pure
|
||||
*/
|
||||
function char(string $c): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::singleChar($c);
|
||||
return satisfy(isEqual($c))->label("'$c'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single character, case-insensitive and case-preserving. On success, it returns the string cased as the
|
||||
* actually parsed input.
|
||||
*
|
||||
* eg charI('a'')->run("ABC") will succeed with "A", not "a".
|
||||
*
|
||||
* @psalm-param string $c A single character
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
*
|
||||
* @see char()
|
||||
* @psalm-pure
|
||||
*/
|
||||
function charI(string $c): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::singleChar($c);
|
||||
$lower = mb_strtolower($c);
|
||||
$upper = mb_strtoupper($c);
|
||||
$label = $lower==$upper ? "'$c'" : "'$lower' or '$upper'";
|
||||
return satisfy(orPred(isEqual($lower), isEqual($upper)))->label($label);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a control character (a non-printing character of the Latin-1 subset of Unicode).
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function controlChar(): Parser
|
||||
{
|
||||
return satisfy(isControl())->label("<controlChar>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an uppercase character A-Z.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function upperChar(): Parser
|
||||
{
|
||||
return satisfy(isUpper())->label("A-Z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a lowercase character a-z.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function lowerChar(): Parser
|
||||
{
|
||||
return satisfy(isLower())->label("a-z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an uppercase or lowercase character A-Z, a-z.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function alphaChar(): Parser
|
||||
{
|
||||
return satisfy(isAlpha())->label("A-Z or a-z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an alpha or numeric character A-Z, a-z, 0-9.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function alphaNumChar(): Parser
|
||||
{
|
||||
return satisfy(isAlphaNum())->label("A-Z or a-z or 0-9");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a printable ASCII char.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function printChar(): Parser
|
||||
{
|
||||
return satisfy(isPrintable())->label("<printChar>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single punctuation character !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function punctuationChar(): Parser
|
||||
{
|
||||
return satisfy(isPunctuation())->label("<punctuation>");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse 0-9. Returns the digit as a string. Use ->map('intval')
|
||||
* or similar to cast it to a numeric type.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function digitChar(): Parser
|
||||
{
|
||||
return satisfy(isDigit())->label('0-9');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a binary character 0 or 1.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function binDigitChar(): Parser
|
||||
{
|
||||
return satisfy(isCharCode([0x30, 0x31]))->label("'0' or '1'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an octodecimal character 0-7.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function octDigitChar(): Parser
|
||||
{
|
||||
return satisfy(isCharCode(range(0x30, 0x37)))->label("0-7");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a hexadecimal numeric character 0123456789abcdefABCDEF.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function hexDigitChar(): Parser
|
||||
{
|
||||
return satisfy(isHexDigit())->label("<hexadecimal>");
|
||||
}
|
||||
679
vendor/parsica-php/parsica/src/combinators.php
vendored
Normal file
679
vendor/parsica-php/parsica/src/combinators.php
vendored
Normal file
@@ -0,0 +1,679 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
use Parsica\Parsica\Internal\Fail;
|
||||
use Parsica\Parsica\Internal\Succeed;
|
||||
use function Parsica\Parsica\Internal\FP\foldl;
|
||||
|
||||
/**
|
||||
* Identity parser, returns the Parser as is.
|
||||
*
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
*
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function identity(Parser $parser): Parser
|
||||
{
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* A parser that will have the argument as its output, no matter what the input was. It doesn't consume any input.
|
||||
*
|
||||
* @psalm-param T $output
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
*
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function pure($output): Parser
|
||||
{
|
||||
return Parser::make("<pure>", fn(Stream $input) => new Succeed($output, $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally parse something, but still succeed if the thing is not there
|
||||
*
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<T|null>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function optional(Parser $parser): Parser
|
||||
{
|
||||
return either($parser, succeed())->label("optional " . $parser->getLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a parser that takes the output from the first parser (if successful) and feeds it to the callable. The callable
|
||||
* must return another parser. If the first parser fails, the first parser is returned.
|
||||
*
|
||||
* This is a monadic bind aka flatmap.
|
||||
*
|
||||
* @psalm-param Parser<T1> $parser
|
||||
* @psalm-param pure-callable(T1) : Parser<T2> $f
|
||||
*
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-pure
|
||||
*/
|
||||
function bind(Parser $parser, callable $f): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var pure-callable(Stream) : ParseResult<T2> $parserFunction
|
||||
*/
|
||||
$parserFunction = static function (Stream $input) use ($parser, $f): ParseResult {
|
||||
$result = $parser->run($input)->map($f);
|
||||
if ($result->isFail()) {
|
||||
return $result;
|
||||
}
|
||||
$p2 = $result->output();
|
||||
return $result->continueWith($p2);
|
||||
};
|
||||
$finalParser = Parser::make($parser->getLabel(), $parserFunction);
|
||||
return $finalParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequential application. Given a parser which outputs a callable, return a new parser that applies the callable on the
|
||||
* output of the second parser.
|
||||
*
|
||||
* The first parser must be of type Parser<callable(T1):T2>. {@see pure()} can be used to wrap a callable in a Parser.
|
||||
*
|
||||
* Callables with more than 1 argument need to be curried: pure(curry(fn($x, $y)))->apply($parser2)->apply($parser3)
|
||||
*
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-param Parser<pure-callable(T1):T2> $parser1
|
||||
* @psalm-param Parser<T1> $parser2
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function apply(Parser $parser1, Parser $parser2): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var pure-callable(Stream): ParseResult<T2>
|
||||
*/
|
||||
$parserFunction = static function (Stream $input) use ($parser2, $parser1): ParseResult {
|
||||
$r1 = $parser1->run($input);
|
||||
if ($r1->isFail()) {
|
||||
return $r1;
|
||||
}
|
||||
$f = $r1->output();
|
||||
Assert::isCallable($f, "apply() can only be used when the output of the first parser is a callable with 1 argument. Use currying for functions with more than 1 argument.");
|
||||
// @todo assert that the arity of $f == 1
|
||||
return $r1->continueWith($parser2)->map($f);
|
||||
};
|
||||
$parser = Parser::make($parser1->getLabel(), $parserFunction);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse something, then follow by something else. Ignore the result of the first parser and return the result of the
|
||||
* second parser.
|
||||
*
|
||||
* @psalm-param Parser<T1> $first
|
||||
* @psalm-param Parser<T2> $second
|
||||
*
|
||||
* @psalm-return Parser<T2>
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @api
|
||||
* @see Parser::sequence()
|
||||
* @psalm-pure
|
||||
*/
|
||||
function sequence(Parser $first, Parser $second): Parser
|
||||
{
|
||||
return bind($first, /** @psalm-param mixed $_ */ fn($_) => $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence two parsers, and return the output of the first one.
|
||||
*
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-param Parser<T1> $first
|
||||
* @psalm-param Parser<T2> $second
|
||||
* @psalm-return Parser<T1>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function keepFirst(Parser $first, Parser $second): Parser
|
||||
{
|
||||
return bind(
|
||||
$first,
|
||||
/** @psalm-suppress MissingClosureParamType */
|
||||
fn($a): Parser => sequence($second, pure($a))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence two parsers, and return the output of the second one.
|
||||
*
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-param Parser<T1> $first
|
||||
* @psalm-param Parser<T2> $second
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function keepSecond(Parser $first, Parser $second): Parser
|
||||
{
|
||||
return sequence($first, $second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Either parse the first thing or the second thing
|
||||
*
|
||||
* @psalm-param Parser<T1> $first
|
||||
* @psalm-param Parser<T2> $second
|
||||
*
|
||||
* @psalm-return Parser<T1|T2>
|
||||
* @api
|
||||
*
|
||||
* @see Parser::or()
|
||||
*
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-pure
|
||||
*/
|
||||
function either(Parser $first, Parser $second): Parser
|
||||
{
|
||||
$label = $first->getLabel() . " or " . $second->getLabel();
|
||||
/**
|
||||
* @psalm-var pure-callable(Stream): ParseResult<T1|T2> $parserFunction
|
||||
*/
|
||||
$parserFunction = static function (Stream $input) use ($second, $first, $label): ParseResult {
|
||||
// @todo Megaparsec doesn't do automatic rollback, for performance reasons, and requires the user to add try
|
||||
// combinators. We could mimic that behaviour as it is probably more performant
|
||||
$r1 = $first->run($input);
|
||||
if ($r1->isSuccess()) {
|
||||
return $r1;
|
||||
}
|
||||
$r2 = $second->run($input);
|
||||
|
||||
if ($r2->isSuccess()) {
|
||||
return $r2;
|
||||
}
|
||||
|
||||
return new Fail($label, $r2->got());
|
||||
};
|
||||
|
||||
return Parser::make($label, $parserFunction);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine the parser with another parser of the same type, which will cause the results to be appended.
|
||||
*
|
||||
* @psalm-param Parser<T|null> $left
|
||||
* @psalm-param Parser<T|null> $right
|
||||
*
|
||||
* @psalm-return Parser<T|null>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function append(Parser $left, Parser $right): Parser
|
||||
{
|
||||
return Parser::make($right->getLabel(), static function (Stream $input) use ($left, $right): ParseResult {
|
||||
$r1 = $left->run($input);
|
||||
$r2 = $r1->continueWith($right);
|
||||
return $r1->append($r2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Append all the passed parsers.
|
||||
*
|
||||
* @psalm-param list<Parser<T|null>> $parsers
|
||||
* @psalm-return Parser<T|null>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-suppress MixedReturnStatement
|
||||
* @psalm-suppress MixedInferredReturnType
|
||||
* @psalm-pure
|
||||
*/
|
||||
function assemble(Parser ...$parsers): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::atLeastOneArg($parsers, "assemble()");
|
||||
$first = array_shift($parsers);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
return array_reduce($parsers, fn(Parser $p1, Parser $p2): Parser => append($p1, $p2), $first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse into an array that consists of the results of all parsers.
|
||||
*
|
||||
* @psalm-param list<Parser<mixed>> $parsers
|
||||
* @psalm-return Parser<mixed>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function collect(Parser ...$parsers): Parser
|
||||
{
|
||||
$toArray =
|
||||
/**
|
||||
* @psalm-param mixed $v
|
||||
* @psalm-return list<mixed>
|
||||
*/
|
||||
fn($v): array => [$v];
|
||||
$arrayParsers = array_map(
|
||||
fn(Parser $parser): Parser => map($parser, $toArray),
|
||||
$parsers
|
||||
);
|
||||
return assemble(...$arrayParsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries each parser one by one, returning the result of the first one that succeeds.
|
||||
*
|
||||
* @no-named-arguments
|
||||
* @psalm-param non-empty-list<Parser<mixed>> $parsers
|
||||
* @psalm-return Parser<mixed>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function any(Parser ...$parsers): Parser
|
||||
{
|
||||
if (empty($parsers)) {
|
||||
throw new InvalidArgumentException("any() expects at least one parser");
|
||||
}
|
||||
|
||||
$labels = array_map(fn(Parser $p): string => $p->getLabel(), $parsers);
|
||||
$label = implode(' or ', $labels);
|
||||
|
||||
return foldl(
|
||||
$parsers,
|
||||
fn(Parser $first, Parser $second): Parser => either($first, $second),
|
||||
fail("")
|
||||
)->label($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries each parser one by one, returning the result of the first one that succeeds.
|
||||
*
|
||||
* Alias for {@see any()}
|
||||
*
|
||||
* @no-named-arguments
|
||||
* @psalm-param non-empty-list<Parser<mixed>> $parsers
|
||||
* @psalm-return Parser<mixed>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function choice(Parser ...$parsers): Parser
|
||||
{
|
||||
return any(...$parsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* One or more repetitions of Parser, with the outputs appended.
|
||||
*
|
||||
* @api
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<T>
|
||||
* @template T
|
||||
* @psalm-suppress MixedArgumentTypeCoercion
|
||||
* @psalm-pure
|
||||
*/
|
||||
function atLeastOne(Parser $parser): Parser
|
||||
{
|
||||
/**
|
||||
* @psalm-var pure-callable(Stream): ParseResult<T> $parserFunction
|
||||
*/
|
||||
$parserFunction = static function (Stream $input) use ($parser): ParseResult {
|
||||
$result = $parser->run($input);
|
||||
if ($result->isFail()) {
|
||||
return $result;
|
||||
}
|
||||
$final = new Succeed(null, $result->remainder());
|
||||
while ($result->isSuccess()) {
|
||||
$final = $final->append($result);
|
||||
$result = $parser->continueFrom($result);
|
||||
}
|
||||
return $final;
|
||||
};
|
||||
return Parser::make(
|
||||
"at least one " . $parser->getLabel(), $parserFunction
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero or more repetitions of Parser, with the outputs appended.
|
||||
*
|
||||
* @TODO Untested
|
||||
*
|
||||
* @api
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<T>
|
||||
* @template T
|
||||
* @psalm-suppress MixedArgumentTypeCoercion
|
||||
* @psalm-pure
|
||||
*/
|
||||
function zeroOrMore(Parser $parser): Parser
|
||||
{
|
||||
/** @var pure-callable(Stream):ParseResult<T> $parserFunction */
|
||||
$parserFunction = static function (Stream $input) use ($parser): ParseResult {
|
||||
$result = new Succeed(null, $input);
|
||||
$final = $result;
|
||||
while ($result->isSuccess()) {
|
||||
$final = $final->append($result);
|
||||
$result = $parser->continueFrom($result);
|
||||
}
|
||||
return $final;
|
||||
};
|
||||
return Parser::make(
|
||||
"zero or more " . $parser->getLabel(), $parserFunction
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something exactly n times
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function repeat(int $n, Parser $parser): Parser
|
||||
{
|
||||
return foldl(
|
||||
array_fill(0, $n - 1, $parser),
|
||||
fn(Parser $l, Parser $r): Parser => append($l, $r),
|
||||
$parser
|
||||
)->label("$n times " . $parser->getLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something exactly n times and return as an array
|
||||
*
|
||||
* @TODO This doesn't feel very elegant.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param positive-int $n
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<list<T>>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function repeatList(int $n, Parser $parser): Parser
|
||||
{
|
||||
/** @palm-var Parser<list<T>> $parser */
|
||||
$parser = map(
|
||||
$parser,
|
||||
/**
|
||||
* @psalm-param T $output
|
||||
* @psalm-return list<T>
|
||||
*/
|
||||
fn($output): array => [$output]
|
||||
);
|
||||
|
||||
$parsers = array_fill(0, $n - 1, $parser);
|
||||
|
||||
return foldl(
|
||||
$parsers,
|
||||
/**
|
||||
* @psalm-param Parser<list<T>> $l
|
||||
* @psalm-param Parser<list<T>> $r
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @psalm-suppress InvalidReturnType
|
||||
* @psalm-suppress InvalidReturnStatement
|
||||
* @psalm-pure
|
||||
*/
|
||||
fn(Parser $l, Parser $r): Parser => append($l, $r),
|
||||
$parser
|
||||
)->label("$n times " . $parser->getLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something one or more times, and output an array of the successful outputs.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function some(Parser $parser): Parser
|
||||
{
|
||||
return map(
|
||||
collect($parser, many($parser)),
|
||||
/**
|
||||
* @psalm-param array{0: T, 1: list<T>} $o
|
||||
* @psalm-return list<T>
|
||||
*/
|
||||
fn(array $o):array => array_merge([$o[0]], $o[1])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse something zero or more times, and output an array of the successful outputs.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function many(Parser $parser): Parser
|
||||
{
|
||||
return Parser::make(
|
||||
"many {$parser->getLabel()}",
|
||||
function (Stream $remainder) use ($parser): ParseResult {
|
||||
$result = [];
|
||||
|
||||
while (true) {
|
||||
$lastResult = $parser->run($remainder);
|
||||
|
||||
if ($lastResult->isFail()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$remainder = $lastResult->remainder();
|
||||
$result[] = $lastResult->output();
|
||||
}
|
||||
|
||||
/** @psalm-var ParseResult<list<T>> $succeed */
|
||||
$succeed = new Succeed($result, $remainder);
|
||||
|
||||
return $succeed;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse $open, followed by $middle, followed by $close, and return the result of $middle. Useful for eg. "(value)".
|
||||
*
|
||||
* @template TO
|
||||
* @template TM
|
||||
* @template TC
|
||||
*
|
||||
* @psalm-param Parser<TO> $open
|
||||
* @psalm-param Parser<TC> $close
|
||||
* @psalm-param Parser<TM> $middle
|
||||
*
|
||||
* @psalm-return Parser<TM>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function between(Parser $open, Parser $close, Parser $middle): Parser
|
||||
{
|
||||
return keepSecond($open, keepFirst($middle, $close));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses zero or more occurrences of $parser, separated by $separator. Returns a list of values.
|
||||
*
|
||||
* The sepBy parser always succeed, even if it doesn't find anything. Use {@see sepBy1()} if you want it to find at
|
||||
* least one value.
|
||||
*
|
||||
* @template TSeparator
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<TSeparator> $separator
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function sepBy(Parser $separator, Parser $parser): Parser
|
||||
{
|
||||
return sepBy1($separator, $parser)->or(pure([]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses one or more occurrences of $parser, separated by $separator. Returns a list of values.
|
||||
*
|
||||
* @template TS
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<TS> $separator
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @psalm-suppress MissingClosureReturnType
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function sepBy1(Parser $separator, Parser $parser): Parser
|
||||
{
|
||||
/** @psalm-suppress MissingClosureParamType */
|
||||
$prepend = fn($x) => fn(array $xs): array => array_merge([$x], $xs);
|
||||
$label = $parser->getLabel() . ", separated by " . $separator->getLabel();
|
||||
return pure($prepend)->apply($parser)->apply(many($separator->sequence($parser)))->label($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses 2 or more occurrences of $parser, separated by $separator. Returns a list of values.
|
||||
*
|
||||
* @template TS
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param Parser<TS> $separator
|
||||
* @psalm-param Parser<T> $parser
|
||||
*
|
||||
* @psalm-return Parser<list<T>>
|
||||
*
|
||||
* @psalm-suppress MissingClosureReturnType
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function sepBy2(Parser $separator, Parser $parser): Parser
|
||||
{
|
||||
/** @psalm-suppress MissingClosureParamType */
|
||||
$prepend = fn($x) => fn(array $xs): array => array_merge([$x], $xs);
|
||||
$label = "at least two of (" . $parser->getLabel() . "), separated by " . $separator->getLabel();
|
||||
return pure($prepend)->apply(keepFirst($parser, $separator))->apply(sepBy1($separator, $parser))->label($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* notFollowedBy only succeeds when $parser fails. It never consumes any input.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* `string("print")` will also match "printXYZ"
|
||||
*
|
||||
* `keepFirst(string("print"), notFollowedBy(alphaNumChar()))` will match "print something" but not "printXYZ something"
|
||||
*
|
||||
* @template T
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<T>
|
||||
* @see Parser::notFollowedBy()
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function notFollowedBy(Parser $parser): Parser
|
||||
{
|
||||
/** @psalm-var Parser<string> $p */
|
||||
$label = "notFollowedBy({$parser->getLabel()})";
|
||||
|
||||
$p = Parser::make($label, static function (Stream $input) use ($label, $parser): ParseResult {
|
||||
$result = $parser->run($input);
|
||||
return $result->isSuccess()
|
||||
? new Fail($label, $input)
|
||||
: new Succeed("", $input);
|
||||
});
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a function over the parser (which in turn maps it over the result).
|
||||
*
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @psalm-param pure-callable(T1) : T2 $transform
|
||||
* @psalm-return Parser<T2>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function map(Parser $parser, callable $transform): Parser
|
||||
{
|
||||
return Parser::make($parser->getLabel(), fn(Stream $input): ParseResult => $parser->run($input)->map($transform));
|
||||
}
|
||||
|
||||
/**
|
||||
* If $parser succeeds (either consuming input or not), lookAhead behaves like $parser succeeded without consuming
|
||||
* anything. If $parser fails, lookAhead has no effect, i.e. it will fail to consume input if $parser fails consuming
|
||||
* input.
|
||||
*
|
||||
* @template T
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-return Parser<T>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function lookAhead(Parser $parser): Parser
|
||||
{
|
||||
return Parser::make(
|
||||
$parser->getLabel(),
|
||||
static function (Stream $input) use ($parser): ParseResult {
|
||||
$parseResult = $parser->run($input);
|
||||
return $parseResult->isSuccess()
|
||||
? new Succeed($parseResult->output(), $input)
|
||||
: new Fail("lookAhead", $input);
|
||||
}
|
||||
);
|
||||
}
|
||||
66
vendor/parsica-php/parsica/src/numeric.php
vendored
Normal file
66
vendor/parsica-php/parsica/src/numeric.php
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
/**
|
||||
* Parse an integer and return it as a string. Use ->map('intval')
|
||||
* or similar to cast it to a numeric type.
|
||||
*
|
||||
* Example: "-123"
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function integer(): Parser
|
||||
{
|
||||
$zeroNine = digitChar();
|
||||
$oneNine = oneOfS("123456789");
|
||||
$minus = char('-');
|
||||
$digits = takeWhile1(isDigit())->label('at least one 0-9');
|
||||
/** @var Parser<string> $parser */
|
||||
$parser = choice(
|
||||
$minus->append($oneNine)->append($digits),
|
||||
$minus->append($zeroNine),
|
||||
$oneNine->append($digits),
|
||||
$zeroNine
|
||||
);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a float and return it as a string. Use ->map('floatval')
|
||||
* or similar to cast it to a numeric type.
|
||||
*
|
||||
* Example: -123.456E-789
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @psalm-suppress InvalidReturnType
|
||||
* @psalm-suppress InvalidReturnStatement
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function float(): Parser
|
||||
{
|
||||
$digits = takeWhile1(isDigit())->label('at least one 0-9');
|
||||
$fraction = char('.')->append($digits);
|
||||
$sign = char('+')->or(char('-'))->or(pure('+'));
|
||||
$exponent = assemble(
|
||||
charI('e')->map(fn(string $s) : string => strtoupper($s)),
|
||||
$sign,
|
||||
$digits
|
||||
);
|
||||
return assemble(
|
||||
integer(),
|
||||
optional($fraction),
|
||||
optional($exponent)
|
||||
)->label("float");
|
||||
}
|
||||
249
vendor/parsica-php/parsica/src/predicates.php
vendored
Normal file
249
vendor/parsica-php/parsica/src/predicates.php
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
|
||||
/**
|
||||
* Creates an equality predicate
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isEqual(string $x): callable
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::singleChar($x);
|
||||
return fn(string $y): bool => $x === $y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates a predicate.
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function notPred(callable $predicate): callable
|
||||
{
|
||||
return fn(string $x): bool => !$predicate($x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean And predicate.
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $first
|
||||
* @psalm-param pure-callable(string) : bool $second
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function andPred(callable $first, callable $second): callable
|
||||
{
|
||||
return fn(string $x): bool => $first($x) && $second($x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean Or predicate.
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $first
|
||||
* @psalm-param pure-callable(string) : bool $second
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function orPred(callable $first, callable $second): callable
|
||||
{
|
||||
return fn(string $x): bool => $first($x) || $second($x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate that checks if a character is in an array of character codes.
|
||||
*
|
||||
* @psalm-param list<int> $chars
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
*
|
||||
* @link https://doc.bccnsoft.com/docs/cppreference2018/en/c/string/wide/iswcntrl.html
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isCharCode(array $chars): callable
|
||||
{
|
||||
return fn(string $x): bool => in_array(mb_ord($x), $chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for a space character, and the control characters \t, \n, \r, \f, \v.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isSpace(): callable
|
||||
{
|
||||
return isCharCode([9, 10, 11, 12, 13, 32, 160]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like 'isSpace', but does not accept newlines and carriage returns.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @see isSpace
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isHSpace(): callable
|
||||
{
|
||||
return isCharCode([9, 11, 12, 32, 160]);
|
||||
}
|
||||
|
||||
/**
|
||||
* True for 0-9
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isDigit(): callable
|
||||
{
|
||||
return isCharCode(range(0x30, 0x39));
|
||||
}
|
||||
|
||||
/**
|
||||
* Control character predicate (a non-printing character of the Latin-1 subset of Unicode).
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isControl(): callable
|
||||
{
|
||||
return isCharCode(range(0x00, 0x1F) + [0x7F]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for a space or a tab character
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isBlank() : callable
|
||||
{
|
||||
return isCharCode([0x9, 0x20]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for a space character, and \t, \n, \r, \f, \v.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isWhitespace() : callable
|
||||
{
|
||||
return isCharCode([0x20, 0x9, 0xA, 0xB, 0xC, 0xD]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for an uppercase character A-Z.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isUpper() : callable
|
||||
{
|
||||
return isCharCode(range(0x41, 0x5A));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for a lowercase character a-z.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isLower()
|
||||
{
|
||||
return isCharCode(range(0x61, 0x7A));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for an uppercase or lowercase character A-Z, a-z.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isAlpha() : callable
|
||||
{
|
||||
return isCharCode(array_merge(range(0x41, 0x5A), range(0x61, 0x7A)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for an alpha or numeric character A-Z, a-z, 0-9.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isAlphaNum() : callable
|
||||
{
|
||||
return isCharCode(array_merge(range(0x30, 0x39), range(0x41, 0x5A), range(0x61, 0x7A)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given character is a hexadecimal numeric character 0123456789abcdefABCDEF.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isHexDigit() : callable
|
||||
{
|
||||
return isCharCode(array_merge(range(0x30, 0x39), range(0x41, 0x46), range(0x61, 0x66)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given character is a printable ASCII char.
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isPrintable() : callable
|
||||
{
|
||||
return isCharCode(range(0x20, 0x7E));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given character is a punctuation character !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
||||
*
|
||||
* @psalm-return pure-callable(string) : bool
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function isPunctuation() : callable
|
||||
{
|
||||
return isCharCode(array_merge(range(0x21, 0x2F), range(0x3A, 0x40), range(0x5B, 0x60), range(0x7B, 0x7E)));
|
||||
}
|
||||
|
||||
|
||||
320
vendor/parsica-php/parsica/src/primitives.php
vendored
Normal file
320
vendor/parsica-php/parsica/src/primitives.php
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
use Parsica\Parsica\Internal\EndOfStream;
|
||||
use Parsica\Parsica\Internal\Fail;
|
||||
use Parsica\Parsica\Internal\Succeed;
|
||||
|
||||
/**
|
||||
* A parser that satisfies a predicate on a single token. Useful as a building block for writing things like char(),
|
||||
* digit()...
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param callable(string) : bool $predicate
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function satisfy(callable $predicate): Parser
|
||||
{
|
||||
$label = "satisfy(predicate)";
|
||||
|
||||
/** @psalm-var pure-callable(Stream) : ParseResult $parserFunction */
|
||||
$parserFunction = static function (Stream $input) use ($label, $predicate): ParseResult {
|
||||
try {
|
||||
$t = $input->take1();
|
||||
} catch (EndOfStream $e) {
|
||||
return new Fail($label, $input);
|
||||
}
|
||||
return $predicate($t->chunk()) ? new Succeed($t->chunk(), $t->stream()) : new Fail($label, $input);
|
||||
};
|
||||
return Parser::make($label, $parserFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip 0 or more characters as long as the predicate holds.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
* @psalm-return Parser<null>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipWhile(callable $predicate): Parser
|
||||
{
|
||||
return takeWhile($predicate)->followedBy(pure(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip 1 or more characters as long as the predicate holds.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipWhile1(callable $predicate): Parser
|
||||
{
|
||||
return takeWhile1($predicate)->followedBy(pure(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep parsing 0 or more characters as long as the predicate holds.
|
||||
*
|
||||
* @template T
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
* @psalm-return Parser<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function takeWhile(callable $predicate): Parser
|
||||
{
|
||||
/** @psalm-pure */
|
||||
$parserFunction = static function (Stream $input) use ($predicate): ParseResult {
|
||||
$t = $input->takeWhile($predicate);
|
||||
return new Succeed($t->chunk(), $t->stream());
|
||||
};
|
||||
return Parser::make(
|
||||
"takeWhile(predicate)", $parserFunction
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Keep parsing 1 or more characters as long as the predicate holds.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-param pure-callable(string) : bool $predicate
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function takeWhile1(callable $predicate): Parser
|
||||
{
|
||||
$label = "takeWhile1(predicate)";
|
||||
return Parser::make($label, static function (Stream $input) use ($label, $predicate): ParseResult {
|
||||
|
||||
try {
|
||||
$t = $input->take1();
|
||||
} catch (EndOfStream $e) {
|
||||
return new Fail($label, $input);
|
||||
}
|
||||
|
||||
if (!$predicate($t->chunk())) {
|
||||
return new Fail($label, $input);
|
||||
}
|
||||
|
||||
$t = $input->takeWhile($predicate);
|
||||
return new Succeed($t->chunk(), $t->stream());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return a single character of anything.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function anySingle(): Parser
|
||||
{
|
||||
return satisfy(
|
||||
/** @psalm-param mixed $_ */
|
||||
fn($_) => true
|
||||
)->label("anySingle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return a single character of anything.
|
||||
*
|
||||
* @TODO This is an alias of anySingle. Should we get rid of one of them?
|
||||
* @psalm-return Parser<string>
|
||||
* @psalm-pure
|
||||
*/
|
||||
function anything(): Parser
|
||||
{
|
||||
return satisfy(fn(string $_) => true)->label("anything");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Match any character but the given one.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function anySingleBut(string $x): Parser
|
||||
{
|
||||
return satisfy(notPred(isEqual($x)))->label("anySingleBut($x)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Succeeds if the current character is in the supplied list of characters. Returns the parsed character.
|
||||
*
|
||||
* @psalm-param list<string> $chars
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function oneOf(array $chars): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::singleChars($chars);
|
||||
return satisfy(fn(string $x) => in_array($x, $chars))->label("one of " . implode('', $chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* A compact form of 'oneOf'.
|
||||
* oneOfS("abc") == oneOf(['a', 'b', 'c'])
|
||||
*
|
||||
* @psalm-param string $chars
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function oneOfS(string $chars): Parser
|
||||
{
|
||||
/** @psalm-var list<string> $split */
|
||||
$split = mb_str_split($chars);
|
||||
return oneOf($split);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The dual of 'oneOf'. Succeeds if the current character is not in the supplied list of characters. Returns the
|
||||
* parsed character.
|
||||
*
|
||||
* @psalm-param list<string> $chars
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function noneOf(array $chars): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::singleChars($chars);
|
||||
return satisfy(fn(string $x) => !in_array($x, $chars))
|
||||
->label("noneOf(" . implode('', $chars) . ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* A compact form of 'noneOf'.
|
||||
* noneOfS("abc") == noneOf(['a', 'b', 'c'])
|
||||
*
|
||||
* @psalm-param string $chars
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function noneOfS(string $chars): Parser
|
||||
{
|
||||
/** @psalm-var list<string> $split */
|
||||
$split = mb_str_split($chars);
|
||||
return noneOf($split);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the rest of the input and return it as a string. This parser never fails, but may return the empty string.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function takeRest(): Parser
|
||||
{
|
||||
return takeWhile(fn(string $_): bool => true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse nothing, but still succeed.
|
||||
*
|
||||
* This serves as the zero parser in `append()` operations.
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function nothing(): Parser
|
||||
{
|
||||
/** @psalm-var pure-callable(Stream):ParseResult<null> $result */
|
||||
$result = fn(Stream $input) : ParseResult => new Succeed(null, $input);
|
||||
$parser = Parser::make("<nothing>", $result);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse everything; that is, consume the rest of the input until the end.
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function everything(): Parser
|
||||
{
|
||||
return Parser::make("<everything>", fn(Stream $input) => new Succeed((string)$input, new StringStream("")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Always succeed, no matter what the input was.
|
||||
*
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function succeed(): Parser
|
||||
{
|
||||
return Parser::make("<always succeed>", fn(Stream $input) => new Succeed(null, $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Always fail, no matter what the input was.
|
||||
*
|
||||
* @return Parser
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function fail(string $label): Parser
|
||||
{
|
||||
return Parser::make($label, fn(Stream $input) => new Fail($label, $input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the end of the input
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function eof(): Parser
|
||||
{
|
||||
$label = "<EOF>";
|
||||
return Parser::make($label, fn(Stream $input): ParseResult => $input->isEOF()
|
||||
? new Succeed("", $input)
|
||||
: new Fail($label, $input)
|
||||
);
|
||||
}
|
||||
25
vendor/parsica-php/parsica/src/recursion.php
vendored
Normal file
25
vendor/parsica-php/parsica/src/recursion.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
/**
|
||||
* Create a recursive parser. Used in combination with Parser#recurse().
|
||||
*
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
*
|
||||
* @template T
|
||||
* @psalm-pure
|
||||
*/
|
||||
function recursive(): Parser
|
||||
{
|
||||
return Parser::recursive();
|
||||
}
|
||||
36
vendor/parsica-php/parsica/src/sideEffects.php
vendored
Normal file
36
vendor/parsica-php/parsica/src/sideEffects.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
/**
|
||||
* If the parser is successful, call the $receiver function with the output of the parser. The resulting parser
|
||||
* behaves identical to the original one. This combinator is useful for expressing side effects during the parsing
|
||||
* process. It can be hooked into existing event publishing libraries by using $receiver as an adapter for those. Other
|
||||
* use cases are logging, caching, performing an action whenever a value is matched in a long-running input stream, ...
|
||||
*
|
||||
* @template T
|
||||
* @psalm-param Parser<T> $parser
|
||||
* @psalm-param callable(T): void $receiver
|
||||
* @psalm-return Parser<T>
|
||||
* @api
|
||||
*/
|
||||
function emit(Parser $parser, callable $receiver): Parser
|
||||
{
|
||||
/** @psalm-var pure-callable(Stream):ParseResult $parserFunction */
|
||||
$parserFunction = static function (Stream $input) use ($receiver, $parser): ParseResult {
|
||||
$result = $parser->run($input);
|
||||
if ($result->isSuccess()) {
|
||||
$receiver($result->output());
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
return Parser::make("emit", $parserFunction);
|
||||
}
|
||||
146
vendor/parsica-php/parsica/src/space.php
vendored
Normal file
146
vendor/parsica-php/parsica/src/space.php
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
/**
|
||||
* Parse a single space character.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function space(): Parser
|
||||
{
|
||||
return char(' ')->label("<space>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single tab character.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function tab(): Parser
|
||||
{
|
||||
return char("\t")->label("<tab>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a space or tab.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function blank(): Parser
|
||||
{
|
||||
return satisfy(isBlank())->label("<blank>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a space character, and \t, \n, \r, \f, \v.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function whitespace(): Parser
|
||||
{
|
||||
return satisfy(isWhitespace())->label("<whitespace>");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a newline character.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function newline(): Parser
|
||||
{
|
||||
return char("\n")->label("<newline>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a carriage return character and a newline character. Return the two characters. {\r\n}
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function crlf(): Parser
|
||||
{
|
||||
return string("\r\n")->label("<crlf>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a newline or a crlf.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function eol(): Parser
|
||||
{
|
||||
return either(newline(), crlf())->label("<EOL>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip zero or more white space characters.
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipSpace(): Parser
|
||||
{
|
||||
return skipWhile(isSpace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Like 'skipSpace', but does not accept newlines and carriage returns.
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
* @api
|
||||
* @see skipSpace
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipHSpace(): Parser
|
||||
{
|
||||
return skipWhile(isHSpace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip one or more white space characters.
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
* @api
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipSpace1(): Parser
|
||||
{
|
||||
return skipWhile1(isSpace())->label("<space>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Like 'skipSpace1', but does not accept newlines and carriage returns.
|
||||
*
|
||||
* @psalm-return Parser<null>
|
||||
* @api
|
||||
* @see skipSpace1
|
||||
* @psalm-pure
|
||||
*/
|
||||
function skipHSpace1(): Parser
|
||||
{
|
||||
return skipWhile1(isHSpace())->label("<space>");
|
||||
}
|
||||
79
vendor/parsica-php/parsica/src/strings.php
vendored
Normal file
79
vendor/parsica-php/parsica/src/strings.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the Parsica library.
|
||||
*
|
||||
* Copyright (c) 2020 Mathias Verraes <mathias@verraes.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Parsica\Parsica;
|
||||
|
||||
use Parsica\Parsica\Internal\Assert;
|
||||
use Parsica\Parsica\Internal\EndOfStream;
|
||||
use Parsica\Parsica\Internal\Fail;
|
||||
use Parsica\Parsica\Internal\Succeed;
|
||||
use function Parsica\Parsica\Internal\FP\foldl;
|
||||
|
||||
/**
|
||||
* Parse a non-empty string.
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @see stringI()
|
||||
* @psalm-pure
|
||||
*/
|
||||
function string(string $str): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmpty($str);
|
||||
$len = mb_strlen($str);
|
||||
$label = "'$str'";
|
||||
/** @psalm-var Parser<string> $parser */
|
||||
$parser = Parser::make($label, static function (Stream $input) use ($label, $len, $str): ParseResult {
|
||||
try {
|
||||
$t = $input->takeN($len);
|
||||
} catch (EndOfStream $e) {
|
||||
return new Fail($label, $input);
|
||||
}
|
||||
return $t->chunk() === $str
|
||||
? new Succeed($str, $t->stream())
|
||||
: new Fail($label, $input);
|
||||
}
|
||||
);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a non-empty string, case-insensitive, and case-preserving. On success, it returns the string cased as the
|
||||
* actually parsed input.
|
||||
* eg stringI("foobar")->tryString("foObAr") will succeed with "foObAr"
|
||||
*
|
||||
* @TODO The implementation could be replaced using Stream::takeWhile
|
||||
*
|
||||
* @psalm-return Parser<string>
|
||||
* @api
|
||||
* @see string()
|
||||
* @psalm-pure
|
||||
*/
|
||||
function stringI(string $str): Parser
|
||||
{
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
Assert::nonEmpty($str);
|
||||
/** @psalm-var list<string> $split */
|
||||
$split = mb_str_split($str);
|
||||
$chars = array_map(
|
||||
fn(string $c): Parser => charI($c),
|
||||
$split
|
||||
);
|
||||
|
||||
/** @psalm-var Parser<string> $parser */
|
||||
$parser = foldl(
|
||||
$chars,
|
||||
/** @psalm-pure */
|
||||
fn(Parser $l, Parser $r): Parser => append($l, $r),
|
||||
succeed()
|
||||
)->label("'$str'");
|
||||
return $parser;
|
||||
}
|
||||
Reference in New Issue
Block a user