/* ibm1130_stddev.c: IBM 1130 standard I/O devices simulator | |
Based on the SIMH simulator package written by Robert M Supnik | |
Brian Knittel | |
Revision History: | |
2004.10.22 - Removed stub for xio_1134_papertape as it's now a supported device | |
2003.11.23 - Fixed bug in new routine "quotefix" that made sim crash | |
for all non-Windows builds :( | |
2003.06.15 - added output translation code to accomodate APL font | |
added input translation feature to assist emulation of 1130 console keyboard for APL | |
changes to console input and output IO emulation, fixed bugs exposed by APL interpreter | |
2002.09.13 - pulled 1132 printer out of this file into ibm1130_prt.c | |
* (C) Copyright 2002, Brian Knittel. | |
* You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN | |
* RISK basis, there is no warranty of fitness for any purpose, and the rest of the | |
* usual yada-yada. Please keep this notice and the copyright in any distributions | |
* or modifications. | |
* | |
* This is not a supported product, but I welcome bug reports and fixes. | |
* Mail to simh@ibm1130.org | |
* | |
* Notes about overstrike mapping: | |
* The 1130 console printer used a Selectric typewriter element. The APL interpreter | |
* used overprinting to construct some APL symbols, for example, a round O overstruck] | |
* with | to get the greek phi. This doesn't accomodate a glass terminal! Instead, | |
* modern APL fonts have separate character codes for the complex characters. | |
* To have APL\1130 output appear correctly, we have to do three things: | |
* | |
* use simh's telnet feature to connect to the 1130 console stream | |
* have the telnet program use an APL font | |
* detect combinations of overstruck symbols, and generate the approrpiate alternate codes. | |
* | |
* There is a built-in table of font mappings and overstrike mappings, for the APLPLUS.TTF | |
* truetype font widely available on the Internet. An font descriptor file can be used | |
* to specify alternate mappings. | |
* | |
* The APL font codes and overstrike mapping can be enabled with the simh command | |
* | |
* set tto apl | |
* | |
* and disabled with | |
* | |
* set tto ascii (this is the default) | |
* | |
* APL also uses the red and black ribbon selection. The emulator will output | |
* ansi red/black foreground commands with the setting | |
* | |
* set tto ansi | |
* | |
* The codes can be disabled with | |
* | |
* set tto noansi (this is the default) | |
* | |
* Finally, when APL mode is active, the emulator does some input key translations | |
* to let the standard ASCII keyboard more closely match the physical layout of the | |
* 1130 console keyboard. The numeric and punctuation key don't have their | |
* traditional meaning under APL. The input mapping lets you use the APL keyboard | |
* layout shown in the APL documentation. | |
* | |
* The translations are: | |
* FROM | |
* ASCII Position on keyboard To 1130 Key APL interpretation | |
* ------------------------------------ -------------------------------- | |
* [ (key to right of P) \r Enter left arrow | |
* ; (1st key to right of L) \b Backspace [ | |
* ' (2nd key to right of L) ^U Erase Fld ] | |
* 2 (key above Q) @ @ up shift | |
* 3 (key above W) % % up right shift | |
* 4 (key above E) * * + | |
* 5 (key above R) < < multiply | |
* 8 (key above U) - - Return | |
* 9 (key above I) / / Backspace | |
* - (key above P) ^Q INT REQ ATTN | |
* Enter - - Return | |
* backsp / / Backspace | |
*/ | |
#include "ibm1130_defs.h" | |
#include <memory.h> | |
/* #define DEBUG_CONSOLE */ | |
/* ---------------------------------------------------------------------------- */ | |
static void badio (char *dev) | |
{ | |
/* the real 1130 just ignores attempts to use uninstalled devices. They get tested | |
* at times, so it's best to just be quiet about this | |
* printf("%s I/O is not yet supported", dev); | |
*/ | |
} | |
void xio_1231_optical (int32 addr, int32 func, int32 modify) {badio("optical mark");} | |
void xio_system7 (int32 addr, int32 func, int32 modify) {badio("System 7");} | |
/* ---------------------------------------------------------------------------- */ | |
#define MAX_OUTPUT_COLUMNS 100 /* width of 1130 console printer */ | |
#define MAX_OS_CHARS 4 /* maximum number of overstruck characters that can be mapped */ | |
#define MAX_OS_MAPPINGS 100 /* maximum number of overstrike mappings */ | |
typedef struct tag_os_map { /* os_map = overstrike mapping */ | |
int ch; /* ch = output character */ | |
int nin; /* nin = number of overstruck characters */ | |
unsigned char inlist[MAX_OS_CHARS]; /* inlist = overstruck ASCII characters, sorted. NOT NULL TERMINATED */ | |
} OS_MAP; | |
extern UNIT *sim_clock_queue; | |
extern int cgi; | |
static int32 tti_dsw = 0; /* device status words */ | |
static int32 tto_dsw = 0; | |
int32 con_dsw = 0; | |
static unsigned char conout_map[256]; /* 1130 console code to ASCII translation. 0 = undefined, 0xFF = IGNR_ = no output */ | |
static unsigned char conin_map[256]; /* input mapping */ | |
static int curcol = 0; /* current typewriter element column, leftmost = 0 */ | |
static int maxcol = 0; /* highest curcol seen in this output line */ | |
static unsigned char black_ribbon[30]; /* output escape sequence for black ribbon shift */ | |
static unsigned char red_ribbon[30]; /* output escape sequence for red ribbon shift */ | |
static OS_MAP os_buf[MAX_OUTPUT_COLUMNS]; /* current typewriter output line, holds character struck in each column */ | |
static OS_MAP os_map[MAX_OS_MAPPINGS]; /* overstrike mapping entries */ | |
static int n_os_mappings; /* number of overstrike mappings */ | |
static t_stat tti_svc(UNIT *uptr); | |
static t_stat tto_svc(UNIT *uptr); | |
static t_stat tti_reset(DEVICE *dptr); | |
static t_stat tto_reset(DEVICE *dptr); | |
static t_stat emit_conout_character(int ch); | |
static t_stat map_conout_character(int ch); | |
static void reset_mapping (void); | |
static void set_conout_mapping(int32 flags); | |
static t_stat validate_conout_mapping(UNIT *uptr, int32 match, char *cvptr, void *desc); | |
static void set_default_mapping(int32 flags); | |
static void finish_conout_mapping(int32 flags); | |
static void strsort (int n, unsigned char *s); /* sorts an array of n characters */ | |
static int os_map_comp (OS_MAP *a, OS_MAP *b); /* compares two mapping entries */ | |
static t_stat font_cmd(int32 flag, char *cptr); /* handles font command */ | |
static void read_map_file(FILE *fd); /* reads a font map file */ | |
static t_bool str_match(char *str, char *keyword); /* keyword/string comparison */ | |
static char * handle_map_ansi_definition(char **pc); /* input line parsers for map file sections */ | |
static char * handle_map_input_definition(char **pc); | |
static char * handle_map_output_definition(char **pc); | |
static char * handle_map_overstrike_definition(char **pc); | |
extern t_stat sim_poll_kbd(void); | |
extern t_stat sim_wait_kbd(void); | |
extern t_stat sim_putchar(int32 out); | |
#define UNIT_V_CSET (UNIT_V_UF + 0) /* user flag: character set */ | |
#define UNIT_V_LOCKED (UNIT_V_UF + 2) /* user flag: keyboard locked */ | |
#define UNIT_V_ANSI (UNIT_V_UF + 3) | |
#define CSET_ASCII (0u << UNIT_V_CSET) | |
#define CSET_1130 (1u << UNIT_V_CSET) | |
#define CSET_APL (2u << UNIT_V_CSET) | |
#define CSET_MASK (3u << UNIT_V_CSET) | |
#define ENABLE_ANSI (1u << UNIT_V_ANSI) | |
#define KEYBOARD_LOCKED (1u << UNIT_V_LOCKED) | |
#define IRQ_KEY 0x11 /* ctrl-Q */ | |
#define PROGRAM_STOP_KEY 0x10 /* ctrl-P */ | |
#include "ibm1130_conout.h" /* conout_to_ascii table */ | |
#include "ibm1130_conin.h" /* ascii_to_conin table */ | |
/* TTI data structures | |
tti_dev TTI device descriptor | |
tti_unit TTI unit descriptor | |
tti_reg TTI register list | |
*/ | |
UNIT tti_unit = { UDATA (&tti_svc, 0, 0), KBD_POLL_WAIT }; | |
REG tti_reg[] = { | |
{ ORDATA (BUF, tti_unit.buf, 16) }, | |
{ ORDATA (DSW, tti_dsw, 16) }, | |
{ DRDATA (POS, tti_unit.pos, 31), PV_LEFT }, | |
{ DRDATA (STIME, tti_unit.wait, 24), REG_NZ + PV_LEFT }, | |
{ NULL } }; | |
MTAB tti_mod[] = { | |
{ CSET_MASK, CSET_ASCII, "ASCII", "ASCII", NULL}, | |
{ CSET_MASK, CSET_1130, "1130", "1130", NULL}, | |
{ 0 } }; | |
DEVICE tti_dev = { | |
"KEYBOARD", &tti_unit, tti_reg, tti_mod, | |
1, 10, 31, 1, 8, 8, | |
NULL, NULL, &tti_reset, | |
NULL, basic_attach, NULL }; | |
/* TTO data structures | |
tto_dev TTO device descriptor | |
tto_unit TTO unit descriptor | |
tto_reg TTO register list | |
*/ | |
/* 14-Nov-03 -- the wait time was SERIAL_OUT_WAIT, but recent versions of SIMH reduced | |
* this to 100, and wouldn't you know it, APL\1130 has about 120 instructions between the XIO WRITE | |
* to the console and the associated WAIT. | |
*/ | |
UNIT tto_unit = { UDATA (&tto_svc, 0, 0), 200 }; | |
REG tto_reg[] = { | |
{ ORDATA (BUF, tto_unit.buf, 16) }, | |
{ ORDATA (DSW, tto_dsw, 16) }, | |
{ DRDATA (POS, tto_unit.pos, 31), PV_LEFT }, | |
{ DRDATA (STIME, tto_unit.wait, 24), PV_LEFT }, | |
{ NULL } }; | |
MTAB tto_mod[] = { | |
{ CSET_MASK, CSET_ASCII, "ASCII", "ASCII", validate_conout_mapping, NULL, NULL}, | |
{ CSET_MASK, CSET_1130, "1130", "1130", validate_conout_mapping, NULL, NULL}, | |
{ CSET_MASK, CSET_APL, "APL", "APL", validate_conout_mapping, NULL, NULL}, | |
{ ENABLE_ANSI,0, "NOANSI", "NOANSI", NULL}, | |
{ ENABLE_ANSI,ENABLE_ANSI, "ANSI", "ANSI", NULL}, | |
{ 0 } }; | |
DEVICE tto_dev = { | |
"TTO", &tto_unit, tto_reg, tto_mod, | |
1, 10, 31, 1, 8, 8, | |
NULL, NULL, &tto_reset, | |
NULL, basic_attach, NULL }; | |
/* Terminal input routines | |
tti_svc process event (character ready) | |
tti_reset process reset | |
tto_svc process event (print character) | |
tto_reset process reset | |
*/ | |
#define TT_DSW_PRINTER_RESPONSE 0x8000 | |
#define TT_DSW_KEYBOARD_RESPONSE 0x4000 | |
#define TT_DSW_INTERRUPT_REQUEST 0x2000 | |
#define TT_DSW_KEYBOARD_CONSOLE 0x1000 | |
#define TT_DSW_PRINTER_BUSY 0x0800 | |
#define TT_DSW_PRINTER_NOT_READY 0x0400 | |
#define TT_DSW_KEYBOARD_BUSY 0x0200 | |
void xio_1131_console (int32 iocc_addr, int32 func, int32 modify) | |
{ | |
int ch; | |
char msg[80]; | |
switch (func) { | |
case XIO_CONTROL: | |
SETBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY); /* select and unlock the keyboard */ | |
keyboard_selected(TRUE); | |
CLRBIT(tti_unit.flags, KEYBOARD_LOCKED); | |
tti_unit.buf = 0; /* no key character yet */ | |
break; | |
case XIO_READ: | |
WriteW(iocc_addr, tti_unit.buf); /* return keycode */ | |
CLRBIT(tti_dsw, TT_DSW_KEYBOARD_BUSY); /* this ends selected mode */ | |
keyboard_selected(FALSE); | |
SETBIT(tti_unit.flags, KEYBOARD_LOCKED); /* keyboard is locked when not selected */ | |
tti_unit.buf = 0; /* subsequent reads will return zero */ | |
break; | |
case XIO_WRITE: | |
ch = (ReadW(iocc_addr) >> 8) & 0xFF; /* get character to write */ | |
tto_unit.buf = emit_conout_character(ch); /* output character and save write status */ | |
/* fprintf(stderr, "[CONOUT] %02x\n", ch); */ | |
SETBIT(tto_dsw, TT_DSW_PRINTER_BUSY); | |
sim_activate(&tto_unit, tto_unit.wait); /* schedule interrupt */ | |
break; | |
case XIO_SENSE_DEV: | |
ACC = tto_dsw | tti_dsw; | |
if (modify & 0x01) { /* reset interrupts */ | |
CLRBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE); | |
CLRBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE); | |
CLRBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST); | |
CLRBIT(ILSW[4], ILSW_4_CONSOLE); | |
} | |
break; | |
default: | |
sprintf(msg, "Invalid console XIO function %x", func); | |
xio_error(msg); | |
} | |
/* fprintf(stderr, "After XIO %04x %04x\n", tti_dsw, tto_dsw); */ | |
} | |
/* emit_conout_character - write character with 1130 console code 'ch' */ | |
t_stat emit_conout_character (int ch) | |
{ | |
t_stat status; | |
#ifdef DEBUG_CONSOLE | |
printf("{%02x}", ch); | |
#endif | |
if ((tto_unit.flags & CSET_MASK) == CSET_1130) /* 1130 (binary) mode, write the raw 8-bit value */ | |
return sim_putchar(ch); | |
if (ch & COUT_IS_CTRL) { | |
/* red/black shift can be combined with another control */ | |
/* if present, emit the color shift characters alone */ | |
if (ch & COUT_CTRL_BLACK) { | |
if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_BLACK)) != SCPE_OK) | |
return status; | |
} | |
else if (ch & COUT_CTRL_RED) { | |
if ((status = map_conout_character(COUT_IS_CTRL|COUT_CTRL_RED)) != SCPE_OK) | |
return status; | |
} | |
ch &= ~(COUT_CTRL_BLACK|COUT_CTRL_RED); /* remove the ribbon shift bits */ | |
if (ch & ~COUT_IS_CTRL) { /* if another control remains, emit it */ | |
if ((status = map_conout_character(ch)) != SCPE_OK) | |
return status; | |
} | |
return SCPE_OK; | |
} | |
return map_conout_character(ch); | |
} | |
static void SendBeep (void) /* notify user keyboard was locked or key was bad */ | |
{ | |
sim_putchar(7); | |
} | |
/* tti_svc - keyboard polling (never stops) */ | |
static t_stat tti_svc (UNIT *uptr) | |
{ | |
int32 temp; | |
if (cgi) /* if running in CGI mode, no keyboard and no keyboard polling! */ | |
return SCPE_OK; | |
/* otherwise, so ^E can interrupt the simulator, */ | |
sim_activate(&tti_unit, tti_unit.wait); /* always continue polling keyboard */ | |
assert(sim_clock_queue != NULL); | |
temp = sim_poll_kbd(); | |
if (temp < SCPE_KFLAG) | |
return temp; /* no char or error? */ | |
temp &= 0xFF; /* remove SCPE_KFLAG */ | |
if ((tti_unit.flags & CSET_MASK) == CSET_ASCII) | |
temp = conin_map[temp] & 0xFF; /* perform input translation */ | |
if (temp == IRQ_KEY) { /* INT REQ (interrupt request) key */ | |
SETBIT(tti_dsw, TT_DSW_INTERRUPT_REQUEST); /* queue interrupt */ | |
SETBIT(ILSW[4], ILSW_4_CONSOLE); | |
calc_ints(); | |
CLRBIT(tti_unit.flags, KEYBOARD_LOCKED); /* keyboard restore, according to func. char. manual */ | |
#ifdef DEBUG_CONSOLE | |
printf("[*IRQ*]"); | |
#endif | |
tti_unit.buf = 0; /* subsequent reads need to return 0 (required by APL\1130) */ | |
return SCPE_OK; | |
} | |
if (temp == PROGRAM_STOP_KEY) { /* simulate the program stop button */ | |
SETBIT(con_dsw, CPU_DSW_PROGRAM_STOP); | |
SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP); | |
calc_ints(); | |
#ifdef DEBUG_CONSOLE | |
printf("[*PSTOP*]"); | |
#endif | |
return SCPE_OK; | |
} | |
if ((tti_unit.flags & KEYBOARD_LOCKED) || ! (tti_dsw & TT_DSW_KEYBOARD_BUSY)) { | |
SendBeep(); | |
return SCPE_OK; | |
} | |
if ((tti_unit.flags & CSET_MASK) == CSET_ASCII) | |
temp = ascii_to_conin[temp]; | |
if (temp == 0) { /* ignore invalid characters */ | |
SendBeep(); | |
calc_ints(); | |
return SCPE_OK; | |
} | |
tti_unit.buf = temp & 0xFFFE; /* save keystroke except last bit (not defined) */ | |
tti_unit.pos = tti_unit.pos + 1; /* but it lets us distinguish 0 from no punch ' ' */ | |
#ifdef DEBUG_CONSOLE | |
printf("[%04x]", tti_unit.buf & 0xFFFF); | |
#endif | |
SETBIT(tti_unit.flags, KEYBOARD_LOCKED); /* prevent further keystrokes */ | |
SETBIT(tti_dsw, TT_DSW_KEYBOARD_RESPONSE); /* queue interrupt */ | |
SETBIT(ILSW[4], ILSW_4_CONSOLE); | |
calc_ints(); | |
/* fprintf(stderr, "TTI interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */ | |
return SCPE_OK; | |
} | |
static t_stat tti_reset (DEVICE *dptr) | |
{ | |
tti_unit.buf = 0; | |
tti_dsw = 0; | |
CLRBIT(ILSW[4], ILSW_4_CONSOLE); | |
calc_ints(); | |
keyboard_selected(FALSE); | |
SETBIT(tti_unit.flags, KEYBOARD_LOCKED); | |
if (cgi) | |
sim_cancel(&tti_unit); /* in cgi mode, never poll keyboard */ | |
else | |
sim_activate(&tti_unit, tti_unit.wait); /* otherwise, always poll keyboard */ | |
return SCPE_OK; | |
} | |
/* basic_attach - fix quotes in filename, then call standard unit attach routine */ | |
t_stat basic_attach (UNIT *uptr, char *cptr) | |
{ | |
return attach_unit(uptr, quotefix(cptr)); /* fix quotes in filenames & attach */ | |
} | |
/* quotefix - strip off quotes around filename, if present */ | |
char * quotefix (char * cptr) | |
{ | |
#ifdef WIN32 /* do this only for Windows builds, for the time being */ | |
char *c; | |
int quote; | |
if (*cptr == '"' || *cptr == '\'') { | |
quote = *cptr++; /* remember quote and skip over it */ | |
for (c = cptr; *c && *c != quote; c++) | |
; /* find closing quote, or end of string */ | |
if (*c) /* terminate string at closing quote */ | |
*c = '\0'; | |
} | |
#endif | |
return cptr; /* return pointer to cleaned-up name */ | |
} | |
t_bool keyboard_is_busy (void) /* return TRUE if keyboard is not expecting a character */ | |
{ | |
return (tti_dsw & TT_DSW_KEYBOARD_BUSY); | |
} | |
static t_stat tto_svc (UNIT *uptr) | |
{ | |
CLRBIT(tto_dsw, TT_DSW_PRINTER_BUSY); | |
SETBIT(tto_dsw, TT_DSW_PRINTER_RESPONSE); | |
SETBIT(ILSW[4], ILSW_4_CONSOLE); | |
calc_ints(); | |
/* fprintf(stderr, "TTO interrupt svc SET %04x %04x\n", tti_dsw, tto_dsw); */ | |
return (t_stat) tto_unit.buf; /* return status saved during output conversion */ | |
} | |
static t_stat tto_reset (DEVICE *dptr) | |
{ | |
tto_unit.buf = 0; | |
tto_dsw = 0; | |
CLRBIT(ILSW[4], ILSW_4_CONSOLE); | |
calc_ints(); | |
sim_cancel(&tto_unit); /* deactivate unit */ | |
set_conout_mapping(tto_unit.flags); /* initialize the overstrike mappings */ | |
/* register the font-mapping command */ | |
register_cmd("FONT", font_cmd, 0, "font MAPFILE use font mapping definitions in MAPFILE\n"); | |
return SCPE_OK; | |
} | |
#ifdef _MSC_VER | |
# pragma warning(disable:4245) /* enable int->char demotion warning caused by characters with high-bit set */ | |
#endif | |
static struct { /* default input mapping for APL */ | |
unsigned char in; | |
unsigned char out; | |
} conin_to_APL[] = | |
{ /* these map input keys to those in like positions on 1130 keyboard */ | |
'[', '\r', /* enter (EOF) is APL left arrow */ | |
';', '\b', /* backspace is APL [ */ | |
'\'', '\x15', /* ctrl-U, erase field, is APL ]*/ | |
'2', '@', /* APL upshift */ | |
'3', '%', /* APL rightshift */ | |
'4', '*', /* APL + and - */ | |
'5', '<', /* APL x and divide */ | |
'8', '-', /* APL return */ | |
'9', '/', /* APL backspace */ | |
'-', IRQ_KEY, /* ctrl-q (INT REQ), APL ATTN */ | |
'\r', '-', /* APL return */ | |
'\b', '/' /* APL backspace */ | |
}; | |
#define NCONIN_TO_APL (sizeof(conin_to_APL)/sizeof(conin_to_APL[0])) | |
static struct { /* default output mapping for APLPLUS font */ | |
unsigned char in; | |
unsigned char out; | |
} conout_to_APL[] = | |
{ | |
'\x01', IGNR_, /* controls */ | |
'\x03', '\n', | |
'\x05', IGNR_, /* (black and red are handled by ansi sequences) */ | |
'\x09', IGNR_, | |
'\x11', '\b', | |
'\x21', ' ', | |
'\x41', '\t', | |
'\x81', CRLF_, | |
'\xC4', '\x30', /* (if you're curious, order here is position on APL typeball) */ | |
'\xE4', '\x38', | |
'\xD4', '\x37', | |
'\xF4', '\x35', | |
'\xDC', '\x33', | |
'\xFC', '\x31', | |
'\xC2', '\x29', | |
'\xE2', '\x9F', | |
'\xD2', '\x89', | |
'\xF2', '\x88', | |
'\xDA', '\xAF', | |
'\xC6', '\x5E', | |
'\xE6', '\xAC', | |
'\xD6', '\x3E', | |
'\xF6', '\x3D', | |
'\xDE', '\x3C', | |
'\xFE', '\xA8', | |
'\xC0', '\x5D', | |
'\xE0', '\x39', | |
'\xD0', '\x36', | |
'\xF0', '\x34', | |
'\xD8', '\x32', | |
'\x84', '\x84', | |
'\xA4', '\x59', | |
'\x94', '\x58', | |
'\xB4', '\x56', | |
'\x9C', '\x54', | |
'\xBC', '\x2F', | |
'\x82', '\x3B', | |
'\xA2', '\x9B', | |
'\x92', '\xBE', | |
'\xB2', '\x87', | |
'\x9A', '\x97', | |
'\x86', '\x85', | |
'\xA6', '\x86', | |
'\x96', '\x9C', | |
'\xB6', '\x9E', | |
'\x9E', '\x7E', | |
'\xBE', '\x5C', | |
'\x80', '\x2C', | |
'\xA0', '\x5A', | |
'\x90', '\x57', | |
'\xB0', '\x55', | |
'\x98', '\x53', | |
'\x44', '\x2B', | |
'\x64', '\x51', | |
'\x54', '\x50', | |
'\x74', '\x4E', | |
'\x5C', '\x4C', | |
'\x7C', '\x4A', | |
'\x42', '\x28', | |
'\x62', '\xBD', | |
'\x52', '\xB1', | |
'\x72', '\x7C', | |
'\x5A', '\x27', | |
'\x46', '\x2D', | |
'\x66', '\x3F', | |
'\x56', '\x2A', | |
'\x76', '\x82', | |
'\x5E', '\x8C', | |
'\x7E', '\xB0', | |
'\x40', '\x5B', | |
'\x60', '\x52', | |
'\x50', '\x4F', | |
'\x70', '\x4D', | |
'\x58', '\x4B', | |
'\x04', '\xD7', | |
'\x24', '\x48', | |
'\x14', '\x47', | |
'\x34', '\x45', | |
'\x1C', '\x43', | |
'\x3C', '\x41', | |
'\x02', '\x3A', | |
'\x22', '\xBC', | |
'\x12', '\x5F', | |
'\x32', '\x98', | |
'\x1A', '\x83', | |
'\x06', '\xF7', | |
'\x26', '\x91', | |
'\x16', '\x92', | |
'\x36', '\xB9', | |
'\x1E', '\x9D', | |
'\x3E', '\xB8', | |
'\x00', '\x2E', | |
'\x20', '\x49', | |
'\x10', '\x46', | |
'\x30', '\x44', | |
'\x18', '\x42', | |
}; | |
#define NCONOUT_TO_APL (sizeof(conout_to_APL)/sizeof(conout_to_APL[0])) | |
static OS_MAP default_os_map[] = /* overstrike mapping for APLPLUS font */ | |
{ | |
'\x8a', 2, "\x5e\x7e", | |
'\x8b', 2, "\x9f\x7e", | |
'\x8d', 2, "\x8c\x27", | |
'\x8e', 3, "\x8c\x2d\x3a", | |
'\x8f', 2, "\x91\x5f", | |
'\x90', 2, "\x92\x7e", | |
'\x93', 2, "\x91\x7c", | |
'\x94', 2, "\x92\x7c", | |
'\x95', 2, "\xb0\x82", | |
'\x96', 2, "\xb0\x83", | |
'\x99', 2, "\x2d\x5c", | |
'\x9a', 2, "\x2d\x2f", | |
'\xae', 2, "\x2c\x2d", | |
'\xb2', 2, "\xb1\x7c", | |
'\xb3', 2, "\xb1\x5c", | |
'\xb4', 2, "\xb1\x2d", | |
'\xb5', 2, "\xb1\x2a", | |
'\xba', 2, "\xb9\x5f", | |
'\xd0', 2, "\x30\x7e", | |
'\xd8', 2, "\x4f\x2f", | |
'\x21', 2, "\x27\x2e", | |
'\xa4', 2, "\xb0\xb1", /* map degree in circle to circle cross (APL uses this as character error symbol) */ | |
'\xf0', 2, "\xb0\xa8", | |
'\xfe', 2, "\x3a\xa8", | |
}; | |
#ifdef _MSC_VER | |
# pragma warning(default:4245) /* enable int->char demotion warning */ | |
#endif | |
/* os_map_comp - compare to OS_MAP entries */ | |
static int os_map_comp (OS_MAP *a, OS_MAP *b) | |
{ | |
unsigned char *sa, *sb; | |
int i; | |
if (a->nin > b->nin) | |
return +1; | |
if (a->nin < b->nin) | |
return -1; | |
sa = a->inlist; | |
sb = b->inlist; | |
for (i = a->nin; --i >= 0;) { | |
if (*sa > *sb) | |
return +1; | |
if (*sa < *sb) | |
return -1; | |
sa++; | |
sb++; | |
} | |
return 0; | |
} | |
/* strsort - sorts the n characters of array 's' using insertion sort */ | |
static void strsort (int n, unsigned char *s) | |
{ | |
unsigned char temp; | |
int i, big; | |
while (--n > 0) { /* repeatedly */ | |
big = 0; /* find largest value of s[0]...s[n] */ | |
for (i = 1; i <= n; i++) | |
if (s[i] > s[big]) big = i; | |
temp = s[n]; /* put largest value at end of array */ | |
s[n] = s[big]; | |
s[big] = temp; | |
} | |
} | |
/* file format: | |
[font XXX] font named XXX | |
OUT failure character | |
OUT IN single character mapping | |
OUT IN IN ... overstrike mapping | |
*/ | |
static void set_conout_mapping (int32 flags) | |
{ | |
curcol = 0; | |
maxcol = 0; | |
/* set the default mappings. We may later override them with settings from an ini file */ | |
set_default_mapping(flags); | |
} | |
/* finish_conout_mapping - sort the finalized overstrike mapping */ | |
static void finish_conout_mapping (int32 flags) | |
{ | |
int i, n, big; | |
OS_MAP temp; | |
for (i = 0; i < n_os_mappings; i++) /* sort the inlist strings individually */ | |
strsort(os_map[i].nin, os_map[i].inlist); | |
for (n = n_os_mappings; --n > 0; ) { /* then sort the os_map array itself with insertion sort */ | |
big = 0; /* find largest value of s[0]...s[n] */ | |
for (i = 1; i <= n; i++) | |
if (os_map_comp(os_map+i, os_map+big) > 0) big = i; | |
if (big != n) { | |
temp = os_map[n]; /* put largest value at end of array */ | |
os_map[n] = os_map[big]; | |
os_map[big] = temp; | |
} | |
} | |
} | |
/* validate_conout_mapping - called when set command gets a new value */ | |
static t_stat validate_conout_mapping (UNIT *uptr, int32 match, char *cvptr, void *desc) | |
{ | |
set_conout_mapping(match); | |
return SCPE_OK; | |
} | |
static void reset_mapping (void) | |
{ | |
int i; | |
black_ribbon[0] = '\0'; /* erase the ribbon sequences */ | |
red_ribbon[0] = '\0'; | |
memset(conout_map, 0, sizeof(conout_map)); /* erase output mapping */ | |
n_os_mappings = 0; /* erase overstrike mapping */ | |
for (i = (sizeof(conin_map)/sizeof(conin_map[0])); --i >= 0; ) | |
conin_map[i] = (unsigned char) i; /* default conin_map is identity map */ | |
} | |
/* set_default_mapping - create standard font and overstrike map */ | |
static void set_default_mapping (int32 flags) | |
{ | |
int i; | |
reset_mapping(); | |
strcpy((char *) black_ribbon, "\033[30m"); | |
strcpy((char *) red_ribbon, "\033[31m"); | |
switch (flags & CSET_MASK) { | |
case CSET_1130: | |
break; | |
case CSET_ASCII: | |
memcpy(conout_map, conout_to_ascii, sizeof(conout_to_ascii)); | |
break; | |
case CSET_APL: | |
for (i = NCONOUT_TO_APL; --i >= 0; ) | |
conout_map[conout_to_APL[i].in] = conout_to_APL[i].out; | |
for (i = NCONIN_TO_APL; --i >= 0; ) | |
conin_map[conin_to_APL[i].in] = conin_to_APL[i].out; | |
memcpy(os_map, default_os_map, sizeof(default_os_map)); | |
n_os_mappings = (sizeof(default_os_map) / sizeof(default_os_map[0])); | |
break; | |
} | |
finish_conout_mapping(flags); /* sort conout mapping if necessary */ | |
} | |
/* sim_putstr - write a string to the console */ | |
t_stat sim_putstr (char *s) | |
{ | |
t_stat status; | |
while (*s) { | |
if ((status = sim_putchar(*s)) != SCPE_OK) | |
return status; | |
s++; | |
} | |
return SCPE_OK; | |
} | |
/* map_conout_character - translate and write a single character */ | |
static t_stat map_conout_character (int ch) | |
{ | |
t_stat status; | |
int i, cmp; | |
if (ch == (COUT_IS_CTRL|COUT_CTRL_BLACK)) | |
return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) black_ribbon) : SCPE_OK; | |
if (ch == (COUT_IS_CTRL|COUT_CTRL_RED)) | |
return (tto_unit.flags & ENABLE_ANSI) ? sim_putstr((char *) red_ribbon) : SCPE_OK; | |
if ((ch = conout_map[ch & 0xFF]) == 0) | |
ch = '?'; /* unknown character? print ? */ | |
if (ch == '\n') { /* newline: reset overstrike buffer */ | |
curcol = 0; | |
maxcol = -1; | |
} | |
else if (ch == '\r') { /* carriage return: rewind to column 0 */ | |
curcol = 0; | |
maxcol = -1; /* assume it advances paper too */ | |
} | |
else if (ch == '\b') { /* backspace: back up one character */ | |
if (curcol > 0) | |
curcol--; | |
} | |
else if (n_os_mappings && ch != (unsigned char) IGNR_) { | |
if (curcol >= MAX_OUTPUT_COLUMNS) | |
map_conout_character('\x81'); /* precede with automatic carriage return/line feed, I guess */ | |
if (curcol > maxcol) { /* first time in this column, no overstrike possible yet */ | |
os_buf[curcol].nin = 0; | |
maxcol = curcol; | |
} | |
if (ch != ' ' && ch != 0) { /* (if it's not a blank or unknown) */ | |
os_buf[curcol].inlist[os_buf[curcol].nin] = (unsigned char) ch; | |
strsort(++os_buf[curcol].nin, os_buf[curcol].inlist); | |
} | |
if (os_buf[curcol].nin == 0) /* if nothing but blanks seen, */ | |
ch = ' '; /* output is a blank */ | |
else if (os_buf[curcol].nin == 1) { /* if only one printing character seen, display it */ | |
ch = os_buf[curcol].inlist[0]; | |
} | |
else { /* otherwise look up mapping */ | |
ch = '?'; | |
for (i = 0; i < n_os_mappings; i++) { | |
cmp = os_map_comp(&os_buf[curcol], &os_map[i]); | |
if (cmp == 0) { /* a hit */ | |
ch = os_map[i].ch; | |
break; | |
} | |
else if (cmp < 0) /* not found */ | |
break; | |
} | |
} | |
if (curcol < MAX_OUTPUT_COLUMNS) /* this should now never happen, as we automatically return */ | |
curcol++; | |
} | |
switch (ch) { | |
case IGNR_: | |
break; | |
case CRLF_: | |
if (! cgi) { | |
if ((status = sim_putchar('\r')) != SCPE_OK) | |
return status; | |
tto_unit.pos++; | |
} | |
if ((status = sim_putchar('\n')) != SCPE_OK) | |
return status; | |
tto_unit.pos++; /* hmm, why do we count these? */ | |
break; | |
default: | |
if ((status = sim_putchar(ch)) != SCPE_OK) | |
return status; | |
tto_unit.pos++; | |
break; | |
} | |
return SCPE_OK; | |
} | |
/* font_cmd - parse a font mapping file. Sets input and output translations */ | |
static t_stat font_cmd (int32 flag, char *cptr) | |
{ | |
char *fname, quote; | |
FILE *fd; | |
while (*cptr && (*cptr <= ' ')) cptr++; /* skip blanks */ | |
if (! *cptr) return SCPE_2FARG; /* argument missing */ | |
fname = cptr; /* save start */ | |
if (*cptr == '\'' || *cptr == '"') { /* quoted string */ | |
quote = *cptr++; /* remember quote character */ | |
fname++; /* skip the quote */ | |
while (*cptr && (*cptr != quote)) /* find closing quote */ | |
cptr++; | |
} | |
else { | |
while (*cptr && (*cptr > ' ')) /* find terminating blank */ | |
cptr++; | |
} | |
*cptr = '\0'; /* terminate name */ | |
if ((fd = fopen(fname, "r")) == NULL) | |
return SCPE_OPENERR; | |
reset_mapping(); /* remove all default mappings */ | |
read_map_file(fd); | |
fclose(fd); | |
finish_conout_mapping(tto_unit.flags); | |
return SCPE_OK; | |
} | |
/* str_match - compare the string str to the keyword, case insensitive */ | |
static t_bool str_match (char *str, char *keyword) | |
{ | |
char kch, sch; | |
while (*keyword) { /* see if str matches the keyword... */ | |
kch = *keyword++; /* get pair of characters */ | |
sch = *str++; | |
if (BETWEEN(kch, 'A', 'Z')) kch += 32; /* change upper to lower case */ | |
if (BETWEEN(sch, 'A', 'Z')) sch += 32; | |
if (kch != sch) /* characters must match; if not, quit */ | |
return FALSE; | |
} | |
return *str <= ' ' || *str == ';'; /* success if the input string ended or is in whitespace or comment */ | |
} | |
/* read_map_file - process definition lines in opened mapping file */ | |
static void read_map_file (FILE *fd) | |
{ | |
char str[256], *c, *errmsg; | |
int lineno = 0; | |
enum {SECT_UNDEFINED, SECT_DEFAULT, SECT_ANSI, SECT_INPUT, SECT_OUTPUT, SECT_OVERSTRIKE} | |
section = SECT_UNDEFINED; | |
while (fgets(str, sizeof(str), fd) != NULL) { | |
++lineno; /* count input lines */ | |
if ((c = strchr(str, '\n')) != NULL) /* terminate at newline */ | |
*c = '\0'; | |
for (c = str; *c && *c <= ' '; c++) /* skip blanks */ | |
; | |
if (c[0] == '\0' || c[0] == ';') /* ignore blank lines and lines starting with ; */ | |
continue; | |
if (*c == '[') { | |
if (str_match(c, "[default]")) { /* check for section separators */ | |
set_default_mapping(tto_unit.flags); | |
section = SECT_UNDEFINED; | |
continue; | |
} | |
if (str_match(c, "[ansi]")) { | |
section = SECT_ANSI; | |
continue; | |
} | |
if (str_match(c, "[input]")) { | |
section = SECT_INPUT; | |
continue; | |
} | |
if (str_match(c, "[output]")) { | |
section = SECT_OUTPUT; | |
continue; | |
} | |
if (str_match(c, "[overstrike]")) { | |
section = SECT_OVERSTRIKE; | |
continue; | |
} | |
} | |
switch (section) { /* if we get here, we have a definition line */ | |
case SECT_ANSI: | |
errmsg = handle_map_ansi_definition(&c); | |
break; | |
case SECT_INPUT: | |
errmsg = handle_map_input_definition(&c); | |
break; | |
case SECT_OUTPUT: | |
errmsg = handle_map_output_definition(&c); | |
break; | |
case SECT_OVERSTRIKE: | |
errmsg = handle_map_overstrike_definition(&c); | |
break; | |
default: | |
errmsg = "line occurs before valid [section]"; | |
break; | |
} | |
if (errmsg == NULL) { /* if no other error detected, */ | |
while (*c && *c <= ' ') /* skip past any whitespace */ | |
c++; | |
if (*c && *c != ';') /* if line doesn't end or run into a comment, complain */ | |
errmsg = "too much stuff on input line"; | |
} | |
if (errmsg != NULL) { /* print error message and offending line */ | |
printf("* Warning: %s", errmsg); | |
switch (section) { /* add section name if possible */ | |
case SECT_ANSI: errmsg = "ansi"; break; | |
case SECT_INPUT: errmsg = "input"; break; | |
case SECT_OUTPUT: errmsg = "output"; break; | |
case SECT_OVERSTRIKE: errmsg = "overstrike"; break; | |
default: errmsg = NULL; break; | |
} | |
if (errmsg != NULL) | |
printf(" in [%s] section", errmsg); | |
printf(", line %d\n%s\n", lineno, str); | |
} | |
} | |
} | |
/* get_num_char - read an octal or hex character specification of exactly 'ndigits' digits | |
* the input pointers is left pointing to the last character of the number, so that it | |
* may be incremented by the caller | |
*/ | |
static char * get_num_char (char **pc, unsigned char *out, int ndigits, int base, char *errmsg) | |
{ | |
int ch = 0, digit; | |
char *c = *pc; | |
while (--ndigits >= 0) { /* collect specified number of digits */ | |
if (BETWEEN(*c, '0', '9')) | |
digit = *c - '0'; | |
else if (BETWEEN(*c, 'A', 'F')) | |
digit = *c - 'A' + 10; | |
else if (BETWEEN(*c, 'a', 'f')) | |
digit = *c - 'a' + 10; | |
else | |
digit = base; | |
if (digit >= base) /* bad digit */ | |
return errmsg; | |
ch = ch * base + digit; /* accumulate digit */ | |
c++; | |
} | |
*out = (unsigned char) ch; /* return parsed character */ | |
*pc = c-1; /* make input pointer point to last character seen */ | |
return NULL; /* no error */ | |
} | |
/* get_characters - read character specification(s) from input string pointed to | |
* by *pc. Results stored in outstr; up to nmax characters parsed. Actual number | |
* found returned in *nout. Returns NULL on success or error message if syntax | |
* error encountered. *pc is advanced to next whitespace or whatever followed input. | |
*/ | |
static char * get_characters (char **pc, unsigned char *outstr, int nmax, int *nout) | |
{ | |
char *c = *pc, *errstr; | |
unsigned char *out = outstr; | |
while (*c && *c <= ' ') /* skip leading whitespace */ | |
c++; | |
while (--nmax >= 0) { /* get up to maximum number of characters */ | |
if (*c == ';' || *c <= ' ') /* we ran into a comment, whitespace or end of string: we're done */ | |
break; | |
if (*c == '\\') { /* backslash escape of some sort */ | |
switch (*++c) { | |
case 'b': /* backspace */ | |
case 'B': | |
*out++ = '\b'; | |
break; | |
case 'e': /* ascii ESCAPE */ | |
case 'E': | |
*out++ = '\033'; | |
break; | |
case 'f': /* formfeed */ | |
case 'F': | |
*out++ = '\f'; | |
break; | |
case 'n': /* newline */ | |
case 'N': | |
*out++ = '\n'; | |
break; | |
case 'r': /* return */ | |
case 'R': | |
*out++ = '\r'; | |
break; | |
case 't': /* tab */ | |
case 'T': | |
*out++ = '\t'; | |
break; | |
case 'x': /* hex specification */ | |
case 'X': | |
c++; | |
if ((errstr = get_num_char(&c, out, 2, 16, "bad hex character")) != NULL) | |
return errstr; | |
out++; /* advance out pointer */ | |
break; | |
default: /* anything else */ | |
if (BETWEEN(*c, '0', '7')) { /* octal specification */ | |
if ((errstr = get_num_char(&c, out, 3, 8, "bad octal character")) != NULL) | |
return errstr; | |
out++; /* advance out pointer */ | |
} | |
else if (BETWEEN(*c, 'A', 'Z') || BETWEEN(*c, 'a', 'z')) | |
return "invalid \\ escape"; /* other \x letters are bad */ | |
else { | |
*out++ = (unsigned char) *c;/* otherwise, accept \x as literal character x */ | |
} | |
break; | |
} | |
} | |
else if (*c == '^') { /* control character */ | |
c++; | |
if (BETWEEN(*c, 'A', 'Z')) /* convert alpha, e.g. A -> 1 */ | |
*out++ = (unsigned char) (*c - 'A' + 1); | |
else if (BETWEEN(*c, 'a', 'z')) | |
*out++ = (unsigned char) (*c - 'z' + 1); | |
else /* non alpha is bad */ | |
return "invalid control letter"; | |
} | |
else if (str_match(c, "IGNORE")) { /* magic word: a character that will never be output */ | |
*out++ = (unsigned char) IGNR_; | |
c += 6; | |
} | |
else { | |
*out++ = (unsigned char) *c; /* save literal character */ | |
} | |
c++; | |
} | |
if (*c && *c != ';' && *c > ' ') /* we should be at end of string, whitespace or comment */ | |
return "too many characters specified"; | |
*pc = c; /* save advanced pointer */ | |
*nout = out-outstr; /* save number of characters stored */ | |
return NULL; /* no error */ | |
} | |
/* handle_map_ansi_definition - process line in [ansi] section */ | |
static char * handle_map_ansi_definition (char **pc) | |
{ | |
unsigned char *outstr; | |
char *errmsg; | |
int n; | |
if (str_match(*pc, "black")) { /* find which string we're setting */ | |
outstr = black_ribbon; /* this is where we'll save the output string */ | |
*pc += 5; /* skip over the token */ | |
} | |
else if (str_match(*pc, "red")) { | |
outstr = red_ribbon; | |
*pc += 3; | |
} | |
else | |
return "invalid variable name"; | |
/* get list of characters */ | |
if ((errmsg = get_characters(pc, outstr, sizeof(black_ribbon)-1, &n)) != NULL) | |
return errmsg; | |
outstr[n] = '\0'; /* null terminate the string */ | |
return (n > 0) ? NULL : "missing output string"; /* NULL if OK, error msg if no characters */ | |
} | |
/* handle_map_input_definition - process line in [input] section */ | |
static char * handle_map_input_definition (char **pc) | |
{ | |
unsigned char cin, cout; | |
char *errmsg; | |
int n; | |
if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL) /* get input character */ | |
return errmsg; | |
if (n != 1) | |
return "missing input character"; | |
if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL) /* get output character */ | |
return errmsg; | |
if (n != 1) | |
return "missing output character"; | |
conin_map[cin] = cout; /* set the mapping */ | |
return NULL; | |
} | |
/* handle_map_output_definition - process line in [output] section */ | |
static char * handle_map_output_definition (char **pc) | |
{ | |
unsigned char cin, cout; | |
char *errmsg; | |
int n; | |
if ((errmsg = get_characters(pc, &cin, 1, &n)) != NULL) /* get input character */ | |
return errmsg; | |
if (n != 1) | |
return "missing input character"; | |
if ((errmsg = get_characters(pc, &cout, 1, &n)) != NULL) /* get output character */ | |
return errmsg; | |
if (n != 1) | |
return "missing output character"; | |
conout_map[cin] = cout; /* set the mapping */ | |
return NULL; | |
} | |
/* handle_map_overstrike_definition - process line in [overstrike] section */ | |
static char * handle_map_overstrike_definition (char **pc) | |
{ | |
unsigned char ch, inlist[MAX_OS_CHARS]; | |
char *errmsg; | |
int nin; | |
if (n_os_mappings >= MAX_OS_MAPPINGS) /* os_map is full, no more room */ | |
return "too many overstrike mappings"; | |
/* get output character */ | |
if ((errmsg = get_characters(pc, &ch, 1, &nin)) != NULL) | |
return errmsg; | |
if (nin != 1) | |
return "missing output character"; | |
/* get input list */ | |
if ((errmsg = get_characters(pc, inlist, MAX_OS_CHARS, &nin)) != NULL) | |
return errmsg; | |
if (nin < 2) /* expect at least two characters overprinted */ | |
return "missing input list"; | |
os_map[n_os_mappings].ch = ch; /* save in next os_map slot */ | |
os_map[n_os_mappings].nin = nin; | |
memmove(os_map[n_os_mappings].inlist, inlist, nin); | |
n_os_mappings++; | |
return NULL; | |
} |