diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-03-25 15:40:59 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-03-25 15:40:59 -0400 |
| commit | 1f9147187d66e95a0ffedd4d5595ec98646b5fe1 (patch) | |
| tree | 14281e5cbde8e15fae63953b72844fb1a11d6f73 /src | |
| parent | d88d8648dc7802fbeac2f28dc5f2ef8fdfe1a9e4 (diff) | |
Make docstring tests use an actual expression AST instead of text
matching
Diffstat (limited to 'src')
| -rw-r--r-- | src/ast.c | 2 | ||||
| -rw-r--r-- | src/ast.h | 3 | ||||
| -rw-r--r-- | src/compile.c | 30 | ||||
| -rw-r--r-- | src/parse.c | 14 | ||||
| -rw-r--r-- | src/stdlib/stdlib.c | 22 | ||||
| -rw-r--r-- | src/stdlib/stdlib.h | 7 | ||||
| -rw-r--r-- | src/types.c | 1 |
7 files changed, 29 insertions, 50 deletions
@@ -165,7 +165,7 @@ CORD ast_to_xml(ast_t *ast) T(Optional, "<Optional>%r</Optional>", ast_to_xml(data.value)) T(NonOptional, "<NonOptional>%r</NonOptional>", ast_to_xml(data.value)) T(Moment, "<Moment/>") - T(DocTest, "<DocTest>%r<output>%r</output></DocTest>", optional_tagged("expression", data.expr), xml_escape(data.output)) + T(DocTest, "<DocTest>%r%r</DocTest>", optional_tagged("expression", data.expr), optional_tagged("expected", data.expected)) T(Use, "<Use>%r%r</Use>", optional_tagged("var", data.var), xml_escape(data.path)) T(InlineCCode, "<InlineCode>%r</InlineCode>", xml_escape(data.code)) T(Deserialize, "<Deserialize><type>%r</type>%r</Deserialize>", type_ast_to_xml(data.type), ast_to_xml(data.value)) @@ -323,8 +323,7 @@ struct ast_s { Moment_t moment; } Moment; struct { - ast_t *expr; - const char *output; + ast_t *expr, *expected; bool skip_source:1; } DocTest; struct { diff --git a/src/compile.c b/src/compile.c index f4f1adeb..1ddc2c36 100644 --- a/src/compile.c +++ b/src/compile.c @@ -39,7 +39,7 @@ static CORD compile_string_literal(CORD literal); CORD promote_to_optional(type_t *t, CORD code) { - if (t == THREAD_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE || t->tag == MomentType) { + if (t == THREAD_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE || t == MATCH_TYPE || t->tag == MomentType) { return code; } else if (t->tag == IntType) { switch (Match(t, IntType)->bits) { @@ -878,24 +878,13 @@ static CORD _compile_statement(env_t *env, ast_t *ast) if (!expr_t) code_err(test->expr, "I couldn't figure out the type of this expression"); - CORD output = CORD_EMPTY; - if (test->output) { - const uint8_t *raw = (const uint8_t*)CORD_to_const_char_star(test->output); - uint8_t buf[128] = {0}; - size_t norm_len = sizeof(buf); - uint8_t *norm = u8_normalize(UNINORM_NFC, (uint8_t*)raw, strlen((char*)raw)+1, buf, &norm_len); - assert(norm[norm_len-1] == 0); - output = CORD_from_char_star((char*)norm); - if (norm && norm != buf) free(norm); - } - CORD setup = CORD_EMPTY; CORD test_code; if (test->expr->tag == Declare) { auto decl = Match(test->expr, Declare); const char *varname = Match(decl->var, Var)->name; if (streq(varname, "_")) - return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .output=output, .skip_source=test->skip_source)); + return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .expected=test->expected, .skip_source=test->skip_source)); CORD var = CORD_all("_$", Match(decl->var, Var)->name); type_t *t = get_type(env, decl->value); CORD val_code = compile_maybe_incref(env, decl->value, t); @@ -922,7 +911,7 @@ static CORD _compile_statement(env_t *env, ast_t *ast) expr_t = lhs_t; } else { // Multi-assign or assignment to potentially non-idempotent targets - if (output && assign->targets->next) + if (test->expected && assign->targets->next) code_err(ast, "Sorry, but doctesting with '=' is not supported for multi-assignments"); test_code = "({ // Assignment\n"; @@ -969,12 +958,16 @@ static CORD _compile_statement(env_t *env, ast_t *ast) } else { test_code = compile(env, test->expr); } - if (test->output) { + if (test->expected) { + type_t *expected_type = get_type(env, test->expected); + if (!type_eq(expr_t, expected_type)) + code_err(ast, "The type on the top of this test (%T) is different from the type on the bottom (%T)", + expr_t, expected_type); return CORD_asprintf( "%rtest(%r, %r, %r, %ld, %ld);", setup, test_code, + compile(env, test->expected), compile_type_info(expr_t), - compile_string_literal(output), (int64_t)(test->expr->start - test->expr->file->text), (int64_t)(test->expr->end - test->expr->file->text)); } else { @@ -1952,8 +1945,10 @@ CORD compile_int_to_type(env_t *env, ast_t *ast, type_t *target) int64_t target_bits = (int64_t)Match(target, IntType)->bits; switch (target_bits) { case TYPE_IBITS64: + if (mpz_cmp_si(i, INT64_MIN) == 0) + return "I64(INT64_MIN)"; if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) - return CORD_asprintf("I64(%s)", c_literal); + return CORD_asprintf("I64(%sL)", c_literal); break; case TYPE_IBITS32: if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0) @@ -2204,6 +2199,7 @@ CORD compile_none(type_t *t) if (t == THREAD_TYPE) return "NULL"; else if (t == PATH_TYPE) return "NONE_PATH"; else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})"; + else if (t == MATCH_TYPE) return "NONE_MATCH"; switch (t->tag) { case BigIntType: return "NONE_INT"; diff --git a/src/parse.c b/src/parse.c index 9ad0db34..cc716578 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2418,22 +2418,14 @@ PARSER(parse_doctest) { spaces(&pos); ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest"); whitespace(&pos); - const char* output = NULL; + ast_t *expected = NULL; if (match(&pos, "=")) { spaces(&pos); - const char *output_start = pos, - *output_end = pos + strcspn(pos, "\r\n"); - if (output_end <= output_start) - parser_err(ctx, output_start, output_end, "You're missing expected output here"); - int64_t trailing_spaces = 0; - while (output_end - trailing_spaces - 1 > output_start && (output_end[-trailing_spaces-1] == ' ' || output_end[-trailing_spaces-1] == '\t')) - ++trailing_spaces; - output = GC_strndup(output_start, (size_t)((output_end - output_start) - trailing_spaces)); - pos = output_end; + expected = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expected expression here"); } else { pos = expr->end; } - return NewAST(ctx->file, start, pos, DocTest, .expr=expr, .output=output); + return NewAST(ctx->file, start, pos, DocTest, .expr=expr, .expected=expected); } PARSER(parse_say) { diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index ae61bd0c..d2800dcd 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -602,29 +602,19 @@ public void end_inspect(const void *expr, const TypeInfo_t *type) } __attribute__((nonnull)) -public void test_value(const void *expr, const TypeInfo_t *type, const char *expected) +public void test_value(const void *expr, const void *expected, const TypeInfo_t *type) { Text_t expr_text = generic_as_text(expr, USE_COLOR, type); - Text_t type_name = generic_as_text(NULL, false, type); - - Text_t expected_text = Text$from_str(expected); - Text_t expr_plain = USE_COLOR ? generic_as_text(expr, false, type) : expr_text; - bool success = Text$equal_values(expr_plain, expected_text); - if (!success) { - OptionalMatch_t colon = Text$find(expected_text, Text(":"), I_small(1)); - if (colon.index.small) { - Text_t with_type = Text$concat(expr_plain, Text(" : "), type_name); - success = Text$equal_values(with_type, expected_text); - } - } + Text_t expected_text = generic_as_text(expected, USE_COLOR, type); + bool success = Text$equal_values(expr_text, expected_text); if (!success) { print_stack_trace(stderr, 2, 4); fprintf(stderr, USE_COLOR - ? "\n\x1b[31;7m ==================== TEST FAILED ==================== \x1b[0;1m\n\nYou expected: \x1b[36;1m%s\x1b[0m\n\x1b[1m But I got:\x1b[m %k\n\n" - : "\n==================== TEST FAILED ====================\n\nYou expected: %s\n But I got: %k\n\n", - expected, &expr_text); + ? "\n\x1b[31;7m ==================== TEST FAILED ==================== \x1b[0;1m\n\nYou expected: \x1b[m%k\x1b[0m\n\x1b[1m But I got:\x1b[m %k\n\n" + : "\n==================== TEST FAILED ====================\n\nYou expected: %k\n But I got: %k\n\n", + &expected_text, &expr_text); fflush(stderr); raise(SIGABRT); diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 1b633dff..49ec43fb 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -39,10 +39,11 @@ void end_inspect(const void *expr, const TypeInfo_t *type); end_inspect(&_expr, typeinfo); \ } __attribute__((nonnull)) -void test_value(const void *expr, const TypeInfo_t *type, const char *expected); -#define test(expr, typeinfo, expected, start, end) {\ +void test_value(const void *expr, const void *expected, const TypeInfo_t *type); +#define test(expr, expected, typeinfo, start, end) {\ auto _expr = expr; \ - test_value(&_expr, typeinfo, expected); \ + auto _expected = expected; \ + test_value(&_expr, &_expected, typeinfo); \ } void say(Text_t text, bool newline); diff --git a/src/types.c b/src/types.c index 506850e8..7e5f262d 100644 --- a/src/types.c +++ b/src/types.c @@ -285,6 +285,7 @@ PUREFUNC bool has_heap_memory(type_t *t) PUREFUNC bool has_stack_memory(type_t *t) { + if (!t) return false; switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); |
