ether-code 0.8.6 → 0.8.8

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.
@@ -31,6 +31,7 @@ const TokenType = {
31
31
  SPREAD: 'SPREAD',
32
32
  ARROW: 'ARROW',
33
33
  FAT_ARROW: 'FAT_ARROW',
34
+ SEMICOLON: 'SEMICOLON',
34
35
 
35
36
  PLUS: 'PLUS',
36
37
  MINUS: 'MINUS',
@@ -75,7 +76,6 @@ const TokenType = {
75
76
 
76
77
  COMMENT: 'COMMENT',
77
78
  BLOCK_COMMENT: 'BLOCK_COMMENT',
78
- SEMICOLON: 'SEMICOLON',
79
79
 
80
80
  EOF: 'EOF',
81
81
  ERROR: 'ERROR'
@@ -100,17 +100,20 @@ class EtherLexer {
100
100
  this.indentStack = [0]
101
101
  this.currentIndent = 0
102
102
  this.atLineStart = true
103
+ this.options = options
104
+ this.lastTokenType = null
103
105
 
104
- this.options = {
105
- trackComments: options.trackComments || false,
106
- allowUnicode: options.allowUnicode !== false,
107
- tabSize: options.tabSize || 2
108
- }
106
+ this.compoundKeywordsCache = null
107
+ }
108
+
109
+ isAtEnd() {
110
+ return this.pos >= this.source.length
109
111
  }
110
112
 
111
113
  peek(offset = 0) {
112
- const idx = this.pos + offset
113
- return idx < this.source.length ? this.source[idx] : null
114
+ const pos = this.pos + offset
115
+ if (pos >= this.source.length) return '\0'
116
+ return this.source[pos]
114
117
  }
115
118
 
116
119
  advance() {
@@ -119,7 +122,6 @@ class EtherLexer {
119
122
  if (char === '\n') {
120
123
  this.line++
121
124
  this.column = 1
122
- this.atLineStart = true
123
125
  } else {
124
126
  this.column++
125
127
  }
@@ -127,65 +129,21 @@ class EtherLexer {
127
129
  }
128
130
 
129
131
  match(expected) {
130
- if (this.peek() === expected) {
131
- this.advance()
132
- return true
133
- }
134
- return false
135
- }
136
-
137
- matchSequence(seq) {
138
- for (let i = 0; i < seq.length; i++) {
139
- if (this.peek(i) !== seq[i]) return false
140
- }
141
- for (let i = 0; i < seq.length; i++) {
142
- this.advance()
143
- }
132
+ if (this.isAtEnd()) return false
133
+ if (this.source[this.pos] !== expected) return false
134
+ this.advance()
144
135
  return true
145
136
  }
146
137
 
147
- isAtEnd() {
148
- return this.pos >= this.source.length
149
- }
150
-
151
- isDigit(char) {
152
- if (!char) return false
153
- return char >= '0' && char <= '9'
154
- }
155
-
156
- isHexDigit(char) {
157
- if (!char) return false
158
- return this.isDigit(char) || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')
159
- }
160
-
161
- isAlpha(char) {
162
- if (!char) return false
163
- const code = char.charCodeAt(0)
164
- if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) return true
165
- if (char === '_') return true
166
- if (this.options.allowUnicode) {
167
- if (code >= 0x00C0 && code <= 0x024F) return true
168
- if (code >= 0x0400 && code <= 0x04FF) return true
169
- if (code >= 0x4E00 && code <= 0x9FFF) return true
170
- if (code >= 0x3040 && code <= 0x30FF) return true
171
- if (code >= 0x0600 && code <= 0x06FF) return true
172
- if (code >= 0x0370 && code <= 0x03FF) return true
173
- }
174
- return false
175
- }
176
-
177
- isAlphaNumeric(char) {
178
- return this.isAlpha(char) || this.isDigit(char)
179
- }
180
-
181
- isWhitespace(char) {
182
- if (!char) return false
183
- return char === ' ' || char === '\t' || char === '\r'
184
- }
185
-
186
- addToken(type, value, startColumn = null) {
187
- const col = startColumn !== null ? startColumn : this.column
188
- this.tokens.push(new Token(type, value, this.line, col, this.currentIndent))
138
+ skipWhitespace() {
139
+ while (!this.isAtEnd()) {
140
+ const char = this.peek()
141
+ if (char === ' ' || char === '\t' || char === '\r') {
142
+ this.advance()
143
+ } else {
144
+ break
145
+ }
146
+ }
189
147
  }
190
148
 
191
149
  tokenize() {
@@ -195,7 +153,7 @@ class EtherLexer {
195
153
 
196
154
  while (this.indentStack.length > 1) {
197
155
  this.indentStack.pop()
198
- this.tokens.push(new Token(TokenType.DEDENT, '', this.line, this.column, 0))
156
+ this.tokens.push(new Token(TokenType.DEDENT, '', this.line, this.column, this.currentIndent))
199
157
  }
200
158
 
201
159
  this.tokens.push(new Token(TokenType.EOF, '', this.line, this.column, 0))
@@ -205,34 +163,22 @@ class EtherLexer {
205
163
  scanToken() {
206
164
  if (this.atLineStart) {
207
165
  this.handleIndentation()
208
- if (this.isAtEnd()) return
166
+ this.atLineStart = false
209
167
  }
210
168
 
211
- const char = this.peek()
169
+ this.skipWhitespace()
212
170
 
213
- if (char === '\n') {
214
- this.advance()
215
- this.addToken(TokenType.NEWLINE, '\n')
216
- return
217
- }
171
+ if (this.isAtEnd()) return
218
172
 
219
- if (this.isWhitespace(char)) {
220
- this.advance()
221
- return
222
- }
173
+ const char = this.peek()
223
174
 
224
- if (char === '#') {
225
- const next = this.peek(1)
226
- if (next && (this.isHexDigit(next) || this.isAlpha(next))) {
227
- this.scanHashToken()
228
- return
229
- }
230
- this.scanComment()
175
+ if (char === '\n') {
176
+ this.handleNewline()
231
177
  return
232
178
  }
233
179
 
234
180
  if (char === '/' && this.peek(1) === '/') {
235
- this.scanComment()
181
+ this.scanLineComment()
236
182
  return
237
183
  }
238
184
 
@@ -241,8 +187,13 @@ class EtherLexer {
241
187
  return
242
188
  }
243
189
 
190
+ if (char === '#') {
191
+ this.scanLineComment()
192
+ return
193
+ }
194
+
244
195
  if (char === '"' || char === "'") {
245
- this.scanString(char)
196
+ this.scanString()
246
197
  return
247
198
  }
248
199
 
@@ -251,24 +202,43 @@ class EtherLexer {
251
202
  return
252
203
  }
253
204
 
254
- if (this.isDigit(char) || (char === '-' && this.isDigit(this.peek(1)))) {
205
+ if (this.isDigit(char) || (char === '.' && this.isDigit(this.peek(1)))) {
255
206
  this.scanNumber()
256
207
  return
257
208
  }
258
209
 
259
- if (this.isAlpha(char)) {
210
+ if (this.isAlpha(char) || char === '_' || char === '$') {
260
211
  this.scanIdentifier()
261
212
  return
262
213
  }
263
214
 
264
- this.scanOperator()
215
+ if (this.isOperator(char)) {
216
+ this.scanOperator()
217
+ return
218
+ }
219
+
220
+ this.advance()
221
+ this.tokens.push(new Token(TokenType.ERROR, `Caractère inattendu: ${char}`, this.line, this.column - 1, this.currentIndent))
222
+ }
223
+
224
+ handleNewline() {
225
+ const startLine = this.line
226
+ const startCol = this.column
227
+ this.advance()
228
+
229
+ while (!this.isAtEnd() && this.peek() === '\n') {
230
+ this.advance()
231
+ }
232
+
233
+ this.tokens.push(new Token(TokenType.NEWLINE, '\n', startLine, startCol, this.currentIndent))
234
+ this.atLineStart = true
265
235
  }
266
236
 
267
237
  handleIndentation() {
268
238
  let indent = 0
269
239
  while (!this.isAtEnd() && (this.peek() === ' ' || this.peek() === '\t')) {
270
240
  if (this.peek() === '\t') {
271
- indent += this.options.tabSize
241
+ indent += 4
272
242
  } else {
273
243
  indent++
274
244
  }
@@ -276,368 +246,238 @@ class EtherLexer {
276
246
  }
277
247
 
278
248
  if (this.isAtEnd() || this.peek() === '\n') {
279
- this.atLineStart = false
280
249
  return
281
250
  }
282
-
283
- if (this.peek() === '#') {
284
- const next = this.peek(1)
285
- if (!next || (!this.isAlpha(next) && !this.isHexDigit(next))) {
286
- this.atLineStart = false
287
- return
288
- }
289
- }
290
251
 
291
252
  if (this.peek() === '/' && this.peek(1) === '/') {
292
- this.atLineStart = false
293
253
  return
294
254
  }
295
255
 
296
- this.currentIndent = indent
297
- this.atLineStart = false
256
+ if (this.peek() === '#') {
257
+ return
258
+ }
298
259
 
299
260
  const currentLevel = this.indentStack[this.indentStack.length - 1]
300
261
 
301
262
  if (indent > currentLevel) {
302
263
  this.indentStack.push(indent)
264
+ this.currentIndent = indent
303
265
  this.tokens.push(new Token(TokenType.INDENT, indent, this.line, 1, indent))
304
266
  } else if (indent < currentLevel) {
305
- while (this.indentStack.length > 1 && this.indentStack[this.indentStack.length - 1] > indent) {
267
+ while (this.indentStack.length > 1 && indent < this.indentStack[this.indentStack.length - 1]) {
306
268
  this.indentStack.pop()
307
- this.tokens.push(new Token(TokenType.DEDENT, '', this.line, 1, indent))
308
- }
309
- if (this.indentStack[this.indentStack.length - 1] !== indent && indent > 0) {
310
- this.indentStack.push(indent)
269
+ this.tokens.push(new Token(TokenType.DEDENT, '', this.line, 1, this.indentStack[this.indentStack.length - 1]))
311
270
  }
271
+ this.currentIndent = this.indentStack[this.indentStack.length - 1]
312
272
  }
313
273
  }
314
274
 
315
- scanComment() {
275
+ scanLineComment() {
316
276
  const startLine = this.line
317
277
  const startCol = this.column
318
- let comment = ''
319
-
278
+
320
279
  if (this.peek() === '#') {
321
280
  this.advance()
322
281
  } else {
323
282
  this.advance()
324
283
  this.advance()
325
284
  }
326
-
327
- while (!this.isAtEnd() && this.peek() !== '\n') {
328
- comment += this.advance()
329
- }
330
-
331
- if (this.options.trackComments) {
332
- this.tokens.push(new Token(TokenType.COMMENT, comment.trim(), startLine, startCol, this.currentIndent))
333
- }
334
- }
335
-
336
- scanHashToken() {
337
- const startLine = this.line
338
- const startCol = this.column
339
-
340
- this.advance()
341
285
 
342
286
  let value = ''
343
- let isHexColor = true
344
- let charCount = 0
345
-
346
- while (!this.isAtEnd()) {
347
- const char = this.peek()
348
- if (this.isHexDigit(char)) {
349
- value += this.advance()
350
- charCount++
351
- } else if (this.isAlpha(char) && !this.isHexDigit(char)) {
352
- isHexColor = false
353
- value += this.advance()
354
- } else if (char === '-' || char === '_') {
355
- isHexColor = false
356
- value += this.advance()
357
- } else {
358
- break
359
- }
287
+ while (!this.isAtEnd() && this.peek() !== '\n') {
288
+ value += this.advance()
360
289
  }
361
290
 
362
- if (isHexColor && (charCount === 3 || charCount === 4 || charCount === 6 || charCount === 8)) {
363
- this.tokens.push(new Token(TokenType.HEX, '#' + value, startLine, startCol, this.currentIndent))
364
- } else {
365
- this.tokens.push(new Token(TokenType.HASH, '#', startLine, startCol, this.currentIndent))
366
- if (value) {
367
- this.tokens.push(new Token(TokenType.IDENTIFIER, value, startLine, startCol + 1, this.currentIndent))
368
- }
369
- }
291
+ this.tokens.push(new Token(TokenType.COMMENT, value.trim(), startLine, startCol, this.currentIndent))
370
292
  }
371
293
 
372
294
  scanBlockComment() {
373
295
  const startLine = this.line
374
296
  const startCol = this.column
375
- let comment = ''
376
-
297
+
377
298
  this.advance()
378
299
  this.advance()
379
-
300
+
301
+ let value = ''
380
302
  while (!this.isAtEnd()) {
381
303
  if (this.peek() === '*' && this.peek(1) === '/') {
382
304
  this.advance()
383
305
  this.advance()
384
306
  break
385
307
  }
386
- comment += this.advance()
387
- }
388
-
389
- if (this.options.trackComments) {
390
- this.tokens.push(new Token(TokenType.BLOCK_COMMENT, comment.trim(), startLine, startCol, this.currentIndent))
308
+ value += this.advance()
391
309
  }
310
+
311
+ this.tokens.push(new Token(TokenType.BLOCK_COMMENT, value.trim(), startLine, startCol, this.currentIndent))
392
312
  }
393
313
 
394
- scanString(quote) {
314
+ scanString() {
395
315
  const startLine = this.line
396
316
  const startCol = this.column
397
- let value = ''
398
- let isBlock = false
399
-
400
- this.advance()
401
-
317
+ const quote = this.advance()
318
+
402
319
  if (this.peek() === quote && this.peek(1) === quote) {
403
320
  this.advance()
404
321
  this.advance()
405
- isBlock = true
322
+ return this.scanBlockString(quote, startLine, startCol)
406
323
  }
407
-
324
+
325
+ let value = ''
326
+ let escaped = false
327
+
408
328
  while (!this.isAtEnd()) {
409
- if (isBlock) {
410
- if (this.peek() === quote && this.peek(1) === quote && this.peek(2) === quote) {
411
- this.advance()
412
- this.advance()
413
- this.advance()
414
- break
415
- }
416
- } else {
417
- if (this.peek() === quote) {
418
- this.advance()
419
- break
420
- }
421
- if (this.peek() === '\n') {
422
- this.tokens.push(new Token(TokenType.ERROR, 'Chaîne non terminée', startLine, startCol, this.currentIndent))
423
- return
424
- }
425
- }
426
-
427
- if (this.peek() === '\\' && !isBlock) {
428
- this.advance()
429
- const escaped = this.advance()
430
- switch (escaped) {
329
+ const char = this.peek()
330
+
331
+ if (escaped) {
332
+ switch (char) {
431
333
  case 'n': value += '\n'; break
432
334
  case 't': value += '\t'; break
433
335
  case 'r': value += '\r'; break
434
336
  case '\\': value += '\\'; break
435
337
  case "'": value += "'"; break
436
338
  case '"': value += '"'; break
437
- case '0': value += '\0'; break
438
- case 'x':
439
- let hex = ''
440
- for (let i = 0; i < 2 && this.isHexDigit(this.peek()); i++) {
441
- hex += this.advance()
442
- }
443
- value += String.fromCharCode(parseInt(hex, 16))
444
- break
445
- case 'u':
446
- let unicode = ''
447
- if (this.peek() === '{') {
448
- this.advance()
449
- while (this.isHexDigit(this.peek())) {
450
- unicode += this.advance()
451
- }
452
- if (this.peek() === '}') this.advance()
453
- } else {
454
- for (let i = 0; i < 4 && this.isHexDigit(this.peek()); i++) {
455
- unicode += this.advance()
456
- }
457
- }
458
- value += String.fromCodePoint(parseInt(unicode, 16))
459
- break
460
- default:
461
- value += escaped
339
+ default: value += char
462
340
  }
341
+ escaped = false
342
+ this.advance()
343
+ } else if (char === '\\') {
344
+ escaped = true
345
+ this.advance()
346
+ } else if (char === quote) {
347
+ this.advance()
348
+ break
349
+ } else if (char === '\n') {
350
+ break
463
351
  } else {
464
352
  value += this.advance()
465
353
  }
466
354
  }
355
+
356
+ this.tokens.push(new Token(TokenType.STRING, value, startLine, startCol, this.currentIndent))
357
+ }
467
358
 
468
- this.tokens.push(new Token(
469
- isBlock ? TokenType.BLOCK_STRING : TokenType.STRING,
470
- value,
471
- startLine,
472
- startCol,
473
- this.currentIndent
474
- ))
359
+ scanBlockString(quote, startLine, startCol) {
360
+ let value = ''
361
+
362
+ while (!this.isAtEnd()) {
363
+ if (this.peek() === quote && this.peek(1) === quote && this.peek(2) === quote) {
364
+ this.advance()
365
+ this.advance()
366
+ this.advance()
367
+ break
368
+ }
369
+ value += this.advance()
370
+ }
371
+
372
+ this.tokens.push(new Token(TokenType.BLOCK_STRING, value, startLine, startCol, this.currentIndent))
475
373
  }
476
374
 
477
375
  scanTemplateString() {
478
376
  const startLine = this.line
479
377
  const startCol = this.column
480
- let value = ''
481
-
482
378
  this.advance()
483
-
379
+
380
+ let value = ''
381
+
484
382
  while (!this.isAtEnd() && this.peek() !== '`') {
485
- if (this.peek() === '\\') {
383
+ if (this.peek() === '\\' && this.peek(1) === '`') {
486
384
  this.advance()
487
385
  value += this.advance()
488
386
  } else {
489
387
  value += this.advance()
490
388
  }
491
389
  }
492
-
493
- if (this.peek() === '`') {
390
+
391
+ if (!this.isAtEnd()) {
494
392
  this.advance()
495
393
  }
496
-
497
- this.tokens.push(new Token(TokenType.BLOCK_STRING, value, startLine, startCol, this.currentIndent))
394
+
395
+ this.tokens.push(new Token(TokenType.STRING, value, startLine, startCol, this.currentIndent))
498
396
  }
499
397
 
500
398
  scanNumber() {
501
399
  const startLine = this.line
502
400
  const startCol = this.column
503
401
  let value = ''
504
- let type = TokenType.INTEGER
505
-
506
- if (this.peek() === '-') {
507
- value += this.advance()
508
- }
509
-
402
+ let tokenType = TokenType.INTEGER
403
+
510
404
  if (this.peek() === '0' && (this.peek(1) === 'x' || this.peek(1) === 'X')) {
511
405
  value += this.advance()
512
406
  value += this.advance()
513
- while (this.isHexDigit(this.peek())) {
407
+ while (!this.isAtEnd() && this.isHexDigit(this.peek())) {
514
408
  value += this.advance()
515
409
  }
516
410
  this.tokens.push(new Token(TokenType.HEX, value, startLine, startCol, this.currentIndent))
517
411
  return
518
412
  }
519
-
520
- if (this.peek() === '0' && (this.peek(1) === 'b' || this.peek(1) === 'B')) {
521
- this.advance()
522
- this.advance()
523
- let binValue = ''
524
- while (this.peek() === '0' || this.peek() === '1') {
525
- binValue += this.advance()
526
- }
527
- this.tokens.push(new Token(TokenType.INTEGER, parseInt(binValue, 2), startLine, startCol, this.currentIndent))
528
- return
529
- }
530
-
531
- if (this.peek() === '0' && (this.peek(1) === 'o' || this.peek(1) === 'O')) {
532
- this.advance()
533
- this.advance()
534
- let octValue = ''
535
- while (this.peek() >= '0' && this.peek() <= '7') {
536
- octValue += this.advance()
537
- }
538
- this.tokens.push(new Token(TokenType.INTEGER, parseInt(octValue, 8), startLine, startCol, this.currentIndent))
539
- return
540
- }
541
-
542
- while (this.isDigit(this.peek()) || this.peek() === '_') {
543
- if (this.peek() !== '_') {
544
- value += this.advance()
545
- } else {
546
- this.advance()
547
- }
413
+
414
+ while (!this.isAtEnd() && this.isDigit(this.peek())) {
415
+ value += this.advance()
548
416
  }
549
-
417
+
550
418
  if (this.peek() === '.' && this.isDigit(this.peek(1))) {
551
- type = TokenType.FLOAT
419
+ tokenType = TokenType.FLOAT
552
420
  value += this.advance()
553
- while (this.isDigit(this.peek()) || this.peek() === '_') {
554
- if (this.peek() !== '_') {
555
- value += this.advance()
556
- } else {
557
- this.advance()
558
- }
559
- }
560
- }
561
-
562
- if (this.peek() === 'e' || this.peek() === 'E') {
563
- const nextChar = this.peek(1)
564
- if (this.isDigit(nextChar) || nextChar === '+' || nextChar === '-') {
565
- type = TokenType.FLOAT
421
+ while (!this.isAtEnd() && this.isDigit(this.peek())) {
566
422
  value += this.advance()
567
- if (this.peek() === '+' || this.peek() === '-') {
568
- value += this.advance()
569
- }
570
- while (this.isDigit(this.peek())) {
571
- value += this.advance()
572
- }
573
423
  }
574
424
  }
575
-
576
- if (type === TokenType.FLOAT) {
577
- let numValue = String(parseFloat(value))
578
- const unit = this.scanCSSUnit()
579
- if (unit) numValue += unit
580
- this.tokens.push(new Token(type, numValue, startLine, startCol, this.currentIndent))
581
- } else {
582
- let numValue = String(parseInt(value, 10))
583
- const unit = this.scanCSSUnit()
584
- if (unit) numValue += unit
585
- this.tokens.push(new Token(type, numValue, startLine, startCol, this.currentIndent))
586
- }
587
- }
588
-
589
- scanCSSUnit() {
590
- const units = ['px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax', '%', 'fr', 's', 'ms', 'deg', 'rad', 'turn', 'ch', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', 'cqw', 'cqh']
591
425
 
592
- for (const unit of units) {
593
- let matches = true
594
- for (let i = 0; i < unit.length; i++) {
595
- const char = this.peek(i)
596
- if (!char || char.toLowerCase() !== unit[i].toLowerCase()) {
597
- matches = false
598
- break
599
- }
426
+ if (this.peek() === 'e' || this.peek() === 'E') {
427
+ tokenType = TokenType.FLOAT
428
+ value += this.advance()
429
+ if (this.peek() === '+' || this.peek() === '-') {
430
+ value += this.advance()
600
431
  }
601
- if (matches) {
602
- const afterUnit = this.peek(unit.length)
603
- if (!afterUnit || !this.isAlpha(afterUnit)) {
604
- let result = ''
605
- for (let i = 0; i < unit.length; i++) {
606
- result += this.advance()
607
- }
608
- return result
609
- }
432
+ while (!this.isAtEnd() && this.isDigit(this.peek())) {
433
+ value += this.advance()
610
434
  }
611
435
  }
612
- return ''
436
+
437
+ const numValue = tokenType === TokenType.INTEGER ? parseInt(value, 10) : parseFloat(value)
438
+ this.tokens.push(new Token(tokenType, numValue, startLine, startCol, this.currentIndent))
613
439
  }
614
440
 
615
441
  scanIdentifier() {
616
442
  const startLine = this.line
617
443
  const startCol = this.column
618
444
  let value = ''
619
-
620
- while (this.isAlphaNumeric(this.peek()) || this.peek() === '-' || this.peek() === '_') {
445
+
446
+ if (this.peek() === '$') {
447
+ value += this.advance()
448
+ }
449
+
450
+ while (!this.isAtEnd() && (this.isAlphaNumeric(this.peek()) || this.peek() === '_' || this.peek() === '-' && this.isAlpha(this.peek(1)))) {
621
451
  value += this.advance()
622
452
  }
623
453
 
624
454
  let keepLooking = true
625
- while (keepLooking && this.peek() === ' ') {
455
+ while (keepLooking && !this.isAtEnd()) {
626
456
  const savedPos = this.pos
627
457
  const savedLine = this.line
628
458
  const savedCol = this.column
629
459
 
630
- this.advance()
631
-
632
- let nextWord = ''
633
- while (this.isAlphaNumeric(this.peek()) || this.peek() === '-' || this.peek() === '_') {
634
- nextWord += this.advance()
635
- }
636
-
637
- if (nextWord) {
638
- const combined = value + ' ' + nextWord
639
- if (this.isCompoundKeyword(combined) || this.couldBeCompoundStart(combined)) {
640
- value = combined
460
+ if (this.peek() === ' ') {
461
+ let spaces = ''
462
+ while (this.peek() === ' ') {
463
+ spaces += this.advance()
464
+ }
465
+
466
+ if (this.isAlpha(this.peek()) || this.peek() === "'" || this.peek() === '-') {
467
+ let nextWord = ''
468
+ while (!this.isAtEnd() && (this.isAlphaNumeric(this.peek()) || this.peek() === '_' || this.peek() === "'" || this.peek() === '-' && this.isAlpha(this.peek(1)))) {
469
+ nextWord += this.advance()
470
+ }
471
+
472
+ const compound = value + ' ' + nextWord
473
+ if (this.isCompoundKeyword(compound) || this.couldBeCompoundStart(compound)) {
474
+ value = compound
475
+ } else {
476
+ this.pos = savedPos
477
+ this.line = savedLine
478
+ this.column = savedCol
479
+ keepLooking = false
480
+ }
641
481
  } else {
642
482
  this.pos = savedPos
643
483
  this.line = savedLine
@@ -664,12 +504,7 @@ class EtherLexer {
664
504
 
665
505
  couldBeCompoundStart(partial) {
666
506
  const normalized = this.normalizeAccents(partial)
667
- const compounds = [
668
- 'retour a la ligne', 'a la soumission', 'a la reinitialisation',
669
- 'inferieur ou egal', 'superieur ou egal',
670
- 'gauche a droite', 'droite a gauche',
671
- 'mettre a jour', 'case a cocher'
672
- ]
507
+ const compounds = this.getCompoundKeywords()
673
508
  return compounds.some(c => c.startsWith(normalized + ' '))
674
509
  }
675
510
 
@@ -685,9 +520,12 @@ class EtherLexer {
685
520
  return str.toLowerCase().split('').map(c => accentsMap[c] || c).join('')
686
521
  }
687
522
 
688
- isCompoundKeyword(phrase) {
689
- const normalized = this.normalizeAccents(phrase)
690
- const compounds = [
523
+ getCompoundKeywords() {
524
+ if (this.compoundKeywordsCache) {
525
+ return this.compoundKeywordsCache
526
+ }
527
+
528
+ this.compoundKeywordsCache = [
691
529
  'en ligne', 'hors ligne', 'au survol', 'au clic', 'au focus',
692
530
  'au double clic', 'au changement', 'a la soumission',
693
531
  'retour a la ligne', 'lecture seule', 'zone texte',
@@ -727,9 +565,173 @@ class EtherLexer {
727
565
  'couleur bordure', 'style bordure', 'largeur bordure',
728
566
  'espacement lettres', 'espacement mots',
729
567
  'espace blanc', 'debordement texte',
730
- 'flou fond', 'filtre fond'
568
+ 'flou fond', 'filtre fond',
569
+
570
+ 'obtenir type', 'definir type',
571
+ 'est tableau', 'est booleen', 'est entier', 'est chaine',
572
+ 'est nul', 'est flottant', 'est numerique', 'est objet',
573
+ 'est ressource', 'est scalaire', 'est iterable', 'est comptable',
574
+ 'est appelable', 'est vide',
575
+ 'valeur entier', 'valeur flottant', 'valeur chaine', 'valeur booleen',
576
+ 'afficher formate', 'formater chaine',
577
+ 'afficher variable', 'exporter variable',
578
+ 'imprimer lisible', 'deboguer zval',
579
+ 'somme tableau', 'filtrer tableau', 'mapper tableau',
580
+ 'fusionner tableaux', 'reduire tableau',
581
+ 'trier tableau', 'inverser tableau', 'melanger tableau',
582
+ 'premier tableau', 'dernier tableau',
583
+ 'cles tableau', 'valeurs tableau',
584
+ 'rechercher tableau', 'existe cle', 'existe tableau',
585
+ 'couper espaces', 'couper gauche', 'couper droite',
586
+ 'en majuscules', 'en minuscules',
587
+ 'premiere majuscule', 'majuscules mots',
588
+ 'longueur chaine', 'sous chaine',
589
+ 'position chaine', 'remplacer chaine',
590
+ 'exploser chaine', 'imploser tableau',
591
+ 'lire fichier', 'ecrire fichier',
592
+ 'fichier existe', 'supprimer fichier',
593
+ 'creer dossier', 'lire dossier', 'supprimer dossier',
594
+ 'encoder json', 'decoder json',
595
+ 'encoder base64', 'decoder base64',
596
+ 'encoder url', 'decoder url',
597
+ 'hacher md5', 'hacher sha1', 'hacher sha256',
598
+ 'hacher mot de passe', 'verifier mot de passe',
599
+ 'mot de passe',
600
+ 'demarrer session', 'detruire session',
601
+ 'sinon si',
602
+ 'nouvelle instance', 'nouveau datetime',
603
+ 'classe abstraite', 'classe finale',
604
+ 'methode abstraite', 'methode finale',
605
+ 'propriete privee', 'propriete protegee', 'propriete publique',
606
+ 'lecture seule',
607
+
608
+ 'creer base de donnees', 'creer table', 'creer vue',
609
+ 'creer index', 'creer index unique',
610
+ 'creer schema', 'creer sequence', 'creer type', 'creer domaine',
611
+ 'creer fonction', 'creer procedure', 'creer declencheur',
612
+ 'creer ou remplacer', 'creer role',
613
+ 'si n\'existe pas', 'si nexiste pas',
614
+ 'modifier table', 'modifier colonne', 'modifier base de donnees',
615
+ 'modifier index', 'modifier sequence', 'modifier vue',
616
+ 'ajouter colonne', 'supprimer colonne',
617
+ 'renommer en', 'renommer colonne', 'modifier type',
618
+ 'definir defaut', 'supprimer defaut',
619
+ 'supprimer base de donnees', 'supprimer table', 'supprimer vue',
620
+ 'supprimer index', 'supprimer schema', 'supprimer sequence',
621
+ 'supprimer fonction', 'supprimer procedure', 'supprimer declencheur',
622
+ 'supprimer role', 'supprimer contrainte',
623
+ 'cle primaire', 'cle etrangere', 'non nul', 'par defaut',
624
+ 'auto increment', 'unique contrainte',
625
+ 'verifier contrainte', 'references table',
626
+ 'sur suppression', 'sur mise a jour',
627
+ 'cascade suppression', 'cascade mise a jour',
628
+ 'definir null', 'sans action',
629
+ 'inserer dans', 'inserer valeurs',
630
+ 'mettre a jour', 'definir valeur',
631
+ 'supprimer de', 'supprimer ou',
632
+ 'selectionner depuis', 'selectionner tout',
633
+ 'selectionner distinct', 'selectionner comme',
634
+ 'depuis table', 'ou condition', 'et condition',
635
+ 'grouper par', 'ordonner par', 'limiter a',
636
+ 'decaler de', 'avoir condition',
637
+ 'jointure gauche', 'jointure droite',
638
+ 'jointure interne', 'jointure externe',
639
+ 'jointure croisee', 'jointure naturelle',
640
+ 'jointure complete',
641
+ 'union tout', 'sauf tout', 'intersection tout',
642
+ 'compter tout', 'somme de', 'moyenne de',
643
+ 'maximum de', 'minimum de',
644
+ 'comme motif', 'similaire a', 'correspond a regex',
645
+ 'correspond regex insensible',
646
+ 'ne correspond pas regex', 'ne correspond pas regex insensible',
647
+ 'est null', 'n\'est pas null', 'nest pas null',
648
+ 'est distinct de', 'n\'est pas distinct de', 'nest pas distinct de',
649
+ 'entre et', 'pas entre',
650
+ 'tout de', 'quelconque de', 'aucun de',
651
+ 'dans liste', 'pas dans liste',
652
+ 'existe sous requete', 'n\'existe pas sous requete',
653
+ 'debut transaction', 'valider transaction', 'annuler transaction',
654
+ 'point de sauvegarde', 'revenir a', 'liberer sauvegarde',
655
+ 'definir transaction', 'lecture seule', 'lecture ecriture',
656
+ 'niveau isolation', 'lecture non validee',
657
+ 'lecture validee', 'lecture repetable', 'serialisable',
658
+ 'verrouiller table', 'deverrouiller tables',
659
+ 'verrouiller ligne', 'pour mise a jour',
660
+ 'pour partage', 'sauter verrouille', 'sans attendre',
661
+ 'accorder privileges', 'revoquer privileges',
662
+ 'accorder tout', 'revoquer tout',
663
+ 'avec option grant', 'option admin',
664
+ 'creer utilisateur', 'supprimer utilisateur',
665
+ 'modifier utilisateur', 'definir mot de passe',
666
+ 'expliquer analyser', 'expliquer verbose',
667
+ 'commentaire sur', 'commentaire ligne', 'commentaire bloc',
668
+ 'tableau contient', 'contenu dans tableau',
669
+ 'chevauchement tableau', 'longueur tableau',
670
+ 'ajouter tableau', 'prefixer tableau',
671
+ 'concatener tableaux', 'supprimer tableau',
672
+ 'remplacer tableau', 'position tableau', 'positions tableau',
673
+ 'vers chaine tableau', 'depuis chaine tableau',
674
+ 'denombrer tableau',
675
+ 'remplacer regex', 'correspondance regex', 'correspondances regex',
676
+ 'diviser regex', 'tableau regex',
677
+ 'extraire json', 'extraire texte json',
678
+ 'contient json', 'existe json',
679
+ 'type json', 'tableau json', 'objet json',
680
+ 'cle json', 'valeur json',
681
+ 'ensemble json', 'supprimer cle json',
682
+ 'definir valeur json', 'inserer json',
683
+ 'concatener json', 'chaque json',
684
+ 'elements json', 'cles json',
685
+ 'vers json', 'depuis json',
686
+ 'maintenant timestamp', 'date courante', 'heure courante',
687
+ 'timestamp courant', 'extraire date',
688
+ 'partie date', 'tronquer date',
689
+ 'age entre', 'intervalle temps',
690
+ 'debut mois', 'fin mois',
691
+ 'ajouter intervalle', 'soustraire intervalle',
692
+ 'fuseau horaire', 'convertir fuseau',
693
+ 'generer serie', 'generer dates',
694
+ 'valeur suivante', 'valeur courante',
695
+ 'definir valeur sequence', 'redemarrer sequence',
696
+ 'fenetre sur', 'partitionner par',
697
+ 'lignes precedentes', 'lignes suivantes',
698
+ 'premiere valeur', 'derniere valeur',
699
+ 'nieme valeur', 'rang dense', 'numero ligne',
700
+ 'pourcentage rang', 'distribution cumulee',
701
+ 'ntile groupe', 'valeur precedente', 'valeur suivante fenetre',
702
+ 'somme cumulative', 'moyenne mobile',
703
+ 'table temporaire', 'table non journalisee',
704
+ 'sur conflit', 'ne rien faire', 'mettre a jour conflit',
705
+ 'retourner tout', 'retourner insere',
706
+ 'avec requete', 'requete recursive',
707
+ 'table virtuelle', 'table materialisee',
708
+ 'rafraichir vue', 'rafraichir concurrent',
709
+ 'activer declencheur', 'desactiver declencheur',
710
+ 'avant insertion', 'apres insertion',
711
+ 'avant mise a jour', 'apres mise a jour',
712
+ 'avant suppression', 'apres suppression',
713
+ 'pour chaque ligne', 'pour chaque instruction',
714
+ 'quand condition', 'executer procedure',
715
+ 'declarer variable', 'definir variable',
716
+ 'debut bloc', 'fin bloc',
717
+ 'si condition', 'sinon si condition', 'sinon condition',
718
+ 'boucle tant que', 'boucle pour', 'sortir boucle',
719
+ 'continuer boucle', 'retourner valeur',
720
+ 'lever exception', 'capturer exception',
721
+ 'bloc exception', 'quand autres',
722
+ 'afficher message', 'afficher avertissement', 'afficher erreur',
723
+ 'curseur pour', 'ouvrir curseur', 'fermer curseur',
724
+ 'recuperer suivant', 'recuperer precedent',
725
+ 'recuperer premier', 'recuperer dernier',
726
+ 'ligne trouvee', 'ligne non trouvee'
731
727
  ]
732
- return compounds.includes(normalized)
728
+
729
+ return this.compoundKeywordsCache
730
+ }
731
+
732
+ isCompoundKeyword(phrase) {
733
+ const normalized = this.normalizeAccents(phrase)
734
+ return this.getCompoundKeywords().includes(normalized)
733
735
  }
734
736
 
735
737
  scanOperator() {
@@ -801,19 +803,11 @@ class EtherLexer {
801
803
 
802
804
  case '-':
803
805
  if (this.match('-')) {
804
- if (this.isAlpha(this.peek()) || this.peek() === '-') {
805
- let varName = '--'
806
- while (this.isAlphaNumeric(this.peek()) || this.peek() === '-' || this.peek() === '_') {
807
- varName += this.advance()
808
- }
809
- this.tokens.push(new Token(TokenType.IDENTIFIER, varName, startLine, startCol, this.currentIndent))
810
- } else {
811
- this.tokens.push(new Token(TokenType.MINUS, '--', startLine, startCol, this.currentIndent))
812
- }
813
- } else if (this.match('>')) {
814
- this.tokens.push(new Token(TokenType.ARROW, '->', startLine, startCol, this.currentIndent))
806
+ this.tokens.push(new Token(TokenType.MINUS, '--', startLine, startCol, this.currentIndent))
815
807
  } else if (this.match('=')) {
816
808
  this.tokens.push(new Token(TokenType.MINUS, '-=', startLine, startCol, this.currentIndent))
809
+ } else if (this.match('>')) {
810
+ this.tokens.push(new Token(TokenType.ARROW, '->', startLine, startCol, this.currentIndent))
817
811
  } else {
818
812
  this.tokens.push(new Token(TokenType.MINUS, '-', startLine, startCol, this.currentIndent))
819
813
  }
@@ -834,10 +828,10 @@ class EtherLexer {
834
828
  break
835
829
 
836
830
  case '/':
837
- if (this.match('=')) {
838
- this.tokens.push(new Token(TokenType.SLASH, '/=', startLine, startCol, this.currentIndent))
839
- } else if (this.match('/')) {
831
+ if (this.match('/')) {
840
832
  this.tokens.push(new Token(TokenType.DOUBLE_SLASH, '//', startLine, startCol, this.currentIndent))
833
+ } else if (this.match('=')) {
834
+ this.tokens.push(new Token(TokenType.SLASH, '/=', startLine, startCol, this.currentIndent))
841
835
  } else {
842
836
  this.tokens.push(new Token(TokenType.SLASH, '/', startLine, startCol, this.currentIndent))
843
837
  }
@@ -859,7 +853,13 @@ class EtherLexer {
859
853
  this.tokens.push(new Token(TokenType.LTE, '<=', startLine, startCol, this.currentIndent))
860
854
  }
861
855
  } else if (this.match('<')) {
862
- this.tokens.push(new Token(TokenType.LT, '<<', startLine, startCol, this.currentIndent))
856
+ if (this.match('=')) {
857
+ this.tokens.push(new Token(TokenType.LT, '<<=', startLine, startCol, this.currentIndent))
858
+ } else {
859
+ this.tokens.push(new Token(TokenType.LT, '<<', startLine, startCol, this.currentIndent))
860
+ }
861
+ } else if (this.match('>')) {
862
+ this.tokens.push(new Token(TokenType.NOT_EQUALS, '<>', startLine, startCol, this.currentIndent))
863
863
  } else {
864
864
  this.tokens.push(new Token(TokenType.LT, '<', startLine, startCol, this.currentIndent))
865
865
  }
@@ -869,8 +869,8 @@ class EtherLexer {
869
869
  if (this.match('=')) {
870
870
  this.tokens.push(new Token(TokenType.GTE, '>=', startLine, startCol, this.currentIndent))
871
871
  } else if (this.match('>')) {
872
- if (this.match('>')) {
873
- this.tokens.push(new Token(TokenType.GT, '>>>', startLine, startCol, this.currentIndent))
872
+ if (this.match('=')) {
873
+ this.tokens.push(new Token(TokenType.GT, '>>=', startLine, startCol, this.currentIndent))
874
874
  } else {
875
875
  this.tokens.push(new Token(TokenType.GT, '>>', startLine, startCol, this.currentIndent))
876
876
  }
@@ -894,8 +894,6 @@ class EtherLexer {
894
894
  this.tokens.push(new Token(TokenType.DOUBLE_PIPE, '||', startLine, startCol, this.currentIndent))
895
895
  } else if (this.match('=')) {
896
896
  this.tokens.push(new Token(TokenType.PIPE, '|=', startLine, startCol, this.currentIndent))
897
- } else if (this.match('>')) {
898
- this.tokens.push(new Token(TokenType.PIPE, '|>', startLine, startCol, this.currentIndent))
899
897
  } else {
900
898
  this.tokens.push(new Token(TokenType.PIPE, '|', startLine, startCol, this.currentIndent))
901
899
  }
@@ -922,6 +920,8 @@ class EtherLexer {
922
920
  }
923
921
  } else if (this.match('.')) {
924
922
  this.tokens.push(new Token(TokenType.QUESTION, '?.', startLine, startCol, this.currentIndent))
923
+ } else if (this.match('-') && this.match('>')) {
924
+ this.tokens.push(new Token(TokenType.ARROW, '?->', startLine, startCol, this.currentIndent))
925
925
  } else {
926
926
  this.tokens.push(new Token(TokenType.QUESTION, '?', startLine, startCol, this.currentIndent))
927
927
  }
@@ -972,6 +972,30 @@ class EtherLexer {
972
972
  }
973
973
  }
974
974
 
975
+ isDigit(char) {
976
+ return char >= '0' && char <= '9'
977
+ }
978
+
979
+ isHexDigit(char) {
980
+ return this.isDigit(char) || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')
981
+ }
982
+
983
+ isAlpha(char) {
984
+ return (char >= 'a' && char <= 'z') ||
985
+ (char >= 'A' && char <= 'Z') ||
986
+ char === '_' ||
987
+ char === '$' ||
988
+ char.charCodeAt(0) > 127
989
+ }
990
+
991
+ isAlphaNumeric(char) {
992
+ return this.isAlpha(char) || this.isDigit(char)
993
+ }
994
+
995
+ isOperator(char) {
996
+ return ':=!,.<>+-*/%&|^~?@#\\()[]{};\n\r\t '.indexOf(char) !== -1
997
+ }
998
+
975
999
  static tokenize(source, options = {}) {
976
1000
  const lexer = new EtherLexer(source, options)
977
1001
  return lexer.tokenize()