Added Ink and ncore to modules

This commit is contained in:
2025-09-30 07:27:58 +02:00
commit 9ec1b9cf82
327 changed files with 14171 additions and 0 deletions

202
README.md Normal file
View File

@@ -0,0 +1,202 @@
# Shader Compiler
A compiler for a custom, platform-agnostic shader language that compiles to platform specific shader languages (currently only HLSL).
## Features
A simple passthrough shader (`passthrough.shd`) can look as follows
```hlsl
vertex main :: (position : float4 @position) -> float4 {
return position;
}
pixel main :: () -> float4 @target {
return float4(1, 1, 1, 1);
}
```
The shader contains both vertex and pixel shader code, where you specify the entry points as above. Entry points can have any and overlapping names, which will be exposed in the meta data of a compiled shader.
There is basic support for most HLSL built-in math functions for the following types:
- Scalar types: int, float
- 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 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);
```
You can also do arithmetic as you would expect
```
x : float = 2.0 * 4.0 + (3 * 2); // int literals automatically convert to floats.
```
There is basic struct support
```hlsl
Camera_Data :: struct {
projection : float4x4;
view : float4x4;
}
```
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;
view : float4x4;
}
```
which will be exposed in the compiled result. `properties` can be renamed to a custom/shorter name like
```
p :: properties {
...
```
You can also define constant buffers
```
camera :: constant_buffer {
projection : float4x4;
view : float4x4;
}
```
## Jai Usage Example
To compile a shader and use the result, you can do the following in jai
```jai
// In the future, you can pass environment defines to the compiler.
ctx : Compiler_Context;
compile_file(*compiler, "shader.shd", allocator);
if ctx.had_error {
log_error("%\n", report_messages(ctx.messages),, temp);
return;
}
// 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
```
Compiler_Context :: struct {
file : Input_File;
environment : Environment;
tokens : [..]Token;;
root : *AST_Node;
nodes : [..]AST_Node;
codegen_result_text : string;
constant_buffers : Static_Array(Type_Variable_Handle, 16);
scope_stack : Scope_Stack;
type_variables : [..]Type_Variable;
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 {
name : string;
fields : Static_Array(Property_Field, 16);
// hints : Field_Hint; // optional hint...
hints : [..]Field_Hint;
buffer_index : u32;
}
Properties :: struct {
fields : [..]Property_Field;
buffer_index : u32;
}
```
A field is just a simple struct with a name and type (and hints such as semantics or custom hints in the future)
```
Field_Hint :: struct {
kind : Hint_Kind;
target_index : int;
custom_hint_name : string;
}
Field :: struct {
name : string;
type : Field_Type;
hints : [..]Field_Hint;
}
Field_Kind :: enum {
Int :: 0;
Half :: 1;
Float :: 2;
Double :: 3;
Texture2D :: 8;
Sampler :: 9;
Function;
Struct;
Array; // Not yet supported
}
Field_Type :: struct {
kind : Field_Kind;
name : string; //@Note(niels): for structs
children : [..]Field;
}
Hint_Kind :: enum {
None;
Position;
Target;
Custom;
}
```
## Notable missing features
- While
- Arrays
- Multiple render targets
- Custom buffers/structured buffers
- Interpolation specifiers
- Proper variant handling with environment defines
- Importing files such as shared utils etc. with something other than textual `#load`

581
ast.jai Normal file
View File

@@ -0,0 +1,581 @@
/////////////////////////////////////
//~ nbr: Node data structure
//
// [ ] Add a way to infer or get file path from a node
AST_Kind :: enum {
Program;
Function;
Return;
//==
// Directives
If_Directive;
Access;
Call;
Struct;
If;
For;
CBuffer;
Buffer;
FieldList;
ArgList;
Variable;
Binary;
Unary;
Integer;
Float;
Expression_Statement;
Field;
Unnamed_Field;
Block;
Error;
}
AST_Node :: struct {
kind : AST_Kind;
// @Note(niels): Children nodes can be interpreted as anything useful.
// for an if-statement we would have at most 2 children
// a property block has a field list child node which has
// a child node for each field declaration etc.
children : [..]*AST_Node;
parent : *AST_Node;
// @Note(niels): Every node can have a name, but most nodes don't. A function or field declaration has one,
// but an if-statement does not
name : string;
integer_value : int;
float_value : float;
token : Token;
array_field : bool;
source_location : Source_Range;
type_variable : Type_Variable_Handle;
foreign_declaration : bool;
// @Incomplete(nb): Change this to just be children and a single token_data field,
// then we can add new node types (hint, typespec, operator) and have them
// as children instead.
hint_tokens : [..]Token;
vertex_entry_point : bool;
pixel_entry_point : bool;
}
// ===========================================================
// Pretty printing
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, skip_indent = true);
}
append(builder, ")");
}
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);
append(builder, "]");
}
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);
append(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], indentation, builder, true);
append(builder, "].");
print_to_builder(builder, "%", node.token.ident_value);
} else {
print_to_builder(builder, " %", node.token.ident_value);
}
}
for hint : node.hint_tokens {
if hint.string_value.count > 0 {
print_to_builder(builder, " (@%)", hint.string_value);
}
}
if !node.array_field && node.children.count > 0 {
append(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;
Dont_Skip_Indent_On_First :: 1 << 3;
}
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 !child continue;
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 {
append(builder, ",");
}
append(builder, " ");
if flags & .NewLine {
append(builder, "\n");
}
}
}
}
op_to_string :: (oper : Token) -> string {
if oper.kind == {
case .TOKEN_PLUS;
return "+";
case .TOKEN_MINUS;
return "-";
case .TOKEN_STAR;
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;
return "=";
case .TOKEN_ISNOTEQUAL;
return "!=";
case .TOKEN_LOGICALOR;
return "||";
case .TOKEN_LOGICALAND;
return "&&";
case .TOKEN_LESS;
return "<";
case .TOKEN_LESSEQUALS;
return "<=";
case .TOKEN_GREATER;
return ">";
case .TOKEN_GREATEREQUALS;
return ">=";
}
return "";
}
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));
pretty_print_node(node.children[0], 0, 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, indentation, builder);
append(builder, ")");
}
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], 0, 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, 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, skip_indent);
}
case .FieldList; {
pretty_print_fieldlist(node, indentation + 2, builder, skip_indent);
}
case .Field; {
pretty_print_field(node, indentation, builder, skip_indent);
}
case .Unnamed_Field; {
pretty_print_field(node, indentation, builder, skip_indent);
}
case .Block; {
pretty_print_block(node, indentation, builder, skip_indent);
}
case .Binary; {
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, skip_indent);
}
case .Variable; {
pretty_print_variable(node, indentation, builder, skip_indent);
}
case .Expression_Statement; {
print_expression_statement(node, indentation, builder, skip_indent);
}
case .Integer; {
print_to_builder(builder, "%", node.integer_value, skip_indent);
}
case .Float; {
print_to_builder(builder, "%", node.float_value, skip_indent);
}
case .Call; {
pretty_print_call(node, indentation, builder, skip_indent);
}
case .Error; {
print_to_builder(builder, "(error \"%\")", node.name, skip_indent);
}
}
}
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 {
if child.kind == .Variable {
append(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);
append(builder, "]");
}
}
}
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 {
append(builder, "foreign ");
}
if declaration.kind == .Function {
append(builder, "fun ");
}
if declaration.vertex_entry_point {
append(builder, "vertex ");
}
if declaration.pixel_entry_point {
append(builder, "pixel ");
}
if declaration.kind == .If_Directive {
append(builder, "#if ");
}
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);
}
}
// if declaration.kind != .If_Directive {
// print_to_builder(builder, "%", declaration.name);
// }
}
if declaration.kind == .Function && declaration.token.kind == .TOKEN_IDENTIFIER{
print_to_builder(builder, " -> %", declaration.token.ident_value);
for hint : declaration.hint_tokens {
if hint.string_value.count > 0 {
print_to_builder(builder, " (@%)", hint.string_value);
}
}
}
if declaration.children.count > 0 {
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, ")");
}
pretty_print_ast :: (root : *AST_Node, allocator : Allocator) -> string {
builder : String_Builder;
init_string_builder(*builder,, allocator);
indentation := 0;
append(*builder, "(");
append(*builder, "program\t\n");
indentation += 1;
declarations := root.children;
for declaration : declarations {
pretty_print_declaration(declaration, indentation, *builder);
if it_index < declarations.count - 1 {
append(*builder, "\n\n");
}
}
append(*builder, ")");
return builder_to_string(*builder,, allocator);
}
#scope_file
indent :: (builder : *String_Builder, indentation : int) {
for 0..indentation - 1 {
append(builder, " ");
}
}

