├── .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 | --------------------------------------------------------------------------------