Compare commits

83 Commits

Author SHA1 Message Date
0c0e31db38 Fix lvalue/rvalue binaries. Fix structured buffer output. 2025-09-29 22:22:00 +02:00
2e23b37405 Finalize struct gen for structured buffers. Rename buffer builtins. 2025-09-29 20:55:27 +02:00
63a68b70b4 More fixes to access and buffer compilation. 2025-09-26 07:01:06 +02:00
6528ca854b Merge branch 'main' of git.nbross.com:nielsbross/Ink-Shader-Language 2025-09-24 14:04:56 +02:00
940b58331d A bunch of array fixes and some buffer stuff that doesn't quite work yet 2025-09-24 14:04:50 +02:00
c26fa892ee Added todos to module 2025-09-18 11:08:52 +00:00
4c84437022 Added comment and todo on resource index in type var. 2025-09-18 11:02:35 +00:00
3fdaebf66d Added todo regarding for loop scoping. 2025-09-18 10:58:44 +00:00
50a404984d Started some fixes for array support. Not entirely there yet. 2025-09-17 21:37:53 +02:00
89904824bb Fixed test to fully deprecate properties. 2025-09-17 12:33:38 +02:00
94daf81a85 Merge branch 'main' of git.nbross.com:nielsbross/Ink-Shader-Language
# Conflicts:
#	AST.jai
#	Check.jai
#	module.jai
2025-09-17 12:32:47 +02:00
607a6a0bed Deprecate properties. Use hinted cbuffers instead. This opens up to use a structured buffer in that way as well if you want instead. 2025-09-17 12:31:37 +02:00
8b2141df16 Fix some if directive stuff. Fix a property output issue. Will be deprecated next commit anyway. 2025-09-17 12:30:36 +02:00
7fefe0ecf6 Ifdefs, moved semantic to check, fixed error reporting for builtins 2025-09-16 11:04:57 +02:00
f99f86bc37 Update readme 2025-09-14 20:21:43 +02:00
d5476b54d7 Fix tests. 2025-09-13 22:02:44 +02:00
622ce388fa Actually fix which allocator we're using in the tests. 2025-09-13 22:01:29 +02:00
b0653b6563 Tracy 2025-09-11 17:29:45 +02:00
d6ea2e4c2f Memory optimization for the general case by reserving significantly fewer tokens in lexer. It was reserving 1 million always! 2025-09-11 12:27:44 +02:00
361a310ed1 Actually pass the allocator in so it's used instead of temp. 2025-09-11 11:33:16 +02:00
78e6d6146e Remove compiler ctx allocator 2025-09-11 11:23:07 +02:00
79ec6cc42f Added the rest of current builtins. Started properly implementing compile tests. 2025-09-11 11:03:02 +02:00
9461fe626f Change result to context for clarity. Fix a bunch of stuff in builtin functions and structs. 2025-09-10 23:21:34 +02:00
ceafd197f5 A bunch of new allocation related stuff. 2025-09-10 06:59:29 +02:00
f4a9592f26 Much better allocation strategy with new ncore arenas. Add more builtin node gen stuff. It's kind of wonky. 2025-09-06 23:30:45 +02:00
9cf51a1534 Broke builtins. 2025-09-06 19:58:46 +02:00
11c936ba7f Started load directive. 2025-09-03 22:31:18 +02:00
4924b01eac Added some initial directive code. Don't quite like the way it's done 2025-09-03 21:05:00 +02:00
603b625e21 Rename of files, improved for handling, add cb hints
- Rename Test to Ink as main file
- Support more errors for for loops.
- Add hints to cbs
2025-09-02 11:55:27 +02:00
9e0728f952 Fixed some error handling and weird consume logic. 2025-09-01 12:58:45 +02:00
94fc3a4dad Added basic for i loops. Missing some breaking tests and more tests. Also want to add for each at some point and it_index. 2025-08-30 22:58:51 +02:00
14f8b20d5f More small changes 2025-08-27 22:12:19 +02:00
4825623c73 Merge branch 'main' of git.nbross.com:nielsbross/Ink-Shader-Language 2025-08-27 21:55:08 +02:00
da87209690 Move compile result stuff out of specific stages. 2025-08-27 21:55:01 +02:00
e0908a67c0 Add hint parsing on constant buffers. Not yet used in output. 2025-08-25 22:08:58 +02:00
ab711b5610 Minor experiment 2025-08-25 21:56:08 +02:00
f6801e3eeb Fix type result on binary compuations. 2025-08-24 11:37:16 +02:00
4f37ed03c0 Remove unnecessary error check in compile_file 2025-08-24 11:22:13 +02:00
5b2e2e936b Add record error to add file. Change compile_file to take varargs 2025-08-24 11:19:59 +02:00
b491a56409 Changed some naming in sem checker. 2025-08-23 22:01:25 +02:00
45f67e16a8 Clean up a little bit of API code. Fix an issue with missing operator. Port random semi complex shader. 2025-08-21 22:22:00 +02:00
01ffe9c73d Properly output compiled file instead of intermediate results in semcheck 2025-08-20 21:07:14 +02:00
382d790c5b Moved more sem result into file. Still feels weird. Whole thing should just be in the file being passed around directly. 2025-08-18 22:39:45 +02:00
27933e599a New ncore 2025-08-18 22:39:25 +02:00
b7e34a22b2 Fix error propagation error in parsing. Semantic checker fix for compile result struct. Test cleanup 2025-08-18 07:11:41 +02:00
c36712b3ed Merge branch 'main' of git.nbross.com:nielsbross/Ink-Shader-Language 2025-08-14 20:11:22 +02:00
6b6c7bce62 Fixed a bunch of semant back and forth but now it looks like I broke some parameter checking in functions. 2025-08-14 14:04:54 +02:00
e356c5a3a9 Merge branch 'main' of git.nbross.com:nielsbross/Ink-Shader-Language 2025-08-13 22:26:57 +02:00
5ec2186a42 Revert "More work towards the new API"
This reverts commit af3e298b29.
2025-08-13 22:26:43 +02:00
af3e298b29 More work towards the new API 2025-08-13 22:22:32 +02:00
af42b61ed6 Struct output shenanigans 2025-01-27 21:38:54 +01:00
cd167d1560 Add missing codegen for minusequals and etc. Do proper type checking on else blocks. Spread single arg constructors not supported in hlsl. 2025-01-23 22:47:09 +01:00
8ce8651d6b Fix some missing operators not getting parsed properly. Fixed field access type checking with depth more than 1. 2025-01-22 22:30:06 +01:00
42c5baa846 if else if else if if else if else 2025-01-20 22:20:47 +01:00
45ea54cf93 Fixed some error handling for invalid if statements. Started if codegen. 2025-01-18 22:22:16 +01:00
b4d119230b Proper pretty printing and error handling on non-cool if condition. 2025-01-15 21:32:18 +01:00
a72a9ff50d More print improvements. 2025-01-15 07:15:31 +01:00
41d1dd406d Merge branch 'error-lolg' 2025-01-14 09:39:09 +01:00
bc69a39570 Attempt to improved error log. 2025-01-14 09:38:05 +01:00
aaeda22fa3 Added broken check for bool if cond. Also added some wonky if/else pretty printing for AST. 2025-01-13 16:33:03 +01:00
4b927b6be9 Add else parsing. Still a few bugs with output. 2025-01-13 09:14:36 +01:00
85b23f90e5 Function overload check cleanup. Added if statement to parsing. 2025-01-12 22:15:02 +01:00
ec31046d30 Added inferred types and missing length function. 2025-01-10 22:44:15 +01:00
8bd766281e Added shader output data to compiled_file API 2025-01-08 22:38:01 +01:00
4053400152 Added unary expressions to semcheck and codegen. 2025-01-08 22:37:48 +01:00
1adb289c10 Add error propagation to lexer. 2025-01-06 22:17:44 +01:00
d65c6359db Fixed some sem check init. 2025-01-05 22:38:08 +01:00
d08529a3eb Simplification of API. 2025-01-04 23:12:54 +01:00
7787d1307b More extension bike shedding. 2025-01-01 23:14:30 +01:00
4deb07027f Fix some tests and do some cleanup. 2025-01-01 23:06:53 +01:00
f13508262b Remove dumbass type constraint crap. 2024-12-29 23:39:57 +01:00
90fb1a035e Add Properties prefix. Fix semantic check regression. 2024-09-26 16:47:34 +02:00
e365067354 Changed type var children to static array. 2024-09-25 13:01:56 +02:00
243d83663a Simplified shader test output 2024-09-20 13:00:17 +02:00
3f93e1a92d Add double access shader test 2024-09-20 12:48:47 +02:00
fca325b761 Deleted some stuff 2024-09-12 07:18:27 +02:00
d3aa4fffeb Quick fix for binary expr codegen. Add uv hint 2024-09-11 13:21:03 +02:00
6eba51cc8c Fix position output hint. 2024-09-11 12:42:59 +02:00
5b237d34de Merge branch 'dev' of git.nbross.com:nielsbross/Shader-Compiler into dev 2024-09-11 07:25:21 +02:00
ff668b6c95 Fix type checker not promoting ints. Fix some issues in the codegen. 2024-09-11 07:24:50 +02:00
c84516d39f Fix type checker not promoting ints. Fix some issues in the codegen. 2024-09-11 07:24:10 +02:00
517209c886 A bunch of allocator stuff 2024-09-10 07:21:27 +02:00
d01fca146c Opened pandoras box 2024-09-09 22:32:36 +02:00
257 changed files with 8854 additions and 4177 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "modules/nbrutil"]
path = modules/ncore
url = git@git.nbross.com:nielsbross/NCore.git
[submodule "modules/tracy"]
path = modules/tracy
url = https://github.com/rluba/jai-tracy.git

358
AST.jai
View File

