diff --git a/CMakeLists.txt b/CMakeLists.txt index a1fc8e95e..c6a17877d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,4 +90,4 @@ add_executable(c3c target_compile_options(c3c PRIVATE -Wimplicit-int -Werror -Wall -Wno-unknown-pragmas -Wextra -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) -target_link_libraries(c3c m ${llvm_libs}) \ No newline at end of file +target_link_libraries(c3c m ${llvm_libs}) diff --git a/README.md b/README.md index 1cf7a10b5..631ac97d8 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,16 @@ There are some small work being done on the parser here, but most of the structu - Improved integration with C. - Generic macros. - Update of error system +- Imports aren't quite stable +- Strings, vararrays aren't finalized +- Stdlib not started +- More tests and support for multi file tests. #### What's working? -- Lexing and parsing works (except for the exceptions noted above). -- Simple "hello world" -- Most simpler C code. +- Lexing and parsing works with some minor exceptions +- Normal code works +- You can use any C function by declaring it as a normal C3 function with external (For more details see missing.txt) diff --git a/resources/testfragments/super_simple.c3 b/resources/testfragments/super_simple.c3 index 3669f1269..c443837ce 100644 --- a/resources/testfragments/super_simple.c3 +++ b/resources/testfragments/super_simple.c3 @@ -1,8 +1,40 @@ module bar; - +import baz; +import gen; typedef int as Bob; +/* +macro namespaceFor(ns; void(int i) $body) +{ + for (usize i = 0; i < ns.avail; i++) + { + @$body(i); + } +} +macro usize nameHashFn(char *strp, usize strl) +{ + usize hash = 5381; + for (usize i = 0; i < strl; i++) + { + hash = ((hash << 5) + hash) ^ cast(strp[i], usize); + } + return hash; +} + +macro INode *nodesGet(nodes, index) +{ + return cast(node + 1, INode**)[index]; +} + +macro lexReturnPuncTok!(tok, skip, implicit lex, implicit srcp) +{ + lex.toktype = tok; + lex.tokp = srcp; + lex.srcp = srcp + skip; + return; +} +*/ func int! testThrow4(int x) { return x > 4 ? TestErr1! : TestErr2!; @@ -776,12 +808,14 @@ func void show() num = frob(num + 1); printf("Num: %d\n", num); } -func void testAllErrors() +func void! testAllErrors() { printf("Try test all\n"); int! i = 7; int*! iptr = &i; + int* xd = iptr!!; + printf("%d\n", *xd); int**! iptrptr = &iptr; catch (err = iptrptr) { @@ -1368,6 +1402,7 @@ public func int! decode(char[] infile, byte[] out) */ func int main(int x) { + baz::runBaz(); testBreak(); show(); testMacros(); diff --git a/resources/testfragments/super_simple2.c3 b/resources/testfragments/super_simple2.c3 new file mode 100644 index 000000000..f2d449024 --- /dev/null +++ b/resources/testfragments/super_simple2.c3 @@ -0,0 +1,17 @@ +module baz; +import gen; + +extern func void printf(char *hello, ...); + +//define Bobafett(int) as BobaInt; + +public func void runBaz() +{ + printf("Baz func\n"); +// BobaInt x = { 1 }; +} + +func void notVisible() +{ + +} \ No newline at end of file diff --git a/resources/testfragments/super_simple3.c3 b/resources/testfragments/super_simple3.c3 new file mode 100644 index 000000000..1a82e2221 --- /dev/null +++ b/resources/testfragments/super_simple3.c3 @@ -0,0 +1,6 @@ +module gen (Type); +/* +struct BobaFett +{ + Type x; +}*/ \ No newline at end of file diff --git a/resources/tests/enum_errors.c3 b/resources/tests/enum_errors.c3 deleted file mode 100644 index 7836596c9..000000000 --- a/resources/tests/enum_errors.c3 +++ /dev/null @@ -1,24 +0,0 @@ - -enum EnumWithErrorWithMissingName : int (int) -// @error The function parameter must be named -{ - TEST -} - -enum EnumWithErrorData : int (int -// @error Unexpected end of parameter list -{ - TEST -} - -enum EnumTestOverflow -{ - VALUE = 0x80000000, -// @error does not fit into 'int' -} - -enum EnumTestErrorType : float -// @error The enum type must be an integer type not 'float' -{ - VALUE_BOOM -} \ No newline at end of file diff --git a/resources/tests/error_decl_ok.c3 b/resources/tests/error_decl_ok.c3 deleted file mode 100644 index e9ced8885..000000000 --- a/resources/tests/error_decl_ok.c3 +++ /dev/null @@ -1,12 +0,0 @@ -module errors; - -error TheError -{ - FOO_MISSING, - NO_SUCH_FILE, -} - -error OtherError -{ - BAR_OVERFLOWED -} diff --git a/src/build/build_options.c b/src/build/build_options.c index e8319cda6..484f6e95f 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -273,6 +273,11 @@ static void parse_option() build_options.lib_dir[build_options.lib_count++] = check_dir(next_arg()); return; } + if (match_longopt("test")) + { + build_options.test_mode = true; + return; + } if (match_longopt("path")) { if (at_end() || next_is_opt()) error_exit("error: --path needs a directory."); diff --git a/src/build/build_options.h b/src/build/build_options.h index 4c1400b73..5d2f2e5f0 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -113,6 +113,7 @@ typedef struct bool debug_mode; bool emit_llvm; bool emit_bitcode; + bool test_mode; } BuildOptions; diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 1a13a0670..330b349d9 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -499,11 +499,6 @@ void fprint_expr_recursive(Context *context, FILE *file, Expr *expr, int indent) if (!expr) return; switch (expr->expr_kind) { - case EXPR_FAIL_CHECK: - DUMP("(fail-check"); - DUMPEXPC(expr); - DUMPEXPR(expr->fail_check_expr); - DUMPEND(); case EXPR_DECL_LIST: DUMP("(decllist"); DUMPASTS(expr->dexpr_list_expr); @@ -649,7 +644,7 @@ void fprint_expr_recursive(Context *context, FILE *file, Expr *expr, int indent) DUMPEND(); case EXPR_GUARD: DUMP("(guard"); - DUMPEXPR(expr->guard_expr); + DUMPEXPR(expr->guard_expr.inner); DUMPEND(); case EXPR_ELSE: if (expr->else_expr.is_jump) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 73cb297a9..bf46bbfbf 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -112,6 +112,10 @@ void compiler_compile(BuildTarget *target) { sema_analysis_pass_decls(contexts[i]); } + VECEACH(contexts, i) + { + sema_analysis_pass_functions(contexts[i]); + } if (diagnostics.errors > 0) exit(EXIT_FAILURE); llvm_codegen_setup(); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index dfe3a1641..8bc097dd1 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -397,7 +397,6 @@ typedef struct void *continue_target; unsigned scope_id; AstId parent; - } LabelDecl; typedef struct @@ -631,6 +630,12 @@ typedef struct TokenId span; } Label; +typedef struct +{ + Expr *inner; + AstId defer; +} ExprGuard; + struct _Expr { ExprKind expr_kind : 8; @@ -647,12 +652,11 @@ struct _Expr ExprRange range_expr; ExprStructValue struct_value_expr; ExprTypeAccess type_access; - Expr *guard_expr; + ExprGuard guard_expr; Expr *trycatch_expr; ExprElse else_expr; ExprBinary binary_expr; ExprTernary ternary_expr; - Expr *fail_check_expr; ExprUnary unary_expr; ExprPostUnary post_expr; ExprCall call_expr; @@ -969,6 +973,18 @@ typedef union { const char *string; long double value; + struct + { + union + { + uint8_t b[8]; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + } char_lit; + char width; + }; } TokenData; typedef struct @@ -987,7 +1003,6 @@ typedef struct typedef struct _Context { - unsigned current_block; BuildTarget *target; Path *module_name; TokenId* module_parameters; @@ -1299,7 +1314,7 @@ static inline uint32_t TOKKLEN(Token token) { return TOKKLOC(token)->length; } #define TOKLEN(T) _Generic((T), TokenId: TOKILEN, Token:TOKKLEN)(T) #define TOKVALID(_tok) (_tok.index != 0) -Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search); +Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search, Decl **private_decl); void parse_file(Context *context); Path *path_create_from_string(Context *context, const char *string, size_t len, SourceSpan span); @@ -1315,13 +1330,14 @@ const char *resolve_status_to_string(ResolveStatus status); void sema_analysis_pass_process_imports(Context *context); void sema_analysis_pass_conditional_compilation(Context *context); void sema_analysis_pass_decls(Context *context); +void sema_analysis_pass_functions(Context *context); bool sema_add_local(Context *context, Decl *decl); bool sema_unwrap_var(Context *context, Decl *decl); bool sema_rewrap_var(Context *context, Decl *decl); bool sema_analyse_statement(Context *context, Ast *statement); -Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl); +Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl, Decl **private_decl); bool sema_resolve_type_info(Context *context, TypeInfo *type_info); bool sema_resolve_type_shallow(Context *context, TypeInfo *type_info); diff --git a/src/compiler/diagnostics.c b/src/compiler/diagnostics.c index 63c0a4aad..be0447f40 100644 --- a/src/compiler/diagnostics.c +++ b/src/compiler/diagnostics.c @@ -23,6 +23,22 @@ typedef enum static void print_error2(SourceLocation *location, const char *message, PrintType print_type) { + if (build_options.test_mode) + { + switch (print_type) + { + case PRINT_TYPE_ERROR: + eprintf("Error|%s|%d|%s\n", location->file->name, location->line, message); + return; + case PRINT_TYPE_PREV: + return; + case PRINT_TYPE_WARN: + eprintf("Warning|%s|%d|%s\n", location->file->name, location->line, message); + return; + default: + UNREACHABLE + } + } static const int LINES_SHOWN = 4; unsigned max_line_length = (int)round(log10(location->line)) + 1; @@ -47,7 +63,7 @@ static void print_error2(SourceLocation *location, const char *message, PrintTyp uint32_t line_number = location->line + 1 - i; SourceLoc line_start = location->file->lines[line_number - 1]; - SourceLoc line_end = line_number == lines_in_file ? location->file->end_id : + SourceLoc line_end = line_number == lines_in_file ? location->file->end_id + 1 : location->file->lines[line_number]; uint32_t line_len = line_end - line_start - 1; start = location->file->contents + line_start - location->file->start_id; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 1f8e0c5e6..d323c9bb3 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -190,7 +190,6 @@ typedef enum EXPR_DESIGNATED_INITIALIZER, EXPR_COMPOUND_LITERAL, EXPR_FAILABLE, - EXPR_FAIL_CHECK, EXPR_DECL_LIST } ExprKind; @@ -365,6 +364,7 @@ typedef enum TOKEN_STRING, // "Teststring" TOKEN_INTEGER, // 123 0x23 0b10010 0o327 + TOKEN_CHAR_LITERAL, // 'a' 'FO' 'BARS' '\u1232' TOKEN_REAL, // 0x23.2p-2a 43.23e23 TOKEN_COMMENT, // Comment diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 4eebc5d2c..d9ae24e8a 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -82,7 +82,7 @@ static bool add_error_token(Lexer *lexer, const char *message, ...) add_generic_token(lexer, TOKEN_INVALID_TOKEN, &loc, &data); va_list list; va_start(list, message); - diag_verror_range(loc, message, list); + sema_verror_range(loc, message, list); va_end(list); return false; } @@ -425,34 +425,132 @@ static inline bool scan_digit(Lexer *lexer) #pragma mark --- Character & string scan +static inline int64_t scan_hex_literal(Lexer *lexer, int positions) +{ + int64_t hex = 0; + for (int j = 0; j < positions; j++) + { + hex <<= 4U; + int i = char_to_nibble(next(lexer)); + if (i < 0) + { + if (lexer->current[-1] == '\'') + { + backtrack(lexer); + return -1; + } + while (lexer->current[0] != '\'' && lexer->current[0] != '\0' && ++j < positions) + { + next(lexer); + } + return -1; + } + hex += i; + } + return hex; +} + static inline bool scan_char(Lexer *lexer) { int width = 0; char c; + union + { + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uint8_t b[8]; + } bytes; while ((c = next(lexer)) != '\'') { - if (c == '\0' || c == '\n') return add_error_token(lexer, "Character literal did not terminate."); - width++; - // Was this an escape? + if (c == '\0') + { + return add_error_token(lexer, "Character literal did not terminate."); + } + if (width > 7) + { + width++; + continue; + } + if (c != '\\') + { + bytes.b[width++] = c; + } if (c == '\\') { - // Yes, so check if it's hex: - if (next(lexer) == 'x') + c = next(lexer); + const char *start = lexer->current; + char escape = is_valid_escape(c); + if (escape == -1) { - // Walk through the two characters. - for (int i = 0; i < 2; i++) + lexer->lexing_start = start; + return add_error_token(lexer, "Invalid escape sequence '\\%c'.", c); + } + switch (escape) + { + case 'x': { - if (!is_hex(next(lexer))) + int64_t hex = scan_hex_literal(lexer, 2); + if (hex < 0) { - return add_error_token(lexer, - "An escape sequence starting with " - "'\\x' needs to be followed by " - "a two digit hexadecimal number."); + lexer->lexing_start = start; + // Fix underlining if this is an unfinished escape. + return add_error_token(lexer, "Expected a two character hex value after \\x."); } + bytes.b[width++] = hex; + break; } + case 'u': + { + int64_t hex = scan_hex_literal(lexer, 4); + if (hex < 0) + { + lexer->lexing_start = start; + return add_error_token(lexer, "Expected a four character hex value after \\u."); + } + if (build_target.little_endian) + { + bytes.b[width++] = hex & 0xFF; + bytes.b[width++] = hex >> 8; + } + else + { + bytes.b[width++] = hex >> 8; + bytes.b[width++] = hex & 0xFF; + } + break; + } + case 'U': + { + int64_t hex = scan_hex_literal(lexer, 8); + if (hex < 0) + { + lexer->lexing_start = start; + return add_error_token(lexer, "Expected an eight character hex value after \\U."); + } + if (build_target.little_endian) + { + bytes.b[width++] = hex & 0xFF; + bytes.b[width++] = (hex >> 8) & 0xFF; + bytes.b[width++] = (hex >> 16) & 0xFF; + bytes.b[width++] = hex >> 24; + } + else + { + bytes.b[width++] = hex >> 24; + bytes.b[width++] = (hex >> 16) & 0xFF; + bytes.b[width++] = (hex >> 8) & 0xFF; + bytes.b[width++] = hex & 0xFF; + } + break; + } + default: + bytes.b[width++] = escape; } } } + if (width == 0) { return add_error_token(lexer, "The character literal was empty."); @@ -461,7 +559,13 @@ static inline bool scan_char(Lexer *lexer) { add_error_token(lexer, "Character literals may only be 1, 2 or 8 characters wide."); } - return add_token(lexer, TOKEN_INTEGER, lexer->lexing_start); + + TokenData *data; + SourceLocation *loc; + add_generic_token(lexer, TOKEN_CHAR_LITERAL, &loc, &data); + data->char_lit.u64 = bytes.u64; + data->width = (char)width; + return true; } static inline bool scan_string(Lexer *lexer) @@ -643,9 +747,9 @@ static bool lexer_scan_token_inner(Lexer *lexer) case ']': return add_token(lexer, TOKEN_RBRACKET, "]"); case '.': - if (match(lexer, '.')) return match(lexer, '.') ? add_token(lexer, TOKEN_ELLIPSIS, "...") : add_token(lexer, - TOKEN_DOTDOT, - ".."); + if (match(lexer, '.')) return match(lexer, '.') + ? add_token(lexer, TOKEN_ELLIPSIS, "...") + : add_token(lexer, TOKEN_DOTDOT, ".."); return add_token(lexer, TOKEN_DOT, "."); case '~': return add_token(lexer, TOKEN_BIT_NOT, "~"); @@ -748,7 +852,13 @@ void lexer_init_with_file(Lexer *lexer, File *file) lexer->lexer_index = file->token_start_id; while(1) { - if (!lexer_scan_token_inner(lexer)) break; + if (!lexer_scan_token_inner(lexer)) + { + if (reached_end(lexer)) break; + while (!reached_end(lexer) && peek(lexer) != '\n') next(lexer); + lexer->lexing_start = lexer->current; + continue; + } } } diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 5c08bb020..1269a0374 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -179,8 +179,6 @@ LLVMValueRef gencontext_emit_address(GenContext *context, Expr *expr) UNREACHABLE case EXPR_MACRO_BLOCK: TODO - case EXPR_FAIL_CHECK: - UNREACHABLE case EXPR_IDENTIFIER: return decl_ref(expr->identifier_expr.decl); case EXPR_UNARY: @@ -928,6 +926,7 @@ static inline LLVMValueRef gencontext_emit_else_jump_expr(GenContext *context, E return value; } + static LLVMValueRef gencontext_emit_else_expr(GenContext *context, Expr *expr) { if (expr->else_expr.is_jump) return gencontext_emit_else_jump_expr(context, expr); @@ -979,6 +978,47 @@ static LLVMValueRef gencontext_emit_else_expr(GenContext *context, Expr *expr) } + +static inline LLVMValueRef gencontext_emit_guard_expr(GenContext *context, Expr *expr) +{ + LLVMBasicBlockRef guard_block = gencontext_create_free_block(context, "guard_block"); + LLVMBasicBlockRef no_err_block = gencontext_create_free_block(context, "noerr_block"); + + // Store catch/error var + PUSH_ERROR(); + + // Set the catch/error var + LLVMValueRef error_var = gencontext_emit_alloca(context, llvm_type(type_error), ""); + + context->error_var = error_var; + context->catch_block = guard_block; + + LLVMValueRef value = gencontext_emit_expr(context, expr->guard_expr.inner); + + // Restore. + POP_ERROR(); + + // Emit success and to end. + gencontext_emit_br(context, no_err_block); + + POP_ERROR(); + + // Emit else + gencontext_emit_block(context, guard_block); + + // Ensure we are on a branch that is non empty. + if (gencontext_check_block_branch_emit(context)) + { + gencontext_emit_defer(context, expr->guard_expr.defer, 0); + LLVMBuildRet(context->builder, gencontext_emit_load(context, type_error, error_var)); + context->current_block = NULL; + context->current_block_is_target = NULL; + } + gencontext_emit_block(context, no_err_block); + + return value; +} + static LLVMValueRef gencontext_emit_binary_expr(GenContext *context, Expr *expr) { BinaryOp binary_op = expr->binary_expr.operator; @@ -1366,33 +1406,6 @@ static inline LLVMValueRef gencontext_emit_failable(GenContext *context, Expr *e return LLVMGetUndef(llvm_type(expr->type)); } -LLVMValueRef gencontext_emit_check_failable(GenContext *context, Expr *expr) -{ - PUSH_ERROR(); - - LLVMBasicBlockRef after_check_block = gencontext_create_free_block(context, "after_check"); - - context->error_var = NULL; - context->catch_block = after_check_block; - - gencontext_emit_expr(context, expr->fail_check_expr); - - POP_ERROR(); - - LLVMBasicBlockRef phi_block = gencontext_create_free_block(context, "checkphi"); - LLVMBasicBlockRef normal_block = context->current_block; - gencontext_emit_br(context, phi_block); - gencontext_emit_block(context, after_check_block); - gencontext_emit_br(context, phi_block); - - gencontext_emit_block(context, phi_block); - LLVMValueRef phi = LLVMBuildPhi(context->builder, llvm_type(type_bool), "val"); - LLVMValueRef logic_values[2] = { llvm_int(type_bool, 0), llvm_int(type_bool, 1) }; - LLVMBasicBlockRef blocks[2] = { after_check_block, normal_block }; - LLVMAddIncoming(phi, logic_values, blocks, 2); - return phi; -} - LLVMValueRef gencontext_emit_expr(GenContext *context, Expr *expr) { NESTED_RETRY: @@ -1405,8 +1418,6 @@ NESTED_RETRY: case EXPR_DESIGNATED_INITIALIZER: // Should only appear when generating designated initializers. UNREACHABLE - case EXPR_FAIL_CHECK: - return gencontext_emit_check_failable(context, expr); case EXPR_FAILABLE: return gencontext_emit_failable(context, expr); case EXPR_TRY: @@ -1436,7 +1447,7 @@ NESTED_RETRY: case EXPR_POST_UNARY: return gencontext_emit_post_unary_expr(context, expr); case EXPR_GUARD: - return gencontext_emit_trycatch_expr(context, expr); + return gencontext_emit_guard_expr(context, expr); case EXPR_TYPEID: return gencontext_emit_typeid(context, expr); case EXPR_TYPE_ACCESS: diff --git a/src/compiler/module.c b/src/compiler/module.c index 307717a2e..5c19cad8b 100644 --- a/src/compiler/module.c +++ b/src/compiler/module.c @@ -4,7 +4,7 @@ #include "compiler_internal.h" -Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search) +Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search, Decl **private_decl) { Decl *decl = stable_get(&module->symbols, symbol); if (decl) @@ -12,11 +12,18 @@ Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch switch (decl->visibility) { case VISIBLE_LOCAL: + *private_decl = decl; + decl = NULL; + break; case VISIBLE_EXTERN: decl = NULL; break; case VISIBLE_MODULE: - if (search == MODULE_SYMBOL_SEARCH_EXTERNAL) decl = NULL; + if (search == MODULE_SYMBOL_SEARCH_EXTERNAL) + { + *private_decl = decl; + decl = NULL; + } break; case VISIBLE_PUBLIC: break; @@ -27,7 +34,7 @@ Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch if (search == MODULE_SYMBOL_SEARCH_THIS) search = MODULE_SYMBOL_SEARCH_PARENT; VECEACH (module->sub_modules, i) { - if ((decl = module_find_symbol(module->sub_modules[i], symbol, search))) break; + if ((decl = module_find_symbol(module->sub_modules[i], symbol, search, private_decl))) break; } } return decl; diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index c793e2bf0..a5e55eeb8 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -224,24 +224,13 @@ static bool token_may_end_expression(TokenType type) return false; } } -static inline Expr *parse_check_failable(Context *context, Expr *left_side) -{ - Expr *expr_unwrap = EXPR_NEW_EXPR(EXPR_FAIL_CHECK, left_side); - advance_and_verify(context, TOKEN_QUESTION); - expr_unwrap->fail_check_expr = left_side; - RANGE_EXTEND_PREV(expr_unwrap); - return expr_unwrap; -} + + static Expr *parse_ternary_expr(Context *context, Expr *left_side) { assert(expr_ok(left_side)); - if (TOKEN_IS(TOKEN_QUESTION) && token_may_end_expression(context->next_tok.type)) - { - return parse_check_failable(context, left_side); - } - Expr *expr_ternary = EXPR_NEW_EXPR(EXPR_TERNARY, left_side); expr_ternary->ternary_expr.cond = left_side; @@ -459,9 +448,10 @@ static Expr *parse_try_expr(Context *context, Expr *left) static Expr *parse_bangbang_expr(Context *context, Expr *left) { - Expr *guard_expr = EXPR_NEW_TOKEN(EXPR_GUARD, context->tok); + Expr *guard_expr = EXPR_NEW_EXPR(EXPR_GUARD, left); advance_and_verify(context, TOKEN_BANGBANG); - guard_expr->guard_expr = left; + guard_expr->guard_expr.inner = left; + RANGE_EXTEND_PREV(guard_expr); return guard_expr; } @@ -499,73 +489,7 @@ static Expr *parse_integer(Context *context, Expr *left) Expr *expr_int = EXPR_NEW_TOKEN(EXPR_CONST, context->tok); const char *string = TOKSTR(context->tok); const char *end = string + TOKLEN(context->tok); - if (string[0] == '\'') - { - union - { - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - uint8_t b[8]; - } bytes; - int pos = 0; - while (++string < end - 1) - { - if (*string == '\\') - { - if (*(++string) == 'x') - { - int hex = 0; - for (int j = 0; j < 2; j++) - { - hex <<= 4U; - char c = *(++string); - if (c < 'A') - { - hex += c - '0'; - } - else if (c < 'a') - { - hex += c - 'A' + 10; - } - else - { - hex += c - 'a' + 10; - } - } - bytes.b[pos++] = hex; - continue; - } - } - bytes.b[pos++] = (unsigned)*string; - } - switch (pos) - { - case 1: - expr_const_set_int(&expr_int->const_expr, bytes.u8, TYPE_U8); - expr_int->type = type_byte; - break; - case 2: - expr_const_set_int(&expr_int->const_expr, bytes.u8, TYPE_U16); - expr_int->type = type_ushort; - break; - case 4: - expr_const_set_int(&expr_int->const_expr, bytes.u8, TYPE_U32); - expr_int->type = type_uint; - break; - case 8: - expr_const_set_int(&expr_int->const_expr, bytes.u8, TYPE_U64); - expr_int->type = type_ulong; - break; - default: - UNREACHABLE - } - expr_int->resolve_status = RESOLVE_DONE; - advance(context); - return expr_int; - } BigInt *i = &expr_int->const_expr.i; bigint_init_unsigned(i, 0); BigInt diff; @@ -636,6 +560,38 @@ static Expr *parse_integer(Context *context, Expr *left) return expr_int; } +static Expr *parse_char_lit(Context *context, Expr *left) +{ + assert(!left && "Had left hand side"); + Expr *expr_int = EXPR_NEW_TOKEN(EXPR_CONST, context->tok); + TokenData *data = tokendata_from_id(context->tok.id); + switch (data->width) + { + case 1: + expr_const_set_int(&expr_int->const_expr, data->char_lit.u8, TYPE_IXX); + expr_int->type = type_compint; + break; + case 2: + expr_const_set_int(&expr_int->const_expr, data->char_lit.u16, TYPE_IXX); + expr_int->type = type_compint; + break; + case 4: + expr_const_set_int(&expr_int->const_expr, data->char_lit.u32, TYPE_IXX); + expr_int->type = type_compint; + break; + case 8: + expr_const_set_int(&expr_int->const_expr, data->char_lit.u64, TYPE_U64); + expr_int->type = type_ulong; + break; + default: + UNREACHABLE + } + + expr_int->resolve_status = RESOLVE_DONE; + advance(context); + return expr_int; +} + static Expr *parse_double(Context *context, Expr *left) { @@ -932,6 +888,7 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_FALSE] = { parse_bool, NULL, PREC_NONE }, [TOKEN_NIL] = { parse_nil, NULL, PREC_NONE }, [TOKEN_INTEGER] = { parse_integer, NULL, PREC_NONE }, + [TOKEN_CHAR_LITERAL] = { parse_char_lit, NULL, PREC_NONE }, [TOKEN_IDENT] = { parse_maybe_scope, NULL, PREC_NONE }, [TOKEN_TYPE_IDENT] = { parse_type_identifier, NULL, PREC_NONE }, [TOKEN_CT_IDENT] = { parse_identifier, NULL, PREC_NONE }, diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 74ce0cf2d..37c0d0da4 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -884,6 +884,7 @@ Ast *parse_stmt(Context *context) case TOKEN_STAR: case TOKEN_AMP: case TOKEN_INTEGER: + case TOKEN_CHAR_LITERAL: case TOKEN_BIT_NOT: case TOKEN_BIT_OR: case TOKEN_BIT_XOR: diff --git a/src/compiler/parser.c b/src/compiler/parser.c index 9034381af..7bd2a6e7d 100644 --- a/src/compiler/parser.c +++ b/src/compiler/parser.c @@ -196,6 +196,8 @@ static void recover_top_level(Context *context) case TOKEN_UNION: case TOKEN_MACRO: case TOKEN_EXTERN: + case TOKEN_ENUM: + case TOKEN_ERR: return; default: advance(context); @@ -209,6 +211,7 @@ static void recover_top_level(Context *context) static inline Path *parse_module_path(Context *context) { + assert(TOKEN_IS(TOKEN_IDENT)); char *scratch_ptr = context->path_scratch; size_t offset = 0; @@ -1791,6 +1794,12 @@ static inline void parse_module(Context *context) return; } + if (!TOKEN_IS(TOKEN_IDENT)) + { + SEMA_TOKEN_ERROR(context->tok, "Module statement should be followed by the name of the module to import."); + return; + } + Path *path = parse_module_path(context); // Expect the module name @@ -1948,6 +1957,7 @@ static inline void parse_current(Context *context) void parse_file(Context *context) { lexer_init_with_file(&context->lexer, context->file); + if (diagnostics.errors) return; parse_current(context); } diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index cb13d9f79..e834a7f1a 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -641,7 +641,7 @@ static inline bool sema_analyse_error(Context *context __unused, Decl *decl) sema_set_struct_size(decl); if (decl->strukt.size > type_size(type_usize)) { - SEMA_ERROR(decl, "Error type may exceed pointer size (%d bytes) it was %d bytes.", type_size(type_usize), error_size); + SEMA_ERROR(decl, "Error type may not exceed pointer size (%d bytes) it was %d bytes.", type_size(type_usize), error_size); return false; } decl->strukt.abi_alignment = type_abi_alignment(type_voidptr); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 3eca7dff5..f91f017ef 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -249,8 +249,13 @@ static inline bool find_possible_inferred_identifier(Type *to, Expr *expr) } static inline bool sema_expr_analyse_identifier(Context *context, Type *to, Expr *expr) { - Decl *ambiguous_decl; - Decl *decl = sema_resolve_symbol(context, expr->identifier_expr.identifier, expr->identifier_expr.path, &ambiguous_decl); + Decl *ambiguous_decl = NULL; + Decl *private_symbol = NULL; + Decl *decl = sema_resolve_symbol(context, + expr->identifier_expr.identifier, + expr->identifier_expr.path, + &ambiguous_decl, + &private_symbol); if (!decl && !expr->identifier_expr.path && to) { if (find_possible_inferred_identifier(to, expr)) return true; @@ -258,7 +263,18 @@ static inline bool sema_expr_analyse_identifier(Context *context, Type *to, Expr if (!decl) { - SEMA_ERROR(expr, "The symbol '%s' could not be found.", expr->identifier_expr.identifier); + if (private_symbol) + { + SEMA_ERROR(expr, "'%s' is not visible from this module.", expr->identifier_expr.identifier); + } + else if (ambiguous_decl) + { + SEMA_ERROR(expr, "The name '%s' ambiguous, please add a path.", expr->identifier_expr.identifier); + } + else + { + SEMA_ERROR(expr, "Identifier '%s' could not be found.", expr->identifier_expr.identifier); + } return false; } @@ -1411,45 +1427,6 @@ static inline bool sema_expr_analyse_cast(Context *context, Type *to, Expr *expr return true; } -static inline bool sema_expr_analyse_fail_check(Context *context, Expr *expr) -{ - Expr *inner = expr->fail_check_expr; - switch (inner->expr_kind) - { - case EXPR_IDENTIFIER: - case EXPR_CALL: - case EXPR_CAST: - case EXPR_EXPR_BLOCK: - case EXPR_GROUP: - case EXPR_TYPE_ACCESS: - case EXPR_SUBSCRIPT: - case EXPR_ACCESS: - case EXPR_TYPEID: - case EXPR_TYPEOF: - break; - default: - SEMA_ERROR(expr, "Ambiguous use of unwrapping operator '?', use '()' around the expression to indicate intent."); - return false; - } - if (!sema_analyse_expr(context, NULL, inner)) return false; - if (!inner->failable) - { - SEMA_ERROR(expr, "You can only check a failable type e.g. '%s!' not '%s'.", - type_to_error_string(inner->type), type_to_error_string(inner->type)); - return false; - } - if (inner->expr_kind == EXPR_IDENTIFIER && (context->current_scope->flags & SCOPE_COND)) - { - Decl *var = inner->identifier_expr.decl; - Decl *decl = COPY(var); - decl->var.kind = VARDECL_ALIAS; - decl->var.alias = var; - sema_unwrap_var(context, decl); - } - expr->type = type_bool; - return true; -} - bool sema_expr_analyse_assign_right_side(Context *context, Expr *expr, Type *left_type, Expr *right, ExprFailableStatus lhs_is_failable) { // 1. Evaluate right side to required type. @@ -2766,6 +2743,26 @@ static inline bool sema_expr_analyse_else(Context *context, Type *to, Expr *expr return cast_implicit(context, expr->else_expr.expr, common); } +static inline bool sema_expr_analyse_guard(Context *context, Type *to, Expr *expr) +{ + Expr *inner = expr->guard_expr.inner; + bool success = sema_analyse_expr(context, to, inner); + expr->guard_expr.defer = context->current_scope->defers.end; + if (!success) return false; + expr->type = inner->type; + if (!inner->failable) + { + SEMA_ERROR(inner, "No failable to rethrow before '!!' in the expression, please remove '!!'."); + return false; + } + if (!context->failable_return) + { + SEMA_ERROR(expr, "This expression implicitly returns with a failable result, but the function does not allow failable results. Did you mean to use 'else' instead?"); + return false; + } + return true; +} + static Ast *ast_shallow_copy(Ast *source) { return COPY(source); @@ -2837,9 +2834,6 @@ static Expr *expr_copy_from_macro(Context *context, Expr *source_expr) case EXPR_DECL_LIST: MACRO_COPY_AST_LIST(expr->dexpr_list_expr); return expr; - case EXPR_FAIL_CHECK: - MACRO_COPY_EXPR(expr->fail_check_expr); - return expr; case EXPR_FAILABLE: MACRO_COPY_EXPR(expr->failable_expr); return expr; @@ -2876,7 +2870,7 @@ static Expr *expr_copy_from_macro(Context *context, Expr *source_expr) case EXPR_POISONED: return source_expr; case EXPR_GUARD: - MACRO_COPY_EXPR(expr->guard_expr); + MACRO_COPY_EXPR(expr->guard_expr.inner); return expr; case EXPR_CONST: return expr; @@ -3234,8 +3228,6 @@ static inline bool sema_analyse_expr_dispatch(Context *context, Type *to, Expr * { case EXPR_DECL_LIST: UNREACHABLE - case EXPR_FAIL_CHECK: - return sema_expr_analyse_fail_check(context, expr); case EXPR_FAILABLE: return sema_expr_analyse_failable(context, to, expr); case EXPR_POISONED: @@ -3259,7 +3251,7 @@ static inline bool sema_analyse_expr_dispatch(Context *context, Type *to, Expr * case EXPR_EXPR_BLOCK: return sema_expr_analyse_expr_block(context, to, expr); case EXPR_GUARD: - return sema_expr_analyse_try(context, expr); + return sema_expr_analyse_guard(context, to, expr); case EXPR_RANGE: SEMA_ERROR(expr, "Range expression was not expected here."); return false; diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c index 75d263f85..b8648d089 100644 --- a/src/compiler/sema_name_resolution.c +++ b/src/compiler/sema_name_resolution.c @@ -26,7 +26,8 @@ static inline bool matches_subpath(Path *path_to_check, Path *path_to_find) return 0 == memcmp(path_to_check->module + compare_start, path_to_find->module, path_to_find->len); } -static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl) +static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl, + Decl **private_decl) { assert(path && "Expected path."); *ambiguous_other_decl = NULL; @@ -41,7 +42,7 @@ static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path if (path->len > import->import.path->len) continue; if (!matches_subpath(import->import.path, path)) continue; path_found = true; - Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL); + Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL, private_decl); if (!found) continue; if (decl) { @@ -63,11 +64,12 @@ static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path return decl; } -Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl) +Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl, + Decl **private_decl) { if (path) { - return sema_resolve_path_symbol(context, symbol, path, ambiguous_other_decl); + return sema_resolve_path_symbol(context, symbol, path, ambiguous_other_decl, private_decl); } *ambiguous_other_decl = NULL; @@ -90,7 +92,7 @@ Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl if (decl) return decl; // Search in the module and child modules. - decl = module_find_symbol(context->module, symbol, MODULE_SYMBOL_SEARCH_THIS); + decl = module_find_symbol(context->module, symbol, MODULE_SYMBOL_SEARCH_THIS, private_decl); if (decl) { @@ -103,7 +105,7 @@ Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl { Decl *import = context->imports[i]; if (!decl_ok(import)) continue; - Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL); + Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL, private_decl); if (!found) continue; if (decl) { @@ -135,7 +137,8 @@ static inline bool sema_append_local(Context *context, Decl *decl) bool sema_add_local(Context *context, Decl *decl) { Decl *dummy; - Decl *other = sema_resolve_symbol(context, decl->name, NULL, &dummy); + Decl *dummy2; + Decl *other = sema_resolve_symbol(context, decl->name, NULL, &dummy, &dummy2); if (other) { sema_shadow_error(decl, other); diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index b8ebbbc16..554618e69 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -122,6 +122,13 @@ void sema_analysis_pass_decls(Context *context) { sema_analyse_decl(context, context->functions[i]); } + DEBUG_LOG("Pass finished with %d error(s).", diagnostics.errors); +} + +void sema_analysis_pass_functions(Context *context) +{ + DEBUG_LOG("Pass: Function analysis %s", context->file->name); + VECEACH(context->methods, i) { analyse_func_body(context, context->methods[i]); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 513226cca..520c67d07 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -535,7 +535,8 @@ static DynamicScope *context_find_scope_by_id(Context *context, unsigned scope_i static inline Decl *sema_analyse_label(Context *context, Ast *stmt) { Decl *ambiguous; - Decl *target = sema_resolve_symbol(context, stmt->contbreak_stmt.label.name, NULL, &ambiguous); + Decl *dummy; + Decl *target = sema_resolve_symbol(context, stmt->contbreak_stmt.label.name, NULL, &ambiguous, &dummy); if (!target) { SEMA_ERROR(stmt, "Cannot find a labelled statement with the name '%s'.", stmt->contbreak_stmt.label.name); @@ -613,7 +614,8 @@ static bool sema_analyse_next_stmt(Context *context, Ast *statement) if (statement->next_stmt.label.name) { Decl *ambiguous; - Decl *target = sema_resolve_symbol(context, statement->next_stmt.label.name, NULL, &ambiguous); + Decl *dummy; + Decl *target = sema_resolve_symbol(context, statement->next_stmt.label.name, NULL, &ambiguous, &dummy); if (!target) { SEMA_ERROR(statement, "Cannot find a switch statement with the name '%s'.", statement->next_stmt.label.name); @@ -1077,10 +1079,11 @@ static bool sema_analyse_catch_stmt(Context *context, Ast *statement) if (left->expr_kind == EXPR_IDENTIFIER) { Decl *ambiguous_decl; + Decl *dummy; Decl *error_var_decl = sema_resolve_symbol(context, left->identifier_expr.identifier, left->identifier_expr.path, - &ambiguous_decl); + &ambiguous_decl, &dummy); if (!error_var_decl) { error_var = decl_new_var(left->span.loc, type_info_new_base(type_error, left->span), VARDECL_LOCAL, diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 8e7842e61..246b56e7d 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -58,14 +58,26 @@ static inline bool sema_resolve_array_type(Context *context, TypeInfo *type) static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info) { - Decl *ambiguous_decl; + Decl *ambiguous_decl = NULL; + Decl *private_decl = NULL; Decl *decl = sema_resolve_symbol(context, TOKSTR(type_info->unresolved.name_loc), type_info->unresolved.path, - &ambiguous_decl); + &ambiguous_decl, &private_decl); if (!decl) { - SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "Unknown type '%s'.", TOKSTR(type_info->unresolved.name_loc)); + if (private_decl) + { + SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "Type '%s' is not visible from this module.", TOKSTR(type_info->unresolved.name_loc)); + } + else if (ambiguous_decl) + { + SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "The type '%s' ambiguous, please add a path.", TOKSTR(type_info->unresolved.name_loc)); + } + else + { + SEMA_TOKID_ERROR(type_info->unresolved.name_loc, "Unknown type '%s'.", TOKSTR(type_info->unresolved.name_loc)); + } return type_info_poison(type_info); } diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index 7802c2abb..df7cc6f6d 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -166,6 +166,8 @@ const char *token_type_to_string(TokenType type) return "INTEGER"; case TOKEN_REAL: return "FLOAT"; + case TOKEN_CHAR_LITERAL: + return "CHAR_LITERAL"; // Comments case TOKEN_COMMENT: diff --git a/src/main.c b/src/main.c index 6dcb63cff..96f68cb94 100644 --- a/src/main.c +++ b/src/main.c @@ -5,7 +5,7 @@ #include "compiler_tests/tests.h" #include "utils/lib.h" -static int version = 100; +static int version = 101; int main(int argc, const char *argv[]) { diff --git a/src/utils/errors.h b/src/utils/errors.h index 8f6d102b6..50d075e1c 100644 --- a/src/utils/errors.h +++ b/src/utils/errors.h @@ -32,7 +32,7 @@ void error_exit(const char *format, ...) __attribute__((noreturn)); #ifndef NDEBUG -#define DEBUG_LOG(_string, ...) eprintf("-- DEBUG: "); eprintf(_string, ##__VA_ARGS__); eprintf("\n") +#define DEBUG_LOG(_string, ...) printf("-- DEBUG: "); printf(_string, ##__VA_ARGS__); printf("\n") #else #define DEBUG_LOG(_string, ...) #endif diff --git a/src/utils/lib.h b/src/utils/lib.h index a1db95483..950669b3f 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -112,10 +112,31 @@ static inline bool is_digit(char c) */ static inline int char_to_nibble(char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c <= 'F') return c - 'A' + 10; - if (c <= 'f') return c - 'a' + 10; - return -1; + char conv[256] = { + ['0'] = 1, + ['1'] = 2, + ['2'] = 3, + ['3'] = 4, + ['4'] = 5, + ['5'] = 6, + ['6'] = 7, + ['7'] = 8, + ['8'] = 9, + ['9'] = 10, + ['A'] = 11, + ['B'] = 12, + ['C'] = 13, + ['D'] = 14, + ['E'] = 15, + ['F'] = 16, + ['a'] = 11, + ['b'] = 12, + ['c'] = 13, + ['d'] = 14, + ['e'] = 15, + ['f'] = 16, + }; + return conv[(unsigned)c] - 1; } static inline bool is_hex_or_(char c) @@ -135,6 +156,45 @@ static inline bool is_hex_or_(char c) } } +static inline char is_valid_escape(char c) +{ + switch (c) + { + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'e': + return 0x1B; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + case 'x': + return 'x'; + case 'u': + return 'u'; + case 'U': + return 'U'; + case '\'': + return '\''; + case '"': + return '"'; + case '\\': + return '\\'; + case '0': + return '\0'; + default: + return -1; + } +} + static inline bool is_hex(char c) { switch (c) diff --git a/test/src/tester.py b/test/src/tester.py new file mode 100644 index 000000000..b11fe5279 --- /dev/null +++ b/test/src/tester.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +import os, sys, shutil, subprocess + +TEST_DIR = '/tmp/c3test/' +class Config: + run_skipped = False + cwd = "." + numtests = 0 + +class File: + def __init__(self, filepath): + with open(filepath) as reader: + self.content = reader.read().splitlines() + self.filename = filepath + + + +class Issues: + def __init__(self, conf, file, single): + self.file = file + self.single = single + self.line = 0 + self.file_start = 0 + self.line_offset = 0 + self.has_errors = False + self.error_message = "unknown" + self.skip = False + self.cur = 0 + self.current_file = None + self.errors = {} + self.warnings = {} + if single: + self.current_file = conf.cwd + "/" + file.filename + + def exit_error(self, message): + print('Error in file ' + self.file.filename + ': ' + message) + exit(-1) + + def set_failed(self): + if not self.has_errors: print(" Failed.") + self.has_errors = True + + def check_line(self, type, file, line, message): + if file == 'test.c3': file = self.file.filename + map = {} + if type == 'Error': + map = self.errors + elif type == 'Warning': + map = self.warnings + else: + self.exit_error("Unknown type: " + type) + key = file + ":" + line + value = map.get(key) + if value == None: return False + if value in message: + del map[key] + return True + else: + return False + + def parse_result(self, lines): + for line in lines: + parts = line.split('|', maxsplit=4) + if len(parts) != 4: self.exit_error("Illegal error result: " + line); + if not self.check_line(parts[0], parts[1], parts[2], parts[3]): + self.set_failed() + print("Unexpected " + parts[0].lower() + " in " + parts[1] + " line " + parts[2] + ":", end="") + print('"' + parts[3] + '"') + if len(self.errors) > 0: + self.set_failed() + print("Expected errors that never occured:") + num = 1 + for key, value in self.errors.items(): + pos = key.split(":", 2) + print(str(num) + ". " + pos[0] + " line: " + pos[1] + " expected: " + value) + num += 1 + + def compile(self, args): + path = os.path.dirname(sys.argv[0]) + "/../../cmake-build-debug/" + code = subprocess.run(path + 'c3c ' + args, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if code.returncode != 0 and code.returncode != 1: + self.set_failed() + print("Error: " + code.stderr) + self.has_errors = True + return + self.parse_result(code.stderr.splitlines(keepends=False)) + + def parse_single(self): + lines = len(self.file.content) + while self.line < lines: + line = self.file.content[self.line].strip() + if "// #" in line: + self.parse_trailing_directive(line) + self.line += 1 + with open(TEST_DIR + 'test.c3', mode='w') as f: + f.write("\n".join(self.file.content)) + f.write("\n") + print("- " + self.file.filename + ":", end="") + self.compile("--test compile " + TEST_DIR + 'test.c3') + + if not self.has_errors: + print(" Passed.") + + def parse_header_directive(self, line): + line = line[4:].strip() + if (line.startswith("warnings:")): + print("TODO" + line) + exit(-1) + elif (line.startswith("file:")): + line = line[5:].strip() + print("NEW FILE" + line) + exit(-1) + elif (line.startswith("expect:")): + line = line[7:].strip() + print("Expect " + line) + exit(-1) + else: + self.exit_error("unknown header directive " + line) + + def parse_trailing_directive(self, line): + line = line.split('// #', 2)[1].strip() + if (line.startswith("warning:")): + print("TODO" + line) + exit(-1) + elif (line.startswith("error:")): + line = line[6:].strip() + self.errors[self.file.filename + ":%d" % (self.line + 1)] = line + else: + self.exit_error("unknown trailing directive " + line) + + def parse_template(self): + lines = len(self.file.content) + while self.line < lines: + line = self.file.content[self.line].strip() + if line.startswith("// #"): + self.parse_header_directive(line) + elif "// #" in line: + self.parse_trailing_directive(line) + self.line += 1 + + print("parse mult") + + def parse(self): + if len(self.file.content) == 0: self.exit_error("File was empty") + is_skip = self.file.content[0].startswith("// #skip") + if is_skip != self.skip: return + + if is_skip: self.line += 1 + if self.single: + self.parse_single() + else: + self.parse_template() + + +def usage(): + print("Usage: " + sys.argv[0] + " [-s]") + print('') + print('Options:') + print(" -s, --skipped only run skipped tests") + exit(-1) + +def handle_file(filepath, conf): + if filepath.endswith('.c3'): + single = True + elif filepath.endswith('.c3t'): + single = False + else: + return + + shutil.rmtree(TEST_DIR, ignore_errors=True) + os.mkdir(TEST_DIR, mode = 0o777) + + conf.numtests += 1 + + issues = Issues(conf, File(filepath), single) + issues.parse() + + + +def handle_dir(filepath, conf): + for file in os.listdir(filepath): + file = filepath + "/" + file + if os.path.isdir(file): + handle_dir(file, conf) + elif os.path.isfile(file): + handle_file(file, conf) + +def main(): + args = len(sys.argv) + conf = Config() + if args != 1 and args > 3: usage() + if args == 3: + if (sys.argv[2] != '-s' and sys.argv[2] != '--skipped'): usage() + conf.run_skipped = True + filepath = sys.argv[1] + if filepath.endswith('/'): filepath = filepath[:-1] + conf.cwd = os.getcwd() + if os.path.isfile(filepath): + handle_file(filepath, conf) + elif os.path.isdir(filepath): + handle_dir(filepath, conf) + else: + usage() + +main() diff --git a/resources/tests/comments_ok.c3 b/test/test_suite/comments/simple_comments.c3 similarity index 100% rename from resources/tests/comments_ok.c3 rename to test/test_suite/comments/simple_comments.c3 diff --git a/test/test_suite/enums/enum_errors.c3 b/test/test_suite/enums/enum_errors.c3 new file mode 100644 index 000000000..81d98223f --- /dev/null +++ b/test/test_suite/enums/enum_errors.c3 @@ -0,0 +1,11 @@ + + +enum EnumTestOverflow +{ + VALUE = 0x80000000, // #error: does not fit into 'int' +} + +enum EnumTestErrorType : float // #error: must be an integer type not 'float' +{ + VALUE_BOOM +} \ No newline at end of file diff --git a/resources/tests/enum_ok.c3 b/test/test_suite/enums/enum_ok.c3 similarity index 100% rename from resources/tests/enum_ok.c3 rename to test/test_suite/enums/enum_ok.c3 diff --git a/test/test_suite/enums/enum_parse_errors.c3 b/test/test_suite/enums/enum_parse_errors.c3 new file mode 100644 index 000000000..58dee5330 --- /dev/null +++ b/test/test_suite/enums/enum_parse_errors.c3 @@ -0,0 +1,19 @@ + +enum EnumWithErrorWithMissingName : int (int) // #error: function parameter must be named +{ + TEST +} + +enum EnumWithErrorData : int (int // #error: end of the parameter list +{ + TEST +} + +error TheError +{ + union // #error: A type name was expected here + { + int a; + int b; + } +} \ No newline at end of file diff --git a/test/test_suite/errors/error_decl_fails.c3 b/test/test_suite/errors/error_decl_fails.c3 new file mode 100644 index 000000000..d4ac2e4d0 --- /dev/null +++ b/test/test_suite/errors/error_decl_fails.c3 @@ -0,0 +1,9 @@ +module foo; + +error TooBig // #error: Error type may not exceed pointer +{ + usize a; + char b; +} + + diff --git a/test/test_suite/errors/error_decl_ok.c3 b/test/test_suite/errors/error_decl_ok.c3 new file mode 100644 index 000000000..3cc9b3173 --- /dev/null +++ b/test/test_suite/errors/error_decl_ok.c3 @@ -0,0 +1,9 @@ +module errors; + +error TheError +{ + int a; +} + +error OtherError; + diff --git a/test/test_suite/expressions/arithmetics.c3 b/test/test_suite/expressions/arithmetics.c3 new file mode 100644 index 000000000..5c640ba49 --- /dev/null +++ b/test/test_suite/expressions/arithmetics.c3 @@ -0,0 +1,38 @@ +module arithmetics; + +func void testAdd(int a, int b) +{ + a = a + b; + a = a +% b; + a +%= b; + a += b; + a += 1; + a +%= 1; +} + +func void testSub(int a, int b) +{ + a = a - b; + a = a -% b; + a -%= b; + a -= b; + a -%= 1; + a -= 1; +} + +func void testMult(int a, int b) +{ + a = a * b; + a = a *% b; + a *%= b; + a *= b; + a *%= 1; + a *= 1; +} + +func void testDiv(int a, int b) +{ + a = a / b; + a /= b; + a /= 1; +} \ No newline at end of file diff --git a/test/test_suite/functions/bar.c3 b/test/test_suite/functions/bar.c3 new file mode 100644 index 000000000..2a0b42e6b --- /dev/null +++ b/test/test_suite/functions/bar.c3 @@ -0,0 +1,6 @@ +module bob; + +func void hello() +{ + int i = 0; +} \ No newline at end of file diff --git a/test/test_suite/functions/foo.c3 b/test/test_suite/functions/foo.c3 new file mode 100644 index 000000000..631da188d --- /dev/null +++ b/test/test_suite/functions/foo.c3 @@ -0,0 +1,3 @@ +// Hello + +func int foo() {} // #error: Missing return statement \ No newline at end of file diff --git a/test/test_suite/functions/multi_module.c3t2 b/test/test_suite/functions/multi_module.c3t2 new file mode 100644 index 000000000..0ac9ebda1 --- /dev/null +++ b/test/test_suite/functions/multi_module.c3t2 @@ -0,0 +1,58 @@ +// @recipe bin + $warnings no-unused + $generate-c + +// #file: file1 +module test1; + +import test2; + +public func void pub1() {} + +func void nonpub1() {} + +public func i32 main(i32 argc, const i8*[] argv) { + return 0; +} + +// #file: file2 +module test2; + +public func void pub2() {} + +func void nonpub2() {} + +// #expect: test1.h + +void test1_pub1(); + +// #expect: test1.c +#include "test1.h" + +static void test1_nonpub1(); + +void test1_pub1() { +} + +static void test1_nonpub1() { +} + +int32_t main(int32_t argc, const char* argv[]) { + return 0; +} + +// @expect{atleast, build/test2.h} + +void test2_pub2(); + +// @expect{atleast, build/test2.c} +#include "test2.h" + +static void test2_nonpub2(); + +void test2_pub2() { +} + +static void test2_nonpub2() { +} + diff --git a/test/test_suite/strings/literal_errors.c3 b/test/test_suite/strings/literal_errors.c3 new file mode 100644 index 000000000..6430a49d3 --- /dev/null +++ b/test/test_suite/strings/literal_errors.c3 @@ -0,0 +1,13 @@ +char bar = '\xaf'; +char bar = '\x0F'; + +char bar = '\xgh'; // #error: Expected a two character + +char baz = '\ueeof'; // #error: Expected a four char +char eofk = '\u233'; // #error: Expected a four char + +char zab = '\Uaokdokok'; // #error: Expected an eight +char zab = '\Uaokdooekfoekfekfkeofkekok'; // #error: Expected an eight +char eofk = '\UaUfko'; // #error: Expected an eight + +char foo = ' // #error: Character literal did not terminate \ No newline at end of file diff --git a/test/test_suite/strings/literal_hex_ok.c3 b/test/test_suite/strings/literal_hex_ok.c3 new file mode 100644 index 000000000..d161d4cdb --- /dev/null +++ b/test/test_suite/strings/literal_hex_ok.c3 @@ -0,0 +1,3 @@ +byte bar1 = '\xaf'; +char bar2 = '\x0F'; +ushort bar4 = '\u0FaF';