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/
.direnv/
compile_commands.json
bootstrap
compile_commands.json
docs/
test/runner

2804
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,27 +1,59 @@
/**
@file
@brief A `spec` configuration.
*/
#ifndef _BOOTSTRAP_CONFIG_H
#define _BOOTSTRAP_CONFIG_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 {
// The directory the `bootstrap` command was run from.
// OWNERSHIP: Does not own this pointer.
/// The directory the `bootstrap` command was run from. Does not take
/// ownership of this pointer.
const char *cwd;
// The root directory housing our specs.
// OWNERSHIP: Does not own this pointer.
/// The root directory housing our specs. Does not take ownership of this
/// pointer.
const char *root_dir;
// The name of the spec we want to bootstrap.
// OWNERSHIP: Does not own this pointer.
/// The name of the spec we want to bootstrap. Does not take ownership of
/// this pointer.
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 *root_dir,
const char *target,
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);
#endif /* BOOTSTRAP_CONFIG_H */

View File

@ -1,22 +1,72 @@
/**
@file
@brief Dynamic `void*` arrays.
*/
#ifndef _BOOTSTRAP_DYN_ARRAY_H
#define _BOOTSTRAP_DYN_ARRAY_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 {
/// The underlying `void*` pointer.
void **buf;
// The size of @buf excluding `NUL`.
/// The size of @ref DynArray.buf.
size_t size;
// The allocated size of @buf including `NUL`.
// The allocated size of @ref DynArray.buf.
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);
/**
@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);
/**
@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);
/**
@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);
#endif /* _BOOTSTRAP_DYN_ARRAY_H */

View File

@ -1,3 +1,7 @@
/**
@file
@brief Error handling.
*/
#ifndef _BOOTSTRAP_ERROR_H
#define _BOOTSTRAP_ERROR_H
@ -5,27 +9,55 @@
#include "string_buf.h"
/**
@brief The various error codes produced by `bootstrap`.
*/
enum ErrorCode {
/// Could not retrieve the value of `$CWD`.
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,
/// There is no file at the location corresponding to the specified spec.
ERROR_CONFIG_TARGET_NOT_FOUND,
/// For generic reasons, `bootstrap` could not access the file at the provided
/// spec.
ERROR_CONFIG_TARGET_INVALID,
/// The file at the location corresponding to the provided spec is not a
/// directory.
ERROR_CONFIG_TARGET_NOT_DIR,
/// For generic reasons, `bootstrap` could not access the `spec.json` file.
ERROR_PARSER_SPEC_JSON_INVALID,
/// The `spec.json` file is not valid JSON.
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,
/// A field in `spec.json` is not an object.
ERROR_VALIDATOR_FIELD_NOT_OBJECT,
/// The `type` of a `spec.json` field is not a string.
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,
/// The `prompt` of a `spec.json` field is not a string.
ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
/// The `run.sh` file could not be found.
ERROR_EVALUATOR_RUN_SH_NOT_FOUND,
/// The `run.sh` file is not executable.
ERROR_EVALUATOR_RUN_SH_NOT_EXEC,
/// A user response to a prompt is not valid.
ERROR_EVALUATOR_RESPONSE_INVALID,
};
/**
@brief A `bootstrap` error.
@see ERROR_NEW
*/
struct Error {
enum ErrorCode code;
const char *message;
@ -47,10 +79,13 @@ static inline struct Error *priv_error_new(
// 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
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(...) \
ALEN0( \
@ -68,7 +103,17 @@ Take the `__VA_ARGS__` list and append a list of decreasing numbers
...) _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, ...) \
ERROR_NEW0(code, ALEN(__VA_ARGS__), __VA_ARGS__)
@ -80,6 +125,14 @@ Create a new `struct Error` instance.
// 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);
#endif /* _BOOTSTRAP_ERROR_H */

View File

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

2
main.c
View File

@ -21,7 +21,7 @@ static int run(const char *root_dir, const char *target) {
struct Error *error = 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);
goto cleanup_cwd;
}

View File

@ -8,7 +8,7 @@
#include "path.h"
struct Error *config_load(
struct Error *config_new(
const char *cwd,
const char *root_dir,
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) {
assert(a);
assert(item);
if (a->size == a->_capacity) {
a->_capacity *= 2;
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_enter_suite("config");
sput_run_test(test_config_load_invalid_args);
sput_run_test(test_config_load_spec_not_found);
sput_run_test(test_config_load_spec_not_dir);
sput_run_test(test_config_load_success);
sput_run_test(test_config_new_invalid_args);
sput_run_test(test_config_new_spec_not_found);
sput_run_test(test_config_new_spec_not_dir);
sput_run_test(test_config_new_success);
sput_enter_suite("dyn_array");
sput_run_test(test_dyn_array_zero_capacity);

View File

@ -33,16 +33,16 @@ static void test_config_teardown(struct TestConfigFixture *fixture) {
free(fixture);
}
static void test_config_load_invalid_args() {
static void test_config_new_invalid_args() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 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");
error_free(error);
error = config_load(fixture->cwd, 0, fixture->target, &config);
error = config_new(fixture->cwd, 0, fixture->target, &config);
sput_fail_unless(
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);
}
static void test_config_load_spec_not_found() {
static void test_config_new_spec_not_found() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 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(
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);
}
static void test_config_load_spec_not_dir() {
static void test_config_new_spec_not_dir() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 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(
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);
}
static void test_config_load_success() {
static void test_config_new_success() {
struct TestConfigFixture *fixture = test_config_setup();
struct Error *error = 0;
struct Config *config = 0;
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");
error = config_new(fixture->cwd, fixture->root_dir, fixture->target, &config);
sput_fail_unless(error == 0, "config_new() success");
sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_new() cwd");
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(
strcmp(config->target, fixture->target) == 0, "config_load() target"
strcmp(config->target, fixture->target) == 0, "config_new() target"
);
config_free(config);