Skip to content

Commit e813b45

Browse files
authored
Merge pull request microsoft#326 from TysonAndre/match-expression
Support match expression v2 RFC
2 parents f0b68cd + 67f09de commit e813b45

23 files changed

+980
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node\DelimitedList;
8+
9+
use Microsoft\PhpParser\Node\DelimitedList;
10+
11+
class MatchArmConditionList extends DelimitedList {
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node\DelimitedList;
8+
9+
use Microsoft\PhpParser\Node\DelimitedList;
10+
11+
class MatchExpressionArmList extends DelimitedList {
12+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node\Expression;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Node\DelimitedList\MatchExpressionArmList;
11+
use Microsoft\PhpParser\Node\Expression;
12+
use Microsoft\PhpParser\Token;
13+
14+
class MatchExpression extends Expression {
15+
/** @var Token `match` */
16+
public $matchToken;
17+
18+
/** @var Token */
19+
public $openParen;
20+
21+
/** @var Node|null */
22+
public $expression;
23+
24+
/** @var Token */
25+
public $closeParen;
26+
27+
/** @var Token */
28+
public $openBrace;
29+
30+
/** @var MatchExpressionArmList|null */
31+
public $arms;
32+
33+
/** @var Token */
34+
public $closeBrace;
35+
36+
const CHILD_NAMES = [
37+
'matchToken',
38+
39+
'openParen',
40+
'expression',
41+
'closeParen',
42+
43+
'openBrace',
44+
'arms',
45+
'closeBrace',
46+
];
47+
}

src/Node/MatchArm.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Node\DelimitedList\MatchArmConditionList;
11+
use Microsoft\PhpParser\Token;
12+
13+
class MatchArm extends Node {
14+
15+
/** @var MatchArmConditionList */
16+
public $conditionList;
17+
18+
/** @var Token */
19+
public $arrowToken;
20+
21+
/** @var Expression */
22+
public $body;
23+
24+
const CHILD_NAMES = [
25+
'conditionList',
26+
'arrowToken',
27+
'body',
28+
];
29+
}

src/Parser.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
EvalIntrinsicExpression,
3232
ExitIntrinsicExpression,
3333
IssetIntrinsicExpression,
34+
MatchExpression,
3435
MemberAccessExpression,
3536
ParenthesizedExpression,
3637
PrefixUpdateExpression,
@@ -61,6 +62,7 @@
6162
use Microsoft\PhpParser\Node\ForeachValue;
6263
use Microsoft\PhpParser\Node\InterfaceBaseClause;
6364
use Microsoft\PhpParser\Node\InterfaceMembers;
65+
use Microsoft\PhpParser\Node\MatchArm;
6466
use Microsoft\PhpParser\Node\MissingMemberDeclaration;
6567
use Microsoft\PhpParser\Node\NamespaceAliasingClause;
6668
use Microsoft\PhpParser\Node\NamespaceUseGroupClause;
@@ -969,6 +971,7 @@ private function isExpressionStartFn() {
969971
case TokenKind::ObjectCastToken:
970972
case TokenKind::StringCastToken:
971973
case TokenKind::UnsetCastToken:
974+
case TokenKind::MatchKeyword:
972975

973976
// anonymous-function-creation-expression
974977
case TokenKind::StaticKeyword:
@@ -1087,6 +1090,8 @@ private function parsePrimaryExpression($parentNode) {
10871090
return $this->parseQualifiedName($parentNode);
10881091
}
10891092
return $this->parseReservedWordExpression($parentNode);
1093+
case TokenKind::MatchKeyword:
1094+
return $this->parseMatchExpression($parentNode);
10901095
}
10911096
if (\in_array($token->kind, TokenStringMaps::RESERVED_WORDS)) {
10921097
return $this->parseQualifiedName($parentNode);
@@ -3572,6 +3577,59 @@ function ($parentNode) {
35723577
return $anonymousFunctionUseClause;
35733578
}
35743579

3580+
private function parseMatchExpression($parentNode) {
3581+
$matchExpression = new MatchExpression();
3582+
$matchExpression->parent = $parentNode;
3583+
$matchExpression->matchToken = $this->eat1(TokenKind::MatchKeyword);
3584+
$matchExpression->openParen = $this->eat1(TokenKind::OpenParenToken);
3585+
$matchExpression->expression = $this->parseExpression($matchExpression);
3586+
$matchExpression->closeParen = $this->eat1(TokenKind::CloseParenToken);
3587+
$matchExpression->openBrace = $this->eat1(TokenKind::OpenBraceToken);
3588+
$matchExpression->arms = $this->parseDelimitedList(
3589+
DelimitedList\MatchExpressionArmList::class,
3590+
TokenKind::CommaToken,
3591+
$this->isMatchConditionStartFn(),
3592+
$this->parseMatchArmFn(),
3593+
$matchExpression);
3594+
$matchExpression->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
3595+
return $matchExpression;
3596+
}
3597+
3598+
private function isMatchConditionStartFn() {
3599+
return function ($token) {
3600+
return $token->kind === TokenKind::DefaultKeyword ||
3601+
$this->isExpressionStart($token);
3602+
};
3603+
}
3604+
3605+
private function parseMatchArmFn() {
3606+
return function ($parentNode) {
3607+
$matchArm = new MatchArm();
3608+
$matchArm->parent = $parentNode;
3609+
$matchArmConditionList = $this->parseDelimitedList(
3610+
DelimitedList\MatchArmConditionList::class,
3611+
TokenKind::CommaToken,
3612+
$this->isMatchConditionStartFn(),
3613+
$this->parseMatchConditionFn(),
3614+
$matchArm
3615+
);
3616+
$matchArmConditionList->parent = $matchArm;
3617+
$matchArm->conditionList = $matchArmConditionList;
3618+
$matchArm->arrowToken = $this->eat1(TokenKind::DoubleArrowToken);
3619+
$matchArm->body = $this->parseExpression($matchArm);
3620+
return $matchArm;
3621+
};
3622+
}
3623+
3624+
private function parseMatchConditionFn() {
3625+
return function ($parentNode) {
3626+
if ($this->token->kind === TokenKind::DefaultKeyword) {
3627+
return $this->eat1(TokenKind::DefaultKeyword);
3628+
}
3629+
return $this->parseExpression($parentNode);
3630+
};
3631+
}
3632+
35753633
private function parseCloneExpression($parentNode) {
35763634
$cloneExpression = new CloneExpression();
35773635
$cloneExpression->parent = $parentNode;

src/PhpTokenizer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// The replacement value is arbitrary - it just has to be different from other values of token constants.
1111
define(__NAMESPACE__ . '\T_COALESCE_EQUAL', defined('T_COALESCE_EQUAL') ? constant('T_COALESCE_EQUAL') : 'T_COALESCE_EQUAL');
1212
define(__NAMESPACE__ . '\T_FN', defined('T_FN') ? constant('T_FN') : 'T_FN');
13+
// 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.
14+
define(__NAMESPACE__ . '\T_MATCH', defined('T_MATCH') ? constant('T_MATCH') : 'T_MATCH');
1315

1416
/**
1517
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
@@ -206,6 +208,7 @@ public static function getTokensArrayFromContent(
206208
T_INTERFACE => TokenKind::InterfaceKeyword,
207209
T_ISSET => TokenKind::IsSetKeyword,
208210
T_LIST => TokenKind::ListKeyword,
211+
T_MATCH => TokenKind::MatchKeyword,
209212
T_NAMESPACE => TokenKind::NamespaceKeyword,
210213
T_NEW => TokenKind::NewKeyword,
211214
T_LOGICAL_OR => TokenKind::OrKeyword,

src/TokenKind.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class TokenKind {
8686
const YieldKeyword = 166;
8787
const YieldFromKeyword = 167;
8888
const FnKeyword = 168;
89+
const MatchKeyword = 169;
8990

9091
const OpenBracketToken = 201;
9192
const CloseBracketToken = 202;

tests/ParserGrammarTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function testOutputTreeClassificationAndLength($testCaseFile, $expectedTo
7676

7777
const FILE_PATTERN = __DIR__ . "/cases/parser/*";
7878
const PHP74_FILE_PATTERN = __DIR__ . "/cases/parser74/*";
79+
const PHP80_FILE_PATTERN = __DIR__ . "/cases/parser80/*";
7980

8081
public function treeProvider() {
8182
$testCases = glob(self::FILE_PATTERN . ".php");
@@ -98,6 +99,13 @@ public function treeProvider() {
9899
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
99100
}
100101
}
102+
if (PHP_VERSION_ID >= 80000) {
103+
$testCases = glob(self::PHP80_FILE_PATTERN . ".php");
104+
foreach ($testCases as $testCase) {
105+
$testProviderArray[basename($testCase)] = [$testCase, $testCase . ".tree", $testCase . ".diag"];
106+
}
107+
}
108+
101109

102110
return $testProviderArray;
103111
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
match(2+2) {}; // UnhandledMatchError
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)