-RELEASE=1
+RELEASE=0
OBJ_FILES=\
build/onyxlex.o \
package builtin
string :: #type []u8;
-cstring :: #type ^u8;
\ No newline at end of file
+cstring :: #type ^u8;
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 }
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+/";
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);
}
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
- Checking which things are allowed to cast to/from should be checked in the checker,
not in the wasm generatation
- [ ] Switch statements
-
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;
Ast_Kind_Break,
Ast_Kind_Continue,
Ast_Kind_Defer,
+ Ast_Kind_Switch,
+ Ast_Kind_Switch_Case,
Ast_Kind_Count
} AstKind;
AstKind kind; \
u32 flags; \
OnyxToken *token; \
- AstNode *next;
+ AstNode *next;
struct AstNode { AstNode_base };
// NOTE: 'type_node' is filled out by the parser.
#define AstTyped_base \
AstNode_base; \
AstType *type_node; \
- Type *type;
+ Type *type;
struct AstTyped { AstTyped_base };
// Expression Nodes
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
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,
Msg_Type_Duplicate_Value,
Msg_Type_Field_No_Value,
+ Msg_Type_Multiple_Cases,
+
Msg_Type_Unresolved_Type,
Msg_Type_Unresolved_Symbol,
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;
# 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'
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
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);
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);
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;
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) {
"alignof",
"defer",
"do",
+ "switch",
+ "case",
"->",
"<-",
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;
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);
"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'",
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);
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
// <symbol> : <type> = <expr>
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);
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);
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) {
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;
"BREAK",
"CONTINUE",
"DEFER",
+ "SWITCH",
+ "SWITCH CASE"
"AST_NODE_KIND_COUNT",
};
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);
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;
*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),