import { CaretJuxtapositionParselet, CaretNode, CaretParser, IsErrorNodeTag, NodeTag, } from "@caret-js/core"; import { MultiplyNode } from "../nodes/multiply"; import { MathExpressionTag } from "../tags/mathExpression"; /** * There are some relatively complicated rules for deciding when adjacent multiplication is/isn't allowed. * The rules are implemented using tags so that custom node types can specify their own behavior. * * There are four tags that nodes can use to control whether or not they appear in adjacent multiplication: * 1. `MathExpressionTag`: This tag indicates that a node represents a math expression (as opposed to, for example, * a math statement like an equation, or a non-math node). Nodes with this tag are enrolled in the default adjacent * multiplication behavior, which is to accept math expression nodes or error nodes on both sides only. * 2. `IsErrorNodeTag`: This tag indicates that a node is an error node (like MissingNode), which is allowed to appear * in adjacent multiplication by default so that as much of the expression as possible can be parsed and errors can be * contained. * 3. `CanBeAdjacentMultLeftSideTag`: This tag can be added to nodes to customize whether they can appear on the left side. * The tag can either contain a boolean value (true/false) or a function that takes the left and right nodes * and returns a boolean. (Both nodes must return true for adjacent multiplication to be allowed.) * 4. `CanBeAdjacentMultRightSideTag`: This tag can be added to nodes to customize whether they can appear on the right side. * The tag can either contain a boolean value (true/false) or a function that takes the left and right nodes * and returns a boolean. (Both nodes must return true for adjacent multiplication to be allowed.) * * A node that does not have any of these tags will never appear in adjacent multiplication. */ export class AdjacentMultiplicationParselet extends CaretJuxtapositionParselet { canParse(left: CaretNode, right: CaretNode): boolean { return isLeftAllowed(left, right) && isRightAllowed(left, right); } parse(parser: CaretParser, left: CaretNode, right: CaretNode): CaretNode { return MultiplyNode.from([left, right]).addTag( new MultiplicationStyleTag("implicit") ); } } function isLeftAllowed(left: CaretNode, right: CaretNode): boolean { if ( left instanceof MultiplyNode && left.getTag(MultiplicationStyleTag)?.style === "implicit" && left.factors.length > 0 ) { return isLeftAllowed(left.factors.at(-1)!, right); } const leftTag = left.getTag(CanBeAdjacentMultLeftSideTag); if (leftTag) { if (typeof leftTag.condition === "boolean") { return leftTag.condition; } else { return leftTag.condition(left, right); } } return left.hasTag(MathExpressionTag) || left.hasTag(IsErrorNodeTag); } function isRightAllowed(left: CaretNode, right: CaretNode): boolean { if ( right instanceof MultiplyNode && right.getTag(MultiplicationStyleTag)?.style === "implicit" && right.factors.length > 0 ) { return isRightAllowed(left, right.factors.at(0)!); } const rightTag = right.getTag(CanBeAdjacentMultRightSideTag); if (rightTag) { if (typeof rightTag.condition === "boolean") { return rightTag.condition; } else { return rightTag.condition(left, right); } } return right.hasTag(MathExpressionTag) || right.hasTag(IsErrorNodeTag); } export class CanBeAdjacentMultLeftSideTag extends NodeTag< false, N > { public readonly allowMultiple = false; constructor( public readonly condition: | boolean | ((left: N, right: CaretNode) => boolean) ) { super(); } equals(other: this): boolean { return ( other instanceof CanBeAdjacentMultLeftSideTag && other.condition === this.condition ); } } export class CanBeAdjacentMultRightSideTag extends NodeTag< false, N > { public readonly allowMultiple = false; constructor( public readonly condition: | boolean | ((left: CaretNode, right: N) => boolean) ) { super(); } equals(other: this): boolean { return ( other instanceof CanBeAdjacentMultRightSideTag && other.condition === this.condition ); } } export class MultiplicationStyleTag extends NodeTag { public readonly allowMultiple = false; constructor(public readonly style: "implicit" | "dot" | "cross") { super(); } equals(other: this): boolean { return ( other instanceof MultiplicationStyleTag && other.style === this.style ); } }