diff --git a/Ghost.Core/Ghost.Core.csproj b/Ghost.Core/Ghost.Core.csproj
index c9aebd0..76b3753 100644
--- a/Ghost.Core/Ghost.Core.csproj
+++ b/Ghost.Core/Ghost.Core.csproj
@@ -26,6 +26,7 @@
+
diff --git a/Ghost.Core/Graphics/ShaderDescriptor.cs b/Ghost.Core/Graphics/ShaderDescriptor.cs
index d24b414..ff99e97 100644
--- a/Ghost.Core/Graphics/ShaderDescriptor.cs
+++ b/Ghost.Core/Graphics/ShaderDescriptor.cs
@@ -52,6 +52,7 @@ public struct PassDescriptor
public string[] includes;
public KeywordsGroup[] keywords;
public PipelineState localPipeline;
+ public string? hlsl;
}
public class ShaderDescriptor
@@ -61,6 +62,7 @@ public class ShaderDescriptor
public PropertyDescriptor[] globalProperties = null!;
public PropertyDescriptor[] properties = null!;
public PassDescriptor[] passes = null!;
+ public string? hlsl;
}
public static class ShaderDescriptorExtensions
diff --git a/Ghost.Core/Utilities/CollectionPool.cs b/Ghost.Core/Utilities/CollectionPool.cs
new file mode 100644
index 0000000..b388bdf
--- /dev/null
+++ b/Ghost.Core/Utilities/CollectionPool.cs
@@ -0,0 +1,22 @@
+using Misaki.HighPerformance.Buffer;
+
+namespace Ghost.Core.Utilities;
+
+public class CollectionPool
+ where TCollection : class, ICollection, new()
+{
+ internal static readonly ObjectPool s_pool = new ObjectPool(() => new TCollection(), 1);
+
+ public static TCollection Rent()
+ {
+ return s_pool.Rent();
+ }
+
+ public static void Return(TCollection collection)
+ {
+ collection.Clear();
+ s_pool.Return(collection);
+ }
+}
+
+public class ListPool : CollectionPool, T>;
\ No newline at end of file
diff --git a/Ghost.DSL/Ghost.DSL.csproj b/Ghost.DSL/Ghost.DSL.csproj
index 755478c..17e40af 100644
--- a/Ghost.DSL/Ghost.DSL.csproj
+++ b/Ghost.DSL/Ghost.DSL.csproj
@@ -7,11 +7,21 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+ MSBuild:Compile
+ false
+ true
+
+
+ MSBuild:Compile
+ false
+ true
+
diff --git a/Ghost.DSL/Grammar/GhostShaderLexer.g4 b/Ghost.DSL/Grammar/GhostShaderLexer.g4
new file mode 100644
index 0000000..2f4f714
--- /dev/null
+++ b/Ghost.DSL/Grammar/GhostShaderLexer.g4
@@ -0,0 +1,33 @@
+lexer grammar GhostShaderLexer;
+
+// Keywords
+SHADER: 'shader';
+PROPERTIES: 'properties';
+PIPELINE: 'pipeline';
+PASS: 'pass';
+DEFINES: 'defines';
+KEYWORDS: 'keywords';
+INCLUDES: 'includes';
+GLOBAL: 'global';
+LOCAL: 'local';
+HLSL: 'hlsl';
+
+// Punctuation
+LBRACE: '{';
+RBRACE: '}';
+LPAREN: '(';
+RPAREN: ')';
+SEMICOLON: ';';
+COMMA: ',';
+EQUALS: '=';
+COLON: ':';
+
+// Literals
+STRING_LITERAL: '"' (~["\r\n] | '\\' .)* '"';
+NUMBER: [0-9]+ ('.' [0-9]+)? | '.' [0-9]+;
+IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
+
+// Whitespace and Comments
+WS: [ \t\r\n]+ -> skip;
+LINE_COMMENT: '//' ~[\r\n]* -> skip;
+BLOCK_COMMENT: '/*' .*? '*/' -> skip;
diff --git a/Ghost.DSL/Grammar/GhostShaderParser.g4 b/Ghost.DSL/Grammar/GhostShaderParser.g4
new file mode 100644
index 0000000..5a26f98
--- /dev/null
+++ b/Ghost.DSL/Grammar/GhostShaderParser.g4
@@ -0,0 +1,94 @@
+parser grammar GhostShaderParser;
+
+options {
+ tokenVocab = GhostShaderLexer;
+}
+
+// Top-level rule
+shaderFile: shader+ EOF;
+
+shader:
+ SHADER STRING_LITERAL LBRACE
+ shaderBody
+ RBRACE;
+
+shaderBody:
+ (propertiesBlock | pipelineBlock | passBlock | functionCall)*;
+
+// Properties block
+propertiesBlock:
+ PROPERTIES LBRACE
+ propertyDeclaration*
+ RBRACE;
+
+propertyDeclaration:
+ scope? IDENTIFIER IDENTIFIER (EQUALS LBRACE propertyInitializer RBRACE)? SEMICOLON;
+
+scope:
+ GLOBAL | LOCAL;
+
+propertyInitializer:
+ (NUMBER | IDENTIFIER) (COMMA (NUMBER | IDENTIFIER))*;
+
+// Pipeline block
+pipelineBlock:
+ PIPELINE LBRACE
+ pipelineStatement*
+ RBRACE;
+
+pipelineStatement:
+ IDENTIFIER EQUALS IDENTIFIER SEMICOLON;
+
+// Pass block
+passBlock:
+ PASS STRING_LITERAL LBRACE
+ passBody
+ RBRACE;
+
+// Template
+passBody:
+ (definesBlock | includesBlock | keywordsBlock | pipelineBlock | hlslBlock | shaderEntry)*;
+
+definesBlock:
+ DEFINES LBRACE
+ defineStatement*
+ RBRACE;
+
+defineStatement:
+ IDENTIFIER SEMICOLON;
+
+includesBlock:
+ INCLUDES LBRACE
+ includeStatement*
+ RBRACE;
+
+includeStatement:
+ STRING_LITERAL SEMICOLON;
+
+keywordsBlock:
+ KEYWORDS LBRACE
+ keywordStatement*
+ RBRACE;
+
+keywordStatement:
+ scope? IDENTIFIER (COMMA IDENTIFIER)* SEMICOLON;
+
+hlslBlock:
+ HLSL LBRACE
+ hlslCode
+ RBRACE;
+
+hlslCode:
+ .*? ; // Capture everything inside hlsl block
+
+shaderEntry:
+ IDENTIFIER STRING_LITERAL COLON STRING_LITERAL SEMICOLON;
+
+functionCall:
+ IDENTIFIER LPAREN functionArguments? RPAREN SEMICOLON;
+
+functionArguments:
+ functionArgument (COMMA functionArgument)*;
+
+functionArgument:
+ STRING_LITERAL | NUMBER | IDENTIFIER;
diff --git a/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs b/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
index edba659..75626c7 100644
--- a/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
+++ b/Ghost.DSL/ShaderCompiler/DSLShaderCompiler.cs
@@ -1,6 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
-using Ghost.DSL.ShaderCompiler.Parser;
+using Ghost.DSL.ShaderParser;
using System.Text;
namespace Ghost.DSL.ShaderCompiler;
@@ -22,107 +22,6 @@ internal static class DSLShaderCompiler
private const string _GLOBAL_PROPERTY_FILE_NAME = "GlobalData.g.hlsl";
private const string _GENERATED_FILE_HEADER = "// Auto-generated shader file. Please do not edit this file directly.";
- // private struct ShaderInheritance
- // {
- // public DSLShaderSemantics? parent;
- // public List? children;
- // }
-
- public static List ParseShaders(TokenStream stream)
- {
- var shaders = new List();
-
- while (stream.TryPeek(out var nextToken))
- {
- if (ShaderBlock.ShouldEnter(nextToken))
- {
- var shader = ShaderBlock.Parse(stream.SliceNextBlock());
- shaders.Add(shader);
- }
- else if (nextToken.Match(TokenType.EndOfFile))
- {
- stream.Consume();
- }
- else
- {
- throw new Exception($"Unexpected token '{nextToken.lexeme}' at top level. Expected 'shader' declaration.");
- }
- }
-
- return shaders;
- }
-
- public static DSLShaderSemantics? SemanticAnalysis(DSLShaderSyntax syntax, out List errors)
- {
- errors = new List();
-
- if (string.IsNullOrWhiteSpace(syntax.name.lexeme))
- {
- errors.Add(new DSLShaderError
- {
- message = "Shader name cannot be empty.",
- line = syntax.name.line,
- column = syntax.name.column
- });
-
- return null;
- }
-
- var shaderModel = ShaderBlock.SemanticAnalysis(syntax, errors);
- return shaderModel;
- }
-
- private static List? TopologicalSort(ReadOnlySpan semantics)
- {
- var inDegrees = new Dictionary();
- var childrenMap = new Dictionary>();
- var semanticsMap = new Dictionary();
-
- foreach (var s in semantics)
- {
- inDegrees[s.name] = 0;
- childrenMap[s.name] = new List();
- semanticsMap[s.name] = s;
- }
-
- foreach (var s in semantics)
- {
- if (!string.IsNullOrEmpty(s.fallback) && semanticsMap.ContainsKey(s.fallback))
- {
- childrenMap[s.fallback].Add(s.name);
- inDegrees[s.name]++;
- }
- }
-
- var queue = new Queue();
- foreach (var s in semantics)
- {
- if (inDegrees[s.name] == 0)
- {
- queue.Enqueue(s);
- }
- }
-
- var sortedList = new List();
- while (queue.Count > 0)
- {
- var current = queue.Dequeue();
- sortedList.Add(current);
-
- foreach (var childName in childrenMap[current.name])
- {
- inDegrees[childName]--;
- if (inDegrees[childName] == 0)
- {
- queue.Enqueue(semanticsMap[childName]);
- }
- }
- }
-
- // If there's a cycle, the graph will not be fully traversed.
- return sortedList.Count == semantics.Length ? sortedList : null;
- }
-
private static string GetPassUniqueId(DSLShaderSemantics shader, PassSemantic pass)
{
return $"{shader.name}_{pass.name}";
@@ -175,7 +74,8 @@ internal static class DSLShaderCompiler
{
var descriptor = new ShaderDescriptor
{
- name = semantics.name
+ name = semantics.name,
+ hlsl = semantics.hlsl
};
var shaderGlobalProperties = semantics.properties?
@@ -217,7 +117,8 @@ internal static class DSLShaderCompiler
localPipeline = localPipeline,
defines = pass.defines?.ToArray() ?? Array.Empty(),
includes = pass.includes?.ToArray() ?? Array.Empty(),
- keywords = pass.keywords?.ToArray() ?? Array.Empty()
+ keywords = pass.keywords?.ToArray() ?? Array.Empty(),
+ hlsl = pass.hlsl
};
}
}
@@ -235,15 +136,27 @@ internal static class DSLShaderCompiler
{
var source = File.ReadAllText(shaderPath);
- var lexer = new Lexer(source);
- var stream = new TokenStream(lexer.Tokenize());
- var shaderInfo = ParseShaders(stream);
- if (shaderInfo.Count == 0)
+ // Use ANTLR4 parser
+ var shaderModels = AntlrShaderCompiler.ParseShaders(source, out var parseErrors);
+
+ if (parseErrors.Count != 0)
+ {
+ var errorMessages = new StringBuilder();
+ foreach (var error in parseErrors)
+ {
+ errorMessages.AppendLine(error.ToString());
+ }
+
+ return Result.Failure("Failed to parse shader due to errors:\n" + errorMessages.ToString());
+ }
+
+ if (shaderModels.Count == 0)
{
return Result.Failure("No shader found in the provided file.");
}
- var model = SemanticAnalysis(shaderInfo[0], out var errors);
+ // Convert to semantics
+ var model = AntlrShaderCompiler.ConvertToSemantics(shaderModels[0], out var errors);
if (errors.Count != 0 || model == null)
{
diff --git a/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs b/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs
index 6fd4179..d9883c0 100644
--- a/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs
+++ b/Ghost.DSL/ShaderCompiler/DSLShaderSemantics.cs
@@ -8,7 +8,7 @@ public enum PropertyScope
Local,
}
-internal class PropertySemantic
+public class PropertySemantic
{
public PropertyScope scope;
public ShaderPropertyType type;
@@ -16,7 +16,7 @@ internal class PropertySemantic
public object? defaultValue;
}
-internal class PipelineSemantic
+public class PipelineSemantic
{
public ZTest? zTest;
public ZWrite? zWrite;
@@ -25,22 +25,23 @@ internal class PipelineSemantic
public ColorWriteMask? colorMask;
}
-internal class PassSemantic
+public class PassSemantic
{
public string name = string.Empty;
public ShaderEntryPoint taskShader;
public ShaderEntryPoint meshShader;
public ShaderEntryPoint pixelShader;
+ public string? hlsl;
public List? defines;
public List? includes;
public List? keywords;
public PipelineSemantic? localPipeline;
}
-internal class DSLShaderSemantics
+public class DSLShaderSemantics
{
public string name = string.Empty;
- public string fallback = string.Empty;
+ public string? hlsl;
public List? properties;
public PipelineSemantic? pipeline;
public List? passes;
diff --git a/Ghost.DSL/ShaderCompiler/DSLShaderSyntax.cs b/Ghost.DSL/ShaderCompiler/DSLShaderSyntax.cs
deleted file mode 100644
index 6a2cee6..0000000
--- a/Ghost.DSL/ShaderCompiler/DSLShaderSyntax.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler;
-
-internal struct FunctionCallDeclaration
-{
- public Token name;
- public List? arguments;
-}
-
-internal struct PropertyDeclaration
-{
- public Token scope;
- public Token type;
- public Token name;
- public List? propertyInitializer;
-}
-
-internal struct ValueDeclaration
-{
- public Token name;
- public Token value;
-}
-
-internal struct HlslDeclaration
-{
- public List? tokens;
-}
-
-internal class PropertiesSyntax
-{
- public List? properties;
- public List? functionCalls;
-}
-
-internal class PipelineSyntax
-{
- public List? values;
- public List? functionCalls;
-}
-
-internal class PassSyntax
-{
- public Token name;
- public PipelineSyntax? localPipeline;
- public HlslDeclaration? hlsl;
- public List? defines;
- public List? includes;
- public List>? keywords;
- public List? functionCalls;
-}
-
-internal class DSLShaderSyntax
-{
- public Token name;
- public PropertiesSyntax? properties;
- public PipelineSyntax? pipeline;
- public List? passes;
- public List? functionCalls;
-}
\ No newline at end of file
diff --git a/Ghost.DSL/ShaderCompiler/Lexer.cs b/Ghost.DSL/ShaderCompiler/Lexer.cs
deleted file mode 100644
index 3febff7..0000000
--- a/Ghost.DSL/ShaderCompiler/Lexer.cs
+++ /dev/null
@@ -1,310 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler;
-
-public class Lexer
-{
- private readonly string _source;
- private int _pos = 0;
- private int _line = 1; // Lines typically start at 1
- private int _column = 1; // Columns typically start at 1
-
- public Lexer(string source)
- {
- _source = source;
- }
-
- public IEnumerable Tokenize()
- {
- while (!IsAtEnd())
- {
- var token = ScanToken();
- if (token != Token.Null)
- {
- yield return token;
- }
- }
-
- yield return new Token(TokenType.EndOfFile, string.Empty, _line, _column);
- }
-
- #region Core Scanning Logic
-
- private Token ScanToken()
- {
- var c = Consume();
-
- // Rule 1: Skip whitespace and handle line tracking
- if (char.IsWhiteSpace(c))
- {
- HandleWhitespace(c);
- return Token.Null;
- }
-
- // Rule 2: Handle comments
- if (c == '/')
- {
- if (HandleComments())
- {
- return Token.Null;
- }
- // If not a comment, fall through to handle '/' as an operator if needed
- }
-
- // Rule 3: Handle string literals
- if (c == '"')
- {
- return ScanStringLiteral();
- }
-
- // Rule 4: Handle numeric literals
- if (char.IsDigit(c) || (c == '.' && char.IsDigit(Peek())))
- {
- return ScanNumericLiteral(c);
- }
-
- // Rule 5: Handle identifiers and keywords
- if (char.IsLetter(c) || c == '_')
- {
- return ScanIdentifierOrKeyword(c);
- }
-
- // Rule 6: Handle single-character tokens (punctuation)
- var punctuationToken = ScanPunctuation(c);
- if (punctuationToken != Token.Null)
- {
- return punctuationToken;
- }
-
- // Rule 7: Skip unknown characters (could log warning in production)
- return Token.Null;
- }
-
- #endregion
-
- #region Rule Implementations
-
- private void HandleWhitespace(char c)
- {
- if (c == '\n')
- {
- _line++;
- _column = 1;
- }
- else if (c == '\r')
- {
- // Handle Windows line endings - peek for \n
- if (Peek() == '\n')
- {
- Consume();
- }
-
- _line++;
- _column = 1;
- }
- else
- {
- _column++;
- }
- }
-
- private bool HandleComments()
- {
- var next = Peek();
-
- if (next == '/') // Single-line comment
- {
- return ScanSingleLineComment();
- }
- else if (next == '*') // Multi-line comment
- {
- return ScanMultiLineComment();
- }
-
- return false; // Not a comment
- }
-
- private bool ScanSingleLineComment()
- {
- // Skip the second '/'
- Consume();
-
- // Consume until end of line
- while (!IsAtEnd() && Peek() != '\n' && Peek() != '\r')
- {
- Consume();
- }
-
- return true;
- }
-
- private bool ScanMultiLineComment()
- {
- // Skip the '*'
- Consume();
-
- while (!IsAtEnd())
- {
- var c = Consume();
-
- if (c == '\n')
- {
- _line++;
- _column = 1;
- }
- else if (c == '*' && Peek() == '/')
- {
- Consume(); // Consume closing '/'
- return true;
- }
- else
- {
- _column++;
- }
- }
-
- // Unclosed comment - could throw error in production
- return true;
- }
-
- private Token ScanStringLiteral()
- {
- var startLine = _line;
- var startColumn = _column - 1; // Account for opening quote
- var start = _pos;
-
- while (!IsAtEnd() && Peek() != '"')
- {
- var c = Peek();
- if (c == '\n')
- {
- _line++;
- _column = 1;
- }
- else if (c == '\\')
- {
- // Handle escape sequences
- Consume(); // Skip backslash
- if (!IsAtEnd())
- {
- Consume();
- }
- continue;
- }
-
- Consume();
- }
-
- if (IsAtEnd())
- {
- // Unterminated string - could throw error in production
- var unterminatedText = _source[start.._pos];
- return new Token(TokenType.StringLiteral, unterminatedText, startLine, startColumn);
- }
-
- var text = _source[start.._pos];
- Consume(); // Consume closing quote
-
- return new Token(TokenType.StringLiteral, text, startLine, startColumn);
- }
-
- private Token ScanNumericLiteral(char firstChar)
- {
- var startColumn = _column - 1;
- var start = _pos - 1; // Include the first character
-
- var hasDot = firstChar == '.';
-
- while (!IsAtEnd())
- {
- var c = Peek();
- if (char.IsDigit(c))
- {
- Consume();
- }
- else if (c == '.' && !hasDot)
- {
- hasDot = true;
- Consume();
- }
- else
- {
- break;
- }
- }
-
- var number = _source[start.._pos];
- return new Token(TokenType.Number, number, _line, startColumn);
- }
-
- private Token ScanIdentifierOrKeyword(char firstChar)
- {
- var startColumn = _column - 1;
- var start = _pos - 1; // Include the first character
-
- while (!IsAtEnd() && (char.IsLetterOrDigit(Peek()) || Peek() == '_'))
- {
- Consume();
- }
-
- var text = _source[start.._pos];
- var tokenType = DetermineIdentifierType(text);
-
- return new Token(tokenType, text, _line, startColumn);
- }
-
- private Token ScanPunctuation(char c)
- {
- var startColumn = _column - 1;
-
- return c switch
- {
- '=' => new Token(TokenType.Equals, "=", _line, startColumn),
- ';' => new Token(TokenType.Semicolon, ";", _line, startColumn),
- ',' => new Token(TokenType.Comma, ",", _line, startColumn),
- '{' => new Token(TokenType.LBrace, "{", _line, startColumn),
- '}' => new Token(TokenType.RBrace, "}", _line, startColumn),
- '(' => new Token(TokenType.LParen, "(", _line, startColumn),
- ')' => new Token(TokenType.RParen, ")", _line, startColumn),
- _ => Token.Null // Unknown punctuation
- };
- }
-
- #endregion
-
- #region Classification Rules
-
- private static TokenType DetermineIdentifierType(string text)
- {
- // Rule: Check if it's a known keyword first
- if (TokenLexicon.IsKeyword(text))
- {
- return TokenType.Keyword;
- }
-
- // Rule: All other identifiers are treated as identifiers
- // (Could extend this to handle functions, types, etc. as separate token types)
- return TokenType.Identifier;
- }
-
- #endregion
-
- #region Helper Methods
-
- private bool IsAtEnd() => _pos >= _source.Length;
-
- private char Consume()
- {
- if (IsAtEnd())
- return '\0';
-
- var c = _source[_pos];
- _pos++;
- _column++;
- return c;
- }
-
- private char Peek() => IsAtEnd() ? '\0' : _source[_pos];
-
- private char PeekNext() => _pos + 1 >= _source.Length ? '\0' : _source[_pos + 1];
-
- #endregion
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/DefinesBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/DefinesBlock.cs
deleted file mode 100644
index ff7e3cb..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/DefinesBlock.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class DefinesBlock : IBlockParser, List>
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.DEFINES);
- }
-
- public static List Parse(TokenStreamSlice stream)
- {
- stream.Expect(TokenType.Keyword);
- stream.Expect(TokenType.LBrace);
-
- var defines = new List();
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.HasMore)
- {
- var defineToken = bodyStream.Expect(TokenType.Identifier);
- defines.Add(defineToken);
- bodyStream.Expect(TokenType.Semicolon);
- }
-
- stream.Expect(TokenType.RBrace);
-
- return defines;
- }
-
- public static List? SemanticAnalysis(List? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var defines = new List(syntax.Count);
- foreach (var defineToken in syntax)
- {
- defines.Add(defineToken.lexeme);
- }
-
- return defines;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/IBlockParser.cs b/Ghost.DSL/ShaderCompiler/Parser/IBlockParser.cs
deleted file mode 100644
index 597d294..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/IBlockParser.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal interface IBlockParser
-{
- public static abstract bool ShouldEnter(Token token);
- public static abstract T? Parse(TokenStreamSlice ts);
- public static abstract U? SemanticAnalysis(T? syntax, List errors);
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/IncludesBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/IncludesBlock.cs
deleted file mode 100644
index 4035bfd..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/IncludesBlock.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class IncludesBlock : IBlockParser, List>
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.INCLUDES);
- }
-
- public static List Parse(TokenStreamSlice stream)
- {
- stream.Expect(TokenType.Keyword);
- stream.Expect(TokenType.LBrace);
-
- var includes = new List();
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.HasMore)
- {
- var includeToken = bodyStream.Expect(TokenType.StringLiteral);
- includes.Add(includeToken);
- bodyStream.Expect(TokenType.Semicolon);
- }
-
- stream.Expect(TokenType.RBrace);
-
- return includes;
- }
-
- public static List? SemanticAnalysis(List? syntax, List errors)
- {
- if (syntax == null || syntax.Count == 0)
- {
- return null;
- }
-
- var includes = new List(syntax.Count);
- foreach (var includeToken in syntax)
- {
- var path = includeToken.lexeme;
- if (File.Exists(path))
- {
- includes.Add(path);
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Included file '{path}' not found.",
- line = includeToken.line,
- column = includeToken.column
- });
- continue;
- }
- }
-
- return includes;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/KeywordsBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/KeywordsBlock.cs
deleted file mode 100644
index d7d1e75..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/KeywordsBlock.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using Ghost.Core.Graphics;
-
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class KeywordsBlock : IBlockParser>, List>
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.KEYWORDS);
- }
-
- public static List> Parse(TokenStreamSlice stream)
- {
- stream.Expect(TokenType.Keyword);
- stream.Expect(TokenType.LBrace);
-
- var keywords = new List>();
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.HasMore)
- {
- var keys = new List();
- while (!bodyStream.Match(TokenType.Semicolon))
- {
- var expectType = TokenType.Identifier;
- if (keys.Count == 0)
- {
- expectType |= TokenType.Keyword;
- }
-
- var argument = bodyStream.Expect(expectType);
- keys.Add(argument);
-
- if (bodyStream.Match(TokenType.Comma))
- {
- bodyStream.Consume();
- }
- }
-
- keywords.Add(keys);
- bodyStream.Expect(TokenType.Semicolon);
- }
-
- stream.Expect(TokenType.RBrace);
-
- return keywords;
- }
-
- public static List? SemanticAnalysis(List>? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var keywords = new List(syntax.Count);
- foreach (var keys in syntax)
- {
- if (keys.Count == 0)
- {
- continue;
- }
-
- var group = new KeywordsGroup();
- group.space = keys[0].lexeme switch
- {
- TokenLexicon.KnownFunctions.LOCAL => KeywordSpace.Local,
- TokenLexicon.KnownFunctions.GLOBAL => KeywordSpace.Global,
- _ => KeywordSpace.Local
- };
-
- for (var i = 0; i < keys.Count; i++)
- {
- var token = keys[i];
- if (i == 0 && token.type == TokenType.Keyword)
- {
- continue;
- }
-
- if (token.type != TokenType.Identifier)
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid keyword '{token.lexeme}' in keywords block.",
- line = token.line,
- column = token.column
- });
- continue;
- }
-
- group.keywords ??= new List(keys.Count);
- group.keywords.Add(token.lexeme);
- }
-
- keywords.Add(group);
- }
-
- return keywords;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/ParseUtility.cs b/Ghost.DSL/ShaderCompiler/Parser/ParseUtility.cs
deleted file mode 100644
index ac9edcb..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/ParseUtility.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal static class ParseUtility
-{
- public static List ParseFunctionArguments(ref TokenStreamSlice stream, TokenType tokenType)
- {
- var args = new List();
- stream.Expect(TokenType.LParen);
-
- while (!stream.Peek().type.Equals(TokenType.RParen))
- {
- var argToken = stream.Expect(tokenType);
- args.Add(argToken);
-
- if (stream.Peek().type == TokenType.Comma)
- {
- stream.Consume();
- }
- else
- {
- break;
- }
- }
-
- stream.Expect(TokenType.RParen);
- return args;
- }
-
- public static FunctionCallDeclaration ParseFunction(ref TokenStreamSlice stream, TokenType tokenType)
- {
- var functionToken = stream.Expect(TokenType.Identifier);
- var args = ParseFunctionArguments(ref stream, tokenType);
- stream.Expect(TokenType.Semicolon);
-
- return new FunctionCallDeclaration
- {
- name = functionToken,
- arguments = args
- };
- }
-
- public static bool TrySliceLine(ref TokenStreamSlice stream, out TokenStreamSlice lineStream)
- {
- var length = 0;
- if (!stream.TryPeek(out var nextToken))
- {
- lineStream = default;
- return false;
- }
-
- while (!nextToken.Match(TokenType.Semicolon) && !nextToken.Match(TokenType.RBrace))
- {
- length++;
-
- if (!stream.TryPeek(length, out nextToken))
- {
- break;
- }
- }
-
- if (length > 0)
- {
- lineStream = stream.Slice(length);
- stream.Consume(); // Consume the semicolon
- return true;
- }
-
- lineStream = default;
- return false;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/PassBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/PassBlock.cs
deleted file mode 100644
index 0e1f1c4..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/PassBlock.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using Ghost.Core.Graphics;
-
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-// TODO: Add pass template support.
-// Pass templates let user to inject their own custom code into the generated HLSL code.
-// This is useful for adding custom lighting models, custom shadowing techniques, or other advanced effects without touching the core shader code.
-internal class PassBlock : IBlockParser
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PASS);
- }
-
- public static PassSyntax Parse(TokenStreamSlice stream)
- {
- var pass = new PassSyntax();
-
- stream.Expect(TokenType.Keyword);
- pass.name = stream.Expect(TokenType.StringLiteral);
- stream.Expect(TokenType.LBrace);
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.TryPeek(out var nextToken))
- {
- if (DefinesBlock.ShouldEnter(nextToken))
- {
- pass.defines = DefinesBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (KeywordsBlock.ShouldEnter(nextToken))
- {
- pass.keywords = KeywordsBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (PipelineBlock.ShouldEnter(nextToken))
- {
- pass.localPipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (IncludesBlock.ShouldEnter(nextToken))
- {
- pass.includes = IncludesBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (nextToken.Match(TokenType.Identifier))
- {
- var func = ParseUtility.ParseFunction(ref bodyStream, TokenType.StringLiteral);
-
- pass.functionCalls ??= new();
- pass.functionCalls.Add(func);
- }
- else
- {
- throw new Exception($"Unexpected token '{nextToken}' in pass body.");
- }
- }
-
- stream.Expect(TokenType.RBrace);
-
- return pass;
- }
-
- public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var semantic = new PassSemantic
- {
- name = syntax.name.lexeme,
- defines = DefinesBlock.SemanticAnalysis(syntax.defines, errors),
- includes = IncludesBlock.SemanticAnalysis(syntax.includes, errors),
- keywords = KeywordsBlock.SemanticAnalysis(syntax.keywords, errors),
- localPipeline = PipelineBlock.SemanticAnalysis(syntax.localPipeline, errors),
- };
-
- if (syntax.functionCalls != null)
- {
- foreach (var func in syntax.functionCalls)
- {
- switch (func.name.lexeme)
- {
- case TokenLexicon.KnownFunctions.TASK_SHADER:
- AnalysisShaderEntry(errors, func, ref semantic.taskShader);
- break;
-
- case TokenLexicon.KnownFunctions.MESH_SHADER:
- AnalysisShaderEntry(errors, func, ref semantic.meshShader);
- break;
-
- case TokenLexicon.KnownFunctions.PIXEL_SHADER:
- AnalysisShaderEntry(errors, func, ref semantic.pixelShader);
- break;
-
- default:
- errors.Add(new DSLShaderError
- {
- message = $"Unknown function '{func.name.lexeme}' in pass {syntax.name.lexeme}.",
- line = func.name.line,
- column = func.name.column
- });
- break;
- }
- }
- }
-
- if (semantic.meshShader.shader == null || semantic.pixelShader.shader == null)
- {
- // TODO: Inheritance from base pass.
- // TODO: Add mesh shader support.
- errors.Add(new DSLShaderError
- {
- message = $"Pass {syntax.name.lexeme} must contain a mesh shader (ms) and a pixel shader (ps) declaration.",
- line = syntax.name.line,
- column = syntax.name.column
- });
- }
-
- return semantic;
- }
-
- private static void AnalysisShaderEntry(List errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint)
- {
- if (func.arguments?.Count != 2)
- {
- errors.Add(new DSLShaderError
- {
- message = "Shader declaration requires exactly two arguments: (shaderPath, entryPoint).",
- line = func.name.line,
- column = func.name.column
- });
- }
- else
- {
- shaderEntryPoint = new ShaderEntryPoint
- {
- shader = func.arguments[0].lexeme,
- entry = func.arguments[1].lexeme
- };
- }
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/PipelineBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/PipelineBlock.cs
deleted file mode 100644
index f9c3a72..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/PipelineBlock.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using Ghost.Core.Graphics;
-
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class PipelineBlock : IBlockParser
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PIPELINE);
- }
-
- public static PipelineSyntax Parse(TokenStreamSlice stream)
- {
- stream.Expect(TokenType.Keyword);
- stream.Expect(TokenType.LBrace);
-
- var pipeline = new PipelineSyntax();
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.HasMore)
- {
- var stateToken = bodyStream.Expect(TokenType.Identifier);
- bodyStream.Expect(TokenType.Equals);
- var valueToken = bodyStream.Expect(TokenType.Identifier | TokenType.Number);
-
- pipeline.values ??= new();
- pipeline.values.Add(new ValueDeclaration
- {
- name = stateToken,
- value = valueToken
- });
-
- bodyStream.Expect(TokenType.Semicolon);
- }
-
- stream.Expect(TokenType.RBrace);
-
- return pipeline;
- }
-
- public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var semantic = new PipelineSemantic();
- if (syntax.values != null)
- {
- foreach (var valueDecl in syntax.values)
- {
- switch (valueDecl.name.lexeme)
- {
- case TokenLexicon.KnownPipelineProperties.ZTEST:
- if (Enum.TryParse(valueDecl.value.lexeme, true, out var zTest))
- {
- semantic.zTest = zTest;
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid ZTest option: {valueDecl.value.lexeme}",
- line = valueDecl.value.line,
- column = valueDecl.value.column
- });
- }
- break;
-
- case TokenLexicon.KnownPipelineProperties.ZWRITE:
- if (Enum.TryParse(valueDecl.value.lexeme, true, out var zWrite))
- {
- semantic.zWrite = zWrite;
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid ZWrite option: {valueDecl.value.lexeme}",
- line = valueDecl.value.line,
- column = valueDecl.value.column
- });
- }
- break;
-
- case TokenLexicon.KnownPipelineProperties.CULL:
- if (Enum.TryParse(valueDecl.value.lexeme, true, out var cull))
- {
- semantic.cull = cull;
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid Cull option: {valueDecl.value.lexeme}",
- line = valueDecl.value.line,
- column = valueDecl.value.column
- });
- }
- break;
-
- case TokenLexicon.KnownPipelineProperties.BLEND:
- if (Enum.TryParse(valueDecl.value.lexeme, true, out var blend))
- {
- semantic.blend = blend;
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid Blend option: {valueDecl.value.lexeme}",
- line = valueDecl.value.line,
- column = valueDecl.value.column
- });
- }
- break;
-
- case TokenLexicon.KnownPipelineProperties.COLORMASK:
- if (Enum.TryParse(valueDecl.value.lexeme, true, out var colorMask))
- {
- semantic.colorMask = colorMask;
- }
- else
- {
- errors.Add(new DSLShaderError
- {
- message = $"Invalid Color Mask value: {valueDecl.value.lexeme}",
- line = valueDecl.value.line,
- column = valueDecl.value.column
- });
- }
- break;
-
- default:
- break;
- }
- }
- }
-
- return semantic;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/PropertiesBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/PropertiesBlock.cs
deleted file mode 100644
index 3429a85..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/PropertiesBlock.cs
+++ /dev/null
@@ -1,437 +0,0 @@
-using Ghost.Core.Graphics;
-using Misaki.HighPerformance.Mathematics;
-using System.Globalization;
-
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class PropertiesBlock : IBlockParser>
-{
- private delegate object? PropertyValueBuilder(List tokens, List errors);
-
- private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, PropertyValueBuilder? Builder);
-
- private static readonly Dictionary s_propTypeInfo = new()
- {
- // Floats
- [ShaderPropertyType.Float] = new(1, TokenType.Number, (syntax, errors) => ParseFloatValue(syntax[0], errors)),
- [ShaderPropertyType.Float2] = new(2, TokenType.Number, (syntax, errors) => new float2(
- ParseFloatValue(syntax[0], errors),
- ParseFloatValue(syntax[1], errors))),
- [ShaderPropertyType.Float3] = new(3, TokenType.Number, (syntax, errors) => new float3(
- ParseFloatValue(syntax[0], errors),
- ParseFloatValue(syntax[1], errors),
- ParseFloatValue(syntax[2], errors))),
- [ShaderPropertyType.Float4] = new(4, TokenType.Number, (syntax, errors) => new float4(
- ParseFloatValue(syntax[0], errors),
- ParseFloatValue(syntax[1], errors),
- ParseFloatValue(syntax[2], errors),
- ParseFloatValue(syntax[3], errors))),
-
- // Ints
- [ShaderPropertyType.Int] = new(1, TokenType.Number, (syntax, errors) => ParseIntValue(syntax[0], errors)),
- [ShaderPropertyType.Int2] = new(2, TokenType.Number, (syntax, errors) => new int2(
- ParseIntValue(syntax[0], errors),
- ParseIntValue(syntax[1], errors))),
- [ShaderPropertyType.Int3] = new(3, TokenType.Number, (syntax, errors) => new int3(
- ParseIntValue(syntax[0], errors),
- ParseIntValue(syntax[1], errors),
- ParseIntValue(syntax[2], errors))),
- [ShaderPropertyType.Int4] = new(4, TokenType.Number, (syntax, errors) => new int4(
- ParseIntValue(syntax[0], errors),
- ParseIntValue(syntax[1], errors),
- ParseIntValue(syntax[2], errors),
- ParseIntValue(syntax[3], errors))),
-
- // UInts
- [ShaderPropertyType.UInt] = new(1, TokenType.Number, (syntax, errors) => ParseUIntValue(syntax[0], errors)),
- [ShaderPropertyType.UInt2] = new(2, TokenType.Number, (syntax, errors) => new uint2(
- ParseUIntValue(syntax[0], errors),
- ParseUIntValue(syntax[1], errors))),
- [ShaderPropertyType.UInt3] = new(3, TokenType.Number, (syntax, errors) => new uint3(
- ParseUIntValue(syntax[0], errors),
- ParseUIntValue(syntax[1], errors),
- ParseUIntValue(syntax[2], errors))),
- [ShaderPropertyType.UInt4] = new(4, TokenType.Number, (syntax, errors) => new uint4(
- ParseUIntValue(syntax[0], errors),
- ParseUIntValue(syntax[1], errors),
- ParseUIntValue(syntax[2], errors),
- ParseUIntValue(syntax[3], errors))),
-
- // Bools (numbers 0/1)
- [ShaderPropertyType.Bool] = new(1, TokenType.Number, (syntax, errors) => ParseBoolValue(syntax[0], errors)),
- [ShaderPropertyType.Bool2] = new(2, TokenType.Number, (syntax, errors) => new bool2(
- ParseBoolValue(syntax[0], errors),
- ParseBoolValue(syntax[1], errors))),
- [ShaderPropertyType.Bool3] = new(3, TokenType.Number, (syntax, errors) => new bool3(
- ParseBoolValue(syntax[0], errors),
- ParseBoolValue(syntax[1], errors),
- ParseBoolValue(syntax[2], errors))),
- [ShaderPropertyType.Bool4] = new(4, TokenType.Number, (syntax, errors) => new bool4(
- ParseBoolValue(syntax[0], errors),
- ParseBoolValue(syntax[1], errors),
- ParseBoolValue(syntax[2], errors),
- ParseBoolValue(syntax[3], errors))),
-
- // Textures (single identifier argument)
- [ShaderPropertyType.Texture2D] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
- [ShaderPropertyType.Texture3D] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
- [ShaderPropertyType.TextureCube] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
- };
-
- private static float ParseFloatValue(Token token, List errors)
- {
- if (!float.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Failed to parse float value '{token.lexeme}'.",
- line = token.line,
- column = token.column
- });
- }
-
- return result;
- }
-
- private static int ParseIntValue(Token token, List errors)
- {
- if (!int.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Failed to parse int value '{token.lexeme}'.",
- line = token.line,
- column = token.column
- });
- }
-
- return result;
- }
-
- private static uint ParseUIntValue(Token token, List errors)
- {
- if (!uint.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Failed to parse uint value '{token.lexeme}'.",
- line = token.line,
- column = token.column
- });
- }
-
- return result;
- }
-
- private static bool ParseBoolValue(Token token, List errors)
- {
- if (!bool.TryParse(token.lexeme, out var result))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Failed to parse bool value '{token.lexeme}'.",
- line = token.line,
- column = token.column
- });
- }
-
- return result;
- }
-
- private static string ParseTextureDefault(Token token, List errors)
- {
- if (!TokenLexicon.IsTextureDefaultValue(token.lexeme))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Texture default value '{token.lexeme}' is not valid.",
- line = token.line,
- column = token.column
- });
- }
-
- return token.lexeme;
- }
-
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PROPERTIES);
- }
-
- private static ShaderPropertyType FromString(string type)
- {
- return type.ToLower() switch
- {
- TokenLexicon.KnownTypes.FLOAT => ShaderPropertyType.Float,
- TokenLexicon.KnownTypes.FLOAT2 => ShaderPropertyType.Float2,
- TokenLexicon.KnownTypes.FLOAT3 => ShaderPropertyType.Float3,
- TokenLexicon.KnownTypes.FLOAT4 => ShaderPropertyType.Float4,
- TokenLexicon.KnownTypes.FLOAT4X4 => ShaderPropertyType.Float4x4,
- TokenLexicon.KnownTypes.INT => ShaderPropertyType.Int,
- TokenLexicon.KnownTypes.INT2 => ShaderPropertyType.Int2,
- TokenLexicon.KnownTypes.INT3 => ShaderPropertyType.Int3,
- TokenLexicon.KnownTypes.INT4 => ShaderPropertyType.Int4,
- TokenLexicon.KnownTypes.UINT => ShaderPropertyType.UInt,
- TokenLexicon.KnownTypes.UINT2 => ShaderPropertyType.UInt2,
- TokenLexicon.KnownTypes.UINT3 => ShaderPropertyType.UInt3,
- TokenLexicon.KnownTypes.UINT4 => ShaderPropertyType.UInt4,
- TokenLexicon.KnownTypes.BOOL => ShaderPropertyType.Bool,
- TokenLexicon.KnownTypes.BOOL2 => ShaderPropertyType.Bool2,
- TokenLexicon.KnownTypes.BOOL3 => ShaderPropertyType.Bool3,
- TokenLexicon.KnownTypes.BOOL4 => ShaderPropertyType.Bool4,
- TokenLexicon.KnownTypes.TEXTURE2D => ShaderPropertyType.Texture2D,
- TokenLexicon.KnownTypes.TEXTURE3D => ShaderPropertyType.Texture3D,
- TokenLexicon.KnownTypes.TEXTURECUBE => ShaderPropertyType.TextureCube,
- TokenLexicon.KnownTypes.TEXTURECUBE_ARRAY => ShaderPropertyType.TextureCubeArray,
- TokenLexicon.KnownTypes.TEXTURE2D_ARRAY => ShaderPropertyType.Texture2DArray,
- TokenLexicon.KnownTypes.SAMPLER => ShaderPropertyType.Sampler,
- _ => ShaderPropertyType.None,
- };
- }
-
- public static PropertiesSyntax Parse(TokenStreamSlice stream)
- {
- stream.Expect(TokenType.Keyword);
- stream.Expect(TokenType.LBrace);
-
- var syntax = new PropertiesSyntax();
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.HasMore)
- {
- var shaderProperty = new PropertyDeclaration();
- if (bodyStream.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.GLOBAL)
- || bodyStream.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.LOCAL))
- {
- var scopeToken = bodyStream.Consume();
- shaderProperty.scope = scopeToken;
- }
-
- var typeToken = bodyStream.Expect(TokenType.Identifier);
- var nameToken = bodyStream.Expect(TokenType.Identifier);
-
- shaderProperty.type = typeToken;
- shaderProperty.name = nameToken;
-
- var nextToken = bodyStream.Consume();
- switch (nextToken.type)
- {
- case TokenType.Equals:
- {
- bodyStream.Expect(TokenType.LBrace);
- while (!bodyStream.Match(TokenType.RBrace))
- {
- var token = bodyStream.Consume();
- if (!token.Match(TokenType.Comma))
- {
- shaderProperty.propertyInitializer ??= new List();
- shaderProperty.propertyInitializer.Add(token);
- }
- }
-
- bodyStream.Expect(TokenType.RBrace);
- bodyStream.Expect(TokenType.Semicolon);
-
- break;
- }
-
- case TokenType.Semicolon:
- break;
- default:
- throw new Exception($"Unexpected token '{nextToken.lexeme}' in property declaration.");
- }
-
- syntax.properties ??= new();
- syntax.properties.Add(shaderProperty);
- }
-
- stream.Expect(TokenType.RBrace);
-
- return syntax;
- }
-
- public static List? SemanticAnalysis(PropertiesSyntax? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var models = new List();
- var usedPropertyNames = new HashSet();
-
- if (syntax.properties != null)
- {
- foreach (var property in syntax.properties)
- {
- var model = new PropertySemantic
- {
- scope = property.scope.lexeme switch
- {
- TokenLexicon.KnownKeywords.GLOBAL => PropertyScope.Global,
- TokenLexicon.KnownKeywords.LOCAL => PropertyScope.Local,
- _ => PropertyScope.Local,
- }
- };
-
- var flowControl = ValidatePropertyType(errors, property, model);
- if (!flowControl)
- {
- continue;
- }
-
- flowControl = ValidatePropertyName(errors, usedPropertyNames, property, model);
- if (!flowControl)
- {
- continue;
- }
-
- if (property.propertyInitializer != null)
- {
- flowControl = ValidatePropertyInitializer(errors, property, model);
- if (!flowControl)
- {
- continue;
- }
- }
-
- usedPropertyNames.Add(property.name.lexeme);
- models.Add(model);
- }
- }
-
- return models;
- }
-
- private static bool ValidatePropertyType(List errors, PropertyDeclaration property, PropertySemantic model)
- {
- if (!TokenLexicon.IsType(property.type.lexeme))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Shader property type '{property.type.lexeme}' is not a valid type.",
- line = property.type.line,
- column = property.type.column
- });
-
- return false;
- }
-
- model.type = FromString(property.type.lexeme);
- return true;
- }
-
- private static bool ValidatePropertyName(List errors, HashSet usedPropertyNames, PropertyDeclaration property, PropertySemantic model)
- {
- if (string.IsNullOrWhiteSpace(property.name.lexeme))
- {
- errors.Add(new DSLShaderError
- {
- message = "Shader property has an empty name.",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
- else if (usedPropertyNames.Contains(property.name.lexeme))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Shader property name '{property.name.lexeme}' is duplicated.",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
-
- model.name = property.name.lexeme;
- return true;
- }
-
- private static bool ValidatePropertyInitializer(List errors, PropertyDeclaration property, PropertySemantic model)
- {
- var initializer = property.propertyInitializer;
- if (initializer == null)
- {
- errors.Add(new DSLShaderError
- {
- message = "Shader property initializer is null.",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
-
- if (!s_propTypeInfo.TryGetValue(model.type, out var info))
- {
- errors.Add(new DSLShaderError
- {
- message = $"No initializer metadata registered for property type '{model.type}'.",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
-
- if (initializer.Count != info.ArgCount)
- {
- errors.Add(new DSLShaderError
- {
- message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {initializer.Count}.",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
-
- // Type check (uniform requirement for all args)
- var hasError = false;
- for (var i = 0; i < initializer.Count; i++)
- {
- var arg = initializer[i];
- if (!arg.Match(info.ArgTokenType))
- {
- errors.Add(new DSLShaderError
- {
- message = $"Shader property constructor argument {i} expects token kind '{info.ArgTokenType}', but got '{arg.type}'.",
- line = arg.line,
- column = arg.column
- });
-
- hasError = true;
- }
- }
-
- if (hasError)
- {
- return false;
- }
-
- // Build default Value if we have a builder (textures currently null / TODO)
- if (info.Builder != null)
- {
- try
- {
- model.defaultValue = info.Builder(initializer, errors);
- }
- catch (Exception ex)
- {
- errors.Add(new DSLShaderError
- {
- message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
- line = property.name.line,
- column = property.name.column
- });
-
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Parser/ShaderBlock.cs b/Ghost.DSL/ShaderCompiler/Parser/ShaderBlock.cs
deleted file mode 100644
index 6270531..0000000
--- a/Ghost.DSL/ShaderCompiler/Parser/ShaderBlock.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler.Parser;
-
-internal class ShaderBlock : IBlockParser
-{
- public static bool ShouldEnter(Token token)
- {
- return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.SHADER);
- }
-
- public static DSLShaderSyntax Parse(TokenStreamSlice stream)
- {
- var shader = new DSLShaderSyntax();
-
- stream.Expect(TokenType.Keyword);
- shader.name = stream.Expect(TokenType.StringLiteral);
- stream.Expect(TokenType.LBrace);
-
- var bodyStream = stream.Slice(stream.Remaining - 1);
- while (bodyStream.TryPeek(out var nextToken))
- {
- if (PropertiesBlock.ShouldEnter(nextToken))
- {
- shader.properties = PropertiesBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (PipelineBlock.ShouldEnter(nextToken))
- {
- shader.pipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
- }
- else if (PassBlock.ShouldEnter(nextToken))
- {
- shader.passes ??= new();
- shader.passes.Add(PassBlock.Parse(bodyStream.SliceNextBlock()));
- }
- else if (nextToken.Match(TokenType.Identifier))
- {
- var func = ParseUtility.ParseFunction(ref bodyStream, TokenType.StringLiteral | TokenType.Number | TokenType.Identifier);
-
- shader.functionCalls ??= new();
- shader.functionCalls.Add(func);
- }
- else
- {
- throw new Exception($"Unexpected token '{nextToken}' in shader body.");
- }
- }
-
- stream.Expect(TokenType.RBrace);
-
- return shader;
- }
-
- public static DSLShaderSemantics? SemanticAnalysis(DSLShaderSyntax? syntax, List errors)
- {
- if (syntax == null)
- {
- return null;
- }
-
- var shaderModel = new DSLShaderSemantics
- {
- name = syntax.name.lexeme,
- properties = PropertiesBlock.SemanticAnalysis(syntax.properties, errors),
- pipeline = PipelineBlock.SemanticAnalysis(syntax.pipeline, errors)
- };
-
- if (syntax.passes != null)
- {
- foreach (var passSyntax in syntax.passes)
- {
- var passModel = PassBlock.SemanticAnalysis(passSyntax, errors);
- if (passModel != null)
- {
- shaderModel.passes ??= new();
- shaderModel.passes.Add(passModel);
- }
- }
- }
-
- if (syntax.functionCalls != null)
- {
- foreach (var func in syntax.functionCalls)
- {
- switch (func.name.lexeme)
- {
- case TokenLexicon.KnownFunctions.FALLBACK:
- if (func.arguments == null || func.arguments.Count != 1)
- {
- errors.Add(new DSLShaderError
- {
- message = "Fallback declaration requires exactly one arguments: (fallback shader name).",
- line = func.name.line,
- column = func.name.column
- });
-
- continue;
- }
-
- shaderModel.fallback = func.arguments[0].lexeme;
- break;
- default:
- errors.Add(new DSLShaderError
- {
- message = $"Unknown function '{func.name.lexeme}' in shader.",
- line = func.name.line,
- column = func.name.column
- });
- break;
- }
- }
- }
-
- return shaderModel;
- }
-}
diff --git a/Ghost.DSL/ShaderCompiler/Token.cs b/Ghost.DSL/ShaderCompiler/Token.cs
deleted file mode 100644
index 66485fc..0000000
--- a/Ghost.DSL/ShaderCompiler/Token.cs
+++ /dev/null
@@ -1,242 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler;
-
-[Flags]
-public enum TokenType
-{
- None = 0,
-
- // Literals
- Identifier = 1 << 0, // variable names, function names, etc.
- Keyword = 1 << 1, // shader, properties, pipeline, pass, etc.
- Number = 1 << 2, // numeric literals (123, 45.67, .5)
- StringLiteral = 1 << 3, // "ForwardVS.hlsl"
-
- // Operators and Punctuation
- Equals = 1 << 4, // =
- Semicolon = 1 << 5, // ;
- Comma = 1 << 6, // ,
-
- // Delimiters
- LBrace = 1 << 7, // {
- RBrace = 1 << 8, // }
- LParen = 1 << 9, // (
- RParen = 1 << 10, // )
-
- // Special
- EndOfFile = 1 << 11, // EOF
-
- // Future extensions (commented out for now)
- // LBracket = 1 << 12, // [
- // RBracket = 1 << 13, // ]
- // Dot = 1 << 14, // .
- // Colon = 1 << 15, // :
- // Plus = 1 << 16, // +
- // Minus = 1 << 17, // -
- // Star = 1 << 18, // *
- // Slash = 1 << 19, // /
-}
-
-public readonly struct Token : IEquatable
-{
- public readonly TokenType type;
- public readonly string lexeme;
- public readonly int line;
- public readonly int column;
-
- public static Token Null => new Token(TokenType.None, string.Empty, -1, -1);
-
- public Token(TokenType type, string lexeme, int line, int column)
- {
- this.type = type;
- this.lexeme = lexeme;
- this.line = line;
- this.column = column;
- }
-
- public override readonly string ToString()
- {
- return $"{type}('{lexeme}') at {line}:{column}";
- }
-
- public bool Equals(Token other)
- {
- return type == other.type && lexeme == other.lexeme && line == other.line && column == other.column;
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(type, lexeme, line, column);
- }
-
- public override bool Equals(object? obj)
- {
- return obj is Token token && Equals(token);
- }
-
- public static bool operator ==(Token left, Token right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(Token left, Token right)
- {
- return !(left == right);
- }
-}
-
-public static class TokenExtensions
-{
- public static bool Match(this Token token, TokenType type, string? lexeme = null)
- {
- if (!type.HasFlag(token.type))
- {
- return false;
- }
-
- if (!string.IsNullOrEmpty(lexeme) && token.lexeme != lexeme)
- {
- return false;
- }
-
- return true;
- }
-
- public static void Expect(this Token token, TokenType type, string? lexeme = null)
- {
- if (!token.Match(type, lexeme))
- {
- var expected = lexeme != null ? $"{type}('{lexeme}')" : type.ToString();
- throw new Exception($"Unexpected token at line {token.line}, column {token.column}. Expected {expected}, got {token.type}('{token.lexeme}').");
- }
- }
-}
-
-internal static class TokenLexicon
-{
- public static class KnownKeywords
- {
- public const string SHADER = "shader";
- public const string PROPERTIES = "properties";
- public const string PIPELINE = "pipeline";
- public const string PASS = "pass";
- public const string DEFINES = "defines";
- public const string KEYWORDS = "keywords";
- public const string INCLUDES = "includes";
- public const string GLOBAL = "global";
- public const string LOCAL = "local";
- }
-
- public static class KnownPipelineProperties
- {
- public const string ZTEST = "ztest";
- public const string ZWRITE = "zwrite";
- public const string CULL = "cull";
- public const string BLEND = "blend";
- public const string COLORMASK = "color_mask";
- }
-
- public static class KnownFunctions
- {
- public const string TASK_SHADER = "ts";
- public const string MESH_SHADER = "ms";
- public const string PIXEL_SHADER = "ps";
- public const string COMPUTE_SHADER = "cs";
- public const string LOCAL = "local";
- public const string GLOBAL = "global";
- public const string FALLBACK = "fallback";
- }
-
- public static class KnownTypes
- {
- // Basic types
- public const string FLOAT = "float";
- public const string FLOAT2 = "float2";
- public const string FLOAT3 = "float3";
- public const string FLOAT4 = "float4";
- public const string FLOAT4X4 = "float4x4";
-
- public const string INT = "int";
- public const string INT2 = "int2";
- public const string INT3 = "int3";
- public const string INT4 = "int4";
-
- public const string UINT = "uint";
- public const string UINT2 = "uint2";
- public const string UINT3 = "uint3";
- public const string UINT4 = "uint4";
-
- public const string BOOL = "bool";
- public const string BOOL2 = "bool2";
- public const string BOOL3 = "bool3";
- public const string BOOL4 = "bool4";
-
- // Texture types
- public const string TEXTURE2D = "tex2d";
- public const string TEXTURE2D_ARRAY = "tex2d_arr";
- public const string TEXTURE3D = "tex3d";
- public const string TEXTURECUBE = "texcube";
- public const string TEXTURECUBE_ARRAY = "texcube_arr";
-
- public const string SAMPLER = "sampler";
- }
-
- public static class KnownTextureValue
- {
- public const string WHITE = "white";
- public const string BLACK = "black";
- public const string GREY = "grey";
- public const string NORMAL = "normal";
- public const string TRANSPARENT = "transparent";
- }
-
- private static readonly HashSet s_keywords = new()
- {
- KnownKeywords.SHADER,
- KnownKeywords.PROPERTIES,
- KnownKeywords.PIPELINE,
- KnownKeywords.PASS,
- KnownKeywords.DEFINES,
- KnownKeywords.KEYWORDS,
- KnownKeywords.INCLUDES,
- KnownKeywords.GLOBAL,
- KnownKeywords.LOCAL,
- };
-
- private static readonly HashSet s_functions = new()
- {
- KnownFunctions.TASK_SHADER,
- KnownFunctions.PIXEL_SHADER,
- KnownFunctions.MESH_SHADER,
- KnownFunctions.COMPUTE_SHADER,
- KnownFunctions.LOCAL,
- KnownFunctions.GLOBAL,
- };
-
- private static readonly HashSet s_types = new()
- {
- KnownTypes.FLOAT, KnownTypes.FLOAT2, KnownTypes.FLOAT3, KnownTypes.FLOAT4, KnownTypes.FLOAT4X4,
- KnownTypes.INT, KnownTypes.INT2, KnownTypes.INT3, KnownTypes.INT4,
- KnownTypes.UINT, KnownTypes.UINT2, KnownTypes.UINT3, KnownTypes.UINT4,
- KnownTypes.BOOL, KnownTypes.BOOL2, KnownTypes.BOOL3, KnownTypes.BOOL4,
- KnownTypes.TEXTURE2D, KnownTypes.TEXTURE2D_ARRAY, KnownTypes.TEXTURE3D,
- KnownTypes.TEXTURECUBE, KnownTypes.TEXTURECUBE_ARRAY,
- KnownTypes.SAMPLER,
- };
-
- private static readonly HashSet s_textureDefaultValues = new()
- {
- KnownTextureValue.WHITE,
- KnownTextureValue.BLACK,
- KnownTextureValue.GREY,
- KnownTextureValue.NORMAL,
- KnownTextureValue.TRANSPARENT,
- };
-
- public static bool IsKeyword(string lexeme) => s_keywords.Contains(lexeme);
-
- public static bool IsFunction(string lexeme) => s_functions.Contains(lexeme);
-
- public static bool IsType(string lexeme) => s_types.Contains(lexeme);
-
- public static bool IsTextureDefaultValue(string lexeme) => s_textureDefaultValues.Contains(lexeme);
-}
diff --git a/Ghost.DSL/ShaderCompiler/TokenStream.cs b/Ghost.DSL/ShaderCompiler/TokenStream.cs
deleted file mode 100644
index 6b10248..0000000
--- a/Ghost.DSL/ShaderCompiler/TokenStream.cs
+++ /dev/null
@@ -1,321 +0,0 @@
-namespace Ghost.DSL.ShaderCompiler;
-
-internal static class TokenStreamImple
-{
- public static Token Peek(ReadOnlySpan tokens, int index, int length)
- {
- if (index + length < tokens.Length)
- {
- return tokens[index + length];
- }
-
- throw new IndexOutOfRangeException();
- }
-
- public static bool TryPeek(ReadOnlySpan tokens, int index, int length, out Token token)
- {
- if (index + length < tokens.Length)
- {
- token = tokens[index + length];
- return true;
- }
-
- token = default;
- return false;
- }
-
- public static bool TryConsume(ReadOnlySpan tokens, ref int index, out Token token)
- {
- if (index < tokens.Length)
- {
- token = tokens[index++];
- return true;
- }
-
- token = default;
- return false;
- }
-
- public static Token Consume(ReadOnlySpan tokens, ref int index, int count = 1)
- {
- if (index + count <= tokens.Length)
- {
- index += count;
- return tokens[index - 1];
- }
-
- throw new IndexOutOfRangeException();
- }
-
- public static bool Match(ReadOnlySpan tokens, int index, TokenType type, string? lexeme)
- {
- var t = Peek(tokens, index, 0);
- if (!t.Match(type, lexeme))
- {
- return false;
- }
-
- return true;
- }
-
- public static int MatchMany(ReadOnlySpan tokens, int index, TokenType type, string? lexeme)
- {
- var count = 0;
- while (TryPeek(tokens, index, 0, out var t) && t.Match(type, lexeme))
- {
- count++;
- }
-
- return count;
- }
-
- public static Token Expect(ReadOnlySpan tokens, ref int index, TokenType type, string? lexeme)
- {
- if (!TryPeek(tokens, index, 0, out var t))
- {
- throw new InvalidOperationException("Expected token but reached end of stream");
- }
-
- if (!t.Match(type, lexeme))
- {
- throw new InvalidOperationException($"Expected token {type}('{lexeme ?? "*"}') but got {t}");
- }
-
- index++;
- return t;
- }
-}
-
-internal class TokenStream
-{
- private readonly Token[] _tokens;
- private int _index = 0;
-
- public int Length => _tokens.Length;
- public int Remaining => _tokens.Length - _index;
- public bool HasMore => _index < _tokens.Length;
- public int Position
- {
- get => _index;
- set
- {
- if (value < 0 || value > _tokens.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(value), "Position must be within the bounds of the token stream.");
- }
- _index = value;
- }
- }
-
- public TokenStream(Token[] tokens)
- {
- _tokens = tokens;
- }
-
- public TokenStream(IEnumerable tokens)
- {
- _tokens = tokens.ToArray();
- }
-
- public Token Peek(int length = 0)
- {
- return TokenStreamImple.Peek(_tokens, _index, length);
- }
-
- public bool TryPeek(out Token token)
- {
- return TokenStreamImple.TryPeek(_tokens, _index, 0, out token);
- }
-
- public bool TryPeek(int length, out Token token)
- {
- return TokenStreamImple.TryPeek(_tokens, _index, length, out token);
- }
-
- public bool TryConsume(out Token token)
- {
- return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
- }
-
- public Token Consume(int count = 1)
- {
- return TokenStreamImple.Consume(_tokens, ref _index, count);
- }
-
- public bool Match(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.Match(_tokens, _index, type, lexeme);
- }
-
- public int MatchMany(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.MatchMany(_tokens, _index, type, lexeme);
- }
-
- public Token Expect(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.Expect(_tokens, ref _index, type, lexeme);
- }
-
- public TokenStreamSlice Slice(int length = -1)
- {
- if (length <= 0)
- {
- length = _tokens.Length - _index;
- }
-
- var slice = _tokens.AsSpan().Slice(_index, length);
- _index += length;
- return new TokenStreamSlice(slice);
- }
-
- public TokenStreamSlice SliceNextBlock()
- {
- var length = 0;
- var lBraceCount = 0;
- var rBraceCount = 0;
-
- Token nextToken;
-
- do
- {
- nextToken = Peek(length);
-
- if (length > 0 && lBraceCount > 0 && lBraceCount == rBraceCount)
- {
- break;
- }
-
- if (nextToken.Match(TokenType.LBrace))
- {
- lBraceCount++;
- }
- else if (nextToken.Match(TokenType.RBrace))
- {
- rBraceCount++;
- }
-
- length++;
- }
- while (_index + length < _tokens.Length);
-
- return Slice(length);
- }
-}
-
-internal ref struct TokenStreamSlice
-{
- private readonly ReadOnlySpan _tokens;
- private int _index;
-
- public readonly int Length => _tokens.Length;
- public readonly int Remaining => _tokens.Length - _index;
- public readonly bool HasMore => _index < _tokens.Length;
-
- public int Position
- {
- readonly get => _index;
- set
- {
- if (value < 0 || value > _tokens.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(value), "Position must be within the bounds of the token stream.");
- }
- _index = value;
- }
- }
-
- internal TokenStreamSlice(ReadOnlySpan tokens)
- {
- _tokens = tokens;
- _index = 0;
- }
-
- public Token Peek(int length = 0)
- {
- return TokenStreamImple.Peek(_tokens, _index, length);
- }
-
- public bool TryPeek(out Token token)
- {
- return TokenStreamImple.TryPeek(_tokens, _index, 0, out token);
- }
-
- public bool TryPeek(int length, out Token token)
- {
- return TokenStreamImple.TryPeek(_tokens, _index, length, out token);
- }
-
- public bool TryConsume(out Token token)
- {
- return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
- }
-
- public Token Consume(int count = 1)
- {
- return TokenStreamImple.Consume(_tokens, ref _index, count);
- }
-
- public bool Match(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.Match(_tokens, _index, type, lexeme);
- }
-
- public int MatchMany(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.MatchMany(_tokens, _index, type, lexeme);
- }
-
- public Token Expect(TokenType type, string? lexeme = null)
- {
- return TokenStreamImple.Expect(_tokens, ref _index, type, lexeme);
- }
-
- public TokenStreamSlice Slice(int length = -1)
- {
- if (length <= 0)
- {
- length = _tokens.Length - _index;
- }
-
- var slice = _tokens.Slice(_index, length);
- _index += length;
- return new TokenStreamSlice(slice);
- }
-
- public TokenStreamSlice SliceNextBlock()
- {
- var length = 0;
- var lBraceCount = 0;
- var rBraceCount = 0;
-
- do
- {
- var nextToken = Peek(length);
-
- if (length > 0 && lBraceCount > 0 && lBraceCount == rBraceCount)
- {
- break;
- }
-
- if (nextToken.Match(TokenType.LBrace))
- {
- lBraceCount++;
- }
- else if (nextToken.Match(TokenType.RBrace))
- {
- rBraceCount++;
- }
-
- length++;
- }
- while (_index + length < _tokens.Length);
-
- if (lBraceCount != rBraceCount)
- {
- throw new InvalidOperationException("Unmatched braces in token stream.");
- }
-
- return Slice(length);
- }
-}
diff --git a/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs b/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs
new file mode 100644
index 0000000..8ab875c
--- /dev/null
+++ b/Ghost.DSL/ShaderParser/AntlrShaderCompiler.cs
@@ -0,0 +1,383 @@
+using Antlr4.Runtime;
+using Ghost.Core.Graphics;
+using Ghost.DSL.ShaderCompiler;
+using Ghost.DSL.ShaderParser.Model;
+
+namespace Ghost.DSL.ShaderParser;
+
+public class AntlrShaderCompiler
+{
+ public static List ParseShaders(string source, out List errors)
+ {
+ errors = new List();
+
+ try
+ {
+ var inputStream = new AntlrInputStream(source);
+ var lexer = new GhostShaderLexer(inputStream);
+
+ // Capture lexer errors
+ lexer.RemoveErrorListeners();
+ var lexerErrorListener = new ErrorListener(errors);
+ lexer.AddErrorListener(lexerErrorListener);
+
+ var tokenStream = new CommonTokenStream(lexer);
+ var parser = new GhostShaderParser(tokenStream);
+
+ // Capture parser errors
+ parser.RemoveErrorListeners();
+ var parserErrorListener = new ErrorListener(errors);
+ parser.AddErrorListener(parserErrorListener);
+
+ var tree = parser.shaderFile();
+
+ if (errors.Count > 0)
+ {
+ return new List();
+ }
+
+ var visitor = new ShaderVisitor();
+ visitor.Visit(tree);
+
+ return visitor.Shaders;
+ }
+ catch (Exception ex)
+ {
+ errors.Add(new DSLShaderError
+ {
+ message = $"Unexpected error during parsing: {ex.Message}",
+ line = -1,
+ column = -1
+ });
+ return new List();
+ }
+ }
+
+ public static DSLShaderSemantics? ConvertToSemantics(ShaderModel model, out List errors)
+ {
+ errors = new List();
+
+ if (string.IsNullOrWhiteSpace(model.Name))
+ {
+ errors.Add(new DSLShaderError
+ {
+ message = "Shader name cannot be empty.",
+ line = 0,
+ column = 0
+ });
+ return null;
+ }
+
+ var semantics = new DSLShaderSemantics
+ {
+ name = model.Name,
+ properties = ConvertProperties(model.Properties, errors),
+ pipeline = ConvertPipeline(model.Pipeline, errors)
+ };
+
+ foreach (var pass in model.Passes)
+ {
+ var passSemantic = ConvertPass(pass, errors);
+ if (passSemantic != null)
+ {
+ semantics.passes ??= new List();
+ semantics.passes.Add(passSemantic);
+ }
+ }
+
+ return semantics;
+ }
+
+ private static List? ConvertProperties(PropertiesBlockModel? properties, List errors)
+ {
+ if (properties == null || properties.Properties.Count == 0)
+ {
+ return null;
+ }
+
+ var result = new List();
+ var usedNames = new HashSet();
+
+ foreach (var prop in properties.Properties)
+ {
+ if (usedNames.Contains(prop.Name))
+ {
+ errors.Add(new DSLShaderError
+ {
+ message = $"Duplicate property name '{prop.Name}'.",
+ line = 0,
+ column = 0
+ });
+ continue;
+ }
+
+ var semantic = new PropertySemantic
+ {
+ name = prop.Name,
+ scope = prop.Scope?.ToLower() == "global" ? PropertyScope.Global : PropertyScope.Local,
+ type = ParsePropertyType(prop.Type, errors)
+ };
+
+ if (prop.Initializer.Count > 0)
+ {
+ semantic.defaultValue = ParsePropertyValue(semantic.type, prop.Initializer, errors);
+ }
+
+ usedNames.Add(prop.Name);
+ result.Add(semantic);
+ }
+
+ return result;
+ }
+
+ private static ShaderPropertyType ParsePropertyType(string type, List errors)
+ {
+ return type.ToLower() switch
+ {
+ "float" => ShaderPropertyType.Float,
+ "float2" => ShaderPropertyType.Float2,
+ "float3" => ShaderPropertyType.Float3,
+ "float4" => ShaderPropertyType.Float4,
+ "float4x4" => ShaderPropertyType.Float4x4,
+ "int" => ShaderPropertyType.Int,
+ "int2" => ShaderPropertyType.Int2,
+ "int3" => ShaderPropertyType.Int3,
+ "int4" => ShaderPropertyType.Int4,
+ "uint" => ShaderPropertyType.UInt,
+ "uint2" => ShaderPropertyType.UInt2,
+ "uint3" => ShaderPropertyType.UInt3,
+ "uint4" => ShaderPropertyType.UInt4,
+ "bool" => ShaderPropertyType.Bool,
+ "bool2" => ShaderPropertyType.Bool2,
+ "bool3" => ShaderPropertyType.Bool3,
+ "bool4" => ShaderPropertyType.Bool4,
+ "tex2d" => ShaderPropertyType.Texture2D,
+ "tex3d" => ShaderPropertyType.Texture3D,
+ "texcube" => ShaderPropertyType.TextureCube,
+ "texcube_arr" => ShaderPropertyType.TextureCubeArray,
+ "tex2d_arr" => ShaderPropertyType.Texture2DArray,
+ "sampler" => ShaderPropertyType.Sampler,
+ _ => ShaderPropertyType.None
+ };
+ }
+
+ private static object? ParsePropertyValue(ShaderPropertyType type, List values, List errors)
+ {
+ // For textures, the value is an identifier (e.g., "white", "black")
+ if (type is ShaderPropertyType.Texture2D or ShaderPropertyType.Texture3D or ShaderPropertyType.TextureCube)
+ {
+ return values.Count > 0 ? values[0] : null;
+ }
+
+ // For samplers, no default value
+ if (type == ShaderPropertyType.Sampler)
+ {
+ return null;
+ }
+
+ // For numeric types, parse the values
+ try
+ {
+ return type switch
+ {
+ ShaderPropertyType.Float => values.Count > 0 ? float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0f,
+ ShaderPropertyType.Float2 => values.Count >= 2 ? new Misaki.HighPerformance.Mathematics.float2(
+ float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.Float3 => values.Count >= 3 ? new Misaki.HighPerformance.Mathematics.float3(
+ float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.Float4 => values.Count >= 4 ? new Misaki.HighPerformance.Mathematics.float4(
+ float.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture),
+ float.Parse(values[3], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.Int => values.Count > 0 ? int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0,
+ ShaderPropertyType.Int2 => values.Count >= 2 ? new Misaki.HighPerformance.Mathematics.int2(
+ int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.Int3 => values.Count >= 3 ? new Misaki.HighPerformance.Mathematics.int3(
+ int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.Int4 => values.Count >= 4 ? new Misaki.HighPerformance.Mathematics.int4(
+ int.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[1], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[2], System.Globalization.CultureInfo.InvariantCulture),
+ int.Parse(values[3], System.Globalization.CultureInfo.InvariantCulture)) : default,
+ ShaderPropertyType.UInt => values.Count > 0 ? uint.Parse(values[0], System.Globalization.CultureInfo.InvariantCulture) : 0u,
+ ShaderPropertyType.Bool => values.Count > 0 && (values[0] == "1" || values[0].ToLower() == "true"),
+ _ => null
+ };
+ }
+ catch (Exception ex)
+ {
+ errors.Add(new DSLShaderError
+ {
+ message = $"Failed to parse property value: {ex.Message}",
+ line = 0,
+ column = 0
+ });
+ return null;
+ }
+ }
+
+ private static PipelineSemantic? ConvertPipeline(PipelineBlockModel? pipeline, List errors)
+ {
+ if (pipeline == null || pipeline.Statements.Count == 0)
+ {
+ return null;
+ }
+
+ var semantic = new PipelineSemantic();
+
+ foreach (var (key, value) in pipeline.Statements)
+ {
+ switch (key.ToLower())
+ {
+ case "ztest":
+ semantic.zTest = value.ToLower() switch
+ {
+ "disabled" => ZTest.Disabled,
+ "less" => ZTest.Less,
+ "lessequal" => ZTest.LessEqual,
+ "equal" => ZTest.Equal,
+ "greaterequal" => ZTest.GreaterEqual,
+ "greater" => ZTest.Greater,
+ "notequal" => ZTest.NotEqual,
+ "always" => ZTest.Always,
+ _ => ZTest.Disabled
+ };
+ break;
+ case "zwrite":
+ semantic.zWrite = value.ToLower() == "on" ? ZWrite.On : ZWrite.Off;
+ break;
+ case "cull":
+ semantic.cull = value.ToLower() switch
+ {
+ "off" => Cull.Off,
+ "front" => Cull.Front,
+ "back" => Cull.Back,
+ _ => Cull.Off
+ };
+ break;
+ case "blend":
+ semantic.blend = value.ToLower() switch
+ {
+ "opaque" => Blend.Opaque,
+ "alpha" => Blend.Alpha,
+ "additive" => Blend.Additive,
+ "multiply" => Blend.Multiply,
+ "premultipliedalpha" => Blend.PremultipliedAlpha,
+ _ => Blend.Opaque
+ };
+ break;
+ case "color_mask":
+ semantic.colorMask = value.ToLower() == "all" ? ColorWriteMask.All : ColorWriteMask.None;
+ break;
+ }
+ }
+
+ return semantic;
+ }
+
+ private static PassSemantic? ConvertPass(PassBlockModel pass, List errors)
+ {
+ var semantic = new PassSemantic
+ {
+ name = pass.Name,
+ hlsl = pass.Hlsl?.Code,
+ defines = pass.Defines?.Defines,
+ includes = pass.Includes?.Includes,
+ localPipeline = ConvertPipeline(pass.LocalPipeline, errors)
+ };
+
+ if (pass.Keywords != null)
+ {
+ semantic.keywords = new List();
+ foreach (var group in pass.Keywords.Groups)
+ {
+ var keywordGroup = new KeywordsGroup
+ {
+ space = group.Scope?.ToLower() == "global" ? KeywordSpace.Global : KeywordSpace.Local,
+ keywords = group.Keywords
+ };
+ semantic.keywords.Add(keywordGroup);
+ }
+ }
+
+ foreach (var entry in pass.ShaderEntries)
+ {
+ var entryType = entry.EntryType.ToLower();
+ var shaderEntry = new ShaderEntryPoint
+ {
+ shader = entry.ShaderPath,
+ entry = entry.EntryPoint
+ };
+
+ switch (entryType)
+ {
+ case "mesh" or "ms":
+ semantic.meshShader = shaderEntry;
+ break;
+ case "pixel" or "ps":
+ semantic.pixelShader = shaderEntry;
+ break;
+ case "task" or "ts":
+ semantic.taskShader = shaderEntry;
+ break;
+ default:
+ errors.Add(new DSLShaderError
+ {
+ message = $"Unknown shader entry type '{entry.EntryType}'.",
+ line = 0,
+ column = 0
+ });
+ break;
+ }
+ }
+
+ if (semantic.meshShader.shader == null || semantic.pixelShader.shader == null)
+ {
+ errors.Add(new DSLShaderError
+ {
+ message = $"Pass '{pass.Name}' must contain a mesh/ms shader and a pixel/ps shader declaration.",
+ line = 0,
+ column = 0
+ });
+ }
+
+ return semantic;
+ }
+
+ private class ErrorListener : BaseErrorListener, IAntlrErrorListener, IAntlrErrorListener
+ {
+ private readonly List _errors;
+
+ public ErrorListener(List errors)
+ {
+ _errors = errors;
+ }
+
+ public void SyntaxError(TextWriter output, IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
+ {
+ _errors.Add(new DSLShaderError
+ {
+ message = msg,
+ line = line,
+ column = charPositionInLine
+ });
+ }
+
+ public new void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
+ {
+ _errors.Add(new DSLShaderError
+ {
+ message = msg,
+ line = line,
+ column = charPositionInLine
+ });
+ }
+ }
+}
diff --git a/Ghost.DSL/ShaderParser/Model/ShaderModel.cs b/Ghost.DSL/ShaderParser/Model/ShaderModel.cs
new file mode 100644
index 0000000..bb55d3f
--- /dev/null
+++ b/Ghost.DSL/ShaderParser/Model/ShaderModel.cs
@@ -0,0 +1,78 @@
+namespace Ghost.DSL.ShaderParser.Model;
+
+public class ShaderModel
+{
+ public string Name { get; set; } = string.Empty;
+ public PropertiesBlockModel? Properties { get; set; }
+ public PipelineBlockModel? Pipeline { get; set; }
+ public List Passes { get; set; } = new();
+ public List FunctionCalls { get; set; } = new();
+}
+
+public class PropertiesBlockModel
+{
+ public List Properties { get; set; } = new();
+}
+
+public class PropertyDeclarationModel
+{
+ public string? Scope { get; set; }
+ public string Type { get; set; } = string.Empty;
+ public string Name { get; set; } = string.Empty;
+ public List Initializer { get; set; } = new();
+}
+
+public class PipelineBlockModel
+{
+ public Dictionary Statements { get; set; } = new();
+}
+
+public class PassBlockModel
+{
+ public string Name { get; set; } = string.Empty;
+ public PipelineBlockModel? LocalPipeline { get; set; }
+ public DefinesBlockModel? Defines { get; set; }
+ public IncludesBlockModel? Includes { get; set; }
+ public KeywordsBlockModel? Keywords { get; set; }
+ public HlslBlockModel? Hlsl { get; set; }
+ public List ShaderEntries { get; set; } = new();
+}
+
+public class DefinesBlockModel
+{
+ public List Defines { get; set; } = new();
+}
+
+public class IncludesBlockModel
+{
+ public List Includes { get; set; } = new();
+}
+
+public class KeywordsBlockModel
+{
+ public List Groups { get; set; } = new();
+}
+
+public class KeywordGroupModel
+{
+ public string? Scope { get; set; }
+ public List Keywords { get; set; } = new();
+}
+
+public class HlslBlockModel
+{
+ public string Code { get; set; } = string.Empty;
+}
+
+public class ShaderEntryModel
+{
+ public string EntryType { get; set; } = string.Empty; // "mesh", "pixel", "task", etc.
+ public string ShaderPath { get; set; } = string.Empty;
+ public string EntryPoint { get; set; } = string.Empty;
+}
+
+public class FunctionCallModel
+{
+ public string Name { get; set; } = string.Empty;
+ public List Arguments { get; set; } = new();
+}
diff --git a/Ghost.DSL/ShaderParser/ShaderVisitor.cs b/Ghost.DSL/ShaderParser/ShaderVisitor.cs
new file mode 100644
index 0000000..34f13cd
--- /dev/null
+++ b/Ghost.DSL/ShaderParser/ShaderVisitor.cs
@@ -0,0 +1,261 @@
+using Antlr4.Runtime.Misc;
+using Ghost.DSL.ShaderParser.Model;
+
+namespace Ghost.DSL.ShaderParser;
+
+public class ShaderVisitor : GhostShaderParserBaseVisitor