You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ui/src/support/page.ts

118 lines
3.8 KiB

import {MathStatement} from './parse'
import * as math from 'mathjs'
import {DepGraph} from 'dependency-graph'
import { v4 as uuidv4 } from 'uuid'
import {EvaluationResult, Maybe, StatementID, VariableName} from '../types'
/**
* Wrapper for a page containing multiple interrelated mathematical statements.
*/
export class MathPage {
/** The statements on the page. */
protected statements: Record<StatementID, MathStatement> = {}
constructor(
/** Unique page ID. */
public readonly id: string,
) {}
/** Get all defined statements. */
getStatements(): MathStatement[] {
return Object.values(this.statements)
}
/** Get a statement by ID if it exists. */
getStatement(id: StatementID): Maybe<MathStatement> {
return this.statements[id]
}
/** Add a statement to this page. */
addStatement(statement: MathStatement): this {
this.statements[statement.id] = statement
return this
}
/** Parse the math expression and add it to the page as a statement. */
addRaw(statement: string): StatementID {
const stmt = new MathStatement(uuidv4() as StatementID, statement)
this.addStatement(stmt)
return stmt.id
}
/** Get all symbols referenced by statements on this page. */
symbols(): math.SymbolNode[] {
return Object.values(this.statements)
.map(x => x.symbols())
.reduce((carry, current) => current.concat(carry), [])
}
/** Get all symbols defined on this page. */
defines(): math.SymbolNode[] {
return Object.values(this.statements)
.map(x => x.defines())
.reduce((carry, current) => current.concat(carry), [])
}
/** Get all symbols used on this page. */
uses(): math.SymbolNode[] {
return Object.values(this.statements)
.map(x => x.uses())
.reduce((carry, current) => current.concat(carry), [])
}
/** Get a mapping of symbol names to the statements where they are defined. */
definers(): Record<VariableName, MathStatement> {
const definers: Record<VariableName, MathStatement> = {}
for ( const statement of Object.values(this.statements) ) {
for ( const symbol of statement.defines() ) {
definers[symbol.name as VariableName] = statement
}
}
return definers
}
/** Get the dependency graph of variable declarations between statements on this page. */
dependencies(): DepGraph<MathStatement> {
const graph = new DepGraph<MathStatement>()
const defined: Record<VariableName, MathStatement> = this.definers()
for ( const statement of Object.values(this.statements) ) {
graph.addNode(statement.id, statement)
}
for ( const statement of Object.values(this.statements) ) {
for ( const symbol of statement.uses() ) {
const provider = defined[symbol.name as VariableName]
if ( !provider ) {
throw new Error('No provider for undefined symbol: ' + symbol.name)
}
graph.addDependency(statement.id, provider.id)
}
}
return graph
}
/** Evaluate the current state of the page and get the result. */
evaluate(): EvaluationResult {
const evaluations: Record<StatementID, any> = {}
const scope: Record<VariableName, any> = {}
const graph = this.dependencies()
for ( const node of graph.overallOrder() ) {
const stmt = this.statements[node as StatementID]
evaluations[stmt.id] = stmt.parse()
.compile()
.evaluate(scope)
}
return {
variables: scope,
statements: evaluations,
}
}
}