aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2025-07-05 22:48:09 +0200
committerMel <mel@rnrd.eu>2025-07-05 22:48:09 +0200
commit620d82a5e314a82784e02b4af387a67d53242149 (patch)
treee91774653130cb34d19901aeaa7cdd6be97f3e93
parentb71f85a7c3ee9e36b6eab9f113436fa990f6e561 (diff)
downloadcatskill-620d82a5e314a82784e02b4af387a67d53242149.tar.zst
catskill-620d82a5e314a82784e02b4af387a67d53242149.zip
Correctly recurse over self-containing non-homogenous postfix-type expressions
Signed-off-by: Mel <mel@rnrd.eu>
-rw-r--r--boot/parse.c208
-rw-r--r--boot/tests/parse/postfix_chaining.cskt13
-rw-r--r--boot/tests/parse/try_must.cskt12
3 files changed, 131 insertions, 102 deletions
diff --git a/boot/parse.c b/boot/parse.c
index c08d247..5d839a8 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -806,158 +806,162 @@ parser_expression_primary(struct Parser* p, struct Parser_Error* error)
}
struct Expression*
-parser_expression_member(struct Parser* p, struct Parser_Error* error)
+parser_expression_postfix_member(
+ struct Parser* p, struct Expression* subject, struct Parser_Error* error)
{
- struct Expression* left = CHECK(parser_expression_primary(p, error));
-
- // NOTE: see `parser_expression_postfix_call_or_construct`.
- while (parser_probe(p, TOKEN_DOT)) {
- parser_next(p);
- struct Token name_token = CHECK(parser_need(p, TOKEN_NAME, error));
- struct String name = name_token.value.name;
+ CHECK(parser_need(p, TOKEN_DOT, error));
- struct Span span = span_merge(left->span, name_token.span);
- union Expression_Value value = { .member = { left, name } };
- left = expression_new(EXPRESSION_MEMBER, value, span, name_token.location);
- }
+ struct Token name_token = CHECK(parser_need(p, TOKEN_NAME, error));
+ struct String name = name_token.value.name;
- return left;
+ struct Span span = span_merge(subject->span, name_token.span);
+ union Expression_Value value = { .member = { subject, name } };
+ return expression_new(EXPRESSION_MEMBER, value, span, name_token.location);
}
struct Expression*
parser_expression_postfix_call_or_construct(
struct Parser* p, struct Expression* subject, struct Parser_Error* error)
{
- // NOTE: because of the way the parser works, we have to parse all subsequent
- // call/construct expressions in the same loop.
- // this is the case with an expression like `meow_function()(123)`,
- // where the hypothetical `meow_function` returns a function pointer.
-
// calls and constructs look identical in the parser.
// * call: `meow_function(123, "hello")`
// * construct: `Meow(num = 123, str = "hello")`
- while (parser_probe(p, TOKEN_ROUND_OPEN)) {
- parser_next(p);
-
- struct String_Array argument_names = string_array_new();
- struct Expression *arguments_head = nil, *arguments_current = nil;
- while (!parser_probe(p, TOKEN_ROUND_CLOSE)) {
+ CHECK(parser_need(p, TOKEN_ROUND_OPEN, error));
- // check if we have a named argument.
- struct String name = string_empty();
- struct Token name_token = parser_peek(p), next = parser_peek_further(p);
- if (token_is(&name_token, TOKEN_NAME) && token_is(&next, TOKEN_ASSIGN)) {
- parser_next(p);
- parser_next(p);
- name = name_token.value.name;
- }
-
- struct Expression* argument = CHECK(parser_expression(p, error));
- if (!arguments_head)
- arguments_head = argument;
- else
- arguments_current->next = argument;
- arguments_current = argument;
+ struct String_Array argument_names = string_array_new();
+ struct Expression *arguments_head = nil, *arguments_current = nil;
+ while (!parser_probe(p, TOKEN_ROUND_CLOSE)) {
+ // check if we have a named argument.
+ struct String name = string_empty();
+ struct Token name_token = parser_peek(p), next = parser_peek_further(p);
+ if (token_is(&name_token, TOKEN_NAME) && token_is(&next, TOKEN_ASSIGN)) {
+ parser_next(p);
+ parser_next(p);
+ name = name_token.value.name;
+ }
- // if we have a named argument, we need to add it to the names array,
- // otherwise we just add an empty string.
- string_array_add(&argument_names, name);
+ struct Expression* argument = CHECK(parser_expression(p, error));
+ if (!arguments_head)
+ arguments_head = argument;
+ else
+ arguments_current->next = argument;
+ arguments_current = argument;
- if (parser_probe(p, TOKEN_COMMA)) parser_next(p);
- }
+ // if we have a named argument, we need to add it to the names array,
+ // otherwise we just add an empty string.
+ string_array_add(&argument_names, name);
- struct Token token = CHECK(parser_need(p, TOKEN_ROUND_CLOSE, error));
- struct Span span = span_merge(subject->span, token.span);
- union Expression_Value value = {
- .call_or_construct = { subject, arguments_head, argument_names }
- };
- subject = expression_new(EXPRESSION_CALL_OR_CONSTRUCT, value, span, token.location);
+ if (parser_probe(p, TOKEN_COMMA)) parser_next(p);
}
- return subject;
+ struct Token token = CHECK(parser_need(p, TOKEN_ROUND_CLOSE, error));
+ struct Span span = span_merge(subject->span, token.span);
+ union Expression_Value value = {
+ .call_or_construct = { subject, arguments_head, argument_names }
+ };
+ return expression_new(EXPRESSION_CALL_OR_CONSTRUCT, value, span, token.location);
}
struct Expression*
parser_expression_postfix_subscript(
struct Parser* p, struct Expression* subject, struct Parser_Error* error)
{
- // NOTE: see `parser_expression_postfix_call_or_construct`.
- while (parser_probe(p, TOKEN_SQUARE_OPEN)) {
- parser_next(p);
+ CHECK(parser_need(p, TOKEN_SQUARE_OPEN, error));
- struct Expression* index = CHECK(parser_expression(p, error));
- struct Token token = CHECK(parser_need(p, TOKEN_SQUARE_CLOSE, error));
+ struct Expression* index = CHECK(parser_expression(p, error));
+ struct Token token = CHECK(parser_need(p, TOKEN_SQUARE_CLOSE, error));
- struct Span span = span_merge(subject->span, span_merge(index->span, token.span));
- union Expression_Value value = { .subscript = { subject, index } };
- subject = expression_new(EXPRESSION_SUBSCRIPT, value, span, token.location);
- }
- return subject;
+ struct Span span = span_merge(subject->span, span_merge(index->span, token.span));
+ union Expression_Value value = { .subscript = { subject, index } };
+ return expression_new(EXPRESSION_SUBSCRIPT, value, span, token.location);
+}
+
+struct Expression*
+parser_expression_postfix_increment_decrement(
+ struct Parser* p, struct Expression* subject, struct Parser_Error* error)
+{
+ struct Token token = parser_peek(p);
+ enum Increment_Decrement_Operation operation = increment_decrement_operation_from_token(&token);
+
+ struct Expression_Increment_Decrement inc_dec = {
+ .prefix = false,
+ .subject = subject,
+ .operation = operation,
+ };
+
+ struct Span span = span_merge(subject->span, token.span);
+ union Expression_Value value = { .increment_decrement = inc_dec };
+ return expression_new(EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
}
struct Expression*
parser_expression_postfix_try(
struct Parser* p, struct Expression* subject, struct Parser_Error* error)
{
- while (parser_probe(p, TOKEN_QUESTION)) {
- struct Token question_token = parser_next(p);
+ struct Token question_token = parser_next(p);
- struct Span span = span_merge(subject->span, question_token.span);
- union Expression_Value value = { .try = { subject } };
- subject = expression_new(EXPRESSION_TRY, value, span, subject->location);
- }
- return subject;
+ struct Span span = span_merge(subject->span, question_token.span);
+ union Expression_Value value = { .try = { subject } };
+ return expression_new(EXPRESSION_TRY, value, span, subject->location);
}
struct Expression*
parser_expression_postfix_must(
struct Parser* p, struct Expression* subject, struct Parser_Error* error)
{
- while (parser_probe(p, TOKEN_BANG)) {
- struct Token bang_token = parser_next(p);
+ struct Token bang_token = CHECK(parser_need(p, TOKEN_BANG, error));
- struct Span span = span_merge(subject->span, bang_token.span);
- union Expression_Value value = { .try = { subject } };
- subject = expression_new(EXPRESSION_MUST, value, span, subject->location);
- }
- return subject;
+ struct Span span = span_merge(subject->span, bang_token.span);
+ union Expression_Value value = { .try = { subject } };
+ return expression_new(EXPRESSION_MUST, value, span, subject->location);
}
struct Expression*
parser_expression_postfix(struct Parser* p, struct Parser_Error* error)
{
- struct Expression* expression = CHECK(parser_expression_member(p, error));
+ struct Expression* expression = CHECK(parser_expression_primary(p, error));
- struct Token token = parser_peek(p);
- switch (token.kind) {
- case TOKEN_ROUND_OPEN:
- return parser_expression_postfix_call_or_construct(p, expression, error);
- case TOKEN_SQUARE_OPEN:
- return parser_expression_postfix_subscript(p, expression, error);
+ // NOTE: we have to parse all subsequent postfix expressions non-recursively.
+ for (;;) {
+ struct Token token = parser_peek(p);
+ switch (token.kind) {
+ case TOKEN_ROUND_OPEN:
+ expression = parser_expression_postfix_call_or_construct(p, expression, error);
+ continue;
+ case TOKEN_SQUARE_OPEN:
+ expression = parser_expression_postfix_subscript(p, expression, error);
+ continue;
+ case TOKEN_DOT:
+ expression = parser_expression_postfix_member(p, expression, error);
+ continue;
+ case TOKEN_QUESTION:
+ expression = parser_expression_postfix_try(p, expression, error);
+ continue;
+ case TOKEN_BANG:
+ expression = parser_expression_postfix_must(p, expression, error);
+ continue;
+ default:;
+ }
- case TOKEN_QUESTION:
- return parser_expression_postfix_try(p, expression, error);
- case TOKEN_BANG:
- return parser_expression_postfix_must(p, expression, error);
- default:;
- }
+ enum Increment_Decrement_Operation inc_dec_op =
+ increment_decrement_operation_from_token(&token);
+ if (inc_dec_op) {
+ parser_next(p);
+ struct Expression_Increment_Decrement inc_dec = {
+ .prefix = false,
+ .subject = expression,
+ .operation = inc_dec_op,
+ };
- enum Increment_Decrement_Operation inc_dec_op =
- increment_decrement_operation_from_token(&token);
- if (inc_dec_op) {
- parser_next(p);
- struct Expression_Increment_Decrement inc_dec = {
- .prefix = false,
- .subject = expression,
- .operation = inc_dec_op,
- };
+ struct Span span = span_merge(expression->span, token.span);
+ union Expression_Value value = { .increment_decrement = inc_dec };
+ expression =
+ expression_new(EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
+ continue;
+ }
- struct Span span = span_merge(expression->span, token.span);
- union Expression_Value value = { .increment_decrement = inc_dec };
- return expression_new(EXPRESSION_INCREMENT_DECREMENT, value, span, token.location);
+ return expression;
}
-
- return expression;
}
struct Expression*
diff --git a/boot/tests/parse/postfix_chaining.cskt b/boot/tests/parse/postfix_chaining.cskt
new file mode 100644
index 0000000..c2267b4
--- /dev/null
+++ b/boot/tests/parse/postfix_chaining.cskt
@@ -0,0 +1,13 @@
+correct handling of different recursive postfix expressions,
+to prevent previous regression of only allowing homogeneous
+postfix recursion.
+
+<<<
+
+a[1][2][3]
+a.b("hi")!?++--
+
+>>>
+
+(expr (subscript (expr (subscript (expr (subscript (expr (name a)) (expr 1))) (expr 2))) (expr 3)))
+(expr (increment/decrement -- postfix (expr (increment/decrement ++ postfix (expr (try (expr (must (expr (call/construct (expr (member of (expr (name a)) named b)) (arg (expr "hi")))))))))))) \ No newline at end of file
diff --git a/boot/tests/parse/try_must.cskt b/boot/tests/parse/try_must.cskt
index e69de29..b111ed5 100644
--- a/boot/tests/parse/try_must.cskt
+++ b/boot/tests/parse/try_must.cskt
@@ -0,0 +1,12 @@
+chaining of try and must expressions, with themselves
+and across multiple members
+
+<<<
+
+a!.b?.c?
+c!!!
+
+>>>
+
+(expr (try (expr (member of (expr (try (expr (member of (expr (must (expr (name a)))) named b)))) named c))))
+(expr (must (expr (must (expr (must (expr (name c)))))))) \ No newline at end of file