From be66844f172f6cc86fe60ace37e94090349f5712 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 9 Apr 2022 02:50:20 -0500 Subject: [PATCH] Implement MathPage evaluation logic --- src/support/page.ts | 52 ++++++++++++++++++++++++++++++++++++++------ src/support/parse.ts | 19 ++++++++++++++-- src/types.ts | 7 ++++++ 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/support/page.ts b/src/support/page.ts index 51a905e..2cebbbd 100644 --- a/src/support/page.ts +++ b/src/support/page.ts @@ -2,58 +2,77 @@ import {MathStatement} from './parse' import * as math from 'mathjs' import {DepGraph} from 'dependency-graph' import { v4 as uuidv4 } from 'uuid' +import {EvaluationResult, StatementID, VariableName} from '../types' +/** + * Wrapper for a page containing multiple interrelated mathematical statements. + */ export class MathPage { - protected statements: Record = {} + /** The statements on the page. */ + protected statements: Record = {} constructor( + /** Unique page ID. */ public readonly id: string, ) {} + /** 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): this { - return this.addStatement(new MathStatement(uuidv4(), statement)) + return this.addStatement(new MathStatement(uuidv4() as StatementID, statement)) } + /** 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), []) } - dependencies(): DepGraph { - const graph = new DepGraph() - const defined: Record = {} + /** 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() ) { - defined[symbol.name] = statement + 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] + const provider = defined[symbol.name as VariableName] if ( !provider ) { throw new Error('No provider for undefined symbol: ' + symbol.name) } @@ -64,4 +83,23 @@ export class MathPage { 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, + } + } } diff --git a/src/support/parse.ts b/src/support/parse.ts index ddbe7be..2c44ce9 100644 --- a/src/support/parse.ts +++ b/src/support/parse.ts @@ -1,6 +1,6 @@ import * as math from 'mathjs' import katex from 'katex' -import {HTMLString, LaTeXString} from '../types' +import {HTMLString, LaTeXString, StatementID} from '../types' /** Base class for walks over MathNode trees. */ export abstract class MathNodeWalk { @@ -72,6 +72,7 @@ export abstract class MathNodeWalk { } +/** A walk that accumulates all different SymbolNode instances in a tree. */ export class SymbolWalk extends MathNodeWalk { walkAccessorNode(node: math.AccessorNode): math.SymbolNode[] { return [ @@ -168,6 +169,7 @@ export class SymbolWalk extends MathNodeWalk { } +/** A walk that accumulates all SymbolNode instances used on the RHS of expressions. */ export class RValSymbolWalk extends SymbolWalk { walkAssignmentNode(node: math.AssignmentNode): math.SymbolNode[] { return this.walk(node.value) @@ -184,6 +186,7 @@ export class RValSymbolWalk extends SymbolWalk { } +/** A walk that accumulates SymbolNode instances used on the LHS of assignments. */ export class LValSymbolWalk extends SymbolWalk { walkAccessorNode(): math.SymbolNode[] { return [] @@ -252,26 +255,35 @@ export class LValSymbolWalk extends SymbolWalk { } } + +/** A single mathematical statement. */ export class MathStatement { constructor( - public readonly id: string, + /** Unique ID of this statement. */ + public readonly id: StatementID, + + /** The raw statement input by the user. */ public readonly raw: string, ) {} + /** Parse the raw statement to an AST. */ parse(): math.MathNode { return math.parse(this.raw) } + /** Convert the statement to its equivalent LaTeX code. */ toLaTeX(): LaTeXString { return this.parse().toTex() as LaTeXString } + /** Render the statement as HTML string. */ toHTMLString(): HTMLString { return katex.renderToString(this.toLaTeX(), { output: 'mathml', }) as HTMLString } + /** Render the statement to a DOM element. */ toDOM(): HTMLSpanElement { const node = document.createElement('span') katex.render(this.toLaTeX(), node, { @@ -280,14 +292,17 @@ export class MathStatement { return node } + /** Get all symbols referenced in this statement. */ symbols(): math.SymbolNode[] { return (new SymbolWalk()).walk(this.parse()) } + /** Get all symbols defined on the LHS of this statement. */ defines(): math.SymbolNode[] { return (new LValSymbolWalk()).walk(this.parse()) } + /** Get all symbols used on the RHS of this statement. */ uses(): math.SymbolNode[] { return (new RValSymbolWalk()).walk(this.parse()) } diff --git a/src/types.ts b/src/types.ts index 1e99c3e..dd71aac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -83,3 +83,10 @@ export function isInteger(num: number): num is Integer { export type LaTeXString = TypeTag<'@app.LaTeXString'> & string export type HTMLString = TypeTag<'@app.HTMLString'> & string +export type StatementID = TypeTag<'@app.StatementID'> & string +export type VariableName = TypeTag<'@app.VariableName'> & string + +export interface EvaluationResult { + variables: Record + statements: Record +}