ether-code 0.7.8 → 0.7.9
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 +1 -1
- package/package.json +2 -2
- package/parsers/ether-parser-base.js +361 -0
- package/parsers/ether-parser-css.js +745 -0
- package/parsers/ether-parser-graphql.js +539 -0
- package/parsers/ether-parser-html.js +588 -0
- package/parsers/ether-parser-js.js +1262 -0
- package/parsers/ether-parser-sql.js +622 -0
package/cli/ether.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ether-code",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
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/
|
|
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 }
|