A world-class math input for the web
1import {
2 CannotParseError,
3 CaretLeafParselet,
4 CaretParser,
5 TokensTag,
6} from "@caret-js/core";
7import { DecimalNode } from "../nodes/decimal";
8import { CharToken } from "@caret-js/core";
9
10// This parselet looks for a sequence of `CharToken`s that form a decimal number,
11// meaning it has digits, optionally a single decimal point, and more digits. The digits
12// are optional on either side of the decimal point, but at least one digit must be present.
13
14export class DecimalParselet extends CaretLeafParselet {
15 parse(parser: CaretParser): DecimalNode {
16 let value = "";
17 let hasLeadingMinus = false;
18 let hasDecimalPoint = false;
19 let hasDigits = false;
20 let consumedTokens: CharToken[] = [];
21
22 // Continue consuming CharTokens that are digits or a decimal point
23 const consumedToken = parser.consume();
24 this.assert(
25 consumedToken instanceof CharToken &&
26 ((consumedToken.char >= "0" && consumedToken.char <= "9") ||
27 consumedToken.char === "." ||
28 consumedToken.char === "-"),
29 "Expected CharToken for decimal number"
30 );
31
32 let currentToken = consumedToken;
33 consumedTokens.push(currentToken);
34
35 while (true) {
36 if (currentToken.char === ".") {
37 hasDecimalPoint = true;
38 } else if (currentToken.char >= "0" && currentToken.char <= "9") {
39 hasDigits = true;
40 } else if (currentToken.char === "-") {
41 hasLeadingMinus = true;
42 }
43 value += currentToken.char;
44 const nextToken = parser.peek();
45 if (!nextToken) break;
46 if (
47 !(
48 nextToken instanceof CharToken &&
49 ((nextToken.char >= "0" && nextToken.char <= "9") ||
50 (nextToken.char === "." && !hasDecimalPoint))
51 )
52 ) {
53 break;
54 }
55 currentToken = nextToken;
56 parser.consume();
57 consumedTokens.push(currentToken);
58 }
59
60 if (!hasDigits) {
61 throw new CannotParseError(this, "Decimal must have at least one digit");
62 }
63
64 return DecimalNode.from(value).addTag(new TokensTag(consumedTokens));
65 }
66}