rip-lang 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,6 +80,8 @@ grammar =
80
80
  o 'For'
81
81
  o 'Switch'
82
82
  o 'Class'
83
+ o 'Component'
84
+ o 'Render'
83
85
  o 'Throw'
84
86
  o 'Yield'
85
87
  o 'Def'
@@ -706,6 +708,39 @@ grammar =
706
708
  o 'CLASS SimpleAssignable EXTENDS Expression Block', '["class", 2, 4, 5]'
707
709
  ]
708
710
 
711
+ # ============================================================================
712
+ # Components
713
+ # ============================================================================
714
+
715
+ # Component: expression-based anonymous class with render support.
716
+ # Usage: Counter = component
717
+ # @count := 0
718
+ # render
719
+ # div "Count: {@count}"
720
+ Component: [
721
+ o 'COMPONENT INDENT ComponentBody OUTDENT', '["component", null, ["block", ...3]]'
722
+ ]
723
+
724
+ ComponentBody: [
725
+ o 'ComponentLine' , '[1]'
726
+ o 'ComponentBody TERMINATOR ComponentLine', '[...1, 3]'
727
+ o 'ComponentBody TERMINATOR'
728
+ ]
729
+
730
+ ComponentLine: [
731
+ o 'Expression'
732
+ o 'ExpressionLine'
733
+ o 'Statement'
734
+ ]
735
+
736
+ # Render block: template DSL for fine-grained reactive DOM.
737
+ # Usage: render
738
+ # div.card
739
+ # h1 "Hello"
740
+ Render: [
741
+ o 'RENDER Block', '["render", 2]'
742
+ ]
743
+
709
744
  # ============================================================================
710
745
  # Import / Export
711
746
  # ============================================================================
@@ -747,6 +782,7 @@ grammar =
747
782
  o 'EXPORT { }' , '["export", "{}"]'
748
783
  o 'EXPORT { ExportSpecifierList OptComma }' , '["export", 3]'
749
784
  o 'EXPORT Class' , '["export", 2]'
785
+ o 'EXPORT Component' , '["export", 2]'
750
786
  o 'EXPORT Def' , '["export", 2]'
751
787
  o 'EXPORT Identifier = Expression' , '["export", ["=", 2, 4]]'
752
788
  o 'EXPORT Identifier = TERMINATOR Expression' , '["export", ["=", 2, 5]]'
@@ -877,7 +913,7 @@ operators = """
877
913
  right YIELD
878
914
  right = : COMPOUND_ASSIGN RETURN THROW EXTENDS
879
915
  right FORIN FOROF FORAS FORASAWAIT BY WHEN
880
- right IF ELSE FOR WHILE UNTIL LOOP SUPER CLASS IMPORT EXPORT DYNAMIC_IMPORT
916
+ right IF ELSE FOR WHILE UNTIL LOOP SUPER CLASS COMPONENT RENDER IMPORT EXPORT DYNAMIC_IMPORT
881
917
  left POST_IF
882
918
  """.trim().split('\n').reverse().map (line) -> line.trim().split /\s+/
883
919
 
@@ -335,7 +335,7 @@ class Generator
335
335
 
336
336
  # Single pass: group items by nextSymbol
337
337
  symbolItems = new Map
338
- for item from itemSet.items when item.nextSymbol and item.nextSymbol isnt '$end'
338
+ for item as itemSet.items when item.nextSymbol and item.nextSymbol isnt '$end'
339
339
  items = symbolItems.get(item.nextSymbol)
340
340
  unless items
341
341
  items = []
@@ -343,7 +343,7 @@ class Generator
343
343
  items.push(item)
344
344
 
345
345
  # Process each symbol with its pre-collected items
346
- for [symbol, items] from symbolItems
346
+ for [symbol, items] as symbolItems
347
347
  @_insertStateWithItems symbol, items, itemSet, states, stateMap
348
348
 
349
349
  @states = states
@@ -359,7 +359,7 @@ class Generator
359
359
  newItems = new Set
360
360
 
361
361
  # Only process item cores we haven't yet seen
362
- for item from workingSet when !itemCores.has(item.id)
362
+ for item as workingSet when !itemCores.has(item.id)
363
363
 
364
364
  # Add item to closure
365
365
  closureSet.items.add(item)
@@ -392,7 +392,7 @@ class Generator
392
392
  _goto: (itemSet, symbol) ->
393
393
  gotoSet = new State
394
394
 
395
- for item from itemSet.items when item.nextSymbol is symbol
395
+ for item as itemSet.items when item.nextSymbol is symbol
396
396
  # Create advanced item (lookaheads will be set from FOLLOW sets later)
397
397
  newItem = new Item item.rule, null, item.dot + 1
398
398
  gotoSet.items.add newItem
@@ -522,11 +522,11 @@ class Generator
522
522
  # Assign FOLLOW sets to reduction items
523
523
  _assignItemLookaheads!: ->
524
524
  for state in @states
525
- for item from state.reductions
525
+ for item as state.reductions
526
526
  follows = @types[item.rule.type]?.follows
527
527
  if follows
528
528
  item.lookaheads.clear()
529
- item.lookaheads.add token for token from follows
529
+ item.lookaheads.add token for token as follows
530
530
 
531
531
  # ============================================================================
532
532
  # Parse Table Generation
@@ -541,19 +541,19 @@ class Generator
541
541
  state = states[k] = {}
542
542
 
543
543
  # Shift and goto actions
544
- for [stackSymbol, gotoState] from itemSet.transitions when @symbolIds[stackSymbol]?
544
+ for [stackSymbol, gotoState] as itemSet.transitions when @symbolIds[stackSymbol]?
545
545
  if types[stackSymbol]
546
546
  state[@symbolIds[stackSymbol]] = gotoState
547
547
  else
548
548
  state[@symbolIds[stackSymbol]] = [SHIFT, gotoState]
549
549
 
550
550
  # Accept action
551
- for item from itemSet.items when item.nextSymbol is "$end" and @symbolIds["$end"]?
551
+ for item as itemSet.items when item.nextSymbol is "$end" and @symbolIds["$end"]?
552
552
  state[@symbolIds["$end"]] = [ACCEPT]
553
553
 
554
554
  # Reduce actions
555
- for item from itemSet.reductions
556
- for stackSymbol from item.lookaheads when @symbolIds[stackSymbol]?
555
+ for item as itemSet.reductions
556
+ for stackSymbol as item.lookaheads when @symbolIds[stackSymbol]?
557
557
  action = state[@symbolIds[stackSymbol]]
558
558
  op = operators[stackSymbol]
559
559
 
@@ -964,7 +964,7 @@ if isRunAsScript
964
964
 
965
965
  if grammar.grammar
966
966
  parts.push ' (rules'
967
- for [name, productions] from Object.entries(grammar.grammar)
967
+ for [name, productions] as Object.entries(grammar.grammar)
968
968
  parts.push " (#{name}"
969
969
  for production in productions
970
970
  [pattern, action, opts] = production
package/src/lexer.js CHANGED
@@ -39,6 +39,8 @@
39
39
  //
40
40
  // ==========================================================================
41
41
 
42
+ import { TEMPLATE_TAGS } from './tags.js';
43
+
42
44
  // ==========================================================================
43
45
  // Token Category Sets
44
46
  // ==========================================================================
@@ -59,6 +61,7 @@ let JS_KEYWORDS = new Set([
59
61
  let RIP_KEYWORDS = new Set([
60
62
  'undefined', 'Infinity', 'NaN',
61
63
  'then', 'unless', 'until', 'loop', 'of', 'by', 'when', 'def',
64
+ 'component', 'render',
62
65
  ]);
63
66
 
64
67
  // Rip aliases: word → operator/value
@@ -158,7 +161,7 @@ let IMPLICIT_FUNC = new Set([
158
161
  ]);
159
162
 
160
163
  // Control flow tokens that don't end implicit calls/objects
161
- let CONTROL_IN_IMPLICIT = new Set(['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH']);
164
+ let CONTROL_IN_IMPLICIT = new Set(['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH', 'COMPONENT']);
162
165
 
163
166
  // Single-liner keywords that get implicit INDENT/OUTDENT
164
167
  let SINGLE_LINERS = new Set(['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']);
@@ -297,6 +300,8 @@ export class Lexer {
297
300
  this.seenExport = false;
298
301
  this.importSpecifierList = false;
299
302
  this.exportSpecifierList = false;
303
+ this.inRenderBlock = false;
304
+ this.renderIndent = 0;
300
305
 
301
306
  // Clean source
302
307
  code = this.clean(code);
@@ -498,6 +503,18 @@ export class Lexer {
498
503
  // Property vs identifier
499
504
  if (colon || (prev && (prev[0] === '.' || prev[0] === '?.' || (!prev.spaced && prev[0] === '@')))) {
500
505
  tag = 'PROPERTY';
506
+
507
+ // In render blocks, consume hyphenated CSS class names: .counter-display → PROPERTY "counter-display"
508
+ if (this.inRenderBlock && prev && prev[0] === '.' && !colon) {
509
+ let rest = this.chunk.slice(idLen);
510
+ while (rest[0] === '-' && /^-[a-zA-Z]/.test(rest)) {
511
+ let m = /^-([a-zA-Z][\w]*)/.exec(rest);
512
+ if (!m) break;
513
+ id += '-' + m[1];
514
+ idLen += 1 + m[1].length;
515
+ rest = this.chunk.slice(idLen);
516
+ }
517
+ }
501
518
  } else {
502
519
  tag = 'IDENTIFIER';
503
520
  }
@@ -551,6 +568,12 @@ export class Lexer {
551
568
  // --- Emit ---
552
569
  let t = this.emit(tag, id, {len: idLen, data: Object.keys(data).length ? data : null});
553
570
 
571
+ // Track render block context for line continuation suppression
572
+ if (tag === 'RENDER') {
573
+ this.inRenderBlock = true;
574
+ this.renderIndent = this.indent;
575
+ }
576
+
554
577
  if (colon) {
555
578
  this.emit(':', ':', {len: 1});
556
579
  return idLen + colon.length;
@@ -687,6 +710,10 @@ export class Lexer {
687
710
 
688
711
  // Emit OUTDENT tokens to reach target indent level
689
712
  outdentTo(targetSize, outdentLength = 0) {
713
+ // Exit render block when outdenting past its level
714
+ if (this.inRenderBlock && targetSize <= this.renderIndent) {
715
+ this.inRenderBlock = false;
716
+ }
690
717
  let moveOut = this.indent - targetSize;
691
718
  while (moveOut > 0) {
692
719
  let lastIndent = this.indents[this.indents.length - 1];
@@ -717,6 +744,10 @@ export class Lexer {
717
744
 
718
745
  // Check if the current line is unfinished (continuation)
719
746
  isUnfinished() {
747
+ // Inside render blocks, a line starting with . is a new element, not method chaining
748
+ if (this.inRenderBlock && LINE_CONTINUER_RE.test(this.chunk) && /^\s*\./.test(this.chunk)) {
749
+ return false;
750
+ }
720
751
  return LINE_CONTINUER_RE.test(this.chunk) || UNFINISHED.has(this.prevTag());
721
752
  }
722
753
 
@@ -1064,6 +1095,7 @@ export class Lexer {
1064
1095
  // Reactive operators
1065
1096
  else if (val === '~=') tag = 'COMPUTED_ASSIGN';
1066
1097
  else if (val === ':=') tag = 'REACTIVE_ASSIGN';
1098
+ else if (val === '<=>') tag = 'BIND';
1067
1099
  else if (val === '~>') tag = 'REACT_ASSIGN';
1068
1100
  else if (val === '=!') tag = 'READONLY_ASSIGN';
1069
1101
  // Export all
@@ -1134,7 +1166,7 @@ export class Lexer {
1134
1166
  }
1135
1167
 
1136
1168
  // ==========================================================================
1137
- // Rewriter — 7 passes
1169
+ // Rewriter — 8 passes
1138
1170
  // ==========================================================================
1139
1171
 
1140
1172
  rewrite(tokens) {
@@ -1143,6 +1175,7 @@ export class Lexer {
1143
1175
  this.closeOpenCalls();
1144
1176
  this.closeOpenIndexes();
1145
1177
  this.normalizeLines();
1178
+ this.rewriteRender();
1146
1179
  this.tagPostfixConditionals();
1147
1180
  this.addImplicitBracesAndParens();
1148
1181
  this.addImplicitCallCommas();
@@ -1196,6 +1229,7 @@ export class Lexer {
1196
1229
  return token[1] !== ';' && SINGLE_CLOSERS.has(token[0]) &&
1197
1230
  !(token[0] === 'TERMINATOR' && EXPRESSION_CLOSE.has(this.tokens[i + 1]?.[0])) &&
1198
1231
  !(token[0] === 'ELSE' && starter !== 'THEN') ||
1232
+ token[0] === 'INDENT' && !token.generated && (starter === '->' || starter === '=>') ||
1199
1233
  token[0] === ',' && (starter === '->' || starter === '=>') && !this.commaInImplicitCall(i) ||
1200
1234
  CALL_CLOSERS.has(token[0]) && (this.tokens[i - 1]?.newLine || this.tokens[i - 1]?.[0] === 'OUTDENT');
1201
1235
  };
@@ -1250,6 +1284,256 @@ export class Lexer {
1250
1284
  });
1251
1285
  }
1252
1286
 
1287
+ // =========================================================================
1288
+ // Render block rewriter
1289
+ // =========================================================================
1290
+ // Transforms template syntax inside render blocks:
1291
+ // - Implicit div for class-only selectors: .card → div.card
1292
+ // - Combine #id selectors: div # main → div#main
1293
+ // - Two-way binding: value <=> username → __bind_value__: username
1294
+ // - Event modifiers: @click.prevent: → [@click.prevent]:
1295
+ // - Dynamic classes: div.('card', x && 'active') → div.__cx__(...)
1296
+ // - Implicit nesting: inject -> before INDENT for template elements
1297
+ // - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
1298
+ // =========================================================================
1299
+ rewriteRender() {
1300
+ let inRender = false;
1301
+ let renderIndentLevel = 0;
1302
+ let currentIndent = 0;
1303
+ let pendingCallEnds = [];
1304
+
1305
+ let isHtmlTag = (name) => {
1306
+ let tagPart = name.split('#')[0];
1307
+ return TEMPLATE_TAGS.has(tagPart);
1308
+ };
1309
+
1310
+ let isComponent = (name) => {
1311
+ if (!name || typeof name !== 'string') return false;
1312
+ return /^[A-Z]/.test(name);
1313
+ };
1314
+
1315
+ let isTemplateTag = (name) => {
1316
+ return isHtmlTag(name) || isComponent(name);
1317
+ };
1318
+
1319
+ let startsWithHtmlTag = (tokens, i) => {
1320
+ let j = i;
1321
+ while (j > 0) {
1322
+ let pt = tokens[j - 1][0];
1323
+ if (pt === 'INDENT' || pt === 'OUTDENT' || pt === 'TERMINATOR' || pt === 'RENDER' || pt === 'CALL_END' || pt === ')') {
1324
+ break;
1325
+ }
1326
+ j--;
1327
+ }
1328
+ return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isHtmlTag(tokens[j][1]);
1329
+ };
1330
+
1331
+ this.scanTokens(function(token, i, tokens) {
1332
+ let tag = token[0];
1333
+ let nextToken = i < tokens.length - 1 ? tokens[i + 1] : null;
1334
+
1335
+ // Track entering render blocks
1336
+ if (tag === 'RENDER') {
1337
+ inRender = true;
1338
+ renderIndentLevel = currentIndent + 1;
1339
+ return 1;
1340
+ }
1341
+
1342
+ // Track indentation
1343
+ if (tag === 'INDENT') {
1344
+ currentIndent++;
1345
+ return 1;
1346
+ }
1347
+
1348
+ if (tag === 'OUTDENT') {
1349
+ currentIndent--;
1350
+
1351
+ // Insert pending CALL_END(s) after this OUTDENT
1352
+ let inserted = 0;
1353
+ while (pendingCallEnds.length > 0 && pendingCallEnds[pendingCallEnds.length - 1] > currentIndent) {
1354
+ let callEndToken = gen('CALL_END', ')', token);
1355
+ tokens.splice(i + 1 + inserted, 0, callEndToken);
1356
+ pendingCallEnds.pop();
1357
+ inserted++;
1358
+ }
1359
+
1360
+ // Exit render block when we outdent past where it started
1361
+ if (inRender && currentIndent < renderIndentLevel) {
1362
+ inRender = false;
1363
+ }
1364
+ return 1 + inserted;
1365
+ }
1366
+
1367
+ // Only process if we're inside a render block
1368
+ if (!inRender) return 1;
1369
+
1370
+ // ─────────────────────────────────────────────────────────────────────
1371
+ // Hyphenated attributes
1372
+ // data-lucide: "search" → "data-lucide": "search"
1373
+ // ─────────────────────────────────────────────────────────────────────
1374
+ if (tag === 'IDENTIFIER' && !token.spaced) {
1375
+ let parts = [token[1]];
1376
+ let j = i + 1;
1377
+ while (j + 1 < tokens.length) {
1378
+ let hyphen = tokens[j];
1379
+ let nextPart = tokens[j + 1];
1380
+ if (hyphen[0] === '-' && !hyphen.spaced &&
1381
+ (nextPart[0] === 'IDENTIFIER' || nextPart[0] === 'PROPERTY')) {
1382
+ parts.push(nextPart[1]);
1383
+ j += 2;
1384
+ if (nextPart[0] === 'PROPERTY') break;
1385
+ } else {
1386
+ break;
1387
+ }
1388
+ }
1389
+ if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === 'PROPERTY') {
1390
+ token[0] = 'STRING';
1391
+ token[1] = `"${parts.join('-')}"`;
1392
+ tokens.splice(i + 1, j - i - 1);
1393
+ return 1;
1394
+ }
1395
+ }
1396
+
1397
+ // ─────────────────────────────────────────────────────────────────────
1398
+ // Implicit div for class-only selectors
1399
+ // .card → div.card
1400
+ // ─────────────────────────────────────────────────────────────────────
1401
+ if (tag === '.') {
1402
+ let prevToken = i > 0 ? tokens[i - 1] : null;
1403
+ let prevTag = prevToken ? prevToken[0] : null;
1404
+ if (prevTag === 'INDENT' || prevTag === 'TERMINATOR') {
1405
+ if (nextToken && nextToken[0] === 'PROPERTY') {
1406
+ let divToken = gen('IDENTIFIER', 'div', token);
1407
+ tokens.splice(i, 0, divToken);
1408
+ return 2;
1409
+ }
1410
+ }
1411
+ }
1412
+
1413
+ // ─────────────────────────────────────────────────────────────────────
1414
+ // Combine #id selectors
1415
+ // div # main → div#main
1416
+ // ─────────────────────────────────────────────────────────────────────
1417
+ if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
1418
+ let next = tokens[i + 1];
1419
+ let nextNext = tokens[i + 2];
1420
+ if (next && next[0] === '#' && nextNext && nextNext[0] === 'PROPERTY') {
1421
+ token[1] = token[1] + '#' + nextNext[1];
1422
+ if (nextNext.spaced) token.spaced = true;
1423
+ tokens.splice(i + 1, 2);
1424
+ return 1;
1425
+ }
1426
+ }
1427
+
1428
+ // ─────────────────────────────────────────────────────────────────────
1429
+ // Two-way binding
1430
+ // value <=> username → __bind_value__: username
1431
+ // ─────────────────────────────────────────────────────────────────────
1432
+ if (tag === 'BIND') {
1433
+ let prevToken = i > 0 ? tokens[i - 1] : null;
1434
+ let nextBindToken = tokens[i + 1];
1435
+ if (prevToken && (prevToken[0] === 'IDENTIFIER' || prevToken[0] === 'PROPERTY') &&
1436
+ nextBindToken && nextBindToken[0] === 'IDENTIFIER') {
1437
+ prevToken[1] = `__bind_${prevToken[1]}__`;
1438
+ token[0] = ':';
1439
+ token[1] = ':';
1440
+ return 1;
1441
+ }
1442
+ }
1443
+
1444
+ // ─────────────────────────────────────────────────────────────────────
1445
+ // Event modifiers
1446
+ // @click.prevent: handler → [@click.prevent]: handler
1447
+ // ─────────────────────────────────────────────────────────────────────
1448
+ if (tag === '@') {
1449
+ let j = i + 1;
1450
+ if (j < tokens.length && tokens[j][0] === 'PROPERTY') {
1451
+ j++;
1452
+ while (j + 1 < tokens.length && tokens[j][0] === '.' && tokens[j + 1][0] === 'PROPERTY') {
1453
+ j += 2;
1454
+ }
1455
+ if (j > i + 2 && j < tokens.length && tokens[j][0] === ':') {
1456
+ let openBracket = gen('[', '[', token);
1457
+ tokens.splice(i, 0, openBracket);
1458
+ let closeBracket = gen(']', ']', tokens[j + 1]);
1459
+ tokens.splice(j + 1, 0, closeBracket);
1460
+ return 2;
1461
+ }
1462
+ }
1463
+ }
1464
+
1465
+ // ─────────────────────────────────────────────────────────────────────
1466
+ // Dynamic classes
1467
+ // div.('card', x && 'active') → div.__cx__('card', x && 'active')
1468
+ // .('card') → div.__cx__('card')
1469
+ // ─────────────────────────────────────────────────────────────────────
1470
+ if (tag === '.' && nextToken && nextToken[0] === '(') {
1471
+ let prevToken = i > 0 ? tokens[i - 1] : null;
1472
+ let prevTag = prevToken ? prevToken[0] : null;
1473
+ let atLineStart = prevTag === 'INDENT' || prevTag === 'TERMINATOR';
1474
+
1475
+ let cxToken = gen('PROPERTY', '__cx__', token);
1476
+ nextToken[0] = 'CALL_START';
1477
+ let depth = 1;
1478
+ for (let j = i + 2; j < tokens.length && depth > 0; j++) {
1479
+ if (tokens[j][0] === '(' || tokens[j][0] === 'CALL_START') depth++;
1480
+ else if (tokens[j][0] === ')') {
1481
+ depth--;
1482
+ if (depth === 0) tokens[j][0] = 'CALL_END';
1483
+ } else if (tokens[j][0] === 'CALL_END') depth--;
1484
+ }
1485
+
1486
+ if (atLineStart) {
1487
+ let divToken = gen('IDENTIFIER', 'div', token);
1488
+ tokens.splice(i, 0, divToken);
1489
+ tokens.splice(i + 2, 0, cxToken);
1490
+ return 3;
1491
+ } else {
1492
+ tokens.splice(i + 1, 0, cxToken);
1493
+ return 2;
1494
+ }
1495
+ }
1496
+
1497
+ // ─────────────────────────────────────────────────────────────────────
1498
+ // Implicit nesting (inject -> before INDENT)
1499
+ // ─────────────────────────────────────────────────────────────────────
1500
+ if (nextToken && nextToken[0] === 'INDENT') {
1501
+ if (tag === '->' || tag === '=>' || tag === 'CALL_START' || tag === '(') {
1502
+ return 1;
1503
+ }
1504
+
1505
+ let isTemplateElement = false;
1506
+
1507
+ if (tag === 'IDENTIFIER' && isTemplateTag(token[1])) {
1508
+ isTemplateElement = true;
1509
+ } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
1510
+ isTemplateElement = startsWithHtmlTag(tokens, i);
1511
+ }
1512
+ else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
1513
+ if (startsWithHtmlTag(tokens, i)) {
1514
+ let commaToken = gen(',', ',', token);
1515
+ let arrowToken = gen('->', '->', token);
1516
+ arrowToken.newLine = true;
1517
+ tokens.splice(i + 1, 0, commaToken, arrowToken);
1518
+ return 3;
1519
+ }
1520
+ }
1521
+
1522
+ if (isTemplateElement) {
1523
+ let callStartToken = gen('CALL_START', '(', token);
1524
+ let arrowToken = gen('->', '->', token);
1525
+ arrowToken.newLine = true;
1526
+
1527
+ tokens.splice(i + 1, 0, callStartToken, arrowToken);
1528
+ pendingCallEnds.push(currentIndent + 1);
1529
+ return 3;
1530
+ }
1531
+ }
1532
+
1533
+ return 1;
1534
+ });
1535
+ }
1536
+
1253
1537
  tagPostfixConditionals() {
1254
1538
  let original = null;
1255
1539