about summary refs log tree commit diff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/build.c3
-rw-r--r--boot/catboot.c8
-rw-r--r--boot/common.c149
-rw-r--r--boot/tests/transpile/basic.cskt4
-rw-r--r--boot/transpile.c191
5 files changed, 289 insertions, 66 deletions
diff --git a/boot/build.c b/boot/build.c
index 5d902d6..1c95414 100644
--- a/boot/build.c
+++ b/boot/build.c
@@ -28,9 +28,6 @@
 #error "MUSL_DEV not defined"
 #endif
 
-const ascii* musl_lib = MUSL_LIB;
-const ascii* musl_dev = MUSL_DEV;
-
 struct Compiler_Command_Result
 {
     integer exit_code;
diff --git a/boot/catboot.c b/boot/catboot.c
index a0b4ca1..40756ac 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -134,8 +134,10 @@ debug_transpile_pass(struct Source_File source_file)
         return parser_result;
     }
 
+    struct Transpile_Output output = transpile_output_from_file(stdout);
+
     struct Transpiler transpiler;
-    transpiler_new(&transpiler, stdout);
+    transpiler_new(&transpiler, output);
     return transpiler_catskill_to_c(&transpiler, &tree);
 }
 
@@ -243,8 +245,10 @@ default_command(struct Command_Arguments* arguments)
         goto end;
     }
 
+    struct Transpile_Output output = transpile_output_from_file(output_file);
+
     struct Transpiler transpiler;
