A world-class math input for the web
at main 122 lines 4.4 kB view raw
1import { CaretNode } from "./node"; 2import { CaretParser } from "./parser"; 3 4export type CaretParselet = 5 | CaretLeafParselet 6 | CaretPrefixParselet 7 | CaretInfixParselet 8 | CaretPostfixParselet 9 | CaretJuxtapositionParselet; 10 11abstract class CaretParseletBase { 12 assert( 13 condition: boolean, 14 errorMessage = `Assertion failed in ${this.constructor.name} parselet` 15 ): asserts condition { 16 if (!condition) { 17 throw new CannotParseError( 18 this as unknown as CaretParselet, 19 errorMessage 20 ); 21 } 22 } 23} 24 25/** 26 * A parselet used to handle leaf nodes. For example, variables and numbers ("x" or "42") can be implemented as leaf parselets. 27 * 28 * This parselet type is also used to handle standalone tokens like fractions and parentheses that contain their own sub-expressions 29 * but do not expect to be paired with any particular sibling tokens. 30 */ 31export abstract class CaretLeafParselet extends CaretParseletBase { 32 /** 33 * Parse beginning from here and return the resulting CaretNode. 34 * @param parser The parser instance. 35 * @returns The resulting `CaretNode`. 36 * @throws `CannotParseError` if this parselet cannot parse at the current position. 37 */ 38 abstract parse(parser: CaretParser): CaretNode; 39} 40 41/** 42 * A parselet used to handle prefix operators. For example, negation ("-a") can be implemented as a prefix parselet. 43 */ 44export abstract class CaretPrefixParselet extends CaretParseletBase { 45 /** 46 * Parse beginning from here and return the resulting CaretNode. 47 * @param parser The parser instance. 48 * @returns The resulting `CaretNode`. 49 * @throws `CannotParseError` if this parselet cannot parse at the current position. 50 */ 51 abstract parse(parser: CaretParser): CaretNode; 52} 53 54/** 55 * A parselet used to handle infix operators. For example, addition ("a + b") can be implemented as an infix parselet. 56 */ 57export abstract class CaretInfixParselet extends CaretParseletBase { 58 /** 59 * The associativity of this infix operator: either "left" or "right". For example, 60 * addition is left-associative (a + b + c is parsed as (a + b) + c), while exponentiation 61 * is right-associative (a ^ b ^ c is parsed as a ^ (b ^ c)). 62 */ 63 public abstract readonly associativity: "left" | "right"; 64 65 /** 66 * Parse an infix expression starting with the given left-hand side node. 67 * @param parser The parser instance. 68 * @param left The left-hand side, which has already been parsed into a `CaretNode`. 69 * @returns The resulting `CaretNode`. 70 * @throws `CannotParseError` if this parselet cannot parse at the current position. 71 */ 72 abstract parse(parser: CaretParser, left: CaretNode): CaretNode; 73} 74 75/** 76 * A parselet used to handle postfix operators. For example, factorial ("n!") 77 * can be implemented as a postfix parselet. 78 */ 79export abstract class CaretPostfixParselet extends CaretParseletBase { 80 /** 81 * Parse a postfix expression starting with the given left-hand side node. 82 * @param parser The parser instance. 83 * @param left The left-hand side, which has already been parsed into a `CaretNode`. 84 * @returns The resulting `CaretNode`. 85 * @throws `CannotParseError` if this parselet cannot parse at the current position. 86 */ 87 abstract parse(parser: CaretParser, left: CaretNode): CaretNode; 88} 89 90/** 91 * A parselet that handles adjacent nodes. For example, implicit multiplication ("2x" or "3(x+1)") 92 * can be implemented as a juxtaposition parselet. 93 */ 94export abstract class CaretJuxtapositionParselet extends CaretParseletBase { 95 /** 96 * Determine whether this parselet can combine two adjacent ("juxtaposed") nodes. If true, the `parse()` method will be called. 97 * @param left The left-hand side node. 98 * @param right The right-hand side node. 99 * @returns `true` if this parselet can process these two nodes, `false` otherwise. 100 */ 101 abstract canParse(left: CaretNode, right: CaretNode): boolean; 102 103 /** 104 * Parse two adjacent ("juxtaposed") nodes into a single CaretNode. 105 * @param parser The parser instance. 106 * @param left The left-hand side node. 107 * @param right The right-hand side node. 108 * @returns The resulting `CaretNode`. 109 */ 110 abstract parse( 111 parser: CaretParser, 112 left: CaretNode, 113 right: CaretNode 114 ): CaretNode; 115} 116 117export class CannotParseError extends Error { 118 constructor(public parselet: CaretParselet, message?: string) { 119 super(message); 120 this.name = "CannotParseError"; 121 } 122}