diff options
| author | Mel <mel@rnrd.eu> | 2025-08-05 22:49:40 +0200 |
|---|---|---|
| committer | Mel <mel@rnrd.eu> | 2025-08-05 22:49:40 +0200 |
| commit | c2c84f4d15d964fb663f390046b1d17441145c61 (patch) | |
| tree | a1724e49fefe143fd82404ffb1a0891ad6bc9755 /boot/runtime/core.c | |
| parent | 1d277d507498d8f3649684e7ff0ad2c3a19792a4 (diff) | |
| download | catskill-c2c84f4d15d964fb663f390046b1d17441145c61.tar.zst catskill-c2c84f4d15d964fb663f390046b1d17441145c61.zip | |
Add core library for catskill transpiled source, output defined types in transpiler
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/runtime/core.c')
| -rw-r--r-- | boot/runtime/core.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/boot/runtime/core.c b/boot/runtime/core.c new file mode 100644 index 0000000..a409604 --- /dev/null +++ b/boot/runtime/core.c @@ -0,0 +1,742 @@ +/* + * the core catskill c-library for + * catskill source files transpiled to c. + * used during bootstrapping, while the full + * catskill standard library is not yet available. + * + * copyright (c) 2025, mel g. <mel@rnrd.eu> + * + * spdx-license-identifier: mpl-2.0 + */ + +// TODO: as mentioned in `boot/common.c`, the catskill transpiled +// source code "core" library should be merged with the catboot +// "common" library. + +#pragma once + +// common headers for both your average catskill program. +// other libc headers can be included with the pragma +// `| c-header "header.h"`. +#include <fcntl.h> +#include <features.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +// types predefined for catskill programs. +// the transpiler outputs these types directly when seen +// in the source code. +#define uint8 uint8_t +#define uint16 uint16_t +#define uint32 uint32_t +#define uint64 uint64_t + +#define int8 int8_t +#define int16 int16_t +#define int32 int32_t +#define int64 int64_t + +#define float32 float +#define float64 double +#define real float64 + +#define uint uint64 +#define integer int64 + +#define ascii char +#define byte char + +#define bool _Bool +#define true 1 +#define false 0 +#define nil NULL + +_Noreturn void +panic(const ascii* message, ...) +{ + fprintf(stderr, "panic: "); + + va_list args; + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +void* +allocate(size_t size) +{ + void* ptr = malloc(size); + if (!ptr) panic("failed to allocate %zu bytes. are we out of memory? :(", size); + return ptr; +} + +void* +reallocate(void* ptr, size_t size) +{ + void* new_ptr = realloc(ptr, size); + if (!new_ptr) panic("failed to reallocate %zu bytes. are we out of memory? :(", size); + if (size == 0) return nil; + return new_ptr; +} + +struct _Array +{ + uint element_size; + void* data; + uint length; + uint capacity; +}; + +// a dynamic array type. +#define Array(type) struct _Array + +#define ARRAY_DEFAULT_CAPACITY 16 + +void array_grow(struct _Array* array); +void array_grow_until(struct _Array* array, uint min_capacity); + +// internal use, try the `array_new` macro. +struct _Array +_array_new(uint element_size) +{ + struct _Array array = { + .element_size = element_size, + .data = nil, + .length = 0, + .capacity = 0, + }; + array_grow(&array); + return array; +} + +// creates a new, empty array for the given type. +#define array_new(type) _array_new(sizeof(type)) + +// returns the number of elements in the array. +uint +array_length(const struct _Array* array) +{ + return array->length; +} + +// returns the total number of elements the array can hold before reallocating. +uint +array_capacity(const struct _Array* array) +{ + return array->capacity; +} + +// true if the array is empty. +bool +array_is_empty(const struct _Array* array) +{ + return array->length == 0; +} + +// resizes the array backing allocation to the new capacity. +// if the new capacity is smaller than the current length, +// the array will be truncated to the new length. +// if the new capacity is 0, the array's data will be freed. +void +array_resize_allocation(struct _Array* array, uint new_capacity) +{ + // reallocate can handle data being nil, so we don't need to check for that. + array->data = reallocate(array->data, new_capacity * array->element_size); + array->capacity = new_capacity; + array->length = array->length < new_capacity ? array->length : new_capacity; +} + +// resizes the array to a new length. +// if the new length is larger than the current capacity, +// the array will be grown to fit, and the new elements will be zero-initialized. +// if the new length is smaller than the current length, +// the array will be truncated, and the discarded elements are zeroed out. +// this function does not shrink the underlying allocation. +void +array_resize(struct _Array* array, uint new_length) +{ + if (new_length > array->capacity) { + // grow array to fit the new length naturally. + array_grow_until(array, new_length); + // initialize the new elements to zero. + memset((uint8*)array->data + (array->length * array->element_size), 0, + (new_length - array->length) * array->element_size); + } else if (new_length < array->length) { + // truncate the array elements to the new length. + memset((uint8*)array->data + (new_length * array->element_size), 0, + (array->length - new_length) * array->element_size); + } + array->length = new_length; +} + +// frees the memory used by the array's data. +void +array_free(struct _Array* array) +{ + array_resize_allocation(array, 0); +} + +// grows the array's capacity naturally. +void +array_grow(struct _Array* array) +{ + if (array->capacity == 0) + array_resize_allocation(array, ARRAY_DEFAULT_CAPACITY); + else + array_resize_allocation(array, array->capacity * 2); +} + +// grows the array's capacity naturally to hold at least `min_capacity` elements. +// if current capacity is already enough, nothing happens. +void +array_grow_until(struct _Array* array, uint min_capacity) +{ + while (array->capacity < min_capacity) array_grow(array); +} + +// internal use, try the `array_get` macro. +void +_array_get(const struct _Array* array, uint index, void* out_element) +{ + if (index >= array->length) panic("index out of bounds: %u (length: %u)", index, array->length); + memcpy(out_element, (uint8*)array->data + (index * array->element_size), array->element_size); +} + +// gets the element at a given index and copies it to `out_element`. +// panic if the index is out of bounds. +#define array_get(array, index, out_element) _array_get(array, index, (void*)(out_element)) + +// internal use, prefer the `array_set` macro. +void +_array_set(struct _Array* array, uint index, const void* element) +{ + if (index >= array->length) { + panic("index out of bounds: %u (length: %u)", index, array->length); + } + memcpy((uint8*)array->data + (index * array->element_size), element, array->element_size); +} + +// sets the element at a given index. +// panic if the index is out of bounds. +#define array_set(array, index, element) _array_set(array, index, (const void*)(element)) + +// internal use, prefer the `array_at` macro. +void* +_array_at(const struct _Array* array, uint index) +{ + if (index >= array->length) { + panic("index out of bounds: %u (length: %u)", index, array->length); + } + return (uint8*)array->data + (index * array->element_size); +} + +// returns a pointer to the element at a given index. +// panics if the index is out of bounds. +#define array_at(type, array, index) ((type*)_array_at(array, index)) + +// internal use, prefer the `array_insert` macro. +void +_array_insert(struct _Array* array, uint index, const void* element) +{ + if (index > array->length) panic("index out of bounds: %u (length: %u)", index, array->length); + array_grow_until(array, array->length + 1); + + // shift elements to the right to make space for the new element + // if we are inserting at the end, we don't need to shift anything. + // if there aren't any elements, we also don't need to shift anything. + if (index < array->length && array->length > 0) + memmove( + (uint8*)array->data + ((index + 1) * array->element_size), + (uint8*)array->data + (index * array->element_size), + (array->length - index) * array->element_size); + // insert the new element at the specified index + memcpy((uint8*)array->data + (index * array->element_size), element, array->element_size); + + ++array->length; +} + +// inserts an element at a given index, shifting subsequent elements. +// panics if the index is out of bounds (greater than length). +#define array_insert(array, index, element) _array_insert(array, index, (const void*)(element)) + +// adds an element to the end of the array. +// internal use; prefer the `array_push` macro. +void +_array_push(struct _Array* array, const void* element) +{ + array_insert(array, array->length, element); +} + +// adds an element to the end of the array. +#define array_push(array, element) _array_push(array, (const void*)(element)) + +// removes an element at a given index, shifting subsequent elements. +// if `removed_element` is not null, the removed element is copied to it. +// panics if the index is out of bounds. +// internal use; prefer the `array_remove` macro. +void +_array_remove(struct _Array* array, uint index, void* removed_element) +{ + if (index >= array->length) { + panic("index out of bounds: %u (length: %u)", index, array->length); + } + + if (removed_element) + memcpy(removed_element, (uint8*)array->data + (index * array->element_size), + array->element_size); + + // shift elements to the left to fill the gap + // if we are removing the last element, we don't need to shift anything. + // if there aren't any elements, there is also nothing to shift. + if (index < array->length - 1 && array->length > 1) + memmove( + (uint8*)array->data + (index * array->element_size), + (uint8*)array->data + ((index + 1) * array->element_size), + (array->length - index - 1) * array->element_size); + + --array->length; +} + +// removes an element at a given index, shifting subsequent elements. +// if `removed_element` is not null, the removed element is copied to it. +// panics if the index is out of bounds. +#define array_remove(array, index, removed_element) \ + _array_remove(array, index, (void*)(removed_element)) + +// removes and optionally returns the last element of the array. +// panics if the array is empty. +// internal use; prefer the `array_pop` macro. +void +_array_pop(struct _Array* array, void* removed_element) +{ + if (array->length == 0) panic("cannot pop from an empty array"); + array_remove(array, array->length - 1, removed_element); +} + +// removes and optionally returns the last element of the array. +// panics if the array is empty. +#define array_pop(array, removed_element) _array_pop(array, (void*)(removed_element)) + +// clears the array, setting its length to 0. +// this does not free the allocated memory. +void +array_clear(struct _Array* array) +{ + memset(array->data, 0, array->element_size * array->length); + array->length = 0; +} + +// appends all elements from array `b` to the end of array `a`. +// panics if the element sizes of the arrays are different. +void +array_append(struct _Array* a, struct _Array* b) +{ + if (a->element_size != b->element_size) + panic("cannot append arrays of different type and element size: %u vs %u", a->element_size, + b->element_size); + + // grow array `a` to fit the new elements, following the array's growth strategy. + array_grow_until(a, a->length + b->length); + + memcpy((uint8*)a->data + (a->length * a->element_size), b->data, b->length * a->element_size); + + a->length += b->length; +} + +// a macro for iterating over each element in an array. +#define FOR_EACH_ARRAY(type, array, action) \ + for (uint i = 0; i < array_length(array); ++i) { \ + type* element = array_at(type, array, i); \ + action; \ + } + +#define ARRAY_FREE_ELEMENTS(type, array, free_action) \ + FOR_EACH_ARRAY(type, array, { free_action(element); }) + +// a macro for iterating over a range of elements in an array. +#define FOR_EACH_IN_RANGE(type, array, start, end, action) \ + for (uint i = start; i < end && i < array_length(array); ++i) { \ + type* element = array_at(type, array, i); \ + action; \ + } + +// a dynamic string type. +// the string is always null-terminated to be compatible with c functions. +// the length of the string does not include the null terminator. +struct String +{ + Array(ascii) data; +}; + +void string_ensure_capacity(struct String* str, uint new_len); + +// creates a new, empty string. +struct String +string_new(void) +{ + struct String str; + str.data = array_new(ascii); + ((ascii*)str.data.data)[0] = '\0'; + return str; +} + +// creates a new string from a c-style string. +struct String +string_from(const ascii* c_str) +{ + struct String str = string_new(); + uint len = strlen(c_str); + string_ensure_capacity(&str, len); + memcpy(str.data.data, c_str, len); + str.data.length = len; + ((ascii*)str.data.data)[len] = '\0'; + return str; +} + +// frees the memory used by the string. +void +string_free(struct String* str) +{ + array_free(&str->data); +} + +// returns the length of the string. +uint +string_length(const struct String* str) +{ + return str->data.length; +} + +// returns the current capacity of the string (excluding the null terminator). +uint +string_capacity(const struct String* str) +{ + return str->data.capacity > 0 ? str->data.capacity - 1 : 0; +} + +Array(ascii) string_backing(const struct String* str) +{ + return str->data; +} + +// checks if the string is empty. +bool +string_is_empty(const struct String* str) +{ + return str->data.length == 0; +} + +// returns a null-terminated c-string representation. +const ascii* +string_c_str(const struct String* str) +{ + return (const ascii*)str->data.data; +} + +// ensures the array has enough capacity for `new_len` characters plus a null terminator. +void +string_ensure_capacity(struct String* str, uint new_len) +{ + uint required_capacity = new_len + 1; + if (required_capacity > str->data.capacity) { + uint new_cap = str->data.capacity; + if (new_cap == 0) new_cap = ARRAY_DEFAULT_CAPACITY; + while (required_capacity > new_cap) new_cap *= 2; + array_resize_allocation(&str->data, new_cap); + } +} + +// appends a character to the end of the string. +void +string_push(struct String* str, ascii c) +{ + uint new_len = str->data.length + 1; + string_ensure_capacity(str, new_len); + ((ascii*)str->data.data)[str->data.length] = c; + str->data.length = new_len; + ((ascii*)str->data.data)[new_len] = '\0'; +} + +// removes and returns the last character of the string. +// panics if the string is empty. +ascii +string_pop(struct String* str) +{ + if (str->data.length == 0) { panic("cannot pop from an empty string"); } + str->data.length--; + ascii c = ((ascii*)str->data.data)[str->data.length]; + ((ascii*)str->data.data)[str->data.length] = '\0'; + return c; +} + +// appends a string to the end of another string. +void +string_append(struct String* dest, const struct String* src) +{ + uint old_len = dest->data.length; + uint new_len = old_len + src->data.length; + string_ensure_capacity(dest, new_len); + memcpy((ascii*)dest->data.data + old_len, src->data.data, src->data.length); + dest->data.length = new_len; + ((ascii*)dest->data.data)[new_len] = '\0'; +} + +// appends a c-style string to the end of a string. +void +string_append_c_str(struct String* dest, const ascii* c_str) +{ + uint c_str_len = strlen(c_str); + uint old_len = dest->data.length; + uint new_len = old_len + c_str_len; + string_ensure_capacity(dest, new_len); + memcpy((ascii*)dest->data.data + old_len, c_str, c_str_len); + dest->data.length = new_len; + ((ascii*)dest->data.data)[new_len] = '\0'; +} + +// clears the string, making it empty. +void +string_clear(struct String* str) +{ + str->data.length = 0; + ((ascii*)str->data.data)[0] = '\0'; +} + +// creates a copy of a string. +struct String +string_clone(const struct String* str) +{ + struct String new_str = string_new(); + string_ensure_capacity(&new_str, str->data.length); + memcpy(new_str.data.data, str->data.data, str->data.length + 1); // copy with null terminator + new_str.data.length = str->data.length; + return new_str; +} + +// compares two strings for equality. +bool +string_equals(const struct String* a, const struct String* b) +{ + if (a->data.length != b->data.length) return false; + if (a->data.length == 0) return true; + return memcmp(a->data.data, b->data.data, a->data.length) == 0; +} + +// compares a string with a c-style string for equality. +bool +string_equals_c_str(const struct String* a, const ascii* b) +{ + uint b_len = strlen(b); + if (a->data.length != b_len) return false; + if (a->data.length == 0) return true; + return memcmp(a->data.data, b, a->data.length) == 0; +} + +// returns a new string that is a slice of the original. +// the slice is from `start` (inclusive) to `end` (exclusive). +// panics if the range is invalid. +struct String +string_slice(const struct String* str, uint start, uint end) +{ + if (start > end || end > str->data.length) { + panic("invalid slice range [%u, %u) for string of length %u", start, end, str->data.length); + } + uint slice_len = end - start; + struct String new_str = string_new(); + string_ensure_capacity(&new_str, slice_len); + memcpy(new_str.data.data, (const ascii*)str->data.data + start, slice_len); + new_str.data.length = slice_len; + ((ascii*)new_str.data.data)[slice_len] = '\0'; + return new_str; +} + +// returns the character at the given index. +// panics if the index is out of bounds. +ascii +string_get(const struct String* str, uint index) +{ + if (index >= str->data.length) { + panic("index out of bounds: %u (length: %u)", index, str->data.length); + } + return ((ascii*)str->data.data)[index]; +} + +// sets the character at the given index. +// panics if the index is out of bounds. +void +string_set(struct String* str, uint index, ascii c) +{ + if (index >= str->data.length) { + panic("index out of bounds: %u (length: %u)", index, str->data.length); + } + ((ascii*)str->data.data)[index] = c; +} + +#define FOR_EACH_IN_STRING(character_variable, str, action) \ + for (uint i = 0; i < string_length(str); ++i) { \ + ascii character_variable = ((ascii*)((str)->data.data))[i]; \ + action; \ + } + +// a slice is a view into a block of memory. +// it does not own the data it points to. +struct _Slice +{ + uint element_size; + void* data; + uint length; +}; + +#define Slice(type) struct _Slice + +// internal use, try the `slice_new` macro. +struct _Slice +slice_new(void* data, uint length, uint element_size) +{ + struct _Slice slice = { .data = data, .length = length, .element_size = element_size }; + return slice; +} + +#define slice_new(data, length) slice_new((void*)(data), (length), sizeof(*(data))) + +// a string_view is a slice of characters. +// it is not null-terminated. +struct String_View +{ + Slice(ascii) data; +}; + +uint +string_view_length(const struct String_View view) +{ + return view.data.length; +} + +Slice(ascii) string_view_backing(const struct String_View view) +{ + return view.data; +} + +// creates a new string_view from a string. +struct String_View +string_view_from_string(const struct String* str) +{ + return (struct String_View){ .data = slice_new(str->data.data, str->data.length) }; +} + +// creates a new string_view from a c-style string. +struct String_View +string_view_from_c_str(const ascii* c_str) +{ + return (struct String_View){ .data = slice_new((void*)c_str, strlen(c_str)) }; +} + +struct String_View +string_view_from_slice(Slice(ascii) slice) +{ + return (struct String_View){ .data = slice }; +} + +// compares two string_views for equality. +bool +string_view_equals(struct String_View a, struct String_View b) +{ + Slice(ascii) a_backing = string_view_backing(a), b_backing = string_view_backing(b); + if (string_view_length(a) != string_view_length(b)) return false; + if (a.data.length == 0) return true; + return memcmp(a_backing.data, b_backing.data, a_backing.length) == 0; +} + +// a file handle. +struct File +{ + FILE* handle; + struct String* path; +}; + +// opens a file and returns a handle to it. +// returns nil if the file cannot be opened. +struct File* +file_open(struct String* path, const ascii* mode) +{ + FILE* handle = fopen(string_c_str(path), mode); + if (!handle) return nil; + + struct File* file = allocate(sizeof(struct File)); + file->handle = handle; + file->path = path; + return file; +} + +// closes a file. +void +file_close(struct File* file) +{ + if (!file) return; + fclose(file->handle); + free(file); +} + +// reads the entire content of a file into a string. +struct String +file_read_all(struct File* file) +{ + fseek(file->handle, 0, SEEK_END); + long file_size = ftell(file->handle); + fseek(file->handle, 0, SEEK_SET); + + struct String content = string_new(); + string_ensure_capacity(&content, file_size); + + fread(content.data.data, 1, file_size, file->handle); + content.data.length = file_size; + ((ascii*)content.data.data)[file_size] = '\0'; + + return content; +} + +// writes the content of a string to a file. +void +file_write(struct File* file, struct String* content) +{ + fwrite(string_c_str(content), 1, string_length(content), file->handle); +} + +// checks if a file exists at the given path. +bool +file_exists(struct String* path) +{ + return access(string_c_str(path), F_OK) != -1; +} + +// memory-maps a file and returns a string_view of its content. +// panic if the file cannot be mapped. +struct String_View +file_map_to_string_view(struct File* file) +{ + struct stat st; + if (fstat(fileno(file->handle), &st) == -1) { panic("could not get file status for mmap"); } + + void* mapped = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fileno(file->handle), 0); + if (mapped == MAP_FAILED) { panic("could not map file to memory"); } + + return string_view_from_slice(slice_new(mapped, st.st_size)); +} + +// unmaps a memory-mapped view. +void +file_unmap(struct File* file, struct String_View view) +{ + (void)file; + munmap(string_view_backing(view).data, string_view_length(view)); +} |
