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;
|
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:
|
/* Detect if current buffer looks incomplete:
|
||||||
* - Unclosed quotes (' or ") with escapes
|
* - Unclosed quotes (' or ") with escapes
|
||||||
* - Unclosed block comment /* ... *\/
|
* - Unclosed block comment /* ... *\/
|
||||||
|
|
@ -315,7 +387,23 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
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);
|
fflush(stdout);
|
||||||
|
|
||||||
char line[4096];
|
char line[4096];
|
||||||
|
|
@ -428,8 +516,20 @@ int main(int argc, char **argv) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||||
printf("^\n");
|
printf("^\n");
|
||||||
|
#ifdef FUN_DEBUG
|
||||||
|
if (hist) {
|
||||||
|
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||||
|
fflush(hist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
printf("Parse error.\n");
|
printf("Parse error.\n");
|
||||||
|
#ifdef FUN_DEBUG
|
||||||
|
if (hist) {
|
||||||
|
fprintf(hist, "// ERROR: parse error\n");
|
||||||
|
fflush(hist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buflen = 0;
|
buflen = 0;
|
||||||
|
|
@ -504,8 +604,20 @@ int main(int argc, char **argv) {
|
||||||
char emsg[256];
|
char emsg[256];
|
||||||
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
|
||||||
printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
|
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 {
|
} else {
|
||||||
printf("Parse error.\n");
|
printf("Parse error.\n");
|
||||||
|
#ifdef FUN_DEBUG
|
||||||
|
if (hist) {
|
||||||
|
fprintf(hist, "// ERROR: parse error\n");
|
||||||
|
fflush(hist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buflen = 0;
|
buflen = 0;
|
||||||
|
|
@ -565,9 +677,14 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
buffer[buflen] = '\0';
|
buffer[buflen] = '\0';
|
||||||
|
|
||||||
/* If buffer looks incomplete, keep reading */
|
/* If buffer looks incomplete or has open indent blocks, keep reading */
|
||||||
if (buffer_looks_incomplete(buffer)) {
|
int indent_debt = compute_open_indent_blocks(buffer);
|
||||||
printf("(incomplete, continue typing)\n");
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -604,8 +721,20 @@ int main(int argc, char **argv) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
for (int i = 1; i < col_no; ++i) putchar(' ');
|
for (int i = 1; i < col_no; ++i) putchar(' ');
|
||||||
printf("^\n");
|
printf("^\n");
|
||||||
|
#ifdef FUN_DEBUG
|
||||||
|
if (hist) {
|
||||||
|
fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
|
||||||
|
fflush(hist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
printf("Parse error.\n");
|
printf("Parse error.\n");
|
||||||
|
#ifdef FUN_DEBUG
|
||||||
|
if (hist) {
|
||||||
|
fprintf(hist, "// ERROR: parse error\n");
|
||||||
|
fflush(hist);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// reset buffer
|
// reset buffer
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue