chore: add moved files added in v14.7.0

This commit is contained in:
Jos de Jong 2025-09-05 10:42:23 +02:00
parent 1e95a76fde
commit 391a534d3e
9 changed files with 1995 additions and 0 deletions

View File

@ -0,0 +1,57 @@
---
layout: default
---
<!-- Note: This file is automatically generated from source code comments. Changes made in this file will be overridden. -->
<h1 id="function-bigint">Function bigint <a href="#function-bigint" title="Permalink">#</a></h1>
Create a bigint or convert a string, boolean, or unit to a bigint.
When value is a matrix, all elements will be converted to bigint.
<h2 id="syntax">Syntax <a href="#syntax" title="Permalink">#</a></h2>
```js
math.bigint(value)
```
<h3 id="parameters">Parameters <a href="#parameters" title="Permalink">#</a></h3>
Parameter | Type | Description
--------- | ---- | -----------
`value` | string &#124; number &#124; BigNumber &#124; bigint &#124; Fraction &#124; boolean &#124; Array &#124; Matrix &#124; null | Value to be converted
<h3 id="returns">Returns <a href="#returns" title="Permalink">#</a></h3>
Type | Description
---- | -----------
bigint &#124; Array &#124; Matrix | The created bigint
<h3 id="throws">Throws <a href="#throws" title="Permalink">#</a></h3>
Type | Description
---- | -----------
<h2 id="examples">Examples <a href="#examples" title="Permalink">#</a></h2>
```js
math.bigint(2) // returns 2n
math.bigint('123') // returns 123n
math.bigint(true) // returns 1n
math.bigint([true, false, true, true]) // returns [1n, 0n, 1n, 1n]
```
<h2 id="see-also">See also <a href="#see-also" title="Permalink">#</a></h2>
[number](number.html),
[bignumber](bignumber.html),
[boolean](boolean.html),
[complex](complex.html),
[index](index.html),
[matrix](matrix.html),
[string](string.html),
[unit](unit.html)

View File

