1 /* v/term.c
2 **
3 */
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <fcntl.h>
7 #include <sys/ioctl.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include <setjmp.h>
11 #include <gmp.h>
12 #include <stdint.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <uv.h>
16 #include <errno.h>
17 #include <curses.h>
18 #include <termios.h>
19 #include <term.h>
20 #include "all.h"
21 #include "vere/vere.h"
22 
23 static        void _term_spinner_cb(void*);
24 static        void _term_read_tn_cb(uv_stream_t* tcp_u,
25                                     ssize_t      siz_i,
26                                     const uv_buf_t *     buf_u);
27 static        void _term_read_cb(uv_stream_t* tcp_u,
28                                  ssize_t      siz_i,
29                                  const uv_buf_t *     buf_u);
30 static inline void _term_suck(u3_utty*, const c3_y*, ssize_t);
31 
32 
33 #define _T_ECHO 1    //  local echo
34 #define _T_CTIM 3    //  suppress GA/char-at-a-time
35 #define _T_NAWS 31   //  negotiate about window size
36 
37 #define _SPIN_COOL_US 500000  //  spinner activation delay when cool
38 #define _SPIN_WARM_US 50000   //  spinner activation delay when warm
39 #define _SPIN_RATE_US 250000  //  spinner rate (microseconds/frame)
40 #define _SPIN_IDLE_US 500000  //  spinner cools down if stopped this long
41 
42 /* _term_msc_out_host(): unix microseconds from current host time.
43 */
44 static c3_d
_term_msc_out_host()45 _term_msc_out_host()
46 {
47   struct timeval tim_tv;
48   gettimeofday(&tim_tv, 0);
49   return 1000000ULL * tim_tv.tv_sec + tim_tv.tv_usec;
50 }
51 
52 static void
_term_alloc(uv_handle_t * had_u,size_t len_i,uv_buf_t * buf)53 _term_alloc(uv_handle_t* had_u,
54             size_t len_i,
55             uv_buf_t* buf
56             )
57 {
58   void* ptr_v = c3_malloc(len_i);
59   *buf = uv_buf_init(ptr_v, len_i);
60 }
61 
62 
63 /* _term_close_cb(): free terminal.
64 */
65 static void
_term_close_cb(uv_handle_t * han_t)66 _term_close_cb(uv_handle_t* han_t)
67 {
68   u3_utty* tty_u = (void*) han_t;
69   if ( u3_Host.uty_u == tty_u ) {
70     u3_Host.uty_u = tty_u->nex_u;
71   }
72   else {
73     u3_utty* uty_u;
74     for (uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
75       if ( uty_u->nex_u == tty_u ) {
76         uty_u->nex_u = tty_u->nex_u;
77         break;
78       }
79     }
80   }
81 
82   {
83     u3_noun tid = u3dc("scot", c3__ud, tty_u->tid_l);
84     u3_noun pax = u3nq(u3_blip, c3__term, tid, u3_nul);
85     u3v_plan(u3k(pax), u3nc(c3__hook, u3_nul));
86     u3z(pax);
87   }
88   free(tty_u);
89 }
90 
91 /* u3_term_io_init(): initialize terminal.
92 */
93 void
u3_term_io_init()94 u3_term_io_init()
95 {
96   u3_utty* uty_u = calloc(1, sizeof(u3_utty));
97 
98   if ( c3y == u3_Host.ops_u.dem ) {
99     uty_u->fid_i = 1;
100 
101     uv_pipe_init(u3L, &(uty_u->pop_u), 0);
102     uv_pipe_open(&(uty_u->pop_u), uty_u->fid_i);
103   }
104   else {
105     //  Initialize event processing.  Rawdog it.
106     //
107     {
108       uty_u->fid_i = 0;                       //  stdin, yes we write to it...
109 
110       uv_pipe_init(u3L, &(uty_u->pop_u), 0);
111       uv_pipe_open(&(uty_u->pop_u), uty_u->fid_i);
112       uv_read_start((uv_stream_t*)&(uty_u->pop_u), _term_alloc, _term_read_cb);
113     }
114 
115     //  Configure horrible stateful terminfo api.
116     //
117     {
118       if ( 0 != setupterm(0, 2, 0) ) {
119         c3_assert(!"init-setupterm");
120       }
121     }
122 
123     //  Load terminfo strings.
124     //
125     {
126       c3_w len_w;
127 
128 #   define _utfo(way, nam) \
129       { \
130         uty_u->ufo_u.way.nam##_y = (const c3_y *) tigetstr(#nam); \
131         c3_assert(uty_u->ufo_u.way.nam##_y); \
132       }
133 
134       uty_u->ufo_u.inn.max_w = 0;
135 
136       _utfo(inn, kcuu1);
137       _utfo(inn, kcud1);
138       _utfo(inn, kcub1);
139       _utfo(inn, kcuf1);
140 
141       _utfo(out, clear);
142       _utfo(out, el);
143       // _utfo(out, el1);
144       _utfo(out, ed);
145       _utfo(out, bel);
146       _utfo(out, cub1);
147       _utfo(out, cuf1);
148       _utfo(out, cuu1);
149       _utfo(out, cud1);
150       // _utfo(out, cub);
151       // _utfo(out, cuf);
152 
153       //  Terminfo chronically reports the wrong sequence for arrow
154       //  keys on xterms.  Drastic fix for ridiculous unacceptable bug.
155       //  Yes, we could fix this with smkx/rmkx, but this is retarded as well.
156       {
157         uty_u->ufo_u.inn.kcuu1_y = (const c3_y*)"\033[A";
158         uty_u->ufo_u.inn.kcud1_y = (const c3_y*)"\033[B";
159         uty_u->ufo_u.inn.kcuf1_y = (const c3_y*)"\033[C";
160         uty_u->ufo_u.inn.kcub1_y = (const c3_y*)"\033[D";
161       }
162 
163       uty_u->ufo_u.inn.max_w = 0;
164       if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcuu1_y)) >
165             uty_u->ufo_u.inn.max_w )
166       {
167         uty_u->ufo_u.inn.max_w = len_w;
168       }
169       if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcud1_y)) >
170             uty_u->ufo_u.inn.max_w )
171       {
172         uty_u->ufo_u.inn.max_w = len_w;
173       }
174       if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcub1_y)) >
175             uty_u->ufo_u.inn.max_w )
176       {
177         uty_u->ufo_u.inn.max_w = len_w;
178       }
179       if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcuf1_y)) >
180             uty_u->ufo_u.inn.max_w )
181       {
182         uty_u->ufo_u.inn.max_w = len_w;
183       }
184     }
185 
186     //  Load old terminal state to restore.
187     //
188     {
189       if ( 0 != tcgetattr(uty_u->fid_i, &uty_u->bak_u) ) {
190         c3_assert(!"init-tcgetattr");
191       }
192       if ( -1 == fcntl(uty_u->fid_i, F_GETFL, &uty_u->cug_i) ) {
193         c3_assert(!"init-fcntl");
194       }
195       uty_u->cug_i &= ~O_NONBLOCK;                // could fix?
196       uty_u->nob_i = uty_u->cug_i | O_NONBLOCK;   // O_NDELAY on older unix
197     }
198 
199     //  Construct raw termios configuration.
200     //
201     {
202       uty_u->raw_u = uty_u->bak_u;
203 
204       uty_u->raw_u.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
205       uty_u->raw_u.c_iflag &= ~(ICRNL | INPCK | ISTRIP);
206       uty_u->raw_u.c_cflag &= ~(CSIZE | PARENB);
207       uty_u->raw_u.c_cflag |= CS8;
208       uty_u->raw_u.c_oflag &= ~(OPOST);
209       uty_u->raw_u.c_cc[VMIN] = 0;
210       uty_u->raw_u.c_cc[VTIME] = 0;
211     }
212 
213     //  Initialize mirror and accumulator state.
214     //
215     {
216       uty_u->tat_u.mir.lin_w = 0;
217       uty_u->tat_u.mir.len_w = 0;
218       uty_u->tat_u.mir.cus_w = 0;
219 
220       uty_u->tat_u.esc.ape = c3n;
221       uty_u->tat_u.esc.bra = c3n;
222 
223       uty_u->tat_u.fut.len_w = 0;
224       uty_u->tat_u.fut.wid_w = 0;
225     }
226   }
227 
228   //  This is terminal 1, linked in host.
229   //
230   {
231     uty_u->tid_l = 1;
232     uty_u->nex_u = 0;
233     u3_Host.uty_u = uty_u;
234   }
235 
236   if ( c3n == u3_Host.ops_u.dem ) {
237     //  Start raw input.
238     //
239     {
240       if ( 0 != tcsetattr(uty_u->fid_i, TCSADRAIN, &uty_u->raw_u) ) {
241         c3_assert(!"init-tcsetattr");
242       }
243       if ( -1 == fcntl(uty_u->fid_i, F_SETFL, uty_u->nob_i) ) {
244         c3_assert(!"init-fcntl");
245       }
246     }
247 
248     //  Start spinner thread.
249     //
250     {
251       uty_u->tat_u.sun.sit_u = (uv_thread_t*)malloc(sizeof(uv_thread_t));
252       if ( uty_u->tat_u.sun.sit_u ) {
253         uv_mutex_init(&uty_u->tat_u.mex_u);
254         uv_mutex_lock(&uty_u->tat_u.mex_u);
255 
256         c3_w ret_w = uv_thread_create(uty_u->tat_u.sun.sit_u,
257                                       _term_spinner_cb,
258                                       uty_u);
259         if ( 0 != ret_w ) {
260           uL(fprintf(uH, "term: spinner start: %s\n", uv_strerror(ret_w)));
261           free(uty_u->tat_u.sun.sit_u);
262           uty_u->tat_u.sun.sit_u = NULL;
263           uv_mutex_unlock(&uty_u->tat_u.mex_u);
264           uv_mutex_destroy(&uty_u->tat_u.mex_u);
265         }
266       }
267     }
268   }
269 }
270 /* u3_term_io_exit(): clean up terminal.
271 */
272 void
u3_term_io_exit(void)273 u3_term_io_exit(void)
274 {
275   if ( c3y == u3_Host.ops_u.dem ) {
276     uv_close((uv_handle_t*)&u3_Host.uty_u->pop_u, NULL);
277   }
278   else {
279     u3_utty* uty_u;
280 
281     for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
282       if ( uty_u->fid_i == -1 ) { continue; }
283       if ( 0 != tcsetattr(uty_u->fid_i, TCSADRAIN, &uty_u->bak_u) ) {
284         c3_assert(!"exit-tcsetattr");
285       }
286       if ( -1 == fcntl(uty_u->fid_i, F_SETFL, uty_u->cug_i) ) {
287         c3_assert(!"exit-fcntl");
288       }
289       write(uty_u->fid_i, "\r\n", 2);
290 
291 #if 0
292       if ( uty_u->tat_u.sun.sit_u ) {
293         uv_thread_t* sit_u = uty_u->tat_u.sun.sit_u;
294         uty_u->tat_u.sun.sit_u = NULL;
295 
296         uv_mutex_unlock(&uty_u->tat_u.mex_u);
297 
298         //  XX can block exit waiting for wakeup (max _SPIN_COOL_US)
299         c3_w ret_w;
300         if ( 0 != (ret_w = uv_thread_join(sit_u)) ) {
301           uL(fprintf(uH, "term: spinner exit: %s\n", uv_strerror(ret_w)));
302         }
303         else {
304           uv_mutex_destroy(&uty_u->tat_u.mex_u);
305         }
306 
307         free(sit_u);
308       }
309 #endif
310     }
311   }
312 }
313 
314 void
u3_term_io_poll(void)315 u3_term_io_poll(void)
316 {
317 }
318 
319 /* _term_it_buf(): create a data buffer.
320 */
321 static u3_ubuf*
_term_it_buf(c3_w len_w,const c3_y * hun_y)322 _term_it_buf(c3_w len_w, const c3_y* hun_y)
323 {
324   u3_ubuf* buf_u = c3_malloc(len_w + sizeof(*buf_u));
325 
326   buf_u->len_w = len_w;
327   memcpy(buf_u->hun_y, hun_y, len_w);
328 
329   buf_u->nex_u = 0;
330   return buf_u;
331 }
332 
333 /* An unusual lameness in libuv.
334 */
335   typedef struct {
336     uv_write_t wri_u;
337     c3_y*      buf_y;
338   } _u3_write_t;
339 
340 /* _term_write_cb(): general write callback.
341 */
342 static void
_term_write_cb(uv_write_t * wri_u,c3_i sas_i)343 _term_write_cb(uv_write_t* wri_u, c3_i sas_i)
344 {
345   _u3_write_t* ruq_u = (void *)wri_u;
346 
347   if ( 0 != sas_i ) {
348     // uL(fprintf(uH, "term: write: ERROR\n"));
349   }
350   free(ruq_u->buf_y);
351   free(ruq_u);
352 }
353 
354 /* _term_it_write_buf(): write buffer uv style.
355 */
356 static void
_term_it_write_buf(u3_utty * uty_u,uv_buf_t buf_u)357 _term_it_write_buf(u3_utty* uty_u, uv_buf_t buf_u)
358 {
359   _u3_write_t* ruq_u = (_u3_write_t*) c3_malloc(sizeof(_u3_write_t));
360 
361   ruq_u->buf_y = (c3_y*)buf_u.base;
362 
363   c3_w ret_w;
364   if ( 0 != (ret_w = uv_write(&ruq_u->wri_u,
365                      (uv_stream_t*)&(uty_u->pop_u),
366                      &buf_u, 1,
367                               _term_write_cb)) )
368   {
369     uL(fprintf(uH, "terminal: %s\n", uv_strerror(ret_w)));
370   }
371 }
372 
373 /* _term_it_write_old(): write buffer, transferring pointer.
374 */
375 static void
_term_it_write_old(u3_utty * uty_u,u3_ubuf * old_u)376 _term_it_write_old(u3_utty* uty_u,
377                    u3_ubuf* old_u)
378 {
379   uv_buf_t buf_u;
380 
381   //  XX extra copy here due to old code.  Use hbod as base directly.
382   //
383   {
384     c3_y* buf_y = c3_malloc(old_u->len_w);
385 
386     memcpy(buf_y, old_u->hun_y, old_u->len_w);
387     buf_u = uv_buf_init((c3_c*)buf_y, old_u->len_w);
388 
389     free(old_u);
390   }
391   _term_it_write_buf(uty_u, buf_u);
392 }
393 
394 /* _term_it_write_bytes(): write bytes, retaining pointer.
395 */
396 static void
_term_it_write_bytes(u3_utty * uty_u,c3_w len_w,const c3_y * hun_y)397 _term_it_write_bytes(u3_utty*    uty_u,
398                      c3_w        len_w,
399                      const c3_y* hun_y)
400 {
401   _term_it_write_old(uty_u, _term_it_buf(len_w, hun_y));
402 }
403 
404 /* _term_it_write_txt(): write null-terminated string, retaining pointer.
405 */
406 static void
_term_it_write_txt(u3_utty * uty_u,const c3_y * hun_y)407 _term_it_write_txt(u3_utty*    uty_u,
408                    const c3_y* hun_y)
409 {
410   _term_it_write_bytes(uty_u, strlen((const c3_c*)hun_y), hun_y);
411 }
412 
413 /* _term_it_write_str(): write null-terminated string, retaining pointer.
414 */
415 static void
_term_it_write_str(u3_utty * uty_u,const c3_c * str_c)416 _term_it_write_str(u3_utty*    uty_u,
417                    const c3_c* str_c)
418 {
419   _term_it_write_txt(uty_u, (const c3_y*) str_c);
420 }
421 
422 /* _term_it_show_wide(): show wide text, retaining.
423 */
424 static void
_term_it_show_wide(u3_utty * uty_u,c3_w len_w,c3_w * txt_w)425 _term_it_show_wide(u3_utty* uty_u, c3_w len_w, c3_w* txt_w)
426 {
427   u3_noun wad   = u3i_words(len_w, txt_w);
428   u3_noun txt   = u3do("tuft", wad);
429   c3_c*   txt_c = u3r_string(txt);
430 
431   _term_it_write_str(uty_u, txt_c);
432   free(txt_c);
433   u3z(txt);
434 
435   uty_u->tat_u.mir.cus_w += len_w;
436 }
437 
438 /* _term_it_show_clear(): clear to the beginning of the current line.
439 */
440 static void
_term_it_show_clear(u3_utty * uty_u)441 _term_it_show_clear(u3_utty* uty_u)
442 {
443   if ( uty_u->tat_u.siz.col_l ) {
444     _term_it_write_str(uty_u, "\r");
445     _term_it_write_txt(uty_u, uty_u->ufo_u.out.el_y);
446 
447     uty_u->tat_u.mir.len_w = 0;
448     uty_u->tat_u.mir.cus_w = 0;
449   }
450 }
451 
452 /* _term_it_show_blank(): blank the screen.
453 */
454 static void
_term_it_show_blank(u3_utty * uty_u)455 _term_it_show_blank(u3_utty* uty_u)
456 {
457   _term_it_write_txt(uty_u, uty_u->ufo_u.out.clear_y);
458 }
459 
460 /* _term_it_show_cursor(): set current line, transferring pointer.
461 */
462 static void
_term_it_show_cursor(u3_utty * uty_u,c3_w cur_w)463 _term_it_show_cursor(u3_utty* uty_u, c3_w cur_w)
464 {
465   if ( cur_w < uty_u->tat_u.mir.cus_w ) {
466     c3_w dif_w = (uty_u->tat_u.mir.cus_w - cur_w);
467 
468     while ( dif_w-- ) {
469       _term_it_write_txt(uty_u, uty_u->ufo_u.out.cub1_y);
470     }
471   }
472   else if ( cur_w > uty_u->tat_u.mir.cus_w ) {
473     c3_w dif_w = (cur_w - uty_u->tat_u.mir.cus_w);
474 
475     while ( dif_w-- ) {
476       _term_it_write_txt(uty_u, uty_u->ufo_u.out.cuf1_y);
477     }
478   }
479   uty_u->tat_u.mir.cus_w = cur_w;
480 }
481 
482 /* _term_it_show_line(): set current line
483 */
484 static void
_term_it_show_line(u3_utty * uty_u,c3_w * lin_w,c3_w len_w)485 _term_it_show_line(u3_utty* uty_u, c3_w* lin_w, c3_w len_w)
486 {
487   _term_it_show_wide(uty_u, len_w, lin_w);
488 
489   if ( lin_w != uty_u->tat_u.mir.lin_w ) {
490     if ( uty_u->tat_u.mir.lin_w ) {
491       free(uty_u->tat_u.mir.lin_w);
492     }
493     uty_u->tat_u.mir.lin_w = lin_w;
494   }
495   uty_u->tat_u.mir.len_w = len_w;
496 }
497 
498 /* _term_it_refresh_line(): refresh current line.
499 */
500 static void
_term_it_refresh_line(u3_utty * uty_u)501 _term_it_refresh_line(u3_utty* uty_u)
502 {
503   c3_w len_w = uty_u->tat_u.mir.len_w;
504   c3_w cus_w = uty_u->tat_u.mir.cus_w;
505 
506   _term_it_show_clear(uty_u);
507   _term_it_show_line(uty_u, uty_u->tat_u.mir.lin_w, len_w);
508   _term_it_show_cursor(uty_u, cus_w);
509 }
510 
511 /* _term_it_show_more(): new current line.
512 */
513 static void
_term_it_show_more(u3_utty * uty_u)514 _term_it_show_more(u3_utty* uty_u)
515 {
516   if ( c3y == u3_Host.ops_u.dem ) {
517     _term_it_write_str(uty_u, "\n");
518   } else {
519     _term_it_write_str(uty_u, "\r\n");
520   }
521   uty_u->tat_u.mir.cus_w = 0;
522 }
523 
524 /* _term_it_path(): path for console file.
525 */
526 static c3_c*
_term_it_path(c3_o fyl,u3_noun pax)527 _term_it_path(c3_o fyl, u3_noun pax)
528 {
529   c3_w len_w;
530   c3_c *pas_c;
531 
532   //  measure
533   //
534   len_w = strlen(u3_Host.dir_c);
535   {
536     u3_noun wiz = pax;
537 
538     while ( u3_nul != wiz ) {
539       len_w += (1 + u3r_met(3, u3h(wiz)));
540       wiz = u3t(wiz);
541     }
542   }
543 
544   //  cut
545   //
546   pas_c = c3_malloc(len_w + 1);
547   strncpy(pas_c, u3_Host.dir_c, len_w);
548   pas_c[len_w] = '\0';
549   {
550     u3_noun wiz   = pax;
551     c3_c*   waq_c = (pas_c + strlen(pas_c));
552 
553     while ( u3_nul != wiz ) {
554       c3_w tis_w = u3r_met(3, u3h(wiz));
555 
556       if ( (c3y == fyl) && (u3_nul == u3t(wiz)) ) {
557         *waq_c++ = '.';
558       } else *waq_c++ = '/';
559 
560       u3r_bytes(0, tis_w, (c3_y*)waq_c, u3h(wiz));
561       waq_c += tis_w;
562 
563       wiz = u3t(wiz);
564     }
565     *waq_c = 0;
566   }
567   u3z(pax);
568   return pas_c;
569 }
570 
571 /* _term_it_save(): save file by path.
572 */
573 static void
_term_it_save(u3_noun pax,u3_noun pad)574 _term_it_save(u3_noun pax, u3_noun pad)
575 {
576   c3_c* pax_c;
577   c3_c* bas_c = 0;
578   c3_w  xap_w = u3kb_lent(u3k(pax));
579   u3_noun xap = u3_nul;
580   u3_noun urb = c3_s4('.','u','r','b');
581   u3_noun put = c3_s3('p','u','t');
582 
583   // directory base and relative path
584   if ( 2 < xap_w ) {
585     u3_noun bas = u3nt(urb, put, u3_nul);
586     bas_c = _term_it_path(c3n, bas);
587     xap = u3qb_scag(xap_w - 2, pax);
588   }
589 
590   pax = u3nt(urb, put, pax);
591   pax_c = _term_it_path(c3y, pax);
592 
593   u3_walk_save(pax_c, 0, pad, bas_c, xap);
594 
595   free(pax_c);
596   free(bas_c);
597 }
598 
599 /* _term_io_belt(): send belt.
600 */
601 static void
_term_io_belt(u3_utty * uty_u,u3_noun blb)602 _term_io_belt(u3_utty* uty_u, u3_noun  blb)
603 {
604   u3_noun tid = u3dc("scot", c3__ud, uty_u->tid_l);
605   u3_noun pax = u3nq(u3_blip, c3__term, tid, u3_nul);
606 
607   u3v_plan(pax, u3nc(c3__belt, blb));
608 }
609 
610 /* _term_io_suck_char(): process a single character.
611 */
612 static void
_term_io_suck_char(u3_utty * uty_u,c3_y cay_y)613 _term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
614 {
615   u3_utat* tat_u = &uty_u->tat_u;
616 
617   if ( c3y == tat_u->esc.ape ) {
618     if ( c3y == tat_u->esc.bra ) {
619       switch ( cay_y ) {
620         default: {
621           _term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
622           break;
623         }
624         case 'A': _term_io_belt(uty_u, u3nc(c3__aro, 'u')); break;
625         case 'B': _term_io_belt(uty_u, u3nc(c3__aro, 'd')); break;
626         case 'C': _term_io_belt(uty_u, u3nc(c3__aro, 'r')); break;
627         case 'D': _term_io_belt(uty_u, u3nc(c3__aro, 'l')); break;
628       }
629       tat_u->esc.ape = tat_u->esc.bra = c3n;
630     }
631     else {
632       if ( (cay_y >= 'a') && (cay_y <= 'z') ) {
633         tat_u->esc.ape = c3n;
634         _term_io_belt(uty_u, u3nc(c3__met, cay_y));
635       }
636       else if ( '.' == cay_y ) {
637         tat_u->esc.ape = c3n;
638         _term_io_belt(uty_u, u3nc(c3__met, c3__dot));
639       }
640       else if ( 8 == cay_y || 127 == cay_y ) {
641         tat_u->esc.ape = c3n;
642         _term_io_belt(uty_u, u3nc(c3__met, c3__bac));
643       }
644       else if ( ('[' == cay_y) || ('O' == cay_y) ) {
645         tat_u->esc.bra = c3y;
646       }
647       else {
648         tat_u->esc.ape = c3n;
649 
650         _term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
651       }
652     }
653   }
654   else if ( 0 != tat_u->fut.wid_w ) {
655     tat_u->fut.syb_y[tat_u->fut.len_w++] = cay_y;
656 
657     if ( tat_u->fut.len_w == tat_u->fut.wid_w ) {
658       u3_noun huv = u3i_bytes(tat_u->fut.wid_w, tat_u->fut.syb_y);
659       u3_noun wug;
660 
661       // uL(fprintf(uH, "muck-utf8 len %d\n", tat_u->fut.len_w));
662       // uL(fprintf(uH, "muck-utf8 %x\n", huv));
663       wug = u3do("turf", huv);
664       // uL(fprintf(uH, "muck-utf32 %x\n", tat_u->fut.len_w));
665 
666       tat_u->fut.len_w = tat_u->fut.wid_w = 0;
667       _term_io_belt(uty_u, u3nt(c3__txt, wug, u3_nul));
668     }
669   }
670   else {
671     if ( (cay_y >= 32) && (cay_y < 127) ) {
672       _term_io_belt(uty_u, u3nt(c3__txt, cay_y, u3_nul));
673     }
674     else if ( 0 == cay_y ) {
675       _term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
676     }
677     else if ( 8 == cay_y || 127 == cay_y ) {
678       _term_io_belt(uty_u, u3nc(c3__bac, u3_nul));
679     }
680     else if ( 13 == cay_y ) {
681       _term_io_belt(uty_u, u3nc(c3__ret, u3_nul));
682     }
683 #if 0
684     else if ( 6 == cay_y ) {
685       _term_io_flow(uty_u);   // XX hack
686     }
687 #endif
688     else if ( cay_y <= 26 ) {
689       _term_io_belt(uty_u, u3nc(c3__ctl, ('a' + (cay_y - 1))));
690     }
691     else if ( 27 == cay_y ) {
692       tat_u->esc.ape = c3y;
693     }
694     else if ( cay_y >= 128 ) {
695       tat_u->fut.len_w = 1;
696       tat_u->fut.syb_y[0] = cay_y;
697 
698       if ( cay_y < 224 ) {
699         tat_u->fut.wid_w = 2;
700       } else if ( cay_y < 240 ) {
701         tat_u->fut.wid_w = 3;
702       } else tat_u->fut.wid_w = 4;
703     }
704   }
705 }
706 /* _term_suck(): process a chunk of input
707 */
708 
709 /*
710  * `nread` (siz_w) is > 0 if there is data available, 0 if libuv is done reading for
711  * now, or < 0 on error.
712  *
713  * The callee is responsible for closing the stream when an error happens
714  * by calling uv_close(). Trying to read from the stream again is undefined.
715  *
716  * The callee is responsible for freeing the buffer, libuv does not reuse it.
717  * The buffer may be a null buffer (where buf->base=NULL and buf->len=0) on
718  * error.
719  */
720 
721 static inline void
_term_suck(u3_utty * uty_u,const c3_y * buf,ssize_t siz_i)722 _term_suck(u3_utty* uty_u, const c3_y* buf, ssize_t siz_i)
723 {
724   u3_lo_open();
725   {
726     if ( siz_i == UV_EOF ) {
727       // nothing
728     } else   if ( siz_i < 0 ) {
729       uL(fprintf(uH, "term %d: read: %s\n", uty_u->tid_l, uv_strerror(siz_i)));
730     }
731     else {
732       c3_i i;
733 
734       for ( i=0; i < siz_i; i++ ) {
735         _term_io_suck_char(uty_u, buf[i]);
736       }
737     }
738   }
739   u3_lo_shut(c3y);
740 }
741 
742 /* _term_read_cb(): server read callback.
743 */
744 static void
_term_read_cb(uv_stream_t * tcp_u,ssize_t siz_i,const uv_buf_t * buf_u)745 _term_read_cb(uv_stream_t* tcp_u,
746               ssize_t      siz_i,
747               const uv_buf_t *     buf_u)
748 {
749   u3_utty* uty_u = (u3_utty*)(void*)tcp_u;
750   _term_suck(uty_u, (const c3_y*)buf_u->base, siz_i);
751   free(buf_u->base);
752 }
753 
754 /* _term_try_write_str(): write null-terminated string (off-thread, retain).
755 */
756 static void
_term_try_write_str(u3_utty * uty_u,const c3_c * hun_y)757 _term_try_write_str(u3_utty*    uty_u,
758                     const c3_c* hun_y)
759 {
760   // c3_i fid_i = uv_fileno(&uty_u->pop_u);
761   c3_i fid_i = uty_u->pop_u.io_watcher.fd;  //  XX old libuv
762   write(fid_i, hun_y, strlen(hun_y));
763 }
764 
765 /* _term_try_move_left(): move the cursor left (off-thread).
766 */
767 static void
_term_try_move_left(u3_utty * uty_u)768 _term_try_move_left(u3_utty* uty_u)
769 {
770   _term_try_write_str(uty_u, (const c3_c*)uty_u->ufo_u.out.cub1_y);
771 }
772 
773 /* _term_show_spinner(): render spinner (off-thread).
774 */
775 static void
_term_show_spinner(u3_utty * uty_u,c3_d lag_d)776 _term_show_spinner(u3_utty* uty_u, c3_d lag_d)
777 {
778   if ( 0 == uty_u->tat_u.sun.eve_d ) {
779     return;
780   }
781 
782   c3_w cus_w = uty_u->tat_u.mir.cus_w;
783 
784   if ( cus_w >= uty_u->tat_u.siz.col_l ) {  //  shenanigans!
785     return;
786   }
787 
788   c3_w bac_w = uty_u->tat_u.siz.col_l - 1 - cus_w;  //  backoff from end of line
789 
790   const c3_c daz_c[] = "|/-\\";
791   const c3_c dal_c[] = "\xc2\xab";
792   const c3_c dar_c[] = "\xc2\xbb";
793 
794   c3_c buf_c[1 + 2 +  4  + 2 + 1];
795   //         | + « + why + » + \0
796 
797   c3_c* cur_c = buf_c;
798 
799   *cur_c++ = daz_c[(lag_d / _SPIN_RATE_US) % strlen(daz_c)];
800   c3_w sol_w = 1;  //  spinner length (utf-32)
801 
802   c3_c* why_c = uty_u->tat_u.sun.why_c;
803   if ( why_c && strlen(why_c) <= 4 ) {
804     strcpy(cur_c, dal_c);
805     cur_c += strlen(dal_c);
806     sol_w += 1;  //  length of dal_c (utf-32)
807 
808     c3_w wel_w = strlen(why_c);
809     strcpy(cur_c, why_c);
810     cur_c += wel_w;
811     sol_w += wel_w;
812 
813     strcpy(cur_c, dar_c);
814     cur_c += strlen(dar_c);
815     sol_w += 1;  //  length of dar_c (utf-32)
816   }
817   *cur_c = '\0';
818 
819   //  One-time cursor backoff.
820   if ( c3n == uty_u->tat_u.sun.diz_o ) {
821     c3_w i_w;
822     for ( i_w = bac_w; i_w < sol_w; i_w++ ) {
823       _term_try_move_left(uty_u);
824     }
825   }
826 
827   _term_try_write_str(uty_u, buf_c);
828   uty_u->tat_u.sun.diz_o = c3y;
829 
830   //  Cursor stays on spinner.
831   while ( sol_w-- ) {
832     _term_try_move_left(uty_u);
833   }
834 }
835 
836 /* _term_start_spinner(): prepare spinner state. RETAIN.
837 */
838 static void
_term_start_spinner(u3_utty * uty_u,u3_noun ovo)839 _term_start_spinner(u3_utty* uty_u, u3_noun ovo)
840 {
841   uty_u->tat_u.sun.diz_o = c3n;
842 
843   c3_d now_d = _term_msc_out_host();
844 
845   //  If we receive an event shortly after a previous spin, use a shorter delay
846   //  to avoid giving the impression of a half-idle system.
847   //
848   c3_d lag_d;
849   if ( now_d - uty_u->tat_u.sun.end_d < _SPIN_IDLE_US ) {
850     lag_d = _SPIN_WARM_US;
851   }
852   else {
853     lag_d = _SPIN_COOL_US;
854   }
855 
856   u3_noun why = u3h(u3t(u3h(u3t(ovo))));
857   if ( c3__term == why ) {
858     u3_noun eve = u3t(u3t(ovo));
859     if ( c3__belt == u3h(eve) && c3__ret == u3h(u3t(eve)) ) {
860       lag_d = 0;  //  No delay for %ret.
861     }
862   }
863   else {
864     uty_u->tat_u.sun.why_c = (c3_c*)u3r_string(why);
865   }
866 
867   uty_u->tat_u.sun.eve_d = now_d + lag_d;
868 
869   uv_mutex_unlock(&uty_u->tat_u.mex_u);
870 }
871 
872 /* _term_stop_spinner(): reset spinner state and restore input line.
873 */
874 static void
_term_stop_spinner(u3_utty * uty_u)875 _term_stop_spinner(u3_utty* uty_u)
876 {
877   uv_mutex_lock(&uty_u->tat_u.mex_u);
878 
879   if ( c3y == uty_u->tat_u.sun.diz_o ) {
880     _term_it_refresh_line(uty_u);
881     uty_u->tat_u.sun.end_d = _term_msc_out_host();
882   }
883   else {
884     uty_u->tat_u.sun.end_d = 0;
885   }
886 
887   uty_u->tat_u.sun.diz_o = c3n;
888   uty_u->tat_u.sun.eve_d = 0;
889   free(uty_u->tat_u.sun.why_c);
890   uty_u->tat_u.sun.why_c = NULL;
891 }
892 
893 /* _term_spinner_cb(): manage spinner (off-thread).
894 */
895 static void
_term_spinner_cb(void * ptr_v)896 _term_spinner_cb(void* ptr_v)
897 {
898   //  This thread shouldn't receive signals.
899   //
900   {
901     sigset_t set;
902     sigfillset(&set);
903     pthread_sigmask(SIG_BLOCK, &set, NULL);
904   }
905 
906   u3_utty* uty_u = (u3_utty*)ptr_v;
907 
908   for ( uv_mutex_lock(&uty_u->tat_u.mex_u);
909         uty_u->tat_u.sun.sit_u;
910         uv_mutex_lock(&uty_u->tat_u.mex_u) )
911   {
912     c3_d eve_d = uty_u->tat_u.sun.eve_d;
913 
914     if ( 0 == eve_d ) {
915       c3_o diz_o = uty_u->tat_u.sun.diz_o;
916       uv_mutex_unlock(&uty_u->tat_u.mex_u);
917       usleep(c3y == diz_o ? _SPIN_WARM_US : _SPIN_COOL_US);
918     }
919     else {
920       c3_d now_d = _term_msc_out_host();
921 
922       if (now_d < eve_d) {
923         uv_mutex_unlock(&uty_u->tat_u.mex_u);
924         usleep(eve_d - now_d);
925       }
926       else {
927         _term_show_spinner(uty_u, now_d - eve_d);
928         uv_mutex_unlock(&uty_u->tat_u.mex_u);
929         usleep(_SPIN_RATE_US);
930       }
931     }
932   }
933 
934   uv_mutex_unlock(&uty_u->tat_u.mex_u);
935 }
936 
937 /* _term_main(): return main or console terminal.
938 */
939 static u3_utty*
_term_main()940 _term_main()
941 {
942   u3_utty* uty_u;
943 
944   for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
945     if ( (uty_u->fid_i != -1) && (uty_u->fid_i <= 2) ) {
946       return uty_u;
947     }
948   }
949   return u3_Host.uty_u;
950 }
951 
952 /* _term_ef_get(): terminal by id.
953 */
954 static u3_utty*
_term_ef_get(c3_l tid_l)955 _term_ef_get(c3_l     tid_l)
956 {
957   if ( 0 != tid_l ) {
958     u3_utty* uty_u;
959 
960     for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
961       if ( tid_l == uty_u->tid_l ) {
962         return uty_u;
963       }
964     }
965   }
966   return _term_main();
967 }
968 
969 /* u3_term_get_blew(): return window size [columns rows].
970 */
971 u3_noun
u3_term_get_blew(c3_l tid_l)972 u3_term_get_blew(c3_l tid_l)
973 {
974   u3_utty*       uty_u = _term_ef_get(tid_l);
975   c3_l           col_l, row_l;
976 
977   struct winsize siz_u;
978   if ( uty_u && (0 == ioctl(uty_u->fid_i, TIOCGWINSZ, &siz_u)) ) {
979     col_l = siz_u.ws_col;
980     row_l = siz_u.ws_row;
981   } else {
982     col_l = 80;
983     row_l = 24;
984   }
985 
986   if ( uty_u ) {
987     uty_u->tat_u.siz.col_l = col_l;
988     uty_u->tat_u.siz.row_l = row_l;
989   }
990 
991   return u3nc(col_l, row_l);
992 }
993 
994 /* u3_term_ef_winc(): window change.  Just console right now.
995 */
996 void
u3_term_ef_winc(void)997 u3_term_ef_winc(void)
998 {
999   u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1000 
1001   u3v_plan(pax, u3nc(c3__blew, u3_term_get_blew(1)));
1002 }
1003 
1004 /* u3_term_ef_ctlc(): send ^C on console.
1005 */
1006 void
u3_term_ef_ctlc(void)1007 u3_term_ef_ctlc(void)
1008 {
1009   u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1010 
1011   u3v_plan(pax, u3nt(c3__belt, c3__ctl, 'c'));
1012 
1013   _term_it_refresh_line(_term_main());
1014 }
1015 
1016 /* u3_term_ef_boil(): initial effects for loaded servers.
1017 */
1018 void
u3_term_ef_boil(void)1019 u3_term_ef_boil(void)
1020 {
1021   {
1022     u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1023 
1024     //  u3v_plan(u3k(pax), u3nc(c3__init, u3k(u3h(u3A->own))));
1025     u3v_plan(u3k(pax), u3nc(c3__harm, u3_nul));
1026     u3v_plan(u3k(pax), u3nc(c3__blew, u3_term_get_blew(1)));
1027     u3v_plan(u3k(pax), u3nc(c3__hail, u3_nul));
1028 
1029     u3z(pax);
1030   }
1031 }
1032 
1033 /* u3_term_ef_verb(): initial effects for verbose events
1034 */
1035 void
u3_term_ef_verb(void)1036 u3_term_ef_verb(void)
1037 {
1038   u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1039 
1040   u3v_plan(pax, u3nc(c3__verb, u3_nul));
1041 }
1042 
1043 /* u3_term_ef_ticket(): initial effects for new ticket.
1044 */
1045 void
u3_term_ef_ticket(c3_c * who_c,c3_c * tic_c)1046 u3_term_ef_ticket(c3_c* who_c, c3_c* tic_c)
1047 {
1048   u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1049   u3_noun who, tic;
1050   u3_noun whu, tuc;
1051 
1052   whu = u3dc("slaw", 'p', u3i_string(who_c));
1053   if ( u3_nul == whu ) {
1054     fprintf(stderr, "ticket: invalid planet '%s'\r\n", who_c);
1055     exit(1);
1056   }
1057   else { who = u3k(u3t(whu)); u3z(whu); }
1058 
1059   tuc = u3dc("slaw", 'p', u3i_string(tic_c));
1060   if ( u3_nul == tuc ) {
1061     fprintf(stderr, "ticket: invalid secret '%s'\r\n", tic_c);
1062     exit(1);
1063   }
1064   else { tic = u3k(u3t(tuc)); u3z(tuc); }
1065 
1066   u3v_plan(pax, u3nt(c3__tick, who, tic));
1067 }
1068 
1069 /* u3_term_ef_bake(): initial effects for new terminal.
1070 */
1071 void
u3_term_ef_bake(u3_noun fav)1072 u3_term_ef_bake(u3_noun fav)
1073 {
1074   u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
1075 
1076   u3v_plan(u3k(pax), u3nc(c3__boot, fav));
1077   // u3v_plan(u3k(pax), u3nq(c3__flow, c3__seat, c3__dojo, u3_nul));
1078   u3v_plan(u3k(pax), u3nc(c3__blew, u3_term_get_blew(1)));
1079   u3v_plan(u3k(pax), u3nc(c3__hail, u3_nul));
1080 
1081   u3z(pax);
1082 }
1083 
1084 /* _term_ef_blit(): send blit to terminal.
1085 */
1086 static void
_term_ef_blit(u3_utty * uty_u,u3_noun blt)1087 _term_ef_blit(u3_utty* uty_u,
1088               u3_noun  blt)
1089 {
1090   switch ( u3h(blt) ) {
1091     default: break;
1092     case c3__bee: {
1093       if ( c3n == u3_Host.ops_u.dem ) {
1094         if ( u3_nul == u3t(blt) ) {
1095           _term_stop_spinner(uty_u);
1096         }
1097         else {
1098           _term_start_spinner(uty_u, u3t(blt));
1099         }
1100       }
1101     } break;
1102 
1103     case c3__bel: {
1104       if ( c3n == u3_Host.ops_u.dem ) {
1105         _term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
1106       }
1107     } break;
1108 
1109     case c3__clr: {
1110       if ( c3n == u3_Host.ops_u.dem ) {
1111         _term_it_show_blank(uty_u);
1112         _term_it_refresh_line(uty_u);
1113       }
1114     } break;
1115 
1116     case c3__hop: {
1117       if ( c3n == u3_Host.ops_u.dem ) {
1118         _term_it_show_cursor(uty_u, u3t(blt));
1119       }
1120     } break;
1121 
1122     case c3__lin: {
1123       u3_noun lin = u3t(blt);
1124       c3_w    len_w = u3kb_lent(u3k(lin));
1125       c3_w*   lin_w = c3_malloc(4 * len_w);
1126 
1127       {
1128         c3_w i_w;
1129 
1130         for ( i_w = 0; u3_nul != lin; i_w++, lin = u3t(lin) ) {
1131           lin_w[i_w] = u3r_word(0, u3h(lin));
1132         }
1133       }
1134 
1135       if ( c3n == u3_Host.ops_u.dem ) {
1136         _term_it_show_clear(uty_u);
1137         _term_it_show_line(uty_u, lin_w, len_w);
1138       } else {
1139         _term_it_show_line(uty_u, lin_w, len_w);
1140       }
1141     } break;
1142 
1143     case c3__mor: {
1144       _term_it_show_more(uty_u);
1145     } break;
1146 
1147     case c3__sav: {
1148       _term_it_save(u3k(u3h(u3t(blt))), u3k(u3t(u3t(blt))));
1149     } break;
1150 
1151     case c3__sag: {
1152       u3_noun pib = u3k(u3t(u3t(blt)));
1153       u3_noun jam;
1154 
1155       jam = u3ke_jam(pib);
1156 
1157       _term_it_save(u3k(u3h(u3t(blt))), jam);
1158     } break;
1159 
1160     case c3__url: {
1161       if ( c3n == u3ud(u3t(blt)) ) {
1162         break;
1163       } else {
1164         c3_c* txt_c = u3r_string(u3t(blt));
1165 
1166         _term_it_show_clear(uty_u);
1167         _term_it_write_str(uty_u, txt_c);
1168         free(txt_c);
1169 
1170         _term_it_show_more(uty_u);
1171         _term_it_refresh_line(uty_u);
1172       }
1173     }
1174   }
1175   u3z(blt);
1176 
1177   return;
1178 }
1179 
1180 /* u3_term_ef_blit(): send %blit list to specific terminal.
1181 */
1182 void
u3_term_ef_blit(c3_l tid_l,u3_noun bls)1183 u3_term_ef_blit(c3_l     tid_l,
1184                 u3_noun  bls)
1185 {
1186   u3_utty* uty_u = _term_ef_get(tid_l);
1187 
1188   if ( 0 == uty_u ) {
1189     // uL(fprintf(uH, "no terminal %d\n", tid_l));
1190     // uL(fprintf(uH, "uty_u %p\n", u3_Host.uty_u));
1191 
1192     u3z(bls); return;
1193   }
1194 
1195   {
1196     u3_noun bis = bls;
1197 
1198     while ( c3y == u3du(bis) ) {
1199       _term_ef_blit(uty_u, u3k(u3h(bis)));
1200       bis = u3t(bis);
1201     }
1202     u3z(bls);
1203   }
1204 }
1205 
1206 /* u3_term_io_hija(): hijack console for fprintf, returning FILE*.
1207 */
1208 FILE*
u3_term_io_hija(void)1209 u3_term_io_hija(void)
1210 {
1211   u3_utty* uty_u = _term_main();
1212 
1213   if ( uty_u ) {
1214     if ( uty_u->fid_i > 2 ) {
1215       //  We *should* in fact, produce some kind of fake FILE* for
1216       //  non-console terminals.  If we use this interface enough...
1217       //
1218       c3_assert(0);
1219     }
1220     else {
1221       if ( c3n == u3_Host.ops_u.dem ) {
1222         if ( 0 != tcsetattr(1, TCSADRAIN, &uty_u->bak_u) ) {
1223           c3_assert(!"hija-tcsetattr");
1224         }
1225         if ( -1 == fcntl(1, F_SETFL, uty_u->cug_i) ) {
1226           c3_assert(!"hija-fcntl");
1227         }
1228         if ( 0 != tcsetattr(0, TCSADRAIN, &uty_u->bak_u) ) {
1229           c3_assert(!"hija-tcsetattr");
1230         }
1231         if ( -1 == fcntl(0, F_SETFL, uty_u->cug_i) ) {
1232           c3_assert(!"hija-fcntl");
1233         }
1234         write(uty_u->fid_i, "\r", 1);
1235         write(uty_u->fid_i, uty_u->ufo_u.out.el_y,
1236                             strlen((c3_c*) uty_u->ufo_u.out.el_y));
1237       }
1238       return stdout;
1239     }
1240   }
1241   else return stdout;
1242 }
1243 
1244 /* u3_term_io_loja(): release console from fprintf.
1245 */
1246 void
u3_term_io_loja(int x)1247 u3_term_io_loja(int x)
1248 {
1249   u3_utty* uty_u = _term_main();
1250 
1251   if ( uty_u ) {
1252     if ( uty_u->fid_i > 2 ) {
1253       //  We *should* in fact, produce some kind of fake FILE* for
1254       //  non-console terminals.  If we use this interface enough...
1255       //
1256       c3_assert(0);
1257     }
1258     else {
1259       if ( c3y == u3_Host.ops_u.dem ) {
1260         fflush(stdout);
1261       }
1262       else {
1263         if ( 0 != tcsetattr(1, TCSADRAIN, &uty_u->raw_u) ) {
1264           c3_assert(!"loja-tcsetattr");
1265         }
1266         if ( -1 == fcntl(1, F_SETFL, uty_u->nob_i) ) {
1267           c3_assert(!"loja-fcntl");
1268         }
1269         if ( 0 != tcsetattr(0, TCSADRAIN, &uty_u->raw_u) ) {
1270           c3_assert(!"loja-tcsetattr");
1271         }
1272         if ( -1 == fcntl(0, F_SETFL, uty_u->nob_i) ) {
1273           c3_assert(!"loja-fcntl");
1274         }
1275         _term_it_refresh_line(uty_u);
1276       }
1277     }
1278   }
1279 }
1280 
1281 /* u3_term_tape_to(): dump a tape to a file.
1282 */
1283 void
u3_term_tape_to(FILE * fil_f,u3_noun tep)1284 u3_term_tape_to(FILE *fil_f, u3_noun tep)
1285 {
1286   u3_noun tap = tep;
1287 
1288   while ( u3_nul != tap ) {
1289     c3_c car_c;
1290 
1291     if ( u3h(tap) >= 127 ) {
1292       car_c = '?';
1293     } else car_c = u3h(tap);
1294 
1295     putc(car_c, fil_f);
1296     tap = u3t(tap);
1297   }
1298   u3z(tep);
1299 }
1300 
1301 /* u3_term_tape(): dump a tape to stdout.
1302 */
1303 void
u3_term_tape(u3_noun tep)1304 u3_term_tape(u3_noun tep)
1305 {
1306   FILE* fil_f = u3_term_io_hija();
1307 
1308   u3_term_tape_to(fil_f, tep);
1309 
1310   u3_term_io_loja(0);
1311 }
1312 
1313 /* u3_term_wall(): dump a wall to stdout.
1314 */
1315 void
u3_term_wall(u3_noun wol)1316 u3_term_wall(u3_noun wol)
1317 {
1318   FILE* fil_f = u3_term_io_hija();
1319   u3_noun wal = wol;
1320 
1321   while ( u3_nul != wal ) {
1322     u3_term_tape_to(fil_f, u3k(u3h(wal)));
1323 
1324     putc(13, fil_f);
1325     putc(10, fil_f);
1326 
1327     wal = u3t(wal);
1328   }
1329   u3_term_io_loja(0);
1330 
1331   u3z(wol);
1332 }
1333