import { BadOptionError, BadStemError, MaskedValueError } from '../errors.js'; import { isNumberingSystem } from '../types/skeleton.js'; import { isUnit } from '../types/unit.js'; import { validOptions } from './options.js'; import { parsePrecisionBlueprint } from './parse-precision-blueprint.js'; /** @internal */ export class TokenParser { constructor(onError) { this.skeleton = {}; this.onError = onError; } badOption(stem, opt) { this.onError(new BadOptionError(stem, opt)); } assertEmpty(key) { const prev = this.skeleton[key]; if (prev) this.onError(new MaskedValueError(key, prev)); } parseToken(stem, options) { if (!validOptions(stem, options, this.onError)) return; const option = options[0]; const res = this.skeleton; switch (stem) { // notation case 'compact-short': case 'compact-long': case 'notation-simple': this.assertEmpty('notation'); res.notation = { style: stem }; break; case 'scientific': case 'engineering': { let expDigits = null; let expSign = undefined; for (const opt of options) { switch (opt) { case 'sign-auto': case 'sign-always': case 'sign-never': case 'sign-accounting': case 'sign-accounting-always': case 'sign-except-zero': case 'sign-accounting-except-zero': expSign = opt; break; default: if (/^\+e+$/.test(opt)) expDigits = opt.length - 1; else { this.badOption(stem, opt); } } } this.assertEmpty('notation'); const source = options.join('/'); res.notation = expDigits && expSign ? { style: stem, source, expDigits, expSign } : expDigits ? { style: stem, source, expDigits } : expSign ? { style: stem, source, expSign } : { style: stem, source }; break; } // unit case 'percent': case 'permille': case 'base-unit': this.assertEmpty('unit'); res.unit = { style: stem }; break; case 'currency': if (/^[A-Z]{3}$/.test(option)) { this.assertEmpty('unit'); res.unit = { style: stem, currency: option }; } else this.badOption(stem, option); break; case 'measure-unit': { if (isUnit(option)) { this.assertEmpty('unit'); res.unit = { style: stem, unit: option }; } else this.badOption(stem, option); break; } // unitPer case 'per-measure-unit': { if (isUnit(option)) { this.assertEmpty('unitPer'); res.unitPer = option; } else this.badOption(stem, option); break; } // unitWidth case 'unit-width-narrow': case 'unit-width-short': case 'unit-width-full-name': case 'unit-width-iso-code': case 'unit-width-hidden': this.assertEmpty('unitWidth'); res.unitWidth = stem; break; // precision case 'precision-integer': case 'precision-unlimited': case 'precision-currency-cash': this.assertEmpty('precision'); res.precision = { style: stem }; break; case 'precision-currency-standard': this.assertEmpty('precision'); if (option === 'w') { res.precision = { style: stem, trailingZero: 'stripIfInteger' }; } else { res.precision = { style: stem }; } break; case 'precision-increment': { const increment = Number(option); if (increment > 0) { this.assertEmpty('precision'); res.precision = { style: stem, increment }; } else this.badOption(stem, option); break; } // roundingMode case 'rounding-mode-ceiling': case 'rounding-mode-floor': case 'rounding-mode-down': case 'rounding-mode-up': case 'rounding-mode-half-even': case 'rounding-mode-half-odd': case 'rounding-mode-half-ceiling': case 'rounding-mode-half-floor': case 'rounding-mode-half-down': case 'rounding-mode-half-up': case 'rounding-mode-unnecessary': this.assertEmpty('roundingMode'); res.roundingMode = stem; break; // integerWidth case 'integer-width': { if (/^\+0*$/.test(option)) { this.assertEmpty('integerWidth'); res.integerWidth = { source: option, min: option.length - 1 }; } else { const m = option.match(/^#*(0*)$/); if (m) { this.assertEmpty('integerWidth'); res.integerWidth = { source: option, min: m[1].length, max: m[0].length }; } else this.badOption(stem, option); } break; } // scale case 'scale': { const scale = Number(option); if (scale > 0) { this.assertEmpty('scale'); res.scale = scale; } else this.badOption(stem, option); break; } // group case 'group-off': case 'group-min2': case 'group-auto': case 'group-on-aligned': case 'group-thousands': this.assertEmpty('group'); res.group = stem; break; // numberingSystem case 'latin': this.assertEmpty('numberingSystem'); res.numberingSystem = 'latn'; break; case 'numbering-system': { if (isNumberingSystem(option)) { this.assertEmpty('numberingSystem'); res.numberingSystem = option; } else this.badOption(stem, option); break; } // sign case 'sign-auto': case 'sign-always': case 'sign-never': case 'sign-accounting': case 'sign-accounting-always': case 'sign-except-zero': case 'sign-accounting-except-zero': this.assertEmpty('sign'); res.sign = stem; break; // decimal case 'decimal-auto': case 'decimal-always': this.assertEmpty('decimal'); res.decimal = stem; break; // precision blueprint default: { const precision = parsePrecisionBlueprint(stem, options, this.onError); if (precision) { this.assertEmpty('precision'); res.precision = precision; } else { this.onError(new BadStemError(stem)); } } } } }