Skip to content

Commit

Permalink
Parse structs
Browse files Browse the repository at this point in the history
  • Loading branch information
MidnightDesign committed Aug 15, 2024
1 parent f9c1bcc commit 20e001f
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 7 deletions.
33 changes: 33 additions & 0 deletions src/Parser/Delimiters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Eventjet\Ausdruck\Parser;

enum Delimiters
{
case Parentheses;
case Brackets;
case CurlyBraces;
case AngleBrackets;

public function start(): string
{
return match ($this) {

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => '(', - self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<', };

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function start() : string { return match ($this) { - self::Parentheses => '(', self::Brackets => '[', self::CurlyBraces => '{', self::AngleBrackets => '<',

Check warning on line 16 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => '(', self::Brackets => '[', - self::CurlyBraces => '{', self::AngleBrackets => '<', }; }
self::Parentheses => '(',
self::Brackets => '[',
self::CurlyBraces => '{',
self::AngleBrackets => '<',
};
}

public function end(): string
{
return match ($this) {

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ public function end() : string { return match ($this) { - self::Parentheses => ')', self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>',

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ { return match ($this) { self::Parentheses => ')', - self::Brackets => ']', self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }

Check warning on line 26 in src/Parser/Delimiters.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "MatchArmRemoval": --- Original +++ New @@ @@ return match ($this) { self::Parentheses => ')', self::Brackets => ']', - self::CurlyBraces => '}', self::AngleBrackets => '>', }; } }
self::Parentheses => ')',
self::Brackets => ']',
self::CurlyBraces => '}',
self::AngleBrackets => '>',
};
}
}
2 changes: 2 additions & 0 deletions src/Parser/Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ enum Token: string
case CloseBracket = ']';
case OpenAngle = '<';
case CloseAngle = '>';
case OpenBrace = '{';
case CloseBrace = '}';
case Or = '||';
case And = '&&';
case Pipe = '|';
Expand Down
2 changes: 2 additions & 0 deletions src/Parser/Tokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public static function tokenize(iterable $chars): iterable
',' => Token::Comma,
'[' => Token::OpenBracket,
']' => Token::CloseBracket,
'{' => Token::OpenBrace,
'}' => Token::CloseBrace,
default => null,
};
if ($singleCharToken !== null) {
Expand Down
20 changes: 19 additions & 1 deletion src/Parser/TypeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,36 @@ final class TypeNode implements Stringable
{
/**
* @param list<self> $args
* @param Delimiters | 'kv' $delimiters
*/
public function __construct(
public readonly string $name,
public readonly array $args,
public readonly Span $location,
public readonly Delimiters|string $delimiters = Delimiters::AngleBrackets,
) {
}

/**
* @param list<self> $fields
*/
public static function struct(array $fields, Span $location): self
{
return new self('', $fields, $location, Delimiters::CurlyBraces);
}

public static function keyValue(self $key, self $value): self
{
return new self('', [$key, $value], $key->location->to($value->location), 'kv');
}

public function __toString(): string
{
if ($this->delimiters === 'kv') {
return sprintf('%s: %s', $this->args[0], $this->args[1]);
}
return $this->args === []
? $this->name
: sprintf('%s<%s>', $this->name, implode(', ', $this->args));
: sprintf('%s%s%s%s', $this->name, $this->delimiters->start(), implode(', ', $this->args), $this->delimiters->end());
}
}
47 changes: 47 additions & 0 deletions src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public static function parse(Peekable $tokens): TypeNode|ParsedToken|null
if ($parsedToken === null) {
return null;
}
if ($parsedToken->token === Token::OpenBrace) {
return self::parseStruct($tokens, $parsedToken->location());
}
$name = $parsedToken->token;
if (!is_string($name)) {
return $parsedToken;
Expand Down Expand Up @@ -126,4 +129,48 @@ private static function parseFunction(Peekable $tokens, Span $fnLocation): TypeN
}
return new TypeNode('fn', array_merge($params, [$returnType]), $fnLocation->to($returnType->location));
}

/**
* @param Peekable<ParsedToken> $tokens
*/
private static function parseStruct(Peekable $tokens, Span $location): TypeNode
{
$openBrace = $tokens->peek()?->location();

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];

