#include #include "ansi_parser.h" #include "ansi_parser_callbacks.h" #include "ascii.h" #include "apars_logging.h" #include "screen.h" /* Ragel constants block */ %%{ machine ansi; write data; }%% static volatile int cs = -1; static volatile bool inside_string = false; // public volatile u32 ansi_parser_char_cnt = 0; volatile bool ansi_parser_inhibit = 0; void ICACHE_FLASH_ATTR ansi_parser_reset(void) { if (cs != ansi_start) { cs = ansi_start; inside_string = false; apars_reset_utf8buffer(); ansi_warn("Parser state reset (timeout?)"); } } #define HISTORY_LEN 10 #if DEBUG_ANSI static char history[HISTORY_LEN + 1]; #endif void ICACHE_FLASH_ATTR apars_show_context(void) { #if DEBUG_ANSI char buf1[HISTORY_LEN*3+2]; char buf2[HISTORY_LEN*3+2]; char *b1 = buf1; char *b2 = buf2; char c; for(int i=0;i 127) c = '.'; b2 += sprintf(b2, "%c ", c); } ansi_dbg("Context: %s", buf2); ansi_dbg(" %s", buf1); #endif } /** * \brief Linear ANSI chars stream parser * * Parses a stream of bytes using a Ragel parser. The defined * grammar does not use 'unget', so the entire buffer is * always processed in a linear manner. * * \attention -> but always check the Ragel output for 'p--' * or 'p -=', that means trouble. * * \param newdata - array of new chars to process * \param len - length of the newdata buffer */ void ICACHE_FLASH_ATTR ansi_parser(char newchar) { // The CSI code is built here static char leadchar; static char interchar; // intermediate CSI char static int arg_ni; static int arg_cnt; static int arg[CSI_N_MAX]; static char string_buffer[ANSI_STR_LEN]; static int str_ni; if (ansi_parser_inhibit) return; // This is used to detect timeout delay (time since last rx char) ansi_parser_char_cnt++; if (termconf->ascii_debug) { apars_handle_plainchar(newchar); return; } // Init Ragel on the first run if (cs == -1) { %% write init; #if DEBUG_ANSI memset(history, 0, sizeof(history)); #endif } #if DEBUG_ANSI for(int i=1; i= ' ') { apars_handle_plainchar(newchar); return; } } // Load new data to Ragel vars const char *p = &newchar; const char *eof = NULL; const char *pe = &newchar + 1; // The parser %%{ #/* ESC = 27; NOESC = (any - ESC - 7); TOK_ST = ESC '\\'; # String terminator - used for OSC commands STR_END = (7 | TOK_ST); # --- Error handler --- action errBadSeq { ansi_warn("Parser error."); apars_show_context(); inside_string = false; // no longer in string, for sure fgoto main; } # --- Regular characters to be printed --- action plain_char { if (fc != 0) { apars_handle_plainchar(fc); } } # --- CSI commands --- action CSI_start { // Reset the CSI builder leadchar = NUL; interchar = NUL; arg_ni = 0; arg_cnt = 0; // Zero out digits for(int i = 0; i < CSI_N_MAX; i++) { arg[i] = 0; } fgoto CSI_body; } action CSI_leading { leadchar = fc; } action CSI_digit { if (arg_cnt == 0) arg_cnt = 1; // x10 + digit if (arg_ni < CSI_N_MAX) { arg[arg_ni] = arg[arg_ni]*10 + (fc - '0'); } } action CSI_semi { if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty arg_cnt++; arg_ni++; } action CSI_intermed { interchar = fc; } action CSI_end { apars_handle_csi(leadchar, arg, arg_cnt, interchar, fc); fgoto main; } #(32..47|60..64) CSI_body := ([?>=] @CSI_leading)? ((digit @CSI_digit)* ';' @CSI_semi)* (digit @CSI_digit)* ([ $*"+,)'&!\-] @CSI_intermed)? (alpha|[`@{}~|]) @CSI_end $!errBadSeq; # --- String commands --- action StrCmd_start { leadchar = fc; str_ni = 0; string_buffer[0] = '\0'; inside_string = true; fgoto STRCMD_body; } action StrCmd_char { string_buffer[str_ni++] = fc; } action StrCmd_end { inside_string = false; string_buffer[str_ni++] = '\0'; apars_handle_string_cmd(leadchar, string_buffer); fgoto main; } # According to the spec, ESC should be allowed inside the string sequence. # We disallow ESC for simplicity, as it's hardly ever used. STRCMD_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq; # --- Single character ESC --- action HASH_code { apars_handle_hash_cmd(fc); fgoto main; } action SHORT_code { apars_handle_short_cmd(fc); fgoto main; } action SPACE_cmd { apars_handle_space_cmd(fc); fgoto main; } # --- Charset selection --- action CharsetCmd_start { leadchar = fc; fgoto charsetcmd_body; } action CharsetCmd_end { apars_handle_chs_designate(leadchar, fc); fgoto main; } charsetcmd_body := (NOESC @CharsetCmd_end) $!errBadSeq; # --- Main parser loop --- main := ( (NOESC @plain_char)* ESC ( ('[' @CSI_start) | ([_\]Pk\^X] @StrCmd_start) | ('#' digit @HASH_code) | (([a-zA-Z0-9=<>~}|@\\] - [PXk]) @SHORT_code) | ([()*+-./%] @CharsetCmd_start) | (' ' [FGLMN] @SPACE_cmd) ) )+ $!errBadSeq; write exec; #*/ }%% }