Skip to content

Commit 71d98b0

Browse files
committed
Refactor getValue to take allowVariantList
1 parent 7356eb4 commit 71d98b0

File tree

7 files changed

+72
-59
lines changed

7 files changed

+72
-59
lines changed

fluent-syntax/src/errors.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ function getErrorMessage(code, args) {
6262
return "Positional arguments must not follow named arguments";
6363
case "E0022":
6464
return "Named arguments must be unique";
65-
case "E0023":
66-
return "VariantLists are only allowed inside of other VariantLists.";
6765
case "E0024":
6866
return "Cannot access variants of a message.";
6967
case "E0025": {

fluent-syntax/src/parser.js

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class FluentParser {
4343
// Poor man's decorators.
4444
const methodNames = [
4545
"getComment", "getMessage", "getTerm", "getAttribute", "getIdentifier",
46-
"getVariant", "getNumber", "getValue", "getPattern", "getVariantList",
46+
"getVariant", "getNumber", "getPattern", "getVariantList",
4747
"getTextElement", "getPlaceable", "getExpression",
4848
"getSelectorExpression", "getCallArg", "getString", "getLiteral"
4949
];
@@ -231,7 +231,7 @@ export default class FluentParser {
231231
ps.skipBlankInline();
232232
ps.expectChar("=");
233233

234-
const value = this.getInlineOrBlock(ps, this.getPattern);
234+
const value = this.getValue(ps, {allowVariantList: false});
235235
const attrs = this.getAttributes(ps);
236236

237237
if (value === null && attrs.length === 0) {
@@ -248,7 +248,11 @@ export default class FluentParser {
248248
ps.skipBlankInline();
249249
ps.expectChar("=");
250250

251-
const value = this.getInlineOrBlock(ps, this.getValue);
251+
// XXX Once https://github.com/projectfluent/fluent/pull/220 lands,
252+
// getTerm will be the only place where VariantLists are still legal. Move
253+
// the code currently present in getValueType up to here then, and remove
254+
// the allowVariantList switch.
255+
const value = this.getValue(ps, {allowVariantList: true});
252256
if (value === null) {
253257
throw new ParseError("E0006", id.name);
254258
}
@@ -257,22 +261,6 @@ export default class FluentParser {
257261
return new AST.Term(id, value, attrs);
258262
}
259263

260-
getInlineOrBlock(ps, method) {
261-
ps.peekBlankInline();
262-
if (ps.isValueStart()) {
263-
ps.skipToPeek();
264-
return method.call(this, ps, {isBlock: false});
265-
}
266-
267-
ps.peekBlankBlock();
268-
if (ps.isValueContinuation()) {
269-
ps.skipToPeek();
270-
return method.call(this, ps, {isBlock: true});
271-
}
272-
273-
return null;
274-
}
275-
276264
getAttribute(ps) {
277265
ps.expectChar(".");
278266

@@ -281,7 +269,7 @@ export default class FluentParser {
281269
ps.skipBlankInline();
282270
ps.expectChar("=");
283271

284-
const value = this.getInlineOrBlock(ps, this.getPattern);
272+
const value = this.getValue(ps, {allowVariantList: false});
285273
if (value === null) {
286274
throw new ParseError("E0012");
287275
}
@@ -328,7 +316,7 @@ export default class FluentParser {
328316
return this.getIdentifier(ps);
329317
}
330318

331-
getVariant(ps, hasDefault) {
319+
getVariant(ps, {hasDefault, allowVariantList}) {
332320
let defaultIndex = false;
333321

334322
if (ps.currentChar === "*") {
@@ -349,21 +337,23 @@ export default class FluentParser {
349337
ps.skipBlank();
350338
ps.expectChar("]");
351339

352-
const value = this.getInlineOrBlock(ps, this.getValue);
340+
// XXX We need to pass allowVariantList all the way down to here because
341+
// nested VariantLists in Terms are legal for now.
342+
const value = this.getValue(ps, {allowVariantList});
353343
if (value === null) {
354344
throw new ParseError("E0012");
355345
}
356346

357347
return new AST.Variant(key, value, defaultIndex);
358348
}
359349

360-
getVariants(ps) {
350+
getVariants(ps, {allowVariantList}) {
361351
const variants = [];
362352
let hasDefault = false;
363353

364354
ps.skipBlank();
365355
while (ps.isVariantStart()) {
366-
const variant = this.getVariant(ps, hasDefault);
356+
const variant = this.getVariant(ps, {allowVariantList, hasDefault});
367357

368358
if (variant.default) {
369359
hasDefault = true;
@@ -419,36 +409,63 @@ export default class FluentParser {
419409
return new AST.NumberLiteral(num);
420410
}
421411

422-
getValue(ps, {isBlock}) {
412+
// getValue distinguished between patterns which start on the same line as the
413+
// identifier (a.k.a. inline signleline patterns and inline multiline
414+
// patterns) and patterns which start on a new line (a.k.a. block multiline
415+
// patterns). The distinction is important for the dedentation logic: the
416+
// indent of the first line of a block pattern must be taken into account when
417+
// calculating the maximum common indent.
418+
getValue(ps, {allowVariantList}) {
423419
ps.peekBlankInline();
424-
const peekOffset = ps.peekOffset;
425-
if (ps.currentPeek === "{") {
420+
if (ps.isValueStart()) {
421+
ps.skipToPeek();
422+
return this.getValueType(
423+
ps, {isBlock: false, allowVariantList});
424+
}
425+
426+
ps.peekBlankBlock();
427+
if (ps.isValueContinuation()) {
428+
ps.skipToPeek();
429+
return this.getValueType(ps, {isBlock: true, allowVariantList});
430+
}
431+
432+
return null;
433+
}
434+
435+
// Parse a VariantList (if allowed) or a Pattern.
436+
getValueType(ps, {isBlock, allowVariantList}) {
437+
ps.peekBlankInline();
438+
if (allowVariantList && ps.currentPeek === "{") {
439+
const start = ps.peekOffset;
426440
ps.peek();
427-
ps.peekBlank();
428-
if (ps.isVariantStart()) {
429-
ps.resetPeek(peekOffset);
430-
ps.skipToPeek();
431-
return this.getVariantList(ps);
441+
ps.peekBlankInline();
442+
if (ps.currentPeek === EOL) {
443+
ps.peekBlank();
444+
if (ps.isVariantStart()) {
445+
ps.resetPeek(start);
446+
ps.skipToPeek();
447+
return this.getVariantList(ps, {allowVariantList});
448+
}
432449
}
433450
}
434451

435452
ps.resetPeek();
436-
return this.getPattern(ps, {isBlock});
453+
const pattern = this.getPattern(ps, {isBlock});
454+
return pattern;
437455
}
438456

439457
getVariantList(ps) {
440458
ps.expectChar("{");
441-
ps.skipBlankInline();
442-
ps.expectLineEnd();
443-
const variants = this.getVariants(ps);
444-
ps.skipBlank();
459+
var variants = this.getVariants(ps, {allowVariantList: true});
445460
ps.expectChar("}");
446461
return new AST.VariantList(variants);
447462
}
448463

449464
getPattern(ps, {isBlock}) {
450465
const elements = [];
451466
if (isBlock) {
467+
// A block pattern is a pattern which starts on a new line. Store and
468+
// measure the indent of this first line for the dedentation logic.
452469
const blankStart = ps.index;
453470
const firstIndent = ps.skipBlankInline();
454471
elements.push(this.getIndent(ps, firstIndent, blankStart));
@@ -487,9 +504,13 @@ export default class FluentParser {
487504
}
488505
}
489506

490-
return new AST.Pattern(this.dedent(elements, commonIndentLength));
507+
const dedented = this.dedent(elements, commonIndentLength);
508+
return new AST.Pattern(dedented);
491509
}
492510

511+
// Create a token representing an indent. It's not part of the AST and it will
512+
// be trimmed and merged into adjacent TextElements, or turned into a new
513+
// TextElement, if it's surrounded by two Placeables.
493514
getIndent(ps, value, start) {
494515
return {
495516
type: "Indent",
@@ -498,6 +519,8 @@ export default class FluentParser {
498519
};
499520
}
500521

522+
// Dedent a list of elements by removing the maximum common indent from the
523+
// beginning of text lines. The common indent is calculated in getPattern.
501524
dedent(elements, commonIndent) {
502525
const trimmed = [];
503526

@@ -528,6 +551,8 @@ export default class FluentParser {
528551
}
529552

530553
if (element.type === "Indent") {
554+
// Is the indent hasn't been merged into a preceding TextElement,
555+
// convert it into a new TextElement.
531556
const textElement = new AST.TextElement(element.value);
532557
if (this.withSpans) {
533558
textElement.addSpan(element.span.start, element.span.end);
@@ -538,7 +563,7 @@ export default class FluentParser {
538563
trimmed.push(element);
539564
}
540565

541-
// Trim trailing whitespace.
566+
// Trim trailing whitespace from the Pattern.
542567
const lastElement = trimmed[trimmed.length - 1];
543568
if (lastElement.type === "TextElement") {
544569
lastElement.value = lastElement.value.replace(trailingWSRe, "");
@@ -600,16 +625,14 @@ export default class FluentParser {
600625

601626
getPlaceable(ps) {
602627
ps.expectChar("{");
628+
ps.skipBlank();
603629
const expression = this.getExpression(ps);
604630
ps.expectChar("}");
605631
return new AST.Placeable(expression);
606632
}
607633

608634
getExpression(ps) {
609-
ps.skipBlank();
610-
611635
const selector = this.getSelectorExpression(ps);
612-
613636
ps.skipBlank();
614637

615638
if (ps.currentChar === "-") {
@@ -638,21 +661,13 @@ export default class FluentParser {
638661
ps.skipBlankInline();
639662
ps.expectLineEnd();
640663

641-
const variants = this.getVariants(ps);
642-
643-
// VariantLists are only allowed in other VariantLists.
644-
if (variants.some(v => v.value.type === "VariantList")) {
645-
throw new ParseError("E0023");
646-
}
647-
664+
const variants = this.getVariants(ps, {allowVariantList: false});
648665
return new AST.SelectExpression(selector, variants);
649666
} else if (selector.type === "AttributeExpression" &&
650667
selector.ref.type === "TermReference") {
651668
throw new ParseError("E0019");
652669
}
653670

654-
ps.skipBlank();
655-
656671
return selector;
657672
}
658673

fluent-syntax/test/fixtures_behavior/variant_lists.ftl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ message1 =
44
*[one] One
55
}
66
7-
# ~ERROR E0023, pos 123
7+
# ~ERROR E0014, pos 97
88
message2 =
99
{ $sel ->
1010
*[one] {
@@ -24,7 +24,7 @@ message2 =
2424
}
2525
}
2626
27-
# ~ERROR E0023, pos 318
27+
# ~ERROR E0014, pos 292
2828
-term3 =
2929
{ $sel ->
3030
*[one] {

fluent-syntax/test/fixtures_structure/leading_dots.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@
836836
],
837837
"span": {
838838
"type": "Span",
839-
"start": 614,
839+
"start": 615,
840840
"end": 684
841841
}
842842
},

fluent-syntax/test/fixtures_structure/sparse-messages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@
322322
],
323323
"span": {
324324
"type": "Span",
325-
"start": 154,
325+
"start": 155,
326326
"end": 208
327327
}
328328
},

fluent-syntax/test/fixtures_structure/term.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@
481481
],
482482
"span": {
483483
"type": "Span",
484-
"start": 198,
484+
"start": 199,
485485
"end": 436
486486
}
487487
},

fluent-syntax/test/fixtures_structure/variant_with_empty_pattern.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
],
7878
"span": {
7979
"type": "Span",
80-
"start": 12,
80+
"start": 13,
8181
"end": 41
8282
}
8383
},

0 commit comments

Comments
 (0)