浏览代码

feat: 使用Win-Console实现stdin输入

SongZihuan 3 年之前
父节点
当前提交
a31fb0b36c
共有 6 个文件被更改,包括 295 次插入39 次删除
  1. 1 0
      include/main.h
  2. 5 2
      include/tool/stdio_.h
  3. 4 2
      src/core/parser.c
  4. 5 7
      src/main.c
  5. 1 0
      src/runtime/aFunlang.c
  6. 279 28
      src/tool/stdio_.c

+ 1 - 0
include/main.h

@@ -3,6 +3,7 @@
 
 #ifdef aFunWIN32_NO_CYGWIN
 #include <io.h>
+#define fileno _fileno
 #define isatty _isatty
 #else
 #include "unistd.h"

+ 5 - 2
include/tool/stdio_.h

@@ -4,12 +4,14 @@
 
 AFUN_TOOL_EXPORT int fgets_stdin(char **dest, int len);
 AFUN_TOOL_EXPORT bool checkStdin(void);
+AFUN_TOOL_EXPORT bool fclear_stdin(void);
 
 #define CLEAR_FERROR(file) (ferror(file) && (clearerr(file), ferror(file)))  /* 出现错误后尝试修复, 并再次检查 */
 #define CLEAR_STDIN(file) ((ferror(stdin) || feof(stdin)) && (clearerr(stdin), (ferror(stdin) || feof(stdin))))
 
 #ifdef aFunWIN32_NO_CYGWIN
-AFUN_TOOL_EXPORT int fgetchar_stdin(void);
+AFUN_TOOL_EXPORT int fgetc_stdin(void);
+AFUN_TOOL_EXPORT char *fgets_stdin_(char *buf, size_t len);
 AFUN_TOOL_EXPORT int fungetc_stdin(int ch);
 
 AFUN_TOOL_EXPORT int fputs_stdout(char *str);
@@ -21,7 +23,8 @@ AFUN_TOOL_EXPORT size_t printf_stdout(size_t buf_len, char *format, ...);
 AFUN_TOOL_EXPORT size_t printf_stderr(size_t buf_len, char *format, ...);
 
 #else
-#define fgetchar_stdin() fgetc(stdin)
+#define fgetc_stdin() fgetc(stdin)
+#define fgets_stdin_(buf, len, stream) fgets(buf, len, stream)
 #define fungetc_stdin(ch) ungetc((ch), stdin)
 
 #define fputs_stdout(str) fputs((str), stdout)

+ 4 - 2
src/core/parser.c

@@ -192,8 +192,10 @@ static size_t readFuncStdin(struct readerDataStdin *data, char *dest, size_t len
 
         if (data->no_first)
             fputs("\r.... ", stdout);
-        else
+        else {
+            fclear_stdin();
             fputs("\r>>>> ", stdout);
+        }
         fflush(stdout);
         data->no_first = true;
         free(data->data);
@@ -208,7 +210,7 @@ static size_t readFuncStdin(struct readerDataStdin *data, char *dest, size_t len
             }
         }
 
