/* Reading and parsing of the keys script for Sim. */ #include "sim.h" #include "common.h" #include "remapdata.h" #include "parser.h" #define K_SCRIPT_NAME "simkscpt" #define MAX_KEY_NAME_LEN ((unsigned)(sizeof ("ENTER_CPAD") - sizeof ((char)'\0'))) #define STO_SYMBOL '\x016' /* -> */ #define STO_SYMBOL_STR "\x016" static const char PARSE_EOF_STR[] = "parse error at EOF."; /* pointer at the current position in the script */ static char *ScriptPtr = NULL; /* pointer after the end of the script */ static char *ScriptEnd = NULL; /* line of the script being parsed */ static unsigned short ScriptLine = 1; /* If ERROR_MSG is not NULL, display it. Else display the PARSE_ERR_LEN next characters of the script. Then leave the program. */ __attribute__ ((noreturn)) static void ShowParseError (const char *error_msg) { #define PARSE_ERR_LEN 8u /* number of letters of the script shown */ HANDLE buffer_handle; char *str_buffer; char err_str[PARSE_ERR_LEN + sizeof ((char)'\0')]; unsigned short quote_size; /* 1 + ... + 1 for ' and ' */ buffer_handle = HeapAlloc (sizeof (K_SCRIPT_NAME "(999): ") + (error_msg ? strlen (error_msg) : (sizeof ("parse error - ") - 1) + 1 + PARSE_ERR_LEN + 1)); str_buffer = HeapDeref (buffer_handle); if (!str_buffer) xST_helpMsg (NO_MEM_STR); else { sprintf (str_buffer, K_SCRIPT_NAME"(%u): ", ScriptLine); if (error_msg) strcat (str_buffer, error_msg); else { /* Do not read outside the file. */ quote_size = min ((unsigned short)PARSE_ERR_LEN, (unsigned short)(long)(ScriptEnd - ScriptPtr)); memcpy (err_str, ScriptPtr, quote_size); err_str[quote_size] = '\0'; strcat (str_buffer, "parse error - '"); strcat (str_buffer, err_str); strcat (str_buffer, "'"); } xST_helpMsg (str_buffer); HeapFree (buffer_handle); } exit (0); /* NOTREACHED */ } /* Read the character from the script and increment the script pointer. Returns EOF if there is no characters to read. */ static short ReadNextChar (void) { if (ScriptPtr == ScriptEnd) return EOF; else return (short)*ScriptPtr++; } /* Read the next character from the script without incrementing the script pointer. */ static short ReadOneChar (void) { if (ScriptPtr == ScriptEnd) return EOF; else return (short)*ScriptPtr; } typedef enum {ES_nothing_done, ES_something_done, ES_newline} ES_Return; /* Jump the next spaces, comments, and/or newline characters in the script. Return what was done. */ static ES_Return EatSpaces (void) { short next_char; BOOL meal_finished; BOOL something_done; BOOL newline; newline = FALSE; something_done = FALSE; meal_finished = FALSE; do { next_char = ReadOneChar(); switch ((char)next_char) { case '\r': newline = TRUE; ReadNextChar(); /* the \r and the the command byte will be eaten */ ScriptLine++; case ' ': ReadNextChar(); something_done = TRUE; break; case ';': ReadNextChar(); do /* eat all the comment */ { next_char = ReadNextChar(); } while (next_char != EOF && next_char != '\r'); if (next_char == '\r') { ReadNextChar(); /* eat the command byte */ ScriptLine++; } something_done = TRUE; break; default: meal_finished = TRUE; } } while (!meal_finished); if (newline) return ES_newline; if (something_done) return ES_something_done; /* if (!something_done) */ return ES_nothing_done; } /* Read the next word in the script. A word ends when a character that can be eaten with EatSpaces() is read, if the character ENDING_CHAR is found, or if the end of the script is reached. The word must have less than MAX_LEN characters, else ShowParseError() is run. The word is copied in DEST_BUF as a null terminated string. Return FALSE if there is no word to read. There is a parse error if the word to read is too big. If ENDING_CHAR is found, it is neither saved in DEST_BUG, nor read from the file. The spaces, \r, comments, etc. at the end of the word will be eaten. If a newline character is eaten, and if NEWLINE_EATEN is not null, return TRUE in the variable pointed to by NEWLINE_EATEN, else return FALSE in this variable. */ static BOOL ReadNextWord (char *dest_buf, char ending_char, unsigned short max_len, BOOL *newline_eaten) { short next_char; unsigned short word_size; ES_Return ES_action; word_size = 0; EatSpaces(); if (ReadOneChar() == EOF) /* if there is no word */ return FALSE; while ((ES_action = EatSpaces()) == ES_nothing_done) { next_char = ReadOneChar(); if (next_char == EOF || next_char == ending_char) break; if (word_size > max_len) ShowParseError (PARSE_EOF_STR); /* NOTREACHED */ *dest_buf++ = ReadNextChar(); word_size++; } *dest_buf = '\0'; /* It will be a string. */ if (newline_eaten) *newline_eaten = (ES_action == ES_newline); return TRUE; } /* Return TRUE if the word pointed to by ScriptPtr is in the form : CfgName: The name of the key configuration will be copied in the buffer pointed to by CFG_NAME. TRUE is written in the BOOL pointed to by newline_eaten (if it is not NULL) if ReadNextWord() has eaten a newline character. */ static BOOL IsKeyCfg (char cfg_name[MAX_CFG_NAME_LEN + sizeof ((char)':') + sizeof ((char)'\0')], BOOL *newline_eaten) { char *colon_ptr; if (!ReadNextWord (cfg_name, ':', MAX_CFG_NAME_LEN + sizeof ((char)':') , newline_eaten)) /* The end of the script has been reached. */ return FALSE; colon_ptr = &cfg_name[strlen (cfg_name) - 1]; if (*colon_ptr == ':') { colon_ptr = '\0'; /* Remove the colon. */ return TRUE; } /* ReadNextWord() as already eaten the spacing characters after the word. */ if (ReadOneChar() == ':') { ReadNextChar(); /* eat the colon */ return TRUE; /* configuration found */ } else return FALSE; } /* Search the key configuration KEY_CFG. ScriptPtr must point to the beginning of the script. ScriptPtr will point to the next character (after '\r[command byte][spaces]KEY_CFG[spaces]:' ). Return TRUE if KEY_CFG is found, else return FALSE. */ static inline BOOL SearchKeyCfg (const char *key_cfg) { BOOL newline_eaten; char cfg_name[MAX_CFG_NAME_LEN + sizeof ((char)':') + sizeof ((char)'\0')]; short next_char; while (1) { if (IsKeyCfg (cfg_name, &newline_eaten) && !strcmp (cfg_name, key_cfg)) return TRUE; if (ReadOneChar() == EOF) return FALSE; /* Else it is not a configuration name, or it is not the right configuration. Search the next line, but only if ReadNextWord() has not already jumped to it. */ if (!newline_eaten) { do { next_char = ReadNextChar (); if (next_char == EOF) return FALSE; /* configuration not found */ } while (next_char != '\r'); ReadNextChar(); /* eat the command byte */ ScriptLine++; } } /* while (1) */ } /* Initialize REMAP_MATRIX. */ static inline void IniRemapMatrix (T_REMAP_MATRIX *remap_matrix) { memset (remap_matrix, REMAP_KEY_NOT_USED, REMAP_MATRIX_SIZE); } typedef enum {ADD_K_success, ADD_K_wrong_original, ADD_K_wrong_new} ADD_K_result; /* Add a new remapped key to REMAP_MATRIX, giving pointers to the name of the original key and the name of the new key. REMAP_MATRIX is a pointer to a T_REMAP_MATRIX matrix of REMAP_ONE_KEY structures. The matrix is a matrix of keys of the original calculator, and the REMAP_ONE_KEY structures contain the parameters (mask and bit to test) of the key for the calculator Sim is run onto. If the original key has already been added to the matrix, it will be overwritten. If EMULATING_THIS_CALC is TRUE, we are emulating a program for this calc. Return ADD_K_success if the key was added successfully, return ADD_K_wrong_original if the name of the original key is invalid, return ADD_K_wrong_new if the name of the new key is invalid. */ static inline ADD_K_result AddToRemapMatrix (T_REMAP_MATRIX *remap_matrix, const char *original_key, const char *new_key, BOOL emulating_this_calc) { /* The macros and the const arrays used here are defined in remapdata.h . */ unsigned short i; const KEY_NAME_AND_PARMS *key_parms, *original_parms, *new_parms; REMAP_ONE_KEY *one_key; unsigned short calc; /* If EMULATING_THIS_CALC is TRUE, the parameters in the member THIS_CALC of the structure KEY_NAME_AND_PARMS must be used, instead of the one in the member EMULATED_CALC. */ calc = emulating_this_calc ? offsetof (KEY_NAME_AND_PARMS, this_calc) : offsetof (KEY_NAME_AND_PARMS, emulated_calc); original_parms = new_parms = NULL; /* Search ORIGINAL_KEY and NEW_KEY in KEYS_DATA_TABLE[]. */ for (i = 0, key_parms = KEYS_DATA_TABLE; i < sizeof (KEYS_DATA_TABLE) / sizeof (KEY_NAME_AND_PARMS) && (!original_parms || !new_parms); i++, key_parms++) { if (!cmpstri (GET_KNAME (key_parms->name_offset), original_key)) original_parms = key_parms; if (!cmpstri (GET_KNAME (key_parms->name_offset), new_key)) new_parms = key_parms; } /* If the original or the new key was not found in KEYS_DATA_TABLE[], if the original key does not exist on the calc emulated, or if the new key does not exist on the calc Sim is running onto, there is an error. */ if (!original_parms || ((RR_PARMS*)((char*)original_parms+calc))->row == RR_NO_KEY) return ADD_K_wrong_original; if (!new_parms || new_parms->this_calc.row == RR_NO_KEY) return ADD_K_wrong_new; /* The parameters of the original key give us the coordinates in REMAP_MATRIX. Warning : the 7th bit is the first column in the the rowread matrix. */ one_key =&(*remap_matrix)[(unsigned)(((RR_PARMS*)((char*)original_parms+calc))->row)] [7-(unsigned)(((RR_PARMS*)((char*)original_parms+calc))->col)]; /* The parameters of the new key give us the contain of REMAP_MATRIX at theses coordinates. */ one_key->row_mask = (short)-1 ^ (1 << new_parms->this_calc.row); one_key->col_bit = new_parms->this_calc.col; return ADD_K_success; } /* Read '[ORIGINAL_KEY][spaces]->[spaces][NEW_KEY][spaces],[spaces]' in a key configuration (the configuration must have been found with SearchKeyCfg() ), and call AddToRemapMatrix() to add the pair to REMAP_MATRIX. If EMULATING_THIS_CALC is TRUE, we are emulating a program for this calc. Throw a parse error if a syntax error was found. Return TRUE if there seems to be other keys pairs after this one. */ static inline BOOL ReadOneKeysPair (T_REMAP_MATRIX *remap_matrix, BOOL emulating_this_calc) { char original_key[MAX_KEY_NAME_LEN + sizeof ((char)'\0')]; char new_key[MAX_KEY_NAME_LEN + sizeof ((char)'\0')]; char next_cfg[MAX_CFG_NAME_LEN + sizeof ((char)':') + sizeof ((char)'\0')]; ADD_K_result add_result; BOOL newline; short next_char; char *temp_script_ptr; if (!ReadNextWord (original_key, STO_SYMBOL, MAX_KEY_NAME_LEN, &newline)) ShowParseError (PARSE_EOF_STR); /* NOTREACHED */ if (!*original_key) ShowParseError ("key is missing before '"STO_SYMBOL_STR"'."); /* NOTREACHED */ if (ReadNextChar() != STO_SYMBOL) { /* If a newline character was eaten, adjust the line counter. */ if (newline) ScriptLine--; ShowParseError ("'"STO_SYMBOL_STR"' not found."); /* NOTREACHED */ } if (!ReadNextWord (new_key, ',', MAX_KEY_NAME_LEN, &newline)) ShowParseError (PARSE_EOF_STR); /* NOTREACHED */ if (!*new_key) ShowParseError ("key is missing after '"STO_SYMBOL_STR"'."); /* NOTREACHED */ add_result = AddToRemapMatrix (remap_matrix, original_key, new_key, emulating_this_calc); if (add_result == ADD_K_wrong_original) { if (newline) ScriptLine--; ShowParseError ("invalid key before '"STO_SYMBOL_STR"'."); /* NOTREACHED */ } if (add_result == ADD_K_wrong_new) { if (newline) ScriptLine--; ShowParseError ("invalid key after '"STO_SYMBOL_STR"'."); /* NOTREACHED */ } next_char = ReadOneChar(); if (next_char == ',') { ReadNextChar(); /* eat this comma */ return TRUE; /* There are other keys pairs after this one. */ } if (next_char == EOF) return FALSE; /* It's the end of this configuration. */ if (!newline) /* There is no comma, but there is no newline character. */ ShowParseError ("',' is missing."); /* NOTREACHED */ temp_script_ptr = ScriptPtr; /* save it */ if (!IsKeyCfg (next_cfg, &newline)) { /* There should be the beginning of another configuration after this one. Make ScriptPtr point to this wrong config name (IsKeyCfg() has modified it). */ ScriptPtr = temp_script_ptr; if (newline) /* (note that the line number will still be wrong if IsKeyCfg() has eaten more than one newline character... But anyway the user should have no problems to find the syntax error). */ ScriptLine--; ShowParseError(NULL); /* NOTREACHED */ } else return FALSE; /* It's the end of this configuration. */ } /* Open the text file K_SCRIPT_NAME. */ static inline void OpenScript (void) { SYM_ENTRY *SymPtr; char *FilePtr; /* Scan all the folders and all the files. */ SymPtr = SymFindFirst (NULL, 2); for ( ;SymPtr != NULL; SymPtr = SymFindNext()) { if (SymPtr->flags.bits.folder == 1) continue; /* It's a folder. */ if (strcmp (SymPtr->name, K_SCRIPT_NAME)) continue; /* Not the right name. */ FilePtr = (char*)HToESI (SymPtr->handle); if (*FilePtr == (char)TEXT_TAG) break; } if (!SymPtr) { xST_helpMsg ("Sim: key script '"K_SCRIPT_NAME"' not found."); exit (0); /* NOTREACHED */ } ScriptLine = 1; ScriptPtr = HeapDeref (SymPtr->handle); /* Make ScriptEnd point on the '\0' before TEXT_TAG at the end of the file. */ ScriptEnd = ScriptPtr + *(unsigned short*)ScriptPtr; /* Jump the size, the cursor position, and the newline character. */ ScriptPtr += 2 * sizeof (short) + sizeof (char); } /* Parse the keys script, and fill REMAP_MATRIX with the data read in the key configuration KEY_CFG. KEY_CFG must have MAX_CFG_NAME_LEN characters at the most. If EMULATING_THIS_CALC is TRUE, we are emulating a program for this calc (the user just wants to remap the keys of a program). */ void ParseKeysScript (T_REMAP_MATRIX *remap_matrix, const char *key_cfg, BOOL emulating_this_calc) { char str_buf[sizeof ("Sim: key config 'wwwwwww' not found.")]; OpenScript(); if (!SearchKeyCfg (key_cfg)) { if (strlen (key_cfg) > 7) ST_helpMsg ("Sim: key configuration not found."); else { sprintf (str_buf, "Sim: key config '%.7s' not found.", key_cfg); xST_helpMsg (str_buf); } exit (0); /* NOTREACHED */ } IniRemapMatrix(remap_matrix); while (ReadOneKeysPair (remap_matrix, emulating_this_calc)) continue; }