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:
Johannes Altmanninger
2021-07-02 23:11:03 +02:00
parent 4437a0d02a
commit ec3d3a481b
9 changed files with 201 additions and 28 deletions

View File

@@ -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);
}