UNPKG

messageformat

Version:

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

201 lines (200 loc) 6.43 kB
import { MessageSyntaxError } from "../errors.js"; import { parseNameValue } from "./names.js"; import { whitespaces } from "./util.js"; import { parseLiteral, parseVariable } from "./values.js"; export function parseExpression(ctx, start) { const { source } = ctx; let pos = start + 1; // '{' pos = whitespaces(source, pos).end; const arg = source[pos] === '$' ? parseVariable(ctx, pos) : parseLiteral(ctx, pos, false); if (arg) { pos = arg.end; const ws = whitespaces(source, pos); if (!ws.hasWS && source[pos] !== '}') { ctx.onError('missing-syntax', pos, ' '); } pos = ws.end; } let functionRef; let markup; let junkError; switch (source[pos]) { case ':': functionRef = parseFunctionRefOrMarkup(ctx, pos, 'function'); pos = functionRef.end; break; case '#': case '/': if (arg) ctx.onError('extra-content', arg.start, arg.end); markup = parseFunctionRefOrMarkup(ctx, pos, 'markup'); pos = markup.end; break; case '@': case '}': if (!arg) ctx.onError('empty-token', start, pos); break; default: if (!arg) { const end = pos + 1; functionRef = { type: 'junk', start: pos, end, source: source[pos] }; junkError = new MessageSyntaxError('parse-error', start, end); ctx.errors.push(junkError); } } const attributes = []; let reqWS = Boolean(functionRef || markup); let ws = whitespaces(source, pos); while (source[ws.end] === '@') { if (reqWS && !ws.hasWS) ctx.onError('missing-syntax', pos, ' '); pos = ws.end; const attr = parseAttribute(ctx, pos); attributes.push(attr); pos = attr.end; reqWS = true; ws = whitespaces(source, pos); } pos = ws.end; const open = { start, end: start + 1, value: '{' }; let close; if (pos >= source.length) { ctx.onError('missing-syntax', pos, '}'); } else { if (source[pos] !== '}') { const errStart = pos; while (pos < source.length && source[pos] !== '}') pos += 1; if (functionRef?.type === 'junk') { functionRef.end = pos; functionRef.source = source.substring(functionRef.start, pos); if (junkError) junkError.end = pos; } else { ctx.onError('extra-content', errStart, pos); } } if (source[pos] === '}') { close = { start: pos, end: pos + 1, value: '}' }; pos += 1; } } const braces = close ? [open, close] : [open]; const end = pos; return markup ? { type: 'expression', start, end, braces, markup, attributes } : { type: 'expression', start, end, braces, arg, functionRef, attributes }; } function parseFunctionRefOrMarkup(ctx, start, type) { const { source } = ctx; const id = parseIdentifier(ctx, start + 1); let pos = id.end; const options = []; let close; while (pos < source.length) { let ws = whitespaces(source, pos); const next = source[ws.end]; if (next === '@' || next === '}') break; if (next === '/' && source[start] === '#') { pos = ws.end + 1; close = { start: pos - 1, end: pos, value: '/' }; ws = whitespaces(source, pos); if (ws.hasWS) ctx.onError('extra-content', pos, ws.end); break; } if (!ws.hasWS) ctx.onError('missing-syntax', pos, ' '); pos = ws.end; const opt = parseOption(ctx, pos); if (opt.end === pos) break; // error options.push(opt); pos = opt.end; } if (type === 'function') { const open = { start, end: start + 1, value: ':' }; return { type, start, end: pos, open, name: id.parts, options }; } else { const open = { start, end: start + 1, value: source[start] }; return { type, start, end: pos, open, name: id.parts, options, close }; } } function parseOption(ctx, start) { const id = parseIdentifier(ctx, start); let pos = whitespaces(ctx.source, id.end).end; let equals; if (ctx.source[pos] === '=') { equals = { start: pos, end: pos + 1, value: '=' }; pos += 1; } else { ctx.onError('missing-syntax', pos, '='); } pos = whitespaces(ctx.source, pos).end; const value = ctx.source[pos] === '$' ? parseVariable(ctx, pos) : parseLiteral(ctx, pos, true); return { start, end: value.end, name: id.parts, equals, value }; } function parseIdentifier(ctx, start) { const { source } = ctx; const name0 = parseNameValue(source, start); if (!name0) { ctx.onError('empty-token', start, start + 1); return { parts: [{ start, end: start, value: '' }], end: start }; } let pos = name0.end; const id0 = { start, end: pos, value: name0.value }; if (source[pos] !== ':') return { parts: [id0], end: pos }; const sep = { start: pos, end: pos + 1, value: ':' }; pos += 1; const name1 = parseNameValue(source, pos); if (name1) { const id1 = { start: pos, end: name1.end, value: name1.value }; return { parts: [id0, sep, id1], end: name1.end }; } else { ctx.onError('empty-token', pos, pos + 1); return { parts: [id0, sep], end: pos }; } } function parseAttribute(ctx, start) { const { source } = ctx; const id = parseIdentifier(ctx, start + 1); let pos = id.end; const ws = whitespaces(source, pos); let equals; let value; if (source[ws.end] === '=') { pos = ws.end + 1; equals = { start: pos - 1, end: pos, value: '=' }; pos = whitespaces(source, pos).end; value = parseLiteral(ctx, pos, true); pos = value.end; } return { start, end: pos, open: { start, end: start + 1, value: '@' }, name: id.parts, equals, value }; }