Files
Ink-Shader-Language/Semantic_Analysis.jai

2189 lines
58 KiB
Plaintext

/////////////////////////////////////
//~ nbr:
//
/////////////////////////////////////
//~ nbr: Error reporting TODOs
//
// [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.
#load "static_array.jai";
#import "Hash_Table";
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;
MAX_TYPE_VARIABLE_CHILDREN :: 16;
children : [MAX_TYPE_VARIABLE_CHILDREN]Type_Variable_Handle;
child_count : int;
//@Note(niels): For constant buffers
resource_index : u32;
uf_parent : Type_Variable_Handle;
source_node : *AST_Node;
}
Type_Variable_Handle :: #type, distinct u32;
Type_Constraint_Handle :: #type, distinct u32;
Type_Constraint_Kind :: enum {
Int_Literal; // [[I]] = int
Float_Literal; // [[F]] = float
Equivalence; // [[X]] = int/float
Equality; // E1 == E2: [[E1]] = [[E2]] && [[E1 == E2]] = bool
Function_Decl; // X(X1, ..., Xn) { return E; }: [[X]] = ([[X1]], ..., [[Xn]]) -> [[E]]
Function_Call; // E(E1, ..., En): [[E]] = ([[E1]], ..., [[En]]) -> [[E(E1, ..., En)]]
}
Type_Constraint :: struct {
kind : Type_Constraint_Kind;
union {
literal : struct {
type_variable : Type_Variable_Handle;
union {
i : int;
f : float;
}
}
equivalence : struct {
lhs : Type_Variable_Handle;
rhs : Type_Variable_Handle;
}
function : struct {
symbol_variable : Type_Variable_Handle;
return_variable : Type_Variable_Handle;
arguments : [16]Type_Variable_Handle;
argument_count : int;
}
}
usage_site : *AST_Node;
binary_operator : Token;
}
Scope_Stack :: struct {
allocator : Allocator;
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;
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;
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;
constraints : [..]Type_Constraint;
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 := h2tv(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 := h2tv(checker, func.type_variable);
if arg_list.children.count != func_var.child_count {
print_to_builder(*builder, "Not enough arguments: Wanted %, got %.\n\n", func_var.child_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 := h2tv(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 := h2tv(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 := h2tv(checker, expect);
got_var := h2tv(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.child_count - 1{
child_handle := got_var.children[i];
child := h2tv(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;
}
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;
variable.uf_parent = handle;
array_add(*checker.result.type_variables, variable);
return h2tv(checker, handle), handle;
}
add_child :: (variable : *Type_Variable, child : Type_Variable_Handle) {
assert(variable.child_count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
variable.children[variable.child_count] = child;
variable.child_count += 1;
}
add_child :: (checker : *Semantic_Checker, handle : Type_Variable_Handle, child : Type_Variable_Handle) {
variable := h2tv(checker, handle);
assert(variable.child_count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
variable.children[variable.child_count] = child;
variable.child_count += 1;
}
init_semantic_checker :: (checker : *Semantic_Checker, root : *AST_Node, path : string) {
checker.program_root = root;
checker.path = path;
// @Incomplete(niels): Use other allocator and/or add static array with convenience functions
array_reserve(*checker.result.type_variables, 2048);
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);
}
h2tv :: (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];
}
h2tv :: (checker : *Semantic_Checker, handle : Type_Variable_Handle) -> *Type_Variable {
return h2tv(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 := h2tv(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 := h2tv(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 := check_node(checker, field);
if type_var > 0 {
h2tv(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 := h2tv(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 := h2tv(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 := h2tv(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.child_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 := h2tv(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 := h2tv(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);
h2tv(checker, handle).return_type_variable= return_handle;
}
if !builtin {
pop_scope(checker);
}
return handle;
}
create_function_constraint :: (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 := h2tv(checker, function.type_variable);
assert(variable.scope > 0, "Declared function is missing scope.");
previous_scope := use_scope(checker, variable.scope);
constraint : Type_Constraint;
constraint.kind = .Function_Decl;
constraint.function.symbol_variable = function.type_variable;
for i : 0..variable.child_count - 1 {
arg_var := variable.children[i];
if arg_var > 0 {
constraint.function.arguments[constraint.function.argument_count] = arg_var;
constraint.function.argument_count += 1;
}
}
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;
constraint.function.return_variable = variable.return_type_variable;
}
} else {
result_var := check_node(checker, statement);
if result_var > 0 {
stm := h2tv(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);
}
array_add(*checker.constraints, constraint);
use_scope(checker, previous_scope);
}
}
create_literal_constraint :: (checker : *Semantic_Checker, value : int, type_variable : Type_Variable_Handle) {
constraint : Type_Constraint;
constraint.kind = .Int_Literal;
constraint.literal.i = value;
constraint.literal.type_variable = type_variable;
array_add(*checker.constraints, constraint);
}
create_literal_constraint :: (checker : *Semantic_Checker, value : float, type_variable : Type_Variable_Handle) -> Type_Constraint_Handle {
constraint : Type_Constraint;
constraint.kind = .Float_Literal;
constraint.literal.f = value;
constraint.literal.type_variable = type_variable;
array_add(*checker.constraints, constraint);
return cast(Type_Constraint_Handle)checker.constraints.count;
}
create_equivalence_constraint :: (checker : *Semantic_Checker, lhs : Type_Variable_Handle, rhs : Type_Variable_Handle, usage_site : *AST_Node = null) -> Type_Constraint_Handle {
constraint : Type_Constraint;
constraint.kind = .Equivalence;
constraint.equivalence.lhs = lhs;
constraint.equivalence.rhs = rhs;
constraint.usage_site = usage_site;
array_add(*checker.constraints, constraint);
return cast(Type_Constraint_Handle)checker.constraints.count;
}
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 := h2tv(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 := h2tv(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);
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);
}
create_equivalence_constraint(checker, handle, rhs, node);
}
return handle;
}
create_call_constraint :: (checker : *Semantic_Checker, node : *AST_Node, type_var : Type_Variable_Handle) {
find_result := find_symbol(checker, node.name, checker.current_scope);
if !find_result {
function_undeclared(checker, node);
return;
}
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;
}
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 := h2tv(checker, func.type_variable);
if arg_count != function.child_count {
continue;
}
if node.children.count == 0 && function.child_count == 0 {
overload_found = true;
break;
}
all_args_match : bool = true;
for arg : arg_vars {
function_param := function.children[it_index];
if !types_compatible(checker, arg.var, function_param) {
if all_args_match {
arg_node = arg.node;
}
fun_param := h2tv(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 := h2tv(checker, function.return_type_variable);
constrained_var := h2tv(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);
}
}
}
check_node :: (checker : *Semantic_Checker, node : *AST_Node) -> Type_Variable_Handle {
if node.kind == {
case .Function; {
create_function_constraint(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 := h2tv(checker, lhs_var);
variable.type = lhs_type.type;
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; {
create_equivalence_constraint(checker, rhs_var, lhs_var, node);
proper_variable, rhs_handle := new_type_variable(checker);
proper_variable.type = h2tv(checker, lhs_var).type;
proper_variable.source_node = h2tv(checker, lhs_var).source_node;
proper_variable.struct_field_parent = h2tv(checker, lhs_var).struct_field_parent;
create_equivalence_constraint(checker, handle, rhs_handle, node);
}
case .TOKEN_ASSIGN; {
create_equivalence_constraint(checker, lhs_var, rhs_var, node);
}
}
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;
type_constraint := create_literal_constraint(checker, node.integer_value, 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;
type_constraint := create_literal_constraint(checker, node.float_value, 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;
create_call_constraint(checker, node, handle);
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 := h2tv(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) -> bool {
lhs_var := h2tv(checker, lhs);
rhs_var := h2tv(checker, rhs);
if lhs_var.type == {
case .Int; #through;
case .Half; #through;
case .Float; #through;
case .Double; {
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;
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 := h2tv(checker, lhs_struct.type_variable);
rhs_struct_var := h2tv(checker, rhs_struct.type_variable);
if lhs_struct_var.child_count != rhs_struct_var.child_count {
return false;
}
for i : 0..lhs_struct_var.child_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;
}
union_terms :: (checker : *Semantic_Checker, lhs_handle : Type_Variable_Handle, rhs_handle : Type_Variable_Handle, usage_site : *AST_Node) {
if !types_compatible(checker, lhs_handle, rhs_handle) {
lhs_var := h2tv(checker, lhs_handle);
rhs_var := h2tv(checker, rhs_handle);
if usage_site {
type_mismatch(checker, usage_site, rhs_var.source_node.parent, rhs_handle, lhs_handle);
} else {
type_mismatch(checker, lhs_var.source_node.parent, rhs_var.source_node.parent, rhs_handle, lhs_handle);
}
} else if lhs_handle != rhs_handle {
lhs := h2tv(checker, lhs_handle);
lhs.uf_parent = rhs_handle;
}
}
unify :: (checker : *Semantic_Checker, lhs_handle : Type_Variable_Handle, rhs_handle : Type_Variable_Handle, usage_site : *AST_Node = null) -> Unification_Result {
rep_lhs := find(checker, lhs_handle);
rep_rhs := find(checker, rhs_handle);
if rep_lhs != rep_rhs {
lhs := h2tv(checker, rep_lhs);
rhs := h2tv(checker, rep_rhs);
if !is_proper(lhs) && !is_proper(rhs) {
union_terms(checker, rep_lhs, rep_rhs, usage_site);
} else if !is_proper(lhs) && is_proper(rhs) {
union_terms(checker, rep_lhs, rep_rhs, usage_site);
} else if is_proper(lhs) && !is_proper(rhs) {
union_terms(checker, rep_rhs, rep_lhs, usage_site);
} else if is_proper(lhs) && is_proper(rhs) {
union_terms(checker, rep_lhs, rep_rhs, usage_site);
//@Incomplete(niels): Evaluate sub-terms for functions.
} else {
return .Unification_Failure;
}
}
return .Unification_Success;
}
union_find :: (checker : *Semantic_Checker) -> bool {
for constraint : checker.constraints {
if constraint.kind == {
case .Int_Literal; {
}
case .Float_Literal; {
}
case .Equivalence; {
if !constraint.equivalence.lhs || !constraint.equivalence.rhs {
return false;
}
handle_lhs := find(checker, constraint.equivalence.lhs);
handle_rhs := find(checker, constraint.equivalence.rhs);
unification_result := unify(checker, handle_lhs, handle_rhs, constraint.usage_site);
if unification_result == .Unification_Failure {
return false;
}
}
case .Function_Decl; {
}
}
}
return true;
}
// 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, context.allocator);
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);
// if checker.result.had_error {
// //@Incomplete(niels): handle error...
// return checker.result;
// }
uf_result := union_find(checker);
if !uf_result {
//@Incomplete(niels): handle error...
// return result;
}
for *type_variable : checker.result.type_variables {
if type_variable.type == .Unresolved_Variable {
rep := h2tv(checker, type_variable.uf_parent);
type_variable.type = rep.type;
}
}
}
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);
array_reserve(*checker.constraints, 1024);
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
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 "";
}
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 := h2tv(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(h2tv(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.child_count - 1 {
child_handle := struct_type.children[it];
child := h2tv(checker, child_handle);
print_to_builder(builder, child.name);
append(builder, " : ");
print_to_builder(builder, type_to_string(child));
if it < struct_type.child_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 := h2tv(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 := h2tv(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");
}
pretty_print_constraint :: (checker : *Semantic_Checker, constraint : Type_Constraint, builder : *String_Builder) {
if constraint.kind == {
case .Int_Literal; {
print_to_builder(builder, "[[%]] = int\n", constraint.literal.i);
}
case .Float_Literal; {
print_to_builder(builder, "[[%]] = float\n", constraint.literal.f);
}
case .Equivalence; {
lhs_var := h2tv(checker, constraint.equivalence.lhs);
rhs_var := h2tv(checker, constraint.equivalence.rhs);
append(builder, "[[");
print_type_variable(builder, lhs_var, checker);
append(builder, "]] = ");
if rhs_var.source_node {
append(builder, "[[");
}
print_type_variable(builder, rhs_var, checker);
if rhs_var.source_node {
append(builder, "]]");
}
append(builder, "\n");
}
case .Function_Decl; {
sym := h2tv(checker, constraint.function.symbol_variable);
print_to_builder(builder, "[[%]] =", sym.name);
append(builder, " (");
if constraint.function.argument_count > 0 {
for i : 0..constraint.function.argument_count - 1 {
arg := h2tv(checker, constraint.function.arguments[i]);
print_to_builder(builder, "[[%]]", arg.name);
if i < constraint.function.argument_count - 1 {
append(builder, ", ");
}
}
}
append(builder, ")");
if constraint.function.return_variable > 0 {
return_var := h2tv(checker, constraint.function.return_variable);
append(builder, " -> ");
append(builder, "[[");
print_type_variable(builder, return_var, checker);
append(builder, "]]", );
append(builder, "\n");
} else {
append(builder, " -> unit\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 := h2tv(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);
}
pretty_print_type_variable :: (checker : *Semantic_Checker, type_variable : *Type_Variable, builder : *String_Builder) {
rep := type_variable.uf_parent;
if type_variable.name.count > 0 {
append(builder, "[[");
print_type_variable(builder, type_variable, checker);
append(builder, "]] = ");
rep_var := h2tv(checker, rep);
if is_proper(type_variable) {
print_to_builder(builder, proper_type_to_string(checker, type_variable, temp));
} else if type_variable.type == .Struct || type_variable.type == .Properties ||
type_variable.type == .CBuffer {
if type_variable.kind == .Declaration {
append(builder, "{");
for 0..type_variable.child_count - 1 {
child_handle := type_variable.children[it];
child := h2tv(checker, child_handle);
print_to_builder(builder, child.name);
append(builder, " : ");
print_to_builder(builder, type_to_string(child));
if it < type_variable.child_count - 1 {
append(builder, ", ");
}
}
append(builder, "}");
} else if type_variable.typename.count > 0 {
print_to_builder(builder, "%", type_variable.typename);
}
} else {
print_from_source_location(builder, rep_var.source_node.source_location);
}
append(builder, "\n");
}
}
pretty_print_type_variables :: (checker : *Semantic_Checker, allocator : Allocator) -> string {
builder : String_Builder;
init_string_builder(*builder,, allocator);
for *type_variable : checker.result.type_variables {
if type_variable.builtin continue;
pretty_print_type_variable(checker, type_variable, *builder);
}
return builder_to_string(*builder,, allocator);
}
pretty_print_type_constraints :: (checker : *Semantic_Checker, allocator : Allocator) -> string {
builder : String_Builder;
init_string_builder(*builder,, allocator);
for *constraint : checker.constraints {
pretty_print_constraint(checker, constraint, *builder);
}
return builder_to_string(*builder,, allocator);
}