shell: handle operators and delimiters better (#11165)

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
Zack Radisic
2024-05-20 19:37:52 +02:00
committed by GitHub
parent a612d22e33
commit ff1db36aaa
2 changed files with 20 additions and 7 deletions

View File

@@ -2482,7 +2482,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
if (self.chars.state == .Single) break :escaped;
if (self.in_subshell == .backtick) {
try self.break_word(true);
try self.break_word_operator();
if (self.last_tok_tag()) |toktag| {
if (toktag != .Delimit) try self.tokens.append(.Delimit);
}
@@ -2588,7 +2588,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
comptime assertSpecialChar('|');
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
try self.break_word(true);
try self.break_word_operator();
const next = self.peek() orelse {
self.add_error("Unexpected EOF");
@@ -2610,7 +2610,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
comptime assertSpecialChar('>');
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
try self.break_word_impl(true, false, true);
try self.break_word_operator();
const redirect = self.eat_simple_redirect(.out);
try self.tokens.append(.{ .Redirect = redirect });
continue;
@@ -2619,7 +2619,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
comptime assertSpecialChar('<');
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
try self.break_word_impl(true, false, true);
try self.break_word_operator();
const redirect = self.eat_simple_redirect(.in);
try self.tokens.append(.{ .Redirect = redirect });
continue;
@@ -2628,7 +2628,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
comptime assertSpecialChar('&');
if (self.chars.state == .Single or self.chars.state == .Double) break :escaped;
try self.break_word(true);
try self.break_word_operator();
const next = self.peek() orelse {
try self.tokens.append(.Ampersand);
@@ -2751,13 +2751,18 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
return try self.break_word_impl(add_delimiter, false, false);
}
/// NOTE: this adds a delimiter
fn break_word_operator(self: *@This()) !void {
return try self.break_word_impl(true, false, true);
}
inline fn isImmediatelyEscapedQuote(self: *@This()) bool {
return (self.chars.state == .Double and
(self.chars.current != null and !self.chars.current.?.escaped and self.chars.current.?.char == '"') and
(self.chars.prev != null and !self.chars.prev.?.escaped and self.chars.prev.?.char == '"'));
}
fn break_word_impl(self: *@This(), add_delimiter: bool, in_normal_space: bool, in_redirect_operator: bool) !void {
fn break_word_impl(self: *@This(), add_delimiter: bool, in_normal_space: bool, in_operator: bool) !void {
const start: u32 = self.word_start;
const end: u32 = self.j;
if (start != end or
@@ -2773,7 +2778,7 @@ pub fn NewLexer(comptime encoding: StringEncoding) type {
if (add_delimiter) {
try self.tokens.append(.Delimit);
}
} else if ((in_normal_space or in_redirect_operator) and self.tokens.items.len > 0 and
} else if ((in_normal_space or in_operator) and self.tokens.items.len > 0 and
// whether or not to add a delimiter token
switch (self.tokens.items[self.tokens.items.len - 1]) {
.Var,

View File

@@ -379,6 +379,14 @@ describe("bunshell", () => {
expect(stdout.toString()).toEqual("LMAO\n");
});
describe("operators no spaces", async () => {
TestBuilder.command`echo LMAO|cat`.stdout("LMAO\n").runAsTest("pipeline");
TestBuilder.command`echo foo&&echo hi`.stdout("foo\nhi\n").runAsTest("&&");
TestBuilder.command`echo foo||echo hi`.stdout("foo\n").runAsTest("||");
TestBuilder.command`echo foo>hi.txt`.ensureTempDir().fileEquals("hi.txt", "foo\n").runAsTest("||");
TestBuilder.command`echo hifriends#lol`.stdout("hifriends#lol\n").runAsTest("#");
});
test("cmd subst", async () => {
const haha = "noice";
const { stdout } = await $`echo $(echo noice)`;