about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-28 04:24:51 +0200
committerMel <mel@rnrd.eu>2026-05-28 04:24:51 +0200
commit4ec376b67d605910cb7757dc2e15a28a942f59ca (patch)
tree4e98614792e5a00175fa29e705142850993d68f6
parenta5b24e3eedc8bd48de99cfb90abb3bececa5d29f (diff)
downloadcatskill-4ec376b67d605910cb7757dc2e15a28a942f59ca.tar.zst
catskill-4ec376b67d605910cb7757dc2e15a28a942f59ca.zip
Correct type modifier combinations and grouping
Signed-off-by: Mel <mel@rnrd.eu>
-rw-r--r--boot/lex.c5
-rw-r--r--boot/parse.c34
-rw-r--r--boot/tests/parse/type_modifiers.cskt22
-rw-r--r--docs/grammar.md4
4 files changed, 55 insertions, 10 deletions
diff --git a/boot/lex.c b/boot/lex.c
index 6c3e86f..29b5294 100644
--- a/boot/lex.c
+++ b/boot/lex.c
@@ -335,7 +335,7 @@ bool
 token_ends_statement(const struct Token* t)
 {
     return token_is(t, TOKEN_END_OF_FILE) || token_is(t, TOKEN_NEWLINE)
-        || token_is(t, TOKEN_SEMICOLON);
+           || token_is(t, TOKEN_SEMICOLON);
 }
 
 bool
@@ -358,6 +358,7 @@ token_can_begin_type(const struct Token* t)
 
     // references
     case TOKEN_AMPERSAND:
+    case TOKEN_AND: // `&&` counts as a double-reference!
         return true;
     default:
         return false;
@@ -877,4 +878,4 @@ lexer_next(struct Lexer* l)
 
     union Token_Value value = (union Token_Value){ .unknown_character = c.character };
     return token_new(TOKEN_SOMETHING, span_width(position, 1), cursor, value);
-}
\ No newline at end of file
+}
diff --git a/boot/parse.c b/boot/parse.c
index 56d767a..ef71540 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -381,6 +381,8 @@ parser_block_node(struct Parser* p, struct Parser_Error* error)
         if (!parser_probe(p, TOKEN_CURLY_CLOSE))
             CHECK_RETURN(parser_end_statement(p, error), struct Tree_Block);
 
+        parser_unglue(p);
+
         if (!head) {
             head = statement;
         } else {
@@ -401,6 +403,7 @@ parser_block_node(struct Parser* p, struct Parser_Error* error)
 }
 
 struct Tree_Type* parser_node_type(struct Parser* p, struct Parser_Error* error);
+struct Tree_Type* parser_node_type_inner(struct Parser* p, struct Parser_Error* error);
 
 struct Tree_Function_Header
 parser_function_header_node(struct Parser* p, struct Parser_Error* error)
@@ -710,6 +713,11 @@ parser_node_type_tuple(struct Parser* p, struct Parser_Error* error)
     }
 
     struct Token close_token = CHECK(parser_need(p, TOKEN_ROUND_CLOSE, error));
+
+    // single-element tuples are equivalent to their inner type,
+    // which allows us to use them for grouping.
+    if (head && !head->next) return head;
+
     struct Span span = span_merge(open_token.span, close_token.span);
     return tree_type_new(
         TREE_TYPE_TUPLE, (union Tree_Type_Value){ .tuple = { head } }, span, open_token.location);
@@ -759,18 +767,31 @@ parser_node_type_array_or_map(struct Parser* p, struct Parser_Error* error)
 struct Tree_Type*
 parser_node_type_reference(struct Parser* p, struct Parser_Error* error)
 {
-    struct Token ampersand_token = CHECK(parser_need(p, TOKEN_AMPERSAND, error));
+    // we accept a fused `&&` token as two references
+    struct Token sigil = parser_next(p);
+    bool double_ref = token_is(&sigil, TOKEN_AND);
+    if (!double_ref && !token_is(&sigil, TOKEN_AMPERSAND)) {
+        parser_error(error, PARSER_ERROR_UNEXPECTED_TOKEN, sigil);
+        return nil;
+    }
 
-    struct Tree_Type* referenced_type = CHECK(parser_node_type(p, error));
+    struct Tree_Type* referenced_type = CHECK(parser_node_type_inner(p, error));
     if (!referenced_type) {
-        parser_error(error, PARSER_ERROR_EXPECTED_TYPE, ampersand_token);
+        parser_error(error, PARSER_ERROR_EXPECTED_TYPE, sigil);
         return nil;
     }
 
-    struct Span span = span_merge(ampersand_token.span, referenced_type->span);
-    return tree_type_new(
+    struct Span span = span_merge(sigil.span, referenced_type->span);
+    struct Tree_Type* ref = tree_type_new(
         TREE_TYPE_REFERENCE, (union Tree_Type_Value){ .reference = { referenced_type } }, span,
-        ampersand_token.location);
+        sigil.location);
+
+    if (double_ref) {
+        ref = tree_type_new(
+            TREE_TYPE_REFERENCE, (union Tree_Type_Value){ .reference = { ref } }, span,
+            sigil.location);
+    }
+    return ref;
 }
 
 struct Tree_Type*
@@ -793,6 +814,7 @@ parser_node_type_inner(struct Parser* p, struct Parser_Error* error)
     case TOKEN_SQUARE_OPEN:
         return parser_node_type_array_or_map(p, error);
     case TOKEN_AMPERSAND:
+    case TOKEN_AND:
         return parser_node_type_reference(p, error);
     default:
         parser_error(error, PARSER_ERROR_UNEXPECTED_TOKEN, token);
diff --git a/boot/tests/parse/type_modifiers.cskt b/boot/tests/parse/type_modifiers.cskt
new file mode 100644
index 0000000..3c639b3
--- /dev/null
+++ b/boot/tests/parse/type_modifiers.cskt
@@ -0,0 +1,22 @@
+checks for various type modifier combinations.
+&...? should always combine into an optional reference to a type.
+1-tuples should always decay into their inner element type,
+allowing for usage as a group.
+
+<<<
+
+var a &int? = nil
+var b &(int?) = nil
+var c (int) = 0
+var d (int, bool) = nil
+var e &&int = nil
+var f &&int? = nil
+
+>>>
+
+(variable (declaration a (type maybe (type reference to (type name int))) (initializer (expr (name nil)))))
+(variable (declaration b (type reference to (type maybe (type name int))) (initializer (expr (name nil)))))
+(variable (declaration c (type name int) (initializer (expr 0))))
+(variable (declaration d (type tuple (type name int) (type name bool)) (initializer (expr (name nil)))))
+(variable (declaration e (type reference to (type reference to (type name int))) (initializer (expr (name nil)))))
+(variable (declaration f (type maybe (type reference to (type reference to (type name int)))) (initializer (expr (name nil)))))
diff --git a/docs/grammar.md b/docs/grammar.md
index 18a8550..fd47c47 100644
--- a/docs/grammar.md
+++ b/docs/grammar.md
@@ -236,10 +236,10 @@ type_variant = "variant" "{" variant_cases "}"
 type_class = "class" "{" class_methods "}"
 type_function = "fun" function_header
 type_structure = "{" struct_fields "}"
-type_tuple = "(" [ type { "," type } ] ")"
+type_tuple = "(" [ type { "," type } ] ")" # 1-tuple is equivalent to a simple group
 type_array = "[" type "]"
 type_map = "[" type "=" type "]"
-type_reference = "&" type
+type_reference = "&" type_inner
 
 # common shapes