Skip to content

Commit

Permalink
Merge pull request #1212 from schmittjoh/remove-hoa-compiler
Browse files Browse the repository at this point in the history
Use doctrine/lexer instead of hoa/compiler
  • Loading branch information
goetas authored Jun 13, 2020
2 parents 5b682e9 + 8ca11fb commit c3e1977
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 341 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"jms/metadata": "^2.0",
"doctrine/annotations": "^1.0",
"doctrine/instantiator": "^1.0.3",
"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
94 changes: 0 additions & 94 deletions src/Type/InnerParser.php

This file was deleted.

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

declare(strict_types=1);

namespace JMS\Serializer\Type;

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

/**
* @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 (\Throwable $e) {
throw new SyntaxError($e->getMessage(), 0, $e);
}
}

protected function getCatchablePatterns(): array
{
return [
'[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 ['\s+'];
}

/**
* {{@inheritDoc}}
*/
protected function getType(&$value)
{
$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;
}
}
147 changes: 132 additions & 15 deletions src/Type/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,149 @@

namespace JMS\Serializer\Type;

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

/**
* @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();

return $this->visitor->visit($ast);
} catch (Exception $e) {
throw new SyntaxError($e->getMessage(), 0, $e);
if (!$this->lexer->token) {
throw new SyntaxError(
'Syntax error, unexpected end of stream'
);
}

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 c3e1977

Please sign in to comment.