First commit

This commit is contained in:
Cody Cook 2024-04-29 19:04:51 -07:00
commit d5bb2f19fa
117 changed files with 68604 additions and 0 deletions

View file

@ -0,0 +1,4 @@
vendor/
phpunit.xml
.php_cs.cache
composer.lock

View file

@ -0,0 +1,15 @@
language: php
php:
- 7.1
- 7.2
- nightly
before_script:
- composer self-update
- composer install --no-interaction --no-dev
matrix:
allow_failures:
- php: nightly
fast_finish: true

View file

@ -0,0 +1,12 @@
CHANGELOG
=========
2.0.0 (2018/06/29)
------------------
* [New] The class `TokenStream` implements `TokenStreamInterface`.
* [New] Added PHPUnit as dev-requirement
* [Fixed] Fixed a typo with the method `parseImplementation` from the class `AbstractParser`.
The previous name was `parseImplentation`.
1.0.0 (2017/11/18)
------------------
* Initial version.

19
vendor/yosymfony/parser-utils/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2017-2018 Víctor Puertas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

145
vendor/yosymfony/parser-utils/README.md vendored Normal file
View file

@ -0,0 +1,145 @@
A library for writing [recursive descent parsers](https://en.wikipedia.org/wiki/Recursive_descent_parser)
in PHP.
[![Build Status](https://travis-ci.org/yosymfony/parser-utils.svg?branch=master)](https://travis-ci.org/yosymfony/parser-utils)
## requires
* PHP >= 7.1
## Installation
The preferred installation method is [composer](https://getcomposer.org):
```bash
composer require yosymfony/parser-utils
```
## An example
First, you need to create a lexer. This one will recognize tokens
```php
use Yosymfony\ParserUtils\BasicLexer;
$lexer = new BasicLexer([
'/^([0-9]+)/x' => 'T_NUMBER',
'/^(\+)/x' => 'T_PLUS',
'/^(-)/x' => 'T_MINUS',
'/^\s+/' => 'T_SPACE', // We do not surround it with parentheses because
// this is not meaningful for us in this case
]);
```
Second, you need a parser for consuming the tokens provided by the lexer.
The `AbstractParser` class contains an abstract method called `parseImplementation`
that receives a `TokenStream` as an argument.
```php
use Yosymfony\ParserUtils\AbstractParser;
class Parser extends AbstractParser
{
protected function parseImplementation(TokenStream $stream)
{
$result = $stream->matchNext('T_NUMBER');
while ($stream->isNextAny(['T_PLUS', 'T_MINUS'])) {
switch ($stream->moveNext()->getName()) {
case 'T_PLUS':
$result += $stream->matchNext('T_NUMBER');
break;
case 'T_MINUS':
$result -= $stream->matchNext('T_NUMBER');
break;
default:
throw new SyntaxErrorException("Something went wrong");
break;
}
}
return $result;
}
}
```
Now, you can see the results:
```php
$parser = new Parser($lexer);
$parser->parse('1 + 1'); // 2
```
### The BasicLexer class
The lexer has the responsibility of recognizing tokens. This one works line by
line. If you want to generate an special `T_NEWLINE` token for each line
of the input, call `$lexer->generateNewlineTokens()` before tokenizing. You can set the
name of this special token using the method `setNewlineTokenName`.
```php
$lexer = new BasicLexer([...]);
$lexer->generateNewlineTokens()
->setNewlineTokenName('T_NL');
$lexer->tokenize('...');
```
Additionally, there is another special token `T_EOS` that determines the end of the input
string. To enable this feature call `$lexer->generateEosToken()` before tokenizing.
You can set the name of this special token using the method `setEosTokenName`.
```php
$lexer = new BasicLexer([...]);
$lexer->generateEosToken()
->setEosTokenName('T_MY_EOS');
$lexer->tokenize('...');
```
### The TokenStream class
This class let you treat with the list of tokens returned by the lexer.
* **moveNext**: Moves the pointer one token forward. Returns a `Token` object or
`null` if there are not more tokens. e.g: `$ts->moveNext()`.
* **matchNext**: Matches the next token and returns its value. This method moves
the pointer one token forward. It will throw an `SyntaxErrorException` exception
if the next token does not match. e.g: `$number = $ts->matchNext('T_NUMBER')`.
* **isNext**: Checks if the next token matches with the token name passed as argument.
e.g: `$ts->isNext('T_PLUS') // true or false`.
* **skipWhile**: Skips tokens while they match with the token name passed
as argument. This method moves the pointer "n" tokens forward until the
last one that match with the token name. e.g: `$ts->skipWhile('T_PLUS')`
* **skipWhileAny**: Skips tokens while they match with one of the token
names passed as argument. This method moves the pointer "n" tokens
forward until the last one that match with one of the token names
e.g: `$ts->skipWhileAny(['T_PLUS', 'T_MINUS'])`
* **isNextSequence**: Checks if the following tokens in the stream match with
the sequence of tokens. e.g: `$ts->isNextSequence(['T_NUMBER', 'T_PLUS', 'T_NUMBER']) // true or false`.
* **isNextAny**: Checks if one of the tokens passed as argument is the next token.
e.g: `$fs->isNextAny(['T_PLUS', 'T_SUB']) // true or false`
* **hasPendingTokens**: Has pending tokens? e.g: `$fs->hasPendingTokens() // true or false`.
* **reset**: Resets the stream to the beginning.
### Tokens
Tokens are instances of `Token` class, a class than contains the following methods:
* **getName**: returns the name of the toke. e.g: `T_SUM`.
* **getValue**: returns the value of the token.
* **getLine**: returns the line in where the token is found.
## Unit tests
You can run the unit tests with the following command:
```bash
$ cd parser-utils
$ composer test
```
## License
This library is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).

View file

@ -0,0 +1,32 @@
{
"name": "yosymfony/parser-utils",
"description": "Parser utilities",
"type": "library",
"keywords": ["parser", "lexer"],
"homepage": "http://github.com/yosymfony/toml",
"license": "MIT",
"authors": [
{
"name": "Victor Puertas",
"email": "vpgugr@gmail.com",
"homepage": "http://yosymfony.com"
}
],
"require": {
"php": ">=7.1"
},
"autoload": {
"psr-4": { "Yosymfony\\ParserUtils\\": "src/" }
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"require-dev": {
"phpunit/phpunit": "^6"
},
"scripts": {
"test": "vendor/bin/phpunit"
}
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Spress Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./vendor</directory>
<directory>./tests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
abstract class AbstractParser
{
protected $lexer;
/**
* Constructor
*
* @param LexerInterface $lexer The lexer
*/
public function __construct(LexerInterface $lexer)
{
$this->lexer = $lexer;
}
/**
* Parse a given input
*
* @param string $input
*
* @return mixed
*/
public function parse(string $input)
{
$ts = $this->lexer->tokenize($input);
$parseResult = $this->parseImplementation($ts);
if ($ts->hasPendingTokens()) {
throw new SyntaxErrorException('There are tokens not processed.');
}
return $parseResult;
}
/**
* Do the real parsing
*
* @param TokenStream $stream The token stream returned by the lexer
*
* @return mixed
*/
abstract protected function parseImplementation(TokenStream $stream);
}

View file

@ -0,0 +1,175 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
class BasicLexer implements LexerInterface
{
protected $newlineTokenName = 'T_NEWLINE';
protected $eosTokenName = 'T_EOS';
protected $activateNewlineToken = false;
protected $activateEOSToken = false;
protected $terminals = [];
/**
* Constructor
*
* @param array $terminal List of terminals
* e.g:
* [
* "/^([)/" => "T_BRAKET_BEGIN"
* ]
*/
public function __construct(array $terminals)
{
$this->terminals = $terminals;
}
/**
* Generates an special "T_NEWLINE" for each line of the input
*
* @return BasicLexer The BasicLexer itself
*/
public function generateNewlineTokens() : BasicLexer
{
$this->activateNewlineToken = true;
return $this;
}
/**
* Generates an special "T_EOS" at the end of the input string
*
* @return BasicLexer The BasicLexer itself
*/
public function generateEosToken() : BasicLexer
{
$this->activateEOSToken = true;
return $this;
}
/**
* Sets the name of the newline token
*
* @param string $name The name of the token
*
* @return BasicLexer The BasicLexer itself
*
* @throws InvalidArgumentException If the name is empty
*/
public function setNewlineTokenName(string $name) : BasicLexer
{
if (strlen($name) == 0) {
throw new \InvalidArgumentException('The name of the newline token must be not empty.');
}
$this->newlineTokenName = $name;
return $this;
}
/**
* Sets the name of the end-of-string token
*
* @param string $name The name of the token
*
* @return BasicLexer The BasicLexer itself
*
* @throws InvalidArgumentException If the name is empty
*/
public function setEosTokenName(string $name) : BasicLexer
{
if (strlen($name) == 0) {
throw new \InvalidArgumentException('The name of the EOS token must be not empty.');
}
$this->eosTokenName = $name;
return $this;
}
/**
* {@inheritdoc}
*/
public function tokenize(string $input) : TokenStream
{
$counter = 0;
$tokens = [];
$lines = explode("\n", $input);
$totalLines = count($lines);
foreach ($lines as $number => $line) {
$offset = 0;
$lineNumber = $number + 1;
while ($offset < strlen($line)) {
list($name, $matches) = $this->match($line, $lineNumber, $offset);
if (isset($matches[1])) {
$token = new Token($matches[1], $name, $lineNumber);
$this->processToken($token, $matches);
$tokens[] = $token;
}
$offset += strlen($matches[0]);
}
if ($this->activateNewlineToken && ++$counter < $totalLines) {
$tokens[] = new Token("\n", $this->newlineTokenName, $lineNumber);
}
}
if ($this->activateEOSToken) {
$tokens[] = new Token('', $this->eosTokenName, $lineNumber);
}
return new TokenStream($tokens);
}
/**
* Returns the first match with the list of terminals
*
* @return array An array with the following keys:
* [0] (string): name of the token
* [1] (array): matches of the regular expression
*
* @throws SyntaxErrorException If the line does not contain any token
*/
protected function match(string $line, int $lineNumber, int $offset) : array
{
$restLine = substr($line, $offset);
foreach ($this->terminals as $pattern => $name) {
if (preg_match($pattern, $restLine, $matches)) {
return [
$name,
$matches,
];
}
}
throw new SyntaxErrorException(sprintf('Lexer error: unable to parse "%s" at line %s.', $line, $lineNumber));
}
/**
* Applies additional actions over a token.
*
* Implement this method if you need to do changes after a token was found.
* This method is invoked for each token found
*
* @param Token $token The token
* @param string[] $matches Set of matches from the regular expression
*
* @return void
*/
protected function processToken(Token $token, array $matches) : void
{
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
/**
* Interface for a lexer
*/
interface LexerInterface
{
/**
* Returns the tokens found
*
* @param string $input The input to be tokenized
*
* @return TokenStream The stream of tokens
*/
public function tokenize(string $input) : TokenStream;
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
/**
* Exception thrown when an error occurs during parsing or tokenizing
*/
class SyntaxErrorException extends \RuntimeException
{
protected $token;
/**
* Constructor
*
* @param string $message The error messsage
* @param Token|null $token The token
* @param \Exception|null $previous The previous exceptio
*/
public function __construct(string $message, Token $token = null, \Exception $previous = null)
{
parent::__construct($message, 0, $previous);
}
/**
* Sets the token associated to the exception
*
* @param Token $token The token
*/
public function setToken(Token $token) : void
{
$this->token = $token;
}
/**
* Returns the token associated to the exception
*
* @return Token|null
*/
public function getToken() : ?Token
{
return $this->token;
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
class Token
{
protected $value;
protected $name;
protected $line;
/**
* Constructor.
*
* @param string $value The value of the token
* @param string $name The name of the token. e.g: T_BRAKET_BEGIN
* @param int $line Line of the code in where the token is found
*/
public function __construct(string $value, string $name, int $line)
{
$this->value = $value;
$this->name = $name;
$this->line = $line;
}
/**
* Returns the value (the match term)
*
* @return string
*/
public function getValue() : string
{
return $this->value;
}
/**
* Returns the name of the token
*
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* Returns the line of the code in where the token is found
*
* @return int
*/
public function getLine() : int
{
return $this->line;
}
public function __toString() : string
{
return sprintf(
"[\n name: %s\n value:%s\n line: %s\n]",
$this->name,
$this->value,
$this->line
);
}
}

View file

@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
class TokenStream implements TokenStreamInterface
{
protected $tokens;
protected $index = -1;
/**
* Constructor
*
* @param Token[] List of tokens
*/
public function __construct(array $tokens)
{
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
public function moveNext() : ?Token
{
return $this->tokens[++$this->index] ?? null;
}
/**
* {@inheritdoc}
*/
public function matchNext(string $tokenName) : string
{
$token = $this->moveNext();
--$this->index;
if ($token->getName() == $tokenName) {
return $this->moveNext()->getValue();
}
throw new SyntaxErrorException(sprintf(
'Syntax error: expected token with name "%s" instead of "%s" at line %s.',
$tokenName,
$token->getName(),
$token->getLine()
));
}
/**
* {@inheritdoc}
*/
public function skipWhile(string $tokenName) : void
{
$this->skipWhileAny([$tokenName]);
}
/**
* {@inheritdoc}
*/
public function skipWhileAny(array $tokenNames) : void
{
while ($this->isNextAny($tokenNames)) {
$this->moveNext();
}
}
/**
* {@inheritdoc}
*/
public function isNext(string $tokenName) : bool
{
$token = $this->moveNext();
--$this->index;
if ($token === null) {
return false;
}
return $token->getName() == $tokenName;
}
/**
* {@inheritdoc}
*/
public function isNextSequence(array $tokenNames) : bool
{
$result = true;
$currentIndex = $this->index;
foreach ($tokenNames as $tokenName) {
$token = $this->moveNext();
if ($token === null || $token->getName() != $tokenName) {
$result = false;
break;
}
}
$this->index = $currentIndex;
return $result;
}
/**
* {@inheritdoc}
*/
public function isNextAny(array $tokenNames) : bool
{
$token = $this->moveNext();
--$this->index;
if ($token === null) {
return false;
}
foreach ($tokenNames as $tokenName) {
if ($tokenName === $token->getName()) {
return true;
}
}
return false;
}
/**
* Returns all tokens
*
* @return token[] List of tokens
*/
public function getAll() : array
{
return $this->tokens;
}
/**
* {@inheritdoc}
*/
public function hasPendingTokens() :bool
{
$tokenCount = count($this->tokens);
if ($tokenCount == 0) {
return false;
}
return $this->index < ($tokenCount - 1);
}
/**
* {@inheritdoc}
*/
public function reset() : void
{
$this->index = -1;
}
}

View file

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils;
interface TokenStreamInterface
{
/**
* Moves the pointer one token forward
*
* @return Token|null The token or null if there are not more tokens
*/
public function moveNext() : ?Token;
/**
* Matches the next token. This method moves the pointer one token forward
* if an error does not occur
*
* @param string $tokenName The name of the token
*
* @return string The value of the token
*
* @throws SyntaxErrorException If the next token does not match
*/
public function matchNext(string $tokenName) : string;
/**
* Skips tokens while they match with the token name passed as argument.
* This method moves the pointer "n" tokens forward until the last one
* that match with the token name
*
* @param string $tokenName The name of the token
*/
public function skipWhile(string $tokenName) : void;
/**
* Skips tokens while they match with one of the token names passed as
* argument. This method moves the pointer "n" tokens forward until the
* last one that match with one of the token names
*
* @param string[] $tokenNames List of token names
*/
public function skipWhileAny(array $tokenNames) : void;
/**
* Checks if the next token matches with the token name passed as argument
*
* @param string $tokenName The name of the token
*
* @return bool
*/
public function isNext(string $tokenName) : bool;
/**
* Checks if the following tokens in the stream match with the sequence of tokens
*
* @param string[] $tokenNames Sequence of token names
*
* @return bool
*/
public function isNextSequence(array $tokenNames) : bool;
/**
* Checks if one of the tokens passed as argument is the next token
*
* @param string[] $tokenNames List of token names. e.g: 'T_PLUS', 'T_SUB'
*
* @return bool
*/
public function isNextAny(array $tokenNames) : bool;
/**
* Has pending tokens?
*
* @return bool
*/
public function hasPendingTokens() :bool;
/**
* Resets the stream to the beginning
*/
public function reset() : void;
}

View file

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils\Test;
use PHPUnit\Framework\TestCase;
use Yosymfony\ParserUtils\AbstractParser;
use Yosymfony\ParserUtils\BasicLexer;
use Yosymfony\ParserUtils\SyntaxErrorException;
use Yosymfony\ParserUtils\TokenStream;
class AbstractParserTest extends TestCase
{
private $parser;
public function setup()
{
$lexer = new BasicLexer([
'/^([0-9]+)/x' => 'T_NUMBER',
'/^(\+)/x' => 'T_PLUS',
'/^(-)/x' => 'T_MINUS',
'/^\s+/' => 'T_SPACE',
]);
$this->parser = $this->getMockBuilder(AbstractParser::class)
->setConstructorArgs([$lexer])
->getMockForAbstractClass();
$this->parser->expects($this->any())
->method('parseImplementation')
->will($this->returnCallback(function (TokenStream $stream) {
$result = $stream->matchNext('T_NUMBER');
while ($stream->isNextAny(['T_PLUS', 'T_MINUS'])) {
switch ($stream->moveNext()->getName()) {
case 'T_PLUS':
$result += $stream->matchNext('T_NUMBER');
break;
case 'T_MINUS':
$result -= $stream->matchNext('T_NUMBER');
break;
default:
throw new SyntaxErrorException("Something went wrong");
break;
}
}
return $result;
}));
}
public function testParseMustReturnTheResultOfTheSum()
{
$this->assertEquals(2, $this->parser->parse('1 + 1'));
}
}

View file

@ -0,0 +1,127 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils\Test;
use PHPUnit\Framework\TestCase;
use Yosymfony\ParserUtils\BasicLexer;
use Yosymfony\ParserUtils\Token;
class BasicLexerTest extends TestCase
{
public function testTokenizeMustReturnsTheListOfTokens()
{
$lexer = new BasicLexer([
'/^([0-9]+)/x' => 'T_NUMBER',
'/^(\+)/x' => 'T_PLUS',
'/^(-)/x' => 'T_MINUS',
]);
$tokens = $lexer->tokenize('1+2')->getAll();
$this->assertEquals([
new Token('1', 'T_NUMBER', 1),
new Token('+', 'T_PLUS', 1),
new Token('2', 'T_NUMBER', 1),
], $tokens);
}
public function testTokenizeMustReturnsTheListOfTokensWithoutThoseDoNotHaveParenthesizedSupatternInTerminalSymbols()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
'/^(\+)/' => 'T_PLUS',
'/^(-)/' => 'T_MINUS',
'/^\s+/' => 'T_SPACE',
]);
$tokens = $lexer->tokenize('1 + 2')->getAll();
$this->assertEquals([
new Token('1', 'T_NUMBER', 1),
new Token('+', 'T_PLUS', 1),
new Token('2', 'T_NUMBER', 1),
], $tokens, 'T_SPACE is not surround with (). e.g: ^(\s+)');
}
public function testTokenizeWithEmptyStringMustReturnsZeroTokens()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
'/^(\+)/' => 'T_PLUS',
'/^(-)/' => 'T_MINUS',
]);
$tokens = $lexer->tokenize('')->getAll();
$this->assertCount(0, $tokens);
}
public function testTokenizeMustReturnsNewLineTokensWhenGenerateNewlineTokensIsEnabled()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
]);
$lexer->generateNewlineTokens();
$ts = $lexer->tokenize("0\n");
$ts->moveNext();
$token = $ts->moveNext();
$this->assertEquals('T_NEWLINE', $token->getName());
$this->assertFalse($ts->hasPendingTokens());
}
public function testTokenizeMustReturnsCustomNewLineTokensWhenThereIsCustomNameAndGenerateNewlineTokensIsEnabled()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
]);
$lexer->setNewlineTokenName('T_MY_NEWLINE')
->generateNewlineTokens();
$ts = $lexer->tokenize("0\n");
$ts->moveNext();
$token = $ts->moveNext();
$this->assertEquals('T_MY_NEWLINE', $token->getName());
$this->assertFalse($ts->hasPendingTokens());
}
public function testTokenizeMustReturnsEosTokenWhenGenerateEosTokenIsEnabled()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
]);
$lexer->generateEosToken();
$ts = $lexer->tokenize("0");
$ts->moveNext();
$token = $ts->moveNext();
$this->assertEquals('T_EOS', $token->getName());
$this->assertFalse($ts->hasPendingTokens());
}
public function testTokenizeMustReturnsCustomNameEosTokenWhenThereIsCustomNameAndGenerateEosTokenIsEnabled()
{
$lexer = new BasicLexer([
'/^([0-9]+)/' => 'T_NUMBER',
]);
$lexer->setEosTokenName('T_MY_EOS')
->generateEosToken();
$ts = $lexer->tokenize("0");
$ts->moveNext();
$token = $ts->moveNext();
$this->assertEquals('T_MY_EOS', $token->getName());
$this->assertFalse($ts->hasPendingTokens());
}
}