-    transpiler_new(&transpiler, output_file);
+    transpiler_new(&transpiler, output);
 
     if (transpiler_catskill_to_c(&transpiler, &tree) != 0) {
         log_error("transpiler finished with errors\n");
diff --git a/boot/common.c b/boot/common.c
index 79f5d63..1c65deb 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -343,6 +343,7 @@ _slice_at(const struct _Slice* slice, uint index)
 REGION(ascii, string)
 
 // a string.
+// an immutable sequence of ascii characters.
 struct String
 {
     ascii* data;
@@ -818,6 +819,154 @@ _internal_string_format(FILE* stream, uint string_length, const ascii* format, .
     va_end(args);
 }
 
+// a string buffer with a fixed capacity, the contents
+// of which can be modified.
+// essentially, a mutable string with a maximum size.
+struct String_Buffer
+{
+    ascii* data;
+    uint length;
+    uint capacity;
+};
+
+// creates a new string buffer with the given capacity.
+struct String_Buffer
+string_buffer_new(uint capacity)
+{
+    check(capacity + 1 < REGION_SIZE - region_string_cursor, "out of string memory for string buffer");
+
+    ascii* at = region_string + region_string_cursor;
+    region_string_cursor += capacity + 1;
+
+    at[0] = '\0';
+
+    return (struct String_Buffer){
+        .data = at,
+        .length = 0,
+        .capacity = capacity,
+    };
+}
+
+// creates a new empty string buffer.
+struct String_Buffer
+string_buffer_empty(void)
+{
+    // we still technically allocate one byte of string memory for the
+    // empty string buffer and its null-terminator, but that shouldn't be an issue.
+    return string_buffer_new(0);
+}
+
+// returns the length of the string buffer.
+uint
+string_buffer_length(const struct String_Buffer* buffer)
+{
+    return buffer->length;
+}
+
+// returns the capacity of the string buffer.
+uint
+string_buffer_capacity(const struct String_Buffer* buffer)
+{
+    return buffer->capacity;
+}
+
+// checks if the string buffer is empty.
+bool
+string_buffer_is_empty(const struct String_Buffer* buffer)
+{
+    return buffer->length == 0;
+}
+
+// returns the character at a given index.
+// does bounds-checking.
+ascii
+string_buffer_at(const struct String_Buffer* buffer, uint index)
+{
+    check(index < buffer->length, "index out of bounds");
+    return buffer->data[index];
+}
+
+// clears the string buffer, setting its length to zero.
+void
+string_buffer_clear(struct String_Buffer* buffer)
+{
+    buffer->length = 0;
+    buffer->data[0] = '\0';
+}
+
+// appends a character to the string buffer.
+void
+string_buffer_push(struct String_Buffer* buffer, ascii c)
+{
+    check(buffer->length < buffer->capacity, "string buffer is full: %u/%u", buffer->length, buffer->capacity);
+    buffer->data[buffer->length] = c;
+    ++buffer->length;
+    buffer->data[buffer->length] = '\0';
+}
+
+// removes the last character from the string buffer.
+// pass a non-nil pointer as `removed_char` to retrieve
+// the removed character.
+void
+string_buffer_pop(struct String_Buffer* buffer, ascii* removed_char)
+{
+    check(buffer->length > 0, "cannot pop from an empty string buffer");
+
+    --buffer->length;
+    if (removed_char) *removed_char = buffer->data[buffer->length];
+    buffer->data[buffer->length] = '\0';
+}
+
+// appends a string to the string buffer.
+void
+string_buffer_append(struct String_Buffer* buffer, struct String s)
+{
+    if (string_is_empty(s)) return;
+
+    check(buffer->length + s.length <= buffer->capacity,
+          "string buffer overflow: %u + %u > %u",
+          buffer->length, s.length, buffer->capacity);
+
+    for (uint i = 0; i < s.length; ++i) {
+        buffer->data[buffer->length + i] = s.data[i];
+    }
+    buffer->length += s.length;
+    buffer->data[buffer->length] = '\0';
+}
+
+// appends a C-style string to the string buffer.
+void
+string_buffer_append_c_str(struct String_Buffer* buffer, const ascii* c_str)
+{
+    uint c_str_len = strlen(c_str);
+    if (c_str_len == 0) return;
+
+    check(buffer->length + c_str_len <= buffer->capacity,
+          "string buffer overflow: %u + %u > %u",
+          buffer->length, c_str_len, buffer->capacity);
+
+    for (uint i = 0; i < c_str_len; ++i)
+        buffer->data[buffer->length + i] = c_str[i];
+
+    buffer->length += c_str_len;
+    buffer->data[buffer->length] = '\0';
+}
+
+// converts the string buffer to an immutable string.
+// allocates a new string in the string region.
+struct String
+string_buffer_to_string(const struct String_Buffer* buffer)
+{
+    return string_new(buffer->data, buffer->length);
+}
+
+// prints the contents of the string buffer to stdout.
+void
+string_buffer_print(const struct String_Buffer* buffer)
+{
+    printf("%.*s", (int32)buffer->length, buffer->data);
+}
+
 // a source file given to the compiler.
 struct Source_File
 {
diff --git a/boot/tests/transpile/basic.cskt b/boot/tests/transpile/basic.cskt
index f904259..d8de24f 100644
--- a/boot/tests/transpile/basic.cskt
+++ b/boot/tests/transpile/basic.cskt
@@ -10,7 +10,7 @@ main = fun () {
 
 >>>
 
-#include "boot/runtime/core.c"
+#include "core.c"
 void catskill_main(void) {
 }
-#include "boot/runtime/runtime.c"
\ No newline at end of file
+#include "runtime.c"
\ No newline at end of file
diff --git a/boot/transpile.c b/boot/transpile.c
index b1171d7..05aa5c4 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -12,6 +12,82 @@
 
 #include "catboot.h"
 
+// the output of the transpilation pass.
+// used for both file output and string buffer output.
+struct Transpile_Output
+{
+    FILE* file;
+    struct String_Buffer string;
+};
+
+struct Transpile_Output
+transpile_output_from_file(FILE* file)
+{
+    check(file != nil, "transpile output file is nil");
+    return (struct Transpile_Output){
+        .file = file,
+        .string = string_buffer_empty(),
+    };
+}
+
+#define TRANSPILE_OUTPUT_MAX_LENGTH 262144 // 256 KiB
+
+struct Transpile_Output
+transpile_output_from_string(void)
+{
+    return (struct Transpile_Output){
+        .file = nil,
+        .string = string_buffer_new(TRANSPILE_OUTPUT_MAX_LENGTH),
+    };
+}
+
+#define TRANSPILE_OUTPUT_MAX_WRITE_LENGTH 4096
+
+void
+transpile_output_write(struct Transpile_Output* output, const ascii* format, ...)
+{
+    va_list args;
+    va_start(args, format);
+
+    if (output->file) {
+        vfprintf(output->file, format, args);
+    } else {
+        ascii buffer[TRANSPILE_OUTPUT_MAX_WRITE_LENGTH];
+        int written = vsnprintf(buffer, TRANSPILE_OUTPUT_MAX_WRITE_LENGTH, format, args);
+        check(written >= 0 && (uint)written < TRANSPILE_OUTPUT_MAX_LENGTH, "transpile output string buffer overflow");
+
+        string_buffer_append_c_str(&output->string, buffer);
+    }
+
+    va_end(args);
+}
+
+#define TRANSPILE_WRITE(...) \
+    transpile_output_write(&transpiler->output, __VA_ARGS__)
+
+struct String
+transpile_output_string(struct Transpile_Output* output)
+{
+    check(output->file == nil, "transpile output is not a string");
+    return string_buffer_to_string(&output->string);
+}
+
+FILE*
+transpile_output_file(struct Transpile_Output* output)
+{
+    check(output->file != nil, "transpile output is not a file");
+    return output->file;
+}
+
+struct Transpile_Output
+transpile_output_to_string(void)
+{
+    return (struct Transpile_Output){
+        .file = nil,
+        .string = string_buffer_empty(),
+    };
+}
+
 struct Transpile_Context
 {
     bool in_function;
@@ -29,7 +105,7 @@ struct Transpile_Context
 
 struct Transpiler
 {
-    FILE* output;
+    struct Transpile_Output output;
     struct Transpile_Context context;
 };
 
@@ -39,7 +115,7 @@ struct Transpiler
     (void)context;
 
 void
-transpiler_new(struct Transpiler* transpiler, FILE* output)
+transpiler_new(struct Transpiler* transpiler, struct Transpile_Output output)
 {
     *transpiler = (struct Transpiler){
         .output = output,
@@ -59,7 +135,7 @@ transpiler_visit_type_node(struct Visit* visit, struct Type_Node* node)
     TRANSPILER_PREAMBLE
 
     if (!node || node->type == TYPE_NODE_NONE) {
-        fprintf(transpiler->output, "void");
+        TRANSPILE_WRITE("void");
         return;
     }
 
@@ -67,26 +143,26 @@ transpiler_visit_type_node(struct Visit* visit, struct Type_Node* node)
     case TYPE_NODE_NAME: {
         struct String name = node->value.name.name;
         if (strcmp(name.data, "int") == 0) {
-            fprintf(transpiler->output, "integer");
+            TRANSPILE_WRITE("integer");
         } else if (strcmp(name.data, "string") == 0) {
-            fprintf(transpiler->output, "struct String");
+            TRANSPILE_WRITE("struct String");
         } else if (strcmp(name.data, "bool") == 0) {
-            fprintf(transpiler->output, "bool");
+            TRANSPILE_WRITE("bool");
         } else if (strcmp(name.data, "float") == 0) {
-            fprintf(transpiler->output, "real");
+            TRANSPILE_WRITE("real");
         } else if (strcmp(name.data, "uint") == 0) {
-            fprintf(transpiler->output, "uint");
+            TRANSPILE_WRITE("uint");
         } else if (strcmp(name.data, "byte") == 0) {
-            fprintf(transpiler->output, "byte");
+            TRANSPILE_WRITE("byte");
         } else if (strcmp(name.data, "ascii") == 0) {
-            fprintf(transpiler->output, "ascii");
+            TRANSPILE_WRITE("ascii");
         } else {
-            fprintf(transpiler->output, "%.*s", (int)name.length, name.data);
+            TRANSPILE_WRITE("%.*s", (int)name.length, name.data);
         }
         break;
     }
     default:
-        fprintf(transpiler->output, "/* unknown type %d */", node->type);
+        TRANSPILE_WRITE("/* unknown type %d */", node->type);
         break;
     }
 }
@@ -96,22 +172,21 @@ transpiler_visit_function_header_node(struct Visit* visit, struct Function_Heade
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "(");
+    TRANSPILE_WRITE("(");
     struct Type_Node* param = header->parameters_type_and_name;
     if (!param) {
-        fprintf(transpiler->output, "void");
+        TRANSPILE_WRITE("void");
     } else {
         bool first = true;
         while (param) {
-            if (!first) { fprintf(transpiler->output, ", "); }
+            if (!first) { TRANSPILE_WRITE(", "); }
             VISIT(visit_type_node, param);
-            fprintf(
-                transpiler->output, " %.*s", (int)param->value_name.length, param->value_name.data);
+            TRANSPILE_WRITE(" %.*s", (int)param->value_name.length, param->value_name.data);
             first = false;
             param = param->next;
         }
     }
-    fprintf(transpiler->output, ")");
+    TRANSPILE_WRITE(")");
 }
 
 void
@@ -119,9 +194,9 @@ transpiler_visit_block_node(struct Visit* visit, struct Block_Node* node)
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "{\n");
+    TRANSPILE_WRITE("{\n");
     FOR_EACH (struct Statement*, statement, node->statements) { VISIT(visit_statement, statement); }
-    fprintf(transpiler->output, "}\n");
+    TRANSPILE_WRITE("}\n");
 }
 
 void
@@ -132,7 +207,7 @@ transpiler_visit_argument_group_node(struct Visit* visit, struct Argument_Group_
     struct Expression* arg = node->arguments;
     bool first = true;
     while (arg) {
-        if (!first) { fprintf(transpiler->output, ", "); }
+        if (!first) { TRANSPILE_WRITE(", "); }
         VISIT(visit_expression, arg);
         first = false;
         arg = arg->next;
@@ -153,19 +228,19 @@ transpiler_visit_statement_declaration(struct Visit* visit, struct Statement* st
 
         VISIT(visit_type_node, header->return_type);
         struct String name = *array_at(struct String, &declaration->inner.names, 0);
-        fprintf(transpiler->output, " %.*s", (int)name.length, name.data);
+        TRANSPILE_WRITE(" %.*s", (int)name.length, name.data);
         VISIT(visit_function_header_node, header);
-        fprintf(transpiler->output, " ");
+        TRANSPILE_WRITE(" ");
         VISIT(visit_block_node, &fun->body);
     } else {
         if (declaration->kind == STATEMENT_DECLARATION_CONSTANT) {
-            fprintf(transpiler->output, "const ");
+            TRANSPILE_WRITE("const ");
         }
         VISIT(visit_type_node, declaration->inner.type);
         struct String name = *array_at(struct String, &declaration->inner.names, 0);
-        fprintf(transpiler->output, " %.*s", (int)name.length, name.data);
+        TRANSPILE_WRITE(" %.*s", (int)name.length, name.data);
         if (initializer) {
-            fprintf(transpiler->output, " = ");
+            TRANSPILE_WRITE(" = ");
             VISIT(visit_expression, initializer);
         }
     }
@@ -176,9 +251,9 @@ transpiler_visit_statement_return(struct Visit* visit, struct Statement* stmt)
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "return");
+    TRANSPILE_WRITE("return");
     if (stmt->value.return_value.value) {
-        fprintf(transpiler->output, " ");
+        TRANSPILE_WRITE(" ");
         VISIT(visit_expression, stmt->value.return_value.value);
     }
 }
@@ -191,11 +266,11 @@ transpiler_visit_statement_conditional(struct Visit* visit, struct Statement* st
     struct Statement_Value_Conditional* conditional = &stmt->value.conditional;
     for (uint i = 0; i < conditional->condition_count; ++i) {
         struct Statement_Conditional_Branch* branch = &conditional->conditions[i];
-        if (i > 0) { fprintf(transpiler->output, "else "); }
+        if (i > 0) { TRANSPILE_WRITE("else "); }
         if (branch->when) {
-            fprintf(transpiler->output, "if (");
+            TRANSPILE_WRITE("if (");
             VISIT(visit_expression, branch->when);
-            fprintf(transpiler->output, ") ");
+            TRANSPILE_WRITE(") ");
         }
         VISIT(visit_block_node, &branch->then);
     }
@@ -209,15 +284,15 @@ transpiler_visit_statement(struct Visit* visit, struct Statement* statement)
     switch (statement->kind) {
     case STATEMENT_EXPRESSION:
         VISIT(visit_expression, statement->value.expression.inner);
-        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        if (transpiler->context.in_function) TRANSPILE_WRITE(";\n");
         break;
     case STATEMENT_DECLARATION:
         VISIT(visit_statement_declaration, statement);
-        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        if (transpiler->context.in_function) TRANSPILE_WRITE(";\n");
         break;
     case STATEMENT_RETURN:
         VISIT(visit_statement_return, statement);
-        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        if (transpiler->context.in_function) TRANSPILE_WRITE(";\n");
         break;
     case STATEMENT_BLOCK:
         VISIT(visit_block_node, &statement->value.block.inner);
@@ -233,7 +308,7 @@ transpiler_visit_expression_integer_literal(struct Visit* visit, struct Expressi
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "%ld", expr->value.integer_literal.value);
+    TRANSPILE_WRITE("%ld", expr->value.integer_literal.value);
 }
 
 void
@@ -241,7 +316,7 @@ transpiler_visit_expression_float_literal(struct Visit* visit, struct Expression
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "%f", expr->value.float_literal.value);
+    TRANSPILE_WRITE("%f", expr->value.float_literal.value);
 }
 
 void
@@ -249,7 +324,7 @@ transpiler_visit_expression_string_literal(struct Visit* visit, struct Expressio
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "\"%.*s\"", (int)expr->value.string_literal.value.length,
+    TRANSPILE_WRITE("\"%.*s\"", (int)expr->value.string_literal.value.length,
             expr->value.string_literal.value.data);
 }
 
@@ -258,7 +333,7 @@ transpiler_visit_expression_boolean_literal(struct Visit* visit, struct Expressi
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(transpiler->output, "%s", expr->value.bool_literal.value ? "true" : "false");
+    TRANSPILE_WRITE("%s", expr->value.bool_literal.value ? "true" : "false");
 }
 
 void
@@ -266,8 +341,7 @@ transpiler_visit_expression_name(struct Visit* visit, struct Expression* expr)
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(
-        transpiler->output, "%.*s", (int)expr->value.name.name.length, expr->value.name.name.data);
+    TRANSPILE_WRITE("%.*s", (int)expr->value.name.name.length, expr->value.name.name.data);
 }
 
 void
@@ -275,10 +349,9 @@ transpiler_visit_expression_unary_operation(struct Visit* visit, struct Expressi
 {
     TRANSPILER_PREAMBLE
 
-    fprintf(
-        transpiler->output, "(%s", unary_operation_to_string(expr->value.unary_operator.operation));
+    TRANSPILE_WRITE("(%s", unary_operation_to_string(expr->value.unary_operator.operation));
     VISIT(visit_expression, expr->value.unary_operator.operand);
-    fprintf(transpiler->output, ")");
+    TRANSPILE_WRITE(")");
 }
 
 void
@@ -294,7 +367,7 @@ transpiler_visit_expression_binary_operation(struct Visit* visit, struct Express
         struct Expression* name_expr = bin_op->left_operand;
 
         VISIT(visit_type_node, header->return_type);
-        fprintf(transpiler->output, " ");
+        TRANSPILE_WRITE(" ");
 
         // check if this is a main function assignment
         if (name_expr && name_expr->kind == EXPRESSION_NAME) {
@@ -303,7 +376,7 @@ transpiler_visit_expression_binary_operation(struct Visit* visit, struct Express
                 transpiler->context.main_function_found = true;
                 transpiler->context.main_function_takes_args =
                     header->parameters_type_and_name != nil;
-                fprintf(transpiler->output, "catskill_main");
+                TRANSPILE_WRITE("catskill_main");
             } else {
                 VISIT(visit_expression, name_expr);
             }
@@ -311,7 +384,7 @@ transpiler_visit_expression_binary_operation(struct Visit* visit, struct Express
             VISIT(visit_expression, name_expr);
         }
         VISIT(visit_function_header_node, header);
-        fprintf(transpiler->output, " ");
+        TRANSPILE_WRITE(" ");
 
         CONTEXT_START(in_function);
         VISIT(visit_block_node, &fun->body);
@@ -319,22 +392,22 @@ transpiler_visit_expression_binary_operation(struct Visit* visit, struct Express
     } else {
         enum Binary_Operation op = expr->value.binary_operator.operation;
         if (op == BINARY_ASSIGN_AND || op == BINARY_ASSIGN_OR) {
-            fprintf(transpiler->output, "(");
+            TRANSPILE_WRITE("(");
             VISIT(visit_expression, expr->value.binary_operator.left_operand);
-            fprintf(transpiler->output, " = ");
+            TRANSPILE_WRITE(" = ");
             VISIT(visit_expression, expr->value.binary_operator.left_operand);
             if (op == BINARY_ASSIGN_AND)
-                fprintf(transpiler->output, " && ");
+                TRANSPILE_WRITE(" && ");
             else
-                fprintf(transpiler->output, " || ");
+                TRANSPILE_WRITE(" || ");
             VISIT(visit_expression, expr->value.binary_operator.right_operand);
-            fprintf(transpiler->output, ")");
+            TRANSPILE_WRITE(")");
         } else {
-            fprintf(transpiler->output, "(");
+            TRANSPILE_WRITE("(");
             VISIT(visit_expression, expr->value.binary_operator.left_operand);
-            fprintf(transpiler->output, " %s ", binary_operation_to_string(op));
+            TRANSPILE_WRITE(" %s ", binary_operation_to_string(op));
             VISIT(visit_expression, expr->value.binary_operator.right_operand);
-            fprintf(transpiler->output, ")");
+            TRANSPILE_WRITE(")");
         }
     }
 }
@@ -345,9 +418,9 @@ transpiler_visit_expression_call(struct Visit* visit, struct Expression* expr)
     TRANSPILER_PREAMBLE
 
     VISIT(visit_expression, expr->value.call.subject);
-    fprintf(transpiler->output, "(");
+    TRANSPILE_WRITE("(");
     VISIT(visit_argument_group_node, &expr->value.call.argument_group);
-    fprintf(transpiler->output, ")");
+    TRANSPILE_WRITE(")");
 }
 
 void
@@ -400,7 +473,7 @@ transpiler_visit_tree(struct Visit* visit, struct Tree* tree)
     // exactly and tell the backend in `./build.c` to look for includes there,
     // but in the real implementation we should embed all runtime files into
     // this executable and then write them out into the temporary build directory.
-    fprintf(transpiler->output, "#include \"core.c\"\n");
+    TRANSPILE_WRITE("#include \"core.c\"\n");
 
     FOR_EACH (struct Statement*, statement, tree->top_level_statements) {
         VISIT(visit_statement, statement);
@@ -411,9 +484,9 @@ transpiler_visit_tree(struct Visit* visit, struct Tree* tree)
     // the catskill source.
     if (transpiler->context.main_function_found) {
         if (transpiler->context.main_function_takes_args) {
-            fprintf(transpiler->output, "\n#define CATSKILL_MAIN_TAKES_ARGS\n");
+            TRANSPILE_WRITE("\n#define CATSKILL_MAIN_TAKES_ARGS\n");
         }
-        fprintf(transpiler->output, "#include \"runtime.c\"\n");
+        TRANSPILE_WRITE("#include \"runtime.c\"\n");
     }
 }