mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Fix, separate out the function pass from the decl pass. Diagnose non-visible symbols as not visible rather than missing. Fix crash when module identifier is incorrect. !! operator added. New character literal parsing. Added simple test framework.
This commit is contained in:
committed by
Christoffer Lerno
parent
b4c661eaad
commit
f45d6ef84b
@@ -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})
|
||||
target_link_libraries(c3c m ${llvm_libs})
|
||||
|
||||
10
README.md
10
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)
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
17
resources/testfragments/super_simple2.c3
Normal file
17
resources/testfragments/super_simple2.c3
Normal file
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
6
resources/testfragments/super_simple3.c3
Normal file
6
resources/testfragments/super_simple3.c3
Normal file
@@ -0,0 +1,6 @@
|
||||
module gen (Type);
|
||||
/*
|
||||
struct BobaFett
|
||||
{
|
||||
Type x;
|
||||
}*/
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
module errors;
|
||||
|
||||
error TheError
|
||||
{
|
||||
FOO_MISSING,
|
||||
NO_SUCH_FILE,
|
||||
}
|
||||
|
||||
error OtherError
|
||||
{
|
||||
BAR_OVERFLOWED
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -113,6 +113,7 @@ typedef struct
|
||||
bool debug_mode;
|
||||
bool emit_llvm;
|
||||
bool emit_bitcode;
|
||||
bool test_mode;
|
||||
} BuildOptions;
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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[])
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
205
test/src/tester.py
Normal file
205
test/src/tester.py
Normal file
@@ -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] + " <file/dir> [-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()
|
||||
11
test/test_suite/enums/enum_errors.c3
Normal file
11
test/test_suite/enums/enum_errors.c3
Normal file
@@ -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
|
||||
}
|
||||
19
test/test_suite/enums/enum_parse_errors.c3
Normal file
19
test/test_suite/enums/enum_parse_errors.c3
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
9
test/test_suite/errors/error_decl_fails.c3
Normal file
9
test/test_suite/errors/error_decl_fails.c3
Normal file
@@ -0,0 +1,9 @@
|
||||
module foo;
|
||||
|
||||
error TooBig // #error: Error type may not exceed pointer
|
||||
{
|
||||
usize a;
|
||||
char b;
|
||||
}
|
||||
|
||||
|
||||
9
test/test_suite/errors/error_decl_ok.c3
Normal file
9
test/test_suite/errors/error_decl_ok.c3
Normal file
@@ -0,0 +1,9 @@
|
||||
module errors;
|
||||
|
||||
error TheError
|
||||
{
|
||||
int a;
|
||||
}
|
||||
|
||||
error OtherError;
|
||||
|
||||
38
test/test_suite/expressions/arithmetics.c3
Normal file
38
test/test_suite/expressions/arithmetics.c3
Normal file
@@ -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;
|
||||
}
|
||||
6
test/test_suite/functions/bar.c3
Normal file
6
test/test_suite/functions/bar.c3
Normal file
@@ -0,0 +1,6 @@
|
||||
module bob;
|
||||
|
||||
func void hello()
|
||||
{
|
||||
int i = 0;
|
||||
}
|
||||
3
test/test_suite/functions/foo.c3
Normal file
3
test/test_suite/functions/foo.c3
Normal file
@@ -0,0 +1,3 @@
|
||||
// Hello
|
||||
|
||||
func int foo() {} // #error: Missing return statement
|
||||
58
test/test_suite/functions/multi_module.c3t2
Normal file
58
test/test_suite/functions/multi_module.c3t2
Normal file
@@ -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() {
|
||||
}
|
||||
|
||||
13
test/test_suite/strings/literal_errors.c3
Normal file
13
test/test_suite/strings/literal_errors.c3
Normal file
@@ -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
|
||||
3
test/test_suite/strings/literal_hex_ok.c3
Normal file
3
test/test_suite/strings/literal_hex_ok.c3
Normal file
@@ -0,0 +1,3 @@
|
||||
byte bar1 = '\xaf';
|
||||
char bar2 = '\x0F';
|
||||
ushort bar4 = '\u0FaF';
|
||||
Reference in New Issue
Block a user