Check warning on line 138 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "NullSafeMethodCall": --- Original +++ New @@ @@ */ private static function parseStruct(Peekable $tokens, Span $location) : TypeNode { - $openBrace = $tokens->peek()?->location(); + $openBrace = $tokens->peek()->location(); assert($openBrace !== null); $tokens->next(); $fields = [];
assert($openBrace !== null);
$tokens->next();
$fields = [];
while (true) {
$nameToken = $tokens->peek();
if ($nameToken === null) {
break;
}
$name = $nameToken->token;
if (!is_string($name)) {
break;
}
$tokens->next();
self::expect($tokens, Token::Colon);
$type = self::parse($tokens);
if ($type === null) {
break;
}
if (!$type instanceof TypeNode) {

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);

Check warning on line 157 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ if ($type === null) { break; } - if (!$type instanceof TypeNode) { + if (!true) { throw SyntaxError::create(sprintf('Expected type, got %s', Token::print($type->token)), $type->location()); } $fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);
throw SyntaxError::create(
sprintf('Expected type, got %s', Token::print($type->token)),
$type->location(),
);
}
$fields[] = TypeNode::keyValue(new TypeNode($name, [], $nameToken->location()), $type);
$token = $tokens->peek();
if ($token === null) {
break;
}
if ($token->token !== Token::Comma) {
break;

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }

Check warning on line 169 in src/Parser/TypeParser.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ break; } if ($token->token !== Token::Comma) { - break; + continue; } $tokens->next(); }
}
$tokens->next();
}
$closeBrace = self::expect($tokens, Token::CloseBrace)->location();
return TypeNode::struct($fields, $location->to($closeBrace));
}
}
17 changes: 17 additions & 0 deletions src/Parser/Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use function array_key_last;
use function array_pop;
use function assert;
use function count;
use function sprintf;

Expand Down Expand Up @@ -53,6 +54,7 @@ public function resolve(TypeNode $node): Type|TypeError
'Option' => $this->resolveOption($this->exactlyOneTypeArg($node)),
'Some' => $this->resolveSome($this->exactlyOneTypeArg($node)),
'None' => self::noArgs(Type::none(), $node),
'' => $this->resolveStruct($node),
default => $this->resolveAlias($node->name) ?? TypeError::create(
sprintf('Unknown type %s', $node->name),
$node->location,
Expand Down Expand Up @@ -190,4 +192,19 @@ private function resolveFunction(TypeNode $node): Type|TypeError
}
return Type::func($returnType, $argTypes);
}

private function resolveStruct(TypeNode $node): Type|TypeError
{
$fields = [];
foreach ($node->args as $field) {
assert(count($field->args) === 2);
[$nameNode, $typeNode] = $field->args;
$type = $this->resolve($typeNode);
if ($type instanceof TypeError) {

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.2 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3 --prefer-lowest

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.1

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;

Check warning on line 203 in src/Parser/Types.php

View workflow job for this annotation

GitHub Actions / Infection - 8.3

Escaped Mutant for Mutator "InstanceOf_": --- Original +++ New @@ @@ assert(count($field->args) === 2); [$nameNode, $typeNode] = $field->args; $type = $this->resolve($typeNode); - if ($type instanceof TypeError) { + if (false) { return $type; } $fields[$nameNode->name] = $type;
return $type;
}
$fields[$nameNode->name] = $type;
}
return Type::struct($fields);
}
}
2 changes: 1 addition & 1 deletion src/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public function __toString(): string
/** @psalm-suppress ImplicitToStringCast */
$fields[] = $name . ': ' . $fieldType;
}
return '{' . implode(', ', $fields) . '}';
return '{ ' . implode(', ', $fields) . ' }';
}
if ($this->name === 'Func') {
$args = $this->args;
Expand Down
118 changes: 118 additions & 0 deletions tests/unit/Parser/TypeParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

use Eventjet\Ausdruck\Parser\Span;
use Eventjet\Ausdruck\Parser\SyntaxError;
use Eventjet\Ausdruck\Parser\TypeNode;
use Eventjet\Ausdruck\Parser\TypeParser;
use Eventjet\Ausdruck\Parser\Types;
use Eventjet\Ausdruck\Type;
use LogicException;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -55,6 +58,104 @@ public static function syntaxErrorCases(): iterable
];
}

/**
* @return iterable<string, array{string, Type}>
*/
public static function parseStringCases(): iterable
{
yield 'Empty struct' => ['{}', Type::struct([])];
yield 'Empty struct with newline' => ["{\n}", Type::struct([])];
yield 'Empty struct with blank line' => ["{\n\n}", Type::struct([])];
yield 'Struct with a single field' => ['{name: string}', Type::struct(['name' => Type::string()])];
yield 'Struct with a single field and whitespace around it' => [
'{ name: string }',
Type::struct(['name' => Type::string()]),
];
yield 'Struct: whitespace after colon' => [
'{name : string}',
Type::struct(['name' => Type::string()]),
];
yield 'Struct: no whitespace after colon' => [
'{name:string}',
Type::struct(['name' => Type::string()]),
];
yield 'Struct with a single field on a separate line' => [
<<<EOF
{
name: string
}
EOF,
Type::struct(['name' => Type::string()]),
];
yield 'Struct with a single field on a separate line with indent' => [
<<<EOF
{
name: string
}
EOF,
Type::struct(['name' => Type::string()]),
];
yield 'Trailing comma after struct field' => ['{name: string,}', Type::struct(['name' => Type::string()])];
yield 'Trailing comma and whitespace after struct field' => [
'{name: string, }',
Type::struct(['name' => Type::string()]),
];
yield 'Struct field on separate line with trailing comma' => [
<<<EOF
{
name: string,
}
EOF,
Type::struct(['name' => Type::string()]),
];
yield 'Struct with multiple fields and no trailing comma' => [
'{name: string, age: int}',
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
];
yield 'Struct with multiple fields, each on a separate line, with no trailing comma' => [
<<<EOF
{
name: string,
age: int
}
EOF,
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
];
yield 'Struct with multiple fields, each on a separate line, with trailing comma' => [
<<<EOF
{
name: string,
age: int,
}
EOF,
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
];
yield 'Struct with multiple fields, all on one separate line' => [
<<<'EOF'
{
name: string, age: int
}
EOF,
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
];
yield 'Struct with multiple fields, all on one separate line and a trailing comma' => [
<<<'EOF'
{
name: string, age: int,
}
EOF,
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
];
yield 'Struct nested inside another struct' => [
'{name: {first: string}}',
Type::struct(['name' => Type::struct(['first' => Type::string()])]),
];
yield 'Comma after nested struct' => [
'{name: {first: string},}',
Type::struct(['name' => Type::struct(['first' => Type::string()])]),
];
}

/**
* @dataProvider syntaxErrorCases
*/
Expand Down Expand Up @@ -94,4 +195,21 @@ public function testSyntaxErrors(string $type, string $expectedMessage): void
self::assertSame((string)$expectedSpan, (string)$error->location);
}
}

/**
* @dataProvider parseStringCases
*/
public function testParseString(string $typeString, Type $expected): void
{
/**
* @psalm-suppress InternalMethod
* @psalm-suppress InternalClass
*/
$node = TypeParser::parseString($typeString);
assert($node instanceof TypeNode);
$actual = (new Types())->resolve($node);

self::assertInstanceOf(Type::class, $actual);
self::assertTrue($actual->equals($expected));
}
}
10 changes: 5 additions & 5 deletions tests/unit/TypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ public static function failingAssertCases(): iterable
yield 'Struct: not an object' => [
Type::struct(['name' => Type::string()]),
'not an object',
'Expected {name: string}, got string',
'Expected { name: string }, got string',
];
yield 'Missing struct field' => [
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
new class {
public string $name = 'John Doe';
},
'Expected {name: string, age: int}, got {name: string}',
'Expected { name: string, age: int }, got { name: string }',
];
yield 'Struct field has wrong type' => [
Type::struct(['name' => Type::string()]),
new class {
public int $name = 42;
},
'Expected {name: string}, got {name: int}',
'Expected { name: string }, got { name: int }',
];
$name = new class {
public string $first = 'John';
Expand All @@ -60,7 +60,7 @@ public function __construct(public object $name)
{
}
},
'Expected {name: {first: string, last: string}}, got {name: {first: string}}',
'Expected { name: { first: string, last: string } }, got { name: { first: string } }',
];
}

Expand Down Expand Up @@ -136,7 +136,7 @@ public static function toStringCases(): iterable
{
yield 'Struct' => [
Type::struct(['name' => Type::string(), 'age' => Type::int()]),
'{name: string, age: int}',
'{ name: string, age: int }',
];
}

Expand Down

0 comments on commit 20e001f

Please sign in to comment.