messageformat
Advanced tools
Comparing version 4.0.0-7 to 4.0.0-8
import type { ParseContext } from './parse-cst.js'; | ||
import type * as CST from './types.js'; | ||
export declare function parseDeclarations(ctx: ParseContext): { | ||
export declare function parseDeclarations(ctx: ParseContext, start: number): { | ||
declarations: CST.Declaration[]; | ||
end: number; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseDeclarations = void 0; | ||
const names_js_1 = require("./names.js"); | ||
const expression_js_1 = require("./expression.js"); | ||
const util_js_1 = require("./util.js"); | ||
const values_js_1 = require("./values.js"); | ||
function parseDeclarations(ctx) { | ||
function parseDeclarations(ctx, start) { | ||
const { source } = ctx; | ||
let pos = (0, util_js_1.whitespaces)(source, 0); | ||
let pos = start; | ||
const declarations = []; | ||
loop: while (source[pos] === '.') { | ||
const keyword = (0, names_js_1.parseNameValue)(source, pos + 1); | ||
const keyword = source.substr(pos, 6); | ||
let decl; | ||
switch (keyword) { | ||
case '': | ||
case 'match': | ||
case '.match': | ||
break loop; | ||
case 'input': | ||
case '.input': | ||
decl = parseInputDeclaration(ctx, pos); | ||
break; | ||
case 'local': | ||
case '.local': | ||
decl = parseLocalDeclaration(ctx, pos); | ||
break; | ||
default: | ||
decl = parseReservedStatement(ctx, pos, '.' + keyword); | ||
decl = parseDeclarationJunk(ctx, pos); | ||
} | ||
declarations.push(decl); | ||
pos = decl.end; | ||
pos += (0, util_js_1.whitespaces)(source, pos); | ||
pos = (0, util_js_1.whitespaces)(source, decl.end).end; | ||
} | ||
@@ -39,3 +36,3 @@ return { declarations, end: pos }; | ||
const keyword = { start, end: pos, value: '.input' }; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
pos = (0, util_js_1.whitespaces)(ctx.source, pos).end; | ||
const value = parseDeclarationValue(ctx, pos); | ||
@@ -54,4 +51,4 @@ if (value.type === 'expression') { | ||
const ws = (0, util_js_1.whitespaces)(source, pos); | ||
pos += ws; | ||
if (ws === 0) | ||
pos = ws.end; | ||
if (!ws.hasWS) | ||
ctx.onError('missing-syntax', pos, ' '); | ||
@@ -75,3 +72,3 @@ let target; | ||
} | ||
pos += (0, util_js_1.whitespaces)(source, pos); | ||
pos = (0, util_js_1.whitespaces)(source, pos).end; | ||
let equals; | ||
@@ -85,3 +82,3 @@ if (source[pos] === '=') { | ||
} | ||
pos += (0, util_js_1.whitespaces)(source, pos); | ||
pos = (0, util_js_1.whitespaces)(source, pos).end; | ||
const value = parseDeclarationValue(ctx, pos); | ||
@@ -98,36 +95,21 @@ return { | ||
} | ||
function parseReservedStatement(ctx, start, keyword) { | ||
let pos = start + keyword.length; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
const body = (0, expression_js_1.parseReservedBody)(ctx, pos); | ||
let end = body.end; | ||
pos = end + (0, util_js_1.whitespaces)(ctx.source, end); | ||
const values = []; | ||
while (ctx.source[pos] === '{') { | ||
if (ctx.source.startsWith('{{', pos)) | ||
break; | ||
const value = (0, expression_js_1.parseExpression)(ctx, pos); | ||
values.push(value); | ||
end = value.end; | ||
pos = end + (0, util_js_1.whitespaces)(ctx.source, end); | ||
} | ||
if (values.length === 0) | ||
ctx.onError('missing-syntax', end, '{'); | ||
return { | ||
type: 'reserved-statement', | ||
start, | ||
end, | ||
keyword: { start, end: keyword.length, value: keyword }, | ||
body, | ||
values | ||
}; | ||
function parseDeclarationValue(ctx, start) { | ||
return ctx.source[start] === '{' | ||
? (0, expression_js_1.parseExpression)(ctx, start) | ||
: parseDeclarationJunk(ctx, start); | ||
} | ||
function parseDeclarationValue(ctx, start) { | ||
function parseDeclarationJunk(ctx, start) { | ||
const { source } = ctx; | ||
if (source[start] === '{') | ||
return (0, expression_js_1.parseExpression)(ctx, start); | ||
const junkEndOffset = source.substring(start).search(/\.[a-z]|{{/); | ||
const end = junkEndOffset === -1 ? source.length : start + junkEndOffset; | ||
const junkEndOffset = source.substring(start + 1).search(/\.[a-z]|{{/); | ||
let end; | ||
if (junkEndOffset === -1) { | ||
end = source.length; | ||
} | ||
else { | ||
end = start + 1 + junkEndOffset; | ||
while (/\s/.test(source[end - 1])) | ||
end -= 1; | ||
} | ||
ctx.onError('missing-syntax', start, '{'); | ||
return { type: 'junk', start, end, source: source.substring(start, end) }; | ||
} |
import type { ParseContext } from './parse-cst.js'; | ||
import type * as CST from './types.js'; | ||
export declare function parseExpression(ctx: ParseContext, start: number): CST.Expression; | ||
export declare function parseReservedBody(ctx: ParseContext, start: number): CST.Syntax<string>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseReservedBody = exports.parseExpression = void 0; | ||
exports.parseExpression = void 0; | ||
const errors_js_1 = require("../errors.js"); | ||
@@ -11,3 +11,3 @@ const names_js_1 = require("./names.js"); | ||
let pos = start + 1; // '{' | ||
pos += (0, util_js_1.whitespaces)(source, pos); | ||
pos = (0, util_js_1.whitespaces)(source, pos).end; | ||
const arg = source[pos] === '$' | ||
@@ -19,8 +19,8 @@ ? (0, values_js_1.parseVariable)(ctx, pos) | ||
const ws = (0, util_js_1.whitespaces)(source, pos); | ||
if (ws === 0 && source[pos] !== '}') { | ||
if (!ws.hasWS && source[pos] !== '}') { | ||
ctx.onError('missing-syntax', pos, ' '); | ||
} | ||
pos += ws; | ||
pos = ws.end; | ||
} | ||
let annotation; | ||
let functionRef; | ||
let markup; | ||
@@ -30,4 +30,4 @@ let junkError; | ||
case ':': | ||
annotation = parseFunctionRefOrMarkup(ctx, pos, 'function'); | ||
pos = annotation.end; | ||
functionRef = parseFunctionRefOrMarkup(ctx, pos, 'function'); | ||
pos = functionRef.end; | ||
break; | ||
@@ -41,15 +41,2 @@ case '#': | ||
break; | ||
case '!': | ||
case '%': | ||
case '^': | ||
case '&': | ||
case '*': | ||
case '+': | ||
case '<': | ||
case '>': | ||
case '?': | ||
case '~': | ||
annotation = parseReservedAnnotation(ctx, pos); | ||
pos = annotation.end; | ||
break; | ||
case '@': | ||
@@ -63,3 +50,3 @@ case '}': | ||
const end = pos + 1; | ||
annotation = { type: 'junk', start: pos, end, source: source[pos] }; | ||
functionRef = { type: 'junk', start: pos, end, source: source[pos] }; | ||
junkError = new errors_js_1.MessageSyntaxError('parse-error', start, end); | ||
@@ -70,8 +57,8 @@ ctx.errors.push(junkError); | ||
const attributes = []; | ||
let reqWS = Boolean(annotation || markup); | ||
let reqWS = Boolean(functionRef || markup); | ||
let ws = (0, util_js_1.whitespaces)(source, pos); | ||
while (source[pos + ws] === '@') { | ||
if (reqWS && ws === 0) | ||
while (source[ws.end] === '@') { | ||
if (reqWS && !ws.hasWS) | ||
ctx.onError('missing-syntax', pos, ' '); | ||
pos += ws; | ||
pos = ws.end; | ||
const attr = parseAttribute(ctx, pos); | ||
@@ -83,3 +70,3 @@ attributes.push(attr); | ||
} | ||
pos += ws; | ||
pos = ws.end; | ||
const open = { start, end: start + 1, value: '{' }; | ||
@@ -95,5 +82,5 @@ let close; | ||
pos += 1; | ||
if (annotation?.type === 'junk') { | ||
annotation.end = pos; | ||
annotation.source = source.substring(annotation.start, pos); | ||
if (functionRef?.type === 'junk') { | ||
functionRef.end = pos; | ||
functionRef.source = source.substring(functionRef.start, pos); | ||
if (junkError) | ||
@@ -115,3 +102,11 @@ junkError.end = pos; | ||
? { type: 'expression', start, end, braces, markup, attributes } | ||
: { type: 'expression', start, end, braces, arg, annotation, attributes }; | ||
: { | ||
type: 'expression', | ||
start, | ||
end, | ||
braces, | ||
arg, | ||
functionRef, | ||
attributes | ||
}; | ||
} | ||
@@ -127,16 +122,16 @@ exports.parseExpression = parseExpression; | ||
let ws = (0, util_js_1.whitespaces)(source, pos); | ||
const next = source[pos + ws]; | ||
const next = source[ws.end]; | ||
if (next === '@' || next === '}') | ||
break; | ||
if (next === '/' && source[start] === '#') { | ||
pos += ws + 1; | ||
pos = ws.end + 1; | ||
close = { start: pos - 1, end: pos, value: '/' }; | ||
ws = (0, util_js_1.whitespaces)(source, pos); | ||
if (ws > 0) | ||
ctx.onError('extra-content', pos, pos + ws); | ||
if (ws.hasWS) | ||
ctx.onError('extra-content', pos, ws.end); | ||
break; | ||
} | ||
if (ws === 0) | ||
if (!ws.hasWS) | ||
ctx.onError('missing-syntax', pos, ' '); | ||
pos += ws; | ||
pos = ws.end; | ||
const opt = parseOption(ctx, pos); | ||
@@ -159,4 +154,3 @@ if (opt.end === pos) | ||
const id = parseIdentifier(ctx, start); | ||
let pos = id.end; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
let pos = (0, util_js_1.whitespaces)(ctx.source, id.end).end; | ||
let equals; | ||
@@ -170,3 +164,3 @@ if (ctx.source[pos] === '=') { | ||
} | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
pos = (0, util_js_1.whitespaces)(ctx.source, pos).end; | ||
const value = ctx.source[pos] === '$' | ||
@@ -179,9 +173,9 @@ ? (0, values_js_1.parseVariable)(ctx, pos) | ||
const { source } = ctx; | ||
const str0 = (0, names_js_1.parseNameValue)(source, start); | ||
if (!str0) { | ||
const name0 = (0, names_js_1.parseNameValue)(source, start); | ||
if (!name0) { | ||
ctx.onError('empty-token', start, start + 1); | ||
return { parts: [{ start, end: start, value: '' }], end: start }; | ||
} | ||
let pos = start + str0.length; | ||
const id0 = { start, end: pos, value: str0 }; | ||
let pos = name0.end; | ||
const id0 = { start, end: pos, value: name0.value }; | ||
if (source[pos] !== ':') | ||
@@ -191,7 +185,6 @@ return { parts: [id0], end: pos }; | ||
pos += 1; | ||
const str1 = (0, names_js_1.parseNameValue)(source, pos); | ||
if (str1) { | ||
const end = pos + str1.length; | ||
const id1 = { start: pos, end, value: str1 }; | ||
return { parts: [id0, sep, id1], end }; | ||
const name1 = (0, names_js_1.parseNameValue)(source, pos); | ||
if (name1) { | ||
const id1 = { start: pos, end: name1.end, value: name1.value }; | ||
return { parts: [id0, sep, id1], end: name1.end }; | ||
} | ||
@@ -203,60 +196,2 @@ else { | ||
} | ||
function parseReservedAnnotation(ctx, start) { | ||
const open = { | ||
start, | ||
end: start + 1, | ||
value: ctx.source[start] | ||
}; | ||
const source = parseReservedBody(ctx, start + 1); // skip sigil | ||
return { | ||
type: 'reserved-annotation', | ||
start, | ||
end: source.end, | ||
open, | ||
source | ||
}; | ||
} | ||
function parseReservedBody(ctx, start) { | ||
let pos = start; | ||
loop: while (pos < ctx.source.length) { | ||
const ch = ctx.source[pos]; | ||
switch (ch) { | ||
case '\\': { | ||
switch (ctx.source[pos + 1]) { | ||
case '\\': | ||
case '{': | ||
case '|': | ||
case '}': | ||
break; | ||
default: | ||
ctx.onError('bad-escape', pos, pos + 2); | ||
} | ||
pos += 2; | ||
break; | ||
} | ||
case '|': | ||
pos = (0, values_js_1.parseQuotedLiteral)(ctx, pos).end; | ||
break; | ||
case '@': | ||
case '{': | ||
case '}': | ||
break loop; | ||
default: { | ||
const cc = ch.charCodeAt(0); | ||
if (cc >= 0xd800 && cc < 0xe000) { | ||
// surrogates are invalid here | ||
ctx.onError('parse-error', pos, pos + 1); | ||
} | ||
pos += 1; | ||
} | ||
} | ||
} | ||
let prev = ctx.source[pos - 1]; | ||
while (pos > start && util_js_1.whitespaceChars.includes(prev)) { | ||
pos -= 1; | ||
prev = ctx.source[pos - 1]; | ||
} | ||
return { start, end: pos, value: ctx.source.substring(start, pos) }; | ||
} | ||
exports.parseReservedBody = parseReservedBody; | ||
function parseAttribute(ctx, start) { | ||
@@ -269,10 +204,7 @@ const { source } = ctx; | ||
let value; | ||
if (source[pos + ws] === '=') { | ||
pos += ws + 1; | ||
if (source[ws.end] === '=') { | ||
pos = ws.end + 1; | ||
equals = { start: pos - 1, end: pos, value: '=' }; | ||
pos += (0, util_js_1.whitespaces)(source, pos); | ||
value = | ||
source[pos] === '$' | ||
? (0, values_js_1.parseVariable)(ctx, pos) | ||
: (0, values_js_1.parseLiteral)(ctx, pos, true); | ||
pos = (0, util_js_1.whitespaces)(source, pos).end; | ||
value = (0, values_js_1.parseLiteral)(ctx, pos, true); | ||
pos = value.end; | ||
@@ -279,0 +211,0 @@ } |
@@ -1,3 +0,6 @@ | ||
export declare function parseNameValue(src: string, start: number): string; | ||
export declare function parseNameValue(src: string, start: number): { | ||
value: string; | ||
end: number; | ||
} | null; | ||
export declare function isValidUnquotedLiteral(str: string): boolean; | ||
export declare function parseUnquotedLiteralValue(source: string, start: number): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseUnquotedLiteralValue = exports.isValidUnquotedLiteral = exports.parseNameValue = void 0; | ||
const bidiChars = new Set([ | ||
0x061c, // ALM | ||
0x200e, // LRM | ||
0x200f, // RLM | ||
0x2066, // LRI | ||
0x2067, // RLI | ||
0x2068, // FSI | ||
0x2069 // PDI | ||
]); | ||
const isNameStartCode = (cc) => (cc >= 0x41 && cc <= 0x5a) || // A-Z | ||
@@ -30,8 +39,19 @@ cc === 0x5f || // _ | ||
function parseNameValue(src, start) { | ||
if (!isNameStartCode(src.charCodeAt(start))) | ||
return ''; | ||
let pos = start + 1; | ||
while (isNameCharCode(src.charCodeAt(pos))) | ||
let pos = start; | ||
let nameStart = start; | ||
let cc = src.charCodeAt(start); | ||
if (bidiChars.has(cc)) { | ||
pos += 1; | ||
return src.substring(start, pos); | ||
nameStart += 1; | ||
cc = src.charCodeAt(pos); | ||
} | ||
if (!isNameStartCode(cc)) | ||
return null; | ||
cc = src.charCodeAt(++pos); | ||
while (isNameCharCode(cc)) | ||
cc = src.charCodeAt(++pos); | ||
const name = src.substring(nameStart, pos); | ||
if (bidiChars.has(cc)) | ||
pos += 1; | ||
return { value: name, end: pos }; | ||
} | ||
@@ -38,0 +58,0 @@ exports.parseNameValue = parseNameValue; |
@@ -38,10 +38,13 @@ "use strict"; | ||
const ctx = new ParseContext(source, opt); | ||
if (source.startsWith('.')) { | ||
const { declarations, end: pos } = (0, declarations_js_1.parseDeclarations)(ctx); | ||
return source.startsWith('.match', pos) | ||
? parseSelectMessage(ctx, pos, declarations) | ||
: parsePatternMessage(ctx, pos, declarations, true); | ||
const pos = (0, util_js_1.whitespaces)(source, 0).end; | ||
if (source.startsWith('.', pos)) { | ||
const { declarations, end } = (0, declarations_js_1.parseDeclarations)(ctx, pos); | ||
return source.startsWith('.match', end) | ||
? parseSelectMessage(ctx, end, declarations) | ||
: parsePatternMessage(ctx, end, declarations, true); | ||
} | ||
else { | ||
return parsePatternMessage(ctx, 0, [], source.startsWith('{{')); | ||
return source.startsWith('{{', pos) | ||
? parsePatternMessage(ctx, pos, [], true) | ||
: parsePatternMessage(ctx, 0, [], false); | ||
} | ||
@@ -52,4 +55,3 @@ } | ||
const pattern = parsePattern(ctx, start, complex); | ||
let pos = pattern.end; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
const pos = (0, util_js_1.whitespaces)(ctx.source, pattern.end).end; | ||
if (pos < ctx.source.length) { | ||
@@ -65,24 +67,29 @@ ctx.onError('extra-content', pos, ctx.source.length); | ||
const match = { start, end: pos, value: '.match' }; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
let ws = (0, util_js_1.whitespaces)(ctx.source, pos); | ||
if (!ws.hasWS) | ||
ctx.onError('missing-syntax', pos, "' '"); | ||
pos = ws.end; | ||
const selectors = []; | ||
while (ctx.source[pos] === '{') { | ||
const sel = (0, expression_js_1.parseExpression)(ctx, pos); | ||
const body = sel.markup ?? sel.annotation; | ||
if (body && body.type !== 'function') { | ||
ctx.onError('bad-selector', body.start, body.end); | ||
} | ||
while (ctx.source[pos] === '$') { | ||
const sel = (0, values_js_1.parseVariable)(ctx, pos); | ||
selectors.push(sel); | ||
pos = sel.end; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
ws = (0, util_js_1.whitespaces)(ctx.source, pos); | ||
if (!ws.hasWS) | ||
ctx.onError('missing-syntax', pos, "' '"); | ||
pos = ws.end; | ||
} | ||
if (selectors.length === 0) { | ||
if (selectors.length === 0) | ||
ctx.onError('empty-token', pos, pos + 1); | ||
} | ||
const variants = []; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
while (pos < ctx.source.length) { | ||
const variant = parseVariant(ctx, pos); | ||
variants.push(variant); | ||
pos = variant.end; | ||
pos += (0, util_js_1.whitespaces)(ctx.source, pos); | ||
if (variant.end > pos) { | ||
variants.push(variant); | ||
pos = variant.end; | ||
} | ||
else { | ||
pos += 1; | ||
} | ||
pos = (0, util_js_1.whitespaces)(ctx.source, pos).end; | ||
} | ||
@@ -106,7 +113,7 @@ if (pos < ctx.source.length) { | ||
const ws = (0, util_js_1.whitespaces)(ctx.source, pos); | ||
pos += ws; | ||
pos = ws.end; | ||
const ch = ctx.source[pos]; | ||
if (ch === '{') | ||
break; | ||
if (pos > start && ws === 0) | ||
if (pos > start && !ws.hasWS) | ||
ctx.onError('missing-syntax', pos, "' '"); | ||
@@ -113,0 +120,0 @@ const key = ch === '*' |
@@ -14,5 +14,5 @@ "use strict"; | ||
for (const decl of cst.declarations) { | ||
const kw = decl.keyword.value; | ||
switch (decl.type) { | ||
case 'input': { | ||
const kw = decl.keyword.value; | ||
const val = stringifyExpression(decl.value); | ||
@@ -23,2 +23,3 @@ str += `${kw} ${val}\n`; | ||
case 'local': { | ||
const kw = decl.keyword.value; | ||
const tgt = stringifyValue(decl.target); | ||
@@ -30,11 +31,5 @@ const eq = decl.equals?.value ?? '='; | ||
} | ||
case 'reserved-statement': { | ||
str += kw; | ||
if (decl.body.value) | ||
str += ' ' + decl.body.value; | ||
for (const exp of decl.values) | ||
str += ' ' + stringifyExpression(exp); | ||
str += '\n'; | ||
case 'junk': | ||
str += decl.source + '\n'; | ||
break; | ||
} | ||
} | ||
@@ -46,3 +41,3 @@ } | ||
for (const sel of cst.selectors) | ||
str += ' ' + stringifyExpression(sel); | ||
str += ' ' + stringifyValue(sel); | ||
for (const { keys, value } of cst.variants) { | ||
@@ -76,3 +71,3 @@ str += '\n'; | ||
return exp?.source ?? ''; | ||
const { braces, arg, annotation, markup, attributes } = exp; | ||
const { braces, arg, functionRef, markup, attributes } = exp; | ||
let str = braces[0]?.value ?? '{'; | ||
@@ -91,16 +86,13 @@ if (markup) { | ||
str += stringifyValue(arg); | ||
if (annotation) | ||
if (functionRef) | ||
str += ' '; | ||
} | ||
switch (annotation?.type) { | ||
switch (functionRef?.type) { | ||
case 'function': | ||
str += annotation.open.value + stringifyIdentifier(annotation.name); | ||
for (const opt of annotation.options) | ||
str += functionRef.open.value + stringifyIdentifier(functionRef.name); | ||
for (const opt of functionRef.options) | ||
str += stringifyOption(opt); | ||
break; | ||
case 'reserved-annotation': | ||
str += annotation.open.value + annotation.source.value; | ||
break; | ||
case 'junk': | ||
str += annotation.source; | ||
str += functionRef.source; | ||
break; | ||
@@ -107,0 +99,0 @@ } |
@@ -23,3 +23,3 @@ import type { MessageSyntaxError } from '../errors.js'; | ||
match: Syntax<'.match'>; | ||
selectors: Expression[]; | ||
selectors: VariableRef[]; | ||
variants: Variant[]; | ||
@@ -29,3 +29,3 @@ errors: MessageSyntaxError[]; | ||
/** @beta */ | ||
export type Declaration = InputDeclaration | LocalDeclaration | ReservedStatement; | ||
export type Declaration = InputDeclaration | LocalDeclaration | Junk; | ||
/** @beta */ | ||
@@ -50,11 +50,2 @@ export interface InputDeclaration { | ||
/** @beta */ | ||
export interface ReservedStatement { | ||
type: 'reserved-statement'; | ||
start: number; | ||
end: number; | ||
keyword: Syntax<string>; | ||
body: Syntax<string>; | ||
values: Expression[]; | ||
} | ||
/** @beta */ | ||
export interface Variant { | ||
@@ -94,3 +85,3 @@ start: number; | ||
arg?: Literal | VariableRef; | ||
annotation?: FunctionRef | ReservedAnnotation | Junk; | ||
functionRef?: FunctionRef | Junk; | ||
markup?: Markup; | ||
@@ -135,10 +126,2 @@ attributes: Attribute[]; | ||
/** @beta */ | ||
export interface ReservedAnnotation { | ||
type: 'reserved-annotation'; | ||
open: Syntax<'!' | '%' | '^' | '&' | '*' | '+' | '<' | '>' | '?' | '~'>; | ||
source: Syntax<string>; | ||
start: number; | ||
end: number; | ||
} | ||
/** @beta */ | ||
export interface Markup { | ||
@@ -170,3 +153,3 @@ type: 'markup'; | ||
equals?: Syntax<'='>; | ||
value?: Literal | VariableRef; | ||
value?: Literal; | ||
} | ||
@@ -173,0 +156,0 @@ /** @beta */ |
@@ -1,2 +0,4 @@ | ||
export declare const whitespaceChars: string[]; | ||
export declare function whitespaces(src: string, start: number): number; | ||
export declare function whitespaces(src: string, start: number): { | ||
hasWS: boolean; | ||
end: number; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.whitespaces = exports.whitespaceChars = void 0; | ||
exports.whitespaceChars = ['\t', '\n', '\r', ' ', '\u3000']; | ||
exports.whitespaces = void 0; | ||
const bidiChars = new Set('\u061C\u200E\u200F\u2066\u2067\u2068\u2069'); | ||
const whitespaceChars = new Set('\t\n\r \u3000'); | ||
function whitespaces(src, start) { | ||
let length = 0; | ||
let ch = src[start]; | ||
while (exports.whitespaceChars.includes(ch)) { | ||
length += 1; | ||
ch = src[start + length]; | ||
let hasWS = false; | ||
let pos = start; | ||
let ch = src[pos]; | ||
while (bidiChars.has(ch)) | ||
ch = src[++pos]; | ||
while (whitespaceChars.has(ch)) { | ||
hasWS = true; | ||
ch = src[++pos]; | ||
} | ||
return length; | ||
while (bidiChars.has(ch) || whitespaceChars.has(ch)) | ||
ch = src[++pos]; | ||
return { hasWS, end: pos }; | ||
} | ||
exports.whitespaces = whitespaces; |
@@ -6,3 +6,2 @@ import type { ParseContext } from './parse-cst.js'; | ||
export declare function parseLiteral(ctx: ParseContext, start: number, required: boolean): CST.Literal | undefined; | ||
export declare function parseQuotedLiteral(ctx: ParseContext, start: number): CST.Literal; | ||
export declare function parseVariable(ctx: ParseContext, start: number): CST.VariableRef; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseVariable = exports.parseQuotedLiteral = exports.parseLiteral = exports.parseText = void 0; | ||
exports.parseVariable = exports.parseLiteral = exports.parseText = void 0; | ||
const names_js_1 = require("./names.js"); | ||
@@ -17,3 +17,3 @@ // Text ::= (TextChar | TextEscape)+ | ||
case '\\': { | ||
const esc = parseEscape(ctx, 'text', i); | ||
const esc = parseEscape(ctx, i); | ||
if (esc) { | ||
@@ -70,3 +70,3 @@ value += ctx.source.substring(pos, i) + esc.value; | ||
case '\\': { | ||
const esc = parseEscape(ctx, 'literal', i); | ||
const esc = parseEscape(ctx, i); | ||
if (esc) { | ||
@@ -118,3 +118,2 @@ value += ctx.source.substring(pos, i) + esc.value; | ||
} | ||
exports.parseQuotedLiteral = parseQuotedLiteral; | ||
function parseVariable(ctx, start) { | ||
@@ -124,56 +123,45 @@ const pos = start + 1; | ||
const name = (0, names_js_1.parseNameValue)(ctx.source, pos); | ||
const end = pos + name.length; | ||
if (!name) | ||
if (!name) { | ||
ctx.onError('empty-token', pos, pos + 1); | ||
return { type: 'variable', start, end, open, name }; | ||
return { type: 'variable', start, end: pos, open, name: '' }; | ||
} | ||
return { type: 'variable', start, end: name.end, open, name: name.value }; | ||
} | ||
exports.parseVariable = parseVariable; | ||
function parseEscape(ctx, scope, start) { | ||
function parseEscape(ctx, start) { | ||
const raw = ctx.source[start + 1]; | ||
switch (raw) { | ||
case '\\': | ||
return { value: raw, length: 1 }; | ||
case '{': | ||
case '}': | ||
if (scope === 'text') | ||
if ('\\{|}'.includes(raw)) | ||
return { value: raw, length: 1 }; | ||
if (ctx.resource) { | ||
let hexLen = 0; | ||
switch (raw) { | ||
case '\t': | ||
case ' ': | ||
return { value: raw, length: 1 }; | ||
break; | ||
case '|': | ||
if (scope === 'literal') | ||
return { value: raw, length: 1 }; | ||
break; | ||
default: | ||
if (ctx.resource) { | ||
let hexLen = 0; | ||
switch (raw) { | ||
case '\t': | ||
case ' ': | ||
return { value: raw, length: 1 }; | ||
case 'n': | ||
return { value: '\n', length: 1 }; | ||
case 'r': | ||
return { value: '\r', length: 1 }; | ||
case 't': | ||
return { value: '\t', length: 1 }; | ||
case 'u': | ||
hexLen = 4; | ||
break; | ||
case 'U': | ||
hexLen = 6; | ||
break; | ||
case 'x': | ||
hexLen = 2; | ||
break; | ||
} | ||
if (hexLen > 0) { | ||
const h0 = start + 2; | ||
const raw = ctx.source.substring(h0, h0 + hexLen); | ||
if (raw.length === hexLen && /^[0-9A-Fa-f]+$/.test(raw)) { | ||
return { | ||
value: String.fromCharCode(parseInt(raw, 16)), | ||
length: 1 + hexLen | ||
}; | ||
} | ||
} | ||
case 'n': | ||
return { value: '\n', length: 1 }; | ||
case 'r': | ||
return { value: '\r', length: 1 }; | ||
case 't': | ||
return { value: '\t', length: 1 }; | ||
case 'u': | ||
hexLen = 4; | ||
break; | ||
case 'U': | ||
hexLen = 6; | ||
break; | ||
case 'x': | ||
hexLen = 2; | ||
break; | ||
} | ||
if (hexLen > 0) { | ||
const h0 = start + 2; | ||
const raw = ctx.source.substring(h0, h0 + hexLen); | ||
if (raw.length === hexLen && /^[0-9A-Fa-f]+$/.test(raw)) { | ||
return { | ||
value: String.fromCharCode(parseInt(raw, 16)), | ||
length: 1 + hexLen | ||
}; | ||
} | ||
} | ||
} | ||
@@ -180,0 +168,0 @@ ctx.onError('bad-escape', start, start + 2); |
@@ -8,5 +8,5 @@ "use strict"; | ||
const part = { type: 'markup', kind, source, name }; | ||
if (options?.length) { | ||
if (options?.size) { | ||
part.options = {}; | ||
for (const { name, value } of options) { | ||
for (const [name, value] of options) { | ||
let rv = (0, resolve_value_js_1.resolveValue)(ctx, value); | ||
@@ -13,0 +13,0 @@ if (typeof rv === 'object' && typeof rv?.valueOf === 'function') { |
@@ -27,3 +27,3 @@ "use strict"; | ||
declarations, | ||
selectors: msg.selectors.map(sel => asExpression(sel, false)), | ||
selectors: msg.selectors.map(sel => asValue(sel)), | ||
variants: msg.variants.map(variant => ({ | ||
@@ -70,9 +70,3 @@ keys: variant.keys.map(key => key.type === '*' ? { type: '*', [exports.cst]: key } : asValue(key)), | ||
default: | ||
return { | ||
type: 'unsupported-statement', | ||
keyword: (decl.keyword?.value ?? '').substring(1), | ||
body: decl.body?.value || undefined, | ||
expressions: decl.values?.map(dv => asExpression(dv, true)) ?? [], | ||
[exports.cst]: decl | ||
}; | ||
throw new errors_js_1.MessageSyntaxError('parse-error', decl.start, decl.end); | ||
} | ||
@@ -83,5 +77,2 @@ } | ||
if (exp.type === 'expression') { | ||
const attributes = exp.attributes.length | ||
? exp.attributes.map(asAttribute) | ||
: undefined; | ||
if (allowMarkup && exp.markup) { | ||
@@ -93,5 +84,6 @@ const cm = exp.markup; | ||
if (cm.options.length) | ||
markup.options = cm.options.map(asOption); | ||
if (attributes) | ||
markup.attributes = attributes; | ||
markup.options = asOptions(cm.options); | ||
if (exp.attributes.length) { | ||
markup.attributes = asAttributes(exp.attributes); | ||
} | ||
markup[exports.cst] = exp; | ||
@@ -101,20 +93,13 @@ return markup; | ||
const arg = exp.arg ? asValue(exp.arg) : undefined; | ||
let annotation; | ||
const ca = exp.annotation; | ||
let functionRef; | ||
const ca = exp.functionRef; | ||
if (ca) { | ||
switch (ca.type) { | ||
case 'function': | ||
annotation = { type: 'function', name: asName(ca.name) }; | ||
if (ca.options.length) | ||
annotation.options = ca.options.map(asOption); | ||
break; | ||
case 'reserved-annotation': | ||
annotation = { | ||
type: 'unsupported-annotation', | ||
source: ca.open.value + ca.source.value | ||
}; | ||
break; | ||
default: | ||
throw new errors_js_1.MessageSyntaxError('parse-error', exp.start, exp.end); | ||
if (ca.type === 'function') { | ||
functionRef = { type: 'function', name: asName(ca.name) }; | ||
if (ca.options.length) | ||
functionRef.options = asOptions(ca.options); | ||
} | ||
else { | ||
throw new errors_js_1.MessageSyntaxError('parse-error', exp.start, exp.end); | ||
} | ||
} | ||
@@ -124,12 +109,13 @@ let expression = arg | ||
: undefined; | ||
if (annotation) { | ||
annotation[exports.cst] = ca; | ||
if (functionRef) { | ||
functionRef[exports.cst] = ca; | ||
if (expression) | ||
expression.annotation = annotation; | ||
expression.functionRef = functionRef; | ||
else | ||
expression = { type: 'expression', annotation }; | ||
expression = { type: 'expression', functionRef: functionRef }; | ||
} | ||
if (expression) { | ||
if (attributes) | ||
expression.attributes = attributes; | ||
if (exp.attributes.length) { | ||
expression.attributes = asAttributes(exp.attributes); | ||
} | ||
expression[exports.cst] = exp; | ||
@@ -141,13 +127,24 @@ return expression; | ||
} | ||
const asOption = (option) => ({ | ||
name: asName(option.name), | ||
value: asValue(option.value), | ||
[exports.cst]: option | ||
}); | ||
function asAttribute(attr) { | ||
const name = asName(attr.name); | ||
return attr.value | ||
? { name, value: asValue(attr.value), [exports.cst]: attr } | ||
: { name, [exports.cst]: attr }; | ||
function asOptions(options) { | ||
const map = new Map(); | ||
for (const opt of options) { | ||
const name = asName(opt.name); | ||
if (map.has(name)) { | ||
throw new errors_js_1.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 errors_js_1.MessageSyntaxError('duplicate-attribute', attr.start, attr.end); | ||
} | ||
map.set(name, attr.value ? asValue(attr.value) : true); | ||
} | ||
return map; | ||
} | ||
function asName(id) { | ||
@@ -154,0 +151,0 @@ switch (id.length) { |
import type * as Model from './types.js'; | ||
export type MessageParserOptions = { | ||
/** | ||
* Parse a private annotation starting with `^` or `&`. | ||
* By default, private annotations are parsed as unsupported annotations. | ||
* | ||
* @returns `pos` as the position at the end of the `annotation`, | ||
* not including any trailing whitespace. | ||
*/ | ||
privateAnnotation?: (source: string, pos: number) => { | ||
pos: number; | ||
annotation: any; | ||
}; | ||
}; | ||
/** | ||
@@ -22,2 +9,2 @@ * A MessageFormat 2 parser for message formatting. | ||
*/ | ||
export declare function parseMessage(source: string, opt?: MessageParserOptions): Model.Message; | ||
export declare function parseMessage(source: string): Model.Message; |
@@ -6,7 +6,7 @@ "use strict"; | ||
const errors_js_1 = require("../errors.js"); | ||
const whitespaceChars = ['\t', '\n', '\r', ' ', '\u3000']; | ||
const bidiChars = new Set('\u061C\u200E\u200F\u2066\u2067\u2068\u2069'); | ||
const whitespaceChars = new Set('\t\n\r \u3000'); | ||
//// Parser State //// | ||
let pos; | ||
let source; | ||
let opt; | ||
//// Utilities & Error Wrappers //// | ||
@@ -26,6 +26,5 @@ // These indirections allow for the function names to be mangled, | ||
} | ||
function parseMessage(source_, opt_ = {}) { | ||
function parseMessage(source_) { | ||
pos = 0; | ||
source = source_; | ||
opt = opt_; | ||
const decl = declarations(); | ||
@@ -35,2 +34,4 @@ if (source.startsWith('.match', pos)) | ||
const quoted = decl.length > 0 || source.startsWith('{{', pos); | ||
if (!quoted && pos > 0) | ||
pos = 0; | ||
const pattern_ = pattern(quoted); | ||
@@ -48,7 +49,7 @@ if (quoted) { | ||
pos += 6; // '.match' | ||
ws(); | ||
ws(true); | ||
const selectors = []; | ||
while (source[pos] === '{') { | ||
selectors.push(expression(false)); | ||
ws(); | ||
while (source[pos] === '$') { | ||
selectors.push(variable()); | ||
ws(true); | ||
} | ||
@@ -114,17 +115,16 @@ if (selectors.length === 0) | ||
const declarations = []; | ||
ws(); | ||
loop: while (source[pos] === '.') { | ||
const keyword = (0, names_js_1.parseNameValue)(source, pos + 1); | ||
const keyword = source.substr(pos, 6); | ||
switch (keyword) { | ||
case 'input': | ||
case '.input': | ||
declarations.push(inputDeclaration()); | ||
break; | ||
case 'local': | ||
case '.local': | ||
declarations.push(localDeclaration()); | ||
break; | ||
case 'match': | ||
case '.match': | ||
break loop; | ||
case '': | ||
default: | ||
throw SyntaxError('parse-error', pos); | ||
default: | ||
declarations.push(unsupportedStatement(keyword)); | ||
} | ||
@@ -159,17 +159,2 @@ ws(); | ||
} | ||
function unsupportedStatement(keyword) { | ||
pos += 1 + keyword.length; // '.' + keyword | ||
ws('{'); | ||
const body = reservedBody(); | ||
const expressions = []; | ||
while (source[pos] === '{') { | ||
if (source.startsWith('{{', pos)) | ||
break; | ||
expressions.push(expression(false)); | ||
ws(); | ||
} | ||
if (expressions.length === 0) | ||
throw SyntaxError('empty-token', pos); | ||
return { type: 'unsupported-statement', keyword, body, expressions }; | ||
} | ||
function expression(allowMarkup) { | ||
@@ -183,3 +168,3 @@ const start = pos; | ||
const sigil = source[pos]; | ||
let annotation; | ||
let functionRef; | ||
let markup; | ||
@@ -192,6 +177,6 @@ switch (sigil) { | ||
pos += 1; // ':' | ||
annotation = { type: 'function', name: identifier() }; | ||
functionRef = { type: 'function', name: identifier() }; | ||
const options_ = options(); | ||
if (options_.length) | ||
annotation.options = options_; | ||
if (options_) | ||
functionRef.options = options_; | ||
break; | ||
@@ -207,25 +192,10 @@ } | ||
const options_ = options(); | ||
if (options_.length) | ||
if (options_) | ||
markup.options = options_; | ||
break; | ||
} | ||
case '^': | ||
case '&': | ||
annotation = privateAnnotation(sigil); | ||
break; | ||
case '!': | ||
case '%': | ||
case '*': | ||
case '+': | ||
case '<': | ||
case '>': | ||
case '?': | ||
case '~': | ||
annotation = unsupportedAnnotation(sigil); | ||
break; | ||
default: | ||
throw SyntaxError('parse-error', pos); | ||
} | ||
while (source[pos] === '@') | ||
attribute(); | ||
const attributes_ = attributes(); | ||
if (markup?.kind === 'open' && source[pos] === '/') { | ||
@@ -236,12 +206,20 @@ markup.kind = 'standalone'; | ||
expect('}', true); | ||
if (annotation) { | ||
return arg | ||
? { type: 'expression', arg, annotation } | ||
: { type: 'expression', annotation }; | ||
if (functionRef) { | ||
const exp = arg | ||
? { type: 'expression', arg, functionRef: functionRef } | ||
: { type: 'expression', functionRef: functionRef }; | ||
if (attributes_) | ||
exp.attributes = attributes_; | ||
return exp; | ||
} | ||
if (markup) | ||
if (markup) { | ||
if (attributes_) | ||
markup.attributes = attributes_; | ||
return markup; | ||
} | ||
if (!arg) | ||
throw SyntaxError('empty-token', start, pos); | ||
return { type: 'expression', arg }; | ||
return attributes_ | ||
? { type: 'expression', arg, attributes: attributes_ } | ||
: { type: 'expression', arg }; | ||
} | ||
@@ -251,3 +229,4 @@ /** Requires and consumes leading and trailing whitespace. */ | ||
ws('/}'); | ||
const options = []; | ||
const options = new Map(); | ||
let isEmpty = true; | ||
while (pos < source.length) { | ||
@@ -257,75 +236,39 @@ const next = source[pos]; | ||
break; | ||
const start = pos; | ||
const name_ = identifier(); | ||
if (options.has(name_)) { | ||
throw SyntaxError('duplicate-option-name', start, pos); | ||
} | ||
ws(); | ||
expect('=', true); | ||
ws(); | ||
options.push({ name: name_, value: value(true) }); | ||
options.set(name_, value(true)); | ||
isEmpty = false; | ||
ws('/}'); | ||
} | ||
return options; | ||
return isEmpty ? null : options; | ||
} | ||
function attribute() { | ||
pos += 1; // '@' | ||
identifier(); // name | ||
ws('=/}'); | ||
if (source[pos] === '=') { | ||
pos += 1; // '=' | ||
ws(); | ||
value(true); // value | ||
ws('/}'); | ||
} | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function privateAnnotation(sigil) { | ||
if (opt.privateAnnotation) { | ||
const res = opt.privateAnnotation(source, pos); | ||
pos = res.pos; | ||
ws('}'); | ||
return res.annotation; | ||
} | ||
return unsupportedAnnotation(sigil); | ||
} | ||
function unsupportedAnnotation(sigil) { | ||
pos += 1; // sigil | ||
return { type: 'unsupported-annotation', source: sigil + reservedBody() }; | ||
} | ||
function reservedBody() { | ||
const start = pos; | ||
loop: while (pos < source.length) { | ||
const next = source[pos]; | ||
switch (next) { | ||
case '\\': { | ||
switch (source[pos + 1]) { | ||
case '\\': | ||
case '{': | ||
case '|': | ||
case '}': | ||
break; | ||
default: | ||
throw SyntaxError('bad-escape', pos, pos + 2); | ||
} | ||
pos += 2; | ||
break; | ||
} | ||
case '|': | ||
quotedLiteral(); | ||
break; | ||
case '@': | ||
pos -= 1; | ||
ws(true); | ||
break loop; | ||
case '{': | ||
case '}': | ||
break loop; | ||
default: { | ||
const cc = next.charCodeAt(0); | ||
if (cc >= 0xd800 && cc < 0xe000) { | ||
// surrogates are invalid here | ||
throw SyntaxError('parse-error', pos); | ||
} | ||
pos += 1; | ||
} | ||
function attributes() { | ||
const attributes = new Map(); | ||
let isEmpty = true; | ||
while (source[pos] === '@') { | ||
const start = pos; | ||
pos += 1; // '@' | ||
const name_ = identifier(); | ||
if (attributes.has(name_)) { | ||
throw SyntaxError('duplicate-attribute', start, pos); | ||
} | ||
ws('=/}'); | ||
if (source[pos] === '=') { | ||
pos += 1; // '=' | ||
ws(); | ||
attributes.set(name_, literal(true)); | ||
ws('/}'); | ||
} | ||
else { | ||
attributes.set(name_, true); | ||
} | ||
isEmpty = false; | ||
} | ||
return source.substring(start, pos).trimEnd(); | ||
return isEmpty ? null : attributes; | ||
} | ||
@@ -339,5 +282,4 @@ function text() { | ||
const esc = source[i + 1]; | ||
if (esc !== '\\' && esc !== '{' && esc !== '}') { | ||
if (!'\\{|}'.includes(esc)) | ||
throw SyntaxError('bad-escape', i, i + 2); | ||
} | ||
value += source.substring(pos, i) + esc; | ||
@@ -358,10 +300,8 @@ i += 1; | ||
function value(required) { | ||
if (source[pos] === '$') { | ||
pos += 1; // '$' | ||
return { type: 'variable', name: name() }; | ||
} | ||
else { | ||
return literal(required); | ||
} | ||
return source[pos] === '$' ? variable() : literal(required); | ||
} | ||
function variable() { | ||
pos += 1; // '$' | ||
return { type: 'variable', name: name() }; | ||
} | ||
function literal(required) { | ||
@@ -387,5 +327,4 @@ if (source[pos] === '|') | ||
const esc = source[i + 1]; | ||
if (esc !== '\\' && esc !== '|') { | ||
if (!'\\{|}'.includes(esc)) | ||
throw SyntaxError('bad-escape', i, i + 2); | ||
} | ||
value += source.substring(pos, i) + esc; | ||
@@ -416,16 +355,21 @@ i += 1; | ||
throw SyntaxError('empty-token', pos); | ||
pos += name.length; | ||
return name; | ||
pos = name.end; | ||
return name.value; | ||
} | ||
function ws(req = false) { | ||
let length = 0; | ||
let next = source[pos]; | ||
while (whitespaceChars.includes(next)) { | ||
length += 1; | ||
next = source[pos + length]; | ||
let hasWS = false; | ||
if (req) { | ||
while (bidiChars.has(next)) | ||
next = source[++pos]; | ||
while (whitespaceChars.has(next)) { | ||
next = source[++pos]; | ||
hasWS = true; | ||
} | ||
} | ||
pos += length; | ||
if (req && !length && (req === true || !req.includes(source[pos]))) { | ||
while (bidiChars.has(next) || whitespaceChars.has(next)) | ||
next = source[++pos]; | ||
if (req && !hasWS && (req === true || !req.includes(source[pos]))) { | ||
throw MissingSyntax(pos, "' '"); | ||
} | ||
} |
import type { Context } from '../format-context.js'; | ||
import type { MessageValue } from '../functions/index.js'; | ||
import type { Expression } from './types.js'; | ||
export declare function resolveExpression(ctx: Context, { arg, annotation }: Expression): MessageValue; | ||
export declare function resolveExpression(ctx: Context, { arg, functionRef }: Expression): MessageValue; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.resolveExpression = void 0; | ||
const resolve_function_annotation_js_1 = require("./resolve-function-annotation.js"); | ||
const resolve_function_ref_js_1 = require("./resolve-function-ref.js"); | ||
const resolve_literal_js_1 = require("./resolve-literal.js"); | ||
const resolve_unsupported_annotation_js_1 = require("./resolve-unsupported-annotation.js"); | ||
const resolve_variable_js_1 = require("./resolve-variable.js"); | ||
function resolveExpression(ctx, { arg, annotation }) { | ||
if (annotation) { | ||
return annotation.type === 'function' | ||
? (0, resolve_function_annotation_js_1.resolveFunctionAnnotation)(ctx, arg, annotation) | ||
: (0, resolve_unsupported_annotation_js_1.resolveUnsupportedAnnotation)(ctx, arg, annotation); | ||
function resolveExpression(ctx, { arg, functionRef }) { | ||
if (functionRef) { | ||
return (0, resolve_function_ref_js_1.resolveFunctionRef)(ctx, arg, functionRef); | ||
} | ||
@@ -14,0 +11,0 @@ switch (arg?.type) { |
@@ -25,3 +25,3 @@ "use strict"; | ||
exports.UnresolvedExpression = UnresolvedExpression; | ||
const isScope = (scope) => scope instanceof Object; | ||
const isScope = (scope) => scope !== null && (typeof scope === 'object' || typeof scope === 'function'); | ||
/** | ||
@@ -59,3 +59,3 @@ * Looks for the longest matching `.` delimited starting substring of name. | ||
const msg = `Variable not available: ${source}`; | ||
ctx.onError(new errors_js_1.MessageResolutionError('unresolved-var', msg, source)); | ||
ctx.onError(new errors_js_1.MessageResolutionError('unresolved-variable', msg, source)); | ||
} | ||
@@ -62,0 +62,0 @@ else if (value instanceof UnresolvedExpression) { |
@@ -1,2 +0,1 @@ | ||
import { MessageFormat } from '../messageformat.js'; | ||
import type { Message } from './types.js'; | ||
@@ -8,2 +7,2 @@ /** | ||
*/ | ||
export declare function stringifyMessage(msg: Message | MessageFormat): string; | ||
export declare function stringifyMessage(msg: Message): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.stringifyMessage = void 0; | ||
const messageformat_js_1 = require("../messageformat.js"); | ||
const names_js_1 = require("../cst/names.js"); | ||
@@ -13,4 +12,2 @@ const type_guards_js_1 = require("./type-guards.js"); | ||
function stringifyMessage(msg) { | ||
if (msg instanceof messageformat_js_1.MessageFormat) | ||
msg = msg.resolvedOptions().message; | ||
let res = ''; | ||
@@ -25,3 +22,3 @@ for (const decl of msg.declarations) | ||
for (const sel of msg.selectors) | ||
res += ' ' + stringifyExpression(sel); | ||
res += ' ' + stringifyVariableRef(sel); | ||
for (const { keys, value } of msg.variants) { | ||
@@ -44,13 +41,2 @@ res += '\n'; | ||
return `.local $${decl.name} = ${stringifyExpression(decl.value)}\n`; | ||
case 'unsupported-statement': { | ||
const parts = [`.${decl.keyword}`]; | ||
if (decl.body) | ||
parts.push(decl.body); | ||
for (const exp of decl.expressions) { | ||
parts.push(exp.type === 'expression' | ||
? stringifyExpression(exp) | ||
: stringifyMarkup(exp)); | ||
} | ||
return parts.join(' '); | ||
} | ||
} | ||
@@ -60,7 +46,9 @@ // @ts-expect-error Guard against non-TS users with bad data | ||
} | ||
function stringifyFunctionAnnotation({ name, options }) { | ||
function stringifyFunctionRef({ name, options }) { | ||
let res = `:${name}`; | ||
if (options) | ||
for (const opt of options) | ||
res += ' ' + stringifyOption(opt); | ||
if (options) { | ||
for (const [key, value] of options) { | ||
res += ' ' + stringifyOption(key, value); | ||
} | ||
} | ||
return res; | ||
@@ -71,8 +59,11 @@ } | ||
res += name; | ||
if (options) | ||
for (const opt of options) | ||
res += ' ' + stringifyOption(opt); | ||
if (options) { | ||
for (const [name, value] of options) { | ||
res += ' ' + stringifyOption(name, value); | ||
} | ||
} | ||
if (attributes) { | ||
for (const attr of attributes) | ||
res += ' ' + stringifyAttribute(attr); | ||
for (const [name, value] of attributes) { | ||
res += ' ' + stringifyAttribute(name, value); | ||
} | ||
} | ||
@@ -88,3 +79,3 @@ res += kind === 'standalone' ? ' /}' : '}'; | ||
} | ||
function stringifyOption({ name, value }) { | ||
function stringifyOption(name, value) { | ||
const valueStr = (0, type_guards_js_1.isVariableRef)(value) | ||
@@ -95,13 +86,8 @@ ? stringifyVariableRef(value) | ||
} | ||
function stringifyAttribute({ name, value }) { | ||
if (!value) | ||
return `@${name}`; | ||
const valueStr = (0, type_guards_js_1.isVariableRef)(value) | ||
? stringifyVariableRef(value) | ||
: stringifyLiteral(value); | ||
return `@${name}=${valueStr}`; | ||
function stringifyAttribute(name, value) { | ||
return value === true ? `@${name}` : `@${name}=${stringifyLiteral(value)}`; | ||
} | ||
function stringifyPattern(pattern, quoted) { | ||
let res = ''; | ||
if (!quoted && typeof pattern[0] === 'string' && pattern[0][0] === '.') { | ||
if (!quoted && typeof pattern[0] === 'string' && /^\s*\./.test(pattern[0])) { | ||
quoted = true; | ||
@@ -111,3 +97,3 @@ } | ||
if (typeof el === 'string') | ||
res += el; | ||
res += stringifyString(el, quoted); | ||
else if (el.type === 'markup') | ||
@@ -120,3 +106,7 @@ res += stringifyMarkup(el); | ||
} | ||
function stringifyExpression({ arg, annotation, attributes }) { | ||
function stringifyString(str, quoted) { | ||
const esc = quoted ? /[\\|]/g : /[\\{}]/g; | ||
return str.replace(esc, '\\$&'); | ||
} | ||
function stringifyExpression({ arg, attributes, functionRef }) { | ||
let res; | ||
@@ -133,13 +123,11 @@ switch (arg?.type) { | ||
} | ||
if (annotation) { | ||
if (functionRef) { | ||
if (res) | ||
res += ' '; | ||
res += | ||
annotation.type === 'function' | ||
? stringifyFunctionAnnotation(annotation) | ||
: annotation.source ?? '�'; | ||
res += stringifyFunctionRef(functionRef); | ||
} | ||
if (attributes) { | ||
for (const attr of attributes) | ||
res += ' ' + stringifyAttribute(attr); | ||
for (const [name, value] of attributes) { | ||
res += ' ' + stringifyAttribute(name, value); | ||
} | ||
} | ||
@@ -146,0 +134,0 @@ return `{${res}}`; |
@@ -1,2 +0,2 @@ | ||
import type { CatchallKey, Expression, FunctionAnnotation, Literal, Markup, Message, PatternMessage, SelectMessage, UnsupportedAnnotation, VariableRef } from './types.js'; | ||
import type { CatchallKey, Expression, FunctionRef, Literal, Markup, Message, PatternMessage, SelectMessage, VariableRef } from './types.js'; | ||
/** @beta */ | ||
@@ -7,3 +7,3 @@ export declare const isCatchallKey: (key: any) => key is CatchallKey; | ||
/** @beta */ | ||
export declare const isFunctionAnnotation: (part: any) => part is FunctionAnnotation; | ||
export declare const isFunctionRef: (part: any) => part is FunctionRef; | ||
/** @beta */ | ||
@@ -20,4 +20,2 @@ export declare const isLiteral: (part: any) => part is Literal; | ||
/** @beta */ | ||
export declare const isUnsupportedAnnotation: (part: any) => part is UnsupportedAnnotation; | ||
/** @beta */ | ||
export declare const isVariableRef: (part: any) => part is VariableRef; |
"use strict"; | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isVariableRef = exports.isUnsupportedAnnotation = exports.isSelectMessage = exports.isPatternMessage = exports.isMessage = exports.isMarkup = exports.isLiteral = exports.isFunctionAnnotation = exports.isExpression = exports.isCatchallKey = void 0; | ||
exports.isVariableRef = exports.isSelectMessage = exports.isPatternMessage = exports.isMessage = exports.isMarkup = exports.isLiteral = exports.isFunctionRef = exports.isExpression = exports.isCatchallKey = void 0; | ||
/** @beta */ | ||
@@ -12,4 +12,4 @@ const isCatchallKey = (key) => !!key && typeof key === 'object' && key.type === '*'; | ||
/** @beta */ | ||
const isFunctionAnnotation = (part) => !!part && typeof part === 'object' && part.type === 'function'; | ||
exports.isFunctionAnnotation = isFunctionAnnotation; | ||
const isFunctionRef = (part) => !!part && typeof part === 'object' && part.type === 'function'; | ||
exports.isFunctionRef = isFunctionRef; | ||
/** @beta */ | ||
@@ -33,6 +33,3 @@ const isLiteral = (part) => !!part && typeof part === 'object' && part.type === 'literal'; | ||
/** @beta */ | ||
const isUnsupportedAnnotation = (part) => !!part && typeof part === 'object' && part.type === 'unsupported-annotation'; | ||
exports.isUnsupportedAnnotation = isUnsupportedAnnotation; | ||
/** @beta */ | ||
const isVariableRef = (part) => !!part && typeof part === 'object' && part.type === 'variable'; | ||
exports.isVariableRef = isVariableRef; |
@@ -8,3 +8,3 @@ import type * as CST from '../cst/types.js'; | ||
*/ | ||
export type MessageNode = Declaration | Variant | CatchallKey | Expression | Literal | VariableRef | FunctionAnnotation | UnsupportedAnnotation | Markup | Option | Attribute; | ||
export type MessageNode = Declaration | Variant | CatchallKey | Expression | Literal | VariableRef | FunctionRef | Markup; | ||
/** | ||
@@ -37,3 +37,3 @@ * The representation of a single message. | ||
*/ | ||
export type Declaration = InputDeclaration | LocalDeclaration | UnsupportedStatement; | ||
export type Declaration = InputDeclaration | LocalDeclaration; | ||
/** @beta */ | ||
@@ -53,12 +53,2 @@ export interface InputDeclaration { | ||
} | ||
/** @beta */ | ||
export interface UnsupportedStatement { | ||
type: 'unsupported-statement'; | ||
keyword: string; | ||
name?: never; | ||
value?: never; | ||
body?: string; | ||
expressions: (Expression | Markup)[]; | ||
[cst]?: CST.Declaration; | ||
} | ||
/** | ||
@@ -79,3 +69,3 @@ * SelectMessage generalises the plural, selectordinal and select | ||
declarations: Declaration[]; | ||
selectors: Expression[]; | ||
selectors: VariableRef[]; | ||
variants: Variant[]; | ||
@@ -112,19 +102,17 @@ comment?: string; | ||
* Expressions are used in declarations, as selectors, and as placeholders. | ||
* Each must include at least an `arg` or an `annotation`, or both. | ||
* Each must include at least an `arg` or a `functionRef`, or both. | ||
* | ||
* @beta | ||
*/ | ||
export type Expression<A extends Literal | VariableRef | undefined = Literal | VariableRef | undefined> = A extends Literal | VariableRef ? { | ||
export type Expression<A extends Literal | VariableRef | undefined = Literal | VariableRef | undefined> = { | ||
type: 'expression'; | ||
attributes?: Attributes; | ||
[cst]?: CST.Expression; | ||
} & (A extends Literal | VariableRef ? { | ||
arg: A; | ||
annotation?: FunctionAnnotation | UnsupportedAnnotation; | ||
attributes?: Attribute[]; | ||
[cst]?: CST.Expression; | ||
functionRef?: FunctionRef; | ||
} : { | ||
type: 'expression'; | ||
arg?: never; | ||
annotation: FunctionAnnotation | UnsupportedAnnotation; | ||
attributes?: Attribute[]; | ||
[cst]?: CST.Expression; | ||
}; | ||
functionRef: FunctionRef; | ||
}); | ||
/** | ||
@@ -164,3 +152,3 @@ * An immediately defined value. | ||
/** | ||
* To resolve a FunctionAnnotation, an externally defined function is called. | ||
* To resolve a FunctionRef, an externally defined function is called. | ||
* | ||
@@ -176,26 +164,9 @@ * @remarks | ||
*/ | ||
export interface FunctionAnnotation { | ||
export interface FunctionRef { | ||
type: 'function'; | ||
name: string; | ||
options?: Option[]; | ||
options?: Options; | ||
[cst]?: CST.FunctionRef; | ||
} | ||
/** | ||
* When the parser encounters an expression with reserved syntax, | ||
* it emits an UnsupportedAnnotation to represent it. | ||
* | ||
* @remarks | ||
* As the meaning of this syntax is not supported, | ||
* it will always resolve with a fallback representation and emit an error. | ||
* | ||
* @beta | ||
*/ | ||
export interface UnsupportedAnnotation { | ||
type: 'unsupported-annotation'; | ||
source: string; | ||
name?: never; | ||
options?: never; | ||
[cst]?: CST.ReservedAnnotation; | ||
} | ||
/** | ||
* Markup placeholders can span ranges of other pattern elements, | ||
@@ -217,29 +188,17 @@ * or represent other inline elements. | ||
name: string; | ||
options?: Option[]; | ||
attributes?: Attribute[]; | ||
options?: Options; | ||
attributes?: Attributes; | ||
[cst]?: CST.Expression; | ||
} | ||
/** | ||
* The options of {@link FunctionAnnotation} and {@link Markup} | ||
* are expressed as `key`/`value` pairs to allow their order to be maintained. | ||
* The options of {@link FunctionRef} and {@link Markup}. | ||
* | ||
* @beta | ||
*/ | ||
export interface Option { | ||
type?: never; | ||
name: string; | ||
value: Literal | VariableRef; | ||
[cst]?: CST.Option; | ||
} | ||
export type Options = Map<string, Literal | VariableRef>; | ||
/** | ||
* The attributes of {@link Expression} and {@link Markup} | ||
* are expressed as `key`/`value` pairs to allow their order to be maintained. | ||
* The attributes of {@link Expression} and {@link Markup}. | ||
* | ||
* @beta | ||
*/ | ||
export interface Attribute { | ||
type?: never; | ||
name: string; | ||
value?: Literal | VariableRef; | ||
[cst]?: CST.Attribute; | ||
} | ||
export type Attributes = Map<string, true | Literal>; |
@@ -16,5 +16,4 @@ import { MessageDataModelError } from '../errors.js'; | ||
* - **Missing Selector Annotation**: | ||
* A _selector_ does not have an _annotation_, | ||
* or contains a _variable_ that does not directly or indirectly | ||
* reference a _declaration_ with an _annotation_. | ||
* A _selector_ does not contains a _variable_ that directly or indirectly | ||
* reference a _declaration_ with a _function_. | ||
* | ||
@@ -27,4 +26,4 @@ * - **Duplicate Declaration**: | ||
* | ||
* - **Duplicate Option Name**: | ||
* The same _identifier_ appears as the name of more than one _option_ in the same _expression_. | ||
* - **Duplicate Variant**: | ||
* The same list of _keys_ is used for more than one _variant_. | ||
* | ||
@@ -31,0 +30,0 @@ * @returns The sets of runtime `functions` and `variables` used by the message. |
@@ -19,5 +19,4 @@ "use strict"; | ||
* - **Missing Selector Annotation**: | ||
* A _selector_ does not have an _annotation_, | ||
* or contains a _variable_ that does not directly or indirectly | ||
* reference a _declaration_ with an _annotation_. | ||
* A _selector_ does not contains a _variable_ that directly or indirectly | ||
* reference a _declaration_ with a _function_. | ||
* | ||
@@ -30,4 +29,4 @@ * - **Duplicate Declaration**: | ||
* | ||
* - **Duplicate Option Name**: | ||
* The same _identifier_ appears as the name of more than one _option_ in the same _expression_. | ||
* - **Duplicate Variant**: | ||
* The same list of _keys_ is used for more than one _variant_. | ||
* | ||
@@ -49,2 +48,3 @@ * @returns The sets of runtime `functions` and `variables` used by the message. | ||
const variables = new Set(); | ||
const variants = new Set(); | ||
let setArgAsDeclared = true; | ||
@@ -56,3 +56,3 @@ (0, visit_js_1.visit)(msg, { | ||
return undefined; | ||
if (decl.value.annotation || | ||
if (decl.value.functionRef || | ||
(decl.type === 'local' && | ||
@@ -73,32 +73,24 @@ decl.value.arg?.type === 'variable' && | ||
}, | ||
expression(expression, context) { | ||
const { arg, annotation } = expression; | ||
if (annotation?.type === 'function') | ||
functions.add(annotation.name); | ||
if (context === 'selector') { | ||
selectorCount += 1; | ||
missingFallback = expression; | ||
if (!annotation && | ||
(arg?.type !== 'variable' || !annotated.has(arg.name))) { | ||
onError('missing-selector-annotation', expression); | ||
} | ||
} | ||
expression({ functionRef }) { | ||
if (functionRef) | ||
functions.add(functionRef.name); | ||
}, | ||
option({ name }, index, options) { | ||
for (let j = index + 1; j < options.length; ++j) { | ||
if (options[j].name === name) { | ||
onError('duplicate-option', options[j]); | ||
value(value, context, position) { | ||
if (value.type !== 'variable') | ||
return; | ||
variables.add(value.name); | ||
switch (context) { | ||
case 'declaration': | ||
if (position !== 'arg' || setArgAsDeclared) { | ||
declared.add(value.name); | ||
} | ||
break; | ||
} | ||
case 'selector': | ||
selectorCount += 1; | ||
missingFallback = value; | ||
if (!annotated.has(value.name)) { | ||
onError('missing-selector-annotation', value); | ||
} | ||
} | ||
}, | ||
value(value, context, position) { | ||
if (value.type === 'variable') { | ||
variables.add(value.name); | ||
if (context === 'declaration' && | ||
(position !== 'arg' || setArgAsDeclared)) { | ||
declared.add(value.name); | ||
} | ||
} | ||
}, | ||
variant(variant) { | ||
@@ -108,2 +100,7 @@ const { keys } = variant; | ||
onError('key-mismatch', variant); | ||
const strKeys = JSON.stringify(keys.map(key => (key.type === 'literal' ? key.value : 0))); | ||
if (variants.has(strKeys)) | ||
onError('duplicate-variant', variant); | ||
else | ||
variants.add(strKeys); | ||
missingFallback &&= keys.every(key => key.type === '*') ? null : variant; | ||
@@ -110,0 +107,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { Attribute, CatchallKey, Declaration, Expression, FunctionAnnotation, Literal, Markup, Message, MessageNode, Option, Pattern, UnsupportedAnnotation, VariableRef, Variant } from './types.js'; | ||
import type { Attributes, CatchallKey, Declaration, Expression, FunctionRef, Literal, Markup, Message, MessageNode, Options, Pattern, VariableRef, Variant } from './types.js'; | ||
/** | ||
@@ -19,10 +19,10 @@ * Apply visitor functions to message nodes. | ||
export declare function visit(msg: Message, visitors: { | ||
annotation?: (annotation: FunctionAnnotation | UnsupportedAnnotation, context: 'declaration' | 'selector' | 'placeholder', argument: Literal | VariableRef | undefined) => (() => void) | void; | ||
attribute?: (attr: Attribute, index: number, attributes: Attribute[], context: 'declaration' | 'selector' | 'placeholder') => (() => void) | void; | ||
attributes?: (attributes: Attributes, context: 'declaration' | 'placeholder') => (() => void) | void; | ||
declaration?: (declaration: Declaration) => (() => void) | void; | ||
expression?: (expression: Expression, context: 'declaration' | 'selector' | 'placeholder') => (() => void) | void; | ||
expression?: (expression: Expression, context: 'declaration' | 'placeholder') => (() => void) | void; | ||
functionRef?: (functionRef: FunctionRef, context: 'declaration' | 'placeholder', argument: Literal | VariableRef | undefined) => (() => void) | void; | ||
key?: (key: Literal | CatchallKey, index: number, keys: (Literal | CatchallKey)[]) => void; | ||
markup?: (markup: Markup, context: 'declaration' | 'placeholder') => (() => void) | void; | ||
node?: (node: MessageNode, ...rest: unknown[]) => void; | ||
option?: (option: Option, index: number, options: Option[], context: 'declaration' | 'selector' | 'placeholder') => (() => void) | void; | ||
options?: (options: Options, context: 'declaration' | 'placeholder') => (() => void) | void; | ||
pattern?: (pattern: Pattern) => (() => void) | void; | ||
@@ -29,0 +29,0 @@ value?: (value: Literal | VariableRef, context: 'declaration' | 'selector' | 'placeholder', position: 'arg' | 'option' | 'attribute') => void; |
@@ -22,22 +22,24 @@ "use strict"; | ||
const { node, pattern } = visitors; | ||
const { annotation = node, attribute = node, declaration = node, expression = node, key = node, markup = node, option = node, value = node, variant = node } = visitors; | ||
const handleOptions = (options, context) => { | ||
if (options) { | ||
for (let i = 0; i < options.length; ++i) { | ||
const opt = options[i]; | ||
const end = option?.(opt, i, options, context); | ||
value?.(opt.value, context, 'option'); | ||
end?.(); | ||
const { functionRef = node, attributes = null, declaration = node, expression = node, key = node, markup = node, options = null, value = node, variant = node } = visitors; | ||
const handleOptions = (options_, context) => { | ||
if (options_) { | ||
const end = options?.(options_, context); | ||
if (value) { | ||
for (const value_ of options_.values()) { | ||
value(value_, context, 'option'); | ||
} | ||
} | ||
end?.(); | ||
} | ||
}; | ||
const handleAttributes = (attributes, context) => { | ||
if (attributes) { | ||
for (let i = 0; i < attributes.length; ++i) { | ||
const attr = attributes[i]; | ||
const end = attribute?.(attr, i, attributes, context); | ||
if (attr.value) | ||
value?.(attr.value, context, 'attribute'); | ||
end?.(); | ||
const handleAttributes = (attributes_, context) => { | ||
if (attributes_) { | ||
const end = attributes?.(attributes_, context); | ||
if (value) { | ||
for (const value_ of attributes_.values()) { | ||
if (value_ !== true) | ||
value(value_, context, 'attribute'); | ||
} | ||
} | ||
end?.(); | ||
} | ||
@@ -53,5 +55,5 @@ }; | ||
value?.(exp.arg, context, 'arg'); | ||
if (exp.annotation) { | ||
const endA = annotation?.(exp.annotation, context, exp.arg); | ||
handleOptions(exp.annotation.options, context); | ||
if (exp.functionRef) { | ||
const endA = functionRef?.(exp.functionRef, context, exp.arg); | ||
handleOptions(exp.functionRef.options, context); | ||
endA?.(); | ||
@@ -63,3 +65,3 @@ } | ||
case 'markup': { | ||
end = context !== 'selector' ? markup?.(exp, context) : undefined; | ||
end = markup?.(exp, context); | ||
handleOptions(exp.options, context); | ||
@@ -83,5 +85,2 @@ handleAttributes(exp.attributes, context); | ||
handleElement(decl.value, 'declaration'); | ||
else | ||
for (const exp of decl.expressions) | ||
handleElement(exp, 'declaration'); | ||
end?.(); | ||
@@ -93,4 +92,5 @@ } | ||
else { | ||
for (const sel of msg.selectors) | ||
handleElement(sel, 'selector'); | ||
if (value) | ||
for (const sel of msg.selectors) | ||
value(sel, 'selector', 'arg'); | ||
for (const vari of msg.variants) { | ||
@@ -97,0 +97,0 @@ const end = variant?.(vari); |
@@ -8,3 +8,3 @@ import { type MessageNode } from './data-model/types.js'; | ||
export declare class MessageError extends Error { | ||
type: 'missing-func' | 'not-formattable' | typeof MessageResolutionError.prototype.type | typeof MessageSelectionError.prototype.type | typeof MessageSyntaxError.prototype.type; | ||
type: 'not-formattable' | 'unknown-function' | typeof MessageResolutionError.prototype.type | typeof MessageSelectionError.prototype.type | typeof MessageSyntaxError.prototype.type; | ||
constructor(type: typeof MessageError.prototype.type, message: string); | ||
@@ -18,3 +18,3 @@ } | ||
export declare class MessageSyntaxError extends MessageError { | ||
type: 'empty-token' | 'bad-escape' | 'bad-input-expression' | 'bad-selector' | 'duplicate-declaration' | 'duplicate-option' | 'extra-content' | 'key-mismatch' | 'parse-error' | 'missing-fallback' | 'missing-selector-annotation' | 'missing-syntax'; | ||
type: 'empty-token' | 'bad-escape' | 'bad-input-expression' | 'duplicate-attribute' | 'duplicate-declaration' | 'duplicate-option-name' | 'duplicate-variant' | 'extra-content' | 'key-mismatch' | 'parse-error' | 'missing-fallback' | 'missing-selector-annotation' | 'missing-syntax'; | ||
start: number; | ||
@@ -30,3 +30,3 @@ end: number; | ||
export declare class MessageDataModelError extends MessageSyntaxError { | ||
type: 'duplicate-declaration' | 'duplicate-option' | 'key-mismatch' | 'missing-fallback' | 'missing-selector-annotation'; | ||
type: 'duplicate-declaration' | 'duplicate-variant' | 'key-mismatch' | 'missing-fallback' | 'missing-selector-annotation'; | ||
constructor(type: typeof MessageDataModelError.prototype.type, node: MessageNode); | ||
@@ -40,3 +40,3 @@ } | ||
export declare class MessageResolutionError extends MessageError { | ||
type: 'bad-input' | 'bad-option' | 'unresolved-var' | 'unsupported-annotation' | 'unsupported-statement'; | ||
type: 'bad-function-result' | 'bad-operand' | 'bad-option' | 'unresolved-variable'; | ||
source: string; | ||
@@ -51,4 +51,5 @@ constructor(type: typeof MessageResolutionError.prototype.type, message: string, source: string); | ||
export declare class MessageSelectionError extends MessageError { | ||
type: 'no-match' | 'not-selectable'; | ||
constructor(type: typeof MessageSelectionError.prototype.type); | ||
type: 'bad-selector' | 'no-match'; | ||
cause?: unknown; | ||
constructor(type: typeof MessageSelectionError.prototype.type, cause?: unknown); | ||
} |
@@ -67,6 +67,9 @@ "use strict"; | ||
class MessageSelectionError extends MessageError { | ||
constructor(type) { | ||
cause; | ||
constructor(type, cause) { | ||
super(type, `Selection error: ${type}`); | ||
if (cause !== undefined) | ||
this.cause = cause; | ||
} | ||
} | ||
exports.MessageSelectionError = MessageSelectionError; |
@@ -46,3 +46,3 @@ "use strict"; | ||
if (Object.keys(res).length <= 1) { | ||
res.dateStyle = 'short'; | ||
res.dateStyle = 'medium'; | ||
res.timeStyle = 'short'; | ||
@@ -59,3 +59,3 @@ } | ||
const date = (ctx, options, input) => dateTimeImplementation(ctx, options, input, res => { | ||
const ds = options.style ?? res.dateStyle ?? 'short'; | ||
const ds = options.style ?? res.dateStyle ?? 'medium'; | ||
for (const name of Object.keys(res)) { | ||
@@ -65,3 +65,9 @@ if (!localeOptions.includes(name)) | ||
} | ||
res.dateStyle = (0, utils_js_1.asString)(ds); | ||
try { | ||
res.dateStyle = (0, utils_js_1.asString)(ds); | ||
} | ||
catch { | ||
const msg = `Value ${ds} is not valid for :date style option`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
} | ||
}); | ||
@@ -81,3 +87,9 @@ exports.date = date; | ||
} | ||
res.timeStyle = (0, utils_js_1.asString)(ts); | ||
try { | ||
res.timeStyle = (0, utils_js_1.asString)(ts); | ||
} | ||
catch { | ||
const msg = `Value ${ts} is not valid for :time style option`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, ctx.source); | ||
} | ||
}); | ||
@@ -105,5 +117,5 @@ exports.time = time; | ||
} | ||
if (!(value instanceof Date)) { | ||
if (!(value instanceof Date) || isNaN(value.getTime())) { | ||
const msg = 'Input is not a date'; | ||
throw new errors_js_1.MessageResolutionError('bad-input', msg, source); | ||
throw new errors_js_1.MessageResolutionError('bad-operand', msg, source); | ||
} | ||
@@ -110,0 +122,0 @@ parseOptions(opt); |
@@ -6,3 +6,3 @@ "use strict"; | ||
const utils_js_1 = require("./utils.js"); | ||
const NotFormattable = Symbol('not-formattable'); | ||
const INT = Symbol('INT'); | ||
/** | ||
@@ -18,65 +18,59 @@ * `number` accepts a number, BigInt or string representing a JSON number as input | ||
function number({ localeMatcher, locales, source }, options, input) { | ||
let value; | ||
const opt = { | ||
localeMatcher | ||
}; | ||
switch (typeof input) { | ||
case 'bigint': | ||
case 'number': | ||
value = input; | ||
break; | ||
case 'object': | ||
if (typeof input?.valueOf === 'function') { | ||
value = input.valueOf(); | ||
if ('options' in input) | ||
Object.assign(opt, input.options); | ||
} | ||
break; | ||
case 'string': | ||
try { | ||
value = JSON.parse(input); | ||
} | ||
catch { | ||
// handled below | ||
} | ||
let value = input; | ||
if (typeof value === 'object') { | ||
const valueOf = value?.valueOf; | ||
if (typeof valueOf === 'function') { | ||
Object.assign(opt, value.options); | ||
value = valueOf.call(value); | ||
} | ||
} | ||
if (typeof value === 'string') { | ||
try { | ||
value = JSON.parse(value); | ||
} | ||
catch { | ||
// handled below | ||
} | ||
} | ||
if (typeof value !== 'bigint' && typeof value !== 'number') { | ||
const msg = 'Input is not numeric'; | ||
throw new errors_js_1.MessageResolutionError('bad-input', msg, source); | ||
throw new errors_js_1.MessageResolutionError('bad-operand', msg, source); | ||
} | ||
if (options) { | ||
for (const [name, value] of Object.entries(options)) { | ||
if (value === undefined) | ||
continue; | ||
try { | ||
switch (name) { | ||
case 'locale': | ||
case 'type': // used internally by Intl.PluralRules, but called 'select' here | ||
break; | ||
case 'minimumIntegerDigits': | ||
case 'minimumFractionDigits': | ||
case 'maximumFractionDigits': | ||
case 'minimumSignificantDigits': | ||
case 'maximumSignificantDigits': | ||
case 'roundingIncrement': | ||
// @ts-expect-error TS types don't know about roundingIncrement | ||
opt[name] = (0, utils_js_1.asPositiveInteger)(value); | ||
break; | ||
case 'useGrouping': | ||
opt[name] = (0, utils_js_1.asBoolean)(value); | ||
break; | ||
default: | ||
// @ts-expect-error Unknown options will be ignored | ||
opt[name] = (0, utils_js_1.asString)(value); | ||
} | ||
for (const [name, optval] of Object.entries(options)) { | ||
if (optval === undefined) | ||
continue; | ||
try { | ||
switch (name) { | ||
case 'locale': | ||
case 'type': // used internally by Intl.PluralRules, but called 'select' here | ||
break; | ||
case 'minimumIntegerDigits': | ||
case 'minimumFractionDigits': | ||
case 'maximumFractionDigits': | ||
case 'minimumSignificantDigits': | ||
case 'maximumSignificantDigits': | ||
case 'roundingIncrement': | ||
// @ts-expect-error TS types don't know about roundingIncrement | ||
opt[name] = (0, utils_js_1.asPositiveInteger)(optval); | ||
break; | ||
case 'useGrouping': | ||
opt[name] = (0, utils_js_1.asBoolean)(optval); | ||
break; | ||
default: | ||
// @ts-expect-error Unknown options will be ignored | ||
opt[name] = (0, utils_js_1.asString)(optval); | ||
} | ||
catch { | ||
const msg = `Value ${value} is not valid for :number option ${name}`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, source); | ||
} | ||
} | ||
catch { | ||
const msg = `Value ${optval} is not valid for :number option ${name}`; | ||
throw new errors_js_1.MessageResolutionError('bad-option', msg, source); | ||
} | ||
} | ||
const formattable = !options[NotFormattable]; | ||
const num = Number.isFinite(value) && options[INT] | ||
? Math.round(value) | ||
: value; | ||
const lc = (0, utils_js_1.mergeLocales)(locales, input, options); | ||
const num = value; | ||
let locale; | ||
@@ -108,17 +102,13 @@ let nf; | ||
}, | ||
toParts: formattable | ||
? () => { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
const parts = nf.formatToParts(num); | ||
locale ??= nf.resolvedOptions().locale; | ||
return [{ type: 'number', source, locale, parts }]; | ||
} | ||
: undefined, | ||
toString: formattable | ||
? () => { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
str ??= nf.format(num); | ||
return str; | ||
} | ||
: undefined, | ||
toParts() { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
const parts = nf.formatToParts(num); | ||
locale ??= nf.resolvedOptions().locale; | ||
return [{ type: 'number', source, locale, parts }]; | ||
}, | ||
toString() { | ||
nf ??= new Intl.NumberFormat(lc, opt); | ||
str ??= nf.format(num); | ||
return str; | ||
}, | ||
valueOf: () => num | ||
@@ -139,3 +129,3 @@ }; | ||
*/ | ||
const integer = (ctx, options, input) => number(ctx, { ...options, maximumFractionDigits: 0, style: 'decimal' }, input); | ||
const integer = (ctx, options, input) => number(ctx, { ...options, maximumFractionDigits: 0, style: 'decimal', [INT]: true }, input); | ||
exports.integer = integer; |
@@ -8,5 +8,5 @@ import type * as CST from './cst/types.js'; | ||
export { messageFromCST, cst } from './data-model/from-cst.js'; | ||
export { type MessageParserOptions, parseMessage } from './data-model/parse.js'; | ||
export { parseMessage } from './data-model/parse.js'; | ||
export { stringifyMessage } from './data-model/stringify.js'; | ||
export { isCatchallKey, isExpression, isFunctionAnnotation, isLiteral, isMarkup, isMessage, isPatternMessage, isSelectMessage, isUnsupportedAnnotation, isVariableRef } from './data-model/type-guards.js'; | ||
export { isCatchallKey, isExpression, isFunctionRef, isLiteral, isMarkup, isMessage, isPatternMessage, isSelectMessage, isVariableRef } from './data-model/type-guards.js'; | ||
export { validate } from './data-model/validate.js'; | ||
@@ -13,0 +13,0 @@ export { visit } from './data-model/visit.js'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.MessageFormat = exports.MessageSyntaxError = exports.MessageSelectionError = exports.MessageResolutionError = exports.MessageError = exports.MessageDataModelError = exports.visit = exports.validate = exports.isVariableRef = exports.isUnsupportedAnnotation = exports.isSelectMessage = exports.isPatternMessage = exports.isMessage = exports.isMarkup = exports.isLiteral = exports.isFunctionAnnotation = exports.isExpression = exports.isCatchallKey = exports.stringifyMessage = exports.parseMessage = exports.cst = exports.messageFromCST = exports.stringifyCST = exports.parseCST = void 0; | ||
exports.MessageFormat = exports.MessageSyntaxError = exports.MessageSelectionError = exports.MessageResolutionError = exports.MessageError = exports.MessageDataModelError = exports.visit = exports.validate = exports.isVariableRef = exports.isSelectMessage = exports.isPatternMessage = exports.isMessage = exports.isMarkup = exports.isLiteral = exports.isFunctionRef = exports.isExpression = exports.isCatchallKey = exports.stringifyMessage = exports.parseMessage = exports.cst = exports.messageFromCST = exports.stringifyCST = exports.parseCST = void 0; | ||
var parse_cst_js_1 = require("./cst/parse-cst.js"); | ||
@@ -18,3 +18,3 @@ Object.defineProperty(exports, "parseCST", { enumerable: true, get: function () { return parse_cst_js_1.parseCST; } }); | ||
Object.defineProperty(exports, "isExpression", { enumerable: true, get: function () { return type_guards_js_1.isExpression; } }); | ||
Object.defineProperty(exports, "isFunctionAnnotation", { enumerable: true, get: function () { return type_guards_js_1.isFunctionAnnotation; } }); | ||
Object.defineProperty(exports, "isFunctionRef", { enumerable: true, get: function () { return type_guards_js_1.isFunctionRef; } }); | ||
Object.defineProperty(exports, "isLiteral", { enumerable: true, get: function () { return type_guards_js_1.isLiteral; } }); | ||
@@ -25,3 +25,2 @@ Object.defineProperty(exports, "isMarkup", { enumerable: true, get: function () { return type_guards_js_1.isMarkup; } }); | ||
Object.defineProperty(exports, "isSelectMessage", { enumerable: true, get: function () { return type_guards_js_1.isSelectMessage; } }); | ||
Object.defineProperty(exports, "isUnsupportedAnnotation", { enumerable: true, get: function () { return type_guards_js_1.isUnsupportedAnnotation; } }); | ||
Object.defineProperty(exports, "isVariableRef", { enumerable: true, get: function () { return type_guards_js_1.isVariableRef; } }); | ||
@@ -28,0 +27,0 @@ var validate_js_1 = require("./data-model/validate.js"); |
@@ -6,3 +6,3 @@ import type { MessageFunctionContext } from './data-model/function-context.js'; | ||
/** | ||
* The runtime function registry available when resolving {@link FunctionAnnotation} elements. | ||
* The runtime function registry available when resolving {@link FunctionRef} elements. | ||
* | ||
@@ -38,3 +38,3 @@ * @beta | ||
#private; | ||
constructor(source: string | Message, locales?: string | string[], options?: MessageFormatOptions); | ||
constructor(locales: string | string[] | undefined, source: string | Message, options?: MessageFormatOptions); | ||
format(msgParams?: Record<string, unknown>, onError?: (error: unknown) => void): string; | ||
@@ -45,6 +45,4 @@ formatToParts(msgParams?: Record<string, unknown>, onError?: (error: unknown) => void): MessagePart[]; | ||
localeMatcher: "lookup" | "best fit"; | ||
locales: string[]; | ||
message: Readonly<Message>; | ||
}; | ||
private createContext; | ||
} |
@@ -30,3 +30,3 @@ "use strict"; | ||
#functions; | ||
constructor(source, locales, options) { | ||
constructor(locales, source, options) { | ||
this.#localeMatcher = options?.localeMatcher ?? 'best fit'; | ||
@@ -106,5 +106,3 @@ this.#locales = Array.isArray(locales) | ||
functions: Object.freeze(this.#functions), | ||
localeMatcher: this.#localeMatcher, | ||
locales: this.#locales.slice(), | ||
message: Object.freeze(this.#message) | ||
localeMatcher: this.#localeMatcher | ||
}; | ||
@@ -123,15 +121,3 @@ } | ||
for (const decl of this.#message.declarations) { | ||
switch (decl.type) { | ||
case 'input': | ||
scope[decl.name] = new resolve_variable_js_1.UnresolvedExpression(decl.value, msgParams); | ||
break; | ||
case 'local': | ||
scope[decl.name] = new resolve_variable_js_1.UnresolvedExpression(decl.value); | ||
break; | ||
default: { | ||
const source = decl.keyword ?? '�'; | ||
const msg = `Reserved ${source} annotation is not supported`; | ||
onError(new errors_js_1.MessageResolutionError('unsupported-statement', msg, source)); | ||
} | ||
} | ||
scope[decl.name] = new resolve_variable_js_1.UnresolvedExpression(decl.value, decl.type === 'input' ? msgParams ?? {} : undefined); | ||
} | ||
@@ -138,0 +124,0 @@ const ctx = { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.selectPattern = void 0; | ||
const resolve_expression_js_1 = require("./data-model/resolve-expression.js"); | ||
const resolve_variable_js_1 = require("./data-model/resolve-variable.js"); | ||
const errors_js_1 = require("./errors.js"); | ||
@@ -12,3 +12,3 @@ function selectPattern(context, message) { | ||
const ctx = message.selectors.map(sel => { | ||
const selector = (0, resolve_expression_js_1.resolveExpression)(context, sel); | ||
const selector = (0, resolve_variable_js_1.resolveVariableRef)(context, sel); | ||
let selectKey; | ||
@@ -19,3 +19,3 @@ if (typeof selector.selectKey === 'function') { | ||
else { | ||
context.onError(new errors_js_1.MessageSelectionError('not-selectable')); | ||
context.onError(new errors_js_1.MessageSelectionError('bad-selector')); | ||
selectKey = () => null; | ||
@@ -42,3 +42,10 @@ } | ||
} | ||
sc.best = sc.keys.size ? sc.selectKey(sc.keys) : null; | ||
try { | ||
sc.best = sc.keys.size ? sc.selectKey(sc.keys) : null; | ||
} | ||
catch (error) { | ||
context.onError(new errors_js_1.MessageSelectionError('bad-selector', error)); | ||
sc.selectKey = () => null; | ||
sc.best = null; | ||
} | ||
// Leave out all candidate variants that aren't the best, | ||
@@ -78,3 +85,3 @@ // or only the catchall ones, if nothing else matches. | ||
default: | ||
context.onError(new errors_js_1.MessageSelectionError('not-selectable')); | ||
context.onError(new errors_js_1.MessageSelectionError('bad-selector')); | ||
return []; | ||
@@ -81,0 +88,0 @@ } |
{ | ||
"name": "messageformat", | ||
"version": "4.0.0-7", | ||
"version": "4.0.0-8", | ||
"description": "Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill", | ||
@@ -21,3 +21,3 @@ "keywords": [ | ||
"type": "git", | ||
"url": "https://github.com/messageformat/messageformat.git", | ||
"url": "git+https://github.com/messageformat/messageformat.git", | ||
"directory": "packages/mf2-messageformat" | ||
@@ -24,0 +24,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
143958
73
3731