Skip to content

Commit f9cb08d

Browse files
authored
feat: define embedded docs for compile, evaluate, parse, and parser, and add test for the examples in embedded docs (#3413)
1 parent 9b8e324 commit f9cb08d

File tree

10 files changed

+143
-6
lines changed

10 files changed

+143
-6
lines changed

src/expression/embeddedDocs/construction/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ export const indexDocs = {
2020
'B[B>1 and B<3]'
2121
],
2222
seealso: [
23-
'bignumber', 'boolean', 'complex', 'matrix,', 'number', 'range', 'string', 'unit'
23+
'bignumber', 'boolean', 'complex', 'matrix', 'number', 'range', 'string', 'unit'
2424
]
2525
}

src/expression/embeddedDocs/embeddedDocs.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ import { conjDocs } from './function/complex/conj.js'
104104
import { imDocs } from './function/complex/im.js'
105105
import { reDocs } from './function/complex/re.js'
106106
import { evaluateDocs } from './function/expression/evaluate.js'
107+
import { parserDocs } from './function/expression/parser.js'
108+
import { parseDocs } from './function/expression/parse.js'
109+
import { compileDocs } from './function/expression/compile.js'
107110
import { helpDocs } from './function/expression/help.js'
108111
import { distanceDocs } from './function/geometry/distance.js'
109112
import { intersectDocs } from './function/geometry/intersect.js'
@@ -429,6 +432,9 @@ export const embeddedDocs = {
429432
// functions - expression
430433
evaluate: evaluateDocs,
431434
help: helpDocs,
435+
parse: parseDocs,
436+
parser: parserDocs,
437+
compile: compileDocs,
432438

433439
// functions - geometry
434440
distance: distanceDocs,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const compileDocs = {
2+
name: 'compile',
3+
category: 'Expression',
4+
syntax: [
5+
'compile(expr) ',
6+
'compile([expr1, expr2, expr3, ...])'
7+
],
8+
description: 'Parse and compile an expression. Returns a an object with a function evaluate([scope]) to evaluate the compiled expression.',
9+
examples: [
10+
'code1 = compile("sqrt(3^2 + 4^2)")',
11+
'code1.evaluate() ',
12+
'code2 = compile("a * b")',
13+
'code2.evaluate({a: 3, b: 4})'
14+
],
15+
seealso: ['parser', 'parse', 'evaluate']
16+
}

src/expression/embeddedDocs/function/expression/evaluate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ export const evaluateDocs = {
1515
'evaluate("sin(x * pi)", { "x": 1/2 })',
1616
'evaluate(["width=2", "height=4","width*height"])'
1717
],
18-
seealso: []
18+
seealso: ['parser', 'parse', 'compile']
1919
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export const parseDocs = {
2+
name: 'parse',
3+
category: 'Expression',
4+
syntax: [
5+
'parse(expr)',
6+
'parse(expr, options)',
7+
'parse([expr1, expr2, expr3, ...])',
8+
'parse([expr1, expr2, expr3, ...], options)'
9+
],
10+
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().',
11+
examples: [
12+
'node1 = parse("sqrt(3^2 + 4^2)")',
13+
'node1.evaluate()',
14+
'code1 = node1.compile()',
15+
'code1.evaluate()',
16+
'scope = {a: 3, b: 4}',
17+
'node2 = parse("a * b")',
18+
'node2.evaluate(scope)',
19+
'code2 = node2.compile()',
20+
'code2.evaluate(scope)'
21+
],
22+
seealso: ['parser', 'evaluate', 'compile']
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const parserDocs = {
2+
name: 'parser',
3+
category: 'Expression',
4+
syntax: [
5+
'parser()'
6+
],
7+
description: 'Create a parser object that keeps a context of variables and their values, allowing the evaluation of expressions in that context.',
8+
examples: [
9+
'myParser = parser()',
10+
'myParser.evaluate("sqrt(3^2 + 4^2)")',
11+
'myParser.set("x", 3)',
12+
'myParser.evaluate("y = x + 3")',
13+
'myParser.evaluate(["y = x + 3", "y = y + 1"])',
14+
'myParser.get("y")'
15+
],
16+
seealso: ['evaluate', 'parse', 'compile']
17+
}

src/expression/embeddedDocs/function/statistics/sum.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const sumDocs = {
1919
'min',
2020
'prod',
2121
'std',
22-
'sum',
2322
'variance'
2423
]
2524
}

src/expression/function/parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const dependencies = ['typed', 'Parser']
55

66
export const createParser = /* #__PURE__ */ factory(name, dependencies, ({ typed, Parser }) => {
77
/**
8-
* Create a parser. The function creates a new `math.Parser` object.
8+
* Create a `math.Parser` object that keeps a context of variables and their values, allowing the evaluation of expressions in that context.
99
*
1010
* Syntax:
1111
*

src/expression/parse.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
4848
}) => {
4949
/**
5050
* Parse an expression. Returns a node tree, which can be evaluated by
51-
* invoking node.evaluate().
51+
* invoking node.evaluate() or transformed into a functional object via node.compile().
5252
*
5353
* Note the evaluating arbitrary expressions may involve security risks,
5454
* 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, ({
6767
*
6868
* let scope = {a:3, b:4}
6969
* const node2 = math.parse('a * b') // 12
70+
* node2.evaluate(scope) // 12
7071
* const code2 = node2.compile()
7172
* code2.evaluate(scope) // 12
7273
* scope.a = 5

test/unit-tests/expression/function/help.test.js

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,59 @@ import assert from 'assert'
22
import math from '../../../../src/defaultInstance.js'
33
import { embeddedDocs } from '../../../../src/expression/embeddedDocs/embeddedDocs.js'
44

5+
let mathDocs = math.create(math.all)
6+
const originalConfig = mathDocs.config()
7+
// Add names to the skipDocs array if they are not meant to have embedded docs
8+
const skipDocs = new Set(['import', 'addScalar', 'divideScalar', 'equalScalar', 'multiplyScalar',
9+
'subtractScalar', 'apply', 'replacer', 'reviver'])
10+
11+
// Add names to skipExamples if their examples in the embedded docs contain acceptable errors
12+
const skipExamples = new Set([])
13+
14+
const testDocs = new Set([
15+
...Object.keys(embeddedDocs),
16+
...Object.keys(math.expression.mathWithTransform)
17+
].filter(name => { return !skipDocs.has(name) }))
18+
19+
const testExamples = new Set([...testDocs].filter(name => {
20+
return !skipExamples.has(name)
21+
}))
22+
23+
function runExamplesInDocs (name) {
24+
mathDocs.config(originalConfig)
25+
// every funciton should have doc.examples
26+
const examples = mathDocs.evaluate(`help("${name}")`).doc.examples
27+
try {
28+
// validate if the examples run without errors
29+
mathDocs.evaluate(examples)
30+
return
31+
} catch {
32+
}
33+
// if they still have errors try with a new math instance
34+
mathDocs = math.create(math.all)
35+
mathDocs.evaluate(examples)
36+
}
37+
38+
function hasValidSeeAlso (name) {
39+
let seeAlso = []
40+
try {
41+
seeAlso = mathDocs.evaluate(`help("${name}")`).doc.seealso
42+
} catch (err) {
43+
return
44+
}
45+
if (seeAlso && seeAlso.length > 0) {
46+
seeAlso.forEach(see => {
47+
if (testDocs.has(see)) {
48+
if (see === name) {
49+
throw new Error(`See also name "${see}" should not be the same as "${name}" in docs for "${name}".`)
50+
}
51+
} else {
52+
throw new Error(`See also with name "${see}" is not a valid documentation name used in docs for "${name}".`)
53+
}
54+
})
55+
}
56+
}
57+
558
describe('help', function () {
659
it('should find documentation for a function by its name', function () {
760
const help = math.help('sin')
@@ -59,11 +112,33 @@ describe('help', function () {
59112
// assert.throws(function () {math.help(undefined)}, /No documentation found/);
60113
assert.throws(function () { math.help(new Date()) }, /No documentation found/)
61114
assert.throws(function () { math.help('nonExistingFunction') }, /No documentation found/)
62-
assert.throws(function () { math.help('parse') }, /No documentation found/)
115+
assert.throws(function () { math.help('addScalar') }, /No documentation found/)
63116
})
64117

65118
it('should LaTeX help', function () {
66119
const expression = math.parse('help(parse)')
67120
assert.strictEqual(expression.toTex(), '\\mathrm{help}\\left( parse\\right)')
68121
})
122+
123+
for (const name of testDocs) {
124+
it(`should find documentation for ${name}`, function () {
125+
assert.doesNotThrow(() => mathDocs.help(name).doc)
126+
})
127+
}
128+
129+
for (const name of testDocs) {
130+
it(`should find examples for ${name}`, function () {
131+
assert.doesNotThrow(() => mathDocs.help(name).doc.examples)
132+
})
133+
}
134+
for (const name of testExamples) {
135+
it(`should run examples for ${name} without errors`, function () {
136+
assert.doesNotThrow(() => runExamplesInDocs(name))
137+
})
138+
}
139+
for (const name of testDocs) {
140+
it(`should have all valid See Also for ${name} that are not ${name}`, function () {
141+
assert.doesNotThrow(() => hasValidSeeAlso(name))
142+
})
143+
}
69144
})

0 commit comments

Comments
 (0)