aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/common.c343
-rw-r--r--boot/parse.c8
-rw-r--r--boot/runtime/core.c4
-rw-r--r--boot/transpile.c8
-rw-r--r--boot/tree.c4
-rw-r--r--boot/visit.c6
6 files changed, 305 insertions, 68 deletions
diff --git a/boot/common.c b/boot/common.c
index b8587bc..e453065 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -8,9 +8,6 @@
* SPDX-License-Identifier: MPL-2.0
*/
-// TODO: merge our common library with `boot/runtime/core.c`
-// to avoid implementing a semi-standard library twice!
-
#pragma once
#include <math.h>
@@ -147,6 +144,146 @@ unreachable()
// statically allocates a region of memory for a type.
#define REGION(type, of) REGION_OF_SIZE(type, of, REGION_SIZE)
+// the global array data region.
+REGION(byte, array_data)
+
+// a simple fixed-size array type that uses static allocation.
+struct _Array
+{
+ uint element_size;
+ void* data;
+ uint length;
+ uint capacity;
+};
+
+#define Array(type) struct _Array
+
+// allocate memory from the static array region.
+void*
+array_allocate_static(size_t size)
+{
+ check(region_array_data_cursor + size <= ARRAY_SIZE(region_array_data), "out of array memory");
+ void* ptr = region_array_data + region_array_data_cursor;
+ region_array_data_cursor += size;
+ return ptr;
+}
+
+// creates a new, empty array with fixed capacity using static allocation.
+struct _Array
+_array_new(uint element_size, uint capacity)
+{
+ void* data = array_allocate_static(capacity * element_size);
+ struct _Array array = {
+ .element_size = element_size,
+ .data = data,
+ .length = 0,
+ .capacity = capacity,
+ };
+ return array;
+}
+
+#define array_new(type, capacity) _array_new(sizeof(type), capacity)
+
+// returns the number of elements in the array.
+uint
+array_length(const struct _Array* array)
+{
+ return array->length;
+}
+
+// returns the total capacity of the array.
+uint
+array_capacity(const struct _Array* array)
+{
+ return array->capacity;
+}
+
+// checks if the array is empty.
+bool
+array_is_empty(const struct _Array* array)
+{
+ return array->length == 0;
+}
+
+// internal use, prefer the array_push macro.
+void
+_array_push(struct _Array* array, const void* element)
+{
+ check(array->length < array->capacity, "array is full: %u/%u", array->length, array->capacity);
+ memcpy((ascii*)array->data + (array->length * array->element_size), element, array->element_size);
+ ++array->length;
+}
+
+#define array_push(array, element) _array_push(array, (const void*)(element))
+
+// internal use, prefer the array_at macro.
+void*
+_array_at(const struct _Array* array, uint index)
+{
+ check(index < array->length, "array index out of bounds: %u (length: %u)", index, array->length);
+ return (ascii*)array->data + (index * array->element_size);
+}
+
+#define array_at(type, array, index) ((type*)_array_at(array, index))
+
+// internal use, prefer the array_get macro.
+void
+_array_get(const struct _Array* array, uint index, void* out_element)
+{
+ check(index < array->length, "array index out of bounds: %u (length: %u)", index, array->length);
+ memcpy(out_element, (ascii*)array->data + (index * array->element_size), array->element_size);
+}
+
+#define array_get(array, index, out_element) _array_get(array, index, (void*)(out_element))
+
+// a slice is a view into a block of memory.
+struct _Slice
+{
+ uint element_size;
+ void* data;
+ uint length;
+};
+
+#define Slice(type) struct _Slice
+
+// creates a new slice from data.
+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)))
+
+// returns the length of a slice.
+uint
+slice_length(const struct _Slice* slice)
+{
+ return slice->length;
+}
+
+// internal use, prefer the slice_at macro.
+void*
+_slice_at(const struct _Slice* slice, uint index)
+{
+ check(index < slice->length, "slice index out of bounds: %u (length: %u)", index, slice->length);
+ return (ascii*)slice->data + (index * slice->element_size);
+}
+
+#define slice_at(type, slice, index) ((type*)_slice_at(slice, index))
+
+// a macro for iterating over each element in an array.
+#define FOR_EACH_ARRAY(type, element_var, array_ptr, action) \
+ for (uint i = 0; i < array_length(array_ptr); ++i) { \
+ type element_var = *array_at(type, array_ptr, i); \
+ action; \
+ }
+
// the global string region.
REGION(ascii, string)
@@ -277,19 +414,137 @@ string_equals_c_str(const struct String a, const ascii* b)
if (string_length(a) == 0) return true;
return memcmp(a.data, b, a.length) == 0;
}
-void
-string_print(struct String s)
+
+// returns a null-terminated C string representation.
+// the string is already null-terminated in our implementation,
+// thus no copy is required.
+const ascii*
+string_c_str(struct String s)
{
- printf("%.*s", (int32)s.length, s.data);
+ return s.data;
+}
+
+// creates a new string with a character appended to
+// the end of the given string.
+struct String
+string_push(struct String s, ascii c)
+{
+ check(region_string_cursor + s.length + 2 < REGION_SIZE, "out of string memory for push");
+
+ ascii* at = region_string + region_string_cursor;
+ region_string_cursor += s.length + 2;
+
+ for (uint i = 0; i < s.length; ++i) at[i] = s.data[i];
+ at[s.length] = c;
+ at[s.length + 1] = '\0';
+
+ return (struct String){
+ .data = at,
+ .length = s.length + 1,
+ };
+}
+
+// creates a new string with the last character of
+// the given string removed.
+// pass a non-nil pointer as `removed_char` to retrieve
+// the removed character.
+struct String
+string_pop(struct String s, ascii* removed_char)
+{
+ check(s.length > 0, "cannot pop from an empty string");
+
+ if (removed_char) *removed_char = s.data[s.length - 1];
+
+ return (struct String){
+ .data = s.data,
+ .length = s.length - 1,
+ };
}
+// creates a new string consisting of string `a` followed by string `b`.
+struct String
+string_append(struct String a, struct String b)
+{
+ if (string_is_empty(b)) return a;
+
+ uint new_length = a.length + b.length;
+ check(region_string_cursor + new_length + 1 < REGION_SIZE, "out of string memory for append");
+
+ ascii* at = region_string + region_string_cursor;
+ region_string_cursor += new_length + 1;
+
+ for (uint i = 0; i < a.length; ++i) at[i] = a.data[i];
+ for (uint i = 0; i < b.length; ++i) at[a.length + i] = b.data[i];
+ at[new_length] = '\0';
+
+ return (struct String){
+ .data = at,
+ .length = new_length,
+ };
+}
+
+// creates a new string consisting of string `a` followed
+// by the contents of the c-style string buffer `b`.
+struct String
+string_append_c_str(struct String a, const ascii* b)
+{
+ uint c_str_len = strlen(b);
+ if (c_str_len == 0) return a;
+
+ uint new_length = a.length + c_str_len;
+ check(region_string_cursor + new_length + 1 < REGION_SIZE, "out of string memory for append_c_str");
+
+ ascii* at = region_string + region_string_cursor;
+ region_string_cursor += new_length + 1;
+
+ for (uint i = 0; i < a.length; ++i) at[i] = a.data[i];
+ for (uint i = 0; i < c_str_len; ++i) at[a.length + i] = b[i];
+ at[new_length] = '\0';
+
+ return (struct String){
+ .data = at,
+ .length = new_length,
+ };
+}
+
+// creates a copy of a string.
+struct String
+string_clone(struct String s)
+{
+ return string_new(s.data, s.length);
+}
+
+// creates a new string consisting of a slice of the original string.
+// the slice range is defined by `[start, end)`.
+// if `start == end`, returns an empty string.
+struct String
+string_slice(struct String s, uint start, uint end)
+{
+ check(start <= end && end <= s.length, "invalid slice range [%u, %u) for string of length %u", start, end, s.length);
+
+ if (start == end) return string_empty();
+
+ uint slice_len = end - start;
+ return string_new(s.data + start, slice_len);
+}
+
+// creates a new string with the contents of string `s`,
+// with the character at index `index` modified to `c`.
+struct String
+string_set(struct String s, uint index, ascii c)
+{
+ check(index < s.length, "index out of bounds: %u (length: %u)", index, s.length);
+
+ struct String new_s = string_clone(s);
+ new_s.data[index] = c; // safe because we just allocated this
+ return new_s;
+}
+
+// prints the contents of string `s` to stdout.
void
-string_format(struct String s, const ascii* format, ...)
+string_print(struct String s)
{
- va_list args;
- va_start(args, format);
- vprintf(format, args);
- va_end(args);
+ printf("%.*s", (int32)s.length, s.data);
}
enum String_Concat_Arg
@@ -301,6 +556,10 @@ enum String_Concat_Arg
#define MAX_STRING_CONCAT_LENGTH 2048
+// concatenates multiple strings and ascii buffers into a single string.
+// each new argument is prepended by a `String_Concat_Arg` value,
+// either `ARG_STRING` or `ARG_ASCII`, followed by the argument itself.
+// the final argument must be `ARG_END`.
struct String
string_concatenate(enum String_Concat_Arg type1, ...)
{
@@ -364,6 +623,8 @@ string_concatenate(enum String_Concat_Arg type1, ...)
return string_new(buffer, total_length);
}
+// creates a new string view from a substring of the given string `s`.
+// the view range is defined by `[start, end)`.
struct String_View
string_substring(struct String s, uint start, uint end)
{
@@ -428,6 +689,25 @@ string_view_at(struct String_View view, uint index)
return view.data[index];
}
+// creates a new string view from a c-style string buffer.
+struct String_View
+string_view_from_c_str(const ascii* c_str)
+{
+ return (struct String_View){
+ .data = (ascii*)c_str,
+ .length = strlen(c_str),
+ };
+}
+
+// compares two string views for equality.
+bool
+string_view_equals(struct String_View a, struct String_View b)
+{
+ if (a.length != b.length) return false;
+ if (a.length == 0) return true;
+ return memcmp(a.data, b.data, a.length) == 0;
+}
+
// prints a string view to stdout.
void
string_view_print(struct String_View view)
@@ -483,47 +763,6 @@ _internal_string_format(FILE* stream, uint string_length, const ascii* format, .
va_end(args);
}
-#define STRING_ARRAY_MAX 8
-
-// a string array, used for storing multiple strings.
-// if we ever need more strings, just bump `STRING_ARRAY_MAX` up.
-struct String_Array
-{
- struct String strings[STRING_ARRAY_MAX];
- uint count;
-};
-
-// initializes a string array with no strings.
-struct String_Array
-string_array_new(void)
-{
- return (struct String_Array){
- .strings = { 0 },
- .count = 0,
- };
-}
-
-// adds a string to the string array.
-bool
-string_array_add(struct String_Array* array, struct String string)
-{
- if (array->count >= STRING_ARRAY_MAX) return false;
-
- array->strings[array->count++] = string;
- return true;
-}
-
-struct String
-string_array_at(const struct String_Array* array, uint index)
-{
- check(index < array->count, "index out of bounds");
- return array->strings[index];
-}
-
-#define STRING_ARRAY_FOR_EACH(cursor, str, array) \
- struct String str = array.strings[0]; \
- for (uint cursor = 0; cursor < array.count; str = array.strings[++cursor])
-
// a source file given to the compiler.
struct Source_File
{
diff --git a/boot/parse.c b/boot/parse.c
index 481fa90..ea6b06b 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -499,7 +499,7 @@ parser_function_header_node(struct Parser* p, struct Parser_Error* error)
struct Argument_Group_Node
parser_argument_group_node(struct Parser* p, struct Parser_Error* error)
{
- struct String_Array argument_names = string_array_new();
+ Array(struct String) argument_names = array_new(struct String, 32);
struct Expression *arguments_head = nil, *arguments_current = nil;
for (;;) {
// check if we have a named argument.
@@ -521,7 +521,7 @@ parser_argument_group_node(struct Parser* p, struct Parser_Error* error)
// if we have a named argument, we need to add it to the names array,
// otherwise we just add an empty string.
- string_array_add(&argument_names, name);
+ array_push(&argument_names, &name);
if (parser_probe(p, TOKEN_COMMA)) {
parser_next(p);
@@ -538,7 +538,7 @@ parser_argument_group_node(struct Parser* p, struct Parser_Error* error)
struct Bare_Declaration_Node
parser_bare_declaration_node(struct Parser* p, struct Parser_Error* error)
{
- struct String_Array names = string_array_new();
+ Array(struct String) names = array_new(struct String, 16);
struct Span span = { 0 };
struct Cursor location = parser_peek(p).location;
@@ -547,7 +547,7 @@ parser_bare_declaration_node(struct Parser* p, struct Parser_Error* error)
CHECK_RETURN(parser_need(p, TOKEN_NAME, error), struct Bare_Declaration_Node);
span = span_is_empty(span) ? name_token.span : span_merge(span, name_token.span);
- string_array_add(&names, name_token.value.name);
+ array_push(&names, &name_token.value.name);
struct Token next = parser_peek(p);
if (token_can_begin_type(&next)) break;
diff --git a/boot/runtime/core.c b/boot/runtime/core.c
index a409604..c911666 100644
--- a/boot/runtime/core.c
+++ b/boot/runtime/core.c
@@ -9,10 +9,6 @@
* 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.
diff --git a/boot/transpile.c b/boot/transpile.c
index 7ab13e9..01531d9 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -152,8 +152,8 @@ transpiler_visit_statement_declaration(struct Visit* visit, struct Statement* st
struct Function_Header_Node* header = &fun->header;
VISIT(visit_type_node, header->return_type);
- fprintf(transpiler->output, " %.*s", (int)declaration->inner.names.strings[0].length,
- declaration->inner.names.strings[0].data);
+ struct String name = *array_at(struct String, &declaration->inner.names, 0);
+ fprintf(transpiler->output, " %.*s", (int)name.length, name.data);
VISIT(visit_function_header_node, header);
fprintf(transpiler->output, " ");
VISIT(visit_block_node, &fun->body);
@@ -162,8 +162,8 @@ transpiler_visit_statement_declaration(struct Visit* visit, struct Statement* st
fprintf(transpiler->output, "const ");
}
VISIT(visit_type_node, declaration->inner.type);
- fprintf(transpiler->output, " %.*s", (int)declaration->inner.names.strings[0].length,
- declaration->inner.names.strings[0].data);
+ struct String name = *array_at(struct String, &declaration->inner.names, 0);
+ fprintf(transpiler->output, " %.*s", (int)name.length, name.data);
if (initializer) {
fprintf(transpiler->output, " = ");
VISIT(visit_expression, initializer);
diff --git a/boot/tree.c b/boot/tree.c
index 228bbd4..751cf02 100644
--- a/boot/tree.c
+++ b/boot/tree.c
@@ -433,7 +433,7 @@ struct Argument_Group_Node
struct Expression* arguments;
// names of the arguments, if given.
// an unnamed argument is represented as an empty string.
- struct String_Array argument_names;
+ Array(struct String) argument_names;
};
// a declaration of a variable, constant, or other binding, without a mutability
@@ -442,7 +442,7 @@ struct Argument_Group_Node
// a bare declaration in a for-loop, for example, is always mutable.
struct Bare_Declaration_Node
{
- struct String_Array names;
+ Array(struct String) names;
struct Expression* initializer;
struct Type_Node* type;
diff --git a/boot/visit.c b/boot/visit.c
index fa49a7d..f9ad51c 100644
--- a/boot/visit.c
+++ b/boot/visit.c
@@ -1027,7 +1027,9 @@ printer_visit_bare_declaration_node(struct Visit* visit, struct Bare_Declaration
TREE_PRINTER_PREAMBLE
PRINT("(declaration ");
- STRING_ARRAY_FOR_EACH (i, name, node->names) { PRINT("%s ", name.data); }
+ FOR_EACH_ARRAY(struct String, name, &node->names, {
+ PRINT("%s ", name.data);
+ })
if (node->type && node->type->type != TYPE_NODE_NONE) {
VISIT(visit_type_node, node->type);
@@ -1107,7 +1109,7 @@ printer_visit_argument_group_node(struct Visit* visit, struct Argument_Group_Nod
uint i = 0;
FOR_EACH (struct Expression*, argument, node->arguments) {
- struct String name = string_array_at(&node->argument_names, i++);
+ struct String name = *array_at(struct String, &node->argument_names, i++);
if (name.data && name.data[0] != '\0') {
PRINT(" (named arg '%s' ", name.data);
} else {