hispano-lang 1.1.7 → 2.1.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/dist/tokenizer.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  class Tokenizer {
7
7
  constructor() {
8
- this.source = '';
8
+ this.source = "";
9
9
  this.tokens = [];
10
10
  this.current = 0;
11
11
  this.startPos = 0;
@@ -27,8 +27,8 @@ class Tokenizer {
27
27
  }
28
28
 
29
29
  this.tokens.push({
30
- type: 'EOF',
31
- lexeme: '',
30
+ type: "EOF",
31
+ lexeme: "",
32
32
  literal: null,
33
33
  line: this.currentLine,
34
34
  });
@@ -43,8 +43,8 @@ class Tokenizer {
43
43
 
44
44
  // Check for comments before advancing
45
45
  if (
46
- this.source[this.current] === '/' &&
47
- this.source[this.current + 1] === '/'
46
+ this.source[this.current] === "/" &&
47
+ this.source[this.current + 1] === "/"
48
48
  ) {
49
49
  this.comment();
50
50
  return;
@@ -53,141 +53,144 @@ class Tokenizer {
53
53
  const char = this.advance();
54
54
 
55
55
  switch (char) {
56
- case ' ':
57
- case '\r':
58
- case '\t':
56
+ case " ":
57
+ case "\r":
58
+ case "\t":
59
59
  // Ignore whitespace
60
60
  break;
61
61
 
62
- case '\n':
62
+ case "\n":
63
63
  this.currentLine++;
64
64
  break;
65
65
 
66
- case '=':
67
- if (this.peek() === '=') {
66
+ case "=":
67
+ if (this.peek() === "=") {
68
68
  this.advance();
69
- this.addToken('EQUAL_EQUAL');
69
+ this.addToken("EQUAL_EQUAL");
70
+ } else if (this.peek() === ">") {
71
+ this.advance();
72
+ this.addToken("ARROW");
70
73
  } else {
71
- this.addToken('EQUAL');
74
+ this.addToken("EQUAL");
72
75
  }
73
76
  break;
74
77
 
75
- case '*':
76
- if (this.peek() === '=') {
78
+ case "*":
79
+ if (this.peek() === "=") {
77
80
  this.advance();
78
- this.addToken('STAR_EQUAL');
81
+ this.addToken("STAR_EQUAL");
79
82
  } else {
80
- this.addToken('STAR');
83
+ this.addToken("STAR");
81
84
  }
82
85
  break;
83
86
 
84
- case '/':
85
- if (this.peek() === '=') {
87
+ case "/":
88
+ if (this.peek() === "=") {
86
89
  this.advance();
87
- this.addToken('SLASH_EQUAL');
90
+ this.addToken("SLASH_EQUAL");
88
91
  } else {
89
- this.addToken('SLASH');
92
+ this.addToken("SLASH");
90
93
  }
91
94
  break;
92
95
 
93
- case '%':
94
- if (this.peek() === '=') {
96
+ case "%":
97
+ if (this.peek() === "=") {
95
98
  this.advance();
96
- this.addToken('PERCENT_EQUAL');
99
+ this.addToken("PERCENT_EQUAL");
97
100
  } else {
98
- this.addToken('PERCENT');
101
+ this.addToken("PERCENT");
99
102
  }
100
103
  break;
101
104
 
102
- case '>':
103
- if (this.peek() === '=') {
105
+ case ">":
106
+ if (this.peek() === "=") {
104
107
  this.advance();
105
- this.addToken('GREATER_EQUAL');
108
+ this.addToken("GREATER_EQUAL");
106
109
  } else {
107
- this.addToken('GREATER');
110
+ this.addToken("GREATER");
108
111
  }
109
112
  break;
110
113
 
111
- case '<':
112
- if (this.peek() === '=') {
114
+ case "<":
115
+ if (this.peek() === "=") {
113
116
  this.advance();
114
- this.addToken('LESS_EQUAL');
117
+ this.addToken("LESS_EQUAL");
115
118
  } else {
116
- this.addToken('LESS');
119
+ this.addToken("LESS");
117
120
  }
118
121
  break;
119
122
 
120
- case '!':
121
- if (this.peek() === '=') {
123
+ case "!":
124
+ if (this.peek() === "=") {
122
125
  this.advance();
123
- this.addToken('BANG_EQUAL');
126
+ this.addToken("BANG_EQUAL");
124
127
  } else {
125
- this.addToken('BANG');
128
+ this.addToken("BANG");
126
129
  }
127
130
  break;
128
131
 
129
- case '+':
130
- if (this.peek() === '+') {
132
+ case "+":
133
+ if (this.peek() === "+") {
131
134
  this.advance();
132
- this.addToken('PLUS_PLUS');
133
- } else if (this.peek() === '=') {
135
+ this.addToken("PLUS_PLUS");
136
+ } else if (this.peek() === "=") {
134
137
  this.advance();
135
- this.addToken('PLUS_EQUAL');
138
+ this.addToken("PLUS_EQUAL");
136
139
  } else {
137
- this.addToken('PLUS');
140
+ this.addToken("PLUS");
138
141
  }
139
142
  break;
140
143
 
141
- case '-':
142
- if (this.peek() === '-') {
144
+ case "-":
145
+ if (this.peek() === "-") {
143
146
  this.advance();
144
- this.addToken('MINUS_MINUS');
145
- } else if (this.peek() === '=') {
147
+ this.addToken("MINUS_MINUS");
148
+ } else if (this.peek() === "=") {
146
149
  this.advance();
147
- this.addToken('MINUS_EQUAL');
150
+ this.addToken("MINUS_EQUAL");
148
151
  } else {
149
- this.addToken('MINUS');
152
+ this.addToken("MINUS");
150
153
  }
151
154
  break;
152
155
 
153
- case '{':
154
- this.addToken('LEFT_BRACE');
156
+ case "{":
157
+ this.addToken("LEFT_BRACE");
155
158
  break;
156
159
 
157
- case '}':
158
- this.addToken('RIGHT_BRACE');
160
+ case "}":
161
+ this.addToken("RIGHT_BRACE");
159
162
  break;
160
163
 
161
- case '(':
162
- this.addToken('LEFT_PAREN');
164
+ case "(":
165
+ this.addToken("LEFT_PAREN");
163
166
  break;
164
167
 
165
- case ')':
166
- this.addToken('RIGHT_PAREN');
168
+ case ")":
169
+ this.addToken("RIGHT_PAREN");
167
170
  break;
168
171
 
169
- case ',':
170
- this.addToken('COMMA');
172
+ case ",":
173
+ this.addToken("COMMA");
171
174
  break;
172
175
 
173
- case ';':
174
- this.addToken('SEMICOLON');
176
+ case ";":
177
+ this.addToken("SEMICOLON");
175
178
  break;
176
179
 
177
- case ':':
178
- this.addToken('COLON');
180
+ case ":":
181
+ this.addToken("COLON");
179
182
  break;
180
183
 
181
- case '[':
182
- this.addToken('LEFT_BRACKET');
184
+ case "[":
185
+ this.addToken("LEFT_BRACKET");
183
186
  break;
184
187
 
185
- case ']':
186
- this.addToken('RIGHT_BRACKET');
188
+ case "]":
189
+ this.addToken("RIGHT_BRACKET");
187
190
  break;
188
191
 
189
- case '.':
190
- this.addToken('DOT');
192
+ case ".":
193
+ this.addToken("DOT");
191
194
  break;
192
195
 
193
196
  case '"':
@@ -198,11 +201,15 @@ class Tokenizer {
198
201
  this.string("'");
199
202
  break;
200
203
 
201
- case '/':
202
- if (this.peek() === '/') {
204
+ case "`":
205
+ this.templateString();
206
+ break;
207
+
208
+ case "/":
209
+ if (this.peek() === "/") {
203
210
  this.comment();
204
211
  } else {
205
- this.addToken('SLASH');
212
+ this.addToken("SLASH");
206
213
  }
207
214
  break;
208
215
 
@@ -213,7 +220,7 @@ class Tokenizer {
213
220
  this.identifier();
214
221
  } else {
215
222
  throw new Error(
216
- `Carácter inesperado: ${char} en la línea ${this.currentLine}`
223
+ `Carácter inesperado: ${char} en la línea ${this.currentLine}`,
217
224
  );
218
225
  }
219
226
  break;
@@ -226,12 +233,12 @@ class Tokenizer {
226
233
  */
227
234
  string(quoteType = '"') {
228
235
  while (this.peek() !== quoteType && !this.isAtEnd()) {
229
- if (this.peek() === '\n') this.currentLine++;
236
+ if (this.peek() === "\n") this.currentLine++;
230
237
  this.advance();
231
238
  }
232
239
 
233
240
  if (this.isAtEnd()) {
234
- throw new Error('Cadena no terminada');
241
+ throw new Error("Cadena no terminada");
235
242
  }
236
243
 
237
244
  // Consume the closing quote
@@ -239,7 +246,68 @@ class Tokenizer {
239
246
 
240
247
  // Extract the string value
241
248
  const value = this.source.substring(this.startPos + 1, this.current - 1);
242
- this.addToken('STRING', value);
249
+ this.addToken("STRING", value);
250
+ }
251
+
252
+ /**
253
+ * Processes a template string with interpolation (backticks)
254
+ * Supports ${expression} syntax for embedding expressions
255
+ */
256
+ templateString() {
257
+ const parts = []; // Literal string parts
258
+ const expressions = []; // Expression source strings
259
+ let currentPart = "";
260
+
261
+ while (!this.isAtEnd()) {
262
+ const char = this.peek();
263
+
264
+ if (char === "`") {
265
+ // End of template string
266
+ this.advance();
267
+ parts.push(currentPart);
268
+ this.addToken("TEMPLATE_STRING", { parts, expressions });
269
+ return;
270
+ }
271
+
272
+ if (char === "$" && this.peekNext() === "{") {
273
+ // Start of interpolation
274
+ parts.push(currentPart);
275
+ currentPart = "";
276
+ this.advance(); // consume $
277
+ this.advance(); // consume {
278
+
279
+ // Extract the expression
280
+ let braceCount = 1;
281
+ let expressionSource = "";
282
+
283
+ while (!this.isAtEnd() && braceCount > 0) {
284
+ const c = this.peek();
285
+ if (c === "{") {
286
+ braceCount++;
287
+ expressionSource += c;
288
+ this.advance();
289
+ } else if (c === "}") {
290
+ braceCount--;
291
+ if (braceCount > 0) {
292
+ expressionSource += c;
293
+ }
294
+ this.advance();
295
+ } else {
296
+ if (c === "\n") this.currentLine++;
297
+ expressionSource += c;
298
+ this.advance();
299
+ }
300
+ }
301
+
302
+ expressions.push(expressionSource);
303
+ } else {
304
+ if (char === "\n") this.currentLine++;
305
+ currentPart += char;
306
+ this.advance();
307
+ }
308
+ }
309
+
310
+ throw new Error("Cadena de plantilla no terminada");
243
311
  }
244
312
 
245
313
  /**
@@ -250,7 +318,7 @@ class Tokenizer {
250
318
  this.advance();
251
319
 
252
320
  // Skip until end of line
253
- while (this.peek() !== '\n' && !this.isAtEnd()) {
321
+ while (this.peek() !== "\n" && !this.isAtEnd()) {
254
322
  this.advance();
255
323
  }
256
324
 
@@ -267,7 +335,7 @@ class Tokenizer {
267
335
  }
268
336
 
269
337
  // Look for decimal part
270
- if (this.peek() === '.' && this.isDigit(this.peekNext())) {
338
+ if (this.peek() === "." && this.isDigit(this.peekNext())) {
271
339
  // Consume the dot
272
340
  this.advance();
273
341
 
@@ -277,7 +345,7 @@ class Tokenizer {
277
345
  }
278
346
 
279
347
  const value = this.source.substring(this.startPos, this.current);
280
- this.addToken('NUMBER', parseFloat(value));
348
+ this.addToken("NUMBER", parseFloat(value));
281
349
  }
282
350
 
283
351
  /**
@@ -285,7 +353,7 @@ class Tokenizer {
285
353
  * @returns {string} Next character
286
354
  */
287
355
  peekNext() {
288
- if (this.current + 1 >= this.source.length) return '\0';
356
+ if (this.current + 1 >= this.source.length) return "\0";
289
357
  return this.source[this.current + 1];
290
358
  }
291
359
 
@@ -300,36 +368,36 @@ class Tokenizer {
300
368
  const text = this.source.substring(this.startPos, this.current);
301
369
 
302
370
  // Special handling for 'y' - only treat as AND in logical contexts
303
- if (text === 'y') {
371
+ if (text === "y") {
304
372
  // Check if this is a logical context by looking at previous tokens
305
373
  const prevToken = this.tokens[this.tokens.length - 1];
306
374
  if (
307
375
  prevToken &&
308
- (prevToken.type === 'IDENTIFIER' ||
309
- prevToken.type === 'NUMBER' ||
310
- prevToken.type === 'STRING' ||
311
- prevToken.type === 'TRUE' ||
312
- prevToken.type === 'FALSE' ||
313
- prevToken.type === 'RIGHT_PAREN' ||
314
- prevToken.type === 'RIGHT_BRACKET')
376
+ (prevToken.type === "IDENTIFIER" ||
377
+ prevToken.type === "NUMBER" ||
378
+ prevToken.type === "STRING" ||
379
+ prevToken.type === "TRUE" ||
380
+ prevToken.type === "FALSE" ||
381
+ prevToken.type === "RIGHT_PAREN" ||
382
+ prevToken.type === "RIGHT_BRACKET")
315
383
  ) {
316
384
  // Check if the next token is a logical operator or end of expression
317
385
  const nextChar = this.peek();
318
386
  if (
319
- nextChar === ' ' ||
320
- nextChar === '\n' ||
321
- nextChar === '\t' ||
322
- nextChar === ')' ||
323
- nextChar === '}' ||
324
- nextChar === ';' ||
387
+ nextChar === " " ||
388
+ nextChar === "\n" ||
389
+ nextChar === "\t" ||
390
+ nextChar === ")" ||
391
+ nextChar === "}" ||
392
+ nextChar === ";" ||
325
393
  this.isAtEnd()
326
394
  ) {
327
- this.addToken('AND');
395
+ this.addToken("AND");
328
396
  } else {
329
- this.addToken('IDENTIFIER');
397
+ this.addToken("IDENTIFIER");
330
398
  }
331
399
  } else {
332
- this.addToken('IDENTIFIER');
400
+ this.addToken("IDENTIFIER");
333
401
  }
334
402
  } else {
335
403
  const type = this.getKeywordType(text);
@@ -344,27 +412,40 @@ class Tokenizer {
344
412
  */
345
413
  getKeywordType(text) {
346
414
  const keywords = {
347
- variable: 'VARIABLE',
348
- mostrar: 'MOSTRAR',
349
- leer: 'LEER',
350
- si: 'SI',
351
- sino: 'SINO',
352
- mientras: 'MIENTRAS',
353
- para: 'PARA',
354
- funcion: 'FUNCION',
355
- retornar: 'RETORNAR',
356
- verdadero: 'TRUE',
357
- falso: 'FALSE',
358
- o: 'OR',
359
- romper: 'ROMPER',
360
- continuar: 'CONTINUAR',
361
- intentar: 'INTENTAR',
362
- capturar: 'CAPTURAR',
363
- nulo: 'NULL',
364
- indefinido: 'UNDEFINED',
415
+ variable: "VARIABLE",
416
+ constante: "CONSTANTE",
417
+ mostrar: "MOSTRAR",
418
+ leer: "LEER",
419
+ si: "SI",
420
+ sino: "SINO",
421
+ mientras: "MIENTRAS",
422
+ para: "PARA",
423
+ funcion: "FUNCION",
424
+ retornar: "RETORNAR",
425
+ verdadero: "TRUE",
426
+ falso: "FALSE",
427
+ o: "OR",
428
+ romper: "ROMPER",
429
+ continuar: "CONTINUAR",
430
+ intentar: "INTENTAR",
431
+ capturar: "CAPTURAR",
432
+ nulo: "NULL",
433
+ indefinido: "UNDEFINED",
434
+ elegir: "ELEGIR",
435
+ caso: "CASO",
436
+ pordefecto: "PORDEFECTO",
437
+ hacer: "HACER",
438
+ cada: "CADA",
439
+ en: "EN",
440
+ clase: "CLASE",
441
+ constructor: "CONSTRUCTOR",
442
+ este: "ESTE",
443
+ nuevo: "NUEVO",
444
+ extiende: "EXTIENDE",
445
+ super: "SUPER",
365
446
  };
366
447
 
367
- return keywords[text] || 'IDENTIFIER';
448
+ return keywords[text] || "IDENTIFIER";
368
449
  }
369
450
 
370
451
  /**
@@ -388,7 +469,7 @@ class Tokenizer {
388
469
  * @returns {string} Current character
389
470
  */
390
471
  peek() {
391
- if (this.isAtEnd()) return '\0';
472
+ if (this.isAtEnd()) return "\0";
392
473
  return this.source[this.current];
393
474
  }
394
475
 
@@ -406,7 +487,7 @@ class Tokenizer {
406
487
  * @returns {boolean} True if it is a digit
407
488
  */
408
489
  isDigit(char) {
409
- return char >= '0' && char <= '9';
490
+ return char >= "0" && char <= "9";
410
491
  }
411
492
 
412
493
  /**
@@ -416,9 +497,9 @@ class Tokenizer {
416
497
  */
417
498
  isAlpha(char) {
418
499
  return (
419
- (char >= 'a' && char <= 'z') ||
420
- (char >= 'A' && char <= 'Z') ||
421
- char === '_'
500
+ (char >= "a" && char <= "z") ||
501
+ (char >= "A" && char <= "Z") ||
502
+ char === "_"
422
503
  );
423
504
  }
424
505
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hispano-lang",
3
- "version": "1.1.7",
3
+ "version": "2.1.0",
4
4
  "description": "Un lenguaje de programación educativo en español para enseñar programación sin barreras de idioma",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,8 @@
26
26
  "build:types": "mkdir -p dist && node scripts/generate-types.js",
27
27
  "demo": "node main.js",
28
28
  "prepublishOnly": "npm run build",
29
- "prepack": "npm run build"
29
+ "prepack": "npm run build",
30
+ "prepare": "husky"
30
31
  },
31
32
  "keywords": [
32
33
  "programming-language",
@@ -58,8 +59,10 @@
58
59
  },
59
60
  "homepage": "https://github.com/nicvazquezdev/hispano-lang#readme",
60
61
  "devDependencies": {
61
- "nodemon": "^3.1.10",
62
62
  "eslint": "^8.57.0",
63
+ "husky": "^9.1.7",
64
+ "lint-staged": "^16.2.7",
65
+ "nodemon": "^3.1.10",
63
66
  "prettier": "^3.2.5"
64
67
  },
65
68
  "dependencies": {
@@ -68,5 +71,11 @@
68
71
  "preferGlobal": true,
69
72
  "publishConfig": {
70
73
  "access": "public"
74
+ },
75
+ "lint-staged": {
76
+ "*.js": [
77
+ "eslint --fix",
78
+ "prettier --write"
79
+ ]
71
80
  }
72
81
  }