Skip to content

Instantly share code, notes, and snippets.

@thekid
Created August 9, 2011 17:17
Show Gist options
  • Select an option

  • Save thekid/1134620 to your computer and use it in GitHub Desktop.

Select an option

Save thekid/1134620 to your computer and use it in GitHub Desktop.
XP Language: Null-Safe Object Operator Patch
<?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;
}
?>
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;'));
+ }
}
?>
<?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