fluent-syntax
Advanced tools
Comparing version 0.11.0 to 0.12.0
# Changelog | ||
## fluent-syntax 0.12.0 (March 26, 2019) | ||
This release of `fluent-syntax` brings support for version 0.9 of the Fluent | ||
Syntax spec. The API remains unchanged. Files written in valid Syntax 0.8 may | ||
parse differently in this release. See the compatibility note below. Consult | ||
the full Syntax 0.9 [changelog][chlog0.9] for details. | ||
[chlog0.9]: https://github.com/projectfluent/fluent/releases/tag/v0.9.0 | ||
- Flatten complex reference expressions. | ||
Reference expressions which may take complex forms, such as a reference | ||
to a message's attribute, or a parameterized reference to an attribute of | ||
a term, are now stored in a simplified manner. Instead of nesting | ||
multiple expression nodes (e.g. `CallExpression` of an | ||
`AttributeExpression` of a `TermReference`), all information is available | ||
directly in the reference expression. | ||
This change affects the following AST nodes: | ||
- `MessageReference` now has an optional `attribute` field, | ||
- `FunctionReference` now has a required `arguments` field, | ||
- `TermReference` now has an optional `attribute` field and an optional | ||
`arguments` field. | ||
- Remove `VariantLists`. | ||
The `VariantLists` and the `VariantExpression` syntax and AST nodes were | ||
deprecated in Syntax 0.9 and have now been removed. | ||
- Rename `StringLiteral.raw` to `value`. | ||
`StringLiteral.value` contains the exact contents of the string literal, | ||
character-for-character. Escape sequences are stored verbatim without | ||
processing. A new method, `Literal.parse`, can be used to process the raw | ||
value of the literal into an unescaped form. | ||
- Rename `args` to `arguments`. | ||
The `args` field of `MessageReference`, `TermReference`, | ||
`FunctionReference`, and `Annotation` has been renamed to `arguments`. | ||
### Backward-incompatible changes: | ||
- `VariantLists` are no longer valid syntax. A syntax error is reported | ||
when a `VariantList` or a `VariantExpression` is found in the parsed file. | ||
## fluent-syntax 0.11.0 (March 25, 2019) | ||
@@ -4,0 +52,0 @@ |
576
compat.js
@@ -1,2 +0,2 @@ | ||
/* fluent-syntax@0.11.0 */ | ||
/* fluent-syntax@0.12.0 */ | ||
(function (global, factory) { | ||
@@ -8,40 +8,2 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
function _slicedToArray(arr, i) { | ||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
} | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArrayLimit(arr, i) { | ||
var _arr = []; | ||
var _n = true; | ||
var _d = false; | ||
var _e = undefined; | ||
try { | ||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { | ||
_arr.push(_s.value); | ||
if (i && _arr.length === i) break; | ||
} | ||
} catch (err) { | ||
_d = true; | ||
_e = err; | ||
} finally { | ||
try { | ||
if (!_n && _i["return"] != null) _i["return"](); | ||
} finally { | ||
if (_d) throw _e; | ||
} | ||
} | ||
return _arr; | ||
} | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance"); | ||
} | ||
/* | ||
@@ -231,10 +193,2 @@ * Base class for all Fluent AST nodes. | ||
} | ||
class VariantList extends SyntaxNode { | ||
constructor(variants) { | ||
super(); | ||
this.type = "VariantList"; | ||
this.variants = variants; | ||
} | ||
} | ||
class Pattern extends SyntaxNode { | ||
@@ -273,25 +227,83 @@ constructor(elements) { | ||
class Expression extends SyntaxNode {} | ||
class StringLiteral extends Expression { | ||
constructor(raw, value) { | ||
super(); | ||
this.type = "StringLiteral"; | ||
this.raw = raw; | ||
class Expression extends SyntaxNode {} // An abstract base class for Literals. | ||
class Literal extends Expression { | ||
constructor(value) { | ||
super(); // The "value" field contains the exact contents of the literal, | ||
// character-for-character. | ||
this.value = value; | ||
} | ||
parse() { | ||
return { | ||
value: this.value | ||
}; | ||
} | ||
} | ||
class NumberLiteral extends Expression { | ||
class StringLiteral extends Literal { | ||
constructor(value) { | ||
super(); | ||
super(value); | ||
this.type = "StringLiteral"; | ||
} | ||
parse() { | ||
// Backslash backslash, backslash double quote, uHHHH, UHHHHHH. | ||
const KNOWN_ESCAPES = /(?:\\\\|\\"|\\u([0-9a-fA-F]{4})|\\U([0-9a-fA-F]{6}))/g; | ||
function from_escape_sequence(match, codepoint4, codepoint6) { | ||
switch (match) { | ||
case "\\\\": | ||
return "\\"; | ||
case "\\\"": | ||
return "\""; | ||
default: | ||
let codepoint = parseInt(codepoint4 || codepoint6, 16); | ||
if (codepoint <= 0xD7FF || 0xE000 <= codepoint) { | ||
// It's a Unicode scalar value. | ||
return String.fromCodePoint(codepoint); | ||
} // Escape sequences reresenting surrogate code points are | ||
// well-formed but invalid in Fluent. Replace them with U+FFFD | ||
// REPLACEMENT CHARACTER. | ||
return "�"; | ||
} | ||
} | ||
let value = this.value.replace(KNOWN_ESCAPES, from_escape_sequence); | ||
return { | ||
value | ||
}; | ||
} | ||
} | ||
class NumberLiteral extends Literal { | ||
constructor(value) { | ||
super(value); | ||
this.type = "NumberLiteral"; | ||
this.value = value; | ||
} | ||
parse() { | ||
let value = parseFloat(this.value); | ||
let decimal_position = this.value.indexOf("."); | ||
let precision = decimal_position > 0 ? this.value.length - decimal_position - 1 : 0; | ||
return { | ||
value, | ||
precision | ||
}; | ||
} | ||
} | ||
class MessageReference extends Expression { | ||
constructor(id) { | ||
let attribute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
super(); | ||
this.type = "MessageReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
} | ||
@@ -302,5 +314,9 @@ | ||
constructor(id) { | ||
let attribute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
let args = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; | ||
super(); | ||
this.type = "TermReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
this.arguments = args; | ||
} | ||
@@ -318,6 +334,7 @@ | ||
class FunctionReference extends Expression { | ||
constructor(id) { | ||
constructor(id, args) { | ||
super(); | ||
this.type = "FunctionReference"; | ||
this.id = id; | ||
this.arguments = args; | ||
} | ||
@@ -335,27 +352,8 @@ | ||
} | ||
class AttributeExpression extends Expression { | ||
constructor(ref, name) { | ||
class CallArguments extends SyntaxNode { | ||
constructor() { | ||
let positional = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
let named = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
super(); | ||
this.type = "AttributeExpression"; | ||
this.ref = ref; | ||
this.name = name; | ||
} | ||
} | ||
class VariantExpression extends Expression { | ||
constructor(ref, key) { | ||
super(); | ||
this.type = "VariantExpression"; | ||
this.ref = ref; | ||
this.key = key; | ||
} | ||
} | ||
class CallExpression extends Expression { | ||
constructor(callee) { | ||
let positional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; | ||
let named = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; | ||
super(); | ||
this.type = "CallExpression"; | ||
this.callee = callee; | ||
this.type = "CallArguments"; | ||
this.positional = positional; | ||
@@ -461,3 +459,3 @@ this.named = named; | ||
this.code = code; | ||
this.args = args; | ||
this.arguments = args; | ||
this.message = message; | ||
@@ -468,2 +466,40 @@ } | ||
function _slicedToArray(arr, i) { | ||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
} | ||
function _arrayWithHoles(arr) { | ||
if (Array.isArray(arr)) return arr; | ||
} | ||
function _iterableToArrayLimit(arr, i) { | ||
var _arr = []; | ||
var _n = true; | ||
var _d = false; | ||
var _e = undefined; | ||
try { | ||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { | ||
_arr.push(_s.value); | ||
if (i && _arr.length === i) break; | ||
} | ||
} catch (err) { | ||
_d = true; | ||
_e = err; | ||
} finally { | ||
try { | ||
if (!_n && _i["return"] != null) _i["return"](); | ||
} finally { | ||
if (_d) throw _e; | ||
} | ||
} | ||
return _arr; | ||
} | ||
function _nonIterableRest() { | ||
throw new TypeError("Invalid attempt to destructure non-iterable instance"); | ||
} | ||
class ParseError extends Error { | ||
@@ -532,3 +568,3 @@ constructor(code) { | ||
case "E0009": | ||
return "The key has to be a simple identifier"; | ||
return "The argument name has to be a simple identifier"; | ||
@@ -959,2 +995,3 @@ case "E0010": | ||
/* eslint no-magic-numbers: [0] */ | ||
const trailingWSRe = /[ \t\n\r]+$/; | ||
@@ -994,3 +1031,3 @@ | ||
const methodNames = ["getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", "getVariant", "getNumber", "getPattern", "getVariantList", "getTextElement", "getPlaceable", "getExpression", "getInlineExpression", "getCallArgument", "getString", "getSimpleExpression", "getLiteral"]; | ||
const methodNames = ["getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", "getVariant", "getNumber", "getPattern", "getTextElement", "getPlaceable", "getExpression", "getInlineExpression", "getCallArgument", "getCallArguments", "getString", "getLiteral"]; | ||
@@ -1204,7 +1241,5 @@ for (var _i = 0; _i < methodNames.length; _i++) { | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); // Syntax 0.8 compat: VariantLists are supported but deprecated. They can | ||
// only be found as values of Terms. Nested VariantLists are not allowed. | ||
ps.expectChar("="); | ||
const value = this.maybeGetPattern(ps); | ||
const value = this.maybeGetVariantList(ps) || this.maybeGetPattern(ps); | ||
if (value === null) { | ||
@@ -1347,18 +1382,17 @@ throw new ParseError("E0006", id.name); | ||
getNumber(ps) { | ||
let num = ""; | ||
let value = ""; | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
ps.next(); | ||
value += `-${this.getDigits(ps)}`; | ||
} else { | ||
value += this.getDigits(ps); | ||
} | ||
num = `${num}${this.getDigits(ps)}`; | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
ps.next(); | ||
num = `${num}${this.getDigits(ps)}`; | ||
value += `.${this.getDigits(ps)}`; | ||
} | ||
return new NumberLiteral(num); | ||
return new NumberLiteral(value); | ||
} // maybeGetPattern distinguishes between patterns which start on the same line | ||
@@ -1392,37 +1426,4 @@ // as the identifier (a.k.a. inline signleline patterns and inline multiline | ||
return null; | ||
} // Deprecated in Syntax 0.8. VariantLists are only allowed as values of Terms. | ||
// Values of Messages, Attributes and Variants must be Patterns. This method | ||
// is only used in getTerm. | ||
maybeGetVariantList(ps) { | ||
ps.peekBlank(); | ||
if (ps.currentPeek === "{") { | ||
const start = ps.peekOffset; | ||
ps.peek(); | ||
ps.peekBlankInline(); | ||
if (ps.currentPeek === EOL) { | ||
ps.peekBlank(); | ||
if (ps.isVariantStart()) { | ||
ps.resetPeek(start); | ||
ps.skipToPeek(); | ||
return this.getVariantList(ps); | ||
} | ||
} | ||
} | ||
ps.resetPeek(); | ||
return null; | ||
} | ||
getVariantList(ps) { | ||
ps.expectChar("{"); | ||
var variants = this.getVariants(ps); | ||
ps.expectChar("}"); | ||
return new VariantList(variants); | ||
} | ||
getPattern(ps, _ref3) { | ||
@@ -1607,3 +1608,3 @@ let isBlock = _ref3.isBlock; | ||
ps.next(); | ||
return [`\\${next}`, next]; | ||
return `\\${next}`; | ||
@@ -1635,9 +1636,3 @@ case "u": | ||
const codepoint = parseInt(sequence, 16); | ||
const unescaped = codepoint <= 0xD7FF || 0xE000 <= codepoint // It's a Unicode scalar value. | ||
? String.fromCodePoint(codepoint) // Escape sequences reresenting surrogate code points are well-formed | ||
// but invalid in Fluent. Replace them with U+FFFD REPLACEMENT | ||
// CHARACTER. | ||
: "�"; | ||
return [`\\${u}${sequence}`, unescaped]; | ||
return `\\${u}${sequence}`; | ||
} | ||
@@ -1664,17 +1659,13 @@ | ||
if (selector.type === "MessageReference") { | ||
throw new ParseError("E0016"); | ||
if (selector.attribute === null) { | ||
throw new ParseError("E0016"); | ||
} else { | ||
throw new ParseError("E0018"); | ||
} | ||
} | ||
if (selector.type === "AttributeExpression" && selector.ref.type === "MessageReference") { | ||
throw new ParseError("E0018"); | ||
} | ||
if (selector.type === "TermReference" || selector.type === "VariantExpression") { | ||
if (selector.type === "TermReference" && selector.attribute === null) { | ||
throw new ParseError("E0017"); | ||
} | ||
if (selector.type === "CallExpression" && selector.callee.type === "TermReference") { | ||
throw new ParseError("E0017"); | ||
} | ||
ps.next(); | ||
@@ -1688,10 +1679,6 @@ ps.next(); | ||
if (selector.type === "AttributeExpression" && selector.ref.type === "TermReference") { | ||
if (selector.type === "TermReference" && selector.attribute !== null) { | ||
throw new ParseError("E0019"); | ||
} | ||
if (selector.type === "CallExpression" && selector.callee.type === "AttributeExpression") { | ||
throw new ParseError("E0019"); | ||
} | ||
return selector; | ||
@@ -1705,64 +1692,2 @@ } | ||
let expr = this.getSimpleExpression(ps); | ||
switch (expr.type) { | ||
case "NumberLiteral": | ||
case "StringLiteral": | ||
case "VariableReference": | ||
return expr; | ||
case "MessageReference": | ||
{ | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
return new AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z_?-]*$/.test(expr.id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
const func = new FunctionReference(expr.id); | ||
if (this.withSpans) { | ||
func.addSpan(expr.span.start, expr.span.end); | ||
} | ||
return new CallExpression(func, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
case "TermReference": | ||
{ | ||
if (ps.currentChar === "[") { | ||
ps.next(); | ||
const key = this.getVariantKey(ps); | ||
ps.expectChar("]"); | ||
return new VariantExpression(expr, key); | ||
} | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
expr = new AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
return new CallExpression(expr, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
default: | ||
throw new ParseError("E0028"); | ||
} | ||
} | ||
getSimpleExpression(ps) { | ||
if (ps.isNumberStart()) { | ||
@@ -1785,3 +1710,16 @@ return this.getNumber(ps); | ||
const id = this.getIdentifier(ps); | ||
return new TermReference(id); | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
let args; | ||
if (ps.currentChar === "(") { | ||
args = this.getCallArguments(ps); | ||
} | ||
return new TermReference(id, attr, args); | ||
} | ||
@@ -1791,3 +1729,21 @@ | ||
const id = this.getIdentifier(ps); | ||
return new MessageReference(id); | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z0-9_-]*$/.test(id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
let args = this.getCallArguments(ps); | ||
return new FunctionReference(id, args); | ||
} | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
return new MessageReference(id, attr); | ||
} | ||
@@ -1806,10 +1762,10 @@ | ||
if (exp.type !== "MessageReference") { | ||
throw new ParseError("E0009"); | ||
if (exp.type === "MessageReference" && exp.attribute === null) { | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new NamedArgument(exp.id, value); | ||
} | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new NamedArgument(exp.id, value); | ||
throw new ParseError("E0009"); | ||
} | ||
@@ -1856,9 +1812,8 @@ | ||
ps.expectChar(")"); | ||
return [positional, named]; | ||
return new CallArguments(positional, named); | ||
} | ||
getString(ps) { | ||
let raw = ""; | ||
ps.expectChar("\""); | ||
let value = ""; | ||
ps.expectChar("\""); | ||
let ch; | ||
@@ -1868,11 +1823,4 @@ | ||
if (ch === "\\") { | ||
const _this$getEscapeSequen = this.getEscapeSequence(ps), | ||
_this$getEscapeSequen2 = _slicedToArray(_this$getEscapeSequen, 2), | ||
sequence = _this$getEscapeSequen2[0], | ||
unescaped = _this$getEscapeSequen2[1]; | ||
raw += sequence; | ||
value += unescaped; | ||
value += this.getEscapeSequence(ps); | ||
} else { | ||
raw += ch; | ||
value += ch; | ||
@@ -1887,3 +1835,3 @@ } | ||
ps.expectChar("\""); | ||
return new StringLiteral(raw, value); | ||
return new StringLiteral(value); | ||
} | ||
@@ -2030,3 +1978,3 @@ | ||
if (message.value) { | ||
parts.push(serializeValue(message.value)); | ||
parts.push(serializePattern(message.value)); | ||
} | ||
@@ -2070,3 +2018,3 @@ | ||
parts.push(`-${term.id.name} =`); | ||
parts.push(serializeValue(term.value)); | ||
parts.push(serializePattern(term.value)); | ||
var _iteratorNormalCompletion3 = true; | ||
@@ -2101,19 +2049,6 @@ var _didIteratorError3 = false; | ||
function serializeAttribute(attribute) { | ||
const value = indent(serializeValue(attribute.value)); | ||
const value = indent(serializePattern(attribute.value)); | ||
return `\n .${attribute.id.name} =${value}`; | ||
} | ||
function serializeValue(value) { | ||
switch (value.type) { | ||
case "Pattern": | ||
return serializePattern(value); | ||
case "VariantList": | ||
return serializeVariantList(value); | ||
default: | ||
throw new Error(`Unknown value type: ${value.type}`); | ||
} | ||
} | ||
function serializePattern(pattern) { | ||
@@ -2130,18 +2065,2 @@ const content = pattern.elements.map(serializeElement).join(""); | ||
function serializeVariantList(varlist) { | ||
const content = varlist.variants.map(serializeVariant).join(""); | ||
return `\n {${indent(content)}\n }`; | ||
} | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializeValue(variant.value)); | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeElement(element) { | ||
@@ -2170,3 +2089,3 @@ switch (element.type) { | ||
// opening and the closing brace. | ||
return `{ ${serializeSelectExpression(expr)}}`; | ||
return `{ ${serializeExpression(expr)}}`; | ||
@@ -2181,3 +2100,3 @@ default: | ||
case "StringLiteral": | ||
return `"${expr.raw}"`; | ||
return `"${expr.value}"`; | ||
@@ -2187,24 +2106,64 @@ case "NumberLiteral": | ||
case "MessageReference": | ||
case "FunctionReference": | ||
return expr.id.name; | ||
case "VariableReference": | ||
return `$${expr.id.name}`; | ||
case "TermReference": | ||
return `-${expr.id.name}`; | ||
{ | ||
let out = `-${expr.id.name}`; | ||
case "VariableReference": | ||
return `$${expr.id.name}`; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
case "AttributeExpression": | ||
return serializeAttributeExpression(expr); | ||
if (expr.arguments) { | ||
out += serializeCallArguments(expr.arguments); | ||
} | ||
case "VariantExpression": | ||
return serializeVariantExpression(expr); | ||
return out; | ||
} | ||
case "CallExpression": | ||
return serializeCallExpression(expr); | ||
case "MessageReference": | ||
{ | ||
let out = expr.id.name; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
return out; | ||
} | ||
case "FunctionReference": | ||
return `${expr.id.name}${serializeCallArguments(expr.arguments)}`; | ||
case "SelectExpression": | ||
return serializeSelectExpression(expr); | ||
{ | ||
let out = `${serializeExpression(expr.selector)} ->`; | ||
var _iteratorNormalCompletion4 = true; | ||
var _didIteratorError4 = false; | ||
var _iteratorError4 = undefined; | ||
try { | ||
for (var _iterator4 = expr.variants[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
let variant = _step4.value; | ||
out += serializeVariant(variant); | ||
} | ||
} catch (err) { | ||
_didIteratorError4 = true; | ||
_iteratorError4 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion4 && _iterator4.return != null) { | ||
_iterator4.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError4) { | ||
throw _iteratorError4; | ||
} | ||
} | ||
} | ||
return `${out}\n`; | ||
} | ||
case "Placeable": | ||
@@ -2218,47 +2177,14 @@ return serializePlaceable(expr); | ||
function serializeSelectExpression(expr) { | ||
const parts = []; | ||
const selector = `${serializeExpression(expr.selector)} ->`; | ||
parts.push(selector); | ||
var _iteratorNormalCompletion4 = true; | ||
var _didIteratorError4 = false; | ||
var _iteratorError4 = undefined; | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializePattern(variant.value)); | ||
try { | ||
for (var _iterator4 = expr.variants[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
const variant = _step4.value; | ||
parts.push(serializeVariant(variant)); | ||
} | ||
} catch (err) { | ||
_didIteratorError4 = true; | ||
_iteratorError4 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion4 && _iterator4.return != null) { | ||
_iterator4.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError4) { | ||
throw _iteratorError4; | ||
} | ||
} | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
parts.push("\n"); | ||
return parts.join(""); | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeAttributeExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
return `${ref}.${expr.name.name}`; | ||
} | ||
function serializeVariantExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
const key = serializeVariantKey(expr.key); | ||
return `${ref}[${key}]`; | ||
} | ||
function serializeCallExpression(expr) { | ||
const callee = serializeExpression(expr.callee); | ||
function serializeCallArguments(expr) { | ||
const positional = expr.positional.map(serializeExpression).join(", "); | ||
@@ -2268,6 +2194,6 @@ const named = expr.named.map(serializeNamedArgument).join(", "); | ||
if (expr.positional.length > 0 && expr.named.length > 0) { | ||
return `${callee}(${positional}, ${named})`; | ||
return `(${positional}, ${named})`; | ||
} | ||
return `${callee}(${positional || named})`; | ||
return `(${positional || named})`; | ||
} | ||
@@ -2400,3 +2326,2 @@ | ||
exports.Term = Term; | ||
exports.VariantList = VariantList; | ||
exports.Pattern = Pattern; | ||
@@ -2407,2 +2332,3 @@ exports.PatternElement = PatternElement; | ||
exports.Expression = Expression; | ||
exports.Literal = Literal; | ||
exports.StringLiteral = StringLiteral; | ||
@@ -2415,5 +2341,3 @@ exports.NumberLiteral = NumberLiteral; | ||
exports.SelectExpression = SelectExpression; | ||
exports.AttributeExpression = AttributeExpression; | ||
exports.VariantExpression = VariantExpression; | ||
exports.CallExpression = CallExpression; | ||
exports.CallArguments = CallArguments; | ||
exports.Attribute = Attribute; | ||
@@ -2420,0 +2344,0 @@ exports.Variant = Variant; |
@@ -1,2 +0,2 @@ | ||
/* fluent-syntax@0.11.0 */ | ||
/* fluent-syntax@0.12.0 */ | ||
(function (global, factory) { | ||
@@ -124,10 +124,2 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
class VariantList extends SyntaxNode { | ||
constructor(variants) { | ||
super(); | ||
this.type = "VariantList"; | ||
this.variants = variants; | ||
} | ||
} | ||
class Pattern extends SyntaxNode { | ||
@@ -167,24 +159,73 @@ constructor(elements) { | ||
class StringLiteral extends Expression { | ||
constructor(raw, value) { | ||
// An abstract base class for Literals. | ||
class Literal extends Expression { | ||
constructor(value) { | ||
super(); | ||
this.type = "StringLiteral"; | ||
this.raw = raw; | ||
// The "value" field contains the exact contents of the literal, | ||
// character-for-character. | ||
this.value = value; | ||
} | ||
parse() { | ||
return {value: this.value}; | ||
} | ||
} | ||
class NumberLiteral extends Expression { | ||
class StringLiteral extends Literal { | ||
constructor(value) { | ||
super(); | ||
super(value); | ||
this.type = "StringLiteral"; | ||
} | ||
parse() { | ||
// Backslash backslash, backslash double quote, uHHHH, UHHHHHH. | ||
const KNOWN_ESCAPES = | ||
/(?:\\\\|\\"|\\u([0-9a-fA-F]{4})|\\U([0-9a-fA-F]{6}))/g; | ||
function from_escape_sequence(match, codepoint4, codepoint6) { | ||
switch (match) { | ||
case "\\\\": | ||
return "\\"; | ||
case "\\\"": | ||
return "\""; | ||
default: | ||
let codepoint = parseInt(codepoint4 || codepoint6, 16); | ||
if (codepoint <= 0xD7FF || 0xE000 <= codepoint) { | ||
// It's a Unicode scalar value. | ||
return String.fromCodePoint(codepoint); | ||
} | ||
// Escape sequences reresenting surrogate code points are | ||
// well-formed but invalid in Fluent. Replace them with U+FFFD | ||
// REPLACEMENT CHARACTER. | ||
return "�"; | ||
} | ||
} | ||
let value = this.value.replace(KNOWN_ESCAPES, from_escape_sequence); | ||
return {value}; | ||
} | ||
} | ||
class NumberLiteral extends Literal { | ||
constructor(value) { | ||
super(value); | ||
this.type = "NumberLiteral"; | ||
this.value = value; | ||
} | ||
parse() { | ||
let value = parseFloat(this.value); | ||
let decimal_position = this.value.indexOf("."); | ||
let precision = decimal_position > 0 | ||
? this.value.length - decimal_position - 1 | ||
: 0; | ||
return {value, precision}; | ||
} | ||
} | ||
class MessageReference extends Expression { | ||
constructor(id) { | ||
constructor(id, attribute = null) { | ||
super(); | ||
this.type = "MessageReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
} | ||
@@ -194,6 +235,8 @@ } | ||
class TermReference extends Expression { | ||
constructor(id) { | ||
constructor(id, attribute = null, args = null) { | ||
super(); | ||
this.type = "TermReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
this.arguments = args; | ||
} | ||
@@ -211,6 +254,7 @@ } | ||
class FunctionReference extends Expression { | ||
constructor(id) { | ||
constructor(id, args) { | ||
super(); | ||
this.type = "FunctionReference"; | ||
this.id = id; | ||
this.arguments = args; | ||
} | ||
@@ -228,25 +272,6 @@ } | ||
class AttributeExpression extends Expression { | ||
constructor(ref, name) { | ||
class CallArguments extends SyntaxNode { | ||
constructor(positional = [], named = []) { | ||
super(); | ||
this.type = "AttributeExpression"; | ||
this.ref = ref; | ||
this.name = name; | ||
} | ||
} | ||
class VariantExpression extends Expression { | ||
constructor(ref, key) { | ||
super(); | ||
this.type = "VariantExpression"; | ||
this.ref = ref; | ||
this.key = key; | ||
} | ||
} | ||
class CallExpression extends Expression { | ||
constructor(callee, positional = [], named = []) { | ||
super(); | ||
this.type = "CallExpression"; | ||
this.callee = callee; | ||
this.type = "CallArguments"; | ||
this.positional = positional; | ||
@@ -348,3 +373,3 @@ this.named = named; | ||
this.code = code; | ||
this.args = args; | ||
this.arguments = args; | ||
this.message = message; | ||
@@ -391,3 +416,3 @@ } | ||
case "E0009": | ||
return "The key has to be a simple identifier"; | ||
return "The argument name has to be a simple identifier"; | ||
case "E0010": | ||
@@ -812,6 +837,5 @@ return "Expected one of the variants to be marked as default (*)"; | ||
"getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", | ||
"getVariant", "getNumber", "getPattern", "getVariantList", | ||
"getTextElement", "getPlaceable", "getExpression", | ||
"getInlineExpression", "getCallArgument", "getString", | ||
"getSimpleExpression", "getLiteral", | ||
"getVariant", "getNumber", "getPattern", "getTextElement", | ||
"getPlaceable", "getExpression", "getInlineExpression", | ||
"getCallArgument", "getCallArguments", "getString", "getLiteral", | ||
]; | ||
@@ -1019,5 +1043,3 @@ for (const name of methodNames) { | ||
// Syntax 0.8 compat: VariantLists are supported but deprecated. They can | ||
// only be found as values of Terms. Nested VariantLists are not allowed. | ||
const value = this.maybeGetVariantList(ps) || this.maybeGetPattern(ps); | ||
const value = this.maybeGetPattern(ps); | ||
if (value === null) { | ||
@@ -1158,18 +1180,17 @@ throw new ParseError("E0006", id.name); | ||
getNumber(ps) { | ||
let num = ""; | ||
let value = ""; | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
ps.next(); | ||
value += `-${this.getDigits(ps)}`; | ||
} else { | ||
value += this.getDigits(ps); | ||
} | ||
num = `${num}${this.getDigits(ps)}`; | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
ps.next(); | ||
num = `${num}${this.getDigits(ps)}`; | ||
value += `.${this.getDigits(ps)}`; | ||
} | ||
return new NumberLiteral(num); | ||
return new NumberLiteral(value); | ||
} | ||
@@ -1199,32 +1220,2 @@ | ||
// Deprecated in Syntax 0.8. VariantLists are only allowed as values of Terms. | ||
// Values of Messages, Attributes and Variants must be Patterns. This method | ||
// is only used in getTerm. | ||
maybeGetVariantList(ps) { | ||
ps.peekBlank(); | ||
if (ps.currentPeek === "{") { | ||
const start = ps.peekOffset; | ||
ps.peek(); | ||
ps.peekBlankInline(); | ||
if (ps.currentPeek === EOL) { | ||
ps.peekBlank(); | ||
if (ps.isVariantStart()) { | ||
ps.resetPeek(start); | ||
ps.skipToPeek(); | ||
return this.getVariantList(ps); | ||
} | ||
} | ||
} | ||
ps.resetPeek(); | ||
return null; | ||
} | ||
getVariantList(ps) { | ||
ps.expectChar("{"); | ||
var variants = this.getVariants(ps); | ||
ps.expectChar("}"); | ||
return new VariantList(variants); | ||
} | ||
getPattern(ps, {isBlock}) { | ||
@@ -1371,3 +1362,3 @@ const elements = []; | ||
ps.next(); | ||
return [`\\${next}`, next]; | ||
return `\\${next}`; | ||
case "u": | ||
@@ -1397,11 +1388,3 @@ return this.getUnicodeEscapeSequence(ps, next, 4); | ||
const codepoint = parseInt(sequence, 16); | ||
const unescaped = codepoint <= 0xD7FF || 0xE000 <= codepoint | ||
// It's a Unicode scalar value. | ||
? String.fromCodePoint(codepoint) | ||
// Escape sequences reresenting surrogate code points are well-formed | ||
// but invalid in Fluent. Replace them with U+FFFD REPLACEMENT | ||
// CHARACTER. | ||
: "�"; | ||
return [`\\${u}${sequence}`, unescaped]; | ||
return `\\${u}${sequence}`; | ||
} | ||
@@ -1428,20 +1411,13 @@ | ||
if (selector.type === "MessageReference") { | ||
throw new ParseError("E0016"); | ||
if (selector.attribute === null) { | ||
throw new ParseError("E0016"); | ||
} else { | ||
throw new ParseError("E0018"); | ||
} | ||
} | ||
if (selector.type === "AttributeExpression" | ||
&& selector.ref.type === "MessageReference") { | ||
throw new ParseError("E0018"); | ||
} | ||
if (selector.type === "TermReference" | ||
|| selector.type === "VariantExpression") { | ||
if (selector.type === "TermReference" && selector.attribute === null) { | ||
throw new ParseError("E0017"); | ||
} | ||
if (selector.type === "CallExpression" | ||
&& selector.callee.type === "TermReference") { | ||
throw new ParseError("E0017"); | ||
} | ||
ps.next(); | ||
@@ -1457,12 +1433,6 @@ ps.next(); | ||
if (selector.type === "AttributeExpression" | ||
&& selector.ref.type === "TermReference") { | ||
if (selector.type === "TermReference" && selector.attribute !== null) { | ||
throw new ParseError("E0019"); | ||
} | ||
if (selector.type === "CallExpression" | ||
&& selector.callee.type === "AttributeExpression") { | ||
throw new ParseError("E0019"); | ||
} | ||
return selector; | ||
@@ -1476,56 +1446,2 @@ } | ||
let expr = this.getSimpleExpression(ps); | ||
switch (expr.type) { | ||
case "NumberLiteral": | ||
case "StringLiteral": | ||
case "VariableReference": | ||
return expr; | ||
case "MessageReference": { | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
return new AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z_?-]*$/.test(expr.id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
const func = new FunctionReference(expr.id); | ||
if (this.withSpans) { | ||
func.addSpan(expr.span.start, expr.span.end); | ||
} | ||
return new CallExpression(func, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
case "TermReference": { | ||
if (ps.currentChar === "[") { | ||
ps.next(); | ||
const key = this.getVariantKey(ps); | ||
ps.expectChar("]"); | ||
return new VariantExpression(expr, key); | ||
} | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
expr = new AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
return new CallExpression(expr, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
default: | ||
throw new ParseError("E0028"); | ||
} | ||
} | ||
getSimpleExpression(ps) { | ||
if (ps.isNumberStart()) { | ||
@@ -1548,3 +1464,15 @@ return this.getNumber(ps); | ||
const id = this.getIdentifier(ps); | ||
return new TermReference(id); | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
let args; | ||
if (ps.currentChar === "(") { | ||
args = this.getCallArguments(ps); | ||
} | ||
return new TermReference(id, attr, args); | ||
} | ||
@@ -1554,5 +1482,23 @@ | ||
const id = this.getIdentifier(ps); | ||
return new MessageReference(id); | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z0-9_-]*$/.test(id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
let args = this.getCallArguments(ps); | ||
return new FunctionReference(id, args); | ||
} | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
return new MessageReference(id, attr); | ||
} | ||
throw new ParseError("E0028"); | ||
@@ -1570,11 +1516,11 @@ } | ||
if (exp.type !== "MessageReference") { | ||
throw new ParseError("E0009"); | ||
if (exp.type === "MessageReference" && exp.attribute === null) { | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new NamedArgument(exp.id, value); | ||
} | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new NamedArgument(exp.id, value); | ||
throw new ParseError("E0009"); | ||
} | ||
@@ -1620,19 +1566,14 @@ | ||
ps.expectChar(")"); | ||
return [positional, named]; | ||
return new CallArguments(positional, named); | ||
} | ||
getString(ps) { | ||
let raw = ""; | ||
ps.expectChar("\""); | ||
let value = ""; | ||
ps.expectChar("\""); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== EOL))) { | ||
if (ch === "\\") { | ||
const [sequence, unescaped] = this.getEscapeSequence(ps); | ||
raw += sequence; | ||
value += unescaped; | ||
value += this.getEscapeSequence(ps); | ||
} else { | ||
raw += ch; | ||
value += ch; | ||
@@ -1648,3 +1589,3 @@ } | ||
return new StringLiteral(raw, value); | ||
return new StringLiteral(value); | ||
} | ||
@@ -1759,3 +1700,3 @@ | ||
if (message.value) { | ||
parts.push(serializeValue(message.value)); | ||
parts.push(serializePattern(message.value)); | ||
} | ||
@@ -1780,3 +1721,3 @@ | ||
parts.push(`-${term.id.name} =`); | ||
parts.push(serializeValue(term.value)); | ||
parts.push(serializePattern(term.value)); | ||
@@ -1793,3 +1734,3 @@ for (const attribute of term.attributes) { | ||
function serializeAttribute(attribute) { | ||
const value = indent(serializeValue(attribute.value)); | ||
const value = indent(serializePattern(attribute.value)); | ||
return `\n .${attribute.id.name} =${value}`; | ||
@@ -1799,14 +1740,2 @@ } | ||
function serializeValue(value) { | ||
switch (value.type) { | ||
case "Pattern": | ||
return serializePattern(value); | ||
case "VariantList": | ||
return serializeVariantList(value); | ||
default: | ||
throw new Error(`Unknown value type: ${value.type}`); | ||
} | ||
} | ||
function serializePattern(pattern) { | ||
@@ -1826,20 +1755,2 @@ const content = pattern.elements.map(serializeElement).join(""); | ||
function serializeVariantList(varlist) { | ||
const content = varlist.variants.map(serializeVariant).join(""); | ||
return `\n {${indent(content)}\n }`; | ||
} | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializeValue(variant.value)); | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeElement(element) { | ||
@@ -1859,3 +1770,2 @@ switch (element.type) { | ||
const expr = placeable.expression; | ||
switch (expr.type) { | ||
@@ -1867,3 +1777,3 @@ case "Placeable": | ||
// opening and the closing brace. | ||
return `{ ${serializeSelectExpression(expr)}}`; | ||
return `{ ${serializeExpression(expr)}}`; | ||
default: | ||
@@ -1878,20 +1788,33 @@ return `{ ${serializeExpression(expr)} }`; | ||
case "StringLiteral": | ||
return `"${expr.raw}"`; | ||
return `"${expr.value}"`; | ||
case "NumberLiteral": | ||
return expr.value; | ||
case "MessageReference": | ||
case "FunctionReference": | ||
return expr.id.name; | ||
case "TermReference": | ||
return `-${expr.id.name}`; | ||
case "VariableReference": | ||
return `$${expr.id.name}`; | ||
case "AttributeExpression": | ||
return serializeAttributeExpression(expr); | ||
case "VariantExpression": | ||
return serializeVariantExpression(expr); | ||
case "CallExpression": | ||
return serializeCallExpression(expr); | ||
case "SelectExpression": | ||
return serializeSelectExpression(expr); | ||
case "TermReference": { | ||
let out = `-${expr.id.name}`; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
if (expr.arguments) { | ||
out += serializeCallArguments(expr.arguments); | ||
} | ||
return out; | ||
} | ||
case "MessageReference": { | ||
let out = expr.id.name; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
return out; | ||
} | ||
case "FunctionReference": | ||
return `${expr.id.name}${serializeCallArguments(expr.arguments)}`; | ||
case "SelectExpression": { | ||
let out = `${serializeExpression(expr.selector)} ->`; | ||
for (let variant of expr.variants) { | ||
out += serializeVariant(variant); | ||
} | ||
return `${out}\n`; | ||
} | ||
case "Placeable": | ||
@@ -1905,37 +1828,21 @@ return serializePlaceable(expr); | ||
function serializeSelectExpression(expr) { | ||
const parts = []; | ||
const selector = `${serializeExpression(expr.selector)} ->`; | ||
parts.push(selector); | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializePattern(variant.value)); | ||
for (const variant of expr.variants) { | ||
parts.push(serializeVariant(variant)); | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
parts.push("\n"); | ||
return parts.join(""); | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeAttributeExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
return `${ref}.${expr.name.name}`; | ||
} | ||
function serializeVariantExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
const key = serializeVariantKey(expr.key); | ||
return `${ref}[${key}]`; | ||
} | ||
function serializeCallExpression(expr) { | ||
const callee = serializeExpression(expr.callee); | ||
function serializeCallArguments(expr) { | ||
const positional = expr.positional.map(serializeExpression).join(", "); | ||
const named = expr.named.map(serializeNamedArgument).join(", "); | ||
if (expr.positional.length > 0 && expr.named.length > 0) { | ||
return `${callee}(${positional}, ${named})`; | ||
return `(${positional}, ${named})`; | ||
} | ||
return `${callee}(${positional || named})`; | ||
return `(${positional || named})`; | ||
} | ||
@@ -2058,3 +1965,2 @@ | ||
exports.Term = Term; | ||
exports.VariantList = VariantList; | ||
exports.Pattern = Pattern; | ||
@@ -2065,2 +1971,3 @@ exports.PatternElement = PatternElement; | ||
exports.Expression = Expression; | ||
exports.Literal = Literal; | ||
exports.StringLiteral = StringLiteral; | ||
@@ -2073,5 +1980,3 @@ exports.NumberLiteral = NumberLiteral; | ||
exports.SelectExpression = SelectExpression; | ||
exports.AttributeExpression = AttributeExpression; | ||
exports.VariantExpression = VariantExpression; | ||
exports.CallExpression = CallExpression; | ||
exports.CallArguments = CallArguments; | ||
exports.Attribute = Attribute; | ||
@@ -2078,0 +1983,0 @@ exports.Variant = Variant; |
{ | ||
"name": "fluent-syntax", | ||
"description": "AST and parser for Fluent", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"homepage": "http://projectfluent.org", | ||
@@ -6,0 +6,0 @@ "author": "Mozilla <l10n-drivers@mozilla.org>", |
107
src/ast.js
@@ -117,10 +117,2 @@ /* | ||
export class VariantList extends SyntaxNode { | ||
constructor(variants) { | ||
super(); | ||
this.type = "VariantList"; | ||
this.variants = variants; | ||
} | ||
} | ||
export class Pattern extends SyntaxNode { | ||
@@ -160,24 +152,73 @@ constructor(elements) { | ||
export class StringLiteral extends Expression { | ||
constructor(raw, value) { | ||
// An abstract base class for Literals. | ||
export class Literal extends Expression { | ||
constructor(value) { | ||
super(); | ||
this.type = "StringLiteral"; | ||
this.raw = raw; | ||
// The "value" field contains the exact contents of the literal, | ||
// character-for-character. | ||
this.value = value; | ||
} | ||
parse() { | ||
return {value: this.value}; | ||
} | ||
} | ||
export class NumberLiteral extends Expression { | ||
export class StringLiteral extends Literal { | ||
constructor(value) { | ||
super(); | ||
super(value); | ||
this.type = "StringLiteral"; | ||
} | ||
parse() { | ||
// Backslash backslash, backslash double quote, uHHHH, UHHHHHH. | ||
const KNOWN_ESCAPES = | ||
/(?:\\\\|\\"|\\u([0-9a-fA-F]{4})|\\U([0-9a-fA-F]{6}))/g; | ||
function from_escape_sequence(match, codepoint4, codepoint6) { | ||
switch (match) { | ||
case "\\\\": | ||
return "\\"; | ||
case "\\\"": | ||
return "\""; | ||
default: | ||
let codepoint = parseInt(codepoint4 || codepoint6, 16); | ||
if (codepoint <= 0xD7FF || 0xE000 <= codepoint) { | ||
// It's a Unicode scalar value. | ||
return String.fromCodePoint(codepoint); | ||
} | ||
// Escape sequences reresenting surrogate code points are | ||
// well-formed but invalid in Fluent. Replace them with U+FFFD | ||
// REPLACEMENT CHARACTER. | ||
return "�"; | ||
} | ||
} | ||
let value = this.value.replace(KNOWN_ESCAPES, from_escape_sequence); | ||
return {value}; | ||
} | ||
} | ||
export class NumberLiteral extends Literal { | ||
constructor(value) { | ||
super(value); | ||
this.type = "NumberLiteral"; | ||
this.value = value; | ||
} | ||
parse() { | ||
let value = parseFloat(this.value); | ||
let decimal_position = this.value.indexOf("."); | ||
let precision = decimal_position > 0 | ||
? this.value.length - decimal_position - 1 | ||
: 0; | ||
return {value, precision}; | ||
} | ||
} | ||
export class MessageReference extends Expression { | ||
constructor(id) { | ||
constructor(id, attribute = null) { | ||
super(); | ||
this.type = "MessageReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
} | ||
@@ -187,6 +228,8 @@ } | ||
export class TermReference extends Expression { | ||
constructor(id) { | ||
constructor(id, attribute = null, args = null) { | ||
super(); | ||
this.type = "TermReference"; | ||
this.id = id; | ||
this.attribute = attribute; | ||
this.arguments = args; | ||
} | ||
@@ -204,6 +247,7 @@ } | ||
export class FunctionReference extends Expression { | ||
constructor(id) { | ||
constructor(id, args) { | ||
super(); | ||
this.type = "FunctionReference"; | ||
this.id = id; | ||
this.arguments = args; | ||
} | ||
@@ -221,25 +265,6 @@ } | ||
export class AttributeExpression extends Expression { | ||
constructor(ref, name) { | ||
export class CallArguments extends SyntaxNode { | ||
constructor(positional = [], named = []) { | ||
super(); | ||
this.type = "AttributeExpression"; | ||
this.ref = ref; | ||
this.name = name; | ||
} | ||
} | ||
export class VariantExpression extends Expression { | ||
constructor(ref, key) { | ||
super(); | ||
this.type = "VariantExpression"; | ||
this.ref = ref; | ||
this.key = key; | ||
} | ||
} | ||
export class CallExpression extends Expression { | ||
constructor(callee, positional = [], named = []) { | ||
super(); | ||
this.type = "CallExpression"; | ||
this.callee = callee; | ||
this.type = "CallArguments"; | ||
this.positional = positional; | ||
@@ -341,5 +366,5 @@ this.named = named; | ||
this.code = code; | ||
this.args = args; | ||
this.arguments = args; | ||
this.message = message; | ||
} | ||
} |
@@ -38,3 +38,3 @@ export class ParseError extends Error { | ||
case "E0009": | ||
return "The key has to be a simple identifier"; | ||
return "The argument name has to be a simple identifier"; | ||
case "E0010": | ||
@@ -41,0 +41,0 @@ return "Expected one of the variants to be marked as default (*)"; |
@@ -43,6 +43,5 @@ /* eslint no-magic-numbers: [0] */ | ||
"getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", | ||
"getVariant", "getNumber", "getPattern", "getVariantList", | ||
"getTextElement", "getPlaceable", "getExpression", | ||
"getInlineExpression", "getCallArgument", "getString", | ||
"getSimpleExpression", "getLiteral", | ||
"getVariant", "getNumber", "getPattern", "getTextElement", | ||
"getPlaceable", "getExpression", "getInlineExpression", | ||
"getCallArgument", "getCallArguments", "getString", "getLiteral", | ||
]; | ||
@@ -250,5 +249,3 @@ for (const name of methodNames) { | ||
// Syntax 0.8 compat: VariantLists are supported but deprecated. They can | ||
// only be found as values of Terms. Nested VariantLists are not allowed. | ||
const value = this.maybeGetVariantList(ps) || this.maybeGetPattern(ps); | ||
const value = this.maybeGetPattern(ps); | ||
if (value === null) { | ||
@@ -389,18 +386,17 @@ throw new ParseError("E0006", id.name); | ||
getNumber(ps) { | ||
let num = ""; | ||
let value = ""; | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
ps.next(); | ||
value += `-${this.getDigits(ps)}`; | ||
} else { | ||
value += this.getDigits(ps); | ||
} | ||
num = `${num}${this.getDigits(ps)}`; | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
ps.next(); | ||
num = `${num}${this.getDigits(ps)}`; | ||
value += `.${this.getDigits(ps)}`; | ||
} | ||
return new AST.NumberLiteral(num); | ||
return new AST.NumberLiteral(value); | ||
} | ||
@@ -430,32 +426,2 @@ | ||
// Deprecated in Syntax 0.8. VariantLists are only allowed as values of Terms. | ||
// Values of Messages, Attributes and Variants must be Patterns. This method | ||
// is only used in getTerm. | ||
maybeGetVariantList(ps) { | ||
ps.peekBlank(); | ||
if (ps.currentPeek === "{") { | ||
const start = ps.peekOffset; | ||
ps.peek(); | ||
ps.peekBlankInline(); | ||
if (ps.currentPeek === EOL) { | ||
ps.peekBlank(); | ||
if (ps.isVariantStart()) { | ||
ps.resetPeek(start); | ||
ps.skipToPeek(); | ||
return this.getVariantList(ps); | ||
} | ||
} | ||
} | ||
ps.resetPeek(); | ||
return null; | ||
} | ||
getVariantList(ps) { | ||
ps.expectChar("{"); | ||
var variants = this.getVariants(ps); | ||
ps.expectChar("}"); | ||
return new AST.VariantList(variants); | ||
} | ||
getPattern(ps, {isBlock}) { | ||
@@ -602,3 +568,3 @@ const elements = []; | ||
ps.next(); | ||
return [`\\${next}`, next]; | ||
return `\\${next}`; | ||
case "u": | ||
@@ -628,11 +594,3 @@ return this.getUnicodeEscapeSequence(ps, next, 4); | ||
const codepoint = parseInt(sequence, 16); | ||
const unescaped = codepoint <= 0xD7FF || 0xE000 <= codepoint | ||
// It's a Unicode scalar value. | ||
? String.fromCodePoint(codepoint) | ||
// Escape sequences reresenting surrogate code points are well-formed | ||
// but invalid in Fluent. Replace them with U+FFFD REPLACEMENT | ||
// CHARACTER. | ||
: "�"; | ||
return [`\\${u}${sequence}`, unescaped]; | ||
return `\\${u}${sequence}`; | ||
} | ||
@@ -659,20 +617,13 @@ | ||
if (selector.type === "MessageReference") { | ||
throw new ParseError("E0016"); | ||
if (selector.attribute === null) { | ||
throw new ParseError("E0016"); | ||
} else { | ||
throw new ParseError("E0018"); | ||
} | ||
} | ||
if (selector.type === "AttributeExpression" | ||
&& selector.ref.type === "MessageReference") { | ||
throw new ParseError("E0018"); | ||
} | ||
if (selector.type === "TermReference" | ||
|| selector.type === "VariantExpression") { | ||
if (selector.type === "TermReference" && selector.attribute === null) { | ||
throw new ParseError("E0017"); | ||
} | ||
if (selector.type === "CallExpression" | ||
&& selector.callee.type === "TermReference") { | ||
throw new ParseError("E0017"); | ||
} | ||
ps.next(); | ||
@@ -688,12 +639,6 @@ ps.next(); | ||
if (selector.type === "AttributeExpression" | ||
&& selector.ref.type === "TermReference") { | ||
if (selector.type === "TermReference" && selector.attribute !== null) { | ||
throw new ParseError("E0019"); | ||
} | ||
if (selector.type === "CallExpression" | ||
&& selector.callee.type === "AttributeExpression") { | ||
throw new ParseError("E0019"); | ||
} | ||
return selector; | ||
@@ -707,56 +652,2 @@ } | ||
let expr = this.getSimpleExpression(ps); | ||
switch (expr.type) { | ||
case "NumberLiteral": | ||
case "StringLiteral": | ||
case "VariableReference": | ||
return expr; | ||
case "MessageReference": { | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
return new AST.AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z_?-]*$/.test(expr.id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
const func = new AST.FunctionReference(expr.id); | ||
if (this.withSpans) { | ||
func.addSpan(expr.span.start, expr.span.end); | ||
} | ||
return new AST.CallExpression(func, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
case "TermReference": { | ||
if (ps.currentChar === "[") { | ||
ps.next(); | ||
const key = this.getVariantKey(ps); | ||
ps.expectChar("]"); | ||
return new AST.VariantExpression(expr, key); | ||
} | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
const attr = this.getIdentifier(ps); | ||
expr = new AST.AttributeExpression(expr, attr); | ||
} | ||
if (ps.currentChar === "(") { | ||
return new AST.CallExpression(expr, ...this.getCallArguments(ps)); | ||
} | ||
return expr; | ||
} | ||
default: | ||
throw new ParseError("E0028"); | ||
} | ||
} | ||
getSimpleExpression(ps) { | ||
if (ps.isNumberStart()) { | ||
@@ -779,3 +670,15 @@ return this.getNumber(ps); | ||
const id = this.getIdentifier(ps); | ||
return new AST.TermReference(id); | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
let args; | ||
if (ps.currentChar === "(") { | ||
args = this.getCallArguments(ps); | ||
} | ||
return new AST.TermReference(id, attr, args); | ||
} | ||
@@ -785,5 +688,23 @@ | ||
const id = this.getIdentifier(ps); | ||
return new AST.MessageReference(id); | ||
if (ps.currentChar === "(") { | ||
// It's a Function. Ensure it's all upper-case. | ||
if (!/^[A-Z][A-Z0-9_-]*$/.test(id.name)) { | ||
throw new ParseError("E0008"); | ||
} | ||
let args = this.getCallArguments(ps); | ||
return new AST.FunctionReference(id, args); | ||
} | ||
let attr; | ||
if (ps.currentChar === ".") { | ||
ps.next(); | ||
attr = this.getIdentifier(ps); | ||
} | ||
return new AST.MessageReference(id, attr); | ||
} | ||
throw new ParseError("E0028"); | ||
@@ -801,11 +722,11 @@ } | ||
if (exp.type !== "MessageReference") { | ||
throw new ParseError("E0009"); | ||
if (exp.type === "MessageReference" && exp.attribute === null) { | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new AST.NamedArgument(exp.id, value); | ||
} | ||
ps.next(); | ||
ps.skipBlank(); | ||
const value = this.getLiteral(ps); | ||
return new AST.NamedArgument(exp.id, value); | ||
throw new ParseError("E0009"); | ||
} | ||
@@ -851,19 +772,14 @@ | ||
ps.expectChar(")"); | ||
return [positional, named]; | ||
return new AST.CallArguments(positional, named); | ||
} | ||
getString(ps) { | ||
let raw = ""; | ||
ps.expectChar("\""); | ||
let value = ""; | ||
ps.expectChar("\""); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== EOL))) { | ||
if (ch === "\\") { | ||
const [sequence, unescaped] = this.getEscapeSequence(ps); | ||
raw += sequence; | ||
value += unescaped; | ||
value += this.getEscapeSequence(ps); | ||
} else { | ||
raw += ch; | ||
value += ch; | ||
@@ -879,3 +795,3 @@ } | ||
return new AST.StringLiteral(raw, value); | ||
return new AST.StringLiteral(value); | ||
} | ||
@@ -882,0 +798,0 @@ |
@@ -100,3 +100,3 @@ import { includes } from "./util"; | ||
if (message.value) { | ||
parts.push(serializeValue(message.value)); | ||
parts.push(serializePattern(message.value)); | ||
} | ||
@@ -121,3 +121,3 @@ | ||
parts.push(`-${term.id.name} =`); | ||
parts.push(serializeValue(term.value)); | ||
parts.push(serializePattern(term.value)); | ||
@@ -134,3 +134,3 @@ for (const attribute of term.attributes) { | ||
function serializeAttribute(attribute) { | ||
const value = indent(serializeValue(attribute.value)); | ||
const value = indent(serializePattern(attribute.value)); | ||
return `\n .${attribute.id.name} =${value}`; | ||
@@ -140,14 +140,2 @@ } | ||
function serializeValue(value) { | ||
switch (value.type) { | ||
case "Pattern": | ||
return serializePattern(value); | ||
case "VariantList": | ||
return serializeVariantList(value); | ||
default: | ||
throw new Error(`Unknown value type: ${value.type}`); | ||
} | ||
} | ||
function serializePattern(pattern) { | ||
@@ -167,20 +155,2 @@ const content = pattern.elements.map(serializeElement).join(""); | ||
function serializeVariantList(varlist) { | ||
const content = varlist.variants.map(serializeVariant).join(""); | ||
return `\n {${indent(content)}\n }`; | ||
} | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializeValue(variant.value)); | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeElement(element) { | ||
@@ -200,3 +170,2 @@ switch (element.type) { | ||
const expr = placeable.expression; | ||
switch (expr.type) { | ||
@@ -208,3 +177,3 @@ case "Placeable": | ||
// opening and the closing brace. | ||
return `{ ${serializeSelectExpression(expr)}}`; | ||
return `{ ${serializeExpression(expr)}}`; | ||
default: | ||
@@ -220,20 +189,33 @@ return `{ ${serializeExpression(expr)} }`; | ||
case "StringLiteral": | ||
return `"${expr.raw}"`; | ||
return `"${expr.value}"`; | ||
case "NumberLiteral": | ||
return expr.value; | ||
case "MessageReference": | ||
case "FunctionReference": | ||
return expr.id.name; | ||
case "TermReference": | ||
return `-${expr.id.name}`; | ||
case "VariableReference": | ||
return `$${expr.id.name}`; | ||
case "AttributeExpression": | ||
return serializeAttributeExpression(expr); | ||
case "VariantExpression": | ||
return serializeVariantExpression(expr); | ||
case "CallExpression": | ||
return serializeCallExpression(expr); | ||
case "SelectExpression": | ||
return serializeSelectExpression(expr); | ||
case "TermReference": { | ||
let out = `-${expr.id.name}`; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
if (expr.arguments) { | ||
out += serializeCallArguments(expr.arguments); | ||
} | ||
return out; | ||
} | ||
case "MessageReference": { | ||
let out = expr.id.name; | ||
if (expr.attribute) { | ||
out += `.${expr.attribute.name}`; | ||
} | ||
return out; | ||
} | ||
case "FunctionReference": | ||
return `${expr.id.name}${serializeCallArguments(expr.arguments)}`; | ||
case "SelectExpression": { | ||
let out = `${serializeExpression(expr.selector)} ->`; | ||
for (let variant of expr.variants) { | ||
out += serializeVariant(variant); | ||
} | ||
return `${out}\n`; | ||
} | ||
case "Placeable": | ||
@@ -247,37 +229,21 @@ return serializePlaceable(expr); | ||
function serializeSelectExpression(expr) { | ||
const parts = []; | ||
const selector = `${serializeExpression(expr.selector)} ->`; | ||
parts.push(selector); | ||
function serializeVariant(variant) { | ||
const key = serializeVariantKey(variant.key); | ||
const value = indent(serializePattern(variant.value)); | ||
for (const variant of expr.variants) { | ||
parts.push(serializeVariant(variant)); | ||
if (variant.default) { | ||
return `\n *[${key}]${value}`; | ||
} | ||
parts.push("\n"); | ||
return parts.join(""); | ||
return `\n [${key}]${value}`; | ||
} | ||
function serializeAttributeExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
return `${ref}.${expr.name.name}`; | ||
} | ||
function serializeVariantExpression(expr) { | ||
const ref = serializeExpression(expr.ref); | ||
const key = serializeVariantKey(expr.key); | ||
return `${ref}[${key}]`; | ||
} | ||
function serializeCallExpression(expr) { | ||
const callee = serializeExpression(expr.callee); | ||
function serializeCallArguments(expr) { | ||
const positional = expr.positional.map(serializeExpression).join(", "); | ||
const named = expr.named.map(serializeNamedArgument).join(", "); | ||
if (expr.positional.length > 0 && expr.named.length > 0) { | ||
return `${callee}(${positional}, ${named})`; | ||
return `(${positional}, ${named})`; | ||
} | ||
return `${callee}(${positional || named})`; | ||
return `(${positional || named})`; | ||
} | ||
@@ -284,0 +250,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
166918
5120