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,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());
}
}