Add an error interface for arbitrary length messages.

pull/9/head
Joshua Potter 2023-11-25 08:55:02 -07:00
parent 2501754886
commit ea71a0d661
13 changed files with 153 additions and 135 deletions

View File

@ -1,5 +1,11 @@
# bootstrap
TODO:
- [ ] Make free-ing data consistent with null pointers.
- [ ] Make error handling consistent.
- [ ] Add documentation throughout (ownership, docstrings, etc.).
- [ ] Have main.c return status code of run.sh.
CLI utility for initializing projects in reproducible ways.
## Overview

View File

@ -1,6 +1,8 @@
#ifndef _BOOTSTRAP_CONFIG_H
#define _BOOTSTRAP_CONFIG_H
#include "error.h"
struct Config {
// The directory the `bootstrap` command was run from.
// OWNERSHIP: Does not own this pointer.
@ -13,20 +15,7 @@ struct Config {
const char *target;
};
enum ConfigError {
// The $CWD could not be retrieved.
CE_ENV_CWD_INVALID = 1,
// The $BOOTSTRAP_ROOT_DIR environment variable is empty.
CE_ENV_ROOT_DIR_INVALID,
// The target argument is invalid.
CE_TARGET_INVALID,
// No spec with the given name was found.
CE_TARGET_NOT_FOUND,
// The spec is not a directory.
CE_TARGET_NOT_DIR,
};
enum ConfigError config_load(
struct Error *config_load(
const char *cwd,
const char *root_dir,
const char *target,

View File

@ -1,12 +1,15 @@
#ifndef _BOOTSTRAP_ERROR_H
#define _BOOTSTRAP_ERROR_H
enum ErrorCode {
SUCCESS = 0,
#include <stdlib.h>
ERROR_CONFIG_ENV_CWD_INVALID,
#include "string_buf.h"
enum ErrorCode {
ERROR_CONFIG_ENV_CWD_INVALID = 1,
ERROR_CONFIG_ENV_ROOT_DIR_INVALID,
ERROR_CONFIG_TARGET_NOT_FOUND,
ERROR_CONFIG_TARGET_INVALID,
ERROR_CONFIG_TARGET_NOT_DIR,
ERROR_PARSER_SPEC_JSON_CANNOT_OPEN,
@ -27,4 +30,55 @@ struct Error {
const char *message;
};
static inline struct Error *priv_error_new(
enum ErrorCode code, size_t n, const char *messages[static n]
) {
struct Error *e = malloc(sizeof(struct Error));
e->code = code;
struct StringBuf *sb = string_buf_new(1024);
for (int i = 0; i < n; ++i) {
string_buf_sappend(sb, messages[i]);
}
e->message = string_buf_convert(sb);
return e;
}
// clang-format off
/**
Return the number of elements of `__VA_ARGS__`.
Take the `__VA_ARGS__` list and append a list of decreasing numbers
31, 30, ..., 0. Then, by using ALEN0, return the 31st element of that list.
*/
#define ALEN(...) \
ALEN0( \
__VA_ARGS__, \
0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, \
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, \
0x0E, 0x0F, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, \
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00)
#define ALEN0( \
_00, _01, _02, _03, _04, _05, _06, _07, \
_08, _09, _0A, _0B, _0C, _0D, _0F, _0E, \
_10, _11, _12, _13, _14, _15, _16, _17, \
_18, _19, _1A, _1B, _1C, _1D, _1E, _1F, \
...) _1F
/**
Create a new `struct Error` instance.
*/
#define ERROR_NEW(code, ...) \
ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__)
#define ERROR_NEW0(code, nargs, ...) \
priv_error_new( \
(code), (nargs + 1), (const char * [nargs + 1]){__VA_ARGS__, "\n"} \
)
// clang-format on
void error_free(struct Error *error);
#endif /* _BOOTSTRAP_ERROR_H */

View File

@ -9,7 +9,13 @@
* A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates
* additional space as needed.
*/
struct StringBuf;
struct StringBuf {
char *buf;
// The length of @buf excluding `NUL`.
size_t size;
// The allocated size of @buf including `NUL`.
size_t _capacity;
};
/**
* Create a new `StringBuf` instance.

25
main.c
View File

@ -5,6 +5,7 @@
#include "cJSON.h"
#include "config.h"
#include "error.h"
#include "evaluator.h"
#include "parser.h"
#include "validator.h"
@ -16,26 +17,11 @@ static int run(const char *root_dir, const char *target) {
root_dir = getenv("BOOTSTRAP_ROOT_DIR");
}
struct Error *error = 0;
struct Config *config = 0;
switch (config_load(cwd, root_dir, target, &config)) {
case CE_ENV_CWD_INVALID:
fprintf(stderr, "Could not retrieve $CWD.\n");
goto cleanup_cwd;
case CE_ENV_ROOT_DIR_INVALID:
fprintf(
stderr,
"Either supply a value to `-d` or specify the $BOOTSTRAP_ROOT_DIR "
"environment variable.\n"
);
goto cleanup_cwd;
case CE_TARGET_INVALID:
fprintf(stderr, "Spec `%s` is invalid.\n", target);
goto cleanup_cwd;
case CE_TARGET_NOT_FOUND:
fprintf(stderr, "Spec `%s` not found.\n", target);
goto cleanup_cwd;
case CE_TARGET_NOT_DIR:
fprintf(stderr, "Spec `%s` is not a directory.\n", target);
if ((error = config_load(cwd, root_dir, target, &config))) {
fprintf(stderr, "%s", error->message);
goto cleanup_cwd;
}
@ -100,6 +86,7 @@ cleanup_config:
cleanup_cwd:
free(cwd);
error_free(error);
return retval;
}

View File

@ -1,3 +0,0 @@
#!/usr/bin/env bash
cp -r template/* "$OUT"

View File

@ -1,35 +0,0 @@
{
description = ''
An opinionated clang flake.
'';
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
codelldb = pkgs.writeShellScriptBin "codelldb" ''
exec ${pkgs.vscode-extensions.vadimcn.vscode-lldb}/share/vscode/extensions/vadimcn.vscode-lldb/adapter/codelldb "$@"
'';
in
{
devShells.default = pkgs.mkShell.override {
# https://nixos.wiki/wiki/Using_Clang_instead_of_GCC
stdenv = pkgs.clangStdenv;
} {
packages = with pkgs; [
bear
clang-tools
codelldb
];
buildInputs = with pkgs; [
ncurses
];
};
});
}

View File

@ -1,3 +0,0 @@
#include <stdlib.h>
int main(int argc, char **argv) { return EXIT_SUCCESS; }

2
specs/clang/template/.envrc → specs/test/run.sh Normal file → Executable file
View File

@ -1,3 +1,3 @@
#!/usr/bin/env bash
use flake
echo "hello world"

View File

@ -1,5 +1,6 @@
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
@ -7,41 +8,48 @@
#include "path.h"
enum ConfigError config_load(
struct Error *config_load(
const char *cwd,
const char *root_dir,
const char *target,
struct Config **config
) {
if (cwd == 0) {
return CE_ENV_CWD_INVALID;
return ERROR_NEW(ERROR_CONFIG_ENV_CWD_INVALID, "Could not retrieve $CWD.");
}
if (root_dir == 0) {
return CE_ENV_ROOT_DIR_INVALID;
return ERROR_NEW(
ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "No specified root directory."
);
}
if (target == 0) {
return CE_TARGET_INVALID;
assert(target);
// Check if the specified directory exists.
const char *segments[] = {root_dir, target};
char *filepath =
join_path_segments(sizeof(segments) / sizeof(char *), segments);
struct stat sb;
int stat_res = stat(filepath, &sb);
struct Error *error = 0;
if (stat_res == -1) {
if (errno == ENOENT) {
error = ERROR_NEW(
ERROR_CONFIG_TARGET_NOT_FOUND, "Spec ", filepath, " not found."
);
} else {
error = ERROR_NEW(
ERROR_CONFIG_TARGET_INVALID, "Spec ", filepath, " is invalid."
);
}
goto cleanup;
}
{ // Check if the specified directory exists.
struct stat sb;
const char *segments[] = {root_dir, target};
char *filepath =
join_path_segments(sizeof(segments) / sizeof(char *), segments);
int stat_res = stat(filepath, &sb);
free(filepath);
if (stat_res == -1) {
if (errno == ENOENT) {
return CE_TARGET_NOT_FOUND;
}
return CE_TARGET_INVALID;
}
if (!S_ISDIR(sb.st_mode)) {
return CE_TARGET_NOT_DIR;
}
if (!S_ISDIR(sb.st_mode)) {
error = ERROR_NEW(
ERROR_CONFIG_TARGET_NOT_DIR, "Spec ", filepath, " is not a directory."
);
goto cleanup;
}
*config = malloc(sizeof(struct Config));
@ -49,7 +57,9 @@ enum ConfigError config_load(
(*config)->root_dir = root_dir;
(*config)->target = target;
return 0;
cleanup:
free(filepath);
return error;
}
void config_free(struct Config *config) {

10
src/error.c Normal file
View File

@ -0,0 +1,10 @@
#include "error.h"
void error_free(struct Error *error) {
if (!error) {
return;
}
free((void *)error->message);
free(error);
error = 0;
}

View File

@ -5,14 +5,6 @@
#include <stdlib.h>
#include <string.h>
struct StringBuf {
char *buf;
// The length of @buf excluding `NUL`.
size_t size;
// The allocated size of @buf including `NUL`.
size_t _capacity;
};
struct StringBuf *string_buf_new(size_t capacity) {
struct StringBuf *sb = malloc(sizeof(struct StringBuf));
sb->buf = calloc(capacity, sizeof(char));
@ -27,12 +19,6 @@ size_t string_buf_size(struct StringBuf *sb) {
return sb->size;
}
const char *string_buf_value(struct StringBuf *sb) {
assert(sb);
return sb->buf;
}
void string_buf_cappend(struct StringBuf *sb, char c) {
assert(sb);

View File

@ -36,15 +36,17 @@ static void test_config_teardown(struct TestConfigFixture *fixture) {
static void test_config_load_invalid_args() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0;
struct Config *config = 0;
enum ConfigError retval = 0;
retval = config_load(0, fixture->root_dir, fixture->target, &config);
sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0");
retval = config_load(fixture->cwd, 0, fixture->target, &config);
sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0");
retval = config_load(fixture->cwd, fixture->root_dir, 0, &config);
sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0");
error = config_load(0, fixture->root_dir, fixture->target, &config);
sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0");
error_free(error);
error = config_load(fixture->cwd, 0, fixture->target, &config);
sput_fail_unless(
error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0"
);
error_free(error);
test_config_teardown(fixture);
}
@ -52,10 +54,14 @@ static void test_config_load_invalid_args() {
static void test_config_load_spec_not_found() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0;
struct Config *config = 0;
enum ConfigError retval =
config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
sput_fail_unless(retval == CE_TARGET_NOT_FOUND, "target not found");
error = config_load(fixture->cwd, fixture->root_dir, "not_found", &config);
sput_fail_unless(
error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found"
);
error_free(error);
test_config_teardown(fixture);
}
@ -63,10 +69,14 @@ static void test_config_load_spec_not_found() {
static void test_config_load_spec_not_dir() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0;
struct Config *config = 0;
enum ConfigError retval =
config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
sput_fail_unless(retval == CE_TARGET_NOT_DIR, "target not dir");
error = config_load(fixture->cwd, fixture->root_dir, "not_dir", &config);
sput_fail_unless(
error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir"
);
error_free(error);
test_config_teardown(fixture);
}
@ -74,11 +84,12 @@ static void test_config_load_spec_not_dir() {
static void test_config_load_success() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0;
struct Config *config = 0;
enum ConfigError retval =
config_load(fixture->cwd, fixture->root_dir, fixture->target, &config);
sput_fail_unless(retval == 0, "config_load() success");
error =
config_load(fixture->cwd, fixture->root_dir, fixture->target, &config);
sput_fail_unless(error == 0, "config_load() success");
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd");
sput_fail_unless(
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir"