Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lexer 3 feature #11

Merged
merged 5 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"license": "MIT",
"require": {
"php": "^8.1",
"doctrine/lexer": "^2.1"
"doctrine/lexer": "^2.1|^3.0"
},
"require-dev": {
"phpunit/phpunit": "~10.0"
Expand Down
15 changes: 3 additions & 12 deletions lib/LongitudeOne/Geo/String/Lexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* Tokenize geographic coordinates.
*
* @extends AbstractLexer<int, int|float|string>
* @extends AbstractLexer<int, int|string>
*/
class Lexer extends AbstractLexer
{
Expand All @@ -35,19 +35,10 @@ class Lexer extends AbstractLexer
public const T_PLUS = 8;
public const T_QUOTE = 13;

/**
* @param string|null $input
*/
public function __construct($input = null)
public function __construct(?string $input = null)
{
if (!is_null($input) && !is_string($input)) {
trigger_error(sprintf(
'Passing a non-string "%s" value in LongitudeOne\Geo\String\Lexer::__construct() is deprecated',
$input), E_USER_DEPRECATED);
}

if (null !== $input) {
$this->setInput((string) $input);
$this->setInput($input);
}
}

Expand Down
48 changes: 18 additions & 30 deletions lib/LongitudeOne/Geo/String/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,11 @@ class Parser
* Constructor.
*
* Setup up instance properties
*
* @param string|float|int|null $input
*/
public function __construct($input = null)
public function __construct(string|int|null $input = null)
{
$this->lexer = new Lexer();

if (is_float($input)) {
trigger_error('Since longitudeone/geo-parser 2.1: Passing a float to LongitudeOne\Geo\String\Parser::__construct() is deprecated. Use a string instead.', E_USER_DEPRECATED);
}

if (null !== $input) {
$this->input = (string) $input;
}
Expand All @@ -68,14 +62,10 @@ public function __construct($input = null)
/**
* Parse input string.
*
* @param int|float|string|null $input
* @return float|int|array<int|float>
*/
public function parse($input = null): float|int|array
public function parse(string|int|null $input = null): float|int|array
{
if (is_float($input)) {
trigger_error('Since longitudeone/geo-parser 2.1: Passing a float to LongitudeOne\Geo\String\Parser::parse() is deprecated. Use a string instead.', E_USER_DEPRECATED);
}

if (null !== $input) {
$this->input = (string) $input;
}
Expand Down Expand Up @@ -202,6 +192,7 @@ private function degrees(): float|int
}

// If degrees isn't a float, it must be an integer
/** @var int $degrees */
$degrees = $this->number();

// If a symbol does not follow integer, this value is complete
Expand All @@ -218,7 +209,7 @@ private function degrees(): float|int
}

// Add minutes to value
$degrees += $this->minutes();
$degrees += (float) $this->minutes();

// Return value
return $degrees;
Expand All @@ -227,20 +218,19 @@ private function degrees(): float|int
/**
* Match token and return value.
*/
private function match(int $token): string|float|int
private function match(int $token): string|int
{
// If the next token isn't type specified throw error
if (!$this->lexer->isNextToken($token)) {
throw $this->syntaxError($this->lexer->getLiteral($token));
throw $this->syntaxError((string) $this->lexer->getLiteral($token));
}

// Move lexer to the next token
$this->lexer->moveNext();

/** @var Token<int, string|int|float> $nextToken nextToken cannot be null, because of the above test. */
/** @var Token<int, string|int> $nextToken nextToken cannot be null, because of the above test. */
$nextToken = $this->lexer->token;

// FIXME We currently return string|int|float and this is not good. We should return only string|int.
return $nextToken->value;
}

Expand All @@ -249,7 +239,7 @@ private function match(int $token): string|float|int
*
* @throws RangeException
*/
private function minutes(): float|int
private function minutes(): string|int
{
// If using colon or minutes is an integer parse value
if (Lexer::T_COLON === $this->nextSymbol || $this->lexer->isNextToken(Lexer::T_INTEGER)) {
Expand All @@ -266,22 +256,18 @@ private function minutes(): float|int

// If using colon and one doesn't follow value is done
if (Lexer::T_COLON === $this->nextSymbol && !$this->lexer->isNextToken(Lexer::T_COLON)) {
return $minutes;
return (string) $minutes;
}

// Match minutes symbol
$this->symbol();

// Add seconds to value
$minutes += $this->seconds();

// Return value
return $minutes;
// Add seconds to value, then return the result.
return (string) ((float) $minutes + (float) $this->seconds());
}

// If minutes is a float there will be no seconds
if ($this->lexer->isNextToken(Lexer::T_FLOAT)) {
/** @var float $minutes */
$minutes = $this->match(Lexer::T_FLOAT);

// Throw exception if minutes are greater than 60
Expand All @@ -290,7 +276,7 @@ private function minutes(): float|int
}

// Get fractional minutes
$minutes /= 60;
$minutes = (string) ((float) $minutes / 60);

// Match minutes symbol
$this->symbol();
Expand All @@ -308,7 +294,7 @@ private function minutes(): float|int
*
* @throws UnexpectedValueException
*/
private function number(): int|float
private function number(): int|string
{
// If the next token is a float match, then return it
if ($this->lexer->isNextToken(Lexer::T_FLOAT)) {
Expand All @@ -327,6 +313,8 @@ private function number(): int|float
/**
* Match and return single value or pair.
*
* @return float|int|array<int|float>
*
* @throws UnexpectedValueException
*/
private function point(): float|int|array
Expand Down Expand Up @@ -372,7 +360,7 @@ private function rangeError(string $type, int $high, ?int $low = null): RangeExc
*
* @throws RangeException
*/
private function seconds(): int|float
private function seconds(): int|string
{
// Seconds value can be an integer or float
if ($this->lexer->isNextTokenAny([Lexer::T_INTEGER, Lexer::T_FLOAT])) {
Expand All @@ -384,7 +372,7 @@ private function seconds(): int|float
}

// Get fractional seconds
$seconds /= 3600;
$seconds = (string) ((float) $seconds / 3600);

// Match seconds symbol if requirement not colon
if (Lexer::T_COLON !== $this->nextSymbol) {
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
displayDetailsOnTestsThatTriggerDeprecations="true"
colors="true" bootstrap="./vendor/autoload.php" cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="Tests">
Expand Down
31 changes: 3 additions & 28 deletions quality/php-stan/phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
parameters:
ignoreErrors:
-
message: "#^Result of && is always false\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Lexer.php

-
message: "#^Type float\\|int\\|string in generic type Doctrine\\\\Common\\\\Lexer\\\\AbstractLexer\\<int, float\\|int\\|string\\> in PHPDoc tag @extends is not subtype of template type V of int\\|string of class Doctrine\\\\Common\\\\Lexer\\\\AbstractLexer\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Lexer.php

-
message: "#^Method LongitudeOne\\\\Geo\\\\String\\\\Parser\\:\\:number\\(\\) should return float\\|int but returns float\\|int\\|string\\.$#"
count: 2
path: ../../lib/LongitudeOne/Geo/String/Parser.php

-
message: "#^Method LongitudeOne\\\\Geo\\\\String\\\\Parser\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Parser.php

-
message: "#^Method LongitudeOne\\\\Geo\\\\String\\\\Parser\\:\\:point\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Parser.php

-
message: "#^Parameter \\#1 \\$expected of method LongitudeOne\\\\Geo\\\\String\\\\Parser\\:\\:syntaxError\\(\\) expects string, int\\|string given\\.$#"
message: "#^Method LongitudeOne\\\\Geo\\\\String\\\\Parser\\:\\:point\\(\\) never returns array\\<float\\|int\\> so it can be removed from the return type\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Parser.php

-
message: "#^Type float\\|int\\|string in generic type Doctrine\\\\Common\\\\Lexer\\\\Token\\<int, float\\|int\\|string\\> in PHPDoc tag @var for variable \\$nextToken is not subtype of template type V of int\\|string of class Doctrine\\\\Common\\\\Lexer\\\\Token\\.$#"
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
path: ../../lib/LongitudeOne/Geo/String/Parser.php

-
message: "#^Method LongitudeOne\\\\Geo\\\\String\\\\Tests\\\\LexerTest\\:\\:dataProvider\\(\\) return type with generic class Doctrine\\\\Common\\\\Lexer\\\\Token does not specify its types\\: T, V$#"
count: 1
path: ../../tests/LongitudeOne/Geo/String/Tests/LexerTest.php
path: ../../tests/LongitudeOne/Geo/String/Tests/LexerTest.php
1 change: 1 addition & 0 deletions tests/LongitudeOne/Geo/String/Tests/LexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
class LexerTest extends TestCase
{
/**
* @see https://stackoverflow.com/questions/78159317/how-to-typehint-this-generator
* return Generator<array{string, Token<int, int|string>[]}>.
*
* @return \Generator<array{string, Token[]}>
Expand Down
12 changes: 6 additions & 6 deletions tests/LongitudeOne/Geo/String/Tests/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ public static function dataSourceGood(): \Generator
yield ['40° 26\' N, 79° 58\' W', [40.4333333333333333, -79.96666666666666669]];
yield ['40.4738° N, 79.553° W', [40.4738, -79.553]];
yield ['40.4738° S, 79.553° W', [-40.4738, -79.553]];
yield ['40° 26.222\' N 79° 58.52\' E', [40.43703333333333, 79.97533333333334]];
yield ['40°26.222\'N 79°58.52\'E', [40.43703333333333, 79.97533333333334]];
yield ['40°26.222\' 79°58.52\'', [40.43703333333333, 79.97533333333334]];
yield ['40° 26.222\' N 79° 58.52\' E', [40.43703333333333, 79.97533333333332]];
yield ['40°26.222\'N 79°58.52\'E', [40.43703333333333, 79.97533333333332]];
yield ['40°26.222\' 79°58.52\'', [40.43703333333333, 79.97533333333332]];
yield ['40.222° -79.5852°', [40.222, -79.5852]];
yield ['40.222°, -79.5852°', [40.222, -79.5852]];
yield ['44°58\'53.9"N 93°19\'25.8"W', [44.98163888888888888, -93.3238333333333334]];
yield ['44°58\'53.9"N, 93°19\'25.8"W', [44.98163888888888888, -93.3238333333333334]];
yield ['44°58\'53.9"N 93°19\'25.7"W', [44.98163888888888888, -93.32380555555557]];
yield ['44°58\'53.9"N, 93°19\'25.7"W', [44.98163888888888888, -93.32380555555557]];
yield ['79:56:55W 40:26:46N', [-79.94861111111111, 40.44611111111111]];
yield ['79:56:55 W, 40:26:46 N', [-79.94861111111111, 40.44611111111111]];
yield ['79°56′55″W, 40°26′46″N', [-79.94861111111111, 40.44611111111111]];
Expand All @@ -135,7 +135,7 @@ public function testBadValues(string $input, string $exception, string $message)
}

/**
* @param int|float|float[]|int[] $expected
* @param int|float|array<int|float> $expected
*
* @dataProvider dataSourceGood
*/
Expand Down