/** * Process the Repeat operator. * * Note: NULL values are simply ignored. If all sub-expressions are NULL, NULL is * returned. * * @return \qtism\runtime\common\OrderedContainer An ordered container filled sequentially by evaluating each sub-expressions, repeated a 'numberRepeats' of times. NULL is returned if all sub-expressions are NULL or numberRepeats < 1. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); // get the value of numberRepeats $expression = $this->getExpression(); $numberRepeats = $expression->getNumberRepeats(); if (gettype($numberRepeats) === 'string') { // Variable reference found. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($numberRepeats); $varValue = $state[$varName]; if (is_null($varValue) === true) { $msg = "The variable with name '{$varName}' could not be resolved."; throw new OperatorProcessingException($msg, $this); } elseif ($varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer value."; throw new OperatorProcessingException($msg, $this); } $numberRepeats = $varValue->getValue(); } if ($numberRepeats < 1) { return null; } $result = null; for ($i = 0; $i < $numberRepeats; $i++) { $refType = null; foreach ($operands as $operand) { // If null, ignore if (is_null($operand) || $operand instanceof Container && $operand->isNull()) { continue; } // Check cardinality. if ($operand->getCardinality() !== Cardinality::SINGLE && $operand->getCardinality() !== Cardinality::ORDERED) { $msg = "The Repeat operator only accepts operands with a single or ordered cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } // Check baseType. $currentType = RuntimeUtils::inferBaseType($operand); if ($refType !== null && $currentType !== $refType) { $msg = "The Repeat operator only accepts operands with the same baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } elseif (is_null($result)) { $refType = $currentType; $result = new OrderedContainer($refType); } // Okay we are good... $operandCardinality = RuntimeUtils::inferCardinality($operand); if ($operandCardinality !== Cardinality::ORDERED) { $operand = new OrderedContainer($currentType, array($operand)); } foreach ($operand as $o) { $result[] = $o instanceof QtiDatatype ? clone $o : $o; } } } if (isset($result) && $result->isNull() !== true) { return $result; } else { return null; } }
/** * Process the RoundTo operator. * * An OperatorProcessingException will be thrown if: * * * The given operand is not a numeric value. * * The cardinality of the operand is not single. * * The value of the 'figures' attribute comes from a templateVariable which does not exist or is not numeric or null. * * @return null|float A single float with the value nearest to that of the expression's value or NULL if the sub-expression is NaN. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); $state = $this->getState(); $operand = $operands[0]; // If the value is null, return null. if ($operands->containsNull()) { return null; } if (!$operands->exclusivelySingle()) { $msg = "The RoundTo operator accepts 1 operand with single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } // Accept only numerical operands. if (!$operands->exclusivelyNumeric()) { $msg = "The RoundTo operand accepts 1 operand with numerical baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } // As per QTI 2.1 spec... if (is_nan($operand->getValue())) { return null; } elseif (is_infinite($operand->getValue())) { return $operand; } $roundingMode = $this->getExpression()->getRoundingMode(); $figures = $this->getExpression()->getFigures(); if (gettype($figures) === 'string') { // try to recover the value from the state. $figuresIdentifier = Utils::sanitizeVariableRef($figures); $figures = $state[$figuresIdentifier]; if (is_null($figures)) { $msg = "The variable '{$figuresIdentifier}' used to set up the 'figures' attribute is null or nonexisting."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$figures instanceof Integer) { $msg = "The variable '{$figuresIdentifier}' used to set up the 'figures' attribute is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } $figures = $figures->getValue(); } if ($roundingMode === RoundingMode::SIGNIFICANT_FIGURES) { if ($figures <= 0) { // As per QTI 2.1 spec. $msg = "The 'figures' attribute must be a non-zero positive integer when mode 'significantFigures' is used, '{$figures}' given."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR); } if ($operand->getValue() == 0) { return new Float(0.0); } $d = ceil(log10($operand->getValue() < 0 ? -$operand->getValue() : $operand->getValue())); $power = $figures - intval($d); $magnitude = pow(10, $power); $shifted = round($operand->getValue() * $magnitude); return new Float(floatval($shifted / $magnitude)); } else { // As per QTI 2.1 spec. if ($figures < 0) { $msg = "The 'figures' attribute must be a integer greater than or equal to zero when mode 'decimalPlaces' is used, '{$figures}' given."; throw new OperatorProcessingException($msg, $this); } return new Float(round($operand->getValue(), $figures)); } }