ESH

Executive Shell
git clone http://git.omkov.net/ESH
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-12-28 01:53:40
Commit4a1246f2dd8f46c225d1bc59a98fa1a7d38497cb
Parent59a2eecffbf9c4899c9539de4938ffe8f3932b49

Add eval builtin

Diffstat

M CMakeLists.txt | 3 ++-
M src/bltn.c | 2 ++
M src/builtin/cd.c | 1 +
A src/builtin/eval.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
M src/eval.c | 4 ++--
M src/exec.c | 23 ++++++++++++-----------
M src/lex.c | 20 ++++++++++----------
R src/lineread.c -> src/lineread/lineread.c | 62 ++++++++++++++++++++++++++++----------------------------------
R src/lineread.h -> src/lineread/lineread.h | 0
M src/main.c | 28 ++++++++++++++--------------
M src/parse.c | 20 ++++++++++----------
M src/util/optget.c | 20 ++++++++++----------
M src/util/stack.c | 2 +-

13 files changed, 144 insertions, 93 deletions

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2e06757..c9e0e5a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,9 +3,10 @@ project(ESH VERSION 0.0.0 LANGUAGES C)
 
 set(CMAKE_C_STANDARD 23)
 set(CMAKE_C_STANDARD_REQUIRED TRUE)
+
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
+add_compile_definitions(PROJECT_VERSION="${PROJECT_VERSION}")
 
 file(GLOB_RECURSE SRC CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.c)
-add_compile_definitions(PROJECT_VERSION="${PROJECT_VERSION}")
 
 add_executable(esh ${SRC})
diff --git a/src/bltn.c b/src/bltn.c
index e8def8d..0858e7a 100644
--- a/src/bltn.c
+++ b/src/bltn.c
@@ -16,11 +16,13 @@ static int bltn_help(int, char *[]) { fputs(version, stdout); return 0; }
 static int bltn_true(int, char *[]) { return 0; }
 
 extern int bltn_cd(int, char *[]);
