Skip to content

Instantly share code, notes, and snippets.

@thekid
Created December 27, 2025 16:52
Show Gist options
  • Select an option

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

Select an option

Save thekid/12b2eae6cce613b9f75928f93f246370 to your computer and use it in GitHub Desktop.
Is operator with binding defaults using =
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..1013b29 100755
--- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php
+++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php
@@ -14,6 +14,7 @@ use lang\ast\nodes\{
MatchExpression,
OffsetExpression,
ScopeExpression,
+ TernaryExpression,
Variable
};
use lang\ast\syntax\Extension;
@@ -70,6 +71,11 @@ class IsOperator implements Extension {
$parse->forward();
$r->restriction= $pattern($parse, $types);
}
+
+ if ('=' === $parse->token->value) {
+ $parse->forward();
+ return new IsOptional($r, $types->expression($parse, 0));
+ }
return $r;
} else if ('>' === $parse->token->value || '>=' === $parse->token->value || '<' === $parse->token->value || '<=' === $parse->token->value) {
$operator= $parse->token->value;
@@ -220,11 +226,11 @@ class IsOperator implements Extension {
new BinaryExpression($use, $pattern->operator, $pattern->value)
);
} else if ($pattern instanceof IsNullable) {
- return new BinaryExpression(
+ return new Braced(new BinaryExpression(
new BinaryExpression(new Literal('null'), '===', $init),
'||',
$match($codegen, $use, $pattern->element)
- );
+ ));
} else if ($pattern instanceof IsCompound) {
$compound= $match($codegen, $init, $pattern->patterns[0]);
for ($i= 1, $s= sizeof($pattern->patterns), $op= $pattern->operator.$pattern->operator; $i < $s; $i++) {
@@ -242,22 +248,39 @@ class IsOperator implements Extension {
}
return $compound;
} else if ($pattern instanceof IsArrayStructure) {
- $compound= new BinaryExpression(
- new InvokeExpression(new Literal('is_array'), [$init]),
- '&&',
- new BinaryExpression(
- new InvokeExpression(new Literal('sizeof'), [$use]),
- $pattern->rest ? '>=' : '===',
- new Literal((string)sizeof($pattern->patterns))
- )
- );
+ $size= new BinaryExpression(
+ new InvokeExpression(new Literal('sizeof'), [$use]),
+ $pattern->rest ? '>=' : '===',
+ new Literal((string)sizeof($pattern->patterns))
+ );
+ $compound= new BinaryExpression(new InvokeExpression(new Literal('is_array'), [$init]), '&&', $size);
+ $optional= 0;
foreach ($pattern->patterns as $key => $p) {
$offset= new Literal((string)$key);
- $compound= new BinaryExpression($compound, '&&', new BinaryExpression(
- new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]),
- '&&',
- $match($codegen, new OffsetExpression($use, $offset), $p),
- ));
+ $exists= new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]);
+
+ if ($p instanceof IsOptional) {
+ $test= new Braced(new TernaryExpression(
+ $exists,
+ $match($codegen, new OffsetExpression($use, $offset), $p->binding),
+ $match($codegen, $p->default, $p->binding)
+ ));
+ $optional++;
+ } else {
+ $test= new BinaryExpression(
+ $exists,
+ '&&',
+ $match($codegen, new OffsetExpression($use, $offset), $p),
+ );
+ }
+
+ $compound= new BinaryExpression($compound, '&&', $test);
+ }
+
+ // Optional implies rest
+ if ($optional) {
+ $size->operator= '>=';
+ $size->right->expression= (string)($size->right->expression - $optional);
}
return $compound;
}
diff --git a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
index 3c02f5e..c675450 100755
--- a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
+++ b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
@@ -115,4 +115,13 @@ class VariableBindingTest extends EmittingTest {
}
}'));
}
+
+ #[Test, Values([[['key' => 1], null], [[], ['matched' => null]], [['key' => 'value'], ['matched' => 'value']]])]
+ public function with_default($input, $expected) {
+ Assert::equals($expected, $this->run('class %T {
+ public function run($input) {
+ return $input is ["key" => $expr & ?string = null] ? ["matched" => $expr] : null;
+ }
+ }', $input));
+ }
}
\ No newline at end of file
<?php namespace lang\ast\syntax\php;
use lang\ast\Type;
use util\Objects;
class IsOptional extends Type {
public $binding, $default;
/**
* Creates a an optional binding "type"
*
* @param lang.ast.syntax.php.IsBinding $binding
* @param lang.ast.Node $default
*/
public function __construct($binding, $default) {
$this->binding= $binding;
$this->default= $default;
}
/** @return string */
public function toString() {
return nameof($this).'('.$this->binding->toString().' ?? '.Objects::stringOf($this->default).')';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment