@@ -43,7 +43,7 @@ export default class FluentParser {
43
43
// Poor man's decorators.
44
44
const methodNames = [
45
45
"getComment" , "getMessage" , "getTerm" , "getAttribute" , "getIdentifier" ,
46
- "getVariant" , "getNumber" , "getValue" , " getPattern", "getVariantList" ,
46
+ "getVariant" , "getNumber" , "getPattern" , "getVariantList" ,
47
47
"getTextElement" , "getPlaceable" , "getExpression" ,
48
48
"getSelectorExpression" , "getCallArg" , "getString" , "getLiteral"
49
49
] ;
@@ -231,7 +231,7 @@ export default class FluentParser {
231
231
ps . skipBlankInline ( ) ;
232
232
ps . expectChar ( "=" ) ;
233
233
234
- const value = this . getInlineOrBlock ( ps , this . getPattern ) ;
234
+ const value = this . getValue ( ps , { allowVariantList : false } ) ;
235
235
const attrs = this . getAttributes ( ps ) ;
236
236
237
237
if ( value === null && attrs . length === 0 ) {
@@ -248,7 +248,11 @@ export default class FluentParser {
248
248
ps . skipBlankInline ( ) ;
249
249
ps . expectChar ( "=" ) ;
250
250
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 } ) ;
252
256
if ( value === null ) {
253
257
throw new ParseError ( "E0006" , id . name ) ;
254
258
}
@@ -257,22 +261,6 @@ export default class FluentParser {
257
261
return new AST . Term ( id , value , attrs ) ;
258
262
}
259
263
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
-
276
264
getAttribute ( ps ) {
277
265
ps . expectChar ( "." ) ;
278
266
@@ -281,7 +269,7 @@ export default class FluentParser {
281
269
ps . skipBlankInline ( ) ;
282
270
ps . expectChar ( "=" ) ;
283
271
284
- const value = this . getInlineOrBlock ( ps , this . getPattern ) ;
272
+ const value = this . getValue ( ps , { allowVariantList : false } ) ;
285
273
if ( value === null ) {
286
274
throw new ParseError ( "E0012" ) ;
287
275
}
@@ -328,7 +316,7 @@ export default class FluentParser {
328
316
return this . getIdentifier ( ps ) ;
329
317
}
330
318
331
- getVariant ( ps , hasDefault ) {
319
+ getVariant ( ps , { hasDefault, allowVariantList } ) {
332
320
let defaultIndex = false ;
333
321
334
322
if ( ps . currentChar === "*" ) {
@@ -349,21 +337,23 @@ export default class FluentParser {
349
337
ps . skipBlank ( ) ;
350
338
ps . expectChar ( "]" ) ;
351
339
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} ) ;
353
343
if ( value === null ) {
354
344
throw new ParseError ( "E0012" ) ;
355
345
}
356
346
357
347
return new AST . Variant ( key , value , defaultIndex ) ;
358
348
}
359
349
360
- getVariants ( ps ) {
350
+ getVariants ( ps , { allowVariantList } ) {
361
351
const variants = [ ] ;
362
352
let hasDefault = false ;
363
353
364
354
ps . skipBlank ( ) ;
365
355
while ( ps . isVariantStart ( ) ) {
366
- const variant = this . getVariant ( ps , hasDefault ) ;
356
+ const variant = this . getVariant ( ps , { allowVariantList , hasDefault} ) ;
367
357
368
358
if ( variant . default ) {
369
359
hasDefault = true ;
@@ -419,36 +409,63 @@ export default class FluentParser {
419
409
return new AST . NumberLiteral ( num ) ;
420
410
}
421
411
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} ) {
423
419
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 ;
426
440
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
+ }
432
449
}
433
450
}
434
451
435
452
ps . resetPeek ( ) ;
436
- return this . getPattern ( ps , { isBlock} ) ;
453
+ const pattern = this . getPattern ( ps , { isBlock} ) ;
454
+ return pattern ;
437
455
}
438
456
439
457
getVariantList ( ps ) {
440
458
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 } ) ;
445
460
ps . expectChar ( "}" ) ;
446
461
return new AST . VariantList ( variants ) ;
447
462
}
448
463
449
464
getPattern ( ps , { isBlock} ) {
450
465
const elements = [ ] ;
451
466
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.
452
469
const blankStart = ps . index ;
453
470
const firstIndent = ps . skipBlankInline ( ) ;
454
471
elements . push ( this . getIndent ( ps , firstIndent , blankStart ) ) ;
@@ -487,9 +504,13 @@ export default class FluentParser {
487
504
}
488
505
}
489
506
490
- return new AST . Pattern ( this . dedent ( elements , commonIndentLength ) ) ;
507
+ const dedented = this . dedent ( elements , commonIndentLength ) ;
508
+ return new AST . Pattern ( dedented ) ;
491
509
}
492
510
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.
493
514
getIndent ( ps , value , start ) {
494
515
return {
495
516
type : "Indent" ,
@@ -498,6 +519,8 @@ export default class FluentParser {
498
519
} ;
499
520
}
500
521
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.
501
524
dedent ( elements , commonIndent ) {
502
525
const trimmed = [ ] ;
503
526
@@ -528,6 +551,8 @@ export default class FluentParser {
528
551
}
529
552
530
553
if ( element . type === "Indent" ) {
554
+ // Is the indent hasn't been merged into a preceding TextElement,
555
+ // convert it into a new TextElement.
531
556
const textElement = new AST . TextElement ( element . value ) ;
532
557
if ( this . withSpans ) {
533
558
textElement . addSpan ( element . span . start , element . span . end ) ;
@@ -538,7 +563,7 @@ export default class FluentParser {
538
563
trimmed . push ( element ) ;
539
564
}
540
565
541
- // Trim trailing whitespace.
566
+ // Trim trailing whitespace from the Pattern .
542
567
const lastElement = trimmed [ trimmed . length - 1 ] ;
543
568
if ( lastElement . type === "TextElement" ) {
544
569
lastElement . value = lastElement . value . replace ( trailingWSRe , "" ) ;
@@ -600,16 +625,14 @@ export default class FluentParser {
600
625
601
626
getPlaceable ( ps ) {
602
627
ps . expectChar ( "{" ) ;
628
+ ps . skipBlank ( ) ;
603
629
const expression = this . getExpression ( ps ) ;
604
630
ps . expectChar ( "}" ) ;
605
631
return new AST . Placeable ( expression ) ;
606
632
}
607
633
608
634
getExpression ( ps ) {
609
- ps . skipBlank ( ) ;
610
-
611
635
const selector = this . getSelectorExpression ( ps ) ;
612
-
613
636
ps . skipBlank ( ) ;
614
637
615
638
if ( ps . currentChar === "-" ) {
@@ -638,21 +661,13 @@ export default class FluentParser {
638
661
ps . skipBlankInline ( ) ;
639
662
ps . expectLineEnd ( ) ;
640
663
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 } ) ;
648
665
return new AST . SelectExpression ( selector , variants ) ;
649
666
} else if ( selector . type === "AttributeExpression" &&
650
667
selector . ref . type === "TermReference" ) {
651
668
throw new ParseError ( "E0019" ) ;
652
669
}
653
670
654
- ps . skipBlank ( ) ;
655
-
656
671
return selector ;
657
672
}
658
673
0 commit comments