Files
bun.sh/src/shell/states/Expansion.zig
taylor.fish 41b1efe12c Rename disabled parameter in Output.scoped (#21769)
It's very confusing.

(For internal tracking: fixes STAB-977)
2025-08-11 20:19:34 -07:00

889 lines
30 KiB
Zig

//! This state node is used for expansions.
//!
//! If a word contains command substitution or glob expansion syntax then it
//! needs to do IO, so we have to keep track of the state for that.
//!
//! TODO PERF: in the case of expanding cmd args, we probably want to use the spawn args arena
//! otherwise the interpreter allocator
pub const Expansion = @This();
base: State,
node: *const ast.Atom,
parent: ParentPtr,
io: IO,
word_idx: u32,
current_out: std.ArrayList(u8),
state: union(enum) {
normal,
braces,
glob,
done,
err: bun.shell.ShellErr,
},
child_state: union(enum) {
idle,
cmd_subst: struct {
cmd: *Script,
quoted: bool = false,
},
// TODO
glob: struct {
initialized: bool = false,
walker: GlobWalker,
},
},
out_exit_code: ExitCode = 0,
out: Result,
out_idx: u32,
pub const ParentPtr = StatePtrUnion(.{
Cmd,
Assigns,
CondExpr,
Subshell,
});
pub const ChildPtr = StatePtrUnion(.{
// Cmd,
Script,
});
pub const Result = union(enum) {
array_of_slice: *std.ArrayList([:0]const u8),
array_of_ptr: *std.ArrayList(?[*:0]const u8),
single: struct {
list: *std.ArrayList(u8),
done: bool = false,
},
const PushAction = enum {
/// We just copied the buf into the result, caller can just do
/// `.clearRetainingCapacity()`
copied,
/// We took ownershipo of the result and placed the pointer in the buf,
/// caller should remove any references to the underlying data.
moved,
};
pub fn pushResultSliceOwned(this: *Result, buf: [:0]const u8) PushAction {
if (comptime bun.Environment.allow_assert) {
assert(buf[buf.len] == 0);
}
switch (this.*) {
.array_of_slice => {
this.array_of_slice.append(buf) catch bun.outOfMemory();
return .moved;
},
.array_of_ptr => {
this.array_of_ptr.append(@as([*:0]const u8, @ptrCast(buf.ptr))) catch bun.outOfMemory();
return .moved;
},
.single => {
if (this.single.done) return .copied;
this.single.list.appendSlice(buf[0 .. buf.len + 1]) catch bun.outOfMemory();
this.single.done = true;
return .copied;
},
}
}
pub fn pushResult(this: *Result, buf: *std.ArrayList(u8)) PushAction {
if (comptime bun.Environment.allow_assert) {
assert(buf.items[buf.items.len - 1] == 0);
}
switch (this.*) {
.array_of_slice => {
this.array_of_slice.append(buf.items[0 .. buf.items.len - 1 :0]) catch bun.outOfMemory();
return .moved;
},
.array_of_ptr => {
this.array_of_ptr.append(@as([*:0]const u8, @ptrCast(buf.items.ptr))) catch bun.outOfMemory();
return .moved;
},
.single => {
if (this.single.done) return .copied;
this.single.list.appendSlice(buf.items[0..]) catch bun.outOfMemory();
return .copied;
},
}
}
};
pub fn format(this: *const Expansion, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("Expansion(0x{x})", .{@intFromPtr(this)});
}
pub fn allocator(this: *Expansion) std.mem.Allocator {
return this.base.allocator();
}
pub fn init(
interpreter: *Interpreter,
shell_state: *ShellExecEnv,
expansion: *Expansion,
node: *const ast.Atom,
parent: ParentPtr,
out_result: Result,
io: IO,
) void {
log("Expansion(0x{x}) init", .{@intFromPtr(expansion)});
expansion.* = .{
.node = node,
.base = State.initBorrowedAllocScope(.expansion, interpreter, shell_state, parent.scopedAllocator()),
.parent = parent,
.word_idx = 0,
.state = .normal,
.child_state = .idle,
.out = out_result,
.out_idx = 0,
.current_out = undefined,
.io = io,
};
expansion.current_out = std.ArrayList(u8).init(expansion.base.allocator());
}
pub fn deinit(expansion: *Expansion) void {
log("Expansion(0x{x}) deinit", .{@intFromPtr(expansion)});
expansion.current_out.deinit();
expansion.io.deinit();
expansion.base.endScope();
}
pub fn start(this: *Expansion) Yield {
if (comptime bun.Environment.allow_assert) {
assert(this.child_state == .idle);
assert(this.word_idx == 0);
}
this.state = .normal;
return .{ .expansion = this };
}
pub fn next(this: *Expansion) Yield {
while (!(this.state == .done or this.state == .err)) {
switch (this.state) {
.normal => {
// initialize
if (this.word_idx == 0) {
var has_unknown = false;
// + 1 for sentinel
const string_size = this.expansionSizeHint(this.node, &has_unknown);
this.current_out.ensureUnusedCapacity(string_size + 1) catch bun.outOfMemory();
}
while (this.word_idx < this.node.atomsLen()) {
if (this.expandVarAndCmdSubst(this.word_idx)) |yield| return yield;
}
if (this.word_idx >= this.node.atomsLen()) {
if (this.node.hasTildeExpansion() and this.node.atomsLen() > 1) {
const homedir = this.base.shell.getHomedir();
defer homedir.deref();
if (this.current_out.items.len > 0) {
switch (this.current_out.items[0]) {
'/', '\\' => {
this.current_out.insertSlice(0, homedir.slice()) catch bun.outOfMemory();
},
else => {
// TODO: Handle username
this.current_out.insert(0, '~') catch bun.outOfMemory();
},
}
}
}
// NOTE brace expansion + cmd subst has weird behaviour we don't support yet, ex:
// echo $(echo a b c){1,2,3}
// >> a b c1 a b c2 a b c3
if (this.node.has_brace_expansion()) {
this.state = .braces;
continue;
}
if (this.node.has_glob_expansion()) {
this.state = .glob;
continue;
}
this.pushCurrentOut();
this.state = .done;
continue;
}
// Shouldn't fall through to here
assert(this.word_idx >= this.node.atomsLen());
return .suspended;
},
.braces => {
var arena = Arena.init(this.base.allocator());
defer arena.deinit();
const arena_allocator = arena.allocator();
const brace_str = this.current_out.items[0..];
var lexer_output = if (bun.strings.isAllASCII(brace_str)) lexer_output: {
@branchHint(.likely);
break :lexer_output Braces.Lexer.tokenize(arena_allocator, brace_str) catch bun.outOfMemory();
} else lexer_output: {
break :lexer_output Braces.NewLexer(.wtf8).tokenize(arena_allocator, brace_str) catch bun.outOfMemory();
};
const expansion_count = Braces.calculateExpandedAmount(lexer_output.tokens.items[0..]);
const stack_max = comptime 16;
comptime {
assert(@sizeOf([]std.ArrayList(u8)) * stack_max <= 256);
}
var maybe_stack_alloc = std.heap.stackFallback(@sizeOf([]std.ArrayList(u8)) * stack_max, arena_allocator);
const stack_alloc = maybe_stack_alloc.get();
const expanded_strings = stack_alloc.alloc(std.ArrayList(u8), expansion_count) catch bun.outOfMemory();
for (0..expansion_count) |i| {
expanded_strings[i] = std.ArrayList(u8).init(this.base.allocator());
}
Braces.expand(
arena_allocator,
lexer_output.tokens.items[0..],
expanded_strings,
lexer_output.contains_nested,
) catch bun.outOfMemory();
this.outEnsureUnusedCapacity(expansion_count);
// Add sentinel values
for (0..expansion_count) |i| {
expanded_strings[i].append(0) catch bun.outOfMemory();
switch (this.out.pushResult(&expanded_strings[i])) {
.copied => {
expanded_strings[i].deinit();
},
.moved => {
expanded_strings[i].clearRetainingCapacity();
},
}
}
if (this.node.has_glob_expansion()) {
this.state = .glob;
} else {
this.state = .done;
}
},
.glob => {
return this.transitionToGlobState();
},
.done, .err => unreachable,
}
}
if (this.state == .done) {
return this.parent.childDone(this, 0);
}
// Parent will inspect the `this.state.err`
if (this.state == .err) {
return this.parent.childDone(this, 1);
}
unreachable;
}
fn transitionToGlobState(this: *Expansion) Yield {
var arena = Arena.init(this.base.allocator());
this.child_state = .{ .glob = .{ .walker = .{} } };
const pattern = this.current_out.items[0..];
const cwd = this.base.shell.cwd();
switch (GlobWalker.initWithCwd(
&this.child_state.glob.walker,
&arena,
pattern,
cwd,
false,
false,
false,
false,
false,
) catch bun.outOfMemory()) {
.result => {},
.err => |e| {
this.state = .{ .err = bun.shell.ShellErr.newSys(e) };
return .{ .expansion = this };
},
}
var task = ShellGlobTask.createOnMainThread(&this.child_state.glob.walker, this);
task.schedule();
return .suspended;
}
pub fn expandVarAndCmdSubst(this: *Expansion, start_word_idx: u32) ?Yield {
switch (this.node.*) {
.simple => |*simp| {
const is_cmd_subst = this.expandSimpleNoIO(simp, &this.current_out, true);
if (is_cmd_subst) {
const io: IO = .{
.stdin = this.base.rootIO().stdin.ref(),
.stdout = .pipe,
.stderr = this.base.rootIO().stderr.ref(),
};
const shell_state = switch (this.base.shell.dupeForSubshell(this.base.allocScope(), this.base.allocator(), io, .cmd_subst)) {
.result => |s| s,
.err => |e| {
this.base.throw(&bun.shell.ShellErr.newSys(e));
return .failed;
},
};
var script = Script.init(this.base.interpreter, shell_state, &this.node.simple.cmd_subst.script, Script.ParentPtr.init(this), io);
this.child_state = .{
.cmd_subst = .{
.cmd = script,
.quoted = simp.cmd_subst.quoted,
},
};
return script.start();
} else {
this.word_idx += 1;
}
},
.compound => |cmp| {
const starting_offset: usize = if (this.node.hasTildeExpansion()) brk: {
this.word_idx += 1;
break :brk 1;
} else 0;
for (cmp.atoms[start_word_idx + starting_offset ..]) |*simple_atom| {
const is_cmd_subst = this.expandSimpleNoIO(simple_atom, &this.current_out, true);
if (is_cmd_subst) {
const io: IO = .{
.stdin = this.base.rootIO().stdin.ref(),
.stdout = .pipe,
.stderr = this.base.rootIO().stderr.ref(),
};
const shell_state = switch (this.base.shell.dupeForSubshell(this.base.allocScope(), this.base.allocator(), io, .cmd_subst)) {
.result => |s| s,
.err => |e| {
this.base.throw(&bun.shell.ShellErr.newSys(e));
return .failed;
},
};
var script = Script.init(this.base.interpreter, shell_state, &simple_atom.cmd_subst.script, Script.ParentPtr.init(this), io);
this.child_state = .{
.cmd_subst = .{
.cmd = script,
.quoted = simple_atom.cmd_subst.quoted,
},
};
return script.start();
} else {
this.word_idx += 1;
this.child_state = .idle;
}
}
},
}
return null;
}
/// Remove a set of values from the beginning and end of a slice.
pub fn trim(slice: []u8, values_to_strip: []const u8) []u8 {
var begin: usize = 0;
var end: usize = slice.len;
while (begin < end and std.mem.indexOfScalar(u8, values_to_strip, slice[begin]) != null) : (begin += 1) {}
while (end > begin and std.mem.indexOfScalar(u8, values_to_strip, slice[end - 1]) != null) : (end -= 1) {}
return slice[begin..end];
}
/// 1. Turn all newlines into spaces
/// 2. Strip last newline if it exists
/// 3. Trim leading, trailing, and consecutive whitespace
fn postSubshellExpansion(this: *Expansion, stdout_: []u8) void {
// 1. and 2.
var stdout = convertNewlinesToSpaces(stdout_);
// Trim leading & trailing whitespace
stdout = trim(stdout, " \n \r\t");
if (stdout.len == 0) return;
// Trim consecutive
var prev_whitespace: bool = false;
var a: usize = 0;
var b: usize = 1;
for (stdout[0..], 0..) |c, i| {
if (prev_whitespace) {
if (c != ' ') {
a = i;
b = i + 1;
prev_whitespace = false;
}
continue;
}
b = i + 1;
if (c == ' ') {
b = i;
prev_whitespace = true;
this.current_out.appendSlice(stdout[a..b]) catch bun.outOfMemory();
this.pushCurrentOut();
}
}
// "aa bbb"
this.current_out.appendSlice(stdout[a..b]) catch bun.outOfMemory();
}
fn convertNewlinesToSpaces(stdout_: []u8) []u8 {
var stdout = brk: {
if (stdout_.len == 0) return stdout_;
if (stdout_[stdout_.len -| 1] == '\n') break :brk stdout_[0..stdout_.len -| 1];
break :brk stdout_[0..];
};
if (stdout.len == 0) {
return stdout;
}
// From benchmarks the SIMD stuff only is faster when chars >= 64
if (stdout.len < 64) {
convertNewlinesToSpacesSlow(0, stdout);
return stdout[0..];
}
const needles: @Vector(16, u8) = @splat('\n');
const spaces: @Vector(16, u8) = @splat(' ');
var i: usize = 0;
while (i + 16 <= stdout.len) : (i += 16) {
const haystack: @Vector(16, u8) = stdout[i..][0..16].*;
stdout[i..][0..16].* = @select(u8, haystack == needles, spaces, haystack);
}
if (i < stdout.len) convertNewlinesToSpacesSlow(i, stdout);
return stdout[0..];
}
fn convertNewlinesToSpacesSlow(i: usize, stdout: []u8) void {
for (stdout[i..], i..) |c, j| {
if (c == '\n') {
stdout[j] = ' ';
}
}
}
pub fn childDone(this: *Expansion, child: ChildPtr, exit_code: ExitCode) Yield {
if (comptime bun.Environment.allow_assert) {
assert(this.state != .done and this.state != .err);
assert(this.child_state != .idle);
}
// Command substitution
if (child.ptr.is(Script)) {
if (comptime bun.Environment.allow_assert) {
assert(this.child_state == .cmd_subst);
}
// This branch is true means that we expanded
// a single command substitution and it failed.
//
// This information is propagated to `Cmd` because in the case
// that the command substitution would be expanded to the
// command name (e.g. `$(lkdfjsldf)`), and it fails, the entire
// command should fail with the exit code of the command
// substitution.
if (exit_code != 0 and
this.node.* == .simple and
this.node.simple == .cmd_subst)
{
this.out_exit_code = exit_code;
}
const stdout = this.child_state.cmd_subst.cmd.base.shell.buffered_stdout().slice();
if (!this.child_state.cmd_subst.quoted) {
this.postSubshellExpansion(stdout);
} else {
const trimmed = std.mem.trimRight(u8, stdout, " \n\t\r");
this.current_out.appendSlice(trimmed) catch bun.outOfMemory();
}
this.word_idx += 1;
this.child_state = .idle;
child.deinit();
return .{ .expansion = this };
}
@panic("Invalid child to Expansion, this indicates a bug in Bun. Please file a report on Github.");
}
fn onGlobWalkDone(this: *Expansion, task: *ShellGlobTask) Yield {
log("{} onGlobWalkDone", .{this});
if (comptime bun.Environment.allow_assert) {
assert(this.child_state == .glob);
}
if (task.err) |*err| {
switch (err.*) {
.syscall => {
this.base.throw(&bun.shell.ShellErr.newSys(task.err.?.syscall));
},
.unknown => |errtag| {
this.base.throw(&.{
.custom = this.base.allocator().dupe(u8, @errorName(errtag)) catch bun.outOfMemory(),
});
},
}
}
if (task.result.items.len == 0) {
// In variable assignments, a glob that fails to match should not produce an error, but instead expand to just the pattern
if (this.parent.ptr.is(Assigns) or (this.parent.ptr.is(Cmd) and this.parent.ptr.as(Cmd).state == .expanding_assigns)) {
this.pushCurrentOut();
this.child_state.glob.walker.deinit(true);
this.child_state = .idle;
this.state = .done;
return .{ .expansion = this };
}
const msg = std.fmt.allocPrint(this.base.allocator(), "no matches found: {s}", .{this.child_state.glob.walker.pattern}) catch bun.outOfMemory();
this.state = .{
.err = bun.shell.ShellErr{
.custom = msg,
},
};
this.child_state.glob.walker.deinit(true);
this.child_state = .idle;
return .{ .expansion = this };
}
for (task.result.items) |sentinel_str| {
// The string is allocated in the glob walker arena and will be freed, so needs to be duped here
const duped = this.base.allocator().dupeZ(u8, sentinel_str[0..sentinel_str.len]) catch bun.outOfMemory();
switch (this.out.pushResultSliceOwned(duped)) {
.copied => {
this.base.allocator().free(duped);
},
.moved => {},
}
}
this.word_idx += 1;
this.child_state.glob.walker.deinit(true);
this.child_state = .idle;
this.state = .done;
return .{ .expansion = this };
}
/// If the atom is actually a command substitution then does nothing and returns true
pub fn expandSimpleNoIO(this: *Expansion, atom: *const ast.SimpleAtom, str_list: *std.ArrayList(u8), comptime expand_tilde: bool) bool {
switch (atom.*) {
.Text => |txt| {
str_list.appendSlice(txt) catch bun.outOfMemory();
},
.Var => |label| {
str_list.appendSlice(this.expandVar(label)) catch bun.outOfMemory();
},
.VarArgv => |int| {
str_list.appendSlice(this.expandVarArgv(int)) catch bun.outOfMemory();
},
.asterisk => {
str_list.append('*') catch bun.outOfMemory();
},
.double_asterisk => {
str_list.appendSlice("**") catch bun.outOfMemory();
},
.brace_begin => {
str_list.append('{') catch bun.outOfMemory();
},
.brace_end => {
str_list.append('}') catch bun.outOfMemory();
},
.comma => {
str_list.append(',') catch bun.outOfMemory();
},
.tilde => {
if (expand_tilde) {
const homedir = this.base.shell.getHomedir();
defer homedir.deref();
str_list.appendSlice(homedir.slice()) catch bun.outOfMemory();
} else str_list.append('~') catch bun.outOfMemory();
},
.cmd_subst => {
// TODO:
// if the command substution is comprised of solely shell variable assignments then it should do nothing
// if (atom.cmd_subst.* == .assigns) return false;
return true;
},
}
return false;
}
pub fn appendSlice(this: *Expansion, buf: *std.ArrayList(u8), slice: []const u8) void {
_ = this;
buf.appendSlice(slice) catch bun.outOfMemory();
}
pub fn pushCurrentOut(this: *Expansion) void {
if (this.current_out.items.len == 0) return;
if (this.current_out.items[this.current_out.items.len - 1] != 0) this.current_out.append(0) catch bun.outOfMemory();
switch (this.out.pushResult(&this.current_out)) {
.copied => {
this.current_out.clearRetainingCapacity();
},
.moved => {
this.current_out = std.ArrayList(u8).init(this.base.allocator());
},
}
}
fn expandVar(this: *const Expansion, label: []const u8) []const u8 {
const value = this.base.shell.shell_env.get(EnvStr.initSlice(label)) orelse brk: {
break :brk this.base.shell.export_env.get(EnvStr.initSlice(label)) orelse return "";
};
defer value.deref();
return value.slice();
}
fn expandVarArgv(this: *const Expansion, original_int: u8) []const u8 {
var int = original_int;
switch (this.base.interpreter.event_loop) {
.js => |event_loop| {
if (int == 0) return bun.selfExePath() catch "";
int -= 1;
const vm = event_loop.virtual_machine;
if (vm.main.len > 0) {
if (int == 0) return vm.main;
int -= 1;
}
if (vm.worker) |worker| {
if (int >= worker.argv.len) return "";
return this.base.interpreter.getVmArgsUtf8(worker.argv, int);
}
const argv = vm.argv;
if (int >= argv.len) return "";
return argv[int];
},
.mini => {
const ctx = this.base.interpreter.command_ctx;
if (int >= 1 + ctx.passthrough.len) return "";
if (int == 0) return ctx.positionals[ctx.positionals.len - 1 - int];
return ctx.passthrough[int - 1];
},
}
}
fn currentWord(this: *Expansion) *const ast.SimpleAtom {
return switch (this.node) {
.simple => &this.node.simple,
.compound => &this.node.compound.atoms[this.word_idx],
};
}
/// Returns the size of the atom when expanded.
/// If the calculation cannot be computed trivially (cmd substitution, brace expansion), this value is not accurate and `has_unknown` is set to true
fn expansionSizeHint(this: *const Expansion, atom: *const ast.Atom, has_unknown: *bool) usize {
return switch (@as(ast.Atom.Tag, atom.*)) {
.simple => this.expansionSizeHintSimple(&atom.simple, has_unknown),
.compound => {
if (atom.compound.brace_expansion_hint) {
has_unknown.* = true;
}
var out: usize = 0;
for (atom.compound.atoms) |*simple| {
out += this.expansionSizeHintSimple(simple, has_unknown);
}
return out;
},
};
}
fn expansionSizeHintSimple(this: *const Expansion, simple: *const ast.SimpleAtom, has_unknown: *bool) usize {
return switch (simple.*) {
.Text => |txt| txt.len,
.Var => |label| this.expandVar(label).len,
.VarArgv => |int| this.expandVarArgv(int).len,
.brace_begin, .brace_end, .comma, .asterisk => 1,
.double_asterisk => 2,
.cmd_subst => |subst| {
_ = subst; // autofix
// TODO check if the command substitution is comprised entirely of assignments or zero-sized things
// if (@as(ast.CmdOrAssigns.Tag, subst.*) == .assigns) {
// return 0;
// }
has_unknown.* = true;
return 0;
},
.tilde => {
has_unknown.* = true;
return 0;
},
};
}
fn outEnsureUnusedCapacity(this: *Expansion, additional: usize) void {
switch (this.out) {
.array_of_ptr => {
this.out.array_of_ptr.ensureUnusedCapacity(additional) catch bun.outOfMemory();
},
.array_of_slice => {
this.out.array_of_slice.ensureUnusedCapacity(additional) catch bun.outOfMemory();
},
.single => {},
}
}
pub const ShellGlobTask = struct {
const debug = bun.Output.scoped(.ShellGlobTask, .hidden);
task: WorkPoolTask = .{ .callback = &runFromThreadPool },
/// Not owned by this struct
expansion: *Expansion,
/// Not owned by this struct
walker: *GlobWalker,
result: std.ArrayList([:0]const u8),
event_loop: jsc.EventLoopHandle,
concurrent_task: jsc.EventLoopTask,
// This is a poll because we want it to enter the uSockets loop
ref: bun.Async.KeepAlive = .{},
err: ?Err = null,
alloc_scope: bun.AllocationScope,
const This = @This();
pub const Err = union(enum) {
syscall: Syscall.Error,
unknown: anyerror,
pub fn toJS(this: Err, globalThis: *JSGlobalObject) JSValue {
return switch (this) {
.syscall => |err| err.toJS(globalThis),
.unknown => |err| jsc.ZigString.fromBytes(@errorName(err)).toJS(globalThis),
};
}
};
pub fn createOnMainThread(walker: *GlobWalker, expansion: *Expansion) *This {
debug("createOnMainThread", .{});
var alloc_scope = bun.AllocationScope.init(bun.default_allocator);
var this = alloc_scope.allocator().create(This) catch bun.outOfMemory();
this.* = .{
.alloc_scope = alloc_scope,
.event_loop = expansion.base.eventLoop(),
.concurrent_task = jsc.EventLoopTask.fromEventLoop(expansion.base.eventLoop()),
.walker = walker,
.expansion = expansion,
.result = std.ArrayList([:0]const u8).init(this.alloc_scope.allocator()),
};
this.ref.ref(this.event_loop);
return this;
}
pub fn runFromThreadPool(task: *WorkPoolTask) void {
debug("runFromThreadPool", .{});
var this: *This = @fieldParentPtr("task", task);
switch (this.walkImpl()) {
.result => {},
.err => |e| {
this.err = .{ .syscall = e };
},
}
this.onFinish();
}
fn walkImpl(this: *This) Maybe(void) {
debug("walkImpl", .{});
var iter = GlobWalker.Iterator{ .walker = this.walker };
defer iter.deinit();
switch (iter.init() catch bun.outOfMemory()) {
.err => |err| return .{ .err = err },
else => {},
}
while (switch (iter.next() catch |e| OOM(e)) {
.err => |err| return .{ .err = err },
.result => |matched_path| matched_path,
}) |path| {
this.result.append(path) catch bun.outOfMemory();
}
return .success;
}
pub fn runFromMainThread(this: *This) void {
debug("runFromJS", .{});
this.expansion.onGlobWalkDone(this).run();
this.ref.unref(this.event_loop);
}
pub fn runFromMainThreadMini(this: *This, _: *void) void {
this.runFromMainThread();
}
pub fn schedule(this: *This) void {
debug("schedule", .{});
WorkPool.schedule(&this.task);
}
pub fn onFinish(this: *This) void {
debug("onFinish", .{});
if (this.event_loop == .js) {
this.event_loop.js.enqueueTaskConcurrent(this.concurrent_task.js.from(this, .manual_deinit));
} else {
this.event_loop.mini.enqueueTaskConcurrent(this.concurrent_task.mini.from(this, "runFromMainThreadMini"));
}
}
pub fn deinit(this: *This) void {
debug("deinit", .{});
this.result.deinit();
var alloc_scope = this.alloc_scope;
alloc_scope.allocator().destroy(this);
alloc_scope.deinit();
}
};
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const assert = bun.assert;
const Maybe = bun.sys.Maybe;
const jsc = bun.jsc;
const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue;
const ExitCode = bun.shell.ExitCode;
const Yield = bun.shell.Yield;
const ast = bun.shell.AST;
const Interpreter = bun.shell.Interpreter;
const Assigns = bun.shell.Interpreter.Assigns;
const Cmd = bun.shell.Interpreter.Cmd;
const CondExpr = bun.shell.Interpreter.CondExpr;
const IO = bun.shell.Interpreter.IO;
const Script = bun.shell.Interpreter.Script;
const ShellExecEnv = Interpreter.ShellExecEnv;
const State = bun.shell.Interpreter.State;
const Subshell = bun.shell.Interpreter.Subshell;
const Arena = bun.shell.interpret.Arena;
const Braces = bun.shell.interpret.Braces;
const EnvStr = bun.shell.interpret.EnvStr;
const GlobWalker = bun.shell.interpret.GlobWalker;
const OOM = bun.shell.interpret.OOM;
const StatePtrUnion = bun.shell.interpret.StatePtrUnion;
const Syscall = bun.shell.interpret.Syscall;
const WorkPool = bun.shell.interpret.WorkPool;
const WorkPoolTask = bun.shell.interpret.WorkPoolTask;
const log = bun.shell.interpret.log;