mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-23 00:01:16 -03:00
Support "$(cmd)" command substitution without line splitting
This adds a hack to the parser. Given a command echo "x$()y z" we virtually insert double quotes before and after the command substitution, so the command internally looks like echo "x"$()"y z" This hack allows to reuse the existing logic for handling (recursive) command substitutions. This makes the quoting syntax more complex; external highlighters should consider adding this if possible. The upside (more Bash compatibility) seems worth it. Closes #159
This commit is contained in:
@@ -145,10 +145,24 @@ tok_t tokenizer_t::read_string() {
|
||||
std::vector<int> paran_offsets;
|
||||
std::vector<int> brace_offsets;
|
||||
std::vector<char> expecting;
|
||||
std::vector<size_t> quoted_cmdsubs;
|
||||
int slice_offset = 0;
|
||||
const wchar_t *const buff_start = this->token_cursor;
|
||||
bool is_first = true;
|
||||
|
||||
auto process_opening_quote = [&](wchar_t quote) -> const wchar_t * {
|
||||
const wchar_t *end = quote_end(this->token_cursor, quote);
|
||||
if (end) {
|
||||
if (*end == L'$') quoted_cmdsubs.push_back(paran_offsets.size());
|
||||
this->token_cursor = end;
|
||||
return nullptr;
|
||||
} else {
|
||||
const wchar_t *error_loc = this->token_cursor;
|
||||
this->token_cursor += std::wcslen(this->token_cursor);
|
||||
return error_loc;
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
wchar_t c = *this->token_cursor;
|
||||
#if false
|
||||
@@ -195,6 +209,19 @@ tok_t tokenizer_t::read_string() {
|
||||
mode &= ~(tok_modes::subshell);
|
||||
}
|
||||
expecting.pop_back();
|
||||
// Check if the ) did complete a quoted command substituion.
|
||||
if (!quoted_cmdsubs.empty() && quoted_cmdsubs.back() == paran_offsets.size()) {
|
||||
quoted_cmdsubs.pop_back();
|
||||
// Quoted command substitutions temporarily close double quotes, after ),
|
||||
// we need to act as if there was an invisible double quote.
|
||||
if (const wchar_t *error_loc = process_opening_quote(L'"')) {
|
||||
if (!this->accept_unfinished) {
|
||||
return this->call_error(tokenizer_error_t::unterminated_quote, buff_start,
|
||||
error_loc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (c == L'}') {
|
||||
if (!expecting.empty() && expecting.back() == L')') {
|
||||
return this->call_error(tokenizer_error_t::expected_pclose_found_bclose,
|
||||
@@ -225,13 +252,8 @@ tok_t tokenizer_t::read_string() {
|
||||
else if (c == L']' && ((mode & tok_modes::array_brackets) == tok_modes::array_brackets)) {
|
||||
mode &= ~(tok_modes::array_brackets);
|
||||
} else if (c == L'\'' || c == L'"') {
|
||||
const wchar_t *end = quote_end(this->token_cursor);
|
||||
if (end) {
|
||||
this->token_cursor = end;
|
||||
} else {
|
||||
const wchar_t *error_loc = this->token_cursor;
|
||||
this->token_cursor += std::wcslen(this->token_cursor);
|
||||
if ((!this->accept_unfinished)) {
|
||||
if (const wchar_t *error_loc = process_opening_quote(c)) {
|
||||
if (!this->accept_unfinished) {
|
||||
return this->call_error(tokenizer_error_t::unterminated_quote, buff_start,
|
||||
error_loc);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user