Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 94 additions & 93 deletions incs/executor.h
Original file line number Diff line number Diff line change
@@ -1,94 +1,95 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* executor.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: marvin <marvin@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/01/10 02:38:39 by marvin #+# #+# */
/* Updated: 2026/01/10 02:38:39 by marvin ### ########.fr */
/* */
/* ************************************************************************** */

/* Executor public API: turning an AST into side-effects.
The main entry is execute_top_level() which walks the tree returned by
the parser. Every execute_* function returns t_execution_state so the
exit code and any ctrl-c signal propagate up cleanly. */

#ifndef EXECUTOR_H
# define EXECUTOR_H

# include "public/executor_types.h"
# include "alias.h"

/* forward declarations to avoid heavy includes */
typedef struct s_shell t_shell;
typedef struct s_ast_node t_ast_node;

/* An expanded, ready-to-run simple command: pre-command assignments,
the argv array, and the resolved executable path. pooled=true means
argv.ctx comes from argv_pool (don't free it, just reset the vec). */
typedef struct executable_cmd_s
{
t_vec_env pre_assigns; /* NAME=VALUE pairs before the command */
t_vec argv; /* NULL-terminated argv word list */
char *fname; /* resolved path (heap) or NULL for builtins */
bool pooled; /* true if argv was borrowed from argv_pool */
} t_executable_cmd;

/* argv backing pool (free_utils2.c): borrow on expand, return on teardown. */
void argv_pool_acquire(t_shell *state, t_executable_cmd *cmd);
void argv_pool_release(t_shell *state, t_executable_cmd *cmd);
void free_argv_pool(t_shell *state);

/* Execution context for one node in the pipeline: which fds to hook up,
which AST node to execute, and whether this command runs in the parent
process (builtins, function calls) and is allowed to modify state. */
typedef struct executable_node_s
{
int infd; /* read end of pipe from previous stage */
int outfd; /* write end of pipe to next stage */
int next_infd; /* saved next read end (pipeline setup) */
t_ast_node *node; /* the AST node being executed */
t_vec_int redirs; /* indices into state->redirects */
bool modify_parent_ctx; /* true for builtins that modify state */
} t_executable_node;

void execute_top_level(t_shell *state);
int exec_string(t_shell *state, char *str);
int exec_file_string(t_shell *state, char *str,
const char *src);
t_execution_state execute_pipeline(t_shell *state,
t_executable_node *exe);
void set_up_redirection(t_shell *state,
t_executable_node *exe);
t_execution_state execute_simple_command(t_shell *state,
t_executable_node *exe);
void forward_exit_status(t_execution_state res);
t_execution_state execute_command(t_shell *state,
t_executable_node *exe);
t_execution_state execute_tree_node(t_shell *state,
t_executable_node *exe);
t_execution_state execute_simple_list(t_shell *state,
t_executable_node *exe);
t_string word_to_string(t_ast_node node);
t_string word_to_hrdoc_string(t_ast_node node);
void set_cmd_status(t_shell *state, t_execution_state res);
t_execution_state res_status(int status);
t_execution_state res_pid(int pid);
void fire_debug_trap(t_shell *state);
void fire_err_trap(t_shell *state, int code);
void fire_return_trap(t_shell *state, int code);
void trap_save_reset(t_shell *state, char **save);
void pseudo_traps_quiet(t_shell *state);
void trap_restore(t_shell *state, char **save);
void exe_res_set_status(t_shell *st, t_execution_state *res);
int find_cmd_path(t_shell *state, char *cmd_name,
char **path_of_exe);
int exec_external_argv(t_shell *state, t_vec *args);
int run_external_sync(t_shell *state, t_vec *args);
int expand_simple_command(t_shell *state, t_ast_node *node,
t_executable_cmd *ret, t_vec_int *redirects);
int (*builtin_func(char *name))(t_shell *state, t_vec argv);

/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* executor.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: marvin <marvin@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/01/10 02:38:39 by marvin #+# #+# */
/* Updated: 2026/01/10 02:38:39 by marvin ### ########.fr */
/* */
/* ************************************************************************** */

/* Executor public API: turning an AST into side-effects.
The main entry is execute_top_level() which walks the tree returned by
the parser. Every execute_* function returns t_execution_state so the
exit code and any ctrl-c signal propagate up cleanly. */

#ifndef EXECUTOR_H
# define EXECUTOR_H

# include "public/executor_types.h"
# include "alias.h"

/* forward declarations to avoid heavy includes */
typedef struct s_shell t_shell;
typedef struct s_ast_node t_ast_node;

/* An expanded, ready-to-run simple command: pre-command assignments,
the argv array, and the resolved executable path. pooled=true means
argv.ctx comes from argv_pool (don't free it, just reset the vec). */
typedef struct executable_cmd_s
{
t_vec_env pre_assigns; /* NAME=VALUE pairs before the command */
t_vec argv; /* NULL-terminated argv word list */
char *fname; /* resolved path (heap) or NULL for builtins */
bool pooled; /* true if argv was borrowed from argv_pool */
} t_executable_cmd;

