public function getType() { $right_type = $this->right->getType(); $op_name = Tag::getOperatorLexeme($this->operator); $type_error = new ScopeError(['message' => "No type overload for operator `{$op_name}' for `{$right_type}'"]); switch ($this->operator) { case '+': case '-': if ($right_type->isString() || $right_type->isNumber()) { return clone $right_type; } throw $type_error; case '~': if ($right_type->isNumber()) { return clone $right_type; } throw $type_error; case Tag::T_NOT: if ($right_type->isBoolean()) { return clone $right_type; } throw $type_error; case '^^': case '*': return clone $right_type; } }
public function format(Parser $parser) { $source = '&('; $source .= Tag::getOperatorLexeme($this->operator); if (null !== $this->right) { $source .= ' '; $source .= $this->right->format($parser); } $source .= ')'; return $this->parenthesize($source); }
public function format(Parser $parser) { $source = $this->left->format($parser); $source .= Tag::getOperatorLexeme($this->operator); return $this->parenthesize($source); }
public function getType() { $type = (object) ['left' => $this->left->getType(), 'right' => 'string' === gettype($this->right) ? null : $this->right->getType()]; $op_name = Tag::getOperatorLexeme($this->operator); $member_access = ['.', '?.']; if (in_array($this->operator, $member_access, true)) { // When member access and the property exists on the left type if (is_array($type->left->props) && array_key_exists($this->right, $type->left->props)) { return $type->left->props[$this->right]; } throw new ScopeError(['message' => "Expression of type `{$type->left}' has no property `{$this->right}'"]); } // Type-checking for assignment. Don't worry. Left-hand assignment was handled on // scope injection if (':-' === $this->operator) { // When right side cannot be attributed to left side if (!$type->right->isExactlySameAs($type->left)) { $target = $this->left instanceof NameExpr ? "variable `{$this->left->name}' :: {$type->left}" : (string) $type->right; throw new ScopeError(['message' => "Trying to set value of type `{$type->right}` to {$target}"]); } // We've just removed covariance and contravariance :) return $type->right; } // Type checking for numeric and string concat operations $numeric_op = ['+', '-', '*', '**', '/', '>>', '<<', '^', '&', '|', Tag::T_MOD]; if (in_array($this->operator, $numeric_op, true)) { if ('+' === $this->operator && $type->left->isString() && $type->right->isString()) { return new Type(NativeQuackType::T_STR); } if ($type->left->isNumber() && $type->right->isNumber()) { return Type::getBaseType([$type->left, $type->right]); } throw new ScopeError(['message' => "No type overload found for operator `{$op_name}' at " . "{{$type->left} {$op_name} {$type->right}}"]); } // Type checking for equality operators and coalescence $eq_op = ['=', '<>', '??', '>', '>=', '<', '<=']; if (in_array($this->operator, $eq_op, true)) { if ($type->left->isExactlySameAs($type->right)) { return '??' === $this->operator ? $type->left : new Type(NativeQuackType::T_BOOL); } throw new ScopeError(['message' => "Why in the world are you trying to compare two expressions of different types? at " . "{{$type->left} {$op_name} {$type->right}}"]); } // Type checking for string matched by regex if ('=~' === $this->operator) { if (!$type->left->isString() || !$type->right->isRegex()) { throw new ScopeError(['message' => "No type overload found for operator `=~' at " . "{{$type->left} =~ {$type->right}}"]); } return new Type(NativeQuackType::T_BOOL); } // Boolean algebra $bool_op = [Tag::T_AND, Tag::T_OR, Tag::T_XOR]; if (in_array($this->operator, $bool_op, true)) { if (!$type->left->isBoolean() || !$type->right->isBoolean()) { throw new ScopeError(['message' => "No type overload found for operator `{$op_name}' " . "{{$type->left} {$op_name} {$type->right}}"]); } return new Type(NativeQuackType::T_BOOL); } }