/* 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 (const 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 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, CONST 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, CONST char *cptr); /* handles font command */ | |
static void read_map_file(FILE *fd); /* reads a font map file */ | |
static t_bool str_match(const char *str, const char *keyword);/* keyword/string comparison */ | |
static const char * handle_map_ansi_definition(char **pc); /* input line parsers for map file sections */ | |
static const char * handle_map_input_definition(char **pc); | |
static const char * handle_map_output_definition(char **pc); | |
static const char * handle_map_overstrike_definition(char **pc); | |
#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' */ | |
static 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 */ | |
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 -- process this even if no keyboard input request pending */ | |
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; | |
} | |
// keyboard is locked or no active input request? | |
if ((tti_unit.flags & KEYBOARD_LOCKED) || ! (tti_dsw & TT_DSW_KEYBOARD_BUSY)) { | |
SendBeep(); | |
calc_ints(); | |
return SCPE_OK; | |
} | |
if ((tti_unit.flags & CSET_MASK) == CSET_ASCII) | |
temp = ascii_to_conin[temp]; | |
if (temp == 0) { /* ignore invalid characters (no mapping to 1130 input code) */ | |
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, CONST char *cptr) | |
{ | |
char gbuf[2*CBUFSIZE]; | |
return attach_unit(uptr, quotefix(cptr, gbuf)); /* fix quotes in filenames & attach */ | |
} | |
/* quotefix - strip off quotes around filename, if present */ | |
CONST char * quotefix (CONST char *cptr, char * buf) | |
{ | |
const char *c; | |
int quote; | |
while (sim_isspace(*cptr)) | |
++cptr; | |
if (*cptr == '"' || *cptr == '\'') { | |
quote = *cptr++; /* remember quote and skip over it */ | |
cptr = buf; | |
for (c = cptr; *c && *c != quote; c++) | |
*buf++ = *c; /* find closing quote, or end of string */ | |
if (*c) /* terminate string at closing quote */ | |
*buf = '\0'; | |
} | |
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) /* disable int->char demotion warning caused by characters with high-bit set */ | |
#endif | |
#ifdef __SUNPRO_C | |
# pragma error_messages (off, E_INIT_DOES_NOT_FIT) /* disable 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 __SUNPRO_C | |
# pragma error_messages (default, E_INIT_DOES_NOT_FIT) /* enable int->char demotion warning caused by characters with high-bit set */ | |
#endif | |
#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, CONST 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, CONST char *iptr) | |
{ | |
char *fname, quote; | |
char gbuf[4*CBUFSIZE], *cptr = gbuf; | |
FILE *fd; | |
gbuf[sizeof(gbuf)-1] = '\0'; | |
strncpy(gbuf, iptr, sizeof(gbuf)-1); | |
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 (const char *str, const 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; | |
const char *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 const char * get_num_char (char **pc, unsigned char *out, int ndigits, int base, const 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 const char * get_characters (char **pc, unsigned char *outstr, int nmax, int *nout) | |
{ | |
char *c = *pc; | |
const char *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 const char * handle_map_ansi_definition (char **pc) | |
{ | |
unsigned char *outstr; | |
const 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 const char * handle_map_input_definition (char **pc) | |
{ | |
unsigned char cin, cout; | |
const 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 const char * handle_map_output_definition (char **pc) | |
{ | |
unsigned char cin, cout; | |
const 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 const char * handle_map_overstrike_definition (char **pc) | |
{ | |
unsigned char ch, inlist[MAX_OS_CHARS]; | |
const 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; | |
} |