From dbd526648b5daa26f55028a1c5aaa1b368664848 Mon Sep 17 00:00:00 2001 From: tuqqu Date: Tue, 8 Aug 2023 01:45:57 +0200 Subject: [PATCH] Embedded struct fields, field tag fix, code improvements --- .php-cs-fixer.dist.php | 11 +- README.md | 12 +- bin/go-parser | 2 +- src/Ast/AstNode.php | 4 +- src/Ast/CaseClause.php | 4 +- src/Ast/CaseLabel.php | 4 +- src/Ast/EmbeddedFieldDecl.php | 18 + src/Ast/Expr/Expr.php | 4 +- src/Ast/Expr/Literal.php | 4 +- src/Ast/Expr/Operand.php | 4 +- src/Ast/Expr/PrimaryExpr.php | 4 +- src/Ast/Expr/SliceExpr.php | 4 +- src/Ast/Expr/StructType.php | 3 +- src/Ast/Expr/Type.php | 4 +- src/Ast/Expr/TypeLit.php | 4 +- src/Ast/Expr/TypeName.php | 4 +- src/Ast/Expr/TypeTerm.php | 4 +- src/Ast/Expr/UnderlyingType.php | 1 - src/Ast/FieldDecl.php | 4 +- src/Ast/IdentList.php | 8 +- src/Ast/SpecType.php | 4 +- src/Ast/Stmt/ConstDecl.php | 4 +- src/Ast/Stmt/Decl.php | 4 +- src/Ast/Stmt/IfStmt.php | 6 +- src/Ast/Stmt/ImportDecl.php | 4 +- src/Ast/Stmt/SimpleStmt.php | 4 +- src/Ast/Stmt/Stmt.php | 4 +- src/Ast/Stmt/SwitchStmt.php | 4 +- src/Ast/Stmt/TypeDecl.php | 4 +- src/Ast/Stmt/VarDecl.php | 4 +- src/Exception/InvalidArgument.php | 4 +- src/Exception/ParseModeError.php | 7 +- src/Lexer/LexError.php | 7 +- src/Lexer/Lexeme.php | 8 +- src/Lexer/Lexer.php | 37 +- src/Lexer/Position.php | 4 +- src/NodeDumper.php | 48 +- src/Parser.php | 122 +- src/SyntaxError.php | 7 +- src/ToStderrErrorHandler.php | 6 +- tests/Lexer/LexerTest.php | 4 +- tests/ParserTest.php | 27 +- tests/data/README.md | 7 +- .../ast/{syntax => core}/declarations.json | 0 .../{syntax => core}/generic_function.json | 0 .../ast/{syntax => core}/generic_typedef.json | 0 .../data/ast/{syntax => core}/interface.json | 0 .../literal_number_complex.json | 0 .../literal_number_float.json | 0 .../{syntax => core}/literal_number_int.json | 0 tests/data/ast/{syntax => core}/params.json | 0 tests/data/ast/core/struct.json | 1076 +++++++++++++++++ tests/data/ast/{example => sample}/file1.json | 16 +- tests/data/ast/{example => sample}/file2.json | 4 +- tests/data/ast/{example => sample}/file3.json | 12 +- tests/data/ast/{example => sample}/file4.json | 8 +- tests/data/ast/{example => sample}/file5.json | 8 +- tests/data/ast/{example => sample}/file6.json | 72 +- .../data/src/{syntax => core}/declarations.go | 0 .../src/{syntax => core}/generic_function.go | 0 .../src/{syntax => core}/generic_typedef.go | 0 tests/data/src/{syntax => core}/interface.go | 0 .../literal_number_complex.go | 0 .../{syntax => core}/literal_number_float.go | 0 .../{syntax => core}/literal_number_int.go | 0 tests/data/src/{syntax => core}/params.go | 0 tests/data/src/core/struct.go | 26 + tests/data/src/{example => sample}/file1.go | 0 tests/data/src/{example => sample}/file2.go | 0 tests/data/src/{example => sample}/file3.go | 0 tests/data/src/{example => sample}/file4.go | 0 tests/data/src/{example => sample}/file5.go | 0 tests/data/src/{example => sample}/file6.go | 0 73 files changed, 1441 insertions(+), 218 deletions(-) create mode 100644 src/Ast/EmbeddedFieldDecl.php rename tests/data/ast/{syntax => core}/declarations.json (100%) rename tests/data/ast/{syntax => core}/generic_function.json (100%) rename tests/data/ast/{syntax => core}/generic_typedef.json (100%) rename tests/data/ast/{syntax => core}/interface.json (100%) rename tests/data/ast/{syntax => core}/literal_number_complex.json (100%) rename tests/data/ast/{syntax => core}/literal_number_float.json (100%) rename tests/data/ast/{syntax => core}/literal_number_int.json (100%) rename tests/data/ast/{syntax => core}/params.json (100%) create mode 100644 tests/data/ast/core/struct.json rename tests/data/ast/{example => sample}/file1.json (99%) rename tests/data/ast/{example => sample}/file2.json (99%) rename tests/data/ast/{example => sample}/file3.json (99%) rename tests/data/ast/{example => sample}/file4.json (99%) rename tests/data/ast/{example => sample}/file5.json (99%) rename tests/data/ast/{example => sample}/file6.json (99%) rename tests/data/src/{syntax => core}/declarations.go (100%) rename tests/data/src/{syntax => core}/generic_function.go (100%) rename tests/data/src/{syntax => core}/generic_typedef.go (100%) rename tests/data/src/{syntax => core}/interface.go (100%) rename tests/data/src/{syntax => core}/literal_number_complex.go (100%) rename tests/data/src/{syntax => core}/literal_number_float.go (100%) rename tests/data/src/{syntax => core}/literal_number_int.go (100%) rename tests/data/src/{syntax => core}/params.go (100%) create mode 100644 tests/data/src/core/struct.go rename tests/data/src/{example => sample}/file1.go (100%) rename tests/data/src/{example => sample}/file2.go (100%) rename tests/data/src/{example => sample}/file3.go (100%) rename tests/data/src/{example => sample}/file4.go (100%) rename tests/data/src/{example => sample}/file5.go (100%) rename tests/data/src/{example => sample}/file6.go (100%) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 559f0f9..73215ca 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -4,12 +4,19 @@ const CONFIG = new PhpCsFixer\Config(); const RULES = [ - '@PSR12' => true, + '@PER' => true, 'strict_param' => true, - 'braces' => false, 'ternary_operator_spaces' => false, 'no_break_comment' => false, 'array_syntax' => ['syntax' => 'short'], + 'no_unused_imports' => true, + 'single_line_empty_body' => true, + 'statement_indentation' => false, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], ]; CONFIG->setRules(RULES); diff --git a/README.md b/README.md index 20716ca..7231ca5 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,13 @@ $errs = $parser->getErrors(); ``` Supported language level: Go 1.18 (generics). -Parser is able to recover itself if a parse error occurs, in this case it will continue parsing at the closest node it is able to recognise. +The parser is capable of recovering itself if a parse error occurs. In such cases, it will continue parsing at the closest node it can recognise. -The resulting AST will be as full as possible, and you have to check `getErrors()` to see errors. +The resulting Abstract Syntax Tree (AST) will be as complete as possible. You need to check `getErrors()` to identify any errors. ## Single declaration parsing -Parser can also handle a single declaration only (e.g. a single function), instead of a fully defined Go program: +The parser can also handle a single declaration (e.g., a single function) instead of an entire Go program: ```php use GoParser\{Parser, ParseMode}; @@ -51,11 +51,11 @@ $decl = $parser->parseSingleDecl(); ## Abstract Syntax Tree -Parsing results in an Abstract Syntax Tree result. See `src/Ast`. +Parsing results in an Abstract Syntax Tree. Refer to `src/Ast` for details. -For the most part the AST nodes structure follows closely the official Golang [specification][1]. +For the most part, the structure of AST nodes closely follows the official Golang [specification][1]. -Some Nodes may also have a bit different name (e.g. `ExpressionList` vs `ExprList`), but mostly the names are either the same or easily recognisable. +Some nodes may have slightly different names (e.g., `ExpressionList` instead of `ExprList`), but in most cases, the names are the same or easily recognisable. ## CLI Package comes with a CLI command: diff --git a/bin/go-parser b/bin/go-parser index 9e382a4..d35ed9a 100755 --- a/bin/go-parser +++ b/bin/go-parser @@ -83,7 +83,7 @@ function parse_argv(array $argv): array $mode = ParseMode::SingleDecl; break; case '--node-dumper': - // default for now + // default break; case '-d': case '--var-dump': diff --git a/src/Ast/AstNode.php b/src/Ast/AstNode.php index d32154d..5c902d7 100644 --- a/src/Ast/AstNode.php +++ b/src/Ast/AstNode.php @@ -4,6 +4,4 @@ namespace GoParser\Ast; -interface AstNode -{ -} +interface AstNode {} diff --git a/src/Ast/CaseClause.php b/src/Ast/CaseClause.php index 13b60b8..6725c65 100644 --- a/src/Ast/CaseClause.php +++ b/src/Ast/CaseClause.php @@ -4,6 +4,4 @@ namespace GoParser\Ast; -interface CaseClause extends AstNode -{ -} +interface CaseClause extends AstNode {} diff --git a/src/Ast/CaseLabel.php b/src/Ast/CaseLabel.php index 3ae2b0c..2da7450 100644 --- a/src/Ast/CaseLabel.php +++ b/src/Ast/CaseLabel.php @@ -4,6 +4,4 @@ namespace GoParser\Ast; -interface CaseLabel extends AstNode -{ -} +interface CaseLabel extends AstNode {} diff --git a/src/Ast/EmbeddedFieldDecl.php b/src/Ast/EmbeddedFieldDecl.php new file mode 100644 index 0000000..0616dbf --- /dev/null +++ b/src/Ast/EmbeddedFieldDecl.php @@ -0,0 +1,18 @@ + $fieldDecls */ public function __construct( public readonly Keyword $keyword, diff --git a/src/Ast/Expr/Type.php b/src/Ast/Expr/Type.php index e0c96e0..eaadfda 100644 --- a/src/Ast/Expr/Type.php +++ b/src/Ast/Expr/Type.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Expr; -interface Type extends TypeTerm, Expr -{ -} +interface Type extends TypeTerm, Expr {} diff --git a/src/Ast/Expr/TypeLit.php b/src/Ast/Expr/TypeLit.php index 39c1344..bd2c5d4 100644 --- a/src/Ast/Expr/TypeLit.php +++ b/src/Ast/Expr/TypeLit.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Expr; -interface TypeLit extends Type -{ -} +interface TypeLit extends Type {} diff --git a/src/Ast/Expr/TypeName.php b/src/Ast/Expr/TypeName.php index c3d73a3..619129f 100644 --- a/src/Ast/Expr/TypeName.php +++ b/src/Ast/Expr/TypeName.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Expr; -interface TypeName extends Type -{ -} +interface TypeName extends Type {} diff --git a/src/Ast/Expr/TypeTerm.php b/src/Ast/Expr/TypeTerm.php index b079a67..9582171 100644 --- a/src/Ast/Expr/TypeTerm.php +++ b/src/Ast/Expr/TypeTerm.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Expr; -interface TypeTerm -{ -} +interface TypeTerm {} diff --git a/src/Ast/Expr/UnderlyingType.php b/src/Ast/Expr/UnderlyingType.php index 6bd0b52..42c5d08 100644 --- a/src/Ast/Expr/UnderlyingType.php +++ b/src/Ast/Expr/UnderlyingType.php @@ -5,7 +5,6 @@ namespace GoParser\Ast\Expr; use GoParser\Ast\Operator; -use GoParser\Lexer\Position; final class UnderlyingType implements TypeTerm, Expr { diff --git a/src/Ast/FieldDecl.php b/src/Ast/FieldDecl.php index 7cae3a8..7333113 100644 --- a/src/Ast/FieldDecl.php +++ b/src/Ast/FieldDecl.php @@ -11,8 +11,8 @@ final class FieldDecl implements AstNode { public function __construct( - public readonly ?IdentList $identList, - public readonly ?Type $type, + public readonly IdentList $identList, + public readonly Type $type, public readonly StringLit|RawStringLit|null $tag, ) {} } diff --git a/src/Ast/IdentList.php b/src/Ast/IdentList.php index 6b2229f..65880c2 100644 --- a/src/Ast/IdentList.php +++ b/src/Ast/IdentList.php @@ -4,12 +4,14 @@ namespace GoParser\Ast; +use GoParser\Ast\Expr\Type; use GoParser\Exception\InvalidArgument; use GoParser\Ast\Expr\Expr; use GoParser\Ast\Expr\Ident; -use GoParser\Ast\Expr\Type; use GoParser\Ast\Expr\SingleTypeName; +use function array_map; + final class IdentList implements AstNode { /** @@ -21,7 +23,7 @@ public function __construct( public static function fromExprList(ExprList $list): self { - return new self(\array_map( + return new self(array_map( static fn (Expr $expr): Ident => $expr instanceof Ident ? $expr : throw new InvalidArgument('Cannot create IdentList from an arbitrary expression list'), @@ -31,7 +33,7 @@ public static function fromExprList(ExprList $list): self public static function fromTypeList(TypeList $list): self { - return new self(\array_map( + return new self(array_map( static fn (Type $type): Ident => $type instanceof SingleTypeName ? $type->name : throw new InvalidArgument('Cannot create IdentList from an arbitrary type list'), diff --git a/src/Ast/SpecType.php b/src/Ast/SpecType.php index 67eb8ee..0997e96 100644 --- a/src/Ast/SpecType.php +++ b/src/Ast/SpecType.php @@ -4,7 +4,9 @@ namespace GoParser\Ast; -enum SpecType implements \JsonSerializable +use JsonSerializable; + +enum SpecType implements JsonSerializable { case Import; case Var; diff --git a/src/Ast/Stmt/ConstDecl.php b/src/Ast/Stmt/ConstDecl.php index 13c0f16..982bfb0 100644 --- a/src/Ast/Stmt/ConstDecl.php +++ b/src/Ast/Stmt/ConstDecl.php @@ -10,6 +10,8 @@ use GoParser\Ast\Keyword; use GoParser\Ast\SpecType; +use function sprintf; + final class ConstDecl implements Decl { public function __construct( @@ -17,7 +19,7 @@ public function __construct( public readonly GroupSpec|ConstSpec $spec, ) { if ($spec->type() !== SpecType::Const) { - throw new InvalidArgument(\sprintf('Cannot create a ConstDecl with Spec of type %s', $spec->type()->name)); + throw new InvalidArgument(sprintf('Cannot create a ConstDecl with Spec of type %s', $spec->type()->name)); } } } diff --git a/src/Ast/Stmt/Decl.php b/src/Ast/Stmt/Decl.php index c2ad202..7d51ffd 100644 --- a/src/Ast/Stmt/Decl.php +++ b/src/Ast/Stmt/Decl.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Stmt; -interface Decl extends Stmt -{ -} +interface Decl extends Stmt {} diff --git a/src/Ast/Stmt/IfStmt.php b/src/Ast/Stmt/IfStmt.php index ef7444e..a9f66e9 100644 --- a/src/Ast/Stmt/IfStmt.php +++ b/src/Ast/Stmt/IfStmt.php @@ -14,14 +14,14 @@ final class IfStmt implements Stmt { public function __construct( - public readonly Keyword $if, + public readonly Keyword $keyword, public readonly ?SimpleStmt $init, public readonly Expr $condition, public readonly BlockStmt $ifBody, - public readonly ?Keyword $else, + public readonly ?Keyword $elseKeyword, public readonly BlockStmt|self|null $elseBody, ) { - if ((bool) $else !== (bool) $elseBody) { + if ((bool) $elseKeyword !== (bool) $elseBody) { throw new InvalidArgument('Both "else" keyword and the body must be present or neither.'); } } diff --git a/src/Ast/Stmt/ImportDecl.php b/src/Ast/Stmt/ImportDecl.php index a5907fd..711b26a 100644 --- a/src/Ast/Stmt/ImportDecl.php +++ b/src/Ast/Stmt/ImportDecl.php @@ -10,6 +10,8 @@ use GoParser\Ast\Keyword; use GoParser\Ast\SpecType; +use function sprintf; + final class ImportDecl implements Decl { public function __construct( @@ -17,7 +19,7 @@ public function __construct( public readonly GroupSpec|ImportSpec $spec, ) { if ($spec->type() !== SpecType::Import) { - throw new InvalidArgument(\sprintf('Cannot create a ImportDecl with Spec of type %s', $spec->type()->name)); + throw new InvalidArgument(sprintf('Cannot create a ImportDecl with Spec of type %s', $spec->type()->name)); } } } diff --git a/src/Ast/Stmt/SimpleStmt.php b/src/Ast/Stmt/SimpleStmt.php index 311292b..a528467 100644 --- a/src/Ast/Stmt/SimpleStmt.php +++ b/src/Ast/Stmt/SimpleStmt.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Stmt; -interface SimpleStmt extends Stmt -{ -} +interface SimpleStmt extends Stmt {} diff --git a/src/Ast/Stmt/Stmt.php b/src/Ast/Stmt/Stmt.php index 25ac734..f7c2a79 100644 --- a/src/Ast/Stmt/Stmt.php +++ b/src/Ast/Stmt/Stmt.php @@ -6,6 +6,4 @@ use GoParser\Ast\AstNode; -interface Stmt extends AstNode -{ -} +interface Stmt extends AstNode {} diff --git a/src/Ast/Stmt/SwitchStmt.php b/src/Ast/Stmt/SwitchStmt.php index 5cf0fac..16c3d37 100644 --- a/src/Ast/Stmt/SwitchStmt.php +++ b/src/Ast/Stmt/SwitchStmt.php @@ -4,6 +4,4 @@ namespace GoParser\Ast\Stmt; -interface SwitchStmt extends Stmt -{ -} +interface SwitchStmt extends Stmt {} diff --git a/src/Ast/Stmt/TypeDecl.php b/src/Ast/Stmt/TypeDecl.php index 0143e35..9fbb255 100644 --- a/src/Ast/Stmt/TypeDecl.php +++ b/src/Ast/Stmt/TypeDecl.php @@ -10,6 +10,8 @@ use GoParser\Ast\SpecType; use GoParser\Ast\TypeSpec; +use function sprintf; + final class TypeDecl implements Decl { public function __construct( @@ -17,7 +19,7 @@ public function __construct( public readonly GroupSpec|TypeSpec $spec, ) { if ($spec->type() !== SpecType::Type) { - throw new InvalidArgument(\sprintf('Cannot create a TypeDecl with Spec of type %s', $spec->type()->name)); + throw new InvalidArgument(sprintf('Cannot create a TypeDecl with Spec of type %s', $spec->type()->name)); } } } diff --git a/src/Ast/Stmt/VarDecl.php b/src/Ast/Stmt/VarDecl.php index 911e827..bc2d630 100644 --- a/src/Ast/Stmt/VarDecl.php +++ b/src/Ast/Stmt/VarDecl.php @@ -10,6 +10,8 @@ use GoParser\Ast\SpecType; use GoParser\Ast\VarSpec; +use function sprintf; + final class VarDecl implements Decl { public function __construct( @@ -17,7 +19,7 @@ public function __construct( public readonly GroupSpec|VarSpec $spec, ) { if ($spec->type() !== SpecType::Var) { - throw new InvalidArgument(\sprintf('Cannot create a VarDecl with Spec of type %s', $spec->type()->name)); + throw new InvalidArgument(sprintf('Cannot create a VarDecl with Spec of type %s', $spec->type()->name)); } } } diff --git a/src/Exception/InvalidArgument.php b/src/Exception/InvalidArgument.php index fd86b93..b14071b 100644 --- a/src/Exception/InvalidArgument.php +++ b/src/Exception/InvalidArgument.php @@ -4,7 +4,9 @@ namespace GoParser\Exception; -final class InvalidArgument extends \InvalidArgumentException +use InvalidArgumentException; + +final class InvalidArgument extends InvalidArgumentException { public function __construct(string $message) { diff --git a/src/Exception/ParseModeError.php b/src/Exception/ParseModeError.php index 77b08c8..9a019a8 100644 --- a/src/Exception/ParseModeError.php +++ b/src/Exception/ParseModeError.php @@ -5,12 +5,15 @@ namespace GoParser\Exception; use GoParser\ParseMode; +use BadMethodCallException; -final class ParseModeError extends \BadMethodCallException +use function sprintf; + +final class ParseModeError extends BadMethodCallException { public function __construct(ParseMode $expected, ParseMode $actual) { - parent::__construct(\sprintf( + parent::__construct(sprintf( 'Expected Parser to be initialised with Parse Mode "%s" , but got "%s"', $expected->name, $actual->name diff --git a/src/Lexer/LexError.php b/src/Lexer/LexError.php index 22acf66..d64f554 100644 --- a/src/Lexer/LexError.php +++ b/src/Lexer/LexError.php @@ -5,8 +5,11 @@ namespace GoParser\Lexer; use GoParser\Error; +use Exception; -final class LexError extends \Exception implements Error +use function sprintf; + +final class LexError extends Exception implements Error { public readonly Position $pos; @@ -19,6 +22,6 @@ public function __construct(string $message, Position $pos) public function __toString(): string { - return \sprintf('%s: %s', $this->pos, $this->message); + return sprintf('%s: %s', $this->pos, $this->message); } } diff --git a/src/Lexer/Lexeme.php b/src/Lexer/Lexeme.php index 9e87c02..8dfbaab 100644 --- a/src/Lexer/Lexeme.php +++ b/src/Lexer/Lexeme.php @@ -4,6 +4,8 @@ namespace GoParser\Lexer; +use function sprintf; + final class Lexeme { public function __construct( @@ -14,11 +16,11 @@ public function __construct( public function __toString(): string { - $str = \sprintf('%s %s', $this->pos, $this->token->name); + $str = sprintf('%s %s', $this->pos, $this->token->name); if ($this->token->isLiteral()) { - $str .= \sprintf(' "%s"', $this->literal ?? ''); + $str .= sprintf(' "%s"', $this->literal ?? ''); } elseif ($this->token->isOperator()) { - $str .= \sprintf(' %s', $this->token->value); + $str .= sprintf(' %s', $this->token->value); } return $str; diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index 9ad986f..fc8ff98 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -4,6 +4,19 @@ namespace GoParser\Lexer; +use OutOfBoundsException; + +use function mb_str_split; +use function strlen; +use function mb_ord; +use function count; +use function ctype_alpha; +use function ctype_alnum; +use function ctype_digit; +use function ctype_xdigit; +use function sprintf; +use function in_array; + final class Lexer { /** @var string[] */ @@ -22,8 +35,8 @@ final class Lexer public function __construct(string $src, ?string $filename = null) { - $this->src = \mb_str_split($src); - $this->len = \strlen($src); + $this->src = mb_str_split($src); + $this->len = strlen($src); $this->filename = $filename; } @@ -253,9 +266,9 @@ public function lex(): void self::isAlphabetic($char) => $this->identifier(), self::isNumeric($char) => $this->number(), default => $this->error( - \sprintf( + sprintf( 'invalid character U+%X \'%s\' in identifier', - \mb_ord($char = $this->read()), + mb_ord($char = $this->read()), $char, ), ), @@ -356,7 +369,6 @@ private function quotedLiteral(string $quote): string case "\n": case null: $this->error('string not terminated'); - // no break because addError returns never default: $literal .= $this->read(); } @@ -543,7 +555,7 @@ private function identifier(): void private function isAutoSemicolon(int $step = 1): bool { - $len = \count($this->lexemes); + $len = count($this->lexemes); if ($len < $step) { return false; @@ -556,6 +568,7 @@ private function isAutoSemicolon(int $step = 1): bool Token::Imag, Token::Rune, Token::String, + Token::RawString, Token::Break, Token::Continue, Token::Fallthrough, @@ -578,17 +591,17 @@ private function pos(): Position private static function isAlphabetic(string $char): bool { - return \ctype_alpha($char) || $char === '_'; + return ctype_alpha($char) || $char === '_'; } private static function isAlphanumeric(string $char): bool { - return \ctype_alnum($char) || $char === '_'; + return ctype_alnum($char) || $char === '_'; } private static function isNumeric(string $char): bool { - return \ctype_digit($char); + return ctype_digit($char); } private static function isOctal(string $char): bool @@ -600,7 +613,7 @@ private static function isOctal(string $char): bool private static function isHex(string $char): bool { - return \ctype_xdigit($char); + return ctype_xdigit($char); } private static function isBinary(string $char): bool @@ -616,7 +629,7 @@ private function read(): string $char = $this->advance(); if ($char === null) { - throw new \OutOfBoundsException(\sprintf('No char at index %d for src of length %d', $this->cur, $this->len)); + throw new OutOfBoundsException(sprintf('No char at index %d for src of length %d', $this->cur, $this->len)); } if ($char === "\n") { @@ -631,7 +644,7 @@ private function read(): string private function match(string ...$char): bool { - return \in_array($this->peek(), $char, true); + return in_array($this->peek(), $char, true); } private function peek(): ?string diff --git a/src/Lexer/Position.php b/src/Lexer/Position.php index a07f72b..52cc9ff 100644 --- a/src/Lexer/Position.php +++ b/src/Lexer/Position.php @@ -4,6 +4,8 @@ namespace GoParser\Lexer; +use function sprintf; + final class Position { public function __construct( @@ -14,7 +16,7 @@ public function __construct( public function __toString(): string { - return \sprintf( + return sprintf( '%s:%d:%d', $this->filename === null ? '' : $this->filename . ':', $this->line, diff --git a/src/NodeDumper.php b/src/NodeDumper.php index ff733f7..13422ec 100644 --- a/src/NodeDumper.php +++ b/src/NodeDumper.php @@ -14,12 +14,22 @@ use GoParser\Ast\Stmt\Stmt; use GoParser\Lexer\Position; use PhpParser\Node\Expr; +use UnitEnum; +use ReflectionClass; +use ReflectionProperty; + +use function sprintf; +use function is_array; +use function str_repeat; +use function fwrite; + +use const STDOUT; final class NodeDumper { public function __construct( private readonly int $startingIndent = 0, - private readonly mixed $stream = \STDOUT, + private readonly mixed $stream = STDOUT, private readonly string $indentWith = '__', private readonly bool $showFilename = false, private readonly bool $showPosition = true, @@ -29,7 +39,7 @@ public function dump(AstNode $node): void { switch (true) { case $node instanceof File: - $this->printlnIndent(\sprintf('[File] %s', (string) $node->filename), 0); + $this->printlnIndent(sprintf('[File] %s', (string) $node->filename), 0); $this->printNode($node->package, 1); $this->printlnIndent('[Imports]: ', 1); @@ -43,7 +53,7 @@ public function dump(AstNode $node): void } break; case $node instanceof Decl: - $this->printlnIndent(\sprintf('[Decl] %s', self::name($node)), 0); + $this->printlnIndent(sprintf('[Decl] %s', self::name($node)), 0); $this->printNode($node, 0); break; default: @@ -56,7 +66,7 @@ private function printNode(AstNode $node, int $indent): void $type = self::type($node); $pos = $this->getPos($node); - $this->printlnIndent(\sprintf('%s %s', $type, $pos), $indent); + $this->printlnIndent(sprintf('%s %s', $type, $pos), $indent); foreach (self::props($node) as $prop) { $this->printProp($node, $prop, $indent); @@ -65,7 +75,7 @@ private function printNode(AstNode $node, int $indent): void private static function name(AstNode $node): string { - return (new \ReflectionClass($node))->getShortName(); + return (new ReflectionClass($node))->getShortName(); } private static function type(AstNode $node): string @@ -83,10 +93,10 @@ private static function type(AstNode $node): string return $type === null ? self::name($node) - : \sprintf("[%s] %s", $type, self::name($node)); + : sprintf("[%s] %s", $type, self::name($node)); } - private function printProp(AstNode $node, \ReflectionProperty $property, int $indent): void + private function printProp(AstNode $node, ReflectionProperty $property, int $indent): void { $value = $property->getValue($node); @@ -94,30 +104,30 @@ private function printProp(AstNode $node, \ReflectionProperty $property, int $in return; } - if ($value instanceof \UnitEnum) { - $this->printlnIndent(\sprintf("%s: %s", $property->name, $value->name), $indent); + if ($value instanceof UnitEnum) { + $this->printlnIndent(sprintf("%s: %s", $property->name, $value->name), $indent); return; } if ($value instanceof AstNode) { - $this->printlnIndent(\sprintf("%s: ", $property->name), $indent); + $this->printlnIndent(sprintf("%s: ", $property->name), $indent); $this->printNode($value, $indent + 1); return; } - if (\is_array($value)) { + if (is_array($value)) { $this->printArray($value, $property, $indent); return; } - $this->printlnIndent(\sprintf("%s: %s", $property->name, $value ?? 'null'), $indent); + $this->printlnIndent(sprintf("%s: %s", $property->name, $value ?? 'null'), $indent); } - private function printArray(array $value, \ReflectionProperty $property, int $indent): void + private function printArray(array $value, ReflectionProperty $property, int $indent): void { - $this->printlnIndent(\sprintf("%s: ", $property->name), $indent); + $this->printlnIndent(sprintf("%s: ", $property->name), $indent); foreach ($value as $item) { - if (\is_array($item)) { + if (is_array($item)) { $this->printArray($item, $property, $indent); } else { $this->printNode($item, $indent); @@ -126,11 +136,11 @@ private function printArray(array $value, \ReflectionProperty $property, int $in } /** - * @return iterable<\ReflectionProperty> + * @return iterable */ private static function props(AstNode $node): iterable { - $ref = new \ReflectionClass($node); + $ref = new ReflectionClass($node); foreach ($ref->getProperties() as $property) { yield $property; } @@ -163,7 +173,7 @@ private function getPos(AstNode $node): string private function indentStr(string $str, int $indent): string { - return \str_repeat($this->indentWith, $indent) . $str; + return str_repeat($this->indentWith, $indent) . $str; } private function printlnIndent(string $txt, int $indent): void @@ -178,6 +188,6 @@ private function println(string $txt): void private function print(string $txt): void { - \fwrite($this->stream, $txt); + fwrite($this->stream, $txt); } } diff --git a/src/Parser.php b/src/Parser.php index 69728af..ca39d91 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -12,6 +12,7 @@ use GoParser\Ast\ConstSpec; use GoParser\Ast\DefaultCase; use GoParser\Ast\ElementList; +use GoParser\Ast\EmbeddedFieldDecl; use GoParser\Ast\Expr\ArrayType; use GoParser\Ast\Expr\BinaryExpr; use GoParser\Ast\Expr\CallExpr; @@ -117,6 +118,14 @@ use GoParser\Lexer\Lexeme; use GoParser\Lexer\Lexer; use GoParser\Lexer\Token; +use OutOfBoundsException; + +use function array_pop; +use function sprintf; +use function array_map; +use function array_key_last; +use function count; +use function in_array; final class Parser { @@ -126,7 +135,6 @@ final class Parser private array $errors = []; private ?File $ast = null; private ?Decl $decl = null; - private int $fallbackPos = 0; private int $pos = 0; private bool $cfHeader = false; private bool $finished = false; @@ -269,7 +277,7 @@ private function error(string $msg): never private function popLastError(): void { - \array_pop($this->errors); + array_pop($this->errors); } private function parsePackageClause(): PackageClause @@ -417,7 +425,7 @@ private function parseStmt(): Stmt Token::Continue => $this->parseContinueStmt(), Token::Break => $this->parseBreakStmt(), - default => $this->error(\sprintf('unrecognised statement "%s"', $this->peek()->token->name)), + default => $this->error(sprintf('unrecognised statement "%s"', $this->peek()->token->name)), }; } @@ -447,18 +455,15 @@ private function parseTypeParams(): TypeParams public function parseTypeParamsOrType(): TypeParams|Type { - $this->fallbackPos = $this->pos; + $fallbackPos = $this->pos; try { return $this->parseTypeParams(); } catch (SyntaxError) { $this->popLastError(); - $this->pos = $this->fallbackPos; - $this->fallbackPos = 0; + $this->pos = $fallbackPos; return $this->parseType(); - } finally { - $this->fallbackPos = 0; } } @@ -531,7 +536,7 @@ private function parseCaseClauses(string $clauseFqcn): array Token::Case => $labelParser(), Token::Default => $this->parseDefaultCase(), default => $this->error( - \sprintf( + sprintf( 'unexpected literal %s, expecting case or default or }', $this->peek()->literal ?? '', ), @@ -1200,7 +1205,7 @@ private function parsePrimaryExpr(): PrimaryExpr Token::LeftParen => $this->parseTypeAssertionExpr($expr), Token::Ident => $this->parseSelectorExpr($expr), // no break - default => $this->error(\sprintf('unexpected token "%s"', $this->peek()->token->name)), + default => $this->error(sprintf('unexpected token "%s"', $this->peek()->token->name)), }; break; default: @@ -1359,7 +1364,7 @@ private function parseIndexOrSliceExpr(Expr $expr): IndexExpr|SliceExpr $maxColons = 2; $i = 0; - while (\count($colons) < $maxColons && $this->match(Token::Colon)) { + while (count($colons) < $maxColons && $this->match(Token::Colon)) { $colons[] = $this->parsePunctuation(Token::Colon); if (!$this->matchAny(Token::Colon, Token::RightBracket)) { @@ -1369,7 +1374,7 @@ private function parseIndexOrSliceExpr(Expr $expr): IndexExpr|SliceExpr $rBrack = $this->parsePunctuation(Token::RightBracket); - return match (\count($colons)) { + return match (count($colons)) { 0 => new IndexExpr( $expr, $lBrack, @@ -1410,7 +1415,7 @@ private function parseOperandOrType(): Operand|Type Token::LeftParen => $this->parseGroupExpr(), Token::Func => $this->parseFuncLit(), default => $this->tryParseType() ?? - $this->error(\sprintf('unknown token "%s" in operand expression', $this->peek()->token->name)), + $this->error(sprintf('unknown token "%s" in operand expression', $this->peek()->token->name)), }; } @@ -1482,7 +1487,7 @@ private function parseParamDecls(bool $variadic, array &$params): void // T1, ...T2 if ($ellipsis !== null) { - $last = \count($typeList->types) - 1; + $last = count($typeList->types) - 1; foreach ($typeList->types as $i => $type) { $params[] = new ParamDecl( @@ -1532,7 +1537,7 @@ private function parseParamDecls(bool $variadic, array &$params): void /** @var ParamDecl[] $params */ $params = [ ...$params, - ...\array_map( + ...array_map( static fn (Type $type): ParamDecl => new ParamDecl(null, null, $type), $typeList->types, ), @@ -1548,7 +1553,7 @@ private function checkNonVariadicLastParam(array $params): void return; } - $last = $params[\array_key_last($params)] ?? null; + $last = $params[array_key_last($params)] ?? null; if ($last !== null && $last->ellipsis !== null) { $this->error('can only use ... with final parameter in list'); @@ -1721,14 +1726,59 @@ private function parseStructType(): StructType $fields = []; while (!$this->match(Token::RightBrace)) { - $identList = $this->parseIdentList(); - $type = $this->tryParseType(); + if ( + $this->checkAheadTill( + Token::Semicolon, + Token::RightBracket, + ) + && !$this->checkAheadAfter( + Token::RightBracket, + Token::Semicolon, + Token::String, + Token::RawString, + ) + ) { + $identList = $this->parseIdentList(); + $type = $this->parseType(); + $tag = $this->tryParseTag(); + + $fields[] = new FieldDecl($identList, $type, $tag); + $this->parseSemicolon(); + + continue; + } + + $typeOrIdent = $this->parseType(); + $tag = $this->tryParseTag(); + + if ($this->match(Token::Semicolon)) { + if ( + !$typeOrIdent instanceof TypeName + && ($typeOrIdent instanceof PointerType && (!$typeOrIdent->type instanceof TypeName)) + ) { + $this->error('unexpected type, expecting field name or embedded type'); + } + + /** @var TypeName|PointerType $typeOrIdent */ + $fields[] = new EmbeddedFieldDecl($typeOrIdent, $tag); + $this->parseSemicolon(); + + continue; + } + + /** @var SingleTypeName $typeOrIdent */ + $idents = [$typeOrIdent->name]; + + if ($this->match(Token::Comma)) { + $this->consume(Token::Comma); + $identList = $this->parseIdentList(); + $idents = [...$idents, ...$identList->idents]; + } + + $identList = new IdentList($idents); + $type = $this->parseType(); + $tag = $this->tryParseTag(); - $tag = match ($this->peek()->token) { - Token::String => $this->parseStringLit(), - Token::RawString => $this->parseRawStringLit(), - default => null, - }; $fields[] = new FieldDecl($identList, $type, $tag); $this->parseSemicolon(); } @@ -1824,7 +1874,7 @@ private function parseIdent(): Ident private function tryParseIdent(): ?Ident { return $this->match(Token::Ident) - ? Ident::fromLexeme($this->consume(Token::Ident)) + ? $this->parseIdent() : null; } @@ -1900,6 +1950,15 @@ private function parseTypeArgs(): array return $args; } + private function tryParseTag(): RawStringLit|StringLit|null + { + return match ($this->peek()->token) { + Token::String => $this->parseStringLit(), + Token::RawString => $this->parseRawStringLit(), + default => null, + }; + } + private function parseStringLit(): StringLit { return StringLit::fromLexeme($this->consume(Token::String)); @@ -1970,9 +2029,12 @@ private function consume(Token $token): Lexeme return $this->advance(); } - $this->error(\sprintf( - 'unexpected token \'%s\', expecting \'%s\'', + $this->error(sprintf( + 'unexpected token \'%s\'%s, expecting \'%s\'', $this->peek()->token->name, + $this->peek()->literal !== null + ? sprintf(' (%s)', (string) $this->peek()->literal) + : '', $token->name, )); } @@ -2009,7 +2071,7 @@ private function match(Token $token): bool private function matchAny(Token ...$tokens): bool { - return \in_array($this->peek()->token, $tokens, true); + return in_array($this->peek()->token, $tokens, true); } private function isAtEnd(): bool @@ -2026,7 +2088,7 @@ private function checkAheadTill(Token $till, Token ...$needles): bool { $i = 0; $j = 0; - $len = \count($needles); + $len = count($needles); $needle = $needles[$j]; while (($peeked = $this->peekBy($i++)->token) !== $till) { @@ -2047,7 +2109,7 @@ private function checkAheadAfter(Token $after, Token ...$needles): bool $i = 0; while ($this->peekBy($i++)->token !== $after); - return \in_array($this->peekBy($i)->token, $needles, true); + return in_array($this->peekBy($i)->token, $needles, true); } private function peekBy(int $by): Lexeme @@ -2066,7 +2128,7 @@ private function peekBy(int $by): Lexeme ++$this->pos; } - throw new \OutOfBoundsException('Cannot peek that far'); + throw new OutOfBoundsException('Cannot peek that far'); } private function recover(ParseMode $mode): void diff --git a/src/SyntaxError.php b/src/SyntaxError.php index 8a6f3c0..34a3e9c 100644 --- a/src/SyntaxError.php +++ b/src/SyntaxError.php @@ -5,8 +5,11 @@ namespace GoParser; use GoParser\Lexer\Position; +use Exception; -class SyntaxError extends \Exception implements Error +use function sprintf; + +class SyntaxError extends Exception implements Error { public readonly Position $pos; @@ -19,6 +22,6 @@ public function __construct(string $message, Position $pos) public function __toString(): string { - return \sprintf('%s syntax error: %s', $this->pos, $this->message); + return sprintf('%s syntax error: %s', $this->pos, $this->message); } } diff --git a/src/ToStderrErrorHandler.php b/src/ToStderrErrorHandler.php index 5df54d7..358259c 100644 --- a/src/ToStderrErrorHandler.php +++ b/src/ToStderrErrorHandler.php @@ -4,10 +4,14 @@ namespace GoParser; +use function fwrite; + +use const STDERR; + final class ToStderrErrorHandler implements ErrorHandler { public function onError(Error $err): void { - \fwrite(\STDERR, $err . "\n"); + fwrite(STDERR, $err . "\n"); } } diff --git a/tests/Lexer/LexerTest.php b/tests/Lexer/LexerTest.php index 53d9d8f..957f093 100644 --- a/tests/Lexer/LexerTest.php +++ b/tests/Lexer/LexerTest.php @@ -7,6 +7,8 @@ use GoParser\Lexer\Lexer; use PHPUnit\Framework\TestCase; +use function explode; + final class LexerTest extends TestCase { /** @@ -18,7 +20,7 @@ public function testLex(string $go, string $tokens): void $lexer->lex(); $lexemes = $lexer->getLexemes(); - $tokens = \explode("\n", $tokens); + $tokens = explode("\n", $tokens); foreach ($lexemes as $i => $lexeme) { self::assertEquals((string) $lexeme, $tokens[$i]); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 8ce0ff3..f963ae7 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -14,11 +14,20 @@ use GoParser\Parser; use PHPUnit\Framework\TestCase; +use function json_encode; +use function glob; +use function sprintf; +use function basename; +use function file_get_contents; + +use const JSON_PRETTY_PRINT; +use const JSON_THROW_ON_ERROR; + final class ParserTest extends TestCase { public function testFile(): void { - $parser = new Parser(' + $parser = new Parser(<<parse(); self::assertFalse($parser->hasErrors()); @@ -53,7 +62,7 @@ public function testDataFiles(string $src, string $expectedAst): void { $parser = new Parser($src); $file = $parser->parse(); - $astJson = \json_encode($file, \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR); + $astJson = json_encode($file, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); self::assertEmpty($parser->getErrors()); self::assertEquals($astJson, $expectedAst); @@ -67,19 +76,19 @@ private static function assertIdent(string $expected, mixed $actual): void private static function dataFiles(): iterable { - yield from self::fileContents('example'); - yield from self::fileContents('syntax'); + yield from self::fileContents('sample'); + yield from self::fileContents('core'); } private static function fileContents(string $dir): iterable { $path = __DIR__ . '/data/'; - $files = \glob(\sprintf($path . '/src/%s/*.go', $dir)); + $files = glob(sprintf($path . '/src/%s/*.go', $dir)); foreach ($files as $file) { - $goProgram = \file_get_contents($file); - $jsonPath = \sprintf($path . '/ast/%s/%s.json', $dir, \basename($file, '.go')); - $parsedJson = \file_get_contents($jsonPath); + $goProgram = file_get_contents($file); + $jsonPath = sprintf($path . '/ast/%s/%s.json', $dir, basename($file, '.go')); + $parsedJson = file_get_contents($jsonPath); yield $file => [$goProgram, $parsedJson]; } diff --git a/tests/data/README.md b/tests/data/README.md index 083ca30..cd761b8 100644 --- a/tests/data/README.md +++ b/tests/data/README.md @@ -1,4 +1,5 @@ -## Files for testing +## Directory structure -* `./syntax` Go files grouped by syntax -* `./example` Go files from various open source projects \ No newline at end of file +* `./src/core` Go test files +* `./src/sample` Go files from various open source projects +* `./ast` Parsed AST in JSON \ No newline at end of file diff --git a/tests/data/ast/syntax/declarations.json b/tests/data/ast/core/declarations.json similarity index 100% rename from tests/data/ast/syntax/declarations.json rename to tests/data/ast/core/declarations.json diff --git a/tests/data/ast/syntax/generic_function.json b/tests/data/ast/core/generic_function.json similarity index 100% rename from tests/data/ast/syntax/generic_function.json rename to tests/data/ast/core/generic_function.json diff --git a/tests/data/ast/syntax/generic_typedef.json b/tests/data/ast/core/generic_typedef.json similarity index 100% rename from tests/data/ast/syntax/generic_typedef.json rename to tests/data/ast/core/generic_typedef.json diff --git a/tests/data/ast/syntax/interface.json b/tests/data/ast/core/interface.json similarity index 100% rename from tests/data/ast/syntax/interface.json rename to tests/data/ast/core/interface.json diff --git a/tests/data/ast/syntax/literal_number_complex.json b/tests/data/ast/core/literal_number_complex.json similarity index 100% rename from tests/data/ast/syntax/literal_number_complex.json rename to tests/data/ast/core/literal_number_complex.json diff --git a/tests/data/ast/syntax/literal_number_float.json b/tests/data/ast/core/literal_number_float.json similarity index 100% rename from tests/data/ast/syntax/literal_number_float.json rename to tests/data/ast/core/literal_number_float.json diff --git a/tests/data/ast/syntax/literal_number_int.json b/tests/data/ast/core/literal_number_int.json similarity index 100% rename from tests/data/ast/syntax/literal_number_int.json rename to tests/data/ast/core/literal_number_int.json diff --git a/tests/data/ast/syntax/params.json b/tests/data/ast/core/params.json similarity index 100% rename from tests/data/ast/syntax/params.json rename to tests/data/ast/core/params.json diff --git a/tests/data/ast/core/struct.json b/tests/data/ast/core/struct.json new file mode 100644 index 0000000..1df0ebf --- /dev/null +++ b/tests/data/ast/core/struct.json @@ -0,0 +1,1076 @@ +{ + "package": { + "keyword": { + "pos": { + "offset": 7, + "line": 1, + "filename": null + }, + "word": "package" + }, + "identifier": { + "pos": { + "offset": 12, + "line": 1, + "filename": null + }, + "name": "test" + } + }, + "imports": [ + { + "keyword": { + "pos": { + "offset": 6, + "line": 3, + "filename": null + }, + "word": "import" + }, + "spec": { + "name": null, + "path": { + "pos": { + "offset": 13, + "line": 3, + "filename": null + }, + "str": "\"time\"" + } + } + } + ], + "decls": [ + { + "keyword": { + "pos": { + "offset": 4, + "line": 5, + "filename": null + }, + "word": "type" + }, + "spec": { + "value": { + "ident": { + "pos": { + "offset": 9, + "line": 5, + "filename": null + }, + "name": "Vec1" + }, + "typeParams": { + "lBracket": { + "pos": { + "offset": 10, + "line": 5, + "filename": null + }, + "value": "[" + }, + "typeParamList": [ + { + "identList": { + "idents": [ + { + "pos": { + "offset": 11, + "line": 5, + "filename": null + }, + "name": "E" + } + ] + }, + "type": { + "typeTerms": [ + { + "name": { + "pos": { + "offset": 15, + "line": 5, + "filename": null + }, + "name": "any" + }, + "typeArgs": null + } + ] + } + } + ], + "rBracket": { + "pos": { + "offset": 16, + "line": 5, + "filename": null + }, + "value": "]" + } + }, + "type": { + "lBrack": { + "pos": { + "offset": 18, + "line": 5, + "filename": null + }, + "value": "[" + }, + "rBrack": { + "pos": { + "offset": 19, + "line": 5, + "filename": null + }, + "value": "]" + }, + "elemType": { + "name": { + "pos": { + "offset": 20, + "line": 5, + "filename": null + }, + "name": "E" + }, + "typeArgs": null + } + } + } + } + }, + { + "keyword": { + "pos": { + "offset": 4, + "line": 6, + "filename": null + }, + "word": "type" + }, + "spec": { + "value": { + "ident": { + "pos": { + "offset": 9, + "line": 6, + "filename": null + }, + "name": "Vec2" + }, + "typeParams": { + "lBracket": { + "pos": { + "offset": 10, + "line": 6, + "filename": null + }, + "value": "[" + }, + "typeParamList": [ + { + "identList": { + "idents": [ + { + "pos": { + "offset": 11, + "line": 6, + "filename": null + }, + "name": "E" + } + ] + }, + "type": { + "typeTerms": [ + { + "name": { + "pos": { + "offset": 15, + "line": 6, + "filename": null + }, + "name": "any" + }, + "typeArgs": null + } + ] + } + } + ], + "rBracket": { + "pos": { + "offset": 16, + "line": 6, + "filename": null + }, + "value": "]" + } + }, + "type": { + "lBrack": { + "pos": { + "offset": 18, + "line": 6, + "filename": null + }, + "value": "[" + }, + "rBrack": { + "pos": { + "offset": 19, + "line": 6, + "filename": null + }, + "value": "]" + }, + "elemType": { + "name": { + "pos": { + "offset": 20, + "line": 6, + "filename": null + }, + "name": "E" + }, + "typeArgs": null + } + } + } + } + }, + { + "keyword": { + "pos": { + "offset": 4, + "line": 7, + "filename": null + }, + "word": "type" + }, + "spec": { + "value": { + "ident": { + "pos": { + "offset": 9, + "line": 7, + "filename": null + }, + "name": "Vec3" + }, + "typeParams": { + "lBracket": { + "pos": { + "offset": 10, + "line": 7, + "filename": null + }, + "value": "[" + }, + "typeParamList": [ + { + "identList": { + "idents": [ + { + "pos": { + "offset": 11, + "line": 7, + "filename": null + }, + "name": "E" + } + ] + }, + "type": { + "typeTerms": [ + { + "name": { + "pos": { + "offset": 22, + "line": 7, + "filename": null + }, + "name": "comparable" + }, + "typeArgs": null + } + ] + } + } + ], + "rBracket": { + "pos": { + "offset": 23, + "line": 7, + "filename": null + }, + "value": "]" + } + }, + "type": { + "lBrack": { + "pos": { + "offset": 25, + "line": 7, + "filename": null + }, + "value": "[" + }, + "rBrack": { + "pos": { + "offset": 26, + "line": 7, + "filename": null + }, + "value": "]" + }, + "elemType": { + "name": { + "pos": { + "offset": 27, + "line": 7, + "filename": null + }, + "name": "E" + }, + "typeArgs": null + } + } + } + } + }, + { + "keyword": { + "pos": { + "offset": 4, + "line": 9, + "filename": null + }, + "word": "type" + }, + "spec": { + "value": { + "ident": { + "pos": { + "offset": 6, + "line": 9, + "filename": null + }, + "name": "a" + }, + "typeParams": null, + "type": { + "keyword": { + "pos": { + "offset": 13, + "line": 9, + "filename": null + }, + "word": "struct" + }, + "lBrace": { + "pos": { + "offset": 15, + "line": 9, + "filename": null + }, + "value": "{" + }, + "fieldDecls": [ + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 10, + "filename": null + }, + "name": "a" + } + ] + }, + "type": { + "name": { + "pos": { + "offset": 15, + "line": 10, + "filename": null + }, + "name": "string" + }, + "typeArgs": null + }, + "tag": null + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 11, + "filename": null + }, + "name": "b" + } + ] + }, + "type": { + "name": { + "pos": { + "offset": 12, + "line": 11, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + }, + "tag": { + "pos": { + "offset": 30, + "line": 11, + "filename": null + }, + "str": "`tag`" + } + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 12, + "filename": null + }, + "name": "c" + } + ] + }, + "type": { + "keyword": { + "pos": { + "offset": 15, + "line": 12, + "filename": null + }, + "word": "struct" + }, + "lBrace": { + "pos": { + "offset": 16, + "line": 12, + "filename": null + }, + "value": "{" + }, + "fieldDecls": [ + { + "identList": { + "idents": [ + { + "pos": { + "offset": 18, + "line": 12, + "filename": null + }, + "name": "x" + } + ] + }, + "type": { + "name": { + "pos": { + "offset": 22, + "line": 12, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + }, + "tag": null + } + ], + "rBrace": { + "pos": { + "offset": 24, + "line": 12, + "filename": null + }, + "value": "}" + } + }, + "tag": { + "pos": { + "offset": 30, + "line": 12, + "filename": null + }, + "str": "\"tag\"" + } + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 13, + "filename": null + }, + "name": "d" + } + ] + }, + "type": { + "lBrack": { + "pos": { + "offset": 10, + "line": 13, + "filename": null + }, + "value": "[" + }, + "rBrack": { + "pos": { + "offset": 11, + "line": 13, + "filename": null + }, + "value": "]" + }, + "elemType": { + "name": { + "pos": { + "offset": 14, + "line": 13, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + } + }, + "tag": null + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 14, + "filename": null + }, + "name": "c" + } + ] + }, + "type": { + "op": { + "pos": { + "offset": 10, + "line": 14, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 13, + "line": 14, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + } + }, + "tag": null + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 15, + "filename": null + }, + "name": "d" + }, + { + "pos": { + "offset": 5, + "line": 15, + "filename": null + }, + "name": "e" + }, + { + "pos": { + "offset": 8, + "line": 15, + "filename": null + }, + "name": "f" + } + ] + }, + "type": { + "lBrack": { + "pos": { + "offset": 10, + "line": 15, + "filename": null + }, + "value": "[" + }, + "rBrack": { + "pos": { + "offset": 11, + "line": 15, + "filename": null + }, + "value": "]" + }, + "elemType": { + "op": { + "pos": { + "offset": 12, + "line": 15, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 18, + "line": 15, + "filename": null + }, + "name": "string" + }, + "typeArgs": null + } + } + }, + "tag": null + }, + { + "identList": { + "idents": [ + { + "pos": { + "offset": 2, + "line": 16, + "filename": null + }, + "name": "g" + }, + { + "pos": { + "offset": 5, + "line": 16, + "filename": null + }, + "name": "h" + }, + { + "pos": { + "offset": 8, + "line": 16, + "filename": null + }, + "name": "i" + } + ] + }, + "type": { + "op": { + "pos": { + "offset": 10, + "line": 16, + "filename": null + }, + "value": "*" + }, + "type": { + "keyword": { + "pos": { + "offset": 13, + "line": 16, + "filename": null + }, + "word": "map" + }, + "lBrack": { + "pos": { + "offset": 14, + "line": 16, + "filename": null + }, + "value": "[" + }, + "keyType": { + "name": { + "pos": { + "offset": 17, + "line": 16, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + }, + "rBrack": { + "pos": { + "offset": 18, + "line": 16, + "filename": null + }, + "value": "]" + }, + "elemType": { + "lBrack": { + "pos": { + "offset": 19, + "line": 16, + "filename": null + }, + "value": "[" + }, + "len": { + "pos": { + "offset": 20, + "line": 16, + "filename": null + }, + "digits": "2" + }, + "rBrack": { + "pos": { + "offset": 21, + "line": 16, + "filename": null + }, + "value": "]" + }, + "elemType": { + "name": { + "pos": { + "offset": 27, + "line": 16, + "filename": null + }, + "name": "string" + }, + "typeArgs": null + } + } + } + }, + "tag": { + "pos": { + "offset": 33, + "line": 16, + "filename": null + }, + "str": "`tag`" + } + }, + { + "type": { + "name": { + "pos": { + "offset": 4, + "line": 17, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + }, + "tag": null + }, + { + "type": { + "name": { + "pos": { + "offset": 7, + "line": 18, + "filename": null + }, + "name": "string" + }, + "typeArgs": null + }, + "tag": { + "pos": { + "offset": 13, + "line": 18, + "filename": null + }, + "str": "`tag`" + } + }, + { + "type": { + "op": { + "pos": { + "offset": 2, + "line": 19, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 6, + "line": 19, + "filename": null + }, + "name": "uint" + }, + "typeArgs": null + } + }, + "tag": null + }, + { + "type": { + "op": { + "pos": { + "offset": 2, + "line": 20, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 8, + "line": 20, + "filename": null + }, + "name": "uint32" + }, + "typeArgs": null + } + }, + "tag": { + "pos": { + "offset": 14, + "line": 20, + "filename": null + }, + "str": "`tag`" + } + }, + { + "type": { + "name": { + "pos": { + "offset": 5, + "line": 21, + "filename": null + }, + "name": "Vec1" + }, + "typeArgs": [ + { + "types": [ + { + "name": { + "pos": { + "offset": 9, + "line": 21, + "filename": null + }, + "name": "int" + }, + "typeArgs": null + } + ] + } + ] + }, + "tag": null + }, + { + "type": { + "name": { + "pos": { + "offset": 5, + "line": 22, + "filename": null + }, + "name": "Vec2" + }, + "typeArgs": [ + { + "types": [ + { + "op": { + "pos": { + "offset": 7, + "line": 22, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 13, + "line": 22, + "filename": null + }, + "name": "uint32" + }, + "typeArgs": null + } + } + ] + } + ] + }, + "tag": { + "pos": { + "offset": 20, + "line": 22, + "filename": null + }, + "str": "`tag`" + } + }, + { + "type": { + "op": { + "pos": { + "offset": 2, + "line": 23, + "filename": null + }, + "value": "*" + }, + "type": { + "name": { + "pos": { + "offset": 6, + "line": 23, + "filename": null + }, + "name": "Vec3" + }, + "typeArgs": [ + { + "types": [ + { + "name": { + "pos": { + "offset": 13, + "line": 23, + "filename": null + }, + "name": "string" + }, + "typeArgs": null + } + ] + } + ] + } + }, + "tag": null + }, + { + "type": { + "packageName": { + "pos": { + "offset": 5, + "line": 24, + "filename": null + }, + "name": "time" + }, + "typeName": { + "name": { + "pos": { + "offset": 10, + "line": 24, + "filename": null + }, + "name": "Time" + }, + "typeArgs": null + } + }, + "tag": { + "pos": { + "offset": 16, + "line": 24, + "filename": null + }, + "str": "\"tag\"" + } + }, + { + "type": { + "op": { + "pos": { + "offset": 2, + "line": 25, + "filename": null + }, + "value": "*" + }, + "type": { + "packageName": { + "pos": { + "offset": 6, + "line": 25, + "filename": null + }, + "name": "time" + }, + "typeName": { + "name": { + "pos": { + "offset": 15, + "line": 25, + "filename": null + }, + "name": "Duration" + }, + "typeArgs": null + } + } + }, + "tag": null + } + ], + "rBrace": { + "pos": { + "offset": 1, + "line": 26, + "filename": null + }, + "value": "}" + } + } + } + } + } + ], + "filename": null +} \ No newline at end of file diff --git a/tests/data/ast/example/file1.json b/tests/data/ast/sample/file1.json similarity index 99% rename from tests/data/ast/example/file1.json rename to tests/data/ast/sample/file1.json index a681096..0dd1188 100644 --- a/tests/data/ast/example/file1.json +++ b/tests/data/ast/sample/file1.json @@ -2080,7 +2080,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 47, @@ -2174,7 +2174,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -2279,7 +2279,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 52, @@ -2373,7 +2373,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -2794,7 +2794,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 64, @@ -2925,7 +2925,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 5, "line": 66, @@ -3124,7 +3124,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -3164,7 +3164,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/ast/example/file2.json b/tests/data/ast/sample/file2.json similarity index 99% rename from tests/data/ast/example/file2.json rename to tests/data/ast/sample/file2.json index 6eebbdd..a3193d2 100644 --- a/tests/data/ast/example/file2.json +++ b/tests/data/ast/sample/file2.json @@ -834,7 +834,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 3, "line": 39, @@ -913,7 +913,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/ast/example/file3.json b/tests/data/ast/sample/file3.json similarity index 99% rename from tests/data/ast/example/file3.json rename to tests/data/ast/sample/file3.json index 0dd23ac..dc728c3 100644 --- a/tests/data/ast/example/file3.json +++ b/tests/data/ast/sample/file3.json @@ -374,7 +374,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 16, @@ -645,7 +645,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -1047,7 +1047,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 3, "line": 27, @@ -1146,7 +1146,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -1157,7 +1157,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 31, @@ -1445,7 +1445,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/ast/example/file4.json b/tests/data/ast/sample/file4.json similarity index 99% rename from tests/data/ast/example/file4.json rename to tests/data/ast/sample/file4.json index 8bf2b40..e232a3c 100644 --- a/tests/data/ast/example/file4.json +++ b/tests/data/ast/sample/file4.json @@ -1317,7 +1317,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 35, @@ -1446,7 +1446,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -4727,7 +4727,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 84, @@ -4959,7 +4959,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/ast/example/file5.json b/tests/data/ast/sample/file5.json similarity index 99% rename from tests/data/ast/example/file5.json rename to tests/data/ast/sample/file5.json index 1336106..58e5aa1 100644 --- a/tests/data/ast/example/file5.json +++ b/tests/data/ast/sample/file5.json @@ -482,7 +482,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 4, "line": 17, @@ -691,7 +691,7 @@ "value": "}" } }, - "else": { + "elseKeyword": { "pos": { "offset": 8, "line": 19, @@ -1299,7 +1299,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 6, "line": 32, @@ -1480,7 +1480,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/ast/example/file6.json b/tests/data/ast/sample/file6.json similarity index 99% rename from tests/data/ast/example/file6.json rename to tests/data/ast/sample/file6.json index 98e1be2..a681bda 100644 --- a/tests/data/ast/example/file6.json +++ b/tests/data/ast/sample/file6.json @@ -1871,7 +1871,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 58, @@ -1979,7 +1979,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -2799,7 +2799,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 77, @@ -2896,7 +2896,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -3482,7 +3482,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 90, @@ -3569,7 +3569,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -3580,7 +3580,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 93, @@ -3843,7 +3843,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 4, "line": 98, @@ -3976,7 +3976,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -4031,7 +4031,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -4571,7 +4571,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 115, @@ -4744,7 +4744,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -5155,7 +5155,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 126, @@ -5234,7 +5234,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -5737,7 +5737,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 4, "line": 134, @@ -5816,7 +5816,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -5918,7 +5918,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 4, "line": 138, @@ -5997,7 +5997,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -6685,7 +6685,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 150, @@ -6772,7 +6772,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -6936,7 +6936,7 @@ "stmtList": { "stmts": [ { - "if": { + "keyword": { "pos": { "offset": 4, "line": 154, @@ -7150,7 +7150,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 5, "line": 157, @@ -7271,7 +7271,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 6, "line": 159, @@ -7372,7 +7372,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -7393,7 +7393,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -7414,7 +7414,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -7644,7 +7644,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 167, @@ -7731,7 +7731,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -8162,7 +8162,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 177, @@ -8249,7 +8249,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -8334,7 +8334,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 181, @@ -8421,7 +8421,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -8515,7 +8515,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 185, @@ -8602,7 +8602,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { @@ -8687,7 +8687,7 @@ } }, { - "if": { + "keyword": { "pos": { "offset": 3, "line": 189, @@ -8774,7 +8774,7 @@ "value": "}" } }, - "else": null, + "elseKeyword": null, "elseBody": null }, { diff --git a/tests/data/src/syntax/declarations.go b/tests/data/src/core/declarations.go similarity index 100% rename from tests/data/src/syntax/declarations.go rename to tests/data/src/core/declarations.go diff --git a/tests/data/src/syntax/generic_function.go b/tests/data/src/core/generic_function.go similarity index 100% rename from tests/data/src/syntax/generic_function.go rename to tests/data/src/core/generic_function.go diff --git a/tests/data/src/syntax/generic_typedef.go b/tests/data/src/core/generic_typedef.go similarity index 100% rename from tests/data/src/syntax/generic_typedef.go rename to tests/data/src/core/generic_typedef.go diff --git a/tests/data/src/syntax/interface.go b/tests/data/src/core/interface.go similarity index 100% rename from tests/data/src/syntax/interface.go rename to tests/data/src/core/interface.go diff --git a/tests/data/src/syntax/literal_number_complex.go b/tests/data/src/core/literal_number_complex.go similarity index 100% rename from tests/data/src/syntax/literal_number_complex.go rename to tests/data/src/core/literal_number_complex.go diff --git a/tests/data/src/syntax/literal_number_float.go b/tests/data/src/core/literal_number_float.go similarity index 100% rename from tests/data/src/syntax/literal_number_float.go rename to tests/data/src/core/literal_number_float.go diff --git a/tests/data/src/syntax/literal_number_int.go b/tests/data/src/core/literal_number_int.go similarity index 100% rename from tests/data/src/syntax/literal_number_int.go rename to tests/data/src/core/literal_number_int.go diff --git a/tests/data/src/syntax/params.go b/tests/data/src/core/params.go similarity index 100% rename from tests/data/src/syntax/params.go rename to tests/data/src/core/params.go diff --git a/tests/data/src/core/struct.go b/tests/data/src/core/struct.go new file mode 100644 index 0000000..47f8ed5 --- /dev/null +++ b/tests/data/src/core/struct.go @@ -0,0 +1,26 @@ +package test + +import "time" + +type Vec1[E any] []E +type Vec2[E any] []E +type Vec3[E comparable] []E + +type a struct { + a string + b int `tag` + c struct{ x int } "tag" + d []int + c *int + d, e, f []*string + g, h, i *map[int][2]string `tag` + int + string `tag` + *uint + *uint32 `tag` + Vec1[int] + Vec2[*uint32] `tag` + *Vec3[string] + time.Time "tag" + *time.Duration +} diff --git a/tests/data/src/example/file1.go b/tests/data/src/sample/file1.go similarity index 100% rename from tests/data/src/example/file1.go rename to tests/data/src/sample/file1.go diff --git a/tests/data/src/example/file2.go b/tests/data/src/sample/file2.go similarity index 100% rename from tests/data/src/example/file2.go rename to tests/data/src/sample/file2.go diff --git a/tests/data/src/example/file3.go b/tests/data/src/sample/file3.go similarity index 100% rename from tests/data/src/example/file3.go rename to tests/data/src/sample/file3.go diff --git a/tests/data/src/example/file4.go b/tests/data/src/sample/file4.go similarity index 100% rename from tests/data/src/example/file4.go rename to tests/data/src/sample/file4.go diff --git a/tests/data/src/example/file5.go b/tests/data/src/sample/file5.go similarity index 100% rename from tests/data/src/example/file5.go rename to tests/data/src/sample/file5.go diff --git a/tests/data/src/example/file6.go b/tests/data/src/sample/file6.go similarity index 100% rename from tests/data/src/example/file6.go rename to tests/data/src/sample/file6.go