@@ -8,19 +8,17 @@ AST_Kind :: enum {
Function;
Return;
// @Incomplete(nb): Should these three really be their own block types?
// Maybe they at least shouldn't need to have their own tokens...
Properties;
Meta;
Instance;
//==
// Directives
If_Directive;
// Hint;
// Type;
// Operator;
Access;
Call;
Struct;
If;
For;
CBuffer;
Buffer;
FieldList;
ArgList;
Variable;
@@ -71,22 +69,25 @@ AST_Node :: struct {
pixel_entry_point : bool;
}
// ===========================================================
// Pretty printing
pretty_print_call :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_call :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(");
append(builder, node.name);
if node.children.count > 0 {
append(builder, " ");
pretty_print_children(node.children[0], indentation, builder, flags = 0);
pretty_print_children(node.children[0], indentation, builder, flags = 0, skip_indent = true);
}
append(builder, ")");
}
pretty_print_arglist :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_arglist :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "[");
pretty_print_children(node, indentation + 1, builder, flags = .NewLine);
@@ -94,8 +95,10 @@ pretty_print_arglist :: (node : *AST_Node, indentation : int, builder : *String_
append(builder, "]");
}
pretty_print_fieldlist :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_fieldlist :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "[");
pretty_print_children(node, indentation + 1, builder, flags = .NewLine);
@@ -103,13 +106,16 @@ pretty_print_fieldlist :: (node : *AST_Node, indentation : int, builder : *Strin
append(builder, "]");
}
pretty_print_field :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
pretty_print_field :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
print_to_builder(builder, tprint("(:= %", node.name));
if node.kind != .Unnamed_Field && node.token.ident_value.count > 0 {
if node.array_field {
append(builder, " [");
pretty_print_node(node.children[0], 0, builder);
pretty_print_node(node.children[0], indentation, builder, true);
append(builder, "].");
print_to_builder(builder, "%", node.token.ident_value);
} else {
@@ -126,31 +132,68 @@ pretty_print_field :: (node : *AST_Node, indentation : int, builder : *String_Bu
if !node.array_field && node.children.count > 0 {
append(builder, " ");
pretty_print_children(node, indentation, builder);
pretty_print_node(node.children[0], indentation, builder, true);
}
append(builder, ")");
}
Children_Print_Flags :: enum_flags {
NewLine :: 1 << 0;
Separator :: 1 << 1;
Space :: 1 << 2;
NewLine :: 1 << 0;
Separator :: 1 << 1;
Space :: 1 << 2;
Dont_Skip_Indent_On_First :: 1 << 3;
}
pretty_print_children :: (parent : *AST_Node, indentation : int, builder : *String_Builder, flags : Children_Print_Flags = .Separator) {
pretty_print_block :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if node.children.count == 0 {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "()");
} else {
flags := Children_Print_Flags.NewLine;
if !skip_indent {
flags |= .Dont_Skip_Indent_On_First;
}
pretty_print_children(node, indentation, builder, flags);
}
}
pretty_print_children :: (parent : *AST_Node, indentation : int, builder : *String_Builder, flags : Children_Print_Flags = .Separator, skip_indent := false) {
if !parent {
return;
}
children := parent.children;
for child : children {
if it_index > 0 {
indent(builder, indentation);
}
// if it_index > 0 {
// indent(builder, indentation);
// }
if !child continue;
pretty_print_node(child, 0, builder);
ind := indentation;
if flags & .Dont_Skip_Indent_On_First {
ind = indentation;
} else {
if it_index == 0 {
ind = 0;
}
}
if skip_indent{
ind = 0;
}
// skip := ifx it_index > 0 then false else true;
if child.kind == .Function {
pretty_print_declaration(child, ind, builder);
} else {
pretty_print_node(child, ind, builder);
}
if it_index != children.count - 1 {
if flags & .Separator {
@@ -176,6 +219,16 @@ op_to_string :: (oper : Token) -> string {
return "*";
case .TOKEN_SLASH;
return "/";
case .TOKEN_MINUSEQUALS;
return "-=";
case .TOKEN_PLUSEQUALS;
return "+=";
case .TOKEN_DIVEQUALS;
return "/=";
case .TOKEN_TIMESEQUALS;
return "*=";
case .TOKEN_MODEQUALS;
return "%=";
case .TOKEN_ISEQUAL;
return "==";
case .TOKEN_ASSIGN;
@@ -198,95 +251,206 @@ op_to_string :: (oper : Token) -> string {
return "";
}
pretty_print_binary :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
append(builder, "(");
pretty_print_binary :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
if node.token.kind == .TOKEN_LEFTBRACKET {
pretty_print_node(node.children[0], 0, builder);
append(builder, "[");
pretty_print_node(node.children[1], 0, builder);
append(builder, "]");
} else {
append(builder, "(");
op := node.token;
print_to_builder(builder, op_to_string(op));
append(builder, " ");
pretty_print_node(node.children[0], 0, builder);
append(builder, " ");
pretty_print_node(node.children[1], 0, builder);
append(builder, ")");
}
}
pretty_print_access :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
pretty_print_node(node.children[0], 0, builder);
append(builder, ".");
pretty_print_node(node.children[1], 0, builder);
}
pretty_print_unary :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
op := node.token;
print_to_builder(builder, op_to_string(op));
append(builder, " ");
pretty_print_children(node, 0, builder, flags = 0);
append(builder, ")");
pretty_print_node(node.children[0], 0, builder);
}
pretty_print_unary :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
}
print_return_node :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
print_return_node :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(return ");
pretty_print_children(node, 0, builder);
pretty_print_children(node, indentation, builder);
append(builder, ")");
}
print_expression_statement :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_if :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(if ");
condition := node.children[0];
pretty_print_node(condition, 0, builder);
append(builder, "\n");
body := node.children[1];
// indent(builder,indentation + 4);
// append(builder, "(");
pretty_print_node(body, indentation + 4, builder);
// append(builder, ")");
if node.children.count == 3 {
append(builder, "\n");
pretty_print_node(node.children[2], indentation + 4, builder);
}
append(builder, ")");
}
pretty_print_for :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(for ");
loop_iterator := node.token;
print_to_builder(builder, "% : ", loop_iterator.ident_value);
pretty_print_node(node.children[0], 0, builder);
append(builder, "..");
pretty_print_node(node.children[1], 0, builder);
append(builder, "\n");
pretty_print_node(node.children[2], indentation + 4, builder);
append(builder, ")");
}
print_expression_statement :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
if node.children[0] {
pretty_print_node(node.children[0], indentation, builder);
pretty_print_node(node.children[0], 0, builder);
}
}
pretty_print_node :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
pretty_print_node :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if node.kind == {
case .Return; {
print_return_node(node, indentation, builder);
print_return_node(node, indentation, builder, skip_indent);
}
case .If; {
pretty_print_if(node, indentation, builder, skip_indent);
}
case .If_Directive; {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(#if ");
condition := node.children[0];
pretty_print_node(condition, 0, builder);
append(builder, "\n");
body := node.children[1];
// indent(builder,indentation + 4);
// append(builder, "(");
pretty_print_node(body, indentation + 4, builder);
// append(builder, ")");
if node.children.count == 3 { //@Note: Else branch
append(builder, "\n");
pretty_print_node(node.children[2], indentation + 4, builder);
}
append(builder, ")");
}
case .For; {
pretty_print_for(node, indentation, builder, skip_indent);
}
case .Struct;
case .ArgList; {
pretty_print_arglist(node, indentation + 2, builder);
pretty_print_arglist(node, indentation + 2, builder, skip_indent);
}
case .FieldList; {
pretty_print_fieldlist(node, indentation + 2, builder);
pretty_print_fieldlist(node, indentation + 2, builder, skip_indent);
}
case .Field; {
pretty_print_field(node, indentation, builder);
pretty_print_field(node, indentation, builder, skip_indent);
}
case .Unnamed_Field; {
pretty_print_field(node, indentation, builder);
pretty_print_field(node, indentation, builder, skip_indent);
}
case .Block; {
pretty_print_children(node, indentation + 2, builder, flags = .NewLine);
pretty_print_block(node, indentation, builder, skip_indent);
}
case .Binary; {
pretty_print_binary(node, indentation, builder);
pretty_print_binary(node, indentation, builder, skip_indent);
}
case .Access; {
pretty_print_access(node, indentation, builder, skip_indent);
}
case .Unary; {
pretty_print_unary(node, indentation, builder);
pretty_print_unary(node, indentation, builder, skip_indent);
}
case .Variable; {
pretty_print_variable(node, indentation, builder);
pretty_print_variable(node, indentation, builder, skip_indent);
}
case .Expression_Statement; {
print_expression_statement(node, indentation, builder);
print_expression_statement(node, indentation, builder, skip_indent);
}
case .Integer; {
print_to_builder(builder, "%", node.integer_value);
print_to_builder(builder, "%", node.integer_value, skip_indent);
}
case .Float; {
print_to_builder(builder, "%", node.float_value);
print_to_builder(builder, "%", node.float_value, skip_indent);
}
case .Call; {
pretty_print_call(node, indentation, builder);
pretty_print_call(node, indentation, builder, skip_indent);
}
case .Error; {
print_to_builder(builder, "(error \"%\")", node.name);
print_to_builder(builder, "(error \"%\")", node.name, skip_indent);
}
}
}
pretty_print_variable :: (node : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_variable :: (node : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
print_to_builder(builder, "%", node.name);
for child : node.children {
print("%\n", child.kind);
if child.kind == .Variable {
append(builder, ".");
pretty_print_variable(child, indentation, builder);
pretty_print_variable(child, indentation, builder, skip_indent = true);
} else if child.kind == .Unary {
append(builder, "[");
pretty_print_node(child.children[0], 0, builder);
@@ -295,8 +459,10 @@ pretty_print_variable :: (node : *AST_Node, indentation : int, builder : *String
}
}
pretty_print_declaration :: (declaration : *AST_Node, indentation : int, builder : *String_Builder) {
indent(builder, indentation);
pretty_print_declaration :: (declaration : *AST_Node, indentation : int, builder : *String_Builder, skip_indent := false) {
if !skip_indent {
indent(builder, indentation);
}
append(builder, "(");
if declaration.foreign_declaration {
@@ -315,23 +481,28 @@ pretty_print_declaration :: (declaration : *AST_Node, indentation : int, builder
append(builder, "pixel ");
}
if declaration.kind == .Properties {
append(builder, "properties");
if declaration.name.count > 0 {
print_to_builder(builder, " %", declaration.name);
}
} else if declaration.kind == .Instance {
append(builder, "instance");
} else if declaration.kind == .Meta {
append(builder, "meta");
if declaration.kind == .If_Directive {
append(builder, "#if ");
}
else {
if declaration.kind == .Struct {
append(builder, "struct ");
} else if declaration.kind == .CBuffer {
append(builder, "constant_buffer ");
if declaration.kind == .Struct {
append(builder, "struct ");
} else if declaration.kind == .CBuffer {
append(builder, "constant_buffer ");
} else if declaration.kind == .Buffer {
append(builder, "buffer ");
}
print_to_builder(builder, "%", declaration.name);
if declaration.kind == .CBuffer || declaration.kind == .Buffer{
for hint : declaration.hint_tokens {
if hint.string_value.count > 0 {
print_to_builder(builder, " (@%)", hint.string_value);
}
}
print_to_builder(builder, "%", declaration.name);
// if declaration.kind != .If_Directive {
// print_to_builder(builder, "%", declaration.name);
// }
}
if declaration.kind == .Function && declaration.token.kind == .TOKEN_IDENTIFIER{
@@ -341,12 +512,35 @@ pretty_print_declaration :: (declaration : *AST_Node, indentation : int, builder
print_to_builder(builder, " (@%)", hint.string_value);
}
}
}
if declaration.children.count > 0 {
print_to_builder(builder, "\n");
pretty_print_children(declaration, indentation + 1, builder, flags = .NewLine);
if declaration.kind == .If_Directive {
pretty_print_node(declaration.children[0], 0, builder);
append(builder, "\n");
pretty_print_node(declaration.children[1], indentation + 5, builder);
if declaration.children.count > 2 {
append(builder, "\n");
if declaration.children[2].kind == .If_Directive {
pretty_print_declaration(declaration.children[2], indentation + 5, builder);
} else {
pretty_print_node(declaration.children[2], indentation + 5, builder);
}
}
} else {
print_to_builder(builder, "\n");
flags := Children_Print_Flags.NewLine;
if declaration.parent && declaration.parent.parent {
if declaration.parent.parent.kind == .If_Directive {
indent(builder, indentation - 1); //@Note: Hack the indent for now... Wow this is stupid, but it works!
}
}
pretty_print_children(declaration, indentation + 1, builder, flags = flags);
}
}
append(builder, ")");

2688
Check.jai Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,50 @@
/////////////////////////////////////
//~ nbr:
//
/////////////////////////////////////
//~ nbr: Codegen TODOs
//
Output_Language :: enum {
HLSL;
GLSL; // @Incomplete
MLSL; // @Incomplete
// SPIRV; // @Incomplete: Should we do this?
}
Codegen_State :: struct {
path : string;
scope_stack : Scope_Stack;
current_scope : Scope_Handle;
type_variables : []Type_Variable;
root : *AST_Node;
output_language : Output_Language;
builder : String_Builder;
result : Codegen_Result;
ctx : *Compiler_Context;
}
Codegen_Result :: struct {
messages : [..]Compiler_Message;
Reserved_HLSL_Words :: string.[
"texture",
"sampler",
"matrix",
"line",
"precise",
"shared",
"triangle",
"triangleadj",
];
had_error : bool;
Reserved_MLSL_Words :: string.[
""
];
result_text : string; // @Incomplete(nb): Result for now, should likely be far more sophisticated.
}
Reserved_GLSL_Words :: string.[
""
];
init_codegen_state :: (state : *Codegen_State, root : *AST_Node, checker_result : Semantic_Check_Result, output_language : Output_Language) {
state.root = root;
state.scope_stack = checker_result.scope_stack;
state.type_variables = checker_result.type_variables;
init_codegen_state :: (state : *Codegen_State, ctx : *Compiler_Context, output_language : Output_Language) {
state.current_scope = cast(Scope_Handle)1;
state.output_language = output_language;
init_string_builder(*state.builder);
@@ -41,13 +54,54 @@ indent :: (state : *Codegen_State, indentation : int) {
for 1..indentation append(*state.builder, " ");
}
emit_field :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
find_result := find_symbol(state.scope_stack, node.name, state.current_scope);
hlsl_type_to_string :: (variables : []Type_Variable, type_handle : Type_Variable_Handle) -> string {
return hlsl_type_to_string(variables, from_handle(variables, type_handle));
}
field := h2tv(state.type_variables, find_result.type_variable);
hlsl_type_to_string :: (variables : []Type_Variable, type_variable : Type_Variable) -> string {
if type_variable.type == {
case .Invalid;
return "{{invalid}}";
case .Unit;
return "()";
case .Int; {
return "int";
}
case .Half; {
return "half";
}
case .Float; {
return "float";
}
case .Double; {
return "double";
}
case .Sampler; {
return "SamplerState";
}
case .Texture2D; {
return "Texture2D";
}
case .Function; #through;
case .Struct; {
return type_variable.typename;
}
case .Array;
return hlsl_type_to_string(variables, type_variable.element_type);
}
return "";
}
emit_field :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
find_result := find_symbol(state.ctx.scope_stack, node.name, state.current_scope);
field := from_handle(state.ctx.type_variables, find_result.type_variable);
indent(state, indentation);
print_to_builder(*state.builder, "% ", type_to_string(field));
print_to_builder(*state.builder, "% ", hlsl_type_to_string(state.ctx.type_variables, field));
print_to_builder(*state.builder, "%", node.name);
if field.type == .Sampler {
@@ -58,34 +112,54 @@ emit_field :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
print_to_builder(*state.builder, " : register(t%)", field.resource_index);
}
for i :0..node.children.count - 1 {
child := node.children[i];
if node.children.count == 1 {
child := node.children[0];
print_to_builder(*state.builder, " = ");
emit_node(state, child, 0);
if field.type == .Array {
append(*state.builder, "[");
emit_node(state, child, 0);
append(*state.builder, "]");
} else {
print_to_builder(*state.builder, " = ");
emit_node(state, child, 0);
}
}
for i :0..field.child_count - 1 {
child := h2tv(state.type_variables, field.children[i]);
if node.parent.kind == .Block {
append(*state.builder, ";");
}
for i :0..field.children.count - 1 {
child := from_handle(state.ctx.type_variables, field.children[i]);
emit_node(state, child.source_node, 0);
}
for hint : node.hint_tokens {
if hint.ident_value == "position" {
// @Incomplete(nb): Should be a lookup table somewhere
if lookup_hint(hint.ident_value) == .Position {
append(*state.builder, " : POSITION");
} else if lookup_hint(hint.ident_value) == .UV {
append(*state.builder, " : TEXCOORD0");
} else if lookup_hint(hint.ident_value) == .Output_Position {
append(*state.builder, " : SV_POSITION");
}
}
}
emit_block :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
previous_scope := state.current_scope;
for statement : node.children {
if statement.type_variable {
state.current_scope = from_handle(state.ctx.type_variables, statement.type_variable).scope;
}
emit_node(state, statement, indentation);
if it_index < node.children.count {
append(*state.builder, ";\n");
append(*state.builder, "\n");
}
}
state.current_scope = previous_scope;
}
emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
@@ -97,7 +171,7 @@ emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
emit_node(state, args.children[0], 0);
append(*state.builder, ".");
print_to_builder(*state.builder, "%(", node.name);
print_to_builder(*state.builder, "Sample(");
for i : 1..args.children.count - 1 {
child := args.children[i];
@@ -108,6 +182,24 @@ emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
append(*state.builder, ", ");
}
}
} else if starts_with(node.name, "float") && node.children[0].children.count == 1 {
args := node.children[0];
print_to_builder(*state.builder, "%(", node.name);
number : string;
number.data = *node.name.data[5];
number.count = node.name.count - 5;
count := parse_int(*number, s32);
for i : 0..count - 1 {
child := args.children[0];
emit_node(state, child, 0);
if i != count - 1 {
append(*state.builder, ", ");
}
}
} else {
print_to_builder(*state.builder, "%(", node.name);
@@ -129,59 +221,9 @@ emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
append(*state.builder, ")");
}
emit_properties :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
find_result := find_symbol(state.scope_stack, ifx node.name.count > 0 then node.name else "properties", state.current_scope);
if !find_result {
message : Compiler_Message;
message.message_kind = .Internal_Error;
message.path = state.path;
message.message = "Attempting to generate undeclared properties buffer. This should never happen at this stage.";
array_add(*state.result.messages, message);
}
assert(find_result != null, "Attempting to generate undeclared properties buffer. This should never happen at this stage.");
variable := h2tv(state.type_variables, find_result.type_variable);
print_to_builder(*state.builder, "cbuffer __PROPERTIES : register(b%) \n{\n", variable.resource_index);
previous_scope := state.current_scope;
state.current_scope = variable.scope;
resources : Static_Array(*AST_Node, 8);
for child : node.children {
if child.kind == .FieldList {
for field : child.children {
tv := h2tv(state.type_variables, field.type_variable);
if tv.type == .Sampler || tv.type == .Texture2D {
array_add(*resources, field);
continue;
}
emit_node(state, field, 1);
append(*state.builder, ";\n");
}
}
}
append(*state.builder, "}\n\n");
for i : 0..resources.count - 1 {
resource := resources[i];
emit_node(state, resource, 0);
append(*state.builder, ";\n");
}
append(*state.builder, "\n");
state.current_scope = previous_scope;
}
emit_function :: (state : *Codegen_State, node : *AST_Node, indentation : int, emit_body := true) {
name := get_actual_function_name(node);
find_result := find_symbol(state.scope_stack, name, state.current_scope);
find_result := find_symbol(state.ctx.scope_stack, name, state.current_scope);
assert(find_result != null, "Attempting to generate undeclared function. This should never happen at this stage.");
if !find_result {
@@ -189,17 +231,17 @@ emit_function :: (state : *Codegen_State, node : *AST_Node, indentation : int, e
message.message_kind = .Internal_Error;
message.path = state.path;
message.message = "Attempting to generate undeclared function. This should never happen at this stage.";
array_add(*state.result.messages, message);
array_add(*state.ctx.messages, message);
}
for func : find_result.functions {
function_variable := h2tv(state.type_variables, func.type_variable);
function_variable := from_handle(state.ctx.type_variables, func.type_variable);
indent(state, indentation);
if function_variable.return_type_variable {
return_variable := h2tv(state.type_variables, function_variable.return_type_variable);
print_to_builder(*state.builder, "% ", type_to_string(return_variable));
return_variable := from_handle(state.ctx.type_variables, function_variable.return_type_variable);
print_to_builder(*state.builder, "% ", hlsl_type_to_string(state.ctx.type_variables, return_variable));
} else {
append(*state.builder, "void ");
}
@@ -272,6 +314,21 @@ emit_operator :: (state : *Codegen_State, op_kind : Token_Kind) {
case .TOKEN_SLASH; {
append(*state.builder, "/");
}
case .TOKEN_MINUSEQUALS; {
append(*state.builder, "-=");
}
case .TOKEN_PLUSEQUALS; {
append(*state.builder, "+=");
}
case .TOKEN_DIVEQUALS; {
append(*state.builder, "/=");
}
case .TOKEN_TIMESEQUALS; {
append(*state.builder, "*=");
}
case .TOKEN_MODEQUALS; {
append(*state.builder, "%=");
}
case .TOKEN_ISEQUAL; {
append(*state.builder, "==");
}
@@ -309,49 +366,77 @@ emit_node :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
}
case .Float; {
print_to_builder(*state.builder, "%f", formatFloat(node.float_value, zero_removal=.ONE_ZERO_AFTER_DECIMAL));
}
case .Properties; {
}
case .Field; {
emit_field(state, node, indentation);
}
case .Block; {
assert(false, "Not implemented yet: block");
}
case .Variable; {
indent(*state.builder, indentation);
type_var := h2tv(state.type_variables, node.type_variable);
is_properties := type_var.typename == "properties";
type_var := from_handle(state.ctx.type_variables, node.type_variable);
if !is_properties {
print_to_builder(*state.builder, "%", node.name);
}
print_to_builder(*state.builder, "%", node.name);
if node.children.count > 0 {
if !is_properties {
append(*state.builder, ".");
}
append(*state.builder, ".");
emit_node(state, node.children[0], 0);
}
}
case .Binary; {
case .Access; {
indent(*state.builder, indentation);
lhs := node.children[0];
rhs := node.children[1];
emit_node(state, lhs, 0);
append(*state.builder, " ");
emit_operator(state, node.token.kind);
append(*state.builder, " ");
print_to_builder(*state.builder, "%.", node.name);
emit_node(state, rhs, 0);
}
case .Binary; {
indent(*state.builder, indentation);
if node.token.kind != .TOKEN_ASSIGN && node.token.kind != .TOKEN_LEFTBRACKET {
if (node.parent.kind == .Binary && node.parent.token.kind != .TOKEN_ASSIGN) || node.parent.kind == .Access {
append(*state.builder, "(");
}
}
lhs := node.children[0];
rhs := node.children[1];
if node.token.kind == .TOKEN_LEFTBRACKET {
emit_node(state, lhs, 0);
append(*state.builder, "[");
emit_node(state, rhs, 0);
append(*state.builder, "]");
} else {
emit_node(state, lhs, 0);
append(*state.builder, " ");
emit_operator(state, node.token.kind);
append(*state.builder, " ");
emit_node(state, rhs, 0);
}
if node.token.kind != .TOKEN_ASSIGN && node.token.kind != .TOKEN_LEFTBRACKET {
if (node.parent.kind == .Binary && node.parent.token.kind != .TOKEN_ASSIGN) || node.parent.kind == .Access {
append(*state.builder, ")");
}
}
}
case .Unary; {
assert(false, "Not implemented yet: unary");
indent(*state.builder, indentation);
emit_operator(state, node.token.kind);
emit_node(state, node.children[0], 0);
}
case .Expression_Statement; {
emit_node(state, node.children[0], indentation);
append(*state.builder, ";");
}
case .Call; {
emit_call(state, node, indentation);
@@ -360,7 +445,72 @@ emit_node :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
indent(*state.builder, indentation);
append(*state.builder, "return ");
emit_node(state, node.children[0], 0);
append(*state.builder, ";");
}
case .For; {
if node.parent.kind != .For {
indent(*state.builder, indentation);
}
append(*state.builder, "for ");
loop_ident := node.token.ident_value;
begin_val := node.children[0].integer_value;
end_val := node.children[1].integer_value;
print_to_builder(*state.builder, "(int % = %; % < %; %++)\n", loop_ident, begin_val, loop_ident, end_val, loop_ident);
indent(*state.builder, indentation);
append(*state.builder, "{\n");
emit_block(state, node.children[2], indentation + 1);
indent(*state.builder, indentation);
append(*state.builder, "}\n");
}
case .If; {
if node.parent.kind != .If {
indent(*state.builder, indentation);
}
append(*state.builder, "if ");
cond := node.children[0];
append(*state.builder, "(");
emit_node(state, cond, 0);
append(*state.builder, ")");
body := node.children[1];
append(*state.builder, "\n");
indent(*state.builder, indentation);
append(*state.builder, "{\n");
emit_block(state, body, indentation + 1);
indent(*state.builder, indentation);
append(*state.builder, "}\n");
if node.children.count == 3 {
emit_else(state, node.children[2], indentation);
}
}
}
}
emit_else :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
indent(*state.builder, indentation);
append(*state.builder, "else ");
if node.kind == .If {
emit_node(state, node, indentation);
} else if node.kind == .Block {
append(*state.builder, "\n");
indent(*state.builder, indentation);
append(*state.builder, "{\n");
emit_block(state, node, indentation + 1);
indent(*state.builder, indentation);
append(*state.builder, "}");
}
}
@@ -374,11 +524,37 @@ emit_field_list :: (state : *Codegen_State, field_list : *AST_Node, indentation
}
}
emit_struct :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
print_to_builder(*state.builder, "struct %", node.name);
emit_struct :: (state : *Codegen_State, node : *AST_Node, indentation : int, name : string = "") {
if name.count > 0 {
print_to_builder(*state.builder, "struct %", name);
} else {
print_to_builder(*state.builder, "struct %", node.name);
}
current_scope := state.current_scope;
state.current_scope = h2tv(state.type_variables, node.type_variable).scope;
state.current_scope = from_handle(state.ctx.type_variables, node.type_variable).scope;
field_list := node.children[0];
if field_list.children.count > 0 {
append(*state.builder, "\n{\n");
} else {
append(*state.builder, " {");
}
emit_field_list(state, field_list, indentation);
append(*state.builder, "};\n\n");
state.current_scope = current_scope;
}
emit_cbuffer :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
variable := from_handle(state.ctx.type_variables, node.type_variable);
print_to_builder(*state.builder, "cbuffer % : register(b%)", variable.name, variable.resource_index);
current_scope := state.current_scope;
state.current_scope = from_handle(state.ctx.type_variables, node.type_variable).scope;
field_list := node.children[0];
@@ -394,25 +570,13 @@ emit_struct :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
state.current_scope = current_scope;
}
emit_cbuffer :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
variable := h2tv(state.type_variables, node.type_variable);
print_to_builder(*state.builder, "cbuffer % : register(b%)", variable.name, variable.resource_index);
emit_buffer :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
variable := from_handle(state.ctx.type_variables, node.type_variable);
element := from_handle(state.ctx.type_variables, variable.element_type);
current_scope := state.current_scope;
state.current_scope = h2tv(state.type_variables, node.type_variable).scope;
emit_struct(state, node, indentation, element.typename);
field_list := node.children[0];
if field_list.children.count > 0 {
append(*state.builder, "\n{\n");
} else {
append(*state.builder, " {");
}
emit_field_list(state, field_list, indentation);
append(*state.builder, "}\n\n");
state.current_scope = current_scope;
print_to_builder(*state.builder, "StructuredBuffer<%> % : register(t%);\n\n", element.typename, variable.name, variable.resource_index);
}
emit_declaration :: (state : *Codegen_State, node : *AST_Node) {
@@ -420,36 +584,62 @@ emit_declaration :: (state : *Codegen_State, node : *AST_Node) {
case .Function; {
emit_function(state, node, 0);
}
case .Properties; {
emit_properties(state, node, 0);
}
case .CBuffer; {
emit_cbuffer(state, node, 0);
}
case .Buffer; {
emit_buffer(state, node, 0);
}
case .Struct; {
emit_struct(state, node, 0);
}
}
}
codegen :: (state : *Codegen_State) -> Codegen_Result {
codegen :: (result : *Compiler_Context, allocator := temp) {
codegen(result, .HLSL);
}
codegen :: (result : *Compiler_Context, output_language : Output_Language, allocator := temp) {
if result.had_error {
return;
}
new_context := context;
new_context.allocator = allocator;
push_context new_context {
init_context_allocators();
defer clear_context_allocators();
state : Codegen_State;
state.ctx = result;
state.current_scope = cast(Scope_Handle)1;
state.output_language = output_language;
init_string_builder(*state.builder);
codegen(*state);
}
}
#scope_file
codegen :: (state : *Codegen_State) {
found_function : bool = false;
found_struct : bool = false;
// found_struct : bool = false;
for variable : state.type_variables {
if variable.type == .Struct && variable.kind == .Declaration && !variable.builtin {
if variable.source_node.kind == .Properties continue;
if variable.source_node.kind == .Meta continue;
print_to_builder(*state.builder, "struct %;\n", variable.source_node.name);
found_struct = true;
}
}
// for variable : state.ctx.type_variables {
// if variable.type == .Struct && variable.kind == .Declaration && !variable.builtin {
// if variable.source_node.kind == .Properties continue;
// if variable.source_node.kind == .Meta continue;
// print_to_builder(*state.builder, "struct %;\n", variable.source_node.name);
// found_struct = true;
// }
// }
if found_struct {
append(*state.builder, "\n");
}
// if found_struct {
// append(*state.builder, "\n");
// }
for variable : state.type_variables {
for variable : state.ctx.type_variables {
if variable.type == .Function && !variable.builtin
&& !variable.source_node.vertex_entry_point && !variable.source_node.pixel_entry_point {
emit_function(state, variable.source_node, 0, false);
@@ -460,22 +650,14 @@ codegen :: (state : *Codegen_State) -> Codegen_Result {
append(*state.builder, "\n");
}
for declaration : state.root.children {
for declaration : state.ctx.root.children {
if declaration.foreign_declaration {
continue;
}
emit_declaration(state, declaration);
}
state.result.result_text = builder_to_string(*state.builder);
return state.result;
}
codegen :: (ast_root : *AST_Node, checker_result : Semantic_Check_Result, output_language : Output_Language) -> Codegen_Result {
codegen_state : Codegen_State;
init_codegen_state(*codegen_state, ast_root, checker_result, output_language);
return codegen(*codegen_state);
state.ctx.codegen_result_text = builder_to_string(*state.builder);
}
#scope_module

View File

@@ -102,20 +102,20 @@ copy_messages :: (source : []Compiler_Message, dest : *[..]Compiler_Message) {
}
}
report_messages :: (messages : []Compiler_Message) -> string {
report_messages :: (ctx : *Compiler_Context, messages : []Compiler_Message) -> string {
builder : String_Builder;
init_string_builder(*builder);
for message : messages {
report_message(*builder, message);
report_message(ctx, *builder, message);
}
return builder_to_string(*builder);
}
report_message :: (builder : *String_Builder, message : Compiler_Message) {
report_message(builder, message.path, message.message, message.source_locations, message.message_kind, message.report_source_location);
report_message :: (ctx : *Compiler_Context, builder : *String_Builder, message : Compiler_Message) {
report_message(ctx, builder, message.path, message.message, message.source_locations, message.message_kind, message.report_source_location);
}
report_message :: (builder : *String_Builder, path : string, message : string, source_locations : []Source_Range, kind : Message_Kind, report_source_location : bool = false) {
report_message :: (ctx : *Compiler_Context, builder : *String_Builder, path : string, message : string, source_locations : []Source_Range, kind : Message_Kind, report_source_location : bool = false) {
append(builder, "\x1b[1;37m");
if path.count > 0 {
print_to_builder(builder, "%:", path);
@@ -140,7 +140,7 @@ report_message :: (builder : *String_Builder, path : string, message : string, s
if report_source_location {
for location : source_locations {
append(builder, "\t");
print_from_source_location(builder, location);
print_from_source_location(ctx, builder, location);
append(builder, "\n\t");
begin := location.begin;

768
Ink.jai Normal file
View File

@@ -0,0 +1,768 @@
/////////////////////////////////////
/*~ nbr: General improvements
- [x] Print out all failed tests in a list at the end
- [x] Use new compiler API with Compile_Result and Compiled_File instead
- [ ] Use unix (posix? bash? ascii?) color codes for errors
- [ ] Print golden file as green and new output as red
- [ ] Rename to Ink.jai
- [ ] Add -test option. -test does the same as test.exe used to do
- [ ] Add -fuzz option to run fuzzer (add args later)
- [ ] Add -output option to output the compiled file. Issue with this is the generated data can't be output like that. Would require serialization.
*/
#import "Basic";
#import "File";
#import "String";
#import "File_Utilities";
#import "Print_Color";
#load "module.jai";
GOLDEN_EXTENSION :: "golden";
LEXER_FOLDER :: "lex";
PARSER_FOLDER :: "parse";
CODEGEN_FOLDER :: "codegen";
COMPILED_FOLDER :: "compiled";
CHECK_FOLDER :: "check";
TESTS_FOLDER :: "test";
SHADER_EXTENSION :: "ink";
SUITE_EXTENSION :: "suite";
Stage_Flags :: enum_flags u16 {
Lexer :: 0x1;
Parser :: 0x2;
Check :: 0x4;
Codegen :: 0x8;
Compile :: 0x10;
}
Output_Type :: enum_flags u16 {
Golden :: 0x1;
StdOut :: 0x2;
}
Result_Type :: enum {
File_Read_Failed;
Golden_File_Not_Found;
StdOut;
Golden_Output;
Passed;
Failed;
}
Result :: struct {
type : Result_Type;
path : string;
stage : Stage_Flags;
golden_path : string;
info_text : string;
}
Test_Case :: struct {
path : string;
stage_flags : Stage_Flags;
}
Test_Suite :: struct {
name : string;
test_cases : [..]Test_Case;
results : [..]Result;
}
get_golden_path :: (file_path : string, stage : Stage_Flags) -> string {
sc := get_scratch();
defer scratch_end(sc);
path := parse_path(file_path,, sc.allocator);
file_without_extension := split(path.words[path.words.count - 1], ".",, sc.allocator);
builder : String_Builder;
builder.allocator = temp;
final_path_length := file_path.count - SHADER_EXTENSION.count + GOLDEN_EXTENSION.count + 1; // +1 for dot
path.words.count -= 1;
path.words.allocator = sc.allocator;
if stage == {
case .Lexer; {
dir := tprint("%/%", TESTS_FOLDER, LEXER_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, LEXER_FOLDER);
}
case .Parser; {
dir := tprint("%/%", TESTS_FOLDER, PARSER_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, PARSER_FOLDER);
}
case .Check; {
dir := tprint("%/%", TESTS_FOLDER, CHECK_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, CHECK_FOLDER);
}
case .Codegen; {
dir := tprint("%/%", TESTS_FOLDER, CODEGEN_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, CODEGEN_FOLDER);
}
case .Compile; {
dir := tprint("%/%", TESTS_FOLDER, COMPILED_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, COMPILED_FOLDER);
}
}
init_string_builder(*builder, file_without_extension.count + GOLDEN_EXTENSION.count + 1);
builder.allocator = sc.allocator;
append(*builder, file_without_extension[0]);
append(*builder, ".");
append(*builder, GOLDEN_EXTENSION);
golden_path := builder_to_string(*builder,, sc.allocator);
array_add(*path.words, golden_path);
final_path := path_to_string(path);
return final_path;
}
do_golden_comparison :: (golden_path : string, comparison_text : string, result : *Result, output_type : Output_Type) {
sc := get_scratch();
defer scratch_end(sc);
if output_type & .Golden {
// Output the comparison file
write_entire_file(golden_path, comparison_text);
result.golden_path = copy_string(golden_path);
result.type = .Golden_Output;
return;
} else {
// Do the comparison
if !file_exists(golden_path) {
result.info_text = tprint("Golden file % does not exist. Please run with -output-as-golden at least once.\n", golden_path);
result.type = .Golden_File_Not_Found;
return;
}
golden_text, ok := read_entire_file(golden_path,, sc.allocator);
if !ok {
result.info_text = tprint("Unable to open golden file %\n", golden_path);
result.type = .Golden_File_Not_Found;
return;
}
comp := replace(comparison_text, "\r\n", "\n",, sc.allocator);
gold := replace(golden_text, "\r\n", "\n",, sc.allocator);
ok = compare(comp, gold) == 0;
if !ok {
result.type = .Failed;
result.info_text = tprint("Golden file:\n%\n===============\n%", gold, comp);
} else {
result.type = .Passed;
}
}
}
run_codegen_test :: (file_path : string, ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = file_path;
lex(ctx, context.allocator);
parse(ctx, context.allocator);
check(ctx, context.allocator);
if ctx.had_error {
result.type = .Failed;
return result;
}
result = run_codegen_test(ctx, output_type);
return result;
}
run_codegen_test :: (ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = ctx.file.path;
result_text : string;
codegen(ctx, context.allocator);
if ctx.had_error {
result.type = .Failed;
result_text = report_messages(ctx, ctx.messages);
return result;
}
result_text = ctx.codegen_result_text;
if output_type & .StdOut {
result.info_text = result_text;
result.type = .StdOut;
return result;
}
golden_path := get_golden_path(ctx.file.path, .Codegen);
do_golden_comparison(golden_path, result_text, *result, output_type);
return result;
}
run_compile_test :: (path : string, output_type : Output_Type = 0) -> Result, Compiler_Context {
ctx : Compiler_Context;
result : Result;
result.path = path;
compile_file(*ctx, path, context.allocator);
if ctx.had_error {
result.type = .Failed;
result.info_text = tprint("Failed compiling: %\n", path);
} else {
sc := get_scratch();
defer scratch_end(sc);
sb : String_Builder;
init_string_builder(*sb,, sc.allocator);
if ctx.vertex_entry_point.name.count > 0 {
print_to_builder(*sb, "[vertex entry point] - %\n", ctx.vertex_entry_point.name);
}
if ctx.pixel_entry_point.name.count > 0 {
print_to_builder(*sb, "[pixel entry point] - %\n", ctx.pixel_entry_point.name);
}
for buf : ctx.buffers {
if buf.kind == {
case .Constant; {
print_to_builder(*sb, "[constant_buffer] - % - %", buf.name, buf.buffer_index);
}
case .Structured; {
print_to_builder(*sb, "[buffer] - % - %", buf.name, buf.buffer_index);
}
if buf.hints.count > 0 {
for hint : buf.hints {
print_to_builder(*sb, " (@%)", hint.custom_hint_name);
}
}
append(*sb, "\n");
indent(*sb, 1);
for field : buf.fields {
append(*sb, "[field] - ");
pretty_print_field(*sb, *field);
append(*sb, "\n");
indent(*sb, 1);
}
}
}
result.info_text = builder_to_string(*sb);
}
if output_type & .StdOut {
result.type = .StdOut;
return result, ctx;
}
golden_path := get_golden_path(ctx.file.path, .Compile);
do_golden_comparison(golden_path, result.info_text, *result, output_type);
return result, ctx;
}
run_lexer_test :: (file_path : string, ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = file_path;
result.stage = .Lexer;
result_text : string;
lex(ctx);
if ctx.had_error {
result.type = .Failed;
result_text = report_messages(ctx, ctx.messages);
} else {
result_text = pretty_print_tokens(ctx.tokens, context.allocator);
}
if output_type & .StdOut {
result.info_text = result_text;
result.type = .StdOut;
return result;
}
golden_path := get_golden_path(file_path, .Lexer);
do_golden_comparison(golden_path, result_text, *result, output_type);
return result;
}
run_parser_test :: (file_path : string, ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = file_path;
lex(ctx);
if ctx.had_error {
result.type = .Passed;
return result;
}
result = run_parser_test(ctx, output_type);
return result;
}
run_parser_test :: (ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
parse(ctx, context.allocator);
result : Result;
result.path = ctx.file.path;
result_text : string;
if ctx.had_error {
result.type = .Failed;
result_text = report_messages(ctx, ctx.messages);
} else {
result_text = pretty_print_ast(ctx.root, context.allocator);
}
if output_type & .StdOut {
result.info_text = result_text;
result.type = .StdOut;
return result;
}
golden_path := get_golden_path(ctx.file.path, .Parser);
do_golden_comparison(golden_path, result_text, *result, output_type);
return result;
}
run_check_test :: (ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = ctx.file.path;
result_text : string;
check(ctx, context.allocator);
if ctx.had_error {
result.type = .Failed;
result_text = report_messages(ctx, ctx.messages);
} else {
result_text = pretty_print_symbol_table(ctx, context.allocator);
}
if output_type & .StdOut {
result.info_text = result_text;
result.type = .StdOut;
return result;
}
golden_path := get_golden_path(ctx.file.path, .Check);
do_golden_comparison(golden_path, result_text, *result, output_type);
return result;
}
run_check_test :: (file_path : string, ctx : *Compiler_Context, output_type : Output_Type = 0) -> Result {
result : Result;
result.path = file_path;
lex(ctx, context.allocator);
parse(ctx, context.allocator);
if ctx.had_error {
result.type = .Failed;
return result;
}
result = run_check_test(ctx, output_type);
return result;
}
make_test_case :: (path : string, stage_flags : Stage_Flags, allocator := context.allocator) -> Test_Case {
test_case : Test_Case;
test_case.path = copy_string(path,, allocator);
replace_chars(test_case.path, "\\", #char "/");
test_case.stage_flags = stage_flags;
return test_case;
}
run_test_new :: (file_path : string, stage_flags : Stage_Flags, results : *[..]Result, output_type : Output_Type = 0, allocator := temp) {
new_context := context;
new_context.allocator = allocator;
push_context new_context {
ctx : Compiler_Context;
ctx.file = make_file(*ctx, file_path);
result : Result;
if stage_flags & .Lexer {
result = run_lexer_test(file_path, *ctx, output_type);
record_result(results, result);
}
if stage_flags & .Parser {
if stage_flags & .Lexer && result.type == .Passed || result.type == .Golden_Output {
result = run_parser_test(*ctx, output_type);
} else {
result = run_parser_test(file_path, *ctx, output_type);
}
record_result(results, result);
}
if stage_flags & .Check {
if stage_flags & .Parser && (result.type == .Passed || result.type == .Golden_Output) {
result = run_check_test(*ctx, output_type);
} else {
result = run_check_test(file_path, *ctx, output_type);
}
record_result(results, result);
}
if stage_flags & .Codegen {
if stage_flags & .Check && (result.type == .Passed || result.type == .Golden_Output) {
result = run_codegen_test(*ctx, output_type);
} else {
result = run_codegen_test(file_path, *ctx, output_type);
}
record_result(results, result);
}
if stage_flags & .Compile {
result = run_compile_test(file_path, output_type);
record_result(results, result);
}
}
}
run_test :: (test_case : Test_Case, results : *[..]Result, output_type : Output_Type = 0, allocator := temp) {
print("%Running test: %......", cyan(), test_case.path);
// path 30
// len 35
// == 5
// path 20
// len = 35
// == 15
len := 50;
rest := len - test_case.path.count;
for i: 0..rest {
print(" ");
}
run_test_new(test_case.path, test_case.stage_flags, results, output_type, allocator);
}
record_result :: (results : *[..]Result, result : Result) {
array_add(results, result);
}
run_test_suite :: (using suite : *Test_Suite, output_type : Output_Type = 0) {
if suite.name.count > 0 {
print("%Running suite: %\n", green(), suite.name);
print("%", reset_color());
}
Fail_Data :: struct {
path : string;
stage : string;
}
test_arena : Allocator = make_arena(Gigabytes(1));
failed_test_paths : [..]Fail_Data;
failed_test_paths.allocator = test_arena;
builder : String_Builder;
init_string_builder(*builder,, test_arena);
for test_case : test_cases {
run_test(test_case, *suite.results, output_type, allocator = test_arena);
for < suite.results {
result := suite.results[it_index];
if compare(result.path, test_case.path) == 0 {
if result.type == {
case .Failed; {
array_add(*failed_test_paths, .{ result.path, stage_to_string(result.stage) });
}
case .File_Read_Failed; {
array_add(*failed_test_paths, .{ result.path, "file not found" });
}
case .Golden_File_Not_Found; {
array_add(*failed_test_paths, .{ result.path, tprint("golden file not found for %", stage_to_string(result.stage)) });
}
}
evaluate_result(result);
} else {
break;
}
}
// print("\n");
}
append(*builder, "\n");
if output_type == 0 {
if failed_test_paths.count == 0 {
green(*builder);
print_to_builder(*builder, "All % tests passed!\n", test_cases.count);
reset_color(*builder);
} else {
print_to_builder(*builder, "%/% tests passed\n", test_cases.count - failed_test_paths.count, test_cases.count);
red(*builder);
print_to_builder(*builder, "% failed\n", failed_test_paths.count);
for failed_test : failed_test_paths {
print_to_builder(*builder, "% failed with error: %\n", failed_test.path, failed_test.stage);
}
reset_color(*builder);
}
}
print("%\n", builder_to_string(*builder,, test_arena));
}
read_suite :: (file_path : string, suite : *Test_Suite, allocator := temp) -> bool {
sc := get_scratch();
defer scratch_end(sc);
bytes, ok := read_entire_file(file_path,, sc.allocator);
if !ok {
log_error("Unable to read suite file %\n", file_path);
return false;
}
path := parse_path(file_path,, sc.allocator);
file_without_extension := split(path.words[path.words.count - 1], ".",, sc.allocator);
suite.name = copy_string(file_without_extension[0],, allocator);
split_lines := split(bytes, "\n",, sc.allocator);
for split_line : split_lines {
if split_line.count == 0 {
break;
}
if split_line[0] == #char "#" {
continue;
}
line := split(split_line, " ",, sc.allocator);
if line[0].count == 0 {
continue;
}
if line[0].data[0] == #char "#" {
continue;
}
if line.count == 1 {
line = split(split_line, "\t",, sc.allocator);
if line.count == 1 {
log_error("Invalid line - % - \n", it_index + 1);
continue;
}
}
test_case_path := line[0];
stage_flags : Stage_Flags;
for i: 0..line.count - 1 {
trimmed := trim(line[i]);
if equal(trimmed, "lex") {
stage_flags |= .Lexer;
} else if equal(trimmed, "parse") {
stage_flags |= .Parser;
} else if equal(trimmed, "check") {
stage_flags |= .Check;
} else if equal(trimmed, "codegen") {
stage_flags |= .Codegen;
} else if equal(trimmed, "compile") {
stage_flags |= .Compile;
}
}
test_case := make_test_case(test_case_path, stage_flags, allocator);
array_add(*suite.test_cases, test_case);
}
return true;
}
read_test :: () {
}
stage_to_string :: (stage : Stage_Flags) -> string {
if #complete stage == {
case .Lexer; return "lexing";
case .Parser; return "parsing";
case .Check; return "checking";
case .Codegen; return "codegen";
case .Compile; return "compiled";
case; return "";
}
}
evaluate_result :: (result : Result) {
stage : string = stage_to_string(result.stage);
if #complete result.type == {
case .File_Read_Failed; {
print(" %", red());
print("failed with File_Read_Failed\n");
}
case .Golden_File_Not_Found; {
print(" %", red());
print("failed with Golden File Not Found for stage %\n", stage);
}
case .StdOut; {
}
case .Golden_Output; {
print(" %", yellow());
print("output new golden file at %\n", result.golden_path);
}
case .Passed; {
print(" %", green());
print("passed %\n", stage);
}
case .Failed; {
print(" %", red());
print("failed %\n", stage);
}
}
if result.info_text.count > 0 {
print("%", cyan());
print("--- Info text ---\n");
print("%", yellow());
print("%\n", result.info_text);
}
print("%", reset_color());
}
main :: () {
args := get_command_line_arguments();
init_context_allocators();
local_temp := make_arena(Megabytes(128));
suites : [..]Test_Suite;
suites.allocator = local_temp;
output_type : Output_Type = 0;
Argument_Parse_State :: enum {
None;
Compile;
Run_Suite;
Run_Test;
}
arg_parse_state : Argument_Parse_State;
current_suite : *Test_Suite;
path : string;
for i: 1..args.count - 1 {
arg := args[i];
if arg == "-output-as-golden" {
output_type |= .Golden;
continue;
} else if arg == "-output" {
output_type |= .StdOut;
continue;
}
if arg_parse_state == {
case .Run_Suite; {
if arg == "-output-as-golden" {
output_type |= .Golden;
} else if arg == "-output" {
output_type |= .StdOut;
} else {
print("%Unknown argument % %\n", red(), arg, reset_color());
}
}
case .Run_Test; {
cases := current_suite.test_cases.count;
if arg == "-lex" {
current_suite.test_cases[cases - 1].stage_flags |= .Lexer;
} else if arg == "-parse" {
current_suite.test_cases[cases - 1].stage_flags |= .Parser;
} else if arg == "-check" {
current_suite.test_cases[cases - 1].stage_flags |= .Check;
} else if arg == "-codegen" {
current_suite.test_cases[cases - 1].stage_flags |= .Codegen;
} else if arg == "-compile" {
current_suite.test_cases[cases - 1].stage_flags |= .Compile;
} else if contains(arg, ".") {
sc := get_scratch();
defer scratch_end(sc);
path_split := split(arg, "\\",, sc.allocator);
split_path := split(path_split[path_split.count - 1], ".",, sc.allocator);
extension := split_path[1];
if extension == SHADER_EXTENSION {
path := copy_string(arg,, local_temp);
test_case := make_test_case(path, 0, local_temp);
array_add(*current_suite.test_cases, test_case);
} else {
print("%Invalid file as argument % %\n", red(), arg, reset_color());
}
} else {
print("%Unknown argument % %\n", red(), arg, reset_color());
}
}
case .None; {
if contains(arg, ".") {
sc := get_scratch();
defer scratch_end(sc);
path_split := split(arg, "\\",, sc.allocator);
split_path := split(path_split[path_split.count - 1], ".",, sc.allocator);
extension := split_path[1];
if extension == SHADER_EXTENSION {
if arg_parse_state == .Run_Suite {
log_error("Unable to run a test while already running suite.");
continue;
}
if !current_suite {
suite : Test_Suite;
suite.results.allocator = local_temp;
suite.test_cases.allocator = local_temp;
array_add(*suites, suite);
current_suite = *suites[0];
}
arg_parse_state = .Run_Test;
path := copy_string(arg,, local_temp);
test_case := make_test_case(path, 0, local_temp);
array_add(*current_suite.test_cases, test_case);
} else if extension == SUITE_EXTENSION {
if arg_parse_state == .Run_Test {
log_error("Unable to run a suite while already running test.");
continue;
}
arg_parse_state = .Run_Suite;
path := copy_string(arg);
suite : Test_Suite;
suite.results.allocator = local_temp;
suite.test_cases.allocator = local_temp;
read_suite(path, *suite, local_temp);
array_add(*suites, suite);
current_suite = *suites[0];
} else {
print("%Invalid file as argument % %\n", red(), arg, reset_color());
}
}
}
}
}
for suite : suites {
run_test_suite(*suite, output_type);
}
clear(local_temp);
}

View File

@@ -5,18 +5,13 @@ Lexer :: struct {
current_line : int;
current_column : int;
result : Lexing_Result;
ctx : *Compiler_Context;
path : string;
}
Lexing_Result :: struct {
tokens : [..]Token;
had_error : bool;
messages : [..]Compiler_Message;
}
Token_Kind :: enum {
TOKEN_INVALID :: 0;
TOKEN_FLOATLITERAL;
TOKEN_INTLITERAL;
@@ -54,11 +49,13 @@ Token_Kind :: enum {
TOKEN_SEMICOLON;
TOKEN_COMMA;
TOKEN_DOT;
TOKEN_DOTDOT;
TOKEN_IDENTIFIER;
// Keywords
TOKEN_BOOL;
TOKEN_BUFFER;
TOKEN_CASE;
TOKEN_CBUFFER;
@@ -95,11 +92,12 @@ Token_Kind :: enum {
TOKEN_OUT;
TOKEN_PIXEL;
TOKEN_PROPERTIES;
TOKEN_PLEX;
TOKEN_RETURN;
TOKEN_REGISTER;
TOKEN_STRING;
TOKEN_STRUCT;
TOKEN_SWITCH;
@@ -133,10 +131,12 @@ Token :: struct {
// This could all be derived on demand
line : int;
length : int;
column : int;
column : int;
index : int;
error : string;
builtin : bool; // @Incomplete: This is kind of a bad idea, but let's just do it for now...
}
Source_Range :: struct {
@@ -217,10 +217,11 @@ identifier_kind :: (using lexer : *Lexer) -> Token_Kind {
identifier.count = length;
if identifier == "bool" return .TOKEN_BOOL;
if identifier == "Buffer" return .TOKEN_BUFFER;
if identifier == "case" return .TOKEN_CASE;
if identifier == "columnmajor" return .TOKEN_COLUMNMAJOR;
if identifier == "const" return .TOKEN_CONST;
if identifier == "constant_buffer" return .TOKEN_CONSTANT_BUFFER;
if identifier == "Constant_Buffer" return .TOKEN_CONSTANT_BUFFER;
if identifier == "continue" return .TOKEN_CONTINUE;
if identifier == "default" return .TOKEN_DEFAULT;
if identifier == "directive" return .TOKEN_DIRECTIVE;
@@ -244,10 +245,10 @@ identifier_kind :: (using lexer : *Lexer) -> Token_Kind {
if identifier == "optional" return .TOKEN_OPTIONAL;
if identifier == "out" return .TOKEN_OUT;
if identifier == "pixel" return .TOKEN_PIXEL;
if identifier == "properties" return .TOKEN_PROPERTIES;
if identifier == "return" return .TOKEN_RETURN;
if identifier == "register" return .TOKEN_REGISTER;
if identifier == "struct" return .TOKEN_STRUCT;
if identifier == "plex" return .TOKEN_STRUCT;
if identifier == "switch" return .TOKEN_SWITCH;
if identifier == "true" return .TOKEN_TRUE;
if identifier == "unorm" return .TOKEN_UNORM;
@@ -264,12 +265,32 @@ identifier_kind :: (using lexer : *Lexer) -> Token_Kind {
error_token :: (lexer : *Lexer, message : string) -> *Token {
token : *Token = new_token(lexer, .TOKEN_ERROR);
lexer.result.had_error = true;
lexer.ctx.had_error = true;
token.error = copy_string(message);
return token;
}
// unable_to_open_file :: (state : *Parse_State, path : string, token : Token) {
// builder : String_Builder;
// init_string_builder(*builder,, temp);
// print_to_builder(*builder, "Unable to open file '%' for reading\n\n", path);
// location := generate_source_location_from_token(state, token);
// indent(*builder, 1);
// cyan(*builder);
// print_to_builder(*builder, "%\n", print_from_source_location(location));
// indent(*builder, 1);
// loc := location.begin;
// print_token_pointer(*builder, loc);
// final_message := builder_to_string(*builder);
// record_error(state, token, final_message, false);
// }
record_error :: (lexer : *Lexer, message : string) {
error : Compiler_Message;
error.message_kind = .Error;
@@ -291,8 +312,8 @@ record_error :: (lexer : *Lexer, message : string) {
array_add(*error.source_locations, source_location);
lexer.result.had_error = true;
array_add(*lexer.result.messages, error);
lexer.ctx.had_error = true;
array_add(*lexer.ctx.messages, error);
}
make_int :: (lexer : *Lexer) -> *Token {
@@ -322,10 +343,6 @@ make_float :: (lexer : *Lexer) -> *Token {
return token;
}
make_string :: () {
}
new_token :: (lexer : *Lexer, kind : Token_Kind) -> *Token {
length := lexer.cursor - lexer.start;
token : Token;
@@ -342,13 +359,54 @@ new_token :: (lexer : *Lexer, kind : Token_Kind) -> *Token {
}
lexer.current_column += length;
array_add(*lexer.result.tokens, token);
return *lexer.result.tokens[lexer.result.tokens.count - 1];
array_add(*lexer.ctx.tokens, token);
return *lexer.ctx.tokens[lexer.ctx.tokens.count - 1];
}
make_directive :: (lexer : *Lexer) -> *Token {
lexer.start += 1;
return make_identifier(lexer, .TOKEN_DIRECTIVE);
ident := make_identifier(lexer, .TOKEN_DIRECTIVE);
if ident.ident_value == "load" {
path_tok := scan_next_token(lexer);
path := path_tok.string_value;
ctx : Compiler_Context;
ctx.environment = lexer.ctx.environment;
ctx.file = make_file(*ctx, path);
if ctx.file.source.count == 0 {
// unable_to_open_file(lexer, path, path_tok);
record_error(lexer, tprint("Unable to open file '%' for reading\n", path));
return error_token(lexer, tprint("Unable to open file '%' for reading\n", path));
}
lex(*ctx);
ctx.tokens.count -= 1; // @Note: remote TOKEN_EOF
lexer.ctx.tokens.count -= 2;
array_resize(*lexer.ctx.tokens, lexer.ctx.tokens.count + ctx.tokens.count);
for tok : ctx.tokens {
lexer.ctx.tokens[it_index] = tok;
}
return scan_next_token(lexer);
} else if ident.ident_value == "add_define" {
new_define := scan_next_token(lexer);
add_define(*lexer.ctx.environment, new_define.ident_value);
lexer.ctx.tokens.count -= 2;
return scan_next_token(lexer);
}
return ident;
}
make_string :: (lexer : *Lexer) -> *Token {
token : *Token = new_token(lexer, .TOKEN_STRING);
name : string = .{ count = token.length - 2,
data = *lexer.input.data[lexer.start + 1] };
token.string_value = name;
return token;
}
make_identifier :: (lexer : *Lexer, kind : Token_Kind) -> *Token {
@@ -367,6 +425,7 @@ make_token :: (lexer : *Lexer, token_kind : Token_Kind) -> *Token {
skip_whitespace :: (lexer : *Lexer) {
while true {
if is_at_end(lexer) return;
c := peek_char(lexer);
if c == {
@@ -421,6 +480,17 @@ scan_next_token :: (lexer : *Lexer) -> *Token {
if is_digit(c) return number(lexer);
if c == {
case #char "\""; {
c = advance(lexer);
// lexer.start = lexer.cursor;
while c != #char "\"" {
c = advance(lexer);
}
// lexer.cursor -= 1;
tok := make_string(lexer);
// advance(lexer);
return tok;
}
case #char "+"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_PLUSEQUALS);
return make_token(lexer, .TOKEN_PLUS);
@@ -490,7 +560,10 @@ scan_next_token :: (lexer : *Lexer) -> *Token {
}
case #char ";"; return make_token(lexer, .TOKEN_SEMICOLON);
case #char ","; return make_token(lexer, .TOKEN_COMMA);
case #char "."; return make_token(lexer, .TOKEN_DOT);
case #char "."; {
if match_character(lexer, #char ".") return make_token(lexer, .TOKEN_DOTDOT);
return make_token(lexer, .TOKEN_DOT);
}
}
s : string = .{ count = 1, data = *c };
@@ -499,41 +572,35 @@ scan_next_token :: (lexer : *Lexer) -> *Token {
// return error_token(lexer, tprint("Invalid token: %", s));
}
lex :: (result : *Compile_Result) {
if result.had_error {
lex :: (ctx : *Compiler_Context, allocator := temp) {
if ctx.had_error {
return;
}
for file : result.files {
new_context := context;
new_context.allocator = allocator;
push_context new_context {
init_context_allocators();
defer clear_context_allocators();
lexer : Lexer;
init_lexer_from_string(*lexer, file.file.source);
lexer.ctx = ctx;
array_reserve(*lexer.ctx.tokens, 1024);
init_lexer_from_string(*lexer, ctx.file.source);
lexer.path = ctx.file.path;
token : *Token = scan_next_token(*lexer);
while token && token.kind != .TOKEN_EOF {
token = scan_next_token(*lexer);
}
// @Incomplete(nb): Temporary until we figure out a good way of passing this stuff around
copy_messages(lexer.result.messages, *result.messages);
}
}
lex :: (lexer : *Lexer, allocator : Allocator = context.allocator) -> Lexing_Result {
lexer.result.tokens.allocator = allocator;
token : *Token = scan_next_token(lexer);
while token && token.kind != .TOKEN_EOF {
token = scan_next_token(lexer);
}
return lexer.result;
}
init_lexer_from_string :: (lexer : *Lexer, input : string) {
ok := read_input_from_string(lexer, input);
if !ok {
record_error(lexer, "Unable to initialize from string\n");
lexer.result.had_error = true;
lexer.ctx.had_error = true;
}
}
@@ -541,7 +608,7 @@ init_lexer_from_file :: (lexer : *Lexer, file_path : string) {
ok := read_input_from_file(lexer, file_path);
if !ok {
record_error(lexer, tprint("Unable to read file: %\n", file_path));
lexer.result.had_error = true;
lexer.ctx.had_error = true;
}
}
@@ -680,40 +747,53 @@ print_token_pointer :: (builder : *String_Builder, token : Token) {
}
}
print_from_source_location :: (builder : *String_Builder, source_location : Source_Range, indentation : int = 0) {
print_from_source_location :: (ctx : *Compiler_Context, builder : *String_Builder, source_location : Source_Range, indentation : int = 0) {
current := source_location.begin;
begin := source_location.begin;
end := source_location.end;
begin_pos := 0;
token_string : string;
count := end.index - begin.index + end.length;
if indentation > 0 {
indent(builder, indentation);
for 0..count - 1 {
c := begin.source[it];
if c == #char "\n" {
append(builder, "\n");
indent(builder, indentation);
} else {
s : string;
s.count = 1;
s.data = *c;
print_to_builder(builder, "%", s);
}
if begin.builtin {
for i : begin.index..end.index - 1 {
tok := ctx.tokens[i];
text : string;
text.data = tok.source;
text.count = tok.length;
print_to_builder(builder, "%", text);
}
} else {
token_string = .{ count = count, data = begin.source };
indent(builder, indentation);
print_to_builder(builder, "%", token_string);
begin_pos := 0;
token_string : string;
count := end.index - begin.index + end.length;
if indentation > 0 {
indent(builder, indentation);
for 0..count - 1 {
c := begin.source[it];
if c == #char "\n" {
append(builder, "\n");
indent(builder, indentation);
} else {
s : string;
s.count = 1;
s.data = *c;
print_to_builder(builder, "%", s);
}
}
} else {
token_string = .{ count = count, data = begin.source };
indent(builder, indentation);
print_to_builder(builder, "%", token_string);
}
}
}
print_from_source_location :: (source_location : Source_Range, allocator := context.allocator, indentation : int = 0) -> string {
print_from_source_location :: (ctx : *Compiler_Context, source_location : Source_Range, allocator := context.allocator, indentation : int = 0) -> string {
sc := get_scratch();
defer scratch_end(sc);
builder : String_Builder;
init_string_builder(*builder,, allocator);
print_from_source_location(*builder, source_location);
init_string_builder(*builder,, sc.allocator);
print_from_source_location(ctx, *builder, source_location,, sc.allocator);
return builder_to_string(*builder,, allocator);
}

File diff suppressed because it is too large Load Diff

View File

@@ -20,15 +20,15 @@ There is basic support for most HLSL built-in math functions for the following t
- Vector types: float2, float3, float4, int2, int3, int4
- Matrices: float4x4
All of the above can be constructed with their namesake constructors i.e. `float4(x, y, z, w);`.
We don't yet support textures and samplers.
We also support Samplers and Texture2D
If you want to declare and use variables you can do it as follows
```hlsl
x : float = 2.0; // no 'f' suffix required or even supported (it gives an error)
y : float = 4.0;
v : float2 = float2(x, y);
v2 := float2(x, y);
```
For now it is required to specify the type of the variable (no type inference).
You can also do arithmetic as you would expect
```
@@ -43,6 +43,7 @@ Camera_Data :: struct {
}
```
And there is a special struct called `properties`, which is used for custom data you want to pass in.
#### ** Note: Properties will likely be deprecated, since the language now supports `@` hints to easily mark buffers and values with metadata.**
```hlsl
properties {
projection : float4x4;
@@ -53,13 +54,14 @@ which will be exposed in the compiled result. `properties` can be renamed to a c
```
p :: properties {
...
}
```
You can also define constant buffers
```
camera :: Constant_Buffer {
camera :: constant_buffer {
projection : float4x4;
view : float4x4;
}
@@ -70,69 +72,68 @@ camera :: Constant_Buffer {
To compile a shader and use the result, you can do the following in jai
```jai
parse_shader :: (path : string, allocator : Allocator) -> Compilation_Result {
// In the future, you can pass environment defines to the compiler.
compiler : Shader_Compiler;
// In the future, you can pass environment defines to the compiler.
ctx : Compiler_Context;
compile_file(*compiler, "shader.shd", allocator);
return compile_file(*compiler, path,, allocator);
}
result := parse_shader("shader.shd", allocator);
if result.had_error {
log_error("%\n", report_messages(result.messages),, temp);
if ctx.had_error {
log_error("%\n", report_messages(ctx.messages),, temp);
return;
}
collection := result.collection;
variant := collection.variants[0];
}
// The ctx now contains all the needed information like the source text, entry points, constant buffers etc.
```
When parsing a shader you get the following struct as a result
```
Compilation_Result :: struct {
messages : [..]Compiler_Message;
Compiler_Context :: struct {
file : Input_File;
had_error : bool;
environment : Environment;
collection : Shader_Variant_Collection;
}
```
tokens : [..]Token;;
root : *AST_Node;
nodes : [..]AST_Node;
A `Shader_Variant_Collection` looks as follows
```
Shader_Variant_Collection :: struct {
properties : Properties;
codegen_result_text : string;
max_constant_buffers :: 16;
cbuffers : Static_Array(Constant_Buffer, max_constant_buffers);
constant_buffers : Static_Array(Type_Variable_Handle, 16);
variants : [..]Shader_Variant;
}
scope_stack : Scope_Stack;
type_variables : [..]Type_Variable;
Shader_Variant :: struct {
text : string;
property_name : string;
vertex_entry_point : struct {
node : *AST_Node;
name : string;
input : [..]Field;
}
pixel_entry_point : struct {
node : *AST_Node;
name : string;
return_value : Field;
}
properties : Properties;
max_constant_buffers :: 16;
cbuffers : Static_Array(Constant_Buffer, max_constant_buffers);
had_error : bool;
messages : [..]Compiler_Message;
}
Constant_Buffer :: struct {
register : int;
name : string;
fields : Static_Array(Property_Field, 16);
// hints : Field_Hint; // optional hint...
hints : [..]Field_Hint;
buffer_index : u32;
}
@@ -192,11 +193,10 @@ Hint_Kind :: enum {
## Notable missing features
- Control flow: if/else, for, while, switch etc.
- While
- Arrays
- Textures and samplers
- Multiple render targets
- Custom buffers/structured buffers
- Interpolation specifiers
- Proper variant handling with environment defines
- Include/importing files such as shared utils etc.
- Importing files such as shared utils etc. with something other than textual `#load`

File diff suppressed because it is too large Load Diff

762
Test.jai
View File

@@ -1,762 +0,0 @@
/////////////////////////////////////
//~ nbr: General improvements
//
// [x] Print out all failed tests in a list at the end
// [ ] Use unix (posix? bash? ascii?) color codes for errors
// [ ] Print golden file as green and new output as red
#import "Basic";
#import "File";
#import "String";
#import "File_Utilities";
#import "Print_Color";
#load "module.jai";
GOLDEN_EXTENSION :: "golden";
LEXER_FOLDER :: "lex";
PARSER_FOLDER :: "parse";
CODEGEN_FOLDER :: "codegen";
COMPILED_FOLDER :: "compiled";
SEMANTIC_ANALYSIS_FOLDER :: "semant";
TESTS_FOLDER :: "test";
SHADER_EXTENSION :: "shd";
SUITE_EXTENSION :: "suite";
Stage_Flags :: enum_flags u16 {
Lexer :: 0x1;
Parser :: 0x2;
Semantic_Analysis :: 0x4;
Codegen :: 0x8;
Compile :: 0x10;
}
Output_Type :: enum_flags u16 {
Golden :: 0x1;
StdOut :: 0x2;
}
Result_Type :: enum {
File_Read_Failed;
Golden_File_Not_Found;
StdOut;
Golden_Output;
Passed;
Failed;
}
Result :: struct {
type : Result_Type;
path : string;
stage : Stage_Flags;
golden_path : string;
info_text : string;
}
Test_Case :: struct {
path : string;
stage_flags : Stage_Flags;
}
Test_Suite :: struct {
name : string;
test_cases : [..]Test_Case;
results : [..]Result;
}
get_golden_path :: (file_path : string, stage : Stage_Flags, allocator := context.allocator) -> string {
path := parse_path(file_path);
file_without_extension := split(path.words[path.words.count - 1], ".");
builder : String_Builder;
builder.allocator = temp;
final_path_length := file_path.count - SHADER_EXTENSION.count + GOLDEN_EXTENSION.count + 1; // +1 for dot
path.words.count -= 1;
if stage == {
case .Lexer; {
dir := tprint("%/%", TESTS_FOLDER, LEXER_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, LEXER_FOLDER);
}
case .Parser; {
dir := tprint("%/%", TESTS_FOLDER, PARSER_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, PARSER_FOLDER);
}
case .Semantic_Analysis; {
dir := tprint("%/%", TESTS_FOLDER, SEMANTIC_ANALYSIS_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, SEMANTIC_ANALYSIS_FOLDER);
}
case .Codegen; {
dir := tprint("%/%", TESTS_FOLDER, CODEGEN_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, CODEGEN_FOLDER);
}
case .Compile; {
dir := tprint("%/%", TESTS_FOLDER, COMPILED_FOLDER);
make_directory_if_it_does_not_exist(dir);
array_add(*path.words, COMPILED_FOLDER);
}
}
init_string_builder(*builder, file_without_extension.count + GOLDEN_EXTENSION.count + 1);
append(*builder, file_without_extension[0]);
append(*builder, ".");
append(*builder, GOLDEN_EXTENSION);
golden_path := builder_to_string(*builder);
array_add(*path.words, golden_path);
final_path := path_to_string(path);
return final_path;
}
run_lexer_test :: (file_path : string, lexer : *Lexer, output_type : Output_Type = 0) -> Result {
ok := read_input_from_file(lexer, file_path);
result_data : Result;
result_data.path = file_path;
result_data.stage = .Lexer;
if !ok {
result_data.type = .File_Read_Failed;
result_data.info_text = tprint("Unable to read file: %\n", file_path);
return result_data;
} else {
result_text : string;
result := lex(lexer, *temp);
if result.had_error {
result_data.type = .Failed;
result_text = report_messages(result.messages);
} else {
result_text = pretty_print_tokens(result.tokens, *temp);
}
if output_type & .StdOut {
result_data.info_text = result_text;
result_data.type = .StdOut;
return result_data;
}
golden_path := get_golden_path(file_path, .Lexer);
do_golden_comparison(golden_path, result_text, *result_data, output_type);
return result_data;
}
}
run_parser_test :: (file_path : string, output_type : Output_Type = 0) -> Result, *AST_Node {
lexer : Lexer;
result_data : Result;
result_data.path = file_path;
ok := read_input_from_file(*lexer, file_path);
if !ok {
log_error("Unable to read file: %\n", file_path);
result_data.type = .File_Read_Failed;
result_data.stage = .Lexer;
return result_data, null;
}
result := lex(*lexer, *temp);
if result.had_error {
result_data.type = .Passed;
return result_data, null;
}
result_data =, root := run_parser_test(*lexer, output_type);
return result_data, root;
}
do_golden_comparison :: (golden_path : string, comparison_text : string, result_data : *Result, output_type : Output_Type) {
if output_type & .Golden {
// Output the comparison file
write_entire_file(golden_path, comparison_text);
result_data.golden_path = copy_string(golden_path);
result_data.type = .Golden_Output;
return;
} else {
// Do the comparison
if !file_exists(golden_path) {
result_data.info_text = tprint("Golden file % does not exist. Please run with -output-as-golden at least once.\n", golden_path);
result_data.type = .Golden_File_Not_Found;
return;
}
golden_text, ok := read_entire_file(golden_path);
if !ok {
result_data.info_text = tprint("Unable to open golden file %\n", golden_path);
result_data.type = .Golden_File_Not_Found;
return;
}
comp := replace(comparison_text, "\r\n", "\n");
gold := replace(golden_text, "\r\n", "\n");
result := compare(comp, gold) == 0;
if !result {
result_data.type = .Failed;
result_data.info_text = tprint("Golden file:\n%\n===============\n%", gold, comp);
} else {
result_data.type = .Passed;
}
}
}
run_parser_test :: (lexer : *Lexer, output_type : Output_Type = 0) -> Result, *AST_Node {
parse_state : Parse_State;
result_data : Result;
result_data.path = lexer.path;
result_data.stage = .Parser;
init_parse_state(*parse_state, lexer.result.tokens, lexer.path, context.allocator);
result := parse(*parse_state);
result_node : *AST_Node;
result_text : string;
if result.had_error {
result_data.type = .Failed;
result_text = report_messages(result.messages,, temp);
} else {
result_text = pretty_print_ast(parse_state.result.root, *temp);
result_node = parse_state.result.root;
}
if output_type & .StdOut {
result_data.info_text = result_text;
result_data.type = .StdOut;
return result_data, result_node;
}
golden_path := get_golden_path(parse_state.path, .Parser);
do_golden_comparison(golden_path, result_text, *result_data, output_type);
return result_data, result_node;
}
run_semantic_analysis_test :: (file_path : string, output_type : Output_Type = 0) -> Result, Semantic_Check_Result {
lexer : Lexer;
result_data : Result;
result_data.path = file_path;
ok := read_input_from_file(*lexer, file_path);
if !ok {
log_error("Unable to read file: %\n", file_path);
result_data.type = .File_Read_Failed;
result_data.stage = .Lexer;
return result_data, .{};
}
lex_result := lex(*lexer, *temp);
if lex_result.had_error {
result_data.type = .Failed;
result_data.stage = .Lexer;
result_data.info_text = report_messages(lex_result.messages);
if output_type & .StdOut {
result_data.type = .StdOut;
return result_data, .{};
}
golden_path := get_golden_path(file_path, .Semantic_Analysis);
do_golden_comparison(golden_path, result_data.info_text, *result_data, output_type);
return result_data, .{};
}
parse_state : Parse_State;
result_data.stage = .Parser;
init_parse_state(*parse_state, lex_result.tokens, lexer.path, context.allocator);
parse_result := parse(*parse_state);
if parse_result.had_error {
result_data.type = .Failed;
result_data.info_text = report_messages(parse_result.messages);
if output_type & .StdOut {
result_data.type = .StdOut;
return result_data, .{};
}
golden_path := get_golden_path(file_path, .Semantic_Analysis);
do_golden_comparison(golden_path, result_data.info_text, *result_data, output_type);
return result_data, .{};
}
result, check_result := run_semantic_analysis_test(file_path, parse_state.result.root, output_type);
return result, check_result;
}
run_semantic_analysis_test :: (file_path : string, root : *AST_Node, output_type : Output_Type = 0) -> Result, Semantic_Check_Result {
result_data : Result;
result_data.path = file_path;
result_data.stage = .Semantic_Analysis;
checker : Semantic_Checker;
init_semantic_checker(*checker, root, file_path);
result_text : string;
result := check(*checker);
if result.had_error {
result_data.type = .Failed;
result_text = report_messages(checker.result.messages);
} else {
result_text = pretty_print_symbol_table(*checker, temp);
constraints := pretty_print_type_constraints(*checker, temp);
type_vars := pretty_print_type_variables(*checker, temp);
print("Constraints\n%\n", constraints);
print("Solution\n%\n", type_vars);
}
if output_type & .StdOut {
result_data.info_text = result_text;
result_data.type = .StdOut;
return result_data, .{};
}
golden_path := get_golden_path(checker.path, .Semantic_Analysis);
do_golden_comparison(golden_path, result_text, *result_data, output_type);
return result_data, result;
}
run_codegen_test :: (path : string, root : *AST_Node, check_result : Semantic_Check_Result, output_type : Output_Type = 0) -> Result, Codegen_Result {
result_data : Result;
result_data.path = path;
result_data.stage = .Codegen;
state : Codegen_State;
init_codegen_state(*state, root, check_result, .HLSL);
result_text : string;
result := codegen(*state);
if result.had_error {
result_data.type = .Failed;
result_data.info_text = report_messages(result.messages);
return result_data, .{};
}
result_text = result.result_text;
if output_type & .StdOut {
result_data.info_text = result_text;
result_data.type = .StdOut;
return result_data, result;
}
golden_path := get_golden_path(path, .Codegen);
do_golden_comparison(golden_path, result_text, *result_data, output_type);
return result_data, result;
}
run_codegen_test :: (path : string, root : *AST_Node, output_type : Output_Type = 0) -> Result, Codegen_Result {
checker : Semantic_Checker;
init_semantic_checker(*checker, root, path);
result_data : Result;
result_data.path = path;
result_data.stage = .Semantic_Analysis;
check_result := check(*checker);
if check_result.had_error {
result_data.type = .Failed;
result_data.info_text = report_messages(check_result.messages);
return result_data, .{};
}
result, codegen_result := run_codegen_test(path, root, check_result, output_type);
return result, codegen_result;
}
run_codegen_test :: (path : string, output_type : Output_Type = 0) -> Result, Codegen_Result {
lexer : Lexer;
result_data : Result;
result_data.path = path;
ok := read_input_from_file(*lexer, path);
if !ok {
log_error("Unable to read file: %\n", path);
result_data.type = .File_Read_Failed;
result_data.stage = .Lexer;
return result_data, .{};
}
lex_result := lex(*lexer, *temp);
if lex_result.had_error {
result_data.type = .Failed;
result_data.stage = .Lexer;
return result_data, .{};
}
parse_state : Parse_State;
result_data.stage = .Parser;
init_parse_state(*parse_state, lex_result.tokens, lexer.path, context.allocator);
parse_result := parse(*parse_state);
if parse_result.had_error {
result_data.type = .Failed;
result_data.info_text = pretty_print_ast(parse_result.root, *temp);
return result_data, .{};
}
result, codegen_result := run_codegen_test(path, parse_result.root, output_type);
return result, codegen_result;
}
run_compile_test :: (path : string, output_type : Output_Type = 0) -> Result, Compilation_Result {
compiler : Shader_Compiler;
result : Result;
compilation_result := compile_file(*compiler, path);
if compilation_result.had_error {
result.type = .Failed;
result.info_text = tprint("Failed compiling: %\n", path);
}
return result, compilation_result;
}
make_test_case :: (path : string, stage_flags : Stage_Flags, allocator := context.allocator) -> Test_Case {
test_case : Test_Case;
test_case.path = copy_string(path,, allocator);
replace_chars(test_case.path, "\\", #char "/");
test_case.stage_flags = stage_flags;
return test_case;
}
run_test :: (file_path : string, stage_flags : Stage_Flags, results : *[..]Result, output_type : Output_Type = 0) {
lexer : Lexer;
result : Result;
if stage_flags & .Lexer {
result = run_lexer_test(file_path, *lexer, output_type);
record_result(results, result);
}
root_node : *AST_Node;
if stage_flags & .Parser {
if stage_flags & .Lexer && result.type == .Passed || result.type == .Golden_Output {
result, root_node = run_parser_test(*lexer, output_type);
} else {
result, root_node = run_parser_test(file_path, output_type);
}
record_result(results, result);
}
check_result : Semantic_Check_Result;
if stage_flags & .Semantic_Analysis {
if stage_flags & .Parser && (result.type == .Passed || result.type == .Golden_Output) {
result, check_result = run_semantic_analysis_test(file_path, root_node, output_type);
} else {
result, check_result = run_semantic_analysis_test(file_path, output_type);
}
record_result(results, result);
}
if stage_flags & .Codegen {
if stage_flags & .Semantic_Analysis && (result.type == .Passed || result.type == .Golden_Output) {
result = run_codegen_test(file_path, root_node, check_result, output_type);
} else if root_node {
result = run_codegen_test(file_path, root_node, output_type);
} else {
result = run_codegen_test(file_path, output_type);
}
record_result(results, result);
}
if stage_flags & .Compile {
result = run_compile_test(file_path, output_type);
}
}
run_test :: (test_case : Test_Case, results : *[..]Result, output_type : Output_Type = 0) {
print("%Running test: %\n", cyan(), test_case.path);
run_test(test_case.path, test_case.stage_flags, results, output_type);
}
record_result :: (results : *[..]Result, result : Result) {
array_add(results, result);
}
run_test_suite :: (using suite : *Test_Suite, output_type : Output_Type = 0) {
if suite.name.count > 0 {
print("%Running suite: %\n", green(), suite.name);
print("%", reset_color());
}
Fail_Data :: struct {
path : string;
stage : string;
}
failed_test_paths : [..]Fail_Data;
failed_test_paths.allocator = temp;
builder : String_Builder;
init_string_builder(*builder,, temp);
for test_case : test_cases {
run_test(test_case, *suite.results, output_type);
for < suite.results {
result := suite.results[it_index];
if compare(result.path, test_case.path) == 0 {
if result.type == {
case .Failed; {
array_add(*failed_test_paths, .{ result.path, stage_to_string(result.stage) });
}
case .File_Read_Failed; {
array_add(*failed_test_paths, .{ result.path, "file not found" });
}
case .Golden_File_Not_Found; {
array_add(*failed_test_paths, .{ result.path, tprint("golden file not found for %", stage_to_string(result.stage)) });
}
}
evaluate_result(result);
} else {
break;
}
}
print("\n");
}
print("\n");
if output_type == 0 {
if failed_test_paths.count == 0 {
green(*builder);
print_to_builder(*builder, "All % tests passed!\n", test_cases.count);
reset_color(*builder);
} else {
print_to_builder(*builder, "%/% tests passed\n", test_cases.count - failed_test_paths.count, test_cases.count);
red(*builder);
print_to_builder(*builder, "% failed\n", failed_test_paths.count);
for failed_test : failed_test_paths {
print_to_builder(*builder, "% failed with error: %\n", failed_test.path, failed_test.stage);
}
reset_color(*builder);
}
}
print("%\n", builder_to_string(*builder));
}
read_suite :: (file_path : string, suite : *Test_Suite) -> bool {
bytes, ok := read_entire_file(file_path);
if !ok {
log_error("Unable to read suite file %\n", file_path);
return false;
}
path := parse_path(file_path);
file_without_extension := split(path.words[path.words.count - 1], ".");
suite.name = copy_string(file_without_extension[0]);
split_lines := split(bytes, "\n");
for split_line : split_lines {
line := split(split_line, " ");
if line[0].count == 0 {
continue;
}
if line[0].data[0] == #char "#" {
continue;
}
if line.count == 1 {
log_error("Invalid line - % - %\n", it_index + 1, line);
continue;
}
test_case_path := line[0];
stage_flags : Stage_Flags;
for i: 0..line.count - 1 {
trimmed := trim(line[i]);
if equal(trimmed, "lex") {
stage_flags |= .Lexer;
} else if equal(trimmed, "parse") {
stage_flags |= .Parser;
} else if equal(trimmed, "semant") {
stage_flags |= .Semantic_Analysis;
} else if equal(trimmed, "codegen") {
stage_flags |= .Codegen;
} else if equal(trimmed, "compile") {
stage_flags |= .Compile;
}
}
test_case := make_test_case(test_case_path, stage_flags);
array_add(*suite.test_cases, test_case);
}
return true;
}
read_test :: () {
}
stage_to_string :: (stage : Stage_Flags) -> string {
if #complete stage == {
case .Lexer; return "lexing";
case .Parser; return "parsing";
case .Semantic_Analysis; return "semantic checking";
case .Codegen; return "codegen";
case .Compile; return "compiled";
case; return "";
}
}
evaluate_result :: (result : Result) {
stage : string = stage_to_string(result.stage);
if #complete result.type == {
case .File_Read_Failed; {
print("%", red());
print("% failed with File_Read_Failed\n", result.path);
}
case .Golden_File_Not_Found; {
print("%", red());
print("% failed with Golden File Not Found for stage %\n", result.path, stage);
}
case .StdOut; {
}
case .Golden_Output; {
print("%", yellow());
print("% output new golden file at %\n", result.path, result.golden_path);
}
case .Passed; {
print("%", green());
print("% passed %\n", result.path, stage);
}
case .Failed; {
print("%", red());
print("% failed %\n", result.path, stage);
}
}
if result.info_text.count > 0 {
print("%", cyan());
print("--- Info text ---\n");
print("%", yellow());
print("%\n", result.info_text);
}
print("%", reset_color());
}
main :: () {
lexer : Lexer;
args := get_command_line_arguments();
suites : [..]Test_Suite;
output_type : Output_Type = 0;
Argument_Parse_State :: enum {
None;
Run_Suite;
Run_Test;
}
arg_parse_state : Argument_Parse_State;
current_suite : *Test_Suite;
path : string;
for i: 1..args.count - 1 {
arg := args[i];
if arg == "-output-as-golden" {
output_type |= .Golden;
continue;
} else if arg == "-output" {
output_type |= .StdOut;
continue;
}
if arg_parse_state == {
case .Run_Suite; {
if arg == "-output-as-golden" {
output_type |= .Golden;
} else if arg == "-output" {
output_type |= .StdOut;
} else {
print("%Unknown argument %\n", red(), arg);
}
}
case .Run_Test; {
cases := current_suite.test_cases.count;
if arg == "-lex" {
current_suite.test_cases[cases - 1].stage_flags |= .Lexer;
} else if arg == "-parse" {
current_suite.test_cases[cases - 1].stage_flags |= .Parser;
} else if arg == "-semant" {
current_suite.test_cases[cases - 1].stage_flags |= .Semantic_Analysis;
} else if arg == "-codegen" {
current_suite.test_cases[cases - 1].stage_flags |= .Codegen;
} else if arg == "-compile" {
current_suite.test_cases[cases - 1].stage_flags |= .Compile;
} else if contains(arg, ".") {
split_path := split(arg, ".");
extension := split_path[1];
if extension == SHADER_EXTENSION {
path := copy_string(arg);
test_case := make_test_case(path, 0);
array_add(*current_suite.test_cases, test_case);
}
} else {
print("%Unknown argument %\n", red, arg);
}
}
case .None; {
if contains(arg, ".") {
split_path := split(arg, ".");
extension := split_path[1];
if extension == SHADER_EXTENSION {
if arg_parse_state == .Run_Suite {
log_error("Unable to run a test while already running suite.");
continue;
}
if !current_suite {
suite : Test_Suite;
array_add(*suites, suite);
current_suite = *suites[0];
}
arg_parse_state = .Run_Test;
path := copy_string(arg);
test_case := make_test_case(path, 0);
array_add(*current_suite.test_cases, test_case);
} else if extension == SUITE_EXTENSION {
if arg_parse_state == .Run_Test {
log_error("Unable to run a suite while already running test.");
continue;
}
arg_parse_state = .Run_Suite;
path := copy_string(arg);
suite : Test_Suite;
read_suite(path, *suite);
array_add(*suites, suite);
current_suite = *suites[0];
}
}
}
}
}
for suite : suites {
run_test_suite(*suite, output_type);
}
}

View File

@@ -74,8 +74,24 @@ int4x4 :: struct {
//~ nbr: Constructors
#foreign float2 :: (float, float) -> float2;
#foreign float2 :: (float2) -> float2;
#foreign float2 :: (float) -> float2;
#foreign float3 :: (float, float, float) -> float3;
#foreign float3 :: (float3) -> float3;
#foreign float3 :: (float2, float) -> float3;
#foreign float3 :: (float, float2) -> float3;
#foreign float3 :: (float) -> float3;
#foreign float4 :: (float, float, float, float) -> float4;
#foreign float4 :: (float4) -> float4;
#foreign float4 :: (float2, float2) -> float4;
#foreign float4 :: (float2, float, float) -> float4;
#foreign float4 :: (float, float2, float) -> float4;
#foreign float4 :: (float, float, float2) -> float4;
#foreign float4 :: (float3, float) -> float4;
#foreign float4 :: (float, float3) -> float4;
#foreign float4 :: (float) -> float4;
//~ nbr: Vectors
#foreign cross :: (float3, float3) -> float3;
@@ -83,6 +99,10 @@ int4x4 :: struct {
#foreign distance :: (float3, float3) -> float;
#foreign distance :: (float4, float4) -> float;
#foreign length :: (float2) -> float;
#foreign length :: (float3) -> float;
#foreign length :: (float4) -> float;
#foreign dot :: (float2, float2) -> float;
#foreign dot :: (float3, float3) -> float;
#foreign dot :: (float4, float4) -> float;
@@ -259,4 +279,11 @@ int4x4 :: struct {
#foreign atan2 :: (float4, float4) -> float4;
#foreign atan2 :: (float4x4, float4x4) -> float4x4;
#foreign sample :: (Texture2D, float2, Sampler) -> float4;
#foreign sample :: (Texture2D, Sampler, float2) -> float4;
#foreign lerp :: (float, float, float) -> float;
#foreign lerp :: (float2, float2, float) -> float2;
#foreign lerp :: (float3, float3, float) -> float3;
#foreign lerp :: (float4, float4, float) -> float4;
#foreign fmod :: (float, float) -> float;

3
check.bat Normal file
View File

@@ -0,0 +1,3 @@
@echo off
jai first.jai -natvis - check

View File

@@ -1,42 +1,95 @@
#import "Basic";
#import "File";
#import "Compiler";
#import "Metaprogram_Plugins";
plugins: [..] *Metaprogram_Plugin;
build :: () {
w := compiler_create_workspace("Shader Compiler Test Build");
w := compiler_create_workspace("Ink Build");
if !w {
print("Workspace creation failed.\n");
return;
}
EXECUTABLE_NAME :: "test";
MAIN_FILE :: "Test.jai";
EXECUTABLE_NAME :: "ink";
MAIN_FILE :: "ink.jai";
options := get_build_options(w);
options.write_added_strings = true;
args := options.compile_time_command_line;
profile : bool = false;
for arg : args {
if arg == {
case "check"; {
options.output_type = .NO_OUTPUT;
}
case "profile"; {
}
}
}
intercept_flags: Intercept_Flags;
plugins_to_create: [..] Plugin_To_Create;
if profile {
tracy : Plugin_To_Create;
tracy.name = "tracy";
array_add(*plugins_to_create, tracy);
}
success := init_plugins(plugins_to_create, *plugins, w);
if !success {
log_error("A plugin init() failed. Exiting.\n");
exit(0);
}
new_path: [..] string;
array_add(*new_path, ..options.import_path);
array_add(*new_path, "modules");
// array_add(*new_path, "modules/shader_parsing");
options.import_path = new_path;
options.output_executable_name = EXECUTABLE_NAME;
wd := get_working_directory();
set_build_options(options, w);
compiler_begin_intercept(w);
for plugins {
if it.before_intercept it.before_intercept(it, *intercept_flags);
}
compiler_begin_intercept(w, intercept_flags);
for plugins if it.add_source it.add_source(it);
add_build_file(MAIN_FILE, w);
// Call message_loop(), which is a routine of ours below that will receive the messages.
message_loop(w);
compiler_end_intercept(w);
for plugins if it.finish it.finish (it);
for plugins if it.shutdown it.shutdown(it);
print("\nDone!\n\n");
set_build_options_dc(.{do_output=false});
set_build_options_dc(.{do_output=false, write_added_strings=false});
}
#run build();
message_loop :: (w: Workspace) {
while true {
// We ask the compiler for the next message. If one is not available,
// we will wait until it becomes available.
message := compiler_wait_for_message();
// Pass the message to all plugins.
for plugins if it.message it.message(it, message);
if message.kind == .COMPLETE break;
}
}
#run, stallable build();

View File

@@ -1,9 +1,34 @@
#load "Lexing.jai";
#load "Error.jai";
#load "Parsing.jai";
#load "Semantic_Analysis.jai";
#load "Codegen.jai";
#load "lexing.jai";
#load "error.jai";
#load "parsing.jai";
#load "check.jai";
#load "codegen.jai";
#import "File_Utilities";
/* TODO
- [x] Remove builtin stringbuilding and replace it with ad-hoc string building when error reporting. In that case we are already building a string anyway, so we can just pass in the string builder
- [ ] Support structured buffers (ro, rw, w)
- [ ] Support mesh and amplification shaders
- [ ] Support compute shaders
- [x] Support #if at top level
- [x] Support #if at block level
- [x] Remove properties block and just use hinted constant buffers instead
```
props :: constant_buffer @properties {
[...]
}
```
- [ ] while loops
- [ ] for-each loops
- [ ] add parameters to hints (meta properties, resource binding indices if needed)
- [ ] consider @entry(stage) syntax instead of the forced keyword
- [ ] Add flags to compiler
- [ ] Generate output flag(s)
- [ ] Possibly final stage flag, so you can just call compile_file and it only does what you need.
- Probably this flag is about which stage you need as the _last_ and not which stages to do, as that doesn't make sense.
- [ ] Multiple output languages?
*/
add_define :: (env : *Environment, key : string) {
for define : env.defines {
@@ -27,10 +52,6 @@ Environment :: struct {
defines : [..]string;
}
Shader_Compiler :: struct {
environment : Environment;
}
Field_Kind :: enum {
Int :: 0;
Half :: 1;
@@ -56,11 +77,35 @@ Hint_Kind :: enum {
None;
Position;
UV;
Target;
Output_Position;
Custom;
}
Hint_Names :: #run -> [(cast(int)Hint_Kind.Target) + 1]string {
names : [(cast(int)Hint_Kind.Target) + 1]string;
names[Hint_Kind.Position] = "position";
names[Hint_Kind.UV] = "uv";
names[Hint_Kind.Target] = "target";
return names;
}
lookup_hint :: (name : string) -> Hint_Kind {
if name == "position" {
return Hint_Kind.Position;
} else if name == "uv" {
return Hint_Kind.UV;
} else if starts_with(name, "target") {
return Hint_Kind.Target;
} else if name == "outposition" {
return Hint_Kind.Output_Position;
}
return .None;
}
Field_Hint :: struct {
kind : Hint_Kind;
@@ -83,122 +128,121 @@ Entry_Point :: struct {
return_value : Field;
}
Shader_Variant :: struct {
text : string;
vertex_entry_point : struct {
name : string;
input : [..]Field;
}
pixel_entry_point : struct {
name : string;
return_value : Field;
}
Buffer_Kind :: enum {
Constant;
Structured;
}
Property_Field :: struct {
base_field : Field;
// @Incomplete(nb): Editor information, min max, etc.
// This should also be compiled out for ship
}
Properties :: struct {
fields : [..]Property_Field;
buffer_index : u32;
}
Constant_Buffer :: struct {
register : int;
Buffer :: struct {
kind : Buffer_Kind;
name : string;
fields : Static_Array(Property_Field, 16);
fields : Static_Array(Field, 16);
hints : [..]Field_Hint;
buffer_index : u32;
}
Shader_Variant_Collection :: struct {
properties : Properties;
max_constant_buffers :: 16;
cbuffers : Static_Array(Constant_Buffer, max_constant_buffers);
variants : [..]Shader_Variant;
}
Input_File :: struct {
source : string;
path : string;
}
Token_Stream :: struct {
tokens : [..]Token;
}
Compiled_File :: struct {
Compiler_Context :: struct {
file : Input_File;
tokens : Token_Stream;
ast_root : *AST_Node;
ast_nodes : [..]AST_Node;
}
Compile_Result :: struct {
files : [..]Compiled_File;
environment : Environment;
tokens : [..]Token;;
root : *AST_Node;
nodes : [..]AST_Node;
codegen_result_text : string;
typed_buffers : Static_Array(Type_Variable_Handle, 32);
// structured_buffers : Static_Array(Type_Variable_Handle, 16);
scope_stack : Scope_Stack;
type_variables : [..]Type_Variable;
vertex_entry_point : struct {
node : *AST_Node;
name : string;
input : [..]Field;
}
pixel_entry_point : struct {
node : *AST_Node;
name : string;
return_value : Field;
}
max_buffers :: 32;
buffers : Static_Array(Buffer, max_buffers);
had_error : bool;
messages : [..]Compiler_Message;
allocator : Allocator;
arena : Arena;
}
//@Incomplete(niels): need to consider allocation
add_file :: (result : *Compile_Result, path : string) {
#add_context scratch_allocators : [2]Allocator;
#add_context scratch_id : int = 0;
init_context_allocators :: () {
if get_arena(context.scratch_allocators[0]) == null {
context.scratch_allocators[0] = make_arena(Megabytes(128));
context.scratch_allocators[1] = make_arena(Megabytes(128));
}
}
clear_context_allocators :: () {
if get_arena(context.scratch_allocators[0]) != null {
clear(context.scratch_allocators[0]);
clear(context.scratch_allocators[1]);
}
}
get_scratch :: (conflict : Allocator = .{}) -> Scratch {
arena := cast(*Arena)conflict.data;
if arena == get_arena(context.scratch_allocators[0]) || context.scratch_id == 0 {
context.scratch_id = 1;
return scratch_begin(*context.scratch_allocators[1]);
}
context.scratch_id = 0;
return scratch_begin(*context.scratch_allocators[0]);
}
record_error :: (result : *Compiler_Context, format : string, args : .. Any) {
error : Compiler_Message;
error.message_kind = .Error;
error.message = sprint(format, args);
array_add(*result.messages, error);
}
make_file :: (result : *Compiler_Context, path : string) -> Input_File {
if !file_exists(path) {
record_error(result, "Unable to load file: %", path);
return .{};
}
file_string, ok := read_entire_file(path);
if !ok {
// record_error(.File_Load_Failed, "Unable to load file: %", path);
return;
record_error(result, "Unable to load file: %", path);
return .{};
}
return make_file_from_string(file_string, path);
}
make_file_from_string :: (source : string, path : string = "") -> Input_File {
input_file : Input_File;
input_file.source = file_string;
input_file.source = source;
input_file.path = path;
compiled_file : Compiled_File;
compiled_file.file = input_file;
array_add(*result.files, compiled_file);
}
// @Incomplete(nb): Will we ever even use this?
from_file :: (path : string) -> Compile_Result {
arr : [1]string;
arr[0] = path;
return from_files(arr);
}
from_files :: (paths : []string) -> Compile_Result {
result : Compile_Result;
for path : paths {
add_file(*result, path);
}
return result;
}
Compilation_Result :: struct {
messages : [..]Compiler_Message;
had_error : bool;
collection : Shader_Variant_Collection;
return input_file;
}
pretty_print_field :: (field : *Field) -> string {
@@ -215,7 +259,7 @@ Min_Field_Name :: 10;
pretty_print_field :: (builder : *String_Builder, field : *Field) {
if field.name.count > 0 {
print_to_builder(builder, "% ", field.name);
append(builder, "- ");
append(builder, ": ");
} else {
append(builder, "return - ");
}
@@ -244,10 +288,17 @@ pretty_print_field :: (builder : *String_Builder, field : *Field) {
case .Struct; {
print_to_builder(builder, "struct : % {", type.name);
newline_after := type.children.count / 4;
for *child : type.children {
pretty_print_field(builder, child);
if it_index < type.children.count - 1 {
append(builder, " ");
append(builder, ", ");
}
if it_index % newline_after == 0 {
append(builder, "\n");
indent(builder, 4);
}
}
@@ -277,11 +328,7 @@ pretty_print_field :: (builder : *String_Builder, field : *Field) {
}
}
type_variable_to_field :: (checker : *Semantic_Checker, variable : Type_Variable_Handle) -> Field {
return type_variable_to_field(checker, h2tv(checker, variable));
}
type_variable_to_field :: (checker : *Semantic_Checker, variable : *Type_Variable) -> Field {
type_variable_to_field :: (ctx : *Compiler_Context, variable : *Type_Variable) -> Field {
field : Field;
field.name = variable.name;
@@ -314,14 +361,14 @@ type_variable_to_field :: (checker : *Semantic_Checker, variable : *Type_Variabl
case .Struct; {
type.kind = Field_Kind.Struct;
find_result := find_symbol(checker, variable.typename, xx 1);
find_result := find_symbol(ctx.scope_stack, variable.typename, xx 1);
assert(find_result != null, "Internal compiler error\n");
type_var := h2tv(checker, find_result.type_variable);
type_var := from_handle(ctx.type_variables, find_result.type_variable);
for i : 0..type_var.child_count - 1 {
for i : 0..type_var.children.count - 1 {
child := type_var.children[i];
child_field := type_variable_to_field(checker, h2tv(checker, child));
child_field := type_variable_to_field(ctx, child);
array_add(*type.children, child_field);
}
@@ -332,11 +379,11 @@ type_variable_to_field :: (checker : *Semantic_Checker, variable : *Type_Variabl
for hint : variable.source_node.hint_tokens {
field_hint : Field_Hint;
if hint.ident_value == "position" {
// @Incomplete(nb): Should be a lookup table somewhere
if lookup_hint(hint.ident_value) == .Position {
field_hint.kind = .Position;
} else if starts_with(hint.ident_value, "target") {
// @Incomplete(nb): Should be a lookup table somewhere
} else if lookup_hint(hint.ident_value) == .UV {
field_hint.kind = .UV;
} else if lookup_hint(hint.ident_value) == .Target {
index_str : string;
index_str.data = *hint.ident_value.data[7];
index_str.count = 1;
@@ -347,7 +394,8 @@ type_variable_to_field :: (checker : *Semantic_Checker, variable : *Type_Variabl
}
field_hint.kind = .Target;
} else {
// @Incomplete(nb): custo hints
field_hint.custom_hint_name = hint.ident_value;
field_hint.kind = .Custom;
}
array_add(*field.hints, field_hint);
}
@@ -357,82 +405,50 @@ type_variable_to_field :: (checker : *Semantic_Checker, variable : *Type_Variabl
return field;
}
compile_file :: (compiler : *Shader_Compiler, paths : []string) -> Compile_Result {
result : Compile_Result;
for path : paths {
add_file(*result, path);
}
lex(*result);
// parse(*result);
// check(*result);
// codegen(*result);
return result;
type_variable_to_field :: (ctx : *Compiler_Context, variable : Type_Variable_Handle) -> Field {
return type_variable_to_field(ctx, from_handle(ctx.type_variables, variable));
}
compile_file :: (compiler : *Shader_Compiler, path : string) -> Compilation_Result {
result : Compilation_Result;
generate_buffer :: (ctx : *Compiler_Context, type_handle : Type_Variable_Handle, buffers : *Static_Array) {
variable := from_handle(ctx.type_variables, type_handle);
lexer : Lexer;
buffer := array_add(buffers);
init_lexer_from_file(*lexer, path);
if lexer.result.had_error {
copy_messages(lexer.result.messages, *result.messages);
result.had_error = true;
return result;
if variable.type == {
case .CBuffer; {
buffer.kind = .Constant;
}
case .Buffer; {
buffer.kind = .Structured;
}
}
buffer.name = variable.name;
for i : 0..variable.children.count - 1 {
child := variable.children[i];
field : Field = type_variable_to_field(ctx, from_handle(ctx.type_variables, child));
array_add(*buffer.fields, field);
}
lex_result := lex(*lexer,, *temp);
if lex_result.had_error {
copy_messages(lex_result.messages, *result.messages);
result.had_error = true;
return result;
buffer.buffer_index = variable.resource_index;
for hint : variable.source_node.hint_tokens {
field_hint : Field_Hint;
field_hint.custom_hint_name = hint.ident_value;
field_hint.kind = .Custom;
array_add(*buffer.hints, field_hint);
}
}
generate_output_data :: (ctx : *Compiler_Context) {
if ctx.had_error {
return;
}
parse_state : Parse_State;
init_parse_state(*parse_state, lex_result.tokens, lexer.path, context.allocator);
if ctx.vertex_entry_point.node {
ctx.vertex_entry_point.name = ctx.vertex_entry_point.node.name;
parse_result := parse(*parse_state);
if parse_result.had_error {
copy_messages(parse_result.messages, *result.messages);
result.had_error = true;
return result;
}
checker : Semantic_Checker;
init_semantic_checker(*checker, parse_result.root, path);
check_result := check(*checker);
if check_result.had_error {
copy_messages(check_result.messages, *result.messages);
result.had_error = true;
return result;
}
state : Codegen_State;
init_codegen_state(*state, parse_result.root, check_result, .HLSL);
result_text : string;
codegen_result := codegen(*state);
if codegen_result.had_error {
copy_messages(codegen_result.messages, *result.messages);
result.had_error = true;
return result;
}
// @Incomplete(nb): Assumes only a single variant for now
variant : Shader_Variant;
variant.text = codegen_result.result_text;
if checker.result.vertex_entry_point {
variant.vertex_entry_point.name = checker.result.vertex_entry_point.name;
type_variable := h2tv(*checker, checker.result.vertex_entry_point.type_variable);
type_variable := from_handle(ctx.type_variables, ctx.vertex_entry_point.node.type_variable);
assert(type_variable.type == .Function);
node := type_variable.source_node;
@@ -440,77 +456,64 @@ compile_file :: (compiler : *Shader_Compiler, path : string) -> Compilation_Resu
if node.children[0].kind == .FieldList {
field_list := node.children[0];
for child : field_list.children {
tv := h2tv(*checker, child.type_variable);
field := type_variable_to_field(*checker, tv);
array_add(*variant.vertex_entry_point.input, field);
tv := from_handle(ctx.type_variables, child.type_variable);
field := type_variable_to_field(ctx, tv);
array_add(*ctx.vertex_entry_point.input, field);
}
}
}
}
for buffer_variable : to_array(*check_result.constant_buffers) {
variable := h2tv(check_result.type_variables, buffer_variable);
cb := array_add(*result.collection.cbuffers);
for i : 0..variable.child_count - 1 {
child := variable.children[i];
field : Property_Field;
field.base_field = type_variable_to_field(*checker, h2tv(*checker, child));;
array_add(*cb.fields, field);
}
cb.buffer_index = variable.resource_index;
for buffer_variable : ctx.typed_buffers {
generate_buffer(ctx, buffer_variable, *ctx.buffers);
}
find_result := find_symbol(*check_result.scope_stack, check_result.property_name, xx 1);
if find_result {
property_variable := h2tv(check_result.type_variables, find_result.type_variable);
if ctx.pixel_entry_point.node {
ctx.pixel_entry_point.name = ctx.pixel_entry_point.node.name;
for i : 0..property_variable.child_count - 1 {
child := property_variable.children[i];
field := type_variable_to_field(*checker, h2tv(*checker, child));
prop_field : Property_Field;
prop_field.base_field = field;
array_add(*result.collection.properties.fields, prop_field);
}
result.collection.properties.buffer_index = property_variable.resource_index;
}
if checker.result.pixel_entry_point {
variant.pixel_entry_point.name = checker.result.pixel_entry_point.name;
type_variable := h2tv(*checker, checker.result.pixel_entry_point.type_variable);
type_variable := from_handle(ctx.type_variables, ctx.pixel_entry_point.node.type_variable);
assert(type_variable.type == .Function);
field := type_variable_to_field(*checker, type_variable.return_type_variable);
for hint : type_variable.source_node.hint_tokens {
field_hint : Field_Hint;
if type_variable.return_type_variable > 0 {
field := type_variable_to_field(ctx, type_variable.return_type_variable);
for hint : type_variable.source_node.hint_tokens {
field_hint : Field_Hint;
if hint.ident_value == "position" {
// @Incomplete(nb): Should be a lookup table somewhere
field_hint.kind = .Position;
} else if starts_with(hint.ident_value, "target") {
// @Incomplete(nb): Should be a lookup table somewhere
index_str : string;
index_str.data = *hint.ident_value.data[7];
index_str.count = 1;
if lookup_hint(hint.ident_value) == .Position {
field_hint.kind = .Position;
} else if lookup_hint(hint.ident_value) == .Target {
index_str : string;
index_str.data = *hint.ident_value.data[7];
index_str.count = 1;
result, ok, remainder := string_to_int(index_str);
if ok {
field_hint.target_index = result;
result, ok, remainder := string_to_int(index_str);
if ok {
field_hint.target_index = result;
}
field_hint.kind = .Target;
} else {
// @Incomplete(nb): custom hints
}
field_hint.kind = .Target;
} else {
// @Incomplete(nb): custo hints
array_add(*field.hints, field_hint);
}
array_add(*field.hints, field_hint);
ctx.pixel_entry_point.return_value = field;
}
variant.pixel_entry_point.return_value = field;
}
array_add(*result.collection.variants, variant);
return result;
}
compile_file :: (ctx : *Compiler_Context, path : string, allocator : Allocator = temp) {
new_context := context;
new_context.allocator = allocator;
push_context new_context {
init_context_allocators();
defer clear_context_allocators();
ctx.file = make_file(ctx, path);
lex(ctx, allocator);
parse(ctx, allocator);
check(ctx, allocator);
codegen(ctx, allocator);
generate_output_data(ctx);
}
}

1
modules/tracy Submodule

Submodule modules/tracy added at 9668d7b8ab

BIN
output.tracy Normal file

Binary file not shown.

View File

@@ -1,12 +1,12 @@
test/assign_arithmetic_expression.shd lex parse
test/empty_vertex_main.shd lex parse
test/empty_vertex_main_with_position_parameter.shd lex parse
test/meta_block.shd lex parse
test/basic_property_and_return_value.shd lex parse
test/function_call_return.shd lex parse
test/struct_field_access_test.shd lex parse
test/pass_and_access_struct_fields_in_functions.shd lex parse
test/field_without_type_specifier.shd lex parse
test/functions_with_same_name.shd lex parse
test/function_with_int_return.shd lex parse
test/type_as_variable_name.shd lex parse
test/assign_arithmetic_expression.inx lex parse
test/empty_vertex_main.inx lex parse
test/empty_vertex_main_with_position_parameter.inx lex parse
test/meta_block.inx lex parse
test/basic_property_and_return_value.inx lex parse
test/function_call_return.inx lex parse
test/struct_field_access_test.inx lex parse
test/pass_and_access_struct_fields_in_functions.inx lex parse
test/field_without_type_specifier.inx lex parse
test/functions_with_same_name.inx lex parse
test/function_with_int_return.inx lex parse
test/type_as_variable_name.inx lex parse

View File

@@ -0,0 +1,4 @@
vertex main :: () {
v : float2;
v.x = (2.0 + ((4.0 - 2.0) * 1.5)) * 3.0;
}

View File

@@ -1,5 +1,6 @@
vertex main :: () -> float4 @position {
arr : [16].float4;
arr[0] = float4(1,1,1);
return arr[0];
arr[0] = float4(1, 1, 1, 1);
pos := arr[1];
return pos;
}

View File

@@ -0,0 +1,5 @@
vertex main :: () {
a : float2;
b : float2;
(a + b).x = 2.0;
}

View File

@@ -0,0 +1,10 @@
P :: struct {
v : float2;
}
vertex main :: () {
p : P;
p.v.x.y = 2.0;
// v : float2;
// v.x.y.z = 2.0;
}

View File

@@ -1,4 +1,4 @@
properties {
properties :: Constant_Buffer @properties {
color : float4;
}

View File

@@ -0,0 +1,8 @@
props :: properties {
resolution : float2;
}
vertex main :: (pos : float3 @position) -> float4 @position {
p := float2(1.0 - 2.0 * props.resolution.x, 1.0 - 2.0 * props.resolution.y);
return float4(p, 1.0, 1.0);
}

11
test/buffers.ink Normal file
View File

@@ -0,0 +1,11 @@
property_buffer :: Buffer {
color : float4;
}
const_buffer :: Constant_Buffer {
color : float4;
}
pixel main :: (index : int) {
return property_buffer[index].color;
}

34
test/builtin_types.ink Normal file
View File

@@ -0,0 +1,34 @@
vertex main :: () {
v2 : float2 = float2(2.0, 2.0);
v2 = float2(2.0);
v2 = float2(v2);
v3 : float3 = float3(2.0, 2.0, 2.0);
v3 = float3(v2, 1.0);
v3 = float3(1.0, v2);
v3 = float3(1.0);
v3 = float3(v3);
v4 : float4 = float4(2.0, 2.0, 2.0, 2.0);
v4 = float4(v4);
v4 = float4(v2, v2);
v4 = float4(v2, 1.0, 1.0);
v4 = float4(1.0, v2, 1.0);
v4 = float4(1.0, 1.0, v2);
v4 = float4(v3, 2.0);
v4 = float4(2.0, v3);
v4 = float4(2.0);
v4 = float4(1.0, 1.0, v2);
v4 = float4(2.0);
v2.x = 2.0;
v2.y = 2.0;
p := v2.x + v3.z;
q := v4.w + v2.x;
m : float4x4;
}

View File

@@ -0,0 +1,6 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[v] : float2
]
]

7
test/check/arrays.golden Normal file
View File

@@ -0,0 +1,7 @@
scope (global) [
[vertex__vs_main] : () -> float4
scope (vertex__vs_main) [
[pos] : float4
[arr] : [16].float4
]
]

View File

@@ -0,0 +1,6 @@
test/bad_double_access.ink:7,4: error: Attempting to access a field on a primitive type 'float'.
p.v.x.
^
declaration:
x: float


View File

@@ -0,0 +1,11 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[v2] : float2
[v4] : float4
[v3] : float3
[p] : float
[m] : float4x4
[q] : float
]
]

View File

@@ -0,0 +1,15 @@
scope (global) [
[pixel__ps_main] : (pos : float4) -> float4
[vertex__vs_main] : (pos : float3) -> float4
[p] : {time : float}
scope (p) [
[time] : float
]
scope (vertex__vs_main) [
[pos] : float3
]
scope (pixel__ps_main) [
[t] : float
[pos] : float4
]
]

View File

@@ -0,0 +1,10 @@
scope (global) [
[vertex__vs_main] : ()
[p] : {v : float2}
scope (p) [
[v] : float2
]
scope (vertex__vs_main) [
[x] : float
]
]

View File

@@ -1,4 +1,4 @@
scope (global) [
[Foo] : {}
[Foo] : {}
scope (Foo) []
]

View File

@@ -0,0 +1,6 @@
test/float_if_cond.ink:0,0: error: Type of expression in if condition has to be bool.
if 1.0
^^^
1.0 has type float


View File

@@ -0,0 +1,10 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[x] : int
scope (block) [
[i] : int
scope (block) []
]
]
]

View File

@@ -0,0 +1,4 @@
test/for_index_outside.ink:6,0: error: Use of undeclared symbol 'i'
 i += 1;
^


View File

@@ -0,0 +1,8 @@
test/functions_with_same_name.ink:2,0: error: Redeclaration of 'foo'
 foo :: () {
^^^
test/functions_with_same_name.ink:1,0: info: Here is the first declaration of 'foo'
 foo :: () {
^^^


View File

@@ -0,0 +1,13 @@
scope (global) [
[vertex__vs_main] : (pos : float4) -> float4
[props] : {projection : float4x4, view : float4x4}
scope (props) [
[projection] : float4x4
[view] : float4x4
]
scope (vertex__vs_main) [
[pos] : float4
[mv] : float4
[mvp] : float4
]
]

View File

@@ -0,0 +1,6 @@
test/if_cond_assign.ink:0,0: error: Type of expression in if condition has to be bool.
if 0 = 100
^^^^^^
if 0 = 100 { has type int


View File

@@ -0,0 +1,8 @@
scope (global) [
[pixel__ps_main] : ()
scope (pixel__ps_main) [ scope (block) [
[alpha_color] : float4
[f] : float
]
]
]

View File

@@ -0,0 +1,4 @@
scope (global) [
[vertex__vs_console_main] : ()
scope (vertex__vs_console_main) []
]

8
test/check/ifdefs.golden Normal file
View File

@@ -0,0 +1,8 @@
scope (global) [
[vertex__vs_skinning_main] : ()
[pixel__ps_main] : ()
scope (vertex__vs_skinning_main) [
[x] : float
]
scope (pixel__ps_main) []
]

View File

@@ -0,0 +1,15 @@
scope (global) [
[foo] : () -> float
[vertex__vs_main] : (pos : float3) -> float4
[bar] : () -> float
scope (bar) []
scope (foo) []
scope (vertex__vs_main) [
[v2] : float2
[i] : int
[v4] : float4
[pos] : float3
[v3] : float3
[f] : float
]
]

View File

@@ -0,0 +1,9 @@
scope (global) [
[vertex__vs_main] : (pos : float3) -> float4
scope (vertex__vs_main) [
[pos] : float3
scope (block) [ scope (block) []
scope (block) []
]
]
]

View File

@@ -0,0 +1,6 @@
test/non_bool_cond.ink:0,0: error: Type of expression in if condition has to be bool.
if 1.0
^^^
1.0 has type float


View File

@@ -1,7 +1,7 @@
scope (global) [
[foo] : (f : Foo) -> float
[vertex__vs_main] : ()
[Foo] : {some_data : float}
[Foo] : {some_data : float}
scope (Foo) [
[some_data] : float
]

View File

@@ -0,0 +1,12 @@
scope (global) [
[pixel__ps_main] : () -> float4
[vertex__vs_main] : (pos : float4) -> float4
[props] : {color : float4}
scope (props) [
[color] : float4
]
scope (vertex__vs_main) [
[pos] : float4
]
scope (pixel__ps_main) []
]

View File

@@ -0,0 +1,8 @@
test/redeclared_variable.ink:3,0: error: Redeclaration of 'x'
 x : float = 5.0
^
test/redeclared_variable.ink:2,0: info: Here is the first declaration of 'x'
 x : float = 1.0
^


View File

@@ -0,0 +1,8 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[b] : float2
[x] : float
[a] : float2
]
]

View File

@@ -0,0 +1,9 @@
scope (global) [
[vertex__vs_main] : (pos : float3) -> float4
scope (vertex__vs_main) [
[pos] : float3
scope (block) []
scope (block) []
scope (block) []
]
]

View File

@@ -0,0 +1,7 @@
scope (global) [
[vertex__vs_main] : (pos : float3) -> float4
scope (vertex__vs_main) [
[pos] : float3
scope (block) []
]
]

View File

@@ -0,0 +1,8 @@
scope (global) [
[vertex__vs_main] : (pos : float3) -> float4
scope (vertex__vs_main) [
[pos] : float3
scope (block) []
scope (block) []
]
]

View File

@@ -1,5 +1,5 @@
scope (global) [
[Data] : {color : float4}
[Data] : {color : float4}
[vertex__vs_main] : ()
scope (Data) [
[color] : float4

View File

@@ -1,6 +1,6 @@
test/struct_access_primitive_type.shd:3,0: error: Attempting to access a field on a primitive type 'int'.
test/struct_access_primitive_type.ink:3,0: error: Attempting to access a field on a primitive type 'int'.
x.d = 4;
^
^
declaration:
x : int = 5


View File

@@ -1,7 +1,7 @@
scope (global) [
[Bar] : {t : Foo}
[Bar] : {t : Foo}
[vertex__vs_main] : ()
[Foo] : {color : float4}
[Foo] : {color : float4}
scope (Foo) [
[color] : float4
]

View File

@@ -0,0 +1,6 @@
test/temp_access.ink:5,10: error: Cannot assign to an lvalue.
 (a + b).x = 2.0;
^^^^^^^^^^^


View File

@@ -0,0 +1,4 @@
test/type_as_variable_name.ink:2,0: error: Invalid variable name 'int'
 int : float = 4.0
^^^


10
test/check/unary.golden Normal file
View File

@@ -0,0 +1,10 @@
scope (global) [
[pixel__ps_ps_main] : (position : float4) -> float4
[vertex__vs_vs_main] : (position : float3) -> float4
scope (vertex__vs_vs_main) [
[position] : float3
]
scope (pixel__ps_ps_main) [
[position] : float4
]
]

View File

@@ -0,0 +1,7 @@
test/undeclared_function.ink:2,0: error: Attempt to call undeclared function 'foo'.
 foo();
^^^


View File

@@ -0,0 +1,4 @@
test/undeclared_symbol.ink:2,10: error: Use of undeclared symbol 'f'
 b : int = f;
^


View File

@@ -1,4 +1,4 @@
test/unknown_overload.shd:6,0: error: Procedure call did not match any of the possible overloads for 'foo'
test/unknown_overload.ink:6,0: error: Procedure call did not match any of the possible overloads for 'foo'
 found:
foo(v, v);
^^^
@@ -7,10 +7,10 @@
 foo(v, v);
^
 Possible overloads:
 foo :: (v1 : float3, v2 : float3) { (test/unknown_overload.shd:1)
 foo :: (v1 : float2, v2 : float2, v3 : float2) { (test/unknown_overload.shd:2)
 foo :: (v1 : float3, v2 : float3) { (test/unknown_overload.ink:1)
 foo :: (v1 : float2, v2 : float2, v3 : float2) { (test/unknown_overload.ink:2)
test/unknown_overload.shd:6,4: error: Type mismatch. Expected float3 got float
test/unknown_overload.ink:6,4: error: Type mismatch. Expected float3 got float
 found:
foo(v, v);
^
@@ -20,7 +20,7 @@
got:
v : float = 2.0
test/unknown_overload.shd:6,7: error: Type mismatch. Expected float3 got float
test/unknown_overload.ink:6,7: error: Type mismatch. Expected float3 got float
 found:
foo(v, v);
^

View File

@@ -1,15 +1,15 @@
test/wrong_argument_count.shd:5,19: error: Use of undeclared symbol 'w'
test/wrong_argument_count.ink:5,19: error: Use of undeclared symbol 'w'
 return x * y * z * w;
^
test/wrong_argument_count.shd:9,0: error: Procedure call did not match any of the possible overloads for 'foo'
test/wrong_argument_count.ink:9,0: error: Procedure call did not match any of the possible overloads for 'foo'
 found:
foo(2.0, 3.0);
^^^
 Possible overloads:
 foo :: (x : float, y : float, z : float) -> float { (test/wrong_argument_count.shd:1)
 foo :: (x : float, y : float, z : float) -> float { (test/wrong_argument_count.ink:1)
 Not enough arguments: Wanted 3, got 2.
 foo :: (x : float, y : float, z : float, w : float) -> float { (test/wrong_argument_count.shd:4)
 foo :: (x : float, y : float, z : float, w : float) -> float { (test/wrong_argument_count.ink:4)
 Not enough arguments: Wanted 4, got 2.

View File

@@ -0,0 +1,30 @@
test/wrong_multiply.ink:4,18: error: Procedure call did not match any of the possible overloads for 'float4'
 found:
result : float4 = float4(1.0, foo * res, 0.0, 1.0);
^^^^^^
 While matching argument 2 in function call.
 result : float4 = float4(1.0, foo * res, 0.0, 1.0);
^
 Possible overloads:
 float4 :: (float, float, float, float)
 float4 :: (float2, float2)
 float4 :: (float2, float, float)
 float4 :: (float, float2, float)
 float4 :: (float, float, float2)
 float4 :: (float, float3)
 float4 :: (float3, float)
 float4 :: (float4)
 float4 :: (float)
test/wrong_multiply.ink:4,34: error: Type mismatch. Expected float got float2
 found:
result : float4 = float4(1.0, foo * res, 0.0, 1.0);
^
expected:
float
got:
result : float4 = float4(1.0, foo * res, 0.0, 1.0);


View File

@@ -1,4 +1,4 @@
test/wrong_type_for_function.shd:11,17: error: Procedure call did not match any of the possible overloads for 'float4'
test/wrong_type_for_function.ink:11,17: error: Procedure call did not match any of the possible overloads for 'float4'
 found:
color : float4 = float4(y, 1.0, 1.0, 1.0);
^^^^^^
@@ -7,9 +7,17 @@
 color : float4 = float4(y, 1.0, 1.0, 1.0);
^
 Possible overloads:
 foreign float4 :: (float, float, float, float) -> float4; (test/wrong_type_for_function.shd:78)
 float4 :: (float, float, float, float)
 float4 :: (float2, float2)
 float4 :: (float2, float, float)
 float4 :: (float, float2, float)
 float4 :: (float, float, float2)
 float4 :: (float, float3)
 float4 :: (float3, float)
 float4 :: (float4)
 float4 :: (float)
test/wrong_type_for_function.shd:11,24: error: Type mismatch. Expected float got float2
test/wrong_type_for_function.ink:11,24: error: Type mismatch. Expected float got float2
 found:
color : float4 = float4(y, 1.0, 1.0, 1.0);
^

47
test/check_all.suite Normal file
View File

@@ -0,0 +1,47 @@
test/assign_arithmetic_expression.ink check
test/arithmetic_parens.ink check
test/basic_property_and_return_value.ink check
test/builtin_types.ink check
test/complicated_computation.ink check
test/constant_buffer.ink check
test/bad_double_access.ink check
test/double_access.ink check
test/empty_struct.ink check
test/empty_vertex_main.ink check
test/empty_vertex_main_with_position_parameter.ink check
test/field_assignment.ink check
test/for_i_loop.ink check
test/function_call.ink check
test/function_call_out_of_order_declaration.ink check
test/function_call_return.ink check
test/functions_with_same_name.ink check
test/function_with_int_return.ink check
test/if_cond_assign.ink check
test/ifdefs.ink check
test/if_def_block.ink check
test/if_def_expression.ink check
test/inferred_types.ink check
test/multiple_functions.ink check
test/multiple_semicolons_everywhere.ink check
test/nested_if.ink check
test/non_bool_cond.ink check
test/pass_and_access_struct_fields_in_functions.ink check
test/passthrough.ink check
test/redeclared_variable.ink check
test/rvalue_binary.ink check
test/simple_else_if.ink check
test/simple_if_else.ink check
test/simple_if.ink check
test/simple_struct_access.ink check
test/struct_access_primitive_type.ink check
test/struct_within_struct.ink check
test/temp_access.ink check
test/type_as_variable_name.ink check
test/unary.ink check
test/undeclared_function.ink check
test/undeclared_symbol.ink check
test/unknown_overload.ink check
test/use_builtin_functions.ink check
test/wrong_argument_count.ink check
test/wrong_multiply.ink check
test/wrong_type_for_function.ink check

View File

@@ -0,0 +1,6 @@
void vs_main()
{
float2 v;
v.x = (2.0f + ((4.0f - 2.0f) * 1.5f)) * 3.0f;
}

View File

@@ -0,0 +1,6 @@
float4 vs_main() : SV_POSITION
{
float4 arr[16];
return arr[0];
}

View File

@@ -1,4 +1,4 @@
cbuffer __PROPERTIES : register(b0)
cbuffer properties : register(b0)
{
float4 color;
}
@@ -10,6 +10,6 @@ float3 vs_main(float3 pos : POSITION) : SV_POSITION
float4 ps_main() : SV_TARGET
{
return color;
return properties.color;
}

View File

@@ -0,0 +1,28 @@
void vs_main()
{
float2 v2 = float2(2.0f, 2.0f);
v2 = float2(2.0f, 2.0f);
v2 = float2(v2, v2);
float3 v3 = float3(2.0f, 2.0f, 2.0f);
v3 = float3(v2, 1.0f);
v3 = float3(1.0f, v2);
v3 = float3(1.0f, 1.0f, 1.0f);
v3 = float3(v3, v3, v3);
float4 v4 = float4(2.0f, 2.0f, 2.0f, 2.0f);
v4 = float4(v4, v4, v4, v4);
v4 = float4(v2, v2);
v4 = float4(v2, 1.0f, 1.0f);
v4 = float4(1.0f, v2, 1.0f);
v4 = float4(1.0f, 1.0f, v2);
v4 = float4(v3, 2.0f);
v4 = float4(2.0f, v3);
v4 = float4(2.0f, 2.0f, 2.0f, 2.0f);
v4 = float4(1.0f, 1.0f, v2);
v4 = float4(2.0f, 2.0f, 2.0f, 2.0f);
v2.x = 2.0f;
v2.y = 2.0f;
float p = v2.x + v3.z;
float q = v4.w + v2.x;
float4x4 m;
}

View File

@@ -2,6 +2,6 @@ void vs_main()
{
float x = 5.0f;
float y = 3000.0f;
float z = y * y + x;
float z = (y * y) + x;
}

View File

@@ -0,0 +1,18 @@
cbuffer camera : register(b0)
{
float4x4 projection;
float4x4 view;
}
float4 vs_main(float4 pos : POSITION) : SV_POSITION
{
float4 mv = mul(camera.view, pos);
float4 mvp = mul(camera.projection, mv);
return mvp;
}
float4 ps_main() : SV_TARGET
{
return float4(0.5f, 0.5f, 0.5f, 1.0f);
}

View File

@@ -0,0 +1,17 @@
cbuffer __PROPERTIES : register(b0)
{
float __PROPERTIES__time;
}
float4 vs_main(float3 pos : POSITION) : SV_POSITION
{
return float4(pos.x, pos.y, pos.z, 1.0f);
}
float4 ps_main(float4 pos : SV_POSITION) : SV_TARGET
{
float t = __PROPERTIES__time;
return float4(1, 1, 1, 1);
}

View File

@@ -1,4 +1,2 @@
struct Foo;
struct Foo {}
struct Foo {};

View File

@@ -0,0 +1,13 @@
cbuffer props : register(b0)
{
float4x4 projection;
float4x4 view;
}
float4 vs_main(float4 pos : POSITION) : SV_POSITION
{
float4 mv = mul(props.view, pos);
float4 mvp = mul(props.projection, mv);
return mvp;
}

View File

@@ -0,0 +1,7 @@
void ps_main()
{
float4 alpha_color = float4(1, 0, 0, 1);
float f = 2.0f;
}

View File

@@ -0,0 +1,4 @@
void vs_console_main()
{
}

View File

@@ -0,0 +1,9 @@
void ps_main()
{
}
void vs_skinning_main()
{
float x = 5.0f;
}

View File

@@ -0,0 +1,24 @@
float bar();
float foo();
float bar()
{
return 5.0f;
}
float foo()
{
return bar();
}
float4 vs_main(float3 pos : POSITION) : SV_POSITION
{
float f = 2.0f;
int i = 10;
f = foo();
float2 v2 = float2(2, 2);
float3 v3 = float3(2, 2, 3);
float4 v4 = float4(4, 5, 6, 7);
return float4(1, 1, 1, 1);
}

Some files were not shown because too many files have changed in this diff Show More