/** Highlight operators (such as $, ~, %, as well as escaped characters. */ static void highlight_param( const wcstring &buffstr, std::vector<int> &colors, int pos, wcstring_list_t *error ) { const wchar_t * const buff = buffstr.c_str(); enum {e_unquoted, e_single_quoted, e_double_quoted} mode = e_unquoted; size_t in_pos, len = buffstr.size(); int bracket_count=0; int normal_status = colors.at(0); for (in_pos=0; in_pos<len; in_pos++) { wchar_t c = buffstr.at(in_pos); switch( mode ) { /* Mode 0 means unquoted string */ case e_unquoted: { if( c == L'\\' ) { size_t start_pos = in_pos; in_pos++; if( wcschr( L"~%", buff[in_pos] ) ) { if( in_pos == 1 ) { colors.at(start_pos) = HIGHLIGHT_ESCAPE; colors.at(in_pos+1) = normal_status; } } else if( buff[in_pos]==L',' ) { if( bracket_count ) { colors.at(start_pos) = HIGHLIGHT_ESCAPE; colors.at(in_pos+1) = normal_status; } } else if( wcschr( L"abefnrtv*?$(){}[]'\"<>^ \\#;|&", buff[in_pos] ) ) { colors.at(start_pos)=HIGHLIGHT_ESCAPE; colors.at(in_pos+1)=normal_status; } else if( wcschr( L"c", buff[in_pos] ) ) { colors.at(start_pos)=HIGHLIGHT_ESCAPE; if (in_pos+2 < colors.size()) colors.at(in_pos+2)=normal_status; } else if( wcschr( L"uUxX01234567", buff[in_pos] ) ) { int i; long long res=0; int chars=2; int base=16; wchar_t max_val = ASCII_MAX; switch( buff[in_pos] ) { case L'u': { chars=4; max_val = UCS2_MAX; break; } case L'U': { chars=8; max_val = WCHAR_MAX; break; } case L'x': { break; } case L'X': { max_val = BYTE_MAX; break; } default: { base=8; chars=3; in_pos--; break; } } for( i=0; i<chars; i++ ) { int d = convert_digit( buff[++in_pos],base); if( d < 0 ) { in_pos--; break; } res=(res*base)|d; } if( (res <= max_val) ) { colors.at(start_pos) = HIGHLIGHT_ESCAPE; colors.at(in_pos+1) = normal_status; } else { colors.at(start_pos) = HIGHLIGHT_ERROR; colors.at(in_pos+1) = normal_status; } } } else { switch( buff[in_pos]){ case L'~': case L'%': { if( in_pos == 0 ) { colors.at(in_pos) = HIGHLIGHT_OPERATOR; colors.at(in_pos+1) = normal_status; } break; } case L'$': { wchar_t n = buff[in_pos+1]; colors.at(in_pos) = (n==L'$'||wcsvarchr(n))? HIGHLIGHT_OPERATOR:HIGHLIGHT_ERROR; colors.at(in_pos+1) = normal_status; break; } case L'*': case L'?': case L'(': case L')': { colors.at(in_pos) = HIGHLIGHT_OPERATOR; colors.at(in_pos+1) = normal_status; break; } case L'{': { colors.at(in_pos) = HIGHLIGHT_OPERATOR; colors.at(in_pos+1) = normal_status; bracket_count++; break; } case L'}': { colors.at(in_pos) = HIGHLIGHT_OPERATOR; colors.at(in_pos+1) = normal_status; bracket_count--; break; } case L',': { if( bracket_count ) { colors.at(in_pos) = HIGHLIGHT_OPERATOR; colors.at(in_pos+1) = normal_status; } break; } case L'\'': { colors.at(in_pos) = HIGHLIGHT_QUOTE; mode = e_single_quoted; break; } case L'\"': { colors.at(in_pos) = HIGHLIGHT_QUOTE; mode = e_double_quoted; break; } } } break; } /* Mode 1 means single quoted string, i.e 'foo' */ case e_single_quoted: { if( c == L'\\' ) { int start_pos = in_pos; switch( buff[++in_pos] ) { case '\\': case L'\'': { colors.at(start_pos) = HIGHLIGHT_ESCAPE; colors.at(in_pos+1) = HIGHLIGHT_QUOTE; break; } case 0: { return; } } } if( c == L'\'' ) { mode = e_unquoted; colors.at(in_pos+1) = normal_status; } break; } /* Mode 2 means double quoted string, i.e. "foo" */ case e_double_quoted: { switch( c ) { case '"': { mode = e_unquoted; colors.at(in_pos+1) = normal_status; break; } case '\\': { int start_pos = in_pos; switch( buff[++in_pos] ) { case L'\0': { return; } case '\\': case L'$': case '"': { colors.at(start_pos) = HIGHLIGHT_ESCAPE; colors.at(in_pos+1) = HIGHLIGHT_QUOTE; break; } } break; } case '$': { wchar_t n = buff[in_pos+1]; colors.at(in_pos) = (n==L'$'||wcsvarchr(n))? HIGHLIGHT_OPERATOR:HIGHLIGHT_ERROR; colors.at(in_pos+1) = HIGHLIGHT_QUOTE; break; } } break; } } } }
wchar_t *unescape( const wchar_t * orig, int flags ) { int mode = 0; int in_pos, out_pos, len; int c; int bracket_count=0; wchar_t prev=0; wchar_t *in; int unescape_special = flags & UNESCAPE_SPECIAL; int allow_incomplete = flags & UNESCAPE_INCOMPLETE; CHECK( orig, 0 ); len = wcslen( orig ); in = wcsdup( orig ); if( !in ) DIE_MEM(); for( in_pos=0, out_pos=0; in_pos<len; (prev=(out_pos>=0)?in[out_pos]:0), out_pos++, in_pos++ ) { c = in[in_pos]; switch( mode ) { /* Mode 0 means unquoted string */ case 0: { if( c == L'\\' ) { switch( in[++in_pos] ) { /* A null character after a backslash is an error, return null */ case L'\0': { if( !allow_incomplete ) { free(in); return 0; } } /* Numeric escape sequences. No prefix means octal escape, otherwise hexadecimal. */ case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'u': case L'U': case L'x': case L'X': { int i; long long res=0; int chars=2; int base=16; int byte = 0; wchar_t max_val = ASCII_MAX; switch( in[in_pos] ) { case L'u': { chars=4; max_val = UCS2_MAX; break; } case L'U': { chars=8; max_val = WCHAR_MAX; break; } case L'x': { break; } case L'X': { byte=1; max_val = BYTE_MAX; break; } default: { base=8; chars=3; in_pos--; break; } } for( i=0; i<chars; i++ ) { int d = convert_digit( in[++in_pos],base); if( d < 0 ) { in_pos--; break; } res=(res*base)|d; } if( (res <= max_val) ) { in[out_pos] = (byte?ENCODE_DIRECT_BASE:0)+res; } else { free(in); return 0; } break; } /* \a means bell (alert) */ case L'a': { in[out_pos]=L'\a'; break; } /* \b means backspace */ case L'b': { in[out_pos]=L'\b'; break; } /* \cX means control sequence X */ case L'c': { in_pos++; if( in[in_pos] >= L'a' && in[in_pos] <= (L'a'+32) ) { in[out_pos]=in[in_pos]-L'a'+1; } else if( in[in_pos] >= L'A' && in[in_pos] <= (L'A'+32) ) { in[out_pos]=in[in_pos]-L'A'+1; } else { free(in); return 0; } break; } /* \x1b means escape */ case L'e': { in[out_pos]=L'\x1b'; break; } /* \f means form feed */ case L'f': { in[out_pos]=L'\f'; break; } /* \n means newline */ case L'n': { in[out_pos]=L'\n'; break; } /* \r means carriage return */ case L'r': { in[out_pos]=L'\r'; break; } /* \t means tab */ case L't': { in[out_pos]=L'\t'; break; } /* \v means vertical tab */ case L'v': { in[out_pos]=L'\v'; break; } default: { if( unescape_special ) in[out_pos++] = INTERNAL_SEPARATOR; in[out_pos]=in[in_pos]; break; } } } else { switch( in[in_pos]) { case L'~': { if( unescape_special && (in_pos == 0) ) { in[out_pos]=HOME_DIRECTORY; } else { in[out_pos] = L'~'; } break; } case L'%': { if( unescape_special && (in_pos == 0) ) { in[out_pos]=PROCESS_EXPAND; } else { in[out_pos]=in[in_pos]; } break; } case L'*': { if( unescape_special ) { if( out_pos > 0 && in[out_pos-1]==ANY_STRING ) { out_pos--; in[out_pos] = ANY_STRING_RECURSIVE; } else in[out_pos]=ANY_STRING; } else { in[out_pos]=in[in_pos]; } break; } case L'?': { if( unescape_special ) { in[out_pos]=ANY_CHAR; } else { in[out_pos]=in[in_pos]; } break; } case L'$': { if( unescape_special ) { in[out_pos]=VARIABLE_EXPAND; } else { in[out_pos]=in[in_pos]; } break; } case L'{': { if( unescape_special ) { bracket_count++; in[out_pos]=BRACKET_BEGIN; } else { in[out_pos]=in[in_pos]; } break; } case L'}': { if( unescape_special ) { bracket_count--; in[out_pos]=BRACKET_END; } else { in[out_pos]=in[in_pos]; } break; } case L',': { if( unescape_special && bracket_count && prev!=BRACKET_SEP) { in[out_pos]=BRACKET_SEP; } else { in[out_pos]=in[in_pos]; } break; } case L'\'': { mode = 1; if( unescape_special ) in[out_pos] = INTERNAL_SEPARATOR; else out_pos--; break; } case L'\"': { mode = 2; if( unescape_special ) in[out_pos] = INTERNAL_SEPARATOR; else out_pos--; break; } default: { in[out_pos] = in[in_pos]; break; } } } break; } /* Mode 1 means single quoted string, i.e 'foo' */ case 1: { if( c == L'\\' ) { switch( in[++in_pos] ) { case '\\': case L'\'': case L'\n': { in[out_pos]=in[in_pos]; break; } case 0: { if( !allow_incomplete ) { free(in); return 0; } else { //We may ever escape a NULL character, but still appending a \ in case I am wrong. in[out_pos] = L'\\'; } } break; default: { in[out_pos++] = L'\\'; in[out_pos]= in[in_pos]; } } } if( c == L'\'' ) { if( unescape_special ) in[out_pos] = INTERNAL_SEPARATOR; else out_pos--; mode = 0; } else { in[out_pos] = in[in_pos]; } break; } /* Mode 2 means double quoted string, i.e. "foo" */ case 2: { switch( c ) { case '"': { mode = 0; if( unescape_special ) in[out_pos] = INTERNAL_SEPARATOR; else out_pos--; break; } case '\\': { switch( in[++in_pos] ) { case L'\0': { if( !allow_incomplete ) { free(in); return 0; } else { //We probably don't need it since NULL character is always appended before ending this function. in[out_pos]=in[in_pos]; } } break; case '\\': case L'$': case '"': case '\n': { in[out_pos]=in[in_pos]; break; } default: { in[out_pos++] = L'\\'; in[out_pos] = in[in_pos]; break; } } break; } case '$': { if( unescape_special ) { in[out_pos]=VARIABLE_EXPAND_SINGLE; } else { in[out_pos]=in[in_pos]; } break; } default: { in[out_pos] = in[in_pos]; break; } } break; } } } if( !allow_incomplete && mode ) { free( in ); return 0; } in[out_pos]=L'\0'; return in; }