@ -0,0 +1,18 @@
# Code Editor Example
This is an example for using [mathjs](https://mathjs.org) with a code editor.
To run your own you need to install the dependancies with.
```
npm install
```
You can start development mode with:
```
npm run dev
```
Or build the project with:
```
npm run build
```

View File

@ -0,0 +1,59 @@
import { parse } from 'mathjs'
/**
* Extracts parsable expressions from a multiline string.
*
* @param {string} str - The multiline string containing expressions.
* @returns {Array<{from: number, to: number, source: string}>} An array of objects,
* where each object represents a parsable expression and contains:
* - from: The starting line number of the expression within the original string.
* - to: The ending line number of the expression within the original string.
* - source: The actual string content of the expression.
*/
export default function getExpressions(str) {
const lines = str.split('\n');
let nextLineToParse = 0;
const result = [];
for (let lineID = 0; lineID < lines.length; lineID++) {
const linesToTest = lines.slice(nextLineToParse, lineID + 1).join('\n');
if (canBeParsed(linesToTest)) {
if (!isEmptyString(linesToTest)) {
result.push({ from: nextLineToParse, to: lineID, source: linesToTest });
}
// Start the next parsing attempt from the line after the successfully parsed expression.
nextLineToParse = lineID + 1;
}
}
// Handle any remaining lines that couldn't be parsed as expressions.
const linesToTest = lines.slice(nextLineToParse).join('\n');
if (!isEmptyString(linesToTest)) {
result.push({ from: nextLineToParse, to: lines.length - 1, source: linesToTest });
}
return result;
}
/**
* Determines whether a given expression can be successfully parsed.
*
* @param {string} expression - The expression to parse.
* @returns {boolean} True if the expression can be parsed, false otherwise.
*/
function canBeParsed(expression) {
try {
parse(expression)
return true
} catch (error) {
return false
}
}
/**
* Checks if a given string is empty or only contains whitespace characters.
*
* @param {string} str - The string to check.
* @returns {boolean} True if the string is empty or only contains whitespace, false otherwise.
*/
function isEmptyString(str) {
return str.trim() === ""
}

View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>math.js | code editor</title>
</head>
<body>
<div id="app">
<div id="editor"></div>
<article id="result" class="markdown-body"></article>
</div>
<script type="module" src="/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,214 @@
import './style.css'
import 'github-markdown-css/github-markdown.css'
import 'katex/dist/katex.min.css'
import { StreamLanguage } from '@codemirror/language'
import { EditorState } from '@codemirror/state'
import { basicSetup, EditorView } from 'codemirror'
import katex from 'katex'
import { all, create } from 'mathjs'
import getExpressions from './getExpressions'
import { mathjsLang } from './mathjs-lang.js'
const timeout = 250 // milliseconds
const digits = 14
const math = create(all)
const parser = math.parser()
const editorDOM = document.querySelector('#editor')
const resultsDOM = document.querySelector('#result')
let processedExpressions
let previousSelectedExpressionIndex
let timer
const doc = [
"round(e, 3)",
"atan2(3, -3) / pi",
"log(10000, 10)",
"sqrt(-4)",
"derivative('x^2 + x', 'x')",
"pow([[-1, 2], [3, 1]], 2)",
"# expressions",
"1.2 * (2 + 4.5)",
"12.7 cm to inch",
"sin(45 deg) ^ 2",
"9 / 3 + 2i",
"det([-1, 2; 3, 1])"
].join('\n')
let startState = EditorState.create({
doc,
extensions: [
basicSetup,
StreamLanguage.define(mathjsLang(math)),
EditorView.lineWrapping,
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// if doc changed debounce and update results after a timeout
clearTimeout(timer)
timer = setTimeout(() => {
updateResults()
previousSelectedExpressionIndex = null
updateSelection()
}, timeout)
} else if (update.selectionSet) {
updateSelection()
}
})
],
})
let editor = new EditorView({
state: startState,
parent: editorDOM
})
/**
* Evaluates a given expression using a parser.
*
* @param {string} expression - The expression to evaluate.
* @returns {any} The result of the evaluation, or the error message if an error occurred.
*/
function calc(expression) {
let result
try {
result = parser.evaluate(expression)
} catch (error) {
result = error.toString()
}
return result
}
/**
* Formats result depending on the type of result
*
* @param {number, string, Help, any} result - The result to format
* @returns {string} The string in HTML with the formated result
*/
const formatResult = math.typed({
'number': result => math.format(result, { precision: digits }),
'string': result => `<code>${result}</code>`,
'Help': result => `<pre>${math.format(result)}</pre>`,
'any': math.typed.referTo(
'number',
fnumber => result => katex.renderToString(math.parse(fnumber(result)).toTex())
)
})
/**
* Processes an array of expressions by evaluating them, formatting the results,
* and determining their visibility.
*
* @param {Array<{from: number, to: number, source: string}>} expressions - An array of objects representing expressions,
* where each object has `from`, `to`, and `source` properties.
* @returns {Array<{from: number, to: number, source: string, outputs: any, visible: boolean}>} An array of processed expressions,
* where each object has additional `outputs` and `visible` properties.
*/
function processExpressions(expressions) {
parser.clear()
return expressions.map(expression => {
const result = calc(expression.source)
const outputs = formatResult(result)
// Determine visibility based on the result type:
// - Undefined results are hidden.
// - Results with an `isResultSet` property are hidden when empty.
// - All other results are visible.
const visible = result === undefined ? false : (result.isResultSet && result.entries.length === 0) ? false : true
return ({
...expression,
outputs,
visible
})
})
}
/**
* Updates the displayed results based on the editor's current content.
*
* @function updateResults
* @requires getExpressions, processExpressions, resultsToHTML
*
* @description
* 1. Extracts expressions from the editor's content.
* 2. Evaluates and analyzes the expressions.
* 3. Generates HTML to display the processed results.
* 4. Renders the generated HTML in the designated results container.
*/
function updateResults() {
// Extract expressions from the editor's content.
const expressions = getExpressions(editor.state.doc.toString());
// Evaluate and analyze the expressions.
processedExpressions = processExpressions(expressions);
// Generate HTML to display the results.
const resultsHtml = resultsToHTML(processedExpressions);
// Render the generated HTML in the results container.
resultsDOM.innerHTML = resultsHtml;
}
/**
* Updates the visual highlighting of results based on the current line selection in the editor.
*
* @function updateSelection
* @requires editor, processedExpressions
*
* @description
* 1. Determines the current line number in the editor's selection.
* 2. Finds the corresponding result (processed expression) that matches the current line.
* 3. If a different result is selected than before:
* - Removes highlighting from the previously selected result.
* - Highlights the newly selected result.
* - Scrolls the newly selected result into view.
*/
function updateSelection() {
const selectedLine = editor.state.doc.lineAt(
editor.state.selection.ranges[editor.state.selection.mainIndex].from
).number - 1;
let selectedExpressionIndex;
processedExpressions.forEach((result, index) => {
if ((selectedLine >= result.from) && (selectedLine <= result.to)) {
selectedExpressionIndex = index;
}
});
if (selectedExpressionIndex !== previousSelectedExpressionIndex) {
const previouslyHighlightedResult = document.querySelector('#result').children[previousSelectedExpressionIndex];
if (previouslyHighlightedResult !== undefined) {
previouslyHighlightedResult.className = null;
}
const currentlySelectedResult = document.querySelector('#result').children[selectedExpressionIndex];
if (currentlySelectedResult !== undefined) {
currentlySelectedResult.className = 'highlighted';
currentlySelectedResult.scrollIntoView({ block: 'nearest', inline: 'start' });
}
previousSelectedExpressionIndex = selectedExpressionIndex;
}
}
/**
* Converts an array of processed results into HTML elements for display.
*
* @function resultsToHTML
* @param {Array<{from: number, to: number, source: string, outputs: any, visible: boolean}>} results - An array of processed results, where each object has:
* - from: The starting line number of the expression.
* - to: The ending line number of the expression.
* - source: The original expression string.
* - outputs: The formatted result of evaluating the expression.
* - visible: A boolean indicating whether the result should be displayed or hidden.
* @returns {string} A string of HTML elements representing the results, where each result is enclosed in a <pre> tag with appropriate styling based on its visibility.
*/
function resultsToHTML(results) {
return results.map(el => {
const elementStyle = el.visible ? '' : 'style="display:none"'
return `<pre ${elementStyle}>${el.outputs}</pre>`
}
).join('')
}
updateResults()

