Add documentation for `config`, `dyn_array`, `error`, and `string_buf`.

pull/9/head
Joshua Potter 2023-11-25 11:51:42 -07:00
parent 16e971af0b
commit 7cc11c86e3
13 changed files with 3044 additions and 103 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
.cache/ .cache/
.direnv/ .direnv/
compile_commands.json
bootstrap bootstrap
compile_commands.json
docs/
test/runner test/runner

2804
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@
TODO: TODO:
- [ ] Add documentation throughout (ownership, docstrings, etc.). - [ ] Add documentation throughout (ownership, docstrings, etc.).
- [ ] Add evaluator tests.
- [ ] Color output to console.
CLI utility for initializing projects in reproducible ways. CLI utility for initializing projects in reproducible ways.

View File

@ -26,6 +26,7 @@
bear bear
clang-tools clang-tools
codelldb codelldb
doxygen
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
ncurses ncurses

View File

@ -1,27 +1,59 @@
/**
@file
@brief A `spec` configuration.
*/
#ifndef _BOOTSTRAP_CONFIG_H #ifndef _BOOTSTRAP_CONFIG_H
#define _BOOTSTRAP_CONFIG_H #define _BOOTSTRAP_CONFIG_H
#include "error.h" #include "error.h"
/**
@brief A collection of parameters that identify a `spec`.
Each member of the @ref Config is expected to outlive the @ref Config instance
itself.
*/
struct Config { struct Config {
// The directory the `bootstrap` command was run from. /// The directory the `bootstrap` command was run from. Does not take
// OWNERSHIP: Does not own this pointer. /// ownership of this pointer.
const char *cwd; const char *cwd;
// The root directory housing our specs. /// The root directory housing our specs. Does not take ownership of this
// OWNERSHIP: Does not own this pointer. /// pointer.
const char *root_dir; const char *root_dir;
// The name of the spec we want to bootstrap. /// The name of the spec we want to bootstrap. Does not take ownership of
// OWNERSHIP: Does not own this pointer. /// this pointer.
const char *target; const char *target;
}; };
struct Error *config_load( /**
@brief Create a new @ref Config instance.
@param cwd
The current working directory the `bootstrap` command was invoked from.
@param root_dir
An absolute path to the collection of specs `bootstrap` will search through.
@param target
The `spec` that should be bootstrapped.
@return
A new @ref Config instance. The caller takes ownership of this value.
@see config_free
*/
struct Error *config_new(
const char *cwd, const char *cwd,
const char *root_dir, const char *root_dir,
const char *target, const char *target,
struct Config **config struct Config **config
); );
/**
@brief Deallocates a previously allocated @ref Config instance.
@param config
A pointer to a @ref Config instance. If null, this function is a no-op.
@see config_new
*/
void config_free(struct Config *config); void config_free(struct Config *config);
#endif /* BOOTSTRAP_CONFIG_H */ #endif /* BOOTSTRAP_CONFIG_H */

View File

