/**
  * Apply the current SetCorrectResponse rule on the current state.
  * 
  * A RuleProcessingException will be thrown if:
  * 
  * * No variable corresponds to the given identifier in the current state.
  * * The target variable is not a ResponseVariable.
  * * The baseType and/or cardinality of the value to be set does not correspond to the baseType and/or cardinality of the target variable.
  * * An error occurs while processing the expression representing the value to be set.
  * 
  * @throws \qtism\runtime\rules\RuleProcessingException
  */
 public function process()
 {
     $rule = $this->getRule();
     $state = $this->getState();
     $variableIdentifier = $rule->getIdentifier();
     $var = $state->getVariable($variableIdentifier);
     if (is_null($var) === true) {
         $msg = "No variable with identifier '{$variableIdentifier}' to be set in the current state.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE);
     } elseif (!$var instanceof ResponseVariable) {
         $msg = "The variable to set '{$variableIdentifier}' is not an instance of 'ResponseVariable'.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE);
     }
     try {
         $expressionEngine = new ExpressionEngine($rule->getExpression(), $state);
         $val = $expressionEngine->process();
         $var->setCorrectResponse($val);
     } catch (InvalidArgumentException $e) {
         $varBaseType = BaseType::getNameByConstant($var->getBaseType()) === false ? 'noBaseType' : BaseType::getNameByConstant($var->getBaseType());
         $varCardinality = Cardinality::getNameByConstant($var->getCardinality());
         // The affected value does not match the baseType of the variable $var.
         $msg = "Unable to set value {$val} to variable '{$variableIdentifier}' (cardinality = {$varCardinality}, baseType = {$varBaseType}).";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_BASETYPE, $e);
     } catch (ExpressionProcessingException $e) {
         $msg = "An error occured while processing the expression bound with the 'SetCorrectResponse' rule.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e);
     }
 }
 /**
  * Process the TemplateConstraint rule. It simply throws a RuleProcessingException with
  * the special code RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED to warn client
  * code that the expression related to the constraint returned false or null.
  *
  * @throws \qtism\runtime\rules\RuleProcessingException with code = RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED.
  */
 public function process()
 {
     $state = $this->getState();
     $rule = $this->getRule();
     $expr = $rule->getExpression();
     $expressionEngine = new ExpressionEngine($expr, $state);
     $val = $expressionEngine->process();
     if (Utils::isNull($val) || $val->getValue() === false) {
         $msg = "Unsatisfied Template Constraint.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::TEMPLATE_CONSTRAINT_UNSATISFIED);
     }
 }
    public function testExpressionEngineSum()
    {
        $expression = $this->createComponentFromXml('
			<sum> <!-- 60 -->
				<product> <!-- 50 -->
					<baseValue baseType="integer">10</baseValue>
					<baseValue baseType="integer">5</baseValue>
				</product>
				<divide> <!-- 10 -->
					<baseValue baseType="integer">50</baseValue>
					<baseValue baseType="integer">5</baseValue>
				</divide>
			</sum>
		');
        $engine = new ExpressionEngine($expression);
        $result = $engine->process();
        $this->assertInstanceOf('qtism\\common\\datatypes\\Float', $result);
        $this->assertEquals(60.0, $result->getValue());
    }
 /**
  * Process the setOutcomeValue/setTemplateValue rule.
  *
  * A RuleProcessingException will be thrown if:
  *
  * * The variable does not exist.
  * * The requested variable is not an OutcomeVariable/TemplateVariable.
  * * The variable's baseType does not match the baseType of the affected value.
  * * An error occurs while processing the related expression.
  *
  * @throws \qtism\runtime\rules\RuleProcessingException If one of the error described above arise.
  */
 public function process()
 {
     $state = $this->getState();
     $rule = $this->getRule();
     $identifier = $rule->getIdentifier();
     $var = $state->getVariable($identifier);
     if (is_null($var) === true) {
         $msg = "No variable with identifier '{$identifier}' to be set in the current state.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE);
     } elseif (Reflection::isInstanceOf($var, $this->getVariableType()) === false) {
         $msg = "The variable to set '{$identifier}' is not an instance of '" . $this->getVariableType() . "'.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE);
     }
     // Process the expression.
     // Its result will be the value to set to the target variable.
     try {
         $expressionEngine = new ExpressionEngine($rule->getExpression(), $state);
         $val = $expressionEngine->process();
     } catch (ExpressionProcessingException $e) {
         $msg = "An error occured while processing the expression bound with the '" . Reflection::shortClassName($rule) . "' rule.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e);
     }
     // The variable exists, its new value is processed.
     try {
         // juggle a little bit (int -> float, float -> int)
         if ($val !== null && $var->getCardinality() === Cardinality::SINGLE) {
             $baseType = $var->getBaseType();
             if ($baseType === BaseType::INTEGER && $val instanceof Float) {
                 $val = new Integer(intval($val->getValue()));
             } elseif ($baseType === BaseType::FLOAT && $val instanceof Integer) {
                 $val = new Float(floatval($val->getValue()));
             }
         }
         $var->setValue($val);
     } catch (InvalidArgumentException $e) {
         $varBaseType = BaseType::getNameByConstant($var->getBaseType()) === false ? 'noBaseType' : BaseType::getNameByConstant($var->getBaseType());
         $varCardinality = Cardinality::getNameByConstant($var->getCardinality());
         // The affected value does not match the baseType of the variable $var.
         $msg = "Unable to set value {$val} to variable '{$identifier}' (cardinality = {$varCardinality}, baseType = {$varBaseType}).";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_BASETYPE, $e);
     }
 }
 /**
  * Move to the next item in the route. 
  * 
  * * If there is no more item in the route to be explored the session ends gracefully.
  * * If there the end of a test part is reached, pending responses are submitted. 
  * 
  * @param boolean $ignoreBranchings Whether or not to ignore branching.
  * @param boolean $ignorePreConditions Whether or not to ignore preConditions.
  * @throws AssessmentTestSessionException If the test session is not running or something wrong happens during deffered outcome processing or branching.
  */
 protected function nextRouteItem($ignoreBranchings = false, $ignorePreConditions = false)
 {
     if ($this->isRunning() === false) {
         $msg = "Cannot move to the next position while the state of the test session is INITIAL or CLOSED.";
         throw new AssessmentTestSessionException($msg, AssessmentTestSessionException::STATE_VIOLATION);
     }
     // If the submitted responses are the one of the last
     // item of the test part, apply deffered response processing.
     if ($this->getRoute()->isLastOfTestPart() === true && $this->getCurrentSubmissionMode() === SubmissionMode::SIMULTANEOUS) {
         // The testPart is complete so deffered response processing must take place.
         $this->defferedResponseProcessing();
     }
     $route = $this->getRoute();
     $stop = false;
     while ($route->valid() === true && $stop === false) {
         // Branchings?
         if ($ignoreBranchings === false && $this->getCurrentNavigationMode() === NavigationMode::LINEAR && count($route->current()->getBranchRules()) > 0) {
             $branchRules = $route->current()->getBranchRules();
             for ($i = 0; $i < count($branchRules); $i++) {
                 $engine = new ExpressionEngine($branchRules[$i]->getExpression(), $this);
                 $condition = $engine->process();
                 if ($condition !== null && $condition->getValue() === true) {
                     $target = $branchRules[$i]->getTarget();
                     if ($target === 'EXIT_TEST') {
                         $this->endTestSession();
                     } else {
                         if ($target === 'EXIT_TESTPART') {
                             $this->moveNextTestPart();
                         } else {
                             if ($target === 'EXIT_SECTION') {
                                 $this->moveNextAssessmentSection();
                             } else {
                                 $route->branch($branchRules[$i]->getTarget());
                             }
                         }
                     }
                     break;
                 }
             }
             if ($i >= count($branchRules)) {
                 // No branch rule returned true. Simple move next.
                 $route->next();
             }
         } else {
             $route->next();
         }
         // Preconditions on target?
         if ($ignorePreConditions === false && $route->valid() === true) {
             $preConditions = $route->current()->getPreConditions();
             if (count($preConditions) > 0) {
                 for ($i = 0; $i < count($preConditions); $i++) {
                     $engine = new ExpressionEngine($preConditions[$i]->getExpression(), $this);
                     $condition = $engine->process();
                     if ($condition !== null && $condition->getValue() === true) {
                         // The item must be presented.
                         $stop = true;
                         break;
                     }
                 }
             } else {
                 $stop = true;
             }
         }
     }
     $this->selectEligibleItems();
     if ($route->valid() === false && $this->isRunning() === true) {
         $this->endTestSession();
     }
 }
 /**
  * Process the OutcomeCondition/ResponseCondition according to the current state.
  *
  * @throws \qtism\runtime\rules\RuleProcessingException
  */
 public function process()
 {
     $state = $this->getState();
     $this->pushTrail($this->getRule());
     $className = ucfirst($this->getQtiNature());
     $nsClass = 'qtism\\data\\rules\\' . $className . 'Condition';
     $ruleGetter = "get{$className}Rules";
     $statementGetter = "get{$className}";
     // + 'If'|'ElseIf'|'Else'
     while (count($this->getTrail()) > 0) {
         $rule = $this->popTrail();
         if (get_class($rule) === $nsClass) {
             // Let's try for if.
             $ifStatement = call_user_func(array($rule, $statementGetter . 'If'));
             $ifExpression = $ifStatement->getExpression();
             $exprEngine = new ExpressionEngine($ifExpression, $state);
             $value = $exprEngine->process();
             if ($value !== null && $value->getValue() === true) {
                 // Follow the if.
                 $this->pushTrail(call_user_func(array($ifStatement, $ruleGetter)));
             } else {
                 // Let's try for else ifs.
                 $followElseIf = false;
                 $elseIfStatements = call_user_func(array($rule, $statementGetter . 'ElseIfs'));
                 foreach ($elseIfStatements as $elseIfStatement) {
                     $elseIfExpression = $elseIfStatement->getExpression();
                     $exprEngine->setComponent($elseIfExpression);
                     $value = $exprEngine->process();
                     if ($value !== null && $value->getValue() === true) {
                         // Follow the current else if.
                         $this->pushTrail(call_user_func(array($elseIfStatement, $ruleGetter)));
                         $followElseIf = true;
                         break;
                     }
                 }
                 $elseStatement = call_user_func(array($rule, $statementGetter . 'Else'));
                 if ($followElseIf === false && is_null($elseStatement) === false) {
                     // No else if followed, the last resort is the else.
                     $this->pushTrail(call_user_func(array($elseStatement, $ruleGetter)));
                 }
             }
         } else {
             // $rule is another Rule than OutcomeCondition/ResponseCondition.
             $processor = $this->getRuleProcessorFactory()->createProcessor($rule);
             $processor->setState($state);
             $processor->process();
         }
     }
 }
 /**
  * Process the LookupOutcomeValue rule.
  *
  * A RuleProcessingException will be thrown if:
  *
  * * The outcome variable to set does not exist.
  * * The variable to set is not an OutcomeVariable
  * * The outcome variable's baseType does not match the baseType of the affected value (the result of the bound expression).
  * * The outcome variable's declaration has no associated lookup table.
  * * The variable's declaration contains a matchTable but the result of the bound expression is not an integer.
  * * The variable's declaration contains an interpolationTable but the result of the bound expression is not an integer, nor a float.
  * * There is no associated table in the variable's declaration.
  * * An error occurs during the processing of the related expression.
  *
  * @throws \qtism\runtime\rules\RuleProcessingException If one of the error described above arise.
  */
 public function process()
 {
     $state = $this->getState();
     $rule = $this->getRule();
     $identifier = $rule->getIdentifier();
     $var = $state->getVariable($identifier);
     if (is_null($var) === true) {
         $msg = "The variable to set '{$identifier}' does not exist in the current state.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::NONEXISTENT_VARIABLE);
     } elseif (!$var instanceof OutcomeVariable) {
         $msg = "The variable to set '{$identifier}' is not an OutcomeVariable.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::WRONG_VARIABLE_TYPE);
     }
     $expression = $rule->getExpression();
     $expressionEngine = new ExpressionEngine($expression, $state);
     try {
         $val = $expressionEngine->process();
         // Let's lookup the associated table.
         $table = $var->getLookupTable();
         if (is_null($table) === true) {
             $msg = "No lookupTable in declaration of variable '{$identifier}'.";
             throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR);
         }
         // $targetVal = The value that will be set to the target variable.
         //
         // As per specs:
         // The default outcome value to be used when no matching table
         // entry is found. If omitted, the NULL value is used.
         $targetVal = $table->getDefaultValue();
         if ($table instanceof InterpolationTable) {
             if (!$val instanceof Float && !$val instanceof Integer && !$val instanceof Duration) {
                 $msg = "The value of variable '{$identifier}' must be integer, float or duration when used with an interpolationTable";
                 throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR);
             }
             foreach ($table->getInterpolationTableEntries() as $entry) {
                 $lowerBound = $entry->getSourceValue();
                 $includeBoundary = $entry->doesIncludeBoundary();
                 if ($includeBoundary === true && $val->getValue() <= $lowerBound) {
                     $targetVal = $entry->getTargetValue();
                     break;
                 } elseif ($includeBoundary === false && $val->getValue() < $lowerBound) {
                     $targetVal = $entry->getTargetValue();
                     break;
                 }
             }
         } else {
             // $table instanceof MatchTable
             if (!$val instanceof Integer) {
                 $msg = "The value of the variable '{$identifier}' must be integer when used with a matchTable.";
                 throw new RuleProcessingException($msg, $this, RuleProcessingException::LOGIC_ERROR);
             }
             foreach ($table->getMatchTableEntries() as $entry) {
                 if ($entry->getSourceValue() === $val->getValue()) {
                     $targetVal = $entry->getTargetValue();
                     break;
                 }
             }
         }
         // assign target value
         try {
             $finalVal = RuntimeUtils::valueToRuntime($targetVal, $var->getBaseType());
             $state[$identifier] = $finalVal;
         } catch (InvalidArgumentException $e) {
             // $targetVal's baseType not compliant with target variable's baseType.
             $msg = "The looked up value's baseType is not compliant with the baseType of variable '{$identifier}'.";
             throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR);
         }
     } catch (ExpressionProcessingException $e) {
         $msg = "An error occured while processing the expression bound to the lookupOutcomeValue rule.";
         throw new RuleProcessingException($msg, $this, RuleProcessingException::RUNTIME_ERROR, $e);
     }
 }