/** * Determines the type from the operation (assumes that the type or the value is unknown) * * @param string $op the operator * @param PC_Obj_MultiType $t1 the type of the first operand * @param PC_Obj_MultiType $t2 the type of the second operand (may be null for unary ops) * @return PC_Obj_MultiType the variable */ protected function get_type_from_op($op, $t1, $t2 = null) { switch ($op) { // bitwise operators have always int as result case '|': case '&': case '^': case '>>': case '<<': case '~': case '?:': return PC_Obj_MultiType::create_int(); // concatenation leads always to string // concatenation leads always to string case '.': return PC_Obj_MultiType::create_string(); case '+': case '-': case '*': case '/': case '%': // if one of them is unknown we don't know whether we would get a float or int if ($t1->is_unknown() || $t1->is_multiple() || $t2 !== null && ($t2->is_unknown() || $t2->is_multiple())) { return new PC_Obj_MultiType(); } $ti1 = $t1->get_first()->get_type(); $ti2 = $t2 === null ? -1 : $t2->get_first()->get_type(); // if both are arrays, the result is an array if ($ti1 == PC_Obj_Type::TARRAY && $ti2 == PC_Obj_Type::TARRAY) { return PC_Obj_MultiType::create_array(); } // if one of them is float, the result is float if ($ti1 == PC_Obj_Type::FLOAT || $ti2 == PC_Obj_Type::FLOAT) { return PC_Obj_MultiType::create_float(); } // otherwise its always int return PC_Obj_MultiType::create_int(); case '==': case '!=': case '===': case '!==': case '<': case '>': case '<=': case '>=': case '&&': case '||': case 'xor': case '!': // always bool return PC_Obj_MultiType::create_bool(); default: FWS_Helper::error('Unknown operator "' . $op . '"'); } }
/** * Checks whether $arg is ok for $param, assumes that $param is a callable and $arg is known. * * @param PC_Obj_Location $loc the location * @param PC_Obj_MultiType $arg the argument */ private function check_callable($loc, $arg) { $first = $arg->get_first(); if ($first->get_type() == PC_Obj_Type::STRING) { if ($first->is_val_unknown()) { return; } $func = $this->env->get_types()->get_function($first->get_value()); if ($func === null) { $this->report($loc, 'The function "' . $first->get_value() . '" does not exist!', PC_Obj_Error::E_S_FUNCTION_MISSING); } } else { if ($first->get_type() == PC_Obj_Type::TARRAY) { if ($first->is_val_unknown()) { return; } $callable = $first->get_value(); if (count($callable) != 2 || !$callable[0]->is_unknown() && $callable[0]->get_first()->get_type() != PC_Obj_Type::OBJECT || !$callable[1]->is_unknown() && $callable[1]->get_first()->get_type() != PC_Obj_Type::STRING) { $this->report($loc, 'Invalid callable: ' . FWS_Printer::to_string($first) . '!', PC_Obj_Error::E_S_CALLABLE_INVALID); } else { if ($callable[0]->is_unknown() || $callable[1]->is_unknown()) { return; } $obj = $callable[0]->get_first(); $name = $callable[1]->get_first(); $classname = $obj->get_class(); $class = $this->env->get_types()->get_class($classname); if (!$class) { $this->report($loc, 'The class "#' . $classname . '#" does not exist!', PC_Obj_Error::E_S_CLASS_MISSING); } else { if (!$class->contains_method($name->get_value())) { $this->report($loc, 'The method "' . $name->get_value() . '" does not exist in the class "#' . $classname . '#"!', PC_Obj_Error::E_S_METHOD_MISSING); } } } } else { if ($first->get_type() != PC_Obj_Type::TCALLABLE) { $this->report($loc, 'Invalid callable: ' . FWS_Printer::to_string($first) . '!', PC_Obj_Error::E_S_CALLABLE_INVALID); } } } }
/** * Handles a cast * * @param string $cast the cast-type: 'int','float','string','array','object','bool' or 'unset' * @param PC_Obj_MultiType $e the expression * @return PC_Obj_MultiType the result */ public function handle_cast($cast, $e) { if (!$e instanceof PC_Obj_MultiType) { return $this->handle_error('$e is invalid'); } // unset casts to null. in this case we don't really know the value // object-cast make no sense here, I think if ($cast == 'unset' || $cast == 'object') { return $this->create_unknown(); } // if we don't know the type or value, just provide the type; in loops as well if ($this->vars->is_in_loop() || $e->is_array_unknown()) { return PC_Obj_MultiType::get_type_by_name($cast); } // we know the value, so perform a cast $res = 0; eval('$res = (' . $cast . ')' . $e->get_first()->get_value_for_eval() . ';'); return $this->get_type_from_php($res); }
/** * Returns a variable pointing to the array-element with given key. If it does not exist, it * will be created as soon as the type is assigned. * * @param PC_Obj_MultiType $key the key (null = append) * @return PC_Obj_Variable the type of the element */ public function array_offset($key) { assert(!$this->type->is_multiple() && !$this->type->is_unknown()); $first = $this->type->get_first(); assert($first->get_type() == PC_Obj_Type::TARRAY); // fetch element or create it $akey = $key; if ($key === null) { $akey = $key = $first->get_next_array_key(); } else { if (!$key->is_unknown()) { $akey = $key; $key = $first->get_array_type($key->get_first()->get_value()); } } $var = new self($this->get_file(), $this->get_line(), '', $key); // connect the var to us $var->arrayref = $this; $var->arrayoff = $akey; return $var; }