UNPKG

messageformat

Version:

Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill

169 lines (168 loc) 5.25 kB
import { MessageSyntaxError } from "../errors.js"; import { parseDeclarations } from "./declarations.js"; import { parseExpression } from "./expression.js"; import { whitespaces } from "./util.js"; import { parseLiteral, parseText, parseVariable } from "./values.js"; export class ParseContext { errors = []; resource; source; constructor(source, opt) { this.resource = opt?.resource ?? false; this.source = source; } onError(type, start, end) { let err; if (type === 'missing-syntax') { const exp = String(end); err = new MessageSyntaxError(type, start, start + exp.length, exp); } else { err = new MessageSyntaxError(type, start, Number(end)); } this.errors.push(err); } } /** * Parse the string syntax representation of a message into * its corresponding {@link CST} representation. */ export function parseCST(source, opt) { const ctx = new ParseContext(source, opt); const pos = whitespaces(source, 0).end; if (source.startsWith('.', pos)) { const { declarations, end } = parseDeclarations(ctx, pos); return source.startsWith('.match', end) ? parseSelectMessage(ctx, end, declarations) : parsePatternMessage(ctx, end, declarations, true); } else { return source.startsWith('{{', pos) ? parsePatternMessage(ctx, pos, [], true) : parsePatternMessage(ctx, 0, [], false); } } function parsePatternMessage(ctx, start, declarations, complex) { const pattern = parsePattern(ctx, start, complex); const pos = whitespaces(ctx.source, pattern.end).end; if (pos < ctx.source.length) { ctx.onError('extra-content', pos, ctx.source.length); } return complex ? { type: 'complex', declarations, pattern, errors: ctx.errors } : { type: 'simple', pattern, errors: ctx.errors }; } function parseSelectMessage(ctx, start, declarations) { let pos = start + 6; // '.match' const match = { start, end: pos, value: '.match' }; let ws = whitespaces(ctx.source, pos); if (!ws.hasWS) ctx.onError('missing-syntax', pos, "' '"); pos = ws.end; const selectors = []; while (ctx.source[pos] === '$') { const sel = parseVariable(ctx, pos); selectors.push(sel); pos = sel.end; ws = whitespaces(ctx.source, pos); if (!ws.hasWS) ctx.onError('missing-syntax', pos, "' '"); pos = ws.end; } if (selectors.length === 0) ctx.onError('empty-token', pos, pos + 1); const variants = []; while (pos < ctx.source.length) { const variant = parseVariant(ctx, pos); if (variant.end > pos) { variants.push(variant); pos = variant.end; } else { pos += 1; } pos = whitespaces(ctx.source, pos).end; } if (pos < ctx.source.length) { ctx.onError('extra-content', pos, ctx.source.length); } return { type: 'select', declarations, match, selectors, variants, errors: ctx.errors }; } function parseVariant(ctx, start) { let pos = start; const keys = []; while (pos < ctx.source.length) { const ws = whitespaces(ctx.source, pos); pos = ws.end; const ch = ctx.source[pos]; if (ch === '{') break; if (pos > start && !ws.hasWS) ctx.onError('missing-syntax', pos, "' '"); let key; if (ch === '*') { key = { type: '*', start: pos, end: pos + 1 }; } else { key = parseLiteral(ctx, pos, true); key.value = key.value.normalize(); } if (key.end === pos) break; // error; reported in pattern.errors keys.push(key); pos = key.end; } const value = parsePattern(ctx, pos, true); return { start, end: value.end, keys, value }; } function parsePattern(ctx, start, quoted) { let pos = start; if (quoted) { if (ctx.source.startsWith('{{', pos)) { pos += 2; } else { ctx.onError('missing-syntax', start, '{{'); return { start, end: start, body: [] }; } } const body = []; loop: while (pos < ctx.source.length) { switch (ctx.source[pos]) { case '{': { const ph = parseExpression(ctx, pos); body.push(ph); pos = ph.end; break; } case '}': break loop; default: { const tx = parseText(ctx, pos); body.push(tx); pos = tx.end; } } } if (quoted) { const q0 = { start, end: start + 2, value: '{{' }; let braces; if (ctx.source.startsWith('}}', pos)) { braces = [q0, { start: pos, end: pos + 2, value: '}}' }]; pos += 2; } else { braces = [q0]; ctx.onError('missing-syntax', pos, '}}'); } return { braces, start, end: pos, body }; } return { start, end: pos, body }; }