ether-code 0.8.3 → 0.8.5

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.
package/cli/ether.js CHANGED
@@ -6,7 +6,7 @@ const http = require('http')
6
6
  const { EtherCompiler } = require('./compiler')
7
7
  const { Watcher } = require('./watcher')
8
8
 
9
- const VERSION = '0.8.3'
9
+ const VERSION = '0.8.5'
10
10
 
11
11
  const COLORS = {
12
12
  reset: '\x1b[0m',
@@ -101,6 +101,8 @@ class PHPGenerator {
101
101
 
102
102
  const generators = {
103
103
  'Program': () => this.generateProgram(node),
104
+ 'DeclareStatement': () => this.generateDeclareStatement(node),
105
+ 'NamespaceDeclaration': () => this.generateNamespaceDeclaration(node),
104
106
  'FunctionDeclaration': () => this.generateFunctionDeclaration(node),
105
107
  'ClassDeclaration': () => this.generateClassDeclaration(node),
106
108
  'InterfaceDeclaration': () => this.generateInterfaceDeclaration(node),
@@ -110,6 +112,7 @@ class PHPGenerator {
110
112
  'PropertyDeclaration': () => this.generatePropertyDeclaration(node),
111
113
  'VariableDeclaration': () => this.generateVariableDeclaration(node),
112
114
  'ConstantDeclaration': () => this.generateConstantDeclaration(node),
115
+ 'DefineDeclaration': () => this.generateDefineDeclaration(node),
113
116
  'IfStatement': () => this.generateIfStatement(node),
114
117
  'ForStatement': () => this.generateForStatement(node),
115
118
  'ForEachStatement': () => this.generateForEachStatement(node),
@@ -176,6 +179,33 @@ class PHPGenerator {
176
179
  }
177
180
  }
178
181
 
182
+ generateDeclareStatement(node) {
183
+ const directives = (node.directives || []).map(d => {
184
+ const name = this.safeString(d.name)
185
+ const value = this.generateNode(d.value)
186
+ return `${name}=${value}`
187
+ }).join(', ')
188
+
189
+ this.writeLine(`declare(${directives});`)
190
+ this.writeLine('')
191
+ }
192
+
193
+ generateNamespaceDeclaration(node) {
194
+ const name = this.safeString(node.name)
195
+ this.writeLine(`namespace ${name};`)
196
+ this.writeLine('')
197
+
198
+ if (node.body) {
199
+ this.generateNode(node.body)
200
+ }
201
+ }
202
+
203
+ generateDefineDeclaration(node) {
204
+ const name = this.safeString(node.name)
205
+ const value = this.generateNode(node.value)
206
+ this.writeLine(`define('${name}', ${value});`)
207
+ }
208
+
179
209
  generateFunctionDeclaration(node) {
180
210
  const name = this.safeString(node.name || node.id?.name || 'anonymous')
181
211
  const translatedName = this.translate(name)
@@ -465,7 +495,7 @@ class PHPGenerator {
465
495
  generateVariableDeclaration(node) {
466
496
  for (const decl of node.declarations || [node]) {
467
497
  const name = this.safeString(decl.name || decl.id?.name || decl.id)
468
- const varName = '$' + this.translate(name)
498
+ const varName = '$' + name
469
499
 
470
500
  if (decl.init !== undefined) {
471
501
  const value = this.generateNode(decl.init)
@@ -489,7 +519,7 @@ class PHPGenerator {
489
519
  }
490
520
 
491
521
  generateIfStatement(node) {
492
- const test = this.generateNode(node.test)
522
+ const test = this.generateNode(node.test || node.condition)
493
523
  this.writeLine(`if (${test}) {`)
494
524
  this.indent++
495
525
  this.generateNode(node.consequent)
@@ -497,7 +527,7 @@ class PHPGenerator {
497
527
 
498
528
  if (node.alternate) {
499
529
  if (node.alternate.type === 'IfStatement') {
500
- this.write('} else')
530
+ this.write('} else ')
501
531
  this.generateIfStatement({ ...node.alternate, isElseIf: true })
502
532
  } else {
503
533
  this.writeLine('} else {')
@@ -536,7 +566,7 @@ class PHPGenerator {
536
566
  }
537
567
 
538
568
  generateWhileStatement(node) {
539
- const test = this.generateNode(node.test)
569
+ const test = this.generateNode(node.test || node.condition)
540
570
  this.writeLine(`while (${test}) {`)
541
571
  this.indent++
542
572
  this.generateNode(node.body)
@@ -549,7 +579,7 @@ class PHPGenerator {
549
579
  this.indent++
550
580
  this.generateNode(node.body)
551
581
  this.indent--
552
- const test = this.generateNode(node.test)
582
+ const test = this.generateNode(node.test || node.condition)
553
583
  this.writeLine(`} while (${test});`)
554
584
  }
555
585
 
@@ -640,7 +670,8 @@ class PHPGenerator {
640
670
  }
641
671
 
642
672
  generateEchoStatement(node) {
643
- const args = (node.arguments || [node.argument]).filter(Boolean).map(a => this.generateNode(a)).join(', ')
673
+ const exprs = node.expressions || node.arguments || [node.argument]
674
+ const args = exprs.filter(Boolean).map(a => this.generateNode(a)).join(', ')
644
675
  this.writeLine(`echo ${args};`)
645
676
  }
646
677
 
@@ -656,10 +687,19 @@ class PHPGenerator {
656
687
  }
657
688
 
658
689
  generateUseStatement(node) {
659
- const name = this.safeString(node.name || node.source)
660
- const alias = node.alias ? ' as ' + node.alias : ''
661
- const type = node.type === 'function' ? 'function ' : (node.type === 'const' ? 'const ' : '')
662
- this.writeLine(`use ${type}${name}${alias};`)
690
+ const typePrefix = node.useType === 'function' ? 'function ' : (node.useType === 'const' ? 'const ' : '')
691
+
692
+ if (node.imports && node.imports.length > 0) {
693
+ for (const imp of node.imports) {
694
+ const path = this.safeString(imp.path)
695
+ const alias = imp.alias ? ' as ' + imp.alias : ''
696
+ this.writeLine(`use ${typePrefix}${path}${alias};`)
697
+ }
698
+ } else {
699
+ const name = this.safeString(node.name || node.source)
700
+ const alias = node.alias ? ' as ' + node.alias : ''
701
+ this.writeLine(`use ${typePrefix}${name}${alias};`)
702
+ }
663
703
  }
664
704
 
665
705
  generateIncludeStatement(node) {
@@ -772,21 +812,21 @@ class PHPGenerator {
772
812
  }
773
813
 
774
814
  generateUnaryExpression(node) {
775
- const argument = this.generateNode(node.argument)
815
+ const argument = this.generateNode(node.argument || node.operand)
776
816
  let op = node.operator
777
817
  if (op === 'non' || op === 'not') op = '!'
778
818
 
779
- if (node.prefix) {
819
+ if (node.prefix !== false) {
780
820
  return `${op}${argument}`
781
821
  }
782
822
  return `${argument}${op}`
783
823
  }
784
824
 
785
825
  generateUpdateExpression(node) {
786
- const argument = this.generateNode(node.argument)
826
+ const argument = this.generateNode(node.argument || node.operand)
787
827
  const op = node.operator
788
828
 
789
- if (node.prefix) {
829
+ if (node.prefix !== false) {
790
830
  return `${op}${argument}`
791
831
  }
792
832
  return `${argument}${op}`
@@ -878,43 +918,41 @@ class PHPGenerator {
878
918
  if (!node) return '$var'
879
919
 
880
920
  const name = this.safeString(node.name || node.id?.name || node)
881
- const translated = this.translate(name)
882
- const translatedStr = String(translated)
921
+ const nameStr = String(name)
883
922
 
884
- if (translatedStr.startsWith('$')) return translatedStr
885
- return '$' + translatedStr
923
+ if (nameStr.startsWith('$')) return nameStr
924
+ return '$' + nameStr
886
925
  }
887
926
 
888
927
  generateIdentifier(node) {
889
928
  if (!node) return '$var'
890
929
 
891
930
  const name = this.safeString(node.name || node.id?.name || node)
892
- const translated = this.translate(name)
893
- const translatedStr = String(translated)
931
+ const nameStr = String(name)
894
932
 
895
- if (translatedStr.startsWith('$')) return translatedStr
933
+ if (nameStr.startsWith('$')) return nameStr
896
934
 
897
935
  const keywords = [
898
936
  'true', 'false', 'null',
899
937
  'self', 'parent', 'static',
900
938
  'this', '$this'
901
939
  ]
902
- if (keywords.includes(translatedStr.toLowerCase())) {
903
- if (translatedStr.toLowerCase() === 'this') return '$this'
904
- return translatedStr
940
+ if (keywords.includes(nameStr.toLowerCase())) {
941
+ if (nameStr.toLowerCase() === 'this') return '$this'
942
+ return nameStr
905
943
  }
906
944
 
907
- if (/^[A-Z]/.test(translatedStr)) return translatedStr
945
+ if (/^[A-Z]/.test(nameStr)) return nameStr
908
946
 
909
- if (translatedStr.includes('(') || translatedStr.includes('::')) return translatedStr
947
+ if (nameStr.includes('(') || nameStr.includes('::')) return nameStr
910
948
 
911
949
  const superglobals = ['_GET', '_POST', '_SERVER', '_SESSION', '_COOKIE', '_FILES', '_REQUEST', '_ENV', 'GLOBALS']
912
- const upper = translatedStr.toUpperCase()
913
- if (superglobals.includes(translatedStr) || superglobals.includes(upper)) {
950
+ const upper = nameStr.toUpperCase()
951
+ if (superglobals.includes(nameStr) || superglobals.includes(upper)) {
914
952
  return '$' + upper
915
953
  }
916
954
 
917
- return '$' + translatedStr
955
+ return '$' + nameStr
918
956
  }
919
957
 
920
958
  generateLiteral(node) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ether-code",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Ether - Le langage intentionnel",
5
5
  "main": "cli/compiler.js",
6
6
  "bin": {
@@ -278,6 +278,10 @@ class EtherParserPHP extends EtherParserBase {
278
278
 
279
279
  const value = token.value != null ? this.normalizeAccents(String(token.value).toLowerCase()) : ''
280
280
 
281
+ if (this.isDeclare(value)) {
282
+ return this.parseDeclare(lang)
283
+ }
284
+
281
285
  if (this.isNamespace(value)) {
282
286
  return this.parseNamespace(lang)
283
287
  }
@@ -371,7 +375,17 @@ class EtherParserPHP extends EtherParserBase {
371
375
  }
372
376
 
373
377
  isNamespace(value) {
374
- return ['namespace', 'espace de noms'].includes(value)
378
+ if (value === 'namespace') return true
379
+ if (value === 'espace') {
380
+ const next1 = this.peek(1)
381
+ const next2 = this.peek(2)
382
+ if (next1 && next2) {
383
+ const val1 = this.normalizeAccents(String(next1.value).toLowerCase())
384
+ const val2 = this.normalizeAccents(String(next2.value).toLowerCase())
385
+ if (val1 === 'de' && val2 === 'noms') return true
386
+ }
387
+ }
388
+ return false
375
389
  }
376
390
 
377
391
  isUse(value) {
@@ -469,8 +483,54 @@ class EtherParserPHP extends EtherParserBase {
469
483
  return value === 'match'
470
484
  }
471
485
 
472
- parseNamespace(lang) {
486
+ isDeclare(value) {
487
+ return ['declare', 'declarer', 'déclarer'].includes(value)
488
+ }
489
+
490
+ parseDeclare(lang) {
473
491
  this.advance()
492
+
493
+ const directives = []
494
+
495
+ if (this.match(TokenType.LPAREN)) {
496
+ do {
497
+ const nameToken = this.current()
498
+ const name = nameToken ? this.safeStr(nameToken.value) : ''
499
+ this.advance()
500
+
501
+ this.match(TokenType.EQUALS)
502
+
503
+ const value = this.parseExpression(lang)
504
+ directives.push({ name, value })
505
+ } while (this.match(TokenType.COMMA))
506
+
507
+ this.match(TokenType.RPAREN)
508
+ } else {
509
+ const nameToken = this.current()
510
+ const name = nameToken ? this.safeStr(nameToken.value) : ''
511
+ this.advance()
512
+
513
+ this.match(TokenType.EQUALS)
514
+
515
+ const value = this.parseExpression(lang)
516
+ directives.push({ name, value })
517
+ }
518
+
519
+ return {
520
+ type: 'DeclareStatement',
521
+ directives: directives
522
+ }
523
+ }
524
+
525
+ parseNamespace(lang) {
526
+ const first = this.advance()
527
+ const firstVal = this.normalizeAccents(String(first.value).toLowerCase())
528
+
529
+ if (firstVal === 'espace') {
530
+ this.advance()
531
+ this.advance()
532
+ }
533
+
474
534
  const name = this.parseNamespacePath(lang)
475
535
  this.skipNewlines()
476
536
 
@@ -1098,6 +1158,7 @@ class EtherParserPHP extends EtherParserBase {
1098
1158
  let variadic = false
1099
1159
  let byReference = false
1100
1160
  let defaultValue = null
1161
+ let name = null
1101
1162
 
1102
1163
  const val = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1103
1164
  if (this.phpVisibility.includes(val)) {
@@ -1111,16 +1172,6 @@ class EtherParserPHP extends EtherParserBase {
1111
1172
  this.advance()
1112
1173
  }
1113
1174
 
1114
- const currentType = this.current()
1115
- if (currentType && currentType.type === TokenType.IDENTIFIER) {
1116
- const typeLower = this.normalizeAccents(String(currentType.value).toLowerCase())
1117
- if (this.phpTypes[typeLower] || typeLower[0] === typeLower[0].toUpperCase()) {
1118
- if (!this.peek(1) || this.peek(1).type !== TokenType.EQUALS) {
1119
- typeHint = this.parseTypeHint(lang)
1120
- }
1121
- }
1122
- }
1123
-
1124
1175
  if (this.match(TokenType.SPREAD) || this.matchValue('...')) {
1125
1176
  variadic = true
1126
1177
  }
@@ -1129,14 +1180,34 @@ class EtherParserPHP extends EtherParserBase {
1129
1180
  byReference = true
1130
1181
  }
1131
1182
 
1132
- const nameToken = this.current()
1133
- if (!nameToken) return null
1183
+ const currentToken = this.current()
1184
+ if (!currentToken) return null
1185
+
1186
+ const nextToken = this.peek(1)
1134
1187
 
1135
- let name = this.safeStr(nameToken.value)
1136
- if (name.startsWith('$')) {
1137
- name = name.substring(1)
1188
+ if (nextToken && nextToken.type === TokenType.COLON) {
1189
+ name = this.safeStr(currentToken.value)
1190
+ if (name.startsWith('$')) name = name.substring(1)
1191
+ this.advance()
1192
+ this.advance()
1193
+ typeHint = this.parseTypeHint(lang)
1194
+ } else {
1195
+ const typeLower = this.normalizeAccents(String(currentToken.value).toLowerCase())
1196
+ if (this.phpTypes[typeLower] || /^[A-Z]/.test(currentToken.value)) {
1197
+ typeHint = this.parseTypeHint(lang)
1198
+ }
1199
+
1200
+ const nameToken = this.current()
1201
+ if (nameToken && nameToken.type === TokenType.IDENTIFIER) {
1202
+ name = this.safeStr(nameToken.value)
1203
+ if (name.startsWith('$')) name = name.substring(1)
1204
+ this.advance()
1205
+ }
1206
+ }
1207
+
1208
+ if (!name) {
1209
+ name = 'param'
1138
1210
  }
1139
- this.advance()
1140
1211
 
1141
1212
  if (this.match(TokenType.EQUALS)) {
1142
1213
  defaultValue = this.parseExpression(lang)
@@ -1252,9 +1323,10 @@ class EtherParserPHP extends EtherParserBase {
1252
1323
  condition = this.parseExpression(lang)
1253
1324
  this.match(TokenType.RPAREN)
1254
1325
  } else {
1255
- condition = this.parseExpression(lang)
1326
+ condition = this.parseConditionUntilColon(lang)
1256
1327
  }
1257
1328
 
1329
+ this.match(TokenType.COLON)
1258
1330
  this.skipNewlines()
1259
1331
  const consequent = this.parseBlock(lang)
1260
1332
 
@@ -1262,31 +1334,64 @@ class EtherParserPHP extends EtherParserBase {
1262
1334
  this.skipNewlines()
1263
1335
 
1264
1336
  const elseVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1265
- if (elseVal === 'sinon si' || elseVal === 'elseif') {
1266
- this.advance()
1267
- alternate = this.parseConditional(lang)
1268
- alternate.type = 'ElseIfStatement'
1269
- } else if (elseVal === 'sinon' || elseVal === 'else') {
1337
+ if (elseVal === 'sinon') {
1270
1338
  this.advance()
1271
1339
 
1272
1340
  const nextVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1273
1341
  if (nextVal === 'si' || nextVal === 'if') {
1274
1342
  alternate = this.parseConditional(lang)
1275
- alternate.type = 'ElseIfStatement'
1343
+ alternate.type = 'IfStatement'
1276
1344
  } else {
1345
+ this.match(TokenType.COLON)
1277
1346
  this.skipNewlines()
1278
1347
  alternate = this.parseBlock(lang)
1279
1348
  }
1349
+ } else if (elseVal === 'else') {
1350
+ this.advance()
1351
+ this.match(TokenType.COLON)
1352
+ this.skipNewlines()
1353
+ alternate = this.parseBlock(lang)
1280
1354
  }
1281
1355
 
1282
1356
  return {
1283
1357
  type: 'IfStatement',
1284
- condition: condition,
1358
+ test: condition,
1285
1359
  consequent: consequent,
1286
1360
  alternate: alternate
1287
1361
  }
1288
1362
  }
1289
1363
 
1364
+ parseConditionUntilColon(lang) {
1365
+ const startPos = this.pos
1366
+ let depth = 0
1367
+
1368
+ while (!this.isAtEnd()) {
1369
+ const token = this.current()
1370
+ if (!token) break
1371
+
1372
+ if (token.type === TokenType.LPAREN) depth++
1373
+ if (token.type === TokenType.RPAREN) depth--
1374
+
1375
+ if (depth === 0 && token.type === TokenType.COLON) {
1376
+ break
1377
+ }
1378
+ if (depth === 0 && token.type === TokenType.NEWLINE) {
1379
+ break
1380
+ }
1381
+
1382
+ this.advance()
1383
+ }
1384
+
1385
+ const endPos = this.pos
1386
+ this.pos = startPos
1387
+
1388
+ if (endPos > startPos) {
1389
+ return this.parseExpression(lang)
1390
+ }
1391
+
1392
+ return { type: 'Literal', value: true }
1393
+ }
1394
+
1290
1395
  parseLoop(lang) {
1291
1396
  const token = this.current()
1292
1397
  const value = this.normalizeAccents(String(token.value).toLowerCase())
@@ -1325,7 +1430,7 @@ class EtherParserPHP extends EtherParserBase {
1325
1430
 
1326
1431
  parseFor(lang) {
1327
1432
  this.advance()
1328
-
1433
+
1329
1434
  let init = null
1330
1435
  let test = null
1331
1436
  let update = null
@@ -1345,8 +1450,23 @@ class EtherParserPHP extends EtherParserBase {
1345
1450
  update = this.parseExpression(lang)
1346
1451
  }
1347
1452
  this.match(TokenType.RPAREN)
1453
+ } else {
1454
+ if (!this.check(TokenType.SEMICOLON) && !this.check(TokenType.COLON)) {
1455
+ init = this.parseExpression(lang)
1456
+ }
1457
+ this.match(TokenType.SEMICOLON)
1458
+
1459
+ if (!this.check(TokenType.SEMICOLON) && !this.check(TokenType.COLON)) {
1460
+ test = this.parseExpression(lang)
1461
+ }
1462
+ this.match(TokenType.SEMICOLON)
1463
+
1464
+ if (!this.check(TokenType.COLON) && !this.check(TokenType.NEWLINE)) {
1465
+ update = this.parseExpression(lang)
1466
+ }
1348
1467
  }
1349
1468
 
1469
+ this.match(TokenType.COLON)
1350
1470
  this.skipNewlines()
1351
1471
  const body = this.parseBlock(lang)
1352
1472
 
@@ -1390,7 +1510,7 @@ class EtherParserPHP extends EtherParserBase {
1390
1510
  }
1391
1511
 
1392
1512
  const valueVar = this.current()
1393
- let value = this.safeStr(valueVar ? valueVar.value : 'value')
1513
+ value = this.safeStr(valueVar ? valueVar.value : 'value')
1394
1514
  if (value.startsWith('$')) value = value.substring(1)
1395
1515
  this.advance()
1396
1516
  } else {
@@ -1398,8 +1518,44 @@ class EtherParserPHP extends EtherParserBase {
1398
1518
  }
1399
1519
 
1400
1520
  this.match(TokenType.RPAREN)
1521
+ } else {
1522
+ const arrayToken = this.current()
1523
+ if (arrayToken) {
1524
+ array = { type: 'Identifier', name: this.safeStr(arrayToken.value) }
1525
+ this.advance()
1526
+ }
1527
+
1528
+ const asVal = this.current() ? this.normalizeAccents(String(this.current().value).toLowerCase()) : ''
1529
+ if (asVal === 'comme' || asVal === 'as') {
1530
+ this.advance()
1531
+ }
1532
+
1533
+ if (this.match(TokenType.AMPERSAND)) {
1534
+ byRef = true
1535
+ }
1536
+
1537
+ const firstVar = this.current()
1538
+ let firstName = this.safeStr(firstVar ? firstVar.value : 'item')
1539
+ if (firstName.startsWith('$')) firstName = firstName.substring(1)
1540
+ this.advance()
1541
+
1542
+ if (this.match(TokenType.DOUBLE_ARROW) || this.matchValue('=>')) {
1543
+ key = firstName
1544
+
1545
+ if (this.match(TokenType.AMPERSAND)) {
1546
+ byRef = true
1547
+ }
1548
+
1549
+ const valueVar = this.current()
1550
+ value = this.safeStr(valueVar ? valueVar.value : 'value')
1551
+ if (value.startsWith('$')) value = value.substring(1)
1552
+ this.advance()
1553
+ } else {
1554
+ value = firstName
1555
+ }
1401
1556
  }
1402
1557
 
1558
+ this.match(TokenType.COLON)
1403
1559
  this.skipNewlines()
1404
1560
  const body = this.parseBlock(lang)
1405
1561
 
@@ -1419,15 +1575,16 @@ class EtherParserPHP extends EtherParserBase {
1419
1575
  condition = this.parseExpression(lang)
1420
1576
  this.match(TokenType.RPAREN)
1421
1577
  } else {
1422
- condition = this.parseExpression(lang)
1578
+ condition = this.parseConditionUntilColon(lang)
1423
1579
  }
1424
1580
 
1581
+ this.match(TokenType.COLON)
1425
1582
  this.skipNewlines()
1426
1583
  const body = this.parseBlock(lang)
1427
1584
 
1428
1585
  return {
1429
1586
  type: 'WhileStatement',
1430
- condition: condition,
1587
+ test: condition,
1431
1588
  body: body
1432
1589
  }
1433
1590
  }