Skip to content

Commit 9eb6fe6

Browse files
gh-24: Add REPL.
1 parent 12ae263 commit 9eb6fe6

3 files changed

Lines changed: 236 additions & 29 deletions

File tree

src/interpreter.c

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,45 +2289,66 @@ static ExecResult exec_stmt_list(Interpreter* interp, StmtList* list, Env* env,
22892289

22902290
// ============ Main entry point ============
22912291

2292-
ExecResult exec_program(Stmt* program, const char* source_path) {
2293-
Interpreter interp = {0};
2294-
interp.global_env = env_create(NULL);
2295-
interp.loop_depth = 0;
2296-
interp.error = NULL;
2297-
interp.in_try_block = false;
2298-
interp.modules = NULL;
2299-
interp.current_thr = NULL;
2292+
void interpreter_init(Interpreter* interp, const char* source_path) {
2293+
if (!interp) return;
2294+
memset(interp, 0, sizeof(*interp));
2295+
interp->global_env = env_create(NULL);
2296+
interp->loop_depth = 0;
2297+
interp->error = NULL;
2298+
interp->in_try_block = false;
2299+
interp->modules = NULL;
2300+
interp->current_thr = NULL;
23002301

23012302
builtins_init();
23022303
mtx_init(&g_tns_lock, 0);
23032304
mtx_init(&g_parfor_merge_lock, 0);
23042305
ns_buffer_init();
2305-
// Record source path of the primary program so imports and MAIN() work
2306+
23062307
if (source_path && source_path[0] != '\0') {
2307-
env_assign(interp.global_env, "__MODULE_SOURCE__", value_str(source_path), TYPE_STR, true);
2308+
env_assign(interp->global_env, "__MODULE_SOURCE__", value_str(source_path), TYPE_STR, true);
23082309
}
2309-
2310-
LabelMap labels = {0};
2311-
ExecResult res = exec_stmt_list(&interp, &program->as.block, interp.global_env, &labels);
2312-
2313-
// Clean up
2314-
for (size_t i = 0; i < labels.count; i++) value_free(labels.items[i].key);
2315-
free(labels.items);
2316-
2317-
env_free(interp.global_env);
2318-
// Free modules
2319-
ModuleEntry* me = interp.modules;
2310+
}
2311+
2312+
void interpreter_destroy(Interpreter* interp) {
2313+
if (!interp) return;
2314+
2315+
if (interp->global_env) {
2316+
env_free(interp->global_env);
2317+
interp->global_env = NULL;
2318+
}
2319+
2320+
ModuleEntry* me = interp->modules;
23202321
while (me) {
23212322
ModuleEntry* next = me->next;
23222323
free(me->name);
23232324
if (me->owns_env) env_free(me->env);
23242325
free(me);
23252326
me = next;
23262327
}
2327-
2328+
interp->modules = NULL;
2329+
2330+
if (interp->error) {
2331+
free(interp->error);
2332+
interp->error = NULL;
2333+
}
2334+
23282335
ns_buffer_shutdown();
23292336
mtx_destroy(&g_tns_lock);
23302337
mtx_destroy(&g_parfor_merge_lock);
2338+
}
2339+
2340+
ExecResult exec_program(Stmt* program, const char* source_path) {
2341+
Interpreter interp;
2342+
interpreter_init(&interp, source_path);
2343+
2344+
LabelMap labels = {0};
2345+
ExecResult res = exec_stmt_list(&interp, &program->as.block, interp.global_env, &labels);
2346+
2347+
// Clean up
2348+
for (size_t i = 0; i < labels.count; i++) value_free(labels.items[i].key);
2349+
free(labels.items);
2350+
2351+
interpreter_destroy(&interp);
23312352
return res;
23322353
}
23332354

src/interpreter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ typedef struct Interpreter {
8080
bool isolate_env_writes;
8181
} Interpreter;
8282

83+
// Initialize/destroy a reusable interpreter session.
84+
// `source_path` sets the primary module source label (e.g. script path or "<repl>").
85+
void interpreter_init(Interpreter* interp, const char* source_path);
86+
void interpreter_destroy(Interpreter* interp);
87+
8388
// Main entry point
8489
ExecResult exec_program(Stmt* program, const char* source_path);
8590

src/main.c

Lines changed: 188 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,190 @@ static int load_extension_input(const char* arg, char** err_out) {
9494
return extensions_load_library(arg, NULL, err_out);
9595
}
9696

97+
static int buf_append(char** buf, size_t* len, size_t* cap, const char* s) {
98+
if (!buf || !len || !cap || !s) return -1;
99+
size_t add = strlen(s);
100+
if (*len + add + 1 > *cap) {
101+
size_t new_cap = (*cap == 0) ? 256 : *cap;
102+
while (*len + add + 1 > new_cap) new_cap *= 2;
103+
char* next = realloc(*buf, new_cap);
104+
if (!next) return -1;
105+
*buf = next;
106+
*cap = new_cap;
107+
}
108+
memcpy(*buf + *len, s, add);
109+
*len += add;
110+
(*buf)[*len] = '\0';
111+
return 0;
112+
}
113+
114+
static char* read_line_dynamic(FILE* in) {
115+
if (!in) return NULL;
116+
char* line = NULL;
117+
size_t len = 0;
118+
size_t cap = 0;
119+
char chunk[512];
120+
121+
while (fgets(chunk, sizeof(chunk), in)) {
122+
if (buf_append(&line, &len, &cap, chunk) != 0) {
123+
free(line);
124+
return NULL;
125+
}
126+
size_t n = strlen(chunk);
127+
if (n > 0 && chunk[n - 1] == '\n') break;
128+
}
129+
130+
if (len == 0 && feof(in)) {
131+
free(line);
132+
return NULL;
133+
}
134+
135+
if (!line) {
136+
line = strdup("");
137+
}
138+
return line;
139+
}
140+
141+
static int is_exit_meta_command(const char* text) {
142+
if (!text) return 0;
143+
const char* p = text;
144+
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
145+
if (strncmp(p, ".exit", 5) != 0) return 0;
146+
p += 5;
147+
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
148+
return *p == '\0';
149+
}
150+
151+
static void repl_update_line_state(const char* line, int* brace_depth, int* line_continuation) {
152+
if (!line || !brace_depth || !line_continuation) return;
153+
154+
int in_single = 0;
155+
int in_double = 0;
156+
int escaped = 0;
157+
size_t comment_pos = strlen(line);
158+
159+
for (size_t i = 0; line[i] != '\0'; i++) {
160+
char c = line[i];
161+
if (escaped) {
162+
escaped = 0;
163+
continue;
164+
}
165+
if (in_single) {
166+
if (c == '\\') escaped = 1;
167+
else if (c == '\'') in_single = 0;
168+
continue;
169+
}
170+
if (in_double) {
171+
if (c == '\\') escaped = 1;
172+
else if (c == '"') in_double = 0;
173+
continue;
174+
}
175+
176+
if (c == '!') {
177+
comment_pos = i;
178+
break;
179+
}
180+
if (c == '\'') {
181+
in_single = 1;
182+
continue;
183+
}
184+
if (c == '"') {
185+
in_double = 1;
186+
continue;
187+
}
188+
if (c == '{') {
189+
(*brace_depth)++;
190+
} else if (c == '}' && *brace_depth > 0) {
191+
(*brace_depth)--;
192+
}
193+
}
194+
195+
size_t end = comment_pos;
196+
while (end > 0) {
197+
char c = line[end - 1];
198+
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
199+
end--;
200+
continue;
201+
}
202+
break;
203+
}
204+
*line_continuation = (end > 0 && line[end - 1] == '^') ? 1 : 0;
205+
}
206+
207+
static int run_repl(void) {
208+
Interpreter interp;
209+
interpreter_init(&interp, "<repl>");
210+
211+
char* entry = NULL;
212+
size_t entry_len = 0;
213+
size_t entry_cap = 0;
214+
int brace_depth = 0;
215+
int line_continuation = 0;
216+
fprintf(stdout, "\x1b[38;2;153;221;255mPrefix REPL. Enter statements, blank line to run buffer.\033[0m\n");
217+
218+
for (;;) {
219+
int in_continuation = (brace_depth > 0) || line_continuation;
220+
fputs(in_continuation ? "\x1b[38;2;153;221;255m..>\033[0m " : "\x1b[38;2;153;221;255m>>>\033[0m ", stdout);
221+
fflush(stdout);
222+
223+
char* line = read_line_dynamic(stdin);
224+
int eof = (line == NULL);
225+
226+
if (!eof) {
227+
if (buf_append(&entry, &entry_len, &entry_cap, line) != 0) {
228+
free(line);
229+
free(entry);
230+
interpreter_destroy(&interp);
231+
fprintf(stderr, "Out of memory\n");
232+
return PREFIX_ERROR_MEMORY;
233+
}
234+
repl_update_line_state(line, &brace_depth, &line_continuation);
235+
free(line);
236+
}
237+
238+
if (!eof && ((brace_depth > 0) || line_continuation)) {
239+
continue;
240+
}
241+
242+
if (entry_len == 0) {
243+
if (eof) break;
244+
continue;
245+
}
246+
247+
if (is_exit_meta_command(entry)) {
248+
break;
249+
}
250+
251+
Lexer lex;
252+
lexer_init(&lex, entry, "<repl>");
253+
254+
Parser parser;
255+
parser_init(&parser, &lex);
256+
Stmt* program = parser_parse(&parser);
257+
258+
if (!parser.had_error) {
259+
ExecResult res = exec_program_in_env(&interp, program, interp.global_env);
260+
if (res.status == EXEC_ERROR) {
261+
fprintf(stderr, "Runtime error: %s at %d:%d\n",
262+
res.error ? res.error : "error",
263+
res.error_line,
264+
res.error_column);
265+
}
266+
}
267+
268+
entry_len = 0;
269+
if (entry) entry[0] = '\0';
270+
brace_depth = 0;
271+
line_continuation = 0;
272+
273+
if (eof) break;
274+
}
275+
276+
free(entry);
277+
interpreter_destroy(&interp);
278+
return PREFIX_SUCCESS;
279+
}
280+
97281
int main(int argc, char** argv) {
98282
const char* path = NULL;
99283
int verbose_flag = 0;
@@ -232,13 +416,10 @@ int main(int argc, char** argv) {
232416
}
233417

234418
if (!path) {
235-
if (explicit_ext_count > 0) {
236-
fprintf(stderr, "No program provided. REPL mode is not implemented in this build.\n");
237-
extensions_shutdown();
238-
builtins_reset_dynamic();
239-
return PREFIX_SUCCESS;
240-
}
241-
path = "../test.pre";
419+
int repl_rc = run_repl();
420+
extensions_shutdown();
421+
builtins_reset_dynamic();
422+
return repl_rc;
242423
}
243424

244425
(void)verbose_flag;

0 commit comments

Comments
 (0)