REPL: Smarter multiline prompting by detecting open blocks via indentation, and when FUN_DEBUG is ON, append parse errors as comments to the history file.
This commit is contained in:
parent
08e7c9a623
commit
f31bf270bd
1 changed files with 133 additions and 4 deletions
137
src/fun.c
137
src/fun.c
|
|
@ -48,6 +48,78 @@ static int starts_with_kw(const char *s, const char *kw) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Compute how many indentation levels (2 spaces per level) are still open.
|
||||
* Only counts significant lines (non-blank, not comment-only), and ignores text inside /* ... *\/ block comments. */
|
||||
static int compute_open_indent_blocks(const char *buf) {
|
||||
int in_block_comment = 0;
|
||||
int open = 0;
|
||||
int have_baseline = 0;
|
||||
int cur = 0; /* current indent level in units of 2 spaces */
|
||||
|
||||
const char *p = buf;
|
||||
while (*p) {
|
||||
/* start of line */
|
||||
const char *line = p;
|
||||
/* find end of line */
|
||||
while (*p && *p != '\n') p++;
|
||||
const char *line_end = p;
|
||||
|
||||
/* advance p past newline if present */
|
||||
if (*p == '\n') p++;
|
||||
|
||||
if (in_block_comment) {
|
||||
/* look for end of block comment marker on this line */
|
||||
const char *q = line;
|
||||
while (q < line_end) {
|
||||
if (q + 1 < line_end && q[0] == '*' && q[1] == '/') { in_block_comment = 0; q += 2; break; }
|
||||
q++;
|
||||
}
|
||||
/* whole line is inside block comment -> skip */
|
||||
if (in_block_comment) continue;
|
||||
}
|
||||
|
||||
/* measure leading spaces */
|
||||
const char *s = line;
|
||||
int spaces = 0;
|
||||
while (s < line_end && *s == ' ') { spaces++; s++; }
|
||||
/* skip tabs for safety but don't treat as indent units */
|
||||
while (s < line_end && *s == '\t') { s++; }
|
||||
|
||||
/* detect line comments and block comment start */
|
||||
const char *t = s;
|
||||
/* blank line */
|
||||
if (t >= line_end) continue;
|
||||
|
||||
if ((t + 1) <= line_end && t[0] == '/' && (t + 1 < line_end && (t[1] == '/' || t[1] == '*'))) {
|
||||
if (t[1] == '/') {
|
||||
/* // comment-only line */
|
||||
continue;
|
||||
} else if (t[1] == '*') {
|
||||
/* /* start of block comment */
|
||||
in_block_comment = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* significant line */
|
||||
int lvl = spaces / 2; /* language uses 2 spaces per level */
|
||||
if (!have_baseline) {
|
||||
cur = lvl;
|
||||
have_baseline = 1;
|
||||
continue;
|
||||
}
|
||||
if (lvl > cur) {
|
||||
open += (lvl - cur);
|
||||
} else if (lvl < cur) {
|
||||
int dec = (cur - lvl);
|
||||
if (dec > open) open = 0;
|
||||
else open -= dec;
|
||||
}
|
||||
cur = lvl;
|
||||
}
|
||||
return open;
|
||||
}
|
||||
|
||||
/* Detect if current buffer looks incomplete:
|
||||
* - Unclosed quotes (' or ") with escapes
|
||||
* - Unclosed block comment /* ... *\/
|
||||
|
|
@ -315,7 +387,23 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
for (;;) {
|
||||
fputs(buflen == 0 ? "fun> " : "... ", stdout);
|
||||
/* ensure temporary null-termination for prompt analysis */
|
||||
if (buflen + 1 > bufcap) {
|
||||
size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
|
||||
while (newcap < buflen + 1) newcap *= 2;
|
||||
buffer = (char*)realloc(buffer, newcap);
|
||||
bufcap = newcap;
|
||||
}
|
||||
buffer[buflen] = '\0';
|
||||
int indent_debt = (buflen > 0) ? compute_open_indent_blocks(buffer) : 0;
|
||||
|
||||
if (buflen == 0) {
|
||||
fputs("fun> ", stdout);
|
||||
} else if (indent_debt > 0) {
|
||||
printf("... %d> ", indent_debt);
|
||||
} else {
|
||||
fputs("... ", stdout);
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
char line[4096];
|
||||
|
|
@ -428,8 +516,20 @@ int main(int argc, char **argv) {
|
|||
printf("\n");
|
||||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||
printf("^\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
printf("Parse error.\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR: parse error\n");
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
buflen = 0;
|
||||
|
|
@ -504,8 +604,20 @@ int main(int argc, char **argv) {
|
|||
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);
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
printf("Parse error.\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR: parse error\n");
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
buflen = 0;
|
||||
|
|
@ -565,9 +677,14 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
buffer[buflen] = '\0';
|
||||
|
||||
/* If buffer looks incomplete, keep reading */
|
||||
if (buffer_looks_incomplete(buffer)) {
|
||||
printf("(incomplete, continue typing)\n");
|
||||
/* If buffer looks incomplete or has open indent blocks, keep reading */
|
||||
int indent_debt = compute_open_indent_blocks(buffer);
|
||||
if (buffer_looks_incomplete(buffer) || indent_debt > 0) {
|
||||
if (indent_debt > 0) {
|
||||
printf("(incomplete, open block indent +%d)\n", indent_debt);
|
||||
} else {
|
||||
printf("(incomplete, continue typing)\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -604,8 +721,20 @@ int main(int argc, char **argv) {
|
|||
printf("\n");
|
||||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||
printf("^\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
printf("Parse error.\n");
|
||||
#ifdef FUN_DEBUG
|
||||
if (hist) {
|
||||
fprintf(hist, "// ERROR: parse error\n");
|
||||
fflush(hist);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// reset buffer
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue