UNPKG

messageformat

Version:

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

161 lines (160 loc) 5.41 kB
import { MessageSyntaxError } from "../errors.js"; /** * Shared symbol used as a key on message data model nodes * to reference their CST source. * * Only set on message data model nodes when parsed by {@link messageFromCST}. */ export const cstKey = Symbol.for('CST'); /** * Convert a CST message structure into its data model representation. * * In the returned {@link Model.Message}, * all nodes include a reference to their source {@link CST} node * as a {@link cstKey} symbol-keyed property. */ export function messageFromCST(msg) { for (const error of msg.errors) throw error; const declarations = msg.declarations ? msg.declarations.map(asDeclaration) : []; if (msg.type === 'select') { return { type: 'select', declarations, selectors: msg.selectors.map(sel => asValue(sel)), variants: msg.variants.map(variant => ({ keys: variant.keys.map(key => key.type === '*' ? { type: '*', [cstKey]: key } : asValue(key)), value: asPattern(variant.value), [cstKey]: variant })), [cstKey]: msg }; } else { return { type: 'message', declarations, pattern: asPattern(msg.pattern), [cstKey]: msg }; } } function asDeclaration(decl) { switch (decl.type) { case 'input': { const value = asExpression(decl.value, false); if (value.arg?.type !== 'variable') { const { start, end } = decl.value; throw new MessageSyntaxError('parse-error', start, end); } return { type: 'input', name: value.arg.name, value: value, [cstKey]: decl }; } case 'local': return { type: 'local', name: asValue(decl.target).name, value: asExpression(decl.value, false), [cstKey]: decl }; default: throw new MessageSyntaxError('parse-error', decl.start, decl.end); } } const asPattern = (pattern) => pattern.body.map(el => el.type === 'text' ? el.value : asExpression(el, true)); function asExpression(exp, allowMarkup) { if (exp.type === 'expression') { if (allowMarkup && exp.markup) { const cm = exp.markup; const name = asName(cm.name); const kind = cm.open.value === '/' ? 'close' : cm.close ? 'standalone' : 'open'; const markup = { type: 'markup', kind, name }; if (cm.options.length) markup.options = asOptions(cm.options); if (exp.attributes.length) { markup.attributes = asAttributes(exp.attributes); } markup[cstKey] = exp; return markup; } const arg = exp.arg ? asValue(exp.arg) : undefined; let functionRef; const ca = exp.functionRef; if (ca) { if (ca.type === 'function') { functionRef = { type: 'function', name: asName(ca.name) }; if (ca.options.length) functionRef.options = asOptions(ca.options); } else { throw new MessageSyntaxError('parse-error', exp.start, exp.end); } } let expression = arg ? { type: 'expression', arg } : undefined; if (functionRef) { functionRef[cstKey] = ca; if (expression) expression.functionRef = functionRef; else expression = { type: 'expression', functionRef: functionRef }; } if (expression) { if (exp.attributes.length) { expression.attributes = asAttributes(exp.attributes); } expression[cstKey] = exp; return expression; } } throw new MessageSyntaxError('parse-error', exp.start, exp.end); } function asOptions(options) { const map = new Map(); for (const opt of options) { const name = asName(opt.name); if (map.has(name)) { throw new MessageSyntaxError('duplicate-option-name', opt.start, opt.end); } map.set(name, asValue(opt.value)); } return map; } function asAttributes(attributes) { const map = new Map(); for (const attr of attributes) { const name = asName(attr.name); if (map.has(name)) { throw new MessageSyntaxError('duplicate-attribute', attr.start, attr.end); } map.set(name, attr.value ? asValue(attr.value) : true); } return map; } function asName(id) { switch (id.length) { case 1: return id[0].value; case 3: return `${id[0].value}:${id[2].value}`; default: throw new MessageSyntaxError('parse-error', id[0]?.start ?? -1, id.at(-1)?.end ?? -1); } } function asValue(value) { switch (value.type) { case 'literal': return { type: 'literal', value: value.value, [cstKey]: value }; case 'variable': return { type: 'variable', name: value.name, [cstKey]: value }; default: throw new MessageSyntaxError('parse-error', value.start, value.end); } }