View file

@ -0,0 +1,261 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils\Test;
use PHPUnit\Framework\TestCase;
use Yosymfony\ParserUtils\SyntaxErrorException;
use Yosymfony\ParserUtils\Token;
use Yosymfony\ParserUtils\TokenStream;
class TokenStreamTest extends TestCase
{
public function testGetAllMustReturnsAllTokens()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertCount(2, $ts->getAll());
}
public function testMoveNextMustReturnsTheFirstTokenTheFirstTime()
{
$token = new Token('+', 'T_PLUS', 1);
$ts = new TokenStream([
$token,
]);
$this->assertEquals($token, $ts->moveNext());
}
public function testMoveNextMustReturnsTheSecondTokenTheSecondTime()
{
$token = new Token('1', 'T_NUMBER', 1);
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
$token,
]);
$ts->moveNext();
$this->assertEquals($token, $ts->moveNext());
}
public function testMoveNextMustReturnsWhenThereAreNotMoreTokens()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
]);
$ts->moveNext();
$this->assertNull($ts->moveNext());
}
public function testMoveNextMustReturnsTheFirstTokenAfterAReset()
{
$token = new Token('1', 'T_NUMBER', 1);
$ts = new TokenStream([
$token,
new Token('+', 'T_PLUS', 1),
]);
$ts->moveNext();
$ts->moveNext();
$ts->reset();
$this->assertEquals($token, $ts->moveNext());
}
public function testMatchNextMustReturnMatchValueWhenTheNameOfNextTokenMatchWithTheNamePassed()
{
$token = new Token('1', 'T_NUMBER', 1);
$ts = new TokenStream([
$token,
]);
$this->assertEquals('1', $ts->matchNext('T_NUMBER'));
}
public function testMatchNextMustThrowExceptionWhenTheNameOfNextTokenDoesNotMatchWithTheNamePassed()
{
$this->expectException(SyntaxErrorException::class);
$this->expectExceptionMessage('Syntax error: expected token with name "T_PLUS" instead of "T_NUMBER" at line 1.');
$token = new Token('1', 'T_NUMBER', 1);
$ts = new TokenStream([
$token,
]);
$ts->matchNext('T_PLUS');
}
public function testIsNextMustReturnsTrueWhenTheNameOfNextTokenMatchWithTheNamePassed()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$ts->moveNext();
$this->assertTrue($ts->isNext('T_NUMBER'));
}
public function testIsNextMustReturnsTrueWhenTheNameOfNextTokenMatchWithTheNamePassedAtTheBeginning()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertTrue($ts->isNext('T_PLUS'));
}
public function testIsNextMustReturnsFalseWhenTheNameOfNextTokenDoesNotMatchWithTheNamePassed()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertFalse($ts->isNext('T_NUMBER'));
}
public function testIsNextMustNotAlterTheTokenStream()
{
$token = new Token('+', 'T_PLUS', 1);
$ts = new TokenStream([
$token,
new Token('1', 'T_NUMBER', 1),
]);
$ts->isNext('T_PLUS');
$this->assertEquals($token, $ts->moveNext(), 'The next token must be T_PLUS');
}
public function testIsNextSequenceMustReturnTrueWhenTheFollowingTokensInTheStreamMatchWithSequence()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertTrue($ts->isNextSequence(['T_PLUS', 'T_NUMBER']));
}
public function testIsNextSequenceMustReturnFalseWhenTheFollowingTokensInTheStreamDoesNotMatchWithSequence()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertFalse($ts->isNextSequence(['T_NUMBER', 'T_PLUS']));
}
public function testIsNextSequenceMustNotAlterTheTokenStream()
{
$token = new Token('+', 'T_PLUS', 1);
$ts = new TokenStream([
$token,
new Token('1', 'T_NUMBER', 1),
]);
$ts->isNextSequence(['T_NUMBER', 'T_PLUS']);
$this->assertEquals($token, $ts->moveNext(), 'The next token must be T_PLUS');
}
public function testIsNextAnyMustReturnTrueWhenNameOfNextTokenMatchWithOneOfTheList()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertTrue($ts->isNextAny(['T_MINUS', 'T_PLUS']));
}
public function testIsNextAnyMustReturnFalseWhenNameOfNextTokenDoesNotMatchWithOneOfTheList()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$this->assertFalse($ts->isNextAny(['T_DIV', 'T_MINUS']));
}
public function testIsNextAnyMustNotAlterTheTokenStream()
{
$token = new Token('+', 'T_PLUS', 1);
$ts = new TokenStream([
$token,
new Token('1', 'T_NUMBER', 1),
]);
$ts->isNextAny(['T_MINUS', 'T_PLUS']);
$this->assertEquals($token, $ts->moveNext(), 'The next token must be T_PLUS');
}
public function testHasPendingTokensMustReturnTrueWhenThereArePendingTokens()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
]);
$this->assertTrue($ts->hasPendingTokens());
}
public function testHasPendingTokensMustReturnFalseWhenTokenStreamIsEmpty()
{
$ts = new TokenStream([]);
$this->assertFalse($ts->hasPendingTokens());
}
public function testHasPendingTokensMustReturnFalseAfterPointingToTheLastToken()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
]);
$ts->moveNext();
$this->assertFalse($ts->hasPendingTokens());
}
public function testSkipWhileMustMovesPointerNTokensForwardUtilLastOneInstanceOfToken()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('+', 'T_PLUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$ts->skipWhile('T_PLUS');
$this->assertTrue($ts->isNext('T_NUMBER'));
}
public function testSkipWhileAnyMustMovesPointerNTokensForwardUtilLastOneInstanceOfOneOfAnyTokens()
{
$ts = new TokenStream([
new Token('+', 'T_PLUS', 1),
new Token('+', 'T_PLUS', 1),
new Token('+', 'T_MINUS', 1),
new Token('1', 'T_NUMBER', 1),
]);
$ts->skipWhileAny(['T_PLUS', 'T_MINUS']);
$this->assertTrue($ts->isNext('T_NUMBER'));
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Yosymfony\ParserUtils package.
*
* (c) YoSymfony <http://github.com/yosymfony>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Yosymfony\ParserUtils\Test;
use PHPUnit\Framework\TestCase;
use Yosymfony\ParserUtils\Token;
class TokenTest extends TestCase
{
public function testConstructorMustSetMatchAndNameAndLine()
{
$token = new Token('+', 'T_PLUS', 1);
$this->assertEquals('+', $token->getValue());
$this->assertEquals('T_PLUS', $token->getName());
$this->assertEquals(1, $token->getLine());
}
}