+extern int bltn_eval(int, char *[]);
 
 static int getret(int, char *[]) { printf("%d\n", _ret); return 0; }
 
 bltn bltns[] = {
 	{ "cd",    &bltn_cd    },
+	{ "eval",  &bltn_eval  },
 	{ "exit",  &bltn_exit  },
 	{ "false", &bltn_false },
 	{ "help",  &bltn_help  },
diff --git a/src/builtin/cd.c b/src/builtin/cd.c
index 32fda42..87c9fe6 100644
--- a/src/builtin/cd.c
+++ b/src/builtin/cd.c
@@ -52,4 +52,5 @@ static const char *const help =
 	"  -L         Handle the operand dot-dot logically\n"
 	"  -P         Handle the operand dot-dot physically\n"
 	"  --help     Display help information\n"
+	"  --version  Display version information\n"
 ;
diff --git a/src/builtin/eval.c b/src/builtin/eval.c
new file mode 100644
index 0000000..1ee3473
--- /dev/null
+++ b/src/builtin/eval.c
@@ -0,0 +1,52 @@
+// Copyright (C) 2023, Jakob Wakeling
+// All rights reserved.
+
+#include "../eval.h"
+#include "../util/optget.h"
+#include "../util/util.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static const char *const help;
+extern const char *const version; /* main.c */
+
+int bltn_eval(int ac, char *av[]) {
+	struct opt opt = OPTGET_INIT; opt.str = ""; opt.lops = (struct lop[]){
+		{ "help",    ARG_NUL, 256 },
+		{ "version", ARG_NUL, 257 },
+		{ NULL, 0, 0 },
+	};
+	
+	for (int c; (c = optget(&opt, av, 0)) != -1;) {
+		switch (c) {
+			case 256: { fputs(help, stdout);    } return 0;
+			case 257: { fputs(version, stdout); } return 0;
+			default: {} return -1;
+		}
+	}
+	
+	char *args, *dest; u64 size = 0;
+	
+	for (u64 i = 0; i < ac - opt.ind; i += 1) {
+		size += (strlen(av[opt.ind + i]) + 1);
+	}
+	
+	args = xcalloc(size, sizeof (*args)); dest = args;
+	
+	for (u64 i = 0; i < ac - opt.ind; i += 1) {
+		if (i != 0) { strncat(dest, " ", 1); dest += 1; }
+		strcat(dest, av[opt.ind + i]); dest += strlen(av[opt.ind + i]);
+	}
+	
+	eval(args, size); return 0;
+}
+
+static const char *const help =
+	"eval - Execute arguments as a command\n"
+	"Usage:\n"
+	"  eval [argument...]\n"
+	"Options:\n"
+	"  --help     Display help information\n"
+	"  --version  Display version information\n"
+;
diff --git a/src/eval.c b/src/eval.c
index 742b8c1..6db9ab2 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -13,11 +13,11 @@ bool Eflag, pflag;
 void eval(char *src, u64 len) {
 	lex l = lex_init(src, len);
 	if (Eflag) { lex_debug(&l); return; }
-	
+
 	for (ast *a; l.t.k != TK_EOF; ast_free(a)) {
 		a = parse(&l); if (!a) { return; }
 		if (pflag) { ast_debug(a, 0); continue; }
-		
+
 		_ret = execute(a, STDIN_FILENO, NULL);
 	}
 }
diff --git a/src/exec.c b/src/exec.c
index 50ff146..240628b 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -12,6 +12,7 @@
 #include <unistd.h>
 
 #include <errno.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -34,16 +35,16 @@ s32 execute(ast *a, int fdi, int fd[2]) {
 static s32 execute_comm(ast *a, int fdi, int fd[2]) {
 	/* If the command is null, then do nothing */
 	if (!a->s) { return _ret; }
-	
+
 	/* Handle builtins */
 	for (bltn *b = bltns; b->name; b += 1) {
 		if (strcmp(a->s, b->name) == 0) {
 			return b->fn(a->c.al - 1, a->c.a);
 		}
 	}
-	
+
 	pid_t pid; int status, rin = -1, rout = -1, rerr = -1;
-	
+
 	/* Open redirect files. */
 	if (a->rin.s && (rin = open(a->rin.s, O_RDONLY)) == -1) {
 		log_warn("%s: %s", a->rin.s, strerror(errno)); return -1;
@@ -56,22 +57,22 @@ static s32 execute_comm(ast *a, int fdi, int fd[2]) {
 		if (rin != -1) { close(rin); } if (rout != -1) { close(rout); }
 		log_warn("%s: %s", a->rerr.s, strerror(errno)); return -1;
 	}
-	
+
 	char **av = a->c.a;
-	
+
 	/* Fork and exec the child process */
 	if ((pid = fork()) == 0) {
 		signal(SIGINT, SIG_DFL);
-		
+
 		if (rin != -1) { dup2(rin, STDIN_FILENO); }
 		else { dup2(fdi, STDIN_FILENO); }
-		
+
 		if (rout != -1) { dup2(rout, STDOUT_FILENO); }
 		else if (fd) { dup2(fd[1], STDOUT_FILENO); }
 		if (fd && fd[0]) { close(fd[0]); }
-		
+
 		if (rerr != -1) { dup2(rerr, STDERR_FILENO); }
-		
+
 		if (execvp((char *)av[0], (char **)av) == -1) {
 			if (errno == ENOENT) { log_warn("%s: Command not found", av[0]); }
 			else { perror((char *)av[0]); } exit(1);
@@ -87,11 +88,11 @@ static s32 execute_comm(ast *a, int fdi, int fd[2]) {
 			waitpid(pid, &status, 0);
 		}
 	}
-	
+
 	if (rin != -1) { close(rin); }
 	if (rout != -1) { close(rout); }
 	if (rerr != -1) { close(rerr); }
-	
+
 	return WEXITSTATUS(status);
 }
 
diff --git a/src/lex.c b/src/lex.c
index 44d54f1..c5945ca 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -33,20 +33,20 @@ tok lex_peek(lex *l) { return T; }
 tok lex_next(lex *l) {
 	if (T.k == TK_EOF) { return T; }
 	tok t = T; T = (tok){ .k = TK_VOID };
-	
+
 	/* Skip null characters and whitespace */
 	skip:; for (; P != Q && (!P[0] || is_space(P[0])); P += 1);
-	
+
 	/* Return the current token immediately if EOF or END is reached */
 	if (P == Q) { T.k = TK_EOF; return t; }
 	if (P[0] == '\n' || P[0] == ';') { P += 1; T.k = TK_END; return t; }
-	
+
 	/* Skip comments */
 	if (P[0] == '#') {
 		for (P += 1; P != Q && P[0] != '\n'; P += 1);
 		if (P[0] == '\n') { P += 1; } goto skip;
 	}
-	
+
 	switch (P[0]) {
 	case '|': { T.k = TK_PIPE; P += 1; } break;
 	case '<': { T.k = TK_RIN;  P += 1; } break;
@@ -54,14 +54,14 @@ tok lex_next(lex *l) {
 		default:  { T.k = TK_ROUT; P += 1; } break;
 		case '>': { T.k = TK_RAPP; P += 2; } break;
 	} break;
-	
+
 	/* Handle words, TODO review quotes and substitutions */
 	default: {
 		stack s = stack_init(sizeof (char), NULL);
-		
+
 		for (; P != Q && P[0] != '\n' && P[0] != ';' && P[0] != ' '; P += 1) {
 			if (P[0] == '|' || P[0] == '<' || P[0] == '>') { break; }
-			
+
 			/* Handle single quotes */
 			else if (P[0] == '\'') for (P += 1;; P += 1) {
 				/* FIXME memory leak upon missing closing ', needs refinement */
@@ -69,15 +69,15 @@ tok lex_next(lex *l) {
 				else if (P[0] == '\'') { break; }
 				else { stack_push(&s, P[0]); }
 			}
-			
+
 			/* Handle all other characters */
 			else { stack_push(&s, P[0]); }
 		}
-		
+
 		T.s = strndup(s.a, s.al); T.k = TK_WORD; stack_free(&s);
 	} break;
 	}
-	
+
 	return t;
 }
 
diff --git a/src/lineread.c b/src/lineread/lineread.c
similarity index 96%
rename from src/lineread.c
rename to src/lineread/lineread.c
index 71050a3..562cc00 100644
--- a/src/lineread.c
+++ b/src/lineread/lineread.c
@@ -1,9 +1,9 @@
 // Copyright (C) 2021, Jakob Wakeling
 // All rights reserved.
 
+#include "../util/log.h"
+#include "../util/util.h"
 #include "lineread.h"
-#include "util/log.h"
-#include "util/util.h"
 
 #include <sys/ioctl.h>
 #include <termios.h>
@@ -16,19 +16,14 @@
 #include <stdlib.h>
 #include <string.h>
 
-typedef struct {
-	char *s; uptr sp, sl, sc;
-	char *prompt; uptr pl, cols;
-} line;
-
-typedef struct {
-	line *a; uptr ap, ah, at, al, ac;
-} hist;
+typedef struct { char *s; uptr sp, sl, sc; } line;
+typedef struct { line *a; uptr ap, ah, at, al, ac; } hist;
 
 static struct termios tco, tcn;
 static bool rawflag = false, ateflag = false;
 
 static hist h = { NULL, 0, 0, 0, 0 };
+static const char *const prompt = "$ ";
 
 static char *linentty(void);
 static char *lineedit(void);
@@ -77,14 +72,14 @@ void linefree(void) { hist_free(&h); }
 static char *linentty(void) {
 	line l; register char *r;
 	l.sp = 0; l.sl = 0; l.sc = 1024;
-	
+
 	if (!(l.s = xmalloc(l.sc * sizeof (*l.s)))) { return NULL; }
-	
+
 	for (register int c; (c = fgetc(stdin));) {
 		if (c == EOF) { if (l.sl) { break; } r = NULL; goto ret; }
 		else { line_push(&l, c); } continue;
 	}
-	
+
 end:;
 	r = strndup(l.s, l.sl);
 ret:;
@@ -96,25 +91,24 @@ ret:;
 /* Dynamically read a line from stdin */
 static char *lineedit(void) {
 	register char *r;
-	
+
 	{
 		if (h.a == NULL) { if (hist_init(&h)) { return NULL; } }
-		
+
 		line m = { NULL, 0, 0, 1024 };
-		m.prompt = "$ "; m.pl = strlen(m.prompt);
-		
-		if (!(m.cols = getcols())) { return NULL; }
+
+		// if (!(m.cols = getcols())) { return NULL; }
 		if (!(m.s = xmalloc(m.sc * sizeof (*m.s)))) { return NULL; } m.s[0] = 0;
-		
+
 		hist_push(&h, m);
 	}
-	
-	tcraw(); fputs(l.prompt, stdout);
-	
+
+	tcraw(); fputs(prompt, stdout);
+
 	for (register int c; (c = fgetc(stdin));) {
 		if (errno) { log_warn("FIXME: %s", strerror(errno)); errno = 0; }
 		// printf("%02X\n", c); continue;
-		
+
 		switch (c) {
 		case '\x01': { line_move_home(&l);        } continue; // CTRL + A
 		case '\x02': { line_move_left(&l);        } continue; // CTRL + B
@@ -162,7 +156,7 @@ static char *lineedit(void) {
 		default:     { line_insert(&l, c);        } continue;
 		}
 	}
-	
+
 end:;
 	r = strndup(l.s, l.sl);
 ret:;
@@ -192,10 +186,10 @@ static void line_esc(line *l, register int c) {
 /* Put stdin into raw mode */
 static inline void tcraw(void) {
 	if (rawflag) { return; }
-	
+
 	tcgetattr(STDIN_FILENO, &tco); tcn = tco;
 	if (!ateflag) { atexit(tcrestore); ateflag = true; }
-	
+
 	/* No break, no CR to NL, no parity check, no strip bit, no flow control */
 	tcn.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
 	/* No post process, 8-bit chars */
@@ -204,7 +198,7 @@ static inline void tcraw(void) {
 	tcn.c_lflag &= ~(ICANON | ECHO | IEXTEN | ISIG);
 	/* Read every byte with no delay */
 	tcn.c_cc[VMIN] = 1; tcn.c_cc[VTIME] = 0;
-	
+
 	tcsetattr(STDIN_FILENO, TCSAFLUSH, &tcn); rawflag = true; return;
 }
 
@@ -228,8 +222,8 @@ static void clearscreen(line *l) {
 /* Refresh line */
 static void line_refresh(line *l) {
 	fputs("\r\x1B[0K", stdout);
-	fputs(l->prompt, stdout); fputs(l->s, stdout);
-	fprintf(stdout, "\r\x1B[%zuC", l->pl + l->sp);
+	fputs(prompt, stdout); fputs(l->s, stdout);
+	fprintf(stdout, "\r\x1B[%zuC", strlen(prompt) + l->sp);
 	return;
 }
 
@@ -312,10 +306,10 @@ static void line_delete(line *l) {
 /* Delete the word preceeding the cursor */
 static void line_delete_word_home(line *l) {
 	size_t p = l->sp;
-	
+
 	for (; l->sp && l->s[l->sp - 1] == ' '; --l->sp);
 	for (; l->sp && l->s[l->sp - 1] != ' '; --l->sp);
-	
+
 	memmove(l->s + l->sp, l->s + p, l->sl - p + 1);
 	l->sl -= p - l->sp; line_refresh(l); return;
 }
@@ -323,10 +317,10 @@ static void line_delete_word_home(line *l) {
 /* Delete the word following the cursor */
 static void line_delete_word_end(line *l) {
 	size_t p = l->sp;
-	
+
 	for (; p != l->sl && l->s[p] == ' '; ++p);
 	for (; p != l->sl && l->s[p] != ' '; ++p);
-	
+
 	memmove(l->s + l->sp, l->s + p, l->sl - p + 1);
 	l->sl -= p - l->sp; line_refresh(l); return;
 }
@@ -386,6 +380,6 @@ static void hist_push(hist *h, line l) {
 		++h->al; h->ap = h->at; if (++h->at == h->ac) { h->at = 0; }
 	}
 	else { h->ap = h->at; if (++h->at == h->ac) { h->at = 0; } h->ah = h->at; }
-	
+
 	free(h->a[h->ap].s); h->a[h->ap] = l; return;
 }
diff --git a/src/lineread.h b/src/lineread/lineread.h
similarity index 100%
rename from src/lineread.h
rename to src/lineread/lineread.h
diff --git a/src/main.c b/src/main.c
index 93ac722..ba0e3ee 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2,7 +2,7 @@
 // All rights reserved.
 
 #include "eval.h"
-#include "lineread.h"
+#include "lineread/lineread.h"
 #include "util/log.h"
 #include "util/optget.h"
 #include "util/util.h"
@@ -25,14 +25,14 @@ bool _loop = true;
 
 int main(int, char *av[]) {
 	struct opt opt = OPTGET_INIT; opt.str = "Ep"; opt.lops = (struct lop[]){
-		{ "help",        ARG_NUL, 256 },
-		{ "version",     ARG_NUL, 257 },
-		{ "debug",       ARG_NUL, 258 },
+		{ "help",    ARG_NUL, 256 },
+		{ "version", ARG_NUL, 257 },
+		{ "debug",   ARG_NUL, 258 },
 		{ NULL, 0, 0 },
 	};
-	
+
 	struct {} args = {};
-	
+
 	for (int c; (c = optget(&opt, av, 1)) != -1;) {
 		switch (c) {
 			case 'E': { Eflag = true;   } break;
@@ -43,19 +43,19 @@ int main(int, char *av[]) {
 			default: {} return -1;
 		}
 	}
-	
+
 	signal(SIGINT, &reset); signal(SIGQUIT, SIG_IGN); signal(SIGTSTP, SIG_IGN);
 	atexit(&linefree);
-	
+
 	do {
 		if (sigsetjmp(jmp, 1)) { fputc('\n', stdout); } jmpflag = true;
-		
-		char *line = lineread();
-		if (!line) { if (errno) { log_warn("lineread: %s", strerror(errno)); } break; }
-		
-		eval(line, strlen(line)); free(line);
+
+		char *l = lineread();
+		if (!l) { if (errno) { log_warn("lineread: %s", strerror(errno)); } break; }
+
+		eval(l, strlen(l)); free(l);
 	} while (_loop);
-	
+
 	return __warned;
 }
 
diff --git a/src/parse.c b/src/parse.c
index 771cee3..6b40c4a 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -35,13 +35,13 @@ static ast *parse_pipe(lex *l, ast *lhs);
 extern ast *parse(lex *l) {
 	for (; T.k == TK_END; lex_next(l));
 	if (T.k == TK_EOF) { return NULL; }
-	
+
 	if (T.k != TK_WORD) {
 		log_warn("Unexpected \"%s\", was expecting a word", tok_ks[T.k]); return NULL;
 	}
-	
+
 	ast *lhs = parse_comm(l);
-	
+
 	for (;;) switch (lex_next(l).k) {
 	case TK_PIPE: { lhs = parse_pipe(l, lhs); } continue;
 	default: {} return lhs;
@@ -51,13 +51,13 @@ extern ast *parse(lex *l) {
 /* Parse a command statement. */
 static ast *parse_comm(lex *l) {
 	assert(T.k == TK_WORD);
-	
+
 	ast *a = ast_init(AK_COMM);
 	a->c = stack_init(sizeof (char *), &free);
-	
+
 	/* Push each command argument onto the child stack */
 	stack_push(&a->c, (a->s = lex_next(l).s));
-	
+
 	for (;;) switch (lex_peek(l).k) {
 	case TK_WORD: { stack_push(&a->c, lex_next(l).s); } continue;
 	case TK_RIN: {
@@ -85,9 +85,9 @@ static ast *parse_pipe(lex *l, ast *lhs) {
 /* Print parser debug output. */
 extern void ast_debug(ast *a, u64 indent) {
 	for (u64 i = 0; i != indent; i += 1) { printf("\t"); }
-	
+
 	printf("%s: %s", ast_ks[a->k], a->s);
-	
+
 	if (a->k == AK_COMM) {
 		for (u64 i = 1; i != a->c.al - 1; i += 1) {
 			printf(" %s", ((char **)a->c.a)[i]);
@@ -95,9 +95,9 @@ extern void ast_debug(ast *a, u64 indent) {
 		if (a->rin.s) { printf(" < %s", a->rin.s); }
 		if (a->rout.s) { printf(a->rout.app ? " >> %s" : " > %s", a->rout.s); }
 	}
-	
+
 	printf("\n");
-	
+
 	if (a->lc) { ast_debug(a->lc, indent + 1); }
 	if (a->rc) { ast_debug(a->rc, indent + 1); }
 }
diff --git a/src/util/optget.c b/src/util/optget.c
index a21e736..893d01e 100644
--- a/src/util/optget.c
+++ b/src/util/optget.c
@@ -19,20 +19,20 @@ int optget(struct opt *opt, char *av[], int flags) {
 		if (!cur) { opt->ind -= opt->nop; opt->nop = 0; return -1; }
 	}
 	else if (!cur || (cur[0] != '-' || cur[1] == 0)) { return -1; }
-	
+
 	int optind = opt->ind, optret;
-	
+
 	if (cur[1] == '-') {
 		if (cur[2] == 0) { if (opt->nop) {
 				permute(av, opt->ind++, opt->nop);
 				opt->ind -= opt->nop; opt->nop = 0;
 			} else { ++opt->ind; } return -1;
 		}
-		
+
 		int optend, lop; optret = '?'; opt->opt = 0; opt->lop = cur;
 		if (!opt->lops) { goto nol; }
 		for (optend = 2; cur[optend] != '=' && cur[optend] != 0; ++optend);
-		
+
 		for (lop = 0; opt->lops[lop].str; ++lop) {
 			if (strncmp(&cur[2], opt->lops[lop].str, (size_t)optend - 2) == 0) {
 				if (!opt->lops[lop].str[optend - 2]) {
@@ -40,7 +40,7 @@ int optget(struct opt *opt, char *av[], int flags) {
 				}
 			}
 		}
-		
+
 		if (opt->lops[lop].arg > ARG_NUL) {
 			if (cur[optend]) { opt->arg = &cur[optend + 1]; }
 			else if (av[opt->ind + 1]) { opt->arg = av[++opt->ind]; }
@@ -50,13 +50,13 @@ int optget(struct opt *opt, char *av[], int flags) {
 			}
 		}
 		else { opt->arg = NULL; }
-		
+
 nol:	opt->pos = 0;
 	}
 	else {
 		optret = opt->opt = cur[opt->pos++]; opt->lop = NULL;
 		const char *optchr = strchr(opt->str, opt->opt);
-		
+
 		if (!optchr) { optret = '?'; }
 		else if (optchr[1] == ':') {
 			if (cur[opt->pos]) { opt->arg = &cur[opt->pos]; }
@@ -66,14 +66,14 @@ nol:	opt->pos = 0;
 		}
 		else { opt->arg = NULL; }
 	}
-	
+
 	if (!opt->pos || !cur[opt->pos]) {
 		++opt->ind; opt->pos = 1;
 		if (opt->nop) for (; optind < opt->ind; ++optind) {
 			permute(av, optind, opt->nop);
 		}
 	}
-	
+
 	if (optret == '?' && opt->str[0] != ':') {
 		if (opt->opt) { fprintf(stderr, "%c: invalid option", opt->opt); }
 		else if (opt->lop) { fprintf(stderr, "%s: invalid option", opt->lop); }
@@ -82,7 +82,7 @@ nol:	opt->pos = 0;
 		if (opt->opt) { fprintf(stderr, "%c: option requires argument", opt->opt); }
 		else if (opt->lop) { fprintf(stderr, "%s: option requires argument", opt->lop); }
 	}
-	
+
 	return optret;
 }
 
diff --git a/src/util/stack.c b/src/util/stack.c
index a56b71f..17eed0f 100644
--- a/src/util/stack.c
+++ b/src/util/stack.c
@@ -18,7 +18,7 @@ void stack_free(stack *s) {
 		if (s->free) for (u64 i = 0; i < s->al; i += 1) {
 			void *e; memcpy(&e, s->a + i * s->el, s->el); s->free(e);
 		}
-		
+
 		free(s->a);
 	}
 }