hispano-lang 1.1.7 → 2.0.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,141 @@ 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
70
  } else {
71
- this.addToken('EQUAL');
71
+ this.addToken("EQUAL");
72
72
  }
73
73
  break;
74
74
 
75
- case '*':
76
- if (this.peek() === '=') {
75
+ case "*":
76
+ if (this.peek() === "=") {
77
77
  this.advance();
78
- this.addToken('STAR_EQUAL');
78
+ this.addToken("STAR_EQUAL");
79
79
  } else {
80
- this.addToken('STAR');
80
+ this.addToken("STAR");
81
81
  }
82
82
  break;
83
83
 
84
- case '/':
85
- if (this.peek() === '=') {
84
+ case "/":
85
+ if (this.peek() === "=") {
86
86
  this.advance();
87
- this.addToken('SLASH_EQUAL');
87
+ this.addToken("SLASH_EQUAL");
88
88
  } else {
89
- this.addToken('SLASH');
89
+ this.addToken("SLASH");
90
90
  }
91
91
  break;
92
92
 
93
- case '%':
94
- if (this.peek() === '=') {
93
+ case "%":
94
+ if (this.peek() === "=") {
95
95
  this.advance();
96
- this.addToken('PERCENT_EQUAL');
96
+ this.addToken("PERCENT_EQUAL");
97
97
  } else {
98
- this.addToken('PERCENT');
98
+ this.addToken("PERCENT");
99
99
  }
100
100
  break;
101
101
 
102
- case '>':
103
- if (this.peek() === '=') {
102
+ case ">":
103
+ if (this.peek() === "=") {
104
104
  this.advance();
105
- this.addToken('GREATER_EQUAL');
105
+ this.addToken("GREATER_EQUAL");
106
106
  } else {
107
- this.addToken('GREATER');
107
+ this.addToken("GREATER");
108
108
  }
109
109
  break;
110
110
 
111
- case '<':
112
- if (this.peek() === '=') {
111
+ case "<":
112
+ if (this.peek() === "=") {
113
113
  this.advance();
114
- this.addToken('LESS_EQUAL');
114
+ this.addToken("LESS_EQUAL");
115
115
  } else {
116
- this.addToken('LESS');
116
+ this.addToken("LESS");
117
117
  }
118
118
  break;
119
119
 
120
- case '!':
121
- if (this.peek() === '=') {
120
+ case "!":
121
+ if (this.peek() === "=") {
122
122
  this.advance();
123
- this.addToken('BANG_EQUAL');
123
+ this.addToken("BANG_EQUAL");
124
124
  } else {
125
- this.addToken('BANG');
125
+ this.addToken("BANG");
126
126
  }
127
127
  break;
128
128
 
129
- case '+':
130
- if (this.peek() === '+') {
129
+ case "+":
130
+ if (this.peek() === "+") {
131
131
  this.advance();
132
- this.addToken('PLUS_PLUS');
133
- } else if (this.peek() === '=') {
132
+ this.addToken("PLUS_PLUS");
133
+ } else if (this.peek() === "=") {
134
134
  this.advance();
135
- this.addToken('PLUS_EQUAL');
135
+ this.addToken("PLUS_EQUAL");
136
136
  } else {
137
- this.addToken('PLUS');
137
+ this.addToken("PLUS");
138
138
  }
139
139
  break;
140
140
 
141
- case '-':
142
- if (this.peek() === '-') {
141
+ case "-":
142
+ if (this.peek() === "-") {
143
143
  this.advance();
144
- this.addToken('MINUS_MINUS');
145
- } else if (this.peek() === '=') {
144
+ this.addToken("MINUS_MINUS");
145
+ } else if (this.peek() === "=") {
146
146
  this.advance();
147
- this.addToken('MINUS_EQUAL');
147
+ this.addToken("MINUS_EQUAL");
148
148
  } else {
149
- this.addToken('MINUS');
149
+ this.addToken("MINUS");
150
150
  }
151
151
  break;
152
152
 
153
- case '{':
154
- this.addToken('LEFT_BRACE');
153
+ case "{":
154
+ this.addToken("LEFT_BRACE");
155
155
  break;
156
156
 
157
- case '}':
158
- this.addToken('RIGHT_BRACE');
157
+ case "}":
158
+ this.addToken("RIGHT_BRACE");
159
159
  break;
160
160
 
161
- case '(':
162
- this.addToken('LEFT_PAREN');
161
+ case "(":
162
+ this.addToken("LEFT_PAREN");
163
163
  break;
164
164
 
165
- case ')':
166
- this.addToken('RIGHT_PAREN');
165
+ case ")":
166
+ this.addToken("RIGHT_PAREN");
167
167
  break;
168
168
 
169
- case ',':
170
- this.addToken('COMMA');
169
+ case ",":
170
+ this.addToken("COMMA");
171
171
  break;
172
172
 
173
- case ';':
174
- this.addToken('SEMICOLON');
173
+ case ";":
174
+ this.addToken("SEMICOLON");
175
175
  break;
176
176
 
177
- case ':':
178
- this.addToken('COLON');
177
+ case ":":
178
+ this.addToken("COLON");
179
179
  break;
180
180
 
181
- case '[':
182
- this.addToken('LEFT_BRACKET');
181
+ case "[":
182
+ this.addToken("LEFT_BRACKET");
183
183
  break;
184
184
 
185
- case ']':
186
- this.addToken('RIGHT_BRACKET');
185
+ case "]":
186
+ this.addToken("RIGHT_BRACKET");
187
187
  break;
188
188
 
189
- case '.':
190
- this.addToken('DOT');
189
+ case ".":
190
+ this.addToken("DOT");
191
191
  break;
192
192
 
193
193
  case '"':
@@ -198,11 +198,15 @@ class Tokenizer {
198
198
  this.string("'");
199
199
  break;
200
200
 
201
- case '/':
202
- if (this.peek() === '/') {
201
+ case "`":
202
+ this.templateString();
203
+ break;
204
+
205
+ case "/":
206
+ if (this.peek() === "/") {
203
207
  this.comment();
204
208
  } else {
205
- this.addToken('SLASH');
209
+ this.addToken("SLASH");
206
210
  }
207
211
  break;
208
212
 
@@ -213,7 +217,7 @@ class Tokenizer {
213
217
  this.identifier();
214
218
  } else {
215
219
  throw new Error(
216
- `Carácter inesperado: ${char} en la línea ${this.currentLine}`
220
+ `Carácter inesperado: ${char} en la línea ${this.currentLine}`,
217
221
  );
218
222
  }
219
223
  break;
@@ -226,12 +230,12 @@ class Tokenizer {
226
230
  */
227
231
  string(quoteType = '"') {
228
232
  while (this.peek() !== quoteType && !this.isAtEnd()) {
229
- if (this.peek() === '\n') this.currentLine++;
233
+ if (this.peek() === "\n") this.currentLine++;
230
234
  this.advance();
231
235
  }
232
236
 
233
237
  if (this.isAtEnd()) {
234
- throw new Error('Cadena no terminada');
238
+ throw new Error("Cadena no terminada");
235
239
  }
236
240
 
237
241
  // Consume the closing quote
@@ -239,7 +243,68 @@ class Tokenizer {
239
243
 
240
244
  // Extract the string value
241
245
  const value = this.source.substring(this.startPos + 1, this.current - 1);
242
- this.addToken('STRING', value);
246
+ this.addToken("STRING", value);
247
+ }
248
+
249
+ /**
250
+ * Processes a template string with interpolation (backticks)
251
+ * Supports ${expression} syntax for embedding expressions
252
+ */
253
+ templateString() {
254
+ const parts = []; // Literal string parts
255
+ const expressions = []; // Expression source strings
256
+ let currentPart = "";
257
+
258
+ while (!this.isAtEnd()) {
259
+ const char = this.peek();
260
+
261
+ if (char === "`") {
262
+ // End of template string
263
+ this.advance();
264
+ parts.push(currentPart);
265
+ this.addToken("TEMPLATE_STRING", { parts, expressions });
266
+ return;
267
+ }
268
+
269
+ if (char === "$" && this.peekNext() === "{") {
270
+ // Start of interpolation
271
+ parts.push(currentPart);
272
+ currentPart = "";
273
+ this.advance(); // consume $
274
+ this.advance(); // consume {
275
+
276
+ // Extract the expression
277
+ let braceCount = 1;
278
+ let expressionSource = "";
279
+
280
+ while (!this.isAtEnd() && braceCount > 0) {
281
+ const c = this.peek();
282
+ if (c === "{") {
283
+ braceCount++;
284
+ expressionSource += c;
285
+ this.advance();
286
+ } else if (c === "}") {
287
+ braceCount--;
288
+ if (braceCount > 0) {
289
+ expressionSource += c;
290
+ }
291
+ this.advance();
292
+ } else {
293
+ if (c === "\n") this.currentLine++;
294
+ expressionSource += c;
295
+ this.advance();
296
+ }
297
+ }
298
+
299
+ expressions.push(expressionSource);
300
+ } else {
301
+ if (char === "\n") this.currentLine++;
302
+ currentPart += char;
303
+ this.advance();
304
+ }
305
+ }
306
+
307
+ throw new Error("Cadena de plantilla no terminada");
243
308
  }
244
309
 
245
310
  /**
@@ -250,7 +315,7 @@ class Tokenizer {
250
315
  this.advance();
251
316
 
252
317
  // Skip until end of line
253
- while (this.peek() !== '\n' && !this.isAtEnd()) {
318
+ while (this.peek() !== "\n" && !this.isAtEnd()) {
254
319
  this.advance();
255
320
  }
256
321
 
@@ -267,7 +332,7 @@ class Tokenizer {
267
332
  }
268
333
 
269
334
  // Look for decimal part
270
- if (this.peek() === '.' && this.isDigit(this.peekNext())) {
335
+ if (this.peek() === "." && this.isDigit(this.peekNext())) {
271
336
  // Consume the dot
272
337
  this.advance();
273
338
 
@@ -277,7 +342,7 @@ class Tokenizer {
277
342
  }
278
343
 
279
344
  const value = this.source.substring(this.startPos, this.current);
280
- this.addToken('NUMBER', parseFloat(value));
345
+ this.addToken("NUMBER", parseFloat(value));
281
346
  }
282
347
 
283
348
  /**
@@ -285,7 +350,7 @@ class Tokenizer {
285
350
  * @returns {string} Next character
286
351
  */
287
352
  peekNext() {
288
- if (this.current + 1 >= this.source.length) return '\0';
353
+ if (this.current + 1 >= this.source.length) return "\0";
289
354
  return this.source[this.current + 1];
290
355
  }
291
356
 
@@ -300,36 +365,36 @@ class Tokenizer {
300
365
  const text = this.source.substring(this.startPos, this.current);
301
366
 
302
367
  // Special handling for 'y' - only treat as AND in logical contexts
303
- if (text === 'y') {
368
+ if (text === "y") {
304
369
  // Check if this is a logical context by looking at previous tokens
305
370
  const prevToken = this.tokens[this.tokens.length - 1];
306
371
  if (
307
372
  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')
373
+ (prevToken.type === "IDENTIFIER" ||
374
+ prevToken.type === "NUMBER" ||
375
+ prevToken.type === "STRING" ||
376
+ prevToken.type === "TRUE" ||
377
+ prevToken.type === "FALSE" ||
378
+ prevToken.type === "RIGHT_PAREN" ||
379
+ prevToken.type === "RIGHT_BRACKET")
315
380
  ) {
316
381
  // Check if the next token is a logical operator or end of expression
317
382
  const nextChar = this.peek();
318
383
  if (
319
- nextChar === ' ' ||
320
- nextChar === '\n' ||
321
- nextChar === '\t' ||
322
- nextChar === ')' ||
323
- nextChar === '}' ||
324
- nextChar === ';' ||
384
+ nextChar === " " ||
385
+ nextChar === "\n" ||
386
+ nextChar === "\t" ||
387
+ nextChar === ")" ||
388
+ nextChar === "}" ||
389
+ nextChar === ";" ||
325
390
  this.isAtEnd()
326
391
  ) {
327
- this.addToken('AND');
392
+ this.addToken("AND");
328
393
  } else {
329
- this.addToken('IDENTIFIER');
394
+ this.addToken("IDENTIFIER");
330
395
  }
331
396
  } else {
332
- this.addToken('IDENTIFIER');
397
+ this.addToken("IDENTIFIER");
333
398
  }
334
399
  } else {
335
400
  const type = this.getKeywordType(text);
@@ -344,27 +409,40 @@ class Tokenizer {
344
409
  */
345
410
  getKeywordType(text) {
346
411
  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',
412
+ variable: "VARIABLE",
413
+ constante: "CONSTANTE",
414
+ mostrar: "MOSTRAR",
415
+ leer: "LEER",
416
+ si: "SI",
417
+ sino: "SINO",
418
+ mientras: "MIENTRAS",
419
+ para: "PARA",
420
+ funcion: "FUNCION",
421
+ retornar: "RETORNAR",
422
+ verdadero: "TRUE",
423
+ falso: "FALSE",
424
+ o: "OR",
425
+ romper: "ROMPER",
426
+ continuar: "CONTINUAR",
427
+ intentar: "INTENTAR",
428
+ capturar: "CAPTURAR",
429
+ nulo: "NULL",
430
+ indefinido: "UNDEFINED",
431
+ elegir: "ELEGIR",
432
+ caso: "CASO",
433
+ pordefecto: "PORDEFECTO",
434
+ hacer: "HACER",
435
+ cada: "CADA",
436
+ en: "EN",
437
+ clase: "CLASE",
438
+ constructor: "CONSTRUCTOR",
439
+ este: "ESTE",
440
+ nuevo: "NUEVO",
441
+ extiende: "EXTIENDE",
442
+ super: "SUPER",
365
443
  };
366
444
 
367
- return keywords[text] || 'IDENTIFIER';
445
+ return keywords[text] || "IDENTIFIER";
368
446
  }
369
447
 
370
448
  /**
@@ -388,7 +466,7 @@ class Tokenizer {
388
466
  * @returns {string} Current character
389
467
  */
390
468
  peek() {
391
- if (this.isAtEnd()) return '\0';
469
+ if (this.isAtEnd()) return "\0";
392
470
  return this.source[this.current];
393
471
  }
394
472
 
@@ -406,7 +484,7 @@ class Tokenizer {
406
484
  * @returns {boolean} True if it is a digit
407
485
  */
408
486
  isDigit(char) {
409
- return char >= '0' && char <= '9';
487
+ return char >= "0" && char <= "9";
410
488
  }
411
489
 
412
490
  /**
@@ -416,9 +494,9 @@ class Tokenizer {
416
494
  */
417
495
  isAlpha(char) {
418
496
  return (
419
- (char >= 'a' && char <= 'z') ||
420
- (char >= 'A' && char <= 'Z') ||
421
- char === '_'
497
+ (char >= "a" && char <= "z") ||
498
+ (char >= "A" && char <= "Z") ||
499
+ char === "_"
422
500
  );
423
501
  }
424
502
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hispano-lang",
3
- "version": "1.1.7",
3
+ "version": "2.0.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
  }