3
build.bat Normal file
View File

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

3
check.bat Normal file
View File

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

2686
check.jai Normal file

File diff suppressed because it is too large Load Diff

664
codegen.jai Normal file
View File

@@ -0,0 +1,664 @@
/////////////////////////////////////
//~ nbr:
//
/////////////////////////////////////
//~ nbr: Codegen TODOs
//
Output_Language :: enum {
HLSL;
GLSL; // @Incomplete
MLSL; // @Incomplete
// SPIRV; // @Incomplete: Should we do this?
}
Codegen_State :: struct {
path : string;
current_scope : Scope_Handle;
output_language : Output_Language;
builder : String_Builder;
ctx : *Compiler_Context;
}
Reserved_HLSL_Words :: string.[
"texture",
"sampler",
"matrix",
"line",
"precise",
"shared",
"triangle",
"triangleadj",
];
Reserved_MLSL_Words :: string.[
""
];
Reserved_GLSL_Words :: string.[
""
];
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);
}
indent :: (state : *Codegen_State, indentation : int) {
for 1..indentation append(*state.builder, " ");
}
hlsl_type_to_string :: (variables : []Type_Variable, type_handle : Type_Variable_Handle) -> string {
return hlsl_type_to_string(variables, from_handle(variables, type_handle));
}
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, "% ", hlsl_type_to_string(state.ctx.type_variables, field));
print_to_builder(*state.builder, "%", node.name);
if field.type == .Sampler {
print_to_builder(*state.builder, " : register(s%)", field.resource_index);
}
if field.type == .Texture2D {
print_to_builder(*state.builder, " : register(t%)", field.resource_index);
}
if node.children.count == 1 {
child := node.children[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);
}
}
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 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");
}
}
state.current_scope = previous_scope;
}
emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
indent(state, indentation);
if node.name == "sample" {
assert(node.children.count > 0);
args := node.children[0];
emit_node(state, args.children[0], 0);
append(*state.builder, ".");
print_to_builder(*state.builder, "Sample(");
for i : 1..args.children.count - 1 {
child := args.children[i];
emit_node(state, child, 0);
if i != args.children.count - 1 {
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);
if node.children.count > 0 {
args := node.children[0];
for child : args.children {
emit_node(state, child, 0);
if it_index != args.children.count - 1 {
append(*state.builder, ", ");
}
}
}
}
append(*state.builder, ")");
}
emit_function :: (state : *Codegen_State, node : *AST_Node, indentation : int, emit_body := true) {
name := get_actual_function_name(node);
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 {
message : Compiler_Message;
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.ctx.messages, message);
}
for func : find_result.functions {
function_variable := from_handle(state.ctx.type_variables, func.type_variable);
indent(state, indentation);
if function_variable.return_type_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 ");
}
print_to_builder(*state.builder, "%", node.name);
previous_scope := state.current_scope;
state.current_scope = function_variable.scope;
append(*state.builder, "(");
if node.children.count > 0 && node.children[0].kind == .FieldList {
params := node.children[0];
for child : params.children {
emit_node(state, child, 0);
if it_index != params.children.count - 1 {
append(*state.builder, ", ");
}
}
}
append(*state.builder, ")");
for hint : node.hint_tokens {
if hint.ident_value == "position" {
// @Incomplete(nb): Should be a lookup table somewhere
append(*state.builder, " : SV_POSITION");
}
if starts_with(hint.ident_value, "target") {
// @Incomplete(nb): Should be a lookup table somewhere
append(*state.builder, " : SV_TARGET");
}
}
if emit_body {
append(*state.builder, "\n{\n");
if node.children.count > 1 {
emit_block(state, node.children[1], indentation + 1);
}
append(*state.builder, "}\n");
} else {
append(*state.builder, ";");
}
append(*state.builder, "\n");
state.current_scope = previous_scope;
}
}
emit_operator :: (state : *Codegen_State, op_kind : Token_Kind) {
if op_kind == {
case .TOKEN_PLUS; {
append(*state.builder, "+");
}
case .TOKEN_MINUS; {
append(*state.builder, "-");
}
case .TOKEN_STAR; {
append(*state.builder, "*");
}
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, "==");
}
case .TOKEN_ASSIGN; {
append(*state.builder, "=");
}
case .TOKEN_ISNOTEQUAL; {
append(*state.builder, "!=");
}
case .TOKEN_LOGICALOR; {
append(*state.builder, "||");
}
case .TOKEN_LOGICALAND; {
append(*state.builder, "&&");
}
case .TOKEN_LESS; {
append(*state.builder, "<");
}
case .TOKEN_LESSEQUALS; {
append(*state.builder, "<=");
}
case .TOKEN_GREATER; {
append(*state.builder, ">");
}
case .TOKEN_GREATEREQUALS; {
append(*state.builder, ">=");
}
}
}
emit_node :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
if node.kind == {
case .Integer; {
print_to_builder(*state.builder, "%", node.integer_value);
}
case .Float; {
print_to_builder(*state.builder, "%f", formatFloat(node.float_value, zero_removal=.ONE_ZERO_AFTER_DECIMAL));
}
case .Field; {
emit_field(state, node, indentation);
}
case .Block; {
assert(false, "Not implemented yet: block");
}
case .Variable; {
indent(*state.builder, indentation);
type_var := from_handle(state.ctx.type_variables, node.type_variable);
print_to_builder(*state.builder, "%", node.name);
if node.children.count > 0 {
append(*state.builder, ".");
emit_node(state, node.children[0], 0);
}
}
case .Access; {
indent(*state.builder, indentation);
lhs := node.children[0];
rhs := node.children[1];
emit_node(state, lhs, 0);
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; {
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);
}
case .Return; {
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, "}");
}
}
emit_field_list :: (state : *Codegen_State, field_list : *AST_Node, indentation : int) {
for child : field_list.children {
emit_node(state, child, 1);
if it_index < field_list.children.count {
append(*state.builder, ";\n");
}
}
}
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 = 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];
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_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);
emit_struct(state, node, indentation, element.typename);
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) {
if node.kind == {
case .Function; {
emit_function(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 :: (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;
// 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");
// }
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);
found_function = true;
}
}
if found_function {
append(*state.builder, "\n");
}
for declaration : state.ctx.root.children {
if declaration.foreign_declaration {
continue;
}
emit_declaration(state, declaration);
}
state.ctx.codegen_result_text = builder_to_string(*state.builder);
}
#scope_module
#import "ncore";

153
error.jai Normal file
View File

@@ -0,0 +1,153 @@
Message_Kind :: enum {
Log;
Warning;
Error;
Internal_Error;
}
Compiler_Message :: struct {
message_kind : Message_Kind;
message : string;
path : string;
source_locations : [..]Source_Range;
report_source_location : bool = true;
}
indent :: (builder : *String_Builder, indentation : int) {
for 1..indentation append(builder, " ");
}
newline :: (builder : *String_Builder) {
append(builder, "\n");
}
green :: (builder : *String_Builder) {
append(builder, green());
}
green :: () -> string {
return "\x1b[92m";
}
red :: (builder : *String_Builder) {
append(builder, red());
}
red :: () -> string {
return "\x1b[91m";
}
yellow :: (builder : *String_Builder) {
append(builder, yellow());
}
yellow :: () -> string {
return "\x1b[93m";
}
cyan :: (builder : *String_Builder) {
append(builder, cyan());
}
cyan :: () -> string {
return "\x1b[96m";
}
white :: (builder : *String_Builder) {
append(builder, white());
}
white :: () -> string {
return "\x1b[97m";
}
reset_color :: () -> string {
return "\x1b[0m";
}
reset_color :: (builder : *String_Builder) {
append(builder, reset_color());
}
add_message :: (messages : *[..]Compiler_Message, text : string, path : string, kind : Message_Kind) {
message : Compiler_Message;
message.message = text;
message.path = path;
message.report_source_location = false;
message.message_kind = kind;
array_add(messages, message);
}
log_message :: (messages : *[..]Compiler_Message, text : string, path : string) {
add_message(messages, text, path, .Log);
}
warning_message :: (messages : *[..]Compiler_Message, text : string, path : string) {
add_message(messages, text, path, .Warning);
}
error_message :: (messages : *[..]Compiler_Message, text : string, path : string) {
add_message(messages, text, path, .Error);
}
internal_error_message :: (messages : *[..]Compiler_Message, text : string, path : string) {
add_message(messages, text, path, .Internal_Error);
}
copy_messages :: (source : []Compiler_Message, dest : *[..]Compiler_Message) {
for message : source {
array_add(dest, message);
}
}
report_messages :: (ctx : *Compiler_Context, messages : []Compiler_Message) -> string {
builder : String_Builder;
init_string_builder(*builder);
for message : messages {
report_message(ctx, *builder, message);
}
return builder_to_string(*builder);
}
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 :: (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);
} else {
append(builder, "internal:");
}
if source_locations.count > 0 {
print_to_builder(builder, "%,%: ", source_locations[0].main_token.line, source_locations[0].main_token.column);
}
if kind == .Log {
append(builder, "\x1b[31mlog: ");
} else if kind == .Error {
append(builder, "\x1b[31merror: ");
}
append(builder, "\x1b[37m");
print_to_builder(builder, "%\n", message);
append(builder, "\x1b[36m");
if report_source_location {
for location : source_locations {
append(builder, "\t");
print_from_source_location(ctx, builder, location);
append(builder, "\n\t");
begin := location.begin;
print_token_pointer(builder, location.main_token);
append(builder, "\n");
}
}
append(builder, "\x1b[37m");
}

