diff --git a/src/expression/embeddedDocs/construction/index.js b/src/expression/embeddedDocs/construction/index.js index b055cb8f2b..4f199b6681 100644 --- a/src/expression/embeddedDocs/construction/index.js +++ b/src/expression/embeddedDocs/construction/index.js @@ -20,6 +20,6 @@ export const indexDocs = { 'B[B>1 and B<3]' ], seealso: [ - 'bignumber', 'boolean', 'complex', 'matrix,', 'number', 'range', 'string', 'unit' + 'bignumber', 'boolean', 'complex', 'matrix', 'number', 'range', 'string', 'unit' ] } diff --git a/src/expression/embeddedDocs/embeddedDocs.js b/src/expression/embeddedDocs/embeddedDocs.js index e3232c89c1..7794e8e006 100644 --- a/src/expression/embeddedDocs/embeddedDocs.js +++ b/src/expression/embeddedDocs/embeddedDocs.js @@ -104,6 +104,9 @@ import { conjDocs } from './function/complex/conj.js' import { imDocs } from './function/complex/im.js' import { reDocs } from './function/complex/re.js' import { evaluateDocs } from './function/expression/evaluate.js' +import { parserDocs } from './function/expression/parser.js' +import { parseDocs } from './function/expression/parse.js' +import { compileDocs } from './function/expression/compile.js' import { helpDocs } from './function/expression/help.js' import { distanceDocs } from './function/geometry/distance.js' import { intersectDocs } from './function/geometry/intersect.js' @@ -429,6 +432,9 @@ export const embeddedDocs = { // functions - expression evaluate: evaluateDocs, help: helpDocs, + parse: parseDocs, + parser: parserDocs, + compile: compileDocs, // functions - geometry distance: distanceDocs, diff --git a/src/expression/embeddedDocs/function/expression/compile.js b/src/expression/embeddedDocs/function/expression/compile.js new file mode 100644 index 0000000000..ed29e8c6b0 --- /dev/null +++ b/src/expression/embeddedDocs/function/expression/compile.js @@ -0,0 +1,16 @@ +export const compileDocs = { + name: 'compile', + category: 'Expression', + syntax: [ + 'compile(expr) ', + 'compile([expr1, expr2, expr3, ...])' + ], + description: 'Parse and compile an expression. Returns a an object with a function evaluate([scope]) to evaluate the compiled expression.', + examples: [ + 'code1 = compile("sqrt(3^2 + 4^2)")', + 'code1.evaluate() ', + 'code2 = compile("a * b")', + 'code2.evaluate({a: 3, b: 4})' + ], + seealso: ['parser', 'parse', 'evaluate'] +} diff --git a/src/expression/embeddedDocs/function/expression/evaluate.js b/src/expression/embeddedDocs/function/expression/evaluate.js index eda7dccb87..394c6fa502 100644 --- a/src/expression/embeddedDocs/function/expression/evaluate.js +++ b/src/expression/embeddedDocs/function/expression/evaluate.js @@ -15,5 +15,5 @@ export const evaluateDocs = { 'evaluate("sin(x * pi)", { "x": 1/2 })', 'evaluate(["width=2", "height=4","width*height"])' ], - seealso: [] + seealso: ['parser', 'parse', 'compile'] } diff --git a/src/expression/embeddedDocs/function/expression/parse.js b/src/expression/embeddedDocs/function/expression/parse.js new file mode 100644 index 0000000000..2bd1174f67 --- /dev/null +++ b/src/expression/embeddedDocs/function/expression/parse.js @@ -0,0 +1,23 @@ +export const parseDocs = { + name: 'parse', + category: 'Expression', + syntax: [ + 'parse(expr)', + 'parse(expr, options)', + 'parse([expr1, expr2, expr3, ...])', + 'parse([expr1, expr2, expr3, ...], options)' + ], + description: 'Parse an expression. Returns a node tree, which can be evaluated by invoking node.evaluate() or transformed into a functional object via node.compile().', + examples: [ + 'node1 = parse("sqrt(3^2 + 4^2)")', + 'node1.evaluate()', + 'code1 = node1.compile()', + 'code1.evaluate()', + 'scope = {a: 3, b: 4}', + 'node2 = parse("a * b")', + 'node2.evaluate(scope)', + 'code2 = node2.compile()', + 'code2.evaluate(scope)' + ], + seealso: ['parser', 'evaluate', 'compile'] +} diff --git a/src/expression/embeddedDocs/function/expression/parser.js b/src/expression/embeddedDocs/function/expression/parser.js new file mode 100644 index 0000000000..75eda8825b --- /dev/null +++ b/src/expression/embeddedDocs/function/expression/parser.js @@ -0,0 +1,17 @@ +export const parserDocs = { + name: 'parser', + category: 'Expression', + syntax: [ + 'parser()' + ], + description: 'Create a parser object that keeps a context of variables and their values, allowing the evaluation of expressions in that context.', + examples: [ + 'myParser = parser()', + 'myParser.evaluate("sqrt(3^2 + 4^2)")', + 'myParser.set("x", 3)', + 'myParser.evaluate("y = x + 3")', + 'myParser.evaluate(["y = x + 3", "y = y + 1"])', + 'myParser.get("y")' + ], + seealso: ['evaluate', 'parse', 'compile'] +} diff --git a/src/expression/embeddedDocs/function/statistics/sum.js b/src/expression/embeddedDocs/function/statistics/sum.js index c6317c0a8c..6c486aa444 100644 --- a/src/expression/embeddedDocs/function/statistics/sum.js +++ b/src/expression/embeddedDocs/function/statistics/sum.js @@ -19,7 +19,6 @@ export const sumDocs = { 'min', 'prod', 'std', - 'sum', 'variance' ] } diff --git a/src/expression/function/parser.js b/src/expression/function/parser.js index b1c87f2b43..eb302ea74f 100644 --- a/src/expression/function/parser.js +++ b/src/expression/function/parser.js @@ -5,7 +5,7 @@ const dependencies = ['typed', 'Parser'] export const createParser = /* #__PURE__ */ factory(name, dependencies, ({ typed, Parser }) => { /** - * Create a parser. The function creates a new `math.Parser` object. + * Create a `math.Parser` object that keeps a context of variables and their values, allowing the evaluation of expressions in that context. * * Syntax: * diff --git a/src/expression/parse.js b/src/expression/parse.js index 8eab0230db..099061016b 100644 --- a/src/expression/parse.js +++ b/src/expression/parse.js @@ -48,7 +48,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ }) => { /** * Parse an expression. Returns a node tree, which can be evaluated by - * invoking node.evaluate(). + * invoking node.evaluate() or transformed into a functional object via node.compile(). * * Note the evaluating arbitrary expressions may involve security risks, * see [https://mathjs.org/docs/expressions/security.html](https://mathjs.org/docs/expressions/security.html) for more information. @@ -67,6 +67,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({ * * let scope = {a:3, b:4} * const node2 = math.parse('a * b') // 12 + * node2.evaluate(scope) // 12 * const code2 = node2.compile() * code2.evaluate(scope) // 12 * scope.a = 5 diff --git a/test/unit-tests/expression/function/help.test.js b/test/unit-tests/expression/function/help.test.js index 123c4789e5..d531de37b8 100644 --- a/test/unit-tests/expression/function/help.test.js +++ b/test/unit-tests/expression/function/help.test.js @@ -2,6 +2,59 @@ import assert from 'assert' import math from '../../../../src/defaultInstance.js' import { embeddedDocs } from '../../../../src/expression/embeddedDocs/embeddedDocs.js' +let mathDocs = math.create(math.all) +const originalConfig = mathDocs.config() +// Add names to the skipDocs array if they are not meant to have embedded docs +const skipDocs = new Set(['import', 'addScalar', 'divideScalar', 'equalScalar', 'multiplyScalar', + 'subtractScalar', 'apply', 'replacer', 'reviver']) + +// Add names to skipExamples if their examples in the embedded docs contain acceptable errors +const skipExamples = new Set([]) + +const testDocs = new Set([ + ...Object.keys(embeddedDocs), + ...Object.keys(math.expression.mathWithTransform) +].filter(name => { return !skipDocs.has(name) })) + +const testExamples = new Set([...testDocs].filter(name => { + return !skipExamples.has(name) +})) + +function runExamplesInDocs (name) { + mathDocs.config(originalConfig) + // every funciton should have doc.examples + const examples = mathDocs.evaluate(`help("${name}")`).doc.examples + try { + // validate if the examples run without errors + mathDocs.evaluate(examples) + return + } catch { + } + // if they still have errors try with a new math instance + mathDocs = math.create(math.all) + mathDocs.evaluate(examples) +} + +function hasValidSeeAlso (name) { + let seeAlso = [] + try { + seeAlso = mathDocs.evaluate(`help("${name}")`).doc.seealso + } catch (err) { + return + } + if (seeAlso && seeAlso.length > 0) { + seeAlso.forEach(see => { + if (testDocs.has(see)) { + if (see === name) { + throw new Error(`See also name "${see}" should not be the same as "${name}" in docs for "${name}".`) + } + } else { + throw new Error(`See also with name "${see}" is not a valid documentation name used in docs for "${name}".`) + } + }) + } +} + describe('help', function () { it('should find documentation for a function by its name', function () { const help = math.help('sin') @@ -59,11 +112,33 @@ describe('help', function () { // assert.throws(function () {math.help(undefined)}, /No documentation found/); assert.throws(function () { math.help(new Date()) }, /No documentation found/) assert.throws(function () { math.help('nonExistingFunction') }, /No documentation found/) - assert.throws(function () { math.help('parse') }, /No documentation found/) + assert.throws(function () { math.help('addScalar') }, /No documentation found/) }) it('should LaTeX help', function () { const expression = math.parse('help(parse)') assert.strictEqual(expression.toTex(), '\\mathrm{help}\\left( parse\\right)') }) + + for (const name of testDocs) { + it(`should find documentation for ${name}`, function () { + assert.doesNotThrow(() => mathDocs.help(name).doc) + }) + } + + for (const name of testDocs) { + it(`should find examples for ${name}`, function () { + assert.doesNotThrow(() => mathDocs.help(name).doc.examples) + }) + } + for (const name of testExamples) { + it(`should run examples for ${name} without errors`, function () { + assert.doesNotThrow(() => runExamplesInDocs(name)) + }) + } + for (const name of testDocs) { + it(`should have all valid See Also for ${name} that are not ${name}`, function () { + assert.doesNotThrow(() => hasValidSeeAlso(name)) + }) + } })