Skip to content

Commit eee1d3e

Browse files
committed
Initial release
Based on xp-framework/compiler#68
0 parents  commit eee1d3e

File tree

8 files changed

+382
-0
lines changed

8 files changed

+382
-0
lines changed

.travis.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# xp-lang/php-is-operator
2+
3+
language: php
4+
5+
dist: trusty
6+
7+
php:
8+
- 7.0
9+
- 7.1
10+
- 7.2
11+
- 7.3
12+
- 7.4snapshot
13+
- nightly
14+
15+
matrix:
16+
allow_failures:
17+
- php: nightly
18+
19+
before_script:
20+
- curl -sSL https://dl.bintray.com/xp-runners/generic/xp-run-8.1.7.sh > xp-run
21+
- composer install --prefer-dist
22+
- echo "vendor/autoload.php" > composer.pth
23+
- echo "vendor/xp-framework/compiler/src/test/php" >> composer.pth
24+
25+
script:
26+
- sh xp-run xp.unittest.TestRunner src/test/php

ChangeLog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Is operator for PHP - ChangeLog
2+
===============================
3+
4+
## ?.?.? / ????-??-??
5+
6+
## 0.1.0 / 2019-09-09
7+
8+
* Hello World! First release - @thekid

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
Is operator for PHP
2+
===================
3+
4+
[![Build Status on TravisCI](https://secure.travis-ci.org/xp-lang/php-is-operator.svg)](http://travis-ci.org/xp-lang/php-is-operator)
5+
[![XP Framework Module](https://raw.githubusercontent.com/xp-framework/web/master/static/xp-framework-badge.png)](https://github.com/xp-framework/core)
6+
[![BSD Licence](https://raw.githubusercontent.com/xp-framework/web/master/static/licence-bsd.png)](https://github.com/xp-framework/core/blob/master/LICENCE.md)
7+
[![Supports PHP 7.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-7_0plus.png)](http://php.net/)
8+
[![Latest Stable Version](https://poser.pugx.org/xp-lang/php-is-operator/version.png)](https://packagist.org/packages/xp-lang/php-is-operator)
9+
10+
Plugin for the [XP Compiler](https://github.com/xp-framework/compiler/) which adds switch expressions to the PHP language.
11+
12+
Before
13+
------
14+
A mix of functions, syntax and XP core functionality `is()`:
15+
16+
```php
17+
is_string($value) // for primitives, use is_[T]()
18+
is_callable($value) // for pseudo types callable, array, object
19+
is_array($value) || $value instanceof \Traversable // no is_iterable in PHP 5 and 7.0
20+
$value instanceof Date // for value types
21+
null === $value || is_int($value) // nullable types cannot be tested directly
22+
is('[:string]', $value) // for types beyond PHP type system
23+
is('string|util.URI', $value) // for types beyond PHP type system
24+
```
25+
26+
After
27+
-----
28+
Anything that works as a parameter, property or return type can be used with the `is` operator.
29+
30+
```php
31+
$value is string
32+
$value is callable
33+
$value is iterable
34+
$value is Date
35+
$value is ?int
36+
$value is array<string, string>
37+
$value is string|URI
38+
```
39+
40+
Installation
41+
------------
42+
After installing the XP Compiler into your project, also include this plugin.
43+
44+
```bash
45+
$ composer require xp-framework/compiler
46+
# ...
47+
48+
$ composer require xp-lang/php-is-operator
49+
# ...
50+
```
51+
52+
No further action is required.
53+
54+
See also
55+
--------
56+
* https://docs.hhvm.com/hack/expressions-and-operators/is
57+
* https://kotlinlang.org/docs/reference/typecasts.html
58+
* https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is

class.pth

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src/main/php/
2+
src/test/php/

composer.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name" : "xp-lang/php-is-operator",
3+
"type" : "library",
4+
"homepage" : "http://xp-framework.net/",
5+
"license" : "BSD-3-Clause",
6+
"description" : "Is operator for PHP",
7+
"keywords": ["language", "module", "xp"],
8+
"require" : {
9+
"xp-framework/core": "^9.0 | ^8.0",
10+
"xp-framework/compiler": "^4.0",
11+
"php" : ">=7.0.0"
12+
},
13+
"require-dev" : {
14+
"xp-framework/unittest": "^9.0 | ^8.0"
15+
},
16+
"autoload" : {
17+
"files" : ["src/main/php/autoload.php"]
18+
}
19+
}

src/main/php/autoload.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php namespace xp;
2+
3+
\lang\ClassLoader::registerPath(__DIR__);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php namespace lang\ast\syntax\php;
2+
3+
use lang\ast\ArrayType;
4+
use lang\ast\FunctionType;
5+
use lang\ast\MapType;
6+
use lang\ast\Node;
7+
use lang\ast\UnionType;
8+
use lang\ast\nodes\BinaryExpression;
9+
use lang\ast\nodes\InstanceOfExpression;
10+
use lang\ast\nodes\InvokeExpression;
11+
use lang\ast\nodes\Literal;
12+
use lang\ast\syntax\Extension;
13+
14+
class IsOperator implements Extension {
15+
16+
public function setup($language, $emitter) {
17+
$language->infix('is', 60, function($parse, $token, $left) {
18+
$t= $this->type($parse, true);
19+
20+
$node= new InstanceOfExpression($left, $t ?: $this->expression($parse, 0));
21+
$node->kind= 'is';
22+
return $node;
23+
});
24+
25+
$test= function($literal, $expr) {
26+
static $is= [
27+
'string' => true,
28+
'int' => true,
29+
'float' => true,
30+
'bool' => true,
31+
'array' => true,
32+
'object' => true,
33+
'iterable' => true,
34+
'callable' => true
35+
];
36+
37+
if (isset($is[$literal])) {
38+
return new InvokeExpression(new Literal('is_'.$literal), [$expr]);
39+
} else {
40+
return new InstanceOfExpression($expr, new Literal($literal));
41+
}
42+
};
43+
44+
$emitter->transform('is', function($codegen, $node) use($test) {
45+
$t= $node->type;
46+
if ($t instanceof Node) {
47+
return new InvokeExpression(new Literal('is'), [$node->type, $node->expression]);
48+
}
49+
50+
// Verify builtin primitives with is_XXX(), value types with instanceof, others using is()
51+
if ($t instanceof FunctionType || $t instanceof ArrayType || $t instanceof MapType || $t instanceof UnionType) {
52+
return new InvokeExpression(new Literal('is'), [new Literal('"'.$t->name().'"'), $node->expression]);
53+
} else {
54+
$literal= $t->literal();
55+
if ('?' === $literal[0]) {
56+
return new BinaryExpression(
57+
new BinaryExpression(new Literal('null'), '===', $node->expression),
58+
'||',
59+
$test(substr($literal, 1), $node->expression)
60+
);
61+
} else {
62+
return $test($literal, $node->expression);
63+
}
64+
}
65+
});
66+
}
67+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php namespace lang\ast\syntax\php\unittest;
2+
3+
use lang\ast\unittest\emit\EmittingTest;
4+
5+
class IsOperatorTest extends EmittingTest {
6+
7+
#[@test]
8+
public function this_is_self() {
9+
$r= $this->run('class <T> {
10+
public function run() {
11+
return $this is self;
12+
}
13+
}');
14+
15+
$this->assertTrue($r);
16+
}
17+
18+
#[@test]
19+
public function new_self_is_static() {
20+
$r= $this->run('class <T> {
21+
public function run() {
22+
return new self() is static;
23+
}
24+
}');
25+
26+
$this->assertTrue($r);
27+
}
28+
29+
#[@test]
30+
public function is_qualified_type() {
31+
$r= $this->run('class <T> {
32+
public function run() {
33+
return new \util\Date() is \util\Date;
34+
}
35+
}');
36+
37+
$this->assertTrue($r);
38+
}
39+
40+
#[@test]
41+
public function is_imported_type() {
42+
$r= $this->run('use util\Date; class <T> {
43+
public function run() {
44+
return new Date() is Date;
45+
}
46+
}');
47+
48+
$this->assertTrue($r);
49+
}
50+
51+
#[@test]
52+
public function is_aliased_type() {
53+
$r= $this->run('use util\Date as D; class <T> {
54+
public function run() {
55+
return new D() is D;
56+
}
57+
}');
58+
59+
$this->assertTrue($r);
60+
}
61+
62+
#[@test]
63+
public function is_type_variable() {
64+
$r= $this->run('class <T> {
65+
public function run() {
66+
$type= self::class;
67+
return new self() is $type;
68+
}
69+
}');
70+
71+
$this->assertTrue($r);
72+
}
73+
74+
#[@test]
75+
public function is_primitive_type() {
76+
$r= $this->run('class <T> {
77+
public function run() {
78+
return [1 is int, true is bool, -6.1 is float, 6.1 is float, "test" is string];
79+
}
80+
}');
81+
82+
$this->assertEquals([true, true, true, true, true], $r);
83+
}
84+
85+
#[@test]
86+
public function is_nullable_type() {
87+
$r= $this->run('class <T> {
88+
public function run() {
89+
return [null is ?int, null is ?self, $this is ?self];
90+
}
91+
}');
92+
93+
$this->assertEquals([true, true, true], $r);
94+
}
95+
96+
#[@test]
97+
public function is_array_pseudo_type() {
98+
$r= $this->run('class <T> {
99+
public function run() {
100+
return [[] is array, [1, 2, 3] is array, ["key" => "value"] is array, null is array];
101+
}
102+
}');
103+
104+
$this->assertEquals([true, true, true, false], $r);
105+
}
106+
107+
#[@test]
108+
public function is_object_pseudo_type() {
109+
$r= $this->run('class <T> {
110+
public function run() {
111+
return [$this is object, function() { } is object, null is object];
112+
}
113+
}');
114+
115+
$this->assertEquals([true, true, false], $r);
116+
}
117+
118+
#[@test]
119+
public function is_callable_pseudo_type() {
120+
$r= $this->run('class <T> {
121+
public function run() {
122+
return [function() { } is callable, [$this, "run"] is callable, null is callable];
123+
}
124+
}');
125+
126+
$this->assertEquals([true, true, false], $r);
127+
}
128+
129+
#[@test]
130+
public function is_native_iterable_type() {
131+
$r= $this->run('class <T> implements \IteratorAggregate {
132+
public function getIterator() {
133+
yield 1;
134+
}
135+
136+
public function run() {
137+
return [[] is iterable, $this is iterable, null is iterable];
138+
}
139+
}');
140+
141+
$this->assertEquals([true, true, false], $r);
142+
}
143+
144+
#[@test]
145+
public function is_map_type() {
146+
$r= $this->run('class <T> {
147+
public function run() {
148+
return [["key" => "value"] is array<string, string>, null is array<string, string>];
149+
}
150+
}');
151+
152+
$this->assertEquals([true, false], $r);
153+
}
154+
155+
#[@test]
156+
public function is_array_type() {
157+
$r= $this->run('class <T> {
158+
public function run() {
159+
return [["key"] is array<string>, ["key"] is array<int>, null is array<string>];
160+
}
161+
}');
162+
163+
$this->assertEquals([true, false, false], $r);
164+
}
165+
166+
#[@test]
167+
public function is_union_type() {
168+
$r= $this->run('class <T> {
169+
public function run() {
170+
return [1 is int|string, "test" is int|string, null is int|string];
171+
}
172+
}');
173+
174+
$this->assertEquals([true, true, false], $r);
175+
}
176+
177+
#[@test]
178+
public function is_function_type() {
179+
$r= $this->run('class <T> {
180+
public function run() {
181+
return [function(int $a): int { } is function(int): int, null is function(int): int];
182+
}
183+
}');
184+
185+
$this->assertEquals([true, false], $r);
186+
}
187+
188+
#[@test]
189+
public function precedence() {
190+
$r= $this->run('class <T> {
191+
public function run() {
192+
$arg= "Test";
193+
return $arg is string ? sprintf("string <%s>", $arg) : typeof($arg)->literal();
194+
}
195+
}');
196+
197+
$this->assertEquals('string <Test>', $r);
198+
}
199+
}

0 commit comments

Comments
 (0)