96
first.jai Normal file
View File

@@ -0,0 +1,96 @@
#import "Basic";
#import "File";
#import "Compiler";
#import "Metaprogram_Plugins";
plugins: [..] *Metaprogram_Plugin;
build :: () {
w := compiler_create_workspace("Ink Build");
if !w {
print("Workspace creation failed.\n");
return;
}
EXECUTABLE_NAME :: "ink";
MAIN_FILE :: "ink.jai";
options := get_build_options(w);
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, "../.");
options.import_path = new_path;
options.output_executable_name = EXECUTABLE_NAME;
wd := get_working_directory();
set_build_options(options, 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, write_added_strings=false});
}
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();

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);
}

802
lexing.jai Normal file
View File

@@ -0,0 +1,802 @@
Lexer :: struct {
input : string;
cursor : int;
start : int;
current_line : int;
current_column : int;
ctx : *Compiler_Context;
path : string;
}
Token_Kind :: enum {
TOKEN_INVALID :: 0;
TOKEN_FLOATLITERAL;
TOKEN_INTLITERAL;
TOKEN_LOGICALOR;
TOKEN_LOGICALAND;
TOKEN_ISEQUAL;
TOKEN_ISNOTEQUAL;
TOKEN_PLUSEQUALS;
TOKEN_MINUSEQUALS;
TOKEN_TIMESEQUALS;
TOKEN_DIVEQUALS;
TOKEN_MODEQUALS;
TOKEN_LESSEQUALS;
TOKEN_LESS;
TOKEN_GREATEREQUALS;
TOKEN_GREATER;
TOKEN_COLON;
TOKEN_DOUBLECOLON;
TOKEN_ASSIGN;
TOKEN_ARROW;
TOKEN_AT;
TOKEN_PLUS;
TOKEN_STAR;
TOKEN_SLASH;
TOKEN_MOD;
TOKEN_MINUS;
TOKEN_LEFTBRACE;
TOKEN_RIGHTBRACE;
TOKEN_LEFTBRACKET;
TOKEN_RIGHTBRACKET;
TOKEN_LEFTPAREN;
TOKEN_RIGHTPAREN;
TOKEN_SEMICOLON;
TOKEN_COMMA;
TOKEN_DOT;
TOKEN_DOTDOT;
TOKEN_IDENTIFIER;
// Keywords
TOKEN_BOOL;
TOKEN_BUFFER;
TOKEN_CASE;
TOKEN_CBUFFER;
TOKEN_COLUMNMAJOR;
TOKEN_CONST;
TOKEN_CONSTANT_BUFFER;
TOKEN_CONTINUE;
TOKEN_DEFAULT;
TOKEN_DIRECTIVE;
TOKEN_DISCARD;
TOKEN_DO;
TOKEN_DOUBLE;
TOKEN_ELSE;
TOKEN_EXPORT;
TOKEN_EXTERN;
TOKEN_FALSE;
TOKEN_FOR;
TOKEN_HALF;
TOKEN_HINT;
TOKEN_IF;
TOKEN_IN;
TOKEN_INOUT;
TOKEN_INSTANCE;
TOKEN_MATRIX;
TOKEN_META;
TOKEN_OPTIONAL;
TOKEN_OUT;
TOKEN_PIXEL;
TOKEN_PLEX;
TOKEN_RETURN;
TOKEN_REGISTER;
TOKEN_STRING;
TOKEN_STRUCT;
TOKEN_SWITCH;
TOKEN_TRUE;
TOKEN_UNORM;
TOKEN_UNSIGNED;
TOKEN_UINT;
TOKEN_VECTOR;
TOKEN_VERTEX;
TOKEN_VOID;
TOKEN_WHILE;
TOKEN_EOF;
TOKEN_ERROR;
}
Token :: struct {
kind : Token_Kind;
union {
ident_value : string;
integer_value : int;
float_value : float;
string_value : string;
}
source : *u8;
// This could all be derived on demand
line : int;
length : 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 {
begin : Token;
end : Token;
main_token : Token;
}
is_at_end :: (using lexer : *Lexer) -> bool {
return input.data[cursor] == #char "\0" || cursor == input.count;
}
peek_char :: (using lexer : *Lexer) -> u8 {
return input.data[cursor];
}
peek_next_char :: (using lexer : *Lexer) -> u8 {
if is_at_end(lexer) return #char "\0";
return input.data[cursor + 1];
}
match_character :: (lexer : *Lexer, expected : u8) -> bool {
if is_at_end(lexer) return false;
if lexer.input.data[lexer.cursor] != expected return false;
lexer.cursor += 1;
return true;
}
identifier :: (lexer : *Lexer) -> *Token {
while is_alpha(peek_char(lexer)) || is_digit(peek_char(lexer)) || peek_char(lexer) == #char "_" {
advance(lexer);
}
return make_identifier(lexer, identifier_kind(lexer));
}
directive :: (lexer : *Lexer) -> *Token {
advance(lexer);
while is_alpha(peek_char(lexer)) || is_digit(peek_char(lexer)) || peek_char(lexer) == #char "_" {
advance(lexer);
}
return make_directive(lexer);
}
number :: (lexer : *Lexer) -> *Token {
while is_digit(peek_char(lexer)) advance(lexer);
is_float := false;
if peek_char(lexer) == #char "." && is_digit(peek_next_char(lexer)) {
is_float = true;
advance(lexer);
f_suffix := false;
while is_digit(peek_char(lexer)) {
advance(lexer);
}
if peek_char(lexer) == #char "f" {
advance(lexer);
record_error(lexer, "We don't use 'f' suffixes for floating point values.");
return null;
}
}
if is_float {
return make_float(lexer);
}
return make_int(lexer);
}
identifier_kind :: (using lexer : *Lexer) -> Token_Kind {
length := cursor - lexer.start;
index := start;
identifier : string;
identifier.data = *input.data[start];
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 == "continue" return .TOKEN_CONTINUE;
if identifier == "default" return .TOKEN_DEFAULT;
if identifier == "directive" return .TOKEN_DIRECTIVE;
if identifier == "discard" return .TOKEN_DIRECTIVE;
if identifier == "discard" return .TOKEN_DISCARD;
if identifier == "do" return .TOKEN_DO;
if identifier == "double" return .TOKEN_DOUBLE;
if identifier == "else" return .TOKEN_ELSE;
if identifier == "export" return .TOKEN_EXPORT;
if identifier == "extern" return .TOKEN_EXTERN;
if identifier == "false" return .TOKEN_FALSE;
if identifier == "for" return .TOKEN_FOR;
if identifier == "half" return .TOKEN_HALF;
if identifier == "hint" return .TOKEN_HINT;
if identifier == "if" return .TOKEN_IF;
if identifier == "in" return .TOKEN_IN;
if identifier == "inout" return .TOKEN_INOUT;
if identifier == "instance" return .TOKEN_INSTANCE;
if identifier == "matrix" return .TOKEN_MATRIX;
if identifier == "meta" return .TOKEN_META;
if identifier == "optional" return .TOKEN_OPTIONAL;
if identifier == "out" return .TOKEN_OUT;
if identifier == "pixel" return .TOKEN_PIXEL;
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;
if identifier == "unsigned" return .TOKEN_UNSIGNED;
if identifier == "uint" return .TOKEN_UINT;
if identifier == "vector" return .TOKEN_VECTOR;
if identifier == "vertex" return .TOKEN_VERTEX;
if identifier == "void" return .TOKEN_VOID;
if identifier == "while" return .TOKEN_WHILE;
return .TOKEN_IDENTIFIER;
}
error_token :: (lexer : *Lexer, message : string) -> *Token {
token : *Token = new_token(lexer, .TOKEN_ERROR);
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;
error.message = message;
error.path = lexer.path;
token := error_token(lexer, message);
source_location : Source_Range;
source_location.main_token = token;
token.length += token.column;
token.source -= token.column;
token.column = 0;
source_location.begin = token;
length := source_location.begin.column;
source_location.end = token;
array_add(*error.source_locations, source_location);
lexer.ctx.had_error = true;
array_add(*lexer.ctx.messages, error);
}
make_int :: (lexer : *Lexer) -> *Token {
token : *Token = new_token(lexer, .TOKEN_INTLITERAL);
str : string = .{ count = token.length,
data = *lexer.input.data[lexer.start] };
value, ok := string_to_int(str);
if ok {
token.integer_value = value;
}
return token;
}
make_float :: (lexer : *Lexer) -> *Token {
token : *Token = new_token(lexer, .TOKEN_FLOATLITERAL);
str : string = .{ count = token.length,
data = *lexer.input.data[lexer.start] };
value, ok := string_to_float(str);
if ok {
token.float_value = value;
}
return token;
}
new_token :: (lexer : *Lexer, kind : Token_Kind) -> *Token {
length := lexer.cursor - lexer.start;
token : Token;
token.kind = kind;
token.line = lexer.current_line;
token.length = length;
token.column = lexer.current_column;
token.index = lexer.cursor - token.length;
if token.length > 0 {
token.source = *lexer.input[token.index];
} else {
token.source = *lexer.input[token.index - 1];
}
lexer.current_column += length;
array_add(*lexer.ctx.tokens, token);
return *lexer.ctx.tokens[lexer.ctx.tokens.count - 1];
}
make_directive :: (lexer : *Lexer) -> *Token {
lexer.start += 1;
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 {
token : *Token = new_token(lexer, kind);
name : string = .{ count = token.length,
data = *lexer.input.data[lexer.start] };
token.ident_value = name;
return token;
}
make_token :: (lexer : *Lexer, token_kind : Token_Kind) -> *Token {
return new_token(lexer, token_kind);
}
skip_whitespace :: (lexer : *Lexer) {
while true {
if is_at_end(lexer) return;
c := peek_char(lexer);
if c == {
case #char " "; {
lexer.current_column += 1;
advance(lexer);
continue;
}
case #char "\r"; #through;
case #char "\t"; {
advance(lexer);
continue;
}
case #char "\n"; {
advance(lexer);
lexer.current_line += 1;
lexer.current_column = 0;
continue;
}
case #char "/"; {
next := peek_next_char(lexer);
if next == #char "/" {
while peek_char(lexer) != #char "\n" && !is_at_end(lexer) {
advance(lexer);
}
continue;
} else {
return;
}
}
}
return;
}
}
advance :: (using lexer : *Lexer) -> u8 {
c := input.data[cursor];
cursor += 1;
return c;
}
scan_next_token :: (lexer : *Lexer) -> *Token {
skip_whitespace(lexer);
lexer.start = lexer.cursor;
if is_at_end(lexer) return make_token(lexer, .TOKEN_EOF);
c := advance(lexer);
if c == #char "#" return directive(lexer);
if is_alpha(c) return identifier(lexer);
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);
}
case #char "-"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_MINUSEQUALS);
if match_character(lexer, #char ">") return make_token(lexer, .TOKEN_ARROW);
return make_token(lexer, .TOKEN_MINUS);
}
case #char "*"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_TIMESEQUALS);
return make_token(lexer, .TOKEN_STAR);
}
case #char "/"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_DIVEQUALS);
return make_token(lexer, .TOKEN_SLASH);
}
case #char "%"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_MODEQUALS);
return make_token(lexer, .TOKEN_MOD);
}
case #char ":"; {
if match_character(lexer, #char ":") return make_token(lexer, .TOKEN_DOUBLECOLON);
return make_token(lexer, .TOKEN_COLON);
}
case #char "@"; {
return make_token(lexer, .TOKEN_AT);
}
case #char "|"; {
if match_character(lexer, #char "|") return make_token(lexer, .TOKEN_LOGICALOR);
}
case #char "&"; {
if match_character(lexer, #char "&") return make_token(lexer, .TOKEN_LOGICALAND);
}
case #char "!"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_ISNOTEQUAL);
}
case #char "="; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_ISEQUAL);
return make_token(lexer, .TOKEN_ASSIGN);
}
case #char ">"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_GREATEREQUALS);
return make_token(lexer, .TOKEN_GREATER);
}
case #char "<"; {
if match_character(lexer, #char "=") return make_token(lexer, .TOKEN_LESSEQUALS);
return make_token(lexer, .TOKEN_LESS);
}
case #char "{"; {
return make_token(lexer, .TOKEN_LEFTBRACE);
}
case #char "}"; {
return make_token(lexer, .TOKEN_RIGHTBRACE);
}
case #char "("; {
return make_token(lexer, .TOKEN_LEFTPAREN);
}
case #char ")"; {
return make_token(lexer, .TOKEN_RIGHTPAREN);
}
case #char "["; {
return make_token(lexer, .TOKEN_LEFTBRACKET);
}
case #char "]"; {
return make_token(lexer, .TOKEN_RIGHTBRACKET);
}
case #char ";"; return make_token(lexer, .TOKEN_SEMICOLON);
case #char ","; return make_token(lexer, .TOKEN_COMMA);
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 };
record_error(lexer, tprint("Invalid token: %", s));
return null;
// return error_token(lexer, tprint("Invalid token: %", s));
}
lex :: (ctx : *Compiler_Context, allocator := temp) {
if ctx.had_error {
return;
}
new_context := context;
new_context.allocator = allocator;
push_context new_context {
init_context_allocators();
defer clear_context_allocators();
lexer : Lexer;
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);
}
}
}
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.ctx.had_error = true;
}
}
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.ctx.had_error = true;
}
}
read_input_from_string :: (lexer : *Lexer, input : string) -> bool {
lexer.input = input;
lexer.cursor = 0;
lexer.start = 0;
lexer.current_line = 1;
lexer.current_column = 0;
return true;
}
read_input_from_file :: (lexer : *Lexer, file_path : string) -> bool {
assert(file_path != "");
value, success := read_entire_file(file_path, true, true);
if !success {
free(value);
return false;
}
lexer.path = copy_string(file_path);
lexer.input = value;
lexer.cursor = 0;
lexer.start = 0;
lexer.current_line = 1;
lexer.current_column = 0;
return true;
}
// ===========================================================
// Pretty printing
pretty_print_token :: (token : *Token, builder : *String_Builder) {
MAX :: 21;
kind_name := enum_names(Token_Kind)[cast(int)token.kind];
diff := MAX - kind_name.count;
print_to_builder(builder, "{kind = %; ", token.kind);
for i : 0..diff - 1 {
append(builder, " ");
}
append_to_length :: (builder : *String_Builder, number : int) {
if number < 10 {
append(builder, " ");
} else if number < 100 {
append(builder, " ");
} else if number < 1000 {
append(builder, " ");
} else if number < 10000 {
append(builder, " ");
}
}
print_to_builder(builder, "; index = %", token.index);
append_to_length(builder, token.index);
print_to_builder(builder, "; length = %", token.length);
append_to_length(builder, token.length);
print_to_builder(builder, "line = %", token.line);
append_to_length(builder, token.line);
print_to_builder(builder, "; column = %", token.column);
append_to_length(builder, token.column);
append(builder, "; value ='");
value_length : int;
if token.kind == .TOKEN_IDENTIFIER {
print_to_builder(builder, "%", token.ident_value);
} else if token.kind == .TOKEN_INTLITERAL {
print_to_builder(builder, "%", token.integer_value);
} else if token.kind == .TOKEN_FLOATLITERAL {
print_to_builder(builder, "%", token.float_value);
} else if token.kind == .TOKEN_ERROR {
print_to_builder(builder, "%", token.error);
} else {
source : string = .{ count = token.length,
data = token.source };
print_to_builder(builder, "%", source);
}
append(builder, "'; }\n");
}
pretty_print_tokens :: (lexer : *Lexer, allocator : Allocator) -> string {
builder : String_Builder;
init_string_builder(*builder,, allocator);
token : *Token = scan_next_token(lexer);
while token && token.kind != .TOKEN_EOF {
pretty_print_token(token, *builder);
token = scan_next_token(lexer);
}
return builder_to_string(*builder,, allocator);
}
pretty_print_tokens :: (tokens : []Token, allocator : Allocator) -> string {
builder : String_Builder;
init_string_builder(*builder,, allocator);
for token : tokens {
pretty_print_token(*token, *builder);
}
return builder_to_string(*builder,, allocator);
}
output_as_code_string :: (lexer : *Lexer, allocator : *Allocator) -> string {
builder : String_Builder;
new_context := context;
new_context.allocator = allocator;
push_context new_context {
init_string_builder(*builder); // @Incomplete: Consider passing builder as argument
token : *Token = scan_next_token(lexer);
while token && token.kind != .TOKEN_EOF {
token = scan_next_token(lexer);
}
return builder_to_string(*builder);
}
}
print_token_pointer :: (builder : *String_Builder, token : Token) {
for i : 0..token.column - 1 {
append(builder, " ");
}
for i : 0..token.length - 1 {
append(builder, "^");
}
}
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;
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 {
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 :: (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,, sc.allocator);
print_from_source_location(ctx, *builder, source_location,, sc.allocator);
return builder_to_string(*builder,, allocator);
}
#import "Basic";
#import "File";

