fluent-syntax
Advanced tools
Comparing version 0.8.1 to 0.9.0
# Changelog | ||
## fluent-syntax 0.9.0 (October 23, 2018) | ||
This release of `fluent-syntax` brings support for version 0.7 of the | ||
Fluent Syntax spec. The API remains unchanged. Files written in valid | ||
Syntax 0.6 may not parse correctly in this release. See the summary of | ||
backwards-incompatible changes below. | ||
- Implement Fluent Syntax 0.7. (#287) | ||
The major new feature of Syntax 0.7 is the relaxation of the indentation | ||
requirement for all non-text elements of patterns. It's finally possible | ||
to leave the closing brace of select expressions unindented: | ||
emails = { $unread_email_count -> | ||
[one] You have one unread email. | ||
*[other] You have { $unread_email_count } unread emails. | ||
} | ||
Consult the [changelog](https://github.com/projectfluent/fluent/releases/tag/v0.7.0) | ||
to learn about other changes in Syntax 0.7. | ||
### Backward-incompatible changes: | ||
- Variant keys can now be either `NumberLiterals` (as previously) or | ||
`Identifiers`. The `VariantName` node class has been removed. Variant | ||
keys with spaces in them produce syntax errors, e.g. `[New York]`. | ||
- `CR` is not a valid EOL character anymore. Please use `LF` or `CRLF`. | ||
- `Tab` is not recognized as syntax whitespace. It can only be used in | ||
translation content. | ||
## fluent-syntax 0.8.1 (August 1, 2018) | ||
@@ -4,0 +34,0 @@ |
706
compat.js
@@ -1,2 +0,2 @@ | ||
/* fluent-syntax@0.8.1 */ | ||
/* fluent-syntax@0.9.0 */ | ||
(function (global, factory) { | ||
@@ -230,9 +230,2 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
} | ||
class VariantName extends Identifier { | ||
constructor(name) { | ||
super(name); | ||
this.type = "VariantName"; | ||
} | ||
} | ||
class BaseComment extends Entry { | ||
@@ -309,131 +302,2 @@ constructor(content) { | ||
class ParserStream { | ||
constructor(string) { | ||
this.string = string; | ||
this.iter = string[Symbol.iterator](); | ||
this.buf = []; | ||
this.peekIndex = 0; | ||
this.index = 0; | ||
this.iterEnd = false; | ||
this.peekEnd = false; | ||
this.ch = this.iter.next().value; | ||
} | ||
next() { | ||
if (this.iterEnd) { | ||
return undefined; | ||
} | ||
if (this.buf.length === 0) { | ||
this.ch = this.iter.next().value; | ||
} else { | ||
this.ch = this.buf.shift(); | ||
} | ||
this.index++; | ||
if (this.ch === undefined) { | ||
this.iterEnd = true; | ||
this.peekEnd = true; | ||
} | ||
this.peekIndex = this.index; | ||
return this.ch; | ||
} | ||
current() { | ||
return this.ch; | ||
} | ||
currentIs(ch) { | ||
return this.ch === ch; | ||
} | ||
currentPeek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
} | ||
const diff = this.peekIndex - this.index; | ||
if (diff === 0) { | ||
return this.ch; | ||
} | ||
return this.buf[diff - 1]; | ||
} | ||
currentPeekIs(ch) { | ||
return this.currentPeek() === ch; | ||
} | ||
peek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
} | ||
this.peekIndex += 1; | ||
const diff = this.peekIndex - this.index; | ||
if (diff > this.buf.length) { | ||
const ch = this.iter.next().value; | ||
if (ch !== undefined) { | ||
this.buf.push(ch); | ||
} else { | ||
this.peekEnd = true; | ||
return undefined; | ||
} | ||
} | ||
return this.buf[diff - 1]; | ||
} | ||
getIndex() { | ||
return this.index; | ||
} | ||
getPeekIndex() { | ||
return this.peekIndex; | ||
} | ||
peekCharIs(ch) { | ||
if (this.peekEnd) { | ||
return false; | ||
} | ||
const ret = this.peek(); | ||
this.peekIndex -= 1; | ||
return ret === ch; | ||
} | ||
resetPeek(pos) { | ||
if (pos) { | ||
if (pos < this.peekIndex) { | ||
this.peekEnd = false; | ||
} | ||
this.peekIndex = pos; | ||
} else { | ||
this.peekIndex = this.index; | ||
this.peekEnd = this.iterEnd; | ||
} | ||
} | ||
skipToPeek() { | ||
const diff = this.peekIndex - this.index; | ||
for (let i = 0; i < diff; i++) { | ||
this.ch = this.buf.shift(); | ||
} | ||
this.index = this.peekIndex; | ||
} | ||
getSlice(start, end) { | ||
return this.string.substring(start, end); | ||
} | ||
} | ||
function _slicedToArray(arr, i) { | ||
@@ -613,35 +477,84 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); | ||
/* eslint no-magic-numbers: "off" */ | ||
const INLINE_WS = [" ", "\t"]; | ||
const SPECIAL_LINE_START_CHARS = ["}", ".", "[", "*"]; | ||
class FTLParserStream extends ParserStream { | ||
skipInlineWS() { | ||
while (this.ch) { | ||
if (!includes(INLINE_WS, this.ch)) { | ||
break; | ||
} | ||
class ParserStream { | ||
constructor(string) { | ||
this.string = string; | ||
this.index = 0; | ||
this.peekOffset = 0; | ||
} | ||
this.next(); | ||
charAt(offset) { | ||
// When the cursor is at CRLF, return LF but don't move the cursor. | ||
// The cursor still points to the EOL position, which in this case is the | ||
// beginning of the compound CRLF sequence. This ensures slices of | ||
// [inclusive, exclusive) continue to work properly. | ||
if (this.string[offset] === "\r" && this.string[offset + 1] === "\n") { | ||
return "\n"; | ||
} | ||
return this.string[offset]; | ||
} | ||
peekInlineWS() { | ||
let ch = this.currentPeek(); | ||
get currentChar() { | ||
return this.charAt(this.index); | ||
} | ||
while (ch) { | ||
if (!includes(INLINE_WS, ch)) { | ||
break; | ||
} | ||
get currentPeek() { | ||
return this.charAt(this.index + this.peekOffset); | ||
} | ||
ch = this.peek(); | ||
next() { | ||
this.peekOffset = 0; // Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index] === "\r" && this.string[this.index + 1] === "\n") { | ||
this.index++; | ||
} | ||
this.index++; | ||
return this.string[this.index]; | ||
} | ||
skipBlankLines() { | ||
peek() { | ||
// Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index + this.peekOffset] === "\r" && this.string[this.index + this.peekOffset + 1] === "\n") { | ||
this.peekOffset++; | ||
} | ||
this.peekOffset++; | ||
return this.string[this.index + this.peekOffset]; | ||
} | ||
resetPeek() { | ||
let offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; | ||
this.peekOffset = offset; | ||
} | ||
skipToPeek() { | ||
this.index += this.peekOffset; | ||
this.peekOffset = 0; | ||
} | ||
} | ||
const EOL = "\n"; | ||
const EOF = undefined; | ||
const SPECIAL_LINE_START_CHARS = ["}", ".", "[", "*"]; | ||
class FluentParserStream extends ParserStream { | ||
skipBlankInline() { | ||
while (this.currentChar === " ") { | ||
this.next(); | ||
} | ||
} | ||
peekBlankInline() { | ||
while (this.currentPeek === " ") { | ||
this.peek(); | ||
} | ||
} | ||
skipBlankBlock() { | ||
let lineCount = 0; | ||
while (true) { | ||
this.peekInlineWS(); | ||
this.peekBlankInline(); | ||
if (this.currentPeekIs("\n")) { | ||
this.skipToPeek(); | ||
if (this.currentPeek === EOL) { | ||
this.next(); | ||
@@ -656,8 +569,8 @@ lineCount++; | ||
peekBlankLines() { | ||
peekBlankBlock() { | ||
while (true) { | ||
const lineStart = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
const lineStart = this.peekOffset; | ||
this.peekBlankInline(); | ||
if (this.currentPeekIs("\n")) { | ||
if (this.currentPeek === EOL) { | ||
this.peek(); | ||
@@ -671,9 +584,16 @@ } else { | ||
skipIndent() { | ||
this.skipBlankLines(); | ||
this.skipInlineWS(); | ||
skipBlank() { | ||
while (this.currentChar === " " || this.currentChar === EOL) { | ||
this.next(); | ||
} | ||
} | ||
peekBlank() { | ||
while (this.currentPeek === " " || this.currentPeek === EOL) { | ||
this.peek(); | ||
} | ||
} | ||
expectChar(ch) { | ||
if (this.ch === ch) { | ||
if (this.currentChar === ch) { | ||
this.next(); | ||
@@ -683,19 +603,7 @@ return true; | ||
if (ch === "\n") { | ||
// Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) | ||
throw new ParseError("E0003", "\u2424"); | ||
} | ||
throw new ParseError("E0003", ch); | ||
} | ||
expectIndent() { | ||
this.expectChar("\n"); | ||
this.skipBlankLines(); | ||
this.expectChar(" "); | ||
this.skipInlineWS(); | ||
} | ||
expectLineEnd() { | ||
if (this.ch === undefined) { | ||
if (this.currentChar === EOF) { | ||
// EOF is a valid line end in Fluent. | ||
@@ -705,9 +613,19 @@ return true; | ||
return this.expectChar("\n"); | ||
if (this.currentChar === EOL) { | ||
this.next(); | ||
return true; | ||
} // Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) | ||
throw new ParseError("E0003", "\u2424"); | ||
} | ||
takeChar(f) { | ||
const ch = this.ch; | ||
const ch = this.currentChar; | ||
if (ch !== undefined && f(ch)) { | ||
if (ch === EOF) { | ||
return EOF; | ||
} | ||
if (f(ch)) { | ||
this.next(); | ||
@@ -717,7 +635,7 @@ return ch; | ||
return undefined; | ||
return null; | ||
} | ||
isCharIDStart(ch) { | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
return false; | ||
@@ -732,12 +650,9 @@ } | ||
isIdentifierStart() { | ||
const ch = this.currentPeek(); | ||
const isID = this.isCharIDStart(ch); | ||
this.resetPeek(); | ||
return isID; | ||
return this.isCharIDStart(this.currentPeek); | ||
} | ||
isNumberStart() { | ||
const ch = this.currentIs("-") ? this.peek() : this.current(); | ||
const ch = this.currentChar === "-" ? this.peek() : this.currentChar; | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
this.resetPeek(); | ||
@@ -755,3 +670,3 @@ return false; | ||
isCharPatternContinuation(ch) { | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
return false; | ||
@@ -763,11 +678,17 @@ } | ||
isPeekValueStart() { | ||
this.peekInlineWS(); | ||
const ch = this.currentPeek(); // Inline Patterns may start with any char. | ||
isValueStart(_ref) { | ||
let _ref$skip = _ref.skip, | ||
skip = _ref$skip === void 0 ? true : _ref$skip; | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peekBlankInline(); | ||
const ch = this.currentPeek; // Inline Patterns may start with any char. | ||
if (ch !== undefined && ch !== "\n") { | ||
if (ch !== EOF && ch !== EOL) { | ||
this.skipToPeek(); | ||
return true; | ||
} | ||
return this.isPeekNextLineValue(); | ||
return this.isNextLineValue({ | ||
skip | ||
}); | ||
} // -1 - any | ||
@@ -779,6 +700,12 @@ // 0 - comment | ||
isPeekNextLineComment() { | ||
isNextLineComment() { | ||
let level = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : -1; | ||
if (!this.currentPeekIs("\n")) { | ||
let _ref2 = arguments.length > 1 ? arguments[1] : undefined, | ||
_ref2$skip = _ref2.skip, | ||
skip = _ref2$skip === void 0 ? false : _ref2$skip; | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
@@ -790,5 +717,3 @@ } | ||
while (i <= level || level === -1 && i < 3) { | ||
this.peek(); | ||
if (!this.currentPeekIs("#")) { | ||
if (this.peek() !== "#") { | ||
if (i <= level && level !== -1) { | ||
@@ -803,7 +728,8 @@ this.resetPeek(); | ||
i++; | ||
} | ||
} // The first char after #, ## or ###. | ||
this.peek(); | ||
if ([" ", "\n"].includes(this.currentPeek())) { | ||
const ch = this.peek(); | ||
if (ch === " " || ch === EOL) { | ||
this.resetPeek(); | ||
@@ -817,22 +743,18 @@ return true; | ||
isPeekNextLineVariantStart() { | ||
if (!this.currentPeekIs("\n")) { | ||
isNextLineVariantStart(_ref3) { | ||
let _ref3$skip = _ref3.skip, | ||
skip = _ref3$skip === void 0 ? false : _ref3$skip; | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peek(); | ||
this.peekBlankLines(); | ||
const ptr = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
this.peekBlank(); | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (this.currentPeekIs("*")) { | ||
if (this.currentPeek === "*") { | ||
this.peek(); | ||
} | ||
if (this.currentPeekIs("[") && !this.peekCharIs("[")) { | ||
if (this.currentPeek === "[") { | ||
this.resetPeek(); | ||
@@ -846,19 +768,10 @@ return true; | ||
isPeekNextLineAttributeStart() { | ||
if (!this.currentPeekIs("\n")) { | ||
return false; | ||
} | ||
isNextLineAttributeStart(_ref4) { | ||
let _ref4$skip = _ref4.skip, | ||
skip = _ref4$skip === void 0 ? true : _ref4$skip; | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peekBlank(); | ||
this.peek(); | ||
this.peekBlankLines(); | ||
const ptr = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (this.currentPeekIs(".")) { | ||
this.resetPeek(); | ||
if (this.currentPeek === ".") { | ||
this.skipToPeek(); | ||
return true; | ||
@@ -871,37 +784,57 @@ } | ||
isPeekNextLineValue() { | ||
if (!this.currentPeekIs("\n")) { | ||
isNextLineValue(_ref5) { | ||
let _ref5$skip = _ref5.skip, | ||
skip = _ref5$skip === void 0 ? true : _ref5$skip; | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peek(); | ||
this.peekBlankLines(); | ||
const ptr = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
this.peekBlankBlock(); | ||
const ptr = this.peekOffset; | ||
this.peekBlankInline(); | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
if (this.currentPeek !== "{") { | ||
if (this.peekOffset - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (!this.isCharPatternContinuation(this.currentPeek)) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
} | ||
if (!this.isCharPatternContinuation(this.currentPeek())) { | ||
if (skip) { | ||
this.skipToPeek(); | ||
} else { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
this.resetPeek(); | ||
return true; | ||
} | ||
skipToNextEntryStart() { | ||
while (this.ch) { | ||
if (this.currentIs("\n") && !this.peekCharIs("\n")) { | ||
skipToNextEntryStart(junkStart) { | ||
let lastNewline = this.string.lastIndexOf(EOL, this.index); | ||
if (junkStart < lastNewline) { | ||
// Last seen newline is _after_ the junk start. It's safe to rewind | ||
// without the risk of resuming at the same broken entry. | ||
this.index = lastNewline; | ||
} | ||
while (this.currentChar) { | ||
// We're only interested in beginnings of line. | ||
if (this.currentChar !== EOL) { | ||
this.next(); | ||
continue; | ||
} // Break if the first char in this line looks like an entry start. | ||
if (this.ch === undefined || this.isIdentifierStart() || this.currentIs("-") || this.currentIs("#")) { | ||
break; | ||
} | ||
const first = this.next(); | ||
if (this.isCharIDStart(first) || first === "-" || first === "#") { | ||
break; | ||
} | ||
this.next(); | ||
} | ||
@@ -911,4 +844,4 @@ } | ||
takeIDStart() { | ||
if (this.isCharIDStart(this.ch)) { | ||
const ret = this.ch; | ||
if (this.isCharIDStart(this.currentChar)) { | ||
const ret = this.currentChar; | ||
this.next(); | ||
@@ -933,14 +866,2 @@ return ret; | ||
takeVariantNameChar() { | ||
const closure = ch => { | ||
const cc = ch.charCodeAt(0); | ||
return cc >= 97 && cc <= 122 || // a-z | ||
cc >= 65 && cc <= 90 || // A-Z | ||
cc >= 48 && cc <= 57 || // 0-9 | ||
cc === 95 || cc === 45 || cc === 32; // _-<space> | ||
}; | ||
return this.takeChar(closure); | ||
} | ||
takeDigit() { | ||
@@ -981,3 +902,3 @@ const closure = ch => { | ||
const start = ps.getIndex(); | ||
const start = ps.index; | ||
const node = fn.call(this, ps, ...args); // Don't re-add the span if the node already has it. This may happen when | ||
@@ -990,3 +911,3 @@ // one decorated function calls another decorated function. | ||
const end = ps.getIndex(); | ||
const end = ps.index; | ||
node.addSpan(start, end); | ||
@@ -1005,3 +926,3 @@ return node; | ||
const methodNames = ["getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", "getTermIdentifier", "getVariant", "getVariantName", "getNumber", "getValue", "getPattern", "getVariantList", "getTextElement", "getPlaceable", "getExpression", "getSelectorExpression", "getCallArg", "getString", "getLiteral", "getVariantList"]; | ||
const methodNames = ["getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", "getTermIdentifier", "getVariant", "getNumber", "getValue", "getPattern", "getVariantList", "getTextElement", "getPlaceable", "getExpression", "getSelectorExpression", "getCallArg", "getString", "getLiteral"]; | ||
@@ -1015,10 +936,10 @@ for (var _i = 0; _i < methodNames.length; _i++) { | ||
parse(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
const entries = []; | ||
let lastComment = null; | ||
while (ps.current()) { | ||
while (ps.currentChar) { | ||
const entry = this.getEntryOrJunk(ps); | ||
const blankLines = ps.skipBlankLines(); // Regular Comments require special logic. Comments may be attached to | ||
const blankLines = ps.skipBlankBlock(); // Regular Comments require special logic. Comments may be attached to | ||
// Messages or Terms if they are followed immediately by them. However | ||
@@ -1029,3 +950,3 @@ // they should parse as standalone when they're followed by Junk. | ||
if (entry.type === "Comment" && blankLines === 0 && ps.current()) { | ||
if (entry.type === "Comment" && blankLines === 0 && ps.currentChar) { | ||
// Stash the comment and decide what to do with it in the next pass. | ||
@@ -1058,3 +979,3 @@ lastComment = entry; | ||
if (this.withSpans) { | ||
res.addSpan(0, ps.getIndex()); | ||
res.addSpan(0, ps.index); | ||
} | ||
@@ -1076,6 +997,6 @@ | ||
parseEntry(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
while (ps.currentIs("#")) { | ||
while (ps.currentChar === "#") { | ||
const skipped = this.getEntryOrJunk(ps); | ||
@@ -1088,3 +1009,3 @@ | ||
ps.skipBlankLines(); | ||
ps.skipBlankBlock(); | ||
} | ||
@@ -1096,3 +1017,3 @@ | ||
getEntryOrJunk(ps) { | ||
const entryStartPos = ps.getIndex(); | ||
const entryStartPos = ps.index; | ||
@@ -1108,7 +1029,13 @@ try { | ||
const errorIndex = ps.getIndex(); | ||
ps.skipToNextEntryStart(); | ||
const nextEntryStart = ps.getIndex(); // Create a Junk instance | ||
let errorIndex = ps.index; | ||
ps.skipToNextEntryStart(entryStartPos); | ||
const nextEntryStart = ps.index; | ||
const slice = ps.getSlice(entryStartPos, nextEntryStart); | ||
if (nextEntryStart < errorIndex) { | ||
// The position of the error must be inside of the Junk's span. | ||
errorIndex = nextEntryStart; | ||
} // Create a Junk instance | ||
const slice = ps.string.substring(entryStartPos, nextEntryStart); | ||
const junk = new Junk(slice); | ||
@@ -1128,7 +1055,7 @@ | ||
getEntry(ps) { | ||
if (ps.currentIs("#")) { | ||
if (ps.currentChar === "#") { | ||
return this.getComment(ps); | ||
} | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
return this.getTerm(ps); | ||
@@ -1154,3 +1081,3 @@ } | ||
while (ps.currentIs("#") && i < (level === -1 ? 2 : level)) { | ||
while (ps.currentChar === "#" && i < (level === -1 ? 2 : level)) { | ||
ps.next(); | ||
@@ -1164,7 +1091,7 @@ i++; | ||
if (!ps.currentIs("\n")) { | ||
if (ps.currentChar !== EOL) { | ||
ps.expectChar(" "); | ||
let ch; | ||
while (ch = ps.takeChar(x => x !== "\n")) { | ||
while (ch = ps.takeChar(x => x !== EOL)) { | ||
content += ch; | ||
@@ -1174,4 +1101,6 @@ } | ||
if (ps.isPeekNextLineComment(level)) { | ||
content += ps.current(); | ||
if (ps.isNextLineComment(level, { | ||
skip: false | ||
})) { | ||
content += ps.currentChar; | ||
ps.next(); | ||
@@ -1204,13 +1133,14 @@ } else { | ||
const id = this.getIdentifier(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({ | ||
skip: true | ||
})) { | ||
var pattern = this.getPattern(ps); | ||
} else { | ||
ps.skipInlineWS(); | ||
} | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({ | ||
skip: true | ||
})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -1228,7 +1158,8 @@ } | ||
const id = this.getTermIdentifier(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({ | ||
skip: true | ||
})) { | ||
var value = this.getValue(ps); | ||
@@ -1239,3 +1170,5 @@ } else { | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({ | ||
skip: true | ||
})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -1250,7 +1183,8 @@ } | ||
const key = this.getIdentifier(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({ | ||
skip: true | ||
})) { | ||
const value = this.getPattern(ps); | ||
@@ -1267,7 +1201,8 @@ return new Attribute(key, value); | ||
while (true) { | ||
ps.expectIndent(); | ||
const attr = this.getAttribute(ps); | ||
attrs.push(attr); | ||
if (!ps.isPeekNextLineAttributeStart()) { | ||
if (!ps.isNextLineAttributeStart({ | ||
skip: true | ||
})) { | ||
break; | ||
@@ -1298,5 +1233,5 @@ } | ||
getVariantKey(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0013"); | ||
@@ -1312,3 +1247,3 @@ } | ||
return this.getVariantName(ps); | ||
return this.getIdentifier(ps); | ||
} | ||
@@ -1319,3 +1254,3 @@ | ||
if (ps.currentIs("*")) { | ||
if (ps.currentChar === "*") { | ||
if (hasDefault) { | ||
@@ -1331,7 +1266,10 @@ throw new ParseError("E0015"); | ||
ps.expectChar("["); | ||
ps.skipBlank(); | ||
const key = this.getVariantKey(ps); | ||
ps.skipBlank(); | ||
ps.expectChar("]"); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({ | ||
skip: true | ||
})) { | ||
const value = this.getValue(ps); | ||
@@ -1349,3 +1287,2 @@ return new Variant(key, value, defaultIndex); | ||
while (true) { | ||
ps.expectIndent(); | ||
const variant = this.getVariant(ps, hasDefault); | ||
@@ -1359,5 +1296,9 @@ | ||
if (!ps.isPeekNextLineVariantStart()) { | ||
if (!ps.isNextLineVariantStart({ | ||
skip: false | ||
})) { | ||
break; | ||
} | ||
ps.skipBlank(); | ||
} | ||
@@ -1372,18 +1313,2 @@ | ||
getVariantName(ps) { | ||
let name = ps.takeIDStart(); | ||
while (true) { | ||
const ch = ps.takeVariantNameChar(); | ||
if (ch) { | ||
name += ch; | ||
} else { | ||
break; | ||
} | ||
} | ||
return new VariantName(name.replace(trailingWSRe, "")); | ||
} | ||
getDigits(ps) { | ||
@@ -1407,3 +1332,3 @@ let num = ""; | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
@@ -1415,3 +1340,3 @@ ps.next(); | ||
if (ps.currentIs(".")) { | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
@@ -1426,9 +1351,13 @@ ps.next(); | ||
getValue(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
ps.peek(); | ||
ps.peekInlineWS(); | ||
ps.peekBlankInline(); | ||
if (ps.isPeekNextLineVariantStart()) { | ||
if (ps.isNextLineVariantStart({ | ||
skip: false | ||
})) { | ||
return this.getVariantList(ps); | ||
} | ||
ps.resetPeek(); | ||
} | ||
@@ -1441,5 +1370,8 @@ | ||
ps.expectChar("{"); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.expectIndent(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
ps.expectChar("}"); | ||
@@ -1451,9 +1383,10 @@ return new VariantList(variants); | ||
const elements = []; | ||
ps.skipInlineWS(); | ||
let ch; | ||
while (ch = ps.current()) { | ||
while (ch = ps.currentChar) { | ||
// The end condition for getPattern's while loop is a newline | ||
// which is not followed by a valid pattern continuation. | ||
if (ch === "\n" && !ps.isPeekNextLineValue()) { | ||
if (ch === EOL && !ps.isNextLineValue({ | ||
skip: false | ||
})) { | ||
break; | ||
@@ -1476,2 +1409,6 @@ } | ||
lastElement.value = lastElement.value.replace(trailingWSRe, ""); | ||
if (lastElement.value === "") { | ||
elements.pop(); | ||
} | ||
} | ||
@@ -1486,3 +1423,3 @@ | ||
while (ch = ps.current()) { | ||
while (ch = ps.currentChar) { | ||
if (ch === "{") { | ||
@@ -1492,4 +1429,6 @@ return new TextElement(buffer); | ||
if (ch === "\n") { | ||
if (!ps.isPeekNextLineValue()) { | ||
if (ch === EOL) { | ||
if (!ps.isNextLineValue({ | ||
skip: false | ||
})) { | ||
return new TextElement(buffer); | ||
@@ -1499,5 +1438,4 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); // Add the new line to the buffer | ||
buffer += ch; | ||
ps.skipBlankInline(); | ||
buffer += EOL; | ||
continue; | ||
@@ -1509,6 +1447,7 @@ } | ||
buffer += this.getEscapeSequence(ps); | ||
} else { | ||
buffer += ch; | ||
ps.next(); | ||
continue; | ||
} | ||
buffer += ch; | ||
ps.next(); | ||
} | ||
@@ -1521,3 +1460,3 @@ | ||
let specials = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ["{", "\\"]; | ||
const next = ps.current(); | ||
const next = ps.currentChar; | ||
@@ -1536,4 +1475,4 @@ if (specials.includes(next)) { | ||
if (ch === undefined) { | ||
throw new ParseError("E0026", sequence + ps.current()); | ||
if (!ch) { | ||
throw new ParseError("E0026", sequence + ps.currentChar); | ||
} | ||
@@ -1558,10 +1497,8 @@ | ||
getExpression(ps) { | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
const selector = this.getSelectorExpression(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.currentIs("-")) { | ||
ps.peek(); | ||
if (!ps.currentPeekIs(">")) { | ||
if (ps.currentChar === "-") { | ||
if (ps.peek() !== ">") { | ||
ps.resetPeek(); | ||
@@ -1585,4 +1522,7 @@ return selector; | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.skipBlank(); | ||
@@ -1598,3 +1538,2 @@ if (variants.length === 0) { | ||
ps.expectIndent(); | ||
return new SelectExpression(selector, variants); | ||
@@ -1605,2 +1544,3 @@ } else if (selector.type === "AttributeExpression" && selector.ref.type === "TermReference") { | ||
ps.skipBlank(); | ||
return selector; | ||
@@ -1610,3 +1550,3 @@ } | ||
getSelectorExpression(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
return this.getPlaceable(ps); | ||
@@ -1621,3 +1561,3 @@ } | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
@@ -1665,5 +1605,5 @@ if (ch === ".") { | ||
const exp = this.getSelectorExpression(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.current() !== ":") { | ||
if (ps.currentChar !== ":") { | ||
return exp; | ||
@@ -1677,3 +1617,3 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
const val = this.getArgVal(ps); | ||
@@ -1687,7 +1627,6 @@ return new NamedArgument(exp.id, val); | ||
const argumentNames = new Set(); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
while (true) { | ||
if (ps.current() === ")") { | ||
if (ps.currentChar === ")") { | ||
break; | ||
@@ -1711,9 +1650,7 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
if (ps.current() === ",") { | ||
if (ps.currentChar === ",") { | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
continue; | ||
@@ -1734,3 +1671,3 @@ } else { | ||
return this.getNumber(ps); | ||
} else if (ps.currentIs('"')) { | ||
} else if (ps.currentChar === '"') { | ||
return this.getString(ps); | ||
@@ -1744,6 +1681,6 @@ } | ||
let val = ""; | ||
ps.expectChar('"'); | ||
ps.expectChar("\""); | ||
let ch; | ||
while (ch = ps.takeChar(x => x !== '"' && x !== "\n")) { | ||
while (ch = ps.takeChar(x => x !== '"' && x !== EOL)) { | ||
if (ch === "\\") { | ||
@@ -1756,7 +1693,7 @@ val += this.getEscapeSequence(ps, ["{", "\\", "\""]); | ||
if (ps.currentIs("\n")) { | ||
if (ps.currentChar === EOL) { | ||
throw new ParseError("E0020"); | ||
} | ||
ps.next(); | ||
ps.expectChar("\""); | ||
return new StringLiteral(val); | ||
@@ -1766,5 +1703,5 @@ } | ||
getLiteral(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0014"); | ||
@@ -2172,10 +2109,6 @@ } | ||
function serializeVariantName(VariantName) { | ||
return VariantName.name; | ||
} | ||
function serializeVariantKey(key) { | ||
switch (key.type) { | ||
case "VariantName": | ||
return serializeVariantName(key); | ||
case "Identifier": | ||
return serializeIdentifier(key); | ||
@@ -2250,3 +2183,2 @@ case "NumberLiteral": | ||
exports.Identifier = Identifier; | ||
exports.VariantName = VariantName; | ||
exports.BaseComment = BaseComment; | ||
@@ -2253,0 +2185,0 @@ exports.Comment = Comment; |
@@ -1,2 +0,2 @@ | ||
/* fluent-syntax@0.8.1 */ | ||
/* fluent-syntax@0.9.0 */ | ||
(function (global, factory) { | ||
@@ -218,9 +218,2 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
class VariantName extends Identifier { | ||
constructor(name) { | ||
super(name); | ||
this.type = "VariantName"; | ||
} | ||
} | ||
class BaseComment extends Entry { | ||
@@ -293,133 +286,2 @@ constructor(content) { | ||
class ParserStream { | ||
constructor(string) { | ||
this.string = string; | ||
this.iter = string[Symbol.iterator](); | ||
this.buf = []; | ||
this.peekIndex = 0; | ||
this.index = 0; | ||
this.iterEnd = false; | ||
this.peekEnd = false; | ||
this.ch = this.iter.next().value; | ||
} | ||
next() { | ||
if (this.iterEnd) { | ||
return undefined; | ||
} | ||
if (this.buf.length === 0) { | ||
this.ch = this.iter.next().value; | ||
} else { | ||
this.ch = this.buf.shift(); | ||
} | ||
this.index++; | ||
if (this.ch === undefined) { | ||
this.iterEnd = true; | ||
this.peekEnd = true; | ||
} | ||
this.peekIndex = this.index; | ||
return this.ch; | ||
} | ||
current() { | ||
return this.ch; | ||
} | ||
currentIs(ch) { | ||
return this.ch === ch; | ||
} | ||
currentPeek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
} | ||
const diff = this.peekIndex - this.index; | ||
if (diff === 0) { | ||
return this.ch; | ||
} | ||
return this.buf[diff - 1]; | ||
} | ||
currentPeekIs(ch) { | ||
return this.currentPeek() === ch; | ||
} | ||
peek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
} | ||
this.peekIndex += 1; | ||
const diff = this.peekIndex - this.index; | ||
if (diff > this.buf.length) { | ||
const ch = this.iter.next().value; | ||
if (ch !== undefined) { | ||
this.buf.push(ch); | ||
} else { | ||
this.peekEnd = true; | ||
return undefined; | ||
} | ||
} | ||
return this.buf[diff - 1]; | ||
} | ||
getIndex() { | ||
return this.index; | ||
} | ||
getPeekIndex() { | ||
return this.peekIndex; | ||
} | ||
peekCharIs(ch) { | ||
if (this.peekEnd) { | ||
return false; | ||
} | ||
const ret = this.peek(); | ||
this.peekIndex -= 1; | ||
return ret === ch; | ||
} | ||
resetPeek(pos) { | ||
if (pos) { | ||
if (pos < this.peekIndex) { | ||
this.peekEnd = false; | ||
} | ||
this.peekIndex = pos; | ||
} else { | ||
this.peekIndex = this.index; | ||
this.peekEnd = this.iterEnd; | ||
} | ||
} | ||
skipToPeek() { | ||
const diff = this.peekIndex - this.index; | ||
for (let i = 0; i < diff; i++) { | ||
this.ch = this.buf.shift(); | ||
} | ||
this.index = this.peekIndex; | ||
} | ||
getSlice(start, end) { | ||
return this.string.substring(start, end); | ||
} | ||
} | ||
class ParseError extends Error { | ||
@@ -512,11 +374,68 @@ constructor(code, ...args) { | ||
const INLINE_WS = [" ", "\t"]; | ||
class ParserStream { | ||
constructor(string) { | ||
this.string = string; | ||
this.index = 0; | ||
this.peekOffset = 0; | ||
} | ||
charAt(offset) { | ||
// When the cursor is at CRLF, return LF but don't move the cursor. | ||
// The cursor still points to the EOL position, which in this case is the | ||
// beginning of the compound CRLF sequence. This ensures slices of | ||
// [inclusive, exclusive) continue to work properly. | ||
if (this.string[offset] === "\r" | ||
&& this.string[offset + 1] === "\n") { | ||
return "\n"; | ||
} | ||
return this.string[offset]; | ||
} | ||
get currentChar() { | ||
return this.charAt(this.index); | ||
} | ||
get currentPeek() { | ||
return this.charAt(this.index + this.peekOffset); | ||
} | ||
next() { | ||
this.peekOffset = 0; | ||
// Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index] === "\r" | ||
&& this.string[this.index + 1] === "\n") { | ||
this.index++; | ||
} | ||
this.index++; | ||
return this.string[this.index]; | ||
} | ||
peek() { | ||
// Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index + this.peekOffset] === "\r" | ||
&& this.string[this.index + this.peekOffset + 1] === "\n") { | ||
this.peekOffset++; | ||
} | ||
this.peekOffset++; | ||
return this.string[this.index + this.peekOffset]; | ||
} | ||
resetPeek(offset = 0) { | ||
this.peekOffset = offset; | ||
} | ||
skipToPeek() { | ||
this.index += this.peekOffset; | ||
this.peekOffset = 0; | ||
} | ||
} | ||
const EOL = "\n"; | ||
const EOF = undefined; | ||
const SPECIAL_LINE_START_CHARS = ["}", ".", "[", "*"]; | ||
class FTLParserStream extends ParserStream { | ||
skipInlineWS() { | ||
while (this.ch) { | ||
if (!includes(INLINE_WS, this.ch)) { | ||
break; | ||
} | ||
class FluentParserStream extends ParserStream { | ||
skipBlankInline() { | ||
while (this.currentChar === " ") { | ||
this.next(); | ||
@@ -526,19 +445,14 @@ } | ||
peekInlineWS() { | ||
let ch = this.currentPeek(); | ||
while (ch) { | ||
if (!includes(INLINE_WS, ch)) { | ||
break; | ||
} | ||
ch = this.peek(); | ||
peekBlankInline() { | ||
while (this.currentPeek === " ") { | ||
this.peek(); | ||
} | ||
} | ||
skipBlankLines() { | ||
skipBlankBlock() { | ||
let lineCount = 0; | ||
while (true) { | ||
this.peekInlineWS(); | ||
this.peekBlankInline(); | ||
if (this.currentPeekIs("\n")) { | ||
this.skipToPeek(); | ||
if (this.currentPeek === EOL) { | ||
this.next(); | ||
@@ -553,9 +467,9 @@ lineCount++; | ||
peekBlankLines() { | ||
peekBlankBlock() { | ||
while (true) { | ||
const lineStart = this.getPeekIndex(); | ||
const lineStart = this.peekOffset; | ||
this.peekInlineWS(); | ||
this.peekBlankInline(); | ||
if (this.currentPeekIs("\n")) { | ||
if (this.currentPeek === EOL) { | ||
this.peek(); | ||
@@ -569,9 +483,16 @@ } else { | ||
skipIndent() { | ||
this.skipBlankLines(); | ||
this.skipInlineWS(); | ||
skipBlank() { | ||
while (this.currentChar === " " || this.currentChar === EOL) { | ||
this.next(); | ||
} | ||
} | ||
peekBlank() { | ||
while (this.currentPeek === " " || this.currentPeek === EOL) { | ||
this.peek(); | ||
} | ||
} | ||
expectChar(ch) { | ||
if (this.ch === ch) { | ||
if (this.currentChar === ch) { | ||
this.next(); | ||
@@ -581,19 +502,7 @@ return true; | ||
if (ch === "\n") { | ||
// Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) | ||
throw new ParseError("E0003", "\u2424"); | ||
} | ||
throw new ParseError("E0003", ch); | ||
} | ||
expectIndent() { | ||
this.expectChar("\n"); | ||
this.skipBlankLines(); | ||
this.expectChar(" "); | ||
this.skipInlineWS(); | ||
} | ||
expectLineEnd() { | ||
if (this.ch === undefined) { | ||
if (this.currentChar === EOF) { | ||
// EOF is a valid line end in Fluent. | ||
@@ -603,16 +512,25 @@ return true; | ||
return this.expectChar("\n"); | ||
if (this.currentChar === EOL) { | ||
this.next(); | ||
return true; | ||
} | ||
// Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) | ||
throw new ParseError("E0003", "\u2424"); | ||
} | ||
takeChar(f) { | ||
const ch = this.ch; | ||
if (ch !== undefined && f(ch)) { | ||
const ch = this.currentChar; | ||
if (ch === EOF) { | ||
return EOF; | ||
} | ||
if (f(ch)) { | ||
this.next(); | ||
return ch; | ||
} | ||
return undefined; | ||
return null; | ||
} | ||
isCharIDStart(ch) { | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
return false; | ||
@@ -627,14 +545,11 @@ } | ||
isIdentifierStart() { | ||
const ch = this.currentPeek(); | ||
const isID = this.isCharIDStart(ch); | ||
this.resetPeek(); | ||
return isID; | ||
return this.isCharIDStart(this.currentPeek); | ||
} | ||
isNumberStart() { | ||
const ch = this.currentIs("-") | ||
const ch = this.currentChar === "-" | ||
? this.peek() | ||
: this.current(); | ||
: this.currentChar; | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
this.resetPeek(); | ||
@@ -651,3 +566,3 @@ return false; | ||
isCharPatternContinuation(ch) { | ||
if (ch === undefined) { | ||
if (ch === EOF) { | ||
return false; | ||
@@ -659,12 +574,15 @@ } | ||
isPeekValueStart() { | ||
this.peekInlineWS(); | ||
const ch = this.currentPeek(); | ||
isValueStart({skip = true}) { | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peekBlankInline(); | ||
const ch = this.currentPeek; | ||
// Inline Patterns may start with any char. | ||
if (ch !== undefined && ch !== "\n") { | ||
if (ch !== EOF && ch !== EOL) { | ||
this.skipToPeek(); | ||
return true; | ||
} | ||
return this.isPeekNextLineValue(); | ||
return this.isNextLineValue({skip}); | ||
} | ||
@@ -676,4 +594,6 @@ | ||
// 2 - resource comment | ||
isPeekNextLineComment(level = -1) { | ||
if (!this.currentPeekIs("\n")) { | ||
isNextLineComment(level = -1, {skip = false}) { | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
@@ -685,4 +605,3 @@ } | ||
while (i <= level || (level === -1 && i < 3)) { | ||
this.peek(); | ||
if (!this.currentPeekIs("#")) { | ||
if (this.peek() !== "#") { | ||
if (i <= level && level !== -1) { | ||
@@ -697,4 +616,5 @@ this.resetPeek(); | ||
this.peek(); | ||
if ([" ", "\n"].includes(this.currentPeek())) { | ||
// The first char after #, ## or ###. | ||
const ch = this.peek(); | ||
if (ch === " " || ch === EOL) { | ||
this.resetPeek(); | ||
@@ -708,25 +628,16 @@ return true; | ||
isPeekNextLineVariantStart() { | ||
if (!this.currentPeekIs("\n")) { | ||
isNextLineVariantStart({skip = false}) { | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peek(); | ||
this.peekBlank(); | ||
this.peekBlankLines(); | ||
const ptr = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (this.currentPeekIs("*")) { | ||
if (this.currentPeek === "*") { | ||
this.peek(); | ||
} | ||
if (this.currentPeekIs("[") && !this.peekCharIs("[")) { | ||
if (this.currentPeek === "[") { | ||
this.resetPeek(); | ||
@@ -739,22 +650,9 @@ return true; | ||
isPeekNextLineAttributeStart() { | ||
if (!this.currentPeekIs("\n")) { | ||
return false; | ||
} | ||
isNextLineAttributeStart({skip = true}) { | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peek(); | ||
this.peekBlank(); | ||
this.peekBlankLines(); | ||
const ptr = this.getPeekIndex(); | ||
this.peekInlineWS(); | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (this.currentPeekIs(".")) { | ||
this.resetPeek(); | ||
if (this.currentPeek === ".") { | ||
this.skipToPeek(); | ||
return true; | ||
@@ -767,41 +665,52 @@ } | ||
isPeekNextLineValue() { | ||
if (!this.currentPeekIs("\n")) { | ||
isNextLineValue({skip = true}) { | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peek(); | ||
this.peekBlankBlock(); | ||
this.peekBlankLines(); | ||
const ptr = this.peekOffset; | ||
const ptr = this.getPeekIndex(); | ||
this.peekBlankInline(); | ||
this.peekInlineWS(); | ||
if (this.currentPeek !== "{") { | ||
if (this.peekOffset - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
if (this.getPeekIndex() - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
if (!this.isCharPatternContinuation(this.currentPeek)) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
} | ||
if (!this.isCharPatternContinuation(this.currentPeek())) { | ||
if (skip) { | ||
this.skipToPeek(); | ||
} else { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
this.resetPeek(); | ||
return true; | ||
} | ||
skipToNextEntryStart() { | ||
while (this.ch) { | ||
if (this.currentIs("\n") && !this.peekCharIs("\n")) { | ||
skipToNextEntryStart(junkStart) { | ||
let lastNewline = this.string.lastIndexOf(EOL, this.index); | ||
if (junkStart < lastNewline) { | ||
// Last seen newline is _after_ the junk start. It's safe to rewind | ||
// without the risk of resuming at the same broken entry. | ||
this.index = lastNewline; | ||
} | ||
while (this.currentChar) { | ||
// We're only interested in beginnings of line. | ||
if (this.currentChar !== EOL) { | ||
this.next(); | ||
if (this.ch === undefined || | ||
this.isIdentifierStart() || | ||
this.currentIs("-") || | ||
this.currentIs("#")) { | ||
break; | ||
} | ||
continue; | ||
} | ||
this.next(); | ||
// Break if the first char in this line looks like an entry start. | ||
const first = this.next(); | ||
if (this.isCharIDStart(first) || first === "-" || first === "#") { | ||
break; | ||
} | ||
} | ||
@@ -811,4 +720,4 @@ } | ||
takeIDStart() { | ||
if (this.isCharIDStart(this.ch)) { | ||
const ret = this.ch; | ||
if (this.isCharIDStart(this.currentChar)) { | ||
const ret = this.currentChar; | ||
this.next(); | ||
@@ -833,14 +742,2 @@ return ret; | ||
takeVariantNameChar() { | ||
const closure = ch => { | ||
const cc = ch.charCodeAt(0); | ||
return ((cc >= 97 && cc <= 122) || // a-z | ||
(cc >= 65 && cc <= 90) || // A-Z | ||
(cc >= 48 && cc <= 57) || // 0-9 | ||
cc === 95 || cc === 45 || cc === 32); // _-<space> | ||
}; | ||
return this.takeChar(closure); | ||
} | ||
takeDigit() { | ||
@@ -879,3 +776,3 @@ const closure = ch => { | ||
const start = ps.getIndex(); | ||
const start = ps.index; | ||
const node = fn.call(this, ps, ...args); | ||
@@ -889,3 +786,3 @@ | ||
const end = ps.getIndex(); | ||
const end = ps.index; | ||
node.addSpan(start, end); | ||
@@ -906,6 +803,6 @@ return node; | ||
"getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", | ||
"getTermIdentifier", "getVariant", "getVariantName", "getNumber", | ||
"getTermIdentifier", "getVariant", "getNumber", | ||
"getValue", "getPattern", "getVariantList", "getTextElement", | ||
"getPlaceable", "getExpression", "getSelectorExpression", "getCallArg", | ||
"getString", "getLiteral", "getVariantList" | ||
"getString", "getLiteral" | ||
]; | ||
@@ -918,4 +815,4 @@ for (const name of methodNames) { | ||
parse(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
@@ -925,5 +822,5 @@ const entries = []; | ||
while (ps.current()) { | ||
while (ps.currentChar) { | ||
const entry = this.getEntryOrJunk(ps); | ||
const blankLines = ps.skipBlankLines(); | ||
const blankLines = ps.skipBlankBlock(); | ||
@@ -935,3 +832,3 @@ // Regular Comments require special logic. Comments may be attached to | ||
// or the Term parsed successfully. | ||
if (entry.type === "Comment" && blankLines === 0 && ps.current()) { | ||
if (entry.type === "Comment" && blankLines === 0 && ps.currentChar) { | ||
// Stash the comment and decide what to do with it in the next pass. | ||
@@ -962,3 +859,3 @@ lastComment = entry; | ||
if (this.withSpans) { | ||
res.addSpan(0, ps.getIndex()); | ||
res.addSpan(0, ps.index); | ||
} | ||
@@ -979,6 +876,6 @@ | ||
parseEntry(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
while (ps.currentIs("#")) { | ||
while (ps.currentChar === "#") { | ||
const skipped = this.getEntryOrJunk(ps); | ||
@@ -989,3 +886,3 @@ if (skipped.type === "Junk") { | ||
} | ||
ps.skipBlankLines(); | ||
ps.skipBlankBlock(); | ||
} | ||
@@ -997,3 +894,3 @@ | ||
getEntryOrJunk(ps) { | ||
const entryStartPos = ps.getIndex(); | ||
const entryStartPos = ps.index; | ||
@@ -1009,8 +906,12 @@ try { | ||
const errorIndex = ps.getIndex(); | ||
ps.skipToNextEntryStart(); | ||
const nextEntryStart = ps.getIndex(); | ||
let errorIndex = ps.index; | ||
ps.skipToNextEntryStart(entryStartPos); | ||
const nextEntryStart = ps.index; | ||
if (nextEntryStart < errorIndex) { | ||
// The position of the error must be inside of the Junk's span. | ||
errorIndex = nextEntryStart; | ||
} | ||
// Create a Junk instance | ||
const slice = ps.getSlice(entryStartPos, nextEntryStart); | ||
const slice = ps.string.substring(entryStartPos, nextEntryStart); | ||
const junk = new Junk(slice); | ||
@@ -1028,7 +929,7 @@ if (this.withSpans) { | ||
getEntry(ps) { | ||
if (ps.currentIs("#")) { | ||
if (ps.currentChar === "#") { | ||
return this.getComment(ps); | ||
} | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
return this.getTerm(ps); | ||
@@ -1053,3 +954,3 @@ } | ||
let i = -1; | ||
while (ps.currentIs("#") && (i < (level === -1 ? 2 : level))) { | ||
while (ps.currentChar === "#" && (i < (level === -1 ? 2 : level))) { | ||
ps.next(); | ||
@@ -1063,6 +964,6 @@ i++; | ||
if (!ps.currentIs("\n")) { | ||
if (ps.currentChar !== EOL) { | ||
ps.expectChar(" "); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== "\n"))) { | ||
while ((ch = ps.takeChar(x => x !== EOL))) { | ||
content += ch; | ||
@@ -1072,4 +973,4 @@ } | ||
if (ps.isPeekNextLineComment(level)) { | ||
content += ps.current(); | ||
if (ps.isNextLineComment(level, {skip: false})) { | ||
content += ps.currentChar; | ||
ps.next(); | ||
@@ -1099,13 +1000,10 @@ } else { | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
var pattern = this.getPattern(ps); | ||
} else { | ||
ps.skipInlineWS(); | ||
} | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({skip: true})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -1124,7 +1022,6 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
var value = this.getValue(ps); | ||
@@ -1135,3 +1032,3 @@ } else { | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({skip: true})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -1148,7 +1045,6 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
const value = this.getPattern(ps); | ||
@@ -1165,7 +1061,6 @@ return new Attribute(key, value); | ||
while (true) { | ||
ps.expectIndent(); | ||
const attr = this.getAttribute(ps); | ||
attrs.push(attr); | ||
if (!ps.isPeekNextLineAttributeStart()) { | ||
if (!ps.isNextLineAttributeStart({skip: true})) { | ||
break; | ||
@@ -1196,5 +1091,5 @@ } | ||
getVariantKey(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0013"); | ||
@@ -1209,3 +1104,3 @@ } | ||
return this.getVariantName(ps); | ||
return this.getIdentifier(ps); | ||
} | ||
@@ -1216,3 +1111,3 @@ | ||
if (ps.currentIs("*")) { | ||
if (ps.currentChar === "*") { | ||
if (hasDefault) { | ||
@@ -1228,8 +1123,11 @@ throw new ParseError("E0015"); | ||
ps.skipBlank(); | ||
const key = this.getVariantKey(ps); | ||
ps.skipBlank(); | ||
ps.expectChar("]"); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
const value = this.getValue(ps); | ||
@@ -1247,3 +1145,2 @@ return new Variant(key, value, defaultIndex); | ||
while (true) { | ||
ps.expectIndent(); | ||
const variant = this.getVariant(ps, hasDefault); | ||
@@ -1257,5 +1154,6 @@ | ||
if (!ps.isPeekNextLineVariantStart()) { | ||
if (!ps.isNextLineVariantStart({skip: false})) { | ||
break; | ||
} | ||
ps.skipBlank(); | ||
} | ||
@@ -1270,17 +1168,2 @@ | ||
getVariantName(ps) { | ||
let name = ps.takeIDStart(); | ||
while (true) { | ||
const ch = ps.takeVariantNameChar(); | ||
if (ch) { | ||
name += ch; | ||
} else { | ||
break; | ||
} | ||
} | ||
return new VariantName(name.replace(trailingWSRe, "")); | ||
} | ||
getDigits(ps) { | ||
@@ -1304,3 +1187,3 @@ let num = ""; | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
@@ -1312,3 +1195,3 @@ ps.next(); | ||
if (ps.currentIs(".")) { | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
@@ -1323,8 +1206,10 @@ ps.next(); | ||
getValue(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
ps.peek(); | ||
ps.peekInlineWS(); | ||
if (ps.isPeekNextLineVariantStart()) { | ||
ps.peekBlankInline(); | ||
if (ps.isNextLineVariantStart({skip: false})) { | ||
return this.getVariantList(ps); | ||
} | ||
ps.resetPeek(); | ||
} | ||
@@ -1337,5 +1222,8 @@ | ||
ps.expectChar("{"); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.expectIndent(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
ps.expectChar("}"); | ||
@@ -1347,10 +1235,9 @@ return new VariantList(variants); | ||
const elements = []; | ||
ps.skipInlineWS(); | ||
let ch; | ||
while ((ch = ps.current())) { | ||
while ((ch = ps.currentChar)) { | ||
// The end condition for getPattern's while loop is a newline | ||
// which is not followed by a valid pattern continuation. | ||
if (ch === "\n" && !ps.isPeekNextLineValue()) { | ||
if (ch === EOL && !ps.isNextLineValue({skip: false})) { | ||
break; | ||
@@ -1372,2 +1259,5 @@ } | ||
lastElement.value = lastElement.value.replace(trailingWSRe, ""); | ||
if (lastElement.value === "") { | ||
elements.pop(); | ||
} | ||
} | ||
@@ -1382,3 +1272,3 @@ | ||
let ch; | ||
while ((ch = ps.current())) { | ||
while ((ch = ps.currentChar)) { | ||
if (ch === "{") { | ||
@@ -1388,4 +1278,4 @@ return new TextElement(buffer); | ||
if (ch === "\n") { | ||
if (!ps.isPeekNextLineValue()) { | ||
if (ch === EOL) { | ||
if (!ps.isNextLineValue({skip: false})) { | ||
return new TextElement(buffer); | ||
@@ -1395,6 +1285,5 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
// Add the new line to the buffer | ||
buffer += ch; | ||
buffer += EOL; | ||
continue; | ||
@@ -1406,6 +1295,7 @@ } | ||
buffer += this.getEscapeSequence(ps); | ||
} else { | ||
buffer += ch; | ||
ps.next(); | ||
continue; | ||
} | ||
buffer += ch; | ||
ps.next(); | ||
} | ||
@@ -1417,3 +1307,3 @@ | ||
getEscapeSequence(ps, specials = ["{", "\\"]) { | ||
const next = ps.current(); | ||
const next = ps.currentChar; | ||
@@ -1432,4 +1322,4 @@ if (specials.includes(next)) { | ||
if (ch === undefined) { | ||
throw new ParseError("E0026", sequence + ps.current()); | ||
if (!ch) { | ||
throw new ParseError("E0026", sequence + ps.currentChar); | ||
} | ||
@@ -1454,12 +1344,11 @@ | ||
getExpression(ps) { | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
const selector = this.getSelectorExpression(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.currentIs("-")) { | ||
ps.peek(); | ||
if (ps.currentChar === "-") { | ||
if (!ps.currentPeekIs(">")) { | ||
if (ps.peek() !== ">") { | ||
ps.resetPeek(); | ||
@@ -1485,5 +1374,8 @@ return selector; | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.skipBlank(); | ||
@@ -1499,4 +1391,2 @@ if (variants.length === 0) { | ||
ps.expectIndent(); | ||
return new SelectExpression(selector, variants); | ||
@@ -1508,2 +1398,4 @@ } else if (selector.type === "AttributeExpression" && | ||
ps.skipBlank(); | ||
return selector; | ||
@@ -1513,3 +1405,3 @@ } | ||
getSelectorExpression(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
return this.getPlaceable(ps); | ||
@@ -1524,3 +1416,3 @@ } | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
@@ -1577,5 +1469,5 @@ if (ch === ".") { | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.current() !== ":") { | ||
if (ps.currentChar !== ":") { | ||
return exp; | ||
@@ -1589,3 +1481,3 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
@@ -1602,7 +1494,6 @@ const val = this.getArgVal(ps); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
while (true) { | ||
if (ps.current() === ")") { | ||
if (ps.currentChar === ")") { | ||
break; | ||
@@ -1624,9 +1515,7 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
if (ps.current() === ",") { | ||
if (ps.currentChar === ",") { | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
continue; | ||
@@ -1646,3 +1535,3 @@ } else { | ||
return this.getNumber(ps); | ||
} else if (ps.currentIs('"')) { | ||
} else if (ps.currentChar === '"') { | ||
return this.getString(ps); | ||
@@ -1656,6 +1545,6 @@ } | ||
ps.expectChar('"'); | ||
ps.expectChar("\""); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== "\n"))) { | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== EOL))) { | ||
if (ch === "\\") { | ||
@@ -1668,7 +1557,7 @@ val += this.getEscapeSequence(ps, ["{", "\\", "\""]); | ||
if (ps.currentIs("\n")) { | ||
if (ps.currentChar === EOL) { | ||
throw new ParseError("E0020"); | ||
} | ||
ps.next(); | ||
ps.expectChar("\""); | ||
@@ -1680,5 +1569,5 @@ return new StringLiteral(val); | ||
getLiteral(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0014"); | ||
@@ -2019,12 +1908,6 @@ } | ||
function serializeVariantName(VariantName) { | ||
return VariantName.name; | ||
} | ||
function serializeVariantKey(key) { | ||
switch (key.type) { | ||
case "VariantName": | ||
return serializeVariantName(key); | ||
case "Identifier": | ||
return serializeIdentifier(key); | ||
case "NumberLiteral": | ||
@@ -2102,3 +1985,2 @@ return serializeNumberLiteral(key); | ||
exports.Identifier = Identifier; | ||
exports.VariantName = VariantName; | ||
exports.BaseComment = BaseComment; | ||
@@ -2105,0 +1987,0 @@ exports.Comment = Comment; |
{ | ||
"name": "fluent-syntax", | ||
"description": "AST and parser for Fluent", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"homepage": "http://projectfluent.org", | ||
@@ -6,0 +6,0 @@ "author": "Mozilla <l10n-drivers@mozilla.org>", |
@@ -211,9 +211,2 @@ /* | ||
export class VariantName extends Identifier { | ||
constructor(name) { | ||
super(name); | ||
this.type = "VariantName"; | ||
} | ||
} | ||
export class BaseComment extends Entry { | ||
@@ -220,0 +213,0 @@ constructor(content) { |
/* eslint no-magic-numbers: [0] */ | ||
import * as AST from "./ast"; | ||
import { FTLParserStream } from "./ftlstream"; | ||
import { EOF, EOL, FluentParserStream } from "./stream"; | ||
import { ParseError } from "./errors"; | ||
@@ -17,3 +17,3 @@ | ||
const start = ps.getIndex(); | ||
const start = ps.index; | ||
const node = fn.call(this, ps, ...args); | ||
@@ -27,3 +27,3 @@ | ||
const end = ps.getIndex(); | ||
const end = ps.index; | ||
node.addSpan(start, end); | ||
@@ -44,6 +44,6 @@ return node; | ||
"getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier", | ||
"getTermIdentifier", "getVariant", "getVariantName", "getNumber", | ||
"getTermIdentifier", "getVariant", "getNumber", | ||
"getValue", "getPattern", "getVariantList", "getTextElement", | ||
"getPlaceable", "getExpression", "getSelectorExpression", "getCallArg", | ||
"getString", "getLiteral", "getVariantList" | ||
"getString", "getLiteral" | ||
]; | ||
@@ -56,4 +56,4 @@ for (const name of methodNames) { | ||
parse(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
@@ -63,5 +63,5 @@ const entries = []; | ||
while (ps.current()) { | ||
while (ps.currentChar) { | ||
const entry = this.getEntryOrJunk(ps); | ||
const blankLines = ps.skipBlankLines(); | ||
const blankLines = ps.skipBlankBlock(); | ||
@@ -73,3 +73,3 @@ // Regular Comments require special logic. Comments may be attached to | ||
// or the Term parsed successfully. | ||
if (entry.type === "Comment" && blankLines === 0 && ps.current()) { | ||
if (entry.type === "Comment" && blankLines === 0 && ps.currentChar) { | ||
// Stash the comment and decide what to do with it in the next pass. | ||
@@ -100,3 +100,3 @@ lastComment = entry; | ||
if (this.withSpans) { | ||
res.addSpan(0, ps.getIndex()); | ||
res.addSpan(0, ps.index); | ||
} | ||
@@ -117,6 +117,6 @@ | ||
parseEntry(source) { | ||
const ps = new FTLParserStream(source); | ||
ps.skipBlankLines(); | ||
const ps = new FluentParserStream(source); | ||
ps.skipBlankBlock(); | ||
while (ps.currentIs("#")) { | ||
while (ps.currentChar === "#") { | ||
const skipped = this.getEntryOrJunk(ps); | ||
@@ -127,3 +127,3 @@ if (skipped.type === "Junk") { | ||
} | ||
ps.skipBlankLines(); | ||
ps.skipBlankBlock(); | ||
} | ||
@@ -135,3 +135,3 @@ | ||
getEntryOrJunk(ps) { | ||
const entryStartPos = ps.getIndex(); | ||
const entryStartPos = ps.index; | ||
@@ -147,8 +147,12 @@ try { | ||
const errorIndex = ps.getIndex(); | ||
ps.skipToNextEntryStart(); | ||
const nextEntryStart = ps.getIndex(); | ||
let errorIndex = ps.index; | ||
ps.skipToNextEntryStart(entryStartPos); | ||
const nextEntryStart = ps.index; | ||
if (nextEntryStart < errorIndex) { | ||
// The position of the error must be inside of the Junk's span. | ||
errorIndex = nextEntryStart; | ||
} | ||
// Create a Junk instance | ||
const slice = ps.getSlice(entryStartPos, nextEntryStart); | ||
const slice = ps.string.substring(entryStartPos, nextEntryStart); | ||
const junk = new AST.Junk(slice); | ||
@@ -166,7 +170,7 @@ if (this.withSpans) { | ||
getEntry(ps) { | ||
if (ps.currentIs("#")) { | ||
if (ps.currentChar === "#") { | ||
return this.getComment(ps); | ||
} | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
return this.getTerm(ps); | ||
@@ -191,3 +195,3 @@ } | ||
let i = -1; | ||
while (ps.currentIs("#") && (i < (level === -1 ? 2 : level))) { | ||
while (ps.currentChar === "#" && (i < (level === -1 ? 2 : level))) { | ||
ps.next(); | ||
@@ -201,6 +205,6 @@ i++; | ||
if (!ps.currentIs("\n")) { | ||
if (ps.currentChar !== EOL) { | ||
ps.expectChar(" "); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== "\n"))) { | ||
while ((ch = ps.takeChar(x => x !== EOL))) { | ||
content += ch; | ||
@@ -210,4 +214,4 @@ } | ||
if (ps.isPeekNextLineComment(level)) { | ||
content += ps.current(); | ||
if (ps.isNextLineComment(level, {skip: false})) { | ||
content += ps.currentChar; | ||
ps.next(); | ||
@@ -237,13 +241,10 @@ } else { | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
var pattern = this.getPattern(ps); | ||
} else { | ||
ps.skipInlineWS(); | ||
} | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({skip: true})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -262,7 +263,6 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
var value = this.getValue(ps); | ||
@@ -273,3 +273,3 @@ } else { | ||
if (ps.isPeekNextLineAttributeStart()) { | ||
if (ps.isNextLineAttributeStart({skip: true})) { | ||
var attrs = this.getAttributes(ps); | ||
@@ -286,7 +286,6 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectChar("="); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
const value = this.getPattern(ps); | ||
@@ -303,7 +302,6 @@ return new AST.Attribute(key, value); | ||
while (true) { | ||
ps.expectIndent(); | ||
const attr = this.getAttribute(ps); | ||
attrs.push(attr); | ||
if (!ps.isPeekNextLineAttributeStart()) { | ||
if (!ps.isNextLineAttributeStart({skip: true})) { | ||
break; | ||
@@ -334,5 +332,5 @@ } | ||
getVariantKey(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0013"); | ||
@@ -347,3 +345,3 @@ } | ||
return this.getVariantName(ps); | ||
return this.getIdentifier(ps); | ||
} | ||
@@ -354,3 +352,3 @@ | ||
if (ps.currentIs("*")) { | ||
if (ps.currentChar === "*") { | ||
if (hasDefault) { | ||
@@ -366,8 +364,11 @@ throw new ParseError("E0015"); | ||
ps.skipBlank(); | ||
const key = this.getVariantKey(ps); | ||
ps.skipBlank(); | ||
ps.expectChar("]"); | ||
if (ps.isPeekValueStart()) { | ||
ps.skipIndent(); | ||
if (ps.isValueStart({skip: true})) { | ||
const value = this.getValue(ps); | ||
@@ -385,3 +386,2 @@ return new AST.Variant(key, value, defaultIndex); | ||
while (true) { | ||
ps.expectIndent(); | ||
const variant = this.getVariant(ps, hasDefault); | ||
@@ -395,5 +395,6 @@ | ||
if (!ps.isPeekNextLineVariantStart()) { | ||
if (!ps.isNextLineVariantStart({skip: false})) { | ||
break; | ||
} | ||
ps.skipBlank(); | ||
} | ||
@@ -408,17 +409,2 @@ | ||
getVariantName(ps) { | ||
let name = ps.takeIDStart(); | ||
while (true) { | ||
const ch = ps.takeVariantNameChar(); | ||
if (ch) { | ||
name += ch; | ||
} else { | ||
break; | ||
} | ||
} | ||
return new AST.VariantName(name.replace(trailingWSRe, "")); | ||
} | ||
getDigits(ps) { | ||
@@ -442,3 +428,3 @@ let num = ""; | ||
if (ps.currentIs("-")) { | ||
if (ps.currentChar === "-") { | ||
num += "-"; | ||
@@ -450,3 +436,3 @@ ps.next(); | ||
if (ps.currentIs(".")) { | ||
if (ps.currentChar === ".") { | ||
num += "."; | ||
@@ -461,8 +447,10 @@ ps.next(); | ||
getValue(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
ps.peek(); | ||
ps.peekInlineWS(); | ||
if (ps.isPeekNextLineVariantStart()) { | ||
ps.peekBlankInline(); | ||
if (ps.isNextLineVariantStart({skip: false})) { | ||
return this.getVariantList(ps); | ||
} | ||
ps.resetPeek(); | ||
} | ||
@@ -475,5 +463,8 @@ | ||
ps.expectChar("{"); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.expectIndent(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
ps.expectChar("}"); | ||
@@ -485,10 +476,9 @@ return new AST.VariantList(variants); | ||
const elements = []; | ||
ps.skipInlineWS(); | ||
let ch; | ||
while ((ch = ps.current())) { | ||
while ((ch = ps.currentChar)) { | ||
// The end condition for getPattern's while loop is a newline | ||
// which is not followed by a valid pattern continuation. | ||
if (ch === "\n" && !ps.isPeekNextLineValue()) { | ||
if (ch === EOL && !ps.isNextLineValue({skip: false})) { | ||
break; | ||
@@ -510,2 +500,5 @@ } | ||
lastElement.value = lastElement.value.replace(trailingWSRe, ""); | ||
if (lastElement.value === "") { | ||
elements.pop(); | ||
} | ||
} | ||
@@ -520,3 +513,3 @@ | ||
let ch; | ||
while ((ch = ps.current())) { | ||
while ((ch = ps.currentChar)) { | ||
if (ch === "{") { | ||
@@ -526,4 +519,4 @@ return new AST.TextElement(buffer); | ||
if (ch === "\n") { | ||
if (!ps.isPeekNextLineValue()) { | ||
if (ch === EOL) { | ||
if (!ps.isNextLineValue({skip: false})) { | ||
return new AST.TextElement(buffer); | ||
@@ -533,6 +526,5 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
// Add the new line to the buffer | ||
buffer += ch; | ||
buffer += EOL; | ||
continue; | ||
@@ -544,6 +536,7 @@ } | ||
buffer += this.getEscapeSequence(ps); | ||
} else { | ||
buffer += ch; | ||
ps.next(); | ||
continue; | ||
} | ||
buffer += ch; | ||
ps.next(); | ||
} | ||
@@ -555,3 +548,3 @@ | ||
getEscapeSequence(ps, specials = ["{", "\\"]) { | ||
const next = ps.current(); | ||
const next = ps.currentChar; | ||
@@ -570,4 +563,4 @@ if (specials.includes(next)) { | ||
if (ch === undefined) { | ||
throw new ParseError("E0026", sequence + ps.current()); | ||
if (!ch) { | ||
throw new ParseError("E0026", sequence + ps.currentChar); | ||
} | ||
@@ -592,12 +585,11 @@ | ||
getExpression(ps) { | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
const selector = this.getSelectorExpression(ps); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.currentIs("-")) { | ||
ps.peek(); | ||
if (ps.currentChar === "-") { | ||
if (!ps.currentPeekIs(">")) { | ||
if (ps.peek() !== ">") { | ||
ps.resetPeek(); | ||
@@ -623,5 +615,8 @@ return selector; | ||
ps.skipInlineWS(); | ||
ps.skipBlankInline(); | ||
ps.expectLineEnd(); | ||
ps.skipBlank(); | ||
const variants = this.getVariants(ps); | ||
ps.skipBlank(); | ||
@@ -637,4 +632,2 @@ if (variants.length === 0) { | ||
ps.expectIndent(); | ||
return new AST.SelectExpression(selector, variants); | ||
@@ -646,2 +639,4 @@ } else if (selector.type === "AttributeExpression" && | ||
ps.skipBlank(); | ||
return selector; | ||
@@ -651,3 +646,3 @@ } | ||
getSelectorExpression(ps) { | ||
if (ps.currentIs("{")) { | ||
if (ps.currentChar === "{") { | ||
return this.getPlaceable(ps); | ||
@@ -662,3 +657,3 @@ } | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
@@ -715,5 +710,5 @@ if (ch === ".") { | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
if (ps.current() !== ":") { | ||
if (ps.currentChar !== ":") { | ||
return exp; | ||
@@ -727,3 +722,3 @@ } | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipBlank(); | ||
@@ -740,7 +735,6 @@ const val = this.getArgVal(ps); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
while (true) { | ||
if (ps.current() === ")") { | ||
if (ps.currentChar === ")") { | ||
break; | ||
@@ -762,9 +756,7 @@ } | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
if (ps.current() === ",") { | ||
if (ps.currentChar === ",") { | ||
ps.next(); | ||
ps.skipInlineWS(); | ||
ps.skipIndent(); | ||
ps.skipBlank(); | ||
continue; | ||
@@ -784,3 +776,3 @@ } else { | ||
return this.getNumber(ps); | ||
} else if (ps.currentIs('"')) { | ||
} else if (ps.currentChar === '"') { | ||
return this.getString(ps); | ||
@@ -794,6 +786,6 @@ } | ||
ps.expectChar('"'); | ||
ps.expectChar("\""); | ||
let ch; | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== "\n"))) { | ||
while ((ch = ps.takeChar(x => x !== '"' && x !== EOL))) { | ||
if (ch === "\\") { | ||
@@ -806,7 +798,7 @@ val += this.getEscapeSequence(ps, ["{", "\\", "\""]); | ||
if (ps.currentIs("\n")) { | ||
if (ps.currentChar === EOL) { | ||
throw new ParseError("E0020"); | ||
} | ||
ps.next(); | ||
ps.expectChar("\""); | ||
@@ -818,5 +810,5 @@ return new AST.StringLiteral(val); | ||
getLiteral(ps) { | ||
const ch = ps.current(); | ||
const ch = ps.currentChar; | ||
if (!ch) { | ||
if (ch === EOF) { | ||
throw new ParseError("E0014"); | ||
@@ -823,0 +815,0 @@ } |
@@ -307,12 +307,6 @@ import { includes } from "./util"; | ||
function serializeVariantName(VariantName) { | ||
return VariantName.name; | ||
} | ||
function serializeVariantKey(key) { | ||
switch (key.type) { | ||
case "VariantName": | ||
return serializeVariantName(key); | ||
case "Identifier": | ||
return serializeIdentifier(key); | ||
case "NumberLiteral": | ||
@@ -319,0 +313,0 @@ return serializeNumberLiteral(key); |
@@ -0,130 +1,377 @@ | ||
/* eslint no-magic-numbers: "off" */ | ||
import { ParseError } from "./errors"; | ||
import { includes } from "./util"; | ||
export class ParserStream { | ||
constructor(string) { | ||
this.string = string; | ||
this.iter = string[Symbol.iterator](); | ||
this.buf = []; | ||
this.peekIndex = 0; | ||
this.index = 0; | ||
this.peekOffset = 0; | ||
} | ||
this.iterEnd = false; | ||
this.peekEnd = false; | ||
charAt(offset) { | ||
// When the cursor is at CRLF, return LF but don't move the cursor. | ||
// The cursor still points to the EOL position, which in this case is the | ||
// beginning of the compound CRLF sequence. This ensures slices of | ||
// [inclusive, exclusive) continue to work properly. | ||
if (this.string[offset] === "\r" | ||
&& this.string[offset + 1] === "\n") { | ||
return "\n"; | ||
} | ||
this.ch = this.iter.next().value; | ||
return this.string[offset]; | ||
} | ||
get currentChar() { | ||
return this.charAt(this.index); | ||
} | ||
get currentPeek() { | ||
return this.charAt(this.index + this.peekOffset); | ||
} | ||
next() { | ||
if (this.iterEnd) { | ||
return undefined; | ||
this.peekOffset = 0; | ||
// Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index] === "\r" | ||
&& this.string[this.index + 1] === "\n") { | ||
this.index++; | ||
} | ||
this.index++; | ||
return this.string[this.index]; | ||
} | ||
if (this.buf.length === 0) { | ||
this.ch = this.iter.next().value; | ||
} else { | ||
this.ch = this.buf.shift(); | ||
peek() { | ||
// Skip over the CRLF as if it was a single character. | ||
if (this.string[this.index + this.peekOffset] === "\r" | ||
&& this.string[this.index + this.peekOffset + 1] === "\n") { | ||
this.peekOffset++; | ||
} | ||
this.peekOffset++; | ||
return this.string[this.index + this.peekOffset]; | ||
} | ||
this.index++; | ||
resetPeek(offset = 0) { | ||
this.peekOffset = offset; | ||
} | ||
if (this.ch === undefined) { | ||
this.iterEnd = true; | ||
this.peekEnd = true; | ||
skipToPeek() { | ||
this.index += this.peekOffset; | ||
this.peekOffset = 0; | ||
} | ||
} | ||
export const EOL = "\n"; | ||
export const EOF = undefined; | ||
const SPECIAL_LINE_START_CHARS = ["}", ".", "[", "*"]; | ||
export class FluentParserStream extends ParserStream { | ||
skipBlankInline() { | ||
while (this.currentChar === " ") { | ||
this.next(); | ||
} | ||
} | ||
this.peekIndex = this.index; | ||
peekBlankInline() { | ||
while (this.currentPeek === " ") { | ||
this.peek(); | ||
} | ||
} | ||
return this.ch; | ||
skipBlankBlock() { | ||
let lineCount = 0; | ||
while (true) { | ||
this.peekBlankInline(); | ||
if (this.currentPeek === EOL) { | ||
this.next(); | ||
lineCount++; | ||
} else { | ||
this.resetPeek(); | ||
return lineCount; | ||
} | ||
} | ||
} | ||
current() { | ||
return this.ch; | ||
peekBlankBlock() { | ||
while (true) { | ||
const lineStart = this.peekOffset; | ||
this.peekBlankInline(); | ||
if (this.currentPeek === EOL) { | ||
this.peek(); | ||
} else { | ||
this.resetPeek(lineStart); | ||
break; | ||
} | ||
} | ||
} | ||
currentIs(ch) { | ||
return this.ch === ch; | ||
skipBlank() { | ||
while (this.currentChar === " " || this.currentChar === EOL) { | ||
this.next(); | ||
} | ||
} | ||
currentPeek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
peekBlank() { | ||
while (this.currentPeek === " " || this.currentPeek === EOL) { | ||
this.peek(); | ||
} | ||
} | ||
const diff = this.peekIndex - this.index; | ||
expectChar(ch) { | ||
if (this.currentChar === ch) { | ||
this.next(); | ||
return true; | ||
} | ||
if (diff === 0) { | ||
return this.ch; | ||
throw new ParseError("E0003", ch); | ||
} | ||
expectLineEnd() { | ||
if (this.currentChar === EOF) { | ||
// EOF is a valid line end in Fluent. | ||
return true; | ||
} | ||
return this.buf[diff - 1]; | ||
if (this.currentChar === EOL) { | ||
this.next(); | ||
return true; | ||
} | ||
// Unicode Character 'SYMBOL FOR NEWLINE' (U+2424) | ||
throw new ParseError("E0003", "\u2424"); | ||
} | ||
currentPeekIs(ch) { | ||
return this.currentPeek() === ch; | ||
takeChar(f) { | ||
const ch = this.currentChar; | ||
if (ch === EOF) { | ||
return EOF; | ||
} | ||
if (f(ch)) { | ||
this.next(); | ||
return ch; | ||
} | ||
return null; | ||
} | ||
peek() { | ||
if (this.peekEnd) { | ||
return undefined; | ||
isCharIDStart(ch) { | ||
if (ch === EOF) { | ||
return false; | ||
} | ||
this.peekIndex += 1; | ||
const cc = ch.charCodeAt(0); | ||
return (cc >= 97 && cc <= 122) || // a-z | ||
(cc >= 65 && cc <= 90); // A-Z | ||
} | ||
const diff = this.peekIndex - this.index; | ||
isIdentifierStart() { | ||
return this.isCharIDStart(this.currentPeek); | ||
} | ||
if (diff > this.buf.length) { | ||
const ch = this.iter.next().value; | ||
if (ch !== undefined) { | ||
this.buf.push(ch); | ||
} else { | ||
this.peekEnd = true; | ||
return undefined; | ||
} | ||
isNumberStart() { | ||
const ch = this.currentChar === "-" | ||
? this.peek() | ||
: this.currentChar; | ||
if (ch === EOF) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
return this.buf[diff - 1]; | ||
const cc = ch.charCodeAt(0); | ||
const isDigit = cc >= 48 && cc <= 57; // 0-9 | ||
this.resetPeek(); | ||
return isDigit; | ||
} | ||
getIndex() { | ||
return this.index; | ||
isCharPatternContinuation(ch) { | ||
if (ch === EOF) { | ||
return false; | ||
} | ||
return !includes(SPECIAL_LINE_START_CHARS, ch); | ||
} | ||
getPeekIndex() { | ||
return this.peekIndex; | ||
isValueStart({skip = true}) { | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peekBlankInline(); | ||
const ch = this.currentPeek; | ||
// Inline Patterns may start with any char. | ||
if (ch !== EOF && ch !== EOL) { | ||
this.skipToPeek(); | ||
return true; | ||
} | ||
return this.isNextLineValue({skip}); | ||
} | ||
peekCharIs(ch) { | ||
if (this.peekEnd) { | ||
// -1 - any | ||
// 0 - comment | ||
// 1 - group comment | ||
// 2 - resource comment | ||
isNextLineComment(level = -1, {skip = false}) { | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
const ret = this.peek(); | ||
let i = 0; | ||
this.peekIndex -= 1; | ||
while (i <= level || (level === -1 && i < 3)) { | ||
if (this.peek() !== "#") { | ||
if (i <= level && level !== -1) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
break; | ||
} | ||
i++; | ||
} | ||
return ret === ch; | ||
// The first char after #, ## or ###. | ||
const ch = this.peek(); | ||
if (ch === " " || ch === EOL) { | ||
this.resetPeek(); | ||
return true; | ||
} | ||
this.resetPeek(); | ||
return false; | ||
} | ||
resetPeek(pos) { | ||
if (pos) { | ||
if (pos < this.peekIndex) { | ||
this.peekEnd = false; | ||
isNextLineVariantStart({skip = false}) { | ||
if (skip === true) throw new Error("Unimplemented"); | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peekBlank(); | ||
if (this.currentPeek === "*") { | ||
this.peek(); | ||
} | ||
if (this.currentPeek === "[") { | ||
this.resetPeek(); | ||
return true; | ||
} | ||
this.resetPeek(); | ||
return false; | ||
} | ||
isNextLineAttributeStart({skip = true}) { | ||
if (skip === false) throw new Error("Unimplemented"); | ||
this.peekBlank(); | ||
if (this.currentPeek === ".") { | ||
this.skipToPeek(); | ||
return true; | ||
} | ||
this.resetPeek(); | ||
return false; | ||
} | ||
isNextLineValue({skip = true}) { | ||
if (this.currentPeek !== EOL) { | ||
return false; | ||
} | ||
this.peekBlankBlock(); | ||
const ptr = this.peekOffset; | ||
this.peekBlankInline(); | ||
if (this.currentPeek !== "{") { | ||
if (this.peekOffset - ptr === 0) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
this.peekIndex = pos; | ||
if (!this.isCharPatternContinuation(this.currentPeek)) { | ||
this.resetPeek(); | ||
return false; | ||
} | ||
} | ||
if (skip) { | ||
this.skipToPeek(); | ||
} else { | ||
this.peekIndex = this.index; | ||
this.peekEnd = this.iterEnd; | ||
this.resetPeek(); | ||
} | ||
return true; | ||
} | ||
skipToPeek() { | ||
const diff = this.peekIndex - this.index; | ||
skipToNextEntryStart(junkStart) { | ||
let lastNewline = this.string.lastIndexOf(EOL, this.index); | ||
if (junkStart < lastNewline) { | ||
// Last seen newline is _after_ the junk start. It's safe to rewind | ||
// without the risk of resuming at the same broken entry. | ||
this.index = lastNewline; | ||
} | ||
while (this.currentChar) { | ||
// We're only interested in beginnings of line. | ||
if (this.currentChar !== EOL) { | ||
this.next(); | ||
continue; | ||
} | ||
for (let i = 0; i < diff; i++) { | ||
this.ch = this.buf.shift(); | ||
// Break if the first char in this line looks like an entry start. | ||
const first = this.next(); | ||
if (this.isCharIDStart(first) || first === "-" || first === "#") { | ||
break; | ||
} | ||
} | ||
} | ||
this.index = this.peekIndex; | ||
takeIDStart() { | ||
if (this.isCharIDStart(this.currentChar)) { | ||
const ret = this.currentChar; | ||
this.next(); | ||
return ret; | ||
} | ||
throw new ParseError("E0004", "a-zA-Z"); | ||
} | ||
getSlice(start, end) { | ||
return this.string.substring(start, end); | ||
takeIDChar() { | ||
const closure = ch => { | ||
const cc = ch.charCodeAt(0); | ||
return ((cc >= 97 && cc <= 122) || // a-z | ||
(cc >= 65 && cc <= 90) || // A-Z | ||
(cc >= 48 && cc <= 57) || // 0-9 | ||
cc === 95 || cc === 45); // _- | ||
}; | ||
return this.takeChar(closure); | ||
} | ||
takeDigit() { | ||
const closure = ch => { | ||
const cc = ch.charCodeAt(0); | ||
return (cc >= 48 && cc <= 57); // 0-9 | ||
}; | ||
return this.takeChar(closure); | ||
} | ||
takeHexDigit() { | ||
const closure = ch => { | ||
const cc = ch.charCodeAt(0); | ||
return (cc >= 48 && cc <= 57) // 0-9 | ||
|| (cc >= 65 && cc <= 70) // A-F | ||
|| (cc >= 97 && cc <= 102); // a-f | ||
}; | ||
return this.takeChar(closure); | ||
} | ||
} |
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
146704
4758