ether-code 0.7.8 → 0.8.0

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.
@@ -0,0 +1,1262 @@
1
+ const { EtherParserBase, TokenType } = require('./ether-parser-base')
2
+
3
+ class EtherParserJS extends EtherParserBase {
4
+ constructor(options = {}) {
5
+ super(options)
6
+ this.initJSTranslations()
7
+ this.loadI18n(['i18n-js.json', 'i18n-ts.json', 'i18n-react.json', 'i18n-node.json', 'i18n-php.json'])
8
+ }
9
+
10
+ initJSTranslations() {
11
+ this.jsTranslations = {
12
+ 'variable': 'let',
13
+ 'constante': 'const',
14
+ 'fonction': 'function',
15
+ 'retourner': 'return',
16
+ 'si': 'if',
17
+ 'sinon': 'else',
18
+ 'pour': 'for',
19
+ 'de': 'of',
20
+ 'dans': 'in',
21
+ 'selon': 'switch',
22
+ 'cas': 'case',
23
+ 'defaut': 'default',
24
+ 'sortir': 'break',
25
+ 'continuer': 'continue',
26
+ 'essayer': 'try',
27
+ 'attraper': 'catch',
28
+ 'finalement': 'finally',
29
+ 'lancer': 'throw',
30
+ 'nouveau': 'new',
31
+ 'supprimer': 'delete',
32
+ 'vrai': 'true',
33
+ 'faux': 'false',
34
+ 'nul': 'null',
35
+ 'indefini': 'undefined',
36
+ 'ceci': 'this',
37
+ 'classe': 'class',
38
+ 'etendre': 'extends',
39
+ 'super': 'super',
40
+ 'constructeur': 'constructor',
41
+ 'statique': 'static',
42
+ 'obtenir': 'get',
43
+ 'definir': 'set',
44
+ 'asynchrone': 'async',
45
+ 'attendre': 'await',
46
+ 'importer': 'import',
47
+ 'exporter': 'export',
48
+ 'depuis': 'from',
49
+ 'comme': 'as',
50
+ 'addition': '+',
51
+ 'soustraction': '-',
52
+ 'multiplication': '*',
53
+ 'division': '/',
54
+ 'modulo': '%',
55
+ 'puissance': '**',
56
+ 'increment': '++',
57
+ 'decrement': '--',
58
+ 'egal': '==',
59
+ 'different': '!=',
60
+ 'superieur': '>',
61
+ 'inferieur': '<',
62
+ 'et': '&&',
63
+ 'ou': '||',
64
+ 'non': '!',
65
+ 'document': 'document',
66
+ 'fenetre': 'window',
67
+ 'console': 'console',
68
+ 'journal': 'console.log',
69
+ 'erreur': 'console.error',
70
+ 'avertir': 'console.warn',
71
+ 'info': 'console.info',
72
+ 'promesse': 'Promise',
73
+ 'resoudre': 'resolve',
74
+ 'rejeter': 'reject',
75
+ 'alors': 'then',
76
+ 'tout': 'all',
77
+ 'course': 'race',
78
+ 'json': 'JSON',
79
+ 'serialiser': 'stringify',
80
+ 'analyser': 'parse',
81
+ 'math': 'Math',
82
+ 'aleatoire': 'random',
83
+ 'arrondir': 'round',
84
+ 'plancher': 'floor',
85
+ 'plafond': 'ceil',
86
+ 'absolu': 'abs',
87
+ 'maximum': 'max',
88
+ 'minimum': 'min',
89
+ 'maintenant': 'now',
90
+ 'ensemble': 'Set',
91
+ 'symbole': 'Symbol',
92
+ 'objet': 'Object',
93
+ 'selecteur': 'querySelector',
94
+ 'sur': 'addEventListener',
95
+ 'longueur': 'length',
96
+ 'taille': 'size',
97
+ 'recuperer': 'fetch',
98
+ 'transformer': 'map',
99
+ 'filtrer': 'filter',
100
+ 'reduire': 'reduce',
101
+ 'trouver': 'find',
102
+ 'certains': 'some',
103
+ 'tous': 'every',
104
+ 'inclut': 'includes',
105
+ 'joindre': 'join',
106
+ 'inverser': 'reverse',
107
+ 'trier': 'sort',
108
+ 'decouper': 'slice',
109
+ 'concatener': 'concat',
110
+ 'ajouter': 'push',
111
+ 'retirer': 'pop',
112
+ 'diviser': 'split',
113
+ 'remplacer': 'replace',
114
+ 'rogner': 'trim',
115
+ 'repeter': 'repeat'
116
+ }
117
+ }
118
+
119
+ translateJSIdentifier(word) {
120
+ if (!word) return word
121
+ const lower = this.normalizeAccents(String(word))
122
+ return this.jsTranslations[lower] || word
123
+ }
124
+
125
+ parse(source, lang = 'js') {
126
+ this.tokenize(source)
127
+ return this.parseProgram(lang)
128
+ }
129
+
130
+ parseProgram(lang) {
131
+ const program = {
132
+ type: 'Program',
133
+ lang: lang,
134
+ body: []
135
+ }
136
+
137
+ this.skipNewlines()
138
+
139
+ while (!this.isAtEnd()) {
140
+ const statement = this.parseStatement(lang)
141
+ if (statement) {
142
+ program.body.push(statement)
143
+ }
144
+ this.skipNewlines()
145
+ }
146
+
147
+ return program
148
+ }
149
+
150
+ parseStatement(lang) {
151
+ this.skipNewlines()
152
+ const token = this.current()
153
+
154
+ if (!token || token.type === TokenType.EOF) return null
155
+
156
+ const value = token.value != null ? String(token.value).toLowerCase() : ''
157
+
158
+ if (this.isVariableDeclaration(value)) {
159
+ return this.parseVariableDeclaration(lang)
160
+ }
161
+
162
+ if (this.isFunctionDeclaration(value)) {
163
+ return this.parseFunctionDeclaration(lang)
164
+ }
165
+
166
+ if (this.isClassDeclaration(value)) {
167
+ return this.parseClassDeclaration(lang)
168
+ }
169
+
170
+ if (this.isConditional(value)) {
171
+ return this.parseConditional(lang)
172
+ }
173
+
174
+ if (this.isLoop(value)) {
175
+ return this.parseLoop(lang)
176
+ }
177
+
178
+ if (this.isTry(value)) {
179
+ return this.parseTryStatement(lang)
180
+ }
181
+
182
+ if (this.isThrow(value)) {
183
+ return this.parseThrowStatement(lang)
184
+ }
185
+
186
+ if (this.isReturn(value)) {
187
+ return this.parseReturn(lang)
188
+ }
189
+
190
+ if (this.isImport(value)) {
191
+ return this.parseImport(lang)
192
+ }
193
+
194
+ if (this.isExport(value)) {
195
+ return this.parseExport(lang)
196
+ }
197
+
198
+ const expr = this.parseExpression(lang)
199
+ if (expr) {
200
+ return {
201
+ type: 'ExpressionStatement',
202
+ expression: expr
203
+ }
204
+ }
205
+ return null
206
+ }
207
+
208
+ isVariableDeclaration(value) {
209
+ const keywords = ['variable', 'var', 'constante', 'const', 'soit', 'let', 'definir']
210
+ return keywords.includes(value)
211
+ }
212
+
213
+ isFunctionDeclaration(value) {
214
+ const keywords = ['fonction', 'func', 'fn', 'methode', 'def', 'procedure']
215
+ return keywords.includes(value)
216
+ }
217
+
218
+ isClassDeclaration(value) {
219
+ const keywords = ['classe', 'class', 'type', 'interface', 'struct', 'structure']
220
+ return keywords.includes(value)
221
+ }
222
+
223
+ isConditional(value) {
224
+ const keywords = ['si', 'if', 'sinon', 'else', 'sinon si', 'elif', 'selon', 'switch', 'cas', 'case']
225
+ return keywords.includes(value)
226
+ }
227
+
228
+ isLoop(value) {
229
+ const keywords = ['pour', 'for', 'while', 'faire', 'do', 'repeter', 'boucle', 'loop', 'chaque', 'each', 'foreach']
230
+ if (keywords.includes(value)) return true
231
+
232
+ if (value === 'tant') {
233
+ const next = this.peek(1)
234
+ if (next && this.normalizeAccents(String(next.value).toLowerCase()) === 'que') {
235
+ return true
236
+ }
237
+ }
238
+
239
+ if (value === 'pour') {
240
+ const next = this.peek(1)
241
+ if (next && this.normalizeAccents(String(next.value).toLowerCase()) === 'chaque') {
242
+ return true
243
+ }
244
+ }
245
+
246
+ return false
247
+ }
248
+
249
+ isTry(value) {
250
+ return ['essayer', 'try'].includes(value)
251
+ }
252
+
253
+ isThrow(value) {
254
+ return ['lancer', 'throw'].includes(value)
255
+ }
256
+
257
+ isReturn(value) {
258
+ const keywords = ['retourner', 'return', 'renvoyer', 'rendre']
259
+ return keywords.includes(value)
260
+ }
261
+
262
+ isImport(value) {
263
+ const keywords = ['importer', 'import', 'depuis', 'from', 'require', 'inclure', 'include', 'utiliser', 'use']
264
+ return keywords.includes(value)
265
+ }
266
+
267
+ isExport(value) {
268
+ const keywords = ['exporter', 'export', 'publier', 'public']
269
+ return keywords.includes(value)
270
+ }
271
+
272
+ parseVariableDeclaration(lang) {
273
+ const kindToken = this.advance()
274
+ const kindValue = kindToken.value.toLowerCase()
275
+
276
+ let kind = 'let'
277
+ if (kindValue === 'constante' || kindValue === 'const') {
278
+ kind = 'const'
279
+ } else if (kindValue === 'var' || kindValue === 'variable globale') {
280
+ kind = 'var'
281
+ }
282
+
283
+ const nameToken = this.current()
284
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) {
285
+ return null
286
+ }
287
+ const name = nameToken.value
288
+ this.advance()
289
+
290
+ let typeAnnotation = null
291
+ if (this.match(TokenType.COLON)) {
292
+ typeAnnotation = this.parseType(lang)
293
+ }
294
+
295
+ let init = null
296
+ if (this.match(TokenType.EQUALS)) {
297
+ init = this.parseExpression(lang)
298
+ }
299
+
300
+ return {
301
+ type: 'VariableDeclaration',
302
+ kind: kind,
303
+ name: name,
304
+ typeAnnotation: typeAnnotation,
305
+ init: init
306
+ }
307
+ }
308
+
309
+ parseFunctionDeclaration(lang) {
310
+ this.advance()
311
+
312
+ let isAsync = false
313
+ let isGenerator = false
314
+
315
+ const modifierToken = this.current()
316
+ if (modifierToken && modifierToken.type === TokenType.IDENTIFIER) {
317
+ const modVal = this.normalizeAccents(modifierToken.value.toLowerCase())
318
+ if (modVal === 'asynchrone' || modVal === 'async') {
319
+ isAsync = true
320
+ this.advance()
321
+ } else if (modVal === 'generatrice' || modVal === 'generator' || modVal === 'generateur') {
322
+ isGenerator = true
323
+ this.advance()
324
+ }
325
+ }
326
+
327
+ const nameToken = this.current()
328
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) {
329
+ return null
330
+ }
331
+ const name = nameToken.value
332
+ this.advance()
333
+
334
+ const params = []
335
+ if (this.match(TokenType.LPAREN)) {
336
+ while (!this.isAtEnd() && !this.match(TokenType.RPAREN)) {
337
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
338
+ if (this.check(TokenType.RPAREN)) break
339
+ const param = this.parseParameter(lang)
340
+ if (param) {
341
+ params.push(param)
342
+ }
343
+ this.match(TokenType.COMMA)
344
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
345
+ }
346
+ } else {
347
+ while (!this.isAtEnd()) {
348
+ const token = this.current()
349
+ if (!token || token.type === TokenType.NEWLINE || token.type === TokenType.COLON || token.type === TokenType.ARROW) {
350
+ break
351
+ }
352
+ if (token.type === TokenType.IDENTIFIER) {
353
+ const param = this.parseParameter(lang)
354
+ if (param) {
355
+ params.push(param)
356
+ }
357
+ } else {
358
+ break
359
+ }
360
+ this.match(TokenType.COMMA)
361
+ }
362
+ }
363
+
364
+ let returnType = null
365
+ if (this.match(TokenType.COLON) || this.match(TokenType.ARROW)) {
366
+ returnType = this.parseType(lang)
367
+ }
368
+
369
+ this.skipNewlines()
370
+ const body = this.parseBlock(lang)
371
+
372
+ return {
373
+ type: 'FunctionDeclaration',
374
+ name: name,
375
+ params: params,
376
+ returnType: returnType,
377
+ body: body,
378
+ async: isAsync,
379
+ generator: isGenerator
380
+ }
381
+ }
382
+
383
+ parseFunctionExpression(lang) {
384
+ this.advance()
385
+
386
+ let name = null
387
+ const nameToken = this.current()
388
+ if (nameToken && nameToken.type === TokenType.IDENTIFIER && !this.check(TokenType.LPAREN)) {
389
+ const nextToken = this.peek(1)
390
+ if (nextToken && nextToken.type === TokenType.LPAREN) {
391
+ name = nameToken.value
392
+ this.advance()
393
+ }
394
+ }
395
+
396
+ const params = []
397
+ if (this.match(TokenType.LPAREN)) {
398
+ while (!this.isAtEnd() && !this.match(TokenType.RPAREN)) {
399
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
400
+ if (this.check(TokenType.RPAREN)) break
401
+ const param = this.parseParameter(lang)
402
+ if (param) {
403
+ params.push(param)
404
+ }
405
+ this.match(TokenType.COMMA)
406
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
407
+ }
408
+ }
409
+
410
+ let returnType = null
411
+ if (this.match(TokenType.COLON) || this.match(TokenType.ARROW)) {
412
+ returnType = this.parseType(lang)
413
+ }
414
+
415
+ this.skipNewlines()
416
+ const body = this.parseBlock(lang)
417
+
418
+ return {
419
+ type: 'FunctionExpression',
420
+ name: name,
421
+ params: params,
422
+ returnType: returnType,
423
+ body: body
424
+ }
425
+ }
426
+
427
+ parseClassDeclaration(lang) {
428
+ this.advance()
429
+
430
+ const nameToken = this.current()
431
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) {
432
+ return null
433
+ }
434
+ const name = nameToken.value
435
+ this.advance()
436
+
437
+ let extends_ = null
438
+ if (this.matchValue('etend') || this.matchValue('herite') || this.matchValue('extends')) {
439
+ const parentToken = this.current()
440
+ if (parentToken) {
441
+ extends_ = parentToken.value
442
+ this.advance()
443
+ }
444
+ }
445
+
446
+ let implements_ = []
447
+ if (this.matchValue('implemente') || this.matchValue('implements')) {
448
+ while (!this.isAtEnd()) {
449
+ const implToken = this.current()
450
+ if (!implToken || implToken.type !== TokenType.IDENTIFIER) break
451
+ implements_.push(implToken.value)
452
+ this.advance()
453
+ if (!this.match(TokenType.COMMA)) break
454
+ }
455
+ }
456
+
457
+ this.skipNewlines()
458
+ const body = this.parseClassBody(lang)
459
+
460
+ return {
461
+ type: 'ClassDeclaration',
462
+ name: name,
463
+ extends: extends_,
464
+ implements: implements_,
465
+ body: body
466
+ }
467
+ }
468
+
469
+ parseClassBody(lang) {
470
+ const methods = []
471
+
472
+ this.skipNewlines()
473
+
474
+ if (!this.match(TokenType.INDENT)) {
475
+ return { type: 'ClassBody', body: methods }
476
+ }
477
+
478
+ while (!this.isAtEnd()) {
479
+ this.skipNewlines()
480
+
481
+ const current = this.current()
482
+
483
+ if (current && current.type === TokenType.DEDENT) {
484
+ this.advance()
485
+ break
486
+ }
487
+
488
+ if (current && (current.type === TokenType.INDENT || current.type === TokenType.NEWLINE)) {
489
+ this.advance()
490
+ continue
491
+ }
492
+
493
+ const method = this.parseClassMember(lang)
494
+ if (method) {
495
+ methods.push(method)
496
+ }
497
+
498
+ this.skipNewlines()
499
+ }
500
+
501
+ return { type: 'ClassBody', body: methods }
502
+ }
503
+
504
+ parseClassMember(lang) {
505
+ let isStatic = false
506
+ let kind = 'method'
507
+
508
+ const current = this.current()
509
+ if (!current || current.type !== TokenType.IDENTIFIER) {
510
+ return null
511
+ }
512
+
513
+ const val = this.normalizeAccents(current.value.toLowerCase())
514
+
515
+ if (val === 'statique' || val === 'static') {
516
+ isStatic = true
517
+ this.advance()
518
+ }
519
+
520
+ const nameToken = this.current()
521
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) {
522
+ return null
523
+ }
524
+
525
+ let name = nameToken.value
526
+ const nameLower = this.normalizeAccents(name.toLowerCase())
527
+
528
+ if (nameLower === 'constructeur' || nameLower === 'constructor') {
529
+ name = 'constructor'
530
+ kind = 'constructor'
531
+ } else if (nameLower === 'obtenir' || nameLower === 'get') {
532
+ kind = 'get'
533
+ this.advance()
534
+ const actualName = this.current()
535
+ if (actualName && actualName.type === TokenType.IDENTIFIER) {
536
+ name = actualName.value
537
+ }
538
+ } else if (nameLower === 'definir' || nameLower === 'set') {
539
+ kind = 'set'
540
+ this.advance()
541
+ const actualName = this.current()
542
+ if (actualName && actualName.type === TokenType.IDENTIFIER) {
543
+ name = actualName.value
544
+ }
545
+ }
546
+
547
+ this.advance()
548
+
549
+ const params = []
550
+ if (this.match(TokenType.LPAREN)) {
551
+ while (!this.isAtEnd() && !this.match(TokenType.RPAREN)) {
552
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
553
+ if (this.check(TokenType.RPAREN)) break
554
+ const param = this.parseParameter(lang)
555
+ if (param) {
556
+ params.push(param)
557
+ }
558
+ this.match(TokenType.COMMA)
559
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
560
+ }
561
+ }
562
+
563
+ this.skipNewlines()
564
+ const body = this.parseBlock(lang)
565
+
566
+ return {
567
+ type: 'MethodDefinition',
568
+ key: { type: 'Identifier', name: name },
569
+ kind: kind,
570
+ static: isStatic,
571
+ params: params,
572
+ body: body
573
+ }
574
+ }
575
+
576
+ parseConditional(lang, isElseIf = false) {
577
+ if (!isElseIf) {
578
+ this.advance()
579
+ }
580
+ const condition = this.parseExpression(lang)
581
+
582
+ this.skipNewlines()
583
+ const consequent = this.parseBlock(lang)
584
+
585
+ let alternate = null
586
+ this.skipNewlines()
587
+
588
+ if (this.matchValue('sinon')) {
589
+ if (this.matchValue('si')) {
590
+ alternate = this.parseConditional(lang, true)
591
+ } else {
592
+ this.skipNewlines()
593
+ alternate = this.parseBlock(lang)
594
+ }
595
+ }
596
+
597
+ return {
598
+ type: 'IfStatement',
599
+ condition: condition,
600
+ consequent: consequent,
601
+ alternate: alternate
602
+ }
603
+ }
604
+
605
+ parseLoop(lang) {
606
+ const keyword = this.advance()
607
+ let keywordValue = keyword.value.toLowerCase()
608
+
609
+ if (keywordValue === 'tant') {
610
+ const next = this.current()
611
+ if (next && this.normalizeAccents(String(next.value).toLowerCase()) === 'que') {
612
+ this.advance()
613
+ keywordValue = 'tant que'
614
+ }
615
+ }
616
+
617
+ if (keywordValue === 'pour') {
618
+ const next = this.current()
619
+ if (next && this.normalizeAccents(String(next.value).toLowerCase()) === 'chaque') {
620
+ this.advance()
621
+ keywordValue = 'pour chaque'
622
+ }
623
+ }
624
+
625
+ if (keywordValue === 'pour' || keywordValue === 'for' || keywordValue === 'pour chaque' || keywordValue === 'chaque') {
626
+ return this.parseForLoop(lang)
627
+ }
628
+
629
+ if (keywordValue === 'tant que' || keywordValue === 'while') {
630
+ return this.parseWhileLoop(lang)
631
+ }
632
+
633
+ if (keywordValue === 'faire' || keywordValue === 'do') {
634
+ return this.parseDoWhileLoop(lang)
635
+ }
636
+
637
+ return this.parseWhileLoop(lang)
638
+ }
639
+
640
+ parseForLoop(lang) {
641
+ let variable = null
642
+ let iterable = null
643
+ let kind = 'of'
644
+
645
+ const varToken = this.current()
646
+ if (varToken && varToken.type === TokenType.IDENTIFIER) {
647
+ variable = varToken.value
648
+ this.advance()
649
+ }
650
+
651
+ if (this.matchValue('dans') || this.matchValue('in')) {
652
+ kind = 'in'
653
+ } else if (this.matchValue('de') || this.matchValue('of')) {
654
+ kind = 'of'
655
+ }
656
+
657
+ iterable = this.parseExpression(lang)
658
+
659
+ this.skipNewlines()
660
+ const body = this.parseBlock(lang)
661
+
662
+ return {
663
+ type: 'ForOfStatement',
664
+ left: { type: 'Identifier', name: variable },
665
+ right: iterable,
666
+ body: body,
667
+ kind: kind
668
+ }
669
+ }
670
+
671
+ parseWhileLoop(lang) {
672
+ const condition = this.parseExpression(lang)
673
+
674
+ this.skipNewlines()
675
+ const body = this.parseBlock(lang)
676
+
677
+ return {
678
+ type: 'WhileStatement',
679
+ condition: condition,
680
+ body: body
681
+ }
682
+ }
683
+
684
+ parseDoWhileLoop(lang) {
685
+ this.skipNewlines()
686
+ const body = this.parseBlock(lang)
687
+
688
+ this.skipNewlines()
689
+ if (this.matchValue('tant que') || this.matchValue('while')) {
690
+ }
691
+
692
+ const condition = this.parseExpression(lang)
693
+
694
+ return {
695
+ type: 'DoWhileStatement',
696
+ body: body,
697
+ condition: condition
698
+ }
699
+ }
700
+
701
+ parseTryStatement(lang) {
702
+ this.advance()
703
+ this.skipNewlines()
704
+
705
+ const block = this.parseBlock(lang)
706
+
707
+ let handler = null
708
+ this.skipNewlines()
709
+
710
+ const current = this.current()
711
+ if (current && current.type === TokenType.IDENTIFIER) {
712
+ const val = this.normalizeAccents(current.value.toLowerCase())
713
+ if (val === 'attraper' || val === 'catch') {
714
+ this.advance()
715
+
716
+ let param = null
717
+ const paramToken = this.current()
718
+ if (paramToken && paramToken.type === TokenType.IDENTIFIER) {
719
+ param = { type: 'Identifier', name: paramToken.value }
720
+ this.advance()
721
+ }
722
+
723
+ this.skipNewlines()
724
+ const catchBlock = this.parseBlock(lang)
725
+
726
+ handler = {
727
+ type: 'CatchClause',
728
+ param: param,
729
+ body: catchBlock
730
+ }
731
+ }
732
+ }
733
+
734
+ let finalizer = null
735
+ this.skipNewlines()
736
+
737
+ const finallyToken = this.current()
738
+ if (finallyToken && finallyToken.type === TokenType.IDENTIFIER) {
739
+ const val = this.normalizeAccents(finallyToken.value.toLowerCase())
740
+ if (val === 'finalement' || val === 'finally') {
741
+ this.advance()
742
+ this.skipNewlines()
743
+ finalizer = this.parseBlock(lang)
744
+ }
745
+ }
746
+
747
+ return {
748
+ type: 'TryStatement',
749
+ block: block,
750
+ handler: handler,
751
+ finalizer: finalizer
752
+ }
753
+ }
754
+
755
+ parseThrowStatement(lang) {
756
+ this.advance()
757
+ const argument = this.parseExpression(lang)
758
+ return {
759
+ type: 'ThrowStatement',
760
+ argument: argument
761
+ }
762
+ }
763
+
764
+ parseReturn(lang) {
765
+ this.advance()
766
+
767
+ const current = this.current()
768
+ if (!current || current.type === TokenType.NEWLINE || current.type === TokenType.DEDENT) {
769
+ return { type: 'ReturnStatement', value: null }
770
+ }
771
+
772
+ const value = this.parseExpression(lang)
773
+ return {
774
+ type: 'ReturnStatement',
775
+ value: value
776
+ }
777
+ }
778
+
779
+ parseImport(lang) {
780
+ this.advance()
781
+
782
+ const imports = []
783
+ let defaultImport = null
784
+ let namespaceImport = null
785
+ let source = null
786
+
787
+ const first = this.current()
788
+ if (first && first.type === TokenType.IDENTIFIER) {
789
+ const val = first.value.toLowerCase()
790
+
791
+ if (val !== 'depuis' && val !== 'from' && val !== 'de') {
792
+ if (this.peek(1) && this.peek(1).type === TokenType.COMMA) {
793
+ defaultImport = first.value
794
+ this.advance()
795
+ this.match(TokenType.COMMA)
796
+ } else if (this.peek(1) && (this.peek(1).value?.toLowerCase() === 'depuis' || this.peek(1).value?.toLowerCase() === 'from')) {
797
+ defaultImport = first.value
798
+ this.advance()
799
+ } else {
800
+ imports.push({ type: 'ImportSpecifier', imported: first.value, local: first.value })
801
+ this.advance()
802
+
803
+ while (this.match(TokenType.COMMA)) {
804
+ const next = this.current()
805
+ if (next && next.type === TokenType.IDENTIFIER) {
806
+ imports.push({ type: 'ImportSpecifier', imported: next.value, local: next.value })
807
+ this.advance()
808
+ }
809
+ }
810
+ }
811
+ }
812
+ }
813
+
814
+ if (this.match(TokenType.LBRACE)) {
815
+ while (!this.isAtEnd() && !this.match(TokenType.RBRACE)) {
816
+ const specToken = this.current()
817
+ if (specToken && specToken.type === TokenType.IDENTIFIER) {
818
+ let imported = specToken.value
819
+ let local = imported
820
+ this.advance()
821
+
822
+ if (this.matchValue('comme') || this.matchValue('as')) {
823
+ const aliasToken = this.current()
824
+ if (aliasToken && aliasToken.type === TokenType.IDENTIFIER) {
825
+ local = aliasToken.value
826
+ this.advance()
827
+ }
828
+ }
829
+
830
+ imports.push({ type: 'ImportSpecifier', imported, local })
831
+ }
832
+ this.match(TokenType.COMMA)
833
+ }
834
+ }
835
+
836
+ if (this.matchValue('depuis') || this.matchValue('de') || this.matchValue('from')) {
837
+ const sourceToken = this.current()
838
+ if (sourceToken && sourceToken.type === TokenType.STRING) {
839
+ source = sourceToken.value.replace(/^["']|["']$/g, '')
840
+ this.advance()
841
+ }
842
+ }
843
+
844
+ return {
845
+ type: 'ImportDeclaration',
846
+ specifiers: imports,
847
+ defaultImport: defaultImport,
848
+ namespaceImport: namespaceImport,
849
+ source: source
850
+ }
851
+ }
852
+
853
+ parseExport(lang) {
854
+ this.advance()
855
+
856
+ let isDefault = false
857
+ if (this.matchValue('defaut') || this.matchValue('default')) {
858
+ isDefault = true
859
+ }
860
+
861
+ const current = this.current()
862
+ if (!current) return null
863
+
864
+ const value = current.value?.toLowerCase() || ''
865
+
866
+ if (this.isFunctionDeclaration(value)) {
867
+ const declaration = this.parseFunctionDeclaration(lang)
868
+ return {
869
+ type: 'ExportDeclaration',
870
+ default: isDefault,
871
+ declaration: declaration
872
+ }
873
+ }
874
+
875
+ if (this.isClassDeclaration(value)) {
876
+ const declaration = this.parseClassDeclaration(lang)
877
+ return {
878
+ type: 'ExportDeclaration',
879
+ default: isDefault,
880
+ declaration: declaration
881
+ }
882
+ }
883
+
884
+ if (this.isVariableDeclaration(value)) {
885
+ const declaration = this.parseVariableDeclaration(lang)
886
+ return {
887
+ type: 'ExportDeclaration',
888
+ default: isDefault,
889
+ declaration: declaration
890
+ }
891
+ }
892
+
893
+ const expr = this.parseExpression(lang)
894
+ return {
895
+ type: 'ExportDeclaration',
896
+ default: isDefault,
897
+ declaration: expr
898
+ }
899
+ }
900
+
901
+ parseExpression(lang) {
902
+ return this.parseAssignment(lang)
903
+ }
904
+
905
+ parseAssignment(lang) {
906
+ const left = this.parseLogicalOr(lang)
907
+
908
+ if (this.match(TokenType.EQUALS)) {
909
+ const right = this.parseAssignment(lang)
910
+ return {
911
+ type: 'AssignmentExpression',
912
+ operator: '=',
913
+ left: left,
914
+ right: right
915
+ }
916
+ }
917
+
918
+ return left
919
+ }
920
+
921
+ parseLogicalOr(lang) {
922
+ let left = this.parseLogicalAnd(lang)
923
+
924
+ while (this.match(TokenType.DOUBLE_PIPE) || this.matchValue('ou')) {
925
+ const right = this.parseLogicalAnd(lang)
926
+ left = {
927
+ type: 'BinaryExpression',
928
+ operator: '||',
929
+ left: left,
930
+ right: right
931
+ }
932
+ }
933
+
934
+ return left
935
+ }
936
+
937
+ parseLogicalAnd(lang) {
938
+ let left = this.parseEquality(lang)
939
+
940
+ while (this.match(TokenType.DOUBLE_AMPERSAND) || this.matchValue('et')) {
941
+ const right = this.parseEquality(lang)
942
+ left = {
943
+ type: 'BinaryExpression',
944
+ operator: '&&',
945
+ left: left,
946
+ right: right
947
+ }
948
+ }
949
+
950
+ return left
951
+ }
952
+
953
+ parseEquality(lang) {
954
+ let left = this.parseComparison(lang)
955
+
956
+ while (true) {
957
+ if (this.matchValue('strictement egal') || this.matchValue('strictement égal')) {
958
+ const right = this.parseComparison(lang)
959
+ left = { type: 'BinaryExpression', operator: '===', left, right }
960
+ } else if (this.matchValue('strictement different') || this.matchValue('strictement différent')) {
961
+ const right = this.parseComparison(lang)
962
+ left = { type: 'BinaryExpression', operator: '!==', left, right }
963
+ } else if (this.match(TokenType.DOUBLE_EQUALS) || this.matchValue('egal') || this.matchValue('egale') || this.matchValue('égal')) {
964
+ const right = this.parseComparison(lang)
965
+ left = { type: 'BinaryExpression', operator: '===', left, right }
966
+ } else if (this.match(TokenType.NOT_EQUALS) || this.matchValue('different') || this.matchValue('différent')) {
967
+ const right = this.parseComparison(lang)
968
+ left = { type: 'BinaryExpression', operator: '!==', left, right }
969
+ } else {
970
+ break
971
+ }
972
+ }
973
+
974
+ return left
975
+ }
976
+
977
+ parseComparison(lang) {
978
+ let left = this.parseAdditive(lang)
979
+
980
+ while (true) {
981
+ if (this.matchValue('superieur ou egal') || this.matchValue('supérieur ou égal') || this.match(TokenType.GTE)) {
982
+ const right = this.parseAdditive(lang)
983
+ left = { type: 'BinaryExpression', operator: '>=', left, right }
984
+ } else if (this.matchValue('inferieur ou egal') || this.matchValue('inférieur ou égal') || this.match(TokenType.LTE)) {
985
+ const right = this.parseAdditive(lang)
986
+ left = { type: 'BinaryExpression', operator: '<=', left, right }
987
+ } else if (this.match(TokenType.LT) || this.matchValue('inferieur') || this.matchValue('inférieur')) {
988
+ const right = this.parseAdditive(lang)
989
+ left = { type: 'BinaryExpression', operator: '<', left, right }
990
+ } else if (this.match(TokenType.GT) || this.matchValue('superieur') || this.matchValue('supérieur')) {
991
+ const right = this.parseAdditive(lang)
992
+ left = { type: 'BinaryExpression', operator: '>', left, right }
993
+ } else if (this.matchValue('instance de') || this.matchValue('instanceof')) {
994
+ const right = this.parseAdditive(lang)
995
+ left = { type: 'BinaryExpression', operator: 'instanceof', left, right }
996
+ } else {
997
+ break
998
+ }
999
+ }
1000
+
1001
+ return left
1002
+ }
1003
+
1004
+ parseAdditive(lang) {
1005
+ let left = this.parseMultiplicative(lang)
1006
+
1007
+ while (true) {
1008
+ if (this.match(TokenType.PLUS) || this.matchValue('plus') || this.matchValue('addition')) {
1009
+ const right = this.parseMultiplicative(lang)
1010
+ left = { type: 'BinaryExpression', operator: '+', left, right }
1011
+ } else if (this.match(TokenType.MINUS) || this.matchValue('moins') || this.matchValue('soustraction')) {
1012
+ const right = this.parseMultiplicative(lang)
1013
+ left = { type: 'BinaryExpression', operator: '-', left, right }
1014
+ } else {
1015
+ break
1016
+ }
1017
+ }
1018
+
1019
+ return left
1020
+ }
1021
+
1022
+ parseMultiplicative(lang) {
1023
+ let left = this.parseUnary(lang)
1024
+
1025
+ while (true) {
1026
+ if (this.match(TokenType.STAR) || this.matchValue('fois') || this.matchValue('multiplie') || this.matchValue('multiplication')) {
1027
+ const right = this.parseUnary(lang)
1028
+ left = { type: 'BinaryExpression', operator: '*', left, right }
1029
+ } else if (this.match(TokenType.SLASH) || this.matchValue('divise') || this.matchValue('division')) {
1030
+ const right = this.parseUnary(lang)
1031
+ left = { type: 'BinaryExpression', operator: '/', left, right }
1032
+ } else if (this.match(TokenType.PERCENT) || this.matchValue('modulo')) {
1033
+ const right = this.parseUnary(lang)
1034
+ left = { type: 'BinaryExpression', operator: '%', left, right }
1035
+ } else if (this.match(TokenType.DOUBLE_STAR) || this.matchValue('puissance')) {
1036
+ const right = this.parseUnary(lang)
1037
+ left = { type: 'BinaryExpression', operator: '**', left, right }
1038
+ } else {
1039
+ break
1040
+ }
1041
+ }
1042
+
1043
+ return left
1044
+ }
1045
+
1046
+ parseUnary(lang) {
1047
+ if (this.match(TokenType.BANG) || this.matchValue('non') || this.matchValue('pas')) {
1048
+ const operand = this.parseUnary(lang)
1049
+ return { type: 'UnaryExpression', operator: '!', operand }
1050
+ }
1051
+
1052
+ if (this.match(TokenType.MINUS)) {
1053
+ const operand = this.parseUnary(lang)
1054
+ return { type: 'UnaryExpression', operator: '-', operand }
1055
+ }
1056
+
1057
+ if (this.matchValue('type de') || this.matchValue('typeof')) {
1058
+ const operand = this.parseUnary(lang)
1059
+ return { type: 'UnaryExpression', operator: 'typeof', operand }
1060
+ }
1061
+
1062
+ if (this.matchValue('attendre') || this.matchValue('await')) {
1063
+ const argument = this.parseUnary(lang)
1064
+ return { type: 'AwaitExpression', argument: argument }
1065
+ }
1066
+
1067
+ if (this.matchValue('ceder') || this.matchValue('yield')) {
1068
+ const argument = this.parseExpression(lang)
1069
+ return { type: 'YieldExpression', argument: argument, delegate: false }
1070
+ }
1071
+
1072
+ if (this.matchValue('nouveau') || this.matchValue('new') || this.matchValue('nouvelle')) {
1073
+ const callee = this.parseCall(lang)
1074
+ if (callee.type === 'CallExpression') {
1075
+ return { type: 'NewExpression', callee: callee.callee, arguments: callee.arguments }
1076
+ }
1077
+ return { type: 'NewExpression', callee, arguments: [] }
1078
+ }
1079
+
1080
+ return this.parseCall(lang)
1081
+ }
1082
+
1083
+ parseCall(lang) {
1084
+ let expr = this.parsePrimary(lang)
1085
+
1086
+ while (true) {
1087
+ if (this.match(TokenType.LPAREN)) {
1088
+ const args = []
1089
+ while (!this.isAtEnd() && !this.match(TokenType.RPAREN)) {
1090
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
1091
+ if (this.check(TokenType.RPAREN)) break
1092
+ args.push(this.parseExpression(lang))
1093
+ this.match(TokenType.COMMA)
1094
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
1095
+ }
1096
+ expr = { type: 'CallExpression', callee: expr, arguments: args }
1097
+ } else if (this.match(TokenType.DOT)) {
1098
+ const property = this.current()
1099
+ if (property && property.type === TokenType.IDENTIFIER) {
1100
+ this.advance()
1101
+ expr = { type: 'MemberExpression', object: expr, property: { type: 'Identifier', name: property.value } }
1102
+ }
1103
+ } else if (this.match(TokenType.LBRACKET)) {
1104
+ const index = this.parseExpression(lang)
1105
+ this.expect(TokenType.RBRACKET)
1106
+ expr = { type: 'IndexExpression', object: expr, index }
1107
+ } else {
1108
+ break
1109
+ }
1110
+ }
1111
+
1112
+ return expr
1113
+ }
1114
+
1115
+ parsePrimary(lang) {
1116
+ const token = this.current()
1117
+
1118
+ if (!token) {
1119
+ return { type: 'Literal', value: null }
1120
+ }
1121
+
1122
+ if (token.type === TokenType.NUMBER || token.type === TokenType.INTEGER || token.type === TokenType.FLOAT) {
1123
+ this.advance()
1124
+ return { type: 'Literal', value: parseFloat(token.value) }
1125
+ }
1126
+
1127
+ if (token.type === TokenType.STRING) {
1128
+ this.advance()
1129
+ return { type: 'Literal', value: token.value.replace(/^["']|["']$/g, '') }
1130
+ }
1131
+
1132
+ if (token.type === TokenType.IDENTIFIER) {
1133
+ const value = token.value.toLowerCase()
1134
+
1135
+ if (value === 'fonction' || value === 'function') {
1136
+ return this.parseFunctionExpression(lang)
1137
+ }
1138
+
1139
+ if (value === 'vrai' || value === 'true') {
1140
+ this.advance()
1141
+ return { type: 'Literal', value: true }
1142
+ }
1143
+
1144
+ if (value === 'faux' || value === 'false') {
1145
+ this.advance()
1146
+ return { type: 'Literal', value: false }
1147
+ }
1148
+
1149
+ if (value === 'nul' || value === 'null' || value === 'rien') {
1150
+ this.advance()
1151
+ return { type: 'Literal', value: null }
1152
+ }
1153
+
1154
+ if (value === 'indefini' || value === 'undefined') {
1155
+ this.advance()
1156
+ return { type: 'Literal', value: undefined }
1157
+ }
1158
+
1159
+ const translated = this.translateJSIdentifier(token.value)
1160
+ this.advance()
1161
+
1162
+ if (translated && translated.includes('.')) {
1163
+ const parts = translated.split('.')
1164
+ let expr = { type: 'Identifier', name: parts[0] }
1165
+ for (let i = 1; i < parts.length; i++) {
1166
+ expr = { type: 'MemberExpression', object: expr, property: { type: 'Identifier', name: parts[i] } }
1167
+ }
1168
+ return expr
1169
+ }
1170
+
1171
+ return { type: 'Identifier', name: translated || token.value }
1172
+ }
1173
+
1174
+ if (token.type === TokenType.LBRACKET) {
1175
+ return this.parseArrayLiteral(lang)
1176
+ }
1177
+
1178
+ if (token.type === TokenType.LBRACE) {
1179
+ return this.parseObjectLiteral(lang)
1180
+ }
1181
+
1182
+ if (token.type === TokenType.LPAREN) {
1183
+ this.advance()
1184
+ const expr = this.parseExpression(lang)
1185
+ this.expect(TokenType.RPAREN)
1186
+ expr.parenthesized = true
1187
+ return expr
1188
+ }
1189
+
1190
+ this.advance()
1191
+ return { type: 'Literal', value: null }
1192
+ }
1193
+
1194
+ parseArrayLiteral(lang) {
1195
+ this.expect(TokenType.LBRACKET)
1196
+ const elements = []
1197
+
1198
+ while (!this.isAtEnd() && !this.match(TokenType.RBRACKET)) {
1199
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
1200
+ if (this.check(TokenType.RBRACKET)) break
1201
+
1202
+ if (this.match(TokenType.SPREAD)) {
1203
+ const argument = this.parseExpression(lang)
1204
+ elements.push({ type: 'SpreadElement', argument })
1205
+ } else {
1206
+ elements.push(this.parseExpression(lang))
1207
+ }
1208
+ this.match(TokenType.COMMA)
1209
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
1210
+ }
1211
+
1212
+ return { type: 'ArrayExpression', elements }
1213
+ }
1214
+
1215
+ parseObjectLiteral(lang) {
1216
+ this.expect(TokenType.LBRACE)
1217
+ const properties = []
1218
+
1219
+ this.skipWhitespace()
1220
+
1221
+ while (!this.isAtEnd() && !this.check(TokenType.RBRACE)) {
1222
+ this.skipWhitespace()
1223
+
1224
+ if (this.match(TokenType.SPREAD)) {
1225
+ const argument = this.parseExpression(lang)
1226
+ properties.push({ type: 'SpreadElement', argument })
1227
+ this.match(TokenType.COMMA)
1228
+ this.skipWhitespace()
1229
+ continue
1230
+ }
1231
+
1232
+ const keyToken = this.current()
1233
+ if (!keyToken || keyToken.type === TokenType.RBRACE) break
1234
+
1235
+ let key
1236
+ if (keyToken.type === TokenType.STRING) {
1237
+ const keyValue = keyToken.value.replace(/^["']|["']$/g, '')
1238
+ key = { type: 'Literal', value: keyValue }
1239
+ this.advance()
1240
+ } else if (keyToken.type === TokenType.IDENTIFIER) {
1241
+ key = { type: 'Identifier', name: keyToken.value }
1242
+ this.advance()
1243
+ } else {
1244
+ break
1245
+ }
1246
+
1247
+ this.match(TokenType.COLON) || this.match(TokenType.EQUALS)
1248
+
1249
+ const value = this.parseExpression(lang)
1250
+ properties.push({ key, value })
1251
+
1252
+ this.match(TokenType.COMMA)
1253
+ this.skipWhitespace()
1254
+ }
1255
+
1256
+ this.match(TokenType.RBRACE)
1257
+
1258
+ return { type: 'ObjectExpression', properties }
1259
+ }
1260
+ }
1261
+
1262
+ module.exports = { EtherParserJS }