nebulara 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/Compiler/nbs-bootstrap.c +2229 -0
- package/Compiler/nbs_cli.c +1553 -0
- package/Compiler/neb-cli.exe +0 -0
- package/Compiler/neb-codegen.c +287 -0
- package/Compiler/neb-ffi.c +303 -0
- package/Compiler/neb-ir.c +307 -0
- package/Compiler/neb-knowledge.c +203 -0
- package/Compiler/neb-pipeline.c +787 -0
- package/Compiler/neb-semantic.c +261 -0
- package/Compiler/nebulara.exe +0 -0
- package/README.md +258 -0
- package/SPEC.md +267 -0
- package/bin/neb.js +36 -0
- package/build/neb-cli.exe +0 -0
- package/build/neb-codegen.exe +0 -0
- package/build/neb-ffi.exe +0 -0
- package/build/neb-knowledge.exe +0 -0
- package/build/neb-pipeline.exe +0 -0
- package/build/nebulara.exe +0 -0
- package/dist/index.js +131 -0
- package/package.json +39 -0
- package/std/collections.nbs +72 -0
- package/std/json.nbs +15 -0
- package/std/kanban.nbs +122 -0
- package/std/math.nbs +52 -0
- package/std/net.nbs +22 -0
- package/std/primitives.nbs +30 -0
- package/std/string.nbs +71 -0
- package/std/time.nbs +15 -0
- package/test/broken.nbs +2 -0
- package/test/hello.nbs +6 -0
- package/test/test-arrays.nbs +22 -0
- package/test/test-charat.nbs +7 -0
- package/test/test-cont-debug.nbs +8 -0
- package/test/test-cont-min.nbs +7 -0
- package/test/test-cont-parens.nbs +7 -0
- package/test/test-cont2.nbs +7 -0
- package/test/test-cont3.nbs +7 -0
- package/test/test-control.nbs +18 -0
- package/test/test-functions.nbs +13 -0
- package/test/test-loops.nbs +17 -0
- package/test/test-mod-for.nbs +6 -0
- package/test/test-mod.nbs +5 -0
- package/test/test-new-features.nbs +39 -0
- package/test/test-recursion.nbs +23 -0
- package/test/test-runtime.nbs +8 -0
- package/test/test-strings.nbs +14 -0
- package/test/test-trycatch.nbs +11 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* neb-pipeline.c - Connected Compilation Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Takes .nbs source -> tokens -> AST -> IR -> JS/Python transpilation
|
|
5
|
+
* Reuses the lexer/parser from nbs-bootstrap.c
|
|
6
|
+
*
|
|
7
|
+
* Build: gcc -o neb-pipeline.exe neb-pipeline.c -static -O2
|
|
8
|
+
* Usage: neb-pipeline.exe <file.nbs> [--target js|py|ir|check]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <stdio.h>
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
#include <ctype.h>
|
|
15
|
+
#include <stdarg.h>
|
|
16
|
+
#include <stdint.h>
|
|
17
|
+
#include "neb-semantic.c"
|
|
18
|
+
|
|
19
|
+
/* ============================================================================
|
|
20
|
+
* LEXER (copied from nbs-bootstrap.c, adapted)
|
|
21
|
+
* ============================================================================ */
|
|
22
|
+
|
|
23
|
+
typedef enum {
|
|
24
|
+
TOK_INT_LIT, TOK_STRING_LIT, TOK_IDENT,
|
|
25
|
+
TOK_FUNC, TOK_DATA, TOK_RUN, TOK_END,
|
|
26
|
+
TOK_IF, TOK_ELSE, TOK_THEN, TOK_WHILE, TOK_FOR, TOK_TO, TOK_STEP,
|
|
27
|
+
TOK_RETURN, TOK_BREAK, TOK_CONTINUE,
|
|
28
|
+
TOK_PRINT, TOK_LET, TOK_CONST,
|
|
29
|
+
TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT,
|
|
30
|
+
TOK_EQ, TOK_NEQ, TOK_LT, TOK_GT, TOK_LTE, TOK_GTE,
|
|
31
|
+
TOK_AND, TOK_OR, TOK_NOT, TOK_ASSIGN,
|
|
32
|
+
TOK_DOT, TOK_COMMA, TOK_COLON, TOK_SEMICOLON,
|
|
33
|
+
TOK_LPAREN, TOK_RPAREN, TOK_LBRACKET, TOK_RBRACKET,
|
|
34
|
+
TOK_ELSEIF, TOK_TRUE, TOK_FALSE, TOK_NULL,
|
|
35
|
+
TOK_EOF, TOK_ERROR
|
|
36
|
+
} NbsTokenType;
|
|
37
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
NbsTokenType type;
|
|
40
|
+
char text[256];
|
|
41
|
+
int64_t int_val;
|
|
42
|
+
int line;
|
|
43
|
+
} NbsToken;
|
|
44
|
+
|
|
45
|
+
typedef struct {
|
|
46
|
+
const char* src;
|
|
47
|
+
int pos, len, line;
|
|
48
|
+
} Lexer;
|
|
49
|
+
|
|
50
|
+
Lexer lexer_new(const char* src) {
|
|
51
|
+
Lexer l = {src, 0, (int)strlen(src), 1};
|
|
52
|
+
return l;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
char lexer_peek(Lexer* l) {
|
|
56
|
+
if (l->pos >= l->len) return 0;
|
|
57
|
+
return l->src[l->pos];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
char lexer_advance(Lexer* l) {
|
|
61
|
+
char c = l->src[l->pos++];
|
|
62
|
+
if (c == '\n') l->line++;
|
|
63
|
+
return c;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void lexer_skip_whitespace(Lexer* l) {
|
|
67
|
+
while (l->pos < l->len) {
|
|
68
|
+
char c = l->src[l->pos];
|
|
69
|
+
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { l->pos++; if (c == '\n') l->line++; }
|
|
70
|
+
else if (c == '#') { while (l->pos < l->len && l->src[l->pos] != '\n') l->pos++; }
|
|
71
|
+
else break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
NbsToken lexer_next(Lexer* l) {
|
|
76
|
+
lexer_skip_whitespace(l);
|
|
77
|
+
NbsToken tok = {0};
|
|
78
|
+
tok.line = l->line;
|
|
79
|
+
if (l->pos >= l->len) { tok.type = TOK_EOF; return tok; }
|
|
80
|
+
char c = l->src[l->pos];
|
|
81
|
+
if (c >= '0' && c <= '9') {
|
|
82
|
+
int64_t val = 0;
|
|
83
|
+
while (l->pos < l->len && l->src[l->pos] >= '0' && l->src[l->pos] <= '9')
|
|
84
|
+
val = val * 10 + lexer_advance(l) - '0';
|
|
85
|
+
tok.type = TOK_INT_LIT;
|
|
86
|
+
tok.int_val = val;
|
|
87
|
+
sprintf(tok.text, "%lld", val);
|
|
88
|
+
return tok;
|
|
89
|
+
}
|
|
90
|
+
if (c == '"') {
|
|
91
|
+
lexer_advance(l);
|
|
92
|
+
int start = l->pos;
|
|
93
|
+
while (l->pos < l->len && l->src[l->pos] != '"') {
|
|
94
|
+
if (l->src[l->pos] == '\\') l->pos++;
|
|
95
|
+
l->pos++;
|
|
96
|
+
}
|
|
97
|
+
int di = 0;
|
|
98
|
+
for (int si = start; si < l->pos && di < 255; si++) {
|
|
99
|
+
if (l->src[si] == '\\' && si + 1 < l->pos) { si++;
|
|
100
|
+
if (l->src[si] == 'n') tok.text[di++] = '\n';
|
|
101
|
+
else if (l->src[si] == 't') tok.text[di++] = '\t';
|
|
102
|
+
else if (l->src[si] == '\\') tok.text[di++] = '\\';
|
|
103
|
+
else if (l->src[si] == '"') tok.text[di++] = '"';
|
|
104
|
+
else tok.text[di++] = l->src[si];
|
|
105
|
+
} else { tok.text[di++] = l->src[si]; }
|
|
106
|
+
}
|
|
107
|
+
tok.text[di] = 0;
|
|
108
|
+
tok.type = TOK_STRING_LIT;
|
|
109
|
+
if (l->pos < l->len) lexer_advance(l);
|
|
110
|
+
return tok;
|
|
111
|
+
}
|
|
112
|
+
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
|
|
113
|
+
int start = l->pos;
|
|
114
|
+
while (l->pos < l->len && (
|
|
115
|
+
(l->src[l->pos] >= 'a' && l->src[l->pos] <= 'z') ||
|
|
116
|
+
(l->src[l->pos] >= 'A' && l->src[l->pos] <= 'Z') ||
|
|
117
|
+
(l->src[l->pos] >= '0' && l->src[l->pos] <= '9') ||
|
|
118
|
+
l->src[l->pos] == '_' || l->src[l->pos] == '!' || l->src[l->pos] == '?'
|
|
119
|
+
)) l->pos++;
|
|
120
|
+
int slen = l->pos - start;
|
|
121
|
+
memcpy(tok.text, l->src + start, slen);
|
|
122
|
+
tok.text[slen] = 0;
|
|
123
|
+
if (strcmp(tok.text, "FUNC!") == 0) tok.type = TOK_FUNC;
|
|
124
|
+
else if (strcmp(tok.text, "END!") == 0) tok.type = TOK_END;
|
|
125
|
+
else if (strcmp(tok.text, "IF?") == 0) tok.type = TOK_IF;
|
|
126
|
+
else if (strcmp(tok.text, "ELSE") == 0) tok.type = TOK_ELSE;
|
|
127
|
+
else if (strcmp(tok.text, "ELSEIF?") == 0) tok.type = TOK_ELSEIF;
|
|
128
|
+
else if (strcmp(tok.text, "WHILE?") == 0) tok.type = TOK_WHILE;
|
|
129
|
+
else if (strcmp(tok.text, "FOR!") == 0) tok.type = TOK_FOR;
|
|
130
|
+
else if (strcmp(tok.text, "TO") == 0) tok.type = TOK_TO;
|
|
131
|
+
else if (strcmp(tok.text, "STEP") == 0) tok.type = TOK_STEP;
|
|
132
|
+
else if (strcmp(tok.text, "RETURN") == 0) tok.type = TOK_RETURN;
|
|
133
|
+
else if (strcmp(tok.text, "BREAK") == 0) tok.type = TOK_BREAK;
|
|
134
|
+
else if (strcmp(tok.text, "CONTINUE") == 0) tok.type = TOK_CONTINUE;
|
|
135
|
+
else if (strcmp(tok.text, "PRINT") == 0) tok.type = TOK_PRINT;
|
|
136
|
+
else if (strcmp(tok.text, "LET") == 0) tok.type = TOK_LET;
|
|
137
|
+
else if (strcmp(tok.text, "CONST") == 0) tok.type = TOK_CONST;
|
|
138
|
+
else if (strcmp(tok.text, "AND") == 0) tok.type = TOK_AND;
|
|
139
|
+
else if (strcmp(tok.text, "OR") == 0) tok.type = TOK_OR;
|
|
140
|
+
else if (strcmp(tok.text, "NOT") == 0) tok.type = TOK_NOT;
|
|
141
|
+
else if (strcmp(tok.text, "NULL") == 0) { tok.type = TOK_NULL; }
|
|
142
|
+
else if (strcmp(tok.text, "TRUE") == 0) { tok.type = TOK_TRUE; }
|
|
143
|
+
else if (strcmp(tok.text, "FALSE") == 0) { tok.type = TOK_FALSE; }
|
|
144
|
+
else tok.type = TOK_IDENT;
|
|
145
|
+
return tok;
|
|
146
|
+
}
|
|
147
|
+
lexer_advance(l);
|
|
148
|
+
tok.text[0] = c; tok.text[1] = 0;
|
|
149
|
+
if (c == '+') tok.type = TOK_PLUS;
|
|
150
|
+
else if (c == '-') tok.type = TOK_MINUS;
|
|
151
|
+
else if (c == '*') tok.type = TOK_STAR;
|
|
152
|
+
else if (c == '/') tok.type = TOK_SLASH;
|
|
153
|
+
else if (c == '%') tok.type = TOK_PERCENT;
|
|
154
|
+
else if (c == '=') {
|
|
155
|
+
if (lexer_peek(l) == '=') { lexer_advance(l); tok.text[1] = '='; tok.text[2] = 0; tok.type = TOK_EQ; }
|
|
156
|
+
else tok.type = TOK_ASSIGN;
|
|
157
|
+
}
|
|
158
|
+
else if (c == '!') {
|
|
159
|
+
if (lexer_peek(l) == '=') { lexer_advance(l); tok.text[1] = '='; tok.text[2] = 0; tok.type = TOK_NEQ; }
|
|
160
|
+
else { tok.type = TOK_ERROR; }
|
|
161
|
+
}
|
|
162
|
+
else if (c == '<') {
|
|
163
|
+
if (lexer_peek(l) == '=') { lexer_advance(l); tok.text[1] = '='; tok.text[2] = 0; tok.type = TOK_LTE; }
|
|
164
|
+
else tok.type = TOK_LT;
|
|
165
|
+
}
|
|
166
|
+
else if (c == '>') {
|
|
167
|
+
if (lexer_peek(l) == '=') { lexer_advance(l); tok.text[1] = '='; tok.text[2] = 0; tok.type = TOK_GTE; }
|
|
168
|
+
else tok.type = TOK_GT;
|
|
169
|
+
}
|
|
170
|
+
else if (c == '(') tok.type = TOK_LPAREN;
|
|
171
|
+
else if (c == ')') tok.type = TOK_RPAREN;
|
|
172
|
+
else if (c == '[') tok.type = TOK_LBRACKET;
|
|
173
|
+
else if (c == ']') tok.type = TOK_RBRACKET;
|
|
174
|
+
else if (c == '.') tok.type = TOK_DOT;
|
|
175
|
+
else if (c == ',') tok.type = TOK_COMMA;
|
|
176
|
+
else if (c == ':') tok.type = TOK_COLON;
|
|
177
|
+
else if (c == ';') tok.type = TOK_SEMICOLON;
|
|
178
|
+
else tok.type = TOK_ERROR;
|
|
179
|
+
return tok;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ============================================================================
|
|
183
|
+
* AST (subset needed for pipeline)
|
|
184
|
+
* ============================================================================ */
|
|
185
|
+
|
|
186
|
+
typedef enum {
|
|
187
|
+
NODE_INT, NODE_STRING, NODE_IDENT, NODE_BINARY, NODE_UNARY,
|
|
188
|
+
NODE_TRUE, NODE_FALSE, NODE_NULL,
|
|
189
|
+
NODE_ASSIGN, NODE_PRINT, NODE_BLOCK, NODE_IF, NODE_WHILE, NODE_FOR,
|
|
190
|
+
NODE_FUNC_DEF, NODE_FUNC_CALL, NODE_RETURN, NODE_BREAK, NODE_CONTINUE,
|
|
191
|
+
NODE_ARRAY_LIT, NODE_ARRAY_INDEX, NODE_ARRAY_LEN,
|
|
192
|
+
NODE_PROGRAM
|
|
193
|
+
} NodeType;
|
|
194
|
+
|
|
195
|
+
typedef struct ASTNode ASTNode;
|
|
196
|
+
struct ASTNode {
|
|
197
|
+
NodeType type;
|
|
198
|
+
int64_t int_val;
|
|
199
|
+
char str_val[256];
|
|
200
|
+
char op[8];
|
|
201
|
+
ASTNode* left;
|
|
202
|
+
ASTNode* right;
|
|
203
|
+
ASTNode* third;
|
|
204
|
+
ASTNode** children;
|
|
205
|
+
int child_count;
|
|
206
|
+
char params[8][128];
|
|
207
|
+
int param_count;
|
|
208
|
+
int line;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
ASTNode* ast_new(NodeType type) {
|
|
212
|
+
ASTNode* n = (ASTNode*)calloc(1, sizeof(ASTNode));
|
|
213
|
+
n->type = type;
|
|
214
|
+
return n;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ============================================================================
|
|
218
|
+
* PARSER (subset: expressions, statements, blocks)
|
|
219
|
+
* ============================================================================ */
|
|
220
|
+
|
|
221
|
+
typedef struct {
|
|
222
|
+
NbsToken tokens[4096];
|
|
223
|
+
int pos, count;
|
|
224
|
+
} Parser;
|
|
225
|
+
|
|
226
|
+
NbsToken parser_peek(Parser* p) {
|
|
227
|
+
if (p->pos >= p->count) return (NbsToken){TOK_EOF};
|
|
228
|
+
return p->tokens[p->pos];
|
|
229
|
+
}
|
|
230
|
+
NbsToken parser_advance(Parser* p) { return p->tokens[p->pos++]; }
|
|
231
|
+
|
|
232
|
+
int get_precedence(NbsTokenType t) {
|
|
233
|
+
switch (t) {
|
|
234
|
+
case TOK_OR: return 1; case TOK_AND: return 2;
|
|
235
|
+
case TOK_EQ: case TOK_NEQ: return 3;
|
|
236
|
+
case TOK_LT: case TOK_GT: case TOK_LTE: case TOK_GTE: return 4;
|
|
237
|
+
case TOK_PLUS: case TOK_MINUS: return 5;
|
|
238
|
+
case TOK_STAR: case TOK_SLASH: case TOK_PERCENT: return 6;
|
|
239
|
+
case TOK_NOT: return 7;
|
|
240
|
+
default: return 0;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
ASTNode* parse_expression(Parser* p);
|
|
245
|
+
ASTNode* parse_primary(Parser* p) {
|
|
246
|
+
NbsToken tok = parser_peek(p);
|
|
247
|
+
if (tok.type == TOK_INT_LIT) { parser_advance(p); ASTNode* n = ast_new(NODE_INT); n->int_val = tok.int_val; strcpy(n->str_val, tok.text); return n; }
|
|
248
|
+
if (tok.type == TOK_TRUE) { parser_advance(p); return ast_new(NODE_TRUE); }
|
|
249
|
+
if (tok.type == TOK_FALSE) { parser_advance(p); return ast_new(NODE_FALSE); }
|
|
250
|
+
if (tok.type == TOK_NULL) { parser_advance(p); return ast_new(NODE_NULL); }
|
|
251
|
+
if (tok.type == TOK_STRING_LIT) { parser_advance(p); ASTNode* n = ast_new(NODE_STRING); strcpy(n->str_val, tok.text); return n; }
|
|
252
|
+
if (tok.type == TOK_IDENT) {
|
|
253
|
+
parser_advance(p);
|
|
254
|
+
if (parser_peek(p).type == TOK_LBRACKET) {
|
|
255
|
+
parser_advance(p);
|
|
256
|
+
ASTNode* idx = parse_expression(p);
|
|
257
|
+
if (parser_peek(p).type == TOK_RBRACKET) parser_advance(p);
|
|
258
|
+
ASTNode* n = ast_new(NODE_ARRAY_INDEX);
|
|
259
|
+
ASTNode* id = ast_new(NODE_IDENT); strcpy(id->str_val, tok.text);
|
|
260
|
+
n->left = id; n->right = idx; return n;
|
|
261
|
+
}
|
|
262
|
+
if (parser_peek(p).type == TOK_LPAREN) {
|
|
263
|
+
parser_advance(p);
|
|
264
|
+
ASTNode* n = ast_new(NODE_FUNC_CALL); strcpy(n->str_val, tok.text);
|
|
265
|
+
n->children = NULL; n->child_count = 0;
|
|
266
|
+
if (parser_peek(p).type != TOK_RPAREN) {
|
|
267
|
+
int cap = 8; n->children = (ASTNode**)malloc(cap * sizeof(ASTNode*));
|
|
268
|
+
do {
|
|
269
|
+
if (n->child_count >= cap) { cap *= 2; n->children = (ASTNode**)realloc(n->children, cap * sizeof(ASTNode*)); }
|
|
270
|
+
n->children[n->child_count++] = parse_expression(p);
|
|
271
|
+
} while (parser_peek(p).type == TOK_COMMA && (parser_advance(p), 1));
|
|
272
|
+
}
|
|
273
|
+
if (parser_peek(p).type == TOK_RPAREN) parser_advance(p);
|
|
274
|
+
return n;
|
|
275
|
+
}
|
|
276
|
+
/* Built-in: LEN, TYPEOF, TO_STRING, TO_NUMBER */
|
|
277
|
+
if ((strcmp(tok.text, "LEN") == 0 || strcmp(tok.text, "TYPEOF") == 0 ||
|
|
278
|
+
strcmp(tok.text, "TO_STRING") == 0 || strcmp(tok.text, "TO_NUMBER") == 0) &&
|
|
279
|
+
parser_peek(p).type == TOK_LPAREN) {
|
|
280
|
+
parser_advance(p);
|
|
281
|
+
ASTNode* n = ast_new(NODE_ARRAY_LEN); /* reuse node type for builtins */
|
|
282
|
+
if (strcmp(tok.text, "TO_STRING") == 0 || strcmp(tok.text, "TO_NUMBER") == 0)
|
|
283
|
+
n->type = NODE_FUNC_CALL;
|
|
284
|
+
strcpy(n->str_val, tok.text);
|
|
285
|
+
n->left = parse_expression(p);
|
|
286
|
+
if (parser_peek(p).type == TOK_RPAREN) parser_advance(p);
|
|
287
|
+
return n;
|
|
288
|
+
}
|
|
289
|
+
ASTNode* n = ast_new(NODE_IDENT); strcpy(n->str_val, tok.text); return n;
|
|
290
|
+
}
|
|
291
|
+
if (tok.type == TOK_LPAREN) { parser_advance(p); ASTNode* expr = parse_expression(p); if (parser_peek(p).type == TOK_RPAREN) parser_advance(p); return expr; }
|
|
292
|
+
if (tok.type == TOK_LBRACKET) {
|
|
293
|
+
parser_advance(p); ASTNode* n = ast_new(NODE_ARRAY_LIT); n->children = NULL; n->child_count = 0;
|
|
294
|
+
int cap = 8; n->children = (ASTNode**)malloc(cap * sizeof(ASTNode*));
|
|
295
|
+
if (parser_peek(p).type != TOK_RBRACKET) {
|
|
296
|
+
do { if (n->child_count >= cap) { cap *= 2; n->children = (ASTNode**)realloc(n->children, cap * sizeof(ASTNode*)); } n->children[n->child_count++] = parse_expression(p); } while (parser_peek(p).type == TOK_COMMA && (parser_advance(p), 1));
|
|
297
|
+
}
|
|
298
|
+
if (parser_peek(p).type == TOK_RBRACKET) parser_advance(p);
|
|
299
|
+
return n;
|
|
300
|
+
}
|
|
301
|
+
if (tok.type == TOK_MINUS) { parser_advance(p); ASTNode* n = ast_new(NODE_UNARY); strcpy(n->op, "-"); n->left = parse_primary(p); return n; }
|
|
302
|
+
if (tok.type == TOK_NOT) { parser_advance(p); ASTNode* n = ast_new(NODE_UNARY); strcpy(n->op, "NOT"); n->left = parse_primary(p); return n; }
|
|
303
|
+
parser_advance(p);
|
|
304
|
+
return ast_new(NODE_INT);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
ASTNode* parse_expression_bp(Parser* p, int min_prec) {
|
|
308
|
+
ASTNode* left = parse_primary(p);
|
|
309
|
+
while (get_precedence(parser_peek(p).type) >= min_prec) {
|
|
310
|
+
NbsToken op = parser_advance(p);
|
|
311
|
+
int prec = get_precedence(op.type);
|
|
312
|
+
ASTNode* right = parse_expression_bp(p, prec + 1);
|
|
313
|
+
ASTNode* bin = ast_new(NODE_BINARY); strcpy(bin->op, op.text); bin->left = left; bin->right = right;
|
|
314
|
+
left = bin;
|
|
315
|
+
}
|
|
316
|
+
return left;
|
|
317
|
+
}
|
|
318
|
+
ASTNode* parse_expression(Parser* p) { return parse_expression_bp(p, 1); }
|
|
319
|
+
|
|
320
|
+
ASTNode* parse_block(Parser* p);
|
|
321
|
+
ASTNode* parse_statement(Parser* p) {
|
|
322
|
+
NbsToken tok = parser_peek(p);
|
|
323
|
+
if (tok.type == TOK_PRINT) { parser_advance(p); ASTNode* n = ast_new(NODE_PRINT); n->left = parse_expression(p); return n; }
|
|
324
|
+
if (tok.type == TOK_LET || tok.type == TOK_CONST || (tok.type == TOK_IDENT && p->pos + 1 < p->count && p->tokens[p->pos + 1].type == TOK_ASSIGN)) {
|
|
325
|
+
if (tok.type == TOK_LET || tok.type == TOK_CONST) parser_advance(p);
|
|
326
|
+
NbsToken name = parser_peek(p); if (name.type == TOK_IDENT) parser_advance(p);
|
|
327
|
+
if (parser_peek(p).type == TOK_ASSIGN) parser_advance(p);
|
|
328
|
+
ASTNode* n = ast_new(NODE_ASSIGN); strcpy(n->str_val, name.text); n->right = parse_expression(p); return n;
|
|
329
|
+
}
|
|
330
|
+
if (tok.type == TOK_IF) {
|
|
331
|
+
parser_advance(p);
|
|
332
|
+
ASTNode* root = ast_new(NODE_IF); ASTNode* current = root;
|
|
333
|
+
root->left = parse_expression(p);
|
|
334
|
+
if (parser_peek(p).type == TOK_THEN) parser_advance(p);
|
|
335
|
+
if (parser_peek(p).type == TOK_COLON) parser_advance(p);
|
|
336
|
+
root->right = parse_block(p);
|
|
337
|
+
while (parser_peek(p).type == TOK_ELSEIF) {
|
|
338
|
+
parser_advance(p); ASTNode* elseif = ast_new(NODE_IF);
|
|
339
|
+
elseif->left = parse_expression(p);
|
|
340
|
+
if (parser_peek(p).type == TOK_THEN) parser_advance(p);
|
|
341
|
+
if (parser_peek(p).type == TOK_COLON) parser_advance(p);
|
|
342
|
+
elseif->right = parse_block(p);
|
|
343
|
+
current->third = elseif; current = elseif;
|
|
344
|
+
}
|
|
345
|
+
if (parser_peek(p).type == TOK_ELSE) { parser_advance(p); if (parser_peek(p).type == TOK_COLON) parser_advance(p); current->third = parse_block(p); }
|
|
346
|
+
if (parser_peek(p).type == TOK_END) parser_advance(p);
|
|
347
|
+
return root;
|
|
348
|
+
}
|
|
349
|
+
if (tok.type == TOK_WHILE) {
|
|
350
|
+
parser_advance(p); ASTNode* n = ast_new(NODE_WHILE); n->left = parse_expression(p);
|
|
351
|
+
if (parser_peek(p).type == TOK_THEN) parser_advance(p);
|
|
352
|
+
if (parser_peek(p).type == TOK_COLON) parser_advance(p);
|
|
353
|
+
n->right = parse_block(p); if (parser_peek(p).type == TOK_END) parser_advance(p); return n;
|
|
354
|
+
}
|
|
355
|
+
if (tok.type == TOK_FOR) {
|
|
356
|
+
parser_advance(p); ASTNode* n = ast_new(NODE_FOR);
|
|
357
|
+
NbsToken var = parser_peek(p); if (var.type == TOK_IDENT) parser_advance(p);
|
|
358
|
+
strcpy(n->str_val, var.text);
|
|
359
|
+
if (parser_peek(p).type == TOK_ASSIGN) parser_advance(p);
|
|
360
|
+
ASTNode* start = parse_expression(p);
|
|
361
|
+
if (parser_peek(p).type == TOK_TO) parser_advance(p);
|
|
362
|
+
ASTNode* end = parse_expression(p);
|
|
363
|
+
ASTNode* step = NULL;
|
|
364
|
+
if (parser_peek(p).type == TOK_STEP) { parser_advance(p); step = parse_expression(p); }
|
|
365
|
+
ASTNode* range = ast_new(NODE_BINARY); strcpy(range->op, "TO"); range->left = start; range->right = end;
|
|
366
|
+
if (step) { ASTNode* sn = ast_new(NODE_BINARY); strcpy(sn->op, "STEP"); sn->left = range; sn->right = step; n->left = sn; }
|
|
367
|
+
else n->left = range;
|
|
368
|
+
if (parser_peek(p).type == TOK_COLON) parser_advance(p);
|
|
369
|
+
n->right = parse_block(p);
|
|
370
|
+
if (parser_peek(p).type == TOK_END) parser_advance(p);
|
|
371
|
+
return n;
|
|
372
|
+
}
|
|
373
|
+
if (tok.type == TOK_FUNC) {
|
|
374
|
+
parser_advance(p); ASTNode* n = ast_new(NODE_FUNC_DEF);
|
|
375
|
+
NbsToken name = parser_peek(p); if (name.type == TOK_IDENT) parser_advance(p);
|
|
376
|
+
strcpy(n->str_val, name.text); n->param_count = 0;
|
|
377
|
+
while (parser_peek(p).type != TOK_COLON && parser_peek(p).type != TOK_EOF) {
|
|
378
|
+
NbsToken param = parser_advance(p);
|
|
379
|
+
if (param.type == TOK_IDENT) strcpy(n->params[n->param_count++], param.text);
|
|
380
|
+
}
|
|
381
|
+
if (parser_peek(p).type == TOK_COLON) parser_advance(p);
|
|
382
|
+
n->right = parse_block(p);
|
|
383
|
+
if (parser_peek(p).type == TOK_END) parser_advance(p);
|
|
384
|
+
return n;
|
|
385
|
+
}
|
|
386
|
+
if (tok.type == TOK_RETURN) { parser_advance(p); ASTNode* n = ast_new(NODE_RETURN); if (parser_peek(p).type != TOK_END && parser_peek(p).type != TOK_EOF && parser_peek(p).type != TOK_ELSE && parser_peek(p).type != TOK_ELSEIF) n->left = parse_expression(p); return n; }
|
|
387
|
+
if (tok.type == TOK_BREAK) { parser_advance(p); return ast_new(NODE_BREAK); }
|
|
388
|
+
if (tok.type == TOK_CONTINUE) { parser_advance(p); return ast_new(NODE_CONTINUE); }
|
|
389
|
+
ASTNode* expr = parse_expression(p); return expr;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
ASTNode* parse_block(Parser* p) {
|
|
393
|
+
ASTNode* block = ast_new(NODE_BLOCK); int cap = 16;
|
|
394
|
+
block->children = (ASTNode**)malloc(cap * sizeof(ASTNode*)); block->child_count = 0;
|
|
395
|
+
while (parser_peek(p).type != TOK_END && parser_peek(p).type != TOK_EOF && parser_peek(p).type != TOK_ELSE && parser_peek(p).type != TOK_ELSEIF) {
|
|
396
|
+
if (block->child_count >= cap) { cap *= 2; block->children = (ASTNode**)realloc(block->children, cap * sizeof(ASTNode*)); }
|
|
397
|
+
block->children[block->child_count++] = parse_statement(p);
|
|
398
|
+
}
|
|
399
|
+
return block;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
ASTNode* parse_program(Parser* p) {
|
|
403
|
+
ASTNode* prog = ast_new(NODE_PROGRAM); int cap = 64;
|
|
404
|
+
prog->children = (ASTNode**)malloc(cap * sizeof(ASTNode*)); prog->child_count = 0;
|
|
405
|
+
while (parser_peek(p).type != TOK_EOF) {
|
|
406
|
+
if (prog->child_count >= cap) { cap *= 2; prog->children = (ASTNode**)realloc(prog->children, cap * sizeof(ASTNode*)); }
|
|
407
|
+
prog->children[prog->child_count++] = parse_statement(p);
|
|
408
|
+
}
|
|
409
|
+
return prog;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* ============================================================================
|
|
413
|
+
* TARGET: JavaScript transpiler (AST -> JS)
|
|
414
|
+
* ============================================================================ */
|
|
415
|
+
|
|
416
|
+
static char js_out[65536];
|
|
417
|
+
static int js_pos = 0;
|
|
418
|
+
static int js_indent = 0;
|
|
419
|
+
|
|
420
|
+
void js_init(void) { js_pos = 0; js_indent = 0; }
|
|
421
|
+
void js_indent_fn(void) { for (int i = 0; i < js_indent; i++) { js_out[js_pos++] = ' '; js_out[js_pos++] = ' '; } }
|
|
422
|
+
void js_str(const char* s) { while (*s) js_out[js_pos++] = *s++; }
|
|
423
|
+
void js_line(void) { js_out[js_pos++] = '\n'; js_indent_fn(); }
|
|
424
|
+
|
|
425
|
+
void js_transpile_expr(ASTNode* n) {
|
|
426
|
+
if (!n) return;
|
|
427
|
+
switch (n->type) {
|
|
428
|
+
case NODE_INT: { char buf[32]; sprintf(buf, "%lld", n->int_val); js_str(buf); } break;
|
|
429
|
+
case NODE_TRUE: js_str("true"); break;
|
|
430
|
+
case NODE_FALSE: js_str("false"); break;
|
|
431
|
+
case NODE_NULL: js_str("null"); break;
|
|
432
|
+
case NODE_STRING: js_str("'"); js_str(n->str_val); js_str("'"); break;
|
|
433
|
+
case NODE_IDENT: js_str(n->str_val); break;
|
|
434
|
+
case NODE_BINARY: js_str("("); js_transpile_expr(n->left); js_str(" "); js_str(n->op); js_str(" "); js_transpile_expr(n->right); js_str(")"); break;
|
|
435
|
+
case NODE_UNARY: js_str(n->op[0] == '-' ? "(-" : "(!"); js_transpile_expr(n->left); js_str(")"); break;
|
|
436
|
+
case NODE_FUNC_CALL: js_str(n->str_val); js_str("("); for (int i = 0; i < n->child_count; i++) { if (i) js_str(", "); js_transpile_expr(n->children[i]); } js_str(")"); break;
|
|
437
|
+
case NODE_ARRAY_INDEX: js_transpile_expr(n->left); js_str("["); js_transpile_expr(n->right); js_str("]"); break;
|
|
438
|
+
case NODE_ARRAY_LEN: js_transpile_expr(n->left); js_str(".length"); break;
|
|
439
|
+
default: js_str("/* ? */"); break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
void js_transpile_stmt(ASTNode* n) {
|
|
444
|
+
if (!n) return;
|
|
445
|
+
switch (n->type) {
|
|
446
|
+
case NODE_PRINT: js_str("console.log("); js_transpile_expr(n->left); js_str(")"); js_line(); break;
|
|
447
|
+
case NODE_ASSIGN: js_str("let "); js_str(n->str_val); js_str(" = "); js_transpile_expr(n->right); js_line(); break;
|
|
448
|
+
case NODE_FUNC_DEF: js_str("function "); js_str(n->str_val); js_str("("); for (int i = 0; i < n->param_count; i++) { if (i) js_str(", "); js_str(n->params[i]); } js_str(") {"); js_line(); js_indent++; js_transpile_stmt(n->right); js_indent--; js_indent_fn(); js_str("}"); js_line(); break;
|
|
449
|
+
case NODE_RETURN: js_str("return "); js_transpile_expr(n->left); js_line(); break;
|
|
450
|
+
case NODE_BREAK: js_str("break"); js_line(); break;
|
|
451
|
+
case NODE_CONTINUE: js_str("continue"); js_line(); break;
|
|
452
|
+
case NODE_IF: js_str("if ("); js_transpile_expr(n->left); js_str(") {"); js_line(); js_indent++; js_transpile_stmt(n->right); js_indent--; js_indent_fn(); js_str("}"); if (n->third && n->third->type == NODE_IF) { js_str(" else "); js_transpile_stmt(n->third); } else if (n->third) { js_str(" else {"); js_line(); js_indent++; js_transpile_stmt(n->third); js_indent--; js_indent_fn(); js_str("}"); } js_line(); break;
|
|
453
|
+
case NODE_WHILE: js_str("while ("); js_transpile_expr(n->left); js_str(") {"); js_line(); js_indent++; js_transpile_stmt(n->right); js_indent--; js_indent_fn(); js_str("}"); js_line(); break;
|
|
454
|
+
case NODE_FOR: js_str("for (let "); js_str(n->str_val); js_str(" = "); if (n->left->type == NODE_BINARY && strcmp(n->left->op, "STEP") == 0) { js_transpile_expr(n->left->left->left); js_str("; "); js_str(n->str_val); js_str(" <= "); js_transpile_expr(n->left->left->right); js_str("; "); js_str(n->str_val); js_str(" += "); js_transpile_expr(n->left->right); } else { js_transpile_expr(n->left->left); js_str("; "); js_str(n->str_val); js_str(" <= "); js_transpile_expr(n->left->right); js_str("; "); js_str(n->str_val); js_str("++"); } js_str(") {"); js_line(); js_indent++; js_transpile_stmt(n->right); js_indent--; js_indent_fn(); js_str("}"); js_line(); break;
|
|
455
|
+
case NODE_BLOCK: for (int i = 0; i < n->child_count; i++) js_transpile_stmt(n->children[i]); break;
|
|
456
|
+
case NODE_PROGRAM: for (int i = 0; i < n->child_count; i++) js_transpile_stmt(n->children[i]); break;
|
|
457
|
+
default: js_transpile_expr(n); js_line(); break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const char* transpile_to_js(ASTNode* ast) {
|
|
462
|
+
js_init();
|
|
463
|
+
js_str("'use strict';"); js_line(); js_line();
|
|
464
|
+
js_transpile_stmt(ast);
|
|
465
|
+
js_out[js_pos] = 0;
|
|
466
|
+
return js_out;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* ============================================================================
|
|
470
|
+
* TARGET: Python transpiler (AST -> Python)
|
|
471
|
+
* ============================================================================ */
|
|
472
|
+
|
|
473
|
+
static char py_out[65536];
|
|
474
|
+
static int py_pos = 0;
|
|
475
|
+
static int py_indent = 0;
|
|
476
|
+
|
|
477
|
+
void py_init(void) { py_pos = 0; py_indent = 0; }
|
|
478
|
+
void py_indent_fn(void) { for (int i = 0; i < py_indent; i++) { py_out[py_pos++] = ' '; py_out[py_pos++] = ' '; py_out[py_pos++] = ' '; py_out[py_pos++] = ' '; } }
|
|
479
|
+
void py_str(const char* s) { while (*s) py_out[py_pos++] = *s++; }
|
|
480
|
+
void py_line(void) { py_out[py_pos++] = '\n'; py_indent_fn(); }
|
|
481
|
+
|
|
482
|
+
void py_transpile_expr(ASTNode* n) {
|
|
483
|
+
if (!n) return;
|
|
484
|
+
switch (n->type) {
|
|
485
|
+
case NODE_INT: { char buf[32]; sprintf(buf, "%lld", n->int_val); py_str(buf); } break;
|
|
486
|
+
case NODE_TRUE: py_str("True"); break;
|
|
487
|
+
case NODE_FALSE: py_str("False"); break;
|
|
488
|
+
case NODE_NULL: py_str("None"); break;
|
|
489
|
+
case NODE_STRING: py_str("'"); py_str(n->str_val); py_str("'"); break;
|
|
490
|
+
case NODE_IDENT: py_str(n->str_val); break;
|
|
491
|
+
case NODE_BINARY: {
|
|
492
|
+
const char* op = n->op;
|
|
493
|
+
if (strcmp(op, "!=") == 0) op = "!=";
|
|
494
|
+
py_str("("); py_transpile_expr(n->left); py_str(" "); py_str(op); py_str(" "); py_transpile_expr(n->right); py_str(")");
|
|
495
|
+
} break;
|
|
496
|
+
case NODE_UNARY: py_str(n->op[0] == '-' ? "(-" : "(not "); py_transpile_expr(n->left); py_str(")"); break;
|
|
497
|
+
case NODE_FUNC_CALL: {
|
|
498
|
+
if (strcmp(n->str_val, "LEN") == 0) { py_str("len("); py_transpile_expr(n->left); py_str(")"); }
|
|
499
|
+
else if (strcmp(n->str_val, "TYPEOF") == 0) { py_str("type("); py_transpile_expr(n->left); py_str(").__name__"); }
|
|
500
|
+
else if (strcmp(n->str_val, "TO_STRING") == 0) { py_str("str("); py_transpile_expr(n->left); py_str(")"); }
|
|
501
|
+
else if (strcmp(n->str_val, "TO_NUMBER") == 0) { py_str("int("); py_transpile_expr(n->left); py_str(")"); }
|
|
502
|
+
else { py_str(n->str_val); py_str("("); for (int i = 0; i < n->child_count; i++) { if (i) py_str(", "); py_transpile_expr(n->children[i]); } py_str(")"); }
|
|
503
|
+
} break;
|
|
504
|
+
case NODE_ARRAY_INDEX: py_transpile_expr(n->left); py_str("["); py_transpile_expr(n->right); py_str("]"); break;
|
|
505
|
+
case NODE_ARRAY_LEN: py_str("len("); py_transpile_expr(n->left); py_str(")"); break;
|
|
506
|
+
default: py_str("pass # ?"); break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
void py_transpile_stmt(ASTNode* n) {
|
|
511
|
+
if (!n) return;
|
|
512
|
+
switch (n->type) {
|
|
513
|
+
case NODE_PRINT: py_str("print("); py_transpile_expr(n->left); py_str(")"); py_line(); break;
|
|
514
|
+
case NODE_ASSIGN: py_str(n->str_val); py_str(" = "); py_transpile_expr(n->right); py_line(); break;
|
|
515
|
+
case NODE_FUNC_DEF: py_str("def "); py_str(n->str_val); py_str("("); for (int i = 0; i < n->param_count; i++) { if (i) py_str(", "); py_str(n->params[i]); } py_str("):"); py_line(); py_indent++; py_transpile_stmt(n->right); py_indent--; py_line(); break;
|
|
516
|
+
case NODE_RETURN: py_str("return "); py_transpile_expr(n->left); py_line(); break;
|
|
517
|
+
case NODE_BREAK: py_str("break"); py_line(); break;
|
|
518
|
+
case NODE_CONTINUE: py_str("continue"); py_line(); break;
|
|
519
|
+
case NODE_IF: py_str("if "); py_transpile_expr(n->left); py_str(":"); py_line(); py_indent++; py_transpile_stmt(n->right); py_indent--; if (n->third && n->third->type == NODE_IF) { py_indent_fn(); py_str("el"); py_transpile_stmt(n->third); } else if (n->third) { py_indent_fn(); py_str("else:"); py_line(); py_indent++; py_transpile_stmt(n->third); py_indent--; } break;
|
|
520
|
+
case NODE_WHILE: py_str("while "); py_transpile_expr(n->left); py_str(":"); py_line(); py_indent++; py_transpile_stmt(n->right); py_indent--; py_line(); break;
|
|
521
|
+
case NODE_FOR: py_str("for "); py_str(n->str_val); py_str(" in range("); if (n->left->type == NODE_BINARY && strcmp(n->left->op, "STEP") == 0) { py_transpile_expr(n->left->left->left); py_str(", "); py_transpile_expr(n->left->left->right); py_str(" + 1, "); py_transpile_expr(n->left->right); } else { py_transpile_expr(n->left->left); py_str(", "); py_transpile_expr(n->left->right); py_str(" + 1"); } py_str("):"); py_line(); py_indent++; py_transpile_stmt(n->right); py_indent--; py_line(); break;
|
|
522
|
+
case NODE_BLOCK: for (int i = 0; i < n->child_count; i++) py_transpile_stmt(n->children[i]); break;
|
|
523
|
+
case NODE_PROGRAM: py_str("from typing import Any\n\n"); for (int i = 0; i < n->child_count; i++) py_transpile_stmt(n->children[i]); break;
|
|
524
|
+
default: py_transpile_expr(n); py_line(); break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const char* transpile_to_python(ASTNode* ast) {
|
|
529
|
+
py_init();
|
|
530
|
+
py_transpile_stmt(ast);
|
|
531
|
+
py_out[py_pos] = 0;
|
|
532
|
+
return py_out;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/* ============================================================================
|
|
536
|
+
* SEMANTIC ANALYSIS BRIDGE
|
|
537
|
+
* ============================================================================ */
|
|
538
|
+
|
|
539
|
+
typedef struct {
|
|
540
|
+
int error_count;
|
|
541
|
+
int warning_count;
|
|
542
|
+
struct { int line; char msg[256]; } errors[128];
|
|
543
|
+
} SemaContext;
|
|
544
|
+
|
|
545
|
+
SemaContext *sema_new_context(void) {
|
|
546
|
+
SemaContext *ctx = (SemaContext *)calloc(1, sizeof(SemaContext));
|
|
547
|
+
scope_push("global");
|
|
548
|
+
return ctx;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void sema_free_context(SemaContext *ctx) {
|
|
552
|
+
scope_pop();
|
|
553
|
+
free(ctx);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
static void sema_ctx_error(SemaContext *ctx, int line, const char *fmt, ...) {
|
|
557
|
+
if (ctx->error_count >= 128) return;
|
|
558
|
+
va_list args;
|
|
559
|
+
va_start(args, fmt);
|
|
560
|
+
vsnprintf(ctx->errors[ctx->error_count].msg, 256, fmt, args);
|
|
561
|
+
va_end(args);
|
|
562
|
+
ctx->errors[ctx->error_count].line = line;
|
|
563
|
+
ctx->error_count++;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
void sema_print_errors(SemaContext *ctx) {
|
|
567
|
+
for (int i = 0; i < ctx->error_count; i++) {
|
|
568
|
+
fprintf(stderr, "Semantic error [line %d]: %s\n",
|
|
569
|
+
ctx->errors[i].line, ctx->errors[i].msg);
|
|
570
|
+
}
|
|
571
|
+
if (ctx->error_count == 0) {
|
|
572
|
+
printf("Semantic analysis: PASS\n");
|
|
573
|
+
} else {
|
|
574
|
+
printf("Semantic analysis: %d error(s), %d warning(s)\n",
|
|
575
|
+
ctx->error_count, ctx->warning_count);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
int sema_has_errors(SemaContext *ctx) {
|
|
580
|
+
return ctx->error_count > 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
static int is_sema_keyword(const char *t) {
|
|
584
|
+
return strcmp(t, "LET") == 0 || strcmp(t, "CONST") == 0 ||
|
|
585
|
+
strcmp(t, "FUNC!") == 0 || strcmp(t, "END!") == 0 ||
|
|
586
|
+
strcmp(t, "IF?") == 0 || strcmp(t, "ELSE") == 0 ||
|
|
587
|
+
strcmp(t, "ELSEIF?") == 0 || strcmp(t, "WHILE?") == 0 ||
|
|
588
|
+
strcmp(t, "FOR!") == 0 || strcmp(t, "TO") == 0 ||
|
|
589
|
+
strcmp(t, "STEP") == 0 || strcmp(t, "RETURN") == 0 ||
|
|
590
|
+
strcmp(t, "BREAK") == 0 || strcmp(t, "CONTINUE") == 0 ||
|
|
591
|
+
strcmp(t, "PRINT") == 0 || strcmp(t, "AND") == 0 ||
|
|
592
|
+
strcmp(t, "OR") == 0 || strcmp(t, "NOT") == 0 ||
|
|
593
|
+
strcmp(t, "TRUE") == 0 || strcmp(t, "FALSE") == 0 ||
|
|
594
|
+
strcmp(t, "NULL") == 0 || strcmp(t, "THEN") == 0 ||
|
|
595
|
+
strcmp(t, "TRY") == 0 || strcmp(t, "CATCH") == 0 ||
|
|
596
|
+
strcmp(t, "THROW") == 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
static int is_sema_builtin(const char *t) {
|
|
600
|
+
return strcmp(t, "PRINT") == 0 || strcmp(t, "LEN") == 0 ||
|
|
601
|
+
strcmp(t, "TYPEOF") == 0 || strcmp(t, "TYPE") == 0 ||
|
|
602
|
+
strcmp(t, "TO_STRING") == 0 || strcmp(t, "TO_NUMBER") == 0 ||
|
|
603
|
+
strcmp(t, "TO_INT") == 0 || strcmp(t, "INPUT") == 0 ||
|
|
604
|
+
strcmp(t, "RANDOM") == 0 || strcmp(t, "TIME") == 0 ||
|
|
605
|
+
strcmp(t, "CONCAT") == 0 || strcmp(t, "WAIT") == 0 ||
|
|
606
|
+
strcmp(t, "SUBSTR") == 0 || strcmp(t, "CHAR_AT") == 0 ||
|
|
607
|
+
strcmp(t, "TRIM") == 0 || strcmp(t, "TO_UPPER") == 0 ||
|
|
608
|
+
strcmp(t, "TO_LOWER") == 0 || strcmp(t, "CHAR") == 0 ||
|
|
609
|
+
strcmp(t, "ORD") == 0 || strcmp(t, "ABS") == 0 ||
|
|
610
|
+
strcmp(t, "MIN") == 0 || strcmp(t, "MAX") == 0 ||
|
|
611
|
+
strcmp(t, "SQRT") == 0 || strcmp(t, "POW") == 0 ||
|
|
612
|
+
strcmp(t, "FLOOR") == 0 || strcmp(t, "CEIL") == 0 ||
|
|
613
|
+
strcmp(t, "ROUND") == 0 || strcmp(t, "PUSH") == 0 ||
|
|
614
|
+
strcmp(t, "POP") == 0 || strcmp(t, "READ_FILE") == 0 ||
|
|
615
|
+
strcmp(t, "WRITE_FILE") == 0 ||
|
|
616
|
+
strcmp(t, "IS_INT") == 0 || strcmp(t, "IS_STRING") == 0 ||
|
|
617
|
+
strcmp(t, "IS_BOOL") == 0 || strcmp(t, "IS_NULL") == 0 ||
|
|
618
|
+
strcmp(t, "IS_ARRAY") == 0 || strcmp(t, "IS_FUNC") == 0 ||
|
|
619
|
+
strcmp(t, "IS_NUMBER") == 0;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static int is_sema_literal_or_punct(const char *t) {
|
|
623
|
+
if (!t || !t[0]) return 1;
|
|
624
|
+
/* String literals (double or single quoted) */
|
|
625
|
+
if (t[0] == '"') return 1;
|
|
626
|
+
if (t[0] == '\'') return 1;
|
|
627
|
+
/* Numbers */
|
|
628
|
+
if ((t[0] == '-' || t[0] == '+') && t[1] >= '0' && t[1] <= '9') {
|
|
629
|
+
int i = 2; for (; t[i]; i++) if (t[i] < '0' || t[i] > '9') return 0;
|
|
630
|
+
return 1;
|
|
631
|
+
}
|
|
632
|
+
if (t[0] >= '0' && t[0] <= '9') return 1;
|
|
633
|
+
/* Single-char punctuation */
|
|
634
|
+
if (t[1] == '\0' && (t[0] == '=' || t[0] == '+' || t[0] == '-' || t[0] == '*' ||
|
|
635
|
+
t[0] == '/' || t[0] == '%' || t[0] == '(' || t[0] == ')' ||
|
|
636
|
+
t[0] == '[' || t[0] == ']' || t[0] == ',' || t[0] == ':' ||
|
|
637
|
+
t[0] == ';' || t[0] == '.' || t[0] == '!' || t[0] == '&' ||
|
|
638
|
+
t[0] == '|' || t[0] == '<' || t[0] == '>')) return 1;
|
|
639
|
+
/* Two-char operators */
|
|
640
|
+
if (strcmp(t, "==") == 0 || strcmp(t, "!=") == 0 ||
|
|
641
|
+
strcmp(t, "<=") == 0 || strcmp(t, ">=") == 0 ||
|
|
642
|
+
strcmp(t, "<<") == 0 || strcmp(t, ">>") == 0) return 1;
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
int sema_analyze(SemaContext *ctx, const char **tokens, int *token_types, int token_count) {
|
|
647
|
+
for (int i = 0; i < token_count; i++) {
|
|
648
|
+
const char *t = tokens[i];
|
|
649
|
+
if (!t) continue;
|
|
650
|
+
/* Skip string and int literals — they're never definitions */
|
|
651
|
+
if (token_types && (token_types[i] == TOK_STRING_LIT || token_types[i] == TOK_INT_LIT)) continue;
|
|
652
|
+
if ((strcmp(t, "LET") == 0 || strcmp(t, "CONST") == 0) && i + 1 < token_count) {
|
|
653
|
+
const char *name = tokens[i + 1];
|
|
654
|
+
if (symbol_lookup(name)) {
|
|
655
|
+
sema_ctx_error(ctx, i + 1, "duplicate definition of '%s'", name);
|
|
656
|
+
} else {
|
|
657
|
+
symbol_define(name, TYPE_UNKNOWN, i + 1, 0, strcmp(t, "LET") == 0);
|
|
658
|
+
}
|
|
659
|
+
i++;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (strcmp(t, "FUNC!") == 0 && i + 1 < token_count) {
|
|
663
|
+
const char *name = tokens[i + 1];
|
|
664
|
+
if (symbol_lookup(name)) {
|
|
665
|
+
sema_ctx_error(ctx, i + 1, "duplicate definition of function '%s'", name);
|
|
666
|
+
} else {
|
|
667
|
+
symbol_define(name, TYPE_FUNC, i + 1, 0, 0);
|
|
668
|
+
}
|
|
669
|
+
i++;
|
|
670
|
+
int depth = 1;
|
|
671
|
+
while (i + 1 < token_count && depth > 0) {
|
|
672
|
+
i++;
|
|
673
|
+
if (strcmp(tokens[i], "FUNC!") == 0 || strcmp(tokens[i], "IF?") == 0 ||
|
|
674
|
+
strcmp(tokens[i], "WHILE?") == 0 || strcmp(tokens[i], "FOR!") == 0 ||
|
|
675
|
+
strcmp(tokens[i], "TRY") == 0) depth++;
|
|
676
|
+
else if (strcmp(tokens[i], "END!") == 0) depth--;
|
|
677
|
+
}
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
for (int i = 0; i < token_count; i++) {
|
|
682
|
+
const char *t = tokens[i];
|
|
683
|
+
if (!t) continue;
|
|
684
|
+
/* Skip string and int literals */
|
|
685
|
+
if (token_types && (token_types[i] == TOK_STRING_LIT || token_types[i] == TOK_INT_LIT)) continue;
|
|
686
|
+
if (strcmp(t, "FUNC!") == 0 && i + 1 < token_count) {
|
|
687
|
+
i++;
|
|
688
|
+
int depth = 1;
|
|
689
|
+
while (i + 1 < token_count && depth > 0) {
|
|
690
|
+
i++;
|
|
691
|
+
if (strcmp(tokens[i], "FUNC!") == 0 || strcmp(tokens[i], "IF?") == 0 ||
|
|
692
|
+
strcmp(tokens[i], "WHILE?") == 0 || strcmp(tokens[i], "FOR!") == 0 ||
|
|
693
|
+
strcmp(tokens[i], "TRY") == 0) depth++;
|
|
694
|
+
else if (strcmp(tokens[i], "END!") == 0) depth--;
|
|
695
|
+
}
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (is_sema_keyword(t) || is_sema_builtin(t) || is_sema_literal_or_punct(t)) continue;
|
|
699
|
+
if (!symbol_lookup(t)) {
|
|
700
|
+
sema_ctx_error(ctx, i + 1, "'%s' is not defined", t);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return ctx->error_count;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* ============================================================================
|
|
707
|
+
* MAIN
|
|
708
|
+
* ============================================================================ */
|
|
709
|
+
|
|
710
|
+
int main(int argc, char** argv) {
|
|
711
|
+
if (argc < 2) {
|
|
712
|
+
fprintf(stderr, "Nebulara Pipeline v2.0\n");
|
|
713
|
+
fprintf(stderr, "Usage: %s <file.nbs> [--target js|py|check] [--check]\n", argv[0]);
|
|
714
|
+
return 1;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const char* target = "check";
|
|
718
|
+
const char* filename = NULL;
|
|
719
|
+
int do_check = 0;
|
|
720
|
+
for (int i = 1; i < argc; i++) {
|
|
721
|
+
if (strcmp(argv[i], "--check") == 0) { do_check = 1; }
|
|
722
|
+
else if (strcmp(argv[i], "--target") == 0 && i + 1 < argc) { target = argv[++i]; }
|
|
723
|
+
else { filename = argv[i]; }
|
|
724
|
+
}
|
|
725
|
+
if (!filename) { fprintf(stderr, "No file specified\n"); return 1; }
|
|
726
|
+
|
|
727
|
+
FILE* f = fopen(filename, "rb");
|
|
728
|
+
if (!f) { fprintf(stderr, "Error: cannot open '%s'\n", filename); return 1; }
|
|
729
|
+
fseek(f, 0, SEEK_END);
|
|
730
|
+
long len = ftell(f);
|
|
731
|
+
fseek(f, 0, SEEK_SET);
|
|
732
|
+
char* src = (char*)malloc(len + 1);
|
|
733
|
+
fread(src, 1, len, f);
|
|
734
|
+
src[len] = 0;
|
|
735
|
+
fclose(f);
|
|
736
|
+
|
|
737
|
+
/* Lex */
|
|
738
|
+
Lexer lexer = lexer_new(src);
|
|
739
|
+
Parser parser = {0};
|
|
740
|
+
while (1) {
|
|
741
|
+
NbsToken tok = lexer_next(&lexer);
|
|
742
|
+
parser.tokens[parser.count++] = tok;
|
|
743
|
+
if (tok.type == TOK_EOF) break;
|
|
744
|
+
if (parser.count >= 4096) { fprintf(stderr, "Error: too many tokens\n"); free(src); return 1; }
|
|
745
|
+
}
|
|
746
|
+
free(src);
|
|
747
|
+
|
|
748
|
+
/* Parse */
|
|
749
|
+
ASTNode* ast = parse_program(&parser);
|
|
750
|
+
|
|
751
|
+
/* Semantic analysis */
|
|
752
|
+
SemaContext *sema_ctx = sema_new_context();
|
|
753
|
+
const char **token_texts = (const char **)malloc(parser.count * sizeof(char *));
|
|
754
|
+
int *token_types = (int *)malloc(parser.count * sizeof(int));
|
|
755
|
+
for (int i = 0; i < parser.count; i++) {
|
|
756
|
+
token_texts[i] = parser.tokens[i].text;
|
|
757
|
+
token_types[i] = parser.tokens[i].type;
|
|
758
|
+
}
|
|
759
|
+
sema_analyze(sema_ctx, token_texts, token_types, parser.count);
|
|
760
|
+
free(token_texts);
|
|
761
|
+
free(token_types);
|
|
762
|
+
|
|
763
|
+
/* --check mode: analyze and report only */
|
|
764
|
+
if (do_check || strcmp(target, "check") == 0) {
|
|
765
|
+
sema_print_errors(sema_ctx);
|
|
766
|
+
int status = sema_has_errors(sema_ctx) ? 1 : 0;
|
|
767
|
+
sema_free_context(sema_ctx);
|
|
768
|
+
return status;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/* Transpile mode: abort if there are semantic errors */
|
|
772
|
+
if (sema_has_errors(sema_ctx)) {
|
|
773
|
+
sema_print_errors(sema_ctx);
|
|
774
|
+
fprintf(stderr, "Aborting transpilation due to semantic errors.\n");
|
|
775
|
+
sema_free_context(sema_ctx);
|
|
776
|
+
return 1;
|
|
777
|
+
}
|
|
778
|
+
sema_free_context(sema_ctx);
|
|
779
|
+
|
|
780
|
+
/* Output */
|
|
781
|
+
if (strcmp(target, "js") == 0) {
|
|
782
|
+
printf("%s", transpile_to_js(ast));
|
|
783
|
+
} else if (strcmp(target, "py") == 0) {
|
|
784
|
+
printf("%s", transpile_to_python(ast));
|
|
785
|
+
}
|
|
786
|
+
return 0;
|
|
787
|
+
}
|