@oat-sa/tao-item-runner-qti
Version:
TAO QTI Item Runner modules
133 lines (113 loc) • 4.78 kB
JavaScript
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2014-2019 (original work) Open Assessment Technlogies SA (under the project TAO-PRODUCT);
*
*/
/**
* The engine that process QTI expressions.
*
* @author Bertrand Chevrier <bertrand@taotesting.com>
*/
import _ from 'lodash';
import processorFactory from 'taoQtiItem/scoring/processor/expressions/processor';
import expressionProcessors from 'taoQtiItem/scoring/processor/expressions/expressions';
import operatorProcessors from 'taoQtiItem/scoring/processor/expressions/operators/operators';
//get the list of available operators
var operators = _.keys(operatorProcessors);
/**
* Creates an engine that can look over the expressions and execute them accordingy.
*
* @exports taoQtiItem/scoring/processor/expressions/engine
* @param {Object} state - the item session state (response and outcome variables)
* @returns {Object} the expression engine
*/
var expressionEngineFactory = function expressionEngineFactory(state) {
var trail = [];
var marker = [];
var operands = [];
var isMarked = function isMarked(expression) {
return marker.includes(expression);
};
var mark = function mark(expression) {
marker.push(expression);
};
var isOperator = function isOperator(expression) {
return operators.includes(expression.qtiClass);
};
var pushSubExpressions = function pushSubExpressions(expression) {
_.forEach(expression.expressions, function (subExpression) {
trail.push(subExpression);
});
};
var popOperands = function popOperands(expression) {
var r = _.reduce(
expression.expressions,
function (result) {
if (operands.length) {
result.push(_.clone(operands.pop()));
}
return result;
},
[]
);
return r;
};
return {
/**
* Execute the engine on the given expression tree
* @param {Object} expression - the expression to process
* @returns {?ProcessingValue} the result of the expression evaluation in the form of a variable
*/
execute: function (expression) {
var currentExpression, currentProcessor, result;
var baseExpression = expression.qtiClass;
trail.push(expression);
//TODO remove the limit and add a timeout
while (trail.length > 0) {
currentExpression = trail.pop();
currentProcessor = null;
if (!isMarked(currentExpression) && isOperator(currentExpression)) {
mark(currentExpression);
trail.push(currentExpression);
//reverse push sub expressions
pushSubExpressions(currentExpression);
} else if (isMarked(currentExpression)) {
// Operator, second pass. Process it.
currentProcessor = processorFactory(currentExpression, state, popOperands(currentExpression));
result = currentProcessor.process();
//add the result to the operand stack
operands.push(result);
} else {
// Simple expression, process it.
currentProcessor = processorFactory(currentExpression, state);
result = currentProcessor.process();
//add the result to the operand stack
operands.push(result);
}
}
return result;
}
};
};
//register all processors
_.forEach(expressionProcessors, function (expressionProcessor, nameWithSuffix) {
const name = nameWithSuffix.replace(/Processor$/, '');
processorFactory.register(name, processorFactory.types.EXPRESSION, expressionProcessor);
});
_.forEach(operatorProcessors, function (operatorProcessor, name) {
processorFactory.register(name, processorFactory.types.OPERATOR, operatorProcessor);
});
export default expressionEngineFactory;