"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/onyx",
- "args": ["-verbose", "examples/02_variables.onyx"],
+ "args": ["-verbose", "progs/poly_test.onyx"],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
}
print_string :: proc (s: string) {
- string_builder_append(^print_buffer, s);
- if s.data[s.count - 1] == #char "\n" do print_buffer_flush();
+ string_builder_append(^print_buffer, s);
+ if s.data[s.count - 1] == #char "\n" do print_buffer_flush();
}
print_cstring :: proc (s: cstring) do string_builder_append(^print_buffer, s);
- Do multiple passes if needed
- Some APIs like WebGL have a ton of foreigns, and most of them aren't even used
- [ ] baked parameters
- - Compile time known parameters
- - Removes the argument from the list and replaces the function with the
- baked function
-
- [ ] Add SIMD intrinsics
- - This also requires adding the v128 SIMD type
+ [X] Variadic arguments
[ ] Put type info in data section so it is runtime accessible
- type name
- struct member names
- array length
+ [ ] baked parameters
+ - Compile time known parameters
+ - Removes the argument from the list and replaces the function with the
+ baked function
+
+ [ ] Add SIMD intrinsics
+ - This also requires adding the v128 SIMD type
+
[ ] Type parameterized structs
[ ] Array literals
- But, profiling says we are spending 50% of the program execution time in the lexer
- That is awful
- [ ] Variadic arguments
-
[ ] 'when' statements
- Compile time conditions
- Only evalutate code blocks that evaluate to be true
Ast_Flag_No_Clone = BH_BIT(18),
Ast_Flag_Cannot_Take_Addr = BH_BIT(19),
+
+ Ast_Flag_Arg_Is_VarArg = BH_BIT(20),
} AstFlags;
typedef enum UnaryOp {
};
};
};
-struct AstParam { AstLocal *local; AstTyped *default_value; };
-struct AstFunction {
+struct AstParam {
+ AstLocal *local;
+ AstTyped *default_value;
+
+ b32 is_vararg : 1;
+};
+struct AstFunction {
AstTyped_base;
Scope *scope;
TYPE_KIND(Array, struct { u32 size; u32 count; Type *elem; }) \
TYPE_KIND(Slice, struct { Type *ptr_to_data; }) \
TYPE_KIND(DynArray, struct { Type *ptr_to_data; }) \
- TYPE_KIND(Enum, struct { char* name; Type* backing; })
+ TYPE_KIND(VarArgs, struct { Type *ptr_to_data; }) \
+ TYPE_KIND(Enum, struct { char* name; Type* backing; })
typedef enum TypeKind {
Type_Kind_Invalid,
Type* type_make_pointer(bh_allocator alloc, Type* to);
Type* type_make_slice(bh_allocator alloc, Type* of);
Type* type_make_dynarray(bh_allocator alloc, Type* of);
+Type* type_make_varargs(bh_allocator alloc, Type* of);
const char* type_get_name(Type* type);
u32 type_get_alignment_log2(Type* type);
--- /dev/null
+#include_file "core/std/wasi"
+
+use package core
+
+main :: proc (args: [] cstring) {
+ program := "+ + * s - /";
+ accumulator := 0;
+
+ for token: program {
+ switch token {
+ case #char "+" do accumulator += 1;
+ case #char "-" do accumulator -= 1;
+ case #char "*" do accumulator *= 2;
+ case #char "/" do accumulator /= 2;
+ case #char "s" do accumulator *= accumulator;
+ case #default ---
+ }
+ }
+
+ print("The program \"");
+ print(program);
+ print("\" calculates the value ");
+ print(accumulator);
+ print("\n");
+}
}
}
+vararg_test :: proc (prefix: string, nums: ..i32) {
+ print(prefix);
+ for num: nums {
+ print(num);
+ print(" ");
+ }
+}
+
+NumInfo :: struct {
+ min : i32;
+ max : i32;
+ sum : i32;
+}
+
+get_num_info :: proc (nums: ..i32) -> NumInfo {
+ ni : NumInfo;
+
+ ni.min = nums[0];
+ for num: nums do if num < ni.min do ni.min = num;
+
+ ni.max = nums[0];
+ for num: nums do if num > ni.max do ni.max = num;
+
+ ni.sum = 0;
+ for num: nums do ni.sum += num;
+
+ return ni;
+}
+
+print_strings :: proc (ss: ..string) {
+ for s: ss do print_string(s);
+}
+
+// At some point, it would be nice to have the ability to say something like:
+// complex_varargs :: proc (nums: ..i32, names: ..string) {
+// ...
+// }
+
main :: proc (args: [] cstring) {
switch_demo();
+ print_strings("This ", "is ", "a ", "test.\n");
+
+ vararg_test("Here are some numbers:\n", 1, 2, 3, 4, 5);
+ print("\n\n");
+
+ ni := get_num_info(1, 2, 3, 4, 5);
+ print("Some information about those numbers:\n");
+ print("Min: ");
+ print(ni.min);
+ print("\nMax: ");
+ print(ni.max);
+ print("\nSum: ");
+ print(ni.sum);
+ print("\n\n");
+
res := compose(5, proc (x: i32) -> i32 do return x * 3;,
proc (x: i32) -> i32 do return x + 5;);
print(res);
fornode->loop_type = For_Loop_Slice;
}
+ else if (iter_type->kind == Type_Kind_VarArgs) {
+ if (fornode->by_pointer) {
+ onyx_report_error(fornode->var->token->pos, "Cannot iterate by pointer over '%s'.", type_get_name(iter_type));
+ return 1;
+ }
+
+ can_iterate = 1;
+
+ fornode->var->type = iter_type->VarArgs.ptr_to_data->Pointer.elem;
+
+ // NOTE: Slices are VarArgs are being treated the same here.
+ fornode->loop_type = For_Loop_Slice;
+ }
else if (iter_type->kind == Type_Kind_DynArray) {
can_iterate = 1;
while (arg != NULL) {
fill_in_type((AstTyped *) arg);
- if (!types_are_compatible(*param_type, arg->type)) goto no_match;
+ if ((*param_type)->kind == Type_Kind_VarArgs) {
+ if (!types_are_compatible((*param_type)->VarArgs.ptr_to_data->Pointer.elem, arg->type))
+ goto no_match;
+ }
+ else if (!types_are_compatible(*param_type, arg->type)) goto no_match;
param_type++;
arg = (AstArgument *) arg->next;
}
}
- // NOTE: If we calling an intrinsic function, translate the
+ // NOTE: If we are calling an intrinsic function, translate the
// call into an intrinsic call node.
if (callee->flags & Ast_Flag_Intrinsic) {
call->kind = Ast_Kind_Intrinsic_Call;
Type **formal_params = callee->type->Function.params;
actual = call->arguments;
+ Type* variadic_type = NULL;
+
i32 arg_pos = 0;
- while (arg_pos < callee->type->Function.param_count && actual != NULL) {
- if (!types_are_compatible(formal_params[arg_pos], actual->type)) {
+ while (1) {
+ if (actual == NULL) break;
+ if (arg_pos >= callee->type->Function.param_count && variadic_type == NULL) break;
+
+ if (variadic_type == NULL && formal_params[arg_pos]->kind == Type_Kind_VarArgs) {
+ variadic_type = formal_params[arg_pos]->VarArgs.ptr_to_data->Pointer.elem;
+ }
+
+ if (variadic_type != NULL) {
+ if (!types_are_compatible(variadic_type, actual->type)) {
+ onyx_report_error(actual->token->pos,
+ "The function '%b' expects a value of type '%s' for the variadic parameter, '%b', got '%s'.",
+ callee->token->text, callee->token->length,
+ type_get_name(formal_params[arg_pos]),
+ callee->params[arg_pos].local->token->text,
+ callee->params[arg_pos].local->token->length,
+ type_get_name(actual->type));
+ return 1;
+ }
+
+ actual->flags |= Ast_Flag_Arg_Is_VarArg;
+
+ } else if (!types_are_compatible(formal_params[arg_pos], actual->type)) {
onyx_report_error(actual->token->pos,
- "The function '%b' expects a value of type '%s' for parameter '%d', got '%s'.",
+ "The function '%b' expects a value of type '%s' for %d%s parameter, got '%s'.",
callee->token->text, callee->token->length,
type_get_name(formal_params[arg_pos]),
- arg_pos,
+ arg_pos + 1,
+ bh_num_suffix(arg_pos + 1),
type_get_name(actual->type));
return 1;
}
else if (aa->addr->type->kind == Type_Kind_Array)
aa->type = aa->addr->type->Array.elem;
else if (aa->addr->type->kind == Type_Kind_Slice
- || aa->addr->type->kind == Type_Kind_DynArray) {
+ || aa->addr->type->kind == Type_Kind_DynArray
+ || aa->addr->type->kind == Type_Kind_VarArgs) {
// If we are accessing on a slice or a dynamic array, implicitly add a field access for the data member
StructMember smem;
b32 check_function_header(AstFunction* func) {
b32 expect_default_param = 0;
+ b32 has_had_varargs = 0;
bh_arr_each(AstParam, param, func->params) {
AstLocal* local = param->local;
return 1;
}
+ if (has_had_varargs && param->is_vararg) {
+ onyx_report_error(local->token->pos,
+ "Can only have one param that is of variable argument type.");
+ return 1;
+ }
+
+ if (has_had_varargs && !param->is_vararg) {
+ onyx_report_error(local->token->pos,
+ "Variable arguments must be last in parameter list");
+ return 1;
+ }
+
if (param->default_value != NULL) expect_default_param = 1;
fill_in_type((AstTyped *) local);
return 1;
}
+ if (param->is_vararg) {
+ has_had_varargs = 1;
+
+ local->type = type_make_varargs(semstate.node_allocator, local->type);
+ }
+
if (local->type->kind != Type_Kind_Array
&& type_size_of(local->type) == 0) {
onyx_report_error(local->token->pos, "Function parameters cannot have zero-width types.");
}
if (parser->curr->type != '=') {
+ if (parser->curr->type == Token_Type_Dot_Dot) {
+ consume_token(parser);
+ curr_param.is_vararg = 1;
+ }
+
i32 old_len = bh_arr_length(*parser->polymorph_context.poly_params);
curr_param.local->type_node = parse_type(parser);
return types_are_compatible(t1->Slice.ptr_to_data->Pointer.elem, t2->Slice.ptr_to_data->Pointer.elem);
}
+ case Type_Kind_VarArgs: {
+ if (t2->kind != Type_Kind_VarArgs) return 0;
+ return types_are_compatible(t1->VarArgs.ptr_to_data->Pointer.elem, t2->VarArgs.ptr_to_data->Pointer.elem);
+ }
+
case Type_Kind_DynArray: {
if (t2->kind != Type_Kind_DynArray) return 0;
return types_are_compatible(t1->DynArray.ptr_to_data->Pointer.elem, t2->DynArray.ptr_to_data->Pointer.elem);
return types_are_compatible(t1->Slice.ptr_to_data->Pointer.elem, t2->Slice.ptr_to_data->Pointer.elem);
}
+ case Type_Kind_VarArgs: {
+ if (t2->kind != Type_Kind_VarArgs) return 0;
+ return types_are_compatible(t1->VarArgs.ptr_to_data->Pointer.elem, t2->VarArgs.ptr_to_data->Pointer.elem);
+ }
+
case Type_Kind_DynArray: {
if (t2->kind != Type_Kind_DynArray) return 0;
return types_are_compatible(t1->DynArray.ptr_to_data->Pointer.elem, t2->DynArray.ptr_to_data->Pointer.elem);
case Type_Kind_Struct: return type->Struct.size;
case Type_Kind_Enum: return type_size_of(type->Enum.backing);
case Type_Kind_Slice: return 8;
+ case Type_Kind_VarArgs: return 8;
case Type_Kind_DynArray: return 12;
default: return 0;
}
case Type_Kind_Struct: return type->Struct.alignment;
case Type_Kind_Enum: return type_alignment_of(type->Enum.backing);
case Type_Kind_Slice: return 4;
+ case Type_Kind_VarArgs: return 4;
case Type_Kind_DynArray: return 4;
default: return 1;
}
if (param_count > 0) {
i32 i = 0;
bh_arr_each(AstParam, param, func->params) {
- if (param->default_value == NULL)
+ if (param->default_value == NULL && !param->is_vararg)
func_type->Function.needed_param_count++;
func_type->Function.params[i++] = param->local->type;
}
return dynarr;
}
+Type* type_make_varargs(bh_allocator alloc, Type* of) {
+ Type* va_type = bh_alloc(alloc, sizeof(Type));
+ va_type->kind = Type_Kind_VarArgs;
+ va_type->VarArgs.ptr_to_data = type_make_pointer(alloc, of);
+
+ return va_type;
+}
+
const char* type_get_name(Type* type) {
if (type == NULL) return "unknown";
return "<anonymous enum>";
case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_name(type->Slice.ptr_to_data->Pointer.elem));
+ case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_name(type->VarArgs.ptr_to_data->Pointer.elem));
case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_name(type->DynArray.ptr_to_data->Pointer.elem));
case Type_Kind_Function: {
return 1;
}
+ case Type_Kind_VarArgs:
case Type_Kind_Slice: {
if (strcmp(member, "data") == 0) {
smem->idx = 0;
return 1;
}
+ // HACK: This relies on the fact that the structures for Slice and VarArgs
+ // are identical. - brendanfh 2020/09/07
+ case Type_Kind_VarArgs:
case Type_Kind_Slice: {
if (idx == 0) {
smem->idx = 0;
if (type_is_pointer(type)) return 1;
if (type->kind == Type_Kind_Slice) return 1;
if (type->kind == Type_Kind_DynArray) return 1;
+ if (type->kind == Type_Kind_VarArgs) return 1;
return 0;
}
if (type->kind == Type_Kind_Struct) return 1;
if (type->kind == Type_Kind_Slice) return 1;
if (type->kind == Type_Kind_DynArray) return 1;
+ if (type->kind == Type_Kind_VarArgs) return 1;
if (type->kind == Type_Kind_Pointer) {
if (type->Pointer.elem->kind == Type_Kind_Struct) return 1;
if (type->Pointer.elem->kind == Type_Kind_Slice) return 1;
if (type->kind == Type_Kind_Struct) return 1;
if (type->kind == Type_Kind_Slice) return 1;
if (type->kind == Type_Kind_DynArray) return 1;
+ if (type->kind == Type_Kind_VarArgs) return 1;
return 0;
}
switch (type->kind) {
case Type_Kind_Struct: return type->Struct.mem_count;
case Type_Kind_Slice: return 2;
+ case Type_Kind_VarArgs: return 2;
case Type_Kind_DynArray: return 3;
default: return 0;
}
switch (type->kind) {
case Type_Kind_Struct: return type_struct_is_simple(type);
case Type_Kind_Slice: return 1;
+ case Type_Kind_VarArgs: return 1;
case Type_Kind_DynArray: return 1;
default: return 0;
}
-}
\ No newline at end of file
+}
// NOTE: Storing structs requires that the location to store it is,
// the top most thing on the stack. Everything requires it to be
// 'under' the other element being stored. -brendanfh 2020/09/04
- if (!it_is_local && var->type->kind != Type_Kind_Struct) {
+ if (!it_is_local && !type_is_structlike(var->type)) {
emit_local_location(mod, &code, var, &offset);
}
if (it_is_local) {
WIL(WI_LOCAL_SET, iter_local);
} else {
- if (var->type->kind != Type_Kind_Struct) {
+ if (!type_is_structlike(var->type)) {
emit_store_instruction(mod, &code, var->type, offset);
} else {
emit_local_location(mod, &code, var, &offset);
// NOTE: Storing structs requires that the location to store it is,
// the top most thing on the stack. Everything requires it to be
// 'under' the other element being stored. -brendanfh 2020/09/04
- if (!it_is_local && var->type->kind != Type_Kind_Struct) {
+ if (!it_is_local && !type_is_structlike(var->type)) {
emit_local_location(mod, &code, var, &offset);
}
if (it_is_local) {
WIL(WI_LOCAL_SET, iter_local);
} else {
- if (var->type->kind != Type_Kind_Struct) {
+ if (!type_is_structlike(var->type)) {
emit_store_instruction(mod, &code, var->type, offset);
} else {
emit_local_location(mod, &code, var, &offset);
EMIT_FUNC(call, AstCall* call) {
bh_arr(WasmInstruction) code = *pcode;
+ u32 stack_grow_amm = 0;
+ u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top);
+
+ u32 vararg_count = 0;
+
for (AstArgument *arg = call->arguments;
arg != NULL;
arg = (AstArgument *) arg->next) {
+ if ((arg->flags & Ast_Flag_Arg_Is_VarArg) && !type_is_structlike(arg->type)) {
+ WID(WI_GLOBAL_GET, stack_top_idx);
+ }
+
emit_expression(mod, &code, arg->value);
+
+ if (arg->flags & Ast_Flag_Arg_Is_VarArg) {
+ if (type_is_structlike(arg->type)) WID(WI_GLOBAL_GET, stack_top_idx);
+
+ emit_store_instruction(mod, &code, arg->type, stack_grow_amm);
+
+ stack_grow_amm += type_size_of(arg->type);
+ vararg_count += 1;
+ }
+ }
+
+ if (vararg_count > 0) {
+ WID(WI_GLOBAL_GET, stack_top_idx);
+ WID(WI_I32_CONST, vararg_count);
}
CallingConvention cc = type_function_get_cc(call->callee->type);
u32 return_align = type_alignment_of(return_type);
bh_align(return_size, return_align);
- u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top);
+ stack_grow_amm += return_size;
- if (cc == CC_Return_Stack) {
+ b32 needs_stack = (cc == CC_Return_Stack) || (vararg_count > 0);
+
+ if (needs_stack) {
WID(WI_GLOBAL_GET, stack_top_idx);
- WID(WI_I32_CONST, return_size);
+ WID(WI_I32_CONST, stack_grow_amm);
WI(WI_I32_ADD);
WID(WI_GLOBAL_SET, stack_top_idx);
}
WID(WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 }));
}
- if (cc == CC_Return_Stack) {
+ if (needs_stack) {
WID(WI_GLOBAL_GET, stack_top_idx);
- WID(WI_I32_CONST, return_size);
+ WID(WI_I32_CONST, stack_grow_amm);
WI(WI_I32_SUB);
WID(WI_GLOBAL_SET, stack_top_idx);
+ }
+ if (cc == CC_Return_Stack) {
WID(WI_GLOBAL_GET, stack_top_idx);
- emit_load_instruction(mod, &code, return_type, 0);
+ emit_load_instruction(mod, &code, return_type, stack_grow_amm - return_size);
}
*pcode = code;
case 'r': *des++ = '\r'; break;
case 'v': *des++ = '\v'; break;
case 'e': *des++ = '\e'; break;
+ case '"': *des++ = '"'; break;
+ case '\\': *des++ = '\\'; break;
case 'x': {
// HACK: This whole way of doing this
i++;