View File

@ -0,0 +1,244 @@
/**
* Create mathjs syntax highlighting for CodeMirror
*
* TODO: this is using CodeMirror v5 functionality, upgrade this to v6
*
* @param {Object} math A mathjs instance
*/
export function mathjsLang(math) {
function wordRegexp(words) {
return new RegExp('^((' + words.join(')|(') + '))\\b')
}
const singleOperators = new RegExp("^[-+*/&|^~<>!%']")
const singleDelimiters = new RegExp('^[([{},:=;.?]')
const doubleOperators = new RegExp('^((==)|(!=)|(<=)|(>=)|(<<)|(>>)|(\\.[-+*/^]))')
const doubleDelimiters = new RegExp('^((!=)|(^\\|))')
const tripleDelimiters = new RegExp('^((>>>)|(<<<))')
const expressionEnd = new RegExp('^[\\])]')
const identifiers = new RegExp('^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*')
const mathFunctions = []
const mathPhysicalConstants = []
const mathIgnore = ['expr', 'type']
const numberLiterals = [
'e',
'E',
'i',
'Infinity',
'LN2',
'LN10',
'LOG2E',
'LOG10E',
'NaN',
'null',
'phi',
'pi',
'PI',
'SQRT1_2',
'SQRT2',
'tau',
'undefined',
'version'
]
// based on https://github.com/josdejong/mathjs/blob/develop/bin/cli.js
for (const expr in math.expression.mathWithTransform) {
if (!mathIgnore.includes(expr)) {
if (typeof math[expr] === 'function') {
mathFunctions.push(expr)
} else if (!numberLiterals.includes(expr)) {
mathPhysicalConstants.push(expr)
}
}
}
// generates a list of all valid units in mathjs
const listOfUnits = []
for (const unit in math.Unit.UNITS) {
for (const prefix in math.Unit.UNITS[unit].prefixes) {
listOfUnits.push(prefix + unit)
}
}
const builtins = wordRegexp(mathFunctions)
const keywords = wordRegexp(['to', 'in', 'and', 'not', 'or', 'xor', 'mod'])
const units = wordRegexp(Array.from(new Set(listOfUnits)))
const physicalConstants = wordRegexp(mathPhysicalConstants)
// tokenizers
function tokenTranspose(stream, state) {
if (!stream.sol() && stream.peek() === "'") {
stream.next()
state.tokenize = tokenBase
return 'operator'
}
state.tokenize = tokenBase
return tokenBase(stream, state)
}
function tokenComment(stream, state) {
if (stream.match(/^.*#}/)) {
state.tokenize = tokenBase
return 'comment'
}
stream.skipToEnd()
return 'comment'
}
function tokenBase(stream, state) {
// whitespaces
if (stream.eatSpace()) return null
// Handle one line Comments
if (stream.match('#{')) {
state.tokenize = tokenComment
stream.skipToEnd()
return 'comment'
}
if (stream.match(/^#/)) {
stream.skipToEnd()
return 'comment'
}
// Handle Number Literals
if (stream.match(/^[0-9.+-]/, false)) {
if (stream.match(/^[+-]?0x[0-9a-fA-F]+[ij]?/)) {
stream.tokenize = tokenBase
return 'number'
}
if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?[ij]?/)) {
return 'number'
}
if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?[ij]?/)) {
return 'number'
}
}
if (stream.match(wordRegexp(numberLiterals))) {
return 'number'
}
// Handle Strings
let m = stream.match(/^"(?:[^"]|"")*("|$)/) || stream.match(/^'(?:[^']|'')*('|$)/)
if (m) {
return m[1] ? 'string' : 'string error'
}
// Handle words
if (stream.match(keywords)) {
return 'keyword'
}
if (stream.match(builtins)) {
return 'builtin'
}
if (stream.match(physicalConstants)) {
return 'tag'
}
if (stream.match(units)) {
return 'attribute'
}
if (stream.match(identifiers)) {
return 'variable'
}
if (stream.match(singleOperators) || stream.match(doubleOperators)) {
return 'operator'
}
if (
stream.match(singleDelimiters) ||
stream.match(doubleDelimiters) ||
stream.match(tripleDelimiters)
) {
return null
}
if (stream.match(expressionEnd)) {
state.tokenize = tokenTranspose
return null
}
// Handle non-detected items
stream.next()
return 'error'
}
return {
name: 'mathjs',
startState: function () {
return {
tokenize: tokenBase
}
},
token: function (stream, state) {
const style = state.tokenize(stream, state)
if (style === 'number' || style === 'variable') {
state.tokenize = tokenTranspose
}
return style
},
languageData: {
commentTokens: { line: '#' },
autocomplete: myCompletions
}
}
function myCompletions(context) {
let word = context.matchBefore(/\w*/)
if (word.from == word.to && !context.explicit) return null
let options = []
mathFunctions.forEach((func) => options.push({ label: func, type: 'function' }))
mathPhysicalConstants.forEach((constant) => options.push({ label: constant, type: 'constant' }))
numberLiterals.forEach((number) => options.push({ label: number, type: 'variable' }))
// units as enum
for (const name in math.Unit.UNITS) {
if (hasOwnPropertySafe(math.Unit.UNITS, name)) {
if (name.startsWith(word.text)) {
options.push({ label: name, type: 'enum' })
}
}
}
for (const name in math.Unit.PREFIXES) {
if (hasOwnPropertySafe(math.Unit.PREFIXES, name)) {
const prefixes = math.Unit.PREFIXES[name]
for (const prefix in prefixes) {
if (hasOwnPropertySafe(prefixes, prefix)) {
if (prefix.startsWith(word.text)) {
options.push({ label: prefix, type: 'enum' })
} else if (word.text.startsWith(prefix)) {
const unitKeyword = word.text.substring(prefix.length)
for (const n in math.Unit.UNITS) {
const fullUnit = prefix + n
if (hasOwnPropertySafe(math.Unit.UNITS, n)) {
if (
!options.includes(fullUnit) &&
n.startsWith(unitKeyword) &&
math.Unit.isValuelessUnit(fullUnit)
) {
options.push({ label: fullUnit, type: 'enum' })
}
}
}
}
}
}
}
}
return {
from: word.from,
options
}
}
}
// helper function to safely check whether an object has a property
// copy from the function in object.js which is ES6
function hasOwnPropertySafe(object, property) {
return object && Object.hasOwnProperty.call(object, property)
}

1303
examples/code_editor/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "code-editor",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "7.1.3"
},
"dependencies": {
"@codemirror/language": "6.11.3",
"@codemirror/state": "6.5.2",
"codemirror": "6.0.2",
"github-markdown-css": "5.8.1",
"katex": "0.16.22",
"mathjs": "14.6.0"
}
}

View File

@ -0,0 +1,60 @@
html,
body {
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
}
#app {
display: flex;
overflow: hidden;
flex: 1;
flex-direction: row;
}
#editor {
flex: 1;
height: auto;
display: flex;
overflow: auto;
}
#editor>.cm-editor {
flex: 1;
overflow: scroll;
}
#app>article {
flex: 1;
overflow: auto;
box-sizing: border-box;
}
article.markdown-body .results {
padding: 0.5em;
margin: 0.5em;
}
article.markdown-body > .highlighted {
background-color: rgba(0, 150, 255, 0.2);
}
article.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 1em;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}