2041 lines
54 KiB
Plaintext
2041 lines
54 KiB
Plaintext
/////////////////////////////////////
|
|
//~ nbr:
|
|
//
|
|
|
|
/////////////////////////////////////
|
|
//~ nbr: Error reporting TODOs
|
|
//
|
|
// [ ] Add and error for using keywords as names, or rename the dx11 keywords in the resulting hlsl shader.
|
|
// [x] Improve error reporting on mismatched overloads when types don't match, but arity does.
|
|
// [x] Improve error reporting for type mismatches in general. It seems like the expect node is not always correct.
|
|
|
|
VERTEX_MAIN_FUNCTION_PREFIX :: "vertex";
|
|
PIXEL_MAIN_FUNCTION_PREFIX :: "pixel";
|
|
PROPERTIES_PREFIX :: "properties";
|
|
|
|
Type_Kind :: enum {
|
|
Invalid :: 128;
|
|
|
|
Int :: 0;
|
|
Half :: 1;
|
|
Float :: 2;
|
|
Double :: 3;
|
|
Texture2D :: 4;
|
|
Sampler :: 5;
|
|
|
|
Bool :: 6;
|
|
|
|
Max_Builtin :: Sampler + 1;
|
|
|
|
Unit;
|
|
Function;
|
|
Call;
|
|
|
|
Unresolved_Variable;
|
|
Unresolved_Expression;
|
|
|
|
Struct;
|
|
Properties;
|
|
CBuffer;
|
|
Array;
|
|
}
|
|
|
|
Type_Variable_Kind :: enum {
|
|
Expression;
|
|
Declaration; // struct, properties, function, etc.
|
|
}
|
|
|
|
Typenames :: string.[
|
|
"int" ,
|
|
"half" ,
|
|
"float" ,
|
|
"double" ,
|
|
"Texture2D",
|
|
"Sampler" ,
|
|
"bool" ,
|
|
];
|
|
|
|
Type_Variable :: struct {
|
|
type : Type_Kind;
|
|
kind : Type_Variable_Kind;
|
|
builtin : bool;
|
|
|
|
name : string;
|
|
|
|
//@Note(niels) For functions
|
|
return_type_variable : Type_Variable_Handle;
|
|
|
|
//@Note(niels) The scope this variable creates (function, struct, global)
|
|
scope : Scope_Handle;
|
|
|
|
//@Note(niels): For struct members
|
|
struct_field_parent : *AST_Node;
|
|
|
|
typename : string;
|
|
is_array : bool;
|
|
|
|
MAX_TYPE_VARIABLE_CHILDREN :: 32;
|
|
children : Static_Array(Type_Variable_Handle, MAX_TYPE_VARIABLE_CHILDREN);
|
|
|
|
//@Note(niels): For constant buffers
|
|
resource_index : u32;
|
|
|
|
source_node : *AST_Node;
|
|
}
|
|
|
|
Type_Variable_Handle :: #type, distinct u32;
|
|
|
|
Scope_Stack :: struct {
|
|
allocator : Allocator;
|
|
arena : Arena;
|
|
|
|
stack : [..]Scope;
|
|
}
|
|
|
|
Defined_Symbol :: struct {
|
|
name : string;
|
|
|
|
type_variable : Type_Variable_Handle;
|
|
source_node : *AST_Node;
|
|
|
|
functions : [..]Defined_Symbol;
|
|
|
|
builtin : bool;
|
|
}
|
|
|
|
Scope_Kind :: enum {
|
|
Global;
|
|
Function;
|
|
Struct;
|
|
Properties;
|
|
}
|
|
|
|
Scope :: struct {
|
|
table : Table(string, Defined_Symbol);
|
|
|
|
//@Note(nb): Only for pretty printing
|
|
longest_key_length : int;
|
|
|
|
name : string;
|
|
children : [..]Scope_Handle;
|
|
parent : Scope_Handle;
|
|
|
|
builtin : bool;
|
|
|
|
kind : Scope_Kind;
|
|
}
|
|
|
|
Scope_Handle :: #type, distinct u32;
|
|
|
|
Checker_State :: enum {
|
|
Type_Checking;
|
|
Adding_Builtins;
|
|
}
|
|
|
|
Semantic_Checker :: struct {
|
|
program_root : *AST_Node;
|
|
path : string;
|
|
|
|
state : Checker_State;
|
|
|
|
current_scope : Scope_Handle;
|
|
|
|
result_file : *Compiled_File;
|
|
|
|
current_buffer_index : u32 = 0;
|
|
current_sampler_index : u32 = 0;
|
|
current_texture_index : u32 = 0;
|
|
|
|
messages : [..]Compiler_Message;
|
|
message_arena : Arena;
|
|
message_allocator : Allocator;
|
|
had_error : bool;
|
|
}
|
|
|
|
record_error :: (checker : *Semantic_Checker, message : string, source_location : Source_Range, report_source_location : bool = true) {
|
|
locations : [1]Source_Range;
|
|
locations[0] = source_location;
|
|
record_error(checker, message, locations, report_source_location);
|
|
}
|
|
|
|
invalid_symbol_name :: (checker : *Semantic_Checker, node : *AST_Node, type : string) {
|
|
record_error(checker, tprint("Invalid % name '%'", type, node.name), node.source_location);
|
|
}
|
|
|
|
symbol_redeclaration :: (checker : *Semantic_Checker, redeclared_node : *AST_Node, symbol : *Defined_Symbol) {
|
|
/*
|
|
|
|
Redeclaration of '%':
|
|
|
|
foo : int;
|
|
^^^
|
|
|
|
Here is the first redeclaration of 'foo':
|
|
|
|
foo : int;
|
|
^^^
|
|
|
|
*/
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Redeclaration of '%'\n", symbol.name);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(redeclared_node.source_location));
|
|
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, redeclared_node.source_location.main_token);
|
|
append(*builder, "\n\n");
|
|
|
|
white(*builder);
|
|
path := checker.path;
|
|
if path.count > 0 {
|
|
print_to_builder(*builder, "%:", checker.path);
|
|
} else {
|
|
append(*builder, "internal:");
|
|
}
|
|
|
|
if symbol.source_node {
|
|
location := symbol.source_node.source_location;
|
|
print_to_builder(*builder, "%,%: info: ", location.begin.line, location.begin.column);
|
|
print_to_builder(*builder, "Here is the first declaration of '%'\n", symbol.name);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(location));
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, symbol.source_node.source_location.main_token);
|
|
}
|
|
|
|
|
|
message := builder_to_string(*builder);
|
|
|
|
record_error(checker, message, redeclared_node.source_location, false);
|
|
}
|
|
|
|
symbol_undeclared :: (checker : *Semantic_Checker, node : *AST_Node) {
|
|
/*
|
|
Use of undeclard symbol '%'.
|
|
b = f;
|
|
^
|
|
*/
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Use of undeclared symbol '%'\n", node.name);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
|
|
print_to_builder(*builder, "%\n", print_from_source_location(node.source_location));
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, node.source_location.main_token);
|
|
|
|
message := builder_to_string(*builder);
|
|
|
|
record_error(checker, message, node.source_location, false);
|
|
}
|
|
|
|
no_matching_overload_found :: (checker : *Semantic_Checker, call : *AST_Node, overloads : *Defined_Symbol, arg_node : *AST_Node = null) {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Procedure call did not match any of the possible overloads for '%'\n", overloads.name);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
append(*builder, "found:\n");
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(call.source_location));
|
|
indent(*builder, 2);
|
|
print_token_pointer(*builder, call.source_location.main_token);
|
|
newline(*builder);
|
|
|
|
if arg_node {
|
|
newline(*builder);
|
|
|
|
white(*builder);
|
|
|
|
location := arg_node.source_location;
|
|
indent(*builder, 1);
|
|
|
|
field_list := arg_node.parent;
|
|
index : s64 = -1;
|
|
for child : field_list.children {
|
|
if child == arg_node {
|
|
index = it_index + 1;
|
|
break;
|
|
}
|
|
}
|
|
print_to_builder(*builder, "While matching argument % in function call.\n", index);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(location));
|
|
indent(*builder, 2);
|
|
print_token_pointer(*builder, arg_node.source_location.main_token);
|
|
newline(*builder);
|
|
}
|
|
|
|
white(*builder);
|
|
indent(*builder, 1);
|
|
append(*builder, "Possible overloads:\n");
|
|
|
|
for func : overloads.functions {
|
|
func_var := from_handle(checker, func.type_variable);
|
|
|
|
cyan(*builder);
|
|
// foo :: (f : float) {} (file_path:line_num)
|
|
func_location := func_var.source_node.source_location;
|
|
indent(*builder, 2);
|
|
|
|
// @Incomplete(niels): We need a way to properly save the path of the declaration
|
|
print_to_builder(*builder, "% (%:%)\n", print_from_source_location(func_location), checker.path,
|
|
func_location.main_token.line);
|
|
|
|
if !arg_node {
|
|
white(*builder);
|
|
|
|
arg_list := call.children[0];
|
|
indent(*builder, 2);
|
|
|
|
func_var := from_handle(checker, func.type_variable);
|
|
|
|
if arg_list.children.count != func_var.children.count {
|
|
print_to_builder(*builder, "Not enough arguments: Wanted %, got %.\n\n", func_var.children.count, arg_list.children.count);
|
|
}
|
|
}
|
|
}
|
|
|
|
locations : [1]Source_Range;
|
|
locations[0] = call.source_location;
|
|
message := builder_to_string(*builder);
|
|
record_error(checker, message, locations, false);
|
|
}
|
|
|
|
|
|
not_all_control_paths_return_value :: (checker : *Semantic_Checker, node : *AST_Node) {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Not all control paths return a value.\n\n");
|
|
|
|
cyan(*builder);
|
|
|
|
indent(*builder, 1);
|
|
|
|
print_to_builder(*builder, "%\n", print_from_source_location(node.source_location));
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, node.token);
|
|
|
|
white(*builder);
|
|
|
|
message := builder_to_string(*builder);
|
|
|
|
record_error(checker, message, node.source_location, false);
|
|
}
|
|
|
|
mismatched_arguments :: (checker : *Semantic_Checker, call : *AST_Node, symbol : *Defined_Symbol, args_call : int, args_def : int) {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
if args_call > args_def {
|
|
print_to_builder(*builder, "Too many arguments: Wanted %, got %\n", args_def, args_call);
|
|
} else {
|
|
print_to_builder(*builder, "Too few arguments: Wanted %, got %\n", args_def, args_call);
|
|
}
|
|
|
|
/*
|
|
Too many arguments: Expected %, got %.
|
|
|
|
found
|
|
foo(2, 3)
|
|
^^^
|
|
|
|
expected
|
|
foo :: (x : int, y : int, z : int)
|
|
|
|
*/
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
append(*builder, "found:\n");
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(call.source_location));
|
|
indent(*builder, 2);
|
|
print_token_pointer(*builder, call.source_location.main_token);
|
|
append(*builder, "\n\n");
|
|
indent(*builder, 1);
|
|
append(*builder, "expected:\n");
|
|
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(symbol.source_node.source_location));
|
|
|
|
locations : [1]Source_Range;
|
|
locations[0] = call.source_location;
|
|
|
|
message := builder_to_string(*builder);
|
|
record_error(checker, message, locations, false);
|
|
}
|
|
|
|
function_undeclared :: (checker : *Semantic_Checker, node : *AST_Node) {
|
|
/*
|
|
Error: Undeclared identifier 'name'.
|
|
|
|
name();
|
|
^^^^
|
|
|
|
*/
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Attempt to call undeclared function '%'.\n\n", node.name);
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(node.source_location));
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, node.source_location.main_token);
|
|
newline(*builder);
|
|
newline(*builder);
|
|
message := builder_to_string(*builder);
|
|
record_error(checker, message, node.source_location, false);
|
|
}
|
|
|
|
field_not_defined_on_struct :: (checker : *Semantic_Checker, node : *AST_Node, struct_symbol : *Defined_Symbol) {
|
|
/*
|
|
Field '%' is not defined in struct '%'.
|
|
b.t = f;
|
|
^
|
|
|
|
declaration:
|
|
Bar :: struct {
|
|
f : Foo;
|
|
}
|
|
*/
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
print_to_builder(*builder, "Field '%' is not defined in struct '%'.\n", node.name, struct_symbol.name);
|
|
|
|
cyan(*builder);
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(node.source_location));
|
|
indent(*builder, 1);
|
|
print_token_pointer(*builder, node.source_location.main_token);
|
|
newline(*builder);
|
|
newline(*builder);
|
|
|
|
indent(*builder, 1);
|
|
append(*builder, "declaration:\n");
|
|
print_from_source_location(*builder, struct_symbol.source_node.source_location, 2);
|
|
|
|
message := builder_to_string(*builder);
|
|
record_error(checker, message, node.source_location, false);
|
|
}
|
|
|
|
field_access_on_primitive_type :: (checker : *Semantic_Checker, node : *AST_Node, handle : Type_Variable_Handle) {
|
|
/*
|
|
Attempting to access a field on a primitive type '%'.
|
|
x.d = 5;
|
|
^^
|
|
|
|
declaration:
|
|
x : int = 5;
|
|
*/
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
variable := from_handle(checker, handle);
|
|
print_to_builder(*builder, "Attempting to access a field on a primitive type '%'.\n", proper_type_to_string(checker.result_file.type_variables, variable));
|
|
|
|
indent(*builder, 1);
|
|
cyan(*builder);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(node.source_location));
|
|
indent(*builder, 1);
|
|
|
|
node_variable := from_handle(checker, node.type_variable);
|
|
|
|
for 0..node.name.count - 1 {
|
|
append(*builder, " ");
|
|
}
|
|
print_token_pointer(*builder, node.source_location.begin);
|
|
|
|
append(*builder, "\n");
|
|
|
|
indent(*builder, 1);
|
|
append(*builder, "declaration:\n");
|
|
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%", print_from_source_location(variable.source_node.source_location));
|
|
|
|
message := builder_to_string(*builder,, temp);
|
|
record_error(checker, message, node.source_location, false);
|
|
|
|
white(*builder);
|
|
}
|
|
|
|
if_condition_has_to_be_boolean_type :: (checker : *Semantic_Checker, usage_site : *AST_Node, handle : Type_Variable_Handle) {
|
|
/*
|
|
Type of expression in if condition has to be bool.
|
|
if 100.0
|
|
^^^^^^^
|
|
|
|
100.0 has type float
|
|
*/
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
variable := from_handle(checker, handle);
|
|
append(*builder, "Type of expression in if condition has to be bool.\n");
|
|
|
|
indent(*builder, 1);
|
|
cyan(*builder);
|
|
|
|
location := usage_site.source_location;
|
|
|
|
print_to_builder(*builder, "%\n", print_from_source_location(location));
|
|
indent(*builder, 1);
|
|
|
|
print_token_pointer(*builder, usage_site.children[0].source_location.begin);
|
|
append(*builder, "\n");
|
|
|
|
indent(*builder, 1);
|
|
|
|
var := from_handle(checker, handle);
|
|
|
|
usage_child := usage_site.children[0];
|
|
usage_loc := usage_child.source_location;
|
|
|
|
print_to_builder(*builder, "% has type %\n", print_from_source_location(*usage_loc), proper_type_to_string(checker.result_file.type_variables, var));
|
|
|
|
message := builder_to_string(*builder,, temp);
|
|
record_error(checker, message, usage_site.source_location, false);
|
|
|
|
white(*builder);
|
|
}
|
|
|
|
type_mismatch :: (checker : *Semantic_Checker, usage_site : *AST_Node, expect_node : *AST_Node, expect : Type_Variable_Handle, got : Type_Variable_Handle) {
|
|
expect_var := from_handle(checker, expect);
|
|
got_var := from_handle(checker, got);
|
|
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, temp);
|
|
|
|
if got_var.builtin {
|
|
print_to_builder(*builder, "% :: (", got_var.name);
|
|
|
|
for i: 0..got_var.children.count - 1{
|
|
child_handle := got_var.children[i];
|
|
child := from_handle(checker, child_handle);
|
|
|
|
print_to_builder(*builder, "% : %", child.name, type_to_string(child));
|
|
}
|
|
}
|
|
|
|
print_to_builder(*builder, "Type mismatch. Expected % got %\n", type_to_string(expect_var), type_to_string(got_var));
|
|
|
|
cyan(*builder);
|
|
location := usage_site.source_location;
|
|
indent(*builder, 1);
|
|
append(*builder, "found:\n");
|
|
indent(*builder, 2);
|
|
print_to_builder(*builder, "%\n", print_from_source_location(location));
|
|
indent(*builder, 2);
|
|
|
|
print_token_pointer(*builder, location.main_token);
|
|
append(*builder, "\n");
|
|
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "expected:\n");
|
|
indent(*builder, 2);
|
|
proper_type_to_string(*builder, checker.result_file.type_variables, expect_var);
|
|
append(*builder, "\n");
|
|
|
|
// indent(*builder, 2);
|
|
|
|
{
|
|
// expect_location := expect_var.source_node.source_location;
|
|
// // token.length = expect_location.end.index + token.length - token.index + expect_location.end.length - 1;
|
|
|
|
// print_token_pointer(*builder, expect_location.main_token);
|
|
append(*builder, "\n");
|
|
}
|
|
|
|
indent(*builder, 1);
|
|
print_to_builder(*builder, "got:\n");
|
|
|
|
indent(*builder, 2);
|
|
|
|
print_to_builder(*builder, "%\n", print_from_source_location(got_var.source_node.source_location));
|
|
|
|
message := builder_to_string(*builder);
|
|
|
|
record_error(checker, message, Source_Range.[usage_site.source_location], false);
|
|
}
|
|
|
|
record_error :: (checker : *Semantic_Checker, error_string : string, locations : []Source_Range, report_source_location : bool = true) {
|
|
error : Compiler_Message;
|
|
error.message_kind = .Error;
|
|
error.report_source_location = report_source_location;
|
|
|
|
for location : locations {
|
|
array_add(*error.source_locations, location);
|
|
}
|
|
|
|
error.path = checker.path;
|
|
error.message = error_string;
|
|
|
|
checker.had_error = true;
|
|
array_add(*checker.messages, error);
|
|
}
|
|
|
|
is_proper :: (var : Type_Variable) -> bool {
|
|
if var.type == {
|
|
case .Function; #through;
|
|
case .Int; #through;
|
|
case .Struct; #through;
|
|
case .Float; {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
use_scope :: (checker : *Semantic_Checker, handle : Scope_Handle) -> Scope_Handle {
|
|
assert(handle > 0, "Invalid scope handle: %", handle);
|
|
previous_scope := checker.current_scope;
|
|
checker.current_scope = handle;
|
|
|
|
return previous_scope;
|
|
}
|
|
|
|
push_scope :: (checker : *Semantic_Checker, name := "", kind : Scope_Kind = .Global) -> *Scope, Scope_Handle {
|
|
new_scope : Scope;
|
|
array_add(*checker.result_file.scope_stack.stack, new_scope);
|
|
|
|
count := checker.result_file.scope_stack.stack.count;
|
|
scope := *checker.result_file.scope_stack.stack[count - 1];
|
|
scope.parent = checker.current_scope;
|
|
scope.name = name;
|
|
scope.kind = kind;
|
|
if checker.state == .Adding_Builtins {
|
|
scope.builtin = true;
|
|
}
|
|
|
|
scope.children.allocator = checker.result_file.scope_stack.allocator;
|
|
|
|
if checker.current_scope {
|
|
scope := get_current_scope(checker);
|
|
array_add(*scope.children, xx count);
|
|
}
|
|
|
|
checker.current_scope = xx count;
|
|
return scope, xx count;
|
|
}
|
|
|
|
pop_scope :: (checker : *Semantic_Checker) -> Scope_Handle {
|
|
scope := get_scope(checker, checker.current_scope);
|
|
if !scope.parent {
|
|
return 0;
|
|
}
|
|
|
|
checker.current_scope = scope.parent;
|
|
|
|
return checker.current_scope;
|
|
}
|
|
|
|
peek_scope :: (checker : *Semantic_Checker) -> *Scope, Scope_Handle {
|
|
if checker.result_file.scope_stack.stack.count == 0 {
|
|
return null, 0;
|
|
}
|
|
|
|
count := checker.result_file.scope_stack.stack.count;
|
|
scope := *checker.result_file.scope_stack.stack[count - 1];
|
|
return scope, xx count;
|
|
}
|
|
|
|
get_current_scope :: (checker : *Semantic_Checker) -> *Scope {
|
|
return get_scope(checker, checker.current_scope);
|
|
}
|
|
|
|
get_scope :: (scope_stack : Scope_Stack, handle : Scope_Handle) -> *Scope {
|
|
if handle == 0 {
|
|
return null;
|
|
}
|
|
|
|
return *scope_stack.stack[handle - 1];
|
|
}
|
|
|
|
get_scope :: (checker : *Semantic_Checker, handle : Scope_Handle) -> *Scope {
|
|
return get_scope(*checker.result_file.scope_stack, handle);
|
|
}
|
|
|
|
add_symbol_to_scope :: (state : Checker_State, scope_stack : *Scope_Stack, scope_handle : Scope_Handle, name : string, symbol : Defined_Symbol) -> *Defined_Symbol {
|
|
scope := get_scope(scope_stack.*, scope_handle);
|
|
scope.longest_key_length = max(scope.longest_key_length, name.count);
|
|
|
|
symbol_to_add : Defined_Symbol;
|
|
symbol_to_add.name = symbol.name;
|
|
symbol_to_add.type_variable = symbol.type_variable;
|
|
symbol_to_add.source_node = symbol.source_node;
|
|
symbol_to_add.functions = symbol.functions;
|
|
|
|
if state == .Adding_Builtins {
|
|
symbol_to_add.builtin = true;
|
|
}
|
|
|
|
return table_set(*scope.table, name, symbol_to_add);
|
|
}
|
|
|
|
new_type_variable :: (checker : *Semantic_Checker) -> *Type_Variable, Type_Variable_Handle {
|
|
variable : Type_Variable;
|
|
handle := cast(Type_Variable_Handle)checker.result_file.type_variables.count + 1;
|
|
array_add(*checker.result_file.type_variables, variable);
|
|
|
|
return from_handle(checker, handle), handle;
|
|
}
|
|
|
|
add_child :: (variable : *Type_Variable, child : Type_Variable_Handle) {
|
|
assert(variable.children.count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
|
|
array_add(*variable.children, child);
|
|
// variable.children[variable.children.count] = child;
|
|
// variable.children.count += 1;
|
|
}
|
|
|
|
add_child :: (checker : *Semantic_Checker, handle : Type_Variable_Handle, child : Type_Variable_Handle) {
|
|
variable := from_handle(checker, handle);
|
|
assert(variable.children.count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
|
|
array_add(*variable.children, child);
|
|
}
|
|
|
|
init_semantic_checker :: (checker : *Semantic_Checker, root : *AST_Node, path : string) {
|
|
checker.current_buffer_index = 0;
|
|
checker.current_sampler_index = 0;
|
|
checker.current_texture_index = 0;
|
|
|
|
array_reserve(*checker.messages, 16);
|
|
|
|
checker.program_root = root;
|
|
checker.path = path;
|
|
|
|
// @Incomplete(niels): Use other allocator and/or add static array with convenience functions
|
|
array_reserve(*checker.result_file.type_variables, 2048);
|
|
|
|
checker.result_file.scope_stack.allocator = make_arena(*checker.result_file.scope_stack.arena);
|
|
array_reserve(*checker.result_file.scope_stack.stack, 256);
|
|
|
|
global_scope, global_handle := push_scope(checker, kind = .Global);
|
|
array_reserve(*global_scope.children, 2048);
|
|
}
|
|
|
|
find_symbol :: (scope_stack : Scope_Stack, name : string, current_scope : Scope_Handle, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
|
|
handle := current_scope;
|
|
current := get_scope(scope_stack, current_scope);
|
|
|
|
while current {
|
|
table := current.table;
|
|
info := table_find_pointer(*table, name);
|
|
if info {
|
|
if containing_scope {
|
|
containing_scope.* = handle;
|
|
}
|
|
return info;
|
|
}
|
|
handle = current.parent;
|
|
current = get_scope(scope_stack, current.parent);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
find_symbol :: (checker : *Semantic_Checker, name : string, current_scope : Scope_Handle, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
|
|
return find_symbol(checker.result_file.scope_stack, name, current_scope, containing_scope);
|
|
}
|
|
|
|
find_symbol :: (name : string, checker : *Semantic_Checker, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
|
|
return find_symbol(checker, name, checker.current_scope, containing_scope);
|
|
}
|
|
|
|
from_handle :: (variables : []Type_Variable, handle : Type_Variable_Handle) -> *Type_Variable {
|
|
assert(handle > 0 && xx handle <= variables.count, tprint("Invalid handle: %. Range is: 1-%", handle, variables.count - 1));
|
|
return *variables[handle - 1];
|
|
}
|
|
|
|
from_handle :: (checker : *Semantic_Checker, handle : Type_Variable_Handle) -> *Type_Variable {
|
|
return from_handle(checker.result_file.type_variables, handle);
|
|
}
|
|
|
|
proper_type_to_string :: (builder : *String_Builder, variables : []Type_Variable, var : Type_Variable) {
|
|
if var.type == {
|
|
case .Int; #through;
|
|
case .Half; #through;
|
|
case .Float; #through;
|
|
case .Double; {
|
|
print_to_builder(builder, "%", Typenames[var.type]);
|
|
}
|
|
case .Struct; {
|
|
print_to_builder(builder, "%", var.typename);
|
|
}
|
|
case .Function; {
|
|
|
|
append(builder, "(");
|
|
|
|
for child : var.source_node.children {
|
|
if child.kind == .FieldList {
|
|
for field : child.children {
|
|
var := field.type_variable;
|
|
print_type_variable(builder, variables, var);
|
|
|
|
if it_index != child.children.count - 1 {
|
|
append(builder, ", ");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
append(builder, ")");
|
|
|
|
if var.return_type_variable > 0 {
|
|
append(builder, " -> ", );
|
|
return_var := from_handle(variables, var.return_type_variable);
|
|
if is_proper(return_var) {
|
|
proper_type_to_string(builder, variables, return_var);
|
|
} else {
|
|
append(builder, "[[");
|
|
print_type_variable(builder, variables, var.return_type_variable);
|
|
append(builder, "]]", );
|
|
}
|
|
} else {
|
|
append(builder, " -> unit");
|
|
}
|
|
}
|
|
case; {
|
|
append(builder, "______not proper type______");
|
|
}
|
|
}
|
|
}
|
|
|
|
proper_type_to_string :: (variables : []Type_Variable, var : Type_Variable, allocator := context.allocator) -> string {
|
|
if var.type == {
|
|
case .Int; #through;
|
|
case .Half; #through;
|
|
case .Float; #through;
|
|
case .Double; {
|
|
return Typenames[var.type];
|
|
}
|
|
case .Function; {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, allocator);
|
|
|
|
proper_type_to_string(*builder, variables, var);
|
|
return builder_to_string(*builder,, allocator);
|
|
}
|
|
case .Struct; {
|
|
return var.typename;
|
|
}
|
|
}
|
|
|
|
return "______not proper type______";
|
|
}
|
|
|
|
|
|
get_type_from_identifier :: (checker : *Semantic_Checker, scope : Scope_Handle, node : *AST_Node, typename : *string = null) -> Type_Kind {
|
|
type_string := node.token.ident_value;
|
|
|
|
if type_string == {
|
|
case Typenames[Type_Kind.Int]; return .Int;
|
|
case Typenames[Type_Kind.Half]; return .Half;
|
|
case Typenames[Type_Kind.Float]; return .Float;
|
|
case Typenames[Type_Kind.Double]; return .Double;
|
|
case Typenames[Type_Kind.Sampler]; return .Sampler;
|
|
case Typenames[Type_Kind.Texture2D]; return .Texture2D;
|
|
}
|
|
|
|
symbol := find_symbol(checker, type_string, scope);
|
|
if symbol {
|
|
symbol_var := from_handle(checker, symbol.type_variable);
|
|
if symbol_var.type == .Struct {
|
|
if typename {
|
|
typename.* = symbol_var.typename;
|
|
}
|
|
return symbol_var.type;
|
|
}
|
|
} else {
|
|
return .Unresolved_Variable;
|
|
}
|
|
|
|
return .Invalid;
|
|
}
|
|
|
|
check_expression :: (node : *AST_Node, checker : *Semantic_Checker) -> Type_Variable_Handle {
|
|
return 0;
|
|
}
|
|
|
|
check_block :: (checker : *Semantic_Checker, node : *AST_Node) {
|
|
for child : node.children {
|
|
check_node(checker, child);
|
|
}
|
|
}
|
|
|
|
declare_struct :: (checker : *Semantic_Checker, node : *AST_Node, name : string) -> Type_Variable_Handle {
|
|
variable, handle := new_type_variable(checker);
|
|
variable.type = .Struct;
|
|
variable.kind = .Declaration;
|
|
variable.name = name;
|
|
variable.source_node = node;
|
|
variable.typename = name;
|
|
node.type_variable = handle;
|
|
|
|
find_result := find_symbol(checker, name, checker.current_scope);
|
|
|
|
if !find_result {
|
|
symbol : Defined_Symbol;
|
|
symbol.name = name;
|
|
symbol.source_node = node;
|
|
symbol.type_variable = handle;
|
|
add_symbol_to_scope(checker.state, *checker.result_file.scope_stack, checker.current_scope, name, symbol);
|
|
} else {
|
|
symbol_redeclaration(checker, node, find_result);
|
|
return 0;
|
|
}
|
|
|
|
scope, scope_handle := push_scope(checker, name, .Struct);
|
|
variable.scope = scope_handle;
|
|
|
|
for child : node.children {
|
|
if child.kind == .FieldList {
|
|
for field : child.children {
|
|
type_var := check_field(checker, field);
|
|
|
|
if type_var > 0 {
|
|
from_handle(checker, type_var).scope = scope_handle;
|
|
add_child(checker, handle, type_var);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pop_scope(checker);
|
|
|
|
return handle;
|
|
}
|
|
|
|
declare_struct :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
return declare_struct(checker, node, node.name);
|
|
}
|
|
|
|
declare_properties :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
name := ifx node.name.count == 0 then "properties" else node.name;
|
|
|
|
if node.name.count > 0 {
|
|
checker.result_file.property_name = name;
|
|
}
|
|
type_var := declare_struct(checker, node, name);
|
|
var := from_handle(checker, type_var);
|
|
var.type = .Properties;
|
|
var.typename = "properties";
|
|
var.resource_index = checker.current_buffer_index;
|
|
checker.current_buffer_index += 1;
|
|
return type_var;
|
|
}
|
|
|
|
declare_cbuffer :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
type_var := declare_struct(checker, node);
|
|
var := from_handle(checker, type_var);
|
|
var.type = .CBuffer;
|
|
var.resource_index = checker.current_buffer_index;
|
|
checker.current_buffer_index += 1;
|
|
array_add(*checker.result_file.constant_buffers, type_var);
|
|
return type_var;
|
|
}
|
|
|
|
get_actual_function_name :: (node : *AST_Node) -> string {
|
|
name_to_check := node.name;
|
|
if node.vertex_entry_point {
|
|
|
|
name_to_check = sprint("%__%", VERTEX_MAIN_FUNCTION_PREFIX, node.name);
|
|
} else if node.pixel_entry_point {
|
|
name_to_check = sprint("%__%", PIXEL_MAIN_FUNCTION_PREFIX, node.name);
|
|
}
|
|
return name_to_check;
|
|
}
|
|
|
|
declare_foreign_function :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
return declare_function(checker, node, true);
|
|
}
|
|
|
|
declare_function :: (checker : *Semantic_Checker, node : *AST_Node, builtin : bool = false) -> Type_Variable_Handle {
|
|
if !node.foreign_declaration && !can_declare(checker, node.name) {
|
|
invalid_symbol_name(checker, node, "function");
|
|
return 0;
|
|
}
|
|
|
|
variable, handle := new_type_variable(checker);
|
|
variable.type = .Function;
|
|
variable.kind = .Declaration;
|
|
variable.name = node.name;
|
|
variable.source_node = node;
|
|
variable.builtin = builtin;
|
|
node.type_variable = handle;
|
|
|
|
name_to_check := get_actual_function_name(node);
|
|
|
|
if node.vertex_entry_point {
|
|
checker.result_file.vertex_entry_point.node = node;
|
|
}
|
|
|
|
if node.pixel_entry_point {
|
|
checker.result_file.pixel_entry_point.node = node;
|
|
}
|
|
find_result := find_symbol(checker, name_to_check, checker.current_scope);
|
|
|
|
if !find_result {
|
|
function : Defined_Symbol;
|
|
function.name = name_to_check;
|
|
function.source_node = node;
|
|
function.type_variable = handle;
|
|
|
|
symbol : Defined_Symbol;
|
|
symbol.name = name_to_check;
|
|
symbol.source_node = node;
|
|
symbol.type_variable = 0;
|
|
array_reserve(*symbol.functions, 32);
|
|
array_add(*symbol.functions, function);
|
|
|
|
add_symbol_to_scope(checker.state, *checker.result_file.scope_stack, checker.current_scope, name_to_check, symbol);
|
|
} else {
|
|
//@Note(niels): This is some ugly code, but it's probably fine for now.
|
|
field_list := node.children[0];
|
|
|
|
for function : find_result.functions {
|
|
func_var := from_handle(checker, function.type_variable);
|
|
if func_var.source_node.children[0].children.count != field_list.children.count {
|
|
continue;
|
|
}
|
|
|
|
all_same : bool = true;
|
|
for i : 0..func_var.children.count - 1 {
|
|
arg := func_var.children[i];
|
|
node_child := field_list.children[i];
|
|
|
|
typename : string;
|
|
arg_type := get_type_from_identifier(checker, checker.current_scope, node_child, *typename);
|
|
other_arg := from_handle(checker, arg);
|
|
|
|
if arg_type != other_arg.type {
|
|
all_same = false;
|
|
break;
|
|
} else {
|
|
if arg_type == .Struct && other_arg.type == .Struct {
|
|
if typename != other_arg.typename {
|
|
all_same = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if all_same {
|
|
symbol_redeclaration(checker, node, find_result);
|
|
return 0;
|
|
} else {
|
|
function : Defined_Symbol;
|
|
function.name = name_to_check;
|
|
function.source_node = node;
|
|
function.type_variable = handle;
|
|
|
|
array_add(*find_result.functions, function);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function : Defined_Symbol;
|
|
function.name = name_to_check;
|
|
function.source_node = node;
|
|
function.type_variable = handle;
|
|
|
|
array_add(*find_result.functions, function);
|
|
}
|
|
|
|
if !builtin {
|
|
scope, scope_handle := push_scope(checker, name_to_check, .Function);
|
|
variable.scope = scope_handle;
|
|
}
|
|
|
|
for child : node.children {
|
|
if child.kind == .FieldList {
|
|
for field : child.children {
|
|
type_var := check_node(checker, field);
|
|
if type_var > 0 {
|
|
if builtin {
|
|
var := from_handle(checker, type_var);
|
|
var.builtin = true;
|
|
}
|
|
add_child(checker, handle, type_var);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if builtin && node.token.ident_value.count > 0 {
|
|
return_var, return_handle := new_type_variable(checker);
|
|
return_var.type = get_type_from_identifier(checker, checker.current_scope, node, *return_var.typename);
|
|
from_handle(checker, handle).return_type_variable= return_handle;
|
|
}
|
|
|
|
if !builtin {
|
|
pop_scope(checker);
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
check_function :: (checker : *Semantic_Checker, node : *AST_Node) {
|
|
name_to_check := get_actual_function_name(node);
|
|
find_result := find_symbol(checker, name_to_check, checker.current_scope);
|
|
|
|
if !find_result {
|
|
assert(false, "Compiler error. Functions should all be declared at this point in time.");
|
|
}
|
|
|
|
handle := find_result.type_variable;
|
|
if node.foreign_declaration {
|
|
return;
|
|
}
|
|
|
|
for function : find_result.functions {
|
|
variable := from_handle(checker, function.type_variable);
|
|
|
|
assert(variable.scope > 0, "Declared function is missing scope.");
|
|
|
|
previous_scope := use_scope(checker, variable.scope);
|
|
|
|
for child : node.children {
|
|
if child.kind == .Block {
|
|
for statement : child.children {
|
|
if statement.kind == .Return {
|
|
result_var := check_node(checker, statement);
|
|
if result_var > 0 {
|
|
variable.return_type_variable = result_var;
|
|
}
|
|
} else {
|
|
result_var := check_node(checker, statement);
|
|
if result_var > 0 {
|
|
stm := from_handle(checker, result_var);
|
|
add_child(variable, result_var);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node.token.ident_value.count > 0 && !variable.return_type_variable{
|
|
not_all_control_paths_return_value(checker, node);
|
|
}
|
|
|
|
use_scope(checker, previous_scope);
|
|
}
|
|
}
|
|
|
|
check_variable :: (checker : *Semantic_Checker, node : *AST_Node, struct_field_parent : *AST_Node = null) -> Type_Variable_Handle {
|
|
find_result := find_symbol(checker, node.name, checker.current_scope);
|
|
// x : int;
|
|
// x.d = 5;
|
|
|
|
if find_result {
|
|
node.type_variable = find_result.type_variable;
|
|
variable := from_handle(checker, find_result.type_variable);
|
|
variable.struct_field_parent = struct_field_parent;
|
|
|
|
if get_scope(checker, checker.current_scope).kind == .Struct {
|
|
variable.scope = checker.current_scope;
|
|
}
|
|
|
|
if node.children.count > 0 {
|
|
if variable.type != .Struct && variable.type != .Properties && variable.type != .CBuffer {
|
|
field_access_on_primitive_type(checker, node, find_result.type_variable);
|
|
return 0;
|
|
} else {
|
|
lookup_name : string = variable.typename;
|
|
if variable.typename == "properties" {
|
|
lookup_name = variable.name;
|
|
}
|
|
struct_symbol := find_symbol(checker, lookup_name, checker.current_scope);
|
|
type_variable := from_handle(checker, struct_symbol.type_variable);
|
|
|
|
previous_scope := use_scope(checker, type_variable.scope);
|
|
child := node.children[0];
|
|
var := find_symbol(checker, child.name, type_variable.scope);
|
|
if var == null {
|
|
field_not_defined_on_struct(checker, child, struct_symbol);
|
|
return 0;
|
|
}
|
|
access := check_variable(checker, child, node);
|
|
use_scope(checker, previous_scope);
|
|
return access;
|
|
}
|
|
}
|
|
return find_result.type_variable;
|
|
}
|
|
|
|
symbol_undeclared(checker, node);
|
|
return 0;
|
|
}
|
|
|
|
can_declare :: (checker : *Semantic_Checker, name : string) -> bool {
|
|
max_value := Type_Kind.Max_Builtin;
|
|
|
|
for i : 0..max_value - 1 {
|
|
kind := cast(Type_Kind)i;
|
|
if name == Typenames[kind] {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//@Incomplete(niels): Handle meta stuff here
|
|
check_field :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
variable, handle := new_type_variable(checker);
|
|
variable.name = node.name;
|
|
typename : string;
|
|
variable.type = get_type_from_identifier(checker, checker.current_scope, node, *typename);
|
|
|
|
variable.is_array = node.array_field;
|
|
|
|
if variable.is_array {
|
|
size_node := node.children[0];
|
|
size_var := check_node(checker, size_node);
|
|
if from_handle(checker, size_var).type != .Int {
|
|
//@Incomplete(niels): Type mismatch here. With integral type required message.
|
|
}
|
|
}
|
|
|
|
if variable.kind == .Declaration && variable.type == .Sampler {
|
|
variable.resource_index = checker.current_sampler_index;
|
|
checker.current_sampler_index += 1;
|
|
}
|
|
|
|
if variable.kind == .Declaration && variable.type == .Texture2D {
|
|
variable.resource_index = checker.current_texture_index;
|
|
checker.current_texture_index += 1;
|
|
}
|
|
|
|
variable.typename = typename;
|
|
variable.source_node = node;
|
|
variable.scope = checker.current_scope;
|
|
node.type_variable = handle;
|
|
|
|
if node.kind != .Unnamed_Field {
|
|
if !can_declare(checker, node.name) {
|
|
invalid_symbol_name(checker, node, "variable");
|
|
return 0;
|
|
}
|
|
|
|
find_result := find_symbol(checker, node.name, checker.current_scope);
|
|
|
|
if !find_result {
|
|
symbol : Defined_Symbol;
|
|
symbol.name = node.name;
|
|
symbol.source_node = node;
|
|
symbol.type_variable = handle;
|
|
add_symbol_to_scope(checker.state, *checker.result_file.scope_stack, checker.current_scope, node.name, symbol);
|
|
} else {
|
|
symbol_redeclaration(checker, node, find_result);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if node.token.ident_value.count > 0 {
|
|
variable.type = get_type_from_identifier(checker, checker.current_scope, node);
|
|
}
|
|
|
|
if node.children.count > 0 {
|
|
rhs : Type_Variable_Handle;
|
|
assert(node.children.count == 1);
|
|
for child : node.children {
|
|
rhs = check_node(checker, child);
|
|
}
|
|
|
|
if handle == 0 || rhs == 0 {
|
|
return handle;
|
|
}
|
|
|
|
l := from_handle(checker, handle);
|
|
r := from_handle(checker, rhs);
|
|
assert(l.type != .Unresolved_Expression && r.type != .Unresolved_Expression);
|
|
|
|
if l.type == .Unresolved_Variable {
|
|
l.type = r.type;
|
|
l.typename = r.typename;
|
|
} else {
|
|
if !types_compatible(checker, handle, rhs) {
|
|
type_mismatch(checker, l.source_node, r.source_node, rhs, handle);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
check_call :: (checker : *Semantic_Checker, node : *AST_Node, type_var : Type_Variable_Handle) -> error : bool {
|
|
find_result := find_symbol(checker, node.name, checker.current_scope);
|
|
|
|
if !find_result {
|
|
function_undeclared(checker, node);
|
|
return true;
|
|
}
|
|
|
|
overload_found := false;
|
|
arg_node : *AST_Node = null;
|
|
|
|
Result :: struct {
|
|
var : Type_Variable_Handle;
|
|
node : *AST_Node;
|
|
}
|
|
arg_vars : [..]Result;
|
|
|
|
// @incomplete(niels): Should be some kind of scratch allocator instead probably?
|
|
arg_vars.allocator = temp;
|
|
arg_count := 0;
|
|
|
|
for child : node.children {
|
|
if child.kind == {
|
|
case .ArgList; {
|
|
arg_count = child.children.count;
|
|
for arg_child : child.children {
|
|
arg_var := check_node(checker, arg_child);
|
|
if arg_var != 0 {
|
|
array_add(*arg_vars, .{ arg_var, arg_child });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if arg_vars.count != arg_count {
|
|
return true;
|
|
}
|
|
|
|
Type_Mismatch_Data :: struct {
|
|
lhs : Result;
|
|
rhs : Result;
|
|
}
|
|
|
|
mismatches : [..]Type_Mismatch_Data;
|
|
mismatches.allocator = temp;
|
|
|
|
for *func : find_result.functions {
|
|
if overload_found {
|
|
break;
|
|
}
|
|
|
|
function := from_handle(checker, func.type_variable);
|
|
|
|
field_list := function.source_node.children[0];
|
|
if arg_count != field_list.children.count {
|
|
continue;
|
|
}
|
|
|
|
if node.children.count == 0 && function.children.count == 0 {
|
|
overload_found = true;
|
|
}
|
|
|
|
if overload_found && function.return_type_variable == 0 {
|
|
break;
|
|
}
|
|
|
|
all_args_match : bool = true;
|
|
|
|
for arg : arg_vars {
|
|
function_param := function.children[it_index];
|
|
|
|
if !types_compatible(checker, arg.var, function_param, true) {
|
|
if all_args_match {
|
|
arg_node = arg.node;
|
|
}
|
|
fun_param := from_handle(checker, function_param);
|
|
mismatch : Type_Mismatch_Data;
|
|
mismatch.lhs = arg;
|
|
mismatch.rhs = .{ function_param, fun_param.source_node };
|
|
array_add(*mismatches, mismatch);
|
|
|
|
all_args_match = false;
|
|
overload_found = false;
|
|
} else {
|
|
overload_found = all_args_match;
|
|
}
|
|
}
|
|
|
|
if overload_found {
|
|
if function.return_type_variable > 0 {
|
|
return_var := from_handle(checker, function.return_type_variable);
|
|
constrained_var := from_handle(checker, type_var);
|
|
constrained_var.type = return_var.type;
|
|
constrained_var.typename = return_var.typename;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !overload_found {
|
|
no_matching_overload_found(checker, node, find_result, arg_node);
|
|
|
|
for mismatch : mismatches {
|
|
type_mismatch(checker, mismatch.lhs.node, mismatch.rhs.node, mismatch.rhs.var, mismatch.lhs.var);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
check_node :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
|
|
if node.kind == {
|
|
case .Function; {
|
|
check_function(checker, node);
|
|
return 0;
|
|
}
|
|
case. Field; {
|
|
field_var := check_field(checker, node);
|
|
return field_var;
|
|
}
|
|
case .Unnamed_Field; {
|
|
field_var := check_field(checker, node);
|
|
return field_var;
|
|
}
|
|
case .Unary; {
|
|
var := check_node(checker, node.children[0]);
|
|
variable, handle := new_type_variable(checker);
|
|
type := from_handle(checker, var);
|
|
variable.type = type.type;
|
|
variable.typename = type.typename;
|
|
variable.scope = type.scope;
|
|
variable.source_node = node;
|
|
node.type_variable = handle;
|
|
add_child(variable, var);
|
|
|
|
return handle;
|
|
}
|
|
case .Binary; {
|
|
lhs_var := check_node(checker, node.children[0]);
|
|
if lhs_var == 0 {
|
|
return 0;
|
|
}
|
|
rhs_var := check_node(checker, node.children[1]);
|
|
if rhs_var == 0 {
|
|
return 0;
|
|
}
|
|
|
|
variable, handle := new_type_variable(checker);
|
|
lhs_type := from_handle(checker, lhs_var);
|
|
variable.type = lhs_type.type;
|
|
variable.typename = lhs_type.typename;
|
|
variable.scope = lhs_type.scope;
|
|
variable.source_node = node;
|
|
node.type_variable = handle;
|
|
add_child(variable, lhs_var);
|
|
add_child(variable, rhs_var);
|
|
|
|
if node.token.kind == {
|
|
case .TOKEN_PLUS; #through;
|
|
case .TOKEN_MINUS; #through;
|
|
case .TOKEN_STAR; #through;
|
|
case .TOKEN_SLASH; {
|
|
if !types_compatible(checker, lhs_var, rhs_var) {
|
|
type_mismatch(checker, node, node.children[1], lhs_var, rhs_var);
|
|
return 0;
|
|
}
|
|
}
|
|
case .TOKEN_ASSIGN; {
|
|
if !types_compatible(checker, lhs_var, rhs_var) {
|
|
type_mismatch(checker, node.parent, node.children[1], lhs_var, rhs_var);
|
|
return 0;
|
|
}
|
|
}
|
|
case .TOKEN_GREATER; #through;
|
|
case .TOKEN_GREATEREQUALS; #through;
|
|
case .TOKEN_LESS; #through;
|
|
case .TOKEN_LESSEQUALS; #through;
|
|
case .TOKEN_LOGICALOR; #through;
|
|
case .TOKEN_ISEQUAL; #through;
|
|
case .TOKEN_ISNOTEQUAL; #through;
|
|
case .TOKEN_LOGICALAND; {
|
|
variable.type = .Bool;
|
|
variable.typename = Typenames[variable.type];
|
|
}
|
|
}
|
|
return handle;
|
|
}
|
|
case .Return; {
|
|
return check_node(checker, node.children[0]);
|
|
}
|
|
case .If; {
|
|
cond_var := check_node(checker, node.children[0]);
|
|
|
|
if cond_var > 0 {
|
|
cond := from_handle(checker, cond_var);
|
|
if cond.type != .Bool {
|
|
if_condition_has_to_be_boolean_type(checker, node, cond_var);
|
|
}
|
|
}
|
|
|
|
body_var := check_block(checker, node.children[1]);
|
|
|
|
if node.children.count == 3 {
|
|
if node.children[2].kind == .If {
|
|
check_node(checker, node.children[2]);
|
|
} else {
|
|
check_block(checker, node.children[2]);
|
|
}
|
|
}
|
|
}
|
|
case .Variable; {
|
|
return check_variable(checker, node);
|
|
}
|
|
case .Integer; {
|
|
type_variable, handle := new_type_variable(checker);
|
|
type_variable.type = .Int;
|
|
type_variable.source_node = node;
|
|
node.type_variable = handle;
|
|
|
|
return handle;
|
|
}
|
|
case .Float; {
|
|
type_variable, handle := new_type_variable(checker);
|
|
type_variable.type = .Float;
|
|
type_variable.source_node = node;
|
|
node.type_variable = handle;
|
|
|
|
return handle;
|
|
}
|
|
case .Expression_Statement; {
|
|
return check_node(checker, node.children[0]);
|
|
}
|
|
case .Call; {
|
|
type_variable, handle := new_type_variable(checker);
|
|
type_variable.type = .Unresolved_Expression;
|
|
type_variable.source_node = node;
|
|
node.type_variable = handle;
|
|
|
|
if check_call(checker, node, handle) {
|
|
return 0;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
traverse :: (checker : *Semantic_Checker, root : *AST_Node) {
|
|
declarations := root.children;
|
|
for declaration : declarations {
|
|
if declaration.kind == .Function {
|
|
if declaration.foreign_declaration {
|
|
fun_handle := declare_foreign_function(checker, declaration);
|
|
} else {
|
|
fun_handle := declare_function(checker, declaration);
|
|
}
|
|
} else if declaration.kind == .Properties {
|
|
declare_properties(checker, declaration);
|
|
} else if declaration.kind == .Struct {
|
|
declare_struct(checker, declaration);
|
|
} else if declaration.kind == .CBuffer {
|
|
declare_cbuffer(checker, declaration);
|
|
}
|
|
}
|
|
|
|
if checker.had_error {
|
|
return;
|
|
}
|
|
|
|
for declaration : declarations {
|
|
check_node(checker, declaration);
|
|
}
|
|
}
|
|
|
|
traverse :: (checker : *Semantic_Checker) {
|
|
traverse(checker, checker.program_root);
|
|
}
|
|
|
|
types_compatible :: (checker : *Semantic_Checker, lhs : Type_Variable_Handle, rhs : Type_Variable_Handle, param_matching : bool = false) -> bool {
|
|
lhs_var := from_handle(checker, lhs);
|
|
rhs_var := from_handle(checker, rhs);
|
|
|
|
if lhs_var.type == {
|
|
case .Int; #through;
|
|
case .Half; #through;
|
|
case .Float; #through;
|
|
case .Double; {
|
|
if !param_matching {
|
|
if rhs_var.type == .Struct {
|
|
if rhs_var.typename == {
|
|
case "float2"; #through;
|
|
case "float3"; #through;
|
|
case "float4"; {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rhs_var.type == .Int || rhs_var.type == .Half ||
|
|
rhs_var.type == .Float || rhs_var.type == .Double;
|
|
}
|
|
case .Sampler; #through;
|
|
case .Texture2D; {
|
|
return rhs_var.type == lhs_var.type;
|
|
}
|
|
case .Struct; {
|
|
lhs_node := lhs_var.source_node;
|
|
rhs_node := rhs_var.source_node;
|
|
if rhs_var.type != .Struct && !param_matching {
|
|
if lhs_var.typename == {
|
|
case "float2"; #through;
|
|
case "float3"; #through;
|
|
case "float4"; {
|
|
return rhs_var.type == .Int || rhs_var.type == .Half || rhs_var.type == .Double || rhs_var.type == .Float;
|
|
}
|
|
}
|
|
}
|
|
|
|
if rhs_var.type != .Struct {
|
|
return false;
|
|
}
|
|
|
|
lhs_struct := find_symbol(checker, lhs_var.typename, xx 1);
|
|
rhs_struct := find_symbol(checker, rhs_var.typename, xx 1);
|
|
|
|
if !lhs_struct || !rhs_struct {
|
|
return false;
|
|
}
|
|
|
|
if !lhs_struct.type_variable || !rhs_struct.type_variable {
|
|
return false;
|
|
}
|
|
|
|
lhs_struct_var := from_handle(checker, lhs_struct.type_variable);
|
|
rhs_struct_var := from_handle(checker, rhs_struct.type_variable);
|
|
|
|
if lhs_struct_var.children.count != rhs_struct_var.children.count {
|
|
return false;
|
|
}
|
|
|
|
for i : 0..lhs_struct_var.children.count - 1 {
|
|
lhs_child := lhs_struct_var.children[i];
|
|
rhs_child := rhs_struct_var.children[i];
|
|
if !types_compatible(checker, lhs_child, rhs_child) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
case .Unresolved_Expression; #through;
|
|
case .Unresolved_Variable; {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
add_hlsl_builtins :: (checker : *Semantic_Checker) {
|
|
source_location := #location().fully_pathed_filename;
|
|
path_array := split(source_location, "/");
|
|
|
|
sb : String_Builder;
|
|
for i : 0..path_array.count - 2 {
|
|
print_to_builder(*sb, path_array[i]);
|
|
append(*sb, "/");
|
|
}
|
|
|
|
append(*sb, "hlsl_builtin.ink");
|
|
|
|
path := builder_to_string(*sb);
|
|
|
|
HLSL_BUILTIN, ok := read_entire_file(path);
|
|
|
|
if !ok {
|
|
messages : [..]Compiler_Message;
|
|
internal_error_message(*messages, "Error loading builtin functions.", checker.path);
|
|
print("%\n", report_messages(messages));
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
checker.state = .Adding_Builtins;
|
|
|
|
lexer : Lexer;
|
|
|
|
init_lexer_from_string(*lexer, HLSL_BUILTIN);
|
|
if lexer.result.had_error {
|
|
print("%\n", report_messages(lexer.result.messages));
|
|
return;
|
|
}
|
|
|
|
lex_result := lex(*lexer,, *temp);
|
|
if lex_result.had_error {
|
|
print("%\n", report_messages(lex_result.messages));
|
|
return;
|
|
}
|
|
|
|
parse_state : Parse_State;
|
|
init_parse_state(*parse_state, lex_result.tokens, lexer.path);
|
|
|
|
parse_result := parse(*parse_state);
|
|
if parse_result.had_error {
|
|
print("%\n", report_messages(parse_result.messages));
|
|
return;
|
|
}
|
|
|
|
type_check(checker, parse_result.root);
|
|
if checker.had_error {
|
|
print("%\n", report_messages(checker.messages));
|
|
return;
|
|
}
|
|
|
|
for *type_var : checker.result_file.type_variables {
|
|
type_var.builtin = true;
|
|
}
|
|
|
|
checker.state = .Type_Checking;
|
|
}
|
|
|
|
type_check :: (checker : *Semantic_Checker, root : *AST_Node) {
|
|
traverse(checker, root);
|
|
}
|
|
|
|
check :: (result : *Compile_Result) {
|
|
if result.had_error {
|
|
return;
|
|
}
|
|
|
|
for *file : result.files {
|
|
checker : Semantic_Checker;
|
|
|
|
checker.current_buffer_index = 0;
|
|
checker.current_sampler_index = 0;
|
|
checker.current_texture_index = 0;
|
|
checker.result_file = file;
|
|
array_reserve(*checker.messages, 32);
|
|
|
|
init_semantic_checker(*checker, file.ast_root, file.file.path);
|
|
|
|
// @Performance: Should have this built in stuff done earlier and only once
|
|
add_hlsl_builtins(*checker);
|
|
|
|
type_check(*checker, file.ast_root);
|
|
|
|
result.had_error |= checker.had_error;
|
|
copy_messages(checker.messages, *result.messages);
|
|
}
|
|
}
|
|
|
|
// ===========================================================
|
|
// Pretty printing
|
|
|
|
#scope_file
|
|
|
|
type_to_string :: (type_variable : Type_Variable) -> string {
|
|
if type_variable.type == {
|
|
case .Invalid;
|
|
return "{{invalid}}";
|
|
case .Unit;
|
|
return "()";
|
|
case .Int; #through;
|
|
case .Half; #through;
|
|
case .Float; #through;
|
|
case .Sampler; #through;
|
|
case .Texture2D; #through;
|
|
case .Double; {
|
|
return Typenames[type_variable.type];
|
|
}
|
|
case .Function; #through;
|
|
case .Struct; {
|
|
return type_variable.typename;
|
|
}
|
|
case .Array;
|
|
return "array";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
#scope_export
|
|
|
|
print_key :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, builder : *String_Builder, name : string) {
|
|
scope := get_scope(scope_stack, current_scope);
|
|
target_length := scope.longest_key_length + 1;
|
|
missing_padding := target_length - name.count;
|
|
str := tprint("[%]", name);
|
|
append(builder, str);
|
|
for 0..missing_padding - 1 {
|
|
append(builder, " ");
|
|
}
|
|
|
|
append(builder, ": ");
|
|
}
|
|
|
|
pretty_print_function :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, variables : []Type_Variable, builder : *String_Builder, name : string, function : Type_Variable, indentation : int) {
|
|
indent(builder, indentation);
|
|
print_key(scope_stack, current_scope, builder, name);
|
|
append(builder, "(");
|
|
|
|
for child : function.source_node.children {
|
|
if child.kind == .FieldList {
|
|
for field : child.children {
|
|
tv := from_handle(variables, field.type_variable);
|
|
if tv.type != .Function {
|
|
if tv.builtin {
|
|
print_to_builder(builder, "%", tv.name);
|
|
} else {
|
|
print_to_builder(builder, "% : %", tv.name, type_to_string(tv));
|
|
}
|
|
} else {
|
|
pretty_print_function(scope_stack, current_scope, variables, builder, "", tv, 0);
|
|
}
|
|
|
|
if it_index < child.children.count - 1 {
|
|
append(builder, ", ");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if function.return_type_variable> 0 {
|
|
print_to_builder(builder, ") -> %\n", type_to_string(from_handle(variables, function.return_type_variable)));
|
|
} else {
|
|
append(builder, ")\n");
|
|
}
|
|
}
|
|
|
|
pretty_print_struct :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, variables : []Type_Variable, builder : *String_Builder, name : string, struct_type : Type_Variable, indentation : int) {
|
|
indent(builder, indentation);
|
|
print_key(scope_stack, current_scope, builder, name);
|
|
append(builder, "{");
|
|
|
|
for 0..struct_type.children.count - 1 {
|
|
child_handle := struct_type.children[it];
|
|
child := from_handle(variables, child_handle);
|
|
print_to_builder(builder, child.name);
|
|
append(builder, " : ");
|
|
print_to_builder(builder, type_to_string(child));
|
|
|
|
if it < struct_type.children.count - 1 {
|
|
append(builder, ", ");
|
|
}
|
|
}
|
|
|
|
append(builder, "}\n");
|
|
}
|
|
|
|
pretty_print_scope :: (current_scope : Scope_Handle, scope_stack : Scope_Stack, variables : []Type_Variable, scope : *Scope, builder : *String_Builder, indentation : int = 0) {
|
|
if scope.builtin {
|
|
return;
|
|
}
|
|
|
|
table := scope.table;
|
|
|
|
indent(builder, indentation);
|
|
append(builder, "scope (");
|
|
if scope.name.count > 0 {
|
|
print_to_builder(builder, "%", scope.name);
|
|
} else {
|
|
append(builder, "global");
|
|
}
|
|
append(builder, ") [");
|
|
if scope.table.count > 0 {
|
|
append(builder, "\n");
|
|
}
|
|
|
|
for table {
|
|
key, value := it_index, it;
|
|
if value.builtin continue;
|
|
|
|
if value.functions.count > 0 {
|
|
for func : value.functions {
|
|
type_variable := from_handle(variables, func.type_variable);
|
|
if type_variable.type == {
|
|
case .Function; {
|
|
pretty_print_function(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
|
|
}
|
|
case .CBuffer; #through;
|
|
case .Properties; #through;
|
|
case .Struct; {
|
|
if type_variable.typename.count > 0 && type_variable.kind != .Declaration {
|
|
indent(builder, indentation + 1);
|
|
print_key(*scope_stack, current_scope, builder, key);
|
|
print_type_variable(builder, variables, type_variable);
|
|
append(builder, "\n");
|
|
// print_to_builder(builder, "%\n", type_variable.typename);
|
|
} else {
|
|
pretty_print_struct(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
|
|
}
|
|
}
|
|
case; {
|
|
indent(builder, indentation + 1);
|
|
print_key(*scope_stack, current_scope, builder, key);
|
|
print_to_builder(builder, "%\n", type_to_string(type_variable));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
type_variable := from_handle(variables, value.type_variable);
|
|
if type_variable.type == {
|
|
case .Function; {
|
|
pretty_print_function(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
|
|
}
|
|
case .CBuffer; #through;
|
|
case .Properties; #through;
|
|
case .Struct; {
|
|
if type_variable.typename.count > 0 && type_variable.kind != .Declaration {
|
|
indent(builder, indentation + 1);
|
|
print_key(*scope_stack, current_scope, builder, key);
|
|
print_to_builder(builder, "%\n", type_variable.typename);
|
|
} else {
|
|
pretty_print_struct(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
|
|
}
|
|
}
|
|
case; {
|
|
indent(builder, indentation + 1);
|
|
print_key(*scope_stack, current_scope, builder, key);
|
|
print_to_builder(builder, "%\n", type_to_string(type_variable));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for child : scope.children {
|
|
child_scope := *scope_stack.stack[child - 1];
|
|
pretty_print_scope(current_scope, *scope_stack, variables, child_scope, builder, indentation + 1);
|
|
}
|
|
|
|
if scope.table.count > 0 {
|
|
indent(builder, indentation);
|
|
}
|
|
append(builder, "]\n");
|
|
}
|
|
|
|
print_type_variable :: (builder : *String_Builder, variables : []Type_Variable, variable : Type_Variable) {
|
|
if variable.builtin {
|
|
if variable.type != .Function || variable.type != .Struct {
|
|
print_to_builder(builder, "%", type_to_string(variable));
|
|
} else {
|
|
print_to_builder(builder, "%", variable.name);
|
|
}
|
|
} else {
|
|
node := variable.source_node;
|
|
if node {
|
|
if node.kind == {
|
|
case .Properties; {
|
|
if node.name.count > 0 {
|
|
print_to_builder(builder, "% : ", node.name);
|
|
}
|
|
|
|
append(builder, "properties");
|
|
}
|
|
case .Meta; {
|
|
append(builder, "meta");
|
|
}
|
|
case .Function; #through;
|
|
case .Struct; #through;
|
|
case .CBuffer; #through;
|
|
case .Field; {
|
|
if variable.struct_field_parent {
|
|
print_to_builder(builder, "%.", variable.struct_field_parent.name);
|
|
}
|
|
|
|
print_to_builder(builder, "%", node.name);
|
|
}
|
|
case .Binary; {
|
|
left_most := node.children[0];
|
|
|
|
while left_most.kind == .Binary {
|
|
left_most = left_most.children[0];
|
|
}
|
|
|
|
right_most := node.children[1];
|
|
while right_most.kind == .Binary {
|
|
right_most = right_most.children[1];
|
|
}
|
|
|
|
source_location : Source_Range;
|
|
source_location.begin = left_most.source_location.main_token;
|
|
source_location.end = right_most.source_location.main_token;
|
|
source_location.main_token = node.source_location.main_token;
|
|
|
|
print_from_source_location(builder, source_location);
|
|
}
|
|
case .Call; {
|
|
if variable.return_type_variable{
|
|
assert(false);
|
|
print_to_builder(builder, "%", variable.typename);
|
|
print_type_variable(builder, variables, variable.return_type_variable);
|
|
}
|
|
|
|
print_to_builder(builder, "%(", node.name);
|
|
|
|
|
|
for child : node.children {
|
|
if child.kind == .ArgList {
|
|
for arg : child.children {
|
|
print_type_variable(builder, variables, arg.type_variable);
|
|
|
|
if it_index < child.children.count - 1 {
|
|
append(builder, ", ");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
append(builder, ")");
|
|
}
|
|
case; {
|
|
print_from_source_location(builder, node.source_location);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
print_type_variable :: (builder : *String_Builder, variables : []Type_Variable, handle : Type_Variable_Handle) {
|
|
variable := from_handle(variables, handle);
|
|
print_type_variable(builder, variables, variable);
|
|
}
|
|
|
|
pretty_print_symbol_table :: (checker : *Semantic_Checker, allocator : Allocator) -> string {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, allocator);
|
|
|
|
pretty_print_scope(xx checker.current_scope, checker.result_file.scope_stack, checker.result_file.type_variables, *checker.result_file.scope_stack.stack[0], *builder);
|
|
|
|
return builder_to_string(*builder,, allocator);
|
|
}
|
|
|
|
pretty_print_symbol_table :: (result : *Compile_Result, allocator : Allocator) -> string {
|
|
builder : String_Builder;
|
|
init_string_builder(*builder,, allocator);
|
|
|
|
for *file : result.files {
|
|
current_scope := cast(Scope_Handle)1;
|
|
pretty_print_scope(current_scope, file.scope_stack, file.type_variables, *file.scope_stack.stack[0], *builder);
|
|
|
|
}
|
|
|
|
|
|
return builder_to_string(*builder,, allocator);
|
|
}
|
|
|
|
#scope_module
|
|
|
|
#import "ncore";
|
|
#import "Hash_Table";
|
|
#import "String";
|