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 = {} 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 { 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 { const definers: Record = {} 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 { const graph = new DepGraph() const defined: Record = 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 = {} const scope: Record = {} 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, } } }