519
module.jai Normal file
View File

@@ -0,0 +1,519 @@
#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 {
if define == key {
return;
}
}
array_add(*env.defines, key);
}
remove_define :: (env : *Environment, key : string) {
for define : env.defines {
if define == key {
env.defines[it_index] = env.defines[env.defines.count - 1];
}
}
}
Environment :: struct {
defines : [..]string;
}
Field_Kind :: enum {
Int :: 0;
Half :: 1;
Float :: 2;
Double :: 3;
Texture2D :: 8;
Sampler :: 9;
Function;
Struct;
Array;
}
Field_Type :: struct {
kind : Field_Kind;
name : string; //@Note(niels): for structs
children : [..]Field;
}
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;
target_index : int;
custom_hint_name : string;
}
Field :: struct {
name : string;
type : Field_Type;
resource_index : u32;
hints : [..]Field_Hint;
}
Entry_Point :: struct {
name : string;
function_input : [..]Field;
return_value : Field;
}
Buffer_Kind :: enum {
Constant;
Structured;
}
Buffer :: struct {
kind : Buffer_Kind;
name : string;
fields : Static_Array(Field, 16);
hints : [..]Field_Hint;
buffer_index : u32;
}
Input_File :: struct {
source : string;
path : string;
}
Compiler_Context :: struct {
file : Input_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;
}
#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(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 = source;
input_file.path = path;
return input_file;
}
pretty_print_field :: (field : *Field) -> string {
builder : String_Builder;
init_string_builder(*builder,, temp);
pretty_print_field(*builder, field);
return builder_to_string(*builder);
}
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, ": ");
} else {
append(builder, "return - ");
}
type := field.type;
if type.kind == {
case .Int; {
append(builder, "int");
}
case .Half; {
append(builder, "half");
}
case .Float; {
append(builder, "float");
}
case .Double; {
append(builder, "double");
}
case .Texture2D; {
append(builder, "texture2D");
}
case .Sampler; {
append(builder, "sampler");
}
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, ", ");
}
if it_index % newline_after == 0 {
append(builder, "\n");
indent(builder, 4);
}
}
append(builder, "} ");
}
case .Array; {
}
}
for hint : field.hints {
if hint.kind == {
case .Position; {
append(builder, "(@position)");
}
case .Target; {
print_to_builder(builder, "(@target%)", hint.target_index);
}
case .Custom; {
print_to_builder(builder, "(@%)", hint.custom_hint_name);
}
}
if it_index != field.hints.count - 1 {
append(builder, ", ");
}
}
}
type_variable_to_field :: (ctx : *Compiler_Context, variable : *Type_Variable) -> Field {
field : Field;
field.name = variable.name;
type : Field_Type;
if variable.type == {
case .Int; {
type.kind = Field_Kind.Int;
}
case .Half; {
type.kind = Field_Kind.Half;
}
case .Float; {
type.kind = Field_Kind.Float;
}
case .Double; {
type.kind = Field_Kind.Double;
}
case .Texture2D; {
type.kind = Field_Kind.Texture2D;
field.resource_index = variable.resource_index;
}
case .Sampler; {
type.kind = Field_Kind.Sampler;
field.resource_index = variable.resource_index;
}
case .Struct; {
type.kind = Field_Kind.Struct;
find_result := find_symbol(ctx.scope_stack, variable.typename, xx 1);
assert(find_result != null, "Internal compiler error\n");
type_var := from_handle(ctx.type_variables, find_result.type_variable);
for i : 0..type_var.children.count - 1 {
child := type_var.children[i];
child_field := type_variable_to_field(ctx, child);
array_add(*type.children, child_field);
}
type.name = variable.typename;
}
}
for hint : variable.source_node.hint_tokens {
field_hint : Field_Hint;
if lookup_hint(hint.ident_value) == .Position {
field_hint.kind = .Position;
} 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;
result, ok, remainder := string_to_int(index_str);
if ok {
field_hint.target_index = result;
}
field_hint.kind = .Target;
} else {
field_hint.custom_hint_name = hint.ident_value;
field_hint.kind = .Custom;
}
array_add(*field.hints, field_hint);
}
field.type = type;
return field;
}
type_variable_to_field :: (ctx : *Compiler_Context, variable : Type_Variable_Handle) -> Field {
return type_variable_to_field(ctx, from_handle(ctx.type_variables, variable));
}
generate_buffer :: (ctx : *Compiler_Context, type_handle : Type_Variable_Handle, buffers : *Static_Array) {
variable := from_handle(ctx.type_variables, type_handle);
buffer := array_add(buffers);
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);
}
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;
}
if ctx.vertex_entry_point.node {
ctx.vertex_entry_point.name = ctx.vertex_entry_point.node.name;
type_variable := from_handle(ctx.type_variables, ctx.vertex_entry_point.node.type_variable);
assert(type_variable.type == .Function);
node := type_variable.source_node;
if node.children.count > 0 {
if node.children[0].kind == .FieldList {
field_list := node.children[0];
for child : field_list.children {
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 : ctx.typed_buffers {
generate_buffer(ctx, buffer_variable, *ctx.buffers);
}
if ctx.pixel_entry_point.node {
ctx.pixel_entry_point.name = ctx.pixel_entry_point.node.name;
type_variable := from_handle(ctx.type_variables, ctx.pixel_entry_point.node.type_variable);
assert(type_variable.type == .Function);
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 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;
}
field_hint.kind = .Target;
} else {
// @Incomplete(nb): custom hints
}
array_add(*field.hints, field_hint);
}
ctx.pixel_entry_point.return_value = field;
}
}
}
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);
}
}

