diff --git a/src/Ast/FunctionNode.php b/src/Ast/FunctionNode.php new file mode 100644 index 0000000..f21eaf9 --- /dev/null +++ b/src/Ast/FunctionNode.php @@ -0,0 +1,159 @@ +phpDocNode = $phpDocNode; + $this->indentation = $indentation; + $this->keyword = $keyword; + $this->name = $name; + $this->parameters = $parameters; + $this->returnType = $returnType; + $this->inheritedVariables = $inheritedVariables; + $this->body = $body; + } + + + public function getName(): ?string + { + return $this->name !== NULL + ? $this->name->getName() + : NULL; + } + + + public function getDocComment(): ?string + { + return $this->phpDocNode !== NULL ? $this->phpDocNode->getContent() : NULL; + } + + + /** + * @return Parameter[] + */ + public function getParameters(): array + { + return $this->parameters->getParameters(); + } + + + public function hasReturnType(): bool + { + return $this->returnType !== NULL; + } + + + public function toString(): string + { + $s = $this->phpDocNode !== NULL ? $this->phpDocNode->toString() : ''; + $s .= $this->indentation; + $s .= $this->keyword; + + if ($this->name !== NULL) { + $s .= $this->name->toString(); + } + + $s .= $this->parameters->toString(); + + if ($this->inheritedVariables !== NULL) { + $s .= $this->inheritedVariables->toString(); + } + + if ($this->returnType !== NULL) { + $s .= $this->returnType->toString(); + } + + $s .= $this->body->toString(); + return $s; + } + + + /** + * @return self + */ + public static function parse( + ?PhpDocNode $phpDocNode, + NodeParser $parser + ) + { + $nodeIndentation = $parser->consumeNodeIndentation(); + $keyword = $parser->consumeTokenAsText(T_FUNCTION); + $parser->consumeWhitespace(); + $name = Name::tryParseFunctionName($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + $parameters = Parameters::parse($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + $inheritedVariables = NULL; + $returnType = NULL; + $body = NULL; + + if ($name === NULL && $parser->isCurrent(T_USE)) { + $inheritedVariables = InheritedVariables::parse($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + } + + if ($parser->isCurrent(':')) { + $returnType = FunctionReturnType::parse($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + } + + if ($parser->isCurrent(T_COMMENT)) { + $parser->consumeAsIndentation(T_COMMENT); + $parser->tryConsumeWhitespace(); + } + + $body = FunctionBody::parse($parser->createSubParser()); + $parser->close(); + + return new self( + $phpDocNode, + $nodeIndentation, + $keyword, + $name, + $parameters, + $inheritedVariables, + $returnType, + $body + ); + } + } diff --git a/src/Ast/InheritedVariable.php b/src/Ast/InheritedVariable.php new file mode 100644 index 0000000..817b261 --- /dev/null +++ b/src/Ast/InheritedVariable.php @@ -0,0 +1,59 @@ +variable = $variable; + $this->suffix = $suffix; + } + + + public function toString(): string + { + $s = $this->variable->toString(); + + if ($this->suffix !== NULL) { + $s .= $this->suffix->toString(); + } + + return $s; + } + + + /** + * @return self + */ + public static function parse(NodeParser $parser) + { + $variable = VariableName::parse($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + $suffix = NULL; + + if ($parser->isCurrent(',')) { + $suffix = Literal::parseToken($parser->createSubParser(), ','); + } + + $parser->close(); + + return new self( + $variable, + $suffix + ); + } + } diff --git a/src/Ast/InheritedVariables.php b/src/Ast/InheritedVariables.php new file mode 100644 index 0000000..1b587e5 --- /dev/null +++ b/src/Ast/InheritedVariables.php @@ -0,0 +1,81 @@ +keyword = $keyword; + $this->opener = $opener; + $this->variables = $variables; + $this->closer = $closer; + } + + + public function toString(): string + { + $s = $this->keyword->toString(); + $s .= $this->opener->toString(); + + foreach ($this->variables as $variable) { + $s .= $variable->toString(); + } + + $s .= $this->closer->toString(); + return $s; + } + + + /** + * @return self + */ + public static function parse(NodeParser $parser) + { + $keyword = Literal::parseToken($parser->createSubParser(), T_USE); + $parser->tryConsumeWhitespace(); + $opener = Literal::parseToken($parser->createSubParser(), '('); + $inheritedVariables = []; + + do { + $parser->tryConsumeWhitespace(); + $inheritedVariables[] = InheritedVariable::parse($parser->createSubParser()); + $parser->tryConsumeWhitespace(); + + } while (!$parser->isCurrent(')')); + + $closer = Literal::parseToken($parser->createSubParser(), ')'); + $parser->close(); + + return new self( + $keyword, + $opener, + $inheritedVariables, + $closer + ); + } + } diff --git a/src/Ast/Name.php b/src/Ast/Name.php index dc0fc5a..e46633a 100644 --- a/src/Ast/Name.php +++ b/src/Ast/Name.php @@ -161,4 +161,22 @@ public static function tryParseClassName(NodeParser $parser) $parser->close(); return $name !== '' ? new self($nodeIndentation, $name) : NULL; } + + + /** + * @return self|NULL + */ + public static function tryParseFunctionName(NodeParser $parser) + { + $nodeIndentation = ''; + $name = ''; + + if ($parser->isCurrent(T_STRING)) { + $nodeIndentation = $parser->consumeNodeIndentation(); + $name = $parser->consumeTokenAsText(T_STRING); + } + + $parser->close(); + return $name !== '' ? new self($nodeIndentation, $name) : NULL; + } } diff --git a/src/Ast/NamespaceNode.php b/src/Ast/NamespaceNode.php index a42789f..a93dbe5 100644 --- a/src/Ast/NamespaceNode.php +++ b/src/Ast/NamespaceNode.php @@ -148,6 +148,9 @@ public static function parse(NodeParser $parser) $parser->consumeAsUnknowContent(T_DOUBLE_COLON); $parser->tryConsumeAsUnknowContent(T_CLASS); + } elseif ($parser->isCurrent(T_FUNCTION)) { + $child = FunctionNode::parse(NULL, $parser->createSubParser()); + } elseif ($parser->isCurrent(T_USE)) { $child = UseNode::parse($parser->createSubParser()); diff --git a/src/Ast/PhpNode.php b/src/Ast/PhpNode.php index 2e7e89a..c09d42a 100644 --- a/src/Ast/PhpNode.php +++ b/src/Ast/PhpNode.php @@ -79,6 +79,9 @@ public static function parse(NodeParser $parser) $parser->consumeAsUnknowContent(T_DOUBLE_COLON); $parser->tryConsumeAsUnknowContent(T_CLASS); + } elseif ($parser->isCurrent(T_FUNCTION)) { + $child = FunctionNode::parse(NULL, $parser->createSubParser()); + } elseif ($parser->isCurrent(T_USE)) { $child = UseNode::parse($parser->createSubParser()); diff --git a/tests/PhpSimpleAst/fixtures/Php.anonymous-classes.dump b/tests/PhpSimpleAst/fixtures/Php.anonymous-classes.dump index ea83a0a..6b5f6b2 100644 --- a/tests/PhpSimpleAst/fixtures/Php.anonymous-classes.dump +++ b/tests/PhpSimpleAst/fixtures/Php.anonymous-classes.dump @@ -2,7 +2,7 @@ CzProject\PhpSimpleAst\Ast\PhpString children: array (1) | 0 => CzProject\PhpSimpleAst\Ast\PhpNode | | openTag: ' CzProject\PhpSimpleAst\Ast\CommentNode | | | | indentation: '\n' | | | | content: '// http://php.net/manual/en/language.oop5.anonymous.php' @@ -436,42 +436,50 @@ CzProject\PhpSimpleAst\Ast\PhpString | | | | | \n | | | | | \n' | | | | content: '// example 4' - | | | 19 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | 19 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: '\n' + | | | | keyword: 'function' + | | | | name: CzProject\PhpSimpleAst\Ast\Name + | | | | | indentation: ' ' + | | | | | name: 'anonymous_class' + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: '' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: null + | | | | returnType: null + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: '\n' + | | | | | blockOpener: '{' + | | | | | children: array (1) + | | | | | | 0 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | | | | | content: string + | | | | | | | | '\n + | | | | | | | | \t return new class {};' + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 20 => CzProject\PhpSimpleAst\Ast\UnknowNode | | | | content: string | | | | | '\n - | | | | | function anonymous_class()\n - | | | | | {\n - | | | | | \t return new' - | | | 20 => CzProject\PhpSimpleAst\Ast\ClassNode - | | | | indentation: ' ' - | | | | keyword: 'class' - | | | | name: null - | | | | constructorValues: null - | | | | extends: null - | | | | implements: null - | | | | blockOpener: ' {' - | | | | children: array (0) - | | | | blockCloser: '}' - | | | 21 => CzProject\PhpSimpleAst\Ast\UnknowNode - | | | | content: string - | | | | | ';\n - | | | | | }\n | | | | | \n | | | | | if (get_class(anonymous_class()) === get_class(anonymous_class())) {\n | | | | | \t echo 'same class';\n | | | | | } else {\n | | | | | \t echo 'different class';\n | | | | | }' - | | | 22 => CzProject\PhpSimpleAst\Ast\CommentNode + | | | 21 => CzProject\PhpSimpleAst\Ast\CommentNode | | | | indentation: string | | | | | '\n | | | | | \n' | | | | content: '// example 5' - | | | 23 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | 22 => CzProject\PhpSimpleAst\Ast\UnknowNode | | | | content: string | | | | | '\n | | | | | echo get_class(new' - | | | 24 => CzProject\PhpSimpleAst\Ast\ClassNode + | | | 23 => CzProject\PhpSimpleAst\Ast\ClassNode | | | | indentation: ' ' | | | | keyword: 'class' | | | | name: null @@ -481,6 +489,6 @@ CzProject\PhpSimpleAst\Ast\PhpString | | | | blockOpener: ' {' | | | | children: array (0) | | | | blockCloser: '}' - | | | 25 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | 24 => CzProject\PhpSimpleAst\Ast\UnknowNode | | | | content: ');\n' | | closeTag: null diff --git a/tests/PhpSimpleAst/fixtures/Php.closure.dump b/tests/PhpSimpleAst/fixtures/Php.closure.dump new file mode 100644 index 0000000..87bcb54 --- /dev/null +++ b/tests/PhpSimpleAst/fixtures/Php.closure.dump @@ -0,0 +1,223 @@ +CzProject\PhpSimpleAst\Ast\PhpString + children: array (1) + | 0 => CzProject\PhpSimpleAst\Ast\PhpNode + | | openTag: ' CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: '\n' + | | | | keyword: 'function' + | | | | name: null + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: ' ' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: null + | | | | returnType: null + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 1 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | | content: ';' + | | | 2 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: string + | | | | | '\n + | | | | | \n' + | | | | keyword: 'function' + | | | | name: null + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: ' ' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: null + | | | | returnType: CzProject\PhpSimpleAst\Ast\FunctionReturnType + | | | | | indentation: ': ' + | | | | | nullable: false + | | | | | type: CzProject\PhpSimpleAst\Ast\Type + | | | | | | indentation: '' + | | | | | | types: array (1) + | | | | | | | 0 => CzProject\PhpSimpleAst\Ast\NamedType + | | | | | | | | indentation: '' + | | | | | | | | nullableSign: '' + | | | | | | | | typeIndentation: '' + | | | | | | | | type: 'void' + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 3 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | | content: ';' + | | | 4 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: string + | | | | | '\n + | | | | | \n' + | | | | keyword: 'function' + | | | | name: CzProject\PhpSimpleAst\Ast\Name + | | | | | indentation: ' ' + | | | | | name: 'name' + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: '' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: null + | | | | returnType: null + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 5 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: string + | | | | | '\n + | | | | | \n' + | | | | keyword: 'function' + | | | | name: CzProject\PhpSimpleAst\Ast\Name + | | | | | indentation: ' ' + | | | | | name: 'name2' + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: '' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: null + | | | | returnType: CzProject\PhpSimpleAst\Ast\FunctionReturnType + | | | | | indentation: ': ' + | | | | | nullable: false + | | | | | type: CzProject\PhpSimpleAst\Ast\Type + | | | | | | indentation: '' + | | | | | | types: array (1) + | | | | | | | 0 => CzProject\PhpSimpleAst\Ast\NamedType + | | | | | | | | indentation: '' + | | | | | | | | nullableSign: '' + | | | | | | | | typeIndentation: '' + | | | | | | | | type: 'void' + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 6 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: string + | | | | | '\n + | | | | | \n' + | | | | keyword: 'function' + | | | | name: null + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: ' ' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: CzProject\PhpSimpleAst\Ast\InheritedVariables + | | | | | keyword: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: ' ' + | | | | | | literal: 'use' + | | | | | opener: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: ' ' + | | | | | | literal: '(' + | | | | | variables: array (1) + | | | | | | 0 => CzProject\PhpSimpleAst\Ast\InheritedVariable + | | | | | | | variable: CzProject\PhpSimpleAst\Ast\VariableName + | | | | | | | | indentation: '' + | | | | | | | | referenceSign: '&' + | | | | | | | | variadic: null + | | | | | | | | name: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | | | | indentation: '' + | | | | | | | | | literal: '$foo' + | | | | | | | suffix: null + | | | | | closer: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: '' + | | | | | | literal: ')' + | | | | returnType: null + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 7 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | | content: ';' + | | | 8 => CzProject\PhpSimpleAst\Ast\FunctionNode + | | | | phpDocNode: null + | | | | indentation: string + | | | | | '\n + | | | | | \n' + | | | | keyword: 'function' + | | | | name: null + | | | | parameters: CzProject\PhpSimpleAst\Ast\Parameters + | | | | | indentation: ' ' + | | | | | opener: '(' + | | | | | parameters: array (0) + | | | | | closer: ')' + | | | | inheritedVariables: CzProject\PhpSimpleAst\Ast\InheritedVariables + | | | | | keyword: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: ' ' + | | | | | | literal: 'use' + | | | | | opener: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: ' ' + | | | | | | literal: '(' + | | | | | variables: array (2) + | | | | | | 0 => CzProject\PhpSimpleAst\Ast\InheritedVariable + | | | | | | | variable: CzProject\PhpSimpleAst\Ast\VariableName + | | | | | | | | indentation: '' + | | | | | | | | referenceSign: '' + | | | | | | | | variadic: null + | | | | | | | | name: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | | | | indentation: '' + | | | | | | | | | literal: '$foo' + | | | | | | | suffix: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | | | indentation: ' ' + | | | | | | | | literal: ',' + | | | | | | 1 => CzProject\PhpSimpleAst\Ast\InheritedVariable + | | | | | | | variable: CzProject\PhpSimpleAst\Ast\VariableName + | | | | | | | | indentation: ' ' + | | | | | | | | referenceSign: '' + | | | | | | | | variadic: null + | | | | | | | | name: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | | | | indentation: '' + | | | | | | | | | literal: '$bar' + | | | | | | | suffix: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | | | indentation: '' + | | | | | | | | literal: ',' + | | | | | closer: CzProject\PhpSimpleAst\Ast\Literal + | | | | | | indentation: ' ' + | | | | | | literal: ')' + | | | | returnType: CzProject\PhpSimpleAst\Ast\FunctionReturnType + | | | | | indentation: ': ' + | | | | | nullable: false + | | | | | type: CzProject\PhpSimpleAst\Ast\Type + | | | | | | indentation: '' + | | | | | | types: array (1) + | | | | | | | 0 => CzProject\PhpSimpleAst\Ast\NamedType + | | | | | | | | indentation: '' + | | | | | | | | nullableSign: '' + | | | | | | | | typeIndentation: '' + | | | | | | | | type: 'void' + | | | | body: CzProject\PhpSimpleAst\Ast\FunctionBody + | | | | | indentation: ' ' + | | | | | blockOpener: '{' + | | | | | children: array (0) + | | | | | blockCloser: string + | | | | | | '\n + | | | | | | }' + | | | 9 => CzProject\PhpSimpleAst\Ast\UnknowNode + | | | | content: ';\n' + | | closeTag: null diff --git a/tests/PhpSimpleAst/fixtures/Php.closure.php b/tests/PhpSimpleAst/fixtures/Php.closure.php new file mode 100644 index 0000000..c736ca5 --- /dev/null +++ b/tests/PhpSimpleAst/fixtures/Php.closure.php @@ -0,0 +1,19 @@ +