One of our teachers assigned his students a project to develop integration for mathematical functions not yet supported in moodle - factorial, permutation, variation, pow, bincoeff and combination.

They did a successful patch for Moodle 3.3 with a couple of changes to question.php and questiontype.php for calculated and calculatedsimple question types. I basically just used their files, tested if everything works, and used it in our 3.3 installation.

However, in 3.4 calculated question types stopped working, so we had to revert the changes.

Now we wanted to turn the student's code into a proper patch and try to have it included in Moodle Core - I already created a tracker item for this reason: https://tracker.moodle.org/browse/MDL-63106

When actually looking at the code and changes our students implemented, I quickly realized that their code was a quick and dirty hack, and if we really want to have this integrated in Moodle core we would have to re-do it in a proper way.

So, my questions are:

- Is there any documentation on how to implement new mathematical functions in Moodle?
- Are all the mathematical functions supported now (https://docs.moodle.org/35/en/Calculated_question_type#Available_functions) just native PHP functions, or have any additional math functions be implemented in Moodle already?
- lib/evalmath/evalmath.class.php has an array of functions:

var $f = array(); // user-defined functions var $v = array(); // variables (and constants) var $vb = array(); // constants var $fb = array( // built-in functions 'sin','sinh','arcsin','asin','arcsinh','asinh', 'cos','cosh','arccos','acos','arccosh','acosh', 'tan','tanh','arctan','atan','arctanh','atanh', 'sqrt','abs','ln','log','exp','floor','ceil',);

- what are "user-defines functions"? is this where our new functions would go?

any help or input is appreciated

I think most people doing serious maths in Moodle tend to use add-on question types such as STACK (https://moodle.org/plugins/qtype_stack) or Formulas question type (https://moodle.org/plugins/qtype_formulas). You might want to take a look at those.

However, that is no reason not to improve the core calculated question type. I don't know exactly the right way to do it, at least not without further thought. You are certainly asking the right questions: thinking about how best to fit in with the existing code is certainly the way to start. Another thing I would encourage you to do early on is to find whatever unit tests already exist for this code, and think about what tests you should add for the new functions. That would be a good way to define exactly what it is you plan to implement.

I will try to have a look at the code myself, tomorrow, to see if I can give a more helpful answer.

OK, so having looked at the code, it seems that qtype_calculated does not use lib/evalmath/evalmath.class.php. It just does this: https://github.com/moodle/moodle/blob/master/question/type/calculated/question.php#L442 (after some extremely careful validation of the input.)

This makes adding non-PHP functions a bit tricky. On the one hand, you want to define the functions with short names that make sense to question authors. On the other hand, you don't wnat to use short names that might clash with other people's code.

that's exactly where our students put their code, it just seemed out of place to me because no other functions where defined there.

So maybe this would be a valid implementation after all?

Basically, what our code looks like is:

protected function calculate_raw($expression) { try { // In older PHP versions this this is a way to validate code passed to eval. // The trick came from http://php.net/manual/en/function.eval.php. if (@eval('return true; $result = ' . $expression . ';')) { return eval('return ' . $expression . ';'); } if(substr_count($expression, 'factorial')>0){ [factorial code] } if(substr_count($expression, 'permutation')>0){ [permutation code] } if(substr_count($expression, 'variation')>0){ [variation code] } if(substr_count($expression, 'bincoeff')>0){ [bincoeff code] } if(substr_count($expression, 'combination')>0){ [combination code] } } catch (Throwable $e) { // PHP7 and later now throws ParseException and friends from eval(), // which is much better. } // In either case of an invalid $expression, we end here. throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $expression); }so would it be actually OK to implement that code in this function? because then I will submit the whole code for review.

thanks,

-stefan

I would guess that to make this work, you would also need to add the new functions to qtype_calculated_find_formula_errors https://github.com/moodle/moodle/blob/master/question/type/calculated/questiontype.php#L1942.

Your approach seems pretty good. I can think of one possible different approach, but I am not sure it is better.

The other approach would be to define functions with names like qtype_calculated_factorial. Those can just be defined as normal functions, since the names won't clash with anything. Then, in calculate_raw, you would need to do a replace qtype_calculated_factorial -> factorial etc.

Note that, irrespective of whether you use your approach, or the one I suggest, I would recommend using a regular-expression match, something like

preg_match('~\bfactorial\s*\(~', $formula)

\b matches start of a word, so that won't match things like 'notfactorial'. Including the Open bracket in what you search for (after zero or more spaces) also reduces false positives.

Please add unit tests for this!