Skip to content

Commit 8aea70f

Browse files
author
Kirill Nesmeyanov
committed
Add conditional types support and parser features
1 parent 1f9f289 commit 8aea70f

20 files changed

+406
-169
lines changed

bin/build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ $grammar = (new Compiler())
3737
->withClassUsage('TypeLang\\Parser\\Node')
3838
->withClassUsage('TypeLang\\Parser\\Exception')
3939
->withClassUsage('TypeLang\\Parser\\Exception\\SemanticException')
40+
->withClassUsage('TypeLang\\Parser\\Exception\\FeatureNotAllowedException')
4041
;
4142

4243
file_put_contents(__DIR__ . '/../resources/grammar.php', $grammar->generate());

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"psr/cache": "^1.0|^2.0|^3.0",
1414
"psr/simple-cache": "^1.0|^2.0|^3.0",
1515
"psr/log": "^1.0|^2.0|^3.0",
16-
"phplrt/lexer": "^3.4",
17-
"phplrt/parser": "^3.4"
16+
"phplrt/lexer": "^3.5",
17+
"phplrt/parser": "^3.5"
1818
},
1919
"autoload": {
2020
"psr-4": {
@@ -25,7 +25,7 @@
2525
"nikic/php-parser": "^4.17",
2626
"phpdocumentor/reflection-docblock": "^5.3",
2727
"friendsofphp/php-cs-fixer": "^3.35",
28-
"phplrt/compiler": "^3.4",
28+
"phplrt/compiler": "^3.5",
2929
"phpunit/phpunit": "^10.4",
3030
"rector/rector": "^0.18",
3131
"vimeo/psalm": "^5.15"

resources/grammar.php

Lines changed: 224 additions & 140 deletions
Large diffs are not rendered by default.

resources/grammar.pp2

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
%include grammar/callable // callable(mixed): void
1313
%include grammar/shape-fields // array { key: int, ...<TKey, TValue> }
1414
%include grammar/named-type // Map<int<0, max>, non-empty-string>
15+
%include grammar/ternary // T is A ? B : C
1516

1617
%pragma root Type
1718

@@ -26,9 +27,25 @@
2627
*/
2728

2829
#Type
29-
: LogicalType()
30+
: Expression()
3031
;
3132

33+
/**
34+
* -----------------------------------------------------------------------------
35+
* Ternary Expression
36+
* -----------------------------------------------------------------------------
37+
*
38+
* Ternary conditional expressions, like:
39+
* - T is A ? B : C - for equality type conditions.
40+
* - T is not A ? B ? C - for non-equality type conditions.
41+
*
42+
*/
43+
44+
Expression
45+
: TernaryExpressionOrLogicalType()
46+
;
47+
48+
3249
/**
3350
* -----------------------------------------------------------------------------
3451
* Logical Statements
@@ -89,7 +106,7 @@ PrefixedNullableType -> {
89106

90107
return $children;
91108
}
92-
: <T_NULLABLE> TypesList()
109+
: <T_QMARK> TypesList()
93110
| TypesList()
94111
;
95112

resources/grammar/callable.pp2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
CallableType -> {
33
$name = \array_shift($children);
44

5+
if ($this->callables === false) {
6+
throw FeatureNotAllowedException::fromFeature('callable types', $offset);
7+
}
8+
59
$parameters = isset($children[0]) && $children[0] instanceof Node\Stmt\Callable\ParametersListNode
610
? \array_shift($children)
711
: new Node\Stmt\Callable\ParametersListNode();

resources/grammar/common.pp2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Identifier -> {
2020
return new Node\Identifier($children->getValue());
2121
}
2222
: <T_NAME>
23+
| <T_EQ>
2324
| <T_BOOL_LITERAL>
2425
| <T_NULL_LITERAL>
2526
;

resources/grammar/lexemes.pp2

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
// Identifier
1717

18-
%token T_THIS \$this
18+
%token T_THIS \$this(?![a-zA-Z0-9\-_\x80-\xff])
19+
%token T_EQ (?i)is(?![a-zA-Z0-9\-_\x80-\xff])
20+
%token T_NEQ (?i)is\h+not(?![a-zA-Z0-9\-_\x80-\xff])
1921
%token T_VARIABLE \$[a-zA-Z_\x80-\xff][a-zA-Z0-9\-_\x80-\xff]*
2022
%token T_NAME [a-zA-Z_\x80-\xff][a-zA-Z0-9\-_\x80-\xff]*
2123

@@ -36,7 +38,7 @@
3638
%token T_COLON :
3739
%token T_ASSIGN =
3840
%token T_NS_DELIMITER \\
39-
%token T_NULLABLE \?
41+
%token T_QMARK \?
4042
%token T_NOT \!
4143
%token T_OR \|
4244
%token T_AMP &

resources/grammar/literals.pp2

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11

22
Literal
3+
: RawLiteral()
4+
| ConstMaskLiteral()
5+
| ClassConstLiteral()
6+
;
7+
8+
RawLiteral -> {
9+
if ($this->literals === false) {
10+
throw FeatureNotAllowedException::fromFeature('literal values', $offset);
11+
}
12+
return $children;
13+
}
314
: StringLiteral()
415
| FloatLiteral()
516
| IntLiteral()
617
| BoolLiteral()
718
| NullLiteral()
8-
| ConstMaskLiteral()
9-
| ClassConstLiteral()
1019
;
1120

1221
VariableLiteral -> {

resources/grammar/shape-fields.pp2

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ ShapeFields -> {
44
return new Node\Stmt\Shape\FieldsListNode();
55
}
66

7+
if ($this->shapes === false) {
8+
throw FeatureNotAllowedException::fromFeature('shape fields', $offset);
9+
}
10+
711
$parameters = null;
812

913
if (\end($children) instanceof Node\Stmt\Template\ArgumentsListNode) {
@@ -90,7 +94,7 @@ ExplicitField -> {
9094
default => new Node\Stmt\Shape\NamedFieldNode($name, $value, $optional),
9195
};
9296
}
93-
: ShapeKey() (<T_NULLABLE>)? ::T_COLON:: ShapeValue()
97+
: ShapeKey() (<T_QMARK>)? ::T_COLON:: ShapeValue()
9498
;
9599

96100
ImplicitField -> {

resources/grammar/ternary.pp2

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
TernaryExpressionOrLogicalType -> {
3+
$count = \count($children);
4+
5+
if ($count === 1) {
6+
return $children[0];
7+
}
8+
9+
if ($this->conditional === false) {
10+
throw FeatureNotAllowedException::fromFeature('conditional expressions', $offset);
11+
}
12+
13+
$condition = match ($children[1]->getName()) {
14+
'T_EQ' => new Node\Stmt\Condition\EqualConditionNode(
15+
$children[0],
16+
$children[2],
17+
),
18+
'T_NEQ' => new Node\Stmt\Condition\NotEqualConditionNode(
19+
$children[0],
20+
$children[2],
21+
),
22+
default => throw new SemanticException(
23+
\sprintf('Invalid conditional operator "%s"', $children[1]->getValue()),
24+
$offset,
25+
SemanticException::CODE_INVALID_OPERATOR,
26+
),
27+
};
28+
29+
return new Node\Stmt\TernaryConditionNode(
30+
$condition,
31+
$children[3],
32+
$children[4],
33+
);
34+
}
35+
: LogicalType() OptionalTernaryExpressionSuffix()
36+
| VariableLiteral() TernaryExpressionSuffix()
37+
;
38+
39+
OptionalTernaryExpressionSuffix
40+
: TernaryExpressionSuffix()?
41+
;
42+
43+
TernaryExpressionSuffix
44+
: (<T_EQ>|<T_NEQ>) (Type()|VariableLiteral())
45+
::T_QMARK:: Type()
46+
::T_COLON:: Type()
47+
;

0 commit comments

Comments
 (0)