Repository: peda-cos/minishell
Pull request #56: Implementa casos especiais de expansão de variável
Author: login: jonnathan-ls
Created: 2025-04-25T03:20:48Z | Merged: 2025-04-28T01:29:01Z

## Descrição

Este PR implementa capacidade de expansão de variável em casos especiais:

- `_`: deve processar o ultimo argumento do comando executado caso tenha sido execução de comando isolada, ou seja, sem pipe em processo.
- `0`: deve retornar o nome do minshell
- `${var}` deve ser capaz de expandir a variável: 
  - **Obs**.: Trata-se de expansão de variável avançada, porém apenas a expansão, não há implementação de capacidades como `${var-default}` ou `${var%trunc}` (mais detalhes em [issuecomment-2816158536](https://github.com/peda-cos/minishell/issues/25#issuecomment-2816158536))
- `$"HOME"` não deve realizar a expansão, o comportamento no bash apenas retorna `HOME` sem o `$`

> Resolve problema relatado na Issue https://github.com/peda-cos/minishell/issues/25

## Escopo das Modificações

- Adapta o `tokenizer` para conseguir tokenizar uma palavra quando tiver `${}`
- Implementa expansão de variável no caso de `0`
- Implementa capacidade de guardar o último comando de argumento processado para poder recuperar quando for solicitado a expansão de variável com `_`
- Implementa capacidade de expandir vríavel quando estiver entre colchetes `${}`
- Adiciona flag de controle de error quando não conseguir processar expansão avançada `${}` evitando que valores (sub-tokens) sejam processados como comando.
  - Ex.: `"0"${var }'1'` deve registrar log de falha apenas e não fazer nada
- Adiciona tratamento para não expandir quando o nome da variável está em aspas

## Resultado

```bash
🧙 Minishell ❯ echo $0
minishell
🧙 Minishell ❯ echo pwd
pwd
🧙 Minishell ❯ $_
/nfs/homes/jlacerda/ft/minishell
🧙 Minishell ❯ 
🧙 Minishell ❯ export v=1
🧙 Minishell ❯ ${}
minishell: ${}: bad substitution
🧙 Minishell ❯ ${ }
minishell: ${ }: bad substitution
🧙 Minishell ❯ "0"${v }'2'
minishell: ${v }: bad substitution
🧙 Minishell ❯ "0"${ v}'2'
minishell: ${ v}: bad substitution
🧙 Minishell ❯ "0"${v}'2'
minishell: 012: command not found
🧙 Minishell ❯ echo "0"${v}'2'
012
🧙 Minishell ❯ echo "0"${v}'2' | wc -c
4
🧙 Minishell ❯ cd ${HOME}
🧙 Minishell ❯ pwd
/nfs/homes/jlacerda
🧙 Minishell ❯ echo $"HOME"
HOME
🧙 Minishell ❯ echo $"HO"ME-$USER
HOME-jlacerda
🧙 Minishell ❯ echo $"HO"ME|$USER
minishell: jlacerda: command not found
🧙 Minishell ❯ echo $"HO"ME'|'$USER
HOME|jlacerda
🧙 Minishell ❯
```

Closes #25

Linked issue #25: Casos de Borda de Expansão de Variável
- **Descrição**: Vários cenários de expansão de variáveis precisam de verificação
- **Problemas para Investigar**:
  - Comportamento da variável sublinhado (`_`)
  - Resolução de nome de shell `$0`
  - Expansão com colchetes (`${var}`)
- **Severidade**: Média - Impacto depende dos casos de uso específicos.

Linked issue comments: user: login: jonnathan-ls
created_at: 2025-04-18T20:38:02Z
body: 

O uso de colchetes para expandir variáveis parece ter muito mais funcionalidades do que apenas expandir.

Trata-se de uma expansão de variável avançada.

Acho que não vale o esforço de cobrirmos @peda-cos, dado que não é solicitado no subject para implementar.

Veja algumas situações com os caracteres - ou % após a variável... (devem existir outros também).

```bash
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ export var=jonh
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ echo ${var%}
jonh
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ echo ${var%h}
jon
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ echo ${var%nh}
jo
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ echo ${var-default}
jonh
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ unset var
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ echo ${var-default}
default
jonnathan-ls@Dell-G15:/mnt/c/Users/jonnathan-ls/42-cursus/minishell$ 
```

> Isso por que (segundo a AI)
>
> **`-` (Valor padrão)**:
> 
> Se a variável estiver definida, retorna o valor dela.
> Se a variável não estiver definida, retorna o valor padrão especificado.
> 
> **`%` (Remoção de sufixo)**:
> 
> Remove o menor sufixo que corresponde ao padrão especificado.
author_association: COLLABORATOR
user: login: jonnathan-ls
created_at: 2025-04-24T03:30:00Z
body: Implementação em andamento nra branch: [feat/advanced-expansion](https://github.com/peda-cos/minishell/tree/feat/advanced-expansion)

Falta finalizar cenário de expansão em colchetes (sem cobrir funcionalidades de expansão avançada)


author_association: COLLABORATOR
user: login: jonnathan-ls
created_at: 2025-04-27T18:39:53Z
body: Correção implementada no PR https://github.com/peda-cos/minishell/pull/56
author_association: COLLABORATOR

Diff:
diff --git a/Makefile b/Makefile
index c323544..d648c7d 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,8 @@ EXECUTOR_SRCS = sources/executor/main.c sources/executor/path_utils.c \
 TOKENIZER_SRCS = sources/tokenizer/utils.c sources/tokenizer/main.c \
 	sources/tokenizer/word.c sources/tokenizer/redirect.c \
 	sources/tokenizer/token.c
-EXPANSION_SRCS = sources/expansion/main.c sources/expansion/utils.c
+EXPANSION_SRCS = sources/expansion/main.c sources/expansion/utils.c \
+	sources/expansion/advanced.c
 VALIDATOR_SRCS = sources/validator/main.c sources/validator/utils.c
 HISTORY_SRCS = sources/history/main.c sources/history/read.c \
 	sources/history/file.c sources/history/write.c
diff --git a/includes/macros.h b/includes/macros.h
index 319b2e5..99f6997 100644
--- a/includes/macros.h
+++ b/includes/macros.h
@@ -6,7 +6,7 @@
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/02/19 19:15:26 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/20 20:11:25 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/25 00:19:13 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -22,12 +22,18 @@
 # define REDIRECT_IN_CHR '<'
 # define REDIRECT_OUT_CHR '>'
 # define SINGLE_QUOTE_CHR '\''
+# define DOLLAR_CHR '$'
+# define BRACKET_OPEN_CHR '{'
+# define BRACKET_CLOSE_CHR '}'
+# define UNDERSCORE_CHR '_'
 # define DOUBLE_QUOTE_CHR '"'
 # define PIPE_STR "|"
 # define APPEND_STR ">>"
 # define HEREDOC_STR "<<"
 # define REDIRECT_IN_STR "<"
 # define REDIRECT_OUT_STR ">"
+# define SINGLE_QUOTE_STR "'"
+# define DOUBLE_QUOTE_STR "\""
 /* ----------------------- */
 
 /* Prompt ---------------- */
diff --git a/includes/minishell.h b/includes/minishell.h
index 2e3e136..9191060 100644
--- a/includes/minishell.h
+++ b/includes/minishell.h
@@ -3,10 +3,10 @@
 /*                                                        :::      ::::::::   */
 /*   minishell.h                                        :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/02/19 19:15:26 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/21 23:22:07 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/25 00:19:05 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -40,6 +40,7 @@ void		execute_parsed_commands(
 				t_command *cmd, char ***env, int *last_exit, t_token *tokens);
 
 /* Environment management */
+void		set_underscore_arg_value(t_command *cmd, char ***envs);
 char		**copy_env(char **envp);
 void		free_env(char **env);
 void		exit_free(
@@ -56,9 +57,11 @@ int			setup_output_redirection(t_process_command_args *param);
 /* Memory cleanup */
 void		free_tokens(t_token *tokens);
 void		free_commands(t_command *cmd);
+void		free_token_content(t_token_content *content);
 
 /* Utility for variable expansion */
-char		*expand_variable(char *str, char **env, int last_exit);
+char		*expand_variable(char *str, char **envs, int last_exit,
+				int *has_error_flag_control);
 
 /* Tokenizer: splits input into tokens */
 t_token		*tokenize_input(char *input);
diff --git a/includes/structs.h b/includes/structs.h
index 6137078..60c2e97 100644
--- a/includes/structs.h
+++ b/includes/structs.h
@@ -3,10 +3,10 @@
 /*                                                        :::      ::::::::   */
 /*   structs.h                                          :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/20 22:48:48 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/21 23:18:24 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/26 22:58:34 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -29,6 +29,7 @@ typedef struct s_token_content
 {
 	struct s_token_content	*next;
 	char					*value;
+	int						in_quotes;
 	int						in_single_quotes;
 }							t_token_content;
 
diff --git a/sources/executor/heredoc.c b/sources/executor/heredoc.c
index 7b4f7b5..301e53e 100644
--- a/sources/executor/heredoc.c
+++ b/sources/executor/heredoc.c
@@ -3,10 +3,10 @@
 /*                                                        :::      ::::::::   */
 /*   heredoc.c                                          :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/25 08:12:39 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/21 23:24:20 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/24 22:59:37 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -40,10 +40,12 @@ static char	*process_heredoc_line(char *line, char **env, int last_exit,
 		int expand_vars)
 {
 	char	*expanded;
+	int		has_error;
 
+	has_error = FALSE;
 	if (!expand_vars)
 		return (ft_strdup(line));
-	expanded = expand_variable(line, env, last_exit);
+	expanded = expand_variable(line, env, last_exit, &has_error);
 	return (expanded);
 }
 
diff --git a/sources/expansion/advanced.c b/sources/expansion/advanced.c
new file mode 100644
index 0000000..9d0de5c
--- /dev/null
+++ b/sources/expansion/advanced.c
@@ -0,0 +1,70 @@
+/* ************************************************************************** */
+/*                                                                            */
+/*                                                        :::      ::::::::   */
+/*   advanced.c                                         :+:      :+:    :+:   */
+/*                                                    +:+ +:+         +:+     */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
+/*                                                +#+#+#+#+#+   +#+           */
+/*   Created: 2025/04/23 21:50:10 by jlacerda          #+#    #+#             */
+/*   Updated: 2025/04/25 22:18:34 by jlacerda         ###   ########.fr       */
+/*                                                                            */
+/* ************************************************************************** */
+
+#include "expansion.h"
+
+static int	is_valid_var_char(char chr)
+{
+	return (ft_isalnum(chr) || chr == UNDERSCORE_CHR);
+}
+
+static char	*extract_braced_var_name(char *str, int *index)
+{
+	int		start;
+	int		end;
+	char	*bracket_content;
+
+	(*index)++;
+	start = *index;
+	end = start;
+	while (str[end] && str[end] != BRACKET_CLOSE_CHR)
+		end++;
+	if (!str[end])
+	{
+		ft_putendl_fd("minishell: syntax error: missing '}'", STDERR_FILENO);
+		return (NULL);
+	}
+	bracket_content = ft_substr(str, start, end - start);
+	*index = end + 1;
+	return (bracket_content);
+}
+
+char	*extract_var_name_in_brackets_entered(
+	char *str, int *index)
+{
+	int		i;
+	char	*var_name;
+
+	var_name = extract_braced_var_name(str, index);
+	if (!var_name)
+		return (NULL);
+	if (var_name[0] == '\0')
+	{
+		ft_putendl_fd("minishell: ${}: bad substitution", STDERR_FILENO);
+		free(var_name);
+		return (NULL);
+	}
+	i = 0;
+	while (var_name[i])
+	{
+		if (!is_valid_var_char(var_name[i]))
+		{
+			ft_putstr_fd("minishell: ${", STDERR_FILENO);
+			ft_putstr_fd(var_name, STDERR_FILENO);
+			ft_putendl_fd("}: bad substitution", STDERR_FILENO);
+			free(var_name);
+			return (NULL);
+		}
+		i++;
+	}
+	return (var_name);
+}
diff --git a/sources/expansion/expansion.h b/sources/expansion/expansion.h
index cf5493a..124fd1e 100644
--- a/sources/expansion/expansion.h
+++ b/sources/expansion/expansion.h
@@ -6,7 +6,7 @@
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/31 14:20:01 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/12 21:27:59 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/24 23:34:29 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -21,4 +21,11 @@ typedef struct s_expansion_ctx
 	int		last_exit;
 }			t_expansion_ctx;
 
+char	*get_env_value(char *key, char **env);
+char	*get_underscore_arg_value(char **envs);
+void	set_underscore_arg_value(t_command *cmd, char ***envs);
+char	*extract_var_name_in_brackets_entered(char *str, int *index);
+char	*get_special_variable(char *str,
+			int *index, char **envs, int last_exit);
+
 #endif
diff --git a/sources/expansion/main.c b/sources/expansion/main.c
index 3ccc656..0f5ab02 100644
--- a/sources/expansion/main.c
+++ b/sources/expansion/main.c
@@ -6,7 +6,7 @@
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/31 21:50:10 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/19 23:35:00 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/27 15:08:41 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -22,22 +22,29 @@ static char	*extract_var_name(const char *str, int *index)
 	return (ft_substr(str, start, *index - start));
 }
 
-static char	*extract_env_value(char *str, char **env, int last_exit, int *index)
+static char	*extract_env_value(
+	char *str, char **envs, int last_exit, int *index)
 {
 	char	*var;
 	char	*value;
+	int		in_brackets_entered;
 
-	if (ft_isspace(str[*index]) || str[*index] == '\0')
-		return (ft_strdup("$"));
-	if (str[*index] == '?')
+	value = get_special_variable(str, index, envs, last_exit);
+	if (value)
 	{
-		*index += 1;
-		return (ft_itoa(last_exit));
+		(*index)++;
+		return (value);
 	}
-	var = extract_var_name(str, index);
+	in_brackets_entered = str[*index] == BRACKET_OPEN_CHR;
+	if (in_brackets_entered)
+		var = extract_var_name_in_brackets_entered(str, index);
+	else
+		var = extract_var_name(str, index);
+	if (in_brackets_entered && !var)
+		return (NULL);
 	if (!var)
 		return (ft_strdup(""));
-	value = get_env_value(var, env);
+	value = get_env_value(var, envs);
 	free(var);
 	if (!value)
 		return (ft_strdup(""));
@@ -59,18 +66,29 @@ static char	*extract_result(char *partial, char *result)
 	return (result);
 }
 
-char	*expand_variable(char *str, char **env, int last_exit)
+static void	process_expansion(char **result, char **value,
+	int *has_error_flag_control)
+{
+	if (!*value)
+		*has_error_flag_control = TRUE;
+	*result = extract_result(*value, *result);
+}
+
+char	*expand_variable(char *str,
+	char **envs, int last_exit, int *has_error_flag_control)
 {
 	int		i;
 	int		start;
 	char	*value;
 	char	*result;
+	int		str_len;
 
 	i = 0;
 	if (!str)
 		return (NULL);
 	result = ft_strdup("");
-	while (str[i])
+	str_len = ft_strlen(str);
+	while (str[i] && i < str_len)
 	{
 		start = i;
 		while (str[i] && str[i] != '$')
@@ -79,8 +97,8 @@ char	*expand_variable(char *str, char **env, int last_exit)
 		if (str[i] == '$')
 		{
 			i++;
-			value = extract_env_value(str, env, last_exit, &i);
-			result = extract_result(value, result);
+			value = extract_env_value(str, envs, last_exit, &i);
+			process_expansion(&result, &value, has_error_flag_control);
 		}
 	}
 	return (result);
diff --git a/sources/expansion/utils.c b/sources/expansion/utils.c
index 33e6f40..35a8f3e 100644
--- a/sources/expansion/utils.c
+++ b/sources/expansion/utils.c
@@ -3,10 +3,10 @@
 /*                                                        :::      ::::::::   */
 /*   utils.c                                            :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/31 21:00:10 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/03/31 22:12:43 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/26 23:50:47 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -33,3 +33,47 @@ char	*get_env_value(char *key, char **env)
 	}
 	return (ft_strdup(""));
 }
+
+char	*get_underscore_arg_value(char **envs)
+{
+	char	*value;
+
+	value = get_env_value("_", envs);
+	if (!value)
+		return (getcwd(NULL, 0));
+	return (value);
+}
+
+void	set_underscore_arg_value(t_command *cmd, char ***envs)
+{
+	int		index;
+	char	*last_arg_value;
+
+	index = 0;
+	while (cmd->args && cmd->args[index])
+		index++;
+	if (index > 0)
+	{
+		last_arg_value = cmd->args[index - 1];
+		update_env_variable("_", last_arg_value, envs);
+	}
+}
+
+char	*get_special_variable(char *str,
+	int *index, char **envs, int last_exit)
+{
+	char	*value;
+
+	value = NULL;
+	if (ft_isspace(str[*index]))
+		value = (ft_strdup("$ "));
+	else if (str[*index] == '\0')
+		value = (ft_strdup("$"));
+	else if (str[*index] == '?')
+		value = ft_itoa(last_exit);
+	else if (str[*index] == '0')
+		value = (ft_strdup("minishell"));
+	else if (str[*index] == '_')
+		value = (get_underscore_arg_value(envs));
+	return (value);
+}
diff --git a/sources/parser/main.c b/sources/parser/main.c
index df3d6b2..a899cc2 100644
--- a/sources/parser/main.c
+++ b/sources/parser/main.c
@@ -12,8 +12,35 @@
 
 #include "parser.h"
 
+static void	process_heredoc_delim(t_token_content *content, t_command	*cmd)
+{
+	char	*temp;
+	char	*quotes_delim;
+	char	*here_doc_delim;
+
+	quotes_delim = NULL;
+	free(cmd->heredoc_delim);
+	if (content->in_quotes && content->in_single_quotes)
+		quotes_delim = SINGLE_QUOTE_STR;
+	else if (content->in_quotes && !content->in_single_quotes)
+		quotes_delim = DOUBLE_QUOTE_STR;
+	if (quotes_delim)
+	{
+		here_doc_delim = ft_strdup(quotes_delim);
+		temp = here_doc_delim;
+		here_doc_delim = ft_strjoin(temp, content->value);
+		free(temp);
+		temp = here_doc_delim;
+		here_doc_delim = ft_strjoin(temp, quotes_delim);
+		free(temp);
+	}
+	else
+		here_doc_delim = ft_strdup(content->value);
+	cmd->heredoc_delim = ft_strdup(here_doc_delim);
+}
+
 static void	set_redirection_target(t_command *cmd, t_token *token,
-		t_content_params *content_params)
+	t_content_params *content_params)
 {
 	char	*value;
 
@@ -38,10 +65,7 @@ static void	set_redirection_target(t_command *cmd, t_token *token,
 		cmd->append = 1;
 	}
 	else if (token->type == HEREDOC)
-	{
-		free(cmd->heredoc_delim);
-		cmd->heredoc_delim = value;
-	}
+		process_heredoc_delim(content_params->content, cmd);
 	else
 		free(value);
 }
diff --git a/sources/tokenizer/redirect.c b/sources/tokenizer/redirect.c
index 6053ab4..c09367b 100644
--- a/sources/tokenizer/redirect.c
+++ b/sources/tokenizer/redirect.c
@@ -6,7 +6,7 @@
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/22 13:18:28 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/19 19:50:06 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/26 21:04:14 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -19,10 +19,10 @@ static t_token	*get_redirect_in_token(char *input, int *index)
 	if (input[*index + 1] == REDIRECT_IN_CHR)
 	{
 		(*index)++;
-		content = new_token_content(ft_strdup(HEREDOC_STR), FALSE);
+		content = new_token_content(ft_strdup(HEREDOC_STR), FALSE, FALSE);
 		return (new_token(content, HEREDOC));
 	}
-	content = new_token_content(ft_strdup(REDIRECT_IN_STR), FALSE);
+	content = new_token_content(ft_strdup(REDIRECT_IN_STR), FALSE, FALSE);
 	return (new_token(content, REDIRECT_IN));
 }
 
@@ -33,10 +33,10 @@ static t_token	*get_redirect_out_token(char *input, int *index)
 	if (input[*index + 1] == REDIRECT_OUT_CHR)
 	{
 		(*index)++;
-		content = new_token_content(ft_strdup(APPEND_STR), FALSE);
+		content = new_token_content(ft_strdup(APPEND_STR), FALSE, FALSE);
 		return (new_token(content, APPEND));
 	}
-	content = new_token_content(ft_strdup(REDIRECT_OUT_STR), FALSE);
+	content = new_token_content(ft_strdup(REDIRECT_OUT_STR), FALSE, FALSE);
 	return (new_token(content, REDIRECT_OUT));
 }
 
@@ -58,7 +58,7 @@ void	add_token_redirect(char *input, int *index, t_token **tokens)
 	chr = input[*index];
 	if (chr == PIPE_CHR)
 	{
-		content = new_token_content(ft_strdup(PIPE_STR), FALSE);
+		content = new_token_content(ft_strdup(PIPE_STR), FALSE, FALSE);
 		token = new_token(content, PIPE);
 	}
 	else
diff --git a/sources/tokenizer/token.c b/sources/tokenizer/token.c
index 0e1bb82..cc09add 100644
--- a/sources/tokenizer/token.c
+++ b/sources/tokenizer/token.c
@@ -3,53 +3,15 @@
 /*                                                        :::      ::::::::   */
 /*   token.c                                            :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/22 13:18:28 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/21 18:02:43 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/27 04:19:20 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
 #include "tokenizer.h"
 
-t_token	*new_token(t_token_content *content, t_token_type type)
-{
-	t_token			*token;
-	t_token_content	*tmp_content;
-
-	token = malloc(sizeof(t_token));
-	if (!token || !content)
-		return (NULL);
-	token->length = 0;
-	token->prev = NULL;
-	token->next = NULL;
-	token->type = type;
-	token->content = content;
-	tmp_content = content;
-	while (tmp_content)
-	{
-		token->length += ft_strlen(tmp_content->value);
-		tmp_content = tmp_content->next;
-	}
-	return (token);
-}
-
-void	add_token(t_token **tokens, t_token *new)
-{
-	t_token	*tmp;
-
-	if (!*tokens)
-	{
-		*tokens = new;
-		return ;
-	}
-	tmp = *tokens;
-	while (tmp->next)
-		tmp = tmp->next;
-	new->prev = tmp;
-	tmp->next = new;
-}
-
 t_token	*get_last_token(t_token **tokens)
 {
 	t_token	*tmp;
@@ -62,7 +24,8 @@ t_token	*get_last_token(t_token **tokens)
 	return (tmp);
 }
 
-t_token_content	*new_token_content(char *value, int in_single_quotes)
+t_token_content	*new_token_content(char *value,
+	int in_quotes, int in_single_quotes)
 {
 	t_token_content	*content;
 
@@ -76,16 +39,57 @@ t_token_content	*new_token_content(char *value, int in_single_quotes)
 	}
 	content->next = NULL;
 	content->value = value;
+	content->in_quotes = in_quotes;
 	content->in_single_quotes = in_single_quotes;
 	return (content);
 }
 
+static char	*append_to_final_value(char *final_value,
+	t_token_content *content, t_content_params *params)
+{
+	int		has_error;
+	char	*temp_value;
+	char	*current_segment;
+
+	has_error = FALSE;
+	if (content->in_single_quotes)
+		current_segment = ft_strdup(content->value);
+	else
+		current_segment = expand_variable(content->value,
+				params->envs, *params->last_exit_code, &has_error);
+	if (has_error)
+	{
+		free(current_segment);
+		free(final_value);
+		return (NULL);
+	}
+	if (!current_segment)
+		return (free(final_value), NULL);
+	temp_value = final_value;
+	final_value = ft_strjoin(final_value, current_segment);
+	free(temp_value);
+	free(current_segment);
+	return (final_value);
+}
+
+static char	*process_final_value(char *final_value,
+	t_content_params *params, t_token_content *content)
+{
+	char	*temp;
+
+	if (content->value[0] == '~' && content->value[1] == '\0')
+	{
+		temp = content->value;
+		content->value = get_env_value("HOME", params->envs);
+		free(temp);
+	}
+	return (append_to_final_value(final_value, content, params));
+}
+
 char	*get_token_content_value(t_content_params *params)
 {
 	t_token_content	*content;
 	char			*final_value;
-	char			*current_segment;
-	char			*temp_value;
 
 	if (!params || !params->content)
 		return (ft_strdup(""));
@@ -95,20 +99,14 @@ char	*get_token_content_value(t_content_params *params)
 	content = params->content;
 	while (content)
 	{
-		if (content->in_single_quotes)
-			current_segment = ft_strdup(content->value);
-		else
-			current_segment = expand_variable(content->value, params->envs,
-					*params->last_exit_code);
-		if (!current_segment)
+		if (ft_strcmp(content->value, "$") == 0
+			&& !content->in_quotes
+			&& (content->next && content->next->in_quotes))
 		{
-			free(final_value);
-			return (NULL);
+			content = content->next;
+			continue ;
 		}
-		temp_value = final_value;
-		final_value = ft_strjoin(final_value, current_segment);
-		free(temp_value);
-		free(current_segment);
+		final_value = process_final_value(final_value, params, content);
 		if (!final_value)
 			return (NULL);
 		content = content->next;
diff --git a/sources/tokenizer/tokenizer.h b/sources/tokenizer/tokenizer.h
index 92629d0..1b6d6d8 100644
--- a/sources/tokenizer/tokenizer.h
+++ b/sources/tokenizer/tokenizer.h
@@ -6,7 +6,7 @@
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/27 22:31:37 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/12 18:56:41 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/26 23:56:26 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -27,8 +27,9 @@ typedef struct s_tokenizer_ctx
 
 t_token			*get_last_token(t_token **tokens);
 void			add_token(t_token **tokens, t_token *new);
-t_token_content	*new_token_content(char *value, int in_single_quotes);
 t_token			*new_token(t_token_content *content, t_token_type type);
+t_token_content	*new_token_content(char *value,
+					int in_quotes, int in_single_quotes);
 
 int				is_metachar(char c);
 int				is_quote_delimiter(char *str, int *index, char delimiter);
diff --git a/sources/tokenizer/utils.c b/sources/tokenizer/utils.c
index d56349b..ff8f67c 100644
--- a/sources/tokenizer/utils.c
+++ b/sources/tokenizer/utils.c
@@ -1,12 +1,12 @@
 /* ************************************************************************** */
 /*                                                                            */
 /*                                                        :::      ::::::::   */
-/*   token.c                                            :+:      :+:    :+:   */
+/*   utils.c                                            :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
 /*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/22 13:18:28 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/15 00:49:40 by jlacerda         ###   ########.fr       */
+/*   Updated: 2025/04/24 23:36:02 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
@@ -32,3 +32,41 @@ int	is_quote_delimiter(char *str, int *index, char delimiter)
 		return (TRUE);
 	return (FALSE);
 }
+
+t_token	*new_token(t_token_content *content, t_token_type type)
+{
+	t_token			*token;
+	t_token_content	*tmp_content;
+
+	token = malloc(sizeof(t_token));
+	if (!token || !content)
+		return (NULL);
+	token->length = 0;
+	token->prev = NULL;
+	token->next = NULL;
+	token->type = type;
+	token->content = content;
+	tmp_content = content;
+	while (tmp_content)
+	{
+		token->length += ft_strlen(tmp_content->value);
+		tmp_content = tmp_content->next;
+	}
+	return (token);
+}
+
+void	add_token(t_token **tokens, t_token *new)
+{
+	t_token	*tmp;
+
+	if (!*tokens)
+	{
+		*tokens = new;
+		return ;
+	}
+	tmp = *tokens;
+	while (tmp->next)
+		tmp = tmp->next;
+	new->prev = tmp;
+	tmp->next = new;
+}
diff --git a/sources/tokenizer/word.c b/sources/tokenizer/word.c
index 2871b7a..f8a31e2 100644
--- a/sources/tokenizer/word.c
+++ b/sources/tokenizer/word.c
@@ -3,15 +3,43 @@
 /*                                                        :::      ::::::::   */
 /*   word.c                                             :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/03/22 13:18:28 by jlacerda          #+#    #+#             */
-/*   Updated: 2025/04/20 00:25:02 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/26 22:40:58 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
 #include "tokenizer.h"
 
+static char	*get_word_in_brackets(char *str, int *index)
+{
+	char	*word;
+	int		word_index;
+
+	word_index = 0;
+	word = (char *)malloc(sizeof(char) * (ft_strlen(str + *index) + 1));
+	if (!word)
+		return (NULL);
+	while (str[*index] && str[*index] != BRACKET_CLOSE_CHR)
+	{
+		if (str[*index] != BACKSLASH_CHR)
+			word[word_index++] = str[*index];
+		(*index)++;
+	}
+	if (str[*index] != BRACKET_CLOSE_CHR)
+	{
+		ft_putstr_fd("minishell: syntax error: unclosed brackets\n",
+			STDERR_FILENO);
+		free(word);
+		return (NULL);
+	}
+	word[word_index++] = str[*index];
+	(*index)++;
+	word[word_index] = '\0';
+	return (word);
+}
+
 static char	*get_word_in_quotes(char *str, int *index, char delimiter)
 {
 	char	*word;
@@ -41,7 +69,7 @@ static char	*get_word_in_quotes(char *str, int *index, char delimiter)
 }
 
 static t_token_content	*append_token_content(t_token_content *content,
-		t_token_word_param *param)
+	t_token_word_param *param)
 {
 	t_token_content	*iter;
 	char			*temp;
@@ -57,12 +85,12 @@ static t_token_content	*append_token_content(t_token_content *content,
 			free(temp);
 			return (NULL);
 		}
-		new_content = new_token_content(ft_strdup(param->word),
+		new_content = new_token_content(ft_strdup(param->word), TRUE,
 				param->delimiter == SINGLE_QUOTE_CHR);
 		free(temp);
 	}
 	else
-		new_content = new_token_content(ft_strdup(param->word), FALSE);
+		new_content = new_token_content(ft_strdup(param->word), FALSE, FALSE);
 	if (!content)
 	{
 		content = new_content;
@@ -86,6 +114,30 @@ static t_token_content	*add_only_content_word(t_token_content *content,
 	return (content);
 }
 
+static t_token_content	*process_word_in_brackets(
+	t_token_content *content, t_token_word_param *param)
+{
+	int		malloc_word_len;
+
+	malloc_word_len = ft_strlen(param->str + *param->str_index);
+	content = add_only_content_word(content, param);
+	if (!content && param->word_index > 0)
+		return (NULL);
+	free(param->word);
+	param->word = (char *)malloc(sizeof(char) * (malloc_word_len + 1));
+	if (!param->word)
+		return (NULL);
+	param->word_index = 0;
+	param->word = get_word_in_brackets(param->str, param->str_index);
+	if (!param->word)
+		return (NULL);
+	param->in_quotes = FALSE;
+	content = append_token_content(content, param);
+	if (!content)
+		return (NULL);
+	return (content);
+}
+
 static t_token_content	*process_word_in_quotes(t_token_content *content,
 		t_token_word_param *param)
 {
@@ -126,6 +178,17 @@ void	add_token_word(char *str, int *index, t_token **tokens)
 	{
 		if (str[*index] == DOT_AND_COMA_CHR)
 			break ;
+		if (str[*index] == DOLLAR_CHR
+			&& str[*index + 1] == BRACKET_OPEN_CHR)
+		{
+			content = process_word_in_brackets(content, &param);
+			if (!content && param.word_index > 0)
+			{
+				free(param.word);
+				return ;
+			}
+			continue ;
+		}
 		if (str[*index] == SINGLE_QUOTE_CHR || str[*index] == DOUBLE_QUOTE_CHR)
 		{
 			content = process_word_in_quotes(content, &param);
diff --git a/sources/utils.c b/sources/utils.c
index 4ef87f8..ab5034c 100644
--- a/sources/utils.c
+++ b/sources/utils.c
@@ -3,15 +3,30 @@
 /*                                                        :::      ::::::::   */
 /*   utils.c                                            :+:      :+:    :+:   */
 /*                                                    +:+ +:+         +:+     */
-/*   By: peda-cos <peda-cos@student.42sp.org.br>    +#+  +:+       +#+        */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
 /*                                                +#+#+#+#+#+   +#+           */
 /*   Created: 2025/02/19 19:05:27 by peda-cos          #+#    #+#             */
-/*   Updated: 2025/04/21 23:25:03 by peda-cos         ###   ########.fr       */
+/*   Updated: 2025/04/25 22:19:01 by jlacerda         ###   ########.fr       */
 /*                                                                            */
 /* ************************************************************************** */
 
 #include "minishell.h"
 
+static void	set_last_arg_without_pipe_executed(
+	t_token *tokens, t_command *cmd, char ***envs)
+{
+	t_token	*current_token;
+
+	if (!tokens || !cmd || !envs)
+		return ;
+	current_token = tokens;
+	while (current_token && current_token->type != PIPE)
+		current_token = current_token->next;
+	if (current_token && current_token->type == PIPE)
+		return ;
+	set_underscore_arg_value(cmd, envs);
+}
+
 void	execute_parent_builtin(t_command *cmd, char ***env, int *last_exit,
 		t_token *tokens)
 {
@@ -97,11 +112,12 @@ void	process_input(char *input, char ***env, int *last_exit)
 	cmd = parse_tokens(tokens, *env, *last_exit);
 	if (cmd == NULL)
 	{
-		*last_exit = 2;
+		*last_exit = 1;
 		free_tokens(tokens);
 		return ;
 	}
 	preprocess_heredocs(cmd);
+	set_last_arg_without_pipe_executed(tokens, cmd, env);
 	execute_parsed_commands(cmd, env, last_exit, tokens);
 	free_tokens(tokens);
 }


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements additional variable expansion cases in minishell with support for special variables, advanced bracket-based expansion, and enhanced error handling.  
- Added processing for special variables such as "_" and "0".  
- Extended tokenizer capabilities for recognizing and expanding "${...}" expressions.  
- Refactored token and expansion utilities and moved token functions to better-organize code.

### Reviewed Changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                | Description                                                                            |
| ----------------------------------- | -------------------------------------------------------------------------------------- |
| sources/utils.c                     | Added function to conditionally update the "_" variable for commands without a pipe.   |
| sources/tokenizer/word.c            | Improved word tokenization to support bracketed variable expansion "${...}".           |
| sources/tokenizer/utils.c           | Relocated token creation functions from token.c for improved organization.             |
| sources/tokenizer/token.c           | Removed duplicate token functions, now sourced from utils.c.                           |
| sources/expansion/utils.c           | Introduced underscore and special variable expansion functions.                        |
| sources/expansion/main.c            | Updated expand_variable to handle errors via a new flag parameter and refine logic.      |
| sources/expansion/advanced.c        | Added advanced variable name extraction for bracketed expansions.                      |
| sources/executor/heredoc.c           | Propagated error flag in expand_variable calls for heredoc support.                      |
| includes/minishell.h                | Updated prototypes to include the new error flag in variable expansion functions.       |
| includes/macros.h                   | Added definitions for new syntax characters (e.g. DOLLAR_CHR, BRACKET_OPEN_CHR).         |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **Makefile**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T03:48:43Z
user: login: jonnathan-ls
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-26T21:02:03Z

Review comments:
diff_hunk: @@ -0,0 +1,67 @@
+/* ************************************************************************** */
+/*                                                                            */
+/*                                                        :::      ::::::::   */
+/*   advanced.c                                         :+:      :+:    :+:   */
+/*                                                    +:+ +:+         +:+     */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
+/*                                                +#+#+#+#+#+   +#+           */
+/*   Created: 2025/04/23 21:50:10 by jlacerda          #+#    #+#             */
+/*   Updated: 2025/04/24 23:30:31 by jlacerda         ###   ########.fr       */
+/*                                                                            */
+/* ************************************************************************** */
+
+#include "expansion.h"
+
+static int	is_valid_var_char(char chr)
+{
+	return (ft_isalnum(chr) || chr == UNDERSCORE_CHR);
+}
+
+static char	*extract_braced_var_name(char *str, int *index)
+{
+	int		start;
+	int		end;
+	char	*bracket_content;
+
+	(*index)++;
+	start = *index;
+	end = start;
+	while (str[end] && str[end] != BRACKET_CLOSE_CHR)
+		end++;
+	if (!str[end])
+	{
+		ft_putendl_fd("minishell: syntax error: missing '}'", STDERR_FILENO);
+		return (NULL);
+	}
+	bracket_content = ft_substr(str, start, end - start);
+	*index = end + 1;
+	return (bracket_content);
+}
+
+char	*extract_var_name_in_brackets_entered(
+	char *str, int *index)
+{
+	int		i;
+	char	*var_name;
+
+	var_name = extract_braced_var_name(str, index);
+	if (var_name[0] == '\0')
+	{
+		ft_putstr_fd("minishell: ${}: bad substitution", STDERR_FILENO);
path: sources/expansion/advanced.c
user: login: Copilot
body: Consider verifying that 'var_name' is not NULL before dereferencing it (e.g., checking var_name[0]) to avoid a potential segmentation fault if extract_braced_var_name fails.
```suggestion
	var_name = extract_braced_var_name(str, index);
	if (var_name == NULL)
	{
		// Error message already handled in extract_braced_var_name
		return (NULL);
	}
	if (var_name[0] == '\0')
	{
		ft_putstr_fd("minishell: ${}: bad substitution", STDERR_FILENO);
		free(var_name);
```
created_at: 2025-04-25T03:48:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/* ************************************************************************** */
+/*                                                                            */
+/*                                                        :::      ::::::::   */
+/*   advanced.c                                         :+:      :+:    :+:   */
+/*                                                    +:+ +:+         +:+     */
+/*   By: jlacerda <jlacerda@student.42.fr>          +#+  +:+       +#+        */
+/*                                                +#+#+#+#+#+   +#+           */
+/*   Created: 2025/04/23 21:50:10 by jlacerda          #+#    #+#             */
+/*   Updated: 2025/04/24 23:30:31 by jlacerda         ###   ########.fr       */
+/*                                                                            */
+/* ************************************************************************** */
+
+#include "expansion.h"
+
+static int	is_valid_var_char(char chr)
+{
+	return (ft_isalnum(chr) || chr == UNDERSCORE_CHR);
+}
+
+static char	*extract_braced_var_name(char *str, int *index)
+{
+	int		start;
+	int		end;
+	char	*bracket_content;
+
+	(*index)++;
+	start = *index;
+	end = start;
+	while (str[end] && str[end] != BRACKET_CLOSE_CHR)
+		end++;
+	if (!str[end])
+	{
+		ft_putendl_fd("minishell: syntax error: missing '}'", STDERR_FILENO);
+		return (NULL);
+	}
+	bracket_content = ft_substr(str, start, end - start);
+	*index = end + 1;
+	return (bracket_content);
+}
+
+char	*extract_var_name_in_brackets_entered(
+	char *str, int *index)
+{
+	int		i;
+	char	*var_name;
+
+	var_name = extract_braced_var_name(str, index);
+	if (var_name[0] == '\0')
+	{
+		ft_putstr_fd("minishell: ${}: bad substitution", STDERR_FILENO);
path: sources/expansion/advanced.c
user: login: jonnathan-ls
body: Adicionado
created_at: 2025-04-26T21:02:03Z
author_association: COLLABORATOR

PR comments:
user: login: jonnathan-ls
created_at: 2025-04-25T03:46:48Z
body: Pendências:

1) Avaliar o motivo de quando executado a falha de primeira, estar saindo do shell.
Ex.: `${ HOME}` na primeira execução aborta o shell. Já ao contrário se executar uma expansão válida `${HOME}` e depois com falha `${HOME }`, ai funciona normalmente, registra o log de error e mais nada.

2) Avaliar como adaptar a atualização do `last_exit` para conter o valor `1`, atualmente está logando o valor  `2` na `STDOUT`

author_association: COLLABORATOR
user: login: jonnathan-ls
created_at: 2025-04-26T21:04:03Z
body: > Pendências: https://github.com/peda-cos/minishell/pull/56#issuecomment-2829309075

Item 1) 

Por algum motivo quando testado em `WSL` o comportamento foi direferente, acaba sempre abortando o minishell devido processamento do `%` (caracter especial para `readline`).

Entretanto, atualmente testando diretamente no linux, o comportamento relatado, não está ocorrendo `><` (vai entender).

```bash
🧙 Minishell ❯ ${}
minishell: ${}: bad substitution
🧙 Minishell ❯ ${}
minishell: ${}: bad substitution
🧙 Minishell ❯ ${ }
minishell: ${ }: bad substitution
🧙 Minishell ❯ ${HOME }
minishell: ${HOME }: bad substitution
🧙 Minishell ❯ "0-"${ HOME}'-1'
minishell: ${ HOME}: bad substitution
🧙 Minishell ❯ "0-"${USER}'-1'
minishell: 0-jlacerda-1: command not found
```

Item 2) Corrigido

```bash
🧙 Minishell ❯ ${}
minishell: ${}: bad substitution
🧙 Minishell ❯ echo $?
1
🧙 Minishell ❯ 
```
author_association: COLLABORATOR
user: login: jonnathan-ls
created_at: 2025-04-28T01:14:11Z
body: Considerando que este PR introduziu uma chave de identificação quando um token estiver em aspas.

Foi adicionado um commit https://github.com/peda-cos/minishell/pull/56/commits/26fa16650d8c275014dd0ee2fb6380fb054af6f7 para que a extração do delimitador para heredoc ocorra de forma correta (obtendo todo caracter fornecido no valor do delimitador.

Capacidade implementada:

- `<< 'delimiter'`
- `<< "delimiter"`
- `<< $delimiter`
author_association: COLLABORATOR