1493
parsing.jai Normal file

File diff suppressed because it is too large Load Diff

BIN
shader_parsing_session.rdbg Normal file

Binary file not shown.

12
test/all.suite Normal file
View File

@@ -0,0 +1,12 @@
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;
}

6
test/arrays.ink Normal file
View File

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

View File

@@ -0,0 +1,3 @@
vertex main :: () {
x : float = 2.0 + 5.0;
}

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

@@ -0,0 +1,11 @@
properties :: Constant_Buffer @properties {
color : float4;
}
vertex main :: (pos : float3 @position) -> float3 @position {
return pos;
}
pixel main :: () -> float4 @target0 {
return properties.color;
}

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 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[x] : float
]
]

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,12 @@
scope (global) [
[pixel__ps_main] : () -> float4
[vertex__vs_main] : (pos : float3) -> float3
[properties] : {color : float4}
scope (properties) [
[color] : float4
]
scope (vertex__vs_main) [
[pos] : float3
]
scope (pixel__ps_main) []
]

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,8 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[x] : float
[z] : float
[y] : float
]
]

View File

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

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
test/field_without_type_specifier.shd:2,0: error: Expected type specifier after field name.
x := 5.0;
^


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,4 @@
test/float_suffix.shd:2,12: error: We don't use 'f' suffixes for floating point values.
 x : float = 2.0f
