feat: nearly complete POSIX.1-2008 compliant terminal interface implementation
[lunaix-os.git] / lunaix-os / hal / term / lcntls / lcntl.c
diff --git a/lunaix-os/hal/term/lcntls/lcntl.c b/lunaix-os/hal/term/lcntls/lcntl.c
new file mode 100644 (file)
index 0000000..4359830
--- /dev/null
@@ -0,0 +1,173 @@
+/**
+ * @file lcntl.c
+ * @author Lunaixsky (lunaxisky@qq.com)
+ * @brief Line controller master that handle all POSIX-control code
+ * @version 0.1
+ * @date 2023-11-25
+ *
+ * @copyright Copyright (c) 2023
+ *
+ */
+#include <hal/term.h>
+#include <usr/lunaix/term.h>
+
+#include <lunaix/process.h>
+#include <lunaix/spike.h>
+
+static inline void
+raise_sig(struct term* at_term, struct linebuffer* lbuf, int sig)
+{
+    term_sendsig(at_term, SIGINT);
+    lbuf->sflags |= LSTATE_SIGRAISE;
+}
+
+static inline int
+lcntl_invoke_slaves(struct term* tdev, struct linebuffer* lbuf, char c)
+{
+    int allow_more = 0;
+    struct term_lcntl *lcntl, *n;
+    llist_for_each(lcntl, n, &tdev->lcntl_stack, lcntls)
+    {
+        allow_more = lcntl->process_and_put(tdev, lbuf, c);
+        if (!allow_more) {
+            break;
+        }
+
+        line_flip(lbuf);
+    }
+
+    return allow_more;
+}
+
+static inline int optimize("ipa-cp-clone")
+lcntl_transform_seq(struct term* tdev, struct linebuffer* lbuf, bool out)
+{
+    struct rbuffer* raw = lbuf->current;
+    struct rbuffer* cooked = lbuf->next;
+    struct rbuffer* output = tdev->line_out.current;
+
+    int i = 0, _if = tdev->iflags & -!out, _of = tdev->oflags & -!!out,
+        _lf = tdev->lflags;
+    int allow_more = 1, latest_eol = cooked->ptr;
+    char c;
+    bool should_flush = false;
+
+#define EOL tdev->cc[_VEOL]
+#define EOF tdev->cc[_VEOF]
+#define ERASE tdev->cc[_VERASE]
+#define KILL tdev->cc[_VKILL]
+#define INTR tdev->cc[_VINTR]
+#define QUIT tdev->cc[_VQUIT]
+#define SUSP tdev->cc[_VSUSP]
+
+    if (!out) {
+        // Keep all cc's (except VMIN & VTIME) up to L2 cache.
+        for (size_t i = 0; i < _VMIN; i++) {
+            prefetch_rd(&tdev->cc[i], 2);
+        }
+    }
+
+    while (rbuffer_get(raw, &c) && allow_more) {
+
+        if (c == '\r' && ((_if & _ICRNL) || (_of & _OCRNL))) {
+            c = '\n';
+        } else if (c == '\n') {
+            if ((_if & _INLCR) || (_of & (_ONLRET | _ONLCR))) {
+                c = '\r';
+            }
+        }
+
+        if (c == '\0') {
+            if ((_if & _BRKINT)) {
+                raise_sig(tdev, lbuf, SIGINT);
+                break;
+            }
+
+            if ((_if & _IGNBRK)) {
+                continue;
+            }
+        }
+
+        if ('a' <= c && c <= 'z') {
+            if ((_if & _IUCLC)) {
+                c = c | 0b100000;
+            } else if ((_of & _OLCUC)) {
+                c = c & ~0b100000;
+            }
+        }
+
+        if (c == '\n') {
+            latest_eol = cooked->ptr + 1;
+            if (!out && (_lf & _ECHONL)) {
+                rbuffer_put(output, c);
+            }
+            should_flush = true;
+        }
+
+        if (out) {
+            goto put_char;
+        }
+
+        // For input procesing
+
+        if (c == '\n' || c == EOL) {
+            lbuf->sflags |= LSTATE_EOL;
+            goto keep;
+        } else if (c == EOF) {
+            lbuf->sflags |= LSTATE_EOF;
+            rbuffer_clear(raw);
+            break;
+        } else if (c == INTR) {
+            raise_sig(tdev, lbuf, SIGINT);
+        } else if (c == QUIT) {
+            raise_sig(tdev, lbuf, SIGKILL);
+            break;
+        } else if (c == SUSP) {
+            raise_sig(tdev, lbuf, SIGSTOP);
+        } else if (c == ERASE) {
+            rbuffer_erase(cooked);
+        } else {
+            goto keep;
+        }
+
+        continue;
+
+    keep:
+        if ((_lf & _ECHO)) {
+            rbuffer_put(output, c);
+        }
+        if ((_lf & _ECHOE) && c == ERASE) {
+            rbuffer_erase(output);
+        }
+        if ((_lf & _ECHOK) && c == KILL) {
+            rbuffer_put(output, c);
+            rbuffer_put(output, '\n');
+        }
+
+    put_char:
+        allow_more = rbuffer_put(cooked, c);
+    }
+
+    if (out || (_lf & _IEXTEN)) {
+        line_flip(lbuf);
+        lcntl_invoke_slaves(tdev, lbuf, c);
+    }
+
+    if (should_flush && !(_lf & _NOFLSH)) {
+        term_flush(tdev);
+    }
+
+    return i;
+}
+
+int
+lcntl_transform_inseq(struct term* tdev)
+{
+    return lcntl_transform_seq(tdev, &tdev->line_in, false);
+}
+
+int
+lcntl_transform_outseq(struct term* tdev)
+{
+    return lcntl_transform_seq(tdev, &tdev->line_in, true);
+}
\ No newline at end of file