Skip to content

Commit

Permalink
Use doctrine lexer instead of HOA
Browse files Browse the repository at this point in the history
  • Loading branch information
goetas committed Jun 13, 2020
1 parent c538b10 commit 8ca11fb
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 570 deletions.
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"jms/metadata": "^2.0",
"doctrine/annotations": "^1.0",
"doctrine/instantiator": "^1.0.3",
"doctrine/lexer": "^1.2",
"hoa/compiler": "^3.17.08.08"
"doctrine/lexer": "^1.1"
},
"suggest": {
"symfony/yaml": "Required if you'd like to use the YAML metadata format.",
Expand Down
96 changes: 0 additions & 96 deletions src/Type/InnerParser.php

This file was deleted.

86 changes: 73 additions & 13 deletions src/Type/Lexer.php
Original file line number Diff line number Diff line change
@@ -1,43 +1,103 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Type;

use Doctrine\Common\Lexer\AbstractLexer;
use Hoa\Exception\Exception;
use JMS\Serializer\Type\Exception\SyntaxError;

class Lexer extends AbstractLexer implements ParserInterface
/**
* @internal
*/
final class Lexer extends AbstractLexer implements ParserInterface
{
public const T_UNKNOWN = 0;
public const T_INTEGER = 1;
public const T_STRING = 2;
public const T_FLOAT = 3;
public const T_ARRAY_START = 4;
public const T_ARRAY_END = 5;
public const T_COMMA = 6;
public const T_TYPE_START = 7;
public const T_TYPE_END = 8;
public const T_IDENTIFIER = 9;
public const T_NULL = 10;

public function parse(string $type): array
{
try {
return $this->getType($type);
} catch (Exception $e) {
} catch (\Throwable $e) {
throw new SyntaxError($e->getMessage(), 0, $e);
}
}

protected function getCatchablePatterns(): array
{
return [
'(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\)*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*', // name
'(\+|\-)?(0|[1-9]\d*)(\.\d+)?', // number
'null',
'""|\'\'', // empty string
'"[^"]+"', // quoted string
"'[^']+'", // apostrophed string
'[a-z][a-z_\\\\0-9]*', // identifier or qualified name
"'(?:[^']|'')*'", // single quoted strings
'(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers
'"(?:[^"]|"")*"', // double quoted strings
'<',
'>',
'\\[',
'\\]',
];
}

protected function getNonCatchablePatterns(): array
{
return [
// TODO: Implement getNonCatchablePatterns() method.
];
return ['\s+'];
}

/**
* {{@inheritDoc}}
*/
protected function getType(&$value)
{
// TODO: Implement getType() method.
$type = self::T_UNKNOWN;

switch (true) {
// Recognize numeric values
case is_numeric($value):
if (false !== strpos($value, '.') || false !== stripos($value, 'e')) {
return self::T_FLOAT;
}

return self::T_INTEGER;

// Recognize quoted strings
case "'" === $value[0]:
$value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));

return self::T_STRING;
case '"' === $value[0]:
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));

return self::T_STRING;
case 'null' === $value:
return self::T_NULL;
// Recognize identifiers, aliased or qualified names
case ctype_alpha($value[0]) || '\\' === $value[0]:
return self::T_IDENTIFIER;
case ',' === $value:
return self::T_COMMA;
case '>' === $value:
return self::T_TYPE_END;
case '<' === $value:
return self::T_TYPE_START;
case ']' === $value:
return self::T_ARRAY_END;
case '[' === $value:
return self::T_ARRAY_START;

// Default
default:
// Do nothing
}

return $type;
}
}
149 changes: 132 additions & 17 deletions src/Type/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,149 @@

namespace JMS\Serializer\Type;

use Hoa\Exception\Exception;
use Hoa\Visitor\Visit;
use JMS\Serializer\Type\Exception\SyntaxError;

@trigger_error(sprintf('Class "%s" is deprecated and will be removed in the next major version, use %s instead.', TypeVisitor::class, Lexer::class), E_USER_DEPRECATED);

/**
* @internal
*/
final class Parser implements ParserInterface
{
/** @var InnerParser */
private $parser;
/**
* @var Lexer
*/
private $lexer;

/** @var Visit */
private $visitor;
/**
* @var bool
*/
private $root = true;

public function __construct()
public function parse(string $string): array
{
$this->parser = new InnerParser();
$this->visitor = new TypeVisitor();
$this->lexer = new Lexer();
$this->lexer->setInput($string);
$this->lexer->moveNext();
return $this->visit();
}

public function parse(string $type): array
/**
* @return mixed
*/
private function visit()
{
try {
$ast = $this->parser->parse($type, 'type');
$this->lexer->moveNext();

if (!$this->lexer->token) {
throw new SyntaxError(
'Syntax error, unexpected end of stream'
);
}

return $this->visitor->visit($ast);
} catch (Exception $e) {
throw new SyntaxError($e->getMessage(), 0, $e);
if (Lexer::T_FLOAT === $this->lexer->token['type']) {
return floatval($this->lexer->token['value']);
} elseif (Lexer::T_INTEGER === $this->lexer->token['type']) {
return intval($this->lexer->token['value']);
} elseif (Lexer::T_NULL === $this->lexer->token['type']) {
return null;
} elseif (Lexer::T_STRING === $this->lexer->token['type']) {
return $this->lexer->token['value'];
} elseif (Lexer::T_IDENTIFIER === $this->lexer->token['type']) {
if ($this->lexer->isNextToken(Lexer::T_TYPE_START)) {
return $this->visitCompoundType();
} elseif ($this->lexer->isNextToken(Lexer::T_ARRAY_START)) {
return $this->visitArrayType();
}
return $this->visitSimpleType();
} elseif (!$this->root && Lexer::T_ARRAY_START === $this->lexer->token['type']) {
return $this->visitArrayType();
}

throw new SyntaxError(sprintf(
'Syntax error, unexpected "%s" (%s)',
$this->lexer->token['value'],
$this->getConstant($this->lexer->token['type'])
));
}

/**
* @return string|mixed[]
*/
private function visitSimpleType()
{
$value = $this->lexer->token['value'];
return ['name' => $value, 'params' => []];
}

private function visitCompoundType(): array
{
$this->root = false;
$name = $this->lexer->token['value'];
$this->match(Lexer::T_TYPE_START);

$params = [];
if (!$this->lexer->isNextToken(Lexer::T_TYPE_END)) {
while (true) {
$params[] = $this->visit();

if ($this->lexer->isNextToken(Lexer::T_TYPE_END)) {
break;
}
$this->match(Lexer::T_COMMA);
}
}
$this->match(Lexer::T_TYPE_END);
return [
'name' => $name,
'params' => $params,
];
}

private function visitArrayType(): array
{
/*
* Here we should call $this->match(Lexer::T_ARRAY_START); to make it clean
* but the token has already been consumed by moveNext() in visit()
*/

$params = [];
if (!$this->lexer->isNextToken(Lexer::T_ARRAY_END)) {
while (true) {
$params[] = $this->visit();
if ($this->lexer->isNextToken(Lexer::T_ARRAY_END)) {
break;
}
$this->match(Lexer::T_COMMA);
}
}
$this->match(Lexer::T_ARRAY_END);
return $params;
}

private function match(int $token): void
{
if (!$this->lexer->lookahead) {
throw new SyntaxError(
sprintf('Syntax error, unexpected end of stream, expected %s', $this->getConstant($token))
);
}

if ($this->lexer->lookahead['type'] === $token) {
$this->lexer->moveNext();

return;
}

throw new SyntaxError(sprintf(
'Syntax error, unexpected "%s" (%s), expected was %s',
$this->lexer->lookahead['value'],
$this->getConstant($this->lexer->lookahead['type']),
$this->getConstant($token)
));
}

private function getConstant(int $value): string
{
$oClass = new \ReflectionClass(Lexer::class);
return array_search($value, $oClass->getConstants());
}
}
Loading

0 comments on commit 8ca11fb

Please sign in to comment.