More REPL enhancements. Type :help in the REPL to get more information.
This commit is contained in:
parent
0704640893
commit
08e7c9a623
5 changed files with 168 additions and 9 deletions
144
src/fun.c
144
src/fun.c
|
|
@ -155,13 +155,18 @@ static void show_repl_help(void) {
|
|||
printf(" :quit | :q | :exit Exit the REPL\n");
|
||||
printf(" :reset Reset VM state (clears globals)\n");
|
||||
printf(" :dump | :globals Dump current globals\n");
|
||||
printf(" :globals [pattern] Dump globals filtering by value substring\n");
|
||||
printf(" :vars [pattern] Alias for :globals\n");
|
||||
printf(" :clear Clear current input buffer\n");
|
||||
printf(" :print Show current buffer\n");
|
||||
printf(" :run Execute current buffer immediately\n");
|
||||
printf(" :profile Execute buffer and show timing + instruction count\n");
|
||||
printf(" :save <file> Save current buffer to file\n");
|
||||
printf(" :load <file> Load file into buffer (does not run)\n");
|
||||
printf(" :paste [run] Enter paste mode; end with a single '.' line (optional 'run')\n");
|
||||
printf(" :history [N] Show last N lines of history (default 50)\n");
|
||||
printf(" :time on|off|toggle Toggle/enable/disable timing\n");
|
||||
printf(" :env [NAME[=VALUE]] Get or set environment variable\n");
|
||||
}
|
||||
|
||||
static char *read_entire_file(const char *path, size_t *out_len) {
|
||||
|
|
@ -216,6 +221,32 @@ static void append_history(FILE *hist, const char *buffer) {
|
|||
fflush(hist);
|
||||
}
|
||||
|
||||
/* ---------- Env helpers ---------- */
|
||||
static void env_show_usage(void) {
|
||||
printf("Usage:\n");
|
||||
printf(" :env NAME Show environment variable NAME\n");
|
||||
printf(" :env NAME=VALUE Set environment variable NAME to VALUE\n");
|
||||
printf(" :env Show this usage\n");
|
||||
}
|
||||
|
||||
static void env_get(const char *name) {
|
||||
const char *v = getenv(name);
|
||||
if (v) printf("%s=%s\n", name, v);
|
||||
else printf("%s is not set\n", name);
|
||||
}
|
||||
|
||||
static void env_set(const char *name, const char *value) {
|
||||
#ifdef _WIN32
|
||||
if (_putenv_s(name, value ? value : "") != 0) {
|
||||
printf("Failed to set %s\n", name);
|
||||
}
|
||||
#else
|
||||
if (setenv(name, value ? value : "", 1) != 0) {
|
||||
printf("Failed to set %s\n", name);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---------- Main ---------- */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
|
@ -297,8 +328,8 @@ int main(int argc, char **argv) {
|
|||
if (line[0] == ':') {
|
||||
/* extract command and argument */
|
||||
char cmd[64] = {0};
|
||||
char arg[1024] = {0};
|
||||
sscanf(line, ":%63s %1023[^\n]", cmd, arg);
|
||||
char arg[2048] = {0};
|
||||
sscanf(line, ":%63s %2047[^\n]", cmd, arg);
|
||||
|
||||
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "q") == 0 || strcmp(cmd, "exit") == 0) {
|
||||
break;
|
||||
|
|
@ -309,9 +340,24 @@ int main(int argc, char **argv) {
|
|||
vm_reset(&vm);
|
||||
printf("VM state reset.\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "dump") == 0 || strcmp(cmd, "globals") == 0) {
|
||||
} else if (strcmp(cmd, "dump") == 0) {
|
||||
vm_dump_globals(&vm);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "globals") == 0 || strcmp(cmd, "vars") == 0) {
|
||||
const char *pattern = lstrip(arg);
|
||||
int filtered = (pattern && *pattern);
|
||||
printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : "");
|
||||
if (filtered) printf("'\n");
|
||||
for (int i = 0; i < VM_MAX_GLOBALS; ++i) {
|
||||
if (vm.globals[i].type == VAL_NIL) continue;
|
||||
char *sv = value_to_string_alloc(&vm.globals[i]);
|
||||
if (!filtered || (sv && strstr(sv, pattern))) {
|
||||
printf("[%d] %s\n", i, sv ? sv : "nil");
|
||||
}
|
||||
free(sv);
|
||||
}
|
||||
printf("===============\n");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "clear") == 0) {
|
||||
buflen = 0;
|
||||
printf("(buffer cleared)\n");
|
||||
|
|
@ -329,7 +375,7 @@ int main(int argc, char **argv) {
|
|||
if (buflen > 0 && buffer[buflen-1] != '\n') printf("\n");
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "run") == 0) {
|
||||
} else if (strcmp(cmd, "run") == 0 || strcmp(cmd, "profile") == 0) {
|
||||
if (buflen == 0) {
|
||||
printf("(buffer empty)\n");
|
||||
continue;
|
||||
|
|
@ -341,16 +387,26 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
buffer[buflen] = '\0';
|
||||
|
||||
clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0;
|
||||
if (strcmp(cmd, "profile") == 0) t_parse0 = clock();
|
||||
Bytecode *bc = parse_string_to_bytecode(buffer);
|
||||
if (strcmp(cmd, "profile") == 0) t_parse1 = clock();
|
||||
|
||||
if (bc) {
|
||||
clock_t t0 = 0, t1 = 0;
|
||||
if (repl_timing) t0 = clock();
|
||||
if (repl_timing || strcmp(cmd, "profile") == 0) t_run0 = clock();
|
||||
vm_run(&vm, bc);
|
||||
if (repl_timing) {
|
||||
t1 = clock();
|
||||
double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
if (repl_timing || strcmp(cmd, "profile") == 0) t_run1 = clock();
|
||||
|
||||
if (strcmp(cmd, "profile") == 0) {
|
||||
double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
double ms_run = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n",
|
||||
ms_parse, ms_run, ms_parse + ms_run, vm.instr_count);
|
||||
} else if (repl_timing) {
|
||||
double ms = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
printf("[time] %.2f ms\n", ms);
|
||||
}
|
||||
|
||||
vm_print_output(&vm);
|
||||
vm_clear_output(&vm);
|
||||
bytecode_free(bc);
|
||||
|
|
@ -406,6 +462,57 @@ int main(int argc, char **argv) {
|
|||
free(filebuf);
|
||||
printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
|
||||
continue;
|
||||
} else if (strcmp(cmd, "paste") == 0) {
|
||||
int run_after = 0;
|
||||
const char *opt = lstrip(arg);
|
||||
if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1;
|
||||
printf("(paste mode: end with single '.' line)%s\n", run_after ? " [will run]" : "");
|
||||
for (;;) {
|
||||
fputs("... paste> ", stdout);
|
||||
fflush(stdout);
|
||||
char pline[8192];
|
||||
if (!fgets(pline, sizeof(pline), stdin)) { puts(""); break; }
|
||||
if ((strcmp(pline, ".\n") == 0) || (strcmp(pline, ".\r\n") == 0) || (strcmp(pline, ".") == 0)) {
|
||||
break;
|
||||
}
|
||||
size_t pl = strlen(pline);
|
||||
if (buflen + pl + 1 > bufcap) {
|
||||
size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
|
||||
while (newcap < buflen + pl + 1) newcap *= 2;
|
||||
buffer = (char*)realloc(buffer, newcap);
|
||||
bufcap = newcap;
|
||||
}
|
||||
memcpy(buffer + buflen, pline, pl);
|
||||
buflen += pl;
|
||||
}
|
||||
if (run_after) {
|
||||
if (buflen + 1 > bufcap) { buffer = (char*)realloc(buffer, buflen + 1); bufcap = buflen + 1; }
|
||||
buffer[buflen] = '\0';
|
||||
Bytecode *bc = parse_string_to_bytecode(buffer);
|
||||
if (bc) {
|
||||
clock_t t0 = clock();
|
||||
vm_run(&vm, bc);
|
||||
clock_t t1 = clock();
|
||||
double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
|
||||
printf("[time] %.2f ms\n", ms);
|
||||
vm_print_output(&vm);
|
||||
vm_clear_output(&vm);
|
||||
bytecode_free(bc);
|
||||
append_history(hist, buffer);
|
||||
} else {
|
||||
int line_no = 0, col_no = 0;
|
||||
char emsg[256];
|
||||
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
||||
printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
|
||||
} else {
|
||||
printf("Parse error.\n");
|
||||
}
|
||||
}
|
||||
buflen = 0;
|
||||
} else {
|
||||
printf("(pasted %zu bytes into buffer)\n", buflen);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "history") == 0) {
|
||||
int n = 50;
|
||||
if (arg[0] != '\0') n = atoi(arg);
|
||||
|
|
@ -422,6 +529,25 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
printf("Timing %s\n", repl_timing ? "enabled" : "disabled");
|
||||
continue;
|
||||
} else if (strcmp(cmd, "env") == 0) {
|
||||
const char *spec = lstrip(arg);
|
||||
if (!spec || *spec == '\0') {
|
||||
env_show_usage();
|
||||
continue;
|
||||
}
|
||||
const char *eq = strchr(spec, '=');
|
||||
if (!eq) {
|
||||
env_get(spec);
|
||||
} else {
|
||||
char name[256];
|
||||
size_t nlen = (size_t)(eq - spec);
|
||||
if (nlen >= sizeof(name)) nlen = sizeof(name) - 1;
|
||||
memcpy(name, spec, nlen);
|
||||
name[nlen] = '\0';
|
||||
const char *val = eq + 1;
|
||||
env_set(name, val);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
printf("Unknown command. Use :help\n");
|
||||
continue;
|
||||
|
|
|
|||
22
src/value.c
22
src/value.c
|
|
@ -90,3 +90,25 @@ int value_is_truthy(const Value *v) {
|
|||
}
|
||||
}
|
||||
|
||||
/* allocate a printable C string for the value; caller must free */
|
||||
char *value_to_string_alloc(const Value *v) {
|
||||
if (!v) return strdup("nil");
|
||||
char buf[128];
|
||||
switch (v->type) {
|
||||
case VAL_INT: {
|
||||
char tmp[64];
|
||||
snprintf(tmp, sizeof(tmp), "%" PRId64, v->i);
|
||||
return strdup(tmp);
|
||||
}
|
||||
case VAL_STRING:
|
||||
return strdup(v->s ? v->s : "");
|
||||
case VAL_FUNCTION: {
|
||||
snprintf(buf, sizeof(buf), "<function@%p>", (void*)v->fn);
|
||||
return strdup(buf);
|
||||
}
|
||||
case VAL_NIL:
|
||||
default:
|
||||
return strdup("nil");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,5 +35,8 @@ void free_value(Value v);
|
|||
void print_value(const Value *v);
|
||||
int value_is_truthy(const Value *v);
|
||||
|
||||
/* stringify into a newly-allocated C string; caller must free */
|
||||
char *value_to_string_alloc(const Value *v);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
6
src/vm.c
6
src/vm.c
|
|
@ -83,6 +83,7 @@ void vm_init(VM *vm) {
|
|||
vm->sp = -1;
|
||||
vm->fp = -1;
|
||||
vm->output_count = 0;
|
||||
vm->instr_count = 0;
|
||||
for (int i = 0; i < VM_MAX_GLOBALS; ++i)
|
||||
vm->globals[i] = make_nil();
|
||||
}
|
||||
|
|
@ -125,6 +126,9 @@ void vm_print_output(VM *vm) {
|
|||
}
|
||||
|
||||
void vm_run(VM *vm, Bytecode *entry) {
|
||||
/* reset instruction count for this run */
|
||||
vm->instr_count = 0;
|
||||
|
||||
/* start with entry frame (no args) */
|
||||
vm_push_frame(vm, entry, 0, NULL);
|
||||
|
||||
|
|
@ -140,6 +144,8 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
}
|
||||
|
||||
Instruction inst = f->fn->instructions[f->ip++];
|
||||
vm->instr_count++; /* count each executed instruction */
|
||||
|
||||
switch (inst.op) {
|
||||
case OP_NOP:
|
||||
break;
|
||||
|
|
|
|||
2
src/vm.h
2
src/vm.h
|
|
@ -34,6 +34,8 @@ typedef struct {
|
|||
|
||||
Value output[VM_OUTPUT_SIZE]; // store printed values
|
||||
int output_count;
|
||||
|
||||
long long instr_count; // executed instructions in the last vm_run
|
||||
} VM;
|
||||
|
||||
// initialize VM (zero state)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue