diff --git a/src/ShaderTools.CodeAnalysis.Hlsl.Tests/Parser/PreprocessorTests.cs b/src/ShaderTools.CodeAnalysis.Hlsl.Tests/Parser/PreprocessorTests.cs index 080f9ce4..19ca2094 100644 --- a/src/ShaderTools.CodeAnalysis.Hlsl.Tests/Parser/PreprocessorTests.cs +++ b/src/ShaderTools.CodeAnalysis.Hlsl.Tests/Parser/PreprocessorTests.cs @@ -1071,6 +1071,66 @@ public void TestPragma() new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive, Text = @"something custom" }); } + [Fact] + public void TestPragmaOnce() + { + const string fooText = @" +#pragma once +#define FOO +float foo = 1.0f; +"; + const string barText = @" +#define BAR +#include +#include ""foo.hlsl"" +float bar = 2.0f; +#pragma once 1 +#pragma once : s +#pragma once() +#pragma once +"; + const string text = @" +#include +#include ""foo.hlsl"" +#include ""bar.hlsl"" +#include +#include ""foo.hlsl"" +#include ""bar.hlsl"" +float rawr = 3.0f; +#define RAWR +"; + var node = Parse( + text, + new HlslParseOptions + { + AdditionalIncludeDirectories = { "test" } + }, + new InMemoryFileSystem(new Dictionary + { + { Path.Combine("test", "foo.hlsl"), fooText }, + { Path.Combine("test2", "bar.hlsl"), barText } + }), Path.Combine("test2", "__Root__.hlsl")); + + TestRoundTripping(node, text); + VerifyDirectivesSpecial(node, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.ObjectLikeDefineDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.ObjectLikeDefineDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.PragmaDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.IncludeDirectiveTrivia, Status = NodeStatus.IsActive }, + new DirectiveInfo { Kind = SyntaxKind.ObjectLikeDefineDirectiveTrivia, Status = NodeStatus.IsActive }); + } + [Fact] public void HandlesVirtualDirectoryMapping_IncludeIsInRootOfVirtualDirectory_IncludeHandledSuccessfully() { @@ -1174,9 +1234,9 @@ private static IReadOnlyList LexAllTokens(string text) return SyntaxFactory.ParseAllTokens(new SourceFile(SourceText.From(text))); } - private static CompilationUnitSyntax Parse(string text, HlslParseOptions options = null, IIncludeFileSystem fileSystem = null) + private static CompilationUnitSyntax Parse(string text, HlslParseOptions options = null, IIncludeFileSystem fileSystem = null, string filePath = "__Root__.hlsl") { - return SyntaxFactory.ParseCompilationUnit(new SourceFile(SourceText.From(text), "__Root__.hlsl"), options, fileSystem); + return SyntaxFactory.ParseCompilationUnit(new SourceFile(SourceText.From(text), filePath), options, fileSystem); } private static void TestRoundTripping(CompilationUnitSyntax node, string text, bool disallowErrors = true) diff --git a/src/ShaderTools.CodeAnalysis.Hlsl/Parser/DirectiveParser.cs b/src/ShaderTools.CodeAnalysis.Hlsl/Parser/DirectiveParser.cs index 5b520d42..2b10c712 100644 --- a/src/ShaderTools.CodeAnalysis.Hlsl/Parser/DirectiveParser.cs +++ b/src/ShaderTools.CodeAnalysis.Hlsl/Parser/DirectiveParser.cs @@ -293,6 +293,9 @@ private PragmaDirectiveTriviaSyntax ParsePragmaDirective(SyntaxToken hash, Synta var eod = ParseEndOfDirective(false); + if (body.Count > 0 && body.First().ContextualKind == SyntaxKind.OnceKeyword) + _lexer.ApplyPragmaOnceDirective(); + return new PragmaDirectiveTriviaSyntax(hash, keyword, body, eod, isActive); } diff --git a/src/ShaderTools.CodeAnalysis.Hlsl/Parser/HlslLexer.cs b/src/ShaderTools.CodeAnalysis.Hlsl/Parser/HlslLexer.cs index e9dedceb..cc6d34b5 100644 --- a/src/ShaderTools.CodeAnalysis.Hlsl/Parser/HlslLexer.cs +++ b/src/ShaderTools.CodeAnalysis.Hlsl/Parser/HlslLexer.cs @@ -17,12 +17,14 @@ namespace ShaderTools.CodeAnalysis.Hlsl.Parser public sealed partial class HlslLexer : ILexer { private readonly IIncludeFileResolver _includeFileResolver; + private readonly HashSet _includeOnceList = new HashSet(); private readonly List _leadingTrivia = new List(); private readonly List _trailingTrivia = new List(); private readonly List _diagnostics = new List(); private LexerMode _mode; public bool ExpandMacros { get; set; } + public void ApplyPragmaOnceDirective() { _includeOnceList.Add(File.FilePath); } private List _expandedMacroTokens; private int _expandedMacroIndex; @@ -338,7 +340,8 @@ private bool LexDirectiveAndExcludedTrivia( if (include != null) { triviaList.Add(includeDirective); - PushIncludeContext(include); + if (!_includeOnceList.Contains(include.FilePath)) + PushIncludeContext(include); return false; } } diff --git a/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxFacts.cs b/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxFacts.cs index 5ffac11a..6ed42a80 100644 --- a/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxFacts.cs +++ b/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxFacts.cs @@ -219,6 +219,8 @@ public static string GetText(this SyntaxKind kind) return "min16int"; case SyntaxKind.Min16UintKeyword: return "min16uint"; + case SyntaxKind.OnceKeyword: + return "once"; case SyntaxKind.OutKeyword: return "out"; case SyntaxKind.PackMatrixKeyword: @@ -2854,6 +2856,8 @@ public static SyntaxKind GetPreprocessorKeywordKind(string text) return SyntaxKind.PackMatrixKeyword; case "warning": return SyntaxKind.WarningKeyword; + case "once": + return SyntaxKind.OnceKeyword; default: return SyntaxKind.IdentifierToken; } @@ -2880,6 +2884,7 @@ public static bool IsPreprocessorKeyword(this SyntaxKind kind) case SyntaxKind.MessageKeyword: case SyntaxKind.PackMatrixKeyword: case SyntaxKind.WarningKeyword: + case SyntaxKind.OnceKeyword: return true; default: diff --git a/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxKind.cs b/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxKind.cs index 3ee96a17..655f521d 100644 --- a/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxKind.cs +++ b/src/ShaderTools.CodeAnalysis.Hlsl/Syntax/SyntaxKind.cs @@ -324,6 +324,7 @@ public enum SyntaxKind NointerpolationKeyword, NoperspectiveKeyword, NullKeyword, + OnceKeyword, OutKeyword, OutputPatchKeyword, PackMatrixKeyword, @@ -415,8 +416,8 @@ public enum SyntaxKind Uint4x2Keyword, Uint4x3Keyword, Uint4x4Keyword, - Uint64_tKeyword, - Uint64_t1Keyword, + Uint64_tKeyword, + Uint64_t1Keyword, Uint64_t2Keyword, Uint64_t3Keyword, Uint64_t4Keyword, @@ -436,8 +437,8 @@ public enum SyntaxKind Uint64_t4x2Keyword, Uint64_t4x3Keyword, Uint64_t4x4Keyword, - Uint16_tKeyword, - Uint16_t1Keyword, + Uint16_tKeyword, + Uint16_t1Keyword, Uint16_t2Keyword, Uint16_t3Keyword, Uint16_t4Keyword,