ether-code 0.9.0 → 0.9.2

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.
@@ -226,12 +226,6 @@ class EtherParserPHP extends EtherParserBase {
226
226
  this.phpModifiers = ['statique', 'static', 'final', 'finale', 'abstrait', 'abstraite', 'abstract', 'readonly', 'lecture seule']
227
227
  }
228
228
 
229
- safeStr(val) {
230
- if (typeof val === 'string') return val
231
- if (val && typeof val === 'object') return String(val.name || val.value || '')
232
- return String(val || '')
233
- }
234
-
235
229
  translatePHP(word) {
236
230
  if (!word) return word
237
231
  const lower = this.normalizeAccents(String(word))
@@ -278,10 +272,6 @@ class EtherParserPHP extends EtherParserBase {
278
272
 
279
273
  const value = token.value != null ? this.normalizeAccents(String(token.value).toLowerCase()) : ''
280
274
 
281
- if (this.isDeclare(value)) {
282
- return this.parseDeclare(lang)
283
- }
284
-
285
275
  if (this.isNamespace(value)) {
286
276
  return this.parseNamespace(lang)
287
277
  }
@@ -290,6 +280,10 @@ class EtherParserPHP extends EtherParserBase {
290
280
  return this.parseUseStatement(lang)
291
281
  }
292
282
 
283
+ if (this.isDeclare(value)) {
284
+ return this.parseDeclareStatement(lang)
285
+ }
286
+
293
287
  if (this.isClassDeclaration(value)) {
294
288
  return this.parseClassDeclaration(lang)
295
289
  }
@@ -326,16 +320,6 @@ class EtherParserPHP extends EtherParserBase {
326
320
  return this.parseSwitch(lang)
327
321
  }
328
322
 
329
- if (token.type === TokenType.IDENTIFIER) {
330
- const multiWordCall = this.tryParseMultiWordCallFromStart(lang)
331
- if (multiWordCall) {
332
- return {
333
- type: 'ExpressionStatement',
334
- expression: multiWordCall
335
- }
336
- }
337
- }
338
-
339
323
  if (this.isTry(value)) {
340
324
  return this.parseTryStatement(lang)
341
325
  }
@@ -385,23 +369,17 @@ class EtherParserPHP extends EtherParserBase {
385
369
  }
386
370
 
387
371
  isNamespace(value) {
388
- if (value === 'namespace' || value === 'espace de noms') return true
389
- if (value === 'espace') {
390
- const next1 = this.peek(1)
391
- const next2 = this.peek(2)
392
- if (next1 && next2) {
393
- const val1 = this.normalizeAccents(String(next1.value).toLowerCase())
394
- const val2 = this.normalizeAccents(String(next2.value).toLowerCase())
395
- if (val1 === 'de' && val2 === 'noms') return true
396
- }
397
- }
398
- return false
372
+ return ['namespace', 'espace de noms'].includes(value)
399
373
  }
400
374
 
401
375
  isUse(value) {
402
376
  return ['utiliser', 'use'].includes(value)
403
377
  }
404
378
 
379
+ isDeclare(value) {
380
+ return ['declarer', 'declare'].includes(value)
381
+ }
382
+
405
383
  isClassDeclaration(value) {
406
384
  const keywords = ['classe', 'class', 'abstrait', 'abstraite', 'abstract', 'final', 'finale', 'readonly']
407
385
  if (keywords.includes(value)) {
@@ -493,54 +471,8 @@ class EtherParserPHP extends EtherParserBase {
493
471
  return value === 'match'
494
472
  }
495
473
 
496
- isDeclare(value) {
497
- return ['declare', 'declarer', 'déclarer'].includes(value)
498
- }
499
-
500
- parseDeclare(lang) {
501
- this.advance()
502
-
503
- const directives = []
504
-
505
- if (this.match(TokenType.LPAREN)) {
506
- do {
507
- const nameToken = this.current()
508
- const name = nameToken ? this.safeStr(nameToken.value) : ''
509
- this.advance()
510
-
511
- this.match(TokenType.EQUALS)
512
-
513
- const value = this.parseExpression(lang)
514
- directives.push({ name, value })
515
- } while (this.match(TokenType.COMMA))
516
-
517
- this.match(TokenType.RPAREN)
518
- } else {
519
- const nameToken = this.current()
520
- const name = nameToken ? this.safeStr(nameToken.value) : ''
521
- this.advance()
522
-
523
- this.match(TokenType.EQUALS)
524
-
525
- const value = this.parseExpression(lang)
526
- directives.push({ name, value })
527
- }
528
-
529
- return {
530
- type: 'DeclareStatement',
531
- directives: directives
532
- }
533
- }
534
-
535
474
  parseNamespace(lang) {
536
- const first = this.advance()
537
- const firstVal = this.normalizeAccents(String(first.value).toLowerCase())
538
-
539
- if (firstVal === 'espace' && firstVal !== 'espace de noms') {
540
- this.advance()
541
- this.advance()
542
- }
543
-
475
+ this.advance()
544
476
  const name = this.parseNamespacePath(lang)
545
477
  this.skipNewlines()
546
478
 
@@ -601,6 +533,16 @@ class EtherParserPHP extends EtherParserBase {
601
533
  })
602
534
  } while (this.match(TokenType.COMMA))
603
535
 
536
+ if (imports.length === 1) {
537
+ return {
538
+ type: 'UseStatement',
539
+ useType: useType,
540
+ name: imports[0].path,
541
+ alias: imports[0].alias,
542
+ imports: imports
543
+ }
544
+ }
545
+
604
546
  return {
605
547
  type: 'UseStatement',
606
548
  useType: useType,
@@ -608,6 +550,42 @@ class EtherParserPHP extends EtherParserBase {
608
550
  }
609
551
  }
610
552
 
553
+ parseDeclareStatement(lang) {
554
+ this.advance()
555
+
556
+ const directives = []
557
+
558
+ do {
559
+ const nameToken = this.current()
560
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) break
561
+
562
+ let name = String(nameToken.value)
563
+ this.advance()
564
+
565
+ if (name === 'strict' && this.current()) {
566
+ const next = this.normalizeAccents(String(this.current().value).toLowerCase())
567
+ if (next === 'types' || next === '_types') {
568
+ name = 'strict_types'
569
+ this.advance()
570
+ }
571
+ }
572
+
573
+ this.match(TokenType.EQUALS)
574
+
575
+ const value = this.parseExpression(lang)
576
+
577
+ directives.push({
578
+ name: name,
579
+ value: value
580
+ })
581
+ } while (this.match(TokenType.COMMA))
582
+
583
+ return {
584
+ type: 'DeclareStatement',
585
+ directives: directives
586
+ }
587
+ }
588
+
611
589
  parseClassDeclaration(lang) {
612
590
  const modifiers = []
613
591
 
@@ -743,6 +721,10 @@ class EtherParserPHP extends EtherParserBase {
743
721
  return this.parseTraitUse(lang)
744
722
  }
745
723
 
724
+ if (val === 'variable' || val === 'var') {
725
+ this.advance()
726
+ }
727
+
746
728
  return this.parsePropertyDeclaration(lang, visibility, isStatic, isReadonly)
747
729
  }
748
730
 
@@ -803,11 +785,6 @@ class EtherParserPHP extends EtherParserBase {
803
785
  }
804
786
 
805
787
  parsePropertyDeclaration(lang, visibility, isStatic, isReadonly) {
806
- const val = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
807
- if (val === 'variable' || val === 'var') {
808
- this.advance()
809
- }
810
-
811
788
  let typeHint = null
812
789
 
813
790
  const current = this.current()
@@ -819,10 +796,12 @@ class EtherParserPHP extends EtherParserBase {
819
796
  }
820
797
 
821
798
  const nameToken = this.current()
822
- let name = this.safeStr(nameToken ? nameToken.value : 'property')
799
+ let name = nameToken ? nameToken.value : 'property'
823
800
 
824
- if (name.startsWith('$')) {
801
+ if (typeof name === 'string' && name.startsWith('$')) {
825
802
  name = name.substring(1)
803
+ } else if (typeof name !== 'string') {
804
+ name = String(name)
826
805
  }
827
806
 
828
807
  if (nameToken) this.advance()
@@ -1091,6 +1070,7 @@ class EtherParserPHP extends EtherParserBase {
1091
1070
  }
1092
1071
  }
1093
1072
 
1073
+ this.match(TokenType.COLON)
1094
1074
  this.skipNewlines()
1095
1075
 
1096
1076
  let body
@@ -1109,8 +1089,6 @@ class EtherParserPHP extends EtherParserBase {
1109
1089
  }
1110
1090
  }
1111
1091
 
1112
- this.match(TokenType.COLON)
1113
- this.skipNewlines()
1114
1092
  body = this.parseBlock(lang)
1115
1093
 
1116
1094
  return {
@@ -1135,7 +1113,7 @@ class EtherParserPHP extends EtherParserBase {
1135
1113
  }
1136
1114
 
1137
1115
  const varToken = this.current()
1138
- let varName = this.safeStr(varToken ? varToken.value : '')
1116
+ let varName = varToken ? String(varToken.value) : ''
1139
1117
  if (varName.startsWith('$')) {
1140
1118
  varName = varName.substring(1)
1141
1119
  }
@@ -1180,7 +1158,6 @@ class EtherParserPHP extends EtherParserBase {
1180
1158
  let variadic = false
1181
1159
  let byReference = false
1182
1160
  let defaultValue = null
1183
- let name = null
1184
1161
 
1185
1162
  const val = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1186
1163
  if (this.phpVisibility.includes(val)) {
@@ -1194,6 +1171,19 @@ class EtherParserPHP extends EtherParserBase {
1194
1171
  this.advance()
1195
1172
  }
1196
1173
 
1174
+ const currentType = this.current()
1175
+ if (currentType && currentType.type === TokenType.IDENTIFIER) {
1176
+ const typeLower = this.normalizeAccents(String(currentType.value).toLowerCase())
1177
+ const nextToken = this.peek(1)
1178
+
1179
+ if (nextToken && nextToken.type === TokenType.COLON) {
1180
+ } else if (this.phpTypes[typeLower] || (typeLower[0] && typeLower[0] === typeLower[0].toUpperCase())) {
1181
+ if (!nextToken || nextToken.type !== TokenType.EQUALS) {
1182
+ typeHint = this.parseTypeHint(lang)
1183
+ }
1184
+ }
1185
+ }
1186
+
1197
1187
  if (this.match(TokenType.SPREAD) || this.matchValue('...')) {
1198
1188
  variadic = true
1199
1189
  }
@@ -1202,33 +1192,17 @@ class EtherParserPHP extends EtherParserBase {
1202
1192
  byReference = true
1203
1193
  }
1204
1194
 
1205
- const currentToken = this.current()
1206
- if (!currentToken) return null
1207
-
1208
- const nextToken = this.peek(1)
1195
+ const nameToken = this.current()
1196
+ if (!nameToken) return null
1209
1197
 
1210
- if (nextToken && nextToken.type === TokenType.COLON) {
1211
- name = this.safeStr(currentToken.value)
1212
- if (name.startsWith('$')) name = name.substring(1)
1213
- this.advance()
1214
- this.advance()
1215
- typeHint = this.parseTypeHint(lang)
1216
- } else {
1217
- const typeLower = this.normalizeAccents(String(currentToken.value).toLowerCase())
1218
- if (this.phpTypes[typeLower] || /^[A-Z]/.test(currentToken.value)) {
1219
- typeHint = this.parseTypeHint(lang)
1220
- }
1221
-
1222
- const nameToken = this.current()
1223
- if (nameToken && nameToken.type === TokenType.IDENTIFIER) {
1224
- name = this.safeStr(nameToken.value)
1225
- if (name.startsWith('$')) name = name.substring(1)
1226
- this.advance()
1227
- }
1198
+ let name = String(nameToken.value)
1199
+ if (name.startsWith('$')) {
1200
+ name = name.substring(1)
1228
1201
  }
1202
+ this.advance()
1229
1203
 
1230
- if (!name) {
1231
- name = 'param'
1204
+ if (this.match(TokenType.COLON)) {
1205
+ typeHint = this.parseTypeHint(lang)
1232
1206
  }
1233
1207
 
1234
1208
  if (this.match(TokenType.EQUALS)) {
@@ -1317,7 +1291,7 @@ class EtherParserPHP extends EtherParserBase {
1317
1291
  }
1318
1292
 
1319
1293
  const nameToken = this.current()
1320
- let name = this.safeStr(nameToken ? nameToken.value : 'variable')
1294
+ let name = nameToken ? String(nameToken.value) : 'variable'
1321
1295
  if (name.startsWith('$')) {
1322
1296
  name = name.substring(1)
1323
1297
  }
@@ -1357,25 +1331,47 @@ class EtherParserPHP extends EtherParserBase {
1357
1331
 
1358
1332
  const elseVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1359
1333
  if (elseVal === 'sinon si' || elseVal === 'elseif') {
1360
- alternate = this.parseConditional(lang)
1361
- alternate.type = 'IfStatement'
1362
- } else if (elseVal === 'sinon') {
1334
+ this.advance()
1335
+
1336
+ let elseCondition
1337
+ if (this.match(TokenType.LPAREN)) {
1338
+ elseCondition = this.parseExpression(lang)
1339
+ this.match(TokenType.RPAREN)
1340
+ } else {
1341
+ elseCondition = this.parseConditionUntilColon(lang)
1342
+ }
1343
+
1344
+ this.match(TokenType.COLON)
1345
+ this.skipNewlines()
1346
+ const elseConsequent = this.parseBlock(lang)
1347
+
1348
+ alternate = {
1349
+ type: 'IfStatement',
1350
+ test: elseCondition,
1351
+ consequent: elseConsequent,
1352
+ alternate: null
1353
+ }
1354
+
1355
+ this.skipNewlines()
1356
+ const nextElse = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1357
+ if (nextElse === 'sinon' || nextElse === 'else') {
1358
+ this.advance()
1359
+ this.match(TokenType.COLON)
1360
+ this.skipNewlines()
1361
+ alternate.alternate = this.parseBlock(lang)
1362
+ }
1363
+ } else if (elseVal === 'sinon' || elseVal === 'else') {
1363
1364
  this.advance()
1364
1365
 
1365
1366
  const nextVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1366
1367
  if (nextVal === 'si' || nextVal === 'if') {
1367
1368
  alternate = this.parseConditional(lang)
1368
- alternate.type = 'IfStatement'
1369
+ alternate.type = 'ElseIfStatement'
1369
1370
  } else {
1370
1371
  this.match(TokenType.COLON)
1371
1372
  this.skipNewlines()
1372
1373
  alternate = this.parseBlock(lang)
1373
1374
  }
1374
- } else if (elseVal === 'else') {
1375
- this.advance()
1376
- this.match(TokenType.COLON)
1377
- this.skipNewlines()
1378
- alternate = this.parseBlock(lang)
1379
1375
  }
1380
1376
 
1381
1377
  return {
@@ -1387,7 +1383,7 @@ class EtherParserPHP extends EtherParserBase {
1387
1383
  }
1388
1384
 
1389
1385
  parseConditionUntilColon(lang) {
1390
- const startPos = this.pos
1386
+ const tokens = []
1391
1387
  let depth = 0
1392
1388
 
1393
1389
  while (!this.isAtEnd()) {
@@ -1404,23 +1400,36 @@ class EtherParserPHP extends EtherParserBase {
1404
1400
  break
1405
1401
  }
1406
1402
 
1403
+ tokens.push(token)
1407
1404
  this.advance()
1408
1405
  }
1409
1406
 
1410
- const endPos = this.pos
1411
- this.pos = startPos
1412
-
1413
- if (endPos > startPos) {
1414
- return this.parseExpression(lang)
1407
+ if (tokens.length === 0) {
1408
+ return { type: 'Literal', value: true }
1415
1409
  }
1416
1410
 
1417
- return { type: 'Literal', value: true }
1411
+ const savedPos = this.pos
1412
+ const savedTokens = this.tokens
1413
+ this.tokens = tokens
1414
+ this.pos = 0
1415
+
1416
+ const expr = this.parseExpression(lang)
1417
+
1418
+ this.tokens = savedTokens
1419
+ this.pos = savedPos
1420
+
1421
+ return expr
1418
1422
  }
1419
1423
 
1420
1424
  parseLoop(lang) {
1421
1425
  const token = this.current()
1422
1426
  const value = this.normalizeAccents(String(token.value).toLowerCase())
1423
1427
 
1428
+ if (value === 'pour chaque' || value === 'foreach') {
1429
+ this.advance()
1430
+ return this.parseForeach(lang)
1431
+ }
1432
+
1424
1433
  if (value === 'pour') {
1425
1434
  const next = this.peek(1)
1426
1435
  if (next && this.normalizeAccents(String(next.value).toLowerCase()) === 'chaque') {
@@ -1431,16 +1440,14 @@ class EtherParserPHP extends EtherParserBase {
1431
1440
  return this.parseFor(lang)
1432
1441
  }
1433
1442
 
1434
- if (value === 'foreach' || value === 'pour chaque') {
1443
+ if (value === 'tant que' || value === 'while') {
1435
1444
  this.advance()
1436
- return this.parseForeach(lang)
1445
+ return this.parseWhile(lang)
1437
1446
  }
1438
1447
 
1439
- if (value === 'while' || value === 'tant') {
1448
+ if (value === 'tant') {
1440
1449
  this.advance()
1441
- if (value === 'tant') {
1442
- this.matchValue('que')
1443
- }
1450
+ this.matchValue('que')
1444
1451
  return this.parseWhile(lang)
1445
1452
  }
1446
1453
 
@@ -1454,7 +1461,7 @@ class EtherParserPHP extends EtherParserBase {
1454
1461
 
1455
1462
  parseFor(lang) {
1456
1463
  this.advance()
1457
-
1464
+
1458
1465
  let init = null
1459
1466
  let test = null
1460
1467
  let update = null
@@ -1475,19 +1482,13 @@ class EtherParserPHP extends EtherParserBase {
1475
1482
  }
1476
1483
  this.match(TokenType.RPAREN)
1477
1484
  } else {
1478
- if (!this.check(TokenType.SEMICOLON) && !this.check(TokenType.COLON)) {
1479
- init = this.parseExpression(lang)
1480
- }
1485
+ init = this.parseForInitUntilSemicolon(lang)
1481
1486
  this.match(TokenType.SEMICOLON)
1482
1487
 
1483
- if (!this.check(TokenType.SEMICOLON) && !this.check(TokenType.COLON)) {
1484
- test = this.parseExpression(lang)
1485
- }
1488
+ test = this.parseForConditionUntilSemicolon(lang)
1486
1489
  this.match(TokenType.SEMICOLON)
1487
1490
 
1488
- if (!this.check(TokenType.COLON) && !this.check(TokenType.NEWLINE)) {
1489
- update = this.parseExpression(lang)
1490
- }
1491
+ update = this.parseForUpdateUntilColon(lang)
1491
1492
  }
1492
1493
 
1493
1494
  this.match(TokenType.COLON)
@@ -1503,6 +1504,82 @@ class EtherParserPHP extends EtherParserBase {
1503
1504
  }
1504
1505
  }
1505
1506
 
1507
+ parseForInitUntilSemicolon(lang) {
1508
+ const tokens = []
1509
+ while (!this.isAtEnd()) {
1510
+ const token = this.current()
1511
+ if (!token) break
1512
+ if (token.type === TokenType.SEMICOLON) break
1513
+ tokens.push(token)
1514
+ this.advance()
1515
+ }
1516
+
1517
+ if (tokens.length === 0) return null
1518
+
1519
+ const savedPos = this.pos
1520
+ const savedTokens = this.tokens
1521
+ this.tokens = tokens
1522
+ this.pos = 0
1523
+
1524
+ const expr = this.parseExpression(lang)
1525
+
1526
+ this.tokens = savedTokens
1527
+ this.pos = savedPos
1528
+
1529
+ return expr
1530
+ }
1531
+
1532
+ parseForConditionUntilSemicolon(lang) {
1533
+ const tokens = []
1534
+ while (!this.isAtEnd()) {
1535
+ const token = this.current()
1536
+ if (!token) break
1537
+ if (token.type === TokenType.SEMICOLON) break
1538
+ tokens.push(token)
1539
+ this.advance()
1540
+ }
1541
+
1542
+ if (tokens.length === 0) return null
1543
+
1544
+ const savedPos = this.pos
1545
+ const savedTokens = this.tokens
1546
+ this.tokens = tokens
1547
+ this.pos = 0
1548
+
1549
+ const expr = this.parseExpression(lang)
1550
+
1551
+ this.tokens = savedTokens
1552
+ this.pos = savedPos
1553
+
1554
+ return expr
1555
+ }
1556
+
1557
+ parseForUpdateUntilColon(lang) {
1558
+ const tokens = []
1559
+ while (!this.isAtEnd()) {
1560
+ const token = this.current()
1561
+ if (!token) break
1562
+ if (token.type === TokenType.COLON) break
1563
+ if (token.type === TokenType.NEWLINE) break
1564
+ tokens.push(token)
1565
+ this.advance()
1566
+ }
1567
+
1568
+ if (tokens.length === 0) return null
1569
+
1570
+ const savedPos = this.pos
1571
+ const savedTokens = this.tokens
1572
+ this.tokens = tokens
1573
+ this.pos = 0
1574
+
1575
+ const expr = this.parseExpression(lang)
1576
+
1577
+ this.tokens = savedTokens
1578
+ this.pos = savedPos
1579
+
1580
+ return expr
1581
+ }
1582
+
1506
1583
  parseForeach(lang) {
1507
1584
  let array = null
1508
1585
  let key = null
@@ -1522,7 +1599,7 @@ class EtherParserPHP extends EtherParserBase {
1522
1599
  }
1523
1600
 
1524
1601
  const firstVar = this.current()
1525
- let firstName = this.safeStr(firstVar ? firstVar.value : 'item')
1602
+ let firstName = firstVar ? String(firstVar.value) : 'item'
1526
1603
  if (firstName.startsWith('$')) firstName = firstName.substring(1)
1527
1604
  this.advance()
1528
1605
 
@@ -1534,7 +1611,7 @@ class EtherParserPHP extends EtherParserBase {
1534
1611
  }
1535
1612
 
1536
1613
  const valueVar = this.current()
1537
- value = this.safeStr(valueVar ? valueVar.value : 'value')
1614
+ value = valueVar ? String(valueVar.value) : 'value'
1538
1615
  if (value.startsWith('$')) value = value.substring(1)
1539
1616
  this.advance()
1540
1617
  } else {
@@ -1543,12 +1620,28 @@ class EtherParserPHP extends EtherParserBase {
1543
1620
 
1544
1621
  this.match(TokenType.RPAREN)
1545
1622
  } else {
1546
- const arrayToken = this.current()
1547
- if (arrayToken) {
1548
- array = { type: 'Identifier', name: this.safeStr(arrayToken.value) }
1623
+ const tokens = []
1624
+ while (!this.isAtEnd()) {
1625
+ const token = this.current()
1626
+ if (!token) break
1627
+ const tokenVal = this.normalizeAccents(String(token.value).toLowerCase())
1628
+ if (tokenVal === 'comme' || tokenVal === 'as') break
1629
+ if (token.type === TokenType.COLON) break
1630
+ if (token.type === TokenType.NEWLINE) break
1631
+ tokens.push(token)
1549
1632
  this.advance()
1550
1633
  }
1551
1634
 
1635
+ if (tokens.length > 0) {
1636
+ const savedPos = this.pos
1637
+ const savedTokens = this.tokens
1638
+ this.tokens = tokens
1639
+ this.pos = 0
1640
+ array = this.parseExpression(lang)
1641
+ this.tokens = savedTokens
1642
+ this.pos = savedPos
1643
+ }
1644
+
1552
1645
  const asVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1553
1646
  if (asVal === 'comme' || asVal === 'as') {
1554
1647
  this.advance()
@@ -1559,23 +1652,27 @@ class EtherParserPHP extends EtherParserBase {
1559
1652
  }
1560
1653
 
1561
1654
  const firstVar = this.current()
1562
- let firstName = this.safeStr(firstVar ? firstVar.value : 'item')
1563
- if (firstName.startsWith('$')) firstName = firstName.substring(1)
1564
- this.advance()
1655
+ if (firstVar && firstVar.type === TokenType.IDENTIFIER) {
1656
+ let firstName = String(firstVar.value)
1657
+ if (firstName.startsWith('$')) firstName = firstName.substring(1)
1658
+ this.advance()
1565
1659
 
1566
- if (this.match(TokenType.DOUBLE_ARROW) || this.matchValue('=>')) {
1567
- key = firstName
1568
-
1569
- if (this.match(TokenType.AMPERSAND)) {
1570
- byRef = true
1660
+ if (this.match(TokenType.DOUBLE_ARROW) || this.matchValue('=>')) {
1661
+ key = firstName
1662
+
1663
+ if (this.match(TokenType.AMPERSAND)) {
1664
+ byRef = true
1665
+ }
1666
+
1667
+ const valueVar = this.current()
1668
+ if (valueVar && valueVar.type === TokenType.IDENTIFIER) {
1669
+ value = String(valueVar.value)
1670
+ if (value.startsWith('$')) value = value.substring(1)
1671
+ this.advance()
1672
+ }
1673
+ } else {
1674
+ value = firstName
1571
1675
  }
1572
-
1573
- const valueVar = this.current()
1574
- value = this.safeStr(valueVar ? valueVar.value : 'value')
1575
- if (value.startsWith('$')) value = value.substring(1)
1576
- this.advance()
1577
- } else {
1578
- value = firstName
1579
1676
  }
1580
1677
  }
1581
1678
 
@@ -1639,7 +1736,7 @@ class EtherParserPHP extends EtherParserBase {
1639
1736
  return {
1640
1737
  type: 'DoWhileStatement',
1641
1738
  body: body,
1642
- condition: condition
1739
+ test: condition
1643
1740
  }
1644
1741
  }
1645
1742
 
@@ -1686,25 +1783,29 @@ class EtherParserPHP extends EtherParserBase {
1686
1783
 
1687
1784
  if (val === 'cas' || val === 'case') {
1688
1785
  this.advance()
1689
- const test = this.parseExpression(lang)
1786
+ const test = this.parseConditionUntilColon(lang)
1690
1787
  this.match(TokenType.COLON)
1691
1788
  this.skipNewlines()
1692
- this.match(TokenType.INDENT)
1693
1789
 
1790
+ const hasInnerIndent = this.match(TokenType.INDENT)
1694
1791
  const consequent = []
1792
+
1695
1793
  while (!this.isAtEnd()) {
1696
1794
  this.skipNewlines()
1697
- const cur = this.current()
1698
- if (!cur) break
1795
+ const nextToken = this.current()
1796
+ if (!nextToken) break
1699
1797
 
1700
- const nextVal = this.normalizeAccents(String(cur.value).toLowerCase())
1701
- if (['cas', 'case', 'defaut', 'default'].includes(nextVal)) {
1798
+ const nextVal = this.normalizeAccents(String(nextToken.value).toLowerCase())
1799
+ if (['cas', 'case', 'defaut', 'default', 'défaut'].includes(nextVal)) {
1702
1800
  break
1703
1801
  }
1704
- if (cur.type === TokenType.RBRACE || cur.type === TokenType.DEDENT) {
1705
- this.advance()
1802
+ if (nextToken.type === TokenType.RBRACE || nextToken.type === TokenType.DEDENT) {
1803
+ if (hasInnerIndent) {
1804
+ this.advance()
1805
+ }
1706
1806
  break
1707
1807
  }
1808
+
1708
1809
  const stmt = this.parseStatement(lang)
1709
1810
  if (stmt) consequent.push(stmt)
1710
1811
  this.skipNewlines()
@@ -1715,21 +1816,26 @@ class EtherParserPHP extends EtherParserBase {
1715
1816
  test: test,
1716
1817
  consequent: consequent
1717
1818
  })
1718
- } else if (val === 'defaut' || val === 'default') {
1819
+ } else if (val === 'defaut' || val === 'default' || val === 'défaut') {
1719
1820
  this.advance()
1720
1821
  this.match(TokenType.COLON)
1721
1822
  this.skipNewlines()
1722
- this.match(TokenType.INDENT)
1723
1823
 
1824
+ const hasInnerIndent = this.match(TokenType.INDENT)
1724
1825
  const consequent = []
1826
+
1725
1827
  while (!this.isAtEnd()) {
1726
1828
  this.skipNewlines()
1727
- const cur = this.current()
1728
- if (!cur) break
1729
- if (cur.type === TokenType.RBRACE || cur.type === TokenType.DEDENT) {
1730
- this.advance()
1829
+ const nextToken = this.current()
1830
+ if (!nextToken) break
1831
+
1832
+ if (nextToken.type === TokenType.RBRACE || nextToken.type === TokenType.DEDENT) {
1833
+ if (hasInnerIndent) {
1834
+ this.advance()
1835
+ }
1731
1836
  break
1732
1837
  }
1838
+
1733
1839
  const stmt = this.parseStatement(lang)
1734
1840
  if (stmt) consequent.push(stmt)
1735
1841
  this.skipNewlines()
@@ -1833,7 +1939,7 @@ class EtherParserPHP extends EtherParserBase {
1833
1939
  do {
1834
1940
  const typeToken = this.current()
1835
1941
  if (typeToken && typeToken.type === TokenType.IDENTIFIER) {
1836
- const typeName = this.safeStr(typeToken.value)
1942
+ const typeName = String(typeToken.value)
1837
1943
  if (typeName.startsWith('$')) {
1838
1944
  param = typeName.substring(1)
1839
1945
  } else {
@@ -1846,7 +1952,7 @@ class EtherParserPHP extends EtherParserBase {
1846
1952
  if (!param) {
1847
1953
  const paramToken = this.current()
1848
1954
  if (paramToken && paramToken.type === TokenType.IDENTIFIER) {
1849
- param = this.safeStr(paramToken.value)
1955
+ param = String(paramToken.value)
1850
1956
  if (param.startsWith('$')) param = param.substring(1)
1851
1957
  this.advance()
1852
1958
  }
@@ -1855,7 +1961,7 @@ class EtherParserPHP extends EtherParserBase {
1855
1961
  } else {
1856
1962
  const typeToken = this.current()
1857
1963
  if (typeToken && typeToken.type === TokenType.IDENTIFIER) {
1858
- types.push(this.safeStr(typeToken.value))
1964
+ types.push(String(typeToken.value))
1859
1965
  this.advance()
1860
1966
  }
1861
1967
 
@@ -1864,7 +1970,7 @@ class EtherParserPHP extends EtherParserBase {
1864
1970
  this.advance()
1865
1971
  const paramToken = this.current()
1866
1972
  if (paramToken && paramToken.type === TokenType.IDENTIFIER) {
1867
- param = this.safeStr(paramToken.value)
1973
+ param = String(paramToken.value)
1868
1974
  if (param.startsWith('$')) param = param.substring(1)
1869
1975
  this.advance()
1870
1976
  }
@@ -1979,7 +2085,7 @@ class EtherParserPHP extends EtherParserBase {
1979
2085
  const variables = []
1980
2086
  do {
1981
2087
  const varToken = this.current()
1982
- let varName = this.safeStr(varToken ? varToken.value : '')
2088
+ let varName = varToken ? String(varToken.value) : ''
1983
2089
  if (varName.startsWith('$')) varName = varName.substring(1)
1984
2090
  this.advance()
1985
2091
  variables.push(varName)
@@ -2196,12 +2302,12 @@ class EtherParserPHP extends EtherParserBase {
2196
2302
  return { type: 'UnaryExpression', operator: '!', operand }
2197
2303
  }
2198
2304
 
2199
- if (this.match(TokenType.MINUS) && !this.check(TokenType.MINUS)) {
2305
+ if (this.match(TokenType.MINUS)) {
2200
2306
  const operand = this.parseUnary(lang)
2201
2307
  return { type: 'UnaryExpression', operator: '-', operand }
2202
2308
  }
2203
2309
 
2204
- if (this.match(TokenType.PLUS) && !this.check(TokenType.PLUS)) {
2310
+ if (this.match(TokenType.PLUS)) {
2205
2311
  const operand = this.parseUnary(lang)
2206
2312
  return { type: 'UnaryExpression', operator: '+', operand }
2207
2313
  }
@@ -2216,15 +2322,12 @@ class EtherParserPHP extends EtherParserBase {
2216
2322
  return { type: 'UnaryExpression', operator: '~', operand }
2217
2323
  }
2218
2324
 
2219
- const current = this.current()
2220
- if (current && current.type === TokenType.PLUS && current.value === '++') {
2221
- this.advance()
2325
+ if (this.match(TokenType.DOUBLE_PLUS)) {
2222
2326
  const operand = this.parseUnary(lang)
2223
2327
  return { type: 'UpdateExpression', operator: '++', prefix: true, operand }
2224
2328
  }
2225
2329
 
2226
- if (current && current.type === TokenType.MINUS && current.value === '--') {
2227
- this.advance()
2330
+ if (this.match(TokenType.DOUBLE_MINUS)) {
2228
2331
  const operand = this.parseUnary(lang)
2229
2332
  return { type: 'UpdateExpression', operator: '--', prefix: true, operand }
2230
2333
  }
@@ -2333,123 +2436,14 @@ class EtherParserPHP extends EtherParserBase {
2333
2436
  }
2334
2437
  }
2335
2438
 
2336
- tryParseMultiWordCall(firstName, lang) {
2337
- const words = [firstName]
2338
- let lookAhead = 0
2339
-
2340
- while (true) {
2341
- const nextToken = this.peek(lookAhead)
2342
- if (!nextToken) break
2343
-
2344
- if (nextToken.type === TokenType.LPAREN) {
2345
- if (words.length > 1) {
2346
- for (let i = 0; i < lookAhead; i++) {
2347
- this.advance()
2348
- }
2349
- this.advance()
2350
-
2351
- const args = []
2352
- while (!this.isAtEnd() && !this.check(TokenType.RPAREN)) {
2353
- this.skipNewlines()
2354
- if (this.check(TokenType.RPAREN)) break
2355
-
2356
- if (this.match(TokenType.SPREAD) || this.matchValue('...')) {
2357
- const arg = this.parseExpression(lang)
2358
- args.push({ type: 'SpreadElement', argument: arg })
2359
- } else {
2360
- const arg = this.parseExpression(lang)
2361
- args.push(arg)
2362
- }
2363
- this.match(TokenType.COMMA)
2364
- this.skipNewlines()
2365
- }
2366
- this.match(TokenType.RPAREN)
2367
-
2368
- const funcName = words.join(' ')
2369
- return {
2370
- type: 'CallExpression',
2371
- callee: { type: 'Identifier', name: funcName },
2372
- arguments: args
2373
- }
2374
- }
2375
- break
2376
- }
2377
-
2378
- if (nextToken.type === TokenType.IDENTIFIER) {
2379
- words.push(this.safeStr(nextToken.value))
2380
- lookAhead++
2381
- } else {
2382
- break
2383
- }
2384
- }
2385
-
2386
- return null
2387
- }
2388
-
2389
- tryParseMultiWordCallFromStart(lang) {
2390
- const words = []
2391
- let lookAhead = 0
2392
-
2393
- while (true) {
2394
- const token = this.peek(lookAhead)
2395
- if (!token) break
2396
-
2397
- if (token.type === TokenType.LPAREN) {
2398
- if (words.length >= 2) {
2399
- for (let i = 0; i < lookAhead; i++) {
2400
- this.advance()
2401
- }
2402
- this.advance()
2403
-
2404
- const args = []
2405
- while (!this.isAtEnd() && !this.check(TokenType.RPAREN)) {
2406
- this.skipNewlines()
2407
- if (this.check(TokenType.RPAREN)) break
2408
-
2409
- if (this.match(TokenType.SPREAD) || this.matchValue('...')) {
2410
- const arg = this.parseExpression(lang)
2411
- args.push({ type: 'SpreadElement', argument: arg })
2412
- } else {
2413
- const arg = this.parseExpression(lang)
2414
- args.push(arg)
2415
- }
2416
- this.match(TokenType.COMMA)
2417
- this.skipNewlines()
2418
- }
2419
- this.match(TokenType.RPAREN)
2420
-
2421
- const funcName = words.join(' ')
2422
- return {
2423
- type: 'CallExpression',
2424
- callee: { type: 'Identifier', name: funcName },
2425
- arguments: args
2426
- }
2427
- }
2428
- break
2429
- }
2430
-
2431
- if (token.type === TokenType.IDENTIFIER) {
2432
- words.push(this.safeStr(token.value))
2433
- lookAhead++
2434
- } else {
2435
- break
2436
- }
2437
- }
2438
-
2439
- return null
2440
- }
2441
-
2442
2439
  parsePostfix(lang) {
2443
2440
  let expr = this.parseCall(lang)
2444
2441
 
2445
- const current = this.current()
2446
- if (current && current.type === TokenType.PLUS && current.value === '++') {
2447
- this.advance()
2442
+ if (this.match(TokenType.DOUBLE_PLUS) || this.matchValue('++')) {
2448
2443
  return { type: 'UpdateExpression', operator: '++', prefix: false, operand: expr }
2449
2444
  }
2450
2445
 
2451
- if (current && current.type === TokenType.MINUS && current.value === '--') {
2452
- this.advance()
2446
+ if (this.match(TokenType.DOUBLE_MINUS) || this.matchValue('--')) {
2453
2447
  return { type: 'UpdateExpression', operator: '--', prefix: false, operand: expr }
2454
2448
  }
2455
2449
 
@@ -2459,13 +2453,6 @@ class EtherParserPHP extends EtherParserBase {
2459
2453
  parseCall(lang) {
2460
2454
  let expr = this.parsePrimary(lang)
2461
2455
 
2462
- if (expr && expr.type === 'Identifier') {
2463
- const multiWordResult = this.tryParseMultiWordCall(expr.name, lang)
2464
- if (multiWordResult) {
2465
- return multiWordResult
2466
- }
2467
- }
2468
-
2469
2456
  while (true) {
2470
2457
  if (this.match(TokenType.LPAREN)) {
2471
2458
  const args = []
@@ -2548,11 +2535,11 @@ class EtherParserPHP extends EtherParserBase {
2548
2535
 
2549
2536
  if (token.type === TokenType.STRING) {
2550
2537
  this.advance()
2551
- let value = this.safeStr(token.value)
2538
+ let value = token.value
2552
2539
  if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
2553
2540
  value = value.slice(1, -1)
2554
2541
  }
2555
- const isDouble = value.startsWith('"') || this.safeStr(token.value).startsWith('"')
2542
+ const isDouble = token.value.startsWith('"')
2556
2543
  return { type: 'StringLiteral', value, doubleQuoted: isDouble }
2557
2544
  }
2558
2545
 
@@ -2562,7 +2549,7 @@ class EtherParserPHP extends EtherParserBase {
2562
2549
  }
2563
2550
 
2564
2551
  if (token.type === TokenType.IDENTIFIER) {
2565
- const value = this.safeStr(token.value)
2552
+ const value = token.value
2566
2553
  const valueLower = this.normalizeAccents(value.toLowerCase())
2567
2554
 
2568
2555
  if (valueLower === 'vrai' || valueLower === 'true') {
@@ -2612,7 +2599,7 @@ class EtherParserPHP extends EtherParserBase {
2612
2599
  return this.parseMatchExpression(lang)
2613
2600
  }
2614
2601
 
2615
- if (value.startsWith('$')) {
2602
+ if (typeof value === 'string' && value.startsWith('$')) {
2616
2603
  this.advance()
2617
2604
  return { type: 'Variable', name: value.substring(1) }
2618
2605
  }
@@ -2623,7 +2610,7 @@ class EtherParserPHP extends EtherParserBase {
2623
2610
 
2624
2611
  if (token.type === TokenType.VARIABLE) {
2625
2612
  this.advance()
2626
- let name = this.safeStr(token.value)
2613
+ let name = String(token.value)
2627
2614
  if (name.startsWith('$')) name = name.substring(1)
2628
2615
  return { type: 'Variable', name }
2629
2616
  }