UNPKG

parjs

Version:

A parser-combinator library for JavaScript.

157 lines (148 loc) 7.13 kB
"use strict"; /** * @module parjs */ /** */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ascii_1 = require("char-info/ascii"); const numeric_helpers_1 = require("./numeric-helpers"); const result_1 = require("../result"); const defaults_1 = __importDefault(require("lodash/defaults")); const parser_1 = require("../parser"); const defaultFloatOptions = { allowExponent: true, allowSign: true, allowImplicitZero: true, allowFloatingPoint: true }; const msgOneOrMoreDigits = "one or more digits"; const msgExponentSign = "exponent sign (+ or -)"; /* This work is really better done using Parjs itself, but it's wrapped in (mostly) a single parser for efficiency purposes. We want a configurable number parser that can parse floating point numbers in any base, with or without a sign, and with or without an exponent... Here are the rules of this parser. Replace {1,2 3, 4} by the digits allowed with the base, which is configurable. BASIC NUMBER FORMS - parser must be one of: a. 1234 : integer b. 12.3 : floating point, allowed if {allowFloatingPoint}. c. .123 : floating point, implicit whole part. Requires {allowFloatingPoint && allowImplicitZero} d. 123. : floating point, implicit fractional part. Requires {allowFloatingPoint && allowImplicitZero} CONFIGURABLE EXTRAS: a. Sign prefix: (+|-) preceeding the number. Allowed if {allowSign}. b. Exponent suffix: (e|E)(+|-)\d+. Allowed if {allowExponent}. Can be combined with {!allowFloatingPoint}. FAILURES: a. '' - no characters consumed. Parser fails. b. '.' - could be understood as an implicit 0.0, but will not be parsed by this parser. c. '1e+' - with {allowExponent}, this fails after consuming '1e+' because it expected an integer after the + but didn't find one. without that flag, this succeeds and parses just 1. SUCCESSES: a. '1abc' - The parser will just consume and return 1. b. '10e+1' - {allowExponent} can be true even if {allowFloatingPoint} isn't. ISSUES: a. If base >= 15 then the character 'e' is a digit and so {allowExponent} must be false since it cannot be parsed. Otherwise, an error is thrown. b. */ /** * Returns a parser that will parse a single floating point number, in decimal * or scientific form. * @param options Options for parsing the floating-point number. */ function float(options = defaultFloatOptions) { options = defaults_1.default(options, defaultFloatOptions); return new class Float extends parser_1.ParjserBase { constructor() { super(...arguments); this.type = "float"; this.expecting = "expecting a floating-point number"; } _apply(ps) { let { allowSign, allowFloatingPoint, allowImplicitZero, allowExponent } = options; let { position, input } = ps; if (position >= input.length) { ps.kind = result_1.ResultKind.SoftFail; return; } let initPos = position; let sign = 1; let hasSign = false, hasWhole = false, hasFraction = false; if (allowSign) { // try parse a sign sign = numeric_helpers_1.NumericHelpers.parseSign(ps); if (sign === 0) { sign = 1; } else { hasSign = true; } } // after a sign there needs to come an integer part (if any). let prevPos = ps.position; numeric_helpers_1.NumericHelpers.parseDigitsInBase(ps, 10); hasWhole = ps.position !== prevPos; // now if allowFloatingPoint, we try to parse a decimal point. let nextChar = input.charCodeAt(ps.position); prevPos = ps.position; if (!allowImplicitZero && !hasWhole) { // fail because we don't allow ".1", and similar without allowImplicitZero. ps.kind = hasSign ? result_1.ResultKind.HardFail : result_1.ResultKind.SoftFail; ps.reason = msgOneOrMoreDigits; return; } // tslint:disable-next-line:label-position floatingParse: { if (allowFloatingPoint && nextChar === ascii_1.AsciiCodes.decimalPoint) { // skip to the char after the decimal point ps.position++; let prevFractionalPos = ps.position; // parse the fractional part numeric_helpers_1.NumericHelpers.parseDigitsInBase(ps, 10); hasFraction = prevFractionalPos !== ps.position; if (!allowImplicitZero && !hasFraction) { // we encountered something like 212. but allowImplicitZero is false. // that means we need to backtrack to the . character and succeed in parsing the integer. // the remainder is not a valid number. break floatingParse; } // after parseDigits has been invoked, the ps.position is on the next character (which could be e). nextChar = input.charCodeAt(ps.position); prevPos = ps.position; } if (!hasWhole && !hasFraction) { // even if allowImplicitZero is true, we still don't parse '.' as '0.0'. ps.kind = hasSign ? result_1.ResultKind.HardFail : result_1.ResultKind.SoftFail; ps.reason = msgOneOrMoreDigits; return; } // note that if we don't allow floating point, the char that might've been '.' will instead be 'e' or 'E'. // if we do allow floating point, then the previous block would've consumed some characters. if (allowExponent && (nextChar === ascii_1.AsciiCodes.e || nextChar === ascii_1.AsciiCodes.E)) { ps.position++; let expSign = numeric_helpers_1.NumericHelpers.parseSign(ps); if (expSign === 0) { ps.kind = result_1.ResultKind.HardFail; ps.reason = msgExponentSign; return; } let prevFractionalPos = ps.position; numeric_helpers_1.NumericHelpers.parseDigitsInBase(ps, 10); if (ps.position === prevFractionalPos) { // we parsed e+ but we did not parse any digits. ps.kind = result_1.ResultKind.HardFail; ps.reason = msgOneOrMoreDigits; return; } } } ps.kind = result_1.ResultKind.Ok; ps.value = parseFloat(input.substring(initPos, ps.position)); } }(); } exports.float = float; //# sourceMappingURL=float.js.map