First Commit of my working state
[simh.git] / Ibm1130 / ibm1130_stddev.c
1 /* ibm1130_stddev.c: IBM 1130 standard I/O devices simulator
2
3 Based on the SIMH simulator package written by Robert M Supnik
4
5 Brian Knittel
6
7 Revision History:
8
9 2004.10.22 - Removed stub for xio_1134_papertape as it's now a supported device
10
11 2003.11.23 - Fixed bug in new routine "quotefix" that made sim crash
12 for all non-Windows builds :(
13
14 2003.06.15 - added output translation code to accomodate APL font
15 added input translation feature to assist emulation of 1130 console keyboard for APL
16 changes to console input and output IO emulation, fixed bugs exposed by APL interpreter
17
18 2002.09.13 - pulled 1132 printer out of this file into ibm1130_prt.c
19
20 * (C) Copyright 2002, Brian Knittel.
21 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
22 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
23 * usual yada-yada. Please keep this notice and the copyright in any distributions
24 * or modifications.
25 *
26 * This is not a supported product, but I welcome bug reports and fixes.
27 * Mail to simh@ibm1130.org
28 *
29 * Notes about overstrike mapping:
30 * The 1130 console printer used a Selectric typewriter element. The APL interpreter
31 * used overprinting to construct some APL symbols, for example, a round O overstruck]
32 * with | to get the greek phi. This doesn't accomodate a glass terminal! Instead,
33 * modern APL fonts have separate character codes for the complex characters.
34 * To have APL\1130 output appear correctly, we have to do three things:
35 *
36 * use simh's telnet feature to connect to the 1130 console stream
37 * have the telnet program use an APL font
38 * detect combinations of overstruck symbols, and generate the approrpiate alternate codes.
39 *
40 * There is a built-in table of font mappings and overstrike mappings, for the APLPLUS.TTF
41 * truetype font widely available on the Internet. An font descriptor file can be used
42 * to specify alternate mappings.
43 *
44 * The APL font codes and overstrike mapping can be enabled with the simh command
45 *
46 * set tto apl
47 *
48 * and disabled with
49 *
50 * set tto ascii (this is the default)
51 *
52 * APL also uses the red and black ribbon selection. The emulator will output
53 * ansi red/black foreground commands with the setting
54 *
55 * set tto ansi
56 *
57 * The codes can be disabled with
58 *
59 * set tto noansi (this is the default)
60 *
61 * Finally, when APL mode is active, the emulator does some input key translations
62 * to let the standard ASCII keyboard more closely match the physical layout of the
63 * 1130 console keyboard. The numeric and punctuation key don't have their
64 * traditional meaning under APL. The input mapping lets you use the APL keyboard
65 * layout shown in the APL documentation.
66 *
67 * The translations are:
68 * FROM
69 * ASCII Position on keyboard To 1130 Key APL interpretation
70 * ------------------------------------ --------------------------------
71 * [ (key to right of P) \r Enter left arrow
72 * ; (1st key to right of L) \b Backspace [
73 * ' (2nd key to right of L) ^U Erase Fld ]
74 * 2 (key above Q) @ @ up shift
75 * 3 (key above W) % % up right shift
76 * 4 (key above E) * * +
77 * 5 (key above R) < < multiply
78 * 8 (key above U) - - Return
79 * 9 (key above I) / / Backspace
80 * - (key above P) ^Q INT REQ ATTN
81 * Enter - - Return
82 * backsp / / Backspace
83 */
84
85 #include "ibm1130_defs.h"
86 #include <memory.h>
87
88 /* #define DEBUG_CONSOLE */
89
90 /* ---------------------------------------------------------------------------- */
91
92 static void badio (char *dev)
93 {
94 /* the real 1130 just ignores attempts to use uninstalled devices. They get tested
95 * at times, so it's best to just be quiet about this
96 * printf("%s I/O is not yet supported", dev);
97 */
98 }
99
100 void xio_1231_optical (int32 addr, int32 func, int32 modify) {badio("optical mark");}
101 void xio_system7 (int32 addr, int32 func, int32 modify) {badio("System 7");}
102
103 /* ---------------------------------------------------------------------------- */
104
105 #define MAX_OUTPUT_COLUMNS 100 /* width of 1130 console printer */
106 #define MAX_OS_CHARS 4 /* maximum number of overstruck characters that can be mapped */
107 #define MAX_OS_MAPPINGS 100 /* maximum number of overstrike mappings */
108
109 typedef struct tag_os_map { /* os_map = overstrike mapping */
110 int ch; /* ch = output character */
111 int nin; /* nin = number of overstruck characters */
112 unsigned char inlist[MAX_OS_CHARS]; /* inlist = overstruck ASCII characters, sorted. NOT NULL TERMINATED */
113 } OS_MAP;
114
115 extern UNIT *sim_clock_queue;
116 extern int cgi;
117
118 static int32 tti_dsw = 0; /* device status words */
119 static int32 tto_dsw = 0;
120 int32 con_dsw = 0;
121
122 static unsigned char conout_map[256]; /* 1130 console code to ASCII translation. 0 = undefined, 0xFF = IGNR_ = no output */
123 static unsigned char conin_map[256]; /* input mapping */
124 static int curcol = 0; /* current typewriter element column, leftmost = 0 */
125 static int maxcol = 0; /* highest curcol seen in this output line */
126 static unsigned char black_ribbon[30]; /* output escape sequence for black ribbon shift */
127 static unsigned char red_ribbon[30]; /* output escape sequence for red ribbon shift */
128
129 static OS_MAP os_buf[MAX_OUTPUT_COLUMNS]; /* current typewriter output line, holds character struck in each column */
130 static OS_MAP os_map[MAX_OS_MAPPINGS]; /* overstrike mapping entries */
131 static int n_os_mappings; /* number of overstrike mappings */
132
133 static t_stat tti_svc(UNIT *uptr);
134 static t_stat tto_svc(UNIT *uptr);
135 static t_stat tti_reset(DEVICE *dptr);
136 static t_stat tto_reset(DEVICE *dptr);
137
138 static t_stat emit_conout_character(int ch);
139 static t_stat map_conout_character(int ch);
140 static void reset_mapping (void);
141 static void set_conout_mapping(int32 flags);
142 static t_stat validate_conout_mapping(UNIT *uptr, int32 match, char *cvptr, void *desc);
143 static void set_default_mapping(int32 flags);
144 static void finish_conout_mapping(int32 flags);
145 static void strsort (int n, unsigned char *s); /* sorts an array of n characters */
146 static int os_map_comp (OS_MAP *a, OS_MAP *b); /* compares two mapping entries */
147 static t_stat font_cmd(int32 flag, char *cptr); /* handles font command */
148 static void read_map_file(FILE *fd); /* reads a font map file */
149 static t_bool str_match(char *str, char *keyword); /* keyword/string comparison */
150 static char * handle_map_ansi_definition(char **pc); /* input line parsers for map file sections */
151 static char * handle_map_input_definition(char **pc);
152 static char * handle_map_output_definition(char **pc);
153 static char * handle_map_overstrike_definition(char **pc);
154
155 extern t_stat sim_poll_kbd(void);
156 extern t_stat sim_wait_kbd(void);
157 extern t_stat sim_putchar(int32 out);
158
159 #define UNIT_V_CSET (UNIT_V_UF + 0) /* user flag: character set */
160 #define UNIT_V_LOCKED (UNIT_V_UF + 2) /* user flag: keyboard locked */
161 #define UNIT_V_ANSI (UNIT_V_UF + 3)
162
163 #define CSET_ASCII (0u << UNIT_V_CSET)
164 #define CSET_1130 (1u << UNIT_V_CSET)
165 #define CSET_APL (2u << UNIT_V_CSET)
166 #define CSET_MASK (3u << UNIT_V_CSET)
167 #define ENABLE_ANSI (1u << UNIT_V_ANSI)
168
169 #define KEYBOARD_LOCKED (1u << UNIT_V_LOCKED)
170
171 #define IRQ_KEY 0x11 /* ctrl-Q */
172 #define PROGRAM_STOP_KEY 0x10 /* ctrl-P */
173
174 #include "ibm1130_conout.h" /* conout_to_ascii table */
175 #include "ibm1130_conin.h" /* ascii_to_conin table */
176
177 /* TTI data structures
178
179 tti_dev TTI device descriptor
180 tti_unit TTI unit descriptor
181 tti_reg TTI register list
182 */
183
184 UNIT tti_unit = { UDATA (&tti_svc, 0, 0), KBD_POLL_WAIT };
185
186 REG tti_reg[] = {
187 { ORDATA (BUF, tti_unit.buf, 16) },
188 { ORDATA (DSW, tti_dsw, 16) },
189 { DRDATA (POS, tti_unit.pos, 31), PV_LEFT },
190 { DRDATA (STIME, tti_unit.wait, 24), REG_NZ + PV_LEFT },
191 { NULL } };
192
193 MTAB tti_mod[] = {
194 { CSET_MASK, CSET_ASCII, "ASCII", "ASCII", NULL},
195 { CSET_MASK, CSET_1130, "1130", "1130", NULL},
196 { 0 } };
197
198 DEVICE tti_dev = {
199 "KEYBOARD", &tti_unit, tti_reg, tti_mod,
200 1, 10, 31, 1, 8, 8,
201 NULL, NULL, &tti_reset,
202 NULL, basic_attach, NULL };
203
204 /* TTO data structures
205
206 tto_dev TTO device descriptor
207 tto_unit TTO unit descriptor
208 tto_reg TTO register list
209 */
210
211 /* 14-Nov-03 -- the wait time was SERIAL_OUT_WAIT, but recent versions of SIMH reduced
212 * this to 100, and wouldn't you know it, APL\1130 has about 120 instructions between the XIO WRITE
213 * to the console and the associated WAIT.
214 */
215
216 UNIT tto_unit = { UDATA (&tto_svc, 0, 0), 200 };
217
218 REG tto_reg[] = {
219 { ORDATA (BUF, tto_unit.buf, 16) },
220 { ORDATA (DSW, tto_dsw, 16) },
221 { DRDATA (POS, tto_unit.pos, 31), PV_LEFT },
222 { DRDATA (STIME, tto_unit.wait, 24), PV_LEFT },
223 { NULL } };
224
225 MTAB tto_mod[] = {
226 { CSET_MASK, CSET_ASCII, "ASCII", "ASCII", validate_conout_mapping, NULL, NULL},
227 { CSET_MASK, CSET_1130, "1130", "1130", validate_conout_mapping, NULL, NULL},
228 { CSET_MASK, CSET_APL, "APL", "APL", validate_conout_mapping, NULL, NULL},
229 { ENABLE_ANSI,0, "NOANSI", "NOANSI", NULL},
230 { ENABLE_ANSI,ENABLE_ANSI, "ANSI", "ANSI", NULL},
231 { 0 } };
232
233 DEVICE tto_dev = {
234 "TTO", &tto_unit, tto_reg, tto_mod,
235 1, 10, 31, 1, 8, 8,
236 NULL, NULL, &tto_reset,
237 NULL, basic_attach, NULL };
238
239 /* Terminal input routines
240
241 tti_svc process event (character ready)
242 tti_reset process reset
243 tto_svc process event (print character)
244 tto_reset process reset
245 */
246
247 #define TT_DSW_PRINTER_RESPONSE 0x8000
248 #define TT_DSW_KEYBOARD_RESPONSE 0x4000
249 #define TT_DSW_INTERRUPT_REQUEST 0x2000
250 #define TT_DSW_KEYBOARD_CONSOLE 0x1000
251 #define TT_DSW_PRINTER_BUSY 0x0800
252 #define TT_DSW_PRINTER_NOT_READY 0x0400
253 #define TT_DSW_KEYBOARD_BUSY 0x0200
254
255 void xio_1131_console (int32 iocc_addr, int32 func, int32 modify)
256 {
257 int ch;
258 char msg[80];
259
260 switch (func) {
261 case XIO_CONTROL:
262 SETBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY); /* select and unlock the keyboard */
263 keyboard_selected(TRUE);
264 CLRBIT(tti_unit.flags, KEYBOARD_LOCKED);
265 tti_unit.buf = 0; /* no key character yet */
266 break;
267
268 case XIO_READ:
269 WriteW(iocc_addr, tti_unit.buf); /* return keycode */
270 CLRBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY); /* this ends selected mode */
271 keyboard_selected(FALSE);
272 SETBIT(tti_unit.flags, KEYBOARD_LOCKED); /* keyboard is locked when not selected */
273 tti_unit.buf = 0; /* subsequent reads will return zero */
274 break;
275
276 case XIO_WRITE:
277 ch = (ReadW(iocc_addr) >> 8) & 0xFF; /* get character to write */
278 tto_unit.buf = emit_conout_character(ch); /* output character and save write status */
279
280 /* fprintf(stderr, "[CONOUT] %02x\n", ch); */
281
282 SETBIT(tto_dsw, TT_DSW_PRINTER_BUSY);
283 sim_activate(&tto_unit, tto_unit.wait); /* schedule interrupt */
284 break;
285
286 case XIO_SENSE_DEV:
287 ACC = tto_dsw | tti_dsw;
288 if (modify & 0x01) { /* reset interrupts */
289 CLRBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE);
290 CLRBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE);
291 CLRBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST);
292 CLRBIT(ILSW[4], ILSW_4_CONSOLE);
293 }
294 break;
295
296 default:
297 sprintf(msg, "Invalid console XIO function %x", func);
298 xio_error(msg);
299 }
300
301 /* fprintf(stderr, "After XIO %04x %04x\n", tti_dsw, tto_dsw); */
302 }
303
304 /* emit_conout_character - write character with 1130 console code 'ch' */
305
306 t_stat emit_conout_character (int ch)
307 {
308 t_stat status;
309
310 #ifdef DEBUG_CONSOLE
311 printf("{%02x}", ch);
312 #endif
313
314 if ((tto_unit.flags & CSET_MASK) == CSET_1130) /* 1130 (binary) mode, write the raw 8-bit value */
315 return sim_putchar(ch);
316
317 if (ch & COUT_IS_CTRL) {
318 /* red/black shift can be combined with another control */
319 /* if present, emit the color shift characters alone */
320
321 if (ch & COUT_CTRL_BLACK) {
322 if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_BLACK)) != SCPE_OK)
323 return status;
324 }
325 else if (ch & COUT_CTRL_RED) {
326 if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_RED)) != SCPE_OK)
327 return status;
328 }
329
330 ch &= ~(COUT_CTRL_BLACK|COUT_CTRL_RED); /* remove the ribbon shift bits */
331
332 if (ch & ~COUT_IS_CTRL) { /* if another control remains, emit it */
333 if ((status = map_conout_character(ch)) != SCPE_OK)
334 return status;
335 }
336
337 return SCPE_OK;
338 }
339
340 return map_conout_character(ch);
341 }
342
343 static void Beep (void) /* notify user keyboard was locked or key was bad */
344 {
345 sim_putchar(7);
346 }
347
348 /* tti_svc - keyboard polling (never stops) */
349
350 static t_stat tti_svc (UNIT *uptr)
351 {
352 int32 temp;
353
354 if (cgi) /* if running in CGI mode, no keyboard and no keyboard polling! */
355 return SCPE_OK;
356 /* otherwise, so ^E can interrupt the simulator, */
357 sim_activate(&tti_unit, tti_unit.wait); /* always continue polling keyboard */
358
359 assert(sim_clock_queue != NULL);
360
361 temp = sim_poll_kbd();
362
363 if (temp < SCPE_KFLAG)
364 return temp; /* no char or error? */
365
366 temp &= 0xFF; /* remove SCPE_KFLAG */
367
368 if ((tti_unit.flags & CSET_MASK) == CSET_ASCII)
369 temp = conin_map[temp] & 0xFF; /* perform input translation */
370
371 if (temp == IRQ_KEY) { /* INT REQ (interrupt request) key */
372 SETBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST); /* queue interrupt */
373 SETBIT(ILSW[4], ILSW_4_CONSOLE);
374 calc_ints();
375
376 CLRBIT(tti_unit.flags, KEYBOARD_LOCKED); /* keyboard restore, according to func. char. manual */
377
378 #ifdef DEBUG_CONSOLE
379 printf("[*IRQ*]");
380 #endif
381 tti_unit.buf = 0; /* subsequent reads need to return 0 (required by APL\1130) */
382 return SCPE_OK;
383 }
384
385 if (temp == PROGRAM_STOP_KEY) { /* simulate the program stop button */
386 SETBIT(con_dsw, CPU_DSW_PROGRAM_STOP);
387 SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);
388 calc_ints();
389
390 #ifdef DEBUG_CONSOLE
391 printf("[*PSTOP*]");
392 #endif
393
394 return SCPE_OK;
395 }
396
397 if ((tti_unit.flags & KEYBOARD_LOCKED) || ! (tti_dsw & TT_DSW_KEYBOARD_BUSY)) {
398 Beep();
399 return SCPE_OK;
400 }
401
402 if ((tti_unit.flags & CSET_MASK) == CSET_ASCII)
403 temp = ascii_to_conin[temp];
404
405 if (temp == 0) { /* ignore invalid characters */
406 Beep();
407 calc_ints();
408 return SCPE_OK;
409 }
410
411 tti_unit.buf = temp & 0xFFFE; /* save keystroke except last bit (not defined) */
412 tti_unit.pos = tti_unit.pos + 1; /* but it lets us distinguish 0 from no punch ' ' */
413
414 #ifdef DEBUG_CONSOLE
415 printf("[%04x]", tti_unit.buf & 0xFFFF);
416 #endif
417
418 SETBIT(tti_unit.flags, KEYBOARD_LOCKED); /* prevent further keystrokes */
419
420 SETBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE); /* queue interrupt */
421 SETBIT(ILSW[4], ILSW_4_CONSOLE);
422 calc_ints();
423
424 /* fprintf(stderr, "TTI interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */
425
426 return SCPE_OK;
427 }
428
429 static t_stat tti_reset (DEVICE *dptr)
430 {
431 tti_unit.buf = 0;
432 tti_dsw = 0;
433
434 CLRBIT(ILSW[4], ILSW_4_CONSOLE);
435 calc_ints();
436 keyboard_selected(FALSE);
437
438 SETBIT(tti_unit.flags, KEYBOARD_LOCKED);
439
440 if (cgi)
441 sim_cancel(&tti_unit); /* in cgi mode, never poll keyboard */
442 else
443 sim_activate(&tti_unit, tti_unit.wait); /* otherwise, always poll keyboard */
444
445 return SCPE_OK;
446 }
447
448 /* basic_attach - fix quotes in filename, then call standard unit attach routine */
449
450 t_stat basic_attach (UNIT *uptr, char *cptr)
451 {
452 return attach_unit(uptr, quotefix(cptr)); /* fix quotes in filenames & attach */
453 }
454
455 /* quotefix - strip off quotes around filename, if present */
456
457 char * quotefix (char * cptr)
458 {
459 #ifdef WIN32 /* do this only for Windows builds, for the time being */
460 char *c;
461 int quote;
462
463 if (*cptr == '"' || *cptr == '\'') {
464 quote = *cptr++; /* remember quote and skip over it */
465
466 for (c = cptr; *c && *c != quote; c++)
467 ; /* find closing quote, or end of string */
468
469 if (*c) /* terminate string at closing quote */
470 *c = '\0';
471 }
472
473 #endif
474 return cptr; /* return pointer to cleaned-up name */
475 }
476
477 t_bool keyboard_is_busy (void) /* return TRUE if keyboard is not expecting a character */
478 {
479 return (tti_dsw & TT_DSW_KEYBOARD_BUSY);
480 }
481
482 static t_stat tto_svc (UNIT *uptr)
483 {
484 CLRBIT(tto_dsw, TT_DSW_PRINTER_BUSY);
485 SETBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE);
486
487 SETBIT(ILSW[4], ILSW_4_CONSOLE);
488 calc_ints();
489
490 /* fprintf(stderr, "TTO interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */
491
492 return (t_stat) tto_unit.buf; /* return status saved during output conversion */
493 }
494
495 static t_stat tto_reset (DEVICE *dptr)
496 {
497 tto_unit.buf = 0;
498 tto_dsw = 0;
499
500 CLRBIT(ILSW[4], ILSW_4_CONSOLE);
501 calc_ints();
502
503 sim_cancel(&tto_unit); /* deactivate unit */
504
505 set_conout_mapping(tto_unit.flags); /* initialize the overstrike mappings */
506 /* register the font-mapping command */
507 register_cmd("FONT", font_cmd, 0, "font MAPFILE use font mapping definitions in MAPFILE\n");
508
509 return SCPE_OK;
510 }
511
512 #ifdef _MSC_VER
513 # pragma warning(disable:4245) /* enable int->char demotion warning caused by characters with high-bit set */
514 #endif
515
516 static struct { /* default input mapping for APL */
517 unsigned char in;
518 unsigned char out;
519 } conin_to_APL[] =
520 { /* these map input keys to those in like positions on 1130 keyboard */
521 '[', '\r', /* enter (EOF) is APL left arrow */
522 ';', '\b', /* backspace is APL [ */
523 '\'', '\x15', /* ctrl-U, erase field, is APL ]*/
524 '2', '@', /* APL upshift */
525 '3', '%', /* APL rightshift */
526 '4', '*', /* APL + and - */
527 '5', '<', /* APL x and divide */
528 '8', '-', /* APL return */
529 '9', '/', /* APL backspace */
530 '-', IRQ_KEY, /* ctrl-q (INT REQ), APL ATTN */
531 '\r', '-', /* APL return */
532 '\b', '/' /* APL backspace */
533 };
534
535 #define NCONIN_TO_APL (sizeof(conin_to_APL)/sizeof(conin_to_APL[0]))
536
537 static struct { /* default output mapping for APLPLUS font */
538 unsigned char in;
539 unsigned char out;
540 } conout_to_APL[] =
541 {
542 '\x01', IGNR_, /* controls */
543 '\x03', '\n',
544 '\x05', IGNR_, /* (black and red are handled by ansi sequences) */
545 '\x09', IGNR_,
546 '\x11', '\b',
547 '\x21', ' ',
548 '\x41', '\t',
549 '\x81', CRLF_,
550
551 '\xC4', '\x30', /* (if you're curious, order here is position on APL typeball) */
552 '\xE4', '\x38',
553 '\xD4', '\x37',
554 '\xF4', '\x35',
555 '\xDC', '\x33',
556 '\xFC', '\x31',
557 '\xC2', '\x29',
558 '\xE2', '\x9F',
559 '\xD2', '\x89',
560 '\xF2', '\x88',
561 '\xDA', '\xAF',
562 '\xC6', '\x5E',
563 '\xE6', '\xAC',
564 '\xD6', '\x3E',
565 '\xF6', '\x3D',
566 '\xDE', '\x3C',
567 '\xFE', '\xA8',
568 '\xC0', '\x5D',
569 '\xE0', '\x39',
570 '\xD0', '\x36',
571 '\xF0', '\x34',
572 '\xD8', '\x32',
573
574 '\x84', '\x84',
575 '\xA4', '\x59',
576 '\x94', '\x58',
577 '\xB4', '\x56',
578 '\x9C', '\x54',
579 '\xBC', '\x2F',
580 '\x82', '\x3B',
581 '\xA2', '\x9B',
582 '\x92', '\xBE',
583 '\xB2', '\x87',
584 '\x9A', '\x97',
585 '\x86', '\x85',
586 '\xA6', '\x86',
587 '\x96', '\x9C',
588 '\xB6', '\x9E',
589 '\x9E', '\x7E',
590 '\xBE', '\x5C',
591 '\x80', '\x2C',
592 '\xA0', '\x5A',
593 '\x90', '\x57',
594 '\xB0', '\x55',
595 '\x98', '\x53',
596
597 '\x44', '\x2B',
598 '\x64', '\x51',
599 '\x54', '\x50',
600 '\x74', '\x4E',
601 '\x5C', '\x4C',
602 '\x7C', '\x4A',
603 '\x42', '\x28',
604 '\x62', '\xBD',
605 '\x52', '\xB1',
606 '\x72', '\x7C',
607 '\x5A', '\x27',
608 '\x46', '\x2D',
609 '\x66', '\x3F',
610 '\x56', '\x2A',
611 '\x76', '\x82',
612 '\x5E', '\x8C',
613 '\x7E', '\xB0',
614 '\x40', '\x5B',
615 '\x60', '\x52',
616 '\x50', '\x4F',
617 '\x70', '\x4D',
618 '\x58', '\x4B',
619
620 '\x04', '\xD7',
621 '\x24', '\x48',
622 '\x14', '\x47',
623 '\x34', '\x45',
624 '\x1C', '\x43',
625 '\x3C', '\x41',
626 '\x02', '\x3A',
627 '\x22', '\xBC',
628 '\x12', '\x5F',
629 '\x32', '\x98',
630 '\x1A', '\x83',
631 '\x06', '\xF7',
632 '\x26', '\x91',
633 '\x16', '\x92',
634 '\x36', '\xB9',
635 '\x1E', '\x9D',
636 '\x3E', '\xB8',
637 '\x00', '\x2E',
638 '\x20', '\x49',
639 '\x10', '\x46',
640 '\x30', '\x44',
641 '\x18', '\x42',
642 };
643
644 #define NCONOUT_TO_APL (sizeof(conout_to_APL)/sizeof(conout_to_APL[0]))
645
646 static OS_MAP default_os_map[] = /* overstrike mapping for APLPLUS font */
647 {
648 '\x8a', 2, "\x5e\x7e",
649 '\x8b', 2, "\x9f\x7e",
650 '\x8d', 2, "\x8c\x27",
651 '\x8e', 3, "\x8c\x2d\x3a",
652 '\x8f', 2, "\x91\x5f",
653 '\x90', 2, "\x92\x7e",
654 '\x93', 2, "\x91\x7c",
655 '\x94', 2, "\x92\x7c",
656 '\x95', 2, "\xb0\x82",
657 '\x96', 2, "\xb0\x83",
658 '\x99', 2, "\x2d\x5c",
659 '\x9a', 2, "\x2d\x2f",
660 '\xae', 2, "\x2c\x2d",
661 '\xb2', 2, "\xb1\x7c",
662 '\xb3', 2, "\xb1\x5c",
663 '\xb4', 2, "\xb1\x2d",
664 '\xb5', 2, "\xb1\x2a",
665 '\xba', 2, "\xb9\x5f",
666 '\xd0', 2, "\x30\x7e",
667 '\xd8', 2, "\x4f\x2f",
668 '\x21', 2, "\x27\x2e",
669 '\xa4', 2, "\xb0\xb1", /* map degree in circle to circle cross (APL uses this as character error symbol) */
670 '\xf0', 2, "\xb0\xa8",
671 '\xfe', 2, "\x3a\xa8",
672 };
673
674 #ifdef _MSC_VER
675 # pragma warning(default:4245) /* enable int->char demotion warning */
676 #endif
677
678 /* os_map_comp - compare to OS_MAP entries */
679
680 static int os_map_comp (OS_MAP *a, OS_MAP *b)
681 {
682 unsigned char *sa, *sb;
683 int i;
684
685 if (a->nin > b->nin)
686 return +1;
687
688 if (a->nin < b->nin)
689 return -1;
690
691 sa = a->inlist;
692 sb = b->inlist;
693
694 for (i = a->nin; --i >= 0;) {
695 if (*sa > *sb)
696 return +1;
697
698 if (*sa < *sb)
699 return -1;
700
701 sa++;
702 sb++;
703 }
704
705 return 0;
706 }
707
708 /* strsort - sorts the n characters of array 's' using insertion sort */
709
710 static void strsort (int n, unsigned char *s)
711 {
712 unsigned char temp;
713 int i, big;
714
715 while (--n > 0) { /* repeatedly */
716 big = 0; /* find largest value of s[0]...s[n] */
717 for (i = 1; i <= n; i++)
718 if (s[i] > s[big]) big = i;
719
720 temp = s[n]; /* put largest value at end of array */
721 s[n] = s[big];
722 s[big] = temp;
723 }
724 }
725
726 /* file format:
727
728 [font XXX] font named XXX
729 OUT failure character
730 OUT IN single character mapping
731 OUT IN IN ... overstrike mapping
732
733 */
734
735 static void set_conout_mapping (int32 flags)
736 {
737 curcol = 0;
738 maxcol = 0;
739
740 /* set the default mappings. We may later override them with settings from an ini file */
741
742 set_default_mapping(flags);
743 }
744
745 /* finish_conout_mapping - sort the finalized overstrike mapping */
746
747 static void finish_conout_mapping (int32 flags)
748 {
749 int i, n, big;
750 OS_MAP temp;
751
752 for (i = 0; i < n_os_mappings; i++) /* sort the inlist strings individually */
753 strsort(os_map[i].nin, os_map[i].inlist);
754
755 for (n = n_os_mappings; --n > 0; ) { /* then sort the os_map array itself with insertion sort */
756 big = 0; /* find largest value of s[0]...s[n] */
757 for (i = 1; i <= n; i++)
758 if (os_map_comp(os_map+i, os_map+big) > 0) big = i;
759
760 if (big != n) {
761 temp = os_map[n]; /* put largest value at end of array */
762 os_map[n] = os_map[big];
763 os_map[big] = temp;
764 }
765 }
766 }
767
768 /* validate_conout_mapping - called when set command gets a new value */
769
770 static t_stat validate_conout_mapping (UNIT *uptr, int32 match, char *cvptr, void *desc)
771 {
772 set_conout_mapping(match);
773 return SCPE_OK;
774 }
775
776 static void reset_mapping (void)
777 {
778 int i;
779
780 black_ribbon[0] = '\0'; /* erase the ribbon sequences */
781 red_ribbon[0] = '\0';
782
783 memset(conout_map, 0, sizeof(conout_map)); /* erase output mapping */
784
785 n_os_mappings = 0; /* erase overstrike mapping */
786
787 for (i = (sizeof(conin_map)/sizeof(conin_map[0])); --i >= 0; )
788 conin_map[i] = (unsigned char) i; /* default conin_map is identity map */
789 }
790
791 /* set_default_mapping - create standard font and overstrike map */
792
793 static void set_default_mapping (int32 flags)
794 {
795 int i;
796
797 reset_mapping();
798
799 strcpy((char *) black_ribbon, "\033[30m");
800 strcpy((char *) red_ribbon, "\033[31m");
801
802 switch (flags & CSET_MASK) {
803 case CSET_1130:
804 break;
805
806 case CSET_ASCII:
807 memcpy(conout_map, conout_to_ascii, sizeof(conout_to_ascii));
808 break;
809
810 case CSET_APL:
811 for (i = NCONOUT_TO_APL; --i >= 0; )
812 conout_map[conout_to_APL[i].in] = conout_to_APL[i].out;
813
814 for (i = NCONIN_TO_APL; --i >= 0; )
815 conin_map[conin_to_APL[i].in] = conin_to_APL[i].out;
816
817 memcpy(os_map, default_os_map, sizeof(default_os_map));
818 n_os_mappings = (sizeof(default_os_map) / sizeof(default_os_map[0]));
819 break;
820 }
821
822 finish_conout_mapping(flags); /* sort conout mapping if necessary */
823 }
824
825 /* sim_putstr - write a string to the console */
826
827 t_stat sim_putstr (char *s)
828 {
829 t_stat status;
830
831 while (*s) {
832 if ((status = sim_putchar(*s)) != SCPE_OK)
833 return status;
834
835 s++;
836 }
837
838 return SCPE_OK;
839 }
840
841 /* map_conout_character - translate and write a single character */
842
843 static t_stat map_conout_character (int ch)
844 {
845 t_stat status;
846 int i, cmp;
847
848 if (ch == (COUT_IS_CTRL|COUT_CTRL_BLACK))
849 return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) black_ribbon) : SCPE_OK;
850
851 if (ch == (COUT_IS_CTRL|COUT_CTRL_RED))
852 return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) red_ribbon) : SCPE_OK;
853
854 if ((ch = conout_map[ch & 0xFF]) == 0)
855 ch = '?'; /* unknown character? print ? */
856
857 if (ch == '\n') { /* newline: reset overstrike buffer */
858 curcol = 0;
859 maxcol = -1;
860 }
861 else if (ch == '\r') { /* carriage return: rewind to column 0 */
862 curcol = 0;
863 maxcol = -1; /* assume it advances paper too */
864 }
865 else if (ch == '\b') { /* backspace: back up one character */
866 if (curcol > 0)
867 curcol--;
868 }
869 else if (n_os_mappings && ch != (unsigned char) IGNR_) {
870 if (curcol >= MAX_OUTPUT_COLUMNS)
871 map_conout_character('\x81'); /* precede with automatic carriage return/line feed, I guess */
872
873 if (curcol > maxcol) { /* first time in this column, no overstrike possible yet */
874 os_buf[curcol].nin = 0;
875 maxcol = curcol;
876 }
877
878 if (ch != ' ' && ch != 0) { /* (if it's not a blank or unknown) */
879 os_buf[curcol].inlist[os_buf[curcol].nin] = (unsigned char) ch;
880 strsort(++os_buf[curcol].nin, os_buf[curcol].inlist);
881 }
882
883 if (os_buf[curcol].nin == 0) /* if nothing but blanks seen, */
884 ch = ' '; /* output is a blank */
885 else if (os_buf[curcol].nin == 1) { /* if only one printing character seen, display it */
886 ch = os_buf[curcol].inlist[0];
887 }
888 else { /* otherwise look up mapping */
889 ch = '?';
890
891 for (i = 0; i < n_os_mappings; i++) {
892 cmp = os_map_comp(&os_buf[curcol], &os_map[i]);
893 if (cmp == 0) { /* a hit */
894 ch = os_map[i].ch;
895 break;
896 }
897 else if (cmp < 0) /* not found */
898 break;
899 }
900 }
901
902 if (curcol < MAX_OUTPUT_COLUMNS) /* this should now never happen, as we automatically return */
903 curcol++;
904 }
905
906 switch (ch) {
907 case IGNR_:
908 break;
909
910 case CRLF_:
911 if (! cgi) {
912 if ((status = sim_putchar('\r')) != SCPE_OK)
913 return status;
914
915 tto_unit.pos++;
916 }
917
918 if ((status = sim_putchar('\n')) != SCPE_OK)
919 return status;
920
921 tto_unit.pos++; /* hmm, why do we count these? */
922 break;
923
924 default:
925 if ((status = sim_putchar(ch)) != SCPE_OK)
926 return status;
927
928 tto_unit.pos++;
929 break;
930 }
931
932 return SCPE_OK;
933 }
934
935 /* font_cmd - parse a font mapping file. Sets input and output translations */
936
937 static t_stat font_cmd (int32 flag, char *cptr)
938 {
939 char *fname, quote;
940 FILE *fd;
941
942 while (*cptr && (*cptr <= ' ')) cptr++; /* skip blanks */
943 if (! *cptr) return SCPE_2FARG; /* argument missing */
944
945 fname = cptr; /* save start */
946 if (*cptr == '\'' || *cptr == '"') { /* quoted string */
947 quote = *cptr++; /* remember quote character */
948 fname++; /* skip the quote */
949
950 while (*cptr && (*cptr != quote)) /* find closing quote */
951 cptr++;
952 }
953 else {
954 while (*cptr && (*cptr > ' ')) /* find terminating blank */
955 cptr++;
956 }
957 *cptr = '\0'; /* terminate name */
958
959 if ((fd = fopen(fname, "r")) == NULL)
960 return SCPE_OPENERR;
961
962 reset_mapping(); /* remove all default mappings */
963
964 read_map_file(fd);
965 fclose(fd);
966
967 finish_conout_mapping(tto_unit.flags);
968 return SCPE_OK;
969 }
970
971 /* str_match - compare the string str to the keyword, case insensitive */
972
973 static t_bool str_match (char *str, char *keyword)
974 {
975 char kch, sch;
976
977 while (*keyword) { /* see if str matches the keyword... */
978 kch = *keyword++; /* get pair of characters */
979 sch = *str++;
980
981 if (BETWEEN(kch, 'A', 'Z')) kch += 32; /* change upper to lower case */
982 if (BETWEEN(sch, 'A', 'Z')) sch += 32;
983
984 if (kch != sch) /* characters must match; if not, quit */
985 return FALSE;
986 }
987
988 return *str <= ' ' || *str == ';'; /* success if the input string ended or is in whitespace or comment */
989 }
990
991 /* read_map_file - process definition lines in opened mapping file */
992
993 static void read_map_file (FILE *fd)
994 {
995 char str[256], *c, *errmsg;
996 int lineno = 0;
997 enum {SECT_UNDEFINED, SECT_DEFAULT, SECT_ANSI, SECT_INPUT, SECT_OUTPUT, SECT_OVERSTRIKE}
998 section = SECT_UNDEFINED;
999
1000 while (fgets(str, sizeof(str), fd) != NULL) {
1001 ++lineno; /* count input lines */
1002
1003 if ((c = strchr(str, '\n')) != NULL) /* terminate at newline */
1004 *c = '\0';
1005
1006 for (c = str; *c && *c <= ' '; c++) /* skip blanks */
1007 ;
1008
1009 if (c[0] == '\0' || c[0] == ';') /* ignore blank lines and lines starting with ; */
1010 continue;
1011
1012 if (*c == '[') {
1013 if (str_match(c, "[default]")) { /* check for section separators */
1014 set_default_mapping(tto_unit.flags);
1015 section = SECT_UNDEFINED;
1016 continue;
1017 }
1018 if (str_match(c, "[ansi]")) {
1019 section = SECT_ANSI;
1020 continue;
1021 }
1022 if (str_match(c, "[input]")) {
1023 section = SECT_INPUT;
1024 continue;
1025 }
1026 if (str_match(c, "[output]")) {
1027 section = SECT_OUTPUT;
1028 continue;
1029 }
1030 if (str_match(c, "[overstrike]")) {
1031 section = SECT_OVERSTRIKE;
1032 continue;
1033 }
1034 }
1035
1036 switch (section) { /* if we get here, we have a definition line */
1037 case SECT_ANSI:
1038 errmsg = handle_map_ansi_definition(&c);
1039 break;
1040 case SECT_INPUT:
1041 errmsg = handle_map_input_definition(&c);
1042 break;
1043 case SECT_OUTPUT:
1044 errmsg = handle_map_output_definition(&c);
1045 break;
1046 case SECT_OVERSTRIKE:
1047 errmsg = handle_map_overstrike_definition(&c);
1048 break;
1049 default:
1050 errmsg = "line occurs before valid [section]";
1051 break;
1052 }
1053
1054 if (errmsg == NULL) { /* if no other error detected, */
1055 while (*c && *c <= ' ') /* skip past any whitespace */
1056 c++;
1057
1058 if (*c && *c != ';') /* if line doesn't end or run into a comment, complain */
1059 errmsg = "too much stuff on input line";
1060 }
1061
1062 if (errmsg != NULL) { /* print error message and offending line */
1063 printf("* Warning: %s", errmsg);
1064
1065 switch (section) { /* add section name if possible */
1066 case SECT_ANSI: errmsg = "ansi"; break;
1067 case SECT_INPUT: errmsg = "input"; break;
1068 case SECT_OUTPUT: errmsg = "output"; break;
1069 case SECT_OVERSTRIKE: errmsg = "overstrike"; break;
1070 default: errmsg = NULL; break;
1071 }
1072 if (errmsg != NULL)
1073 printf(" in [%s] section", errmsg);
1074
1075 printf(", line %d\n%s\n", lineno, str);
1076 }
1077 }
1078 }
1079
1080 /* get_num_char - read an octal or hex character specification of exactly 'ndigits' digits
1081 * the input pointers is left pointing to the last character of the number, so that it
1082 * may be incremented by the caller
1083 */
1084
1085 static char * get_num_char (char **pc, unsigned char *out, int ndigits, int base, char *errmsg)
1086 {
1087 int ch = 0, digit;
1088 char *c = *pc;
1089
1090 while (--ndigits >= 0) { /* collect specified number of digits */
1091 if (BETWEEN(*c, '0', '9'))
1092 digit = *c - '0';
1093 else if (BETWEEN(*c, 'A', 'F'))
1094 digit = *c - 'A' + 10;
1095 else if (BETWEEN(*c, 'a', 'f'))
1096 digit = *c - 'a' + 10;
1097 else
1098 digit = base;
1099
1100 if (digit >= base) /* bad digit */
1101 return errmsg;
1102
1103 ch = ch * base + digit; /* accumulate digit */
1104 c++;
1105 }
1106
1107 *out = (unsigned char) ch; /* return parsed character */
1108 *pc = c-1; /* make input pointer point to last character seen */
1109 return NULL; /* no error */
1110 }
1111
1112 /* get_characters - read character specification(s) from input string pointed to
1113 * by *pc. Results stored in outstr; up to nmax characters parsed. Actual number
1114 * found returned in *nout. Returns NULL on success or error message if syntax
1115 * error encountered. *pc is advanced to next whitespace or whatever followed input.
1116 */
1117
1118 static char * get_characters (char **pc, unsigned char *outstr, int nmax, int *nout)
1119 {
1120 char *c = *pc, *errstr;
1121 unsigned char *out = outstr;
1122
1123 while (*c && *c <= ' ') /* skip leading whitespace */
1124 c++;
1125
1126 while (--nmax >= 0) { /* get up to maximum number of characters */
1127 if (*c == ';' || *c <= ' ') /* we ran into a comment, whitespace or end of string: we're done */
1128 break;
1129
1130 if (*c == '\\') { /* backslash escape of some sort */
1131 switch (*++c) {
1132 case 'b': /* backspace */
1133 case 'B':
1134 *out++ = '\b';
1135 break;
1136
1137 case 'e': /* ascii ESCAPE */
1138 case 'E':
1139 *out++ = '\033';
1140 break;
1141
1142 case 'f': /* formfeed */
1143 case 'F':
1144 *out++ = '\f';
1145 break;
1146
1147 case 'n': /* newline */
1148 case 'N':
1149 *out++ = '\n';
1150 break;
1151
1152 case 'r': /* return */
1153 case 'R':
1154 *out++ = '\r';
1155 break;
1156
1157 case 't': /* tab */
1158 case 'T':
1159 *out++ = '\t';
1160 break;
1161
1162 case 'x': /* hex specification */
1163 case 'X':
1164 c++;
1165 if ((errstr = get_num_char(&c, out, 2, 16, "bad hex character")) != NULL)
1166 return errstr;
1167
1168 out++; /* advance out pointer */
1169 break;
1170
1171 default: /* anything else */
1172 if (BETWEEN(*c, '0', '7')) { /* octal specification */
1173 if ((errstr = get_num_char(&c, out, 3, 8, "bad octal character")) != NULL)
1174 return errstr;
1175
1176 out++; /* advance out pointer */
1177 }
1178 else if (BETWEEN(*c, 'A', 'Z') || BETWEEN(*c, 'a', 'z'))
1179 return "invalid \\ escape"; /* other \x letters are bad */
1180 else {
1181 *out++ = (unsigned char) *c;/* otherwise, accept \x as literal character x */
1182 }
1183 break;
1184 }
1185 }
1186 else if (*c == '^') { /* control character */
1187 c++;
1188 if (BETWEEN(*c, 'A', 'Z')) /* convert alpha, e.g. A -> 1 */
1189 *out++ = (unsigned char) (*c - 'A' + 1);
1190 else if (BETWEEN(*c, 'a', 'z'))
1191 *out++ = (unsigned char) (*c - 'z' + 1);
1192 else /* non alpha is bad */
1193 return "invalid control letter";
1194 }
1195 else if (str_match(c, "IGNORE")) { /* magic word: a character that will never be output */
1196 *out++ = (unsigned char) IGNR_;
1197 c += 6;
1198 }
1199 else {
1200 *out++ = (unsigned char) *c; /* save literal character */
1201 }
1202
1203 c++;
1204 }
1205
1206 if (*c && *c != ';' && *c > ' ') /* we should be at end of string, whitespace or comment */
1207 return "too many characters specified";
1208
1209 *pc = c; /* save advanced pointer */
1210 *nout = out-outstr; /* save number of characters stored */
1211
1212 return NULL; /* no error */
1213 }
1214
1215 /* handle_map_ansi_definition - process line in [ansi] section */
1216
1217 static char * handle_map_ansi_definition (char **pc)
1218 {
1219 unsigned char *outstr;
1220 char *errmsg;
1221 int n;
1222
1223 if (str_match(*pc, "black")) { /* find which string we're setting */
1224 outstr = black_ribbon; /* this is where we'll save the output string */
1225 *pc += 5; /* skip over the token */
1226 }
1227 else if (str_match(*pc, "red")) {
1228 outstr = red_ribbon;
1229 *pc += 3;
1230 }
1231 else
1232 return "invalid variable name";
1233 /* get list of characters */
1234 if ((errmsg = get_characters(pc, outstr, sizeof(black_ribbon)-1, &n)) != NULL)
1235 return errmsg;
1236
1237 outstr[n] = '\0'; /* null terminate the string */
1238
1239 return (n > 0) ? NULL : "missing output string"; /* NULL if OK, error msg if no characters */
1240 }
1241
1242 /* handle_map_input_definition - process line in [input] section */
1243
1244 static char * handle_map_input_definition (char **pc)
1245 {
1246 unsigned char cin, cout;
1247 char *errmsg;
1248 int n;
1249
1250 if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL) /* get input character */
1251 return errmsg;
1252
1253 if (n != 1)
1254 return "missing input character";
1255
1256 if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL) /* get output character */
1257 return errmsg;
1258
1259 if (n != 1)
1260 return "missing output character";
1261
1262 conin_map[cin] = cout; /* set the mapping */
1263 return NULL;
1264 }
1265
1266 /* handle_map_output_definition - process line in [output] section */
1267
1268 static char * handle_map_output_definition (char **pc)
1269 {
1270 unsigned char cin, cout;
1271 char *errmsg;
1272 int n;
1273
1274 if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL) /* get input character */
1275 return errmsg;
1276
1277 if (n != 1)
1278 return "missing input character";
1279
1280 if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL) /* get output character */
1281 return errmsg;
1282
1283 if (n != 1)
1284 return "missing output character";
1285
1286 conout_map[cin] = cout; /* set the mapping */
1287 return NULL;
1288 }
1289
1290 /* handle_map_overstrike_definition - process line in [overstrike] section */
1291
1292 static char * handle_map_overstrike_definition (char **pc)
1293 {
1294 unsigned char ch, inlist[MAX_OS_CHARS];
1295 char *errmsg;
1296 int nin;
1297
1298 if (n_os_mappings >= MAX_OS_MAPPINGS) /* os_map is full, no more room */
1299 return "too many overstrike mappings";
1300 /* get output character */
1301 if ((errmsg = get_characters(pc, &ch, 1, &nin)) != NULL)
1302 return errmsg;
1303
1304 if (nin != 1)
1305 return "missing output character";
1306 /* get input list */
1307 if ((errmsg = get_characters(pc, inlist, MAX_OS_CHARS, &nin)) != NULL)
1308 return errmsg;
1309
1310 if (nin < 2) /* expect at least two characters overprinted */
1311 return "missing input list";
1312
1313 os_map[n_os_mappings].ch = ch; /* save in next os_map slot */
1314 os_map[n_os_mappings].nin = nin;
1315 memmove(os_map[n_os_mappings].inlist, inlist, nin);
1316
1317 n_os_mappings++;
1318 return NULL;
1319 }