Played around with nitfol build process, adding rules to generate the
[rodin/chimara.git] / tests / multiwin.c
1 #include <libchimara/glk.h>
2
3 /* multiwin.c: Sample program for Glk API, version 0.5.
4     Designed by Andrew Plotkin <erkyrath@eblong.com>
5     http://www.eblong.com/zarf/glk/index.html
6     This program is in the public domain.
7 */
8
9 /* This example demonstrates multiple windows and timed input in the
10     Glk API. */
11
12 /* This is the cleanest possible form of a Glk program. It includes only
13     "glk.h", and doesn't call any functions outside Glk at all. We even
14     define our own string functions, rather than relying on the
15     standard libraries. */
16
17 /* We also define our own TRUE and FALSE and NULL. */
18 #ifndef TRUE
19 #define TRUE 1
20 #endif
21 #ifndef FALSE
22 #define FALSE 0
23 #endif
24 #ifndef NULL
25 #define NULL 0
26 #endif
27
28 /* The story and status windows. */
29 static winid_t mainwin1 = NULL;
30 static winid_t mainwin2 = NULL;
31 static winid_t statuswin = NULL;
32
33 /* Key windows don't get stored in a global variable; we'll find them
34     by iterating over the list and looking for this rock value. */
35 #define KEYWINROCK (97)
36
37 /* For the two main windows, we keep a flag saying whether that window
38     has a line input request pending. (Because if it does, we need to
39     cancel the line input before printing to that window.) */
40 static int inputpending1, inputpending2;
41 /* When we cancel line input, we should remember how many characters
42     had been typed. This lets us restart the input with those characters
43     already in place. */
44 static int already1, already2;
45
46 /* There's a three-second timer which can be on or off. */
47 static int timer_on = FALSE;
48
49 /* Forward declarations */
50 void glk_main(void);
51
52 static void draw_statuswin(void);
53 static void draw_keywins(void);
54 static void perform_key(winid_t win, glui32 key);
55 static void perform_timer(void);
56
57 static int str_eq(char *s1, char *s2);
58 static int str_len(char *s1);
59 static char *str_cpy(char *s1, char *s2);
60 static char *str_cat(char *s1, char *s2);
61 static void num_to_str(char *buf, int num);
62
63 static void verb_help(winid_t win);
64 static void verb_jump(winid_t win);
65 static void verb_yada(winid_t win);
66 static void verb_both(winid_t win);
67 static void verb_clear(winid_t win);
68 static void verb_page(winid_t win);
69 static void verb_pageboth(winid_t win);
70 static void verb_timer(winid_t win);
71 static void verb_untimer(winid_t win);
72 static void verb_chars(winid_t win);
73 static void verb_quit(winid_t win);
74
75 /* The glk_main() function is called by the Glk system; it's the main entry
76     point for your program. */
77 void glk_main(void)
78 {
79     char commandbuf1[256]; /* For mainwin1 */
80     char commandbuf2[256]; /* For mainwin2 */
81
82     /* Open the main windows. */
83     mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
84     if (!mainwin1) {
85         /* It's possible that the main window failed to open. There's
86             nothing we can do without it, so exit. */
87         return; 
88     }
89     
90     /* Open a second window: a text grid, above the main window, five 
91         lines high. It is possible that this will fail also, but we accept 
92         that. */
93     statuswin = glk_window_open(mainwin1, 
94         winmethod_Above | winmethod_Fixed, 
95         5, wintype_TextGrid, 0);
96         
97     /* And a third window, a second story window below the main one. */
98     mainwin2 = glk_window_open(mainwin1, 
99         winmethod_Below | winmethod_Proportional, 
100         50, wintype_TextBuffer, 0);
101         
102     /* We're going to be switching from one window to another all the
103         time. So we'll be setting the output stream on a case-by-case
104         basis. Every function that prints must set the output stream
105         first. (Contrast model.c, where the output stream is always the
106         main window, and every function that changes that must set it
107         back afterwards.) */
108     
109     glk_set_window(mainwin1);
110     glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n");
111     glk_put_string("By Andrew Plotkin.\nRelease 3.\n");
112     glk_put_string("Type \"help\" for a list of commands.\n");
113     
114     glk_set_window(mainwin2);
115     glk_put_string("Note that the upper left-hand window accepts character");
116     glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to");
117     glk_put_string(" split the window vertically, 'c' to close a window,");
118     glk_put_string(" and any other key (including special keys) to display");
119     glk_put_string(" key codes. All new windows accept these same keys as");
120     glk_put_string(" well.\n\n");
121     glk_put_string("This bottom window accepts normal line input.\n");
122     
123     if (statuswin) {
124         /* For fun, let's open a fourth window now, splitting the status
125             window. */
126         winid_t keywin;
127         keywin = glk_window_open(statuswin, 
128             winmethod_Left | winmethod_Proportional, 
129             66, wintype_TextGrid, KEYWINROCK);
130         if (keywin) {
131             glk_request_char_event(keywin);
132         }
133     }
134     
135     /* Draw the key window now, since we don't draw it every input (as
136         we do the status window. */
137     draw_keywins();
138
139     inputpending1 = FALSE;
140     inputpending2 = FALSE;
141     already1 = 0;
142     already2 = 0;
143     
144     while (1) {
145         char *cx, *cmd;
146         int doneloop, len;
147         winid_t whichwin;
148         event_t ev;
149         
150         draw_statuswin();
151         /* We're not redrawing the key windows every command. */
152         
153         /* Either main window, or both, could already have line input
154             pending. If so, leave that window alone. If there is no
155             input pending on a window, set a line input request, but
156             keep around any characters that were in the buffer already. */
157         
158         if (mainwin1 && !inputpending1) {
159             glk_set_window(mainwin1);
160             glk_put_string("\n>");
161             /* We request up to 255 characters. The buffer can hold 256, 
162                 but we are going to stick a null character at the end, so 
163                 we have to leave room for that. Note that the Glk library 
164                 does *not* put on that null character. */
165             glk_request_line_event(mainwin1, commandbuf1, 255, already1);
166             inputpending1 = TRUE;
167         }
168         
169         if (mainwin2 && !inputpending2) {
170             glk_set_window(mainwin2);
171             glk_put_string("\n>");
172             /* See above. */
173             glk_request_line_event(mainwin2, commandbuf2, 255, already2);
174             inputpending2 = TRUE;
175         }
176         
177         doneloop = FALSE;
178         while (!doneloop) {
179         
180             /* Grab an event. */
181             glk_select(&ev);
182             
183             switch (ev.type) {
184             
185                 case evtype_LineInput:
186                     /* If the event comes from one main window or the other,
187                         we mark that window as no longer having line input
188                         pending. We also set commandbuf to point to the
189                         appropriate buffer. Then we leave the event loop. */
190                     if (mainwin1 && ev.win == mainwin1) {
191                         whichwin = mainwin1;
192                         inputpending1 = FALSE;
193                         cmd = commandbuf1;
194                         doneloop = TRUE;
195                     }
196                     else if (mainwin2 && ev.win == mainwin2) {
197                         whichwin = mainwin2;
198                         inputpending2 = FALSE;
199                         cmd = commandbuf2;
200                         doneloop = TRUE;
201                     }
202                     break;
203                     
204                 case evtype_CharInput:
205                     /* It's a key event, from one of the keywins. We
206                         call a subroutine rather than exiting the
207                         event loop (although I could have done it
208                         that way too.) */
209                     perform_key(ev.win, ev.val1);
210                     break;
211                 
212                 case evtype_Timer:
213                     /* It's a timer event. This does exit from the event
214                         loop, since we're going to interrupt input in
215                         mainwin1 and then re-print the prompt. */
216                     whichwin = NULL;
217                     cmd = NULL; 
218                     doneloop = TRUE;
219                     break;
220                     
221                 case evtype_Arrange:
222                     /* Windows have changed size, so we have to redraw the
223                         status window and key window. But we stay in the
224                         event loop. */
225                     draw_statuswin();
226                     draw_keywins();
227                     break;
228             }
229         }
230         
231         if (cmd == NULL) {
232             /* It was a timer event. */
233             perform_timer();
234             continue;
235         }
236         
237         /* It was a line input event. cmd now points at a line of input
238             from one of the main windows. */
239         
240         /* The line we have received in commandbuf is not null-terminated.
241             We handle that first. */
242         len = ev.val1; /* Will be between 0 and 255, inclusive. */
243         cmd[len] = '\0';
244         
245         /* Then squash to lower-case. */
246         for (cx = cmd; *cx; cx++) { 
247             *cx = glk_char_to_lower(*cx);
248         }
249         
250         /* Then trim whitespace before and after. */
251         
252         for (cx = cmd; *cx == ' '; cx++, len--) { };
253         
254         cmd = cx;
255         
256         for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { };
257         *(cx+1) = '\0';
258         
259         /* cmd now points to a nice null-terminated string. We'll do the
260             simplest possible parsing. */
261         if (str_eq(cmd, "")) {
262             glk_set_window(whichwin);
263             glk_put_string("Excuse me?\n");
264         }
265         else if (str_eq(cmd, "help")) {
266             verb_help(whichwin);
267         }
268         else if (str_eq(cmd, "yada")) {
269             verb_yada(whichwin);
270         }
271         else if (str_eq(cmd, "both")) {
272             verb_both(whichwin);
273         }
274         else if (str_eq(cmd, "clear")) {
275             verb_clear(whichwin);
276         }
277         else if (str_eq(cmd, "page")) {
278             verb_page(whichwin);
279         }
280         else if (str_eq(cmd, "pageboth")) {
281             verb_pageboth(whichwin);
282         }
283         else if (str_eq(cmd, "timer")) {
284             verb_timer(whichwin);
285         }
286         else if (str_eq(cmd, "untimer")) {
287             verb_untimer(whichwin);
288         }
289         else if (str_eq(cmd, "chars")) {
290             verb_chars(whichwin);
291         }
292         else if (str_eq(cmd, "jump")) {
293             verb_jump(whichwin);
294         }
295         else if (str_eq(cmd, "quit")) {
296             verb_quit(whichwin);
297         }
298         else {
299             glk_set_window(whichwin);
300             glk_put_string("I don't understand the command \"");
301             glk_put_string(cmd);
302             glk_put_string("\".\n");
303         }
304         
305         if (whichwin == mainwin1)
306             already1 = 0;
307         else if (whichwin == mainwin2)
308             already2 = 0;
309     }
310 }
311
312 static void draw_statuswin(void)
313 {
314     glui32 width, height;
315     
316     if (!statuswin) {
317         /* It is possible that the window was not successfully 
318             created. If that's the case, don't try to draw it. */
319         return;
320     }
321     
322     glk_set_window(statuswin);
323     glk_window_clear(statuswin);
324     
325     glk_window_get_size(statuswin, &width, &height);
326     
327     /* Draw a decorative compass rose in the center. */
328     width = (width/2);
329     if (width > 0)
330         width--;
331     height = (height/2);
332     if (height > 0)
333         height--;
334         
335     glk_window_move_cursor(statuswin, width, height+0);
336     glk_put_string("\\|/");
337     glk_window_move_cursor(statuswin, width, height+1);
338     glk_put_string("-*-");
339     glk_window_move_cursor(statuswin, width, height+2);
340     glk_put_string("/|\\");
341     
342 }
343
344 /* This draws some corner decorations in *every* key window -- the
345     one created at startup, and any later ones. It finds them all
346     with glk_window_iterate. */
347 static void draw_keywins(void)
348 {
349     winid_t win;
350     glui32 rock;
351     glui32 width, height;
352     
353     for (win = glk_window_iterate(NULL, &rock);
354             win;
355             win = glk_window_iterate(win, &rock)) {
356         if (rock == KEYWINROCK) {
357             glk_set_window(win);
358             glk_window_clear(win);
359             glk_window_get_size(win, &width, &height);
360             glk_window_move_cursor(win, 0, 0);
361             glk_put_char('O');
362             glk_window_move_cursor(win, width-1, 0);
363             glk_put_char('O');
364             glk_window_move_cursor(win, 0, height-1);
365             glk_put_char('O');
366             glk_window_move_cursor(win, width-1, height-1);
367             glk_put_char('O');
368         }
369     }
370 }
371
372 /* React to character input in a key window. */
373 static void perform_key(winid_t win, glui32 key)
374 {
375     glui32 width, height, len;
376     int ix;
377     char buf[128], keyname[64];
378     
379     if (key == 'h' || key == 'v') {
380         winid_t newwin;
381         glui32 loc;
382         /* Open a new keywindow. */
383         if (key == 'h')
384             loc = winmethod_Right | winmethod_Proportional;
385         else
386             loc = winmethod_Below | winmethod_Proportional;
387         newwin = glk_window_open(win, 
388             loc, 50, wintype_TextGrid, KEYWINROCK);
389         /* Since the new window has rock value KEYWINROCK, the
390             draw_keywins() routine will redraw it. */
391         if (newwin) {
392             /* Request character input. In this program, only keywins
393                 get char input, so the CharInput events always call
394                 perform_key() -- and so the new window will respond
395                 to keys just as this one does. */
396             glk_request_char_event(newwin);
397             /* We now have to redraw the keywins, because any or all of
398                 them could have changed size when we opened newwin.
399                 glk_window_open() does not generate Arrange events; we
400                 have to do the redrawing manually. */
401             draw_keywins();
402         }
403         /* Re-request character input for this window, so that future
404             keys are accepted. */
405         glk_request_char_event(win);
406         return;
407     }
408     else if (key == 'c') {
409         /* Close this keywindow. */
410         glk_window_close(win, NULL);
411         /* Again, any key windows could have changed size. Also the
412             status window could have (if this was the last key window). */
413         draw_keywins();
414         draw_statuswin();
415         return;
416     }
417     
418     /* Print a string naming the key that was just hit. */
419     
420     switch (key) {
421         case ' ':
422             str_cpy(keyname, "space");
423             break;
424         case keycode_Left:
425             str_cpy(keyname, "left");
426             break;
427         case keycode_Right:
428             str_cpy(keyname, "right");
429             break;
430         case keycode_Up:
431             str_cpy(keyname, "up");
432             break;
433         case keycode_Down:
434             str_cpy(keyname, "down");
435             break;
436         case keycode_Return:
437             str_cpy(keyname, "return");
438             break;
439         case keycode_Delete:
440             str_cpy(keyname, "delete");
441             break;
442         case keycode_Escape:
443             str_cpy(keyname, "escape");
444             break;
445         case keycode_Tab:
446             str_cpy(keyname, "tab");
447             break;
448         case keycode_PageUp:
449             str_cpy(keyname, "page up");
450             break;
451         case keycode_PageDown:
452             str_cpy(keyname, "page down");
453             break;
454         case keycode_Home:
455             str_cpy(keyname, "home");
456             break;
457         case keycode_End:
458             str_cpy(keyname, "end");
459             break;
460         default:
461             if (key >= keycode_Func1 && key < keycode_Func12) {
462                 str_cpy(keyname, "function key");
463             }
464             else if (key < 32) {
465                 str_cpy(keyname, "ctrl-");
466                 keyname[5] = '@' + key;
467                 keyname[6] = '\0';
468             }
469             else if (key <= 255) {
470                 keyname[0] = key;
471                 keyname[1] = '\0';
472             }
473             else {
474                 str_cpy(keyname, "unknown key");
475             }
476             break;
477     }
478     
479     str_cpy(buf, "Key: ");
480     str_cat(buf, keyname);
481     
482     len = str_len(buf);
483     
484     /* Print the string centered in this window. */
485     glk_set_window(win);
486     glk_window_get_size(win, &width, &height);
487     glk_window_move_cursor(win, 0, height/2);
488     for (ix=0; ix<width; ix++)
489         glk_put_char(' ');
490         
491     width = width/2;
492     len = len/2;
493     
494     if (width > len)
495         width = width-len;
496     else
497         width = 0;
498     
499     glk_window_move_cursor(win, width, height/2);
500     glk_put_string(buf);
501     
502     /* Re-request character input for this window, so that future
503         keys are accepted. */
504     glk_request_char_event(win);
505 }
506
507 /* React to a timer event. This just prints "Tick" in mainwin1, but it
508     first has to cancel line input if any is pending. */
509 static void perform_timer()
510 {
511     event_t ev;
512     
513     if (!mainwin1)
514         return;
515     
516     if (inputpending1) {
517         glk_cancel_line_event(mainwin1, &ev);
518         if (ev.type == evtype_LineInput)
519             already1 = ev.val1;
520         inputpending1 = FALSE;
521     }
522
523     glk_set_window(mainwin1);
524     glk_put_string("Tick.\n");
525 }
526
527 /* This is a utility function. Given a main window, it finds the
528     "other" main window (if both actually exist) and cancels line
529     input in that other window (if input is pending.) It does not
530     set the output stream to point there, however. If there is only
531     one main window, this returns 0. */
532 static winid_t print_to_otherwin(winid_t win)
533 {
534     winid_t otherwin = NULL;
535     event_t ev;
536
537     if (win == mainwin1) {
538         if (mainwin2) {
539             otherwin = mainwin2;
540             glk_cancel_line_event(mainwin2, &ev);
541             if (ev.type == evtype_LineInput)
542                 already2 = ev.val1;
543             inputpending2 = FALSE;
544         }
545     }
546     else if (win == mainwin2) {
547         if (mainwin1) {
548             otherwin = mainwin1;
549             glk_cancel_line_event(mainwin1, &ev);
550             if (ev.type == evtype_LineInput)
551                 already1 = ev.val1;
552             inputpending1 = FALSE;
553         }
554     }
555     
556     return otherwin;
557 }
558
559 static void verb_help(winid_t win)
560 {
561     glk_set_window(win);
562     
563     glk_put_string("This model only understands the following commands:\n");
564     glk_put_string("HELP: Display this list.\n");
565     glk_put_string("JUMP: Print a short message.\n");
566     glk_put_string("YADA: Print a long paragraph.\n");
567     glk_put_string("BOTH: Print a short message in both main windows.\n");
568     glk_put_string("CLEAR: Clear one window.\n");
569     glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n");
570     glk_put_string("PAGEBOTH: Print thirty lines in each window.\n");
571     glk_put_string("TIMER: Turn on a timer, which ticks in the upper ");
572     glk_put_string("main window every three seconds.\n");
573     glk_put_string("UNTIMER: Turns off the timer.\n");
574     glk_put_string("CHARS: Prints the entire Latin-1 character set.\n");
575     glk_put_string("QUIT: Quit and exit.\n");
576 }
577
578 static void verb_jump(winid_t win)
579 {
580     glk_set_window(win);
581     
582     glk_put_string("You jump on the fruit, spotlessly.\n");
583 }
584
585 /* Print some text in both windows. This uses print_to_otherwin() to
586     find the other window and prepare it for printing. */
587 static void verb_both(winid_t win)
588 {
589     winid_t otherwin;
590     
591     glk_set_window(win);
592     glk_put_string("Something happens in this window.\n");
593     
594     otherwin = print_to_otherwin(win);
595     
596     if (otherwin) {
597         glk_set_window(otherwin);
598         glk_put_string("Something happens in the other window.\n");
599     }
600 }
601
602 /* Clear a window. */
603 static void verb_clear(winid_t win)
604 {
605     glk_window_clear(win);
606 }
607
608 /* Print thirty lines. */
609 static void verb_page(winid_t win)
610 {
611     int ix;
612     char buf[32];
613     
614     glk_set_window(win);
615     for (ix=0; ix<30; ix++) {
616         num_to_str(buf, ix);
617         glk_put_string(buf);
618         glk_put_char('\n');
619     }
620 }
621
622 /* Print thirty lines in both windows. This gets fancy by printing
623     to each window alternately, without setting the output stream,
624     by using glk_put_string_stream() instead of glk_put_string(). 
625     There's no particular difference; this is just a demonstration. */
626 static void verb_pageboth(winid_t win)
627 {
628     int ix;
629     winid_t otherwin;
630     strid_t str, otherstr;
631     char buf[32];
632     
633     str = glk_window_get_stream(win);
634     otherwin = print_to_otherwin(win);
635     if (otherwin) 
636         otherstr = glk_window_get_stream(otherwin);
637     else
638         otherstr = NULL;
639
640     for (ix=0; ix<30; ix++) {
641         num_to_str(buf, ix);
642         str_cat(buf, "\n");
643         glk_put_string_stream(str, buf);
644         if (otherstr)
645             glk_put_string_stream(otherstr, buf);
646     }
647 }
648
649 /* Turn on the timer. The timer prints a tick in mainwin1 every three
650     seconds. */
651 static void verb_timer(winid_t win)
652 {
653     glk_set_window(win);
654     
655     if (timer_on) {
656         glk_put_string("The timer is already running.\n");
657         return;
658     }
659     
660     if (glk_gestalt(gestalt_Timer, 0) == 0) {
661         glk_put_string("Your Glk library does not support timer events.\n");
662         return;
663     }
664     
665     glk_put_string("A timer starts running in the upper window.\n");
666     glk_request_timer_events(3000); /* Every three seconds. */
667     timer_on = TRUE;
668 }
669
670 /* Turn off the timer. */
671 static void verb_untimer(winid_t win)
672 {
673     glk_set_window(win);
674     
675     if (!timer_on) {
676         glk_put_string("The timer is not currently running.\n");
677         return;
678     }
679     
680     glk_put_string("The timer stops running.\n");
681     glk_request_timer_events(0);
682     timer_on = FALSE;
683 }
684
685 /* Print every character, or rather try to. */
686 static void verb_chars(winid_t win)
687 {
688     int ix;
689     char buf[16];
690     
691     glk_set_window(win);
692     
693     for (ix=0; ix<256; ix++) {
694         num_to_str(buf, ix);
695         glk_put_string(buf);
696         glk_put_string(": ");
697         glk_put_char(ix);
698         glk_put_char('\n');
699     }
700 }
701
702 static void verb_yada(winid_t win)
703 {
704     /* This is a goofy (and overly ornate) way to print a long paragraph. 
705         It just shows off line wrapping in the Glk implementation. */
706     #define NUMWORDS (13)
707     static char *wordcaplist[NUMWORDS] = {
708         "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
709             "Ha", "Ni", "Na"
710     };
711     static char *wordlist[NUMWORDS] = {
712         "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
713             "gazoon", "ting", "floo", "zonk", "loof", "lob",
714     };
715     static int wcount1 = 0;
716     static int wcount2 = 0;
717     static int wstep = 1;
718     static int jx = 0;
719     int ix;
720     int first = TRUE;
721     
722     glk_set_window(win);
723     
724     for (ix=0; ix<85; ix++) {
725         if (ix > 0) {
726             glk_put_string(" ");
727         }
728                 
729         if (first) {
730             glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
731             first = FALSE;
732         }
733         
734         glk_put_string(wordlist[jx]);
735         jx = (jx + wstep) % NUMWORDS;
736         wcount1++;
737         if (wcount1 >= NUMWORDS) {
738             wcount1 = 0;
739             wstep++;
740             wcount2++;
741             if (wcount2 >= NUMWORDS-2) {
742                 wcount2 = 0;
743                 wstep = 1;
744             }
745         }
746         
747         if ((ix % 17) == 16) {
748             glk_put_string(".");
749             first = TRUE;
750         }
751     }
752     
753     glk_put_char('\n');
754 }
755
756 static void verb_quit(winid_t win)
757 {
758     glk_set_window(win);
759     
760     glk_put_string("Thanks for playing.\n");
761     glk_exit();
762     /* glk_exit() actually stops the process; it does not return. */
763 }
764
765 /* simple string length test */
766 static int str_len(char *s1)
767 {
768     int len;
769     for (len = 0; *s1; s1++)
770         len++;
771     return len;
772 }
773
774 /* simple string comparison test */
775 static int str_eq(char *s1, char *s2)
776 {
777     for (; *s1 && *s2; s1++, s2++) {
778         if (*s1 != *s2)
779             return FALSE;
780     }
781     
782     if (*s1 || *s2)
783         return FALSE;
784     else
785         return TRUE;
786 }
787
788 /* simple string copy */
789 static char *str_cpy(char *s1, char *s2)
790 {
791     char *orig = s1;
792     
793     for (; *s2; s1++, s2++)
794         *s1 = *s2;
795     *s1 = '\0';
796     
797     return orig;
798 }
799
800 /* simple string concatenate */
801 static char *str_cat(char *s1, char *s2)
802 {
803     char *orig = s1;
804     
805     while (*s1)
806         s1++;
807     for (; *s2; s1++, s2++)
808         *s1 = *s2;
809     *s1 = '\0';
810     
811     return orig;
812 }
813
814 /* simple number printer */
815 static void num_to_str(char *buf, int num)
816 {
817     int ix;
818     int size = 0;
819     char tmpc;
820     
821     if (num == 0) {
822         str_cpy(buf, "0");
823         return;
824     }
825     
826     if (num < 0) {
827         buf[0] = '-';
828         buf++;
829         num = -num;
830     }
831     
832     while (num) {
833         buf[size] = '0' + (num % 10);
834         size++;
835         num /= 10;
836     }
837     for (ix=0; ix<size/2; ix++) {
838         tmpc = buf[ix];
839         buf[ix] = buf[size-ix-1];
840         buf[size-ix-1] = tmpc;
841     }
842     buf[size] = '\0';
843 }