ether-code 0.9.4 → 0.9.6

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.9.4'
9
+ const VERSION = '0.9.6'
10
10
 
11
11
  const COLORS = {
12
12
  reset: '\x1b[0m',
@@ -134,7 +134,6 @@ class PHPGenerator {
134
134
  'formater nombre': 'number_format',
135
135
  'date': 'date',
136
136
  'temps': 'time',
137
- 'maintenant': 'time',
138
137
  'creer date': 'mktime',
139
138
  'date vers temps': 'strtotime',
140
139
  'formater date': 'date_format',
@@ -305,23 +304,60 @@ class PHPGenerator {
305
304
  return this.keywordMap[normalized] || word
306
305
  }
307
306
 
307
+ translateMethodName(word) {
308
+ if (!word) return word
309
+ const normalized = this.normalizeAccents(word)
310
+ const magicMethods = {
311
+ 'constructeur': '__construct',
312
+ 'destructeur': '__destruct',
313
+ 'obtenir': '__get',
314
+ 'definir': '__set',
315
+ 'appeler': '__call',
316
+ 'vers chaine': '__toString',
317
+ 'invoquer': '__invoke',
318
+ 'cloner': '__clone',
319
+ 'serialiser': '__serialize',
320
+ 'deserialiser': '__unserialize',
321
+ 'deboguer': '__debugInfo'
322
+ }
323
+ return magicMethods[normalized] || word
324
+ }
325
+
308
326
  extractTypeName(typeNode) {
309
327
  if (!typeNode) return ''
310
- if (typeof typeNode === 'string') return this.translateKeyword(typeNode)
328
+ if (typeof typeNode === 'string') {
329
+ const translated = this.translateKeyword(typeNode)
330
+ return this.prefixGlobalClass(translated)
331
+ }
311
332
  if (typeNode.type === 'TypeHint') {
312
- const types = (typeNode.types || []).map(t => this.translateKeyword(t))
333
+ const types = (typeNode.types || []).map(t => {
334
+ const translated = this.translateKeyword(t)
335
+ return this.prefixGlobalClass(translated)
336
+ })
313
337
  const result = types.join('|')
314
338
  return typeNode.nullable ? '?' + result : result
315
339
  }
316
- if (typeNode.name) return this.translateKeyword(typeNode.name)
317
- if (typeNode.type === 'Identifier') return this.translateKeyword(typeNode.name || typeNode.value || '')
318
- if (typeNode.value) return this.translateKeyword(typeNode.value)
340
+ if (typeNode.name) {
341
+ const translated = this.translateKeyword(typeNode.name)
342
+ return this.prefixGlobalClass(translated)
343
+ }
344
+ if (typeNode.type === 'Identifier') {
345
+ const translated = this.translateKeyword(typeNode.name || typeNode.value || '')
346
+ return this.prefixGlobalClass(translated)
347
+ }
348
+ if (typeNode.value) {
349
+ const translated = this.translateKeyword(typeNode.value)
350
+ return this.prefixGlobalClass(translated)
351
+ }
319
352
  return ''
320
353
  }
321
354
 
322
355
  generate(ast) {
323
356
  this.output = ''
324
357
  this.indent = 0
358
+ this.closureVariables = new Set()
359
+
360
+ this.collectClosureVariables(ast)
325
361
 
326
362
  if (ast.phpTag !== false) {
327
363
  this.output = '<?php\n'
@@ -346,6 +382,32 @@ class PHPGenerator {
346
382
  return this.output.trim()
347
383
  }
348
384
 
385
+ collectClosureVariables(ast) {
386
+ const nodes = Array.isArray(ast) ? ast : (ast?.body || [ast])
387
+
388
+ for (const node of nodes) {
389
+ if (!node) continue
390
+
391
+ if (node.type === 'VariableDeclaration') {
392
+ const init = node.init || node.value
393
+ if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression' || init.type === 'ClosureExpression')) {
394
+ const name = node.name || node.id?.name
395
+ if (name) this.closureVariables.add(name)
396
+ }
397
+ }
398
+
399
+ if (node.body) {
400
+ this.collectClosureVariables(node.body)
401
+ }
402
+ if (node.consequent) {
403
+ this.collectClosureVariables(Array.isArray(node.consequent) ? node.consequent : [node.consequent])
404
+ }
405
+ if (node.alternate) {
406
+ this.collectClosureVariables(Array.isArray(node.alternate) ? node.alternate : [node.alternate])
407
+ }
408
+ }
409
+ }
410
+
349
411
  generateNode(node) {
350
412
  if (!node) return ''
351
413
 
@@ -381,6 +443,8 @@ class PHPGenerator {
381
443
  return this.generateForStatement(node)
382
444
  case 'ForEachStatement':
383
445
  return this.generateForEachStatement(node)
446
+ case 'ForeachStatement':
447
+ return this.generateForEachStatement(node)
384
448
  case 'WhileStatement':
385
449
  return this.generateWhileStatement(node)
386
450
  case 'DoWhileStatement':
@@ -615,7 +679,7 @@ class PHPGenerator {
615
679
  generateClassMember(node) {
616
680
  if (!node) return
617
681
 
618
- if (node.type === 'UseTraitStatement') {
682
+ if (node.type === 'UseTraitStatement' || node.type === 'TraitUse') {
619
683
  this.writeLine(`use ${node.traits.join(', ')};`)
620
684
  return
621
685
  }
@@ -634,7 +698,7 @@ class PHPGenerator {
634
698
  this.writeLine(`${visibility} ${isStatic}${isReadonly}${typeHint}${name}${value};`)
635
699
  } else if (node.type === 'Method' || node.kind === 'method' || node.type === 'MethodDeclaration') {
636
700
  const name = node.name || node.key?.name || ''
637
- const translatedName = this.translateKeyword(name)
701
+ const translatedName = this.translateMethodName(name)
638
702
  const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
639
703
  let returnType = ''
640
704
 
@@ -688,7 +752,7 @@ class PHPGenerator {
688
752
 
689
753
  const visibility = 'public'
690
754
  const name = node.name || node.key?.name || ''
691
- const translatedName = this.translateKeyword(name)
755
+ const translatedName = this.translateMethodName(name)
692
756
  const params = (node.params || []).map(p => this.generateParam(p)).join(', ')
693
757
  let returnType = ''
694
758
 
@@ -871,21 +935,26 @@ class PHPGenerator {
871
935
  this.generateBody(node.block)
872
936
  this.indent--
873
937
 
874
- if (node.handler) {
875
- const exceptionType = node.handler.exceptionType || node.handler.type || 'Exception'
876
- const param = node.handler.param
877
- ? this.generateVariable(node.handler.param)
938
+ const catches = node.catches || (node.handler ? [node.handler] : [])
939
+ for (const catchClause of catches) {
940
+ const types = catchClause.types || []
941
+ let exceptionType = types.length > 0
942
+ ? types.map(t => this.prefixGlobalClass(t)).join('|')
943
+ : this.prefixGlobalClass(catchClause.exceptionType || catchClause.type || 'Exception')
944
+ const param = catchClause.param
945
+ ? this.generateVariable(catchClause.param)
878
946
  : '$e'
879
947
  this.writeLine(`} catch (${exceptionType} ${param}) {`)
880
948
  this.indent++
881
- this.generateBody(node.handler.body)
949
+ this.generateBody(catchClause.body)
882
950
  this.indent--
883
951
  }
884
952
 
885
- if (node.finalizer) {
953
+ const finallyBlock = node.finally || node.finalizer
954
+ if (finallyBlock) {
886
955
  this.writeLine('} finally {')
887
956
  this.indent++
888
- this.generateBody(node.finalizer)
957
+ this.generateBody(finallyBlock)
889
958
  this.indent--
890
959
  }
891
960
 
@@ -893,6 +962,31 @@ class PHPGenerator {
893
962
  return ''
894
963
  }
895
964
 
965
+ prefixGlobalClass(name) {
966
+ const phpGlobalClasses = [
967
+ 'Exception', 'Error', 'TypeError', 'ValueError', 'ArgumentCountError',
968
+ 'ArithmeticError', 'DivisionByZeroError', 'ParseError', 'AssertionError',
969
+ 'CompileError', 'UnhandledMatchError', 'FiberError',
970
+ 'ErrorException', 'BadFunctionCallException', 'BadMethodCallException',
971
+ 'DomainException', 'InvalidArgumentException', 'LengthException',
972
+ 'LogicException', 'OutOfBoundsException', 'OutOfRangeException',
973
+ 'OverflowException', 'RangeException', 'RuntimeException',
974
+ 'UnderflowException', 'UnexpectedValueException',
975
+ 'PDOException', 'JsonException', 'IntlException',
976
+ 'Generator', 'Iterator', 'IteratorAggregate', 'Traversable',
977
+ 'ArrayAccess', 'Countable', 'Serializable', 'Stringable',
978
+ 'DateTime', 'DateTimeImmutable', 'DateTimeInterface', 'DateInterval', 'DatePeriod',
979
+ 'PDO', 'PDOStatement', 'Closure', 'stdClass', 'WeakReference', 'WeakMap',
980
+ 'SplFileInfo', 'SplFileObject', 'SplTempFileObject',
981
+ 'ArrayObject', 'ArrayIterator', 'RecursiveArrayIterator',
982
+ 'Fiber', 'ReflectionClass', 'ReflectionMethod', 'ReflectionProperty'
983
+ ]
984
+ if (phpGlobalClasses.includes(name) && !name.startsWith('\\')) {
985
+ return '\\' + name
986
+ }
987
+ return name
988
+ }
989
+
896
990
  generateReturnStatement(node) {
897
991
  if (node.argument) {
898
992
  const arg = this.generateNode(node.argument)
@@ -932,19 +1026,82 @@ class PHPGenerator {
932
1026
 
933
1027
  generateCallExpression(node) {
934
1028
  let callee
1029
+ let originalName = ''
935
1030
 
936
1031
  if (typeof node.callee === 'string') {
937
- callee = this.translateFunction(node.callee)
1032
+ originalName = node.callee
1033
+ if (this.closureVariables && this.closureVariables.has(node.callee)) {
1034
+ callee = '$' + node.callee
1035
+ } else {
1036
+ callee = this.translateFunction(node.callee)
1037
+ }
938
1038
  } else if (node.callee.type === 'Identifier') {
939
- callee = this.translateFunction(node.callee.name)
1039
+ originalName = node.callee.name
1040
+ if (this.closureVariables && this.closureVariables.has(node.callee.name)) {
1041
+ callee = '$' + node.callee.name
1042
+ } else {
1043
+ callee = this.translateFunction(node.callee.name)
1044
+ }
1045
+ } else if (node.callee.type === 'Variable') {
1046
+ callee = this.generateVariable(node.callee)
940
1047
  } else if (node.callee.type === 'MemberExpression' || node.callee.type === 'StaticMemberExpression') {
941
1048
  callee = this.generateNode(node.callee)
942
1049
  } else {
943
1050
  callee = this.generateNode(node.callee)
944
1051
  }
945
1052
 
946
- const args = (node.arguments || []).map(a => this.generateNode(a)).join(', ')
947
- return `${callee}(${args})`
1053
+ let args = (node.arguments || []).map(a => this.generateNode(a))
1054
+
1055
+ args = this.reorderArguments(callee, args, originalName)
1056
+ args = this.addDefaultArguments(callee, args)
1057
+
1058
+ return `${callee}(${args.join(', ')})`
1059
+ }
1060
+
1061
+ reorderArguments(func, args, originalName) {
1062
+ const normalized = this.normalizeAccents(originalName)
1063
+
1064
+ if ((func === 'str_replace' || normalized === 'remplacer') && args.length === 3) {
1065
+ return [args[1], args[2], args[0]]
1066
+ }
1067
+
1068
+ if ((func === 'array_map' || normalized === 'mapper' || normalized === 'mapper tableau' || normalized === 'transformer') && args.length === 2) {
1069
+ return [args[1], args[0]]
1070
+ }
1071
+
1072
+ return args
1073
+ }
1074
+
1075
+ addDefaultArguments(func, args) {
1076
+ if (func === 'password_hash' && args.length === 1) {
1077
+ return [...args, 'PASSWORD_DEFAULT']
1078
+ }
1079
+
1080
+ return args
1081
+ }
1082
+
1083
+ isNativeFunction(name) {
1084
+ const natives = [
1085
+ 'echo', 'print', 'var_dump', 'print_r', 'die', 'exit',
1086
+ 'strlen', 'substr', 'strpos', 'str_replace', 'strtoupper', 'strtolower',
1087
+ 'trim', 'ltrim', 'rtrim', 'explode', 'implode', 'sprintf', 'printf',
1088
+ 'count', 'array_push', 'array_pop', 'array_shift', 'array_unshift',
1089
+ 'array_merge', 'array_filter', 'array_map', 'array_reduce', 'array_keys',
1090
+ 'array_values', 'array_sum', 'array_unique', 'array_reverse', 'sort',
1091
+ 'in_array', 'array_key_exists', 'array_search',
1092
+ 'is_array', 'is_string', 'is_int', 'is_float', 'is_bool', 'is_null',
1093
+ 'is_numeric', 'is_object', 'is_callable', 'isset', 'empty', 'gettype',
1094
+ 'intval', 'floatval', 'strval', 'boolval', 'settype',
1095
+ 'abs', 'ceil', 'floor', 'round', 'sqrt', 'pow', 'rand', 'max', 'min', 'pi',
1096
+ 'date', 'time', 'strtotime', 'mktime',
1097
+ 'file_exists', 'file_get_contents', 'file_put_contents', 'fopen', 'fclose', 'fread', 'fwrite',
1098
+ 'json_encode', 'json_decode', 'preg_match', 'preg_match_all', 'preg_replace',
1099
+ 'header', 'session_start', 'session_destroy', 'password_hash', 'password_verify',
1100
+ 'include', 'include_once', 'require', 'require_once',
1101
+ 'class_exists', 'function_exists', 'method_exists', 'defined', 'define', 'constant',
1102
+ 'new', 'throw'
1103
+ ]
1104
+ return natives.includes(name)
948
1105
  }
949
1106
 
950
1107
  generateMemberExpression(node) {
@@ -952,9 +1109,9 @@ class PHPGenerator {
952
1109
  let property
953
1110
 
954
1111
  if (typeof node.property === 'string') {
955
- property = node.property
1112
+ property = this.translateMemberProperty(node.property)
956
1113
  } else if (node.property.type === 'Identifier') {
957
- property = this.translateFunction(node.property.name)
1114
+ property = this.translateMemberProperty(node.property.name)
958
1115
  } else {
959
1116
  property = this.generateNode(node.property)
960
1117
  }
@@ -967,6 +1124,26 @@ class PHPGenerator {
967
1124
  return `${object}${operator}${property}`
968
1125
  }
969
1126
 
1127
+ translateMemberProperty(name) {
1128
+ const translations = {
1129
+ 'valeur': 'value',
1130
+ 'nom': 'name',
1131
+ 'message': 'message',
1132
+ 'code': 'code',
1133
+ 'fichier': 'file',
1134
+ 'ligne': 'line',
1135
+ 'precedent': 'previous',
1136
+ 'trace': 'trace',
1137
+ 'formater': 'format',
1138
+ 'ajouter': 'add',
1139
+ 'modifier': 'modify',
1140
+ 'obtenir': 'get',
1141
+ 'definir': 'set'
1142
+ }
1143
+ const normalized = this.normalizeAccents(name)
1144
+ return translations[normalized] || name
1145
+ }
1146
+
970
1147
  generateStaticMemberExpression(node) {
971
1148
  const cls = node.class?.name || this.generateNode(node.class)
972
1149
  let member = node.member?.name || this.generateNode(node.member)
@@ -1060,12 +1237,6 @@ class PHPGenerator {
1060
1237
  const name = node.name || node
1061
1238
  if (typeof name !== 'string') return '$var'
1062
1239
 
1063
- const normalized = this.normalizeAccents(name)
1064
-
1065
- if (this.functionMap[normalized]) {
1066
- return this.functionMap[normalized]
1067
- }
1068
-
1069
1240
  const translated = this.translateKeyword(name)
1070
1241
 
1071
1242
  if (translated.startsWith('$')) return translated
@@ -1073,11 +1244,11 @@ class PHPGenerator {
1073
1244
  return translated
1074
1245
  }
1075
1246
  if (/^[A-Z]/.test(translated)) return translated
1076
- if (translated === name && !name.startsWith('$')) {
1247
+ if (!name.startsWith('$')) {
1077
1248
  return '$' + name
1078
1249
  }
1079
1250
 
1080
- return translated.startsWith('$') ? translated : '$' + translated
1251
+ return name
1081
1252
  }
1082
1253
 
1083
1254
  generateVariable(node) {
@@ -1110,9 +1281,9 @@ class PHPGenerator {
1110
1281
  generateNewExpression(node) {
1111
1282
  let callee
1112
1283
  if (typeof node.callee === 'string') {
1113
- callee = node.callee
1284
+ callee = this.prefixGlobalClass(node.callee)
1114
1285
  } else if (node.callee.type === 'Identifier') {
1115
- callee = node.callee.name
1286
+ callee = this.prefixGlobalClass(node.callee.name)
1116
1287
  } else {
1117
1288
  callee = this.generateNode(node.callee)
1118
1289
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ether-code",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Ether - Le langage intentionnel",
5
5
  "main": "cli/compiler.js",
6
6
  "bin": {
@@ -159,7 +159,6 @@ class EtherParserPHP extends EtherParserBase {
159
159
  'json decoder': 'json_decode',
160
160
  'date': 'date',
161
161
  'temps': 'time',
162
- 'maintenant': 'time',
163
162
  'demarrer session': 'session_start',
164
163
  'detruire session': 'session_destroy',
165
164
  'definir cookie': 'setcookie',