feat: nearly complete POSIX.1-2008 compliant terminal interface implementation
[lunaix-os.git] / lunaix-os / hal / term / lcntls / lcntl.c
1 /**
2  * @file lcntl.c
3  * @author Lunaixsky (lunaxisky@qq.com)
4  * @brief Line controller master that handle all POSIX-control code
5  * @version 0.1
6  * @date 2023-11-25
7  *
8  * @copyright Copyright (c) 2023
9  *
10  */
11 #include <hal/term.h>
12 #include <usr/lunaix/term.h>
13
14 #include <lunaix/process.h>
15 #include <lunaix/spike.h>
16
17 static inline void
18 raise_sig(struct term* at_term, struct linebuffer* lbuf, int sig)
19 {
20     term_sendsig(at_term, SIGINT);
21     lbuf->sflags |= LSTATE_SIGRAISE;
22 }
23
24 static inline int
25 lcntl_invoke_slaves(struct term* tdev, struct linebuffer* lbuf, char c)
26 {
27     int allow_more = 0;
28     struct term_lcntl *lcntl, *n;
29     llist_for_each(lcntl, n, &tdev->lcntl_stack, lcntls)
30     {
31         allow_more = lcntl->process_and_put(tdev, lbuf, c);
32         if (!allow_more) {
33             break;
34         }
35
36         line_flip(lbuf);
37     }
38
39     return allow_more;
40 }
41
42 static inline int optimize("ipa-cp-clone")
43 lcntl_transform_seq(struct term* tdev, struct linebuffer* lbuf, bool out)
44 {
45     struct rbuffer* raw = lbuf->current;
46     struct rbuffer* cooked = lbuf->next;
47     struct rbuffer* output = tdev->line_out.current;
48
49     int i = 0, _if = tdev->iflags & -!out, _of = tdev->oflags & -!!out,
50         _lf = tdev->lflags;
51     int allow_more = 1, latest_eol = cooked->ptr;
52     char c;
53     bool should_flush = false;
54
55 #define EOL tdev->cc[_VEOL]
56 #define EOF tdev->cc[_VEOF]
57 #define ERASE tdev->cc[_VERASE]
58 #define KILL tdev->cc[_VKILL]
59 #define INTR tdev->cc[_VINTR]
60 #define QUIT tdev->cc[_VQUIT]
61 #define SUSP tdev->cc[_VSUSP]
62
63     if (!out) {
64         // Keep all cc's (except VMIN & VTIME) up to L2 cache.
65         for (size_t i = 0; i < _VMIN; i++) {
66             prefetch_rd(&tdev->cc[i], 2);
67         }
68     }
69
70     while (rbuffer_get(raw, &c) && allow_more) {
71
72         if (c == '\r' && ((_if & _ICRNL) || (_of & _OCRNL))) {
73             c = '\n';
74         } else if (c == '\n') {
75             if ((_if & _INLCR) || (_of & (_ONLRET | _ONLCR))) {
76                 c = '\r';
77             }
78         }
79
80         if (c == '\0') {
81             if ((_if & _BRKINT)) {
82                 raise_sig(tdev, lbuf, SIGINT);
83                 break;
84             }
85
86             if ((_if & _IGNBRK)) {
87                 continue;
88             }
89         }
90
91         if ('a' <= c && c <= 'z') {
92             if ((_if & _IUCLC)) {
93                 c = c | 0b100000;
94             } else if ((_of & _OLCUC)) {
95                 c = c & ~0b100000;
96             }
97         }
98
99         if (c == '\n') {
100             latest_eol = cooked->ptr + 1;
101             if (!out && (_lf & _ECHONL)) {
102                 rbuffer_put(output, c);
103             }
104             should_flush = true;
105         }
106
107         if (out) {
108             goto put_char;
109         }
110
111         // For input procesing
112
113         if (c == '\n' || c == EOL) {
114             lbuf->sflags |= LSTATE_EOL;
115             goto keep;
116         } else if (c == EOF) {
117             lbuf->sflags |= LSTATE_EOF;
118             rbuffer_clear(raw);
119             break;
120         } else if (c == INTR) {
121             raise_sig(tdev, lbuf, SIGINT);
122         } else if (c == QUIT) {
123             raise_sig(tdev, lbuf, SIGKILL);
124             break;
125         } else if (c == SUSP) {
126             raise_sig(tdev, lbuf, SIGSTOP);
127         } else if (c == ERASE) {
128             rbuffer_erase(cooked);
129         } else {
130             goto keep;
131         }
132
133         continue;
134
135     keep:
136         if ((_lf & _ECHO)) {
137             rbuffer_put(output, c);
138         }
139         if ((_lf & _ECHOE) && c == ERASE) {
140             rbuffer_erase(output);
141         }
142         if ((_lf & _ECHOK) && c == KILL) {
143             rbuffer_put(output, c);
144             rbuffer_put(output, '\n');
145         }
146
147     put_char:
148         allow_more = rbuffer_put(cooked, c);
149     }
150
151     if (out || (_lf & _IEXTEN)) {
152         line_flip(lbuf);
153         lcntl_invoke_slaves(tdev, lbuf, c);
154     }
155
156     if (should_flush && !(_lf & _NOFLSH)) {
157         term_flush(tdev);
158     }
159
160     return i;
161 }
162
163 int
164 lcntl_transform_inseq(struct term* tdev)
165 {
166     return lcntl_transform_seq(tdev, &tdev->line_in, false);
167 }
168
169 int
170 lcntl_transform_outseq(struct term* tdev)
171 {
172     return lcntl_transform_seq(tdev, &tdev->line_in, true);
173 }