about summary refs log tree commit diff
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 {