@ -1,22 +1,72 @@
/**
@file
@brief Dynamic `void*` arrays.
*/
#ifndef _BOOTSTRAP_DYN_ARRAY_H #ifndef _BOOTSTRAP_DYN_ARRAY_H
#define _BOOTSTRAP_DYN_ARRAY_H #define _BOOTSTRAP_DYN_ARRAY_H
#include <stdlib.h> #include <stdlib.h>
/**
@brief A dynamic array of generic pointers.
A `void*` wrapper that grows larger as needed. If more space is needed during an
append operation, the capacity of the internal array is doubled.
*/
struct DynArray { struct DynArray {
/// The underlying `void*` pointer.
void **buf; void **buf;
// The size of @buf excluding `NUL`. /// The size of @ref DynArray.buf.
size_t size; size_t size;
// The allocated size of @buf including `NUL`. // The allocated size of @ref DynArray.buf.
size_t _capacity; size_t _capacity;
}; };
/**
@brief Create a new @ref DynArray instance.
@param capacity
The initial size of the internal array. To avoid too many reallocations, aim to
make this value large enough to accommodate the eventual size of the buffer.
@see dyn_array_free
*/
struct DynArray *dyn_array_new(size_t capacity); struct DynArray *dyn_array_new(size_t capacity);
/**
@brief Returns the number of items contained in the internal buffer.
@param a
A valid pointer to a @ref DynArray instance.
@return
The number of items contained in the internal buffer.
*/
size_t dyn_array_size(struct DynArray *a); size_t dyn_array_size(struct DynArray *a);
/**
@brief Appends a new item onto the end of the internal @ref DynArray.buf.
This function takes ownership of @p item and will attempt to `free` the
parameter when the @ref DynArray is `free`'d. For this reason, only provide
entries that have been allocated on the heap.
@param a
A valid pointer to a @ref DynArray instance.
@param item
A valid pointer to a heap-allocated object.
@see dyn_array_free
*/
void dyn_array_push(struct DynArray *a, void *item); void dyn_array_push(struct DynArray *a, void *item);
/**
@brief Deallocates a previously allocated @ref DynArray instance.
@param a
A pointer to a @ref DynArray instance. If null, this function is a no-op.
@see dyn_array_new
*/
void dyn_array_free(struct DynArray *a); void dyn_array_free(struct DynArray *a);
#endif /* _BOOTSTRAP_DYN_ARRAY_H */ #endif /* _BOOTSTRAP_DYN_ARRAY_H */

View File

@ -1,3 +1,7 @@
/**
@file
@brief Error handling.
*/
#ifndef _BOOTSTRAP_ERROR_H #ifndef _BOOTSTRAP_ERROR_H
#define _BOOTSTRAP_ERROR_H #define _BOOTSTRAP_ERROR_H
@ -5,27 +9,55 @@
#include "string_buf.h" #include "string_buf.h"
/**
@brief The various error codes produced by `bootstrap`.
*/
enum ErrorCode { enum ErrorCode {
/// Could not retrieve the value of `$CWD`.
ERROR_CONFIG_ENV_CWD_INVALID = 1, ERROR_CONFIG_ENV_CWD_INVALID = 1,
/// No root directory was specified. This must either be set via the command
/// line flag `-d` or environment variable `$BOOTSTRAP_ROOT_DIR`. If both are
/// provided, the command line flag takes priority.
ERROR_CONFIG_ENV_ROOT_DIR_INVALID, ERROR_CONFIG_ENV_ROOT_DIR_INVALID,
/// There is no file at the location corresponding to the specified spec.
ERROR_CONFIG_TARGET_NOT_FOUND, ERROR_CONFIG_TARGET_NOT_FOUND,
/// For generic reasons, `bootstrap` could not access the file at the provided
/// spec.
ERROR_CONFIG_TARGET_INVALID, ERROR_CONFIG_TARGET_INVALID,
/// The file at the location corresponding to the provided spec is not a
/// directory.
ERROR_CONFIG_TARGET_NOT_DIR, ERROR_CONFIG_TARGET_NOT_DIR,
/// For generic reasons, `bootstrap` could not access the `spec.json` file.
ERROR_PARSER_SPEC_JSON_INVALID, ERROR_PARSER_SPEC_JSON_INVALID,
/// The `spec.json` file is not valid JSON.
ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX, ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
/// The top-level JSON object of the `spec.json` file is not an object.
ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT, ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT,
/// A field in `spec.json` is not an object.
ERROR_VALIDATOR_FIELD_NOT_OBJECT, ERROR_VALIDATOR_FIELD_NOT_OBJECT,
/// The `type` of a `spec.json` field is not a string.
ERROR_VALIDATOR_FIELD_TYPE_INVALID, ERROR_VALIDATOR_FIELD_TYPE_INVALID,
/// The `type` of a `spec.json` field does not correspond to a known prompt
/// type.
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN, ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
/// The `prompt` of a `spec.json` field is not a string.
ERROR_VALIDATOR_FIELD_PROMPT_INVALID, ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
/// The `run.sh` file could not be found.
ERROR_EVALUATOR_RUN_SH_NOT_FOUND, ERROR_EVALUATOR_RUN_SH_NOT_FOUND,
/// The `run.sh` file is not executable.
ERROR_EVALUATOR_RUN_SH_NOT_EXEC, ERROR_EVALUATOR_RUN_SH_NOT_EXEC,
/// A user response to a prompt is not valid.
ERROR_EVALUATOR_RESPONSE_INVALID, ERROR_EVALUATOR_RESPONSE_INVALID,
}; };
/**
@brief A `bootstrap` error.
@see ERROR_NEW
*/
struct Error { struct Error {
enum ErrorCode code; enum ErrorCode code;
const char *message; const char *message;
@ -47,10 +79,13 @@ static inline struct Error *priv_error_new(
// clang-format off // clang-format off
/** /**
Return the number of elements of `__VA_ARGS__`. @brief Return the number of elements of `__VA_ARGS__`.
Take the `__VA_ARGS__` list and append a list of decreasing numbers 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. 31, 30, ..., 0.
@return
The number of elements of `__VA_ARGS__`.
*/ */
#define ALEN(...) \ #define ALEN(...) \
ALEN0( \ ALEN0( \
@ -68,7 +103,17 @@ Take the `__VA_ARGS__` list and append a list of decreasing numbers
...) _1F ...) _1F
/** /**
Create a new `struct Error` instance. @brief Creates a new @ref Error instance.
It is the responsibility of the caller to free the @ref Error instance.
@param __VA_ARGS__
Allows supplying up to 31 `const char*` instances. These arguments will be
concatenated together and supplied to the new @ref Error instance.
@return
A new @ref Error instance. The caller takes ownership of this value.
@see error_free
*/ */
#define ERROR_NEW(code, ...) \ #define ERROR_NEW(code, ...) \
ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__) ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__)
@ -80,6 +125,14 @@ Create a new `struct Error` instance.
// clang-format on // clang-format on
/**
@brief Deallocates a previously allocated @ref Error isntance.
@param error
A pointer to a @ref Error instance. If null, this function is a no-op.
@see ERROR_NEW
*/
void error_free(struct Error *error); void error_free(struct Error *error);
#endif /* _BOOTSTRAP_ERROR_H */ #endif /* _BOOTSTRAP_ERROR_H */

View File

@ -1,102 +1,100 @@
/**
@file
@brief Dynamic character arrays.
*/
#ifndef _BOOTSTRAP_STRING_BUF_H #ifndef _BOOTSTRAP_STRING_BUF_H
#define _BOOTSTRAP_STRING_BUF_H #define _BOOTSTRAP_STRING_BUF_H
#include <stdlib.h> #include <stdlib.h>
/** /**
* @brief A dynamic character array. @brief A dynamic character array.
*
* A `char*` wrapper. Appending `char`s or NUL-terminated strings allocates A `char*` wrapper that grows larger as needed. Supports appending individual
* additional space as needed. `char`s or NUL-terminated strings. If more space is needed during an append
*/ operation, the capacity is doubled repeatedly until the requested data fits.
*/
struct StringBuf { struct StringBuf {
/// The underlying `char*` pointer.
char *buf; char *buf;
// The length of @buf excluding `NUL`. /// The size of @ref StringBuf.buf (excluding the `NUL` character).
size_t size; size_t size;
// The allocated size of @buf including `NUL`. /// The allocated size of @ref StringBuf.buf (including the `NUL` character).
size_t _capacity; size_t _capacity;
}; };
/** /**
* Create a new `StringBuf` instance. @brief Create a new @ref StringBuf instance.
*
* @param capacity @param capacity
* The initial size of the internal array (including the trailing `NUL` The initial size of the internal array (including the trailing `NUL`
* character). To avoid too many reallocations, aim to make this value large character). To avoid too many reallocations, aim to make this value large
* enough to accommodate the size the string is expected to eventually take. enough to accommodate the eventual size of the desired string.
* @return @return
* A new `StringBuf` instance. The caller takes ownership of this value. A new @ref StringBuf instance. The caller takes ownership of this value.
*
* @see string_buf_free @see string_buf_free
*/ */
struct StringBuf *string_buf_new(size_t capacity); struct StringBuf *string_buf_new(size_t capacity);
/** /**
* Return the number of characters contained in the internal buffer. @brief Returns the number of characters contained in the internal buffer
* (excluding the NUL character).
* @param sb
* A valid pointer to a `StringBuf` instance. @param sb
* @return A valid pointer to a @ref StringBuf instance.
* The number of characters contained in the internal buffer. @return
*/ The number of characters contained in the internal buffer.
*/
size_t string_buf_size(struct StringBuf *sb); size_t string_buf_size(struct StringBuf *sb);
/** /**
* Return the internal `NUL`-terminated string buffer. @brief Appends a character to the end of the provided @ref StringBuf.
*
* @param sb
* A valid pointer to a `StringBuf` instance.
* @return
* The internally managed string.
*/
const char *string_buf_value(struct StringBuf *sb);
/** If appending would cause the internal buffer to overflow, doubles the capacity
* Append a character to the end of a `StringBuf`. of the internal array to accommodate.
*
* If appending would cause the internal buffer to overflow, reallocates the @param sb
* internal array to accommodate. A valid pointer to a @ref StringBuf instance.
* @param c
* @param sb The `char` to append to the end of @p sb.
* A valid pointer to a `StringBuf` instance. */
* @param c
* The `char` to append to the end of `sb`.
*/
void string_buf_cappend(struct StringBuf *sb, char c); void string_buf_cappend(struct StringBuf *sb, char c);
/** /**
* Append a `NUL`-terminated string to the end of a `StringBuf`. @brief Appends a NUL-terminated string to the end of a @ref StringBuf.
*
* If appending would cause the internal buffer to overflow, `realloc`s are If appending would cause the internal buffer to overflow, doubles the capacity
* performed internally to accommodate. of the internal array the necessary number of times to accommodate.
*
* @param sb @param sb
* A valid pointer to a `StringBuf` instance. A valid pointer to a @ref StringBuf instance.
* @param s @param s
* The `char*` to append to the end of `sb`. The `char*` to append to the end of @p sb.
*/ */
void string_buf_sappend(struct StringBuf *sb, const char s[static 1]); void string_buf_sappend(struct StringBuf *sb, const char s[static 1]);
/** /**
* Convert a `StringBuf` instance into a `char*`. @brief Converts a @ref StringBuf instance into a `char*`.
*
* @param sb This function frees the memory associated with @p sb.
* A valid pointer to a `StringBuf` instance.
* @return @param sb
* A null pointer if `sb` is null. Otherwise a `NUL`-terminated string A valid pointer to a @ref StringBuf instance.
* corresponding to the value of `sb`. The caller takes ownership of this @return
* value. A null pointer if @p sb is null. Otherwise a NUL-terminated string
*/ corresponding to the value of @p sb. The caller takes ownership of this value.
*/
const char *string_buf_convert(struct StringBuf *sb); const char *string_buf_convert(struct StringBuf *sb);
/** /**
* Deallocate a previously allocated `StringBuf` instance. @brief Deallocates a previously allocated @ref StringBuf instance.
*
* @param sb @param sb
* A valid pointer to a `StringBuf` instance. A pointer to a @ref StringBuf instance. If null, this function is a no-op.
*
* @see string_buf_new @see string_buf_new
*/ */
void string_buf_free(struct StringBuf *sb); void string_buf_free(struct StringBuf *sb);
#endif /* _BOOTSTRAP_STRING_BUF_H */ #endif /* _BOOTSTRAP_STRING_BUF_H */

2
main.c
View File

@ -21,7 +21,7 @@ static int run(const char *root_dir, const char *target) {
struct Error *error = 0; struct Error *error = 0;
struct Config *config = 0; struct Config *config = 0;
if ((error = config_load(cwd, root_dir, target, &config))) { if ((error = config_new(cwd, root_dir, target, &config))) {
fprintf(stderr, "%s", error->message); fprintf(stderr, "%s", error->message);
goto cleanup_cwd; goto cleanup_cwd;
} }

View File

@ -8,7 +8,7 @@
#include "path.h" #include "path.h"
struct Error *config_load( struct Error *config_new(
const char *cwd, const char *cwd,
const char *root_dir, const char *root_dir,
const char *target, const char *target,

View File

@ -18,6 +18,7 @@ size_t dyn_array_size(struct DynArray *a) {
void dyn_array_push(struct DynArray *a, void *item) { void dyn_array_push(struct DynArray *a, void *item) {
assert(a); assert(a);
assert(item);
if (a->size == a->_capacity) { if (a->size == a->_capacity) {
a->_capacity *= 2; a->_capacity *= 2;
a->buf = realloc(a->buf, sizeof(void *) * a->_capacity); a->buf = realloc(a->buf, sizeof(void *) * a->_capacity);

View File

@ -10,10 +10,10 @@ int main(int argc, char *argv[]) {
sput_start_testing(); sput_start_testing();
sput_enter_suite("config"); sput_enter_suite("config");
sput_run_test(test_config_load_invalid_args); sput_run_test(test_config_new_invalid_args);
sput_run_test(test_config_load_spec_not_found); sput_run_test(test_config_new_spec_not_found);
sput_run_test(test_config_load_spec_not_dir); sput_run_test(test_config_new_spec_not_dir);
sput_run_test(test_config_load_success); sput_run_test(test_config_new_success);
sput_enter_suite("dyn_array"); sput_enter_suite("dyn_array");
sput_run_test(test_dyn_array_zero_capacity); sput_run_test(test_dyn_array_zero_capacity);

View File

@ -33,16 +33,16 @@ static void test_config_teardown(struct TestConfigFixture *fixture) {
free(fixture); free(fixture);
} }
static void test_config_load_invalid_args() { static void test_config_new_invalid_args() {
struct TestConfigFixture *fixture = test_config_setup(); struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0; struct Error *error = 0;
struct Config *config = 0; struct Config *config = 0;
error = config_load(0, fixture->root_dir, fixture->target, &config); error = config_new(0, fixture->root_dir, fixture->target, &config);
sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0"); sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0");
error_free(error); error_free(error);
error = config_load(fixture->cwd, 0, fixture->target, &config); error = config_new(fixture->cwd, 0, fixture->target, &config);
sput_fail_unless( sput_fail_unless(
error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0" error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0"
); );
@ -51,13 +51,13 @@ static void test_config_load_invalid_args() {
test_config_teardown(fixture); test_config_teardown(fixture);
} }
static void test_config_load_spec_not_found() { static void test_config_new_spec_not_found() {
struct TestConfigFixture *fixture = test_config_setup(); struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0; struct Error *error = 0;
struct Config *config = 0; struct Config *config = 0;
error = config_load(fixture->cwd, fixture->root_dir, "not_found", &config); error = config_new(fixture->cwd, fixture->root_dir, "not_found", &config);
sput_fail_unless( sput_fail_unless(
error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found" error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found"
); );
@ -66,13 +66,13 @@ static void test_config_load_spec_not_found() {
test_config_teardown(fixture); test_config_teardown(fixture);
} }
static void test_config_load_spec_not_dir() { static void test_config_new_spec_not_dir() {
struct TestConfigFixture *fixture = test_config_setup(); struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0; struct Error *error = 0;
struct Config *config = 0; struct Config *config = 0;
error = config_load(fixture->cwd, fixture->root_dir, "not_dir", &config); error = config_new(fixture->cwd, fixture->root_dir, "not_dir", &config);
sput_fail_unless( sput_fail_unless(
error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir" error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir"
); );
@ -81,21 +81,20 @@ static void test_config_load_spec_not_dir() {
test_config_teardown(fixture); test_config_teardown(fixture);
} }
static void test_config_load_success() { static void test_config_new_success() {
struct TestConfigFixture *fixture = test_config_setup(); struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0; struct Error *error = 0;
struct Config *config = 0; struct Config *config = 0;
error = error = config_new(fixture->cwd, fixture->root_dir, fixture->target, &config);
config_load(fixture->cwd, fixture->root_dir, fixture->target, &config); sput_fail_unless(error == 0, "config_new() success");
sput_fail_unless(error == 0, "config_load() success"); sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_new() cwd");
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd");
sput_fail_unless( sput_fail_unless(
strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir" strcmp(config->root_dir, fixture->root_dir) == 0, "config_new() root_dir"
); );
sput_fail_unless( sput_fail_unless(
strcmp(config->target, fixture->target) == 0, "config_load() target" strcmp(config->target, fixture->target) == 0, "config_new() target"
); );
config_free(config); config_free(config);