Created
August 9, 2011 17:17
-
-
Save thekid/1134620 to your computer and use it in GitHub Desktop.
XP Language: Null-Safe Object Operator Patch
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 | |
| /* This class is part of the XP framework | |
| * | |
| * $Id$ | |
| */ | |
| uses('xp.compiler.ast.Node'); | |
| /** | |
| * Coalesce operator | |
| * | |
| * Note: The following two are equivalent: | |
| * <code> | |
| * $a= $b ?? $c; | |
| * $a= NULL === ($__r= @$b) ? $c : $__r; | |
| * </code> | |
| */ | |
| class CoalesceNode extends xp·compiler·ast·Node { | |
| public $expression = NULL; | |
| public $default = NULL; | |
| } | |
| ?> |
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/compiler/src/main/php/xp/compiler/ast/MemberAccessNode.class.php b/compiler/src/main/php/xp/compiler/ast/MemberAccessNode.class.php | |
| index a19f3e4..f9ae224 100644 | |
| --- a/compiler/src/main/php/xp/compiler/ast/MemberAccessNode.class.php | |
| +++ b/compiler/src/main/php/xp/compiler/ast/MemberAccessNode.class.php | |
| @@ -16,16 +16,19 @@ | |
| class MemberAccessNode extends xp·compiler·ast·Node { | |
| public $target= NULL; | |
| public $name= ''; | |
| + public $verify= FALSE; | |
| /** | |
| * Constructor | |
| * | |
| * @param xp.compiler.ast.Node target | |
| * @param string name | |
| + * @param bool verify | |
| */ | |
| - public function __construct($target= NULL, $name= '') { | |
| + public function __construct($target= NULL, $name= '', $verify= FALSE) { | |
| $this->target= $target; | |
| $this->name= $name; | |
| + $this->verify= $verify; | |
| } | |
| /** | |
| diff --git a/compiler/src/main/php/xp/compiler/ast/MethodCallNode.class.php b/compiler/src/main/php/xp/compiler/ast/MethodCallNode.class.php | |
| index a8039d3..9918a24 100644 | |
| --- a/compiler/src/main/php/xp/compiler/ast/MethodCallNode.class.php | |
| +++ b/compiler/src/main/php/xp/compiler/ast/MethodCallNode.class.php | |
| @@ -17,6 +17,7 @@ | |
| public $target= NULL; | |
| public $name= ''; | |
| public $arguments= array(); | |
| + public $verify= FALSE; | |
| /** | |
| * Creates a new InvocationNode object | |
| @@ -24,11 +25,13 @@ | |
| * @param xp.compiler.ast.Node target | |
| * @param string name | |
| * @param xp.compiler.ast.Node[] arguments | |
| + * @param bool verify | |
| */ | |
| - public function __construct($target= NULL, $name= '', $arguments= NULL) { | |
| + public function __construct($target= NULL, $name= '', $arguments= NULL, $verify= FALSE) { | |
| $this->target= $target; | |
| $this->name= $name; | |
| $this->arguments= $arguments; | |
| + $this->verify= $verify; | |
| } | |
| } | |
| ?> | |
| diff --git a/compiler/src/main/php/xp/compiler/emit/source/Emitter.class.php b/compiler/src/main/php/xp/compiler/emit/source/Emitter.class.php | |
| index 8338b65..1abd933 100644 | |
| --- a/compiler/src/main/php/xp/compiler/emit/source/Emitter.class.php | |
| +++ b/compiler/src/main/php/xp/compiler/emit/source/Emitter.class.php | |
| @@ -421,6 +421,12 @@ | |
| // Manually verify as we can then rely on call target type being available | |
| if (!$this->checks->verify($call, $this->scope[0], $this, TRUE)) return; | |
| + | |
| + // - $instance?.method() | |
| + if ($call->verify) { | |
| + $op->insert('($··r= ', $mark); | |
| + $op->append(') === NULL ? NULL : $··r'); | |
| + } | |
| // Rewrite for unsupported syntax | |
| // - new Date().toString() to create(new Date()).toString() | |
| @@ -439,7 +445,7 @@ | |
| $op->append('->'.$call->name); | |
| $this->emitInvocationArguments($op, (array)$call->arguments); | |
| - | |
| + | |
| // Record type | |
| $this->scope[0]->setType($call, $ptr->hasMethod($call->name) ? $ptr->getMethod($call->name)->returns : TypeName::$VAR); | |
| } | |
| @@ -481,6 +487,12 @@ | |
| // Manually verify as we can then rely on call target type being available | |
| if (!$this->checks->verify($access, $this->scope[0], $this, TRUE)) return; | |
| + // - $instance?.property | |
| + if ($access->verify) { | |
| + $op->insert('($··r= ', $mark); | |
| + $op->append(') === NULL ? NULL : $··r'); | |
| + } | |
| + | |
| // Rewrite for unsupported syntax | |
| // - new Person().name to create(new Person()).name | |
| // - (<expr>).name to create(<expr>).name | |
| @@ -676,6 +688,20 @@ | |
| } | |
| /** | |
| + * Emit coalesce | |
| + * | |
| + * @param resource op | |
| + * @param xp.compiler.ast.CoalesceNode coalesce | |
| + */ | |
| + protected function emitCoalesce($op, CoalesceNode $coalesce) { | |
| + $op->append('NULL === ($··r= @'); | |
| + $this->emitOne($op, $coalesce->expresssion); | |
| + $op->append(') ? '); | |
| + $this->emitOne($op, $coalesce->default); | |
| + $op->append(': $··r'); | |
| + } | |
| + | |
| + /** | |
| * Emit ternary operator node | |
| * | |
| * Note: The following two are equivalent: | |
| diff --git a/compiler/src/main/php/xp/compiler/grammar/xp.jay b/compiler/src/main/php/xp/compiler/grammar/xp.jay | |
| index 024099e..6278529 100644 | |
| --- a/compiler/src/main/php/xp/compiler/grammar/xp.jay | |
| +++ b/compiler/src/main/php/xp/compiler/grammar/xp.jay | |
| @@ -54,6 +54,7 @@ | |
| 'xp.compiler.ast.BinaryOpNode', | |
| 'xp.compiler.ast.BooleanOpNode', | |
| 'xp.compiler.ast.UnaryOpNode', | |
| + 'xp.compiler.ast.CoalesceNode', | |
| 'xp.compiler.ast.TernaryNode', | |
| 'xp.compiler.ast.SwitchNode', | |
| 'xp.compiler.ast.CaseNode', | |
| @@ -74,6 +75,7 @@ | |
| %left ',' | |
| %left T_BOOLEAN_OR | |
| %left T_BOOLEAN_AND | |
| +%left T_COALESCE | |
| %right '=' T_ADD_EQUAL T_SUB_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL | |
| %left '?' ':' | |
| %left '|' | |
| @@ -124,6 +126,8 @@ | |
| %token T_AS 331 | |
| %token T_THIS 332 | |
| %token T_CONST 334 | |
| +%token T_SOAK 335 | |
| +%token T_COALESCE 336 | |
| %token T_VARIABLE 340 | |
| %token T_RETURN 341 | |
| @@ -466,6 +470,7 @@ parameters: | |
| parameter: | |
| paramtyperef T_VARIABLE initialization_opt { $$= array_merge(array('name' => $2), $1); $3 && $$['default']= $3; } | |
| | paramtyperef T_DOTS '.' T_VARIABLE { $$= array_merge(array('name' => $4, 'vararg' => TRUE), $1); } | |
| + | paramtyperef T_SOAK T_DOTS T_VARIABLE { $$= array_merge(array('name' => $4, 'vararg' => TRUE, 'check' => FALSE), $1); } | |
| ; | |
| paramtyperef: | |
| @@ -780,6 +785,11 @@ expression: | |
| $$->expression= $3; | |
| $$->conditional= $5; | |
| } | |
| + | expression T_COALESCE expression { | |
| + $$= $yyLex->create(new CoalesceNode()); | |
| + $$->expresssion= $1; | |
| + $$->default= $3; | |
| + } | |
| | expression T_BOOLEAN_OR expression { | |
| $$= $yyLex->create(new BinaryOpNode()); | |
| $$->lhs= $1; | |
| @@ -949,17 +959,22 @@ chain: | |
| '[' { $1= $yyLex->create(new ArrayAccessNode(NULL)); } expression_opt ']' { | |
| $1->offset= $3; | |
| } | |
| - | '.' member { | |
| - $$= $yyLex->create(new MemberAccessNode(NULL, $2)); | |
| + | access member { | |
| + $$= $yyLex->create(new MemberAccessNode(NULL, $2, $1)); | |
| } | |
| - | '.' member '(' { $1= $yyLex->create(new MethodCallNode(NULL, $2)); } expressionlist_opt ')' { | |
| + | access member '(' { $1= $yyLex->create(new MethodCallNode(NULL, $2, NULL, $1)); } expressionlist_opt ')' { | |
| $1->arguments= $5; | |
| } | |
| - | '.' '(' { $1= $yyLex->create(new InstanceCallNode(NULL)); } expressionlist_opt ')' { | |
| + | access '(' { $1= $yyLex->create(new InstanceCallNode(NULL)); } expressionlist_opt ')' { | |
| $1->arguments= $4; | |
| } | |
| ; | |
| +access: | |
| + '.' { $$= FALSE; } | |
| + | T_SOAK { $$= TRUE; } | |
| +; | |
| + | |
| member: | |
| T_WORD | |
| | T_IN | |
| diff --git a/compiler/src/main/php/xp/compiler/syntax/xp/Lexer.class.php b/compiler/src/main/php/xp/compiler/syntax/xp/Lexer.class.php | |
| index 3903f43..a5e2b75 100644 | |
| --- a/compiler/src/main/php/xp/compiler/syntax/xp/Lexer.class.php | |
| +++ b/compiler/src/main/php/xp/compiler/syntax/xp/Lexer.class.php | |
| @@ -91,6 +91,7 @@ | |
| '|' => array('||' => xp·compiler·syntax·xp·Parser::T_BOOLEAN_OR, '|=' => xp·compiler·syntax·xp·Parser::T_OR_EQUAL), | |
| '&' => array('&&' => xp·compiler·syntax·xp·Parser::T_BOOLEAN_AND, '&=' => xp·compiler·syntax·xp·Parser::T_AND_EQUAL), | |
| '^' => array('^=' => xp·compiler·syntax·xp·Parser::T_XOR_EQUAL), | |
| + '?' => array('?.' => xp·compiler·syntax·xp·Parser::T_SOAK, '??' => xp·compiler·syntax·xp·Parser::T_COALESCE), | |
| ); | |
| const | |
| diff --git a/compiler/src/test/php/net/xp_lang/tests/execution/source/OperatorTest.class.php b/compiler/src/test/php/net/xp_lang/tests/execution/source/OperatorTest.class.php | |
| index 7d16d92..5b64afe 100644 | |
| --- a/compiler/src/test/php/net/xp_lang/tests/execution/source/OperatorTest.class.php | |
| +++ b/compiler/src/test/php/net/xp_lang/tests/execution/source/OperatorTest.class.php | |
| @@ -324,5 +324,41 @@ | |
| public function conditionalAssignmentResult() { | |
| $this->assertEquals(1, $this->run('$a= 2; $b= 0; $a && $b+= 1; return $b;')); | |
| } | |
| + | |
| + /** | |
| + * Test coalesce operator | |
| + * | |
| + */ | |
| + #[@test] | |
| + public function coalesce() { | |
| + $this->assertEquals(2, $this->run('$a= 2; return $a ?? 1;')); | |
| + } | |
| + | |
| + /** | |
| + * Test coalesce operator | |
| + * | |
| + */ | |
| + #[@test] | |
| + public function coalesceWithNull() { | |
| + $this->assertEquals(1, $this->run('$a= null; return $a ?? 1;')); | |
| + } | |
| + | |
| + /** | |
| + * Test coalesce operator | |
| + * | |
| + */ | |
| + #[@test] | |
| + public function coalesceWithUnset() { | |
| + $this->assertEquals(1, $this->run('unset($a); return $a ?? 1;')); | |
| + } | |
| + | |
| + /** | |
| + * Test coalesce operator | |
| + * | |
| + */ | |
| + #[@test] | |
| + public function coalesceWithUnsetOffset() { | |
| + $this->assertEquals(1, $this->run('$a= [1, 2, 3]; return $a[4] ?? 1;')); | |
| + } | |
| } | |
| ?> |
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 | |
| /* This class is part of the XP framework | |
| * | |
| * $Id$ | |
| */ | |
| $package= 'net.xp_lang.tests.execution.source'; | |
| uses('net.xp_lang.tests.execution.source.ExecutionTest'); | |
| /** | |
| * Tests null-safe object operator ?. | |
| * | |
| */ | |
| class net·xp_lang·tests·execution·source·NullSafeObjectOperatorTest extends ExecutionTest { | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test] | |
| public function nullDotMethod() { | |
| $this->assertNull($this->run('$instance= null; return $instance?.method();')); | |
| } | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test] | |
| public function nullReturnedFromMethod() { | |
| $this->assertNull($this->run('$v= new util.collections.Vector<var>(); $v.add(null); return $v.get(0)?.hashCode();')); | |
| } | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test] | |
| public function nullNotReturnedFromMethod() { | |
| $this->assertNotEquals(NULL, $this->run('$v= new util.collections.Vector<var>(); $v.add($this); return $v.get(0)?.hashCode();')); | |
| } | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test] | |
| public function twoNullSafeObjectOperatorsInOneChain() { | |
| $this->assertNull($this->run('$v= new util.collections.Vector<var>(); $v.add(null); return $v?.get(0)?.hashCode();')); | |
| } | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test] | |
| public function nullDotProperty() { | |
| $this->assertNull($this->run('$instance= null; return $instance?.property;')); | |
| } | |
| /** | |
| * Test | |
| * | |
| */ | |
| #[@test, @ignore('Generates syntax error, unexpected ")"')] | |
| public function nullDotInvoke() { | |
| $this->compile('$instance= null; return $instance?.();'); | |
| } | |
| } | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment