Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Node/DelimitedList/MatchArmConditionList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class MatchArmConditionList extends DelimitedList {
}
12 changes: 12 additions & 0 deletions src/Node/DelimitedList/MatchExpressionArmList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\DelimitedList;

use Microsoft\PhpParser\Node\DelimitedList;

class MatchExpressionArmList extends DelimitedList {
}
47 changes: 47 additions & 0 deletions src/Node/Expression/MatchExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node\Expression;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList\MatchExpressionArmList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Token;

class MatchExpression extends Expression {
/** @var Token `match` */
public $matchToken;

/** @var Token */
public $openParen;

/** @var Node|null */
public $expression;

/** @var Token */
public $closeParen;

/** @var Token */
public $openBrace;

/** @var MatchExpressionArmList|null */
public $arms;

/** @var Token */
public $closeBrace;

const CHILD_NAMES = [
'matchToken',

'openParen',
'expression',
'closeParen',

'openBrace',
'arms',
'closeBrace',
];
}
29 changes: 29 additions & 0 deletions src/Node/MatchArm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

namespace Microsoft\PhpParser\Node;

use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\DelimitedList\MatchArmConditionList;
use Microsoft\PhpParser\Token;

class MatchArm extends Node {

/** @var MatchArmConditionList */
public $conditionList;

/** @var Token */
public $arrowToken;

/** @var Expression */
public $body;

const CHILD_NAMES = [
'conditionList',
'arrowToken',
'body',
];
}
58 changes: 58 additions & 0 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
EvalIntrinsicExpression,
ExitIntrinsicExpression,
IssetIntrinsicExpression,
MatchExpression,
MemberAccessExpression,
ParenthesizedExpression,
PrefixUpdateExpression,
Expand Down Expand Up @@ -61,6 +62,7 @@
use Microsoft\PhpParser\Node\ForeachValue;
use Microsoft\PhpParser\Node\InterfaceBaseClause;
use Microsoft\PhpParser\Node\InterfaceMembers;
use Microsoft\PhpParser\Node\MatchArm;
use Microsoft\PhpParser\Node\MissingMemberDeclaration;
use Microsoft\PhpParser\Node\NamespaceAliasingClause;
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
Expand Down Expand Up @@ -969,6 +971,7 @@ private function isExpressionStartFn() {
case TokenKind::ObjectCastToken:
case TokenKind::StringCastToken:
case TokenKind::UnsetCastToken:
case TokenKind::MatchKeyword:

// anonymous-function-creation-expression
case TokenKind::StaticKeyword:
Expand Down Expand Up @@ -1087,6 +1090,8 @@ private function parsePrimaryExpression($parentNode) {
return $this->parseQualifiedName($parentNode);
}
return $this->parseReservedWordExpression($parentNode);
case TokenKind::MatchKeyword:
return $this->parseMatchExpression($parentNode);
}
if (\in_array($token->kind, TokenStringMaps::RESERVED_WORDS)) {
return $this->parseQualifiedName($parentNode);
Expand Down Expand Up @@ -3572,6 +3577,59 @@ function ($parentNode) {
return $anonymousFunctionUseClause;
}

private function parseMatchExpression($parentNode) {
$matchExpression = new MatchExpression();
$matchExpression->parent = $parentNode;
$matchExpression->matchToken = $this->eat1(TokenKind::MatchKeyword);
$matchExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
$matchExpression->expression = $this->parseExpression($matchExpression);
$matchExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
$matchExpression->openBrace = $this->eat1(TokenKind::OpenBraceToken);
$matchExpression->arms = $this->parseDelimitedList(
DelimitedList\MatchExpressionArmList::class,
TokenKind::CommaToken,
$this->isMatchConditionStartFn(),
$this->parseMatchArmFn(),
$matchExpression);
$matchExpression->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
return $matchExpression;
}

private function isMatchConditionStartFn() {
return function ($token) {
return $token->kind === TokenKind::DefaultKeyword ||
$this->isExpressionStart($token);
};
}

private function parseMatchArmFn() {
return function ($parentNode) {
$matchArm = new MatchArm();
$matchArm->parent = $parentNode;
$matchArmConditionList = $this->parseDelimitedList(
DelimitedList\MatchArmConditionList::class,
TokenKind::CommaToken,
$this->isMatchConditionStartFn(),
$this->parseMatchConditionFn(),
$matchArm
);
$matchArmConditionList->parent = $matchArm;
$matchArm->conditionList = $matchArmConditionList;
$matchArm->arrowToken = $this->eat1(TokenKind::DoubleArrowToken);
$matchArm->body = $this->parseExpression($matchArm);
return $matchArm;
};
}

private function parseMatchConditionFn() {
return function ($parentNode) {
if ($this->token->kind === TokenKind::DefaultKeyword) {
return $this->eat1(TokenKind::DefaultKeyword);
}
return $this->parseExpression($parentNode);
};
}

private function parseCloneExpression($parentNode) {
$cloneExpression = new CloneExpression();
$cloneExpression->parent = $parentNode;
Expand Down
3 changes: 3 additions & 0 deletions src/PhpTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// The replacement value is arbitrary - it just has to be different from other values of token constants.
define(__NAMESPACE__ . '\T_COALESCE_EQUAL', defined('T_COALESCE_EQUAL') ? constant('T_COALESCE_EQUAL') : 'T_COALESCE_EQUAL');
define(__NAMESPACE__ . '\T_FN', defined('T_FN') ? constant('T_FN') : 'T_FN');
// If this predaates PHP 8.0, T_MATCH is unavailable. The replacement value is arbitrary - it just has to be different from other values of token constants.
define(__NAMESPACE__ . '\T_MATCH', defined('T_MATCH') ? constant('T_MATCH') : 'T_MATCH');

/**
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
Expand Down Expand Up @@ -206,6 +208,7 @@ public static function getTokensArrayFromContent(
T_INTERFACE => TokenKind::InterfaceKeyword,
T_ISSET => TokenKind::IsSetKeyword,
T_LIST => TokenKind::ListKeyword,
T_MATCH => TokenKind::MatchKeyword,
T_NAMESPACE => TokenKind::NamespaceKeyword,
T_NEW => TokenKind::NewKeyword,
T_LOGICAL_OR => TokenKind::OrKeyword,
Expand Down
1 change: 1 addition & 0 deletions src/TokenKind.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class TokenKind {
const YieldKeyword = 166;
const YieldFromKeyword = 167;
const FnKeyword = 168;
const MatchKeyword = 169;

const OpenBracketToken = 201;
const CloseBracketToken = 202;
Expand Down
8 changes: 8 additions & 0 deletions tests/ParserGrammarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function testOutputTreeClassificationAndLength($testCaseFile, $expectedTo

const FILE_PATTERN = __DIR__ . "/cases/parser/*";
const PHP74_FILE_PATTERN = __DIR__ . "/cases/parser74/*";
const PHP80_FILE_PATTERN = __DIR__ . "/cases/parser80/*";

public function treeProvider() {
$testCases = glob(self::FILE_PATTERN . ".php");
Expand All @@ -98,6 +99,13 @@ public function treeProvider() {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
}
if (PHP_VERSION_ID >= 80000) {
$testCases = glob(self::PHP80_FILE_PATTERN . ".php");
foreach ($testCases as $testCase) {
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
}
}


return $testProviderArray;
}
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/parser80/matchExpression1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
match(2+2) {}; // UnhandledMatchError
1 change: 1 addition & 0 deletions tests/cases/parser80/matchExpression1.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
77 changes: 77 additions & 0 deletions tests/cases/parser80/matchExpression1.php.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ExpressionStatement": {
"expression": {
"MatchExpression": {
"matchToken": {
"kind": "MatchKeyword",
"textLength": 5
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"expression": {
"BinaryExpression": {
"leftOperand": {
"NumericLiteral": {
"children": {
"kind": "IntegerLiteralToken",
"textLength": 1
}
}
},
"operator": {
"kind": "PlusToken",
"textLength": 1
},
"rightOperand": {
"NumericLiteral": {
"children": {
"kind": "IntegerLiteralToken",
"textLength": 1
}
}
}
}
},
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"arms": null,
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}
2 changes: 2 additions & 0 deletions tests/cases/parser80/matchExpression2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
$a = match(1) { default => 0 };
1 change: 1 addition & 0 deletions tests/cases/parser80/matchExpression2.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading