aboutsummaryrefslogtreecommitdiff
path: root/src/parse/functions.c
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-08-25 00:49:29 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-08-25 00:49:29 -0400
commit1f93e82303f19029394c3e1d2c375c23d56a6498 (patch)
tree3d7fa048d36cb903f2f9af5827c720a586125f26 /src/parse/functions.c
parentc6014873d83889e4bf598f6073c771480da20b7b (diff)
Split out function parsing.
Diffstat (limited to 'src/parse/functions.c')
-rw-r--r--src/parse/functions.c162
1 files changed, 162 insertions, 0 deletions
diff --git a/src/parse/functions.c b/src/parse/functions.c
new file mode 100644
index 00000000..69c695fd
--- /dev/null
+++ b/src/parse/functions.c
@@ -0,0 +1,162 @@
+// Logic for parsing functions
+
+#include <gc.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <unictype.h>
+#include <uniname.h>
+
+#include "../ast.h"
+#include "../stdlib/util.h"
+#include "context.h"
+#include "errors.h"
+#include "functions.h"
+#include "parse.h"
+#include "utils.h"
+
+public
+arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) {
+ arg_ast_t *args = NULL;
+ for (;;) {
+ const char *batch_start = *pos;
+ ast_t *default_val = NULL;
+ type_ast_t *type = NULL;
+
+ typedef struct name_list_s {
+ const char *name;
+ struct name_list_s *next;
+ } name_list_t;
+
+ name_list_t *names = NULL;
+ for (;;) {
+ whitespace(pos);
+ const char *name = get_id(pos);
+ if (!name) break;
+ whitespace(pos);
+
+ if (match(pos, ":")) {
+ type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here");
+ names = new (name_list_t, .name = name, .next = names);
+ whitespace(pos);
+ if (match(pos, "="))
+ default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
+ break;
+ } else if (strncmp(*pos, "==", 2) != 0 && match(pos, "=")) {
+ default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='");
+ names = new (name_list_t, .name = name, .next = names);
+ break;
+ } else if (name) {
+ names = new (name_list_t, .name = name, .next = names);
+ spaces(pos);
+ if (!match(pos, ",")) break;
+ } else {
+ break;
+ }
+ }
+ if (!names) break;
+ if (!default_val && !type)
+ parser_err(ctx, batch_start, *pos,
+ "I expected a ':' and type, or '=' and a default value after this parameter (", names->name,
+ ")");
+
+ REVERSE_LIST(names);
+ for (; names; names = names->next)
+ args = new (arg_ast_t, .name = names->name, .type = type, .value = default_val, .next = args);
+
+ if (!match_separator(pos)) break;
+ }
+
+ REVERSE_LIST(args);
+ return args;
+}
+
+public
+ast_t *parse_func_def(parse_ctx_t *ctx, const char *pos) {
+ const char *start = pos;
+ if (!match_word(&pos, "func")) return NULL;
+
+ ast_t *name = optional(ctx, &pos, parse_var);
+ if (!name) return NULL;
+
+ spaces(&pos);
+
+ expect_str(ctx, start, &pos, "(", "I expected a parenthesis for this function's arguments");
+
+ arg_ast_t *args = parse_args(ctx, &pos);
+ spaces(&pos);
+ type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL;
+ whitespace(&pos);
+ bool is_inline = false;
+ ast_t *cache_ast = NULL;
+ for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) {
+ const char *flag_start = pos;
+ if (match_word(&pos, "inline")) {
+ is_inline = true;
+ } else if (match_word(&pos, "cached")) {
+ if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1");
+ } else if (match_word(&pos, "cache_size")) {
+ whitespace(&pos);
+ if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'");
+ whitespace(&pos);
+ cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache");
+ }
+ }
+ expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this function definition");
+
+ ast_t *body = expect(ctx, start, &pos, parse_block, "This function needs a body block");
+ return NewAST(ctx->file, start, pos, FunctionDef, .name = name, .args = args, .ret_type = ret_type, .body = body,
+ .cache = cache_ast, .is_inline = is_inline);
+}
+
+ast_t *parse_convert_def(parse_ctx_t *ctx, const char *pos) {
+ const char *start = pos;
+ if (!match_word(&pos, "convert")) return NULL;
+
+ spaces(&pos);
+
+ if (!match(&pos, "(")) return NULL;
+
+ arg_ast_t *args = parse_args(ctx, &pos);
+ spaces(&pos);
+ type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL;
+ whitespace(&pos);
+ bool is_inline = false;
+ ast_t *cache_ast = NULL;
+ for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) {
+ const char *flag_start = pos;
+ if (match_word(&pos, "inline")) {
+ is_inline = true;
+ } else if (match_word(&pos, "cached")) {
+ if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1");
+ } else if (match_word(&pos, "cache_size")) {
+ whitespace(&pos);
+ if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'");
+ whitespace(&pos);
+ cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache");
+ }
+ }
+ expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this function definition");
+
+ ast_t *body = expect(ctx, start, &pos, parse_block, "This function needs a body block");
+ return NewAST(ctx->file, start, pos, ConvertDef, .args = args, .ret_type = ret_type, .body = body,
+ .cache = cache_ast, .is_inline = is_inline);
+}
+
+public
+ast_t *parse_lambda(parse_ctx_t *ctx, const char *pos) {
+ const char *start = pos;
+ if (!match_word(&pos, "func")) return NULL;
+ spaces(&pos);
+ if (!match(&pos, "(")) return NULL;
+ arg_ast_t *args = parse_args(ctx, &pos);
+ spaces(&pos);
+ type_ast_t *ret = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL;
+ spaces(&pos);
+ expect_closing(ctx, &pos, ")", "I was expecting a ')' to finish this anonymous function's arguments");
+ ast_t *body = optional(ctx, &pos, parse_block);
+ if (!body) body = NewAST(ctx->file, pos, pos, Block, .statements = NULL);
+ return NewAST(ctx->file, start, pos, Lambda, .id = ctx->next_lambda_id++, .args = args, .ret_type = ret,
+ .body = body);
+}