@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
80 lines (65 loc) • 2.71 kB
JavaScript
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')
}