Skip to content

Commit 7e4b291

Browse files
committed
Add support of suffixed variadic syntax
1 parent cab16b3 commit 7e4b291

File tree

2 files changed

+117
-30
lines changed

2 files changed

+117
-30
lines changed

resources/grammar/callable.pp2

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ CallableParameter
5050
: MaybeDefaultCallableParameter()
5151
;
5252

53-
// Parses "<param>" or "<param> =" expression
53+
// Expects expression:
54+
// - "<param>"
55+
// - "<param> ="
5456
MaybeDefaultCallableParameter -> {
5557
if (\count($children) === 1) {
5658
return $children[0];
@@ -63,62 +65,133 @@ MaybeDefaultCallableParameter -> {
6365
$children[0]->optional = true;
6466
return $children[0];
6567
}
66-
: MaybeNamedCallableParameter() <T_ASSIGN>?
68+
: ( MaybePrefixedVariadicTypedNamedCallableParameter()
69+
| MaybeModifiersNamedCallableParameter() )
70+
<T_ASSIGN>?
6771
;
6872

69-
// Parses "$variable" or "<param> $variable", or "<param>" expression
70-
MaybeNamedCallableParameter -> {
71-
if ($children instanceof Node\Stmt\Callable\CallableParameterNode) {
73+
// Expects expression:
74+
// - "&...<var_param>"
75+
// - "...&<var_param>"
76+
// - "...<var_param>"
77+
// - "&<var_param>"
78+
// - "<var_param>"
79+
MaybeModifiersNamedCallableParameter -> {
80+
if (!\is_array($children)) {
7281
return $children;
7382
}
7483

75-
if (\count($children) === 1) {
76-
return $children[0];
84+
$result = \end($children);
85+
86+
foreach ($children as $modifier) {
87+
if ($modifier instanceof \Phplrt\Contracts\Lexer\TokenInterface) {
88+
switch ($modifier->getName()) {
89+
case 'T_AMP':
90+
$result->output = true;
91+
break;
92+
case 'T_ELLIPSIS':
93+
if ($result->variadic) {
94+
throw SemanticException::fromVariadicRedefinition($offset);
95+
}
96+
$result->variadic = true;
97+
break;
98+
}
99+
}
77100
}
78101

79-
$children[0]->name = $children[1];
80-
return $children[0];
102+
return $result;
81103
}
82-
: MaybeVariableCallableParameter()
83-
| MaybeVariadicCallableParameter() VariableLiteral()?
104+
: <T_AMP> <T_ELLIPSIS> MaybeSuffixedVariadicNamedCallableParameter()
105+
| <T_ELLIPSIS> <T_AMP> MaybeSuffixedVariadicNamedCallableParameter()
106+
| <T_ELLIPSIS> MaybeSuffixedVariadicNamedCallableParameter()
107+
| <T_AMP> MaybeSuffixedVariadicNamedCallableParameter()
108+
| MaybeSuffixedVariadicNamedCallableParameter()
84109
;
85110

86-
// Parses "$variable" expression
87-
MaybeVariableCallableParameter -> {
88-
return new Node\Stmt\Callable\CallableParameterNode(null, $children[0]);
111+
// Expects expression:
112+
// - "$<var>..."
113+
// - "$<var>"
114+
MaybeSuffixedVariadicNamedCallableParameter -> {
115+
$result = new Node\Stmt\Callable\CallableParameterNode(null, $children[0]);
116+
117+
if (\count($children) !== 1) {
118+
$result->variadic = true;
119+
}
120+
121+
return $result;
89122
}
90123
: VariableLiteral()
124+
<T_ELLIPSIS>?
91125
;
92126

93-
// Parses "...<param>" or "<param>...", or "<param>" expression
94-
MaybeVariadicCallableParameter -> {
95-
if (count($children) === 1) {
127+
MaybePrefixedVariadicTypedNamedCallableParameter -> {
128+
if (\count($children) === 1) {
96129
return $children[0];
97130
}
98131

99-
if ($children[0] instanceof Node\Stmt\Callable\ParameterNode) {
100-
$children[0]->variadic = true;
101-
return $children[0];
132+
if ($children[1]->variadic) {
133+
throw SemanticException::fromVariadicRedefinition($offset);
102134
}
103135

104136
$children[1]->variadic = true;
105137
return $children[1];
106138
}
107-
: <T_ELLIPSIS> MaybeOutputCallableParameter() // Prefixed variadic argument (Psalm format)
108-
| MaybeOutputCallableParameter() <T_ELLIPSIS>? // Suffixed variadic argument (PhpStan + Psalm)
139+
: <T_ELLIPSIS>? MaybeTypedNamedCallableParameter()
109140
;
110141

111-
// Parses "<param>&" or "<param>" expression
112-
MaybeOutputCallableParameter -> {
113-
$argument = new Node\Stmt\Callable\CallableParameterNode($children[0]);
142+
// Expects expression:
143+
// - "<typed_param> $<var>"
144+
// - "<typed_param>"
145+
MaybeTypedNamedCallableParameter -> {
146+
if (\count($children) === 1) {
147+
return $children[0];
148+
}
114149

115-
if (\count($children) !== 1) {
116-
$argument->output = true;
150+
$children[0]->name = $children[1];
151+
return $children[0];
152+
}
153+
: MaybeModifiersTypedCallableParameter()
154+
VariableLiteral()?
155+
;
156+
157+
// Expects expression:
158+
// - "<type>...&"
159+
// - "<type>&..."
160+
// - "<type>&"
161+
// - "<type>..."
162+
// - "<type>"
163+
MaybeModifiersTypedCallableParameter -> {
164+
$result = \reset($children);
165+
166+
foreach ($children as $modifier) {
167+
if ($modifier instanceof \Phplrt\Contracts\Lexer\TokenInterface) {
168+
switch ($modifier->getName()) {
169+
case 'T_AMP':
170+
$result->output = true;
171+
break;
172+
case 'T_ELLIPSIS':
173+
if ($result->variadic) {
174+
throw SemanticException::fromVariadicRedefinition($offset);
175+
}
176+
$result->variadic = true;
177+
break;
178+
}
179+
}
117180
}
118181

119-
return $argument;
182+
return $result;
183+
}
184+
: TypedCallableParameter()
185+
( <T_AMP> <T_ELLIPSIS>?
186+
| <T_ELLIPSIS> <T_AMP>? )?
187+
;
188+
189+
// Expects expression:
190+
// - "<type>"
191+
TypedCallableParameter -> {
192+
return new Node\Stmt\Callable\CallableParameterNode($children[0]);
120193
}
121-
: Type() <T_AMP>?
194+
: Type()
122195
;
123196

124197
CallableReturnType

src/Exception/SemanticException.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class SemanticException extends \LogicException implements ParserExceptionInterf
1212

1313
final public const ERROR_CODE_VARIADIC_WITH_DEFAULT = 0x03;
1414

15-
final public const ERROR_CODE_INVALID_OPERATOR = 0x04;
15+
final public const ERROR_CODE_VARIADIC_ALREADY_VARIADIC = 0x04;
16+
17+
final public const ERROR_CODE_INVALID_OPERATOR = 0x05;
1618

1719
protected const CODE_LAST = self::ERROR_CODE_INVALID_OPERATOR;
1820

@@ -73,6 +75,18 @@ public static function fromVariadicWithDefault(int $offset = 0): self
7375
return new static($offset, $message, self::ERROR_CODE_VARIADIC_WITH_DEFAULT);
7476
}
7577

78+
/**
79+
* @param int<0, max> $offset
80+
*
81+
* @return static
82+
*/
83+
public static function fromVariadicRedefinition(int $offset = 0): self
84+
{
85+
$message = 'Either prefix or postfix variadic syntax should be used, but not both';
86+
87+
return new static($offset, $message, self::ERROR_CODE_VARIADIC_ALREADY_VARIADIC);
88+
}
89+
7690
/**
7791
* @param non-empty-string $operator
7892
* @param int<0, max> $offset

0 commit comments

Comments
 (0)