about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--boot/catboot.c59
-rw-r--r--boot/catboot.h1
-rw-r--r--boot/parse.c2
-rw-r--r--boot/transpile.c417
4 files changed, 467 insertions, 12 deletions
diff --git a/boot/catboot.c b/boot/catboot.c
index 155c3c5..9afb710 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -105,9 +105,7 @@ debug_parse_pass(struct Source_File source_file)
 
     struct Tree tree;
     int parser_result = parser_do_your_thing(&parser, &tree);
-    if (parser_result != 0) {
-        fprintf(stderr, "parser finished with errors\n");
-    }
+    if (parser_result != 0) fprintf(stderr, "parser finished with errors\n");
 
     tree_printer(&tree);
 
@@ -123,6 +121,7 @@ enum Command_Result
 struct Command_Arguments
 {
     const ascii* input;
+    const ascii* output;
 };
 
 enum Command_Result
@@ -167,18 +166,50 @@ test_parse_command(struct Command_Arguments* arguments)
 enum Command_Result
 default_command(struct Command_Arguments* arguments)
 {
+    enum Command_Result result = COMMAND_OK;
+
     struct String source = read_file(arguments->input);
     struct Source_File source_file = {
         .source = source,
         .path = string_from_static_c_string(arguments->input),
     };
 
-    debug_lex_pass(source);
-    printf("\n");
-    if (debug_parse_pass(source_file)) return COMMAND_FAIL;
-    printf("\n");
+    struct Lexer lexer;
+    lexer_new(&lexer, source);
 
-    return COMMAND_OK;
+    struct Parser parser;
+    parser_new(&parser, &lexer, source_file);
+
+    struct Tree tree;
+    if (parser_do_your_thing(&parser, &tree) != 0) {
+        fprintf(stderr, "parser finished with errors\n");
+        goto end;
+    }
+
+    FILE* output_file = nil;
+    if (arguments->output) {
+        output_file = fopen(arguments->output, "w");
+        if (!output_file) {
+            fprintf(stderr, "could not open output file: %s\n", arguments->output);
+            result = COMMAND_FAIL;
+            goto end;
+        }
+    } else {
+        output_file = stdout;
+    }
+
+    struct Transpiler transpiler;
+    transpiler_new(&transpiler, output_file);
+
+    if (transpiler_catskill_to_c(&transpiler, &tree) != 0) {
+        fprintf(stderr, "transpiler finished with errors\n");
+        result = COMMAND_FAIL;
+    }
+
+    if (output_file != stdout) fclose(output_file);
+
+end:
+    return result;
 }
 
 typedef enum Command_Result (*Command_Function)(struct Command_Arguments*);
@@ -209,7 +240,7 @@ main(const int32 argc, ascii* argv[])
     }
 
     bool have_command = false;
-    ascii *command_name = nil, short_command_name = '\0', *input = nil;
+    ascii *command_name = nil, short_command_name = '\0', *input = nil, *output = nil;
     for (uint a = 1; a < argc; ++a) {
         ascii* arg = argv[a];
 
@@ -224,12 +255,16 @@ main(const int32 argc, ascii* argv[])
             }
             have_command = true;
         } else {
-            check(!input, "multiple inputs given");
-            input = arg;
+            if (!input)
+                input = arg;
+            else if (!output)
+                output = arg;
+            else
+                failure("too many arguments, expected at most 2: input and output");
         }
     }
 
-    struct Command_Arguments arguments = { .input = input };
+    struct Command_Arguments arguments = { .input = input, .output = output };
     for (uint d = 0; d < ARRAY_SIZE(command_definitions); ++d) {
         struct Command_Definition* definition = &command_definitions[d];
 
diff --git a/boot/catboot.h b/boot/catboot.h
index 4c40676..621ed11 100644
--- a/boot/catboot.h
+++ b/boot/catboot.h
@@ -18,3 +18,4 @@
 #include "tree.c"
 #include "parse.c"
 #include "visit.c"
+#include "transpile.c"
diff --git a/boot/parse.c b/boot/parse.c
index 091a6f7..481fa90 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -1646,3 +1646,5 @@ parser_do_your_thing(struct Parser* p, struct Tree* tree)
 
 #undef CHECK
 #undef CHECK_RETURN
+#undef CONTEXT_START
+#undef CONTEXT_END
diff --git a/boot/transpile.c b/boot/transpile.c
new file mode 100644
index 0000000..bebcbbc
--- /dev/null
+++ b/boot/transpile.c
@@ -0,0 +1,417 @@
+/*
+ * transpiler from catskill to the c programming language.
+ * takes a direct catskill syntax tree and produces a c source file,
+ * without an intermediate representation.
+ *
+ * Copyright (c) 2025, Mel G. <mel@rnrd.eu>
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+#pragma once
+
+#include "catboot.h"
+#include "visit.c"
+#include <stdio.h>
+
+struct Transpile_Context
+{
+    bool in_function;
+    struct String function_name;
+    struct String function_return_type;
+};
+
+#define CONTEXT_START(name)        \
+    context = transpiler->context; \
+    transpiler->context.name = true;
+
+#define CONTEXT_END(name) transpiler->context.name = context.name;
+
+struct Transpiler
+{
+    FILE* output;
+    struct Transpile_Context context;
+};
+
+#define TRANSPILER_PREAMBLE                                 \
+    DATA_FOR_VISIT(struct Transpiler, transpiler)           \
+    struct Transpile_Context context = transpiler->context; \
+    (void)context;
+
+void
+transpiler_new(struct Transpiler* transpiler, FILE* output)
+{
+    *transpiler = (struct Transpiler){
+        .output = output,
+        .context = {
+            .in_function = false,
+            .function_name = string_empty(),
+            .function_return_type = string_empty(),
+        },
+    };
+}
+
+void
+transpiler_visit_type_node(struct Visit* visit, struct Type_Node* node)
+{
+    TRANSPILER_PREAMBLE
+
+    if (!node || node->type == TYPE_NODE_NONE) {
+        fprintf(transpiler->output, "void");
+        return;
+    }
+
+    switch (node->type) {
+    case TYPE_NODE_NAME: {
+        struct String name = node->value.name.name;
+        if (strcmp(name.data, "int") == 0) {
+            fprintf(transpiler->output, "int64_t");
+        } else if (strcmp(name.data, "string") == 0) {
+            fprintf(transpiler->output, "const char*");
+        } else if (strcmp(name.data, "bool") == 0) {
+            fprintf(transpiler->output, "bool");
+        } else if (strcmp(name.data, "float") == 0) {
+            fprintf(transpiler->output, "double");
+        } else {
+            fprintf(transpiler->output, "%.*s", (int)name.length, name.data);
+        }
+        break;
+    }
+    default:
+        fprintf(transpiler->output, "/* unknown type %d */", node->type);
+        break;
+    }
+}
+
+void
+transpiler_visit_function_header_node(struct Visit* visit, struct Function_Header_Node* header)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "(");
+    struct Type_Node* param = header->parameters_type_and_name;
+    if (!param) {
+        fprintf(transpiler->output, "void");
+    } else {
+        bool first = true;
+        while (param) {
+            if (!first) { fprintf(transpiler->output, ", "); }
+            VISIT(visit_type_node, param);
+            fprintf(
+                transpiler->output, " %.*s", (int)param->value_name.length, param->value_name.data);
+            first = false;
+            param = param->next;
+        }
+    }
+    fprintf(transpiler->output, ")");
+}
+
+void
+transpiler_visit_block_node(struct Visit* visit, struct Block_Node* node)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "{\n");
+    FOR_EACH (struct Statement*, statement, node->statements) { VISIT(visit_statement, statement); }
+    fprintf(transpiler->output, "}\n");
+}
+
+void
+transpiler_visit_argument_group_node(struct Visit* visit, struct Argument_Group_Node* node)
+{
+    TRANSPILER_PREAMBLE
+
+    struct Expression* arg = node->arguments;
+    bool first = true;
+    while (arg) {
+        if (!first) { fprintf(transpiler->output, ", "); }
+        VISIT(visit_expression, arg);
+        first = false;
+        arg = arg->next;
+    }
+}
+
+void
+transpiler_visit_statement_declaration(struct Visit* visit, struct Statement* stmt)
+{
+    TRANSPILER_PREAMBLE
+
+    struct Statement_Value_Declaration* declaration = &stmt->value.declaration;
+    struct Expression* initializer = declaration->inner.initializer;
+
+    if (initializer && initializer->kind == EXPRESSION_FUNCTION) {
+        struct Expression_Function* fun = &initializer->value.function;
+        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);
+        VISIT(visit_function_header_node, header);
+        fprintf(transpiler->output, " ");
+        VISIT(visit_block_node, &fun->body);
+    } else {
+        if (declaration->kind == STATEMENT_DECLARATION_CONSTANT) {
+            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);
+        if (initializer) {
+            fprintf(transpiler->output, " = ");
+            VISIT(visit_expression, initializer);
+        }
+    }
+}
+
+void
+transpiler_visit_statement_return(struct Visit* visit, struct Statement* stmt)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "return");
+    if (stmt->value.return_value.value) {
+        fprintf(transpiler->output, " ");
+        VISIT(visit_expression, stmt->value.return_value.value);
+    }
+}
+
+void
+transpiler_visit_statement_conditional(struct Visit* visit, struct Statement* stmt)
+{
+    TRANSPILER_PREAMBLE
+
+    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 (branch->when) {
+            fprintf(transpiler->output, "if (");
+            VISIT(visit_expression, branch->when);
+            fprintf(transpiler->output, ") ");
+        }
+        VISIT(visit_block_node, &branch->then);
+    }
+}
+
+void
+transpiler_visit_statement(struct Visit* visit, struct Statement* statement)
+{
+    TRANSPILER_PREAMBLE
+
+    switch (statement->kind) {
+    case STATEMENT_EXPRESSION:
+        VISIT(visit_expression, statement->value.expression.inner);
+        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        break;
+    case STATEMENT_DECLARATION:
+        VISIT(visit_statement_declaration, statement);
+        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        break;
+    case STATEMENT_RETURN:
+        VISIT(visit_statement_return, statement);
+        if (transpiler->context.in_function) fprintf(transpiler->output, ";\n");
+        break;
+    case STATEMENT_BLOCK:
+        VISIT(visit_block_node, &statement->value.block.inner);
+        break;
+    default:
+        walk_statement(visit, statement);
+        break;
+    }
+}
+
+void
+transpiler_visit_expression_integer_literal(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "%ld", expr->value.integer_literal.value);
+}
+
+void
+transpiler_visit_expression_float_literal(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "%f", expr->value.float_literal.value);
+}
+
+void
+transpiler_visit_expression_string_literal(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "\"%.*s\"", (int)expr->value.string_literal.value.length,
+            expr->value.string_literal.value.data);
+}
+
+void
+transpiler_visit_expression_boolean_literal(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(transpiler->output, "%s", expr->value.bool_literal.value ? "true" : "false");
+}
+
+void
+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);
+}
+
+void
+transpiler_visit_expression_unary_operation(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    fprintf(
+        transpiler->output, "(%s", unary_operation_to_string(expr->value.unary_operator.operation));
+    VISIT(visit_expression, expr->value.unary_operator.operand);
+    fprintf(transpiler->output, ")");
+}
+
+void
+transpiler_visit_expression_binary_operation(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+    struct Expression_Binary_Operator* bin_op = &expr->value.binary_operator;
+
+    if (bin_op->operation == BINARY_ASSIGN && bin_op->right_operand->kind == EXPRESSION_FUNCTION) {
+        struct Expression* fun_expr = bin_op->right_operand;
+        struct Expression_Function* fun = &fun_expr->value.function;
+        struct Function_Header_Node* header = &fun->header;
+        struct Expression* name_expr = bin_op->left_operand;
+
+        VISIT(visit_type_node, header->return_type);
+        fprintf(transpiler->output, " ");
+        VISIT(visit_expression, name_expr);
+        VISIT(visit_function_header_node, header);
+        fprintf(transpiler->output, " ");
+
+        CONTEXT_START(in_function);
+        VISIT(visit_block_node, &fun->body);
+        CONTEXT_END(in_function);
+    } else {
+        enum Binary_Operation op = expr->value.binary_operator.operation;
+        if (op == BINARY_ASSIGN_AND || op == BINARY_ASSIGN_OR) {
+            fprintf(transpiler->output, "(");
+            VISIT(visit_expression, expr->value.binary_operator.left_operand);
+            fprintf(transpiler->output, " = ");
+            VISIT(visit_expression, expr->value.binary_operator.left_operand);
+            if (op == BINARY_ASSIGN_AND)
+                fprintf(transpiler->output, " && ");
+            else
+                fprintf(transpiler->output, " || ");
+            VISIT(visit_expression, expr->value.binary_operator.right_operand);
+            fprintf(transpiler->output, ")");
+        } else {
+            fprintf(transpiler->output, "(");
+            VISIT(visit_expression, expr->value.binary_operator.left_operand);
+            fprintf(transpiler->output, " %s ", binary_operation_to_string(op));
+            VISIT(visit_expression, expr->value.binary_operator.right_operand);
+            fprintf(transpiler->output, ")");
+        }
+    }
+}
+
+void
+transpiler_visit_expression_call(struct Visit* visit, struct Expression* expr)
+{
+    TRANSPILER_PREAMBLE
+
+    VISIT(visit_expression, expr->value.call.subject);
+    fprintf(transpiler->output, "(");
+    VISIT(visit_argument_group_node, &expr->value.call.argument_group);
+    fprintf(transpiler->output, ")");
+}
+
+void
+transpiler_visit_expression(struct Visit* visit, struct Expression* expression)
+{
+    TRANSPILER_PREAMBLE
+
+    switch (expression->kind) {
+    case EXPRESSION_INTEGER_LITERAL:
+        VISIT(visit_expression_integer_literal, expression);
+        break;
+    case EXPRESSION_FLOAT_LITERAL:
+        VISIT(visit_expression_float_literal, expression);
+        break;
+    case EXPRESSION_STRING_LITERAL:
+        VISIT(visit_expression_string_literal, expression);
+        break;
+    case EXPRESSION_BOOLEAN_LITERAL:
+        VISIT(visit_expression_boolean_literal, expression);
+        break;
+    case EXPRESSION_NAME:
+        VISIT(visit_expression_name, expression);
+        break;
+    case EXPRESSION_UNARY_OPERATION:
+        VISIT(visit_expression_unary_operation, expression);
+        break;
+    case EXPRESSION_BINARY_OPERATION:
+        VISIT(visit_expression_binary_operation, expression);
+        break;
+    case EXPRESSION_CALL:
+        VISIT(visit_expression_call, expression);
+        break;
+    default:
+        walk_expression(visit, expression);
+        break;
+    }
+}
+
+void
+transpiler_visit_tree(struct Visit* visit, struct Tree* tree)
+{
+    TRANSPILER_PREAMBLE
+
+    // include some standard c headers
+    // catskill should have it's own standard library,
+    // but until bootstrapping is complete, we will just use a
+    // normal libc (preferably musl).
+    // other headers can be included by the user
+    // with the pragma `| c-header "header.h"`.
+    fprintf(transpiler->output, "#include <stdio.h>\n");
+    fprintf(transpiler->output, "#include <stdint.h>\n");
+    fprintf(transpiler->output, "#include <stdbool.h>\n\n");
+
+    FOR_EACH (struct Statement*, statement, tree->top_level_statements) {
+        VISIT(visit_statement, statement);
+    }
+}
+
+struct Visit_Table transpiler_visit_functions = {
+    .visit_tree = transpiler_visit_tree,
+    .visit_statement = transpiler_visit_statement,
+    .visit_statement_declaration = transpiler_visit_statement_declaration,
+    .visit_statement_conditional = transpiler_visit_statement_conditional,
+    .visit_statement_return = transpiler_visit_statement_return,
+    .visit_expression = transpiler_visit_expression,
+    .visit_expression_integer_literal = transpiler_visit_expression_integer_literal,
+    .visit_expression_float_literal = transpiler_visit_expression_float_literal,
+    .visit_expression_string_literal = transpiler_visit_expression_string_literal,
+    .visit_expression_boolean_literal = transpiler_visit_expression_boolean_literal,
+    .visit_expression_name = transpiler_visit_expression_name,
+    .visit_expression_unary_operation = transpiler_visit_expression_unary_operation,
+    .visit_expression_binary_operation = transpiler_visit_expression_binary_operation,
+    .visit_expression_call = transpiler_visit_expression_call,
+    .visit_type_node = transpiler_visit_type_node,
+    .visit_function_header_node = transpiler_visit_function_header_node,
+    .visit_block_node = transpiler_visit_block_node,
+    .visit_argument_group_node = transpiler_visit_argument_group_node,
+};
+
+int
+transpiler_catskill_to_c(struct Transpiler* transpiler, struct Tree* tree)
+{
+    struct Visit visit = { .table = &transpiler_visit_functions, .user_data = transpiler };
+    visit_table_fill_defaults(visit.table);
+
+    walk(&visit, tree);
+
+    return 0;
+}