///////////////////////////////////// //~ 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"; Semantic_Type :: enum { Invalid :: 128; Int :: 0; Half :: 1; Float :: 2; Double :: 3; Texture2D :: 4; Sampler :: 5; 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" , ]; Type_Variable :: struct { type : Semantic_Type; 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; Semantic_Check_Result :: struct { messages : [..]Compiler_Message; message_arena : Arena; message_allocator : Allocator; had_error : bool; vertex_entry_point : *AST_Node; pixel_entry_point : *AST_Node; constant_buffers : Static_Array(Type_Variable_Handle, 16); scope_stack : Scope_Stack; type_variables : [..]Type_Variable; type_var_arena : Arena; type_var_allocator : Allocator; property_name : string; } Checker_State :: enum { Type_Checking; Adding_Builtins; } Semantic_Checker :: struct { program_root : *AST_Node; path : string; state : Checker_State; current_scope : Scope_Handle; // type_variables : [..]Type_Variable; current_buffer_index : u32 = 0; current_sampler_index : u32 = 0; current_texture_index : u32 = 0; result : Semantic_Check_Result; } 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, 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); } 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, 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.result.had_error = true; array_add(*checker.result.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.scope_stack.stack, new_scope); count := checker.result.scope_stack.stack.count; scope := *checker.result.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.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.scope_stack.stack.count == 0 { return null, 0; } count := checker.result.scope_stack.stack.count; scope := *checker.result.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.scope_stack, handle); } add_symbol_to_scope :: (checker : *Semantic_Checker, scope_handle : Scope_Handle, name : string, symbol : Defined_Symbol) -> *Defined_Symbol { scope := get_scope(checker, 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 checker.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.type_variables.count + 1; array_add(*checker.result.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.result.messages, 16); checker.program_root = root; checker.path = path; // @Incomplete(niels): Use other allocator and/or add static array with convenience functions checker.result.type_var_allocator = make_arena(*checker.result.type_var_arena); array_reserve(*checker.result.type_variables, 2048); checker.result.scope_stack.allocator = make_arena(*checker.result.scope_stack.arena); array_reserve(*checker.result.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.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.type_variables, handle); } proper_type_to_string :: (builder : *String_Builder, checker : *Semantic_Checker, 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, checker, var); if it_index != child.children.count - 1 { append(builder, ", "); } } } } append(builder, ")"); if var.return_type_variable > 0 { append(builder, " -> ", ); return_var := from_handle(checker, var.return_type_variable); if is_proper(return_var) { proper_type_to_string(builder, checker, return_var); } else { append(builder, "[["); print_type_variable(builder, checker, var.return_type_variable); append(builder, "]]", ); } } else { append(builder, " -> unit"); } } case; { append(builder, "______not proper type______"); } } } proper_type_to_string :: (checker : *Semantic_Checker, 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, checker, 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) -> Semantic_Type { type_string := node.token.ident_value; if type_string == { case Typenames[Semantic_Type.Int]; return .Int; case Typenames[Semantic_Type.Half]; return .Half; case Typenames[Semantic_Type.Float]; return .Float; case Typenames[Semantic_Type.Double]; return .Double; case Typenames[Semantic_Type.Sampler]; return .Sampler; case Typenames[Semantic_Type.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, 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 := create_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.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.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.vertex_entry_point = node; } if node.pixel_entry_point { checker.result.pixel_entry_point = 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, 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; } create_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); } } create_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 := create_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 := Semantic_Type.Max_Builtin; for i : 0..max_value - 1 { kind := cast(Semantic_Type)i; if name == Typenames[kind] { return false; } } return true; } //@Incomplete(niels): Handle meta stuff here create_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, 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 !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); if arg_count != function.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; { create_function(checker, node); return 0; } case. Field; { field_var := create_field(checker, node); return field_var; } case .Unnamed_Field; { field_var := create_field(checker, node); return field_var; } 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, true) { type_mismatch(checker, node, node.children[1], lhs_var, rhs_var); return 0; } } case .TOKEN_ASSIGN; { if !types_compatible(checker, lhs_var, rhs_var, true) { type_mismatch(checker, node.parent, node.children[1], lhs_var, rhs_var); return 0; } } } return handle; } case .Return; { return check_node(checker, node.children[0]); } case .Variable; { return create_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.result.had_error { return; } for declaration : declarations { check_node(checker, declaration); } } traverse :: (checker : *Semantic_Checker) { traverse(checker, checker.program_root); } // find :: (checker : *Semantic_Checker, root_handle : Type_Variable_Handle) -> Type_Variable_Handle { // assert(root_handle > 0); // root := from_handle(checker, root_handle); // // if root.uf_parent != root_handle { // // root.uf_parent = find(checker, root.uf_parent); // // } // return root.uf_parent; // } Unification_Result :: enum { Unification_Success; Unification_Failure; } 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; { if rhs_var.type != .Struct { return false; } lhs_node := lhs_var.source_node; rhs_node := rhs_var.source_node; if rhs_var.type != .Struct { 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; } } } 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; } // HLSL_BUILTIN :: #run -> string { // T := #load "hlsl_builtin.jai"; // return ""; // }; 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.shd"); 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); check_result := checker.result; if check_result.had_error { print("%\n", report_messages(check_result.messages)); return; } for *type_var : check_result.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; array_reserve(*checker.result.messages, 16); init_semantic_checker(*checker, file.ast_root, file.file.path); add_hlsl_builtins(*checker); type_check(*checker, file.ast_root); file.semantic_check_result = checker.result; copy_messages(checker.result.messages, *result.messages); } } check :: (checker : *Semantic_Checker, root : *AST_Node) -> Semantic_Check_Result { checker.current_buffer_index = 0; checker.current_sampler_index = 0; checker.current_texture_index = 0; array_reserve(*checker.result.messages, 16); add_hlsl_builtins(checker); type_check(checker, root); return checker.result; } check :: (checker : *Semantic_Checker) -> Semantic_Check_Result { return check(checker, checker.program_root); } // =========================================================== // 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 :: (checker : *Semantic_Checker, builder : *String_Builder, name : string) { scope := get_current_scope(checker); 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 :: (checker : *Semantic_Checker, builder : *String_Builder, name : string, function : Type_Variable, indentation : int) { indent(builder, indentation); print_key(checker, builder, name); append(builder, "("); for child : function.source_node.children { if child.kind == .FieldList { for field : child.children { tv := from_handle(checker, 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(checker, 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(checker, function.return_type_variable))); } else { append(builder, ")\n"); } } pretty_print_struct :: (checker : *Semantic_Checker, builder : *String_Builder, name : string, struct_type : Type_Variable, indentation : int) { indent(builder, indentation); print_key(checker, builder, name); append(builder, "{"); for 0..struct_type.children.count - 1 { child_handle := struct_type.children[it]; child := from_handle(checker, 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 :: (checker : *Semantic_Checker, 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(checker, func.type_variable); if type_variable.type == { case .Function; { pretty_print_function(checker, 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(checker, builder, key); print_type_variable(builder, type_variable, checker); append(builder, "\n"); // print_to_builder(builder, "%\n", type_variable.typename); } else { pretty_print_struct(checker, builder, key, type_variable, 1); } } case; { indent(builder, indentation + 1); print_key(checker, builder, key); print_to_builder(builder, "%\n", type_to_string(type_variable)); } } } } else { type_variable := from_handle(checker, value.type_variable); if type_variable.type == { case .Function; { pretty_print_function(checker, 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(checker, builder, key); print_to_builder(builder, "%\n", type_variable.typename); } else { pretty_print_struct(checker, builder, key, type_variable, 1); } } case; { indent(builder, indentation + 1); print_key(checker, builder, key); print_to_builder(builder, "%\n", type_to_string(type_variable)); } } } } for child : scope.children { child_scope := *checker.result.scope_stack.stack[child - 1]; pretty_print_scope(checker, child_scope, builder, indentation + 1); } if scope.table.count > 0 { indent(builder, indentation); } append(builder, "]\n"); } print_type_variable :: (builder : *String_Builder, variable : Type_Variable, checker : *Semantic_Checker) { 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, checker, 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, checker, 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, checker : *Semantic_Checker, handle : Type_Variable_Handle) { variable := from_handle(checker, handle); print_type_variable(builder, variable, checker); } pretty_print_symbol_table :: (checker : *Semantic_Checker, allocator : Allocator) -> string { builder : String_Builder; init_string_builder(*builder,, allocator); pretty_print_scope(checker, *checker.result.scope_stack.stack[0], *builder); return builder_to_string(*builder,, allocator); } #scope_module #import "ncore"; #import "Hash_Table"; #import "String";