UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

80 lines (65 loc) 2.71 kB
import uniq from 'lodash/uniq' import {mathjsLoadingWrapper} from '../components/formula/common/util' import {Decimal} from '@instructure/quiz-i18n/util/Decimal' import t from '@instructure/quiz-i18n/format-message' export const variablesFromItemBody = itemBody => { const body = itemBody || '' const matches = body.match(/`[A-Za-z]\w*`/g) || [] const matchesWithoutBackticks = matches.map(match => match.replace(/`/g, '')) return uniq(matchesWithoutBackticks).sort() } export const formulaSyntaxError = (variables, formula) => { const substitutions = {} variables.forEach(varName => (substitutions[varName] = 1)) try { // TODO: This will fail if nothing has called components/formula/Edit/util/loadMathJs() yet. // we should either change this function signature to return a promise and do // loadMathjs().then(mathjs => mathjs.eval...) or make sure it is always preloaded by // the time we get here., const math = mathjsLoadingWrapper.mathjs // This parses the formula detecting if the locale uses comma as separator const parsedFormula = parseFormulaDecimalSeparator( window.document.documentElement.lang || 'en-US', formula, ) // we replace function e() with the constant e // and function pi() with the constant pi // this is to match legacy canvas quizzes behavior which accepted both e/pi and e()/pi() const node = math.parse(parsedFormula) const transformed = node.transform((node, path, parent) => { if (node.isFunctionNode && node.fn.isSymbolNode && node.fn.name === 'e') { return new math.expression.node.ConstantNode(Math.E) } else if (node.isFunctionNode && node.fn.isSymbolNode && node.fn.name === 'pi') { return new math.expression.node.ConstantNode(Math.PI) } else { return node } }) const compiled = transformed.compile() compiled.eval(substitutions) } catch (e) { const message = e.message console.error('Formula Error: ' + e.message) let match = message.match(/Undefined symbol ([A-Za-z]+)/) if (match) { return t('Undefined symbol {symbol}', {symbol: match[1]}) } match = message.match(/Unexpected end of expression/) if (match) { return t('Unexpected end of expression') } match = message.match(/Unexpected operator ([^\s]+)/) if (match) { return t('Unexpected operator {operator}', {operator: match[1]}) } return t('Invalid formula') } return null } export const parseFormulaDecimalSeparator = (locale, formula) => { const {decimal} = Decimal.getDelimiters(locale) if (decimal === '.') { return formula } return formula.replace(/(\d+),(\d+)/g, '$1.$2') }