-        int ch = fgetchar_stdin();
+        int ch = fgetc_stdin();
         if (ferror(stdin) || feof(stdin)) {  // 被中断
             clearerr(stdin);
             resetStdinSignalFunc();

+ 5 - 7
src/main.c

@@ -181,11 +181,10 @@ static int mainRun(ff_FFlags *ff) {
         do {
             if (ferror(stdin) || feof(stdin)) {  // 错误应在实际程序中处理, 若在此仍处于错误状态则直接返回
                 writeErrorLog(aFunlangLogger, "stdin error/eof");
-                exit_code = -1;
                 break;
             }
-            exit_code = runCodeFromStdin("stdin", env);
-        } while (exit_code == 0 && isCoreExit(env) != 1);  // exit_code == -1 表示stdin出现错误
+            runCodeFromStdin("stdin", env);
+        } while (isCoreExit(env) != 1);
         exit_code = getCoreExitCode(env);
         destructAFunEnvironment(env);
     } else {
@@ -280,18 +279,17 @@ static int mainCL(ff_FFlags *ff) {
     defineRunEnv(&ri);  // 由aFunCore提前接管
 
     if (rl != NULL)
-        exit_code = runCodeFromRunList(rl, NULL, save_aub, env);
+        runCodeFromRunList(rl, NULL, save_aub, env);
 
     if (tty_stdin && command_line && isCoreExit(env) != 1) {
         printWelcomeInfo();
         do {
             if (ferror(stdin) || feof(stdin)) {  // 错误应在实际程序中处理, 若在此仍处于错误状态则直接返回
                 writeErrorLog(aFunlangLogger, "stdin error/eof");
-                exit_code = -1;
                 break;
             }
-            exit_code = runCodeFromStdin("stdin", env);
-        } while (exit_code == 0 && isCoreExit(env) != 1);
+            runCodeFromStdin("stdin", env);
+        } while (isCoreExit(env) != 1);
     }
 
     exit_code = getCoreExitCode(env);

+ 1 - 0
src/runtime/aFunlang.c

@@ -4,6 +4,7 @@
 
 #ifdef aFunWIN32_NO_CYGWIN
 #include <io.h>
+#define fileno _fileno
 #define isatty _isatty
 #else
 #include "unistd.h"

+ 279 - 28
src/tool/stdio_.c

@@ -26,7 +26,242 @@
 // 获取CodePage, 并将内存中utf-8字符串转换为对应编码输出
 // cygwin环境下, 终端默认为uft-8
 
-static bool stdin_empty = true;  // stdin读取内容遇到 \n, 用于检测stdin是否为空
+#define BUFF_SIZE (40960)
+static char buffer[BUFF_SIZE] = "";
+static size_t index = 0;
+static size_t next = 0;
+static size_t end = 0;
+
+static int setCursorPosition(HANDLE std_o, CONSOLE_SCREEN_BUFFER_INFO *info_, SHORT x_) {
+    CONSOLE_SCREEN_BUFFER_INFO info;
+    if (info_ == NULL) {
+        if (!GetConsoleScreenBufferInfo(std_o, &info))
+            return -1;
+        info_ = &info;
+    }
+    int x = info_->dwCursorPosition.X;
+    int y = info_->dwCursorPosition.Y;
+
+    x += x_;
+    while (x >= info_->dwSize.X) {
+        x -= info_->dwSize.X;
+        y++;
+    }
+
+    while (x < 0) {
+        x += info_->dwSize.X;
+        y--;
+    }
+
+    if (y < 0)
+        y = 0;
+    else if (y > info_->dwSize.Y)
+        y = info_->dwSize.Y;
+
+    SetConsoleCursorPosition(std_o, (COORD){.X=(SHORT)x, .Y=(SHORT)y});
+    return 1;
+}
+
+static int nextToEnd(HANDLE *std_o) {
+    CONSOLE_SCREEN_BUFFER_INFO info;
+    if (!GetConsoleScreenBufferInfo(std_o, &info))
+        return 0;
+    if (setCursorPosition(std_o, &info, (SHORT)(end - next)) == -1)
+        return 0;
+    next = end;
+    return 1;
+}
+
+static int moveBuffer(void) {
+    if (index == 0)
+        return 0;
+    memmove(buffer, buffer + index, BUFF_SIZE - index);
+    end = end - index;
+    next = next - index;
+    index = 0;
+    memset(buffer + end, 0, BUFF_SIZE - end);
+    return 1;
+}
+
+static int backChar(HANDLE std_o) {
+    if (index != next) {  // 删除一个字符
+        if (setCursorPosition(std_o, NULL, -1) == -1)  // 先一定位置在-1
+            return 0;
+
+        CONSOLE_SCREEN_BUFFER_INFO info;
+        if (!GetConsoleScreenBufferInfo(std_o, &info))
+            return 0;
+        memmove(buffer + next - 1, buffer + next, end - next + 1);
+
+        SetConsoleCursorPosition(std_o, info.dwCursorPosition);
+        for (size_t n = next - 1; n < end; n++)
+            fputc(' ', stdout);
+
+        SetConsoleCursorPosition(std_o, info.dwCursorPosition);
+        fputs(buffer + next - 1, stdout);
+
+        SetConsoleCursorPosition(std_o, info.dwCursorPosition);
+        next--;
+        end--;
+    }
+    return 1;
+}
+
+static int enterChar(HANDLE std_o) {
+    if(!nextToEnd(std_o))
+        return 0;
+    buffer[end] = '\n';
+    end++;
+    next++;
+    fputc('\n', stdout);
+    return 1;
+}
+
+/*
+ * 函数名: newChar
+ * 目标: 记录字符并显示
+ * 返回1表示成功
+ * 返回0表示失败
+ */
+static int newChar(HANDLE std_i, char ch) {
+    if (ch == 0)
+        return 1;
+    if (end == BUFF_SIZE && !moveBuffer())  // 对比 end 而不是 next
+        return 0;
+
+    if (next != end) {  // insert 模式
+        CONSOLE_SCREEN_BUFFER_INFO info;
+        if (!GetConsoleScreenBufferInfo(std_i, &info))
+            return 0;
+        memmove(buffer + next + 1, buffer + next, end - next);
+        buffer[next] = ch;
+        fputs(buffer + next, stdout);
+        if (setCursorPosition(std_i, &info, 1) == -1)
+            return 0;
+    } else {
+        buffer[next] = ch;
+        fputc(ch, stdout);
+    }
+
+    next++;
+    end++;
+    return 1;
+}
+
+/*
+ * 函数名: checkNewInput
+ * 目标: 获取输入并且显示
+ * 返回-1表示遭遇错误
+ * 返回0表示未完成行读取
+ * 返回1表示完成行读取
+ */
+static int checkNewInput(HANDLE *std_i, HANDLE *std_o) {
+    DWORD len = 0;
+    DWORD oldm;
+    if (!GetConsoleMode(std_i, &oldm))
+        return -1;
+
+    if (!GetNumberOfConsoleInputEvents(std_i, &len))
+        return -1;
+
+    for (int i = 0; i < len; i++) {
+        INPUT_RECORD record;
+        DWORD read_len;
+        if (!ReadConsoleInputA(std_i, &record, 1, &read_len) || read_len == 0)
+            return -1;
+        if (record.EventType == KEY_EVENT) {
+            if (!record.Event.KeyEvent.bKeyDown)
+                continue;
+            else if (record.Event.KeyEvent.wVirtualKeyCode == VK_BACK) {  // 退格
+                if (backChar(std_o) == 0)
+                    return -1;
+                continue;
+            } if (record.Event.KeyEvent.wVirtualKeyCode == VK_RETURN) {  // 回车
+                if (enterChar(std_o) == 0)
+                    return -1;
+                return 1;
+            } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_LEFT) {  // 左
+                CONSOLE_SCREEN_BUFFER_INFO info;
+                if (!GetConsoleScreenBufferInfo(std_o, &info))
+                    return -1;
+                if (next > index) {
+                    next--;
+                    if (setCursorPosition(std_o, NULL, -1) == -1)
+                        return 0;
+                }
+                return 0;
+            } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT) {  // 右
+                if (next < end) {
+                    next++;
+                    if(setCursorPosition(std_o, NULL, 1) == -1)
+                        return 0;
+                }
+                return 0;
+            }
+
+            for (int r = record.Event.KeyEvent.wRepeatCount; r > 0; r--) {
+                if (newChar(std_o, record.Event.KeyEvent.uChar.AsciiChar) == 0)
+                    return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+static int fcheck_stdin(HANDLE *std_i, HANDLE *std_o) {
+    if (end == index || end == 0 || buffer[end - 1] != '\n')
+        return checkNewInput(std_i, std_o);
+    return 1;
+}
+
+int fgetc_stdin(void) {
+    HANDLE *std_i = GetStdHandle(STD_INPUT_HANDLE);
+    HANDLE *std_o = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
+        return EOF;
+
+    for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) {  // 阻塞
+        if (fs == -1)
+            return EOF;
+    }
+
+    int re = (int)buffer[index];
+    index++;
+    return re;
+}
+
+char *fgets_stdin_(char *buf, size_t len) {
+    HANDLE *std_i = GetStdHandle(STD_INPUT_HANDLE);
+    HANDLE *std_o = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
+        return NULL;
+
+    for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) {  // 阻塞
+        if (fs == -1)
+            return NULL;
+    }
+
+    size_t len_ = len - 1;
+    if (end - index < len_)
+        len_ = end - index;
+    memcpy(buf, buffer+index, len_);
+    index += len_;
+    nextToEnd(std_o);
+    buf[len_] = '\0';  // 最后一位
+    return buf;
+}
+
+bool fclear_stdin(void) {
+    HANDLE *std_o = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (std_o == INVALID_HANDLE_VALUE)
+        return true;
+    nextToEnd(std_o);
+    index = 0;
+    end = 0;
+    next = 0;
+    memset(buffer, 0, BUFF_SIZE);
+    return false;
+}
 
 static int convertMultiByte(char **dest, char *str, UINT from, UINT to) {
     if (str == NULL || dest == NULL)
@@ -55,7 +290,7 @@ int fgets_stdin(char **dest, int len) {
     char *wstr = calloc(len, sizeof(char));
     int re = 0;
 
-    if (!_isatty(fileno(stdin))) {
+    if (!_isatty(_fileno(stdin))) {
         *dest = NEW_STR(len);
         re = fgets(*dest, len, stdin) != NULL;
         if (!re)
@@ -64,28 +299,25 @@ int fgets_stdin(char **dest, int len) {
     }
 
     UINT code_page = GetConsoleCP();
-    if (fgets(wstr, len, stdin) != NULL)
+    if (fgets_stdin_(wstr, len) != NULL)
         re = convertMultiByte(dest, wstr, code_page, CP_UTF8);
-    if (strchr(wstr, '\n') != NULL)
-        stdin_empty = true;  // 没有读取到\n说明stdin还有内容
-    else
-        stdin_empty = false;  // 没有读取到\n说明stdin还有内容
     return re;
 }
 
-int fgetchar_stdin(void) {
-    int ch = getc(stdin);
-    if (ch == '\n')
-        stdin_empty = true;
-    else
-        stdin_empty = false;
-    return ch;
-}
-
 int fungetc_stdin(int ch) {
-    int re = ungetc(ch, stdin);
-    stdin_empty = false;
-    return re;
+    if (ch == 0 || index == 0 && end == BUFF_SIZE)
+        return 0;
+
+    if (index != 0) {
+        index--;
+        buffer[index] = (char)ch;
+    } else if (end != BUFF_SIZE) {  // index == 0;
+        memmove(buffer, buffer + 1, end);  // 往回移动
+        end++;
+        next++;
+        buffer[0] = (char) ch;
+    }
+    return 1;
 }
 
 /*
@@ -95,11 +327,12 @@ int fungetc_stdin(int ch) {
  * 无内容则返回false
  */
 bool checkStdin(void) {
-    if (!_isatty(fileno(stdin)))
-        return true;
-    if (!stdin_empty)
-        return true;
-    return _kbhit();
+    HANDLE *std_i = GetStdHandle(STD_INPUT_HANDLE);
+    HANDLE *std_o = GetStdHandle(STD_OUTPUT_HANDLE);
+    int fs = fcheck_stdin(std_i, std_o);
+    if (fs == 0)
+        return false;
+    return true;
 }
 
 static int fputs_std_(char *str, FILE *std) {
@@ -115,14 +348,14 @@ static int fputs_std_(char *str, FILE *std) {
 }
 
 int fputs_stdout(char *str) {
-    if (_isatty(fileno(stdout)))
+    if (_isatty(_fileno(stdout)))
         return fputs_std_(str, stdout);
     fputs(str, stdout);
     return 1;
 }
 
 int fputs_stderr(char *str) {
-    if (_isatty(fileno(stderr)))
+    if (_isatty(_fileno(stderr)))
         return fputs_std_(str, stderr);
     fputs(str, stderr);
     return 1;
@@ -141,13 +374,13 @@ static size_t vprintf_std_(FILE *std, size_t buf_len, char *format, va_list ap)
 }
 
 size_t vprintf_stdout(size_t buf_len, char *format, va_list ap) {
-    if (_isatty(fileno(stdout)))
+    if (_isatty(_fileno(stdout)))
         return vprintf_std_(stdout, buf_len, format, ap);
     return vfprintf(stdout, format, ap);
 }
 
 size_t vprintf_stderr(size_t buf_len, char *format, va_list ap) {
-    if (_isatty(fileno(stderr)))
+    if (_isatty(_fileno(stderr)))
         return vprintf_std_(stderr, buf_len, format, ap);
     return vfprintf(stderr, format, ap);
 }
@@ -210,4 +443,22 @@ bool checkStdin(void) {
     return re;
 }
 
+bool fclear_stdin(void) {
+    if (!isatty(fileno(stdin)))
+        return true;
+    bool re = false;
+
+    int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
+    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
+
+    int ch;
+    do {
+        ch = fgetc(stdin);
+        CLEAR_FERROR(stdin);
+    } while (ch != EOF);
+
+    fcntl(STDIN_FILENO, F_SETFL, oldf);
+    return !ferror(stdin) && !feof(stdin);
+}
+
 #endif