gofront 0.0.1

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/src/lexer.js ADDED
@@ -0,0 +1,465 @@
1
+ // Lexer: tokenizes GoFront source into a flat token stream.
2
+ // Implements Go-style automatic semicolon insertion.
3
+
4
+ export const T = {
5
+ // Literals
6
+ INT: "INT",
7
+ FLOAT: "FLOAT",
8
+ STRING: "STRING",
9
+ IDENT: "IDENT",
10
+
11
+ // Keywords
12
+ FUNC: "func",
13
+ VAR: "var",
14
+ CONST: "const",
15
+ TYPE: "type",
16
+ STRUCT: "struct",
17
+ INTERFACE: "interface",
18
+ IF: "if",
19
+ ELSE: "else",
20
+ FOR: "for",
21
+ RANGE: "range",
22
+ RETURN: "return",
23
+ PACKAGE: "package",
24
+ IMPORT: "import",
25
+ TRUE: "true",
26
+ FALSE: "false",
27
+ NIL: "nil",
28
+ BREAK: "break",
29
+ CONTINUE: "continue",
30
+ SWITCH: "switch",
31
+ CASE: "case",
32
+ DEFAULT: "default",
33
+ MAP: "map",
34
+ NEW: "new",
35
+ DEFER: "defer",
36
+ ASYNC: "async",
37
+ AWAIT: "await",
38
+
39
+ // Operators
40
+ PLUS: "+",
41
+ MINUS: "-",
42
+ STAR: "*",
43
+ SLASH: "/",
44
+ PERCENT: "%",
45
+ EQ: "==",
46
+ NEQ: "!=",
47
+ LT: "<",
48
+ GT: ">",
49
+ LTE: "<=",
50
+ GTE: ">=",
51
+ AND: "&&",
52
+ OR: "||",
53
+ NOT: "!",
54
+ ASSIGN: "=",
55
+ DEFINE: ":=",
56
+ PLUS_ASSIGN: "+=",
57
+ MINUS_ASSIGN: "-=",
58
+ STAR_ASSIGN: "*=",
59
+ SLASH_ASSIGN: "/=",
60
+ PERCENT_ASSIGN: "%=",
61
+ INC: "++",
62
+ DEC: "--",
63
+ AMP: "&",
64
+ PIPE: "|",
65
+ CARET: "^",
66
+ LSHIFT: "<<",
67
+ RSHIFT: ">>",
68
+ ELLIPSIS: "...",
69
+ FALLTHROUGH: "fallthrough",
70
+
71
+ // Delimiters
72
+ LPAREN: "(",
73
+ RPAREN: ")",
74
+ LBRACE: "{",
75
+ RBRACE: "}",
76
+ LBRACKET: "[",
77
+ RBRACKET: "]",
78
+ COMMA: ",",
79
+ DOT: ".",
80
+ COLON: ":",
81
+ SEMICOLON: ";",
82
+
83
+ EOF: "EOF",
84
+ };
85
+
86
+ // Tokens where a trailing newline inserts a semicolon (Go spec §Semicolons)
87
+ const SEMI_TRIGGERS = new Set([
88
+ T.IDENT,
89
+ T.INT,
90
+ T.FLOAT,
91
+ T.STRING,
92
+ T.RPAREN,
93
+ T.RBRACE,
94
+ T.RBRACKET,
95
+ T.INC,
96
+ T.DEC,
97
+ T.TRUE,
98
+ T.FALSE,
99
+ T.NIL,
100
+ T.BREAK,
101
+ T.CONTINUE,
102
+ T.RETURN,
103
+ T.FALLTHROUGH,
104
+ ]);
105
+
106
+ const KEYWORDS = new Set([
107
+ "func",
108
+ "var",
109
+ "const",
110
+ "type",
111
+ "struct",
112
+ "interface",
113
+ "if",
114
+ "else",
115
+ "for",
116
+ "range",
117
+ "return",
118
+ "package",
119
+ "import",
120
+ "true",
121
+ "false",
122
+ "nil",
123
+ "break",
124
+ "continue",
125
+ "switch",
126
+ "case",
127
+ "default",
128
+ "map",
129
+ "new",
130
+ "fallthrough",
131
+ "defer",
132
+ "async",
133
+ "await",
134
+ ]);
135
+
136
+ export class Token {
137
+ constructor(type, value, line, col) {
138
+ this.type = type;
139
+ this.value = value;
140
+ this.line = line;
141
+ this.col = col;
142
+ }
143
+ toString() {
144
+ return `Token(${this.type}, ${JSON.stringify(this.value)}, ${this.line}:${this.col})`;
145
+ }
146
+ }
147
+
148
+ export class LexError extends Error {
149
+ constructor(msg, line, col, filename, sourceCode) {
150
+ const loc = filename ? `${filename}:${line}:${col}` : `${line}:${col}`;
151
+ let lineContext = "";
152
+ if (line && sourceCode) {
153
+ const lines = sourceCode.split("\n");
154
+ const lineStr = lines[line - 1];
155
+ if (lineStr !== undefined) {
156
+ lineContext = `\n ${line} | ${lineStr}`;
157
+ }
158
+ }
159
+ super(`Lex error at ${loc}: ${msg}${lineContext}`);
160
+ this.line = line;
161
+ this.col = col;
162
+ }
163
+ }
164
+
165
+ export class Lexer {
166
+ constructor(source, filename = null) {
167
+ this.src = source;
168
+ this.pos = 0;
169
+ this.line = 1;
170
+ this.col = 1;
171
+ this.tokens = [];
172
+ this.filename = filename;
173
+ }
174
+
175
+ // ── Primitives ──────────────────────────────────────────────
176
+
177
+ peek(n = 0) {
178
+ return this.src[this.pos + n];
179
+ }
180
+
181
+ advance() {
182
+ const ch = this.src[this.pos++];
183
+ if (ch === "\n") {
184
+ this.line++;
185
+ this.col = 1;
186
+ } else {
187
+ this.col++;
188
+ }
189
+ return ch;
190
+ }
191
+
192
+ match(ch) {
193
+ if (this.src[this.pos] === ch) {
194
+ this.advance();
195
+ return true;
196
+ }
197
+ return false;
198
+ }
199
+
200
+ err(msg) {
201
+ throw new LexError(msg, this.line, this.col, this.filename, this.src);
202
+ }
203
+
204
+ // ── Skip whitespace and comments ────────────────────────────
205
+
206
+ skip() {
207
+ while (this.pos < this.src.length) {
208
+ const ch = this.peek();
209
+ if (ch === " " || ch === "\t" || ch === "\r") {
210
+ this.advance();
211
+ } else if (ch === "/" && this.peek(1) === "/") {
212
+ while (this.pos < this.src.length && this.peek() !== "\n")
213
+ this.advance();
214
+ } else if (ch === "/" && this.peek(1) === "*") {
215
+ this.advance();
216
+ this.advance();
217
+ while (this.pos < this.src.length) {
218
+ if (this.peek() === "*" && this.peek(1) === "/") {
219
+ this.advance();
220
+ this.advance();
221
+ break;
222
+ }
223
+ this.advance();
224
+ }
225
+ } else {
226
+ break;
227
+ }
228
+ }
229
+ }
230
+
231
+ // ── Literals ─────────────────────────────────────────────────
232
+
233
+ readString() {
234
+ const l = this.line,
235
+ c = this.col;
236
+ this.advance(); // opening "
237
+ let s = "";
238
+ while (this.pos < this.src.length && this.peek() !== '"') {
239
+ if (this.peek() === "\n") this.err("Unterminated string literal");
240
+ if (this.peek() === "\\") {
241
+ this.advance();
242
+ const esc = this.advance();
243
+ switch (esc) {
244
+ case "n":
245
+ s += "\n";
246
+ break;
247
+ case "t":
248
+ s += "\t";
249
+ break;
250
+ case "r":
251
+ s += "\r";
252
+ break;
253
+ case '"':
254
+ s += '"';
255
+ break;
256
+ case "\\":
257
+ s += "\\";
258
+ break;
259
+ case "0":
260
+ s += "\0";
261
+ break;
262
+ default:
263
+ s += `\\${esc}`;
264
+ }
265
+ } else {
266
+ s += this.advance();
267
+ }
268
+ }
269
+ if (this.pos >= this.src.length)
270
+ throw new LexError("Unterminated string", l, c, this.filename);
271
+ this.advance(); // closing "
272
+ return s;
273
+ }
274
+
275
+ readRawString() {
276
+ this.advance(); // opening `
277
+ let s = "";
278
+ while (this.pos < this.src.length && this.peek() !== "`")
279
+ s += this.advance();
280
+ this.advance(); // closing `
281
+ return s;
282
+ }
283
+
284
+ readNumber() {
285
+ let n = "";
286
+ let isFloat = false;
287
+ while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
288
+ n += this.advance();
289
+ if (this.peek() === "." && /[0-9]/.test(this.peek(1))) {
290
+ isFloat = true;
291
+ n += this.advance();
292
+ while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
293
+ n += this.advance();
294
+ }
295
+ if (this.peek() === "e" || this.peek() === "E") {
296
+ isFloat = true;
297
+ n += this.advance();
298
+ if (this.peek() === "+" || this.peek() === "-") n += this.advance();
299
+ while (this.pos < this.src.length && /[0-9]/.test(this.peek()))
300
+ n += this.advance();
301
+ }
302
+ return { n, isFloat };
303
+ }
304
+
305
+ readIdent() {
306
+ let s = "";
307
+ while (this.pos < this.src.length && /[a-zA-Z0-9_]/.test(this.peek()))
308
+ s += this.advance();
309
+ return s;
310
+ }
311
+
312
+ // ── Semicolon insertion ──────────────────────────────────────
313
+
314
+ shouldSemi() {
315
+ if (!this.tokens.length) return false;
316
+ return SEMI_TRIGGERS.has(this.tokens[this.tokens.length - 1].type);
317
+ }
318
+
319
+ push(type, value, line, col) {
320
+ this.tokens.push(new Token(type, value, line, col));
321
+ }
322
+
323
+ // ── Main tokenize loop ───────────────────────────────────────
324
+
325
+ tokenize() {
326
+ while (this.pos < this.src.length) {
327
+ this.skip();
328
+ if (this.pos >= this.src.length) break;
329
+
330
+ const l = this.line,
331
+ c = this.col,
332
+ ch = this.peek();
333
+
334
+ // Newline → maybe semicolon
335
+ if (ch === "\n") {
336
+ if (this.shouldSemi()) this.push(T.SEMICOLON, ";", l, c);
337
+ this.advance();
338
+ continue;
339
+ }
340
+
341
+ // String literals
342
+ if (ch === '"') {
343
+ this.push(T.STRING, this.readString(), l, c);
344
+ continue;
345
+ }
346
+ if (ch === "`") {
347
+ this.push(T.STRING, this.readRawString(), l, c);
348
+ continue;
349
+ }
350
+
351
+ // Numbers
352
+ if (/[0-9]/.test(ch)) {
353
+ const { n, isFloat } = this.readNumber();
354
+ this.push(isFloat ? T.FLOAT : T.INT, n, l, c);
355
+ continue;
356
+ }
357
+
358
+ // Identifiers & keywords
359
+ if (/[a-zA-Z_]/.test(ch)) {
360
+ const id = this.readIdent();
361
+ this.push(KEYWORDS.has(id) ? id : T.IDENT, id, l, c);
362
+ continue;
363
+ }
364
+
365
+ // Operators & punctuation
366
+ this.advance();
367
+ switch (ch) {
368
+ case "+":
369
+ if (this.match("+")) this.push(T.INC, "++", l, c);
370
+ else if (this.match("=")) this.push(T.PLUS_ASSIGN, "+=", l, c);
371
+ else this.push(T.PLUS, "+", l, c);
372
+ break;
373
+ case "-":
374
+ if (this.match("-")) this.push(T.DEC, "--", l, c);
375
+ else if (this.match("=")) this.push(T.MINUS_ASSIGN, "-=", l, c);
376
+ else this.push(T.MINUS, "-", l, c);
377
+ break;
378
+ case "*":
379
+ if (this.match("=")) this.push(T.STAR_ASSIGN, "*=", l, c);
380
+ else this.push(T.STAR, "*", l, c);
381
+ break;
382
+ case "/":
383
+ if (this.match("=")) this.push(T.SLASH_ASSIGN, "/=", l, c);
384
+ else this.push(T.SLASH, "/", l, c);
385
+ break;
386
+ case "%":
387
+ if (this.match("=")) this.push(T.PERCENT_ASSIGN, "%=", l, c);
388
+ else this.push(T.PERCENT, "%", l, c);
389
+ break;
390
+ case "=":
391
+ if (this.match("=")) this.push(T.EQ, "==", l, c);
392
+ else this.push(T.ASSIGN, "=", l, c);
393
+ break;
394
+ case "!":
395
+ if (this.match("=")) this.push(T.NEQ, "!=", l, c);
396
+ else this.push(T.NOT, "!", l, c);
397
+ break;
398
+ case "<":
399
+ if (this.match("<")) this.push(T.LSHIFT, "<<", l, c);
400
+ else if (this.match("=")) this.push(T.LTE, "<=", l, c);
401
+ else this.push(T.LT, "<", l, c);
402
+ break;
403
+ case ">":
404
+ if (this.match(">")) this.push(T.RSHIFT, ">>", l, c);
405
+ else if (this.match("=")) this.push(T.GTE, ">=", l, c);
406
+ else this.push(T.GT, ">", l, c);
407
+ break;
408
+ case "&":
409
+ if (this.match("&")) this.push(T.AND, "&&", l, c);
410
+ else this.push(T.AMP, "&", l, c);
411
+ break;
412
+ case "|":
413
+ if (this.match("|")) this.push(T.OR, "||", l, c);
414
+ else this.push(T.PIPE, "|", l, c);
415
+ break;
416
+ case "^":
417
+ this.push(T.CARET, "^", l, c);
418
+ break;
419
+ case ":":
420
+ if (this.match("=")) this.push(T.DEFINE, ":=", l, c);
421
+ else this.push(T.COLON, ":", l, c);
422
+ break;
423
+ case ".":
424
+ if (this.peek() === "." && this.peek(1) === ".") {
425
+ this.advance();
426
+ this.advance();
427
+ this.push(T.ELLIPSIS, "...", l, c);
428
+ } else {
429
+ this.push(T.DOT, ".", l, c);
430
+ }
431
+ break;
432
+ case "(":
433
+ this.push(T.LPAREN, "(", l, c);
434
+ break;
435
+ case ")":
436
+ this.push(T.RPAREN, ")", l, c);
437
+ break;
438
+ case "{":
439
+ this.push(T.LBRACE, "{", l, c);
440
+ break;
441
+ case "}":
442
+ this.push(T.RBRACE, "}", l, c);
443
+ break;
444
+ case "[":
445
+ this.push(T.LBRACKET, "[", l, c);
446
+ break;
447
+ case "]":
448
+ this.push(T.RBRACKET, "]", l, c);
449
+ break;
450
+ case ",":
451
+ this.push(T.COMMA, ",", l, c);
452
+ break;
453
+ case ";":
454
+ this.push(T.SEMICOLON, ";", l, c);
455
+ break;
456
+ default:
457
+ this.err(`Unexpected character: '${ch}'`);
458
+ }
459
+ }
460
+
461
+ if (this.shouldSemi()) this.push(T.SEMICOLON, ";", this.line, this.col);
462
+ this.push(T.EOF, "", this.line, this.col);
463
+ return this.tokens;
464
+ }
465
+ }