├── .editorconfig
├── tests
├── Parser
│ ├── data
│ │ ├── EXPR_SYNTAX_NUM_PREFIX.txt
│ │ ├── EXPR_SYNTAX_PARAM.txt
│ │ ├── TYPE.txt
│ │ ├── generated
│ │ │ └── syntax-checks.json.gz
│ │ ├── MAGIC_CONSTANT.txt
│ │ ├── CAST.txt
│ │ ├── __ROOT__.txt
│ │ ├── STMT.txt
│ │ ├── $v.txt
│ │ ├── ident.txt
│ │ ├── EXPR_SYNTAX_NUM_PART.txt
│ │ ├── EXPR_CTX.txt
│ │ ├── EXPR_CTX_CONTEXT.txt
│ │ ├── BINOP.txt
│ │ ├── STMT_SINGLE.txt
│ │ ├── EXPR_STRING_PART.txt
│ │ ├── WORD_CONTEXT.txt
│ │ ├── STMT_COMPOUND.txt
│ │ ├── EXPR.txt
│ │ ├── EXPR_STRING.txt
│ │ ├── WORD_TOKEN.txt
│ │ └── EXPR_SYNTAX.txt
│ └── ParserTest.php
├── Nodes
│ ├── data
│ │ ├── KEY.txt
│ │ ├── __ROOT__.txt
│ │ └── $v.txt
│ ├── NodeTest.php
│ └── CompoundNodeTest.php
├── Testing
│ ├── Fuzz
│ │ ├── FuzzRule.php
│ │ ├── Permute.php
│ │ ├── Cycle.php
│ │ └── WeightedPermute.php
│ ├── AssertThrows.php
│ └── NodeAssertions.php
├── LiteralParser
│ └── data
│ │ ├── single_quoted_strings.txt
│ │ └── double_quoted_strings.txt
├── SystemTest.php
└── LexerTest.php
├── .gitignore
├── src
├── Exception
│ ├── CoercionException.php
│ ├── SyntaxException.php
│ ├── TodoException.php
│ ├── LiteralParsingException.php
│ ├── ParseException.php
│ ├── TreeException.php
│ ├── PhiException.php
│ └── ValidationException.php
├── Nodes
│ ├── Statements
│ │ ├── LoopStatement.php
│ │ ├── Catch_.php
│ │ ├── DoWhileStatement.php
│ │ ├── Finally_.php
│ │ ├── NopStatement.php
│ │ ├── GotoStatement.php
│ │ ├── SwitchCase.php
│ │ ├── TryStatement.php
│ │ ├── SwitchStatement.php
│ │ ├── UnsetStatement.php
│ │ ├── DeclareStatement.php
│ │ ├── StaticVariable.php
│ │ ├── DeclareDirective.php
│ │ ├── NamespaceStatement.php
│ │ ├── StaticVariableStatement.php
│ │ ├── Else_.php
│ │ ├── LabelStatement.php
│ │ ├── EchoStatement.php
│ │ ├── ThrowStatement.php
│ │ ├── UseStatement.php
│ │ ├── BreakStatement.php
│ │ ├── GlobalStatement.php
│ │ ├── ContinueStatement.php
│ │ ├── Elseif_.php
│ │ ├── WhileStatement.php
│ │ ├── InlineHtmlStatement.php
│ │ ├── ExpressionStatement.php
│ │ ├── ReturnStatement.php
│ │ ├── UseAlias.php
│ │ ├── ForStatement.php
│ │ ├── IfStatement.php
│ │ ├── UseDeclaration.php
│ │ ├── BlockStatement.php
│ │ ├── ConstStatement.php
│ │ ├── FunctionStatement.php
│ │ ├── ForeachStatement.php
│ │ └── LoopFlowStatement.php
│ ├── Statement.php
│ ├── Oop
│ │ ├── OopDeclaration.php
│ │ ├── OopMember.php
│ │ ├── Method.php
│ │ ├── TraitUseModification.php
│ │ ├── Property.php
│ │ ├── TraitUseAs.php
│ │ ├── TraitMethodRef.php
│ │ ├── TraitDeclaration.php
│ │ ├── Extends_.php
│ │ ├── TraitUseInsteadof.php
│ │ ├── Implements_.php
│ │ ├── InterfaceDeclaration.php
│ │ ├── TraitUse.php
│ │ ├── ClassConstant.php
│ │ └── ClassDeclaration.php
│ ├── Helpers
│ │ ├── MemberName.php
│ │ ├── Key.php
│ │ ├── NormalMemberName.php
│ │ ├── VariableMemberName.php
│ │ ├── Default_.php
│ │ ├── Argument.php
│ │ ├── ReturnType.php
│ │ └── Parameter.php
│ ├── Expressions
│ │ ├── NewExpression.php
│ │ ├── StringLiteral.php
│ │ ├── IncludeLikeExpression.php
│ │ ├── StringInterpolation
│ │ │ ├── InterpolatedStringVariable.php
│ │ │ ├── InterpolatedStringPart.php
│ │ │ ├── NormalInterpolatedStringVariable.php
│ │ │ ├── ConfusingInterpolatedStringVariable.php
│ │ │ ├── VariableInterpolatedStringVariable.php
│ │ │ ├── ConfusingInterpolatedStringVariableName.php
│ │ │ ├── notes.md
│ │ │ ├── BracedInterpolatedStringVariable.php
│ │ │ ├── NormalInterpolatedStringVariableArrayAccess.php
│ │ │ └── ConstantInterpolatedStringPart.php
│ │ ├── ConstantStringLiteral.php
│ │ ├── LongArrayExpression.php
│ │ ├── MagicConstant.php
│ │ ├── NumberLiteral.php
│ │ ├── ShortArrayExpression.php
│ │ ├── VariableExpression.php
│ │ ├── AnonymousFunctionUse.php
│ │ ├── CastExpression.php
│ │ ├── BinopExpression.php
│ │ ├── EvalExpression.php
│ │ ├── PrintExpression.php
│ │ ├── PreDecrementExpression.php
│ │ ├── PreIncrementExpression.php
│ │ ├── PostDecrementExpression.php
│ │ ├── PostIncrementExpression.php
│ │ ├── EmptyExpression.php
│ │ ├── IncludeExpression.php
│ │ ├── MagicConstant
│ │ │ ├── DirMagicConstant.php
│ │ │ ├── FileMagicConstant.php
│ │ │ ├── LineMagicConstant.php
│ │ │ ├── ClassMagicConstant.php
│ │ │ ├── TraitMagicConstant.php
│ │ │ ├── MethodMagicConstant.php
│ │ │ ├── FunctionMagicConstant.php
│ │ │ └── NamespaceMagicConstant.php
│ │ ├── NormalVariableExpression.php
│ │ ├── RequireExpression.php
│ │ ├── ExececutionExpression.php
│ │ ├── VariableVariableExpression.php
│ │ ├── ExitExpression.php
│ │ ├── SingleQuotedStringLiteral.php
│ │ ├── IncludeOnceExpression.php
│ │ ├── RequireOnceExpression.php
│ │ ├── Cast
│ │ │ ├── ArrayCastExpression.php
│ │ │ ├── FloatCastExpression.php
│ │ │ ├── UnsetCastExpression.php
│ │ │ ├── BooleanCastExpression.php
│ │ │ ├── IntegerCastExpression.php
│ │ │ ├── ObjectCastExpression.php
│ │ │ └── StringCastExpression.php
│ │ ├── SuppressErrorsExpression.php
│ │ ├── CombinedAssignExpression.php
│ │ ├── NameExpression.php
│ │ ├── CloneExpression.php
│ │ ├── AssignExpression.php
│ │ ├── CombinedAssignment
│ │ │ ├── AddAssignExpression.php
│ │ │ ├── PowerAssignExpression.php
│ │ │ ├── DivideAssignExpression.php
│ │ │ ├── ModuloAssignExpression.php
│ │ │ ├── ConcatAssignExpression.php
│ │ │ ├── MultiplyAssignExpression.php
│ │ │ ├── SubtractAssignExpression.php
│ │ │ ├── CoalesceAssignExpression.php
│ │ │ ├── BitwiseOrAssignExpression.php
│ │ │ ├── ShiftLeftAssignExpression.php
│ │ │ ├── BitwiseAndAssignExpression.php
│ │ │ ├── BitwiseXorAssignExpression.php
│ │ │ └── ShiftRightAssignExpression.php
│ │ ├── FloatLiteral.php
│ │ ├── CrementExpression.php
│ │ ├── AnonymousFunctionUseBinding.php
│ │ ├── PowerExpression.php
│ │ ├── ModuloExpression.php
│ │ ├── ConcatExpression.php
│ │ ├── IsEqualExpression.php
│ │ ├── ArrayItem.php
│ │ ├── LessThanExpression.php
│ │ ├── CoalesceExpression.php
│ │ ├── ShiftLeftExpression.php
│ │ ├── SymbolOrExpression.php
│ │ ├── BitwiseOrExpression.php
│ │ ├── IsNotEqualExpression.php
│ │ ├── KeywordOrExpression.php
│ │ ├── ShiftRightExpression.php
│ │ ├── SpaceshipExpression.php
│ │ ├── SymbolAndExpression.php
│ │ ├── BitwiseAndExpression.php
│ │ ├── BitwiseXorExpression.php
│ │ ├── IsIdenticalExpression.php
│ │ ├── KeywordAndExpression.php
│ │ ├── KeywordXorExpression.php
│ │ ├── IsNotIdenticalExpression.php
│ │ ├── LessThanOrEqualsExpression.php
│ │ ├── NowdocStringLiteral.php
│ │ ├── YieldFromExpression.php
│ │ ├── GreaterThanOrEqualsExpression.php
│ │ ├── GreaterThanExpression.php
│ │ ├── NotExpression.php
│ │ ├── ArrowFunctionExpression.php
│ │ ├── InterpolatedStringLiteral.php
│ │ ├── DoubleQuotedStringLiteral.php
│ │ ├── AnonymousClassNewExpression.php
│ │ ├── AddExpression.php
│ │ ├── DivideExpression.php
│ │ ├── MultiplyExpression.php
│ │ ├── SubtractExpression.php
│ │ ├── NormalAnonymousFunctionExpression.php
│ │ ├── NormalNewExpression.php
│ │ ├── AliasExpression.php
│ │ ├── PropertyAccessExpression.php
│ │ ├── MethodCallExpression.php
│ │ ├── AnonymousFunctionExpression.php
│ │ ├── UnaryPlusExpression.php
│ │ ├── UnaryMinusExpression.php
│ │ ├── BitwiseNotExpression.php
│ │ ├── ConstantAccessExpression.php
│ │ ├── IntegerLiteral.php
│ │ ├── ClassNameResolutionExpression.php
│ │ ├── StaticPropertyAccessExpression.php
│ │ ├── StaticMethodCallExpression.php
│ │ ├── StaticExpression.php
│ │ ├── HeredocStringLiteral.php
│ │ ├── ParenthesizedExpression.php
│ │ ├── IssetExpression.php
│ │ ├── YieldExpression.php
│ │ ├── TernaryExpression.php
│ │ └── ListExpression.php
│ ├── Type.php
│ ├── Blocks
│ │ ├── RegularBlock.php
│ │ ├── AlternativeFormatBlock.php
│ │ └── ImplicitBlock.php
│ ├── ValidationTraits
│ │ ├── SymmetricPrecedence.php
│ │ ├── ForbidTrailingSeparator.php
│ │ ├── ValidateYieldContext.php
│ │ ├── NumericBinopExpression.php
│ │ ├── DocStringLiteral.php
│ │ ├── UnaryOpExpression.php
│ │ ├── IsConstantClassNameHelper.php
│ │ ├── IsObjectAccessibleHelper.php
│ │ ├── IsNewableHelper.php
│ │ ├── NonAssocBinopExpression.php
│ │ ├── LeftAssocBinopExpression.php
│ │ └── RightAssocBinopExpression.php
│ ├── Types
│ │ ├── NullableType.php
│ │ ├── SpecialType.php
│ │ └── NamedType.php
│ ├── Block.php
│ ├── RootNode.php
│ └── Expression.php
├── Specification.php
├── WrapperNode.php
├── Parser.php
├── Specifications
│ ├── IsToken.php
│ ├── IsInstanceOf.php
│ ├── HasParent.php
│ ├── Or_.php
│ └── And_.php
├── PhpVersion.php
└── Util
│ ├── Util.php
│ └── Console.php
├── phpunit.xml
├── meta
├── resources
│ └── nodedefs
│ │ └── types.php
└── bin
│ ├── generate_nodes.php
│ └── optimize_parser.php
├── phpstan.neon
└── composer.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = tab
3 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_SYNTAX_NUM_PREFIX.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | {*empty*}
4 | 0
5 | 1
6 | 0x
7 | 0b
8 |
--------------------------------------------------------------------------------
/tests/Nodes/data/KEY.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | Nodes\Helpers\Key::__instantiateUnchecked(EXPR, `=>`)
4 | null
5 |
--------------------------------------------------------------------------------
/tests/Nodes/data/__ROOT__.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | use Phi\{Nodes, Nodes\Expressions as Expr, Token, TokenType}; return EXPR;
4 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_SYNTAX_PARAM.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | $v
4 | $v = ident
5 | ...$v
6 | &$v
7 | &$v = ident
8 | {*empty*}
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /cache/
3 | /tests/Parser/data/generated/syntax-checks.json
4 | /vendor/
5 | /composer.lock
6 |
7 | .phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/tests/Parser/data/TYPE.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | ident
4 | ident\ident
5 | ?ident
6 | array
7 | string
8 | iterable
9 | ident|ident
10 |
--------------------------------------------------------------------------------
/tests/Parser/data/generated/syntax-checks.json.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rainbow-alex/phi/HEAD/tests/Parser/data/generated/syntax-checks.json.gz
--------------------------------------------------------------------------------
/src/Exception/CoercionException.php:
--------------------------------------------------------------------------------
1 | ident $a + $b - $c
5 | // this reduces the chances of false positives in assertions
6 |
7 | $a
8 | $b
9 | $c
10 | $d
11 | $e
12 | $f
13 | $g
14 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/LongArrayExpression.php:
--------------------------------------------------------------------------------
1 | */
12 | abstract public function generateRhs(&$state);
13 | }
14 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/VariableExpression.php:
--------------------------------------------------------------------------------
1 | ident --> foo::bar()->baz
5 | // this reduces the chances of false positives in assertions
6 |
7 | foo
8 | bar
9 | baz
10 | qux
11 | quux
12 | corge
13 |
--------------------------------------------------------------------------------
/src/Nodes/Blocks/AlternativeFormatBlock.php:
--------------------------------------------------------------------------------
1 | getType()), $got);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Nodes/Helpers/NormalMemberName.php:
--------------------------------------------------------------------------------
1 | getToken()->getSource();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Nodes/Blocks/ImplicitBlock.php:
--------------------------------------------------------------------------------
1 | getStatement()];
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests/
6 |
7 |
8 |
9 |
10 | src/
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/NormalInterpolatedStringVariable.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CastExpression.php:
--------------------------------------------------------------------------------
1 | THIS] [[4]]
6 | [THIS => &THIS] [[4]]
7 | [THIS, THIS] [[4]]
8 | list(THIS) [[4]]
9 | list(&THIS) [[4]]
10 | list(THIS => THIS) [[4]]
11 | list(THIS => &THIS) [[4]]
12 | list(THIS, THIS) [[4]]
13 | $v->ident [[4]]
14 | $v::$v [[4]]
15 | ident()
16 | (THIS) [[4]]
17 | $v[]
18 | $v[$v]
19 | $v
20 | 0
21 | {*empty*} [[4]]
22 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/Else_.php:
--------------------------------------------------------------------------------
1 | getBlock()->convertToPhpParser()
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/SymmetricPrecedence.php:
--------------------------------------------------------------------------------
1 | getPrecedence();
14 | }
15 |
16 | public function getRightPrecedence(): int
17 | {
18 | return $this->getPrecedence();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/LabelStatement.php:
--------------------------------------------------------------------------------
1 | getLabel()->getSource());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/EchoStatement.php:
--------------------------------------------------------------------------------
1 | getExpressions()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ThrowStatement.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_CTX_CONTEXT.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | // test read/write/alias expression context checking
4 |
5 | EXPR_CTX = $v;
6 | $v = EXPR_CTX;
7 |
8 | EXPR_CTX =& $v;
9 | $v =& EXPR_CTX;
10 |
11 | foreach ($v as EXPR_CTX) {}
12 | foreach ($v as &EXPR_CTX) {}
13 | foreach ($v as EXPR_CTX => $v) {}
14 | foreach ($v as $v => &EXPR_CTX) {}
15 |
16 | ident(EXPR_CTX);
17 | ident(...EXPR_CTX);
18 |
19 | const ident = EXPR;
20 | const ident = EXPR_CONST;
21 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/BinopExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->isConstant() && $this->getRight()->isConstant();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/EvalExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PrintExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/UseStatement.php:
--------------------------------------------------------------------------------
1 | getDeclarations()->convertToPhpParser()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PreDecrementExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PreIncrementExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Specifications/IsToken.php:
--------------------------------------------------------------------------------
1 | type = $type;
17 | }
18 |
19 | public function isSatisfiedBy(Node $node): bool
20 | {
21 | return $node instanceof Token && $node->getType() === $this->type;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PostDecrementExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PostIncrementExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/BreakStatement.php:
--------------------------------------------------------------------------------
1 | getLevels();
17 | return new Break_($levels ? $levels->convertToPhpParser() : null);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Specifications/IsInstanceOf.php:
--------------------------------------------------------------------------------
1 | class = $class;
18 | }
19 |
20 | public function isSatisfiedBy(Node $node): bool
21 | {
22 | return $node instanceof $this->class;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/EmptyExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IncludeExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser(), Include_::TYPE_INCLUDE);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/DirMagicConstant.php:
--------------------------------------------------------------------------------
1 | getToken()->getSource(), 1));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/RequireExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser(), Include_::TYPE_REQUIRE);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/GlobalStatement.php:
--------------------------------------------------------------------------------
1 | getVariables()->convertToPhpParser()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Testing/AssertThrows.php:
--------------------------------------------------------------------------------
1 | getParts()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/FileMagicConstant.php:
--------------------------------------------------------------------------------
1 | getName()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/ClassMagicConstant.php:
--------------------------------------------------------------------------------
1 | getLevels();
17 | return new Continue_($levels ? $levels->convertToPhpParser() : null);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/Elseif_.php:
--------------------------------------------------------------------------------
1 | getCondition()->convertToPhpParser(),
18 | $this->getBlock()->convertToPhpParser()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Exception/TreeException.php:
--------------------------------------------------------------------------------
1 | repr() . " is missing '" . $childName . "'", $node);
14 | }
15 |
16 | public static function cantDetachList(Node $node): self
17 | {
18 | return new self("List nodes can't be detached", $node);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ExitExpression.php:
--------------------------------------------------------------------------------
1 | getExpression();
18 | return new Exit_($expr ? $expr->convertToPhpParser() : null);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/MethodMagicConstant.php:
--------------------------------------------------------------------------------
1 | getToken()->getSource(), true));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/WhileStatement.php:
--------------------------------------------------------------------------------
1 | getCondition()->convertToPhpParser(),
18 | $this->getBlock()->convertToPhpParser()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IncludeOnceExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser(), Include_::TYPE_INCLUDE_ONCE);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/RequireOnceExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser(), Include_::TYPE_REQUIRE_ONCE);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Types/NullableType.php:
--------------------------------------------------------------------------------
1 | getType();
17 | }
18 |
19 | public function convertToPhpParser()
20 | {
21 | return new \PhpParser\Node\NullableType($this->getType()->convertToPhpParser());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/FunctionMagicConstant.php:
--------------------------------------------------------------------------------
1 | = self::PHP_NEXT)
18 | {
19 | throw new \InvalidArgumentException('Unsupported PHP version: ' . $phpVersion);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/ArrayCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/FloatCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/UnsetCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MagicConstant/NamespaceMagicConstant.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/InlineHtmlStatement.php:
--------------------------------------------------------------------------------
1 | getContent();
18 | return new InlineHTML($content ? $content->getSource() : "");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/BooleanCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/IntegerCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/ObjectCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/Cast/StringCastExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ExpressionStatement.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Testing/Fuzz/Permute.php:
--------------------------------------------------------------------------------
1 | options = $options;
18 | }
19 |
20 | public function initState(&$state): void
21 | {
22 | }
23 |
24 | public function generateRhs(&$state)
25 | {
26 | return $this->options;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ReturnStatement.php:
--------------------------------------------------------------------------------
1 | getExpression();
18 | return new Return_(
19 | $expression ? $expression->convertToPhpParser() : null
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->isTemporary())
19 | {
20 | throw ValidationException::invalidExpressionInContext($this->getLeft());
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/NameExpression.php:
--------------------------------------------------------------------------------
1 | getName()->convertToPhpParser());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CloneExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->convertToPhpParser());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/ConfusingInterpolatedStringVariable.php:
--------------------------------------------------------------------------------
1 | getVariable()->convertToPhpParser();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Parser/data/BINOP.txt:
--------------------------------------------------------------------------------
1 | {"algo": "weightedPermute", "max": 100}
2 |
3 | =
4 | =&
5 | and
6 | or
7 | xor
8 | &&
9 | ||
10 | ===
11 | !== [[99]]
12 | == [[99]]
13 | != [[99]]
14 | < [[99]]
15 | <= [[99]]
16 | > [[99]]
17 | >= [[99]]
18 | <=> [[99]]
19 | ??
20 | ?:
21 | instanceof
22 | +
23 | - [[99]]
24 | . [[99]]
25 | *
26 | / [[99]]
27 | % [[99]]
28 | &
29 | |
30 | ^
31 | <<
32 | >>
33 | **
34 | +=
35 | -= [[99]]
36 | .= [[99]]
37 | *= [[99]]
38 | /= [[99]]
39 | %= [[99]]
40 | **= [[99]]
41 | &= [[99]]
42 | |= [[99]]
43 | ^= [[99]]
44 | <<= [[99]]
45 | >>= [[99]]
46 | ??=
47 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/VariableInterpolatedStringVariable.php:
--------------------------------------------------------------------------------
1 | getName()->convertToPhpParser());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Nodes/Types/SpecialType.php:
--------------------------------------------------------------------------------
1 | getName()->getType() === TokenType::T_STATIC;
19 | }
20 |
21 | public function convertToPhpParser()
22 | {
23 | return new Identifier($this->getName()->getSource());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Types/NamedType.php:
--------------------------------------------------------------------------------
1 | getName()->isSpecialType())
18 | {
19 | return new Identifier($this->getName()->getParts()->getItems()[0]->getSource());
20 | }
21 | else
22 | {
23 | return $this->getName()->convertToPhpParser();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/UseAlias.php:
--------------------------------------------------------------------------------
1 | getName();
19 |
20 | if (TokenType::isReservedWord($name))
21 | {
22 | throw ValidationException::invalidNameInContext($name);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Testing/Fuzz/Cycle.php:
--------------------------------------------------------------------------------
1 | options = $options;
18 | }
19 |
20 | public function initState(&$state): void
21 | {
22 | $state = 0;
23 | }
24 |
25 | public function generateRhs(&$state)
26 | {
27 | yield $this->options[$state++ % count($this->options)];
28 | $state--;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Parser/ParserTest.php:
--------------------------------------------------------------------------------
1 | parseExpression('list($v)');
23 |
24 | self::assertTrue(true);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/AddAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/FloatLiteral.php:
--------------------------------------------------------------------------------
1 | getPhpVersion()))->parseFloatLiteral($this->getToken()->getSource());
18 | }
19 |
20 | public function convertToPhpParser()
21 | {
22 | return new DNumber($this->getValue());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/PowerAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/DivideAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/ModuloAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CrementExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->isTemporary())
19 | {
20 | throw ValidationException::invalidExpressionInContext($this);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/ConcatAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/MultiplyAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/SubtractAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/ConfusingInterpolatedStringVariableName.php:
--------------------------------------------------------------------------------
1 | getName()->getSource());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/CoalesceAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AnonymousFunctionUseBinding.php:
--------------------------------------------------------------------------------
1 | getVariable()->getSource(), 1)),
20 | $this->hasByReference()
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/BitwiseOrAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/ShiftLeftAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/BitwiseAndAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/BitwiseXorAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CombinedAssignment/ShiftRightAssignExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Nodes/Helpers/Default_.php:
--------------------------------------------------------------------------------
1 | getValue()->isConstant())
18 | {
19 | throw ValidationException::invalidExpressionInContext($this->getValue());
20 | }
21 | }
22 |
23 | public function convertToPhpParser()
24 | {
25 | return $this->getValue()->convertToPhpParser();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Specifications/HasParent.php:
--------------------------------------------------------------------------------
1 | parentSpecification = $parentSpecification;
16 | }
17 |
18 | public function isSatisfiedBy(Node $node): bool
19 | {
20 | while ($node = $node->getParent())
21 | {
22 | if ($this->parentSpecification->isSatisfiedBy($node))
23 | {
24 | return true;
25 | }
26 | }
27 |
28 | return false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Nodes/NodeTest.php:
--------------------------------------------------------------------------------
1 | parse(__FILE__);
19 |
20 | self::assertNodeTreeIsCorrect($ast);
21 | $clone = $ast->clone();
22 |
23 | self::assertNodeTreeIsCorrect($ast);
24 | self::assertNodeTreeIsCorrect($clone);
25 |
26 | self::assertNodeStructEquals($ast, $clone);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Nodes/Helpers/Argument.php:
--------------------------------------------------------------------------------
1 | getExpression()->_validate($this->hasUnpack() ? self::CTX_READ : self::CTX_READ|self::CTX_LENIENT_READ);
18 | }
19 |
20 | public function convertToPhpParser()
21 | {
22 | return new Arg($this->getExpression()->convertToPhpParser(), false, $this->hasUnpack());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ForStatement.php:
--------------------------------------------------------------------------------
1 | $this->getInitExpressions()->convertToPhpParser(),
18 | 'cond' => $this->getConditionExpressions()->convertToPhpParser(),
19 | 'loop' => $this->getStepExpressions()->convertToPhpParser(),
20 | 'stmts' => $this->getBlock()->convertToPhpParser(),
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/ForbidTrailingSeparator.php:
--------------------------------------------------------------------------------
1 | $list
15 | */
16 | private static function forbidTrailingSeparator(SeparatedNodesList $list): void
17 | {
18 | $separators = $list->getSeparators();
19 | if ($trailingSeparator = \end($separators))
20 | {
21 | throw ValidationException::invalidSyntax($trailingSeparator);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/LiteralParser/data/single_quoted_strings.txt:
--------------------------------------------------------------------------------
1 | ' === ERROR
2 | foo === ERROR
3 | "foo" === ERROR
4 | '' === EMPTY
5 | 'foo' === foo
6 | '<0A>' === <0A>
7 | '\<0A>' === \<0A>
8 |
9 | '\' === ERROR
10 | '\\' === \
11 | '\\\' === ERROR
12 | '\\\\' === \\
13 |
14 | 'foo\bar' === foo\bar
15 | 'foo\\bar' === foo\bar
16 | 'foo\\\bar' === foo\\bar
17 | 'foo\\\\bar' === foo\\bar
18 |
19 | 'foo'bar' === ERROR
20 | 'foo\'bar' === foo'bar
21 | 'foo\\'bar' === ERROR
22 | 'foo\\\'bar' === foo\'bar
23 |
24 | 'foo"bar' === foo"bar
25 | 'foo\"bar' === foo\"bar
26 | 'foo\\"bar' === foo\"bar
27 | 'foo\nbar' === foo\nbar
28 | 'foo\tbar' === foo\tbar
29 | 'foo\x07bar' === foo\x07bar
30 |
--------------------------------------------------------------------------------
/src/Specifications/Or_.php:
--------------------------------------------------------------------------------
1 | specifications = $specifications;
18 | }
19 |
20 | public function isSatisfiedBy(Node $node): bool
21 | {
22 | foreach ($this->specifications as $specification)
23 | {
24 | if ($specification->isSatisfiedBy($node))
25 | {
26 | return true;
27 | }
28 | }
29 |
30 | return false;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Specifications/And_.php:
--------------------------------------------------------------------------------
1 | specifications = $specifications;
18 | }
19 |
20 | public function isSatisfiedBy(Node $node): bool
21 | {
22 | foreach ($this->specifications as $specification)
23 | {
24 | if (!$specification->isSatisfiedBy($node))
25 | {
26 | return false;
27 | }
28 | }
29 |
30 | return true;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/meta/resources/nodedefs/types.php:
--------------------------------------------------------------------------------
1 | token("questionMark", T::S_QUESTION_MARK)
14 | ->node("type", Type::class)
15 | ->constructor("type"),
16 |
17 | (new NodeDef(NamedType::class))
18 | ->node("name", Name::class)
19 | ->constructor("name"),
20 |
21 | (new NodeDef(SpecialType::class))
22 | ->token("name", [T::T_ARRAY, T::T_CALLABLE, T::T_STATIC])
23 | ->constructor("name"),
24 | ];
25 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PowerExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Block.php:
--------------------------------------------------------------------------------
1 |
15 | * @phpstan-return iterable
16 | */
17 | abstract public function getStatements();
18 |
19 | public function convertToPhpParser()
20 | {
21 | $statements = Util::iterableToArray($this->getStatements());
22 | $statements = BlockStatement::flatten($statements);
23 | return \array_map(function (Statement $s) { return $s->convertToPhpParser(); }, $statements);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ModuloExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
19 | }
20 |
21 | protected function getPrecedence(): int
22 | {
23 | return self::PRECEDENCE_MUL;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ConcatExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
19 | }
20 |
21 | protected function getPrecedence(): int
22 | {
23 | return self::PRECEDENCE_ADD;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IsEqualExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ArrayItem.php:
--------------------------------------------------------------------------------
1 | getKey();
17 | $value = $this->getValue();
18 | if (!$value)
19 | {
20 | return null;
21 | }
22 | return new \PhpParser\Node\Expr\ArrayItem(
23 | $value->convertToPhpParser(),
24 | $key ? $key->getExpression()->convertToPhpParser() : null,
25 | $this->hasByReference(),
26 | [],
27 | $this->hasUnpack()
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/LessThanExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/CoalesceExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ShiftLeftExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/SymbolOrExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/IfStatement.php:
--------------------------------------------------------------------------------
1 | getElseClause();
18 | return new If_(
19 | $this->getCondition()->convertToPhpParser(),
20 | [
21 | 'stmts' => $this->getBlock()->convertToPhpParser(),
22 | 'elseifs' => $this->getElseifClauses()->convertToPhpParser(),
23 | 'else' => $else ? $else->convertToPhpParser() : null,
24 | ]
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/BitwiseOrExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IsNotEqualExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/KeywordOrExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
19 | }
20 |
21 | protected function getPrecedence(): int
22 | {
23 | return self::PRECEDENCE_KEYWORD_OR;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ShiftRightExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/SpaceshipExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/SymbolAndExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Util/Util.php:
--------------------------------------------------------------------------------
1 | $it
12 | * @phpstan-return array
13 | */
14 | public static function iterableToArray(iterable $it): array
15 | {
16 | if (is_array($it))
17 | {
18 | return $it;
19 | }
20 | else
21 | {
22 | /** @var \Traversable $it */
23 | return \iterator_to_array($it, false);
24 | }
25 | }
26 |
27 | public static function stringSplice(string $string, int $offset, int $length, string $replacement = ''): string
28 | {
29 | return \substr($string, 0, $offset) . $replacement . \substr($string, $offset + $length);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Nodes/data/$v.txt:
--------------------------------------------------------------------------------
1 | {"algo": "cycle"}
2 |
3 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$a"))
4 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$b"))
5 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$c"))
6 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$d"))
7 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$e"))
8 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$f"))
9 | Expr\NormalVariableExpression::__instantiateUnchecked(new Token(TokenType::T_VARIABLE, "\$g"))
10 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/BitwiseAndExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/BitwiseXorExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IsIdenticalExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/KeywordAndExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
19 | }
20 |
21 | protected function getPrecedence(): int
22 | {
23 | return self::PRECEDENCE_KEYWORD_AND;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/KeywordXorExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
19 | }
20 |
21 | protected function getPrecedence(): int
22 | {
23 | return self::PRECEDENCE_KEYWORD_XOR;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Oop/TraitUse.php:
--------------------------------------------------------------------------------
1 | getTraits()->getItems() as $name)
17 | {
18 | if (!$name->isUsableAsTraitUse())
19 | {
20 | throw ValidationException::invalidNameInContext($name);
21 | }
22 | }
23 | }
24 |
25 | public function convertToPhpParser()
26 | {
27 | return new \PhpParser\Node\Stmt\TraitUse(
28 | $this->getTraits()->convertToPhpParser()
29 | );
30 | // TODO more
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IsNotIdenticalExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/LessThanOrEqualsExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/NowdocStringLiteral.php:
--------------------------------------------------------------------------------
1 | validateEndDelimiter();
19 | }
20 |
21 | public function convertToPhpParser()
22 | {
23 | $content = $this->getContent();
24 | return new String_(
25 | $content ? \substr($content->getSource(), 0, -1) : "" // trim 1 newline
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/YieldFromExpression.php:
--------------------------------------------------------------------------------
1 | validatePrecedence();
21 |
22 | $this->validateYieldContext();
23 | }
24 |
25 | protected function getPrecedence(): int
26 | {
27 | return self::PRECEDENCE_YIELD;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Nodes/CompoundNodeTest.php:
--------------------------------------------------------------------------------
1 | setKeyword($keyword);
19 |
20 | self::assertTrue($keyword->isAttached());
21 | self::assertTrue($node->hasKeyword());
22 | self::assertSame($keyword, $node->getKeyword());
23 |
24 | $keyword->detach();
25 |
26 | self::assertFalse($keyword->isAttached());
27 | self::assertFalse($node->hasKeyword());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/GreaterThanOrEqualsExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/GreaterThanExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/NotExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->isConstant();
20 | }
21 |
22 | public function getRightPrecedence(): int
23 | {
24 | return self::PRECEDENCE_BOOLEAN_NOT;
25 | }
26 |
27 | public function convertToPhpParser()
28 | {
29 | return new BooleanNot($this->getExpression()->convertToPhpParser());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Parser/data/STMT_SINGLE.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | global $v;
4 | global $v, $v;
5 | global $v,;
6 | global EXPR4;
7 |
8 | while (0) break;
9 | while (0) break -1;
10 | while (0) break 0;
11 | while (0) break 1;
12 | while (0) break EXPR;
13 | while (0) break 123456789123456789123456789;
14 | while (0) { function ident() { break; } }
15 |
16 | while (0) continue;
17 | while (0) continue -1;
18 | while (0) continue 0;
19 | while (0) continue 1;
20 | while (0) continue EXPR;
21 | while (0) { function ident() { continue; } }
22 |
23 | echo EXPR;
24 | echo $v, $v;
25 |
26 | return EXPR;
27 | return;
28 |
29 | throw EXPR;
30 |
31 | function ident() { yield; }
32 | function ident() { yield EXPR; }
33 | function ident() { yield EXPR => $v; }
34 | function ident() { yield $v => EXPR; }
35 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ArrowFunctionExpression.php:
--------------------------------------------------------------------------------
1 | getReturnType();
17 | return new ArrowFunction([
18 | 'static' => $this->hasStaticKeyword(),
19 | 'byRef' => $this->hasByReference(),
20 | 'params' => $this->getParameters()->convertToPhpParser(),
21 | 'returnType' => $returnType ? $returnType->convertToPhpParser() : null,
22 | 'expr' => $this->getBody()->convertToPhpParser(),
23 | ]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/InterpolatedStringLiteral.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | abstract public function getParts(): NodesList;
18 |
19 | public function isConstant(): bool
20 | {
21 | foreach ($this->getParts() as $part)
22 | {
23 | if ($part instanceof InterpolatedStringVariable)
24 | {
25 | return false;
26 | }
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/UseDeclaration.php:
--------------------------------------------------------------------------------
1 | getName();
19 | if (!$name->isUsableAsUse())
20 | {
21 | throw ValidationException::invalidNameInContext($name);
22 | }
23 | }
24 |
25 | public function convertToPhpParser()
26 | {
27 | $alias = $this->getAlias();
28 | return new UseUse(
29 | $this->getName()->convertToPhpParser(true),
30 | $alias ? $alias->getName()->getSource() : null
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Nodes/Oop/ClassConstant.php:
--------------------------------------------------------------------------------
1 | getName()->getSource() === 'class')
20 | {
21 | throw ValidationException::invalidSyntax($this->getName(), [TokenType::T_STRING]);
22 | }
23 | }
24 |
25 | public function convertToPhpParser()
26 | {
27 | // TODO multi
28 | return new ClassConst(
29 | [
30 | new Const_($this->getName()->getSource(), $this->getValue()->convertToPhpParser()),
31 | ]
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/DoubleQuotedStringLiteral.php:
--------------------------------------------------------------------------------
1 | getParts()->convertToPhpParser();
19 |
20 | if (count($parts) === 0)
21 | {
22 | return new String_("");
23 | }
24 | else if (count($parts) === 1 && $parts[0] instanceof EncapsedStringPart)
25 | {
26 | return new String_($parts[0]->value);
27 | }
28 | else
29 | {
30 | return new Encapsed($parts);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_STRING_PART.txt:
--------------------------------------------------------------------------------
1 | {"algo": "weightedPermute", "max": 3}
2 |
3 | EXPR_STRING_PART{*empty*}EXPR_STRING_PART
4 |
5 | {*empty*}
6 | {*space*}{*space*}{*space*}{*space*}
7 | {*\t*}
8 | {*\n*}
9 | {*nbsp*}
10 |
11 | ident ident
12 | ident{*\n*}ident
13 |
14 | "
15 | '
16 | `
17 | \
18 | \\
19 | \"
20 | \'
21 | \`
22 | \n
23 | \t
24 |
25 | \0
26 | \07
27 | \073 [[3]]
28 | \083 [[3]]
29 |
30 | \xABC
31 | \xf7 [[3]]
32 | \xFF [[3]]
33 | \xdE [[3]]
34 | \xNF [[3]]
35 |
36 | \u{123}
37 | \u{FFEF} [[3]]
38 | \u{abcd} [[3]]
39 | \u{10FFFF} [[3]]
40 | \u{110000} [[3]]
41 |
42 | $
43 | \$
44 | $v
45 | ${ident}
46 | ${EXPR4} [[3]]
47 | {EXPR4} [[3]]
48 |
49 | $v->ident
50 | $v->ident->ident [[3]]
51 | $v->
52 |
53 | $v[0]
54 | $v[0][1] [[3]]
55 | $v[ 0] [[3]]
56 | $v[0 ] [[3]]
57 | $v[ [[3]]
58 | $v[] [[3]]
59 | $v[ident] [[3]]
60 | $v[EXPR5] [[3]]
61 |
62 | HEREDOC
63 | NOWDOC
64 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AnonymousClassNewExpression.php:
--------------------------------------------------------------------------------
1 | getExtends();
18 | $implements = $this->getImplements();
19 | return new New_(
20 | new Class_(
21 | null,
22 | [
23 | 'extends' => $extends ? $extends->convertToPhpParser() : null,
24 | 'implements' => $implements ? $implements->convertToPhpParser() : null,
25 | 'stmts' => $this->members->convertToPhpParser(),
26 | ]
27 | ),
28 | $this->getArguments()->convertToPhpParser()
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/ValidateYieldContext.php:
--------------------------------------------------------------------------------
1 | getParent(); $parent; $parent = $parent->getParent())
18 | {
19 | if (
20 | $parent instanceof FunctionStatement
21 | || $parent instanceof Method
22 | || $parent instanceof NormalAnonymousFunctionExpression
23 | )
24 | {
25 | break;
26 | }
27 | else if ($parent instanceof RootNode)
28 | {
29 | throw ValidationException::invalidExpressionInContext($this);
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AddExpression.php:
--------------------------------------------------------------------------------
1 | validatePrecedence();
26 | $this->validateOperandTypes();
27 | }
28 |
29 | public function convertToPhpParser()
30 | {
31 | return new Plus($this->getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/DivideExpression.php:
--------------------------------------------------------------------------------
1 | validatePrecedence();
26 | $this->validateOperandTypes(true);
27 | }
28 |
29 | public function convertToPhpParser()
30 | {
31 | return new Div($this->getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/meta/bin/generate_nodes.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | shortGeneratedClassName() . ".php", $src);
31 | }
32 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MultiplyExpression.php:
--------------------------------------------------------------------------------
1 | validatePrecedence();
26 | $this->validateOperandTypes();
27 | }
28 |
29 | public function convertToPhpParser()
30 | {
31 | return new Mul($this->getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/SubtractExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
21 | }
22 |
23 | protected function extraValidation(int $flags): void
24 | {
25 | $this->validatePrecedence();
26 | $this->validateOperandTypes();
27 | }
28 |
29 | protected function getPrecedence(): int
30 | {
31 | return self::PRECEDENCE_ADD;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/NormalAnonymousFunctionExpression.php:
--------------------------------------------------------------------------------
1 | getUse();
17 | $returnType = $this->getReturnType();
18 | return new Closure([
19 | "static" => $this->hasStaticModifier(),
20 | "byRef" => $this->hasByReference(),
21 | "params" => $this->getParameters()->convertToPhpParser(),
22 | "uses" => $use ? $use->getBindings()->convertToPhpParser() : [],
23 | "returnType" => $returnType ? $returnType->getType()->convertToPhpParser() : null,
24 | "stmts" => $this->getBody()->convertToPhpParser(),
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/BlockStatement.php:
--------------------------------------------------------------------------------
1 | getBlock()->getStatements()));
26 | }
27 | }
28 |
29 | $statements = \array_values(\array_filter($statements, function (Statement $statement)
30 | {
31 | return !($statement instanceof NopStatement);
32 | }));
33 |
34 | return $statements;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/SystemTest.php:
--------------------------------------------------------------------------------
1 | parse($path, $source);
21 | $ast->validate();
22 |
23 | self::assertSame($source, (string) $ast);
24 | }
25 |
26 | public function files_to_parse()
27 | {
28 | $files = (new Finder())->in(__DIR__ . "/../src")->filter(function (\SplFileInfo $p)
29 | {
30 | return $p->getExtension() === "php";
31 | });
32 |
33 | /** @var SplFileInfo $file */
34 | foreach ($files as $file)
35 | {
36 | yield $file->getRelativePathname() => [$file->getRealPath()];
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Nodes/Helpers/ReturnType.php:
--------------------------------------------------------------------------------
1 | getType()->unwrapNullable();
21 | if ($type instanceof SpecialType && $type->isStatic())
22 | {
23 | throw ValidationException::invalidSyntax($type);
24 | }
25 | else if ($type instanceof NamedType && !$type->getName()->isUsableAsReturnType())
26 | {
27 | throw ValidationException::invalidSyntax($type);
28 | }
29 | }
30 |
31 | public function convertToPhpParser()
32 | {
33 | return $this->getType()->convertToPhpParser();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Nodes/RootNode.php:
--------------------------------------------------------------------------------
1 | getStatements()->getItems();
19 | $statements = BlockStatement::flatten($statements);
20 |
21 | // drop the initial getContent();
27 | if (!$content || $content->getSource() === "")
28 | {
29 | \array_shift($statements);
30 | }
31 | }
32 | }
33 |
34 | return \array_map(function (Statement $s) { return $s->convertToPhpParser(); }, $statements);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/NormalNewExpression.php:
--------------------------------------------------------------------------------
1 | getClass()))
20 | {
21 | throw ValidationException::invalidExpressionInContext($this->getClass());
22 | }
23 | }
24 |
25 | public function convertToPhpParser()
26 | {
27 | $class = $this->getClass();
28 | if ($class instanceof NameExpression)
29 | {
30 | $class = $class->getName();
31 | }
32 | return new New_(
33 | $class->convertToPhpParser(),
34 | $this->getArguments()->convertToPhpParser()
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/LiteralParser/data/double_quoted_strings.txt:
--------------------------------------------------------------------------------
1 | " === ERROR
2 | foo === ERROR
3 | 'foo' === ERROR
4 | "" === EMPTY
5 | "foo" === foo
6 | "<0A>" === <0A>
7 | "\<0A>" === \<0A>
8 |
9 | "\" === ERROR
10 | "\\" === \
11 | "\\\" === ERROR
12 | "\\\\" === \\
13 |
14 | "foo\bar" === foo\bar
15 | "foo\\bar" === foo\bar
16 | "foo\\\bar" === foo\\bar
17 | "foo\\\\bar" === foo\\bar
18 |
19 | "foo'bar" === foo'bar
20 | "foo\'bar" === foo\'bar
21 | "foo\\'bar" === foo\'bar
22 | "foo\\\'bar" === foo\\'bar
23 |
24 | "foo"bar" === ERROR
25 | "foo\"bar" === foo"bar
26 | "foo\\"bar# === ERROR
27 | "foo\\\"bar" === foo\"bar
28 |
29 | "\n" === <0A>
30 | "\r" === <0D>
31 | "\t" === <09>
32 |
33 | "\0" === <00>
34 | "\101" === A
35 | "\102" === B
36 | "\1021" === B1
37 | "\8" === \8
38 | "\107" === <47>
39 | "\108" === <08 38>
40 |
41 | "\x41" === A
42 | "\X42" === B
43 | "\xGG" === \xGG
44 |
45 | "\u{0}" === <00>
46 | "\u{41}" === A
47 | "\u{42}" === B
48 | "\u{1F4A9}" ===
49 |
50 | "\h" === \h
51 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AliasExpression.php:
--------------------------------------------------------------------------------
1 | getLeft();
23 |
24 | if ($left->isTemporary())
25 | {
26 | throw ValidationException::invalidExpressionInContext($left);
27 | }
28 |
29 | $right = $this->getRight();
30 | if ($right->isTemporary())
31 | {
32 | throw ValidationException::invalidExpressionInContext($right);
33 | }
34 | }
35 |
36 | public function convertToPhpParser()
37 | {
38 | return new AssignRef($this->getLeft()->convertToPhpParser(), $this->getRight()->convertToPhpParser());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ConstStatement.php:
--------------------------------------------------------------------------------
1 | getName()))
20 | {
21 | throw ValidationException::invalidSyntax($this->getName());
22 | }
23 |
24 | if (!$this->getValue()->isConstant())
25 | {
26 | throw ValidationException::invalidExpressionInContext($this->getValue());
27 | }
28 | }
29 |
30 | public function convertToPhpParser()
31 | {
32 | // TODO multi
33 | return new Const_(
34 | [
35 | new \PhpParser\Node\Const_($this->getName()->getSource(), $this->getValue()->convertToPhpParser()),
36 | ]
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/NumericBinopExpression.php:
--------------------------------------------------------------------------------
1 | getLeft();
20 | $right = $this->getRight();
21 |
22 | if ($left instanceof ArrayExpression && $right instanceof NumberLiteral && $left->isConstant())
23 | {
24 | throw ValidationException::invalidExpressionInContext($left);
25 | }
26 |
27 | if (!$leftOnly && $left instanceof NumberLiteral && $right instanceof ArrayExpression && $right->isConstant())
28 | {
29 | throw ValidationException::invalidExpressionInContext($right);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/FunctionStatement.php:
--------------------------------------------------------------------------------
1 | getParameters());
20 | }
21 |
22 | public function convertToPhpParser()
23 | {
24 | $returnType = $this->getReturnType();
25 | return new Function_(
26 | $this->getName()->getSource(),
27 | [
28 | 'byRef' => $this->hasByReference(),
29 | 'params' => $this->getParameters()->convertToPhpParser(),
30 | 'returnType' => $returnType ? $returnType->convertToPhpParser() : null,
31 | 'stmts' => $this->getBody()->convertToPhpParser(),
32 | ]
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/DocStringLiteral.php:
--------------------------------------------------------------------------------
1 | getPhpVersion() < PhpVersion::PHP_7_3)
19 | {
20 | $end = $this->getRightDelimiter();
21 | $nextBytes = $end->getRightWhitespace();
22 |
23 | $nextToken = $end->getNextToken();
24 | if ($nextToken)
25 | {
26 | $nextBytes .= $nextToken->toPhp();
27 | $nextToken = $nextToken->getNextToken();
28 | if ($nextToken)
29 | {
30 | $nextBytes .= $nextToken->getLeftWhitespace();
31 | }
32 | }
33 |
34 | if (!\preg_match('{^;?(\r?\n)}', $nextBytes))
35 | {
36 | throw ValidationException::invalidSyntax($semi ?? $end);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Parser/data/WORD_CONTEXT.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | // test use of keyword tokens in identifier positions, special classes, etc.
4 |
5 | $v->WORD_TOKEN;
6 | $v->WORD_TOKEN();
7 | ident::WORD_TOKEN;
8 | ident::WORD_TOKEN();
9 | new WORD_TOKEN;
10 | new WORD_TOKEN();
11 | WORD_TOKEN::ident;
12 | WORD_TOKEN::class;
13 | WORD_TOKEN;
14 |
15 | use WORD_TOKEN;
16 | use ident as WORD_TOKEN;
17 |
18 | class WORD_TOKEN {}
19 | class ident { const WORD_TOKEN = 0; }
20 | class ident { function WORD_TOKEN() {}; }
21 | class ident { use WORD_TOKEN; }
22 | class ident { use ident { WORD_TOKEN as ident; }
23 | class ident { use ident { WORD_TOKEN::ident as ident; }
24 | class ident { use ident { ident::WORD_TOKEN as ident; }
25 | class ident { use ident { ident as WORD_TOKEN; }
26 | class ident { use ident { ident as public WORD_TOKEN; }
27 |
28 | const WORD_TOKEN = 0;
29 |
30 | // if (0) works around redeclare errors
31 | if (0) { function WORD_TOKEN() {} }
32 | function ident(WORD_TOKEN $v) {}
33 | function ident(): WORD_TOKEN {}
34 |
35 | WORD_TOKEN:
36 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/PropertyAccessExpression.php:
--------------------------------------------------------------------------------
1 | getObject()->isConstant();
21 | }
22 |
23 | protected function extraValidation(int $flags): void
24 | {
25 | if (!self::isObjectAccessible($this->getObject()))
26 | {
27 | throw ValidationException::invalidSyntax($this->getOperator());
28 | }
29 | }
30 |
31 | public function convertToPhpParser()
32 | {
33 | return new PropertyFetch(
34 | $this->getObject()->convertToPhpParser(),
35 | $this->getName()->convertToPhpParser()
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Nodes/Oop/ClassDeclaration.php:
--------------------------------------------------------------------------------
1 | getName()) || TokenType::isSpecialClass($this->getName()))
19 | {
20 | throw ValidationException::invalidNameInContext($this->getName());
21 | }
22 | }
23 |
24 | public function convertToPhpParser()
25 | {
26 | $extends = $this->getExtends();
27 | $implements = $this->getImplements();
28 | return new Class_($this->getName()->getSource(), [
29 | 'extends' => $extends ? $extends->convertToPhpParser() : null,
30 | 'implements' => $implements ? $implements->convertToPhpParser() : null,
31 | 'stmts' => $this->members->convertToPhpParser(),
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/MethodCallExpression.php:
--------------------------------------------------------------------------------
1 | getObject()))
26 | {
27 | throw ValidationException::invalidSyntax($this->getOperator());
28 | }
29 | }
30 |
31 | public function convertToPhpParser()
32 | {
33 | return new MethodCall(
34 | $this->getObject()->convertToPhpParser(),
35 | $this->getName()->convertToPhpParser(),
36 | $this->getArguments()->convertToPhpParser()
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/phpstan/phpstan-strict-rules/rules.neon
3 |
4 | parameters:
5 | level: max
6 | paths:
7 | - ./src
8 | ignoreErrors:
9 | # i'm ok with non-booleans in conditions
10 | - '{^Only booleans are allowed in .+}'
11 | # assertions can be redundant
12 | - '{^Call to function assert\(\) with .* will always evaluate to true\.$}'
13 | # runtime checks of generics
14 | - '{^Instanceof between T .+ will always evaluate to true\.$}'
15 |
16 | - # phpstan gets tons wrong here, mostly because read changes the return value of peek()
17 | path: src/Parser.src.php
18 | message: '{is unreachable|will always evaluate|always terminates|is always (true|false)}'
19 | - # we know this null check is safe, phpstan can't
20 | path: src/Nodes/Generated/
21 | message: '{^Cannot call method _validate\(\) on .+\|null\.$}'
22 | - # in some contexts this check *is* needed (e.g. yield)
23 | path: src/Nodes/ValidationTraits/UnaryOpExpression.php
24 | message: '{^Left side of \&\& is always true\.$}'
25 | -
26 | path: src/Parser.opt.php
27 | message: '{}'
28 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/AnonymousFunctionExpression.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | abstract public function getParameters(): SeparatedNodesList;
22 |
23 | protected function extraValidation(int $flags): void
24 | {
25 | foreach (\array_slice($this->getParameters()->getItems(), 0, -1) as $parameter)
26 | {
27 | /** @var Parameter $parameter */
28 | if ($unpack = $parameter->getUnpack())
29 | {
30 | throw ValidationException::invalidSyntax($unpack);
31 | }
32 | }
33 |
34 | self::forbidTrailingSeparator($this->getParameters());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/UnaryPlusExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->isConstant();
26 | }
27 |
28 | protected function extraValidation(int $flags): void
29 | {
30 | $expression = $this->getExpression();
31 | if ($expression instanceof ArrayExpression && $expression->isConstant())
32 | {
33 | throw ValidationException::invalidSyntax($expression);
34 | }
35 |
36 | $this->validatePrecedence();
37 | }
38 |
39 | public function convertToPhpParser()
40 | {
41 | return new UnaryPlus($this->getExpression()->convertToPhpParser());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/UnaryMinusExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->isConstant();
26 | }
27 |
28 | protected function extraValidation(int $flags): void
29 | {
30 | $expression = $this->getExpression();
31 | if ($expression instanceof ArrayExpression && $expression->isConstant())
32 | {
33 | throw ValidationException::invalidSyntax($expression);
34 | }
35 |
36 | $this->validatePrecedence();
37 | }
38 |
39 | public function convertToPhpParser()
40 | {
41 | return new UnaryMinus($this->getExpression()->convertToPhpParser());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/UnaryOpExpression.php:
--------------------------------------------------------------------------------
1 | getExpression();
16 | if ($expression && $expression->getLeftPrecedence() < $this->getRightPrecedence())
17 | {
18 | throw ValidationException::badPrecedence($expression);
19 | }
20 | }
21 |
22 | protected function extraValidation(int $flags): void
23 | {
24 | $this->validatePrecedence();
25 | }
26 |
27 | private function autocorrectPrecedence(): void
28 | {
29 | $expression = $this->getExpression();
30 | if ($expression && $expression->getLeftPrecedence() < $this->getRightPrecedence())
31 | {
32 | $expression = $expression->wrapIn(new ParenthesizedExpression());
33 | $expression->_autocorrect();
34 | }
35 | }
36 |
37 | protected function extraAutocorrect(): void
38 | {
39 | $this->autocorrectPrecedence();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/IsConstantClassNameHelper.php:
--------------------------------------------------------------------------------
1 | getExpression();
25 |
26 | if ($expression instanceof NameExpression)
27 | {
28 | return false;
29 | }
30 | }
31 |
32 | if (
33 | $expression instanceof StaticExpression
34 | || $expression instanceof ArrayExpression
35 | || $expression instanceof ArrayAccessExpression
36 | )
37 | {
38 | return false;
39 | }
40 |
41 | return $expression->isConstant();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rainbow-alex/phi",
3 | "description": "Parse PHP code; analyse it, modify it, format it, validate it; safely convert it back to source.",
4 | "license": "GPL-3.0-only",
5 | "authors": [
6 | {
7 | "name": "Alex Deleyn",
8 | "email": "alex.deleyn@gmail.com"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "Phi\\": "src/"
14 | }
15 | },
16 | "autoload-dev": {
17 | "psr-4": {
18 | "Phi\\Meta\\": "meta/",
19 | "Phi\\Tests\\": "tests/"
20 | }
21 | },
22 | "require": {
23 | "php": "^7.2",
24 | "ext-mbstring": "*"
25 | },
26 | "suggest": {
27 | "nikic/php-parser": "Convert phi ASTs to and from php-parser ASTs"
28 | },
29 | "require-dev": {
30 | "ext-json": "*",
31 | "ext-pcntl": "*",
32 | "ext-posix": "*",
33 | "ext-zlib": "^7.2",
34 | "nikic/php-parser": "^4.3",
35 | "phpdocumentor/reflection-docblock": "^4.3",
36 | "phpstan/phpstan": "^0.12.8",
37 | "phpstan/phpstan-strict-rules": "^0.12.2",
38 | "phpunit/phpunit": "^8.0",
39 | "symfony/finder": "^5.0"
40 | },
41 | "scripts": {
42 | "gen": "php meta/bin/generate_nodes.php; php meta/bin/optimize_parser.php"
43 | },
44 | "config": {
45 | "sort-packages": true
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/BitwiseNotExpression.php:
--------------------------------------------------------------------------------
1 | getExpression()->isConstant();
19 | }
20 |
21 | protected function extraValidation(int $flags): void
22 | {
23 | $expression = $this->getExpression();
24 | if (
25 | ($expression instanceof ArrayExpression && $expression->isConstant())
26 | || $expression instanceof ExitExpression
27 | || ($expression instanceof EmptyExpression && $expression->getExpression() instanceof NumberLiteral)
28 | || ($expression instanceof NotExpression && $expression->getExpression() instanceof NumberLiteral)
29 | )
30 | {
31 | throw ValidationException::invalidSyntax($expression);
32 | }
33 | }
34 |
35 | public function convertToPhpParser()
36 | {
37 | return new BitwiseNot($this->getExpression()->convertToPhpParser());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Exception/PhiException.php:
--------------------------------------------------------------------------------
1 | node = $node;
19 | parent::__construct($message . " in " . $this->getContext());
20 | }
21 |
22 | public function getNode(): ?Node
23 | {
24 | return $this->node;
25 | }
26 |
27 | private function token(): ?Token
28 | {
29 | if ($this->node)
30 | {
31 | foreach ($this->node->iterTokens() as $token)
32 | {
33 | return $token;
34 | }
35 | }
36 |
37 | return null;
38 | }
39 |
40 | public function getSourceLine(): ?int
41 | {
42 | $token = $this->token();
43 | return $token ? $token->getLine() : null;
44 | }
45 |
46 | public function getSourceFilename(): ?string
47 | {
48 | $token = $this->token();
49 | return $token ? $token->getFilename() : null;
50 | }
51 |
52 | public function getContext(): string
53 | {
54 | return ($this->getSourceFilename() ?? "") . " on line " . ($this->getSourceLine() ?? "?");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Testing/Fuzz/WeightedPermute.php:
--------------------------------------------------------------------------------
1 | */
12 | private $options;
13 |
14 | /**
15 | * @param array $options
16 | */
17 | public function __construct(int $max, array $options)
18 | {
19 | $this->max = $max;
20 | $this->options = $options;
21 | }
22 |
23 | public function withMax(int $max): self
24 | {
25 | $clone = clone $this;
26 | $clone->max = $max;
27 | return $clone;
28 | }
29 |
30 | public function map(callable $fn): self
31 | {
32 | $clone = clone $this;
33 | $clone->options = [];
34 | foreach ($this->options as $option => $weight)
35 | {
36 | $clone->options[$fn((string) $option)] = $weight;
37 | }
38 | return $clone;
39 | }
40 |
41 | public function initState(&$state): void
42 | {
43 | $state = 0;
44 | }
45 |
46 | public function generateRhs(&$state)
47 | {
48 | foreach ($this->options as $rhs => $weight)
49 | {
50 | if ($state + $weight <= $this->max)
51 | {
52 | $state += $weight;
53 | yield $rhs;
54 | $state -= $weight;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/IsObjectAccessibleHelper.php:
--------------------------------------------------------------------------------
1 | getClass());
23 | }
24 |
25 | protected function extraValidation(int $flags): void
26 | {
27 | $class = $this->getClass();
28 |
29 | if (!self::isStaticAccessible($class))
30 | {
31 | throw ValidationException::invalidSyntax($class);
32 | }
33 | }
34 |
35 | public function convertToPhpParser()
36 | {
37 | $class = $this->getClass();
38 | if ($class instanceof NameExpression)
39 | {
40 | $class = $class->getName();
41 | }
42 | return new ClassConstFetch($class->convertToPhpParser(), $this->getName()->convertToPhpParser());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IntegerLiteral.php:
--------------------------------------------------------------------------------
1 | getPhpVersion()))->validateIntegerLiteral($this->getToken()->getSource());
23 | }
24 | catch (LiteralParsingException $e)
25 | {
26 | throw ValidationException::invalidSyntax($this);
27 | }
28 | }
29 |
30 | /**
31 | * @return int|float
32 | */
33 | public function getValue()
34 | {
35 | return (new LiteralParser($this->getPhpVersion()))->parseIntegerLiteral($this->getToken()->getSource());
36 | }
37 |
38 | public function convertToPhpParser()
39 | {
40 | $value = $this->getValue();
41 | if (\is_float($value))
42 | {
43 | // overflow occurred
44 | return new DNumber($value);
45 | }
46 | else
47 | {
48 | return new LNumber($value);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ClassNameResolutionExpression.php:
--------------------------------------------------------------------------------
1 | getClass());
21 | }
22 |
23 | protected function extraValidation(int $flags): void
24 | {
25 | $class = $this->getClass();
26 | if (!(
27 | $class instanceof NameExpression
28 | || $class instanceof StaticExpression
29 | || $class instanceof StringLiteral
30 | ))
31 | {
32 | throw ValidationException::invalidExpressionInContext($class);
33 | }
34 | }
35 |
36 | public function convertToPhpParser()
37 | {
38 | $class = $this->getClass();
39 | if ($class instanceof NameExpression)
40 | {
41 | $class = $class->getName();
42 | }
43 | return new ClassConstFetch($class->convertToPhpParser(), $this->getKeyword()->convertToPhpParser());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/IsNewableHelper.php:
--------------------------------------------------------------------------------
1 | , ::
15 | while (true)
16 | {
17 | if ($expression instanceof Nodes\Expressions\ArrayAccessExpression)
18 | {
19 | $expression = $expression->getExpression();
20 |
21 | // weird, but `new foo[expr]` is not allowed
22 | if ($expression instanceof Nodes\Expressions\NameExpression)
23 | {
24 | return false;
25 | }
26 | }
27 | else if ($expression instanceof Nodes\Expressions\PropertyAccessExpression)
28 | {
29 | $expression = $expression->getObject();
30 | }
31 | else if ($expression instanceof Nodes\Expressions\StaticPropertyAccessExpression)
32 | {
33 | $expression = $expression->getClass();
34 | }
35 | else
36 | {
37 | break;
38 | }
39 | }
40 |
41 | return (
42 | ($expression instanceof Nodes\Expressions\NameExpression && $expression->getName()->isNewable())
43 | || $expression instanceof Nodes\Expressions\StaticExpression
44 | || $expression instanceof Nodes\Expressions\VariableExpression
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Util/Console.php:
--------------------------------------------------------------------------------
1 | EXPR) {}
39 | foreach ($v as $v => &EXPR) {}
40 | foreach ($v as EXPR => $v) {}
41 | foreach ($v as $v): STMT endforeach;
42 |
43 | while ($v) STMT
44 | while ($v): STMT endwhile;
45 |
46 | { STMT }
47 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/NonAssocBinopExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->getPrecedence() <= $this->getPrecedence())
13 | {
14 | throw ValidationException::badPrecedence($this->getLeft());
15 | }
16 | if ($this->getRight()->getPrecedence() <= $this->getPrecedence())
17 | {
18 | throw ValidationException::badPrecedence($this->getRight());
19 | }
20 | }
21 |
22 | protected function extraValidation(int $flags): void
23 | {
24 | $this->validatePrecedence();
25 | }
26 |
27 | private function autocorrectPrecedence(): void
28 | {
29 | if ($this->getLeft()->getPrecedence() <= $this->getPrecedence())
30 | {
31 | $left = $this->getLeft();
32 | $wrapped = new ParenthesizedExpression($left);
33 | $wrapped->_autocorrect();
34 | $this->setLeft($wrapped);
35 | }
36 |
37 | if ($this->getRight()->getPrecedence() <= $this->getPrecedence())
38 | {
39 | $right = $this->getRight();
40 | $wrapped = new ParenthesizedExpression($right);
41 | $wrapped->_autocorrect();
42 | $this->setRight($wrapped);
43 | }
44 | }
45 |
46 | protected function extraAutocorrect(): void
47 | {
48 | $this->autocorrectPrecedence();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/ForeachStatement.php:
--------------------------------------------------------------------------------
1 | getKey())
20 | {
21 | if ($key->getExpression() instanceof ListExpression || $key->getExpression() instanceof ShortArrayExpression)
22 | {
23 | throw ValidationException::invalidExpressionInContext($key->getExpression());
24 | }
25 | }
26 |
27 | $value = $this->getValue();
28 | if ($this->hasByReference() && $value->isTemporary())
29 | {
30 | throw ValidationException::invalidExpressionInContext($value);
31 | }
32 | }
33 |
34 | public function convertToPhpParser()
35 | {
36 | $key = $this->getKey();
37 | return new Foreach_(
38 | $this->getIterable()->convertToPhpParser(),
39 | $this->getValue()->convertToPhpParser(),
40 | [
41 | 'keyVar' => $key ? $key->getExpression()->convertToPhpParser() : null,
42 | 'byRef' => $this->hasByReference(),
43 | 'stmts' => $this->getBlock()->convertToPhpParser(),
44 | ]
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StaticPropertyAccessExpression.php:
--------------------------------------------------------------------------------
1 | getClass();
27 |
28 | if (!self::isStaticAccessible($class))
29 | {
30 | throw ValidationException::invalidSyntax($this->getOperator());
31 | }
32 | }
33 |
34 | public function convertToPhpParser()
35 | {
36 | $class = $this->getClass();
37 | if ($class instanceof NameExpression)
38 | {
39 | $class = $class->getName();
40 | }
41 | $name = $this->getName();
42 | if ($name instanceof NormalVariableExpression)
43 | {
44 | $ppName = new VarLikeIdentifier(substr($name->getToken()->getSource(), 1));
45 | }
46 | else
47 | {
48 | $ppName = $name->convertToPhpParser();
49 | }
50 | return new StaticPropertyFetch($class->convertToPhpParser(), $ppName);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Nodes/Expression.php:
--------------------------------------------------------------------------------
1 | getPrecedence();
49 | }
50 |
51 | public function getRightPrecedence(): int
52 | {
53 | return $this->getPrecedence();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/notes.md:
--------------------------------------------------------------------------------
1 | # String interpolation syntax
2 |
3 | What an ugly mess.
4 |
5 | Here are examples of each syntax and the equivalent using concatenation.
6 |
7 | # NormalInterpolatedStringVariable
8 |
9 | "$foo" --> . $foo .
10 | "$foo->bar" --> . $foo->bar .
11 | "$foo->bar->baz" --> . $foo->bar . "->baz"
12 |
13 | # NormalInterpolatedStringVariableArrayAccess
14 |
15 | This could have been ArrayAccessExpression, but the semantics are different; see the last example.
16 |
17 | "$foo[0]" --> . $foo[0] .
18 | "$foo[-1]" --> . $foo[-1] .
19 | "$foo[bar]" --> . $foo["bar"] . # Note the quotes here...
20 |
21 | # BracedInterpolatedStringVariable
22 |
23 | I wish these could have just been optional braces around NormalInterpolatedStringVariable, but again, semantics are different.
24 |
25 | "{$foo}" --> . $foo .
26 | "{$foo[bar]}" --> . $foo[bar] . # here bar is interpreted as a constant, the value of which will be the index
27 | "{$foo[EXPR]}" --> . $foo[EXPR] . # allows any expression in the index position
28 | "{$foo->bar->baz}" --> . $foo->bar->baz .
29 |
30 | # VariableInterpolatedStringVariable
31 |
32 | "${foo::bar}" --> . ${foo::bar} .
33 | "${(foo)}" --> . ${foo} .
34 |
35 | # ConfusingInterpolatedStringVariable
36 |
37 | This is where it gets really messed up:
38 |
39 | "${foo}" --> . $foo .
40 | "${foo[0]}" --> . $foo[0] .
41 | "${foo[EXPR]}" --> . $foo[EXPR] .
42 |
43 | That's right, depending on what follows after `${foo` it is interpreted differently...
44 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR.txt:
--------------------------------------------------------------------------------
1 | {"algo": "weightedPermute", "max": 6}
2 |
3 | THIS BINOP THIS [[4]]
4 | THIS ? THIS : THIS [[3]]
5 | THIS[] [[4]]
6 | THIS[THIS] [[2]]
7 | THIS() [[3]]
8 | THIS->ident [[3]]
9 | THIS->$v [[8]]
10 | THIS->{THIS} [[8]]
11 | THIS::ident [[3]]
12 | THIS::$v [[4]]
13 | THIS::{THIS} [[4]]
14 | THIS::class [[4]]
15 | THIS->ident() [[3]]
16 | THIS->$v() [[8]]
17 | THIS->{THIS}() [[4]]
18 | THIS::ident() [[3]]
19 | THIS::$v() [[8]]
20 | THIS::{THIS}() [[8]]
21 | new THIS [[3]]
22 | new THIS() [[4]]
23 | new class {} [[4]]
24 | clone THIS [[3]]
25 | print THIS [[4]]
26 | include THIS [[4]]
27 | include_once THIS [[8]]
28 | require THIS [[8]]
29 | require_once THIS [[8]]
30 | THIS++ [[4]]
31 | THIS-- [[8]]
32 | ++THIS [[4]]
33 | --THIS [[8]]
34 | -THIS [[4]]
35 | +THIS [[4]]
36 | !THIS [[4]]
37 | ~THIS [[4]]
38 | @THIS [[4]]
39 | (int) THIS [[4]]
40 | (string) THIS [[8]]
41 | (unset) THIS [[8]]
42 | ($v) [[4]]
43 | die() [[4]]
44 | die(THIS) [[8]]
45 | empty(THIS) [[4]]
46 | eval(THIS) [[4]]
47 | isset(THIS) [[4]]
48 | `ident ident` [[4]]
49 | [] [[4]]
50 | [THIS] [[8]]
51 | [THIS, THIS] [[4]]
52 | list(THIS) [[8]]
53 | (THIS) [[4]]
54 | function () {} [[4]]
55 | static function () {} [[8]]
56 | fn () => THIS [[4]]
57 | static fn () => THIS [[8]]
58 | __FILE__ [[4]]
59 | MAGIC_CONSTANT [[8]]
60 | ident [[2]]
61 | \ident [[8]]
62 | ident\ident [[8]]
63 | \ident\ident [[8]]
64 | static [[4]]
65 | $v
66 | $$v [[8]]
67 | ${THIS} [[8]]
68 | "ident" [[4]]
69 | 'ident' [[8]]
70 | 123 [[3]]
71 | 4.5 [[4]]
72 | .6 [[8]]
73 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StaticMethodCallExpression.php:
--------------------------------------------------------------------------------
1 | getClass()))
29 | {
30 | throw ValidationException::invalidSyntax($this->getOperator());
31 | }
32 |
33 | self::forbidTrailingSeparator($this->getArguments());
34 | }
35 |
36 | public function convertToPhpParser()
37 | {
38 | $class = $this->getClass();
39 | if ($class instanceof NameExpression)
40 | {
41 | $class = $class->getName();
42 | }
43 | $name = $this->getName();
44 | if ($name instanceof NormalMemberName)
45 | {
46 | $name = $name->getToken();
47 | }
48 | return new StaticCall(
49 | $class->convertToPhpParser(),
50 | $name->convertToPhpParser(),
51 | $this->getArguments()->convertToPhpParser()
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StaticExpression.php:
--------------------------------------------------------------------------------
1 | parent;
26 | if ($parent && !(
27 | $parent instanceof ConstantAccessExpression
28 | || $parent instanceof ClassNameResolutionExpression
29 | || $parent instanceof StaticPropertyAccessExpression
30 | || $parent instanceof StaticMethodCallExpression
31 | || $parent instanceof NormalNewExpression
32 | || ($parent instanceof InstanceofExpression && $parent->getClass() === $this)
33 | ))
34 | {
35 | throw ValidationException::invalidNameInContext($this->getToken());
36 | }
37 |
38 | while ($parent = $parent->getParent())
39 | {
40 | if ($parent instanceof FunctionStatement || $parent instanceof NormalAnonymousFunctionExpression)
41 | {
42 | throw ValidationException::invalidSyntax($this);
43 | break;
44 | }
45 | }
46 | }
47 |
48 | public function convertToPhpParser()
49 | {
50 | return new Name([$this->getToken()->getSource()]);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/LeftAssocBinopExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->getPrecedence() < $this->getPrecedence())
18 | {
19 | throw ValidationException::badPrecedence($this->getLeft());
20 | }
21 | if ($this->getRight()->getPrecedence() <= $this->getPrecedence())
22 | {
23 | throw ValidationException::badPrecedence($this->getRight());
24 | }
25 | }
26 |
27 | protected function extraValidation(int $flags): void
28 | {
29 | $this->validatePrecedence();
30 | }
31 |
32 | private function autocorrectPrecedence(): void
33 | {
34 | $left = $this->getLeft();
35 | if ($left->getPrecedence() < $this->getPrecedence())
36 | {
37 | $left = $left->wrapIn(new ParenthesizedExpression());
38 | $left->_autocorrect();
39 | }
40 |
41 | $right = $this->getRight();
42 | if ($right->getPrecedence() <= $this->getPrecedence())
43 | {
44 | $right = $right->wrapIn(new ParenthesizedExpression());
45 | $right->_autocorrect();
46 | }
47 | }
48 |
49 | protected function extraAutocorrect(): void
50 | {
51 | $this->autocorrectPrecedence();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/HeredocStringLiteral.php:
--------------------------------------------------------------------------------
1 | validateEndDelimiter();
21 | }
22 |
23 | public function convertToPhpParser()
24 | {
25 | $parts = $this->getParts()->convertToPhpParser();
26 |
27 | $parts = \array_values(\array_filter($parts, function ($part): bool
28 | {
29 | return !($part instanceof EncapsedStringPart) || $part->value !== "";
30 | }));
31 |
32 | // trim one newline, dropping the entire string part if it becomes empty
33 | $lastPart = \end($parts);
34 | if ($lastPart instanceof EncapsedStringPart)
35 | {
36 | $lastPart->value = \substr($lastPart->value, 0, -1);
37 | if ($lastPart->value === "")
38 | {
39 | \array_pop($parts);
40 | }
41 | }
42 |
43 | if (count($parts) === 0)
44 | {
45 | return new String_("");
46 | }
47 | else if (count($parts) === 1 && $parts[0] instanceof EncapsedStringPart)
48 | {
49 | return new String_($parts[0]->value);
50 | }
51 | else
52 | {
53 | return new Encapsed($parts);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/BracedInterpolatedStringVariable.php:
--------------------------------------------------------------------------------
1 | getVariable();
24 | if (!(
25 | $variable instanceof NormalVariableExpression
26 | || $variable instanceof ArrayAccessExpression
27 | || $variable instanceof PropertyAccessExpression
28 | || $variable instanceof FunctionCallExpression
29 | || $variable instanceof MethodCallExpression
30 | || $variable instanceof StaticPropertyAccessExpression
31 | || $variable instanceof StaticMethodCallExpression
32 | ))
33 | {
34 | throw ValidationException::invalidExpressionInContext($variable);
35 | }
36 | }
37 |
38 | public function convertToPhpParser()
39 | {
40 | return $this->getVariable()->convertToPhpParser();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Nodes/Statements/LoopFlowStatement.php:
--------------------------------------------------------------------------------
1 | getParent();
21 | while ($parent)
22 | {
23 | if ($parent instanceof LoopStatement || $parent instanceof SwitchStatement)
24 | {
25 | $maxLevels++;
26 | }
27 | else if (
28 | $parent instanceof FunctionStatement
29 | || $parent instanceof Method
30 | || $parent instanceof NormalAnonymousFunctionExpression
31 | || $parent instanceof RootNode
32 | )
33 | {
34 | $foundRoot = true;
35 | break;
36 | }
37 |
38 | $parent = $parent->getParent();
39 | }
40 |
41 | $levels = $this->getLevels();
42 |
43 | if ($levels && $levels->getValue() <= 0)
44 | {
45 | throw ValidationException::invalidExpression($levels);
46 | }
47 |
48 | if ($foundRoot)
49 | {
50 | if ($maxLevels === 0)
51 | {
52 | throw ValidationException::invalidStatementInContext($this);
53 | }
54 |
55 | if ($levels && $levels->getValue() > $maxLevels)
56 | {
57 | throw ValidationException::invalidExpression($levels);
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Nodes/Helpers/Parameter.php:
--------------------------------------------------------------------------------
1 | hasUnpack())
23 | {
24 | if ($default = $this->getDefault())
25 | {
26 | throw ValidationException::invalidSyntax($default);
27 | }
28 | }
29 |
30 | if ($type = $this->getType())
31 | {
32 | $type = $type->unwrapNullable();
33 | if ($type instanceof SpecialType && $type->isStatic())
34 | {
35 | throw ValidationException::invalidNameInContext($type->getName());
36 | }
37 | else if ($type instanceof NamedType && !$type->getName()->isUsableAsParameterType())
38 | {
39 | throw ValidationException::invalidNameInContext($type->getName());
40 | }
41 | }
42 | }
43 |
44 | public function convertToPhpParser()
45 | {
46 | $type = $this->getType();
47 | $default = $this->getDefault();
48 | return new Param(
49 | new Variable(substr($this->getVariable()->getSource(), 1)),
50 | $default ? $default->convertToPhpParser() : null,
51 | $type ? $type->convertToPhpParser() : null,
52 | $this->hasByReference(),
53 | $this->hasUnpack()
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Nodes/ValidationTraits/RightAssocBinopExpression.php:
--------------------------------------------------------------------------------
1 | getLeft()->getRightPrecedence() <= $this->getLeftPrecedence())
18 | {
19 | throw ValidationException::badPrecedence($this->getLeft());
20 | }
21 | if ($this->getRight()->getLeftPrecedence() < $this->getRightPrecedence())
22 | {
23 | throw ValidationException::badPrecedence($this->getRight());
24 | }
25 | }
26 |
27 | protected function extraValidation(int $flags): void
28 | {
29 | $this->validatePrecedence();
30 | }
31 |
32 | private function autocorrectPrecedence(): void
33 | {
34 | $left = $this->getLeft();
35 | if ($this->getLeft()->getRightPrecedence() <= $this->getLeftPrecedence())
36 | {
37 | $left = $left->wrapIn(new ParenthesizedExpression());
38 | $left->_autocorrect();
39 | }
40 |
41 | $right = $this->getRight();
42 | if ($this->getRight()->getLeftPrecedence() < $this->getRightPrecedence())
43 | {
44 | $right = $right->wrapIn(new ParenthesizedExpression());
45 | $right->_autocorrect();
46 | }
47 | }
48 |
49 | protected function extraAutocorrect(): void
50 | {
51 | $this->autocorrectPrecedence();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_STRING.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | 'EXPR_STRING_PART';
4 |
5 | "EXPR_STRING_PART";
6 |
7 | `EXPR_STRING_PART`;
8 |
9 | // normal heredocs
10 | <<
17 | */
18 | class ParenthesizedExpression extends Expression implements WrapperNode
19 | {
20 | use GeneratedParenthesizedExpression;
21 |
22 | public function isConstant(): bool
23 | {
24 | return $this->getExpression()->isConstant();
25 | }
26 |
27 | public function isTemporary(): bool
28 | {
29 | return $this->getExpression()->isTemporary();
30 | }
31 |
32 | protected function extraValidation(int $flags): void
33 | {
34 | if ($flags & self::CTX_WRITE)
35 | {
36 | $parent = $this->getParent();
37 | if (
38 | $parent instanceof ForeachStatement
39 | || ($parent instanceof Key && $parent->getParent() instanceof ForeachStatement)
40 | || $parent instanceof AssignExpression
41 | || $parent instanceof CombinedAssignExpression
42 | || $parent instanceof CrementExpression
43 | )
44 | {
45 | throw ValidationException::invalidExpressionInContext($this);
46 | }
47 | }
48 |
49 | parent::extraValidation($flags);
50 | }
51 |
52 | public function convertToPhpParser()
53 | {
54 | return $this->getExpression()->convertToPhpParser();
55 | }
56 |
57 | public function wrapNode(Node $node): void
58 | {
59 | $this->setExpression($node);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/IssetExpression.php:
--------------------------------------------------------------------------------
1 | getExpressions() as $expression)
22 | {
23 | while ($expression instanceof ParenthesizedExpression)
24 | {
25 | $expression = $expression->getExpression();
26 | }
27 |
28 | if (
29 | (
30 | $expression->isTemporary()
31 | && !$expression instanceof ArrayAccessExpression
32 | && !$expression instanceof PropertyAccessExpression
33 | )
34 | || $expression instanceof FunctionCallExpression
35 | || $expression instanceof MethodCallExpression
36 | || $expression instanceof StaticMethodCallExpression
37 | )
38 | {
39 | throw ValidationException::invalidExpressionInContext($expression);
40 | }
41 | }
42 |
43 | if ($this->getPhpVersion() < PhpVersion::PHP_7_3)
44 | {
45 | self::forbidTrailingSeparator($this->getExpressions());
46 | }
47 | }
48 |
49 | public function convertToPhpParser()
50 | {
51 | $expressions = [];
52 | foreach ($this->getExpressions() as $phiExpr)
53 | {
54 | $expressions[] = $phiExpr->convertToPhpParser();
55 | }
56 | return new Isset_($expressions);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/NormalInterpolatedStringVariableArrayAccess.php:
--------------------------------------------------------------------------------
1 | getIndex();
27 | if ($this->hasMinus() && $index->getType() !== TokenType::T_NUM_STRING)
28 | {
29 | throw ValidationException::invalidSyntax($index);
30 | }
31 | }
32 |
33 | public function convertToPhpParser()
34 | {
35 | $index = $this->getIndex();
36 | if ($index->getType() === TokenType::T_STRING)
37 | {
38 | $dim = new String_($index->getSource());
39 | }
40 | else if ($index->getType() === TokenType::T_VARIABLE)
41 | {
42 | $dim = new Variable(\substr($index->getSource(), 1));
43 | }
44 | else
45 | {
46 | // note: alternative int notations like e.g. 0x0 are not considered T_NUM_STRING
47 | $dim = new LNumber(($this->hasMinus() ? -1 : 1) * (int) $index->getSource());
48 | }
49 |
50 | return new ArrayDimFetch($this->getVariable()->convertToPhpParser(), $dim);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/StringInterpolation/ConstantInterpolatedStringPart.php:
--------------------------------------------------------------------------------
1 | getGrandParent();
23 | if ($grandParent instanceof DoubleQuotedStringLiteral)
24 | {
25 | return '"';
26 | }
27 | else if ($grandParent instanceof ExececutionExpression)
28 | {
29 | return '`';
30 | }
31 | else
32 | {
33 | return null;
34 | }
35 | }
36 |
37 | protected function extraValidation(int $flags): void
38 | {
39 |
40 | try
41 | {
42 | (new LiteralParser($this->getPhpVersion()))->parseStringContentEscapes(
43 | $this->getContent()->getSource(),
44 | $this->getQuoteType()
45 | );
46 | }
47 | catch (LiteralParsingException $e)
48 | {
49 | throw ValidationException::invalidSyntax($this);
50 | }
51 | }
52 |
53 | public function convertToPhpParser()
54 | {
55 | return new EncapsedStringPart(String_::parseEscapeSequences(
56 | $this->getContent()->getSource(),
57 | $this->getQuoteType(),
58 | true
59 | ));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/YieldExpression.php:
--------------------------------------------------------------------------------
1 | getKey();
28 | if ($key && $key->getExpression()->getPrecedence() <= $this->getPrecedence())
29 | {
30 | throw ValidationException::badPrecedence($key->getExpression());
31 | }
32 |
33 | $this->validatePrecedence();
34 |
35 | $this->validateYieldContext();
36 | }
37 |
38 | protected function extraAutocorrect(): void
39 | {
40 | $key = $this->getKey();
41 | if ($key && $key->getExpression()->getPrecedence() <= $this->getPrecedence())
42 | {
43 | $keyExpression = $key->getExpression()->wrapIn(new ParenthesizedExpression());
44 | $keyExpression->autocorrect();
45 | }
46 |
47 | $this->autocorrectPrecedence();
48 | }
49 |
50 | public function convertToPhpParser()
51 | {
52 | $key = $this->getKey();
53 | $value = $this->getExpression();
54 | return new Yield_(
55 | $value ? $value->convertToPhpParser() : null,
56 | $key ? $key->getExpression()->convertToPhpParser() : null
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Parser/data/WORD_TOKEN.txt:
--------------------------------------------------------------------------------
1 | {"algo": "weightedPermute", "max": 1}
2 |
3 | \THIS
4 | \ident\THIS
5 | \THIS\ident
6 |
7 | // regular identifier
8 | aardvark [[0]]
9 |
10 | // regular keywords
11 | abstract [[0]]
12 | and
13 | as
14 | break
15 | case
16 | catch
17 | clone
18 | const
19 | continue
20 | declare
21 | default
22 | die
23 | do
24 | echo
25 | else
26 | elseif
27 | empty
28 | enddeclare
29 | endfor
30 | endforeach
31 | endif
32 | endswitch
33 | endwhile
34 | eval
35 | exit
36 | extends
37 | final
38 | finally
39 | fn
40 | for
41 | foreach
42 | function
43 | global
44 | goto
45 | if
46 | implements
47 | include
48 | include_once
49 | instanceof
50 | insteadof
51 | interface
52 | isset
53 | list
54 | namespace
55 | new
56 | or
57 | print
58 | private
59 | protected
60 | public
61 | require
62 | require_once
63 | return
64 | switch
65 | throw
66 | trait
67 | try
68 | unset
69 | use
70 | var
71 | while
72 | xor
73 | yield
74 | __halt_compiler
75 |
76 | // overloaded keywords
77 | array [[0]]
78 | callable [[0]]
79 | class [[0]]
80 | static [[0]]
81 |
82 | // magic constants
83 | __CLASS__ [[0]]
84 | __DIR__
85 | __FILE__
86 | __FUNCTION__
87 | __LINE__
88 | __METHOD__
89 | __NAMESPACE__
90 | __TRAIT__
91 |
92 | // reserved words - usable as type
93 | int [[0]]
94 | float
95 | bool
96 | string
97 | void
98 | iterable
99 | object
100 |
101 | // reserved words - other
102 | null [[0]]
103 | true [[0]]
104 | false [[0]]
105 |
106 | // soft reserved words
107 | resource [[0]]
108 | mixed
109 | numeric
110 |
111 | // special class names
112 | static [[0]]
113 | self [[0]]
114 | parent [[0]]
115 |
--------------------------------------------------------------------------------
/meta/bin/optimize_parser.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | peek()->getType()', '$this->types[$this->i]', $src);
15 | $src = str_replace('$this->peek(1)->getType()', '$this->types[$this->i + 1]', $src);
16 | $src = str_replace('$this->peek(2)->getType()', '$this->types[$this->i + 2]', $src);
17 |
18 | $src = str_replace('$this->peek()', '$this->tokens[$this->i]', $src);
19 | $src = str_replace('$this->peek(1)', '$this->tokens[$this->i + 1]', $src);
20 | $src = str_replace('$this->peek(2)', '$this->tokens[$this->i + 2]', $src);
21 |
22 | $src = str_replace('$this->read()', '$this->tokens[$this->i++]', $src);
23 | $src = preg_replace('{\$this->opt\(([^)]+)\)}', '($this->types[$this->i] === $1 ? $this->tokens[$this->i++] : null)', $src);
24 |
25 | // replace constants with their literal values
26 | // doesn't do much yet, let's check again after I replace if/else with switch
27 | /** @see https://derickrethans.nl/php7.2-switch.html */
28 |
29 | assert(class_exists(TokenType::class));
30 | $src = preg_replace_callback('{T::([ST]_[A-Z0-9_]+)}', function ($m)
31 | {
32 | return constant(TokenType::class . "::" . $m[1]);
33 | }, $src);
34 |
35 | assert(class_exists(Expression::class));
36 | $src = preg_replace_callback('{Expr::(PRECEDENCE_[A-Z0-9_]+)}', function ($m)
37 | {
38 | return constant(Expression::class . "::" . $m[1]);
39 | }, $src);
40 |
41 | file_put_contents(__DIR__ . "/../../src/Parser.opt.php", $src);
42 |
--------------------------------------------------------------------------------
/tests/LexerTest.php:
--------------------------------------------------------------------------------
1 | getIf();
19 | return $this->getCondition()->isConstant()
20 | && (!$then || $then->isConstant())
21 | && $this->getElse()->isConstant();
22 | }
23 |
24 | protected function getPrecedence(): int
25 | {
26 | return self::PRECEDENCE_TERNARY;
27 | }
28 |
29 | protected function extraValidation(int $flags): void
30 | {
31 | $condition = $this->getCondition();
32 | if ($condition->getPrecedence() < $this->getPrecedence())
33 | {
34 | throw ValidationException::badPrecedence($condition);
35 | }
36 |
37 | $else = $this->getElse();
38 | if ($else->getPrecedence() <= $this->getPrecedence())
39 | {
40 | throw ValidationException::badPrecedence($else);
41 | }
42 | }
43 |
44 | protected function extraAutocorrect(): void
45 | {
46 | $condition = $this->getCondition();
47 | if ($condition->getPrecedence() < $this->getPrecedence())
48 | {
49 | $condition = $condition->wrapIn(new ParenthesizedExpression());
50 | $condition->_autocorrect();
51 | }
52 |
53 | $else = $this->getElse();
54 | if ($else->getPrecedence() <= $this->getPrecedence())
55 | {
56 | $else = $else->wrapIn(new ParenthesizedExpression());
57 | $else->_autocorrect();
58 | }
59 | }
60 |
61 | public function convertToPhpParser()
62 | {
63 | return new Ternary(
64 | $this->getCondition()->convertToPhpParser(),
65 | ($if = $this->getIf()) ? $if->convertToPhpParser() : null,
66 | $this->getElse()->convertToPhpParser()
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Nodes/Expressions/ListExpression.php:
--------------------------------------------------------------------------------
1 | getPhpVersion();
20 |
21 | foreach ($this->getItems() as $item)
22 | {
23 | if ($key = $item->getKey())
24 | {
25 | $key->getExpression()->_validate(self::CTX_READ);
26 | }
27 |
28 | if ($phpVersion < PhpVersion::PHP_7_3 && $byRef = $item->getByReference())
29 | {
30 | throw ValidationException::invalidSyntax($byRef);
31 | }
32 |
33 | if ($value = $item->getValue())
34 | {
35 | if ($value instanceof ArrayExpression) // and vice versa for array
36 | {
37 | throw ValidationException::invalidExpressionInContext($value);
38 | }
39 |
40 | if ($item->hasByReference()) // 7.3 and up, already validated
41 | {
42 | $value->_validate(self::CTX_ALIAS_WRITE);
43 | }
44 | else
45 | {
46 | $value->_validate(self::CTX_WRITE);
47 | }
48 | }
49 | else
50 | {
51 | if ($byRef = $item->getByReference())
52 | {
53 | throw ValidationException::invalidSyntax($byRef->getNextToken() ?? $byRef);
54 | }
55 |
56 | // empty value is otherwise ok
57 | }
58 | }
59 |
60 | if (\count($this->items) === 0)
61 | {
62 | throw ValidationException::invalidExpressionInContext($this);
63 | }
64 | }
65 |
66 | public function convertToPhpParser()
67 | {
68 | $items = $this->getItems()->convertToPhpParser();
69 | if ($this->getItems()->hasTrailingSeparator())
70 | {
71 | $items[] = null;
72 | }
73 | return new List_($items);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Testing/NodeAssertions.php:
--------------------------------------------------------------------------------
1 | getChildNodes() as $child)
20 | {
21 | self::assertSame($node, $child->getParent());
22 | self::assertNodeTreeIsCorrect($child);
23 | }
24 | }
25 |
26 | private static function assertNodeStructEquals(Node $node1, Node $node2): void
27 | {
28 | $repr1 = self::reprNode($node1);
29 | $repr2 = self::reprNode($node2);
30 | self::assertEquals($repr1, $repr2,
31 | "Nodes don't match\n"
32 | . $repr1 . "\n"
33 | . $repr2
34 | );
35 | }
36 |
37 | public static function reprNode(Node $node): string
38 | {
39 | if ($node instanceof Nodes\Expressions\NumberLiteral)
40 | {
41 | return $node->getToken()->getSource();
42 | }
43 | else if ($node instanceof CompoundNode)
44 | {
45 | $children = [];
46 | foreach ($node->getChildNodes() as $child)
47 | {
48 | if ($child)
49 | {
50 | $children[] = self::reprNode($child);
51 | }
52 | }
53 | return $node->repr() . "(" . implode(", ", $children) . ")";
54 | }
55 | else if ($node instanceof NodesList || $node instanceof SeparatedNodesList)
56 | {
57 | $items = [];
58 | foreach ($node->getChildNodes() as $item)
59 | {
60 | $items[] = self::reprNode($item);
61 | }
62 | return "[" . implode(", ", $items) . "]";
63 | }
64 | else if ($node instanceof Token)
65 | {
66 | if (in_array($node->getType(), [TokenType::S_LEFT_PARENTHESIS, TokenType::S_RIGHT_PARENTHESIS], true))
67 | {
68 | return "`" . $node->getSource() . "`";
69 | }
70 | return $node->getSource();
71 | }
72 | else
73 | {
74 | throw new \RuntimeException(\get_class($node));
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Exception/ValidationException.php:
--------------------------------------------------------------------------------
1 | repr() . " is required", $node);
19 | }
20 |
21 | /**
22 | * @param int[] $expected expected token types
23 | */
24 | public static function invalidSyntax(Node $node, ?array $expected = null): self
25 | {
26 | if (!$expected)
27 | {
28 | return new self("Invalid syntax", $node);
29 | }
30 | else
31 | {
32 | return new self(
33 | "Token is expected to be one of " . \implode(", ", \array_map([TokenType::class, "typeToString"], $expected)),
34 | $node
35 | );
36 | }
37 | }
38 |
39 | public static function missingWhitespace(Node $node): self
40 | {
41 | return new self("Missing whitespace", $node);
42 | }
43 |
44 | public static function invalidExpression(Expression $node, ?Node $pointAt = null): self
45 | {
46 | return new self("Invalid expression", $pointAt ?? $node);
47 | }
48 |
49 | public static function invalidStatementInContext(Statement $node): self
50 | {
51 | return new self("Statement is not valid in this context", $node);
52 | }
53 |
54 | public static function invalidExpressionInContext(Expression $node, ?Node $pointAt = null): self
55 | {
56 | return new self("Expression is not valid in this context", $pointAt ?? $node);
57 | }
58 |
59 | public static function badPrecedence(Expression $node): self
60 | {
61 | return new self($node->repr() . " is of the wrong precedence for this place", $node);
62 | }
63 |
64 | /** @param Name|Token $node */
65 | public static function invalidNameInContext(Node $node): self
66 | {
67 | return new self("Can't use this name in this context", $node);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/Parser/data/EXPR_SYNTAX.txt:
--------------------------------------------------------------------------------
1 | {"algo": "permute"}
2 |
3 | // specifically to test expression syntax edge cases
4 |
5 | $v <> $v
6 |
7 | CAST EXPR
8 |
9 | die($v,)
10 | die($v, $v)
11 | exit()
12 | exit(EXPR)
13 | exit($v,)
14 | exit($v, $v)
15 |
16 | empty()
17 | empty(EXPR)
18 | empty($v,)
19 | empty($v, $v)
20 |
21 | eval()
22 | eval(EXPR)
23 | eval($v,)
24 | eval($v, $v)
25 |
26 | isset()
27 | isset(EXPR)
28 | isset($v,)
29 | isset($v, $v)
30 |
31 | []
32 | [$v]
33 | [$v,]
34 | [$v, $v]
35 | [EXPR => $v]
36 | [...$v]
37 | [$v, ...$v]
38 | [...$v, $v]
39 | [...$v] = $v
40 | [...]
41 | [&$v]
42 | [$v => &$v]
43 | [...&$v]
44 | array()
45 | array($v)
46 | array($v,)
47 | array($v, $v)
48 | array(EXPR => $v)
49 | array(...$v)
50 | array($v, ...$v)
51 | array(...$v, $v)
52 | array(...$v) = $v
53 | array(...)
54 | array(&$v)
55 | array($v => &$v)
56 | array(...&$v)
57 |
58 | fn () => null
59 | static fn () => null
60 | fn & () => null
61 | fn (EXPR_SYNTAX_PARAM) => null
62 | fn (EXPR_SYNTAX_PARAM, EXPR_SYNTAX_PARAM) => null
63 | fn (): TYPE => null
64 | fn () => {}
65 |
66 | function () {}
67 | static function () {}
68 | function & () {}
69 | function (EXPR_SYNTAX_PARAM) {}
70 | function (EXPR_SYNTAX_PARAM, EXPR_SYNTAX_PARAM) {}
71 | function () use () {}
72 | function () use ($v) {}
73 | function () use (&$v) {}
74 | function () use ($v, $v) {}
75 | function () use ($v,) {}
76 | function (): TYPE {}
77 |
78 | EXPR_SYNTAX_NUM_PREFIX{*empty*}EXPR_SYNTAX_NUM_PART
79 | EXPR_SYNTAX_NUM_PREFIX{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART
80 | EXPR_SYNTAX_NUM_PREFIX{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART
81 | EXPR_SYNTAX_NUM_PREFIX{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART
82 | EXPR_SYNTAX_NUM_PREFIX{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART{*empty*}EXPR_SYNTAX_NUM_PART
83 |
84 | // integer overflow
85 | // TODO 9431597651654654687651616546512616546546218794564645434
86 |
--------------------------------------------------------------------------------