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