^^^^


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,6 @@
scope (global) [
[foo] : () -> int
[vertex__vs_main] : ()
scope (foo) []
scope (vertex__vs_main) []
]

View File

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

View File

@@ -0,0 +1,6 @@
scope (global) [
[pixel__ps_main] : () -> float4
[vertex__vs_main] : ()
scope (vertex__vs_main) []
scope (pixel__ps_main) []
]

View File

@@ -0,0 +1,6 @@
scope (global) [
[vertex__vs_main] : (pos : float3) -> int
scope (vertex__vs_main) [
[pos] : float3
]
]

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,13 @@
scope (global) [
[pixel__ps_main] : () -> float4
[vertex__vs_main] : (pos : float3, uv : float2) -> float3
[properties] : properties
scope (properties) [
[color] : float4
]
scope (vertex__vs_main) [
[pos] : float3
[uv] : float2
]
scope (pixel__ps_main) []
]

View File

@@ -0,0 +1,11 @@
scope (global) [
[foo] : () -> int
[vertex__vs_main] : ()
[bar] : () -> float
scope (foo) []
scope (bar) []
scope (vertex__vs_main) [
[x] : int
[y] : float
]
]

View File

@@ -0,0 +1,13 @@
scope (global) [
[pixel__ps_main] : () -> float4
[foo] : () -> float4
[vertex__vs_main] : (pos : float3) -> float3
scope (vertex__vs_main) [
[pos] : float3
]
scope (foo) []
scope (pixel__ps_main) [
[y] : float4
[color] : float4
]
]

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

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

