Minor fixes to comments.
[AROS.git] / rom / devs / console / support.c
blobb6023ac4289d4030e7e7ec713793aeaaeee17ad6
1 /*
2 Copyright © 1995-2001, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: Support functions for console.device
6 Lang: english
7 */
9 #include <proto/exec.h>
10 #include <exec/lists.h>
11 #include <exec/io.h>
12 #include <exec/memory.h>
14 #include <proto/intuition.h>
15 #include <intuition/classes.h>
17 #include <devices/conunit.h>
18 #include <string.h>
19 #include <stdio.h>
21 #include "console_gcc.h"
23 #include "consoleif.h"
25 #define SDEBUG 0
26 #define DEBUG 0
27 #include <aros/debug.h>
29 static BOOL getparamcommand(BYTE *cmd_ptr, UBYTE **writestr_ptr, UBYTE *numparams_ptr, LONG toparse, IPTR *p_tab, Object *unit, struct ConsoleBase *ConsoleDevice);
30 static BOOL string2command(BYTE *cmd_ptr, UBYTE **writestr_ptr, UBYTE *numparams_ptr, LONG toparse, IPTR *p_tab, Object *unit, struct ConsoleBase *ConsoleDevice);
32 #define ESC 0x1B
33 #define CSI 0x9B
35 #define NIL 0x00
36 #define BELL 0x07
37 #define BACKSPACE 0x08
38 #define HTAB 0x09
39 #define LINEFEED 0x0A
40 #define VTAB 0x0B
41 #define FORMFEED 0x0C
42 #define CARRIAGE_RETURN 0x0D
43 #define SHIFT_OUT 0x0E
44 #define SHIFT_IN 0x0F
45 #define INDEX 0x84
46 #define NEXT_LINE 0x85
47 #define H_TAB_SET 0x88
48 #define REVERSE_INDEX 0x8D
51 #define FIRST_CSI_CMD 0x40
53 /***********************
54 ** writeToConsole() **
55 ***********************/
58 ** SGR is the command with most params: 4
59 ** stegerg: RKRMs say it can have any number of parameters in any order. So instead of 4
60 ** we assume and hope that there will never be more than 16 params :-\
63 #define MAX_COMMAND_PARAMS 16
66 ULONG writeToConsole(struct ConUnit *unit, STRPTR buf, ULONG towrite, struct ConsoleBase *ConsoleDevice)
68 IPTR param_tab[MAX_COMMAND_PARAMS];
70 BYTE command = 0;
71 UBYTE numparams;
72 UBYTE *orig_write_str, *write_str;
73 LONG written, orig_towrite;
75 write_str = orig_write_str = (UBYTE *)buf;
78 EnterFunc(bug("WriteToConsole(ioreq=%p)\n"));
81 orig_towrite = towrite;
83 D(bug("Number of chars to write %d\n", towrite));
86 /* Interpret string into a command and execute command */
88 /* DEBUG aid */
90 #if DEBUG
92 UWORD i;
93 for (i = 0; i < towrite; i ++)
94 kprintf("%x", write_str[i]);
96 kprintf("\n");
99 #endif
100 while (towrite > 0)
102 numparams = 0;
104 if (!string2command(&command, &write_str, &numparams, towrite, param_tab, (Object *)unit, ConsoleDevice))
105 break;
108 Console_UnRenderCursor((Object *)unit);
109 Console_DoCommand((Object *)unit, command, numparams, param_tab);
110 Console_RenderCursor((Object *)unit);
112 towrite = orig_towrite - (write_str - orig_write_str);
114 } /* while (characters left to interpret) */
116 written = write_str - orig_write_str;
118 ReturnInt("WriteToConsole", LONG, written);
122 /**********************
123 ** string2command() **
124 **********************/
127 static const UBYTE str_slm[] = {0x32, 0x30, 0x68 }; /* Set linefeed mode */
128 static const UBYTE str_rnm[] = {0x32, 0x30, 0x6C }; /* Reset linefeed mode */
129 static const UBYTE str_ssm[] = {0x3E, 0x31, 0x68 }; /* Set autoscroll mode */
130 static const UBYTE str_rsm[] = {0x3E, 0x31, 0x6C }; /* Reset autoscroll mode */
131 static const UBYTE str_swm[] = {0x3E, 0x37, 0x68 }; /* Set autowrap mode */
132 static const UBYTE str_rwm[] = {0x3E, 0x37, 0x6C }; /* Reset autowrap mode */
133 static const UBYTE str_dsr[] = {0x36, 0x6E }; /* device status report */
134 static const UBYTE str_con[] = {' ', 'p'}; /* cursor visible */
135 static const UBYTE str_con2[] = {'1', ' ', 'p'}; /* cursor visible */
136 static const UBYTE str_cof[] = {'0', ' ', 'p'}; /* cursor invisible */
137 static const UBYTE str_srq1[] = {' ', 'q'}; /* window status request */
138 static const UBYTE str_srq2[] = {'0', ' ','q'}; /* window status request */
140 #define NUM_SPECIAL_COMMANDS 12
141 static const struct special_cmd_descr
143 BYTE Command;
144 STRPTR CommandStr;
145 BYTE Length;
146 } scd_tab[NUM_SPECIAL_COMMANDS] = {
148 {C_SET_LF_MODE, (STRPTR)str_slm, 3 },
149 {C_RESET_LF_MODE, (STRPTR)str_rnm, 3 },
150 {C_SET_AUTOSCROLL_MODE, (STRPTR)str_ssm, 3 },
151 {C_RESET_AUTOSCROLL_MODE, (STRPTR)str_rsm, 3 },
152 {C_SET_AUTOWRAP_MODE, (STRPTR)str_swm, 3 },
153 {C_RESET_AUTOWRAP_MODE, (STRPTR)str_rwm, 3 },
154 {C_DEVICE_STATUS_REPORT, (STRPTR)str_dsr, 2 },
155 {C_CURSOR_VISIBLE, (STRPTR)str_con, 2 },
156 {C_CURSOR_VISIBLE, (STRPTR)str_con2, 3 },
157 {C_CURSOR_INVISIBLE, (STRPTR)str_cof, 3 },
158 {C_WINDOW_STATUS_REQUEST, (STRPTR)str_srq1, 2 },
159 {C_WINDOW_STATUS_REQUEST, (STRPTR)str_srq2, 3 }
162 #if DEBUG
163 static UBYTE *cmd_names[NUM_CONSOLE_COMMANDS] =
166 "Ascii", /* C_ASCII = 0 */
168 "Esc", /* C_ESC */
169 "Bell", /* C_BELL, */
170 "Backspace", /* C_BACKSPACE, */
171 "HTab", /* C_HTAB, */
172 "Linefeed", /* C_LINEFEED, */
173 "VTab", /* C_VTAB, */
174 "Formefeed", /* C_FORMFEED, */
175 "Carriage return", /* C_CARRIAGE_RETURN, */
176 "Shift In", /* C_SHIFT_IN, */
177 "Shift Out", /* C_SHIFT_OUT, */
178 "Index", /* C_INDEX, */
179 "Nex Line", /* C_NEXT_LINE, */
180 "Tab set", /* C_H_TAB_SET, */
181 "Reverse Idx", /* C_REVERSE_IDX, */
182 "Set LF Mode", /* C_SET_LF_MODE, */
183 "Reset LF Mode", /* C_RESET_lF_MODE, */
184 "Device Status Report", /* C_DEVICE_STATUS_REPORT, */
186 "Insert Char", /* C_INSERT_CHAR, */
187 "Cursor Up", /* C_CURSOR_UP, */
188 "Cursor Down", /* C_CURSOR_DOWN, */
189 "Cursor Forward", /* C_CURSOR_FORWARD, */
190 "Cursor Backward", /* C_CURSOR_BACKWARD, */
191 "Cursor Next Line", /* C_CURSOR_NEXT_LINE, */
192 "Cursor Prev Line", /* C_CURSOR_PREV_LINE, */
193 "Cursor Pos", /* C_CURSOR_POS, */
194 "Cursor HTab", /* C_CURSOR_HTAB, */
195 "Erase In Display", /* C_ERASE_IN_DISPLAY, */
196 "Erase In Line", /* C_ERASE_IN_LINE, */
197 "Insert Line", /* C_INSERT_LINE, */
198 "Delete Line", /* C_DELETE_LINE, */
199 "Delete Char", /* C_DELETE_CHAR, */
200 "Scroll Up", /* C_SCROLL_UP, */
201 "Scroll Down", /* C_SCROLL_DOWN, */
202 "Cursor Tab Ctrl", /* C_CURSOR_TAB_CTRL, */
203 "Cursor Backtab", /* C_CURSOR_BACKTAB, */
204 "Select Graphic Rendition", /* C_SELECT_GRAPHIC_RENDITION */
205 "Window Status Request", /* C_WINDOW_STATUS_REQUEST */
206 "Cursor Visible", /* C_CURSOR_VISIBLE, */
207 "Cursor Invisible", /* C_CURSOR_INVISIBLE, */
208 "Set Raw Events", /* C_SET_RAWEVENTS, */
209 "Reset Raw Events", /* C_RESET_RAWEVENTS */
210 "Set Auto Wrap Mode", /* C_SET_AUTOWRAP_MODE */
211 "Reset Auto Wrap Mode", /* C_RESET_AUTOWRAP_MODE */
212 "Set Auto Scroll Mode", /* C_SET_AUTOSCROLL_MODE */
213 "Reset Auto Scroll Mode", /* C_RESET_AUTOSCROLL_MODE */
214 "Set Page Length", /* C_SET_PAGE_LENGTH */
215 "Set Line Length", /* C_SET_LINE_LENGTH */
216 "Set Left Offset", /* C_SET_LEFT_OFFSET */
217 "Set Top Offset" /* C_SET_TOP_OFFSET */
219 #endif
221 static BOOL check_special(STRPTR string, LONG toparse)
223 return
225 (*string == CSI) || (toparse >= 2 && (string[0] == ESC) && (string[1] == '[')) || /* CSI */
226 (*string == NIL) ||
227 (*string == BELL) ||
228 (*string == BACKSPACE) ||
229 (*string == HTAB) ||
230 (*string == LINEFEED) ||
231 (*string == FORMFEED) ||
232 (*string == CARRIAGE_RETURN) ||
233 (*string == SHIFT_OUT) ||
234 (*string == SHIFT_IN) ||
235 (*string == ESC) ||
236 (*string == INDEX) ||
237 (*string == H_TAB_SET) ||
238 (*string == REVERSE_INDEX)
242 static BOOL string2command( BYTE *cmd_ptr
243 , UBYTE **writestr_ptr
244 , UBYTE *numparams_ptr
245 , LONG toparse
246 , IPTR *p_tab
247 , Object *unit
248 , struct ConsoleBase *ConsoleDevice)
250 UBYTE *write_str = *writestr_ptr;
252 UBYTE *csi_str = write_str;
253 LONG csi_toparse = 0;
255 BOOL found = FALSE,
256 csi = FALSE;
259 EnterFunc(bug("StringToCommand(toparse=%d)\n", toparse));
262 /* Look for <CSI> */
263 if (*write_str == CSI)
266 csi_str ++;
267 csi = TRUE;
268 csi_toparse = toparse - 1;
270 else if (toparse >= 2)
272 if ( (write_str[0] == ESC) && (write_str[1] == '[') )
274 csi_str += 2;
275 csi_toparse = toparse - 2;
276 csi = TRUE;
280 if (csi)
282 D(bug("CSI found, getting command\n"));
284 /* Search for the longest commands first */
286 if (!found)
288 BYTE i;
289 /* Look for some special commands */
290 for (i = 0; ((i < NUM_SPECIAL_COMMANDS) && (!found)) ; i ++ )
292 /* Check whether command sequence is longer than input */
293 if (scd_tab[i].Length > csi_toparse)
294 continue; /* if so, check next command sequence */
296 D(bug("Comparing for special command %d, idx %d, cmdstr %p, len %d, csistr %p \n",
297 scd_tab[i].Command, i, scd_tab[i].CommandStr, scd_tab[i].Length, csi_str));
298 /* Command match ? */
299 if (0 == strncmp(csi_str, scd_tab[i].CommandStr, scd_tab[i].Length))
301 D(bug("Special command found\n"));
302 csi_str += scd_tab[i].Length;
303 *cmd_ptr = scd_tab[i].Command;
305 found = TRUE;
308 } /* for (each special command) */
312 /* A parameter command ? (Ie. one of the commands that takes parameters) */
313 if (!found)
314 found = getparamcommand(cmd_ptr, &csi_str, numparams_ptr, csi_toparse, p_tab, unit, ConsoleDevice);
316 } /* if (CSI was found) */
318 if (found)
319 write_str = csi_str;
320 else
322 /* Look for standalone codes */
323 switch (*write_str)
325 case NIL:
326 *cmd_ptr = C_NIL;
327 found = TRUE;
328 break;
330 case BELL:
331 *cmd_ptr = C_BELL;
332 found = TRUE;
333 break;
335 case BACKSPACE:
336 *cmd_ptr = C_BACKSPACE;
337 found = TRUE;
338 break;
340 case HTAB:
341 *cmd_ptr = C_HTAB;
342 found = TRUE;
343 break;
345 case LINEFEED:
346 *cmd_ptr = C_LINEFEED;
347 found = TRUE;
348 break;
350 case VTAB:
351 *cmd_ptr = C_VTAB;
352 found = TRUE;
353 break;
355 case FORMFEED:
356 *cmd_ptr = C_FORMFEED;
357 found = TRUE;
358 break;
360 case CARRIAGE_RETURN:
361 *cmd_ptr = C_CARRIAGE_RETURN;
362 found = TRUE;
363 break;
365 case SHIFT_OUT:
366 *cmd_ptr = C_SHIFT_OUT;
367 found = TRUE;
368 break;
370 case SHIFT_IN:
371 *cmd_ptr = C_SHIFT_IN;
372 found = TRUE;
373 break;
375 case ESC:
376 *cmd_ptr = C_ESC;
377 found = TRUE;
378 break;
380 case INDEX:
381 *cmd_ptr = C_INDEX;
382 found = TRUE;
383 break;
385 case NEXT_LINE:
386 *cmd_ptr = C_NEXT_LINE;
387 found = TRUE;
388 break;
390 case H_TAB_SET:
391 *cmd_ptr = C_H_TAB_SET;
392 found = TRUE;
393 break;
395 case REVERSE_INDEX:
396 *cmd_ptr = C_REVERSE_IDX;
397 found = TRUE;
398 break;
400 } /* (switch) */
402 if (found)
404 /* Found special char. Increase pointer */
406 write_str ++;
411 if (!found) /* Still not any found ? Try to print as plain ASCII */
413 *cmd_ptr = C_ASCII_STRING;
415 p_tab[0] = (IPTR)write_str;
416 *numparams_ptr = 2;
417 found = TRUE;
421 toparse--;
422 write_str++;
423 } while (toparse && !check_special(write_str, toparse));
425 p_tab[1] = (IPTR)(write_str - (UBYTE *)p_tab[0]); /* store the string length */
428 D(bug("FOUND CMD: %s\n", cmd_names[*cmd_ptr]));
430 /* Return pointer to first character AFTER last interpreted char */
431 *writestr_ptr = write_str;
433 ReturnBool ("StringToCommand", found);
437 /************************
438 ** getparamcommand() **
439 ************************/
441 /* !!! IMPORTANT !!!
442 If you add a command here, you should also add default values for
443 its parameters in Console::GetDefaultParams()
445 static const struct Command
447 BYTE Command;
448 UBYTE MaxParams;
450 } csi2command[] = {
452 { C_INSERT_CHAR , 1 }, /* 0x40 @ */
453 { C_CURSOR_UP , 1 }, /* 0x41 A */
454 { C_CURSOR_DOWN , 1 }, /* 0x42 B */
455 { C_CURSOR_FORWARD , 1 }, /* 0x43 C */
456 { C_CURSOR_BACKWARD , 1 }, /* 0x44 D */
457 { C_CURSOR_NEXT_LINE , 1 }, /* 0x45 E */
458 { C_CURSOR_PREV_LINE , 1 }, /* 0x46 F */
459 { -1 , }, /* 0x47 G */
460 { C_CURSOR_POS , 2 }, /* 0x48 H */
461 { C_CURSOR_HTAB , 1 }, /* 0x49 I */
463 { C_ERASE_IN_DISPLAY , 0 }, /* 0x4A J */
464 { C_ERASE_IN_LINE , 0 }, /* 0x4B K */
465 { C_INSERT_LINE , 0 }, /* 0x4C L */
466 { C_DELETE_LINE , 0 }, /* 0x4D M */
467 { -1 , }, /* 0x4E N */
468 { -1 , }, /* 0x4F O */
469 { C_DELETE_CHAR , 1 }, /* 0x50 P */
470 { -1 , }, /* 0x51 Q */
471 { -1 , }, /* 0x52 R */
472 { C_SCROLL_UP , 1 }, /* 0x53 S */
473 { C_SCROLL_DOWN , 1 }, /* 0x54 T */
474 { -1 , }, /* 0x55 U */
475 { -1 , }, /* 0x56 V */
476 { C_CURSOR_TAB_CTRL , 1 }, /* 0x57 W */
477 { -1 , }, /* 0x58 X */
478 { -1 , }, /* 0x59 Y */
479 { C_CURSOR_BACKTAB , 1 }, /* 0x5A Z */
480 { -1 , }, /* 0x5B [ */
481 { -1 , }, /* 0x5C \ */
482 { -1 , }, /* 0x5D ] */
483 { -1 , }, /* 0x5E ^ */
484 { -1 , }, /* 0x5F _ */
485 { -1 , }, /* 0x60 ` */
486 { -1 , }, /* 0x61 a */
487 { -1 , }, /* 0x62 b */
488 { -1 , }, /* 0x63 c */
489 { -1 , }, /* 0x64 d */
490 { -1 , }, /* 0x65 e */
491 { -1 , }, /* 0x66 f */
492 { -1 , }, /* 0x67 g */
493 { -1 , }, /* 0x68 h */
494 { -1 , }, /* 0x69 i */
495 { -1 , }, /* 0x6A j */
496 { -1 , }, /* 0x6B k */
497 { -1 , }, /* 0x6C l */
498 { C_SELECT_GRAPHIC_RENDITION, MAX_COMMAND_PARAMS }, /* 0x6D m */
499 { -1 , }, /* 0x6E n */
500 { -1 , }, /* 0x6F o */
501 { -1 , }, /* 0x70 p */
502 { -1 , }, /* 0x71 q */
503 { -1 , }, /* 0x72 r */
504 { -1 , }, /* 0x73 s */
505 { C_SET_PAGE_LENGTH , 1 }, /* 0x74 t */
506 { C_SET_LINE_LENGTH , }, /* 0x75 u */
507 { -1 , }, /* 0x76 v */
508 { -1 , }, /* 0x77 w */
509 { C_SET_LEFT_OFFSET , 1 }, /* 0x78 x */
510 { C_SET_TOP_OFFSET , 1 }, /* 0x79 y */
511 { -1 , }, /* 0x7A z */
512 { C_SET_RAWEVENTS , MAX_COMMAND_PARAMS }, /* 0x7B { */
513 { -1 , }, /* 0x7C | */
514 { C_RESET_RAWEVENTS , MAX_COMMAND_PARAMS }, /* 0x7D } */
518 #define PARAM_BUF_SIZE MAX_COMMAND_PARAMS
519 /* Parameters for commands are parsed and filled into this one */
520 struct cmd_params
522 UBYTE numparams; /* Parameters stored */
524 /* Since parameters may be optional, only supplied parameters
525 are saved, along with their number. For example
526 for the command CURSOR POSITION, if only the sencond parameter
527 (column) is specified in the write stream, then
528 numparams will be 1 and for the one entry, paramno will be 1 (C counting)
529 and val will be <column>.
530 Row will have to be set to some default value.
532 struct cmd_param
534 UBYTE paramno; /* Starts counting at 0 */
535 UBYTE val;
536 } tab[PARAM_BUF_SIZE];
540 static BOOL getparamcommand(BYTE *cmd_ptr
541 , UBYTE **writestr_ptr
542 , UBYTE *numparams_ptr
543 , LONG toparse
544 , IPTR *p_tab
545 , Object *unit
546 , struct ConsoleBase *ConsoleDevice)
548 /* This function checks for a command with parameters in
549 ** the string. The problem is that the parameters come
550 ** before the comand ID, and the parameters are optional.
551 ** This means that a parameter which has the same value
552 ** as a command ID may be mistakenly taken for being
553 ** end of the command. Therefore we must continue scanning
554 ** even if we found a command ID.
557 struct cmd_params params = {};
559 BYTE cmd = -1;
560 BYTE cmd_next_idx = 0; /* Index to byte after the command */
562 /* write_str points to first character after <CSI> */
563 UBYTE *write_str = *writestr_ptr;
565 UBYTE num_params = 0;
567 BOOL done = FALSE,
568 found = FALSE;
570 BOOL next_can_be_separator = TRUE,
571 next_can_be_param = TRUE,
572 next_can_be_commandid = TRUE,
573 last_was_param = FALSE;
575 UBYTE num_separators_found = 0;
577 while (!done)
579 /* In case it's a parameter */
581 if (toparse <= 0)
583 done = TRUE;
584 break;
587 switch (*write_str)
589 case 0x40:
590 case 0x41:
591 case 0x42:
592 case 0x43:
593 case 0x44:
594 case 0x45:
595 case 0x46:
596 case 0x48:
597 case 0x49:
599 case 0x4A:
600 case 0x4B:
601 case 0x4C:
602 case 0x4D:
604 case 0x50:
605 case 0x53:
606 case 0x54:
607 case 0x57:
608 case 0x5A:
609 case 0x6D:
610 case 0x74:
611 case 0x75:
612 case 0x78:
613 case 0x79:
614 case 0x7B:
615 case 0x7D:
617 UBYTE idx = *write_str - FIRST_CSI_CMD;
618 UBYTE maxparams = csi2command[idx].MaxParams;
620 if (next_can_be_commandid)
622 /* FIXME: Should also do a MinParams compare */
623 if (num_params <= maxparams) /* Valid command ? */
626 /* Assure that there are not to many separators in a command */
627 if ((num_separators_found < maxparams)
628 /* FIXME: 0-param commands can be moved to special-command-handlin in string2command() */
629 || ((num_separators_found == 0) && (maxparams == 0)))
631 cmd = csi2command[idx].Command;
633 /* Save index to where the next command will start */
634 cmd_next_idx = write_str - *writestr_ptr + 1;
636 params.numparams = num_params;
641 done = TRUE;
643 break;
646 case ';': /* parameter separator, skip it */
648 if (!next_can_be_separator)
650 /* Error */
651 done = TRUE;
652 break;
655 next_can_be_separator = FALSE;
656 next_can_be_param = TRUE;
657 next_can_be_commandid = FALSE;
658 last_was_param = FALSE;
660 num_separators_found ++;
662 break;
664 case '0':
665 case '1':
666 case '2':
667 case '3':
668 case '4':
669 case '5':
670 case '6':
671 case '7':
672 case '8':
673 case '9':
674 case '>': /* because of SGR background color param :-( */
675 if (!next_can_be_param)
677 /* Error */
678 done = TRUE;
679 break;
682 if (!last_was_param)
684 num_params++;
685 if (num_params > MAX_COMMAND_PARAMS)
687 done = TRUE;
688 break;
690 params.tab[num_params - 1].paramno = num_params - 1;
691 params.tab[num_params - 1].val = 0;
693 last_was_param = TRUE;
696 params.tab[num_params - 1].val *= 10;
697 if (*write_str == '>')
699 params.tab[num_params - 1].val += 5;
700 } else {
701 params.tab[num_params - 1].val += (*write_str) - '0';
704 next_can_be_separator = TRUE;
705 next_can_be_commandid = TRUE;
706 break;
708 default:
709 /* Error */
710 done = TRUE;
711 break;
713 } /* switch */
716 write_str ++;
717 toparse --;
719 } /* while (!done) */
721 if (cmd != -1)
723 *cmd_ptr = cmd;
724 found = TRUE;
726 /* Continue parsing on the first byte after the command */
727 *writestr_ptr += cmd_next_idx;
730 if (found)
732 UBYTE i;
733 /* First fill in some default values in p_tab */
734 Console_GetDefaultParams(unit, *cmd_ptr, p_tab);
736 for (i = 0; i < params.numparams; i ++)
738 /* Override with parsed values */
739 D(bug("CMD %s: Setting param %d to %d\n"
740 , cmd_names[*cmd_ptr]
741 , params.tab[i].paramno
742 , params.tab[i].val));
744 p_tab[params.tab[i].paramno] = params.tab[i].val;
747 *numparams_ptr = params.numparams;
750 return found;
753 VOID printstring(STRPTR string, ULONG len, struct ConsoleBase *ConsoleDevice)
755 while (len --)
757 kprintf("%d/%c ", *string, *string);
758 string ++;
761 kprintf("\n");