/* argv backing pool (free_utils2.c): borrow on expand, return on teardown. */
void argv_pool_acquire(t_shell *state, t_executable_cmd *cmd);
void argv_pool_release(t_shell *state, t_executable_cmd *cmd);
void free_argv_pool(t_shell *state);

/* Execution context for one node in the pipeline: which fds to hook up,
which AST node to execute, and whether this command runs in the parent
process (builtins, function calls) and is allowed to modify state. */
typedef struct executable_node_s
{
int infd; /* read end of pipe from previous stage */
int outfd; /* write end of pipe to next stage */
int next_infd; /* saved next read end (pipeline setup) */
t_ast_node *node; /* the AST node being executed */
t_vec_int redirs; /* indices into state->redirects */
bool modify_parent_ctx; /* true for builtins that modify state */
} t_executable_node;

void execute_top_level(t_shell *state);
int exec_string(t_shell *state, char *str);
int exec_file_string(t_shell *state, char *str,
const char *src);
t_execution_state execute_pipeline(t_shell *state,
t_executable_node *exe);
void set_up_redirection(t_shell *state,
t_executable_node *exe);
t_execution_state execute_simple_command(t_shell *state,
t_executable_node *exe);
void forward_exit_status(t_execution_state res);
t_execution_state execute_command(t_shell *state,
t_executable_node *exe);
t_execution_state execute_tree_node(t_shell *state,
t_executable_node *exe);
t_execution_state execute_simple_list(t_shell *state,
t_executable_node *exe);
t_string word_to_string(t_ast_node node);
t_string word_to_hrdoc_string(t_ast_node node);
void set_cmd_status(t_shell *state, t_execution_state res);
t_execution_state res_status(int status);
t_execution_state res_pid(int pid);
void fire_debug_trap(t_shell *state);
void fire_err_trap(t_shell *state, int code);
void fire_return_trap(t_shell *state, int code);
void trap_save_reset(t_shell *state, char **save);
void pseudo_traps_quiet(t_shell *state);
void trap_restore(t_shell *state, char **save);
void exe_res_set_status(t_shell *st, t_execution_state *res);
int find_cmd_path(t_shell *state, char *cmd_name,
char **path_of_exe);
int exec_external_argv(t_shell *state, t_vec *args);
int run_external_sync(t_shell *state, t_vec *args);
int expand_simple_command(t_shell *state, t_ast_node *node,
t_executable_cmd *ret, t_vec_int *redirects);
int (*builtin_func(t_shell *state, char *name))(t_shell *s,
t_vec argv);