View File

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

View File

@@ -0,0 +1,9 @@
scope (global) [
[vertex__vs_main] : (x : float, y : float, z : float, w : float)
scope (vertex__vs_main) [
[x] : float
[z] : float
[y] : float
[w] : 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

@@ -0,0 +1,11 @@
scope (global) [
[Data] : {color : float4}
[vertex__vs_main] : ()
scope (Data) [
[color] : float4
]
scope (vertex__vs_main) [
[x] : float4
[d] : Data
]
]

View File

@@ -0,0 +1,6 @@
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

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

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_function_name.shd:1,0: error: Invalid function name 'int'
 int :: () {
^^^


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

@@ -0,0 +1,33 @@
test/unknown_overload.ink:6,0: error: Procedure call did not match any of the possible overloads for 'foo'
 found:
foo(v, v);
^^^
 While matching argument 1 in function call.
 foo(v, v);
^
 Possible overloads:
 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.ink:6,4: error: Type mismatch. Expected float3 got float
 found:
foo(v, v);
^
expected:
float3
got:
v : float = 2.0
test/unknown_overload.ink:6,7: error: Type mismatch. Expected float3 got float
 found:
foo(v, v);
^
expected:
float3
got:
v : float = 2.0


View File

@@ -0,0 +1,6 @@
scope (global) [
[vertex__vs_main] : ()
scope (vertex__vs_main) [
[f] : float4
]
]

View File

@@ -0,0 +1,16 @@
test/wrong_argument_count.ink:5,19: error: Use of undeclared symbol 'w'
 return x * y * z * w;
^
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.ink:1)
 Not enough arguments: Wanted 3, got 2.
 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

@@ -0,0 +1,30 @@
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);
^^^^^^
 While matching argument 1 in function call.
 color : float4 = float4(y, 1.0, 1.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_type_for_function.ink:11,24: error: Type mismatch. Expected float got float2
 found:
color : float4 = float4(y, 1.0, 1.0, 1.0);
^
expected:
float
got:
y : float2 = foo()


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

@@ -0,0 +1,5 @@
void vs_main()
{
float x = 2.0f + 5.0f;
}

View File

@@ -0,0 +1,15 @@
cbuffer properties : register(b0)
{
float4 color;
}
float3 vs_main(float3 pos : POSITION) : SV_POSITION
{
return pos;
}
float4 ps_main() : SV_TARGET
{
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

@@ -0,0 +1,7 @@
void vs_main()
{
float x = 5.0f;
float y = 3000.0f;
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

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

View File

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

View File

@@ -0,0 +1,5 @@
float3 vs_main(float3 pos : POSITION)
{
return pos;
}

View File

@@ -0,0 +1,7 @@
float4 vs_main(float4 pos : POSITION) : SV_POSITION
{
float x = 5.0f;
x = 7.0f;
return pos;
}

View File

@@ -0,0 +1,12 @@
int foo();
int foo()
{
return 4;
}
void vs_main()
{
foo();
}

View File

@@ -0,0 +1,11 @@
void foo();
void vs_main()
{
foo();
}
void foo()
{
}

View File

@@ -0,0 +1,9 @@
void vs_main()
{
}
float4 ps_main() : SV_TARGET
{
return float4(1, 1, 1, 1);
}

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()
{
}

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