/** * Process the RandomInteger expression. * * * Throws an ExpressionProcessingException if 'min' is greater than 'max'. * * Throws an ExpressionProcessingException if a variable reference is not found in the current state. * * Throws an ExpressionProcessingException if a variable reference's value is not an integer. * * @return integer A random integer value. * @throws \qtism\runtime\expressions\ExpressionProcessingException */ public function process() { $expr = $this->getExpression(); $min = $expr->getMin(); $max = $expr->getMax(); $step = $expr->getStep(); $state = $this->getState(); $min = gettype($min) === 'integer' ? $min : $state[Utils::sanitizeVariableRef($min)]->getValue(); $max = gettype($max) === 'integer' ? $max : $state[Utils::sanitizeVariableRef($max)]->getValue(); $step = gettype($step) === 'integer' ? $step : $state[Utils::sanitizeVariableRef($step)]->getValue(); if (gettype($min) === 'integer' && gettype($max) === 'integer' && gettype($step) === 'integer') { if ($min > $max) { $msg = "'min':'{$min}' is greater than 'max':'{$max}'."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::LOGIC_ERROR); } if ($step === 1) { return new QtiInteger(mt_rand($min, $max)); } else { $distance = $min < 0 ? $max + abs($min) : $max - $min; $mult = mt_rand(0, intval(floor($distance / $step))); $random = new QtiInteger($min + $mult * $step); return $random; } } else { $msg = "At least one of the following variables is not an integer: 'min', 'max', 'step' while processing RandomInteger."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::WRONG_VARIABLE_BASETYPE); } }
/** * Process the RandomFloat expression. * * * Throws an ExpressionProcessingException if 'min' is greater than 'max'. * * @return float A Random float value. * @throws \qtism\runtime\expressions\ExpressionProcessingException */ public function process() { $expr = $this->getExpression(); $min = $expr->getMin(); $max = $expr->getMax(); $state = $this->getState(); $min = is_float($min) ? $min : $state[Utils::sanitizeVariableRef($min)]->getValue(); $max = is_float($max) ? $max : $state[Utils::sanitizeVariableRef($max)]->getValue(); if (is_float($min) && is_float($max)) { if ($min <= $max) { return new Float($min + lcg_value() * abs($max - $min)); } else { $msg = "'min':'{$min}' is greater than 'max':'{$max}'."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::LOGIC_ERROR); } } else { $msg = "At least one of the following values is not a float: 'min', 'max'."; throw new ExpressionProcessingException($msg, $this, ExpressionProcessingException::WRONG_VARIABLE_BASETYPE); } }
/** * Process the Index operator. * * @return mixed|null A QTIRuntime compliant scalar value. NULL is returned if expression->n exceeds the number of values in the container or the sub-expression is NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull()) { return null; } if ($operands->exclusivelyOrdered() === false) { $msg = "The Index operator only accepts values with a cardinality of ordered."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } $n = $this->getExpression()->getN(); if (gettype($n) === 'string') { // The value of $n comes from the state. $state = $this->getState(); if (($index = $state[ProcessingUtils::sanitizeVariableRef($n)]) !== null) { if ($index instanceof QtiInteger) { $n = $index->getValue(); } else { $msg = "The value '{$index}' is not an integer. Ordered containers can be only accessed by integers."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } } else { $msg = "Unknown variable reference '{$n}'."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } } if ($n < 1) { $msg = "The value of 'n' must be a non-zero postive integer, '{$n}' given."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR); } $n = $n - 1; // QTI indexes begin at 1... if ($n > count($operands[0]) - 1) { // As per specs, if n exceeds the number of values in the container, // the result of the index operator is NULL. return null; } return $operands[0][$n]; }
/** * Process the EqualRounded operator. * * @return boolean|null A boolean with a value of true if the two expressions are numerically equal after rounding and false if they are not. If either sub-expression is NULL, the operator results in NULL. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull()) { return null; } if ($operands->exclusivelySingle() === false) { $msg = "The EqualRounded operator only accepts operands with a single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } if ($operands->exclusivelyNumeric() === false) { $msg = "The EqualRounded operator only accepts operands with an integer or float baseType."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } // delegate the rounding to the RoundTo operator. $expression = $this->getExpression(); $roundingMode = $expression->getRoundingMode(); $figures = $expression->getFigures(); if (gettype($figures) === 'string') { // Variable reference to deal with. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($figures); $varValue = $state[$varName]; if (is_null($varValue) === true) { $msg = "The variable with name '{$varName}' could not be resolved."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } $figures = $varValue->getValue(); } $rounded = new OperandsCollection(); // will contain the rounded operands. foreach ($operands as $operand) { $baseType = RuntimeUtils::inferBaseType($operand); $subExpression = new BaseValue($baseType, $operand); $roundToExpression = new RoundTo(new ExpressionCollection(array($subExpression)), $figures, $roundingMode); $roundToProcessor = new RoundToProcessor($roundToExpression, new OperandsCollection(array($operand))); try { $rounded[] = $roundToProcessor->process(); } catch (OperatorProcessingException $e) { $msg = "An error occured while rounding '{$operand}'."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::LOGIC_ERROR, $e); } } return new Boolean($rounded[0]->getValue() == $rounded[1]->getValue()); }
/** * Process the Equal operator. * * @return boolean|null Whether the two expressions are numerically equal and false if they are not or NULL if either sub-expression is NULL. * @throws OperatorProcessingException */ public function process() { $operands = $this->getOperands(); if ($operands->containsNull() === true) { return null; } if ($operands->exclusivelySingle() === false) { $msg = "The Equal operator only accepts operands with a single cardinality."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_CARDINALITY); } if ($operands->exclusivelyNumeric() === false) { $msg = "The Equal operator only accepts operands with an integer or float baseType"; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } $operand1 = $operands[0]; $operand2 = $operands[1]; $expression = $this->getExpression(); if ($expression->getToleranceMode() === ToleranceMode::EXACT) { return new Boolean($operand1->getValue() == $operand2->getValue()); } else { $tolerance = $expression->getTolerance(); if (gettype($tolerance[0]) === 'string') { $strTolerance = $tolerance; $tolerance = array(); // variableRef to handle. $state = $this->getState(); $tolerance0Name = Utils::sanitizeVariableRef($strTolerance[0]); $varValue = $state[$tolerance0Name]; if (is_null($varValue)) { $msg = "The variable with name '{$tolerance0Name}' could not be resolved."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } else { if (!$varValue instanceof Float) { $msg = "The variable with name '{$tolerance0Name}' is not a float."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } } $tolerance[] = $varValue->getValue(); if (isset($strTolerance[1]) && gettype($strTolerance[1]) === 'string') { // A second variableRef to handle. $tolerance1Name = Utils::sanitizeVariableRef($strTolerance[1]); if (($varValue = $state[$tolerance1Name]) !== null && $varValue instanceof Float) { $tolerance[] = $varValue->getValue(); } } } if ($expression->getToleranceMode() === ToleranceMode::ABSOLUTE) { $t0 = $operand1->getValue() - $tolerance[0]; $t1 = $operand1->getValue() + (isset($tolerance[1]) ? $tolerance[1] : $tolerance[0]); $moreThanLower = $expression->doesIncludeLowerBound() ? $operand2->getValue() >= $t0 : $operand2->getValue() > $t0; $lessThanUpper = $expression->doesIncludeUpperBound() ? $operand2->getValue() <= $t1 : $operand2->getValue() < $t1; return new Boolean($moreThanLower && $lessThanUpper); } else { // Tolerance mode RELATIVE $tolerance = $expression->getTolerance(); $t0 = $operand1->getValue() * (1 - $tolerance[0] / 100); $t1 = $operand1->getValue() * (1 + (isset($tolerance[1]) ? $tolerance[1] : $tolerance[0]) / 100); $moreThanLower = $expression->doesIncludeLowerBound() ? $operand2->getValue() >= $t0 : $operand2->getValue() > $t0; $lessThanUpper = $expression->doesIncludeUpperBound() ? $operand2->getValue() <= $t1 : $operand2->getValue() < $t1; return new Boolean($moreThanLower && $lessThanUpper); } } }
/** * Process the AnyN processor. * * @return boolean|null A boolean value of true if at least min of the sub-expressions are true and at most max of the sub-expressions are true. NULL is returned if the correct value for the operator cannot be determined. * @throws \qtism\runtime\expressions\operators\OperatorProcessingException */ public function process() { $operands = $this->getOperands(); // Retrieve the values of min and max. $min = $this->getExpression()->getMin(); $max = $this->getExpression()->getMax(); // @todo write a generic method to retrieve variable references. if (is_string($min) === true) { // variable reference for 'min' to handle. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($min); $varValue = $state[$varName]; if (is_null($varValue)) { $msg = "The variable with name '{$varName}' could not be resolved or is null."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE); } else { $min = $varValue->getValue(); } } if (is_string($max) === true) { // variable reference for 'max' to handle. $state = $this->getState(); $varName = Utils::sanitizeVariableRef($max); $varValue = $state[$varName]; if (is_null($varValue)) { $msg = "The variable with name '{$varName}' could not be resolved or is null."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::NONEXISTENT_VARIABLE); } elseif (!$varValue instanceof Integer) { $msg = "The variable with name '{$varName}' is not an integer."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_VARIABLE_BASETYPE); } else { $max = $varValue->getValue(); } } $nullCount = 0; $trueCount = 0; foreach ($operands as $operand) { if (is_null($operand)) { $nullCount++; continue; } elseif ($operand instanceof Boolean) { if ($operand->getValue() === true) { $trueCount++; } } else { // Not null, not a boolean, we have a problem... $msg = "The AnyN operator only accepts values with cardinality single and baseType boolean."; throw new OperatorProcessingException($msg, $this, OperatorProcessingException::WRONG_BASETYPE_OR_CARDINALITY); } } if ($trueCount >= $min && $trueCount <= $max) { return new Boolean(true); } else { // Should we return false or null? if ($trueCount + $nullCount >= $min && $trueCount + $nullCount <= $max) { // It could have match if nulls were true values. return null; } else { return new Boolean(false); } } }
/** * @dataProvider sanitizeVariableRefInvalidProvider */ public function testSanitizeVariableRefInvalid($value) { $this->setExpectedException('\\InvalidArgumentException'); $ref = Utils::sanitizeVariableRef($value); }