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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ether-code",
3
- "version": "0.7.8",
3
+ "version": "0.8.0",
4
4
  "description": "Ether - Le langage intentionnel",
5
5
  "main": "cli/compiler.js",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "generators/",
39
39
  "lexer/",
40
40
  "i18n/",
41
- "parsers/ether-parser.js",
41
+ "parsers/",
42
42
  "ether-compiler.js",
43
43
  "README.md",
44
44
  "LICENSE"
@@ -0,0 +1,361 @@
1
+ const fs = require('fs')
2
+ const { EtherLexer, Token, TokenType } = require('../lexer/ether-lexer')
3
+
4
+ class EtherParserBase {
5
+ constructor(options = {}) {
6
+ this.tokens = []
7
+ this.pos = 0
8
+ this.currentIndent = 0
9
+ this.i18n = {}
10
+ this.reverseMaps = {}
11
+ this.targetLang = options.targetLang || 'auto'
12
+ this.i18nDir = options.i18nDir || './i18n'
13
+
14
+ this.initCompoundTerms()
15
+ }
16
+
17
+ initCompoundTerms() {
18
+ this.compoundOperators = {
19
+ 'strictement egal': '===',
20
+ 'strictement égal': '===',
21
+ 'strictement different': '!==',
22
+ 'strictement différent': '!==',
23
+ 'superieur ou egal': '>=',
24
+ 'supérieur ou égal': '>=',
25
+ 'inferieur ou egal': '<=',
26
+ 'inférieur ou égal': '<=',
27
+ 'coalescence nulle': '??',
28
+ 'chainage optionnel': '?.',
29
+ 'chaînage optionnel': '?.'
30
+ }
31
+
32
+ this.compoundKeywords = {
33
+ 'sinon si': 'else if',
34
+ 'tant que': 'while',
35
+ 'pour chaque': 'forEach',
36
+ 'fonction asynchrone': 'async function',
37
+ 'fonction async': 'async function',
38
+ 'fonction generatrice': 'function*',
39
+ 'fonction génératrice': 'function*',
40
+ 'variable globale': 'var'
41
+ }
42
+ }
43
+
44
+ normalizeAccents(str) {
45
+ return str.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '')
46
+ }
47
+
48
+ loadI18n(files) {
49
+ for (const file of files) {
50
+ const lang = file.replace('i18n-', '').replace('.json', '')
51
+ const path = `${this.i18nDir}/${file}`
52
+
53
+ try {
54
+ if (fs.existsSync(path)) {
55
+ this.i18n[lang] = JSON.parse(fs.readFileSync(path, 'utf-8'))
56
+ this.buildReverseMap(lang)
57
+ }
58
+ } catch (e) {}
59
+ }
60
+ }
61
+
62
+ buildReverseMap(lang) {
63
+ this.reverseMaps[lang] = {}
64
+ const data = this.i18n[lang]
65
+
66
+ const processSection = (section, targetKey = lang) => {
67
+ if (!section || typeof section !== 'object') return
68
+
69
+ for (const [key, translations] of Object.entries(section)) {
70
+ if (translations && typeof translations === 'object') {
71
+ if (translations.fr) {
72
+ const frTerms = Array.isArray(translations.fr) ? translations.fr : [translations.fr]
73
+ const target = translations[targetKey] || translations.css || translations.html || translations.js || translations.value
74
+
75
+ for (const term of frTerms) {
76
+ if (typeof term === 'string') {
77
+ this.reverseMaps[lang][term.toLowerCase()] = target
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ for (const [sectionName, sectionData] of Object.entries(data)) {
86
+ processSection(sectionData)
87
+ }
88
+ }
89
+
90
+ peek(offset = 0) {
91
+ const idx = this.pos + offset
92
+ return idx < this.tokens.length ? this.tokens[idx] : null
93
+ }
94
+
95
+ current() {
96
+ return this.tokens[this.pos] || null
97
+ }
98
+
99
+ advance() {
100
+ const token = this.tokens[this.pos]
101
+ this.pos++
102
+ return token
103
+ }
104
+
105
+ expect(type) {
106
+ const token = this.current()
107
+ if (!token || token.type !== type) {
108
+ throw new Error(`Attendu ${type}, reçu ${token ? token.type : 'EOF'}`)
109
+ }
110
+ return this.advance()
111
+ }
112
+
113
+ match(type) {
114
+ if (this.current() && this.current().type === type) {
115
+ return this.advance()
116
+ }
117
+ return null
118
+ }
119
+
120
+ check(type) {
121
+ const token = this.current()
122
+ return token && token.type === type
123
+ }
124
+
125
+ matchValue(value) {
126
+ const token = this.current()
127
+ if (!token || token.value === null || token.value === undefined) return null
128
+ const tokenVal = String(token.value).toLowerCase()
129
+ const valueLower = value.toLowerCase()
130
+
131
+ if (tokenVal === valueLower) {
132
+ return this.advance()
133
+ }
134
+
135
+ if (valueLower.includes(' ')) {
136
+ const parts = valueLower.split(' ')
137
+ if (tokenVal === parts[0]) {
138
+ const savedPos = this.pos
139
+ const tokens = [this.advance()]
140
+ let matched = true
141
+
142
+ for (let i = 1; i < parts.length && matched; i++) {
143
+ const nextToken = this.current()
144
+ if (nextToken && String(nextToken.value).toLowerCase() === parts[i]) {
145
+ tokens.push(this.advance())
146
+ } else {
147
+ matched = false
148
+ }
149
+ }
150
+
151
+ if (matched) {
152
+ return tokens[tokens.length - 1]
153
+ } else {
154
+ this.pos = savedPos
155
+ }
156
+ }
157
+ }
158
+
159
+ return null
160
+ }
161
+
162
+ isAtEnd() {
163
+ return this.pos >= this.tokens.length || (this.current() && this.current().type === TokenType.EOF)
164
+ }
165
+
166
+ skipNewlines() {
167
+ while (this.match(TokenType.NEWLINE)) {}
168
+ }
169
+
170
+ skipWhitespace() {
171
+ while (this.match(TokenType.NEWLINE) || this.match(TokenType.INDENT) || this.match(TokenType.DEDENT)) {}
172
+ }
173
+
174
+ tryMatchCompoundOperator() {
175
+ const current = this.current()
176
+ if (!current || current.type !== TokenType.IDENTIFIER) return null
177
+
178
+ const val1 = this.normalizeAccents(String(current.value))
179
+ const next = this.peek(1)
180
+ const next2 = this.peek(2)
181
+
182
+ if (next && next.type === TokenType.IDENTIFIER) {
183
+ const val2 = this.normalizeAccents(String(next.value))
184
+
185
+ if (next2 && next2.type === TokenType.IDENTIFIER) {
186
+ const val3 = this.normalizeAccents(String(next2.value))
187
+ const compound3 = `${val1} ${val2} ${val3}`
188
+ if (this.compoundOperators[compound3]) {
189
+ this.advance()
190
+ this.advance()
191
+ this.advance()
192
+ return this.compoundOperators[compound3]
193
+ }
194
+ }
195
+
196
+ const compound2 = `${val1} ${val2}`
197
+ if (this.compoundOperators[compound2]) {
198
+ this.advance()
199
+ this.advance()
200
+ return this.compoundOperators[compound2]
201
+ }
202
+ }
203
+
204
+ return null
205
+ }
206
+
207
+ tryMatchCompoundKeyword() {
208
+ const current = this.current()
209
+ if (!current || current.type !== TokenType.IDENTIFIER) return null
210
+
211
+ const val1 = this.normalizeAccents(String(current.value))
212
+ const next = this.peek(1)
213
+
214
+ if (next && next.type === TokenType.IDENTIFIER) {
215
+ const val2 = this.normalizeAccents(String(next.value))
216
+ const compound2 = `${val1} ${val2}`
217
+ if (this.compoundKeywords[compound2]) {
218
+ this.advance()
219
+ this.advance()
220
+ return this.compoundKeywords[compound2]
221
+ }
222
+ }
223
+
224
+ return null
225
+ }
226
+
227
+ tokenize(source) {
228
+ const lexer = new EtherLexer(source)
229
+ this.tokens = lexer.tokenize()
230
+ this.pos = 0
231
+ return this.tokens
232
+ }
233
+
234
+ parseBlock(lang) {
235
+ const body = []
236
+
237
+ const hasBrace = this.match(TokenType.LBRACE)
238
+ this.skipNewlines()
239
+
240
+ if (hasBrace) {
241
+ this.match(TokenType.INDENT)
242
+ }
243
+
244
+ if (hasBrace || this.match(TokenType.INDENT)) {
245
+ while (!this.isAtEnd()) {
246
+ this.skipNewlines()
247
+
248
+ const current = this.current()
249
+
250
+ if (current && current.type === TokenType.DEDENT) {
251
+ this.advance()
252
+ if (hasBrace) {
253
+ this.skipNewlines()
254
+ this.match(TokenType.RBRACE)
255
+ }
256
+ break
257
+ }
258
+
259
+ if (hasBrace && current && current.type === TokenType.RBRACE) {
260
+ this.advance()
261
+ break
262
+ }
263
+
264
+ if (current && current.type === TokenType.COMMA) {
265
+ break
266
+ }
267
+
268
+ if (current && current.type === TokenType.RPAREN) {
269
+ break
270
+ }
271
+
272
+ if (current && (current.type === TokenType.INDENT || current.type === TokenType.NEWLINE)) {
273
+ this.advance()
274
+ continue
275
+ }
276
+
277
+ const statement = this.parseStatement(lang)
278
+ if (statement && statement.expression?.value !== null) {
279
+ body.push(statement)
280
+ } else if (statement && statement.type !== 'ExpressionStatement') {
281
+ body.push(statement)
282
+ }
283
+
284
+ this.skipNewlines()
285
+ }
286
+ }
287
+
288
+ return {
289
+ type: 'BlockStatement',
290
+ body: body
291
+ }
292
+ }
293
+
294
+ parseParameter(lang) {
295
+ const nameToken = this.current()
296
+ if (!nameToken || nameToken.type !== TokenType.IDENTIFIER) {
297
+ return null
298
+ }
299
+ const name = nameToken.value
300
+ this.advance()
301
+
302
+ let typeAnnotation = null
303
+ if (this.match(TokenType.COLON)) {
304
+ typeAnnotation = this.parseType(lang)
305
+ }
306
+
307
+ let defaultValue = null
308
+ if (this.match(TokenType.EQUALS)) {
309
+ defaultValue = this.parseExpression(lang)
310
+ }
311
+
312
+ return {
313
+ type: 'Parameter',
314
+ name: name,
315
+ typeAnnotation: typeAnnotation,
316
+ default: defaultValue
317
+ }
318
+ }
319
+
320
+ parseType(lang) {
321
+ const token = this.current()
322
+ if (!token || token.type !== TokenType.IDENTIFIER) {
323
+ return null
324
+ }
325
+
326
+ const typeName = token.value
327
+ this.advance()
328
+
329
+ let typeArgs = []
330
+ if (this.match(TokenType.LT)) {
331
+ while (!this.isAtEnd() && !this.match(TokenType.GT)) {
332
+ const arg = this.parseType(lang)
333
+ if (arg) typeArgs.push(arg)
334
+ this.match(TokenType.COMMA)
335
+ }
336
+ }
337
+
338
+ let isArray = false
339
+ if (this.match(TokenType.LBRACKET)) {
340
+ this.match(TokenType.RBRACKET)
341
+ isArray = true
342
+ }
343
+
344
+ return {
345
+ type: 'TypeAnnotation',
346
+ name: typeName,
347
+ typeArguments: typeArgs,
348
+ isArray: isArray
349
+ }
350
+ }
351
+
352
+ parseStatement(lang) {
353
+ throw new Error('parseStatement must be implemented by subclass')
354
+ }
355
+
356
+ parseExpression(lang) {
357
+ throw new Error('parseExpression must be implemented by subclass')
358
+ }
359
+ }
360
+
361
+ module.exports = { EtherParserBase, TokenType }