aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-01-25 00:57:56 +0100
committerMel <mel@rnrd.eu>2026-01-25 00:57:56 +0100
commit4dc6b49db40eaebf700d5c26c93c5f33b3db60ac (patch)
tree8b1450f743e27016ae4d78780a5e0e4901622bdc /boot
parentfce4a99dbfab5af623bb05d9e10807efeced5a47 (diff)
downloadcatskill-4dc6b49db40eaebf700d5c26c93c5f33b3db60ac.tar.zst
catskill-4dc6b49db40eaebf700d5c26c93c5f33b3db60ac.zip
Allow different transpile pass output types
Signed-off-by: Mel <mel@rnrd.eu>
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");
}
}