From: Brendan Hansen Date: Tue, 25 Aug 2020 03:48:53 +0000 (-0500) Subject: added basics on switch statements; will need much testing X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=390561143ea3e23f9c2456914c03f4ac5f1375c5;p=onyx.git added basics on switch statements; will need much testing --- diff --git a/Makefile b/Makefile index 05c150b3..fa739dda 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -RELEASE=1 +RELEASE=0 OBJ_FILES=\ build/onyxlex.o \ diff --git a/core/builtin.onyx b/core/builtin.onyx index ebac5985..58fdd589 100644 --- a/core/builtin.onyx +++ b/core/builtin.onyx @@ -1,4 +1,4 @@ package builtin string :: #type []u8; -cstring :: #type ^u8; \ No newline at end of file +cstring :: #type ^u8; diff --git a/core/string.onyx b/core/string.onyx index 1743e19f..0668398f 100644 --- a/core/string.onyx +++ b/core/string.onyx @@ -4,10 +4,7 @@ use package builtin { string, cstring } use package memory use package wasi -Buffer :: struct { - data : rawptr = null; - len : u32 = 0; -} +Buffer :: #type []void; string_make :: proc #overloaded { string_make_from_cstring } @@ -139,9 +136,9 @@ u64_to_string :: proc (n_: u64, base: u64, buf: Buffer) -> string { n := n_; str :: cast(^u8) buf.data; - for i: 0, buf.len do str[i] = #char "\0"; + for i: 0, buf.count do str[i] = #char "\0"; - c := cast(^u8) ^str[buf.len - 1]; + c := cast(^u8) ^str[buf.count - 1]; len := 0; s :: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; @@ -188,7 +185,7 @@ string_builder_add_u64 :: proc (use sb: ^StringBuilder, n: u64) -> ^StringBuilde string_builder_add_u64_with_base :: proc (use sb: ^StringBuilder, n: u64, base: u64) -> ^StringBuilder { buf : [256] u8; - s := u64_to_string(n, base, Buffer.{ cast(^u8) buf, 256 }); + s := u64_to_string(n, base, Buffer.{ cast(rawptr) buf, 256 }); return string_builder_add_string(sb, s); } diff --git a/docs/plan b/docs/plan index b34d368c..9fcbc957 100644 --- a/docs/plan +++ b/docs/plan @@ -207,6 +207,15 @@ HOW: count : u32; } + [X] Switch statements + + [ ] fallthrough on cases in switch statements + + [ ] #file and #line directives + - string and u32 respectively that represent the current file and line number where the directive is + + [ ] transmute + [ ] 'use' enums and packages at an arbitrary scope [ ] convert to using an 'atom' like table @@ -259,8 +268,6 @@ HOW: - Checking which things are allowed to cast to/from should be checked in the checker, not in the wasm generatation - [ ] Switch statements - diff --git a/include/onyxastnodes.h b/include/onyxastnodes.h index c67803f9..796d8795 100644 --- a/include/onyxastnodes.h +++ b/include/onyxastnodes.h @@ -34,6 +34,8 @@ typedef struct AstBlock AstBlock; typedef struct AstIfWhile AstIfWhile; typedef struct AstFor AstFor; typedef struct AstDefer AstDefer; +typedef struct AstSwitchCase AstSwitchCase; +typedef struct AstSwitch AstSwitch; typedef struct AstType AstType; typedef struct AstBasicType AstBasicType; @@ -128,6 +130,8 @@ typedef enum AstKind { Ast_Kind_Break, Ast_Kind_Continue, Ast_Kind_Defer, + Ast_Kind_Switch, + Ast_Kind_Switch_Case, Ast_Kind_Count } AstKind; @@ -255,7 +259,7 @@ typedef enum CallingConvention { AstKind kind; \ u32 flags; \ OnyxToken *token; \ - AstNode *next; + AstNode *next; struct AstNode { AstNode_base }; // NOTE: 'type_node' is filled out by the parser. @@ -272,7 +276,7 @@ struct AstNode { AstNode_base }; #define AstTyped_base \ AstNode_base; \ AstType *type_node; \ - Type *type; + Type *type; struct AstTyped { AstTyped_base }; // Expression Nodes @@ -334,6 +338,25 @@ struct AstIfWhile { AstBlock *true_stmt; AstBlock *false_stmt; }; +struct AstSwitchCase { AstTyped *value; AstBlock *block; }; +struct AstSwitch { + AstNode_base; + + // NOTE: These are not currently used; + Scope *scope; + AstLocal *local; + AstBinaryOp *assignment; + + AstTyped *expr; + + bh_arr(AstSwitchCase) cases; + AstBlock *default_case; + + // NOTE: This is a mapping from the compile time known case value + // to a pointer to the block that it is associated with. + bh_imap case_map; + u64 min_case, max_case; +}; // Type Nodes // NOTE: This node is very similar to an AstNode, just diff --git a/include/onyxlex.h b/include/onyxlex.h index 614eee80..8d9ad1f9 100644 --- a/include/onyxlex.h +++ b/include/onyxlex.h @@ -30,6 +30,8 @@ typedef enum TokenType { Token_Type_Keyword_Alignof, Token_Type_Keyword_Defer, Token_Type_Keyword_Do, + Token_Type_Keyword_Case, + Token_Type_Keyword_Switch, Token_Type_Right_Arrow, Token_Type_Left_Arrow, diff --git a/include/onyxmsgs.h b/include/onyxmsgs.h index 0e9ada8f..81c41865 100644 --- a/include/onyxmsgs.h +++ b/include/onyxmsgs.h @@ -33,6 +33,8 @@ typedef enum MsgType { Msg_Type_Duplicate_Value, Msg_Type_Field_No_Value, + Msg_Type_Multiple_Cases, + Msg_Type_Unresolved_Type, Msg_Type_Unresolved_Symbol, diff --git a/include/onyxwasm.h b/include/onyxwasm.h index 2b6d9761..11a6c4a6 100644 --- a/include/onyxwasm.h +++ b/include/onyxwasm.h @@ -243,6 +243,12 @@ typedef struct WasmInstruction { WasmInstructionData data; } WasmInstruction; +typedef struct BranchTable { + u32 count; + u32 default_case; + u32 cases[]; +} BranchTable; + #define LOCAL_IS_WASM 0x8000000000000 typedef struct LocalAllocator { u32 param_count; diff --git a/misc/onyx.sublime-syntax b/misc/onyx.sublime-syntax index e60ade04..4640a218 100644 --- a/misc/onyx.sublime-syntax +++ b/misc/onyx.sublime-syntax @@ -23,7 +23,7 @@ contexts: # strings in YAML. When using single quoted strings, only single quotes # need to be escaped: this is done by using two single quotes next to each # other. - - match: '\b(package|struct|proc|use|global|enum|if|elseif|else|for|while|do|break|continue|return|as|cast|sizeof|alignof|defer)\b' + - match: '\b(package|struct|proc|use|global|enum|if|elseif|else|for|while|do|break|continue|return|as|cast|sizeof|alignof|defer|switch|case)\b' scope: keyword.control.onyx - match: '\b(bool|void|i8|u8|i16|u16|i32|u32|i64|u64|f32|f64|rawptr)\b' diff --git a/misc/onyx.vim b/misc/onyx.vim index 13337566..071e6e19 100644 --- a/misc/onyx.vim +++ b/misc/onyx.vim @@ -13,6 +13,7 @@ set cpo&vim syn keyword onyxKeyword package struct proc use global syn keyword onyxKeyword if elseif else syn keyword onyxKeyword for while do +syn keyword onyxKeyword switch case syn keyword onyxKeyword break continue return defer syn keyword onyxKeyword as cast sizeof alignof diff --git a/onyx b/onyx index 8d63f147..7083980b 100755 Binary files a/onyx and b/onyx differ diff --git a/progs/wasi_test.onyx b/progs/wasi_test.onyx index ec764c00..7227834d 100644 --- a/progs/wasi_test.onyx +++ b/progs/wasi_test.onyx @@ -273,13 +273,20 @@ main :: proc (args: []cstring) { acc := 0; for i: 0, tokens.count { - tok :: tokens.data[i].data[0]; - - if tok == #char "+" do acc += 1; - elseif tok == #char "-" do acc -= 1; - elseif tok == #char "*" do acc *= 2; - elseif tok == #char "/" do acc /= 2; - elseif tok == #char "s" do acc *= acc; + switch tokens.data[i].data[0] { + case #char "+" do acc += 1; + case #char "-" do acc -= 1; + case #char "*" do acc *= 2; + case #char "/" do acc /= 2; + + case #char "s" do acc *= acc; + + case #default { + print("Unexpected token: "); + print_u64_with_base(cast(u64) tokens.data[i].data[0], 16l); + print("\n"); + } + } } string_builder_clear(^sb); diff --git a/src/onyxchecker.c b/src/onyxchecker.c index c93ed3c4..f24d4b0f 100644 --- a/src/onyxchecker.c +++ b/src/onyxchecker.c @@ -12,6 +12,7 @@ CHECK(return, AstReturn* retnode); CHECK(if, AstIfWhile* ifnode); CHECK(while, AstIfWhile* whilenode); CHECK(for, AstFor* fornode); +CHECK(switch, AstSwitch* switchnode); CHECK(call, AstCall* call); CHECK(binaryop, AstBinaryOp** pbinop, b32 assignment_is_ok); CHECK(unaryop, AstUnaryOp** punop); @@ -156,6 +157,52 @@ CHECK(for, AstFor* fornode) { return 0; } +CHECK(switch, AstSwitch* switchnode) { + if (check_expression(&switchnode->expr)) return 1; + if (!type_is_integer(switchnode->expr->type)) { + onyx_message_add(Msg_Type_Literal, + switchnode->expr->token->pos, + "expected integer type for switch expression"); + return 1; + } + + bh_imap_init(&switchnode->case_map, global_heap_allocator, bh_arr_length(switchnode->cases) * 2); + + switchnode->min_case = 0xffffffffffffffff; + + bh_arr_each(AstSwitchCase, sc, switchnode->cases) { + if (check_block(sc->block)) return 1; + if (check_expression(&sc->value)) return 1; + + if (sc->value->kind != Ast_Kind_NumLit) { + onyx_message_add(Msg_Type_Literal, + sc->value->token->pos, + "case statement expected compile time known integer"); + return 1; + } + + promote_numlit_to_larger((AstNumLit *) sc->value); + + u64 value = ((AstNumLit *) sc->value)->value.l; + switchnode->min_case = bh_min(switchnode->min_case, value); + switchnode->max_case = bh_max(switchnode->max_case, value); + + if (bh_imap_has(&switchnode->case_map, value)) { + onyx_message_add(Msg_Type_Multiple_Cases, + sc->value->token->pos, + value); + return 1; + } + + bh_imap_put(&switchnode->case_map, value, (u64) sc->block); + } + + if (switchnode->default_case) + check_block(switchnode->default_case); + + return 0; +} + static AstTyped* match_overloaded_function(AstCall* call, AstOverloadedFunction* ofunc) { bh_arr_each(AstTyped *, node, ofunc->overloads) { AstFunction* overload = (AstFunction *) *node; @@ -958,6 +1005,7 @@ CHECK(statement, AstNode* stmt) { case Ast_Kind_If: return check_if((AstIfWhile *) stmt); case Ast_Kind_While: return check_while((AstIfWhile *) stmt); case Ast_Kind_For: return check_for((AstFor *) stmt); + case Ast_Kind_Switch: return check_switch((AstSwitch *) stmt); case Ast_Kind_Block: return check_block((AstBlock *) stmt); case Ast_Kind_Defer: { if (!semstate.defer_allowed) { diff --git a/src/onyxlex.c b/src/onyxlex.c index e826ce6b..88c849be 100644 --- a/src/onyxlex.c +++ b/src/onyxlex.c @@ -28,6 +28,8 @@ static const char* token_type_names[] = { "alignof", "defer", "do", + "switch", + "case", "->", "<-", @@ -258,6 +260,7 @@ whitespace_skipped: LITERAL_TOKEN("break", 1, Token_Type_Keyword_Break); break; case 'c': + LITERAL_TOKEN("case", 1, Token_Type_Keyword_Case); LITERAL_TOKEN("cast", 1, Token_Type_Keyword_Cast); LITERAL_TOKEN("continue", 1, Token_Type_Keyword_Continue); break; @@ -290,6 +293,7 @@ whitespace_skipped: case 's': LITERAL_TOKEN("sizeof", 1, Token_Type_Keyword_Sizeof); LITERAL_TOKEN("struct", 1, Token_Type_Keyword_Struct); + LITERAL_TOKEN("switch", 1, Token_Type_Keyword_Switch); break; case 't': LITERAL_TOKEN("true", 1, Token_Type_Literal_True); diff --git a/src/onyxmsgs.c b/src/onyxmsgs.c index c65b0d72..539a8730 100644 --- a/src/onyxmsgs.c +++ b/src/onyxmsgs.c @@ -31,6 +31,8 @@ static const char* msg_formats[] = { "duplicate value for struct member '%b'", "no value provided for field '%b'", + "multiple cases for '%l'", + "unable to resolve type for symbol '%b'", "unable to resolve symbol '%b'", diff --git a/src/onyxparser.c b/src/onyxparser.c index 5c1b18c8..580ed3cb 100644 --- a/src/onyxparser.c +++ b/src/onyxparser.c @@ -30,6 +30,7 @@ static AstTyped* parse_expression(OnyxParser* parser); static AstIfWhile* parse_if_stmt(OnyxParser* parser); static AstIfWhile* parse_while_stmt(OnyxParser* parser); static AstFor* parse_for_stmt(OnyxParser* parser); +static AstSwitch* parse_switch_stmt(OnyxParser* parser); static b32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret); static AstReturn* parse_return_statement(OnyxParser* parser); static AstBlock* parse_block(OnyxParser* parser); @@ -842,6 +843,54 @@ static AstFor* parse_for_stmt(OnyxParser* parser) { return for_node; } +static AstSwitch* parse_switch_stmt(OnyxParser* parser) { + AstSwitch* switch_node = make_node(AstSwitch, Ast_Kind_Switch); + switch_node->token = expect_token(parser, Token_Type_Keyword_Switch); + + bh_arr_new(global_heap_allocator, switch_node->cases, 4); + + switch_node->expr = parse_expression(parser); + expect_token(parser, '{'); + + AstTyped** batch_cases = NULL; + bh_arr_new(global_scratch_allocator, batch_cases, 16); + + while (parser->curr->type == Token_Type_Keyword_Case) { + expect_token(parser, Token_Type_Keyword_Case); + if (parser->hit_unexpected_token) return switch_node; + + if (parse_possible_directive(parser, "default")) { + switch_node->default_case = parse_block(parser); + continue; + } + + AstTyped* value = parse_expression(parser); + bh_arr_push(batch_cases, value); + while (parser->curr->type == ',') { + if (parser->hit_unexpected_token) return switch_node; + + consume_token(parser); + value = parse_expression(parser); + bh_arr_push(batch_cases, value); + } + + AstBlock* block = parse_block(parser); + + AstSwitchCase sc_node; + sc_node.block = block; + + bh_arr_each(AstTyped *, value, batch_cases) { + sc_node.value = *value; + bh_arr_push(switch_node->cases, sc_node); + } + + bh_arr_clear(batch_cases); + } + + expect_token(parser, '}'); + return switch_node; +} + // Returns 1 if the symbol was consumed. Returns 0 otherwise // ret is set to the statement to insert // : = @@ -972,6 +1021,11 @@ static AstNode* parse_statement(OnyxParser* parser) { retval = (AstNode *) parse_for_stmt(parser); break; + case Token_Type_Keyword_Switch: + needs_semicolon = 0; + retval = (AstNode *) parse_switch_stmt(parser); + break; + case Token_Type_Keyword_Break: { AstBreak* bnode = make_node(AstBreak, Ast_Kind_Break); bnode->token = expect_token(parser, Token_Type_Keyword_Break); diff --git a/src/onyxsymres.c b/src/onyxsymres.c index 4c88b126..169cf9ea 100644 --- a/src/onyxsymres.c +++ b/src/onyxsymres.c @@ -19,6 +19,7 @@ static void symres_return(AstReturn* ret); static void symres_if(AstIfWhile* ifnode); static void symres_while(AstIfWhile* whilenode); static void symres_for(AstFor* fornode); +static void symres_switch(AstSwitch* switchnode); static void symres_statement_chain(AstNode** walker); static b32 symres_statement(AstNode** stmt); static void symres_block(AstBlock* block); @@ -389,6 +390,18 @@ static void symres_for(AstFor* fornode) { scope_leave(); } +static void symres_switch(AstSwitch* switchnode) { + symres_expression(&switchnode->expr); + + bh_arr_each(AstSwitchCase, sc, switchnode->cases) { + symres_expression(&sc->value); + symres_block(sc->block); + } + + if (switchnode->default_case) + symres_block(switchnode->default_case); +} + // NOTE: Returns 1 if the statment should be removed static b32 symres_statement(AstNode** stmt) { switch ((*stmt)->kind) { @@ -397,6 +410,7 @@ static b32 symres_statement(AstNode** stmt) { case Ast_Kind_If: symres_if((AstIfWhile *) *stmt); return 0; case Ast_Kind_While: symres_while((AstIfWhile *) *stmt); return 0; case Ast_Kind_For: symres_for((AstFor *) *stmt); return 0; + case Ast_Kind_Switch: symres_switch((AstSwitch *) *stmt); return 0; case Ast_Kind_Call: symres_call((AstCall *) *stmt); return 0; case Ast_Kind_Argument: symres_expression((AstTyped **) &((AstArgument *) *stmt)->value); return 0; case Ast_Kind_Block: symres_block((AstBlock *) *stmt); return 0; diff --git a/src/onyxutils.c b/src/onyxutils.c index 3ed98c55..e8152d23 100644 --- a/src/onyxutils.c +++ b/src/onyxutils.c @@ -73,6 +73,8 @@ static const char* ast_node_names[] = { "BREAK", "CONTINUE", "DEFER", + "SWITCH", + "SWITCH CASE" "AST_NODE_KIND_COUNT", }; diff --git a/src/onyxwasm.c b/src/onyxwasm.c index 89025298..80936ab7 100644 --- a/src/onyxwasm.c +++ b/src/onyxwasm.c @@ -360,6 +360,7 @@ COMPILE_FUNC(load_instruction, Type* type, u32 offset); COMPILE_FUNC(if, AstIfWhile* if_node); COMPILE_FUNC(while, AstIfWhile* while_node); COMPILE_FUNC(for, AstFor* for_node); +COMPILE_FUNC(switch, AstSwitch* switch_node); COMPILE_FUNC(defer, AstDefer* defer); COMPILE_FUNC(deferred_stmts, AstNode* node); COMPILE_FUNC(binop, AstBinaryOp* binop); @@ -452,6 +453,7 @@ COMPILE_FUNC(statement, AstNode* stmt) { case Ast_Kind_If: compile_if(mod, &code, (AstIfWhile *) stmt); break; case Ast_Kind_While: compile_while(mod, &code, (AstIfWhile *) stmt); break; case Ast_Kind_For: compile_for(mod, &code, (AstFor *) stmt); break; + case Ast_Kind_Switch: compile_switch(mod, &code, (AstSwitch *) stmt); break; case Ast_Kind_Break: compile_structured_jump(mod, &code, ((AstBreak *) stmt)->count); break; case Ast_Kind_Continue: compile_structured_jump(mod, &code, -((AstContinue *) stmt)->count); break; case Ast_Kind_Block: compile_block(mod, &code, (AstBlock *) stmt, 1); break; @@ -804,6 +806,70 @@ COMPILE_FUNC(for, AstFor* for_node) { *pcode = code; } +COMPILE_FUNC(switch, AstSwitch* switch_node) { + bh_arr(WasmInstruction) code = *pcode; + + bh_imap block_map; + bh_imap_init(&block_map, global_heap_allocator, bh_arr_length(switch_node->cases)); + + if (switch_node->default_case != NULL) { + WID(WI_BLOCK_START, 0x40); + bh_arr_push(mod->structured_jump_target, 0); + } + + u64 block_num = 0; + bh_arr_each(AstSwitchCase, sc, switch_node->cases) { + if (bh_imap_has(&block_map, (u64) sc->block)) continue; + + WID(WI_BLOCK_START, 0x40); + bh_arr_push(mod->structured_jump_target, 0); + + bh_imap_put(&block_map, (u64) sc->block, block_num); + block_num++; + } + + u64 count = switch_node->max_case + 1 - switch_node->min_case; + BranchTable* bt = bh_alloc(global_heap_allocator, sizeof(BranchTable) + sizeof(u32) * count); + bt->count = count; + bt->default_case = block_num; + fori (i, 0, bt->count) bt->cases[i] = bt->default_case; + + bh_arr_each(bh__imap_entry, sc, switch_node->case_map.entries) { + bt->cases[sc->key - switch_node->min_case] = bh_imap_get(&block_map, (u64) sc->value); + } + + WID(WI_BLOCK_START, 0x40); + compile_expression(mod, &code, switch_node->expr); + if (switch_node->min_case != 0) { + WID(WI_I32_CONST, switch_node->min_case); + WI(WI_I32_SUB); + } + WIL(WI_JUMP_TABLE, (u64) bt); + WI(WI_BLOCK_END); + + bh_arr_each(AstSwitchCase, sc, switch_node->cases) { + if (bh_imap_get(&block_map, (u64) sc->block) == 0xdeadbeef) continue; + + u64 bn = bh_imap_get(&block_map, (u64) sc->block); + + compile_block(mod, &code, sc->block, 0); + WID(WI_JUMP, block_num - bn); + WI(WI_BLOCK_END); + bh_arr_pop(mod->structured_jump_target); + + bh_imap_put(&block_map, (u64) sc->block, 0xdeadbeef); + } + + if (switch_node->default_case != NULL) { + compile_block(mod, &code, switch_node->default_case, 0); + WI(WI_BLOCK_END); + bh_arr_pop(mod->structured_jump_target); + } + + bh_imap_free(&block_map); + *pcode = code; +} + COMPILE_FUNC(defer, AstDefer* defer) { bh_arr_push(mod->deferred_stmts, ((DeferredStmt) { .depth = bh_arr_length(mod->structured_jump_target),