import { CaretNode } from "./node"; import { CaretParser } from "./parser"; export type CaretParselet = | CaretLeafParselet | CaretPrefixParselet | CaretInfixParselet | CaretPostfixParselet | CaretJuxtapositionParselet; abstract class CaretParseletBase { assert( condition: boolean, errorMessage = `Assertion failed in ${this.constructor.name} parselet` ): asserts condition { if (!condition) { throw new CannotParseError( this as unknown as CaretParselet, errorMessage ); } } } /** * A parselet used to handle leaf nodes. For example, variables and numbers ("x" or "42") can be implemented as leaf parselets. * * This parselet type is also used to handle standalone tokens like fractions and parentheses that contain their own sub-expressions * but do not expect to be paired with any particular sibling tokens. */ export abstract class CaretLeafParselet extends CaretParseletBase { /** * Parse beginning from here and return the resulting CaretNode. * @param parser The parser instance. * @returns The resulting `CaretNode`. * @throws `CannotParseError` if this parselet cannot parse at the current position. */ abstract parse(parser: CaretParser): CaretNode; } /** * A parselet used to handle prefix operators. For example, negation ("-a") can be implemented as a prefix parselet. */ export abstract class CaretPrefixParselet extends CaretParseletBase { /** * Parse beginning from here and return the resulting CaretNode. * @param parser The parser instance. * @returns The resulting `CaretNode`. * @throws `CannotParseError` if this parselet cannot parse at the current position. */ abstract parse(parser: CaretParser): CaretNode; } /** * A parselet used to handle infix operators. For example, addition ("a + b") can be implemented as an infix parselet. */ export abstract class CaretInfixParselet extends CaretParseletBase { /** * The associativity of this infix operator: either "left" or "right". For example, * addition is left-associative (a + b + c is parsed as (a + b) + c), while exponentiation * is right-associative (a ^ b ^ c is parsed as a ^ (b ^ c)). */ public abstract readonly associativity: "left" | "right"; /** * Parse an infix expression starting with the given left-hand side node. * @param parser The parser instance. * @param left The left-hand side, which has already been parsed into a `CaretNode`. * @returns The resulting `CaretNode`. * @throws `CannotParseError` if this parselet cannot parse at the current position. */ abstract parse(parser: CaretParser, left: CaretNode): CaretNode; } /** * A parselet used to handle postfix operators. For example, factorial ("n!") * can be implemented as a postfix parselet. */ export abstract class CaretPostfixParselet extends CaretParseletBase { /** * Parse a postfix expression starting with the given left-hand side node. * @param parser The parser instance. * @param left The left-hand side, which has already been parsed into a `CaretNode`. * @returns The resulting `CaretNode`. * @throws `CannotParseError` if this parselet cannot parse at the current position. */ abstract parse(parser: CaretParser, left: CaretNode): CaretNode; } /** * A parselet that handles adjacent nodes. For example, implicit multiplication ("2x" or "3(x+1)") * can be implemented as a juxtaposition parselet. */ export abstract class CaretJuxtapositionParselet extends CaretParseletBase { /** * Determine whether this parselet can combine two adjacent ("juxtaposed") nodes. If true, the `parse()` method will be called. * @param left The left-hand side node. * @param right The right-hand side node. * @returns `true` if this parselet can process these two nodes, `false` otherwise. */ abstract canParse(left: CaretNode, right: CaretNode): boolean; /** * Parse two adjacent ("juxtaposed") nodes into a single CaretNode. * @param parser The parser instance. * @param left The left-hand side node. * @param right The right-hand side node. * @returns The resulting `CaretNode`. */ abstract parse( parser: CaretParser, left: CaretNode, right: CaretNode ): CaretNode; } export class CannotParseError extends Error { constructor(public parselet: CaretParselet, message?: string) { super(message); this.name = "CannotParseError"; } }