#endif
4 changes: 3 additions & 1 deletion incs/ft_builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ bool zopt_apply(t_shell *state, const char *n, bool on);
int builtin_let(t_shell *state, t_vec argv);
int builtin_kill(t_shell *state, t_vec argv);
int builtin_printf(t_shell *state, t_vec argv);
int (*builtin_func(char *name))(t_shell *state,
int (*builtin_func(t_shell *state, char *name))(t_shell *s,
t_vec argv);
int (*zsh_builtin_func(char *name))(t_shell *s, t_vec argv);
int builtin_which(t_shell *state, t_vec argv);

#endif
2 changes: 1 addition & 1 deletion src/builtins/builtin_builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ int builtin_builtin(t_shell *state, t_vec argv)

if (argv.len < 2)
return (0);
f = builtin_func(((char **)argv.ctx)[1]);
f = builtin_func(state, ((char **)argv.ctx)[1]);
if (!f)
{
ft_eprintf("%s: builtin: %s: not a shell builtin\n",
Expand Down
8 changes: 5 additions & 3 deletions src/builtins/builtin_cmd2.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ static int command_v(t_shell *state, char *name)

if (alias_get(&state->aliases, name))
return (ft_printf("alias "), alias_print_one(&state->aliases, name));
if (type_is_keyword(name) || builtin_func(name) || func_lookup(state, name))
if (type_is_keyword(name) || builtin_func(state, name)
|| func_lookup(state, name))
return (ft_printf("%s\n", name), 0);
if (ft_strchr(name, '/'))
{
Expand Down Expand Up @@ -120,8 +121,9 @@ static int command_run(t_shell *state, t_vec argv, size_t start)
i = start;
while (i < argv.len)
vec_push(&sub, &av[i++]);
if (builtin_func(av[start]))
return (rc = builtin_func(av[start])(state, sub), xfree(sub.ctx), rc);
if (builtin_func(state, av[start]))
return (rc = builtin_func(state, av[start])(state, sub),
xfree(sub.ctx), rc);
rc = run_external_sync(state, &sub);
return (xfree(sub.ctx), rc);
}
Expand Down
25 changes: 16 additions & 9 deletions src/builtins/builtin_declare4.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
`} 2>/dev/null`, so the closing brace is not added a second time. */
static void print_body(t_shell *state, t_shell_func *fn)
{
const char *t;
size_t n;
char *body;

if (!fn->text)
Expand All @@ -35,13 +33,7 @@ static void print_body(t_shell *state, t_shell_func *fn)
"(defined by eval?)\n", state->ctx, fn->name);
return ;
}
t = fn->text;
while (*t == ' ' || *t == '\t' || *t == '\n')
t++;
n = ft_strlen(t);
while (n > 0 && (t[n - 1] == ' ' || t[n - 1] == '\t' || t[n - 1] == '\n'))
n--;
body = ft_strndup(t, n);
body = func_text_trimmed(fn->text);
if (fn->body.node_type == AST_COMMAND)
ft_printf("%s ()\n{\n%s\n", fn->name, body);
else
Expand Down Expand Up @@ -76,6 +68,21 @@ static int bodies_of(t_shell *state, t_vec argv, size_t i)
return (missing);
}

/* A function's source text without the blank space around it, as a new
string. Shared by `declare -f` and zsh's `which`, which print the same
body under different headers. */
char *func_text_trimmed(const char *t)
{
size_t n;

while (*t == ' ' || *t == '\t' || *t == '\n')
t++;
n = ft_strlen(t);
while (n > 0 && (t[n - 1] == ' ' || t[n - 1] == '\t' || t[n - 1] == '\n'))
n--;
return (ft_strndup(t, n));
}

int declare_functions(t_shell *state, t_vec argv, size_t i, bool bodies)
{
if (i < argv.len && ft_strcmp(((char **)argv.ctx)[i], "--") == 0)
Expand Down
6 changes: 3 additions & 3 deletions src/builtins/builtin_type.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

/* Check via the dispatch hash table — no need to maintain a separate list.
The dispatch table IS the authoritative set of builtins. */
int type_is_builtin(const char *name)
int type_is_builtin(t_shell *state, const char *name)
{
return (builtin_func((char *)name) != NULL);
return (builtin_func(state, (char *)name) != NULL);
}

/* POSIX reserved words (plus the common function/{/}). */
Expand Down Expand Up @@ -100,7 +100,7 @@ int type_one(t_shell *state, const char *name)
return (ft_printf("%s is a shell keyword\n", name), 0);
if (func_lookup(state, (char *)name))
return (ft_printf("%s is a function\n", name), 0);
if (type_is_builtin(name))
if (type_is_builtin(state, name))
return (ft_printf("%s is a shell builtin\n", name), 0);
return (type_one_path(state, name));
}
4 changes: 2 additions & 2 deletions src/builtins/builtin_type3.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ int type_t_one(t_shell *state, const char *name)
return (ft_printf("keyword\n"), 0);
if (func_lookup(state, (char *)name))
return (ft_printf("function\n"), 0);
if (type_is_builtin(name))
if (type_is_builtin(state, name))
return (ft_printf("builtin\n"), 0);
if (cmd_hash_lookup(&state->cmd_cache, name))
return (ft_printf("file\n"), 0);
Expand All @@ -47,7 +47,7 @@ int type_p_one(t_shell *state, const char *name, int force)
char *path;

if (!force && (type_is_keyword(name) || func_lookup(state, (char *)name)
|| type_is_builtin(name)))
|| type_is_builtin(state, name)))
return (0);
if (ft_strchr(name, '/') && access(name, X_OK) == 0)
return (ft_printf("%s\n", name), 0);
Expand Down
67 changes: 67 additions & 0 deletions src/builtins/builtin_zsh_which.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* builtin_zsh_which.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: dlesieur <dlesieur@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/10/18 03:00:00 by dlesieur #+# #+# */
/* Updated: 2026/10/18 03:00:00 by dlesieur ### ########.fr */
/* */
/* ************************************************************************** */

#include "builtins_private.h"

/* zsh's `which` (its `whence -c`), a builtin in zsh mode only -- see
zsh_builtin_func() in hash_builtins_zsh.c for why the gate. Plugins call
it as `builtin which`: oh-my-zsh's sudo widget runs `builtin which -a`
on every Esc Esc, and without it each toggle printed "not a shell
builtin" over the prompt.
-a lists every match instead of the first, -p skips aliases, reserved
words, functions and builtins; any other letter is zsh's "bad option",
status 1. A name nothing answers to prints "NAME not found" on stdout
and makes the status 1, as zsh does; no operand at all is status 1. */
static int which_opts(t_shell *state, t_vec argv, size_t *i, int *flags)
{
char *a;
size_t k;

while (*i < argv.len)
{
a = ((char **)argv.ctx)[*i];
if (a[0] != '-' || !a[1])
break ;
(*i)++;
if (!ft_strcmp(a, "--"))
break ;
k = 0;
while (a[++k])
{
if (a[k] == 'a')
*flags |= WHICH_ALL;
else if (a[k] == 'p')
*flags |= WHICH_PATH;
else
return (ft_eprintf("%s: which: bad option: -%c\n",
state->ctx, a[k]), 1);
}
}
return (0);
}

int builtin_which(t_shell *state, t_vec argv)
{
size_t i;
int flags;
int rc;

i = 1;
flags = 0;
if (which_opts(state, argv, &i, &flags))
return (1);
rc = (i >= argv.len);
while (i < argv.len)
if (which_one(state, ((char **)argv.ctx)[i++], flags))
rc = 1;
return (rc);
}
Loading
Loading