diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 9de14957..45b41fcd 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -38,6 +38,7 @@ "TestAll", "TestCodeGen", "TestCompiler", + "TestCore", "TestIntegration", "TestParser", "TestRuntime", diff --git a/Cesium.CodeGen/CompilationOptions.cs b/Cesium.CodeGen/CompilationOptions.cs index f07c4273..9f2bdad5 100644 --- a/Cesium.CodeGen/CompilationOptions.cs +++ b/Cesium.CodeGen/CompilationOptions.cs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT +using Cesium.Core.Warnings; using Mono.Cecil; using TruePath; @@ -19,7 +20,8 @@ public record CompilationOptions( IList DefineConstants, IList AdditionalIncludeDirectories, bool ProducePreprocessedFile, - bool ProduceAstFile) + bool ProduceAstFile, + WarningsSet WarningSet = WarningsSet.None) { public virtual bool Equals(CompilationOptions? other) { @@ -36,7 +38,8 @@ public virtual bool Equals(CompilationOptions? other) && DefineConstants.SequenceEqual(other.DefineConstants) && AdditionalIncludeDirectories.SequenceEqual(other.AdditionalIncludeDirectories) && ProducePreprocessedFile == other.ProducePreprocessedFile - && ProduceAstFile == other.ProduceAstFile; + && ProduceAstFile == other.ProduceAstFile + && WarningSet == other.WarningSet; } public override int GetHashCode() @@ -63,6 +66,7 @@ public override int GetHashCode() } hashCode.Add(ProducePreprocessedFile); hashCode.Add(ProduceAstFile); + hashCode.Add(WarningSet); return hashCode.ToHashCode(); } } diff --git a/Cesium.CodeGen/CompilerWarningProcessor.cs b/Cesium.CodeGen/CompilerWarningProcessor.cs new file mode 100644 index 00000000..a07afdaa --- /dev/null +++ b/Cesium.CodeGen/CompilerWarningProcessor.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using Cesium.Core; +using Cesium.Core.Warnings; + +namespace Cesium.CodeGen; + +public class CompilerWarningProcessor(WarningsSet set) : IWarningProcessor +{ + public void EmitWarning(CompilerWarning warning) + { + if (set.HasFlag(warning.Set)) + Console.Error.WriteLine($"Warning: {warning.Message} [-W{warning.Set.ToString().FromCamelToKebab()}]"); + } +} diff --git a/Cesium.CodeGen/Contexts/FunctionScope.cs b/Cesium.CodeGen/Contexts/FunctionScope.cs index 12fb8925..c777db19 100644 --- a/Cesium.CodeGen/Contexts/FunctionScope.cs +++ b/Cesium.CodeGen/Contexts/FunctionScope.cs @@ -9,6 +9,7 @@ using Cesium.CodeGen.Ir.Expressions; using Cesium.CodeGen.Ir.Types; using Cesium.Core; +using Cesium.Core.Warnings; using Mono.Cecil; using Mono.Cecil.Cil; using PointerType = Cesium.CodeGen.Ir.Types.PointerType; @@ -19,7 +20,9 @@ internal record FunctionScope(TranslationUnitContext Context, FunctionInfo Funct { public AssemblyContext AssemblyContext => Context.AssemblyContext; public ModuleDefinition Module => Context.Module; + public IWarningProcessor WarningProcessor => Context.WarningProcessor; public TargetArchitectureSet ArchitectureSet => AssemblyContext.ArchitectureSet; + public FunctionInfo? GetFunctionInfo(string identifier) => Context.GetFunctionInfo(identifier); diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 82e9d125..60884dbd 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -12,6 +12,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Security.AccessControl; +using Cesium.Core.Warnings; using PointerType = Cesium.CodeGen.Ir.Types.PointerType; namespace Cesium.CodeGen.Contexts; @@ -27,6 +28,8 @@ public class TranslationUnitContext public TypeDefinition ModuleType => Module.GetType(""); public TypeDefinition GlobalType => AssemblyContext.GlobalType; + public IWarningProcessor WarningProcessor { get; } + private TypeDefinition? _translationUnitLevelType; private Dictionary Functions { get; } = new(); @@ -39,6 +42,7 @@ public TranslationUnitContext(AssemblyContext assemblyContext, string name) { AssemblyContext = assemblyContext; Name = name; + WarningProcessor = new CompilerWarningProcessor(assemblyContext.CompilationOptions.WarningSet); } /// diff --git a/Cesium.Compiler.Tests/JsonObjectFileTests.cs b/Cesium.Compiler.Tests/JsonObjectFileTests.cs index b2c551a7..80bfcb4b 100644 --- a/Cesium.Compiler.Tests/JsonObjectFileTests.cs +++ b/Cesium.Compiler.Tests/JsonObjectFileTests.cs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT using Cesium.CodeGen; +using Cesium.Core.Warnings; using Cesium.TestFramework; using Mono.Cecil; using TruePath; @@ -41,7 +42,8 @@ public void SupportedExtensions(string fileName, bool result) => new("/nonexistent-folder/include") ], ProducePreprocessedFile: false, - ProduceAstFile: true + ProduceAstFile: true, + WarningsSet.All ); [Fact] diff --git a/Cesium.Compiler.Tests/verified/JsonObjectFileTests.ObjectFileGetsDumpedCorrectly.verified.txt b/Cesium.Compiler.Tests/verified/JsonObjectFileTests.ObjectFileGetsDumpedCorrectly.verified.txt index c6ced1e7..f5adcb7c 100644 --- a/Cesium.Compiler.Tests/verified/JsonObjectFileTests.ObjectFileGetsDumpedCorrectly.verified.txt +++ b/Cesium.Compiler.Tests/verified/JsonObjectFileTests.ObjectFileGetsDumpedCorrectly.verified.txt @@ -27,6 +27,7 @@ "/nonexistent-folder/include" ], "ProducePreprocessedFile": false, - "ProduceAstFile": true + "ProduceAstFile": true, + "WarningSet": "All" } -} \ No newline at end of file +} diff --git a/Cesium.Compiler/Arguments.cs b/Cesium.Compiler/Arguments.cs index 7b93ebd1..b957a5ce 100644 --- a/Cesium.Compiler/Arguments.cs +++ b/Cesium.Compiler/Arguments.cs @@ -60,7 +60,7 @@ public class Arguments public int OptimizationLevel { get; init; } = 0; [Option('W', HelpText = "Enable warnings set")] - public string WarningsSet { get; init; } = ""; + public IEnumerable WarningsSet { get; init; } = Array.Empty(); [Option('E', HelpText = "Produce preprocessed file")] public bool ProducePreprocessedFile { get; init; } = false; diff --git a/Cesium.Compiler/Main.cs b/Cesium.Compiler/Main.cs index 64bc992b..5bf5ec95 100644 --- a/Cesium.Compiler/Main.cs +++ b/Cesium.Compiler/Main.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using Cesium.CodeGen; using Cesium.Core; +using Cesium.Core.Warnings; using Mono.Cecil; using TruePath; @@ -36,6 +37,18 @@ public static async Task Main(string[] args) ".dll" => ModuleKind.Dll, var o => throw new CompilationException($"Unknown file extension: {o}. \"modulekind\" is not specified.") }; + + var warningsSet = options.WarningsSet.Aggregate(WarningsSet.None, (set, s) => + { + var warn = s.FromKebabToCamel(); + if (Enum.TryParse(warn, true, out var parsed)) + { + return set | parsed; + } + + throw new CompilationException($"Unknown warning: {warn}"); + }); + var compilationOptions = new CompilationOptions( targetRuntime, targetArchitectureSet, @@ -48,7 +61,8 @@ public static async Task Main(string[] args) options.DefineConstant.ToList(), options.IncludeDirectories.Select(x => new LocalPath(x)).ToList(), options.ProducePreprocessedFile, - options.DumpAst); + options.DumpAst, + warningsSet); if (options.ProduceObjectFileImitation) { diff --git a/Cesium.Compiler/WarningProcessor.cs b/Cesium.Compiler/WarningProcessor.cs index d66be7ad..f8094e26 100644 --- a/Cesium.Compiler/WarningProcessor.cs +++ b/Cesium.Compiler/WarningProcessor.cs @@ -6,9 +6,9 @@ namespace Cesium.Compiler; -public class WarningProcessor : IWarningProcessor +public class WarningProcessor : IWarningProcessor { - public void EmitWarning(PreprocessorWarning warning) + public void EmitWarning(DiagnosticWarning warning) { Console.Error.WriteLine($"{warning.Location}: warning: {warning.Message}"); } diff --git a/Cesium.Core.Tests/Cesium.Core.Tests.csproj b/Cesium.Core.Tests/Cesium.Core.Tests.csproj new file mode 100644 index 00000000..7f745d55 --- /dev/null +++ b/Cesium.Core.Tests/Cesium.Core.Tests.csproj @@ -0,0 +1,31 @@ + + + + + + net10.0 + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/Cesium.Core.Tests/StringFormatExtensionsTest.cs b/Cesium.Core.Tests/StringFormatExtensionsTest.cs new file mode 100644 index 00000000..c9691a29 --- /dev/null +++ b/Cesium.Core.Tests/StringFormatExtensionsTest.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +namespace Cesium.Core.Tests; + +public class StringFormatExtensionsTest +{ + [Theory] + [InlineData("", "")] + [InlineData("hello", "Hello")] + [InlineData("hello-world", "HelloWorld")] + [InlineData("-hello", "Hello")] + [InlineData("hello-", "Hello")] + [InlineData("hello--world", "HelloWorld")] + [InlineData("---", "")] + [InlineData("hello-123", "Hello123")] + [InlineData("a-b-123", "AB123")] + [InlineData("HELLO-WORLD", "HELLOWORLD")] + [InlineData("a", "A")] + [InlineData("a-b", "AB")] + public void StringFromKebabToCamelTest(string input, string expected) + => Assert.Equal(expected, input.FromKebabToCamel()); + + [Theory] + [InlineData("", "")] + [InlineData("Hello", "hello")] + [InlineData("HelloWorld", "hello-world")] + [InlineData("hello", "hello")] + [InlineData("HELLO", "hello")] + [InlineData("XMLHttpRequest", "xml-http-request")] + [InlineData("Hello123", "hello-123")] + [InlineData("Hello123World", "hello-123-world")] + [InlineData("A", "a")] + [InlineData("a", "a")] + [InlineData("123", "123")] + [InlineData("A1B2", "a-1-b-2")] + [InlineData("AbcDef", "abc-def")] + public void StringFromCamelToKebabTest(string input, string expected) + => Assert.Equal(expected, input.FromCamelToKebab()); +} diff --git a/Cesium.Core/StringFormatExtensions.cs b/Cesium.Core/StringFormatExtensions.cs new file mode 100644 index 00000000..7775013b --- /dev/null +++ b/Cesium.Core/StringFormatExtensions.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System.Text; + +namespace Cesium.Core; + +public static class StringFormatExtensions +{ + extension(ReadOnlySpan str) + { + public string FromKebabToCamel() + { + if (str.IsEmpty) return string.Empty; + + var sb = new StringBuilder(); + + foreach (var part in str.Split('-')) + { + if (part.Start.Value == part.End.Value) + continue; + + sb.Append(char.ToUpper(str[part.Start])); + + if (part.Start.Value != part.End.Value) + sb.Append(str[(part.Start.Value + 1)..part.End]); + } + + return sb.ToString(); + } + + public string FromCamelToKebab() + { + if (str.IsEmpty) return string.Empty; + + var sb = new StringBuilder(); + + for (int i = 0; i < str.Length; i++) + { + if (char.IsLower(str[i])) + sb.Append(str[i]); + else if (i == 0) + sb.Append(char.ToLower(str[i])); + else if (char.IsDigit(str[i]) && !char.IsDigit(str[i - 1])) + sb.Append('-').Append(str[i]); + else if (char.IsDigit(str[i])) + sb.Append(str[i]); + else if (char.IsLower(str[i - 1])) + sb.Append('-').Append(char.ToLower(str[i])); + else if (i + 1 == str.Length || char.IsUpper(str[i + 1])) + sb.Append(char.ToLower(str[i])); + else + sb.Append('-').Append(char.ToLower(str[i])); + } + + return sb.ToString(); + } + } +} diff --git a/Cesium.Core/Warnings/CompilerWarning.cs b/Cesium.Core/Warnings/CompilerWarning.cs new file mode 100644 index 00000000..3a922278 --- /dev/null +++ b/Cesium.Core/Warnings/CompilerWarning.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +namespace Cesium.Core.Warnings; + +public record CompilerWarning(string Message, WarningsSet Set) + : DiagnosticWarning(new SourceLocationInfo(string.Empty, 0, 0), Message); +// TODO: In future we have to determine a location where a warning is triggered diff --git a/Cesium.Core/Warnings/DiagnosticWarning.cs b/Cesium.Core/Warnings/DiagnosticWarning.cs new file mode 100644 index 00000000..32646dd5 --- /dev/null +++ b/Cesium.Core/Warnings/DiagnosticWarning.cs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +namespace Cesium.Core.Warnings; + +public record DiagnosticWarning(SourceLocationInfo Location, string Message); diff --git a/Cesium.Core/Warnings/IWarningProcessor.cs b/Cesium.Core/Warnings/IWarningProcessor.cs index d01acf9a..ddc278c9 100644 --- a/Cesium.Core/Warnings/IWarningProcessor.cs +++ b/Cesium.Core/Warnings/IWarningProcessor.cs @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2025 Cesium contributors +// SPDX-FileCopyrightText: 2025-2026 Cesium contributors // // SPDX-License-Identifier: MIT namespace Cesium.Core.Warnings; -public interface IWarningProcessor +public interface IWarningProcessor where T : DiagnosticWarning { - public void EmitWarning(PreprocessorWarning warning); + public void EmitWarning(T warning); } diff --git a/Cesium.Core/Warnings/PreprocessorWarning.cs b/Cesium.Core/Warnings/PreprocessorWarning.cs index 65145af3..ca9bd217 100644 --- a/Cesium.Core/Warnings/PreprocessorWarning.cs +++ b/Cesium.Core/Warnings/PreprocessorWarning.cs @@ -4,4 +4,5 @@ namespace Cesium.Core.Warnings; -public record PreprocessorWarning(SourceLocationInfo Location, string Message); +public record PreprocessorWarning(SourceLocationInfo Location, string Message) + : DiagnosticWarning(Location, Message); diff --git a/Cesium.Core/Warnings/WarningsSet.cs b/Cesium.Core/Warnings/WarningsSet.cs new file mode 100644 index 00000000..b98e0a53 --- /dev/null +++ b/Cesium.Core/Warnings/WarningsSet.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +namespace Cesium.Core.Warnings; + +[Flags] +public enum WarningsSet : uint +{ + None = 0, + + // --- Group "All" --- + ReturnType = 1u << 0, // -Wreturn-type + Format = 1u << 1, // -Wformat + Unused = 1u << 2, // -Wunused + Implicit = 1u << 3, // -Wimplicit + Parentheses = 1u << 4, // -Wparentheses + Switch = 1u << 5, // -Wswitch + Uninitialized = 1u << 6, // -Wuninitialized + MissingBraces = 1u << 7, // -Wmissing-braces + SequencePoint = 1u << 8, // -Wsequence-point + + // --- Group "Extra" --- + SignCompare = 1u << 9, // -Wsign-compare + TypeLimits = 1u << 10, // -Wtype-limits + UnusedParameter = 1u << 11, // -Wunused-parameter + MissingFieldInits = 1u << 12, // -Wmissing-field-initializers + + // --- Group "Pedantic" --- + Shadow = 1u << 13, // -Wshadow + Conversion = 1u << 14, // -Wconversion + PointerArith = 1u << 15, // -Wpointer-arith + CastAlign = 1u << 16, // -Wcast-align + FloatEqual = 1u << 17, // -Wfloat-equal + LogicalOp = 1u << 18, // -Wlogical-op + Pedantic = 1u << 19, // -Wpedantic + + // --- Combinations --- + + All = ReturnType | Format | Unused | Implicit | Parentheses | Switch | Uninitialized | MissingBraces | SequencePoint, + Extra = SignCompare | TypeLimits | UnusedParameter | MissingFieldInits, + Full = All | Extra | Shadow | Conversion | PointerArith | CastAlign | FloatEqual | LogicalOp | Pedantic, +} diff --git a/Cesium.Preprocessor/CPreprocessor.cs b/Cesium.Preprocessor/CPreprocessor.cs index 1f2a3e05..d9d98426 100644 --- a/Cesium.Preprocessor/CPreprocessor.cs +++ b/Cesium.Preprocessor/CPreprocessor.cs @@ -20,7 +20,7 @@ public record CPreprocessor( ILexer> Lexer, IIncludeContext IncludeContext, IMacroContext MacroContext, - IWarningProcessor WarningProcessor) + IWarningProcessor WarningProcessor) { private readonly MacroExpansionEngine _macroExpansion = new(WarningProcessor, MacroContext); diff --git a/Cesium.Preprocessor/MacroExpansionEngine.cs b/Cesium.Preprocessor/MacroExpansionEngine.cs index 67df6c1c..418224d8 100644 --- a/Cesium.Preprocessor/MacroExpansionEngine.cs +++ b/Cesium.Preprocessor/MacroExpansionEngine.cs @@ -13,7 +13,7 @@ namespace Cesium.Preprocessor; -public class MacroExpansionEngine(IWarningProcessor warningProcessor, IMacroContext macroContext) +public class MacroExpansionEngine(IWarningProcessor warningProcessor, IMacroContext macroContext) { public IEnumerable> ExpandMacros(IEnumerable> tokens) { diff --git a/Cesium.Preprocessor/TransactionalLexer.cs b/Cesium.Preprocessor/TransactionalLexer.cs index 2fb87902..f95ec73c 100644 --- a/Cesium.Preprocessor/TransactionalLexer.cs +++ b/Cesium.Preprocessor/TransactionalLexer.cs @@ -11,7 +11,7 @@ namespace Cesium.Preprocessor; internal class TransactionalLexer( IEnumerable> tokens, - IWarningProcessor warningProcessor) : IDisposable + IWarningProcessor warningProcessor) : IDisposable { private readonly List> _allTokens = ToList(tokens, warningProcessor); private int _nextTokenToReturn; @@ -94,7 +94,7 @@ public void Dispose() private static List> ToList( IEnumerable> tokens, - IWarningProcessor? warningProcessor) + IWarningProcessor? warningProcessor) { var result = new List>(); diff --git a/Cesium.TestFramework/PreprocessorUtil.cs b/Cesium.TestFramework/PreprocessorUtil.cs index 718cc4dd..234a3692 100644 --- a/Cesium.TestFramework/PreprocessorUtil.cs +++ b/Cesium.TestFramework/PreprocessorUtil.cs @@ -30,7 +30,7 @@ public static async Task DoPreprocess( } } - IWarningProcessor warningProcessor = onWarning == null + IWarningProcessor warningProcessor = onWarning == null ? new ListWarningProcessor() : new LambdaWarningProcessor(onWarning); using (warningProcessor as IDisposable) diff --git a/Cesium.TestFramework/WarningProcessors.cs b/Cesium.TestFramework/WarningProcessors.cs index d521e119..5ad26fc8 100644 --- a/Cesium.TestFramework/WarningProcessors.cs +++ b/Cesium.TestFramework/WarningProcessors.cs @@ -6,12 +6,12 @@ namespace Cesium.TestFramework; -public class LambdaWarningProcessor(Action onWarning) : IWarningProcessor +public class LambdaWarningProcessor(Action onWarning) : IWarningProcessor { public void EmitWarning(PreprocessorWarning warning) => onWarning(warning); } -public sealed class ListWarningProcessor : IWarningProcessor, IDisposable +public sealed class ListWarningProcessor : IWarningProcessor, IDisposable { public readonly List Warnings = new(); public void EmitWarning(PreprocessorWarning warning) diff --git a/Cesium.sln b/Cesium.sln index 6c5c87fe..ece13ab9 100644 --- a/Cesium.sln +++ b/Cesium.sln @@ -105,6 +105,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cesium.Templates", "Cesium.Templates\Cesium.Templates.csproj", "{D26ED37B-1F41-4001-8755-47026763A32A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cesium.Core.Tests", "Cesium.Core.Tests\Cesium.Core.Tests.csproj", "{827F4CCF-8204-43D8-BFA4-DA704FCAD1F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -185,6 +187,10 @@ Global {D26ED37B-1F41-4001-8755-47026763A32A}.Debug|Any CPU.Build.0 = Debug|Any CPU {D26ED37B-1F41-4001-8755-47026763A32A}.Release|Any CPU.ActiveCfg = Release|Any CPU {D26ED37B-1F41-4001-8755-47026763A32A}.Release|Any CPU.Build.0 = Release|Any CPU + {827F4CCF-8204-43D8-BFA4-DA704FCAD1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827F4CCF-8204-43D8-BFA4-DA704FCAD1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827F4CCF-8204-43D8-BFA4-DA704FCAD1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827F4CCF-8204-43D8-BFA4-DA704FCAD1F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/Build.Tests.cs b/build/Build.Tests.cs index 156e49c6..4ac7e105 100644 --- a/build/Build.Tests.cs +++ b/build/Build.Tests.cs @@ -24,6 +24,9 @@ partial class Build Target TestRuntime => _ => _ .Executes(() => ExecuteTests(Solution.Cesium_Runtime_Tests)); + Target TestCore => _ => _ + .Executes(() => ExecuteTests(Solution.Cesium_Core_Tests)); + Target TestSdk => _ => _ .DependsOn(PackCompilerBundleNuPkg) .DependsOn(PackSdk) @@ -35,6 +38,7 @@ partial class Build .DependsOn(TestIntegration) .DependsOn(TestParser) .DependsOn(TestRuntime) + .DependsOn(TestCore) .DependsOn(TestSdk); void ExecuteTests(Project project)