Created
December 27, 2025 16:27
-
-
Save thekid/6f6798bc05995caac6b7e825d6d5a76f to your computer and use it in GitHub Desktop.
Catch with is operator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/src/main/php/lang/ast/syntax/php/IsOperator.class.php b/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| index 7e34fc1..f100d10 100755 | |
| --- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| +++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| @@ -6,6 +6,8 @@ use lang\ast\nodes\{ | |
| Assignment, | |
| BinaryExpression, | |
| Braced, | |
| + CatchStatement, | |
| + IfStatement, | |
| InstanceExpression, | |
| InstanceOfExpression, | |
| InvokeExpression, | |
| @@ -14,11 +16,13 @@ use lang\ast\nodes\{ | |
| MatchExpression, | |
| OffsetExpression, | |
| ScopeExpression, | |
| + ThrowExpression, | |
| + TryStatement, | |
| Variable | |
| }; | |
| use lang\ast\syntax\Extension; | |
| use lang\ast\types\{IsArray, IsLiteral, IsFunction, IsMap, IsUnion, IsIntersection, IsNullable, IsValue}; | |
| -use lang\ast\{Node, Token}; | |
| +use lang\ast\{Node, Type, Token}; | |
| class IsOperator implements Extension { | |
| @@ -170,6 +174,69 @@ class IsOperator implements Extension { | |
| return new MatchExpression($patterns ? null : $condition, $cases, $default, $token->line); | |
| }); | |
| + $language->stmt('try', function($parse, $token) use($pattern) { | |
| + $parse->expecting('{', 'try'); | |
| + $statements= $this->statements($parse); | |
| + $parse->expecting('}', 'try'); | |
| + | |
| + $catches= []; | |
| + while ('catch' === $parse->token->value) { | |
| + $parse->forward(); | |
| + | |
| + $parse->expecting('(', 'catch'); | |
| + if ('name' === $parse->token->kind) { | |
| + | |
| + // catch (Exception $e), catch (A|B $e) | |
| + // catch (Exception) | |
| + $types= new IsCompound([], '|'); | |
| + do { | |
| + $types->patterns[]= new IsValue($parse->scope->resolve($parse->token->value)); | |
| + $parse->forward(); | |
| + } while ('|' === $parse->token->value && $parse->forward() | true); | |
| + | |
| + if ('variable' === $parse->token->kind) { | |
| + $parse->forward(); | |
| + $variable= $parse->token->value; | |
| + $parse->forward(); | |
| + } else { | |
| + $variable= null; | |
| + } | |
| + } else if ('variable' === $parse->token->kind) { | |
| + | |
| + // catch ($e) | |
| + // catch ($e is Exception) | |
| + // catch ($e is Exception|Error) | |
| + // catch ($e is Exception(message: $message)) | |
| + $parse->forward(); | |
| + $variable= $parse->token->value; | |
| + $parse->forward(); | |
| + | |
| + if ('is' === $parse->token->value) { | |
| + $parse->forward(); | |
| + $types= $pattern($parse, $this); | |
| + } else { | |
| + $types= null; | |
| + } | |
| + } | |
| + $parse->expecting(')', 'catch'); | |
| + | |
| + $parse->expecting('{', 'catch'); | |
| + $catches[]= new CatchStatement($types, $variable, $this->statements($parse), $parse->token->line); | |
| + $parse->expecting('}', 'catch'); | |
| + } | |
| + | |
| + if ('finally' === $parse->token->value) { | |
| + $parse->forward(); | |
| + $parse->expecting('{', 'finally'); | |
| + $finally= $this->statements($parse); | |
| + $parse->expecting('}', 'finally'); | |
| + } else { | |
| + $finally= null; | |
| + } | |
| + | |
| + return new TryStatement($statements, $catches, $finally, $token->line); | |
| + }); | |
| + | |
| $match= function($codegen, $expression, $pattern) use(&$match) { | |
| // Basic type matching, literal comparison and variable binding | |
| @@ -269,5 +336,34 @@ class IsOperator implements Extension { | |
| $emitter->transform('is', function($codegen, $node) use($match) { | |
| return $match($codegen, $node->expression, $node->pattern); | |
| }); | |
| + | |
| + $emitter->transform('try', function($codegen, $node) use($match) { | |
| + if (empty($node->catches)) return $node; | |
| + | |
| + // Rewrite catches into if/else cascade | |
| + $bind= new Variable($codegen->symbol()); | |
| + $catches= [new CatchStatement(null, $bind->pointer, [], $node->catches[0]->line)]; | |
| + $cascade= &$catches[0]->body; | |
| + | |
| + foreach ($node->catches as $catch) { | |
| + $handle= new IfStatement( | |
| + $catch->types ? $match($codegen, $bind, $catch->types) : new Literal('1'), | |
| + $catch->variable | |
| + ? [new Assignment(new Variable($catch->variable), '=', $bind), ...$catch->body] | |
| + : $catch->body | |
| + , | |
| + [] | |
| + ); | |
| + | |
| + if ($cascade) { | |
| + $cascade[0]->otherwise= [$handle]; | |
| + $cascade= &$handle->otherwise; | |
| + } else { | |
| + $cascade= [$handle]; | |
| + } | |
| + } | |
| + | |
| + return [new TryStatement($node->body, $catches, $node->finally)]; | |
| + }); | |
| } | |
| } | |
| \ No newline at end of file |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php namespace lang\ast\syntax\php\unittest; | |
| use lang\ast\unittest\emit\EmittingTest; | |
| use test\{Assert, Test, Values}; | |
| class CatchTest extends EmittingTest { | |
| #[Test, Values(['$e', 'IllegalAccessException $e', 'IllegalAccessException|Error $e'])] | |
| public function default_try_with($catch) { | |
| Assert::equals('Test', $this->run('use lang\\IllegalAccessException; class %T { | |
| public function run() { | |
| try { | |
| throw new IllegalAccessException("Test"); | |
| } catch ('.$catch.') { | |
| return $e->getMessage(); | |
| } | |
| } | |
| }')); | |
| } | |
| #[Test] | |
| public function try_without_variable() { | |
| Assert::equals('Caught', $this->run('use lang\\IllegalAccessException; class %T { | |
| public function run() { | |
| try { | |
| throw new IllegalAccessException("Test"); | |
| } catch (IllegalAccessException) { | |
| return "Caught"; | |
| } | |
| } | |
| }')); | |
| } | |
| #[Test, Values(['IllegalAccessException', 'IllegalAccessException|Error'])] | |
| public function try_with_pattern_with($type) { | |
| Assert::equals('Test', $this->run('use lang\\IllegalAccessException; class %T { | |
| public function run() { | |
| try { | |
| throw new IllegalAccessException("Test"); | |
| } catch ($e is '.$type.') { | |
| return $e->getMessage(); | |
| } | |
| } | |
| }')); | |
| } | |
| #[Test] | |
| public function try_with_pattern_binding() { | |
| Assert::equals('Test', $this->run('use lang\\IllegalAccessException; class %T { | |
| public function run() { | |
| try { | |
| throw new IllegalAccessException("Test"); | |
| } catch ($e is IllegalAccessException(message: $message)) { | |
| return $message; | |
| } | |
| } | |
| }')); | |
| } | |
| #[Test, Values([[404, 'Client error'], [503, 'Server error'], [600, 'Unknown error 600']])] | |
| public function try_with_pattern_matching($status, $expected) { | |
| Assert::equals($expected, $this->run('use lang\\ast\\syntax\\php\\unittest\\Error; class %T { | |
| public function run($status) { | |
| try { | |
| throw new Error($status); | |
| } catch ($e is Error(status: >=400 & <500)) { | |
| return "Client error"; | |
| } catch ($e is Error(status: >=500 & <600)) { | |
| return "Server error"; | |
| } catch ($e is Error) { | |
| return "Unknown error ".$e->status; | |
| } | |
| } | |
| }', $status)); | |
| } | |
| #[Test] | |
| public function try_with_unmatched_pattern() { | |
| Assert::equals('Uncaught', $this->run('use lang\\IllegalAccessException; class %T { | |
| public function run() { | |
| try { | |
| throw new IllegalAccessException("Test"); | |
| } catch ($e is IllegalAccessException(message: "Database")|Error(status: 500)) { | |
| return "Internal server error"; | |
| } catch ($e) { | |
| return "Uncaught"; | |
| } | |
| } | |
| }')); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php namespace lang\ast\syntax\php\unittest; | |
| use lang\Throwable; | |
| class Error extends Throwable { | |
| public $status; | |
| public function __construct($status, $message= 'Error') { | |
| parent::__construct('Status #'.$status.': '.$message); | |
| $this->status= $status; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment