From 979480b8e89bcf507d56f30e412bec28139a80c7 Mon Sep 17 00:00:00 2001 From: Lucas Girouard-Stranks Date: Wed, 13 Apr 2022 23:53:55 -0400 Subject: [PATCH] Cross-parsing (#77) * Multi-pass wip * Wip * Start adding tests * Beginning of integration tests * Create features folder * Fix `.csproj` * Cleanup tests * Add platform check for integration tests * Improve exception output for Clang errors + move around some files * Fix linux C header helper * Update error message * Re-structure for integration tests * Restructure for logging * Restructure more for logging and DI * Start logging clang explorer * Update logs + libclang generated code * Seal classes * Finish logging for extract * Finish first round of integration tests * Use main handler for backwards compatibility * Fix config file path option * Change `config.json` schema * Restructure + add schema * Fix build errors * Finish schema attributes * Rollback to uppercase `C2CS` for C# project name * Add Linux system include path * Update GitHub action * Update GitHub action * Update main.yml * Log when system directory does not exist * Short circuit when a use case step fails * Refactor integration test fixture data * Assert ast is not empty in fixture * Add log for failing Clang parse diagnostics * Change log to error * Change failure log to use error log level * Update GitHub actions * Restructure * Update GitHub actions * Update GitHub actions * Fix mistake * Fix clang arguments builder * Improve logging for failed use case step * Restructure * Don't throw * Add linux system include directory * Update GitHub actions * Update `c2cs_helper.h` * Write code to console * Upload test bin files * Update GitHub actions * Upload test data files for debugging * Upload test data for debugging with platform name * Upload test data only on failure * Use `gnu` for Windows when parsing C code for Windows target * Update `c2cs_helper.h` * Restructure + use target platform arch + os when checking plat mismatch * Update `c2cs_helper.h` for windows-gnu * Update libclang`Index.h` * Fix enums and some small things * Use official Clang on Linux * Fix failing integration test * Use hard coded check for libclang on Linux as find is taking too long * Install and use Clang from direct LLVM downloads in GitHub actions * Try to fix GitHub actions * Fix GitHub action workflow * Fix GitHub action workflow * Move scripts out of `/.github/ folder * Try to debug GitHub action workflow * Try to debug GitHub action workflow more * Try to debug GitHub action workflow again * Update main.yml * Clone repository before executing scripts * install gcc-multilib to fix C compilation/parsing errors x * Use specific configuration files per platform to avoid Clang bug * Don't pack clang dynamic link libraries * Use PowerShell for shell on Windows * Try to fix Windows GitHub action workflow * Try to fix Windows GitHub action workflow * Try to fix Windows GitHub action workflow * Try to fix * Try to fix * Try to fix * Fix system header includes * Don't include native libraries during pack * Log clang installed path * Update main.yml * Upload main.yml * Update main.yml * Use dynamic link libraries for integration tests * Download libclang faster using NuGet * Use `unzip` * Escape when running scripts * Fix integration tests for Windows * Try to fix Windows installation of Clang mingw64 * Try to fix Windows installation of Clang mingw64 * Rename request to configuration * Update main.yml * Update main.yml * Update main.yml * Update docs * Update schema * Update hello world example config * Update docs * Update docs --- .github/workflows/main.yml | 76 +- C2CS.sln | 101 +- README.md | 15 +- docs/README.md | 241 +++- ext/clang/include/clang-c/Index.h | 61 +- mingw-w64-x86_64.cmake | 19 - schema.json | 1 + scripts/install-clang-macos.sh | 8 + scripts/install-clang-x64-ubuntu_18_04.sh | 16 + scripts/install-clang-x64-ubuntu_20_04.sh | 16 + scripts/install-clang-x64-windows.sh | 16 + src/cs/Directory.Build.props | 2 +- src/cs/examples/Directory.Build.props | 5 +- src/cs/examples/Directory.Build.targets | 5 + .../helloworld/helloworld-c/Program.cs | 18 +- .../helloworld/helloworld-c/config.json | 27 +- .../helloworld-c/helloworld-c.csproj | 2 +- .../my_c_library/src/my_c_library.c | 2 +- .../helloworld-cs/helloworld-cs.csproj | 7 +- .../helloworld/helloworld-cs/my_c_library.cs | 12 +- .../production/C2CS.Common/C2CS.Common.csproj | 11 - .../Foundation/UseCases/IUseCaseRequest.cs | 16 - .../UseCases/IUseCaseRequestHandler.cs | 18 - .../Foundation/UseCases/UseCaseException.cs | 62 - .../Foundation/UseCases/UseCaseHandler.cs | 163 --- .../UseCases/UseCaseResponseUnit.cs | 16 - .../UseCases/UseCaseStepAttribute.cs | 20 - .../C2CS.Common/Platform/Platform.cs | 141 -- .../C2CS.Feature.BindgenCSharp.csproj | 22 - .../Data/CSharpConstant.cs | 21 - .../Data/CSharpEnum.cs | 24 - .../Data/CSharpEnumValue.cs | 20 - .../Data/CSharpFunction.cs | 26 - .../Data/CSharpFunctionParameter.cs | 20 - .../Data/CSharpFunctionPointer.cs | 24 - .../Data/CSharpFunctionPointerParameter.cs | 20 - .../Data/CSharpNode.cs | 21 - .../Data/CSharpOpaqueType.cs | 17 - .../Data/CSharpPseudoEnum.cs | 24 - .../Data/CSharpPseudoEnumValue.cs | 20 - .../Data/CSharpStruct.cs | 25 - .../Data/CSharpTypedef.cs | 13 - .../MacroObjectAlreadyExistsDiagnostic.cs | 14 - .../MacroObjectNotTranspiledDiagnostic.cs | 14 - .../Diagnostics/SystemTypedefDiagnostic.cs | 14 - .../C2CS.Feature.BindgenCSharp/Input.cs | 157 --- .../C2CS.Feature.BindgenCSharp/UseCase.cs | 125 -- .../Domain/BuildTarget.cs | 15 - .../Domain/DomainMapper.cs | 165 --- ....Feature.ExtractAbstractSyntaxTreeC.csproj | 17 - .../TypeFromIgnoredHeaderDiagnostic.cs | 14 - .../Input.cs | 128 -- .../UseCase.cs | 234 ---- src/cs/production/C2CS/C2CS.csproj | 31 +- .../production/C2CS/C2CS.csproj.DotSettings | 2 +- .../C2CS/CommandLineArgumentsProvider.cs | 14 + .../production/C2CS/CommandLineInterface.cs | 106 ++ src/cs/production/C2CS/CommandLineService.cs | 45 + src/cs/production/C2CS/Configuration.cs | 166 --- src/cs/production/C2CS/Data/Configuration.cs | 28 + .../ConfigurationJsonSerializer.cs | 98 ++ .../ConfigurationSerializerContext.cs | 4 +- .../C2CS/Data/Serialization/Logging.cs | 32 + src/cs/production/C2CS/Program.cs | 66 +- src/cs/production/C2CS/Startup.cs | 59 + src/cs/production/Directory.Build.props | 2 +- src/cs/production/clang-c/clang-c.csproj | 20 - src/cs/production/clang-cs/ClangLocation.cs | 98 -- .../clang-cs/ClangTranslationUnitParser.cs | 103 -- .../BuildLibraryConfiguration.cs} | 8 +- .../BuildLibraryInput.cs} | 22 +- .../BuildLibraryOutput.cs} | 5 +- .../BuildLibraryUseCase.cs | 31 + .../BuildLibraryValidator.cs} | 11 +- .../C2CS.Feature.BuildLibraryC.csproj | 11 +- ....Feature.BuildLibraryC.csproj.DotSettings} | 7 +- .../Data/BuildProject.cs} | 8 +- .../Data/BuildProjectCMake.cs | 16 + .../Data/BuildProjectType.cs} | 6 +- .../Data/BuildSolution.cs} | 4 +- .../Data/BuildTarget.cs | 19 + .../Data}/BuildTargetResult.cs | 2 +- .../Serialization/JsonSerializerContext.cs} | 4 +- .../Domain/CMake.cs | 148 ++ .../C2CS.Feature.BuildLibraryC/Startup.cs} | 12 +- .../C2CS.Feature.ReadCodeC.csproj | 23 + .../C2CS.Feature.ReadCodeC.csproj.DotSettings | 11 + .../Data/CAbstractSyntaxTree.cs | 11 +- .../C2CS.Feature.ReadCodeC}/Data/CEnum.cs | 10 +- .../Data/CEnumValue.cs | 8 +- .../C2CS.Feature.ReadCodeC}/Data/CFunction.cs | 10 +- .../Data/CFunctionCallingConvention.cs | 2 +- .../Data/CFunctionParameter.cs | 10 +- .../Data/CFunctionPointer.cs | 10 +- .../Data/CFunctionPointerParameter.cs | 10 +- .../C2CS.Feature.ReadCodeC}/Data/CKind.cs | 2 +- .../C2CS.Feature.ReadCodeC/Data/CLocation.cs | 95 ++ .../Data/CMacroDefinition.cs | 8 +- .../C2CS.Feature.ReadCodeC}/Data/CNode.cs | 16 +- .../Data/CNodeWithLocation.cs | 24 + .../Data/COpaqueType.cs | 8 +- .../C2CS.Feature.ReadCodeC}/Data/CRecord.cs | 10 +- .../Data/CRecordField.cs | 10 +- .../C2CS.Feature.ReadCodeC}/Data/CType.cs | 16 +- .../C2CS.Feature.ReadCodeC}/Data/CTypedef.cs | 10 +- .../C2CS.Feature.ReadCodeC}/Data/CVariable.cs | 8 +- .../Data/ReadCodeCConfiguration.cs | 25 + ...eadCodeCConfigurationAbstractSyntaxTree.cs | 43 + .../Data/Serialization/CJsonSerializer.cs | 94 ++ .../Serialization/CJsonSerializerContext.cs | 2 +- .../Serialization/CLocationJsonConverter.cs | 34 + .../Data/Serialization/Logging.cs | 55 + .../Domain}/ClangException.cs | 4 +- .../Domain}/ClangExtensions.cs | 71 +- .../ClangTranslationUnitExplorer.cs} | 799 +++++------ .../ClangTranslationUnitExplorerContext.cs | 79 ++ .../ClangTranslationUnitExplorerNode.cs} | 29 +- .../MacroAlreadyExistsDiagnostic.cs | 19 + .../Diagnostics/PlatformMismatchDiagnostic.cs | 19 + .../TypeFromIgnoredHeaderDiagnostic.cs | 19 + .../Domain/ExploreCode/Logging.cs | 143 ++ .../Domain/InstallClang/ClangInstaller.cs | 135 ++ .../Domain/InstallClang/Logging.cs | 44 + .../ParseCode}/ClangArgumentsBuilder.cs | 165 ++- .../ParseCode/ClangTranslationUnitParser.cs | 127 ++ .../ClangTranslationUnitParserDiagnostic.cs | 14 + .../Domain/ParseCode/Logging.cs | 44 + .../ReadCodeCAbstractSyntaxTreeOptions.cs | 27 + .../Domain/ReadCodeCInput.cs | 13 + .../Domain/ReadCodeCOutput.cs | 12 + .../Domain/ReadCodeCValidator.cs | 170 +++ .../ReadCodeCUseCase.cs | 143 ++ .../C2CS.Feature.ReadCodeC/Startup.cs | 30 + .../C2CS.Feature.WriteCodeCSharp.csproj | 27 + ...Feature.WriteCodeCSharp.csproj.DotSettings | 7 + .../Data/Model/CSharpAbstractSyntaxTree.cs | 14 + .../Data/Model/CSharpAliasStruct.cs | 30 + .../Data/Model/CSharpConstant.cs | 35 + .../Data/Model/CSharpEnum.cs | 37 + .../Data/Model/CSharpEnumValue.cs | 30 + .../Data/Model/CSharpFunction.cs | 44 + .../Model}/CSharpFunctionCallingConvention.cs | 2 +- .../Data/Model/CSharpFunctionParameter.cs | 31 + .../Data/Model/CSharpFunctionPointer.cs | 37 + .../Model/CSharpFunctionPointerParameter.cs | 26 + .../Data/Model/CSharpNode.cs | 69 + .../Data/Model/CSharpNodes.cs} | 12 +- .../Data/Model/CSharpOpaqueStruct.cs | 26 + .../Data/Model/CSharpStruct.cs | 39 + .../Data/Model}/CSharpStructField.cs | 23 +- .../Data/Model}/CSharpType.cs | 4 +- .../Data/Model}/CSharpTypeAlias.cs | 6 +- .../Data/WriteCodeCSharpConfiguration.cs | 52 + .../BuilderCSharpAbstractSyntaxTree.cs | 378 ++++++ .../CodeGenerator/GeneratorCSharpCode.cs} | 206 +-- .../Domain/ExtensionsRosyln.cs} | 4 +- .../Domain/Mapper/CSharpMapper.cs | 1196 +++++++++++++++++ .../Domain/Mapper/CSharpMapperContext.cs | 27 + .../Domain/Mapper}/CSharpMapperParameters.cs | 76 +- .../Diagnostics/SystemTypedefDiagnostic.cs | 20 + .../TranspileMacroObjectFailureDiagnostic.cs | 20 + .../Domain/WriteCodeCSharpInput.cs | 29 + .../Domain/WriteCodeCSharpOutput.cs} | 6 +- .../Domain/WriteCodeCSharpValidator.cs | 175 +++ .../C2CS.Feature.WriteCodeCSharp/Startup.cs | 16 + .../WriteCodeCSharpUseCase.cs | 132 ++ .../C2CS.Common/C2CS.Common.csproj | 20 + .../C2CS.Common.csproj.DotSettings | 11 + .../C2CS.Common/Foundation/ArrayDeque.cs | 2 +- .../Foundation/ConfigurationException.cs | 2 +- .../Serialization/SnakeCaseNamingPolicy.cs | 26 + .../Foundation/Diagnostics/Diagnostic.cs | 57 +- .../Foundation/Diagnostics/DiagnosticPanic.cs | 12 +- .../Diagnostics/DiagnosticSeverity.cs | 2 +- .../Foundation/Diagnostics/DiagnosticsSink.cs | 4 +- .../Logging/LoggingEventRegistry.cs | 16 + .../UseCases/Exceptions/UseCaseException.cs | 67 + .../Exceptions/UseCaseStepFailedException.cs | 13 + .../Foundation/UseCases/UseCase.cs | 170 +++ .../UseCases/UseCaseConfiguration.cs | 15 + .../Foundation/UseCases/UseCaseLogging.cs | 71 + .../Foundation/UseCases/UseCaseOutput.cs | 19 +- .../Foundation/UseCases/UseCaseValidator.cs} | 9 +- .../C2CS.Common/Native/Native.cs | 166 +++ .../C2CS.Common/Native/NativeArchitecture.cs} | 26 +- .../Native/NativeOperatingSystem.cs} | 15 +- .../NativePlatformJsonConverter.cs | 34 + .../C2CS.Common/Native/TargetPlatform.cs | 274 ++++ .../C2CS.Common/Native}/Terminal.cs | 11 +- .../C2CS.Runtime/C2CS.Runtime.csproj | 2 +- .../C2CS.Runtime.csproj.DotSettings | 0 .../C2CS.Runtime/CBool.cs | 0 .../C2CS.Runtime/CChar.cs | 0 .../C2CS.Runtime/CCharWide.cs | 0 .../C2CS.Runtime/CString.cs | 0 .../C2CS.Runtime/CStringWide.cs | 0 .../C2CS.Runtime/CStrings.cs | 0 .../{ => infrastructure}/clang-c/Program.cs | 4 +- .../{ => infrastructure}/clang-c/api.txt | 0 .../infrastructure/clang-c/clang-c.csproj | 27 + .../{ => infrastructure}/clang-c/config.json | 14 +- .../clang-cs/clang-cs.csproj | 5 + .../{ => infrastructure}/clang-cs/clang.cs | 1009 ++++++++------ .../C2CS.Tests.Integration.csproj | 53 + ...C2CS.Tests.Integration.csproj.DotSettings} | 2 +- .../IntegrationTest.cs} | 8 +- .../tests/C2CS.Tests.Integration/Startup.cs | 14 + .../tests/C2CS.Tests.Integration/TestHost.cs | 15 + .../my_c_library/c/CMakeLists.txt | 21 + .../my_c_library/c/include/c2cs_helper.h | 79 ++ .../my_c_library/c/include/my_c_library.h | 20 + .../my_c_library/c/src/my_c_library.c | 45 + .../my_c_library/config_linux.json | 14 + .../my_c_library/config_macos.json | 14 + .../my_c_library/config_windows.json | 14 + .../my_c_library/cs/BindgenCSharpTests.cs | 100 ++ .../my_c_library/cs/BuildLibraryCTests.cs | 24 + ...ractAbstractSyntaxTreeCIntegrationTests.cs | 137 ++ .../cs/Fixtures/BindgenCSharpFixture.cs | 80 ++ .../ExtractAbstractSyntaxTreeCFixture.cs | 90 ++ .../my_c_library/cs/Startup.cs | 16 + src/cs/tests/Directory.Build.props | 18 + src/cs/tests/Directory.Build.targets | 5 + 223 files changed, 8136 insertions(+), 3722 deletions(-) delete mode 100644 mingw-w64-x86_64.cmake create mode 100644 schema.json create mode 100755 scripts/install-clang-macos.sh create mode 100755 scripts/install-clang-x64-ubuntu_18_04.sh create mode 100755 scripts/install-clang-x64-ubuntu_20_04.sh create mode 100755 scripts/install-clang-x64-windows.sh delete mode 100644 src/cs/production/C2CS.Common/C2CS.Common.csproj delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequest.cs delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequestHandler.cs delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseException.cs delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseHandler.cs delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseResponseUnit.cs delete mode 100644 src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepAttribute.cs delete mode 100644 src/cs/production/C2CS.Common/Platform/Platform.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/C2CS.Feature.BindgenCSharp.csproj delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpConstant.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnum.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnumValue.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunction.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionParameter.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointer.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointerParameter.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpNode.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpOpaqueType.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnum.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnumValue.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStruct.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypedef.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectAlreadyExistsDiagnostic.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectNotTranspiledDiagnostic.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/SystemTypedefDiagnostic.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/Input.cs delete mode 100644 src/cs/production/C2CS.Feature.BindgenCSharp/UseCase.cs delete mode 100644 src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTarget.cs delete mode 100644 src/cs/production/C2CS.Feature.BuildLibraryC/Domain/DomainMapper.cs delete mode 100644 src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/C2CS.Feature.ExtractAbstractSyntaxTreeC.csproj delete mode 100644 src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs delete mode 100644 src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Input.cs delete mode 100644 src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/UseCase.cs create mode 100644 src/cs/production/C2CS/CommandLineArgumentsProvider.cs create mode 100644 src/cs/production/C2CS/CommandLineInterface.cs create mode 100644 src/cs/production/C2CS/CommandLineService.cs delete mode 100644 src/cs/production/C2CS/Configuration.cs create mode 100644 src/cs/production/C2CS/Data/Configuration.cs create mode 100644 src/cs/production/C2CS/Data/Serialization/ConfigurationJsonSerializer.cs rename src/cs/production/C2CS/{ => Data/Serialization}/ConfigurationSerializerContext.cs (83%) create mode 100644 src/cs/production/C2CS/Data/Serialization/Logging.cs create mode 100644 src/cs/production/C2CS/Startup.cs delete mode 100644 src/cs/production/clang-c/clang-c.csproj delete mode 100644 src/cs/production/clang-cs/ClangLocation.cs delete mode 100644 src/cs/production/clang-cs/ClangTranslationUnitParser.cs rename src/cs/production/{C2CS.Common/Foundation/UseCases/UseCaseOutputStatus.cs => features/C2CS.Feature.BuildLibraryC/BuildLibraryConfiguration.cs} (62%) rename src/cs/production/{C2CS.Feature.BuildLibraryC/Input.cs => features/C2CS.Feature.BuildLibraryC/BuildLibraryInput.cs} (66%) rename src/cs/production/{C2CS.Feature.BuildLibraryC/Output.cs => features/C2CS.Feature.BuildLibraryC/BuildLibraryOutput.cs} (72%) create mode 100644 src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryUseCase.cs rename src/cs/production/{C2CS.Feature.BuildLibraryC/Program.cs => features/C2CS.Feature.BuildLibraryC/BuildLibraryValidator.cs} (50%) rename src/cs/production/{ => features}/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj (53%) rename src/cs/production/{C2CS.Common/C2CS.Common.csproj.DotSettings => features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings} (55%) rename src/cs/production/{C2CS.Feature.BuildLibraryC/Data/BuildTargetData.cs => features/C2CS.Feature.BuildLibraryC/Data/BuildProject.cs} (68%) create mode 100644 src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectCMake.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Output.cs => features/C2CS.Feature.BuildLibraryC/Data/BuildProjectType.cs} (69%) rename src/cs/production/{C2CS.Feature.BuildLibraryC/Data/InputData.cs => features/C2CS.Feature.BuildLibraryC/Data/BuildSolution.cs} (83%) create mode 100644 src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTarget.cs rename src/cs/production/{C2CS.Feature.BuildLibraryC/Domain => features/C2CS.Feature.BuildLibraryC/Data}/BuildTargetResult.cs (92%) rename src/cs/production/{C2CS.Feature.BuildLibraryC/Data/Serialization/InputDataSerializerContext.cs => features/C2CS.Feature.BuildLibraryC/Data/Serialization/JsonSerializerContext.cs} (79%) create mode 100644 src/cs/production/features/C2CS.Feature.BuildLibraryC/Domain/CMake.cs rename src/cs/production/{C2CS.Feature.BuildLibraryC/Handler.cs => features/C2CS.Feature.BuildLibraryC/Startup.cs} (51%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj.DotSettings rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CAbstractSyntaxTree.cs (85%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CEnum.cs (79%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CEnumValue.cs (76%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CFunction.cs (81%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CFunctionCallingConvention.cs (83%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CFunctionParameter.cs (78%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CFunctionPointer.cs (80%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CFunctionPointerParameter.cs (77%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CKind.cs (88%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CLocation.cs rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CMacroDefinition.cs (72%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CNode.cs (85%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNodeWithLocation.cs rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/COpaqueType.cs (65%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CRecord.cs (81%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CRecordField.cs (76%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CType.cs (75%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CTypedef.cs (72%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/CVariable.cs (69%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfiguration.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfigurationAbstractSyntaxTree.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializer.cs rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC => features/C2CS.Feature.ReadCodeC}/Data/Serialization/CJsonSerializerContext.cs (89%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CLocationJsonConverter.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/Logging.cs rename src/cs/production/{clang-cs => features/C2CS.Feature.ReadCodeC/Domain}/ClangException.cs (83%) rename src/cs/production/{clang-cs => features/C2CS.Feature.ReadCodeC/Domain}/ClangExtensions.cs (87%) rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC/Logic/CTranslationUnitExplorer.cs => features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorer.cs} (65%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerContext.cs rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC/Domain/ClangExplorerNode.cs => features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerNode.cs} (58%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/MacroAlreadyExistsDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/PlatformMismatchDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Logging.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/ClangInstaller.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/Logging.cs rename src/cs/production/{clang-cs => features/C2CS.Feature.ReadCodeC/Domain/ParseCode}/ClangArgumentsBuilder.cs (55%) create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangTranslationUnitParser.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Diagnostics/ClangTranslationUnitParserDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Logging.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCAbstractSyntaxTreeOptions.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCInput.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCOutput.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCValidator.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/ReadCodeCUseCase.cs create mode 100644 src/cs/production/features/C2CS.Feature.ReadCodeC/Startup.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj.DotSettings create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAbstractSyntaxTree.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAliasStruct.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpConstant.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnum.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnumValue.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunction.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Data => features/C2CS.Feature.WriteCodeCSharp/Data/Model}/CSharpFunctionCallingConvention.cs (86%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionParameter.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointer.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointerParameter.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNode.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Data/CSharpAbstractSyntaxTree.cs => features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNodes.cs} (61%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpOpaqueStruct.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStruct.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Data => features/C2CS.Feature.WriteCodeCSharp/Data/Model}/CSharpStructField.cs (57%) rename src/cs/production/{C2CS.Feature.BindgenCSharp/Data => features/C2CS.Feature.WriteCodeCSharp/Data/Model}/CSharpType.cs (87%) rename src/cs/production/{C2CS.Feature.BindgenCSharp/Data => features/C2CS.Feature.WriteCodeCSharp/Data/Model}/CSharpTypeAlias.cs (81%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/WriteCodeCSharpConfiguration.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/BuilderCSharpAbstractSyntaxTree.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Logic/CSharpCodeGenerator.cs => features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/GeneratorCSharpCode.cs} (76%) rename src/cs/production/{C2CS.Feature.BindgenCSharp/Logic/RosylnExtensions.cs => features/C2CS.Feature.WriteCodeCSharp/Domain/ExtensionsRosyln.cs} (95%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapper.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperContext.cs rename src/cs/production/{C2CS.Feature.BindgenCSharp/Logic => features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper}/CSharpMapperParameters.cs (61%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/SystemTypedefDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/TranspileMacroObjectFailureDiagnostic.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpInput.cs rename src/cs/production/{C2CS.Feature.ExtractAbstractSyntaxTreeC/Output.cs => features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpOutput.cs} (58%) create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpValidator.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Startup.cs create mode 100644 src/cs/production/features/C2CS.Feature.WriteCodeCSharp/WriteCodeCSharpUseCase.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj create mode 100644 src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj.DotSettings rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/ArrayDeque.cs (99%) rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/ConfigurationException.cs (95%) create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/Data/Serialization/SnakeCaseNamingPolicy.cs rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs (53%) rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs (52%) rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs (95%) rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs (83%) create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/Logging/LoggingEventRegistry.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseException.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseStepFailedException.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCase.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseConfiguration.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseLogging.cs rename src/cs/production/{ => infrastructure}/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs (58%) rename src/cs/production/{C2CS.Common/Foundation/UseCases/IBaseUseCaseRequest.cs => infrastructure/C2CS.Common/Foundation/UseCases/UseCaseValidator.cs} (50%) create mode 100644 src/cs/production/infrastructure/C2CS.Common/Native/Native.cs rename src/cs/production/{C2CS.Common/Platform/RuntimeArchitecture.cs => infrastructure/C2CS.Common/Native/NativeArchitecture.cs} (59%) rename src/cs/production/{C2CS.Common/Platform/RuntimeOperatingSystem.cs => infrastructure/C2CS.Common/Native/NativeOperatingSystem.cs} (89%) create mode 100644 src/cs/production/infrastructure/C2CS.Common/Native/Serialization/NativePlatformJsonConverter.cs create mode 100644 src/cs/production/infrastructure/C2CS.Common/Native/TargetPlatform.cs rename src/cs/production/{C2CS.Common/Platform => infrastructure/C2CS.Common/Native}/Terminal.cs (96%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/C2CS.Runtime.csproj (89%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/C2CS.Runtime.csproj.DotSettings (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CBool.cs (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CChar.cs (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CCharWide.cs (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CString.cs (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CStringWide.cs (100%) rename src/cs/production/{ => infrastructure}/C2CS.Runtime/CStrings.cs (100%) rename src/cs/production/{ => infrastructure}/clang-c/Program.cs (81%) rename src/cs/production/{ => infrastructure}/clang-c/api.txt (100%) create mode 100644 src/cs/production/infrastructure/clang-c/clang-c.csproj rename src/cs/production/{ => infrastructure}/clang-c/config.json (88%) rename src/cs/production/{ => infrastructure}/clang-cs/clang-cs.csproj (74%) rename src/cs/production/{ => infrastructure}/clang-cs/clang.cs (78%) create mode 100644 src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj rename src/cs/{production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings => tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj.DotSettings} (75%) rename src/cs/{production/C2CS.Common/Foundation/UseCases/UseCaseStepMetaData.cs => tests/C2CS.Tests.Integration/IntegrationTest.cs} (58%) create mode 100644 src/cs/tests/C2CS.Tests.Integration/Startup.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/TestHost.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/c/CMakeLists.txt create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/c2cs_helper.h create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/my_c_library.h create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/c/src/my_c_library.c create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/config_linux.json create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/config_macos.json create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/config_windows.json create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BindgenCSharpTests.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BuildLibraryCTests.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/ExtractAbstractSyntaxTreeCIntegrationTests.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/BindgenCSharpFixture.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/ExtractAbstractSyntaxTreeCFixture.cs create mode 100644 src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Startup.cs create mode 100644 src/cs/tests/Directory.Build.props create mode 100644 src/cs/tests/Directory.Build.targets diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe04761d..e75c3362 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,15 +8,71 @@ on: jobs: dotnet-job: name: ".NET" - runs-on: ubuntu-latest + runs-on: ${{ matrix.platform.os }} + strategy: + fail-fast: false + matrix: + platform: + - { name: Windows x64 (mingw64), os: windows-latest, shell: 'msys2 {0}', msystem: mingw64, msys-env: mingw-w64-x86_64 } + - { name: Windows x64, os: windows-latest, shell: bash } + # Using Ubuntu 18.04 because of GitHub Actions Runner bug: https://github.com/actions/runner/issues/1819#issuecomment-1098641309 + - { name: Linux x64, os: ubuntu-18.04, shell: bash } + - { name: macOS x64, os: macos-latest, shell: bash } + defaults: + run: + shell: ${{ matrix.platform.shell }} steps: - - uses: bottlenoselabs/github-actions-dotnet@v2 - with: - repository: '${{ github.repository }}' - dotnet-sdk-version: '6.0.x' - is-enabled-pack: 'true' - upload-myget-org: "${{ github.event_name == 'push' || startsWith(github.ref, 'refs/tags/v') }}" - upload-nuget-org: "${{ startsWith(github.ref, 'refs/tags/v') }}" - myget-access-token: '${{ secrets.MYGET_ACCESS_TOKEN }}' - nuget-access-token: '${{ secrets.NUGET_ACCESS_TOKEN }}' + + - name: "Set up MSYS2" + if: matrix.platform.shell == 'msys2 {0}' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.platform.msystem }} + install: >- + ${{ matrix.platform.msys-env }}-gcc + ${{ matrix.platform.msys-env }}-cmake + ${{ matrix.platform.msys-env }}-ninja + ${{ matrix.platform.msys-env }}-pkg-config + unzip + + - name: "Clone Git repository" + uses: actions/checkout@v1 + with: + fetch-depth: 0 + submodules: 'recursive' + + - name: "Install Clang: Windows" + if: matrix.platform.os == 'windows-latest' + run: | + source "${{ github.workspace }}\scripts\install-clang-x64-windows.sh" + + - name: "Install Clang: Linux" + if: matrix.platform.os == 'ubuntu-18.04' + run: | + sudo apt-get update + sudo apt-get install gcc-multilib + source "${{ github.workspace }}/scripts/install-clang-x64-ubuntu_18_04.sh" + + - name: "Install Clang: macOS" + if: matrix.platform.os == 'macos-latest' + run: | + source "${{ github.workspace }}/scripts/install-clang-macos.sh" + + - name: ".NET" + uses: bottlenoselabs/github-actions-dotnet@v3 + with: + dotnet-sdk-version: "6.0.x" + solution-or-project: "${{ github.workspace }}/C2CS.sln" + is-enabled-upload-myget: "${{ matrix.platform.os == 'ubuntu-18.04' && ( github.event_name == 'push' || startsWith(github.ref, 'refs/tags/v') ) }}" + is-enabled-upload-nuget: "${{ matrix.platform.os == 'ubuntu-18.04' && startsWith(github.ref, 'refs/tags/v') }}" + myget-access-token: "${{ secrets.MYGET_ACCESS_TOKEN }}" + nuget-access-token: "${{ secrets.NUGET_ACCESS_TOKEN }}" + + - uses: actions/upload-artifact@v3 + if: ${{ failure() }} + with: + name: "Failing Test Data ${{ matrix.platform.name }}" + path: | + ${{ github.workspace }}/bin/*Tests*/**/*.cs + ${{ github.workspace }}/bin/*Tests*/**/ast/*.json diff --git a/C2CS.sln b/C2CS.sln index fe24a810..f6bca9d6 100644 --- a/C2CS.sln +++ b/C2CS.sln @@ -10,21 +10,29 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "helloworld-c", "src\cs\exam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "helloworld-cs", "src\cs\examples\helloworld\helloworld-cs\helloworld-cs.csproj", "{3549CD6F-971A-4456-8F80-EFC826AECC04}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clang-cs", "src\cs\production\clang-cs\clang-cs.csproj", "{D22C766F-7E8B-4E81-899E-6CA1E624779B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS", "src\cs\production\C2CS\C2CS.csproj", "{2E53A130-519C-4C5F-8D76-86CEA17A160F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clang-c", "src\cs\production\clang-c\clang-c.csproj", "{2CF10B9A-7845-4DB7-9B02-75D62A4DABA4}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{995D2D9A-AAF6-4034-8625-5BB812C2870F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS", "src\cs\production\C2CS\C2CS.csproj", "{2E53A130-519C-4C5F-8D76-86CEA17A160F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Tests.Integration", "src\cs\tests\C2CS.Tests.Integration\C2CS.Tests.Integration.csproj", "{3C179AE6-E9B5-4B89-B441-863FF393CC85}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "features", "features", "{FF3312B1-10D2-4B8A-AD43-3F6D9CE5E61F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.ReadCodeC", "src\cs\production\features\C2CS.Feature.ReadCodeC\C2CS.Feature.ReadCodeC.csproj", "{EB040AE8-5CF1-4A79-9881-2B5CAED4D224}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.BuildLibraryC", "src\cs\production\features\C2CS.Feature.BuildLibraryC\C2CS.Feature.BuildLibraryC.csproj", "{00E6F00B-FF25-40AA-BE41-5A736FA1541C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.WriteCodeCSharp", "src\cs\production\features\C2CS.Feature.WriteCodeCSharp\C2CS.Feature.WriteCodeCSharp.csproj", "{56251683-F977-4AFC-A51D-733F9D022A61}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Runtime", "src\cs\production\C2CS.Runtime\C2CS.Runtime.csproj", "{16C004F6-755D-4976-9EEB-A61BE1223AF2}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "infrastructure", "infrastructure", "{3F11D389-67E8-40D9-B235-224F27F7BFC8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Common", "src\cs\production\C2CS.Common\C2CS.Common.csproj", "{2B7BFB9A-E304-4855-ABEF-A4F426BC78F3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clang-cs", "src\cs\production\infrastructure\clang-cs\clang-cs.csproj", "{BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.BuildLibraryC", "src\cs\production\C2CS.Feature.BuildLibraryC\C2CS.Feature.BuildLibraryC.csproj", "{9BA29F5A-ECF9-41E3-944C-28B7D837D50B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clang-c", "src\cs\production\infrastructure\clang-c\clang-c.csproj", "{E206B026-28AB-41D9-8B44-A3EC8A216FA3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.ExtractAbstractSyntaxTreeC", "src\cs\production\C2CS.Feature.ExtractAbstractSyntaxTreeC\C2CS.Feature.ExtractAbstractSyntaxTreeC.csproj", "{1D574489-0CE2-4BAC-AA53-92AA69FB69FD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Runtime", "src\cs\production\infrastructure\C2CS.Runtime\C2CS.Runtime.csproj", "{C3ACC4B9-2573-4171-BA52-959725A74D25}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Feature.BindgenCSharp", "src\cs\production\C2CS.Feature.BindgenCSharp\C2CS.Feature.BindgenCSharp.csproj", "{A126534B-01B0-477B-9DDB-43BA54D09CA1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C2CS.Common", "src\cs\production\infrastructure\C2CS.Common\C2CS.Common.csproj", "{9500EF1F-4F03-4D02-99E6-5140C806EB0C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -40,50 +48,57 @@ Global {3549CD6F-971A-4456-8F80-EFC826AECC04}.Debug|Any CPU.Build.0 = Debug|Any CPU {3549CD6F-971A-4456-8F80-EFC826AECC04}.Release|Any CPU.ActiveCfg = Release|Any CPU {3549CD6F-971A-4456-8F80-EFC826AECC04}.Release|Any CPU.Build.0 = Release|Any CPU - {D22C766F-7E8B-4E81-899E-6CA1E624779B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D22C766F-7E8B-4E81-899E-6CA1E624779B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D22C766F-7E8B-4E81-899E-6CA1E624779B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D22C766F-7E8B-4E81-899E-6CA1E624779B}.Release|Any CPU.Build.0 = Release|Any CPU - {2CF10B9A-7845-4DB7-9B02-75D62A4DABA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CF10B9A-7845-4DB7-9B02-75D62A4DABA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CF10B9A-7845-4DB7-9B02-75D62A4DABA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CF10B9A-7845-4DB7-9B02-75D62A4DABA4}.Release|Any CPU.Build.0 = Release|Any CPU {2E53A130-519C-4C5F-8D76-86CEA17A160F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E53A130-519C-4C5F-8D76-86CEA17A160F}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E53A130-519C-4C5F-8D76-86CEA17A160F}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E53A130-519C-4C5F-8D76-86CEA17A160F}.Release|Any CPU.Build.0 = Release|Any CPU - {16C004F6-755D-4976-9EEB-A61BE1223AF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {16C004F6-755D-4976-9EEB-A61BE1223AF2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {16C004F6-755D-4976-9EEB-A61BE1223AF2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16C004F6-755D-4976-9EEB-A61BE1223AF2}.Release|Any CPU.Build.0 = Release|Any CPU - {2B7BFB9A-E304-4855-ABEF-A4F426BC78F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B7BFB9A-E304-4855-ABEF-A4F426BC78F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B7BFB9A-E304-4855-ABEF-A4F426BC78F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B7BFB9A-E304-4855-ABEF-A4F426BC78F3}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA29F5A-ECF9-41E3-944C-28B7D837D50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA29F5A-ECF9-41E3-944C-28B7D837D50B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA29F5A-ECF9-41E3-944C-28B7D837D50B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA29F5A-ECF9-41E3-944C-28B7D837D50B}.Release|Any CPU.Build.0 = Release|Any CPU - {1D574489-0CE2-4BAC-AA53-92AA69FB69FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D574489-0CE2-4BAC-AA53-92AA69FB69FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D574489-0CE2-4BAC-AA53-92AA69FB69FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D574489-0CE2-4BAC-AA53-92AA69FB69FD}.Release|Any CPU.Build.0 = Release|Any CPU - {A126534B-01B0-477B-9DDB-43BA54D09CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A126534B-01B0-477B-9DDB-43BA54D09CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A126534B-01B0-477B-9DDB-43BA54D09CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A126534B-01B0-477B-9DDB-43BA54D09CA1}.Release|Any CPU.Build.0 = Release|Any CPU + {3C179AE6-E9B5-4B89-B441-863FF393CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C179AE6-E9B5-4B89-B441-863FF393CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C179AE6-E9B5-4B89-B441-863FF393CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C179AE6-E9B5-4B89-B441-863FF393CC85}.Release|Any CPU.Build.0 = Release|Any CPU + {EB040AE8-5CF1-4A79-9881-2B5CAED4D224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB040AE8-5CF1-4A79-9881-2B5CAED4D224}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB040AE8-5CF1-4A79-9881-2B5CAED4D224}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB040AE8-5CF1-4A79-9881-2B5CAED4D224}.Release|Any CPU.Build.0 = Release|Any CPU + {00E6F00B-FF25-40AA-BE41-5A736FA1541C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00E6F00B-FF25-40AA-BE41-5A736FA1541C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00E6F00B-FF25-40AA-BE41-5A736FA1541C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00E6F00B-FF25-40AA-BE41-5A736FA1541C}.Release|Any CPU.Build.0 = Release|Any CPU + {56251683-F977-4AFC-A51D-733F9D022A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56251683-F977-4AFC-A51D-733F9D022A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56251683-F977-4AFC-A51D-733F9D022A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56251683-F977-4AFC-A51D-733F9D022A61}.Release|Any CPU.Build.0 = Release|Any CPU + {BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71}.Release|Any CPU.Build.0 = Release|Any CPU + {E206B026-28AB-41D9-8B44-A3EC8A216FA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E206B026-28AB-41D9-8B44-A3EC8A216FA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E206B026-28AB-41D9-8B44-A3EC8A216FA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E206B026-28AB-41D9-8B44-A3EC8A216FA3}.Release|Any CPU.Build.0 = Release|Any CPU + {C3ACC4B9-2573-4171-BA52-959725A74D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3ACC4B9-2573-4171-BA52-959725A74D25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3ACC4B9-2573-4171-BA52-959725A74D25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3ACC4B9-2573-4171-BA52-959725A74D25}.Release|Any CPU.Build.0 = Release|Any CPU + {9500EF1F-4F03-4D02-99E6-5140C806EB0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9500EF1F-4F03-4D02-99E6-5140C806EB0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9500EF1F-4F03-4D02-99E6-5140C806EB0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9500EF1F-4F03-4D02-99E6-5140C806EB0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {69316361-0268-46E8-ADE6-89102DEB3166} = {E5C474A9-491B-4719-B253-09F93D5CEA5B} {1DD50076-2E66-4EC4-99AA-D8F2539EF48D} = {69316361-0268-46E8-ADE6-89102DEB3166} {3549CD6F-971A-4456-8F80-EFC826AECC04} = {69316361-0268-46E8-ADE6-89102DEB3166} - {D22C766F-7E8B-4E81-899E-6CA1E624779B} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {2CF10B9A-7845-4DB7-9B02-75D62A4DABA4} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} {2E53A130-519C-4C5F-8D76-86CEA17A160F} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {16C004F6-755D-4976-9EEB-A61BE1223AF2} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {2B7BFB9A-E304-4855-ABEF-A4F426BC78F3} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {9BA29F5A-ECF9-41E3-944C-28B7D837D50B} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {1D574489-0CE2-4BAC-AA53-92AA69FB69FD} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} - {A126534B-01B0-477B-9DDB-43BA54D09CA1} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} + {3C179AE6-E9B5-4B89-B441-863FF393CC85} = {995D2D9A-AAF6-4034-8625-5BB812C2870F} + {FF3312B1-10D2-4B8A-AD43-3F6D9CE5E61F} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} + {EB040AE8-5CF1-4A79-9881-2B5CAED4D224} = {FF3312B1-10D2-4B8A-AD43-3F6D9CE5E61F} + {00E6F00B-FF25-40AA-BE41-5A736FA1541C} = {FF3312B1-10D2-4B8A-AD43-3F6D9CE5E61F} + {56251683-F977-4AFC-A51D-733F9D022A61} = {FF3312B1-10D2-4B8A-AD43-3F6D9CE5E61F} + {3F11D389-67E8-40D9-B235-224F27F7BFC8} = {0B6FBBF3-28F7-4374-9CDD-3C8B1DFD2AB8} + {BEE5DEBA-4A80-47FC-9C0C-80F136EBBA71} = {3F11D389-67E8-40D9-B235-224F27F7BFC8} + {E206B026-28AB-41D9-8B44-A3EC8A216FA3} = {3F11D389-67E8-40D9-B235-224F27F7BFC8} + {C3ACC4B9-2573-4171-BA52-959725A74D25} = {3F11D389-67E8-40D9-B235-224F27F7BFC8} + {9500EF1F-4F03-4D02-99E6-5140C806EB0C} = {3F11D389-67E8-40D9-B235-224F27F7BFC8} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 8982a901..21369664 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,7 @@ C to C# library bindings code generator. In go `.h` file, out come `.cs` file. ## Documentation -For documentation see the [docs/README.md](docs/README.md). This includes: - -- [How to install `C2CS`](docs/README.md#install) -- [How to use `C2CS`](docs/README.md#how-to-use). -- [How to build `C2CS` and the examples from source](docs/README.md#building-from-source). -- [Examples](docs/README.md#examples). +For documentation on how to install, use, or build `C2CS` see the [docs/README.md](docs/README.md). ## Background: Why? @@ -24,13 +19,15 @@ If you are not familiar already with interoperability of C/C++ with C#, it's ass ### Solution -Automatically generate the bindings by compiling/parsing a C `.h` file. Essentially, the C public programmer application interface (API, those functions you want to call) is tranpiled to C# for the target application binary interface (ABI, operating- system/architecture combo) for use via platform invoke (P/Invoke). +Automatically generate the bindings by compiling/parsing a C `.h` file. The C API (application programmer interface; those functions you want to call) is tranpiled to C# for the target ABI (application binary interface; a Clang target triple `arch-vendor-os-environment`, e.g. `x86_64-pc-windows-msvc`) for use via P/Invoke (platform invoke). -This includes all C extern functions which are transpiled to `static` methods respecitively in C# using `DllImport` attribute. Also includes transpiling all the C types to C# which are found through transitive property to the extern functions such as: `struct`s, `enum`s, and `const`s. C# `struct`s are generated instead of `class`es on purpose to achieve 1-1 bit-representation of C to C# types called *blittable* types. The reason for blittable types is to achieve pass-through marshalling and active avoidance of the Garbage Collector in C# for best possible runtime performance and portability when doing interoperability with C. +Includes all C extern functions which are transpiled to `static` methods respecitively in C# using `DllImport` attribute. The C types which are found through transitive property to the extern functions such as: `struct`s, `enum`s, and `const`s are also transpiled to C#. C# `struct`s are generated instead of `class`es on purpose to achieve 1-1 bit-representation of C to C# types called *blittable* types. The reason for blittable types is to achieve pass-through marshalling and active avoidance of the Garbage Collector in C# for best possible runtime performance and portability when doing interoperability with C. This is all accomplished by using [libclang](https://clang.llvm.org/docs/Tooling.html) for parsing C and [Roslyn](https://github.com/dotnet/roslyn) for generating C#. All naming is left as found in the C header `.h` file(s). -![c2cs|width=400px](./docs/c2cs.png) +

+ +

### Limitations diff --git a/docs/README.md b/docs/README.md index 46d69d0a..161364c7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,26 @@ # Documentation -Here you will find documentation for `C2CS` including: - -- [Installing `C2CS`](#installing-c2cs) -- [How to use `C2CS`](#how-to-use-c2cs). -- [How to use `C2CS.Runtime`](#how-to-use-c2csruntime). -- [How to build `C2CS` from source](#building-c2cs-from-source). -- [Examples](#examples). +Here you will find documentation for `C2CS`: + +- [Documentation](#documentation) + - [Installing `C2CS`](#installing-c2cs) + - [Latest release](#latest-release) + - [Latest pre-release](#latest-pre-release) + - [How to use `C2CS`](#how-to-use-c2cs) + - [Configuration `.json` properties](#configuration-json-properties) + - [Cross-parsing with `C2CS`](#cross-parsing-with-c2cs) + - [How to use `C2CS.Runtime`](#how-to-use-c2csruntime) + - [Custom C# project properties for `C2CS.Runtime`](#custom-c-project-properties-for-c2csruntime) + - [`SIZEOF_WCHAR_T`:](#sizeof_wchar_t) + - [Building `C2CS` from source](#building-c2cs-from-source) + - [Prerequisites](#prerequisites) + - [Visual Studio / Rider / MonoDevelop](#visual-studio--rider--monodevelop) + - [Command Line Interface (CLI)](#command-line-interface-cli) + - [Debugging `C2CS` from source](#debugging-c2cs-from-source) + - [Debugging using logging](#debugging-using-logging) + - [Examples](#examples) + - [Hello world](#hello-world) + - [libclang](#libclang) ## Installing `C2CS` @@ -32,90 +46,119 @@ dotnet nuget locals all --clear ## How to use `C2CS` -To generate bindings for a C library you need to use a configuration `.json` file which specifies the input to C2CS. +To generate bindings for a C library you need to use a configuration `.json` file which specifies the input to C2CS. See the next sub-section below for documention on each property. See the [Hello World `config.json` file](src/cs/examples/helloworld/helloworld-c/config.json) for an example with annotated comments. ```json { - "inputFilePath": "path/to/library.h", - "outputFilePath": "path/to/library.cs" + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "ast": { + "input_file": "path/to/my_c_library/include/my_c_library.h" + }, + "cs": { + "output_file": "path/to/my_c_library/cs/my_c_library.cs" + } } ``` -By default running `c2cs` via terminal will search for a `config.json` file in the current directory. If you want to use a specific `.json` file, specify the file path as the first argument: `c2cs myConfig.json` +By default running `c2cs` via terminal will search for a `config.json` file in the current directory. If you want to use a specific `.json` file, specify the file path as the first argument: `c2cs -c myConfig.json`. ### Configuration `.json` properties -#### `inputFilePath` - -Path of the input `.h` header file. - -#### `outputFilePath` - -Path of the output C# `.cs` file. If not specified, defaults to a file path using the current directory, a file name without extension that matches the `inputFilePath`, and a `.cs` file name extension. - -#### `abstractSyntaxTreeOutputFilePath` - -Path of the intermediate output abstract syntax tree `.json` file. If not specified, defaults to a random temporary file. - -#### `libraryName` - -The name of the dynamic link library (without the file extension) used for platform invoke (P/Invoke) with C#. If not specified, the library name is the same as the name of the `inputFilePath` without the directory name and without the file extension. - -#### `namespaceName` - -The name of the namespace to be used for the C# static class. If not specified, the namespace is the same as the `libraryName`. - -#### `className` - -The name of the C# static class. If not specified, the class name is the same as the `libraryName`. - -#### `headerCodeRegionFilePath` - -Path of the text file which to add the file's contents to the top of the C# file. Useful for comments, extra namespace using statements, or additional code that needs to be added to the generated C# file. +The configuration properties are documented in the schema file: https://github.com/bottlenoselabs/c2cs/schema.json. By adding the schema to your JSON file, as seen in the example above, you can get intellisense in Visual Studio Code or your text editor of choice. -#### `footerCodeRegionFilePath` +## Cross-parsing with `C2CS` -Path of the text file which to add the file's contents to the bottom of the C# file. Useful for comments or additional code that needs to be added to the generated C# file. +What if you want to generate bindings for each platform in one `.cs` file? Cross-parsing is the way to do that. -#### `mappedTypeNames` +Let's take a look at a more complicated example by adding multiple target platforms to generate C# bindings for. (Omitting this information generates C# bindings using the host platform as the target.) Each target platform is a [Clang "target triple"](https://clang.llvm.org/docs/CrossCompilation.html) in the form of `arch-vendor-os[-environment]`. This is basically "cross-parsing" (as opposed to cross-compilation) of the C header `.h` file. -Pairs of strings for re-mapping type names. Each pair has source name and a target name. The source name may be found when parsing C code and get mapped to the target name when generating C# code. Does not change the type's bit layout. - -#### `isEnabledFindSdk` - -Determines whether the software development kit (SDK) for C/C++ is attempted to be found. Default is `true`. If `true`, the C/C++ header files for the current operating system are attempted to be found. In such a case, if the C/C++ header files can not be found, then an error is generated which halts the program. If `false`, the C/C++ header files will likely be missing causing Clang to generate parsing errors which also halts the program. In such a case, the missing C/C++ header files can be supplied to Clang using `clangArguments` such as `"-isystemPATH/TO/SYSTEM/HEADER/DIRECTORY"`. - -#### `machineBitWidth` - -The bit width of the computer architecture to use when parsing C code. Default is `null`. If `null`, the bit width of host operating system's computer architecture is used. E.g. the default for x64 Windows is `64`. Possible values are `null`, `32` where pointers are 4 bytes, or `64` where pointers are 8 bytes. - -#### `includeDirectories` - -Search directory paths to use for `#include` usages when parsing C code. If `null`, uses the directory path of `inputFilePath`. - -#### `defines` - -Object-like macros to use when parsing C code. - -#### `excludedHeaderFiles` - -C header file names to exclude. File names are relative to `includeDirectories`. - -#### `ignoredTypeNames` - -Type names that may be found when parsing C code that will be ignored when generating C# code. Types are ignored after mapping type names using `mappedTypeNames`. +```json +{ + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "ast": { + "input_file": "path/to/my_c_library/include/my_c_library.h", + "platforms": { + "aarch64-pc-windows-msvc": {}, + "x86_64-pc-windows-msvc": {}, + "aarch64-apple-darwin": {}, + "x86_64-apple-darwin": {}, + "aarch64-unknown-linux-gnu": {}, + "x86_64-unknown-linux-gnu": {} + } + }, + "cs": { + "output_file": "path/to/my_c_library/cs/my_c_library.cs" + } +} +``` -#### `opaqueTypeNames` +The only problem with this approach is that sometimes a Clang "target triple" may not not be available or work correctly with your version of Clang that is installed for your host operating system. +- macOS Apple Sillicon `aarch64-apple-darwin` may not be understood in your version of Clang that is installed. +- I found a bug where "cross-parsing" from Ubuntu 20.04 as the host `x86_64-unknown-linux-gnu` to Windows `x86_64-pc-windows-msvc` results in wrong type size information. + +Thus, what I recommend is that different configuration files are used for each host to parse the C header `.h` file. Then use `c2cs ast -c config_ast_x.json` to generate the ASTs (abstract syntax trees) for each Clang target triple. -Type names that may be found when parsing C code that will be interpreted as opaque types. Opaque types are often used with a pointer to hide the information about the bit layout behind the pointer. +`config_ast_windows.json` +```json +{ + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "ast": { + "input_file": "path/to/my_c_library/include/my_c_library.h", + "platforms": { + "aarch64-pc-windows-msvc": {}, + "x86_64-pc-windows-msvc": {} + } + } +} +``` -#### `functionNamesWhitelist` +`config_ast_macos.json` +```json +{ + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "ast": { + "input_file": "path/to/my_c_library/include/my_c_library.h", + "platforms": { + "aarch64-apple-darwin": {}, + "x86_64-apple-darwin": {}, + "aarch64-apple-ios": {} + } + } +} +``` -The C function names to explicitly include when parsing C code. Default is `null`. If `null`, no white list applies to which all C function names that are found are eligible for C# code generation. Note that C function names which are excluded also exclude any transitive types. +`config_ast_linux.json` +```json +{ + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "ast": { + "input_file": "path/to/my_c_library/include/my_c_library.h", + "platforms": { + "aarch64-unknown-linux-gnu": {}, + "x86_64-unknown-linux-gnu": {}, + "aarch64-linux-android": {} + } + } +} +``` -#### `clangArguments` +Once the AST files are generated, move them all over to any operating system and generate the C# bindings for all of them at once using `c2cs cs -c config_cs.json`. -Additional Clang arguments to use when parsing C code. +`config_cs.json` +```json +{ + "$schema": "https://github.com/bottlenoselabs/c2cs/schema.json", + "directory": "path/to/my_c_library/ast", + "cs": { + "output_file": "path/to/my_c_library/cs/my_c_library.cs" + } +} +``` ## How to use `C2CS.Runtime` @@ -163,6 +206,60 @@ Open `./C2CS.sln` `dotnet build` +## Debugging `C2CS` from source + +### Debugging using logging + +Structured logging is added for sanity checking of normal/expected operation. It is also extremely helpful for diagnosing or digging into a problem. In more advanced situations it is used for automated black box testing. Any log can easily be identifiable back to the code via a quick search. + +An example of some logging output for console: + +``` +2022-31-03 01:54:24 info: [0] Configuration load: Success. Path: path/to/c2cs/bin/helloworld-c/Debug/net6.0/config.json. +2022-31-03 01:54:24 info: [2] => Extract AST C - Started +2022-31-03 01:54:24 info: [5] => Extract AST C => Install Clang macOS - Step started +2022-31-03 01:54:24 trce: [8] => Extract AST C => Install Clang macOS - Success +2022-31-03 01:54:24 info: [6] => Extract AST C => Install Clang macOS - Step finished in 0.001 seconds +2022-31-03 01:54:24 info: [5] => Extract AST C => Parse x86_64-pc-windows - Step started +2022-31-03 01:54:25 trce: [10] => Extract AST C => Parse x86_64-pc-windows - Success. Path: path/to/c2cs/src/cs/examples/helloworld/helloworld-c/my_c_library/include/my_c_library.h ; Clang arguments: --language=c --std=c11 -Wno-pragma-once-outside-header -fno-blocks --include-directory=/Users/lstranks/Programming/bottlenose/c2cs/src/cs/examples/helloworld/helloworld-c/my_c_library/include --target=x86_64-pc-windows ; Diagnostics: 0 +2022-31-03 01:54:25 info: [6] => Extract AST C => Parse x86_64-pc-windows - Step finished in 0.119 seconds +2022-31-03 01:54:25 info: [5] => Extract AST C => Extract x86_64-pc-windows - Step started +2022-31-03 01:54:25 trce: [13] => Extract AST C => Extract x86_64-pc-windows - Translation unit my_c_library.h +2022-31-03 01:54:25 trce: [17] => Extract AST C => Extract x86_64-pc-windows - Enum my_enum_week_day +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type signed int +2022-31-03 01:54:25 trce: [16] => Extract AST C => Extract x86_64-pc-windows - Function hello_world +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type void +2022-31-03 01:54:25 trce: [16] => Extract AST C => Extract x86_64-pc-windows - Function pass_string +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type char* +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type char +2022-31-03 01:54:25 trce: [16] => Extract AST C => Extract x86_64-pc-windows - Function pass_integers_by_value +2022-31-03 01:54:25 trce: [16] => Extract AST C => Extract x86_64-pc-windows - Function pass_integers_by_reference +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type uint16_t* +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type int32_t* +2022-31-03 01:54:25 trce: [22] => Extract AST C => Extract x86_64-pc-windows - Type uint64_t* +2022-31-03 01:54:25 trce: [16] => Extract AST C => Extract x86_64-pc-windows - Function pass_enum +2022-31-03 01:54:25 trce: [12] => Extract AST C => Extract x86_64-pc-windows - Success +2022-31-03 01:54:25 info: [6] => Extract AST C => Extract x86_64-pc-windows - Step finished in 0.026 seconds +2022-31-03 01:54:25 info: [5] => Extract AST C => Write x86_64-pc-windows - Step started +2022-31-03 01:54:25 trce: [25] => Extract AST C => Write x86_64-pc-windows - Write abstract syntax tree C: Success. Path: path/to/c2cs/src/cs/examples/helloworld/helloworld-c/my_c_library/ast/x86_64-pc-windows.json +2022-31-03 01:54:25 info: [6] => Extract AST C => Write x86_64-pc-windows - Step finished in 0.086 seconds +... +2022-31-03 01:54:25 info: [3] => Extract AST C - Success in 0.495 seconds +``` + +Things to notice from left to right: + +- Date +- Time +- Log level: + - `trce` stands for "trace". Ignorable at a glance; used for verbose information about what the program is doing exactly. + - `info`. Used for general higher level information about what the program is doing. Good for building automation tests from logging. + - `warn` stands for "warning". Suspicious; indicative of an expected but undesired outcome. Does not halt the program. + - `error`. Unacceptable; indicative of an unexpected result which should get fixed. Does not halt the program. + - `crit`. Crash; gracefully exit the program with a stack trace. +- `[number]`: Used for automated tests and otherwise quick searching. Each kind of log is easily identifiable by a unique numeric identifier. E.g., when any use case begins it is 2. When any use case end successfully it is 3. +- `=>`: The scope of the log. Helps keep track of the context of the log. Scopes can be nested with more than one `=>`. E.g. there is a scope for the use case of "Extract AST C" and a scope for "Extract x86_64-pc-windows". `x86_64-pc-windows` is the target platform to parse the C code with Clang. + ## Examples Here you will find examples of C libraries being demonstrated with `C2CS` as smoke tests or otherwise used directly. diff --git a/ext/clang/include/clang-c/Index.h b/ext/clang/include/clang-c/Index.h index e305283b..f28601c3 100644 --- a/ext/clang/include/clang-c/Index.h +++ b/ext/clang/include/clang-c/Index.h @@ -33,7 +33,7 @@ * compatible, thus CINDEX_VERSION_MAJOR is expected to remain stable. */ #define CINDEX_VERSION_MAJOR 0 -#define CINDEX_VERSION_MINOR 61 +#define CINDEX_VERSION_MINOR 62 #define CINDEX_VERSION_ENCODE(major, minor) (((major)*10000) + ((minor)*1)) @@ -2568,7 +2568,55 @@ enum CXCursorKind { */ CXCursor_OMPScanDirective = 287, - CXCursor_LastStmt = CXCursor_OMPScanDirective, + /** OpenMP tile directive. + */ + CXCursor_OMPTileDirective = 288, + + /** OpenMP canonical loop. + */ + CXCursor_OMPCanonicalLoop = 289, + + /** OpenMP interop directive. + */ + CXCursor_OMPInteropDirective = 290, + + /** OpenMP dispatch directive. + */ + CXCursor_OMPDispatchDirective = 291, + + /** OpenMP masked directive. + */ + CXCursor_OMPMaskedDirective = 292, + + /** OpenMP unroll directive. + */ + CXCursor_OMPUnrollDirective = 293, + + /** OpenMP metadirective directive. + */ + CXCursor_OMPMetaDirective = 294, + + /** OpenMP loop directive. + */ + CXCursor_OMPGenericLoopDirective = 295, + + /** OpenMP teams loop directive. + */ + CXCursor_OMPTeamsGenericLoopDirective = 296, + + /** OpenMP target teams loop directive. + */ + CXCursor_OMPTargetTeamsGenericLoopDirective = 297, + + /** OpenMP parallel loop directive. + */ + CXCursor_OMPParallelGenericLoopDirective = 298, + + /** OpenMP target parallel loop directive. + */ + CXCursor_OMPTargetParallelGenericLoopDirective = 299, + + CXCursor_LastStmt = CXCursor_OMPTargetParallelGenericLoopDirective, /** * Cursor that represents the translation unit itself. @@ -3274,8 +3322,9 @@ enum CXTypeKind { CXType_UAccum = 37, CXType_ULongAccum = 38, CXType_BFloat16 = 39, + CXType_Ibm128 = 40, CXType_FirstBuiltin = CXType_Void, - CXType_LastBuiltin = CXType_BFloat16, + CXType_LastBuiltin = CXType_Ibm128, CXType_Complex = 100, CXType_Pointer = 101, @@ -3368,7 +3417,8 @@ enum CXTypeKind { CXType_OCLIntelSubgroupAVCImeDualRefStreamin = 175, CXType_ExtVector = 176, - CXType_Atomic = 177 + CXType_Atomic = 177, + CXType_BTFTagAttributed = 178 }; /** @@ -3394,6 +3444,7 @@ enum CXCallingConv { CXCallingConv_PreserveMost = 14, CXCallingConv_PreserveAll = 15, CXCallingConv_AArch64VectorCall = 16, + CXCallingConv_SwiftAsync = 17, CXCallingConv_Invalid = 100, CXCallingConv_Unexposed = 200 @@ -3866,7 +3917,7 @@ enum CXTypeNullabilityKind { /** * Generally behaves like Nullable, except when used in a block parameter that * was imported into a swift async method. There, swift will assume that the - * parameter can get null even if no error occured. _Nullable parameters are + * parameter can get null even if no error occurred. _Nullable parameters are * assumed to only get null on error. */ CXTypeNullability_NullableResult = 4 diff --git a/mingw-w64-x86_64.cmake b/mingw-w64-x86_64.cmake deleted file mode 100644 index c7dec617..00000000 --- a/mingw-w64-x86_64.cmake +++ /dev/null @@ -1,19 +0,0 @@ -set(CMAKE_SYSTEM_NAME Windows) -set(TOOLCHAIN_PREFIX x86_64-w64-mingw32) - -# cross compilers to use for C, C++ and Fortran -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) -set(CMAKE_Fortran_COMPILER ${TOOLCHAIN_PREFIX}-gfortran) -set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) - -# target environment on the build host system -set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) - -# modify default behavior of FIND_XXX() commands -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -set(CMAKE_C_FLAGS_INIT "-static-libgcc -static-libstdc++") -set(CMAKE_CXX_FLAGS_INIT "-static-libgcc -static-libstdc++") \ No newline at end of file diff --git a/schema.json b/schema.json new file mode 100644 index 00000000..c4002760 --- /dev/null +++ b/schema.json @@ -0,0 +1 @@ +{"type":"object","properties":{"directory":{"type":"string","description":"Path of the input and output abstract syntax tree directory. By default, the directory will be used to write a \u0060.json\u0060 file for each target platform\u0027s abstract syntax tree that has been extracted. By default, the same abstract syntax tree \u0060.json\u0060 files will then be read when generating C# code."},"ast":{"type":"object","properties":{"WorkingDirectory":{"type":"string"},"input_file":{"type":"string","description":"Path of the input \u0060.h\u0060 header file containing C code."},"platforms":{"type":"object","additionalProperties":{"type":"object","properties":{"find_system_headers":{"type":"boolean","description":"Determines whether system C/C\u002B\u002B headers are attempted to be found and passed to Clang. Default is \u0060true\u0060."},"include":{"type":"array","items":{"type":"string","description":"Search directory paths to use for \u0060#include\u0060 usages when parsing C code."},"description":"Search directory paths to use for \u0060#include\u0060 usages when parsing C code."},"defines":{"type":"array","items":{"type":"string","description":"Object-like macros to use when parsing C code."},"description":"Object-like macros to use when parsing C code."},"exclude":{"type":"array","items":{"type":"string","description":"C header file names to exclude. File names are relative to the \u0060IncludeDirectories\u0060 property."},"description":"C header file names to exclude. File names are relative to the \u0060IncludeDirectories\u0060 property."},"function_names":{"type":"array","items":{"type":"string","description":"The C function names to explicitly include when parsing C code. Default is \u0060null\u0060. If \u0060null\u003C\u0060, no white list applies. Note that C function names which are excluded also exclude any transitive types."},"description":"The C function names to explicitly include when parsing C code. Default is \u0060null\u0060. If \u0060null\u003C\u0060, no white list applies. Note that C function names which are excluded also exclude any transitive types."},"opaque_names":{"type":"array","items":{"type":"string","description":"Type names that may be found when parsing C code that will be interpreted as opaque types. Opaque types are often used with a pointer to hide the information about the bit layout behind the pointer."},"description":"Type names that may be found when parsing C code that will be interpreted as opaque types. Opaque types are often used with a pointer to hide the information about the bit layout behind the pointer."},"clang_arguments":{"type":"array","items":{"type":"string","description":"Additional Clang arguments to use when parsing C code."},"description":"Additional Clang arguments to use when parsing C code."}},"description":"The target platform configurations for extracting the abstract syntax trees. Each target platform is a Clang target triple. See the C2CS docs for more details about what target platforms are available."},"description":"The target platform configurations for extracting the abstract syntax trees. Each target platform is a Clang target triple. See the C2CS docs for more details about what target platforms are available."}},"description":"The configuration for reading the \u0060.h\u0060 C header file."},"cs":{"type":"object","properties":{"WorkingDirectory":{"type":"string"},"output_file":{"type":"string","description":"Path of the output C# \u0060.cs\u0060 file."},"library_name":{"type":"string","description":"The name of the dynamic link library (without the file extension) used for platform invoke (P/Invoke) with C#. If not specified, the library name is the same as the name of the \u0060OutputFilePath\u0060 property without the directory name and without the file extension."},"namespace_name":{"type":"string","description":"The name of the namespace to be used for the C# static class. If not specified, the namespace is the same as the \u0060LibraryName\u0060 property."},"class_name":{"type":"string","description":"The name of the C# static class. If not specified, the class name is the same as the \u0060LibraryName\u0060 property."},"region_header_file":{"type":"string","description":"Path of the text file which to add the file\u0027s contents to the top of the C# file. Useful for comments, extra namespace using statements, or additional code that needs to be added to the generated C# file."},"region_footer_file":{"type":"string","description":"Path of the text file which to add the file\u0027s contents to the bottom of the C# file. Useful for comments or additional code that needs to be added to the generated C# file."},"mapped":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"description":"Pairs of strings for re-mapping type names. Each pair has source name and a target name. Does not change the bit layout of types."},"description":"Pairs of strings for re-mapping type names. Each pair has source name and a target name. Does not change the bit layout of types."},"ignored":{"type":"array","items":{"type":"string","description":"Names of types, functions, enums, constants, or anything else that may be found when parsing C code that will be ignored when generating C# code. Type names are ignored after mapping type names using \u0060MappedTypeNames\u0060 property."},"description":"Names of types, functions, enums, constants, or anything else that may be found when parsing C code that will be ignored when generating C# code. Type names are ignored after mapping type names using \u0060MappedTypeNames\u0060 property."}},"description":"The configuration for writing the \u0060.cs\u0060 C# source code file."}}} \ No newline at end of file diff --git a/scripts/install-clang-macos.sh b/scripts/install-clang-macos.sh new file mode 100755 index 00000000..dfe806e0 --- /dev/null +++ b/scripts/install-clang-macos.sh @@ -0,0 +1,8 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +INSTALL_DIR="$DIR/../lib" + +mkdir -p "$INSTALL_DIR" + +cp "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib" "$INSTALL_DIR/libclang.dylib" \ No newline at end of file diff --git a/scripts/install-clang-x64-ubuntu_18_04.sh b/scripts/install-clang-x64-ubuntu_18_04.sh new file mode 100755 index 00000000..d7d4f685 --- /dev/null +++ b/scripts/install-clang-x64-ubuntu_18_04.sh @@ -0,0 +1,16 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +DOWNLOAD_DIR="/tmp/c2cs" +INSTALL_DIR="$DIR/../lib" + +mkdir -p "$DOWNLOAD_DIR" +mkdir -p "$INSTALL_DIR" + +cd $DOWNLOAD_DIR +curl -OL https://www.nuget.org/api/v2/package/libclang.runtime.ubuntu.18.04-x64 +unzip "libclang.runtime.ubuntu.18.04-x64" +cd $DIR + +cp "$DOWNLOAD_DIR/runtimes/ubuntu.18.04-x64/native/libclang.so" "$INSTALL_DIR/libclang.so" +rm -rf "$DOWNLOAD_DIR" \ No newline at end of file diff --git a/scripts/install-clang-x64-ubuntu_20_04.sh b/scripts/install-clang-x64-ubuntu_20_04.sh new file mode 100755 index 00000000..7a1fbe8f --- /dev/null +++ b/scripts/install-clang-x64-ubuntu_20_04.sh @@ -0,0 +1,16 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +DOWNLOAD_DIR="/tmp/c2cs" +INSTALL_DIR="$DIR/../lib" + +mkdir -p "$DOWNLOAD_DIR" +mkdir -p "$INSTALL_DIR" + +cd $DOWNLOAD_DIR +curl -OL https://www.nuget.org/api/v2/package/libclang.runtime.ubuntu.20.04-x64 +unzip "libclang.runtime.ubuntu.20.04-x64" +cd $DIR + +cp "$DOWNLOAD_DIR/runtimes/ubuntu.20.04-x64/native/libclang.so" "$INSTALL_DIR/libclang.so" +rm -rf "$DOWNLOAD_DIR" \ No newline at end of file diff --git a/scripts/install-clang-x64-windows.sh b/scripts/install-clang-x64-windows.sh new file mode 100755 index 00000000..349d5651 --- /dev/null +++ b/scripts/install-clang-x64-windows.sh @@ -0,0 +1,16 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +DOWNLOAD_DIR="/tmp/c2cs" +INSTALL_DIR="$DIR/../lib" + +mkdir -p "$DOWNLOAD_DIR" +mkdir -p "$INSTALL_DIR" + +cd $DOWNLOAD_DIR +curl -OL https://www.nuget.org/api/v2/package/libclang.runtime.win-x64 +unzip "libclang.runtime.win-x64" +cd $DIR + +cp "$DOWNLOAD_DIR/runtimes/win-x64/native/libclang.dll" "$INSTALL_DIR/libclang.dll" +rm -rf "$DOWNLOAD_DIR" \ No newline at end of file diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 739cb6b1..2e2cf0cc 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/src/cs/examples/Directory.Build.props b/src/cs/examples/Directory.Build.props index 8200ee98..e83daae0 100644 --- a/src/cs/examples/Directory.Build.props +++ b/src/cs/examples/Directory.Build.props @@ -11,11 +11,12 @@ false - + - false + true false false + false \ No newline at end of file diff --git a/src/cs/examples/Directory.Build.targets b/src/cs/examples/Directory.Build.targets index 72a6208f..01972cb5 100644 --- a/src/cs/examples/Directory.Build.targets +++ b/src/cs/examples/Directory.Build.targets @@ -2,4 +2,9 @@ + + + false + + \ No newline at end of file diff --git a/src/cs/examples/helloworld/helloworld-c/Program.cs b/src/cs/examples/helloworld/helloworld-c/Program.cs index fa79b8dc..3c9255af 100644 --- a/src/cs/examples/helloworld/helloworld-c/Program.cs +++ b/src/cs/examples/helloworld/helloworld-c/Program.cs @@ -12,12 +12,11 @@ private static void Main() var rootDirectory = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "../../../..")); if (!BuildLibrary(rootDirectory)) { - // Error building C library + Console.WriteLine("Error building C library"); return; } GenerateBindingsCSharp(); - // GenerateBindingsCSharp(rootDirectory); } private static bool BuildLibrary(string rootDirectory) @@ -30,19 +29,6 @@ private static bool BuildLibrary(string rootDirectory) private static void GenerateBindingsCSharp() { - C2CS.Program.Main(); + C2CS.Program.Main(Array.Empty()); } - -// private static void GenerateBindingsCSharp(string rootDirectory) -// { -// var arguments = @$" -// cs -// {rootDirectory}/src/cs/examples/helloworld/helloworld-c/my_c_library/ast/ast.json -// -n -// my_c_library_namespace -// "; -// var argumentsArray = -// arguments.Split(new[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); -// C2CS.Program.Main(argumentsArray); -// } } diff --git a/src/cs/examples/helloworld/helloworld-c/config.json b/src/cs/examples/helloworld/helloworld-c/config.json index 3dda08bb..019d5c38 100644 --- a/src/cs/examples/helloworld/helloworld-c/config.json +++ b/src/cs/examples/helloworld/helloworld-c/config.json @@ -1,19 +1,12 @@ { - "inputFilePath": "../../../../src/cs/examples/helloworld/helloworld-c/my_c_library/include/my_c_library.h", - "outputFilePath": "../../../../src/cs/examples/helloworld/helloworld-cs/my_c_library.cs", - "abstractSyntaxTreeOutputFilePath": null, - "libraryName": null, - "namespaceName": "my_c_library_namespace", - "className": null, - "headerCodeRegionFilePath": null, - "footerCodeRegionFilePath": null, - "mappedTypeNames": null, - "isEnabledFindSdk": true, - "machineBitWidth": null, - "includeDirectories": null, - "defines": null, - "excludedHeaderFiles": null, - "ignoredTypeNames": null, - "opaqueTypeNames": null, - "clangArguments": null + "//schema": "Use `https://www.github.com/bottlenoselabs/c2cs/schema.json` or `path/to/c2cs/schema.json` for the schema. You can generate the schema at any time by using `c2cs schema` in terminal.", + "$schema": "../../../../../schema.json", + "directory": "../../../../src/cs/examples/helloworld/helloworld-c/my_c_library/ast", + "ast": { + "input_file": "../../../../src/cs/examples/helloworld/helloworld-c/my_c_library/include/my_c_library.h" + }, + "cs": { + "output_file": "../../../../src/cs/examples/helloworld/helloworld-cs/my_c_library.cs", + "namespace_name": "my_c_library_namespace" + } } \ No newline at end of file diff --git a/src/cs/examples/helloworld/helloworld-c/helloworld-c.csproj b/src/cs/examples/helloworld/helloworld-c/helloworld-c.csproj index 7be72c3b..cee222c0 100644 --- a/src/cs/examples/helloworld/helloworld-c/helloworld-c.csproj +++ b/src/cs/examples/helloworld/helloworld-c/helloworld-c.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/cs/examples/helloworld/helloworld-c/my_c_library/src/my_c_library.c b/src/cs/examples/helloworld/helloworld-c/my_c_library/src/my_c_library.c index d8425602..53fa6335 100644 --- a/src/cs/examples/helloworld/helloworld-c/my_c_library/src/my_c_library.c +++ b/src/cs/examples/helloworld/helloworld-c/my_c_library/src/my_c_library.c @@ -5,7 +5,7 @@ void hello_world(void) { - printf("Hello, World!\n"); + printf("Hello world from C!\n"); } void pass_string(const char* s) diff --git a/src/cs/examples/helloworld/helloworld-cs/helloworld-cs.csproj b/src/cs/examples/helloworld/helloworld-cs/helloworld-cs.csproj index 4a62c57a..de673173 100644 --- a/src/cs/examples/helloworld/helloworld-cs/helloworld-cs.csproj +++ b/src/cs/examples/helloworld/helloworld-cs/helloworld-cs.csproj @@ -3,14 +3,9 @@ net6.0 - + - - - - - diff --git a/src/cs/examples/helloworld/helloworld-cs/my_c_library.cs b/src/cs/examples/helloworld/helloworld-cs/my_c_library.cs index 04749ebd..444f140d 100644 --- a/src/cs/examples/helloworld/helloworld-cs/my_c_library.cs +++ b/src/cs/examples/helloworld/helloworld-cs/my_c_library.cs @@ -25,26 +25,26 @@ public static unsafe partial class my_c_library private const string LibraryName = "my_c_library"; // Function @ my_c_library.h:21:28 - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void hello_world(); // Function @ my_c_library.h:22:28 - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void pass_string(CString s); // Function @ my_c_library.h:23:28 - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void pass_integers_by_value(ushort a, int b, ulong c); // Function @ my_c_library.h:24:28 - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void pass_integers_by_reference(ushort* a, int* b, ulong* c); // Function @ my_c_library.h:25:28 - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void pass_enum(my_enum_week_day e); - // Enum @ my_c_library.h:19:3 + // Enum @ my_c_library.h:11:14 public enum my_enum_week_day : int { MY_ENUM_WEEK_DAY_UNKNOWN = 0, diff --git a/src/cs/production/C2CS.Common/C2CS.Common.csproj b/src/cs/production/C2CS.Common/C2CS.Common.csproj deleted file mode 100644 index 0b609123..00000000 --- a/src/cs/production/C2CS.Common/C2CS.Common.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - net6.0 - enable - C2CS - false - - - diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequest.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequest.cs deleted file mode 100644 index d193eee2..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequest.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Diagnostics.CodeAnalysis; - -namespace C2CS; - -[SuppressMessage("Design", "CA1040:Avoid empty interfaces", Justification = "Marker interface.")] -public interface IUseCaseRequest : IUseCaseRequest -{ -} - -[SuppressMessage("Design", "CA1040:Avoid empty interfaces", Justification = "Marker interface.")] -public interface IUseCaseRequest : IBaseUseCaseRequest -{ -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequestHandler.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequestHandler.cs deleted file mode 100644 index 1672678a..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/IUseCaseRequestHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Threading; -using System.Threading.Tasks; - -namespace C2CS; - -public interface IUseCaseRequestHandler - where TRequest : IUseCaseRequest -{ - Task Handle(TRequest request, CancellationToken cancellationToken); -} - -public interface IRequestHandler : IUseCaseRequestHandler - where TRequest : IUseCaseRequest -{ -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseException.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseException.cs deleted file mode 100644 index 3aa44afd..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseException.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Diagnostics; - -namespace C2CS; - -public class UseCaseException : Exception -{ - public UseCaseException() - : this(CreateMessage(string.Empty)) - { - } - - public UseCaseException(string message) - : base(CreateMessage(message)) - { - } - - public UseCaseException(string message, Exception innerException) - : base(CreateMessage(message), innerException) - { - } - - private static string CreateMessage(string message) - { - var name = UseCaseNamespaceName(); - return string.IsNullOrEmpty(message) ? name : $"{name}: {message}"; - } - - private static string UseCaseNamespaceName(int skipFrames = 3) - { - while (true) - { - var method = new StackFrame(skipFrames, false).GetMethod()!; - var declaringType = method.DeclaringType; - var typeNamespace = declaringType?.Namespace!; - if (string.IsNullOrEmpty(typeNamespace)) - { - skipFrames++; - continue; - } - - var currentNamespaceName = typeof(UseCaseException).Namespace!; - if (!typeNamespace.StartsWith(currentNamespaceName, StringComparison.InvariantCulture)) - { - skipFrames++; - continue; - } - - var candidateNamespaceName = typeNamespace[currentNamespaceName.Length..].Trim('.'); - const string useCasesNamespaceName = "UseCases."; - if (candidateNamespaceName.StartsWith(useCasesNamespaceName, StringComparison.InvariantCulture)) - { - candidateNamespaceName = candidateNamespaceName[useCasesNamespaceName.Length..]; - } - - return candidateNamespaceName; - } - } -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseHandler.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseHandler.cs deleted file mode 100644 index 64089665..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseHandler.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using JetBrains.Annotations; - -namespace C2CS; - -[PublicAPI] -public abstract class UseCaseHandler - where TOutput : UseCaseOutput, new() -{ - private readonly string _useCaseName; - private readonly Stopwatch _stepStopwatch; - private readonly UseCaseStepMetaData[] _stepsMetaData; - private int _stepIndex; - - protected UseCaseHandler() - { - _useCaseName = GetUseCaseName(); - _stepStopwatch = new Stopwatch(); - _stepsMetaData = GetStepsMetaData().ToArray(); - } - - private IEnumerable GetStepsMetaData() - { - var methods = GetType().GetRuntimeMethods(); - var useCaseStepAttributes = methods - .Select(x => x.GetCustomAttribute()) - .Where(x => x != null) - .Cast() - .ToArray(); - - foreach (var attribute in useCaseStepAttributes) - { - var useCaseStepMetaData = new UseCaseStepMetaData - { - Name = attribute.StepName - }; - - yield return useCaseStepMetaData; - } - } - - protected DiagnosticsSink Diagnostics { get; } = new(); - - public TOutput Execute(TInput input) - { - var response = Begin(input); - - try - { - Execute(input, response); - } - catch (Exception e) - { - Panic(e); - - if (Debugger.IsAttached) - { - throw; - } - } - - End(response); - return response; - } - - protected abstract void Execute(TInput input, TOutput output); - - private TOutput Begin(TInput request) - { - _stepStopwatch.Reset(); - GarbageCollect(); - Console.WriteLine( - $"{_useCaseName}: Started."); - return new TOutput(); - } - - private void End(TOutput response) - { - // _stepIndex = 0; - response.WithDiagnostics(Diagnostics.GetAll()); - - if (response.Status == UseCaseOutputStatus.Success) - { - Console.Write( - $"{_useCaseName}: Finished successfully."); - if (response.Diagnostics.Length > 0) - { - Console.WriteLine( - $" However there are {response.Diagnostics.Length} diagnostics to report. This may be indicative of unexpected results. Please review the following diagnostics:"); - } - else - { - Console.WriteLine(); - } - } - else - { - Console.WriteLine( - $"{_useCaseName}: Finished unsuccessfully. Review the following diagnostics for reason(s) why:"); - } - - PrintDiagnostics(response.Diagnostics); - GarbageCollect(); - } - - private void Panic(Exception e) - { - var diagnostic = new DiagnosticPanic(e); - Diagnostics.Add(diagnostic); - } - - protected void BeginStep() - { - var stepMetaData = _stepsMetaData[_stepIndex++]; - var stepCount = _stepsMetaData.Length; - Console.WriteLine($"\tStarted step ({_stepIndex}/{stepCount}) '{stepMetaData.Name}'"); - _stepStopwatch.Start(); - GarbageCollect(); - } - - protected void EndStep() - { - var stepMetaData = _stepsMetaData[_stepIndex - 1]; - var stepCount = _stepsMetaData.Length; - _stepStopwatch.Stop(); - Console.WriteLine( - $"\tFinished step ({_stepIndex}/{stepCount}) '{stepMetaData.Name}' in {_stepStopwatch.Elapsed.TotalMilliseconds} ms"); - _stepStopwatch.Reset(); - GarbageCollect(); - } - - private string GetUseCaseName() - { - var type = GetType()!; - var namespaceName = type.Namespace; - var lastNamespaceIndex = namespaceName!.LastIndexOf('.'); - var name = namespaceName![(lastNamespaceIndex + 1)..]; - return name; - } - - private static void PrintDiagnostics(ImmutableArray diagnostics) - { - foreach (var diagnostic in diagnostics) - { - Console.WriteLine(diagnostic); - } - } - - private static void GarbageCollect() - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - } -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseResponseUnit.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseResponseUnit.cs deleted file mode 100644 index 9a5d3963..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseResponseUnit.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Threading.Tasks; - -namespace C2CS; - -public readonly record struct UseCaseResponseUnit -{ - // ReSharper disable once UnassignedReadonlyField - public static readonly UseCaseResponseUnit Value; - - public static Task Task { get; } = System.Threading.Tasks.Task.FromResult(Value); - - public override string ToString() => "()"; -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepAttribute.cs b/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepAttribute.cs deleted file mode 100644 index cdd5fd46..00000000 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; - -namespace C2CS; - -[AttributeUsage( - AttributeTargets.Method, - AllowMultiple = false, - Inherited = false)] -public sealed class UseCaseStepAttribute : Attribute -{ - public string StepName { get; } - - public UseCaseStepAttribute(string stepName) - { - StepName = stepName; - } -} diff --git a/src/cs/production/C2CS.Common/Platform/Platform.cs b/src/cs/production/C2CS.Common/Platform/Platform.cs deleted file mode 100644 index 10143494..00000000 --- a/src/cs/production/C2CS.Common/Platform/Platform.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Runtime.InteropServices; - -namespace C2CS; - -/// -/// The collection of utilities for platform interoperability with native libraries in C#. -/// -public static class Platform -{ - /// - /// Gets the current . - /// - public static RuntimeOperatingSystem HostOperatingSystem => GetRuntimeOperatingSystem(); - - /// - /// Gets the current . - /// - public static RuntimeArchitecture HostArchitecture => GetRuntimeArchitecture(); - - /// - /// Gets the library file name extension given a . - /// - /// The runtime platform. - /// - /// A containing the library file name extension for the - /// . - /// - /// is not available yet with .NET 5. - /// is not a known valid value. - public static string LibraryFileNameExtension(RuntimeOperatingSystem operatingSystem) - { - return operatingSystem switch - { - RuntimeOperatingSystem.Windows => ".dll", - RuntimeOperatingSystem.Xbox => ".dll", - RuntimeOperatingSystem.macOS => ".dylib", - RuntimeOperatingSystem.tvOS => ".dylib", - RuntimeOperatingSystem.iOS => ".dylib", - RuntimeOperatingSystem.Linux => ".so", - RuntimeOperatingSystem.FreeBSD => ".so", - RuntimeOperatingSystem.Android => ".so", - RuntimeOperatingSystem.PlayStation => ".so", - RuntimeOperatingSystem.Browser => throw new NotImplementedException(), - RuntimeOperatingSystem.Switch => throw new NotImplementedException(), - RuntimeOperatingSystem.Unknown => throw new NotImplementedException(), - _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null) - }; - } - - /// - /// Gets the library file name prefix for a . - /// - /// The runtime platform. - /// - /// A containing the library file name prefix for the - /// . - /// - /// is not available yet with .NET 5. - /// is not a known valid value. - public static string LibraryFileNamePrefix(RuntimeOperatingSystem targetOperatingSystem) - { - switch (targetOperatingSystem) - { - case RuntimeOperatingSystem.Windows: - case RuntimeOperatingSystem.Xbox: - return string.Empty; - case RuntimeOperatingSystem.macOS: - case RuntimeOperatingSystem.tvOS: - case RuntimeOperatingSystem.iOS: - case RuntimeOperatingSystem.Linux: - case RuntimeOperatingSystem.FreeBSD: - case RuntimeOperatingSystem.Android: - case RuntimeOperatingSystem.PlayStation: - return "lib"; - case RuntimeOperatingSystem.Browser: - case RuntimeOperatingSystem.Switch: - case RuntimeOperatingSystem.Unknown: - throw new NotImplementedException(); - default: - throw new ArgumentOutOfRangeException(nameof(targetOperatingSystem), targetOperatingSystem, null); - } - } - - private static RuntimeArchitecture GetRuntimeArchitecture() - { - return RuntimeInformation.OSArchitecture switch - { - Architecture.Arm64 => RuntimeArchitecture.ARM64, - Architecture.Arm => RuntimeArchitecture.ARM32, - Architecture.X86 => RuntimeArchitecture.X86, - Architecture.X64 => RuntimeArchitecture.X64, - Architecture.Wasm => RuntimeArchitecture.Unknown, - Architecture.S390x => RuntimeArchitecture.Unknown, - _ => throw new ArgumentOutOfRangeException() - }; - } - - private static RuntimeOperatingSystem GetRuntimeOperatingSystem() - { - if (OperatingSystem.IsWindows()) - { - return RuntimeOperatingSystem.Windows; - } - - if (OperatingSystem.IsMacOS()) - { - return RuntimeOperatingSystem.macOS; - } - - if (OperatingSystem.IsLinux()) - { - return RuntimeOperatingSystem.Linux; - } - - if (OperatingSystem.IsAndroid()) - { - return RuntimeOperatingSystem.Android; - } - - if (OperatingSystem.IsIOS()) - { - return RuntimeOperatingSystem.iOS; - } - - if (OperatingSystem.IsTvOS()) - { - return RuntimeOperatingSystem.tvOS; - } - - if (OperatingSystem.IsBrowser()) - { - return RuntimeOperatingSystem.Browser; - } - - return RuntimeOperatingSystem.Unknown; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/C2CS.Feature.BindgenCSharp.csproj b/src/cs/production/C2CS.Feature.BindgenCSharp/C2CS.Feature.BindgenCSharp.csproj deleted file mode 100644 index 6a88ba88..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/C2CS.Feature.BindgenCSharp.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net6.0 - enable - enable - $(NoWarn);CA1724 - - - - - - - - - - - - - - - diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpConstant.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpConstant.cs deleted file mode 100644 index 710d0289..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpConstant.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpConstant : CSharpNode -{ - public string Type; - public string Value; - - public CSharpConstant( - string name, - string locationComment, - string type, - string value) - : base(name, locationComment) - { - Type = type; - Value = value; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnum.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnum.cs deleted file mode 100644 index 546414c4..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnum.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpEnum( - string Name, - string CodeLocationComment, - CSharpType IntegerType, - ImmutableArray Values) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpType IntegerType = IntegerType; - public readonly ImmutableArray Values = Values; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnumValue.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnumValue.cs deleted file mode 100644 index 4ad407fc..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpEnumValue.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpEnumValue( - string Name, - string CodeLocationComment, - long Value) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly long Value = Value; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunction.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunction.cs deleted file mode 100644 index 1a7e50c2..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunction.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpFunction( - string Name, - string CodeLocationComment, - CSharpFunctionCallingConvention CallingConvention, - CSharpType ReturnType, - ImmutableArray Parameters) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpFunctionCallingConvention CallingConvention = CallingConvention; - public readonly ImmutableArray Parameters = Parameters; - public readonly CSharpType ReturnType = ReturnType; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionParameter.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionParameter.cs deleted file mode 100644 index 19151110..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionParameter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpFunctionParameter( - string Name, - string CodeLocationComment, - CSharpType Type) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpType Type = Type; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointer.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointer.cs deleted file mode 100644 index 9d9f0130..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpFunctionPointer( - string Name, - string CodeLocationComment, - CSharpType ReturnType, - ImmutableArray Parameters) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly ImmutableArray Parameters = Parameters; - public readonly CSharpType ReturnType = ReturnType; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointerParameter.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointerParameter.cs deleted file mode 100644 index 741b77a7..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionPointerParameter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpFunctionPointerParameter( - string Name, - string CodeLocationComment, - CSharpType Type) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpType Type = Type; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpNode.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpNode.cs deleted file mode 100644 index 26549e89..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpNode.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpNode( - string? Name, - string? CodeLocationComment) -{ - public readonly string CodeLocationComment = - string.IsNullOrEmpty(CodeLocationComment) ? string.Empty : CodeLocationComment; - - public readonly string Name = string.IsNullOrEmpty(Name) ? string.Empty : Name; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return $"{Name} {CodeLocationComment}"; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpOpaqueType.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpOpaqueType.cs deleted file mode 100644 index 15b90510..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpOpaqueType.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpOpaqueType( - string Name, - string CodeLocationComment) - : CSharpNode(Name, CodeLocationComment) -{ - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnum.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnum.cs deleted file mode 100644 index 55204e4f..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnum.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpPseudoEnum( - string Name, - string CodeLocationComment, - CSharpType IntegerType, - ImmutableArray Values) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpType IntegerType = IntegerType; - public readonly ImmutableArray Values = Values; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnumValue.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnumValue.cs deleted file mode 100644 index c3477919..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpPseudoEnumValue.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpPseudoEnumValue( - string Name, - string CodeLocationComment, - long Value) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly long Value = Value; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStruct.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStruct.cs deleted file mode 100644 index afc68d05..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStruct.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpStruct( - string CodeLocationComment, - CSharpType Type, - ImmutableArray Fields, - ImmutableArray NestedStructs) - : CSharpNode(Type.Name, CodeLocationComment) -{ - public readonly ImmutableArray Fields = Fields; - public readonly ImmutableArray NestedStructs = NestedStructs; - public readonly CSharpType Type = Type; - - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() - { - return base.ToString(); - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypedef.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypedef.cs deleted file mode 100644 index dbdb283b..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypedef.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Data; - -public record CSharpTypedef( - string Name, - string CodeLocationComment, - CSharpType UnderlyingType) - : CSharpNode(Name, CodeLocationComment) -{ - public readonly CSharpType UnderlyingType = UnderlyingType; -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectAlreadyExistsDiagnostic.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectAlreadyExistsDiagnostic.cs deleted file mode 100644 index 9adab9f7..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectAlreadyExistsDiagnostic.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Diagnostics; - -public class MacroObjectAlreadyExistsDiagnostic : Diagnostic -{ - public MacroObjectAlreadyExistsDiagnostic(string name, ClangLocation loc) - : base(DiagnosticSeverity.Warning) - { - Summary = - $"The object-like macro '{name}' at {loc.FilePath}:{loc.LineNumber}:{loc.LineColumn} already previously exists."; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectNotTranspiledDiagnostic.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectNotTranspiledDiagnostic.cs deleted file mode 100644 index e8fba9d9..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/MacroObjectNotTranspiledDiagnostic.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Diagnostics; - -public class MacroObjectNotTranspiledDiagnostic : Diagnostic -{ - public MacroObjectNotTranspiledDiagnostic(string name, ClangLocation loc) - : base(DiagnosticSeverity.Warning) - { - Summary = - $"The object-like macro '{name}' at {loc.FilePath}:{loc.LineNumber}:{loc.LineColumn} was not transpiled."; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/SystemTypedefDiagnostic.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/SystemTypedefDiagnostic.cs deleted file mode 100644 index 31117771..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Diagnostics/SystemTypedefDiagnostic.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.BindgenCSharp.Diagnostics; - -public class SystemTypedefDiagnostic : Diagnostic -{ - public SystemTypedefDiagnostic(string typeName, ClangLocation loc, string underlyingTypeName) - : base(DiagnosticSeverity.Warning) - { - Summary = - $"The typedef '{typeName}' at {loc.FilePath}:{loc.LineNumber}:{loc.LineColumn} is a system alias to the system type '{underlyingTypeName}'. If you intend to have cross-platform bindings this is a problem; please create an issue on GitHub."; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Input.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/Input.cs deleted file mode 100644 index d8fa2777..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Input.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; -using C2CS.Feature.BindgenCSharp.Data; - -namespace C2CS.Feature.BindgenCSharp; - -public class Input -{ - public string InputFilePath { get; } - - public string OutputFilePath { get; } - - public ImmutableArray TypeAliases { get; } - - public ImmutableArray IgnoredTypeNames { get; } - - public string LibraryName { get; } - - public string ClassName { get; } - - public string NamespaceName { get; } - - public string HeaderCodeRegion { get; } - - public string FooterCodeRegion { get; } - - public Input( - string? inputFilePath, - string? outputFilePath, - string? libraryName, - string? @namespace, - string? className, - ImmutableArray? mappedTypeNames, - ImmutableArray? ignoredTypeNames, - string? headerCodeRegionFilePath, - string? footerCodeRegionFilePath) - { - InputFilePath = VerifyInputFilePath(inputFilePath); - OutputFilePath = VerifyOutputFilePath(outputFilePath, InputFilePath); - ClassName = VerifyClassName(className, OutputFilePath); - LibraryName = VerifyLibraryName(libraryName, ClassName); - NamespaceName = VerifyNamespace(@namespace, LibraryName); - TypeAliases = VerifyTypeAliases(mappedTypeNames); - IgnoredTypeNames = VerifyIgnoredTypeNames(ignoredTypeNames); - HeaderCodeRegion = VerifyHeaderCodeRegion(headerCodeRegionFilePath); - FooterCodeRegion = VerifyFooterCodeRegion(footerCodeRegionFilePath); - } - - private static string VerifyInputFilePath(string? inputFilePath) - { - if (string.IsNullOrWhiteSpace(inputFilePath)) - { - throw new UseCaseException("The input file can not be null, empty, or whitespace."); - } - - if (!File.Exists(inputFilePath)) - { - throw new UseCaseException($"The input file does not exist: {inputFilePath}"); - } - - return inputFilePath; - } - - private static string VerifyOutputFilePath(string? outputFilePath, string inputFilePath) - { - if (!string.IsNullOrEmpty(outputFilePath)) - { - return Path.GetFullPath(outputFilePath); - } - - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(inputFilePath); - var defaultFilePath = Path.Combine(Environment.CurrentDirectory, $"{fileNameWithoutExtension}.cs"); - return defaultFilePath; - } - - private static ImmutableArray VerifyTypeAliases(ImmutableArray? mappedTypeNames) - { - if (mappedTypeNames == null || mappedTypeNames.Value.IsDefaultOrEmpty) - { - return ImmutableArray.Empty; - } - - var builder = ImmutableArray.CreateBuilder(); - foreach (var typeAlias in mappedTypeNames) - { - builder.Add(typeAlias); - } - - return builder.ToImmutable(); - } - - private static ImmutableArray VerifyIgnoredTypeNames(ImmutableArray? ignoredTypeNames) - { - if (ignoredTypeNames == null || ignoredTypeNames.Value.IsDefaultOrEmpty) - { - return ImmutableArray.Empty; - } - - var array = ignoredTypeNames.Value - .Where(x => !string.IsNullOrEmpty(x)) - .Cast(); - return array.ToImmutableArray(); - } - - private static string VerifyLibraryName(string? libraryName, string className) - { - return !string.IsNullOrEmpty(libraryName) ? libraryName : className; - } - - private static string VerifyNamespace(string? @namespace, string libraryName) - { - return !string.IsNullOrEmpty(@namespace) ? @namespace : libraryName; - } - - private static string VerifyClassName(string? className, string outputFilePath) - { - string result; - if (string.IsNullOrEmpty(className)) - { - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputFilePath); - var firstIndexOfPeriod = fileNameWithoutExtension.IndexOf('.', StringComparison.InvariantCulture); - result = firstIndexOfPeriod == -1 - ? fileNameWithoutExtension - : fileNameWithoutExtension[..firstIndexOfPeriod]; - } - else - { - result = className; - } - - return result; - } - - private static string VerifyHeaderCodeRegion(string? headerCodeRegionFilePath) - { - if (string.IsNullOrEmpty(headerCodeRegionFilePath)) - { - return string.Empty; - } - - var code = File.ReadAllText(headerCodeRegionFilePath); - return code; - } - - private static string VerifyFooterCodeRegion(string? footerCodeRegionFilePath) - { - if (string.IsNullOrEmpty(footerCodeRegionFilePath)) - { - return string.Empty; - } - - var code = File.ReadAllText(footerCodeRegionFilePath); - return code; - } -} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/UseCase.cs b/src/cs/production/C2CS.Feature.BindgenCSharp/UseCase.cs deleted file mode 100644 index 49ccaeaf..00000000 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/UseCase.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; -using System.Text.Json; -using System.Text.Json.Serialization; -using C2CS.Feature.BindgenCSharp.Data; -using C2CS.Feature.BindgenCSharp.Logic; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data.Serialization; - -namespace C2CS.Feature.BindgenCSharp; - -public class UseCase : UseCaseHandler -{ - protected override void Execute(Input input, Output output) - { - Validate(input); - - var abstractSyntaxTree = LoadCAbstractSyntaxTreeFromFileStorage(input.InputFilePath); - - var abstractSyntaxTreeCSharp = MapCAbstractSyntaxTreeToCSharp( - abstractSyntaxTree, - input.TypeAliases, - input.IgnoredTypeNames, - abstractSyntaxTree.Bitness, - Diagnostics); - - var codeCSharp = GenerateCSharpCode( - abstractSyntaxTreeCSharp, - input.ClassName, - input.LibraryName, - input.NamespaceName, - input.HeaderCodeRegion, - input.FooterCodeRegion); - - WriteCSharpCodeToFileStorage(input.OutputFilePath, codeCSharp); - } - - private static void Validate(Input request) - { - if (!File.Exists(request.InputFilePath)) - { - throw new UseCaseException($"File does not exist: `{request.InputFilePath}`."); - } - } - - [UseCaseStep("Load C abstract syntax tree from file storage.")] - private CAbstractSyntaxTree LoadCAbstractSyntaxTreeFromFileStorage(string inputFilePath) - { - BeginStep(); - var fileContents = File.ReadAllText(inputFilePath); - var serializerOptions = new JsonSerializerOptions - { - WriteIndented = true, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }; - var serializerContext = new CJsonSerializerContext(serializerOptions); - var abstractSyntaxTree = JsonSerializer.Deserialize(fileContents, serializerContext.CAbstractSyntaxTree)!; - EndStep(); - - return abstractSyntaxTree; - } - - [UseCaseStep("Map C abstract syntax tree to C#")] - private CSharpAbstractSyntaxTree MapCAbstractSyntaxTreeToCSharp( - CAbstractSyntaxTree abstractSyntaxTree, - ImmutableArray typeAliases, - ImmutableArray ignoredTypeNames, - int bitness, - DiagnosticsSink diagnostics) - { - BeginStep(); - var mapperParameters = new CSharpMapperParameters( - typeAliases, ignoredTypeNames, bitness, diagnostics); - var mapper = new CSharpMapper(mapperParameters); - var result = mapper.AbstractSyntaxTree(abstractSyntaxTree); - EndStep(); - - return result; - } - - [UseCaseStep("Generate C# code")] - private string GenerateCSharpCode( - CSharpAbstractSyntaxTree abstractSyntaxTree, - string className, - string libraryName, - string namespaceName, - string headerCodeRegion, - string footerCodeRegion) - { - BeginStep(); - var codeGenerator = new CSharpCodeGenerator( - className, libraryName, namespaceName, headerCodeRegion, footerCodeRegion); - var result = codeGenerator.EmitCode(abstractSyntaxTree); - EndStep(); - - return result; - } - - [UseCaseStep("Write C# code to file storage")] - private void WriteCSharpCodeToFileStorage( - string outputFilePath, string codeCSharp) - { - BeginStep(); - var outputDirectory = Path.GetDirectoryName(outputFilePath)!; - if (string.IsNullOrEmpty(outputDirectory)) - { - outputDirectory = AppContext.BaseDirectory; - outputFilePath = Path.Combine(Environment.CurrentDirectory, outputFilePath); - } - - if (!Directory.Exists(outputDirectory)) - { - Directory.CreateDirectory(outputDirectory); - } - - File.WriteAllText(outputFilePath, codeCSharp); - Console.WriteLine(outputFilePath); - EndStep(); - } -} diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTarget.cs b/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTarget.cs deleted file mode 100644 index 29e246f2..00000000 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTarget.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.BuildLibraryC.Domain; - -public record BuildTarget -{ - public RuntimeOperatingSystem OperatingSystem { get; init; } - - public ImmutableArray TargetArchitectures { get; init; } - - public bool IsEnabledCombineTargetArchitectures { get; init; } -} diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/DomainMapper.cs b/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/DomainMapper.cs deleted file mode 100644 index 87977c5e..00000000 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/DomainMapper.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; -using C2CS.Feature.BuildLibraryC.Data; - -namespace C2CS.Feature.BuildLibraryC.Domain; - -// NOTE: Maps from Data layer to Domain layer and/or vice-versa -public static class DomainMapper -{ - public static Input InputFrom(InputData data) - { - var buildTargets = BuildTargetsFrom(data.BuildTargets); - var input = new Input(buildTargets); - - return input; - } - - private static ImmutableArray BuildTargetsFrom(ImmutableArray? data) - { - if (data == null || data.Value.IsDefaultOrEmpty) - { - return ImmutableArray.Empty; - } - - var builder = ImmutableArray.CreateBuilder(); - foreach (var buildTargetData in data) - { - if (buildTargetData == null) - { - continue; - } - - var buildTarget = MapBuildTargetFrom(buildTargetData); - builder.Add(buildTarget); - } - - return builder.ToImmutable(); - } - - private static BuildTarget MapBuildTargetFrom(BuildTargetData data) - { - var operatingSystem = MapOperatingSystemFrom(data.OperatingSystem); - var targetArchitectures = MapTargetArchitecturesFrom(operatingSystem, data.TargetArchitectures); - var isEnabledCombineTargetArchitectures = MapIsEnabledCombineTargetArchitecturesFrom(operatingSystem, data.IsEnabledCombineArchitectures); - - var buildTarget = new BuildTarget - { - OperatingSystem = operatingSystem, - TargetArchitectures = targetArchitectures, - IsEnabledCombineTargetArchitectures = isEnabledCombineTargetArchitectures - }; - - return buildTarget; - } - - private static RuntimeOperatingSystem MapOperatingSystemFrom(string? operatingSystemString) - { - if (!Enum.TryParse(operatingSystemString, out var operatingSystem) || - operatingSystem is RuntimeOperatingSystem.Unknown) - { - return Platform.HostOperatingSystem; - } - - return operatingSystem; - } - - private static ImmutableArray MapTargetArchitecturesFrom( - RuntimeOperatingSystem operatingSystem, - ImmutableArray? architectureStrings) - { - var results = ImmutableArray.CreateBuilder(); - if (architectureStrings == null) - { - results.Add(Platform.HostArchitecture); - return results.ToImmutable(); - } - - var architecturesHashSetBuilder = ImmutableHashSet.CreateBuilder(); - foreach (var architectureString in architectureStrings) - { - if (string.IsNullOrEmpty(architectureString) || - !Enum.TryParse(architectureString, out var architecture) || - architecture is RuntimeArchitecture.Unknown) - { - continue; - } - - architecturesHashSetBuilder.Add(architecture); - } - - if (architecturesHashSetBuilder.Count == 0) - { - architecturesHashSetBuilder.Add(Platform.HostArchitecture); - } - - var architecturesHashSet = architecturesHashSetBuilder.ToImmutable(); - - switch (operatingSystem) - { - case RuntimeOperatingSystem.Windows: - case RuntimeOperatingSystem.macOS: - case RuntimeOperatingSystem.Linux: - FilterDesktopArchitectures(architecturesHashSet, results); - break; - case RuntimeOperatingSystem.FreeBSD: - case RuntimeOperatingSystem.Android: - case RuntimeOperatingSystem.iOS: - case RuntimeOperatingSystem.tvOS: - case RuntimeOperatingSystem.Browser: - case RuntimeOperatingSystem.PlayStation: - case RuntimeOperatingSystem.Xbox: - case RuntimeOperatingSystem.Switch: - // TODO: Needs testing; requires hardware. - throw new NotImplementedException(); - case RuntimeOperatingSystem.Unknown: - throw new NotSupportedException(); - default: - throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null); - } - - static void FilterDesktopArchitectures( - ImmutableHashSet architectures, - ImmutableArray.Builder builder) - { - if (architectures.Contains(RuntimeArchitecture.X64)) - { - builder.Add(RuntimeArchitecture.X64); - } - else if (architectures.Contains(RuntimeArchitecture.X86)) - { - builder.Add(RuntimeArchitecture.X86); - } - else if (architectures.Contains(RuntimeArchitecture.ARM64)) - { - builder.Add(RuntimeArchitecture.ARM64); - } - else if (architectures.Contains(RuntimeArchitecture.ARM32)) - { - builder.Add(RuntimeArchitecture.ARM32); - } - } - - return results.ToImmutable(); - } - - private static bool MapIsEnabledCombineTargetArchitecturesFrom( - RuntimeOperatingSystem operatingSystem, bool? combineTargetArchitectures) - { - if (combineTargetArchitectures == null) - { - return false; - } - - if (operatingSystem != RuntimeOperatingSystem.macOS && - operatingSystem != RuntimeOperatingSystem.iOS && - operatingSystem != RuntimeOperatingSystem.tvOS) - { - return false; - } - - return combineTargetArchitectures.Value; - } -} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/C2CS.Feature.ExtractAbstractSyntaxTreeC.csproj b/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/C2CS.Feature.ExtractAbstractSyntaxTreeC.csproj deleted file mode 100644 index 85450dae..00000000 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/C2CS.Feature.ExtractAbstractSyntaxTreeC.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - net6.0 - enable - enable - $(NoWarn);CA1724 - - - - - - - - - diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs b/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs deleted file mode 100644 index 34793545..00000000 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Diagnostics; - -public class TypeFromIgnoredHeaderDiagnostic : Diagnostic -{ - public TypeFromIgnoredHeaderDiagnostic(string typeName, string headerFilePath) - : base(DiagnosticSeverity.Warning) - { - Summary = - $"The type '{typeName}' belongs to the ignored header file '{headerFilePath}', but is used in the abstract syntax tree."; - } -} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Input.cs b/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Input.cs deleted file mode 100644 index 0ac31ada..00000000 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Input.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; - -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC; - -public class Input -{ - public string InputFilePath { get; } - - public string OutputFilePath { get; } - - public bool IsEnabledFindSdk { get; } - - public int MachineBitWidth { get; } - - public ImmutableArray IncludeDirectories { get; } - - public ImmutableArray ExcludedHeaderFiles { get; } - - public ImmutableArray OpaqueTypeNames { get; } - - public ImmutableArray FunctionNamesWhitelist { get; } - - public ImmutableArray ClangDefines { get; } - - public ImmutableArray ClangArguments { get; } - - public Input( - string? inputFilePath, - string? outputFilePath, - bool? isEnabledFindSdk, - int? machineBitWidth, - ImmutableArray? includeDirectories, - ImmutableArray? excludedHeaderFiles, - ImmutableArray? opaqueTypeNames, - ImmutableArray? functionNamesWhitelist, - ImmutableArray? defines, - ImmutableArray? clangArgs) - { - InputFilePath = VerifyInputFilePath(inputFilePath); - OutputFilePath = VerifyOutputFilePath(outputFilePath); - IsEnabledFindSdk = isEnabledFindSdk ?? true; - MachineBitWidth = VerifyMachineBitWidth(machineBitWidth); - IncludeDirectories = VerifyIncludeDirectories(includeDirectories, InputFilePath); - ExcludedHeaderFiles = VerifyImmutableArray(excludedHeaderFiles); - OpaqueTypeNames = VerifyImmutableArray(opaqueTypeNames); - FunctionNamesWhitelist = VerifyImmutableArray(functionNamesWhitelist); - ClangDefines = VerifyImmutableArray(defines); - ClangArguments = VerifyImmutableArray(clangArgs); - } - - private static string VerifyInputFilePath(string? inputFilePath) - { - if (string.IsNullOrEmpty(inputFilePath)) - { - throw new ConfigurationException("The input file can not be null, empty, or whitespace."); - } - - return Path.GetFullPath(inputFilePath); - } - - private static string VerifyOutputFilePath(string? outputFilePath) - { - if (!string.IsNullOrEmpty(outputFilePath)) - { - return Path.GetFullPath(outputFilePath); - } - - var defaultFilePath = Path.GetTempFileName(); - return defaultFilePath; - } - - private static int VerifyMachineBitWidth(int? machineBitWidth) - { - if (machineBitWidth == null) - { - return Platform.HostArchitecture switch - { - RuntimeArchitecture.ARM32 or RuntimeArchitecture.X86 => 32, - RuntimeArchitecture.ARM64 or RuntimeArchitecture.X64 => 64, - _ => throw new UseCaseException("Unknown runtime architecture.") - }; - } - - return machineBitWidth.Value; - } - - private static ImmutableArray VerifyIncludeDirectories( - ImmutableArray? includeDirectories, - string inputFilePath) - { - var result = VerifyImmutableArray(includeDirectories); - - if (result.IsDefaultOrEmpty) - { - var directoryPath = Path.GetDirectoryName(inputFilePath)!; - if (string.IsNullOrEmpty(directoryPath)) - { - directoryPath = Environment.CurrentDirectory; - } - - result = new[] - { - Path.GetFullPath(directoryPath) - }.ToImmutableArray(); - } - else - { - result = result.Select(Path.GetFullPath).ToImmutableArray(); - } - - return result; - } - - private static ImmutableArray VerifyImmutableArray(ImmutableArray? array) - { - if (array == null || array.Value.IsDefaultOrEmpty) - { - return ImmutableArray.Empty; - } - - var result = array.Value - .Where(x => !string.IsNullOrEmpty(x)).Cast().ToImmutableArray(); - return result; - } -} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/UseCase.cs b/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/UseCase.cs deleted file mode 100644 index 1adb3b6c..00000000 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/UseCase.cs +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System.Collections.Immutable; -using System.IO.Compression; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text.Json; -using System.Text.Json.Serialization; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data.Serialization; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Logic; -using static bottlenoselabs.clang; - -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC; - -public class UseCase : UseCaseHandler -{ - private static string _clangNativeLibraryPath = null!; - - protected override void Execute(Input input, Output output) - { - Validate(input); - - SetupClang(Platform.HostOperatingSystem); - - var translationUnit = Parse( - input.InputFilePath, - input.IsEnabledFindSdk, - input.IncludeDirectories, - input.ClangDefines, - input.MachineBitWidth, - input.ClangArguments); - - var abstractSyntaxTreeC = Explore( - translationUnit, - input.IncludeDirectories, - input.ExcludedHeaderFiles, - input.OpaqueTypeNames, - input.FunctionNamesWhitelist, - input.MachineBitWidth); - - Write( - input.OutputFilePath, - abstractSyntaxTreeC); - } - - [UseCaseStep("Setup Clang")] - private void SetupClang(RuntimeOperatingSystem operatingSystem) - { - BeginStep(); - - if (operatingSystem == RuntimeOperatingSystem.macOS) - { - _clangNativeLibraryPath = "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib"; - if (!File.Exists(_clangNativeLibraryPath)) - { - throw new ClangException( - "Please install CommandLineTools for macOS. This will install `libclang.dylib`. Use the command `xcode-select --install`."); - } - } - else if (operatingSystem == RuntimeOperatingSystem.Linux) - { - _clangNativeLibraryPath = Path.Combine(AppContext.BaseDirectory, "libclang.so"); - if (!File.Exists(_clangNativeLibraryPath)) - { - DownloadLibClang("ubuntu.20.04-x64", _clangNativeLibraryPath); - } - } - else if (operatingSystem == RuntimeOperatingSystem.Windows) - { - _clangNativeLibraryPath = Path.Combine(AppContext.BaseDirectory, "libclang.dll"); - if (!File.Exists(_clangNativeLibraryPath)) - { - DownloadLibClang("win-x64", _clangNativeLibraryPath); - } - } - - EndStep(); - - static void DownloadLibClang(string runtimeIdentifier, string target) - { - var zipFilePath = Path.Combine(AppContext.BaseDirectory, "libclang.zip"); - if (File.Exists(zipFilePath)) - { - File.Delete(zipFilePath); - } - - DownloadFile( - $"https://www.nuget.org/api/v2/package/libclang.runtime.{runtimeIdentifier}", - zipFilePath); - - var extractDirectory = Path.Combine(AppContext.BaseDirectory, "libclang"); - if (Directory.Exists(extractDirectory)) - { - Directory.Delete(extractDirectory, true); - } - - Directory.CreateDirectory(extractDirectory); - ZipFile.ExtractToDirectory(zipFilePath, extractDirectory); - - var fileExtension = Path.GetExtension(target); - File.Copy( - Path.Combine(extractDirectory, $"runtimes/{runtimeIdentifier}/native/libclang{fileExtension}"), - target); - } - - static void DownloadFile(string url, string filePath) - { - using var client = new HttpClient(); - var uri = new Uri(url); - using var response = client.GetStreamAsync(uri).Result; - using var fileStream = new FileStream(filePath, FileMode.CreateNew); - response.CopyToAsync(fileStream).Wait(); - } - - try - { - NativeLibrary.SetDllImportResolver(typeof(bottlenoselabs.clang).Assembly, ResolveClang); - } - catch (ArgumentException) - { - // already set; ignore - } - } - - private static IntPtr ResolveClang(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) - { - if (!NativeLibrary.TryLoad(_clangNativeLibraryPath, out var handle)) - { - throw new ClangException($"Could not load libclang: {_clangNativeLibraryPath}"); - } - - return handle; - } - - private static void Validate(Input request) - { - if (!File.Exists(request.InputFilePath)) - { - throw new UseCaseException($"The input file does not exist: `{request.InputFilePath}`."); - } - - foreach (var includeDirectory in request.IncludeDirectories) - { - if (!Directory.Exists(includeDirectory)) - { - throw new UseCaseException($"The include directory does not exist: `{includeDirectory}`."); - } - } - } - - [UseCaseStep("Parse C code from disk")] - private CXTranslationUnit Parse( - string inputFilePath, - bool automaticallyFindSoftwareDevelopmentKit, - ImmutableArray includeDirectories, - ImmutableArray defines, - int bitness, - ImmutableArray clangArguments) - { - BeginStep(); - var clangArgs = ClangArgumentsBuilder.Build( - automaticallyFindSoftwareDevelopmentKit, - includeDirectories, - defines, - bitness, - clangArguments); - var result = ClangTranslationUnitParser.Parse(inputFilePath, clangArgs); - EndStep(); - return result; - } - - [UseCaseStep("Extract C abstract syntax tree")] - private CAbstractSyntaxTree Explore( - CXTranslationUnit translationUnit, - ImmutableArray includeDirectories, - ImmutableArray excludedHeaderFiles, - ImmutableArray opaqueTypeNames, - ImmutableArray functionNamesWhitelist, - int machineBitWidth) - { - BeginStep(); - var clangExplorer = new CTranslationUnitExplorer( - Diagnostics, includeDirectories, excludedHeaderFiles, opaqueTypeNames, functionNamesWhitelist); - var result = clangExplorer.AbstractSyntaxTree(translationUnit, machineBitWidth); - EndStep(); - return result; - } - - [UseCaseStep("Write C abstract syntax tree to disk")] - private void Write( - string outputFilePath, CAbstractSyntaxTree abstractSyntaxTree) - { - BeginStep(); - var outputDirectory = Path.GetDirectoryName(outputFilePath)!; - if (string.IsNullOrEmpty(outputDirectory)) - { - outputDirectory = AppContext.BaseDirectory; - outputFilePath = Path.Combine(Environment.CurrentDirectory, outputFilePath); - } - - if (!Directory.Exists(outputDirectory)) - { - Directory.CreateDirectory(outputDirectory); - } - - if (File.Exists(outputFilePath)) - { - File.Delete(outputFilePath); - } - - var serializerOptions = new JsonSerializerOptions - { - WriteIndented = true, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }; - var serializerContext = new CJsonSerializerContext(serializerOptions); - var fileContents = JsonSerializer.Serialize(abstractSyntaxTree, serializerContext.Options); - - // File.WriteAllText doesn't flush until process exits on macOS .NET 5 lol - using var fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate); - using var textWriter = new StreamWriter(fileStream); - textWriter.Write(fileContents); - textWriter.Close(); - fileStream.Close(); - - Console.WriteLine(outputFilePath); - EndStep(); - } -} diff --git a/src/cs/production/C2CS/C2CS.csproj b/src/cs/production/C2CS/C2CS.csproj index 1d863c4d..6b319f0a 100644 --- a/src/cs/production/C2CS/C2CS.csproj +++ b/src/cs/production/C2CS/C2CS.csproj @@ -7,7 +7,13 @@ C2CS false en - $(NoWarn);CA1724 + $(NoWarn);CA1724;CA1812 + win-x64;osx-x64;osx-arm64;linux-x64 + + + + + false @@ -21,18 +27,27 @@ + + + + + + + + - - + + + - - - - - + + + + + diff --git a/src/cs/production/C2CS/C2CS.csproj.DotSettings b/src/cs/production/C2CS/C2CS.csproj.DotSettings index 33a5359c..f4a900d7 100644 --- a/src/cs/production/C2CS/C2CS.csproj.DotSettings +++ b/src/cs/production/C2CS/C2CS.csproj.DotSettings @@ -16,7 +16,7 @@ True True True - True + False True True True diff --git a/src/cs/production/C2CS/CommandLineArgumentsProvider.cs b/src/cs/production/C2CS/CommandLineArgumentsProvider.cs new file mode 100644 index 00000000..1aea7ff1 --- /dev/null +++ b/src/cs/production/C2CS/CommandLineArgumentsProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS; + +internal sealed class CommandLineArgumentsProvider +{ + public readonly string[] CommandLineArguments; + + public CommandLineArgumentsProvider(string[] commandLineArguments) + { + CommandLineArguments = commandLineArguments; + } +} diff --git a/src/cs/production/C2CS/CommandLineInterface.cs b/src/cs/production/C2CS/CommandLineInterface.cs new file mode 100644 index 00000000..53e81a09 --- /dev/null +++ b/src/cs/production/C2CS/CommandLineInterface.cs @@ -0,0 +1,106 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.CommandLine; +using System.IO; +using System.Text.Json; +using C2CS.Data; +using C2CS.Data.Serialization; +using C2CS.Feature.ReadCodeC; +using C2CS.Feature.WriteCodeCSharp; +using Json.Schema; +using Json.Schema.Generation; +using Microsoft.Extensions.DependencyInjection; + +namespace C2CS; + +internal class CommandLineInterface : RootCommand +{ + private readonly ConfigurationJsonSerializer _configurationJsonSerializer; + private readonly IServiceProvider _serviceProvider; + + public CommandLineInterface( + ConfigurationJsonSerializer configurationJsonSerializer, + IServiceProvider serviceProvider) + : base("C2CS - C to C# bindings code generator.") + { + _configurationJsonSerializer = configurationJsonSerializer; + _serviceProvider = serviceProvider; + + var configurationOption = new Option( + new[] { "--configurationFilePath", "-c" }, + "File path of the configuration `.json` file.") + { + IsRequired = false + }; + + var abstractSyntaxTreeCommand = new Command( + "ast", "Dump the abstract syntax tree of a C `.h` file to one or more `.json` files per platform."); + abstractSyntaxTreeCommand.AddOption(configurationOption); + abstractSyntaxTreeCommand.SetHandler(HandleAbstractSyntaxTreesC, configurationOption); + AddCommand(abstractSyntaxTreeCommand); + + var bindgenCSharpCommand = new Command( + "cs", "Generate a C# bindings `.cs` file from one or more C abstract syntax tree `.json` files per platform."); + bindgenCSharpCommand.AddOption(configurationOption); + bindgenCSharpCommand.SetHandler(HandleBindgenCSharp, configurationOption); + AddCommand(bindgenCSharpCommand); + + var configurationGenerateSchemaCommand = new Command( + "schema", "Generate the `schema.json` file for the configuration in the working directory."); + configurationGenerateSchemaCommand.SetHandler(GenerateSchema); + this.SetHandler(Handle, configurationOption); + AddCommand(configurationGenerateSchemaCommand); + } + + private void Handle(string configurationFilePath) + { + HandleAbstractSyntaxTreesC(configurationFilePath); + HandleBindgenCSharp(configurationFilePath); + } + + private void HandleAbstractSyntaxTreesC(string configurationFilePath) + { + if (string.IsNullOrEmpty(configurationFilePath)) + { + configurationFilePath = "config.json"; + } + + var configuration = _configurationJsonSerializer.Read(configurationFilePath); + var request = configuration.ReadC; + if (request == null) + { + return; + } + + var useCase = _serviceProvider.GetService()!; + useCase.Execute(request); + } + + private void HandleBindgenCSharp(string configurationFilePath) + { + if (string.IsNullOrEmpty(configurationFilePath)) + { + configurationFilePath = "config.json"; + } + + var configuration = _configurationJsonSerializer.Read(configurationFilePath); + var request = configuration.WriteCSharp; + if (request == null) + { + return; + } + + var useCase = _serviceProvider.GetService()!; + useCase.Execute(request); + } + + private static void GenerateSchema() + { + var schemaBuilder = new JsonSchemaBuilder().FromType(); + var schema = schemaBuilder.Build(); + var json = JsonSerializer.Serialize(schema); + File.WriteAllText("schema.json", json); + } +} diff --git a/src/cs/production/C2CS/CommandLineService.cs b/src/cs/production/C2CS/CommandLineService.cs new file mode 100644 index 00000000..648610f7 --- /dev/null +++ b/src/cs/production/C2CS/CommandLineService.cs @@ -0,0 +1,45 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.CommandLine; +using System.CommandLine.Help; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace C2CS; + +internal sealed class CommandLineService : IHostedService +{ + private readonly IApplicationLifetime _applicationLifetime; + private readonly string[] _commandLineArguments; + private readonly RootCommand _rootCommand; + + public CommandLineService( + IApplicationLifetime applicationLifetime, + CommandLineArgumentsProvider commandLineArgumentsProvider, + RootCommand command) + { + _applicationLifetime = applicationLifetime; + _commandLineArguments = commandLineArgumentsProvider.CommandLineArguments; + _rootCommand = command; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _applicationLifetime.ApplicationStarted.Register(() => Task.Run(Main, cancellationToken)); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private void Main() + { + Environment.ExitCode = _rootCommand.Invoke(_commandLineArguments); + _applicationLifetime.StopApplication(); + } +} diff --git a/src/cs/production/C2CS/Configuration.cs b/src/cs/production/C2CS/Configuration.cs deleted file mode 100644 index c71f5d73..00000000 --- a/src/cs/production/C2CS/Configuration.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; -using C2CS.Feature.BindgenCSharp.Data; - -namespace C2CS; - -// NOTE: Properties are required for System.Text.Json serialization -// NOTE: This class is considered un-sanitized input; all strings and other types could be null. -public class Configuration -{ - /// - /// Path of the input `.h` header file. - /// - public string? InputFilePath { get; set; } - - /// - /// Path of the output C# `.cs` file. If not specified, defaults to a file path using the current directory, a - /// file name without extension that matches the , and a `.cs` file name extension. - /// - public string? OutputFilePath { get; set; } - - /// - /// Path of the intermediate output abstract syntax tree `.json` file. If not specified, defaults to a random - /// temporary file. - /// - public string? AbstractSyntaxTreeOutputFilePath { get; set; } - - /// - /// The name of the dynamic link library (without the file extension) used for platform invoke (P/Invoke) with - /// C#. If not specified, the library name is the same as the name of the without - /// the directory name and without the file extension. - /// - public string? LibraryName { get; set; } - - /// - /// The name of the namespace to be used for the C# static class. If not specified, the namespace is the same as the - /// . - /// - public string? NamespaceName { get; set; } - - /// - /// The name of the C# static class. If not specified, the class name is the same as the - /// . - /// - public string? ClassName { get; set; } - - /// - /// Path of the text file which to add the file's contents to the top of the C# file. Useful for comments, extra - /// namespace using statements, or additional code that needs to be added to the generated C# file. - /// - public string? HeaderCodeRegionFilePath { get; set; } - - /// - /// Path of the text file which to add the file's contents to the bottom of the C# file. Useful for comments or - /// additional code that needs to be added to the generated C# file. - /// - public string? FooterCodeRegionFilePath { get; set; } - - /// - /// Pairs of strings for re-mapping type names. Each pair has source name and a target name. The source name may - /// be found when parsing C code and get mapped to the target name when generating C# code. Does not change the - /// type's bit layout. - /// - public ImmutableArray? MappedTypeNames { get; set; } - - /// - /// Determines whether the software development kit (SDK) for C/C++ is attempted to be found. Default is - /// true. If true, the C/C++ header files for the current operating system are attempted to be - /// found. In such a case, if the C/C++ header files can not be found, then an error is generated which halts - /// the program. If false, the C/C++ header files will likely be missing causing Clang to generate - /// parsing errors which also halts the program. In such a case, the missing C/C++ header files can be supplied - /// to Clang using such as "-isystemPATH/TO/SYSTEM/HEADER/DIRECTORY". - /// - public bool? IsEnabledFindSdk { get; set; } = true; - - /// - /// The bit width of the computer architecture to use when parsing C code. Default is null. If - /// null, the bit width of host operating system's computer architecture is used. E.g. the default for - /// x64 Windows is `64`. Possible values are null, 32 where pointers are 4 bytes, or 64 - /// where pointers are 8 bytes. - /// - public int? MachineBitWidth { get; set; } - - /// - /// Search directory paths to use for `#include` usages when parsing C code. If null, uses the - /// directory path of . - /// - public ImmutableArray? IncludeDirectories { get; set; } - - /// - /// Object-like macros to use when parsing C code. - /// - public ImmutableArray? Defines { get; set; } - - /// - /// C header file names to exclude. File names are relative to . - /// - public ImmutableArray? ExcludedHeaderFiles { get; set; } - - /// - /// Type names that may be found when parsing C code that will be ignored when generating C# code. - /// Types are ignored after mapping type names using . - /// - public ImmutableArray? IgnoredTypeNames { get; set; } - - /// - /// The C function names to explicitly include when parsing C code. Default is null. If null, - /// no white list applies to which all C function names that are found are eligible for C# code generation. - /// Note that C function names which are excluded also exclude any transitive types. - /// - public ImmutableArray? FunctionNamesWhiteList { get; set; } - - /// - /// Type names that may be found when parsing C code that will be interpreted as opaque types. Opaque types are - /// often used with a pointer to hide the information about the bit layout behind the pointer. - /// - public ImmutableArray? OpaqueTypeNames { get; set; } - - /// - /// Additional Clang arguments to use when parsing C code. - /// - public ImmutableArray? ClangArguments { get; set; } - - public static Configuration GetFrom(IReadOnlyList? args) - { - var argsCount = args?.Count ?? 0; - var configurationFilePath = argsCount switch - { - 1 => args![0], - 0 => Path.Combine(Environment.CurrentDirectory, "config.json"), - _ => throw new InvalidOperationException( - "Unsupported number of arguments. Please specify zero arguments or one argument with the file path of the configuration `.json` file.") - }; - - try - { - var fileContents = File.ReadAllText(configurationFilePath); - var jsonSerializerOptions = new JsonSerializerOptions - { - WriteIndented = true, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, - - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }; - var serializerContext = new C2CS.ConfigurationSerializerContext(jsonSerializerOptions); - var configuration = JsonSerializer.Deserialize(fileContents, serializerContext.Configuration)!; - return configuration; - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } -} diff --git a/src/cs/production/C2CS/Data/Configuration.cs b/src/cs/production/C2CS/Data/Configuration.cs new file mode 100644 index 00000000..97e7f682 --- /dev/null +++ b/src/cs/production/C2CS/Data/Configuration.cs @@ -0,0 +1,28 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Text.Json.Serialization; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.WriteCodeCSharp.Data; +using JetBrains.Annotations; + +namespace C2CS.Data; + +// NOTE: Properties are required for System.Text.Json serialization +// NOTE: This class is considered un-sanitized input; all strings and other types could be null. +// NOTE: This class must have a unique name across namespaces for usage in System.Text.Json source generators. +[PublicAPI] +public sealed class Configuration +{ + [JsonPropertyName("directory")] + [Json.Schema.Generation.Description("Path of the input and output abstract syntax tree directory. By default, the directory will be used to write a `.json` file for each target platform's abstract syntax tree that has been extracted. By default, the same abstract syntax tree `.json` files will then be read when generating C# code.")] + public string? InputOutputFileDirectory { get; set; } + + [JsonPropertyName("ast")] + [Json.Schema.Generation.Description("The configuration for reading the `.h` C header file.")] + public ReadCodeCConfiguration? ReadC { get; set; } + + [JsonPropertyName("cs")] + [Json.Schema.Generation.Description("The configuration for writing the `.cs` C# source code file.")] + public WriteCodeCSharpConfiguration? WriteCSharp { get; set; } +} diff --git a/src/cs/production/C2CS/Data/Serialization/ConfigurationJsonSerializer.cs b/src/cs/production/C2CS/Data/Serialization/ConfigurationJsonSerializer.cs new file mode 100644 index 00000000..ed0e2941 --- /dev/null +++ b/src/cs/production/C2CS/Data/Serialization/ConfigurationJsonSerializer.cs @@ -0,0 +1,98 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.IO.Abstractions; +using System.Text.Json; +using System.Text.Json.Serialization; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Foundation.Data.Serialization; +using Microsoft.Extensions.Logging; + +namespace C2CS.Data.Serialization; + +public sealed class ConfigurationJsonSerializer +{ + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly ConfigurationSerializerContext _serializerContext; + + public ConfigurationJsonSerializer(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + + var jsonSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + Converters = + { + new JsonStringEnumConverter(SnakeCaseNamingPolicy.Instance) + } + }; + _serializerContext = new ConfigurationSerializerContext(jsonSerializerOptions); + } + + public Configuration Read(string filePath) + { + var fullFilePath = _fileSystem.Path.GetFullPath(filePath); + + try + { + var fileContents = _fileSystem.File.ReadAllText(fullFilePath); + var configuration = JsonSerializer.Deserialize(fileContents, _serializerContext.Configuration)!; + + Polyfill(configuration); + + _logger.ConfigurationLoadSuccess(fullFilePath); + return configuration; + } + catch (Exception e) + { + _logger.ConfigurationLoadFailure(fullFilePath, e); + throw; + } + } + + private static void Polyfill(Configuration configuration) + { + var requestExtractC = configuration.ReadC; + if (requestExtractC?.ConfigurationAbstractSyntaxTrees != null) + { + foreach (var (_, extractAbstractSyntaxTreeC) in requestExtractC.ConfigurationAbstractSyntaxTrees) + { + if (extractAbstractSyntaxTreeC != null) + { + PolyfillExtractAbstractSyntaxTreeC(configuration, extractAbstractSyntaxTreeC); + } + } + } + + var requestBindgenCSharp = configuration.WriteCSharp; + if (requestBindgenCSharp != null) + { + PolyfillBindgenCSharp(configuration, requestBindgenCSharp); + } + } + + private static void PolyfillExtractAbstractSyntaxTreeC( + Configuration configuration, ReadCodeCConfigurationAbstractSyntaxTree extract) + { + if (string.IsNullOrEmpty(extract.OutputFileDirectory)) + { + extract.OutputFileDirectory = configuration.InputOutputFileDirectory; + } + } + + private static void PolyfillBindgenCSharp(Configuration configuration, WriteCodeCSharpConfiguration write) + { + if (string.IsNullOrEmpty(write.InputFileDirectory)) + { + write.InputFileDirectory = configuration.InputOutputFileDirectory; + } + } +} diff --git a/src/cs/production/C2CS/ConfigurationSerializerContext.cs b/src/cs/production/C2CS/Data/Serialization/ConfigurationSerializerContext.cs similarity index 83% rename from src/cs/production/C2CS/ConfigurationSerializerContext.cs rename to src/cs/production/C2CS/Data/Serialization/ConfigurationSerializerContext.cs index 0c55d293..bdae56f6 100644 --- a/src/cs/production/C2CS/ConfigurationSerializerContext.cs +++ b/src/cs/production/C2CS/Data/Serialization/ConfigurationSerializerContext.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; -namespace C2CS; +namespace C2CS.Data.Serialization; [JsonSourceGenerationOptions( WriteIndented = true, @@ -11,6 +11,6 @@ namespace C2CS; PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(Configuration))] -internal partial class ConfigurationSerializerContext : JsonSerializerContext +public partial class ConfigurationSerializerContext : JsonSerializerContext { } diff --git a/src/cs/production/C2CS/Data/Serialization/Logging.cs b/src/cs/production/C2CS/Data/Serialization/Logging.cs new file mode 100644 index 00000000..5b403138 --- /dev/null +++ b/src/cs/production/C2CS/Data/Serialization/Logging.cs @@ -0,0 +1,32 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using C2CS.Foundation; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Data.Serialization; + +public static class Logging +{ + private static readonly Action ActionConfigurationSuccess = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Configuration load: success."), + "Configuration load: Success. Path: {FilePath}."); + + private static readonly Action ActionConfigurationFailure = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Configuration load: failure."), + "Configuration load. Failed. Path: {FilePath}."); + + public static void ConfigurationLoadSuccess(this ILogger logger, string filePath) + { + ActionConfigurationSuccess(logger, filePath, null!); + } + + public static void ConfigurationLoadFailure(this ILogger logger, string filePath, Exception exception) + { + ActionConfigurationFailure(logger, filePath, exception); + } +} diff --git a/src/cs/production/C2CS/Program.cs b/src/cs/production/C2CS/Program.cs index 5e791d64..7c726bad 100644 --- a/src/cs/production/C2CS/Program.cs +++ b/src/cs/production/C2CS/Program.cs @@ -2,74 +2,16 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System; -using System.Linq; -using C2CS.Feature.ExtractAbstractSyntaxTreeC; +using Microsoft.Extensions.Hosting; namespace C2CS; public static class Program { - public static int Main(string[]? args = null) + public static int Main(string[] args) { - if (args != null && args.Length == 2 && args[0] == "build") - { - return Feature.BuildLibraryC.Program.Main(args.Skip(1).ToArray()); - } - else - { - var configuration = Configuration.GetFrom(args); - return EntryPoint(configuration); - } - } - - // ReSharper disable once MemberCanBePrivate.Global - public static int EntryPoint(Configuration configuration) - { - var jsonFilePath = ExtractAbstractSyntaxTreeC(configuration); - BindgenCSharp(jsonFilePath, configuration); + using var host = Startup.CreateHost(args); + host.Run(); return Environment.ExitCode; } - - private static string ExtractAbstractSyntaxTreeC(Configuration c) - { - var request = new Feature.ExtractAbstractSyntaxTreeC.Input( - c.InputFilePath, - c.AbstractSyntaxTreeOutputFilePath, - c.IsEnabledFindSdk, - c.MachineBitWidth, - c.IncludeDirectories, - c.ExcludedHeaderFiles, - c.OpaqueTypeNames, - c.FunctionNamesWhiteList, - c.Defines, - c.ClangArguments); - var useCase = new UseCase(); - var response = useCase.Execute(request); - if (response.Status == UseCaseOutputStatus.Failure) - { - Environment.Exit(1); - } - - return request.OutputFilePath; - } - - private static void BindgenCSharp(string inputFilePath, Configuration c) - { - var request = new Feature.BindgenCSharp.Input( - inputFilePath, - c.OutputFilePath, - c.LibraryName, - c.NamespaceName, - c.ClassName, - c.MappedTypeNames, - c.IgnoredTypeNames, - c.HeaderCodeRegionFilePath, - c.FooterCodeRegionFilePath); - var useCase = new Feature.BindgenCSharp.UseCase(); - var response = useCase.Execute(request); - if (response.Status == UseCaseOutputStatus.Failure) - { - Environment.Exit(1); - } - } } diff --git a/src/cs/production/C2CS/Startup.cs b/src/cs/production/C2CS/Startup.cs new file mode 100644 index 00000000..2c84db81 --- /dev/null +++ b/src/cs/production/C2CS/Startup.cs @@ -0,0 +1,59 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.CommandLine; +using System.IO.Abstractions; +using C2CS.Data.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; + +namespace C2CS; + +public static class Startup +{ + public static IHost CreateHost(string[] args) + { + return new HostBuilder() + .UseConsoleLifetime() + .BuildHostCommon(args) + .Build(); + } + + public static IHostBuilder BuildHostCommon(this IHostBuilder builder, string[]? args = null) + { + return builder + .ConfigureServices(services => ConfigureServices(services, args)) + .UseServiceProviderFactory(new DefaultServiceProviderFactory(new ServiceProviderOptions + { + ValidateScopes = true, + ValidateOnBuild = true + })); + } + + private static void ConfigureServices(IServiceCollection services, string[]? args) + { + services.AddSingleton(); + services.AddSingleton(new CommandLineArgumentsProvider(args ?? Environment.GetCommandLineArgs())); + services.AddLogging(x => + x.AddSimpleConsole(options => + { + options.ColorBehavior = LoggerColorBehavior.Enabled; + options.SingleLine = true; + options.IncludeScopes = true; + options.UseUtcTimestamp = true; + options.TimestampFormat = "yyyy-dd-MM HH:mm:ss "; + })); + services.AddSingleton(x => + x.GetRequiredService().CreateLogger(string.Empty)); + services.AddHostedService(); + services.AddSingleton(); + services.AddSingleton(); + + Feature.ReadCodeC.Startup.ConfigureServices(services); + Feature.WriteCodeCSharp.Startup.ConfigureServices(services); + Feature.BuildLibraryC.Startup.ConfigureServices(services); + } +} diff --git a/src/cs/production/Directory.Build.props b/src/cs/production/Directory.Build.props index e2be5638..0b794275 100644 --- a/src/cs/production/Directory.Build.props +++ b/src/cs/production/Directory.Build.props @@ -9,7 +9,7 @@ latest - + true true diff --git a/src/cs/production/clang-c/clang-c.csproj b/src/cs/production/clang-c/clang-c.csproj deleted file mode 100644 index 910f3275..00000000 --- a/src/cs/production/clang-c/clang-c.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - - net6.0 - Exe - - - - - - - - - - PreserveNewest - - - - diff --git a/src/cs/production/clang-cs/ClangLocation.cs b/src/cs/production/clang-cs/ClangLocation.cs deleted file mode 100644 index 80d09101..00000000 --- a/src/cs/production/clang-cs/ClangLocation.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Text.Json.Serialization; - -// NOTE: Properties are required for System.Text.Json serialization -public struct ClangLocation : IComparable, IEquatable -{ - [JsonPropertyName("fileName")] - public string FileName { get; set; } - - [JsonPropertyName("filePath")] - public string FilePath { get; set; } - - [JsonPropertyName("line")] - public int LineNumber { get; set; } - - [JsonPropertyName("column")] - public int LineColumn { get; set; } - - [JsonPropertyName("isBuiltin")] - public bool IsBuiltin { get; set; } - - public override string ToString() - { - if (LineNumber == 0 && LineColumn == 0) - { - return $"{FileName}"; - } - - return string.IsNullOrEmpty(FilePath) || FilePath == FileName - ? $"{FileName}:{LineNumber}:{LineColumn}" - : $"{FileName}:{LineNumber}:{LineColumn} ({FilePath})"; - } - - public bool Equals(ClangLocation other) - { - return FilePath == other.FilePath && LineNumber == other.LineNumber; - } - - public override bool Equals(object? obj) - { - return obj is ClangLocation other && Equals(other); - } - - public override int GetHashCode() - { - return HashCode.Combine(FilePath, LineNumber, LineColumn); - } - - public int CompareTo(ClangLocation other) - { - // ReSharper disable once JoinDeclarationAndInitializer - int result; - - result = string.Compare(FileName, other.FileName, StringComparison.Ordinal); - // ReSharper disable once ConvertIfStatementToReturnStatement - if (result != 0) - { - return result; - } - - result = LineNumber.CompareTo(other.LineNumber); - - return result; - } - - public static bool operator ==(ClangLocation first, ClangLocation second) - { - return first.Equals(second); - } - - public static bool operator !=(ClangLocation first, ClangLocation second) - { - return !(first == second); - } - - public static bool operator <(ClangLocation first, ClangLocation second) - { - return first.CompareTo(second) < 0; - } - - public static bool operator >(ClangLocation first, ClangLocation second) - { - return first.CompareTo(second) > 0; - } - - public static bool operator >=(ClangLocation first, ClangLocation second) - { - return first.CompareTo(second) >= 0; - } - - public static bool operator <=(ClangLocation first, ClangLocation second) - { - return first.CompareTo(second) <= 0; - } -} diff --git a/src/cs/production/clang-cs/ClangTranslationUnitParser.cs b/src/cs/production/clang-cs/ClangTranslationUnitParser.cs deleted file mode 100644 index b4d71069..00000000 --- a/src/cs/production/clang-cs/ClangTranslationUnitParser.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. -// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. - -using System; -using System.Collections.Immutable; -using static bottlenoselabs.clang; - -public static class ClangTranslationUnitParser -{ - public static CXTranslationUnit Parse( - string headerFilePath, - ImmutableArray clangArgs) - { - var clangArgsConcat = string.Join(" ", clangArgs); - Console.WriteLine($"libclang: Parsing '{headerFilePath}' with the following arguments..."); - Console.WriteLine($"\t{clangArgsConcat}"); - - if (!TryParseTranslationUnit(headerFilePath, clangArgs, out var translationUnit)) - { - throw new ClangException("libclang failed."); - } - - var diagnostics = GetCompilationDiagnostics(translationUnit); - if (diagnostics.IsDefaultOrEmpty) - { - return translationUnit; - } - - var defaultDisplayOptions = clang_defaultDiagnosticDisplayOptions(); - Console.Error.WriteLine("Clang diagnostics:"); - var hasErrors = false; - foreach (var diagnostic in diagnostics) - { - Console.Error.Write("\t"); - var clangString = clang_formatDiagnostic(diagnostic, defaultDisplayOptions); - var diagnosticStringC = clang_getCString(clangString); - var diagnosticString = Runtime.CStrings.String(diagnosticStringC); - Console.Error.WriteLine(diagnosticString); - - var severity = clang_getDiagnosticSeverity(diagnostic); - if (severity == CXDiagnosticSeverity.CXDiagnostic_Error || - severity == CXDiagnosticSeverity.CXDiagnostic_Fatal) - { - hasErrors = true; - } - } - - if (hasErrors) - { - throw new ClangException("Clang parsing errors."); - } - - return translationUnit; - } - - private static unsafe bool TryParseTranslationUnit( - string filePath, - ImmutableArray commandLineArgs, - out CXTranslationUnit translationUnit) - { - // ReSharper disable BitwiseOperatorOnEnumWithoutFlags - const uint options = 0x00001000 | // CXTranslationUnit_IncludeAttributedTypes - 0x00004000 | // CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles - 0x00000040 | // CXTranslationUnit_SkipFunctionBodies - 0x1 | // CXTranslationUnit_DetailedPreprocessingRecord - 0x0; - - var index = clang_createIndex(0, 0); - var cSourceFilePath = Runtime.CStrings.CString(filePath); - var cCommandLineArgs = Runtime.CStrings.CStringArray(commandLineArgs.AsSpan()); - - CXErrorCode errorCode; - fixed (CXTranslationUnit* translationUnitPointer = &translationUnit) - { - errorCode = clang_parseTranslationUnit2( - index, - cSourceFilePath, - cCommandLineArgs, - commandLineArgs.Length, - (CXUnsavedFile*)IntPtr.Zero, - 0, - options, - translationUnitPointer); - } - - var result = errorCode == CXErrorCode.CXError_Success; - return result; - } - - private static ImmutableArray GetCompilationDiagnostics(CXTranslationUnit translationUnit) - { - var diagnosticsCount = (int)clang_getNumDiagnostics(translationUnit); - var builder = ImmutableArray.CreateBuilder(diagnosticsCount); - - for (uint i = 0; i < diagnosticsCount; ++i) - { - var diagnostic = clang_getDiagnostic(translationUnit, i); - builder.Add(diagnostic); - } - - return builder.ToImmutable(); - } -} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutputStatus.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryConfiguration.cs similarity index 62% rename from src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutputStatus.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryConfiguration.cs index dab3716f..d005495d 100644 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutputStatus.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryConfiguration.cs @@ -1,10 +1,10 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS; +using C2CS.Foundation.UseCases; -public enum UseCaseOutputStatus +namespace C2CS.Feature.BuildLibraryC; + +public class BuildLibraryConfiguration : UseCaseConfiguration { - Failure, - Success } diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Input.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryInput.cs similarity index 66% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Input.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryInput.cs index 94b9e817..7549a827 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Input.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryInput.cs @@ -1,24 +1,24 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using System.Collections.Immutable; using System.Text.Json; using System.Text.Json.Serialization; -using C2CS.Feature.BuildLibraryC.Data.Serialization; -using C2CS.Feature.BuildLibraryC.Domain; +using C2CS.Feature.BuildLibraryC.Data; +using C2CS.Foundation; +using JsonSerializerContext = C2CS.Feature.BuildLibraryC.Data.Serialization.JsonSerializerContext; namespace C2CS.Feature.BuildLibraryC; -public class Input +public class BuildLibraryInput { - public ImmutableArray BuildTargets { get; } + public BuildProject Project { get; } - public Input(ImmutableArray buildTargets) + public BuildLibraryInput(BuildProject project) { - BuildTargets = buildTargets; + Project = project; } - public static Input GetFrom(IReadOnlyList? args) + public static BuildLibraryInput GetFrom(IReadOnlyList? args) { var argsCount = args?.Count ?? 0; @@ -44,9 +44,9 @@ public static Input GetFrom(IReadOnlyList? args) } }; - var serializerContext = new InputDataSerializerContext(jsonSerializerOptions); - var data = JsonSerializer.Deserialize(fileContents, serializerContext.InputData)!; - var input = DomainMapper.InputFrom(data); + var serializerContext = new JsonSerializerContext(jsonSerializerOptions); + var buildProject = JsonSerializer.Deserialize(fileContents, serializerContext.BuildProject)!; + var input = new BuildLibraryInput(buildProject); return input; } catch (Exception e) diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Output.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryOutput.cs similarity index 72% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Output.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryOutput.cs index d1b4b9df..d124c0a6 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Output.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryOutput.cs @@ -2,11 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; -using C2CS.Feature.BuildLibraryC.Domain; +using C2CS.Feature.BuildLibraryC.Data; +using C2CS.Foundation.UseCases; namespace C2CS.Feature.BuildLibraryC; -public class Output : UseCaseOutput +public class BuildLibraryOutput : UseCaseOutput { public ImmutableArray BuildTargetResults { get; } } diff --git a/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryUseCase.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryUseCase.cs new file mode 100644 index 00000000..cf5912aa --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryUseCase.cs @@ -0,0 +1,31 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation.UseCases; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.BuildLibraryC; + +public class BuildLibraryUseCase : UseCase +{ + public override string Name => "Build C library"; + + public BuildLibraryUseCase(ILogger logger, IServiceProvider services, BuildLibraryValidator validator) + : base(logger, services, validator) + { + } + + protected override void Execute(BuildLibraryInput input, BuildLibraryOutput output) + { + var targets = input.Project.Targets; + if (targets.IsDefaultOrEmpty) + { + return; + } + + foreach (var buildTarget in input.Project.Targets) + { + Console.WriteLine(buildTarget); + } + } +} diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Program.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryValidator.cs similarity index 50% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Program.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryValidator.cs index 4e1857f6..9722364f 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Program.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/BuildLibraryValidator.cs @@ -1,15 +1,14 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using C2CS.Foundation.UseCases; + namespace C2CS.Feature.BuildLibraryC; -public static class Program +public class BuildLibraryValidator : UseCaseValidator { - public static int Main(string[]? args = null) + public override BuildLibraryInput Validate(BuildLibraryConfiguration configuration) { - var input = Input.GetFrom(args); - var handler = new Handler(); - var output = handler.Execute(input); - return output.Status == UseCaseOutputStatus.Success ? 0 : 1; + throw new NotImplementedException(); } } diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj b/src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj similarity index 53% rename from src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj index 420c789c..08c76e90 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj @@ -5,14 +5,19 @@ net6.0 enable enable - Exe C2CS.Feature.BuildLibraryC $(NoWarn);CA1724 - - + + + + + + + + diff --git a/src/cs/production/C2CS.Common/C2CS.Common.csproj.DotSettings b/src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings similarity index 55% rename from src/cs/production/C2CS.Common/C2CS.Common.csproj.DotSettings rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings index 553cc259..159773b2 100644 --- a/src/cs/production/C2CS.Common/C2CS.Common.csproj.DotSettings +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings @@ -1,4 +1,5 @@  - True - True - True \ No newline at end of file + True + False + True + False \ No newline at end of file diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/BuildTargetData.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProject.cs similarity index 68% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Data/BuildTargetData.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProject.cs index 4748a819..b2d8f181 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/BuildTargetData.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProject.cs @@ -7,11 +7,9 @@ namespace C2CS.Feature.BuildLibraryC.Data; // NOTE: Properties are required for System.Text.Json serialization // NOTE: This class is considered un-sanitized input; all strings and other types could be null. -public class BuildTargetData +public abstract class BuildProject { - public string? OperatingSystem { get; set; } + public BuildProjectType Type { get; set; } - public ImmutableArray? TargetArchitectures { get; set; } - - public bool? IsEnabledCombineArchitectures { get; set; } + public ImmutableArray Targets { get; set; } } diff --git a/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectCMake.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectCMake.cs new file mode 100644 index 00000000..8aceee89 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectCMake.cs @@ -0,0 +1,16 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.BuildLibraryC.Data; + +// NOTE: Properties are required for System.Text.Json serialization +// NOTE: This class is considered un-sanitized input; all strings and other types could be null. +public class BuildProjectCMake : BuildProject +{ + public string CMakeListsDirectoryPath { get; set; } = string.Empty; + + public BuildProjectCMake() + { + Type = BuildProjectType.CMake; + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Output.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectType.cs similarity index 69% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Output.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectType.cs index 108d6d4b..90aca7c5 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Output.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildProjectType.cs @@ -1,8 +1,10 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.BindgenCSharp; +namespace C2CS.Feature.BuildLibraryC.Data; -public class Output : UseCaseOutput +public enum BuildProjectType { + Unknown = 0, + CMake } diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/InputData.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildSolution.cs similarity index 83% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Data/InputData.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildSolution.cs index 0f1c524b..6a79f9e4 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/InputData.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildSolution.cs @@ -7,7 +7,7 @@ namespace C2CS.Feature.BuildLibraryC.Data; // NOTE: Properties are required for System.Text.Json serialization // NOTE: This class is considered un-sanitized input; all strings and other types could be null. -public class InputData +public class BuildSolution { - public ImmutableArray? BuildTargets { get; set; } + public ImmutableArray Projects { get; set; } } diff --git a/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTarget.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTarget.cs new file mode 100644 index 00000000..cea1d744 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTarget.cs @@ -0,0 +1,19 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.BuildLibraryC.Data; + +// NOTE: Properties are required for System.Text.Json serialization +// NOTE: This class is considered un-sanitized input; all strings and other types could be null. +public class BuildTarget +{ + public NativeOperatingSystem OperatingSystem { get; set; } = NativeOperatingSystem.Unknown; + + public ImmutableArray TargetArchitectures { get; set; } = ImmutableArray.Empty; + + public bool IsEnabledCombineArchitectures { get; set; } + + public string OutputDirectoryPath { get; set; } = string.Empty; +} diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTargetResult.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTargetResult.cs similarity index 92% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTargetResult.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTargetResult.cs index 4403518d..b741e876 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Domain/BuildTargetResult.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/BuildTargetResult.cs @@ -1,7 +1,7 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.BuildLibraryC.Domain; +namespace C2CS.Feature.BuildLibraryC.Data; public class BuildTargetResult { diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/Serialization/InputDataSerializerContext.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/Serialization/JsonSerializerContext.cs similarity index 79% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Data/Serialization/InputDataSerializerContext.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/Serialization/JsonSerializerContext.cs index a3f676c2..9cefddef 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Data/Serialization/InputDataSerializerContext.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Data/Serialization/JsonSerializerContext.cs @@ -10,7 +10,7 @@ namespace C2CS.Feature.BuildLibraryC.Data.Serialization; DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, GenerationMode = JsonSourceGenerationMode.Metadata)] -[JsonSerializable(typeof(InputData))] -internal partial class InputDataSerializerContext : JsonSerializerContext +[JsonSerializable(typeof(BuildProject))] +internal partial class JsonSerializerContext : System.Text.Json.Serialization.JsonSerializerContext { } diff --git a/src/cs/production/features/C2CS.Feature.BuildLibraryC/Domain/CMake.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Domain/CMake.cs new file mode 100644 index 00000000..e74a55e6 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Domain/CMake.cs @@ -0,0 +1,148 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +// namespace C2CS.Feature.BuildLibraryC.Domain.Logic; +// +// public static class CMake +// { +// public static bool GenerateBuildFiles(string cMakeDirectoryPath, string libraryOutputDirectoryPath) +// { +// if (!Directory.Exists(cMakeDirectoryPath)) +// { +// throw new DirectoryNotFoundException(cMakeDirectoryPath); +// } +// +// var outPath = libraryOutputDirectoryPath.Replace("\\", "/", StringComparison.InvariantCulture); +// var shellCommand = +// $"cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={outPath} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY={outPath} -DCMAKE_RUNTIME_OUTPUT_DIRECTORY={outPath} -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE={outPath}"; +// +// var isSuccess = shellCommand.Shell(cMakeDirectoryPath, windowsUsePowerShell: false); +// if (!isSuccess) +// { +// return false; +// } +// +// isSuccess = "cmake --build cmake-build-release --config Release" +// .Shell(cMakeDirectoryPath, windowsUsePowerShell: false); +// if (!isSuccess) +// { +// return false; +// } +// +// var outputDirectoryPath = Path.Combine(cMakeDirectoryPath, "lib"); +// if (!Directory.Exists(outputDirectoryPath)) +// { +// return false; +// } +// +// var runtimePlatform = Platform.HostOperatingSystem; +// var libraryFileNameExtension = Platform.LibraryFileNameExtension(runtimePlatform); +// var outputFilePaths = Directory.EnumerateFiles( +// outputDirectoryPath, $"*{libraryFileNameExtension}", SearchOption.AllDirectories); +// foreach (var outputFilePath in outputFilePaths) +// { +// var targetFilePath = outputFilePath.Replace( +// outputDirectoryPath, libraryOutputDirectoryPath, StringComparison.InvariantCulture); +// var targetFileName = Path.GetFileName(targetFilePath); +// +// if (runtimePlatform == RuntimeOperatingSystem.Windows) +// { +// if (targetFileName.StartsWith("lib", StringComparison.InvariantCulture)) +// { +// targetFileName = targetFileName[3..]; +// } +// } +// +// var targetFileDirectoryPath = Path.GetDirectoryName(targetFilePath)!; +// targetFilePath = Path.Combine(targetFileDirectoryPath, targetFileName); +// if (!Directory.Exists(targetFileDirectoryPath)) +// { +// Directory.CreateDirectory(targetFileDirectoryPath); +// } +// +// if (File.Exists(targetFilePath)) +// { +// File.Delete(targetFilePath); +// } +// +// File.Copy(outputFilePath, targetFilePath); +// } +// +// Directory.Delete(outputDirectoryPath, true); +// Directory.Delete($"{cMakeDirectoryPath}/cmake-build-release", true); +// +// return true; +// } +// +// public static bool Build(string rootDirectory, string cMakeDirectoryPath, string libraryOutputDirectoryPath) +// { +// if (!Directory.Exists(rootDirectory)) +// { +// throw new DirectoryNotFoundException(cMakeDirectoryPath); +// } +// +// if (!Directory.Exists(cMakeDirectoryPath)) +// { +// throw new DirectoryNotFoundException(cMakeDirectoryPath); +// } +// +// var libraryOutputDirectoryPathNormalized = libraryOutputDirectoryPath.Replace("\\", "/", StringComparison.InvariantCulture); +// var isSuccess = $"cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={libraryOutputDirectoryPathNormalized} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY={libraryOutputDirectoryPathNormalized} -DCMAKE_RUNTIME_OUTPUT_DIRECTORY={libraryOutputDirectoryPathNormalized} -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE={libraryOutputDirectoryPathNormalized}" +// .Shell(cMakeDirectoryPath, windowsUsePowerShell: false); +// if (!isSuccess) +// { +// return false; +// } +// +// isSuccess = "cmake --build cmake-build-release --config Release" +// .Shell(cMakeDirectoryPath, windowsUsePowerShell: false); +// if (!isSuccess) +// { +// return false; +// } +// +// var outputDirectoryPath = Path.Combine(cMakeDirectoryPath, "lib"); +// if (!Directory.Exists(outputDirectoryPath)) +// { +// return false; +// } +// +// var runtimePlatform = Platform.HostOperatingSystem; +// var libraryFileNameExtension = Platform.LibraryFileNameExtension(runtimePlatform); +// var outputFilePaths = Directory.EnumerateFiles( +// outputDirectoryPath, $"*{libraryFileNameExtension}", SearchOption.AllDirectories); +// foreach (var outputFilePath in outputFilePaths) +// { +// var targetFilePath = outputFilePath.Replace( +// outputDirectoryPath, libraryOutputDirectoryPath, StringComparison.InvariantCulture); +// var targetFileName = Path.GetFileName(targetFilePath); +// +// if (runtimePlatform == RuntimeOperatingSystem.Windows) +// { +// if (targetFileName.StartsWith("lib", StringComparison.InvariantCulture)) +// { +// targetFileName = targetFileName[3..]; +// } +// } +// +// var targetFileDirectoryPath = Path.GetDirectoryName(targetFilePath)!; +// targetFilePath = Path.Combine(targetFileDirectoryPath, targetFileName); +// if (!Directory.Exists(targetFileDirectoryPath)) +// { +// Directory.CreateDirectory(targetFileDirectoryPath); +// } +// +// if (File.Exists(targetFilePath)) +// { +// File.Delete(targetFilePath); +// } +// +// File.Copy(outputFilePath, targetFilePath); +// } +// +// Directory.Delete(outputDirectoryPath, true); +// Directory.Delete($"{cMakeDirectoryPath}/cmake-build-release", true); +// +// return true; +// } +// } diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/Handler.cs b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Startup.cs similarity index 51% rename from src/cs/production/C2CS.Feature.BuildLibraryC/Handler.cs rename to src/cs/production/features/C2CS.Feature.BuildLibraryC/Startup.cs index be8686fc..f2e7f2a4 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/Handler.cs +++ b/src/cs/production/features/C2CS.Feature.BuildLibraryC/Startup.cs @@ -1,15 +1,15 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using Microsoft.Extensions.DependencyInjection; + namespace C2CS.Feature.BuildLibraryC; -public class Handler : UseCaseHandler +public static class Startup { - protected override void Execute(Input input, Output output) + public static void ConfigureServices(IServiceCollection services) { - foreach (var buildTarget in input.BuildTargets) - { - Console.WriteLine(buildTarget); - } + services.AddSingleton(); + services.AddSingleton(); } } diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj b/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj new file mode 100644 index 00000000..b87bbaba --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj @@ -0,0 +1,23 @@ + + + + + net6.0 + enable + enable + $(NoWarn);CA1724 + + + + + + + + + + + + + + + diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj.DotSettings b/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj.DotSettings new file mode 100644 index 00000000..39563875 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/C2CS.Feature.ReadCodeC.csproj.DotSettings @@ -0,0 +1,11 @@ + + False + False + False + False + False + True + False + False + True + True \ No newline at end of file diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CAbstractSyntaxTree.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CAbstractSyntaxTree.cs similarity index 85% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CAbstractSyntaxTree.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CAbstractSyntaxTree.cs index d48f3e52..f47e8708 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CAbstractSyntaxTree.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CAbstractSyntaxTree.cs @@ -3,19 +3,17 @@ using System.Collections.Immutable; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] public record CAbstractSyntaxTree { [JsonPropertyName("fileName")] public string FileName { get; set; } = string.Empty; - [JsonPropertyName("bitness")] - public int Bitness { get; set; } + [JsonPropertyName("platform")] + public TargetPlatform Platform { get; set; } = TargetPlatform.Unknown; [JsonPropertyName("functions")] public ImmutableArray Functions { get; set; } = ImmutableArray.Empty; @@ -29,9 +27,6 @@ public record CAbstractSyntaxTree [JsonPropertyName("enums")] public ImmutableArray Enums { get; set; } = ImmutableArray.Empty; - [JsonPropertyName("pseudoEnums")] - public ImmutableArray PseudoEnums { get; set; } = ImmutableArray.Empty; - [JsonPropertyName("opaqueTypes")] public ImmutableArray OpaqueTypes { get; set; } = ImmutableArray.Empty; diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnum.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnum.cs similarity index 79% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnum.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnum.cs index c8c31bf4..64b5ba50 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnum.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnum.cs @@ -2,14 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CEnum : CNode +public record CEnum : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -23,8 +22,9 @@ public record CEnum : CNode [JsonPropertyName("values")] public ImmutableArray Values { get; set; } = ImmutableArray.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"Enum '{Type}': {IntegerType} @ {Location.ToString()}"; + return $"Enum '{Type}': {IntegerType} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnumValue.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnumValue.cs similarity index 76% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnumValue.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnumValue.cs index b681366a..0aa528a1 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CEnumValue.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CEnumValue.cs @@ -1,13 +1,12 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] public record CEnumValue : CNode { [JsonPropertyName("name")] @@ -16,8 +15,9 @@ public record CEnumValue : CNode [JsonPropertyName("value")] public long Value { get; set; } + [ExcludeFromCodeCoverage] public override string ToString() { - return $"EnumValue '{Name}' = {Value} @ {Location.ToString()}"; + return $"EnumValue '{Name}' = {Value}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunction.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunction.cs similarity index 81% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunction.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunction.cs index 913093b6..ea6d27ef 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunction.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunction.cs @@ -2,14 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CFunction : CNode +public record CFunction : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -23,8 +22,9 @@ public record CFunction : CNode [JsonPropertyName("parameters")] public ImmutableArray Parameters { get; set; } = ImmutableArray.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"FunctionExtern '{Name}' @ {Location.ToString()}"; + return $"FunctionExtern '{Name}' @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionCallingConvention.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionCallingConvention.cs similarity index 83% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionCallingConvention.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionCallingConvention.cs index af72063a..2bb3bb71 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionCallingConvention.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionCallingConvention.cs @@ -1,7 +1,7 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; public enum CFunctionCallingConvention { diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionParameter.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionParameter.cs similarity index 78% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionParameter.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionParameter.cs index cd264878..49ae1d02 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionParameter.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionParameter.cs @@ -1,14 +1,13 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CFunctionParameter : CNode +public record CFunctionParameter : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -16,8 +15,9 @@ public record CFunctionParameter : CNode [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"FunctionExternParameter '{Name}': {Type} @ {Location.ToString()}"; + return $"FunctionExternParameter '{Name}': {Type} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointer.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointer.cs similarity index 80% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointer.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointer.cs index 41d43e96..435427e5 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointer.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointer.cs @@ -2,14 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CFunctionPointer : CNode +public record CFunctionPointer : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -24,8 +23,9 @@ public record CFunctionPointer : CNode public ImmutableArray Parameters { get; set; } = ImmutableArray.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"FunctionPointer {Type} @ {Location.ToString()}"; + return $"FunctionPointer {Type} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointerParameter.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointerParameter.cs similarity index 77% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointerParameter.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointerParameter.cs index 0c6cf945..7b17037f 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CFunctionPointerParameter.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CFunctionPointerParameter.cs @@ -1,14 +1,13 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CFunctionPointerParameter : CNode +public record CFunctionPointerParameter : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -16,8 +15,9 @@ public record CFunctionPointerParameter : CNode [JsonPropertyName("type")] public string Type { get; set; } = null!; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"FunctionPointerParameter '{Name}': {Type} @ {Location.ToString()}"; + return $"FunctionPointerParameter '{Name}': {Type} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CKind.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CKind.cs similarity index 88% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CKind.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CKind.cs index 84acd65f..8ad7e05d 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CKind.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CKind.cs @@ -1,7 +1,7 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; public enum CKind { diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CLocation.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CLocation.cs new file mode 100644 index 00000000..2df66ba4 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CLocation.cs @@ -0,0 +1,95 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Text.Json.Serialization; + +namespace C2CS.Feature.ReadCodeC.Data; + +// NOTE: Properties are required for System.Text.Json serialization + +public record struct CLocation : IComparable +{ +#pragma warning disable CA2211 + public static CLocation System = new() + { + IsSystem = true + }; +#pragma warning restore CA2211 + + [JsonPropertyName("fileName")] + public string FileName { get; set; } + + [JsonPropertyName("filePath")] + public string FilePath { get; set; } + + [JsonPropertyName("line")] + public int LineNumber { get; set; } + + [JsonPropertyName("column")] + public int LineColumn { get; set; } + + [JsonIgnore] + public bool IsSystem { get; set; } + + public override string ToString() + { +#pragma warning disable CA1308 + if (IsSystem) + { + return nameof(System); + } +#pragma warning restore CA1308 + + if (LineNumber == 0 && LineColumn == 0) + { + return $"{FileName}"; + } + + return string.IsNullOrEmpty(FilePath) || FilePath == FileName + ? $"{FileName}:{LineNumber}:{LineColumn}" + : $"{FileName}:{LineNumber}:{LineColumn} ({FilePath})"; + } + + public int CompareTo(CLocation other) + { + var result = string.Compare(FileName, other.FileName, StringComparison.Ordinal); + if (result != 0) + { + return result; + } + + result = LineNumber.CompareTo(other.LineNumber); + if (result != 0) + { + return result; + } + + result = LineColumn.CompareTo(other.LineColumn); + if (result != 0) + { + return result; + } + + return result; + } + + public static bool operator <(CLocation first, CLocation second) + { + return first.CompareTo(second) < 0; + } + + public static bool operator >(CLocation first, CLocation second) + { + return first.CompareTo(second) > 0; + } + + public static bool operator >=(CLocation first, CLocation second) + { + return first.CompareTo(second) >= 0; + } + + public static bool operator <=(CLocation first, CLocation second) + { + return first.CompareTo(second) <= 0; + } +} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CMacroDefinition.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CMacroDefinition.cs similarity index 72% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CMacroDefinition.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CMacroDefinition.cs index 65458ba1..64d2f835 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CMacroDefinition.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CMacroDefinition.cs @@ -2,11 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; -public record CMacroDefinition : CNode +public record CMacroDefinition : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -14,8 +15,9 @@ public record CMacroDefinition : CNode [JsonPropertyName("tokens")] public ImmutableArray Tokens { get; set; } = ImmutableArray.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"Macro '{Name}' @ {Location.ToString()}"; + return $"Macro '{Name}' @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CNode.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNode.cs similarity index 85% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CNode.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNode.cs index d9e06735..0abaec15 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CNode.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNode.cs @@ -2,17 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CNode : IComparable +public abstract record CNode : IComparable { - [JsonPropertyName("location")] - public ClangLocation Location { get; set; } - [JsonIgnore] public CKind Kind => GetKind(); @@ -28,10 +23,15 @@ public int CompareTo(CNode? other) return 1; } - var result = Location.CompareTo(other.Location); + var result = CompareToInternal(other); return result; } + protected virtual int CompareToInternal(CNode? other) + { + return 0; + } + private CKind GetKind() { return this switch diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNodeWithLocation.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNodeWithLocation.cs new file mode 100644 index 00000000..1495dd8d --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CNodeWithLocation.cs @@ -0,0 +1,24 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Text.Json.Serialization; +using C2CS.Feature.ReadCodeC.Data.Serialization; + +namespace C2CS.Feature.ReadCodeC.Data; + +public abstract record CNodeWithLocation : CNode +{ + [JsonPropertyName("location")] + [JsonConverter(typeof(CLocationJsonConverter))] + public CLocation Location { get; set; } + + protected override int CompareToInternal(CNode? other) + { + if (other is not CNodeWithLocation other2) + { + return base.CompareToInternal(other); + } + + return Location.CompareTo(other2.Location); + } +} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/COpaqueType.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/COpaqueType.cs similarity index 65% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/COpaqueType.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/COpaqueType.cs index 0a703c7a..b5d8dd1a 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/COpaqueType.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/COpaqueType.cs @@ -1,17 +1,19 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; -public record COpaqueType : CNode +public record COpaqueType : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"OpaqueType '{Name}' @ {Location.ToString()}"; + return $"OpaqueType '{Name}' @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecord.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecord.cs similarity index 81% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecord.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecord.cs index d72dbbde..dd64ee52 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecord.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecord.cs @@ -2,14 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CRecord : CNode +public record CRecord : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -23,9 +22,10 @@ public record CRecord : CNode [JsonPropertyName("nestedRecords")] public ImmutableArray NestedRecords { get; set; } = ImmutableArray.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { var kind = IsUnion ? "Union" : "Struct"; - return $"{kind} {Name} @ {Location.ToString()}"; + return $"{kind} {Name} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecordField.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecordField.cs similarity index 76% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecordField.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecordField.cs index 6fb40ec1..1c8499db 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CRecordField.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CRecordField.cs @@ -1,14 +1,13 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CRecordField : CNode +public record CRecordField : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -22,8 +21,9 @@ public record CRecordField : CNode [JsonPropertyName("padding")] public int Padding { get; set; } + [ExcludeFromCodeCoverage] public override string ToString() { - return $"RecordField '{Name}': {Type} @ {Location.ToString()}"; + return $"RecordField '{Name}': {Type} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CType.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CType.cs similarity index 75% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CType.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CType.cs index 7fdea27d..9a62b074 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CType.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CType.cs @@ -1,13 +1,13 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; +using C2CS.Feature.ReadCodeC.Data.Serialization; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] public class CType { [JsonPropertyName("name")] @@ -28,15 +28,11 @@ public class CType [JsonPropertyName("arraySize")] public int? ArraySize { get; set; } - [JsonPropertyName("isSystem")] - public bool IsSystem { get; set; } - - [JsonPropertyName("isAnonymous")] - public bool IsAnonymous { get; set; } - [JsonPropertyName("location")] - public ClangLocation? Location { get; set; } + [JsonConverter(typeof(CLocationJsonConverter))] + public CLocation Location { get; set; } + [ExcludeFromCodeCoverage] public override string ToString() { return Name; diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CTypedef.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CTypedef.cs similarity index 72% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CTypedef.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CTypedef.cs index b4c459d2..7dd492da 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CTypedef.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CTypedef.cs @@ -1,14 +1,13 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public record CTypedef : CNode +public record CTypedef : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -16,8 +15,9 @@ public record CTypedef : CNode [JsonPropertyName("underlyingType")] public string UnderlyingType { get; set; } = string.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"Record '{Name}': {UnderlyingType} @ {Location.ToString()}"; + return $"Record '{Name}': {UnderlyingType} @ {Location}"; } } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CVariable.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CVariable.cs similarity index 69% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CVariable.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CVariable.cs index 68507c88..9c350e3a 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/CVariable.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/CVariable.cs @@ -1,11 +1,12 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +namespace C2CS.Feature.ReadCodeC.Data; -public record CVariable : CNode +public record CVariable : CNodeWithLocation { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -13,8 +14,9 @@ public record CVariable : CNode [JsonPropertyName("type")] public string Type { get; set; } = string.Empty; + [ExcludeFromCodeCoverage] public override string ToString() { - return $"Variable '{Name}': {Type} @ {Location.ToString()}"; + return $"Variable '{Name}': {Type} @ {Location}"; } } diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfiguration.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfiguration.cs new file mode 100644 index 00000000..22842736 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfiguration.cs @@ -0,0 +1,25 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Text.Json.Serialization; +using C2CS.Foundation.UseCases; +using JetBrains.Annotations; + +namespace C2CS.Feature.ReadCodeC.Data; + +// NOTE: Properties are required for System.Text.Json serialization +// NOTE: This class is considered un-sanitized input; all strings and other types could be null. +// NOTE: This class must have a unique name across namespaces for usage in System.Text.Json source generators. +[PublicAPI] +public sealed class ReadCodeCConfiguration : UseCaseConfiguration +{ + [JsonPropertyName("input_file")] + [Json.Schema.Generation.Description("Path of the input `.h` header file containing C code.")] + public string? InputFilePath { get; set; } + + [JsonPropertyName("platforms")] + [Json.Schema.Generation.Description("The target platform configurations for extracting the abstract syntax trees. Each target platform is a Clang target triple. See the C2CS docs for more details about what target platforms are available.")] +#pragma warning disable CA2227 + public Dictionary? ConfigurationAbstractSyntaxTrees { get; set; } +#pragma warning restore CA2227 +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfigurationAbstractSyntaxTree.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfigurationAbstractSyntaxTree.cs new file mode 100644 index 00000000..0eb10105 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/ReadCodeCConfigurationAbstractSyntaxTree.cs @@ -0,0 +1,43 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace C2CS.Feature.ReadCodeC.Data; + +[PublicAPI] +public sealed class ReadCodeCConfigurationAbstractSyntaxTree +{ + [JsonIgnore] + public string? OutputFileDirectory { get; set; } + + [JsonPropertyName("find_system_headers")] + [Json.Schema.Generation.Description("Determines whether system C/C++ headers are attempted to be found and passed to Clang. Default is `true`.")] + public bool? IsEnabledFindSystemHeaders { get; set; } = true; + + [JsonPropertyName("include")] + [Json.Schema.Generation.Description("Search directory paths to use for `#include` usages when parsing C code.")] + public ImmutableArray? IncludeDirectories { get; set; } + + [JsonPropertyName("defines")] + [Json.Schema.Generation.Description("Object-like macros to use when parsing C code.")] + public ImmutableArray? Defines { get; set; } + + [JsonPropertyName("exclude")] + [Json.Schema.Generation.Description("C header file names to exclude. File names are relative to the `IncludeDirectories` property.")] + public ImmutableArray? ExcludedHeaderFiles { get; set; } + + [JsonPropertyName("function_names")] + [Json.Schema.Generation.Description("The C function names to explicitly include when parsing C code. Default is `null`. If `null<`, no white list applies. Note that C function names which are excluded also exclude any transitive types.")] + public ImmutableArray? FunctionNamesWhiteList { get; set; } + + [JsonPropertyName("opaque_names")] + [Json.Schema.Generation.Description("Type names that may be found when parsing C code that will be interpreted as opaque types. Opaque types are often used with a pointer to hide the information about the bit layout behind the pointer.")] + public ImmutableArray? OpaqueTypeNames { get; set; } + + [Json.Schema.Generation.Description("Additional Clang arguments to use when parsing C code.")] + [JsonPropertyName("clang_arguments")] + public ImmutableArray? ClangArguments { get; set; } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializer.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializer.cs new file mode 100644 index 00000000..cef45be6 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializer.cs @@ -0,0 +1,94 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.IO.Abstractions; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Data.Serialization; + +public class CJsonSerializer +{ + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly CJsonSerializerContext _context; + + public CJsonSerializer(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + + var serializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }; + _context = new CJsonSerializerContext(serializerOptions); + } + + public CAbstractSyntaxTree Read(string filePath) + { + CAbstractSyntaxTree result; + + var fullFilePath = _fileSystem.Path.GetFullPath(filePath); + + try + { + var fileContents = _fileSystem.File.ReadAllText(fullFilePath); + result = JsonSerializer.Deserialize(fileContents, _context.CAbstractSyntaxTree)!; + _logger.ReadAbstractSyntaxTreeCSuccess(fullFilePath); + } + catch (Exception e) + { + _logger.ReadAbstractSyntaxTreeCFailure(fullFilePath, e); + throw; + } + + return result; + } + + public void Write(CAbstractSyntaxTree abstractSyntaxTree, string filePath) + { + var fullFilePath = _fileSystem.Path.GetFullPath(filePath); + + var outputDirectory = _fileSystem.Path.GetDirectoryName(fullFilePath)!; + if (string.IsNullOrEmpty(outputDirectory)) + { + outputDirectory = AppContext.BaseDirectory; + fullFilePath = Path.Combine(Environment.CurrentDirectory, fullFilePath); + } + + try + { + if (!_fileSystem.Directory.Exists(outputDirectory)) + { + _fileSystem.Directory.CreateDirectory(outputDirectory); + } + + if (_fileSystem.File.Exists(fullFilePath)) + { + _fileSystem.File.Delete(fullFilePath); + } + + var fileContents = JsonSerializer.Serialize(abstractSyntaxTree, _context.Options); + + using var fileStream = _fileSystem.File.OpenWrite(fullFilePath); + using var textWriter = new StreamWriter(fileStream); + textWriter.Write(fileContents); + textWriter.Close(); + fileStream.Close(); + + _logger.WriteAbstractSyntaxTreeCSuccess(fullFilePath); + } + catch (Exception e) + { + _logger.WriteAbstractSyntaxTreeCFailure(fullFilePath, e); + throw; + } + } +} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/Serialization/CJsonSerializerContext.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializerContext.cs similarity index 89% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/Serialization/CJsonSerializerContext.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializerContext.cs index b8186fbe..82033211 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Data/Serialization/CJsonSerializerContext.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CJsonSerializerContext.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Data.Serialization; +namespace C2CS.Feature.ReadCodeC.Data.Serialization; [JsonSourceGenerationOptions( WriteIndented = true, diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CLocationJsonConverter.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CLocationJsonConverter.cs new file mode 100644 index 00000000..9c427ef1 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/CLocationJsonConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace C2CS.Feature.ReadCodeC.Data.Serialization; + +#pragma warning disable CA1308 + +public class CLocationJsonConverter : JsonConverter +{ + public override CLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + return CLocation.System; + } + + public override void Write(Utf8JsonWriter writer, CLocation value, JsonSerializerOptions options) + { + if (value.IsSystem) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, value, options); + } + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/Logging.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/Logging.cs new file mode 100644 index 00000000..21c61ffa --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Data/Serialization/Logging.cs @@ -0,0 +1,55 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Data.Serialization; + +internal static class Logging +{ + private static readonly Action ActionReadAbstractSyntaxTreeSuccess = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Serialize abstract syntax tree C: success."), + "- Read abstract syntax tree C: Success. Path: {FilePath}"); + + private static readonly Action ActionReadAbstractSyntaxTreeFailure = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Serialize abstract syntax tree C: failure."), + "- Read abstract syntax tree C. Failed. Path: {FilePath}."); + + private static readonly Action ActionWriteAbstractSyntaxTreeSuccess = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Write abstract syntax tree C: success."), + "- Write abstract syntax tree C: Success. Path: {FilePath}"); + + private static readonly Action ActionWriteAbstractSyntaxTreeFailure = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Write abstract syntax tree C: failure."), + "- Write abstract syntax tree C. Failed. Path: {FilePath}."); + + public static void ReadAbstractSyntaxTreeCSuccess(this ILogger logger, string filePath) + { + ActionReadAbstractSyntaxTreeSuccess(logger, filePath, null!); + } + + public static void ReadAbstractSyntaxTreeCFailure(this ILogger logger, string filePath, Exception exception) + { + ActionReadAbstractSyntaxTreeFailure(logger, filePath, exception); + } + + public static void WriteAbstractSyntaxTreeCSuccess(this ILogger logger, string filePath) + { + ActionWriteAbstractSyntaxTreeSuccess(logger, filePath, null!); + } + + public static void WriteAbstractSyntaxTreeCFailure(this ILogger logger, string filePath, Exception exception) + { + ActionWriteAbstractSyntaxTreeFailure(logger, filePath, exception); + } +} diff --git a/src/cs/production/clang-cs/ClangException.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangException.cs similarity index 83% rename from src/cs/production/clang-cs/ClangException.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangException.cs index 5877f1a2..ee3cff5e 100644 --- a/src/cs/production/clang-cs/ClangException.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangException.cs @@ -1,9 +1,9 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using System; +namespace C2CS.Feature.ReadCodeC.Domain; -public class ClangException : Exception +public sealed class ClangException : Exception { public ClangException() { diff --git a/src/cs/production/clang-cs/ClangExtensions.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangExtensions.cs similarity index 87% rename from src/cs/production/clang-cs/ClangExtensions.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangExtensions.cs index 93a29a01..c980caf2 100644 --- a/src/cs/production/clang-cs/ClangExtensions.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ClangExtensions.cs @@ -1,14 +1,14 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Runtime.InteropServices; -using System.Threading; +using C2CS.Feature.ReadCodeC.Data; using static bottlenoselabs.clang; +namespace C2CS.Feature.ReadCodeC.Domain; + public static unsafe class ClangExtensions { public delegate bool VisitPredicate(CXCursor child, CXCursor parent); @@ -262,56 +262,18 @@ private static string NameInternal(this CXType clangType) return result; } - public static ClangLocation FileLocation(this CXType type, CXCursor cursor) + public static CLocation FileLocation(this CXType type, CXCursor cursor) { - // The cursor type could be a pointer, which if so, we need to drill down to the pointee type - // if we don't, then the location will be the declaration of where the pointer is used which is not - // probably the expected outcome - // however... function pointers are a special case to which they don't have a declaration unless they are - // from a typedef - CXCursor declaration; - - if (type.kind == CXTypeKind.CXType_Attributed) - { - type = clang_Type_getModifiedType(type); - } - - if (type.kind == CXTypeKind.CXType_Pointer) - { - type = clang_getPointeeType(type); - } - - if (type.kind == CXTypeKind.CXType_FunctionProto || - type.kind == CXTypeKind.CXType_FunctionNoProto) - { - declaration = cursor; - } - else - { - while (type.kind == CXTypeKind.CXType_Pointer) - { - type = clang_getPointeeType(type); - } - - if (type.IsPrimitive()) - { - declaration = cursor; - } - else - { - declaration = clang_getTypeDeclaration(type); - } - } - + var declaration = type.IsPrimitive() ? cursor : clang_getTypeDeclaration(type); return FileLocation(declaration); } - public static ClangLocation FileLocation(this CXCursor cursor) + public static CLocation FileLocation(this CXCursor cursor) { if (cursor.kind == CXCursorKind.CXCursor_TranslationUnit) { var filePath = cursor.Name(); - return new ClangLocation + return new CLocation { FileName = Path.GetFileName(filePath), FilePath = filePath @@ -326,10 +288,22 @@ public static ClangLocation FileLocation(this CXCursor cursor) clang_getFileLocation(location, &file, &lineNumber, &columnNumber, &offset); + var isInSystemHeader = clang_Location_isInSystemHeader(location) > 0U; + if (isInSystemHeader) + { + return CLocation.System; + } + + var isPrimitive = clang_getCursorType(cursor).IsPrimitive(); + if (isPrimitive) + { + return CLocation.System; + } + var handle = (IntPtr)file.Data; if (handle == IntPtr.Zero) { - return new ClangLocation + return new CLocation { FileName = string.Empty }; @@ -338,13 +312,12 @@ public static ClangLocation FileLocation(this CXCursor cursor) var fileName = clang_getFileName(file); string fileNamePath = clang_getCString(fileName); - return new ClangLocation + return new CLocation { FileName = Path.GetFileName(fileNamePath), FilePath = string.IsNullOrEmpty(fileNamePath) ? string.Empty : Path.GetFullPath(fileNamePath), LineNumber = (int)lineNumber, - LineColumn = (int)columnNumber, - IsBuiltin = clang_getCursorType(cursor).IsPrimitive() + LineColumn = (int)columnNumber }; } diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Logic/CTranslationUnitExplorer.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorer.cs similarity index 65% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Logic/CTranslationUnitExplorer.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorer.cs index 3880604d..3f938a97 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Logic/CTranslationUnitExplorer.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorer.cs @@ -3,279 +3,152 @@ using System.Collections.Immutable; using System.Diagnostics; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Diagnostics; -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Domain; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.ReadCodeC.Domain.ExploreCode.Diagnostics; +using C2CS.Foundation.UseCases.Exceptions; +using Microsoft.Extensions.Logging; using static bottlenoselabs.clang; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Logic; +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode; -public class CTranslationUnitExplorer +public sealed class ClangTranslationUnitExplorer { - private readonly DiagnosticsSink _diagnostics; - private readonly List _enums = new(); - private readonly ArrayDeque _frontierGeneral = new(); - private readonly ArrayDeque _frontierMacros = new(); - private readonly List _functionPointers = new(); - private readonly List _functions = new(); - private readonly ImmutableHashSet _ignoredFiles; - - private readonly HashSet _ignoredMacroTokens = new() - { - // print conversion specifiers - "PRId8", - "PRId16", - "PRId32", - "PRId64", - "PRIdFAST8", - "PRIdFAST16", - "PRIdFAST32", - "PRIdFAST64", - "PRIdLEAST8", - "PRIdLEAST16", - "PRIdLEAST32", - "PRIdLEAST64", - "PRIdMAX", - "PRIdPTR", - "PRIi8", - "PRIi16", - "PRIi32", - "PRIi64", - "PRIiFAST8", - "PRIiFAST16", - "PRIiFAST32", - "PRIiFAST64", - "PRIiLEAST8", - "PRIiLEAST16", - "PRIiLEAST32", - "PRIiLEAST64", - "PRIiMAX", - "PRIiPTR", - "PRIo8", - "PRIo16", - "PRIo32", - "PRIo64", - "PRIoFAST8", - "PRIoFAST16", - "PRIoFAST32", - "PRIoFAST64", - "PRIoLEAST8", - "PRIoLEAST16", - "PRIoLEAST32", - "PRIoLEAST64", - "PRIoMAX", - "PRIoPTR", - "PRIu8", - "PRIu16", - "PRIu32", - "PRIu64", - "PRIuFAST8", - "PRIuFAST16", - "PRIuFAST32", - "PRIuFAST64", - "PRIuLEAST8", - "PRIuLEAST16", - "PRIuLEAST32", - "PRIuLEAST64", - "PRIuMAX", - "PRIuPTR", - "PRIx8", - "PRIx16", - "PRIx32", - "PRIx64", - "PRIxFAST8", - "PRIxFAST16", - "PRIxFAST32", - "PRIxFAST64", - "PRIxLEAST8", - "PRIxLEAST16", - "PRIxLEAST32", - "PRIxLEAST64", - "PRIxMAX", - "PRIxPTR", - "PRIX8", - "PRIX16", - "PRIX32", - "PRIX64", - "PRIXFAST8", - "PRIXFAST16", - "PRIXFAST32", - "PRIXFAST64", - "PRIXLEAST8", - "PRIXLEAST16", - "PRIXLEAST32", - "PRIXLEAST64", - "PRIXMAX", - "PRIXPTR" - }; - - private readonly ImmutableArray _includeDirectories; - private readonly ImmutableHashSet _functionNamesWhitelist; - private readonly HashSet _macroFunctionLikeNames = new(); - - private readonly List _macroObjects = new(); - - private readonly HashSet _names = new(); - private readonly List _opaqueDataTypes = new(); - private readonly ImmutableHashSet _opaqueTypeNames; - private readonly List _pseudoEnums = new(); - private readonly List _records = new(); - - private readonly HashSet _systemIgnoredTypeNames = new() - { - "FILE", - "DIR", - "size_t", - "ssize_t", - "int8_t", - "uint8_t", - "int16_t", - "uint16_t", - "int32_t", - "uint32_t", - "int64_t", - "uint64_t", - "uintptr_t", - "intptr_t", - "va_list" - }; - - private readonly List _typedefs = new(); - private readonly List _types = new(); - private readonly Dictionary _typesByName = new(); - private readonly Dictionary _validTypeNames = new(); - private readonly List _variables = new(); - - public CTranslationUnitExplorer( - DiagnosticsSink diagnostics, - ImmutableArray includeDirectories, - ImmutableArray ignoredFiles, - ImmutableArray opaqueTypes, - ImmutableArray functionNamesWhitelist) + private readonly ILogger _logger; + + public ClangTranslationUnitExplorer(ILogger logger) { - _diagnostics = diagnostics; - _ignoredFiles = ignoredFiles.ToImmutableHashSet(); - _includeDirectories = includeDirectories; - _opaqueTypeNames = opaqueTypes.ToImmutableHashSet(); - _functionNamesWhitelist = functionNamesWhitelist.ToImmutableHashSet(); + _logger = logger; } - public CAbstractSyntaxTree AbstractSyntaxTree(CXTranslationUnit translationUnit, int bitness) + public CAbstractSyntaxTree AbstractSyntaxTree( + ClangTranslationUnitExplorerContext context, CXTranslationUnit translationUnit) { - VisitTranslationUnit(translationUnit); - Explore(); + CAbstractSyntaxTree result; - var cursor = clang_getTranslationUnitCursor(translationUnit); - var location = Location(cursor); - - var functions = _functions.ToImmutableArray(); - var functionPointers = _functionPointers.ToImmutableArray(); - var records = _records.ToImmutableArray(); - var enums = _enums.ToImmutableArray(); - var opaqueTypes = _opaqueDataTypes.ToImmutableArray(); - var typedefs = _typedefs.ToImmutableArray(); - var variables = _variables.ToImmutableArray(); - var constants = _macroObjects.ToImmutableArray(); - - var pseudoEnums = new List(); - var enumNames = _enums.Select(x => x.Name).ToImmutableHashSet(); - foreach (var pseudoEnum in _pseudoEnums) - { - if (!enumNames.Contains(pseudoEnum.Name)) - { - pseudoEnums.Add(pseudoEnum); - } + try + { + VisitTranslationUnit(context, translationUnit); + Explore(context); + result = Result(context, translationUnit); + _logger.ExploreCodeSuccess(); } + catch (Exception e) + { + _logger.ExploreCodeFailed(e); + throw; + } + + return result; + } + + private CAbstractSyntaxTree Result( + ClangTranslationUnitExplorerContext context, + CXTranslationUnit translationUnit) + { + var cursor = clang_getTranslationUnitCursor(translationUnit); + var location = Location(context, cursor); + + var functions = context.Functions.ToImmutableArray(); + var functionPointers = context.FunctionPointers.ToImmutableArray(); + var records = context.Records.ToImmutableArray(); + var enums = context.Enums.ToImmutableArray(); + var opaqueTypes = context.OpaqueDataTypes.ToImmutableArray(); + var typedefs = context.Typedefs.ToImmutableArray(); + var variables = context.Variables.ToImmutableArray(); + var constants = context.MacroObjects.ToImmutableArray(); - return new CAbstractSyntaxTree + var result = new CAbstractSyntaxTree { FileName = location.FileName, - Bitness = bitness, + Platform = context.TargetPlatform, Functions = functions, FunctionPointers = functionPointers, Records = records, Enums = enums, - PseudoEnums = pseudoEnums.ToImmutableArray(), OpaqueTypes = opaqueTypes, Typedefs = typedefs, Variables = variables, - Types = _types.ToImmutableArray(), + Types = context.Types.ToImmutableArray(), Constants = constants }; + + return result; } - private void VisitTranslationUnit(CXTranslationUnit translationUnit) + private void VisitTranslationUnit(ClangTranslationUnitExplorerContext context, CXTranslationUnit translationUnit) { var cursor = clang_getTranslationUnitCursor(translationUnit); var type = clang_getCursorType(cursor); - var location = Location(cursor); + var location = Location(context, cursor); + + _logger.ExploreCodeTranslationUnit(location.FileName); AddExplorerNode( + context, CKind.TranslationUnit, location, null, cursor, type, - type, string.Empty, string.Empty); } - private void Explore() + private void Explore(ClangTranslationUnitExplorerContext context) { - while (_frontierGeneral.Count > 0) + while (context.FrontierGeneral.Count > 0) { - var node = _frontierGeneral.PopFront()!; - ExploreNode(node); + var node = context.FrontierGeneral.PopFront()!; + ExploreNode(context, node); } - while (_frontierMacros.Count > 0) + while (context.FrontierMacros.Count > 0) { - var node = _frontierMacros.PopFront()!; - ExploreNode(node); + var node = context.FrontierMacros.PopFront()!; + ExploreNode(context, node); } } - private void ExploreNode(ClangExplorerNode node) + private void ExploreNode(ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (node.Kind) { case CKind.TranslationUnit: - ExploreTranslationUnit(node); + ExploreTranslationUnit(context, node); break; case CKind.Variable: - ExploreVariable(node.Name!, node.TypeName!, node.Cursor, node.Type, node.Location, node.Parent!); + ExploreVariable(context, node.Name!, node.TypeName!, node.Cursor, node.Type, node.Location, node.Parent!); break; case CKind.Function: - ExploreFunction(node.Name!, node.Cursor, node.Type, node.Location, node.Parent!); + ExploreFunction(context, node.Name!, node.Cursor, node.Type, node.Location, node.Parent!); break; case CKind.Typedef: - ExploreTypedef(node, node.Parent!); + ExploreTypedef(context, node, node.Parent!); break; case CKind.OpaqueType: - ExploreOpaqueType(node.TypeName!, node.Location); + ExploreOpaqueType(context, node.TypeName!, node.Location); break; case CKind.Enum: - ExploreEnum(node.TypeName!, node.Cursor, node.Type, node.Location, node.Parent!); + ExploreEnum(context, node.TypeName!, node.Cursor, node.Type, node.Location, node.Parent!); break; case CKind.Record: - ExploreRecord(node, node.Parent!); + ExploreRecord(context, node, node.Parent!); break; case CKind.FunctionPointer: ExploreFunctionPointer( - node.TypeName!, node.Cursor, node.Type, node.OriginalType, node.Location, node.Parent!); + context, node.TypeName!, node.Cursor, node.Type, node.Location, node.Parent!); break; case CKind.Array: - ExploreArray(node); + VisitArray(context, node); break; case CKind.Pointer: - ExplorePointer(node); + ExplorePointer(context, node); break; case CKind.Primitive: break; case CKind.MacroDefinition: - ExploreMacro(node); + ExploreMacro(context, node); break; default: var up = new UseCaseException($"Unexpected explorer node '{node.Kind}'."); @@ -283,7 +156,7 @@ private void ExploreNode(ClangExplorerNode node) } } - private bool IsIgnored(CXType type, CXCursor cursor) + private bool IsIgnored(ClangTranslationUnitExplorerContext context, CXType type, CXCursor cursor) { if (cursor.kind == CXCursorKind.CXCursor_TranslationUnit) { @@ -301,10 +174,10 @@ private bool IsIgnored(CXType type, CXCursor cursor) if (kind == CKind.Array) { var elementType = clang_getElementType(actualType); - return IsIgnored(elementType, cursor); + return IsIgnored(context, elementType, cursor); } - var fileLocation = kind == CKind.MacroDefinition ? Location(cursor) : Location(cursor, actualType); + var fileLocation = kind == CKind.MacroDefinition ? Location(context, cursor) : Location(context, cursor, actualType); if (string.IsNullOrEmpty(fileLocation.FileName)) { var up = new UseCaseException( @@ -312,7 +185,7 @@ private bool IsIgnored(CXType type, CXCursor cursor) throw up; } - foreach (var includeDirectory in _includeDirectories) + foreach (var includeDirectory in context.IncludeDirectories) { if (!fileLocation.FileName.Contains(includeDirectory, StringComparison.InvariantCulture)) { @@ -324,15 +197,16 @@ private bool IsIgnored(CXType type, CXCursor cursor) break; } - return _ignoredFiles.Contains(fileLocation.FileName); + return context.IgnoredFiles.Contains(fileLocation.FileName); } - private void ExploreTranslationUnit(ClangExplorerNode node) + private void ExploreTranslationUnit( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node) { var interestingCursors = node.Cursor.GetDescendents(IsCursorOfInterest); foreach (var cursor in interestingCursors) { - VisitTranslationUnitCursor(node, cursor); + VisitTranslationUnitCursor(context, node, cursor); } static bool IsCursorOfInterest(CXCursor cursor, CXCursor cursorParent) @@ -374,7 +248,8 @@ static bool IsCursorOfInterest(CXCursor cursor, CXCursor cursorParent) } } - private void VisitTranslationUnitCursor(ClangExplorerNode parentNode, CXCursor cursor) + private void VisitTranslationUnitCursor( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode parentNode, CXCursor cursor) { var kind = cursor.kind switch { @@ -396,16 +271,16 @@ private void VisitTranslationUnitCursor(ClangExplorerNode parentNode, CXCursor c if (kind == CKind.MacroDefinition) { - var location = Location(cursor); - AddExplorerNode(kind, location, parentNode, cursor, default, default, name, string.Empty); + var location = Location(context, cursor); + AddExplorerNode(context, kind, location, parentNode, cursor, default, name, string.Empty); } else { var type = clang_getCursorType(cursor); - var location = Location(cursor, type); + var location = Location(context, cursor, type); var typeName = TypeName(parentNode.TypeName!, kind, type, cursor); - var isIgnored = IsIgnored(type, cursor); + var isIgnored = IsIgnored(context, type, cursor); if (isIgnored) { return; @@ -413,53 +288,68 @@ private void VisitTranslationUnitCursor(ClangExplorerNode parentNode, CXCursor c if (kind == CKind.Enum) { - ExploreEnum(typeName, cursor, type, location, parentNode, true); + ExploreEnum(context, typeName, cursor, type, location, parentNode); } else { - AddExplorerNode(kind, location, parentNode, cursor, type, type, name, typeName); + AddExplorerNode(context, kind, location, parentNode, cursor, type, name, typeName); } } } - private void ExploreArray(ClangExplorerNode node) + private void VisitArray( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node) { var elementType = clang_getElementType(node.Type); var (kind, type) = TypeKind(elementType); var typeCursor = clang_getTypeDeclaration(type); var cursor = typeCursor.kind == CXCursorKind.CXCursor_NoDeclFound ? node.Cursor : typeCursor; var typeName = TypeName(node.TypeName!, kind, type, typeCursor); - VisitType(node, cursor, node.Cursor, type, type, typeName); + VisitType(context, node, cursor, node.Cursor, type, typeName); } - private void ExplorePointer(ClangExplorerNode node) + private void ExplorePointer( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node) { var pointeeType = clang_getPointeeType(node.Type); var (kind, type) = TypeKind(pointeeType); var typeCursor = clang_getTypeDeclaration(type); var typeName = TypeName(node.TypeName!, kind, type, typeCursor); - VisitType(node, typeCursor, typeCursor, type, type, typeName); + VisitType(context, node, typeCursor, typeCursor, type, typeName); } - private void ExploreMacro(ClangExplorerNode node) + private void ExploreMacro(ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node) { var name = node.Name!; + if (context.Names.Contains(name)) + { + var diagnostic = new MacroAlreadyExistsDiagnostic(name); + context.Diagnostics.Add(diagnostic); + return; + } + var location = node.Location; // Function-like macros currently not implemented // https://github.com/lithiumtoast/c2cs/issues/35 if (clang_Cursor_isMacroFunctionLike(node.Cursor) != 0) { - _macroFunctionLikeNames.Add(name); + context.MacroFunctionLikeNames.Add(name); return; } - // It is assumed that macros with a name which starts with an underscore are not supposed to be exposed in the public API + // Assume that macros with a name which starts with an underscore are not supposed to be exposed in the public API if (name.StartsWith("_", StringComparison.InvariantCulture)) { return; } + // Assume that macro ending with "API_DECL" are not interesting for bindgen + if (name.EndsWith("API_DECL", StringComparison.InvariantCulture)) + { + return; + } + // libclang doesn't have a thing where we can easily get a value of a macro // we need to: // 1. get the text range of the cursor @@ -505,12 +395,13 @@ private void ExploreMacro(ClangExplorerNode node) // Ignore macros with certain tokens foreach (var token in tokens) { - if (_macroFunctionLikeNames.Contains(token)) + if (context.MacroFunctionLikeNames.Contains(token)) { return; } - if (_ignoredMacroTokens.Contains(token)) + // cinttypes.h + if (token.StartsWith("PRI", StringComparison.InvariantCulture)) { return; } @@ -536,11 +427,25 @@ private void ExploreMacro(ClangExplorerNode node) } // Ignore macros which are forward declarations - if (tokens.Length == 1 && _names.Contains(tokens[0])) + if (tokens.Length == 1 && context.Names.Contains(tokens[0])) { return; } + if (name == "C2CS_RUNTIME_TARGET_PLATFORM_NAME") + { + var actualPlatformName = tokens.Length != 1 ? string.Empty : tokens[0].Replace("\"", string.Empty, StringComparison.InvariantCulture); + var actualPlatform = new TargetPlatform(actualPlatformName); + var expectedPlatform = context.TargetPlatform; + if (actualPlatform != expectedPlatform) + { + var diagnostic = new PlatformMismatchDiagnostic(actualPlatform, expectedPlatform); + context.Diagnostics.Add(diagnostic); + } + + return; + } + var macro = new CMacroDefinition { Name = name, @@ -548,18 +453,23 @@ private void ExploreMacro(ClangExplorerNode node) Location = location }; - _macroObjects.Add(macro); + context.Names.Add(name); + context.MacroObjects.Add(macro); + _logger.ExploreCodeMacro(name); } private void ExploreVariable( + ClangTranslationUnitExplorerContext context, string name, string typeName, CXCursor cursor, CXType type, - ClangLocation location, - ClangExplorerNode parentNode) + CLocation location, + ClangTranslationUnitExplorerNode parentNode) { - VisitType(parentNode, cursor, cursor, type, type, typeName); + _logger.ExploreCodeVariable(name); + + VisitType(context, parentNode, cursor, cursor, type, typeName); var variable = new CVariable { @@ -568,30 +478,33 @@ private void ExploreVariable( Type = typeName }; - _variables.Add(variable); - _names.Add(name); + context.Variables.Add(variable); + context.Names.Add(name); } private void ExploreFunction( + ClangTranslationUnitExplorerContext context, string name, CXCursor cursor, CXType type, - ClangLocation location, - ClangExplorerNode parentNode) + CLocation location, + ClangTranslationUnitExplorerNode parentNode) { - if (!_functionNamesWhitelist.IsEmpty && !_functionNamesWhitelist.Contains(name)) + if (!context.FunctionNamesWhitelist.IsEmpty && !context.FunctionNamesWhitelist.Contains(name)) { return; } + _logger.ExploreCodeFunction(name); + var callingConvention = CreateFunctionCallingConvention(type); var resultType = clang_getCursorResultType(cursor); var (kind, actualType) = TypeKind(resultType); var resultTypeName = TypeName(parentNode.TypeName!, kind, actualType, cursor); - VisitType(parentNode, cursor, cursor, resultType, resultType, resultTypeName); + VisitType(context, parentNode, cursor, cursor, resultType, resultTypeName); - var parameters = CreateFunctionParameters(cursor, parentNode); + var functionParameters = CreateFunctionParameters(context, cursor, parentNode); var function = new CFunction { @@ -599,21 +512,28 @@ private void ExploreFunction( Location = location, CallingConvention = callingConvention, ReturnType = resultTypeName, - Parameters = parameters + Parameters = functionParameters }; - _functions.Add(function); - _names.Add(function.Name); + context.Functions.Add(function); + context.Names.Add(function.Name); } private void ExploreEnum( + ClangTranslationUnitExplorerContext context, string typeName, CXCursor cursor, CXType type, - ClangLocation location, - ClangExplorerNode parentNode, - bool isPseudo = false) + CLocation location, + ClangTranslationUnitExplorerNode parentNode) { + if (context.Names.Contains(typeName)) + { + return; + } + + _logger.ExploreCodeEnum(typeName); + var typeCursor = clang_getTypeDeclaration(type); if (typeCursor.kind == CXCursorKind.CXCursor_NoDeclFound) { @@ -623,9 +543,9 @@ private void ExploreEnum( var integerType = clang_getEnumDeclIntegerType(typeCursor); var integerTypeName = TypeName(parentNode.TypeName!, CKind.Enum, integerType, cursor); - VisitType(parentNode, cursor, cursor, integerType, integerType, integerTypeName); + VisitType(context, parentNode, cursor, cursor, integerType, integerTypeName); - var enumValues = CreateEnumValues(typeCursor); + var enumValues = CreateEnumValues(context, typeCursor); var @enum = new CEnum { @@ -636,33 +556,28 @@ private void ExploreEnum( Values = enumValues }; - if (isPseudo) - { - _pseudoEnums.Add(@enum); - } - else - { - _enums.Add(@enum); - } - - _names.Add(@enum.Name); + context.Enums.Add(@enum); + context.Names.Add(@enum.Name); } - private void ExploreRecord(ClangExplorerNode node, ClangExplorerNode parentNode) + private void ExploreRecord( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode node, ClangTranslationUnitExplorerNode parentNode) { var typeName = node.TypeName!; var location = node.Location; - if (_opaqueTypeNames.Contains(typeName)) + if (context.OpaqueTypesNames.Contains(typeName)) { - ExploreOpaqueType(typeName, location); + ExploreOpaqueType(context, typeName, location); return; } + _logger.ExploreCodeRecord(typeName); + var cursor = node.Cursor; - var fields = CreateRecordFields(typeName, cursor, parentNode); - var nestedNodes = CreateNestedNodes(typeName, cursor, node); + var fields = CreateRecordFields(context, typeName, cursor, parentNode); + var nestedNodes = CreateNestedNodes(context, typeName, cursor, node); var nestedRecords = nestedNodes.Where(x => x is CRecord).Cast().ToImmutableArray(); @@ -687,23 +602,24 @@ private void ExploreRecord(ClangExplorerNode node, ClangExplorerNode parentNode) return; } - _records.Add(record); - _names.Add(record.Name); + context.Records.Add(record); + context.Names.Add(record.Name); } private void ExploreTypedef( - ClangExplorerNode node, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + ClangTranslationUnitExplorerNode node, + ClangTranslationUnitExplorerNode parentNode) { var typeName = node.TypeName!; var location = node.Location; - if (_opaqueTypeNames.Contains(typeName)) + if (context.OpaqueTypesNames.Contains(typeName)) { - ExploreOpaqueType(typeName, location); + ExploreOpaqueType(context, typeName, location); return; } - var type = node.Type; var underlyingType = clang_getTypedefDeclUnderlyingType(node.Cursor); var (aliasKind, aliasType) = TypeKind(underlyingType); var aliasCursor = clang_getTypeDeclaration(aliasType); @@ -712,18 +628,20 @@ private void ExploreTypedef( switch (aliasKind) { case CKind.Enum: - ExploreEnum(typeName, cursor, aliasType, location, parentNode); + ExploreEnum(context, typeName, cursor, aliasType, location, parentNode); return; case CKind.Record: - ExploreRecord(node, parentNode); + ExploreRecord(context, node, parentNode); return; case CKind.FunctionPointer: - ExploreFunctionPointer(typeName, cursor, aliasType, type, location, parentNode); + ExploreFunctionPointer(context, typeName, cursor, aliasType, location, parentNode); return; } + _logger.ExploreCodeTypedef(typeName); + var aliasTypeName = TypeName(parentNode.TypeName!, aliasKind, aliasType, cursor); - VisitType(parentNode, cursor, node.Cursor, aliasType, aliasType, aliasTypeName); + VisitType(context, parentNode, cursor, node.Cursor, aliasType, aliasTypeName); var typedef = new CTypedef { @@ -732,76 +650,77 @@ private void ExploreTypedef( UnderlyingType = aliasTypeName }; - _typedefs.Add(typedef); - _names.Add(typedef.Name); + context.Typedefs.Add(typedef); + context.Names.Add(typedef.Name); } - private void ExploreOpaqueType(string typeName, ClangLocation location) + private void ExploreOpaqueType(ClangTranslationUnitExplorerContext context, string typeName, CLocation location) { + _logger.ExploreCodeOpaqueType(typeName); + var opaqueDataType = new COpaqueType { Name = typeName, Location = location }; - _opaqueDataTypes.Add(opaqueDataType); - _names.Add(opaqueDataType.Name); + context.OpaqueDataTypes.Add(opaqueDataType); + context.Names.Add(opaqueDataType.Name); } private void ExploreFunctionPointer( + ClangTranslationUnitExplorerContext context, string typeName, CXCursor cursor, CXType type, - CXType originalType, - ClangLocation location, - ClangExplorerNode parentNode) + CLocation location, + ClangTranslationUnitExplorerNode parentNode) { + _logger.ExploreCodeFunctionPointer(typeName); + if (type.kind == CXTypeKind.CXType_Pointer) { type = clang_getPointeeType(type); } - var functionPointer = CreateFunctionPointer(typeName, cursor, parentNode, originalType, type, location); + var functionPointer = CreateFunctionPointer(context, typeName, cursor, parentNode, type, location); - _functionPointers.Add(functionPointer); - _names.Add(functionPointer.Name); + context.FunctionPointers.Add(functionPointer); + context.Names.Add(functionPointer.Name); } - private bool TypeNameIsValid(CXType type, string typeName) + private bool TypeNameIsValid(ClangTranslationUnitExplorerContext context, string typeName) { - if (_validTypeNames.TryGetValue(typeName, out var value)) + if (context.ValidTypeNames.TryGetValue(typeName, out var isValid)) { - return value; + return isValid; } - var isSystem = type.IsSystem(); - var isIgnored = _systemIgnoredTypeNames.Contains(typeName); - if (isSystem && isIgnored) - { - value = false; - } - else - { - value = true; - } + var isIgnored = context.SystemIgnoredTypeNames.Contains(typeName); + isValid = !isIgnored; - _validTypeNames.Add(typeName, value); - return value; + context.ValidTypeNames.Add(typeName, isValid); + return isValid; } - private bool RegisterTypeIsNew(string typeName, CXType type, CXCursor cursor) + private bool IsNewType(ClangTranslationUnitExplorerContext context, string typeName, CXType type, CXCursor cursor) { if (string.IsNullOrEmpty(typeName)) { - return true; + return false; } - var alreadyVisited = _typesByName.TryGetValue(typeName, out var typeC); + var alreadyVisited = context.TypesByName.TryGetValue(typeName, out var typeC); if (alreadyVisited) { + if (typeC == null) + { + return false; + } + // attempt to see if we have a definition for a previous opaque type, to which we should that info instead // this can happen if one header file has a forward type, but another header file has the definition - if (typeC!.Kind != CKind.OpaqueType) + if (typeC.Kind != CKind.OpaqueType) { return false; } @@ -812,26 +731,25 @@ private bool RegisterTypeIsNew(string typeName, CXType type, CXCursor cursor) return false; } - typeC = Type(typeName, cursor, type); - _typesByName[typeName] = typeC; + typeC = Type(context, typeName, cursor, type); + context.TypesByName[typeName] = typeC; return true; } - typeC = Type(typeName, cursor, type); - - _typesByName.Add(typeName, typeC); - _types.Add(typeC); + typeC = Type(context, typeName, cursor, type); + context.TypesByName.Add(typeName, typeC); + context.Types.Add(typeC); return true; } private void AddExplorerNode( + ClangTranslationUnitExplorerContext context, CKind kind, - ClangLocation location, - ClangExplorerNode? parent, + CLocation location, + ClangTranslationUnitExplorerNode? parent, CXCursor cursor, CXType type, - CXType originalType, string name, string typeName) { @@ -843,29 +761,28 @@ private void AddExplorerNode( throw up; } - var isIgnored = IsIgnored(type, cursor); + var isIgnored = IsIgnored(context, type, cursor); if (isIgnored) { return; } - var node = new ClangExplorerNode( + var node = new ClangTranslationUnitExplorerNode( kind, location, parent, cursor, type, - originalType, name, typeName); if (kind == CKind.MacroDefinition) { - _frontierMacros.PushBack(node); + context.FrontierMacros.PushBack(node); } else { - _frontierGeneral.PushBack(node); + context.FrontierGeneral.PushBack(node); } } @@ -883,7 +800,9 @@ private static CFunctionCallingConvention CreateFunctionCallingConvention(CXType } private ImmutableArray CreateFunctionParameters( - CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var builder = ImmutableArray.CreateBuilder(); @@ -892,7 +811,7 @@ private ImmutableArray CreateFunctionParameters( foreach (var parameterCursor in parameterCursors) { - var functionExternParameter = FunctionParameter(parameterCursor, parentNode); + var functionExternParameter = FunctionParameter(context, parameterCursor, parentNode); builder.Add(functionExternParameter); } @@ -900,7 +819,8 @@ private ImmutableArray CreateFunctionParameters( return result; } - private CFunctionParameter FunctionParameter(CXCursor cursor, ClangExplorerNode parentNode) + private CFunctionParameter FunctionParameter( + ClangTranslationUnitExplorerContext context, CXCursor cursor, ClangTranslationUnitExplorerNode parentNode) { var type = clang_getCursorType(cursor); var name = cursor.Name(); @@ -908,8 +828,8 @@ private CFunctionParameter FunctionParameter(CXCursor cursor, ClangExplorerNode var (kind, typeActual) = TypeKind(type); var typeName = TypeName(parentNode.TypeName!, kind, typeActual, cursor); - VisitType(parentNode, cursor, cursor, type, type, typeName); - var codeLocation = Location(cursor, type); + VisitType(context, parentNode, cursor, cursor, type, typeName); + var codeLocation = Location(context, cursor, type); return new CFunctionParameter { @@ -920,19 +840,19 @@ private CFunctionParameter FunctionParameter(CXCursor cursor, ClangExplorerNode } private CFunctionPointer CreateFunctionPointer( + ClangTranslationUnitExplorerContext context, string typeName, CXCursor cursor, - ClangExplorerNode parentNode, - CXType originalType, + ClangTranslationUnitExplorerNode parentNode, CXType type, - ClangLocation location) + CLocation location) { - var parameters = CreateFunctionPointerParameters(cursor, parentNode); + var functionPointerParameters = CreateFunctionPointerParameters(context, cursor, parentNode); var returnType = clang_getResultType(type); var (kind, actualReturnType) = TypeKind(returnType); var returnTypeName = TypeName(parentNode.TypeName!, kind, actualReturnType, cursor); - VisitType(parentNode, cursor, cursor, returnType, returnType, returnTypeName); + VisitType(context, parentNode, cursor, cursor, returnType, returnTypeName); var name = string.Empty; if (cursor.kind == CXCursorKind.CXCursor_TypedefDecl) @@ -950,14 +870,16 @@ private CFunctionPointer CreateFunctionPointer( Location = location, Type = typeName, ReturnType = returnTypeName, - Parameters = parameters + Parameters = functionPointerParameters }; return functionPointer; } private ImmutableArray CreateFunctionPointerParameters( - CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var builder = ImmutableArray.CreateBuilder(); @@ -966,7 +888,7 @@ private ImmutableArray CreateFunctionPointerParameter foreach (var parameterCursor in parameterCursors) { - var functionPointerParameter = CreateFunctionPointerParameter(parameterCursor, parentNode); + var functionPointerParameter = CreateFunctionPointerParameter(context, parameterCursor, parentNode); builder.Add(functionPointerParameter); } @@ -975,16 +897,18 @@ private ImmutableArray CreateFunctionPointerParameter } private CFunctionPointerParameter CreateFunctionPointerParameter( - CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var type = clang_getCursorType(cursor); - var codeLocation = Location(cursor, type); + var codeLocation = Location(context, cursor, type); var name = cursor.Name(); var (kind, actualType) = TypeKind(type); var typeName = TypeName(parentNode.TypeName!, kind, actualType, cursor); - VisitType(parentNode, cursor, cursor, type, type, typeName); + VisitType(context, parentNode, cursor, cursor, type, typeName); return new CFunctionPointerParameter { @@ -995,7 +919,10 @@ private CFunctionPointerParameter CreateFunctionPointerParameter( } private ImmutableArray CreateRecordFields( - string recordName, CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + string recordName, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var builder = ImmutableArray.CreateBuilder(); @@ -1014,17 +941,18 @@ private ImmutableArray CreateRecordFields( foreach (var fieldCursor in fieldCursors) { - var recordField = CreateRecordField(recordName, fieldCursor, parentNode); + var recordField = CreateRecordField(context, recordName, fieldCursor, parentNode); builder.Add(recordField); } - CalculatePaddingForStructFields(cursor, builder); + CalculatePaddingForStructFields(context, cursor, builder); var result = builder.ToImmutable(); return result; } private void CalculatePaddingForStructFields( + ClangTranslationUnitExplorerContext context, CXCursor cursor, ImmutableArray.Builder builder) { @@ -1033,7 +961,7 @@ private void CalculatePaddingForStructFields( var recordField = builder[i]; var fieldPrevious = builder[i - 1]; var typeName = Value(fieldPrevious.Type); - var type = _typesByName[typeName]; + var type = context.TypesByName[typeName]; var fieldPreviousTypeSizeOf = type.SizeOf; var expectedFieldOffset = fieldPrevious.Offset + fieldPreviousTypeSizeOf; var hasPadding = recordField.Offset != 0 && recordField.Offset != expectedFieldOffset; @@ -1052,7 +980,7 @@ private void CalculatePaddingForStructFields( var cursorType = clang_getCursorType(cursor); var recordSize = (int)clang_Type_getSizeOf(cursorType); var typeName = Value(fieldLast.Type); - var type = _typesByName[typeName]; + var type = context.TypesByName[typeName]; var fieldLastTypeSize = type.SizeOf; var expectedLastFieldOffset = recordSize - fieldLastTypeSize; if (fieldLast.Offset != expectedLastFieldOffset) @@ -1076,15 +1004,18 @@ private string Value(string typeName) } private CRecordField CreateRecordField( - string recordName, CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + string recordName, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var name = cursor.Name(); var type = clang_getCursorType(cursor); - var codeLocation = Location(cursor, type); + var codeLocation = Location(context, cursor, type); var (kind, actualType) = TypeKind(type); var typeName = TypeName(recordName, kind, actualType, cursor); - VisitType(parentNode, cursor, cursor, type, type, typeName); + VisitType(context, parentNode, cursor, cursor, type, typeName); var offset = (int)(clang_Cursor_getOffsetOfField(cursor) / 8); @@ -1098,7 +1029,10 @@ private CRecordField CreateRecordField( } private ImmutableArray CreateNestedNodes( - string parentTypeName, CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + string parentTypeName, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var builder = ImmutableArray.CreateBuilder(); @@ -1131,7 +1065,7 @@ private ImmutableArray CreateNestedNodes( foreach (var nestedCursor in nestedCursors) { - var record = CreateNestedNode(parentTypeName, nestedCursor, parentNode); + var record = CreateNestedNode(context, parentTypeName, nestedCursor, parentNode); builder.Add(record); } @@ -1144,21 +1078,24 @@ private ImmutableArray CreateNestedNodes( } private CNode CreateNestedNode( - string parentTypeName, CXCursor cursor, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + string parentTypeName, + CXCursor cursor, + ClangTranslationUnitExplorerNode parentNode) { var type = clang_getCursorType(cursor); var isPointer = type.kind == CXTypeKind.CXType_Pointer; if (!isPointer) { - return CreateNestedStruct(parentTypeName, cursor, type, parentNode); + return CreateNestedStruct(context, parentTypeName, cursor, type, parentNode); } var pointeeType = clang_getPointeeType(type); if (pointeeType.kind == CXTypeKind.CXType_FunctionProto) { var typeName = TypeName(parentTypeName, CKind.FunctionPointer, pointeeType, cursor); - var location = Location(cursor, type); - return CreateFunctionPointer(typeName, cursor, parentNode, type, pointeeType, location); + var location = Location(context, cursor, type); + return CreateFunctionPointer(context, typeName, cursor, parentNode, pointeeType, location); } var up = new UseCaseException("Unknown mapping for nested node."); @@ -1166,13 +1103,17 @@ private CNode CreateNestedNode( } private CNode CreateNestedStruct( - string parentTypeName, CXCursor cursor, CXType type, ClangExplorerNode parentNode) + ClangTranslationUnitExplorerContext context, + string parentTypeName, + CXCursor cursor, + CXType type, + ClangTranslationUnitExplorerNode parentNode) { - var location = Location(cursor, type); + var location = Location(context, cursor, type); var typeName = TypeName(parentTypeName, CKind.Record, type, cursor); - var recordFields = CreateRecordFields(typeName, cursor, parentNode); - var nestedNodes = CreateNestedNodes(typeName, cursor, parentNode); + var recordFields = CreateRecordFields(context, typeName, cursor, parentNode); + var nestedNodes = CreateNestedNodes(context, typeName, cursor, parentNode); var nestedRecords = nestedNodes.Where(x => x is CRecord).Cast().ToImmutableArray(); var typeCursor = clang_getTypeDeclaration(type); @@ -1188,7 +1129,7 @@ private CNode CreateNestedStruct( }; } - private ImmutableArray CreateEnumValues(CXCursor cursor) + private ImmutableArray CreateEnumValues(ClangTranslationUnitExplorerContext context, CXCursor cursor) { var builder = ImmutableArray.CreateBuilder(); @@ -1217,18 +1158,16 @@ private ImmutableArray CreateEnumValues(CXCursor cursor) private CEnumValue CreateEnumValue(CXCursor cursor, string? name = null) { var value = clang_getEnumConstantDeclValue(cursor); - var location = Location(cursor); name ??= cursor.Name(); return new CEnumValue { - Location = location, Name = name, Value = value }; } - private CType Type(string typeName, CXCursor cursor, CXType clangType) + private CType Type(ClangTranslationUnitExplorerContext context, string typeName, CXCursor cursor, CXType clangType) { var type = clangType; var declaration = clang_getTypeDeclaration(type); @@ -1237,70 +1176,47 @@ private CType Type(string typeName, CXCursor cursor, CXType clangType) declaration = cursor; } - // if (type.kind == CXTypeKind.CXType_IncompleteArray) - // { - // var elementType = clang_getElementType(type); - // if (elementType.kind == CXTypeKind.CXType_Typedef) - // { - // var elementCursor = clang_getTypeDeclaration(elementType); - // var underlyingCursor = ClangUnderlyingCursor(elementCursor); - // elementType = clang_getTypedefDeclUnderlyingType(x); - // } - // - // if (elementType.kind == CXTypeKind.CXType_Pointer) - // { - // sizeOfValue = (int)clang_Type_getSizeOf(elementType); - // } - // } - - var sizeOf = SizeOf(type); + var sizeOf = SizeOf(context, type); var alignOfValue = (int)clang_Type_getAlignOf(type); int? alignOf = alignOfValue >= 0 ? alignOfValue : null; var arraySizeValue = (int)clang_getArraySize(type); int? arraySize = arraySizeValue >= 0 ? arraySizeValue : null; var isSystemType = type.IsSystem(); + var (kind, kindType) = TypeKind(type); + int? elementSize = null; - if (type.kind == CXTypeKind.CXType_ConstantArray) + if (kind == CKind.Array) { - var elementType = clang_getElementType(type); + var (_, arrayType) = TypeKind(kindType); + var elementType = clang_getElementType(arrayType); elementSize = (int)clang_Type_getSizeOf(elementType); } - ClangLocation? location = null; - - var typeKind = TypeKind(type); - if (typeKind.Kind != CKind.Primitive && !isSystemType) - { - location = Location(declaration, type); - } + var location = Location(context, declaration, type); var cType = new CType { Name = typeName, - Kind = typeKind.Kind, + Kind = kind, SizeOf = sizeOf, AlignOf = alignOf, ElementSize = elementSize, ArraySize = arraySize, - IsSystem = isSystemType, Location = location }; - if (location != null) + var fileName = location.FileName; + if (context.IgnoredFiles.Contains(fileName)) { - var fileName = location.Value.FileName; - if (_ignoredFiles.Contains(fileName)) - { - var diagnostic = new TypeFromIgnoredHeaderDiagnostic(typeName, fileName); - _diagnostics.Add(diagnostic); - } + var diagnostic = new TypeFromIgnoredHeaderDiagnostic(typeName, fileName); + context.Diagnostics.Add(diagnostic); } return cType; } - private int SizeOf(CXType type) + private int SizeOf(ClangTranslationUnitExplorerContext context, CXType type) { var sizeOf = (int)clang_Type_getSizeOf(type); if (sizeOf >= 0) @@ -1330,7 +1246,7 @@ private int SizeOf(CXType type) case CKind.Pointer: return (int)clang_Type_getSizeOf(underlyingType); default: - var location = Location(clang_getTypeDeclaration(type), type); + var location = Location(context, clang_getTypeDeclaration(type), type); throw new UseCaseException( $"Unexpected case when determining size for Clang type: {location}. Please submit an issue on GitHub!"); } @@ -1438,11 +1354,11 @@ private static CXCursor ClangUnderlyingCursor(CXCursor cursor) } private void VisitType( - ClangExplorerNode parentNode, + ClangTranslationUnitExplorerContext context, + ClangTranslationUnitExplorerNode parentNode, CXCursor cursor, CXCursor originalCursor, CXType type, - CXType originalType, string typeName) { if (cursor.kind == CXCursorKind.CXCursor_NoDeclFound) @@ -1450,55 +1366,49 @@ private void VisitType( throw new UseCaseException("NoDecl cursor."); } - if (!RegisterTypeIsNew(typeName, type, cursor)) + if (!IsNewType(context, typeName, type, cursor)) { return; } - var isValidTypeName = TypeNameIsValid(type, typeName); + var isValidTypeName = TypeNameIsValid(context, typeName); if (!isValidTypeName) { return; } - var typeKind = TypeKind(type); - if (typeKind.Kind == CKind.Pointer) + var (kind, kindType) = TypeKind(type); + if (kind == CKind.Typedef) { - var pointeeType = clang_getPointeeType(typeKind.Type); - var pointeeKind = TypeKind(pointeeType); + VisitTypedef(context, parentNode, kindType, typeName); + return; + } + + _logger.ExploreCodeVisitType(typeName); + + if (kind == CKind.Pointer) + { + var pointeeType = clang_getPointeeType(kindType); + var (pointeeKind, pointeeKindType) = TypeKind(pointeeType); var pointeeCursor = clang_getTypeDeclaration(pointeeType); var pointeeCursor2 = pointeeCursor.kind == CXCursorKind.CXCursor_NoDeclFound ? cursor : pointeeCursor; - var pointeeTypeName = TypeName(parentNode.TypeName!, pointeeKind.Kind, pointeeKind.Type, pointeeCursor2); + var pointeeTypeName = TypeName(parentNode.TypeName!, pointeeKind, pointeeKindType, pointeeCursor2); VisitType( + context, parentNode, pointeeCursor2, originalCursor, - pointeeKind.Type, - type, + pointeeKindType, pointeeTypeName); - return; - } - - if (typeKind.Kind == CKind.Typedef) - { - VisitTypedef(parentNode, typeKind.Type, typeName); } else { - CXType locationType; - if (type.kind == CXTypeKind.CXType_IncompleteArray || - type.kind == CXTypeKind.CXType_ConstantArray) - { - locationType = clang_getElementType(type); - } - else if (type.kind == CXTypeKind.CXType_Pointer) - { - locationType = clang_getPointeeType(type); - } - else + var locationType = type.kind switch { - locationType = type; - } + CXTypeKind.CXType_IncompleteArray or CXTypeKind.CXType_ConstantArray => clang_getElementType(type), + CXTypeKind.CXType_Pointer => clang_getPointeeType(type), + _ => type + }; var locationCursor = clang_getTypeDeclaration(locationType); if (locationCursor.kind == CXCursorKind.CXCursor_NoDeclFound) @@ -1511,24 +1421,25 @@ private void VisitType( locationCursor = originalCursor; } - var location = Location(locationCursor); + var location = Location(context, locationCursor); AddExplorerNode( - typeKind.Kind, + context, + kind, location, parentNode, cursor, - typeKind.Type, - originalType, + kindType, string.Empty, typeName); } } - private void VisitTypedef(ClangExplorerNode parentNode, CXType type, string typeName) + private void VisitTypedef( + ClangTranslationUnitExplorerContext context, ClangTranslationUnitExplorerNode parentNode, CXType type, string typeName) { var typedefCursor = clang_getTypeDeclaration(type); - var location = Location(typedefCursor); - AddExplorerNode(CKind.Typedef, location, parentNode, typedefCursor, type, type, string.Empty, typeName); + var location = Location(context, typedefCursor); + AddExplorerNode(context, CKind.Typedef, location, parentNode, typedefCursor, type, string.Empty, typeName); } private string TypeName(string? parentTypeName, CKind kind, CXType type, CXCursor cursor) @@ -1570,11 +1481,7 @@ private string TypeName(string? parentTypeName, CKind kind, CXType type, CXCurso _ => throw new UseCaseException($"Unexpected node kind '{kind}'.") }; - if (type.kind == CXTypeKind.CXType_IncompleteArray) - { - typeName = $"{typeName}"; - } - else if (type.kind == CXTypeKind.CXType_ConstantArray) + if (type.kind == CXTypeKind.CXType_ConstantArray) { var arraySize = clang_getArraySize(type); typeName = $"{typeName}[{arraySize}]"; @@ -1585,31 +1492,12 @@ private string TypeName(string? parentTypeName, CKind kind, CXType type, CXCurso throw new UseCaseException($"Type name was not found for '{kind}'."); } - if (kind == CKind.Enum) - { - typeName = typeName switch - { - // TRICK: Force signed integer; enums in C could be signed or unsigned depending the platform architecture. - // This makes for a slightly different bindings between Windows/macOS/Linux where the enum is different type - "unsigned char" or "char" => "signed char", - "short" or "unsigned short" => "signed short", - "short int" or "unsigned short int" => "signed short int", - "unsigned" => "signed", - "int" or "unsigned int" => "signed int", - "long" or "unsigned long" => "signed long", - "long int" or "unsigned long int" => "signed long int", - "long long" or "unsigned long long" => "signed long long", - "long long int" or "unsigned long long int" => "signed long long int", - _ => typeName - }; - } - return typeName; } - private ClangLocation Location(CXCursor cursor, CXType? type = null) + private CLocation Location(ClangTranslationUnitExplorerContext context, CXCursor cursor, CXType? type = null) { - ClangLocation location; + CLocation location; if (type == null) { location = cursor.FileLocation(); @@ -1623,13 +1511,16 @@ private ClangLocation Location(CXCursor cursor, CXType? type = null) } } - foreach (var includeDirectory in _includeDirectories) + if (!string.IsNullOrEmpty(location.FilePath)) { - if (location.FilePath.Contains(includeDirectory, StringComparison.InvariantCulture)) + foreach (var includeDirectory in context.IncludeDirectories) { - location.FilePath = location.FilePath - .Replace(includeDirectory, string.Empty, StringComparison.InvariantCulture).Trim('/', '\\'); - break; + if (location.FilePath.Contains(includeDirectory, StringComparison.InvariantCulture)) + { + location.FilePath = location.FilePath + .Replace(includeDirectory, string.Empty, StringComparison.InvariantCulture).Trim('/', '\\'); + break; + } } } diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerContext.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerContext.cs new file mode 100644 index 00000000..408968ca --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerContext.cs @@ -0,0 +1,79 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Foundation; +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode; + +public sealed class ClangTranslationUnitExplorerContext +{ + public DiagnosticsSink Diagnostics { get; } + + public ImmutableArray IncludeDirectories { get; } + + public ImmutableArray IgnoredFiles { get; } + + public ImmutableArray OpaqueTypesNames { get; } + + public ImmutableArray FunctionNamesWhitelist { get; } + + public TargetPlatform TargetPlatform { get; } + + internal readonly List Enums = new(); + internal readonly ArrayDeque FrontierGeneral = new(); + internal readonly ArrayDeque FrontierMacros = new(); + internal readonly List FunctionPointers = new(); + internal readonly List Functions = new(); + internal readonly HashSet MacroFunctionLikeNames = new(); + internal readonly List MacroObjects = new(); + internal readonly HashSet Names = new(); + internal readonly List OpaqueDataTypes = new(); + internal readonly List Records = new(); + internal readonly HashSet SystemIgnoredTypeNames = DefaultSystemIgnoredTypeNames(); + internal readonly List Typedefs = new(); + internal readonly List Types = new(); + internal readonly Dictionary TypesByName = new(); + internal readonly Dictionary ValidTypeNames = new(); + internal readonly List Variables = new(); + + public ClangTranslationUnitExplorerContext( + DiagnosticsSink diagnostics, + ImmutableArray includeDirectories, + ImmutableArray ignoredFiles, + ImmutableArray opaqueTypeNames, + ImmutableArray functionNamesWhitelist, + TargetPlatform targetPlatform) + { + Diagnostics = diagnostics; + IncludeDirectories = includeDirectories; + IgnoredFiles = ignoredFiles; + OpaqueTypesNames = opaqueTypeNames; + FunctionNamesWhitelist = functionNamesWhitelist; + TargetPlatform = targetPlatform; + } + + private static HashSet DefaultSystemIgnoredTypeNames() + { + return new HashSet + { + "FILE", + "DIR", + "size_t", + "ssize_t", + "int8_t", + "uint8_t", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "uintptr_t", + "intptr_t", + "va_list" + }; + } +} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Domain/ClangExplorerNode.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerNode.cs similarity index 58% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Domain/ClangExplorerNode.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerNode.cs index cf7acc19..809c528c 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Domain/ClangExplorerNode.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/ClangTranslationUnitExplorerNode.cs @@ -1,29 +1,27 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using C2CS.Feature.ExtractAbstractSyntaxTreeC.Data; +using C2CS.Feature.ReadCodeC.Data; using static bottlenoselabs.clang; -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC.Domain; +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode; -public class ClangExplorerNode +internal sealed class ClangTranslationUnitExplorerNode { public readonly CXCursor Cursor; public readonly CKind Kind; - public readonly ClangLocation Location; + public readonly CLocation Location; public readonly string? Name; - public readonly CXType OriginalType; - public readonly ClangExplorerNode? Parent; + public readonly ClangTranslationUnitExplorerNode? Parent; public readonly CXType Type; public readonly string? TypeName; - public ClangExplorerNode( + public ClangTranslationUnitExplorerNode( CKind kind, - ClangLocation location, - ClangExplorerNode? parent, + CLocation location, + ClangTranslationUnitExplorerNode? parent, CXCursor cursor, CXType type, - CXType originalType, string? name, string? typeName) { @@ -33,15 +31,7 @@ public ClangExplorerNode( { if (type.IsPrimitive()) { - // Primitives don't have a location - Location = new ClangLocation - { - FilePath = string.Empty, - FileName = "Builtin", - LineColumn = 0, - LineNumber = 0, - IsBuiltin = true - }; + Location = CLocation.System; } else { @@ -56,7 +46,6 @@ public ClangExplorerNode( Parent = parent; Cursor = cursor; Type = type; - OriginalType = originalType; Name = name; TypeName = typeName; } diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/MacroAlreadyExistsDiagnostic.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/MacroAlreadyExistsDiagnostic.cs new file mode 100644 index 00000000..76e06df8 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/MacroAlreadyExistsDiagnostic.cs @@ -0,0 +1,19 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode.Diagnostics; + +public sealed class MacroAlreadyExistsDiagnostic : Diagnostic +{ + public MacroAlreadyExistsDiagnostic(string name) + : base(DiagnosticSeverity.Warning, CreateMessage(name)) + { + } + + private static string CreateMessage(string name) + { + return $"A macro with the '{name}' already exists."; + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/PlatformMismatchDiagnostic.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/PlatformMismatchDiagnostic.cs new file mode 100644 index 00000000..198f8616 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/PlatformMismatchDiagnostic.cs @@ -0,0 +1,19 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode.Diagnostics; + +public sealed class PlatformMismatchDiagnostic : Diagnostic +{ + public PlatformMismatchDiagnostic(TargetPlatform actualPlatform, TargetPlatform expectedPlatform) + : base(DiagnosticSeverity.Error, CreateMessage(actualPlatform, expectedPlatform)) + { + } + + private static string CreateMessage(TargetPlatform actualPlatform, TargetPlatform expectedPlatform) + { + return $"The C library was expected to have runtime of platform '{expectedPlatform}' but the header file specified a runtime platform of '{actualPlatform}'."; + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs new file mode 100644 index 00000000..8e317eab --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Diagnostics/TypeFromIgnoredHeaderDiagnostic.cs @@ -0,0 +1,19 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode.Diagnostics; + +public sealed class TypeFromIgnoredHeaderDiagnostic : Diagnostic +{ + public TypeFromIgnoredHeaderDiagnostic(string typeName, string headerFilePath) + : base(DiagnosticSeverity.Warning, CreateMessage(typeName, headerFilePath)) + { + } + + private static string CreateMessage(string typeName, string headerFilePath) + { + return $"The type '{typeName}' belongs to the ignored header file '{headerFilePath}', but is used in the abstract syntax tree."; + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Logging.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Logging.cs new file mode 100644 index 00000000..8822e1c2 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ExploreCode/Logging.cs @@ -0,0 +1,143 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Domain.ExploreCode; + +internal static class Logging +{ + private static readonly Action ActionExploreCodeFailed = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Failed."), + "- Failed"); + + private static readonly Action ActionExploreCodeSuccess = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Success"), + "- Success"); + + private static readonly Action ActionExploreCodeTranslationUnit = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Translation unit"), + "- Translation unit {FilePath}"); + + private static readonly Action ActionExploreCodeMacro = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Macro"), + "- Macro {Name}"); + + private static readonly Action ActionExploreCodeVariable = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Variable"), + "- Variable {Name}"); + + private static readonly Action ActionExploreCodeFunction = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Function"), + "- Function {Name}"); + + private static readonly Action ActionExploreCodeEnum = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Enum"), + "- Enum {Name}"); + + private static readonly Action ActionExploreCodeRecord = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Record"), + "- Record {Name}"); + + private static readonly Action ActionExploreCodeTypedef = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Typedef"), + "- Typedef {Name}"); + + private static readonly Action ActionExploreCodeOpaqueType = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Opaque type"), + "- Opaque type {Name}"); + + private static readonly Action ActionExploreCodeFunctionPointer = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Function pointer"), + "- Function pointer {Name}"); + + private static readonly Action ActionExploreCodeType = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Explore C header file: Type"), + "- Type {TypeName}"); + + public static void ExploreCodeFailed(this ILogger logger, Exception exception) + { + ActionExploreCodeFailed(logger, exception); + } + + public static void ExploreCodeSuccess(this ILogger logger) + { + ActionExploreCodeSuccess(logger, null!); + } + + public static void ExploreCodeTranslationUnit(this ILogger logger, string filePath) + { + ActionExploreCodeTranslationUnit(logger, filePath, null!); + } + + public static void ExploreCodeMacro(this ILogger logger, string name) + { + ActionExploreCodeMacro(logger, name, null!); + } + + public static void ExploreCodeVariable(this ILogger logger, string name) + { + ActionExploreCodeVariable(logger, name, null!); + } + + public static void ExploreCodeFunction(this ILogger logger, string name) + { + ActionExploreCodeFunction(logger, name, null!); + } + + public static void ExploreCodeEnum(this ILogger logger, string name) + { + ActionExploreCodeEnum(logger, name, null!); + } + + public static void ExploreCodeRecord(this ILogger logger, string name) + { + ActionExploreCodeRecord(logger, name, null!); + } + + public static void ExploreCodeTypedef(this ILogger logger, string name) + { + ActionExploreCodeTypedef(logger, name, null!); + } + + public static void ExploreCodeOpaqueType(this ILogger logger, string name) + { + ActionExploreCodeOpaqueType(logger, name, null!); + } + + public static void ExploreCodeFunctionPointer(this ILogger logger, string name) + { + ActionExploreCodeFunctionPointer(logger, name, null!); + } + + public static void ExploreCodeVisitType(this ILogger logger, string name) + { + ActionExploreCodeType(logger, name, null!); + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/ClangInstaller.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/ClangInstaller.cs new file mode 100644 index 00000000..0fb37484 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/ClangInstaller.cs @@ -0,0 +1,135 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.IO.Abstractions; +using System.IO.Compression; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Domain.InstallClang; + +public sealed class ClangInstaller +{ + private bool _isInstalled; + private readonly object _lock = new(); + + private string _clangNativeLibraryFilePath = null!; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public ClangInstaller(ILogger logger, IFileSystem fileSystem) + { + _logger = logger; + _fileSystem = fileSystem; + } + + public void Install(NativeOperatingSystem operatingSystem) + { + lock (_lock) + { + if (_isInstalled) + { + _logger.InstallClangSuccessAlreadyInstalled(_clangNativeLibraryFilePath); + return; + } + + _clangNativeLibraryFilePath = GetClangFilePath(operatingSystem); + NativeLibrary.SetDllImportResolver(typeof(bottlenoselabs.clang).Assembly, ResolveClang); + _logger.InstallClangSuccess(_clangNativeLibraryFilePath); + _isInstalled = true; + } + } + + private string GetClangFilePath(NativeOperatingSystem operatingSystem) + { + string result; + + try + { + result = operatingSystem switch + { + NativeOperatingSystem.Windows => GetClangFilePathWindows(), + NativeOperatingSystem.Linux => GetClangFilePathLinux(), + NativeOperatingSystem.macOS => GetClangFilePathMacOs(), + _ => string.Empty + }; + } + catch (Exception e) + { + _logger.InstallClangFailed(e); + throw; + } + + return result; + } + + private string GetClangFilePathWindows() + { + var filePaths = new[] + { + Path.Combine(AppContext.BaseDirectory, "libclang.dll"), + Path.Combine(AppContext.BaseDirectory, "clang.dll"), + @"C:\Program Files\LLVM\bin\libclang.dll" // choco install llvm + }; + + const string errorMessage = "`libclang.dll` or `clang.dll` is missing. Please put a `libclang.dll` or `clang.dll` file next to this application or install Clang for Windows. To install Clang for Windows using Chocolatey, use the command `choco install llvm`."; + return SearchForClangFilePath(errorMessage, filePaths); + } + + private string GetClangFilePathLinux() + { + var filePaths = new[] + { + Path.Combine(AppContext.BaseDirectory, "libclang.so"), + "/usr/lib/llvm-10/lib/libclang.so.1" // apt-get install clang + }; + + const string errorMessage = "`libclang.so`is missing. Please put a `libclang.so` file next to this application or install Clang for Linux. To install Clang for Debian-based linux distributions, use the command `apt-get update && apt-get install clang`."; + return SearchForClangFilePath(errorMessage, filePaths); + } + + private string GetClangFilePathMacOs() + { + var filePaths = new[] + { + Path.Combine(AppContext.BaseDirectory, "libclang.dylib"), + "/Library/Developer/CommandLineTools/usr/lib/libclang.dylib" // xcode-select --install + }; + + const string errorMessage = "`libclang.dylib` is missing. Please put a `libclang.dylib` file next to this application or install CommandLineTools for macOS using the command `xcode-select --install`."; + return SearchForClangFilePath(errorMessage, filePaths); + } + + private string SearchForClangFilePath(string errorMessage, params string[] filePaths) + { + var installedFilePath = string.Empty; + foreach (var filePath in filePaths) + { + if (!_fileSystem.File.Exists(filePath)) + { + continue; + } + + installedFilePath = filePath; + break; + } + + if (string.IsNullOrEmpty(installedFilePath)) + { + throw new InvalidOperationException(errorMessage); + } + + return installedFilePath; + } + + private IntPtr ResolveClang(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (!NativeLibrary.TryLoad(_clangNativeLibraryFilePath, out var handle)) + { + throw new ClangException($"Could not load libclang: {_clangNativeLibraryFilePath}"); + } + + return handle; + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/Logging.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/Logging.cs new file mode 100644 index 00000000..e51931f4 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/InstallClang/Logging.cs @@ -0,0 +1,44 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Domain.InstallClang; + +internal static class Logging +{ + private static readonly Action ActionFailed = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Install Clang: Failed."), + "- Failed"); + + private static readonly Action ActionSuccess = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Install Clang: Success"), + "- Success, file path: {FilePath}"); + + private static readonly Action ActionSuccessAlreadyInstalled = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Install Clang: Success, already installed"), + "- Success, already installed, file path: {FilePath}"); + + public static void InstallClangFailed(this ILogger logger, Exception exception) + { + ActionFailed(logger, exception); + } + + public static void InstallClangSuccess(this ILogger logger, string filePath) + { + ActionSuccess(logger, filePath, null!); + } + + public static void InstallClangSuccessAlreadyInstalled(this ILogger logger, string filePath) + { + ActionSuccessAlreadyInstalled(logger, filePath, null!); + } +} diff --git a/src/cs/production/clang-cs/ClangArgumentsBuilder.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangArgumentsBuilder.cs similarity index 55% rename from src/cs/production/clang-cs/ClangArgumentsBuilder.cs rename to src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangArgumentsBuilder.cs index 918d7534..fec83e86 100644 --- a/src/cs/production/clang-cs/ClangArgumentsBuilder.cs +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangArgumentsBuilder.cs @@ -1,44 +1,55 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using System; using System.Collections.Immutable; -using System.IO; -using System.Runtime.InteropServices; -using C2CS; +using System.IO.Abstractions; -public static class ClangArgumentsBuilder +namespace C2CS.Feature.ReadCodeC.Domain.ParseCode; + +public class ClangArgumentsBuilder { - public static ImmutableArray Build( - bool automaticallyFindSoftwareDevelopmentKit, + private readonly IFileSystem _fileSystem; + + public ClangArgumentsBuilder(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public ImmutableArray? Build( + bool automaticallyFindSystemHeaders, ImmutableArray includeDirectories, ImmutableArray defines, - int? bitness, + TargetPlatform targetPlatform, ImmutableArray additionalArgs) { - var builder = ImmutableArray.CreateBuilder(); + var args = ImmutableArray.CreateBuilder(); - AddDefault(builder); - AddBitness( - builder, - bitness ?? (RuntimeInformation.OSArchitecture is Architecture.Arm64 or Architecture.X64 ? 64 : 32)); - AddUserIncludes(builder, includeDirectories); - AddDefines(builder, defines); - AddAdditionalArgs(builder, additionalArgs); + AddDefaults(args, targetPlatform); + AddUserIncludes(args, includeDirectories); + AddDefines(args, defines); + AddTargetTriple(args, targetPlatform); + AddAdditionalArgs(args, additionalArgs); - if (automaticallyFindSoftwareDevelopmentKit) + if (automaticallyFindSystemHeaders) { - AddSystemIncludes(builder); + var systemIncludeDirectories = SystemIncludeDirectories(targetPlatform); + AddSystemIncludeDirectories(args, systemIncludeDirectories); } - return builder.ToImmutable(); + return args.ToImmutable(); } - private static void AddDefault(ImmutableArray.Builder args) + private void AddTargetTriple(ImmutableArray.Builder args, TargetPlatform platform) + { + var targetTripleString = $"--target={platform}"; + args.Add(targetTripleString); + } + + private void AddDefaults(ImmutableArray.Builder args, TargetPlatform platform) { args.Add("--language=c"); - if (Platform.HostOperatingSystem == RuntimeOperatingSystem.Linux) + if (platform.OperatingSystem == NativeOperatingSystem.Linux) { args.Add("--std=gnu11"); } @@ -51,12 +62,7 @@ private static void AddDefault(ImmutableArray.Builder args) args.Add("-fno-blocks"); } - private static void AddBitness(ImmutableArray.Builder args, int bitness) - { - args.Add($"-m{bitness}"); - } - - private static void AddUserIncludes( + private void AddUserIncludes( ImmutableArray.Builder args, ImmutableArray includeDirectories) { if (includeDirectories.IsDefaultOrEmpty) @@ -71,7 +77,7 @@ private static void AddUserIncludes( } } - private static void AddDefines(ImmutableArray.Builder args, ImmutableArray defines) + private void AddDefines(ImmutableArray.Builder args, ImmutableArray defines) { if (defines.IsDefaultOrEmpty) { @@ -85,7 +91,7 @@ private static void AddDefines(ImmutableArray.Builder args, ImmutableArr } } - private static void AddAdditionalArgs(ImmutableArray.Builder args, ImmutableArray additionalArgs) + private void AddAdditionalArgs(ImmutableArray.Builder args, ImmutableArray additionalArgs) { if (additionalArgs.IsDefaultOrEmpty) { @@ -98,38 +104,68 @@ private static void AddAdditionalArgs(ImmutableArray.Builder args, Immut } } - private static void AddSystemIncludes(ImmutableArray.Builder args) + private void AddSystemIncludeDirectories( + ImmutableArray.Builder args, ImmutableArray directories) { - var runtime = Platform.HostOperatingSystem; - switch (runtime) + foreach (var directory in directories) { - case RuntimeOperatingSystem.Windows: - AddSystemIncludesWindows(args); + if (!_fileSystem.Directory.Exists(directory)) + { + continue; + } + + args.Add($"-isystem{directory}"); + } + } + + private ImmutableArray SystemIncludeDirectories(TargetPlatform targetPlatform) + { + var hostOperatingSystem = Native.OperatingSystem; + var targetOperatingSystem = targetPlatform.OperatingSystem; + + var directories = ImmutableArray.CreateBuilder(); + + switch (hostOperatingSystem) + { + case NativeOperatingSystem.Windows: + { + SystemIncludeDirectoriesWindowsHost(directories); break; - case RuntimeOperatingSystem.macOS: - AddSystemIncludesMac(args); + } + + case NativeOperatingSystem.macOS: + { + SystemIncludesDirectoriesMacOsHost(directories); + + if (targetOperatingSystem == NativeOperatingSystem.macOS) + { + SystemIncludesDirectoriesMacOsTarget(directories); + } + break; - case RuntimeOperatingSystem.Linux: - AddSystemIncludesLinux(args); + } + + case NativeOperatingSystem.Linux: + { + SystemIncludeDirectoriesLinuxHost(directories); break; - default: - throw new NotImplementedException(); + } } + + return directories.ToImmutable(); } - private static void AddSystemIncludesLinux(ImmutableArray.Builder args) + private void SystemIncludeDirectoriesLinuxHost(ImmutableArray.Builder directories) { - // TODO: Is this always going to work? Be good if this was more bullet proof. If you know better fix it! - const string directoryPath = "/usr/lib/gcc/x86_64-linux-gnu/9/include/"; - var systemIncludeCommandLineArg = $"-isystem{directoryPath}"; - args.Add(systemIncludeCommandLineArg); + directories.Add("/usr/include"); + directories.Add("/usr/include/x86_64-linux-gnu"); } - private static void AddSystemIncludesWindows(ImmutableArray.Builder clangArgumentsBuilder) + private void SystemIncludeDirectoriesWindowsHost(ImmutableArray.Builder directories) { var sdkDirectoryPath = Environment.ExpandEnvironmentVariables(@"%ProgramFiles(x86)%\Windows Kits\10\Include"); - if (!string.IsNullOrEmpty(sdkDirectoryPath) && !Directory.Exists(sdkDirectoryPath)) + if (!string.IsNullOrEmpty(sdkDirectoryPath) && !_fileSystem.Directory.Exists(sdkDirectoryPath)) { throw new ClangException( "Please install the software development kit (SDK) for Windows 10: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/"); @@ -142,22 +178,22 @@ private static void AddSystemIncludesWindows(ImmutableArray.Builder clan $"Unable to find a Windows SDK version. Expected a Windows SDK version at '{sdkDirectoryPath}'. Do you need install the a software development kit for Windows? https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/"); } - var systemIncludeCommandLineArgSdk = $@"-isystem{sdkHighestVersionDirectoryPath}\ucrt"; - clangArgumentsBuilder.Add(systemIncludeCommandLineArgSdk); + var systemIncludeCommandLineArgSdk = $@"{sdkHighestVersionDirectoryPath}\ucrt"; + directories.Add(systemIncludeCommandLineArgSdk); var vsWhereFilePath = Environment.ExpandEnvironmentVariables( @"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"); var visualStudioInstallationDirectoryPath = "-latest -property installationPath".ShellCaptureOutput(fileName: vsWhereFilePath); - if (!File.Exists(vsWhereFilePath) || string.IsNullOrEmpty(visualStudioInstallationDirectoryPath)) + if (!_fileSystem.File.Exists(vsWhereFilePath) || string.IsNullOrEmpty(visualStudioInstallationDirectoryPath)) { throw new ClangException( "Please install Visual Studio 2017 or later (community, professional, or enterprise)."); } var mscvVersionsDirectoryPath = Path.Combine(visualStudioInstallationDirectoryPath, @"VC\Tools\MSVC"); - if (!Directory.Exists(mscvVersionsDirectoryPath)) + if (!_fileSystem.Directory.Exists(mscvVersionsDirectoryPath)) { throw new ClangException( $"Please install the Microsoft Visual C++ (MSVC) build tools for Visual Studio ({visualStudioInstallationDirectoryPath})."); @@ -171,19 +207,18 @@ private static void AddSystemIncludesWindows(ImmutableArray.Builder clan } var mscvIncludeDirectoryPath = Path.Combine(mscvHighestVersionDirectoryPath, "include"); - if (!Directory.Exists(mscvIncludeDirectoryPath)) + if (!_fileSystem.Directory.Exists(mscvIncludeDirectoryPath)) { throw new ClangException( $"Please install Microsoft Visual C++ (MSVC) build tools for Visual Studio ({visualStudioInstallationDirectoryPath})."); } - var systemIncludeCommandLineArg = $"-isystem{mscvIncludeDirectoryPath}"; - clangArgumentsBuilder.Add(systemIncludeCommandLineArg); + directories.Add(mscvIncludeDirectoryPath); } - private static void AddSystemIncludesMac(ImmutableArray.Builder clangArgumentsBuilder) + private void SystemIncludesDirectoriesMacOsHost(ImmutableArray.Builder directories) { - if (!Directory.Exists("/Library/Developer/CommandLineTools")) + if (!_fileSystem.Directory.Exists("/Library/Developer/CommandLineTools")) { throw new ClangException( "Please install CommandLineTools for macOS. This will install Clang. Use the command `xcode-select --install`."); @@ -198,30 +233,32 @@ private static void AddSystemIncludesMac(ImmutableArray.Builder clangArg $"Unable to find a version of clang. Expected a version of clang at '{commandLineToolsClangDirectoryPath}'. Do you need to install CommandLineTools for macOS?"); } - var systemIncludeCommandLineArgClang = $"-isystem{clangHighestVersionDirectoryPath}/include"; - clangArgumentsBuilder.Add(systemIncludeCommandLineArgClang); + var systemIncludeCommandLineArgClang = $"{clangHighestVersionDirectoryPath}/include"; + directories.Add(systemIncludeCommandLineArgClang); + } + private void SystemIncludesDirectoriesMacOsTarget(ImmutableArray.Builder directories) + { var softwareDevelopmentKitDirectoryPath = "xcrun --sdk macosx --show-sdk-path".ShellCaptureOutput(); - if (!Directory.Exists(softwareDevelopmentKitDirectoryPath)) + if (!_fileSystem.Directory.Exists(softwareDevelopmentKitDirectoryPath)) { throw new ClangException( "Please install XCode for macOS. This will install the software development kit (SDK) which gives access to common C/C++/ObjC headers."); } - var systemIncludeCommandLineArgSdk = $"-isystem{softwareDevelopmentKitDirectoryPath}/usr/include"; - clangArgumentsBuilder.Add(systemIncludeCommandLineArgSdk); + directories.Add($"{softwareDevelopmentKitDirectoryPath}/usr/include"); } - private static string GetHighestVersionDirectoryPathFrom(string sdkDirectoryPath) + private string GetHighestVersionDirectoryPathFrom(string sdkDirectoryPath) { - var versionDirectoryPaths = Directory.EnumerateDirectories(sdkDirectoryPath); + var versionDirectoryPaths = _fileSystem.Directory.EnumerateDirectories(sdkDirectoryPath); var result = string.Empty; var highestVersion = Version.Parse("0.0.0"); foreach (var directoryPath in versionDirectoryPaths) { - var versionStringIndex = directoryPath.LastIndexOf(Path.DirectorySeparatorChar); + var versionStringIndex = directoryPath.LastIndexOf(_fileSystem.Path.DirectorySeparatorChar); var versionString = directoryPath[(versionStringIndex + 1)..]; if (!Version.TryParse(versionString, out var version)) { diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangTranslationUnitParser.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangTranslationUnitParser.cs new file mode 100644 index 00000000..e73f2675 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/ClangTranslationUnitParser.cs @@ -0,0 +1,127 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Domain.ParseCode.Diagnostics; +using C2CS.Foundation.Diagnostics; +using Microsoft.Extensions.Logging; +using static bottlenoselabs.clang; + +namespace C2CS.Feature.ReadCodeC.Domain.ParseCode; + +public sealed class ClangTranslationUnitParser +{ + private readonly ILogger _logger; + + public ClangTranslationUnitParser(ILogger logger) + { + _logger = logger; + } + + public CXTranslationUnit Parse( + DiagnosticsSink diagnosticsSink, + string filePath, + ImmutableArray arguments) + { + var argumentsString = string.Join(" ", arguments); + + if (!TryParseTranslationUnit(filePath, arguments, out var translationUnit)) + { + var up = new ClangException(); + _logger.ParseTranslationUnitFailedArguments(filePath, argumentsString, up); + throw up; + } + + var clangDiagnostics = GetClangDiagnostics(translationUnit); + var isSuccess = true; + if (!clangDiagnostics.IsDefaultOrEmpty) + { + var defaultDisplayOptions = clang_defaultDiagnosticDisplayOptions(); + foreach (var clangDiagnostic in clangDiagnostics) + { + var clangString = clang_formatDiagnostic(clangDiagnostic, defaultDisplayOptions); + var diagnosticStringC = clang_getCString(clangString); + var diagnosticString = Runtime.CStrings.String(diagnosticStringC); + var severity = clang_getDiagnosticSeverity(clangDiagnostic); + + var diagnosticSeverity = severity switch + { + CXDiagnosticSeverity.CXDiagnostic_Fatal => DiagnosticSeverity.Panic, + CXDiagnosticSeverity.CXDiagnostic_Error => DiagnosticSeverity.Error, + CXDiagnosticSeverity.CXDiagnostic_Warning => DiagnosticSeverity.Warning, + CXDiagnosticSeverity.CXDiagnostic_Note => DiagnosticSeverity.Information, + CXDiagnosticSeverity.CXDiagnostic_Ignored => DiagnosticSeverity.Information, + _ => DiagnosticSeverity.Error + }; + + if (severity == CXDiagnosticSeverity.CXDiagnostic_Error || + severity == CXDiagnosticSeverity.CXDiagnostic_Fatal) + { + isSuccess = false; + } + + var diagnostic = new ClangTranslationUnitParserDiagnostic(diagnosticSeverity, diagnosticString); + diagnosticsSink.Add(diagnostic); + } + } + + if (isSuccess) + { + _logger.ParseTranslationUnitSuccess(filePath, argumentsString, clangDiagnostics.Length); + } + else + { + _logger.ParseTranslationUnitFailedDiagnostics(filePath, argumentsString, clangDiagnostics.Length); + } + + return translationUnit; + } + + private static unsafe bool TryParseTranslationUnit( + string filePath, + ImmutableArray commandLineArgs, + out CXTranslationUnit translationUnit) + { + // ReSharper disable BitwiseOperatorOnEnumWithoutFlags + const uint options = 0x00001000 | // CXTranslationUnit_IncludeAttributedTypes + 0x00004000 | // CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles + 0x00000040 | // CXTranslationUnit_SkipFunctionBodies + 0x1 | // CXTranslationUnit_DetailedPreprocessingRecord + 0x0; + + var index = clang_createIndex(0, 0); + var cSourceFilePath = Runtime.CStrings.CString(filePath); + var cCommandLineArgs = Runtime.CStrings.CStringArray(commandLineArgs.AsSpan()); + + CXErrorCode errorCode; + fixed (CXTranslationUnit* translationUnitPointer = &translationUnit) + { + errorCode = clang_parseTranslationUnit2( + index, + cSourceFilePath, + cCommandLineArgs, + commandLineArgs.Length, + (CXUnsavedFile*)IntPtr.Zero, + 0, + options, + translationUnitPointer); + } + + var result = errorCode == CXErrorCode.CXError_Success; + return result; + } + + private static ImmutableArray GetClangDiagnostics(CXTranslationUnit translationUnit) + { + var diagnosticsCount = (int)clang_getNumDiagnostics(translationUnit); + var builder = ImmutableArray.CreateBuilder(diagnosticsCount); + + for (uint i = 0; i < diagnosticsCount; ++i) + { + var diagnostic = clang_getDiagnostic(translationUnit, i); + builder.Add(diagnostic); + } + + return builder.ToImmutable(); + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Diagnostics/ClangTranslationUnitParserDiagnostic.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Diagnostics/ClangTranslationUnitParserDiagnostic.cs new file mode 100644 index 00000000..f0079317 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Diagnostics/ClangTranslationUnitParserDiagnostic.cs @@ -0,0 +1,14 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.ReadCodeC.Domain.ParseCode.Diagnostics; + +public sealed class ClangTranslationUnitParserDiagnostic : Diagnostic +{ + public ClangTranslationUnitParserDiagnostic(DiagnosticSeverity severity, string message) + : base(severity, message) + { + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Logging.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Logging.cs new file mode 100644 index 00000000..5fb97ea8 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ParseCode/Logging.cs @@ -0,0 +1,44 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Foundation; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.ReadCodeC.Domain.ParseCode; + +internal static class Logging +{ + private static readonly Action ActionParseFailedArguments = + LoggerMessage.Define( + LogLevel.Error, + LoggingEventRegistry.CreateEventIdentifier("Parse translation unit: Failed"), + "- Failed. The arguments are incorrect or invalid. Path: {FilePath} ; Clang arguments: {Arguments}"); + + private static readonly Action ActionParseSuccess = + LoggerMessage.Define( + LogLevel.Trace, + LoggingEventRegistry.CreateEventIdentifier("Parse translation unit: Success"), + "- Success. Path: {FilePath} ; Clang arguments: {Arguments} ; Diagnostics: {DiagnosticsCount}"); + + private static readonly Action ActionParseFailedDiagnostics = + LoggerMessage.Define( + LogLevel.Error, + LoggingEventRegistry.CreateEventIdentifier("Parse translation unit: Failed"), + "- Failed. One or more Clang diagnostics are reported when parsing that are an error or fatal. Path: {FilePath} ; Clang arguments: {Arguments} ; Diagnostics: {DiagnosticsCount}"); + + public static void ParseTranslationUnitFailedArguments(this ILogger logger, string filePath, string arguments, Exception exception) + { + ActionParseFailedArguments(logger, filePath, arguments, exception); + } + + public static void ParseTranslationUnitSuccess(this ILogger logger, string filePath, string arguments, int diagnosticsCount) + { + ActionParseSuccess(logger, filePath, arguments, diagnosticsCount, null!); + } + + public static void ParseTranslationUnitFailedDiagnostics(this ILogger logger, string filePath, string arguments, int diagnosticsCount) + { + ActionParseFailedDiagnostics(logger, filePath, arguments, diagnosticsCount, null!); + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCAbstractSyntaxTreeOptions.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCAbstractSyntaxTreeOptions.cs new file mode 100644 index 00000000..299483c3 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCAbstractSyntaxTreeOptions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.ReadCodeC.Domain; + +public sealed class ReadCodeCAbstractSyntaxTreeOptions +{ + public string OutputFilePath { get; init; } = string.Empty; + + public bool IsEnabledFindSystemHeaders { get; init; } + + public TargetPlatform TargetPlatform { get; init; } + + public ImmutableArray IncludeDirectories { get; init; } + + public ImmutableArray ExcludedHeaderFiles { get; init; } + + public ImmutableArray OpaqueTypeNames { get; init; } + + public ImmutableArray FunctionNamesWhitelist { get; init; } + + public ImmutableArray ClangDefines { get; init; } + + public ImmutableArray ClangArguments { get; init; } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCInput.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCInput.cs new file mode 100644 index 00000000..0a8bb471 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCInput.cs @@ -0,0 +1,13 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.ReadCodeC.Domain; + +public sealed class ReadCodeCInput +{ + public string InputFilePath { get; init; } = string.Empty; + + public ImmutableArray AbstractSyntaxTreesOptions { get; set; } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCOutput.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCOutput.cs new file mode 100644 index 00000000..9054be5f --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCOutput.cs @@ -0,0 +1,12 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Foundation.UseCases; + +namespace C2CS.Feature.ReadCodeC.Domain; + +public sealed class ReadCodeCOutput : UseCaseOutput +{ + public ImmutableArray AbstractSyntaxTreesOptions { get; set; } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCValidator.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCValidator.cs new file mode 100644 index 00000000..628dc418 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Domain/ReadCodeCValidator.cs @@ -0,0 +1,170 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Foundation; +using C2CS.Foundation.UseCases; +using C2CS.Foundation.UseCases.Exceptions; + +namespace C2CS.Feature.ReadCodeC.Domain; + +public sealed class ReadCodeCValidator : UseCaseValidator +{ + public override ReadCodeCInput Validate(ReadCodeCConfiguration configuration) + { + var inputFilePath = VerifyInputFilePath(configuration.InputFilePath); + + var optionsBuilder = ImmutableArray.CreateBuilder(); + if (configuration.ConfigurationAbstractSyntaxTrees == null) + { + var abstractSyntaxTreeRequests = new Dictionary(); + var targetPlatform = Native.Platform; + abstractSyntaxTreeRequests.Add(targetPlatform.ToString(), null); + configuration.ConfigurationAbstractSyntaxTrees = abstractSyntaxTreeRequests; + } + + foreach (var (targetPlatformString, configurationAbstractSyntaxTree) in configuration.ConfigurationAbstractSyntaxTrees) + { + var targetPlatform = VerifyTargetPlatform(targetPlatformString); + var outputFilePath = VerifyOutputFilePath(configurationAbstractSyntaxTree?.OutputFileDirectory, targetPlatform); + var isEnabledFindSystemHeaders = configurationAbstractSyntaxTree?.IsEnabledFindSystemHeaders ?? true; + var includeDirectories = + VerifyIncludeDirectories(configurationAbstractSyntaxTree?.IncludeDirectories, inputFilePath); + var excludedHeaderFiles = VerifyImmutableArray(configurationAbstractSyntaxTree?.ExcludedHeaderFiles); + var opaqueTypeNames = VerifyImmutableArray(configurationAbstractSyntaxTree?.OpaqueTypeNames); + var functionNamesWhitelist = VerifyImmutableArray(configurationAbstractSyntaxTree?.FunctionNamesWhiteList); + var clangDefines = VerifyImmutableArray(configurationAbstractSyntaxTree?.Defines); + var clangArguments = VerifyImmutableArray(configurationAbstractSyntaxTree?.ClangArguments); + + var inputAbstractSyntaxTree = new ReadCodeCAbstractSyntaxTreeOptions + { + TargetPlatform = targetPlatform, + OutputFilePath = outputFilePath, + IsEnabledFindSystemHeaders = isEnabledFindSystemHeaders, + IncludeDirectories = includeDirectories, + ExcludedHeaderFiles = excludedHeaderFiles, + OpaqueTypeNames = opaqueTypeNames, + FunctionNamesWhitelist = functionNamesWhitelist, + ClangDefines = clangDefines, + ClangArguments = clangArguments + }; + + optionsBuilder.Add(inputAbstractSyntaxTree); + } + + return new ReadCodeCInput + { + InputFilePath = inputFilePath, + AbstractSyntaxTreesOptions = optionsBuilder.ToImmutable() + }; + } + + private static string VerifyInputFilePath(string? inputFilePath) + { + if (string.IsNullOrEmpty(inputFilePath)) + { + throw new ConfigurationException("The input file can not be null, empty, or whitespace."); + } + + var filePath = Path.GetFullPath(inputFilePath); + + if (!File.Exists(filePath)) + { + throw new UseCaseException($"The input file does not exist: `{filePath}`."); + } + + return filePath; + } + + private static string VerifyOutputFilePath(string? outputFileDirectory, TargetPlatform targetPlatform) + { + string directoryPath; + // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression + if (string.IsNullOrEmpty(outputFileDirectory)) + { + directoryPath = Path.Combine(Environment.CurrentDirectory, "ast"); + } + else + { + directoryPath = Path.GetFullPath(outputFileDirectory); + } + + var defaultFilePath = Path.Combine(directoryPath, targetPlatform + ".json"); + return defaultFilePath; + } + + private static TargetPlatform VerifyTargetPlatform(string? targetPlatformString) + { + if (string.IsNullOrEmpty(targetPlatformString)) + { + throw new UseCaseException("Platform can not be null."); + } + + var platform = new TargetPlatform(targetPlatformString); + + if (platform.Architecture == NativeArchitecture.Unknown && platform.OperatingSystem == NativeOperatingSystem.Unknown) + { + throw new UseCaseException($"Unknown platform `{platform}`."); + } + + if (platform.OperatingSystem == NativeOperatingSystem.Unknown) + { + throw new UseCaseException($"Unknown operating system for platform '{platform}'."); + } + + if (platform.Architecture == NativeArchitecture.Unknown) + { + throw new UseCaseException($"Unknown architecture for platform '{platform}'."); + } + + return platform; + } + + private static ImmutableArray VerifyIncludeDirectories( + ImmutableArray? includeDirectories, + string inputFilePath) + { + var result = VerifyImmutableArray(includeDirectories); + + if (result.IsDefaultOrEmpty) + { + var directoryPath = Path.GetDirectoryName(inputFilePath)!; + if (string.IsNullOrEmpty(directoryPath)) + { + directoryPath = Environment.CurrentDirectory; + } + + result = new[] + { + Path.GetFullPath(directoryPath) + }.ToImmutableArray(); + } + else + { + result = result.Select(Path.GetFullPath).ToImmutableArray(); + } + + foreach (var includeDirectory in result) + { + if (!Directory.Exists(includeDirectory)) + { + throw new UseCaseException($"The include directory does not exist: `{includeDirectory}`."); + } + } + + return result; + } + + private static ImmutableArray VerifyImmutableArray(ImmutableArray? array) + { + if (array == null || array.Value.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var result = array.Value + .Where(x => !string.IsNullOrEmpty(x)).Cast().ToImmutableArray(); + return result; + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/ReadCodeCUseCase.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/ReadCodeCUseCase.cs new file mode 100644 index 00000000..82e1cb71 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/ReadCodeCUseCase.cs @@ -0,0 +1,143 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.ReadCodeC.Data.Serialization; +using C2CS.Feature.ReadCodeC.Domain; +using C2CS.Feature.ReadCodeC.Domain.ExploreCode; +using C2CS.Feature.ReadCodeC.Domain.InstallClang; +using C2CS.Feature.ReadCodeC.Domain.ParseCode; +using C2CS.Foundation.UseCases; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using static bottlenoselabs.clang; + +namespace C2CS.Feature.ReadCodeC; + +public sealed class ReadCodeCUseCase : UseCase< + ReadCodeCConfiguration, ReadCodeCInput, ReadCodeCOutput> +{ + private readonly IServiceProvider _services; + + public override string Name => "Extract AST C"; + + public ReadCodeCUseCase(ILogger logger, IServiceProvider services, ReadCodeCValidator validator) + : base(logger, services, validator) + { + _services = services; + } + + protected override void Execute(ReadCodeCInput input, ReadCodeCOutput output) + { + InstallClang(Native.OperatingSystem); + + var builder = ImmutableArray.CreateBuilder(); + + foreach (var options in input.AbstractSyntaxTreesOptions) + { + var translationUnit = Parse( + input.InputFilePath, + options.IsEnabledFindSystemHeaders, + options.IncludeDirectories, + options.ClangDefines, + options.TargetPlatform, + options.ClangArguments); + + if (!translationUnit.HasValue) + { + continue; + } + + var abstractSyntaxTreeC = Explore( + translationUnit.Value, + options.IncludeDirectories, + options.ExcludedHeaderFiles, + options.OpaqueTypeNames, + options.FunctionNamesWhitelist, + options.TargetPlatform); + + Write(options.OutputFilePath, abstractSyntaxTreeC, options.TargetPlatform); + + builder.Add(options); + } + + output.AbstractSyntaxTreesOptions = builder.ToImmutable(); + } + + private void InstallClang(NativeOperatingSystem operatingSystem) + { + BeginStep($"Install Clang {operatingSystem}"); + + var installer = _services.GetService()!; + installer.Install(operatingSystem); + + EndStep(); + } + + private CXTranslationUnit? Parse( + string inputFilePath, + bool automaticallyFindSystemHeaders, + ImmutableArray includeDirectories, + ImmutableArray defines, + TargetPlatform targetPlatform, + ImmutableArray clangArguments) + { + BeginStep($"Parse {targetPlatform}"); + + var clangArgumentsBuilder = _services.GetService()!; + + var arguments = clangArgumentsBuilder.Build( + automaticallyFindSystemHeaders, + includeDirectories, + defines, + targetPlatform, + clangArguments); + + if (arguments == null) + { + EndStep(); + return null; + } + + var parser = _services.GetService()!; + var result = parser.Parse( + Diagnostics, inputFilePath, arguments.Value); + + EndStep(); + return result; + } + + private CAbstractSyntaxTree Explore( + CXTranslationUnit translationUnit, + ImmutableArray includeDirectories, + ImmutableArray excludedHeaderFiles, + ImmutableArray opaqueTypeNames, + ImmutableArray functionNamesWhitelist, + TargetPlatform platform) + { + BeginStep($"Extract {platform}"); + + var context = new ClangTranslationUnitExplorerContext( + Diagnostics, + includeDirectories, + excludedHeaderFiles, + opaqueTypeNames, + functionNamesWhitelist, + platform); + var clangExplorer = _services.GetService()!; + var result = clangExplorer.AbstractSyntaxTree(context, translationUnit); + + EndStep(); + return result; + } + + private void Write( + string outputFilePath, CAbstractSyntaxTree abstractSyntaxTree, TargetPlatform platform) + { + BeginStep($"Write {platform}"); + var cJsonSerializer = _services.GetService()!; + cJsonSerializer.Write(abstractSyntaxTree, outputFilePath); + EndStep(); + } +} diff --git a/src/cs/production/features/C2CS.Feature.ReadCodeC/Startup.cs b/src/cs/production/features/C2CS.Feature.ReadCodeC/Startup.cs new file mode 100644 index 00000000..9cb85610 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.ReadCodeC/Startup.cs @@ -0,0 +1,30 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Feature.ReadCodeC.Data.Serialization; +using C2CS.Feature.ReadCodeC.Domain; +using C2CS.Feature.ReadCodeC.Domain.ExploreCode; +using C2CS.Feature.ReadCodeC.Domain.InstallClang; +using C2CS.Feature.ReadCodeC.Domain.ParseCode; +using Microsoft.Extensions.DependencyInjection; + +namespace C2CS.Feature.ReadCodeC; + +public static class Startup +{ + public static void ConfigureServices(IServiceCollection services) + { + // Data + services.AddSingleton(); + + // Logic + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // Use case + services.AddTransient(); + services.AddSingleton(); + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj new file mode 100644 index 00000000..ccc7625b --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj @@ -0,0 +1,27 @@ + + + + + net6.0 + enable + enable + $(NoWarn);CA1724 + + + + + + + + + + + + + + + + + + + diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj.DotSettings b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj.DotSettings new file mode 100644 index 00000000..e0625894 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/C2CS.Feature.WriteCodeCSharp.csproj.DotSettings @@ -0,0 +1,7 @@ + + False + False + False + False + True + False \ No newline at end of file diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAbstractSyntaxTree.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAbstractSyntaxTree.cs new file mode 100644 index 00000000..406971e2 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAbstractSyntaxTree.cs @@ -0,0 +1,14 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed record CSharpAbstractSyntaxTree +{ + public CSharpNodes PlatformAgnosticNodes { get; init; } = null!; + + public ImmutableArray<(TargetPlatform Platform, CSharpNodes Nodes)> PlatformSpecificNodes { get; init; } = + ImmutableArray<(TargetPlatform Platform, CSharpNodes Nodes)>.Empty; +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAliasStruct.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAliasStruct.cs new file mode 100644 index 00000000..5869ec26 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpAliasStruct.cs @@ -0,0 +1,30 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpAliasStruct : CSharpNode +{ + public readonly CSharpType UnderlyingType; + + public CSharpAliasStruct( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + CSharpType underlyingType) + : base(platform, name, codeLocationComment, sizeOf) + { + UnderlyingType = underlyingType; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpAliasStruct other2) + { + return false; + } + + return UnderlyingType == other2.UnderlyingType; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpConstant.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpConstant.cs new file mode 100644 index 00000000..fee52596 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpConstant.cs @@ -0,0 +1,35 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpConstant : CSharpNode +{ + public readonly string Type; + + public readonly string Value; + + public CSharpConstant( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + string type, + string value) + : base(platform, name, codeLocationComment, sizeOf) + { + Type = type; + Value = value; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpConstant other2) + { + return false; + } + + return Type == other2.Type && + Value == other2.Value; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnum.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnum.cs new file mode 100644 index 00000000..883a99bb --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnum.cs @@ -0,0 +1,37 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpEnum : CSharpNode +{ + public readonly CSharpType IntegerType; + + public readonly ImmutableArray Values; + + public CSharpEnum( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + CSharpType integerType, + ImmutableArray values) + : base(platform, name, codeLocationComment, sizeOf) + { + IntegerType = integerType; + Values = values; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpEnum other2) + { + return false; + } + + return IntegerType == other2.IntegerType && + Values.SequenceEqual(other2.Values); + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnumValue.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnumValue.cs new file mode 100644 index 00000000..84156a79 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpEnumValue.cs @@ -0,0 +1,30 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpEnumValue : CSharpNode +{ + public readonly long Value; + + public CSharpEnumValue( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + long value) + : base(platform, name, codeLocationComment, sizeOf) + { + Value = value; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpEnumValue other2) + { + return false; + } + + return Value == other2.Value; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunction.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunction.cs new file mode 100644 index 00000000..6e2865a0 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunction.cs @@ -0,0 +1,44 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpFunction : CSharpNode +{ + public readonly CSharpFunctionCallingConvention CallingConvention; + + public readonly ImmutableArray Parameters; + + public readonly CSharpType ReturnType; + + public CSharpFunction( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + CSharpFunctionCallingConvention callingConvention, + CSharpType returnType, + ImmutableArray parameters) + : base(platform, name, codeLocationComment, sizeOf) + { + CallingConvention = callingConvention; + ReturnType = returnType; + Parameters = parameters; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpFunction other2) + { + return false; + } + + var result = CallingConvention == other2.CallingConvention && + ReturnType == other2.ReturnType && + Parameters.SequenceEqual(other2.Parameters); + + return result; + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionCallingConvention.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionCallingConvention.cs similarity index 86% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionCallingConvention.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionCallingConvention.cs index 8ad7fe67..21c4e5ef 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpFunctionCallingConvention.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionCallingConvention.cs @@ -1,7 +1,7 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.BindgenCSharp.Data; +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; public enum CSharpFunctionCallingConvention { diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionParameter.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionParameter.cs new file mode 100644 index 00000000..b96e02c2 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionParameter.cs @@ -0,0 +1,31 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpFunctionParameter : CSharpNode +{ + public readonly CSharpType Type; + + public CSharpFunctionParameter( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + CSharpType type) + : base(platform, name, codeLocationComment, sizeOf) + { + Type = type; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpFunctionParameter other2) + { + return false; + } + + var result = Type == other2.Type; + return result; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointer.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointer.cs new file mode 100644 index 00000000..00121b30 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointer.cs @@ -0,0 +1,37 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpFunctionPointer : CSharpNode +{ + public readonly ImmutableArray Parameters; + + public readonly CSharpType ReturnType; + + public CSharpFunctionPointer( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf, + CSharpType returnType, + ImmutableArray parameters) + : base(platform, name, codeLocationComment, sizeOf) + { + Parameters = parameters; + ReturnType = returnType; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpFunctionPointer other2) + { + return false; + } + + return ReturnType == other2.ReturnType && + Parameters.SequenceEqual(other2.Parameters); + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointerParameter.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointerParameter.cs new file mode 100644 index 00000000..a74b9953 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpFunctionPointerParameter.cs @@ -0,0 +1,26 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpFunctionPointerParameter : CSharpNode +{ + public readonly CSharpType Type; + + public CSharpFunctionPointerParameter( + TargetPlatform platform, string name, string codeLocationComment, int? sizeOf, CSharpType type) + : base(platform, name, codeLocationComment, sizeOf) + { + Type = type; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpFunctionPointerParameter other2) + { + return false; + } + + return Type == other2.Type; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNode.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNode.cs new file mode 100644 index 00000000..5a3ef42c --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNode.cs @@ -0,0 +1,69 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public abstract class CSharpNode : IEquatable +{ + public readonly TargetPlatform Platform; + + public readonly string Name; + + public readonly string CodeLocationComment; + + public readonly int? SizeOf; + + protected CSharpNode(TargetPlatform platform, string? name, string? codeLocationComment, int? sizeOf) + { + Platform = platform; + Name = string.IsNullOrEmpty(name) ? string.Empty : name; + CodeLocationComment = string.IsNullOrEmpty(codeLocationComment) ? string.Empty : codeLocationComment; + SizeOf = sizeOf; + } + + public override string ToString() + { + return $"{Name} {CodeLocationComment} {Platform}"; + } + + public virtual bool Equals(CSharpNode? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + var result = Name == other.Name && SizeOf == other.SizeOf; + if (!result) + { + Console.WriteLine(); + } + + return result; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((CSharpNode)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, CodeLocationComment, SizeOf); + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpAbstractSyntaxTree.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNodes.cs similarity index 61% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpAbstractSyntaxTree.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNodes.cs index 84fb9434..75a60a66 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpAbstractSyntaxTree.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpNodes.cs @@ -3,23 +3,21 @@ using System.Collections.Immutable; -namespace C2CS.Feature.BindgenCSharp.Data; +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; -public class CSharpAbstractSyntaxTree +public sealed record CSharpNodes { - public ImmutableArray FunctionExterns { get; init; } + public ImmutableArray Functions { get; init; } public ImmutableArray FunctionPointers { get; init; } public ImmutableArray Structs { get; init; } - public ImmutableArray Typedefs { get; init; } + public ImmutableArray AliasStructs { get; init; } - public ImmutableArray OpaqueDataTypes { get; init; } + public ImmutableArray OpaqueStructs { get; init; } public ImmutableArray Enums { get; init; } - public ImmutableArray PseudoEnums { get; init; } - public ImmutableArray Constants { get; init; } } diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpOpaqueStruct.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpOpaqueStruct.cs new file mode 100644 index 00000000..05670b49 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpOpaqueStruct.cs @@ -0,0 +1,26 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpOpaqueStruct : CSharpNode +{ + public CSharpOpaqueStruct( + TargetPlatform platform, + string name, + string codeLocationComment, + int? sizeOf) + : base(platform, name, codeLocationComment, sizeOf) + { + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpOpaqueStruct) + { + return false; + } + + return true; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStruct.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStruct.cs new file mode 100644 index 00000000..1d57adba --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStruct.cs @@ -0,0 +1,39 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; + +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; + +public sealed class CSharpStruct : CSharpNode +{ + public readonly ImmutableArray Fields; + public readonly ImmutableArray NestedStructs; + public readonly CSharpType Type; + + public CSharpStruct( + TargetPlatform platform, + string codeLocationComment, + int? sizeOf, + CSharpType type, + ImmutableArray fields, + ImmutableArray nestedStructs) + : base(platform, type.Name, codeLocationComment, sizeOf) + { + Fields = fields; + NestedStructs = nestedStructs; + Type = type; + } + + public override bool Equals(CSharpNode? other) + { + if (!base.Equals(other) || other is not CSharpStruct other2) + { + return false; + } + + return Type == other2.Type && + Fields.SequenceEqual(other2.Fields) && + NestedStructs.SequenceEqual(other2.NestedStructs); + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStructField.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStructField.cs similarity index 57% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStructField.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStructField.cs index f0419435..08af05cb 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpStructField.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpStructField.cs @@ -1,9 +1,9 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.BindgenCSharp.Data; +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; -public record CSharpStructField : CSharpNode +public sealed class CSharpStructField : CSharpNode { public readonly string BackingFieldName; public readonly bool IsWrapped; @@ -12,13 +12,15 @@ public record CSharpStructField : CSharpNode public readonly CSharpType Type; public CSharpStructField( + TargetPlatform platform, string name, string codeLocationComment, + int? sizeOf, CSharpType type, int offset, int padding, bool isWrapped) - : base(name, codeLocationComment) + : base(platform, name, codeLocationComment, sizeOf) { Type = type; Offset = offset; @@ -27,10 +29,17 @@ public CSharpStructField( BackingFieldName = name.StartsWith("@", StringComparison.InvariantCulture) ? $"_{name[1..]}" : $"_{name}"; } - // Required for debugger string with records - // ReSharper disable once RedundantOverriddenMember - public override string ToString() + public override bool Equals(CSharpNode? other) { - return base.ToString(); + if (!base.Equals(other) || other is not CSharpStructField other2) + { + return false; + } + + return BackingFieldName == other2.BackingFieldName && + IsWrapped == other2.IsWrapped && + Offset == other2.Offset && + Padding == other2.Padding && + Type == other2.Type; } } diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpType.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpType.cs similarity index 87% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpType.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpType.cs index b67d98d8..37cc5119 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpType.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpType.cs @@ -1,9 +1,9 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.BindgenCSharp.Data; +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; -public class CSharpType +public sealed record CSharpType { public string? Name { get; init; } diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypeAlias.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpTypeAlias.cs similarity index 81% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypeAlias.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpTypeAlias.cs index b7f3d658..b1e675dd 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Data/CSharpTypeAlias.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/Model/CSharpTypeAlias.cs @@ -2,13 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Text.Json.Serialization; -using JetBrains.Annotations; -namespace C2CS.Feature.BindgenCSharp.Data; +namespace C2CS.Feature.WriteCodeCSharp.Data.Model; // NOTE: Properties are required for System.Text.Json serialization -[PublicAPI] -public class CSharpTypeAlias +public sealed class CSharpTypeAlias { [JsonPropertyName("source")] public string Source { get; set; } = string.Empty; diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/WriteCodeCSharpConfiguration.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/WriteCodeCSharpConfiguration.cs new file mode 100644 index 00000000..ac94ffc0 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Data/WriteCodeCSharpConfiguration.cs @@ -0,0 +1,52 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using System.Text.Json.Serialization; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Foundation.UseCases; +using JetBrains.Annotations; + +namespace C2CS.Feature.WriteCodeCSharp.Data; + +// NOTE: Properties are required for System.Text.Json serialization +// NOTE: This class must have a unique name across namespaces for usage in System.Text.Json source generators. +[PublicAPI] +public sealed class WriteCodeCSharpConfiguration : UseCaseConfiguration +{ + [JsonIgnore] + [Json.Schema.Generation.Description("Path of the input abstract syntax tree directory. The directory should contain one or more previously generated abstract syntax tree `.json` files which each have a file name of the target platform.")] + public string? InputFileDirectory { get; set; } + + [JsonPropertyName("output_file")] + [Json.Schema.Generation.Description("Path of the output C# `.cs` file.")] + public string? OutputFilePath { get; set; } + + [JsonPropertyName("library_name")] + [Json.Schema.Generation.Description("The name of the dynamic link library (without the file extension) used for platform invoke (P/Invoke) with C#. If not specified, the library name is the same as the name of the `OutputFilePath` property without the directory name and without the file extension.")] + public string? LibraryName { get; set; } + + [JsonPropertyName("namespace_name")] + [Json.Schema.Generation.Description("The name of the namespace to be used for the C# static class. If not specified, the namespace is the same as the `LibraryName` property.")] + public string? NamespaceName { get; set; } + + [JsonPropertyName("class_name")] + [Json.Schema.Generation.Description("The name of the C# static class. If not specified, the class name is the same as the `LibraryName` property.")] + public string? ClassName { get; set; } + + [JsonPropertyName("region_header_file")] + [Json.Schema.Generation.Description("Path of the text file which to add the file's contents to the top of the C# file. Useful for comments, extra namespace using statements, or additional code that needs to be added to the generated C# file.")] + public string? HeaderCodeRegionFilePath { get; set; } + + [JsonPropertyName("region_footer_file")] + [Json.Schema.Generation.Description("Path of the text file which to add the file's contents to the bottom of the C# file. Useful for comments or additional code that needs to be added to the generated C# file.")] + public string? FooterCodeRegionFilePath { get; set; } + + [JsonPropertyName("mapped")] + [Json.Schema.Generation.Description("Pairs of strings for re-mapping type names. Each pair has source name and a target name. Does not change the bit layout of types.")] + public ImmutableArray? MappedTypeNames { get; set; } + + [JsonPropertyName("ignored")] + [Json.Schema.Generation.Description("Names of types, functions, enums, constants, or anything else that may be found when parsing C code that will be ignored when generating C# code. Type names are ignored after mapping type names using `MappedTypeNames` property.")] + public ImmutableArray? IgnoredNames { get; set; } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/BuilderCSharpAbstractSyntaxTree.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/BuilderCSharpAbstractSyntaxTree.cs new file mode 100644 index 00000000..58eb2b57 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/BuilderCSharpAbstractSyntaxTree.cs @@ -0,0 +1,378 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; + +namespace C2CS.Feature.WriteCodeCSharp.Domain.CodeGenerator; + +public sealed class BuilderCSharpAbstractSyntaxTree +{ + private readonly HashSet _platforms = new(); + private readonly Dictionary> _candidateNodesByName = new(); + + private readonly ImmutableArray.Builder _agnosticFunctions = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticFunctionPointers = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticStructs = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticAliasStructs = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticOpaqueStructs = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticEnums = ImmutableArray.CreateBuilder(); + private readonly ImmutableArray.Builder _agnosticConstants = ImmutableArray.CreateBuilder(); + + private readonly Dictionary.Builder> _functionsByPlatform = new(); + private readonly Dictionary.Builder> _functionPointersByPlatform = new(); + private readonly Dictionary.Builder> _structsByPlatform = new(); + private readonly Dictionary.Builder> _aliasStructsByPlatform = new(); + private readonly Dictionary.Builder> _opaqueStructsByPlatform = new(); + private readonly Dictionary.Builder> _enumsByPlatform = new(); + private readonly Dictionary.Builder> _constantsByPlatform = new(); + + public void Add(TargetPlatform platform, CSharpNodes nodes) + { + AddPlatform(platform); + AddCandidateFunctions(platform, nodes.Functions); + AddCandidateFunctionPointers(platform, nodes.FunctionPointers); + AddCandidateStructs(platform, nodes.Structs); + AddCandidateAliasStructs(platform, nodes.AliasStructs); + AddOpaqueStructs(platform, nodes.OpaqueStructs); + AddCandidateEnums(platform, nodes.Enums); + AddCandidateConstants(platform, nodes.Constants); + } + + private void AddPlatform(TargetPlatform platform) + { + var alreadyAdded = _platforms.Contains(platform); + if (alreadyAdded) + { + return; + } + + _platforms.Add(platform); + _functionsByPlatform[platform] = ImmutableArray.CreateBuilder(); + _functionPointersByPlatform[platform] = ImmutableArray.CreateBuilder(); + _structsByPlatform[platform] = ImmutableArray.CreateBuilder(); + _aliasStructsByPlatform[platform] = ImmutableArray.CreateBuilder(); + _enumsByPlatform[platform] = ImmutableArray.CreateBuilder(); + _constantsByPlatform[platform] = ImmutableArray.CreateBuilder(); + _opaqueStructsByPlatform[platform] = ImmutableArray.CreateBuilder(); + } + + public CSharpAbstractSyntaxTree Build() + { + foreach (var (_, nodes) in _candidateNodesByName) + { + CreateNodes(nodes); + } + + var ast = new CSharpAbstractSyntaxTree + { + PlatformAgnosticNodes = PlatformAgnosticNodes(), + PlatformSpecificNodes = PlatformSpecificNodes() + }; + + return ast; + } + + private void CreateNodes(List platformNodes) + { + if (platformNodes.Count == 0) + { + return; + } + + var allAreSame = true; + var firstPlatform = platformNodes.First(); + var firstNode = firstPlatform.CSharpNode; + foreach (var platformNode in platformNodes) + { + var canBeMerged = CanMergeNodes(platformNode.CSharpNode, firstNode); + if (canBeMerged) + { + continue; + } + + allAreSame = false; + break; + } + + if (allAreSame) + { + CreatePlatformAgnosticNode(firstNode); + } + else + { + foreach (var platformNode in platformNodes) + { + CreatePlatformSpecificNode(platformNode.Platform, platformNode.CSharpNode); + } + } + } + + private bool CanMergeNodes(CSharpNode firstNode, CSharpNode secondNode) + { + if (!firstNode.Equals(secondNode)) + { + var result = firstNode.Equals(secondNode); + return false; + } + + return true; + } + + private CSharpNodes PlatformAgnosticNodes() + { + var sharedNodes = new CSharpNodes + { + Functions = _agnosticFunctions.ToImmutable(), + FunctionPointers = _agnosticFunctionPointers.ToImmutable(), + Structs = _agnosticStructs.ToImmutable(), + AliasStructs = _agnosticAliasStructs.ToImmutable(), + OpaqueStructs = _agnosticOpaqueStructs.ToImmutable(), + Enums = _agnosticEnums.ToImmutable(), + Constants = _agnosticConstants.ToImmutable() + }; + + return sharedNodes; + } + + private ImmutableArray<(TargetPlatform Platform, CSharpNodes Nodes)> PlatformSpecificNodes() + { + var builder = ImmutableArray.CreateBuilder<(TargetPlatform, CSharpNodes)>(); + foreach (var platform in _platforms) + { + var platformNodes = BuildPlatformNodes(platform); + if (platformNodes == null) + { + continue; + } + + builder.Add((platform, platformNodes)); + } + + var platformSpecificNodes = builder.ToImmutable(); + return platformSpecificNodes; + } + + private CSharpNodes? BuildPlatformNodes(TargetPlatform platform) + { + var functions = _functionsByPlatform[platform].ToImmutableArray(); + var functionPointers = _functionPointersByPlatform[platform].ToImmutableArray(); + var structs = _structsByPlatform[platform].ToImmutableArray(); + var aliasStructs = _aliasStructsByPlatform[platform].ToImmutableArray(); + var opaqueStructs = _opaqueStructsByPlatform[platform].ToImmutableArray(); + var enums = _enumsByPlatform[platform].ToImmutableArray(); + var constants = _constantsByPlatform[platform].ToImmutableArray(); + + if (functions.IsDefaultOrEmpty && + functionPointers.IsDefaultOrEmpty && + structs.IsDefaultOrEmpty && + aliasStructs.IsDefaultOrEmpty && + opaqueStructs.IsDefaultOrEmpty && + enums.IsDefaultOrEmpty && + constants.IsDefaultOrEmpty) + { + return null; + } + + var nodes = new CSharpNodes + { + Functions = functions, + FunctionPointers = functionPointers, + Structs = structs, + AliasStructs = aliasStructs, + OpaqueStructs = opaqueStructs, + Enums = enums, + Constants = constants + }; + + return nodes; + } + + private void AddCandidateFunctions( + TargetPlatform platform, ImmutableArray functions) + { + foreach (var function in functions) + { + AddCandidateNode(platform, function); + } + } + + private void AddCandidateFunctionPointers( + TargetPlatform platform, ImmutableArray functionPointers) + { + foreach (var functionPointer in functionPointers) + { + AddCandidateNode(platform, functionPointer); + } + } + + private void AddCandidateStructs( + TargetPlatform platform, ImmutableArray structs) + { + foreach (var @struct in structs) + { + AddCandidateNode(platform, @struct); + } + } + + private void AddCandidateAliasStructs( + TargetPlatform platform, ImmutableArray aliasStructs) + { + foreach (var aliasStruct in aliasStructs) + { + AddCandidateNode(platform, aliasStruct); + } + } + + private void AddOpaqueStructs( + TargetPlatform platform, ImmutableArray opaqueDataTypes) + { + foreach (var opaqueType in opaqueDataTypes) + { + AddCandidateNode(platform, opaqueType); + } + } + + private void AddCandidateEnums( + TargetPlatform platform, ImmutableArray enums) + { + foreach (var @enum in enums) + { + AddCandidateNode(platform, @enum); + } + } + + private void AddCandidateConstants( + TargetPlatform platform, ImmutableArray constants) + { + foreach (var constant in constants) + { + AddCandidateNode(platform, constant); + } + } + + private void AddCandidateNode(TargetPlatform platform, CSharpNode node) + { + var candidateNode = new PlatformCandidateNode + { + Platform = platform, + CSharpNode = node + }; + + var isFirstTimeEncounteredName = !_candidateNodesByName.TryGetValue(node.Name, out var nodes); + if (isFirstTimeEncounteredName) + { + nodes = new List { candidateNode }; + _candidateNodesByName.Add(node.Name, nodes); + } + else + { + nodes!.Add(candidateNode); + } + } + + private void CreatePlatformAgnosticNode(CSharpNode node) + { + switch (node) + { + case CSharpFunction function: + AddNodeFunction(null, function); + break; + case CSharpFunctionPointer functionPointer: + AddNodeFunctionPointer(null, functionPointer); + break; + case CSharpStruct @struct: + AddNodeStruct(null, @struct); + break; + case CSharpAliasStruct aliasStruct: + AddNodeAliasStruct(null, aliasStruct); + break; + case CSharpOpaqueStruct opaqueStruct: + AddNodeOpaqueStruct(null, opaqueStruct); + break; + case CSharpEnum @enum: + AddNodeEnum(null, @enum); + break; + case CSharpConstant constant: + AddNodeConstant(null, constant); + break; + } + } + + private void CreatePlatformSpecificNode(TargetPlatform platform, CSharpNode node) + { + switch (node) + { + case CSharpFunction function: + AddNodeFunction(platform, function); + break; + case CSharpFunctionPointer functionPointer: + AddNodeFunctionPointer(platform, functionPointer); + break; + case CSharpStruct @struct: + AddNodeStruct(platform, @struct); + break; + case CSharpAliasStruct aliasStruct: + AddNodeAliasStruct(platform, aliasStruct); + break; + case CSharpOpaqueStruct opaqueStruct: + AddNodeOpaqueStruct(platform, opaqueStruct); + break; + case CSharpEnum @enum: + AddNodeEnum(platform, @enum); + break; + case CSharpConstant constant: + AddNodeConstant(platform, constant); + break; + } + } + + private void AddNodeFunction(TargetPlatform? platform, CSharpFunction node) + { + var builder = platform != null ? _functionsByPlatform[platform.Value] : _agnosticFunctions; + builder.Add(node); + } + + private void AddNodeFunctionPointer(TargetPlatform? platform, CSharpFunctionPointer node) + { + var builder = platform != null ? _functionPointersByPlatform[platform.Value] : _agnosticFunctionPointers; + builder.Add(node); + } + + private void AddNodeStruct(TargetPlatform? platform, CSharpStruct node) + { + var builder = platform != null ? _structsByPlatform[platform.Value] : _agnosticStructs; + builder.Add(node); + } + + private void AddNodeAliasStruct(TargetPlatform? platform, CSharpAliasStruct node) + { + var builder = platform != null ? _aliasStructsByPlatform[platform.Value] : _agnosticAliasStructs; + builder.Add(node); + } + + private void AddNodeOpaqueStruct(TargetPlatform? platform, CSharpOpaqueStruct node) + { + var builder = platform != null ? _opaqueStructsByPlatform[platform.Value] : _agnosticOpaqueStructs; + builder.Add(node); + } + + private void AddNodeEnum(TargetPlatform? platform, CSharpEnum node) + { + var builder = platform != null ? _enumsByPlatform[platform.Value] : _agnosticEnums; + builder.Add(node); + } + + private void AddNodeConstant(TargetPlatform? platform, CSharpConstant node) + { + var builder = platform != null ? _constantsByPlatform[platform.Value] : _agnosticConstants; + builder.Add(node); + } + + private record struct PlatformCandidateNode + { + public TargetPlatform Platform; + public CSharpNode CSharpNode; + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpCodeGenerator.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/GeneratorCSharpCode.cs similarity index 76% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpCodeGenerator.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/GeneratorCSharpCode.cs index 64be6031..a62b4c69 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpCodeGenerator.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/CodeGenerator/GeneratorCSharpCode.cs @@ -3,16 +3,18 @@ using System.Collections.Immutable; using System.Reflection; -using C2CS.Feature.BindgenCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Foundation.UseCases.Exceptions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace C2CS.Feature.BindgenCSharp.Logic; +namespace C2CS.Feature.WriteCodeCSharp.Domain.CodeGenerator; -public class CSharpCodeGenerator +public sealed class GeneratorCSharpCode { private readonly string _className; private readonly string _libraryName; @@ -20,7 +22,7 @@ public class CSharpCodeGenerator private readonly string _headerCodeRegion; private readonly string _footerCodeRegion; - public CSharpCodeGenerator( + public GeneratorCSharpCode( string className, string libraryName, string namespaceName, @@ -36,17 +38,40 @@ public CSharpCodeGenerator( public string EmitCode(CSharpAbstractSyntaxTree abstractSyntaxTree) { - var builder = ImmutableArray.CreateBuilder(); - FunctionExterns(builder, abstractSyntaxTree.FunctionExterns); - FunctionPointers(builder, abstractSyntaxTree.FunctionPointers); - Structs(builder, abstractSyntaxTree.Structs); - OpaqueDataTypes(builder, abstractSyntaxTree.OpaqueDataTypes); - Typedefs(builder, abstractSyntaxTree.Typedefs); - Enums(builder, abstractSyntaxTree.Enums); - PseudoEnums(builder, abstractSyntaxTree.PseudoEnums); - Constants(builder, abstractSyntaxTree.Constants); - - var members = builder.ToArray(); + var members = new List(); + + var sharedNodes = abstractSyntaxTree.PlatformAgnosticNodes; + FunctionExterns(members, sharedNodes.Functions); + FunctionPointers(members, sharedNodes.FunctionPointers); + Structs(members, sharedNodes.Structs); + OpaqueDataTypes(members, sharedNodes.OpaqueStructs); + Typedefs(members, sharedNodes.AliasStructs); + Enums(members, sharedNodes.Enums); + Constants(members, sharedNodes.Constants); + + var platformSpecificNodes = abstractSyntaxTree.PlatformSpecificNodes; + if (!platformSpecificNodes.IsDefaultOrEmpty) + { + foreach (var (platform, nodes) in platformSpecificNodes) + { + var platformSpecificMembers = new List(); + + FunctionExterns(platformSpecificMembers, nodes.Functions); + FunctionPointers(platformSpecificMembers, nodes.FunctionPointers); + Structs(platformSpecificMembers, nodes.Structs); + OpaqueDataTypes(platformSpecificMembers, nodes.OpaqueStructs); + Typedefs(platformSpecificMembers, nodes.AliasStructs); + Enums(platformSpecificMembers, nodes.Enums); + Constants(platformSpecificMembers, nodes.Constants); + + var platformSpecificClassName = platform.ToString().Replace("-", "_", StringComparison.InvariantCulture); + var platformSpecificClass = ClassDeclaration(platformSpecificClassName) + .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) + .AddMembers(platformSpecificMembers.ToArray()); + + members.Add(platformSpecificClass); + } + } var compilationUnit = CompilationUnit( _className, @@ -54,7 +79,7 @@ public string EmitCode(CSharpAbstractSyntaxTree abstractSyntaxTree) _namespaceName, _headerCodeRegion, _footerCodeRegion, - members); + members.ToArray()); return compilationUnit.ToFullString(); } @@ -152,7 +177,7 @@ public static unsafe partial class {className} } private void FunctionExterns( - ImmutableArray.Builder builder, + List members, ImmutableArray functionExterns) { foreach (var functionExtern in functionExterns) @@ -174,7 +199,7 @@ private void FunctionExterns( } var member = FunctionExtern(functionExtern); - builder.Add(member); + members.Add(member); } } @@ -203,13 +228,18 @@ private MethodDeclarationSyntax FunctionExtern(CSharpFunction function) } private void FunctionPointers( - ImmutableArray.Builder builder, + List members, ImmutableArray functionPointers) { + if (functionPointers.IsDefaultOrEmpty) + { + return; + } + foreach (var functionPointer in functionPointers) { var member = FunctionPointer(functionPointer); - builder.Add(member); + members.Add(member); } } @@ -241,13 +271,18 @@ public struct {functionPointerName} } private void Structs( - ImmutableArray.Builder builder, + List members, ImmutableArray structs) { + if (structs.IsDefaultOrEmpty) + { + return; + } + foreach (var @struct in structs) { var member = EmitStruct(@struct); - builder.Add(member); + members.Add(member); } } @@ -421,22 +456,27 @@ public Span<{elementType}> {field.Name} } private static void OpaqueDataTypes( - ImmutableArray.Builder builder, - ImmutableArray opaqueDataTypes) + List members, + ImmutableArray opaqueDataTypes) { + if (opaqueDataTypes.IsDefaultOrEmpty) + { + return; + } + foreach (var opaqueDataType in opaqueDataTypes) { var member = EmitOpaqueStruct(opaqueDataType); - builder.Add(member); + members.Add(member); } } - private static StructDeclarationSyntax EmitOpaqueStruct(CSharpOpaqueType opaqueType) + private static StructDeclarationSyntax EmitOpaqueStruct(CSharpOpaqueStruct opaqueStruct) { var code = $@" -{opaqueType.CodeLocationComment} +{opaqueStruct.CodeLocationComment} [StructLayout(LayoutKind.Sequential)] -public struct {opaqueType.Name} +public struct {opaqueStruct.Name} {{ }} "; @@ -445,28 +485,33 @@ public struct {opaqueType.Name} } private static void Typedefs( - ImmutableArray.Builder builder, - ImmutableArray typedefs) + List members, + ImmutableArray typedefs) { + if (typedefs.IsDefaultOrEmpty) + { + return; + } + foreach (var typedef in typedefs) { var member = Typedef(typedef); - builder.Add(member); + members.Add(member); } } - private static StructDeclarationSyntax Typedef(CSharpTypedef typedef) + private static StructDeclarationSyntax Typedef(CSharpAliasStruct aliasStruct) { var code = $@" -{typedef.CodeLocationComment} -[StructLayout(LayoutKind.Explicit, Size = {typedef.UnderlyingType.SizeOf}, Pack = {typedef.UnderlyingType.AlignOf})] -public struct {typedef.Name} +{aliasStruct.CodeLocationComment} +[StructLayout(LayoutKind.Explicit, Size = {aliasStruct.UnderlyingType.SizeOf}, Pack = {aliasStruct.UnderlyingType.AlignOf})] +public struct {aliasStruct.Name} {{ - [FieldOffset(0)] // size = {typedef.UnderlyingType.SizeOf}, padding = 0 - public {typedef.UnderlyingType.Name} Data; + [FieldOffset(0)] // size = {aliasStruct.UnderlyingType.SizeOf}, padding = 0 + public {aliasStruct.UnderlyingType.Name} Data; - public static implicit operator {typedef.UnderlyingType.Name}({typedef.Name} data) => data.Data; - public static implicit operator {typedef.Name}({typedef.UnderlyingType.Name} data) => new() {{Data = data}}; + public static implicit operator {aliasStruct.UnderlyingType.Name}({aliasStruct.Name} data) => data.Data; + public static implicit operator {aliasStruct.Name}({aliasStruct.UnderlyingType.Name} data) => new() {{Data = data}}; }} "; @@ -475,25 +520,41 @@ public struct {typedef.Name} } private static void Enums( - ImmutableArray.Builder builder, + List members, ImmutableArray enums) { + if (enums.IsDefaultOrEmpty) + { + return; + } + foreach (var @enum in enums) { var member = Enum(@enum); - builder.Add(member); + members.Add(member); } } private static EnumDeclarationSyntax Enum(CSharpEnum @enum) { - var values = EnumValues(@enum.IntegerType.Name ?? string.Empty, @enum.Values); + var enumName = @enum.Name; + var enumSizeOf = @enum.SizeOf!.Value; + var enumIntegerTypeName = enumSizeOf switch + { + 1 => "sbyte", + 2 => "short", + 4 => "int", + 8 => "long", + _ => throw new NotImplementedException($"The enum size is not supported: '{enumName}' of size {@enum.SizeOf}.") + }; + + var values = EnumValues(enumIntegerTypeName, @enum.Values); var valuesString = values.Select(x => x.ToFullString()); var members = string.Join(",\n", valuesString); var code = $@" {@enum.CodeLocationComment} -public enum {@enum.Name} : {@enum.IntegerType} +public enum {enumName} : {enumIntegerTypeName} {{ {members} }} @@ -504,13 +565,13 @@ public enum {@enum.Name} : {@enum.IntegerType} } private static EnumMemberDeclarationSyntax[] EnumValues( - string enumTypeName, ImmutableArray values) + string enumIntegerTypeName, ImmutableArray values) { var builder = ImmutableArray.CreateBuilder(values.Length); foreach (var value in values) { - var enumEqualsValue = EmitEnumEqualsValue(value.Value, enumTypeName); + var enumEqualsValue = EmitEnumEqualsValue(value.Value, enumIntegerTypeName); var member = EnumMemberDeclaration(value.Name) .WithEqualsValue(enumEqualsValue); @@ -520,64 +581,33 @@ private static EnumMemberDeclarationSyntax[] EnumValues( return builder.ToArray(); } - private static EqualsValueClauseSyntax EmitEnumEqualsValue(long value, string enumTypeName) + private static EqualsValueClauseSyntax EmitEnumEqualsValue(long value, string enumIntegerTypeName) { - var literalToken = enumTypeName switch + var literalToken = enumIntegerTypeName switch { + "sbyte" => Literal((sbyte)value), + "short" => Literal((short)value), "int" => Literal((int)value), - "uint" => Literal((uint)value), - _ => throw new NotImplementedException($"The enum type is not yet supported: {enumTypeName}.") + "long" => Literal(value), + _ => throw new NotImplementedException($"The enum integer type name is not supported: {enumIntegerTypeName}.") }; return EqualsValueClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, literalToken)); } - private static void PseudoEnums( - ImmutableArray.Builder builder, - ImmutableArray pseudoEnums) - { - foreach (var pseudoEnum in pseudoEnums) - { - PseudoEnum(builder, pseudoEnum); - } - } - - private static void PseudoEnum( - ImmutableArray.Builder builder, - CSharpPseudoEnum pseudoEnum) + private static void Constants( + List members, + ImmutableArray constants) { - var hasAddedLocationComment = false; - foreach (var pseudoEnumConstant in pseudoEnum.Values) + if (constants.IsDefaultOrEmpty) { - if (!hasAddedLocationComment) - { - hasAddedLocationComment = true; - var code = $@" -{pseudoEnum.CodeLocationComment.Replace("Enum ", $"Pseudo enum '{pseudoEnum.Name}' ", StringComparison.InvariantCulture)} -public const int {pseudoEnumConstant.Name} = {pseudoEnumConstant.Value}; -"; - var member = ParseMemberCode(code); - builder.Add(member); - } - else - { - var code = $@" -public const int {pseudoEnumConstant.Name} = {pseudoEnumConstant.Value}; -".TrimStart(); - var member = ParseMemberCode(code); - builder.Add(member); - } + return; } - } - private static void Constants( - ImmutableArray.Builder builder, - ImmutableArray constants) - { foreach (var constant in constants) { var field = Constant(constant); - builder.Add(field); + members.Add(field); } } diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/RosylnExtensions.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/ExtensionsRosyln.cs similarity index 95% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Logic/RosylnExtensions.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/ExtensionsRosyln.cs index bd990e6d..631fba2d 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/RosylnExtensions.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/ExtensionsRosyln.cs @@ -6,10 +6,10 @@ using Microsoft.CodeAnalysis.CSharp; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace C2CS.Feature.BindgenCSharp.Logic; +namespace C2CS.Feature.WriteCodeCSharp.Domain; [PublicAPI] -public static class RosylnExtensions +public static class ExtensionsRosyln { public static T AddRegion(this T node, string regionName) where T : SyntaxNode diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapper.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapper.cs new file mode 100644 index 00000000..a56e9bfc --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapper.cs @@ -0,0 +1,1196 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using System.Globalization; +using System.Text.RegularExpressions; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Feature.WriteCodeCSharp.Domain.Mapper.Diagnostics; +using C2CS.Foundation.UseCases.Exceptions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace C2CS.Feature.WriteCodeCSharp.Domain.Mapper; + +public sealed class CSharpMapper +{ + private readonly CSharpMapperParameters _parameters; + + private readonly ImmutableHashSet _builtinAliases; + private readonly Dictionary _generatedFunctionPointersNamesByCNames = new(); + private readonly ImmutableHashSet _ignoredNames; + private readonly ImmutableDictionary _userTypeNameAliases; + + public CSharpMapper(CSharpMapperParameters parameters) + { + _parameters = parameters; + + var userAliases = new Dictionary(); + var builtinAliases = new HashSet(); + + foreach (var typeAlias in parameters.TypeAliases) + { + userAliases.Add(typeAlias.Source, typeAlias.Target); + + if (typeAlias.Target + is "byte" + or "sbyte" + or "short" + or "ushort" + or "int" + or "uint" + or "long" + or "ulong" + or "CBool" + or "CChar" + or "CCharWide") + { + builtinAliases.Add(typeAlias.Source); + } + } + + _userTypeNameAliases = userAliases.ToImmutableDictionary(); + _builtinAliases = builtinAliases.ToImmutableHashSet(); + _ignoredNames = parameters.IgnoredTypeNames + .Concat(parameters.SystemTypeNameAliases.Keys) + .ToImmutableHashSet(); + } + + public ImmutableDictionary Map( + ImmutableArray abstractSyntaxTrees) + { + var builder = ImmutableDictionary.CreateBuilder(); + + foreach (var ast in abstractSyntaxTrees) + { + var platformNodes = CSharpNodes(ast); + builder.Add(ast.Platform, platformNodes); + } + + return builder.ToImmutable(); + } + + private CSharpNodes CSharpNodes(CAbstractSyntaxTree ast) + { + var context = new CSharpMapperContext(ast.Platform, ast.Types); + + var functions = Functions(context, ast.Functions); + var structs = Structs(context, ast.Records); + // Typedefs need to be processed first as they can generate aliases on the fly + var aliasStructs = AliasStructs(context, ast.Typedefs); + var functionPointers = FunctionPointers(context, ast.FunctionPointers); + var opaqueDataTypes = OpaqueDataTypes(context, ast.OpaqueTypes); + var enums = Enums(context, ast.Enums); + var constants = Constants(context, ast.Constants); + + var nodes = new CSharpNodes + { + Functions = functions, + FunctionPointers = functionPointers, + Structs = structs, + AliasStructs = aliasStructs, + OpaqueStructs = opaqueDataTypes, + Enums = enums, + Constants = constants + }; + return nodes; + } + + private ImmutableArray Functions( + CSharpMapperContext context, + ImmutableArray clangFunctionExterns) + { + var builder = ImmutableArray.CreateBuilder(clangFunctionExterns.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var clangFunctionExtern in clangFunctionExterns) + { + var functionExtern = Function(context, clangFunctionExtern); + builder.Add(functionExtern); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpFunction Function(CSharpMapperContext context, CFunction cFunction) + { + var name = cFunction.Name; + var originalCodeLocationComment = OriginalCodeLocationComment(cFunction); + + var cType = CType(context, cFunction.ReturnType); + + var returnType = Type(context, cType); + var callingConvention = CSharpFunctionCallingConvention(cFunction.CallingConvention); + var parameters = CSharpFunctionParameters(context, cFunction.Parameters); + + var result = new CSharpFunction( + context.Platform, + name, + originalCodeLocationComment, + null, + callingConvention, + returnType, + parameters); + + return result; + } + + private static CType CType(CSharpMapperContext context, string typeName) + { + if (context.TypesByName.TryGetValue(typeName, out var type)) + { + return type; + } + + var up = new UseCaseException($"Expected a type with the name '{typeName}' but it was not found."); + throw up; + } + + private static CSharpFunctionCallingConvention CSharpFunctionCallingConvention( + CFunctionCallingConvention callingConvention) + { + var result = callingConvention switch + { + CFunctionCallingConvention.Cdecl => Data.Model.CSharpFunctionCallingConvention.Cdecl, + CFunctionCallingConvention.StdCall => Data.Model.CSharpFunctionCallingConvention.StdCall, + _ => throw new ArgumentOutOfRangeException( + nameof(callingConvention), callingConvention, null) + }; + + return result; + } + + private ImmutableArray CSharpFunctionParameters( + CSharpMapperContext context, + ImmutableArray functionExternParameters) + { + var builder = ImmutableArray.CreateBuilder(functionExternParameters.Length); + var parameterNames = new List(); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var functionExternParameterC in functionExternParameters) + { + var parameterName = CSharpUniqueParameterName(functionExternParameterC.Name, parameterNames); + parameterNames.Add(parameterName); + var functionExternParameterCSharp = + FunctionParameter(context, functionExternParameterC, parameterName); + builder.Add(functionExternParameterCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private static string CSharpUniqueParameterName(string parameterName, List parameterNames) + { + if (string.IsNullOrEmpty(parameterName)) + { + parameterName = "param"; + } + + while (parameterNames.Contains(parameterName)) + { + var numberSuffixMatch = Regex.Match(parameterName, "\\d$"); + if (numberSuffixMatch.Success) + { + var parameterNameWithoutSuffix = parameterName.Substring(0, numberSuffixMatch.Index); + parameterName = ParameterNameUniqueSuffix(parameterNameWithoutSuffix, numberSuffixMatch.Value); + } + else + { + parameterName = ParameterNameUniqueSuffix(parameterName, string.Empty); + } + } + + return parameterName; + + static string ParameterNameUniqueSuffix(string parameterNameWithoutSuffix, string parameterSuffix) + { + if (string.IsNullOrEmpty(parameterSuffix)) + { + return parameterNameWithoutSuffix + "2"; + } + + var parameterSuffixNumber = + int.Parse(parameterSuffix, NumberStyles.Integer, CultureInfo.InvariantCulture); + parameterSuffixNumber += 1; + var parameterName = parameterNameWithoutSuffix + parameterSuffixNumber; + return parameterName; + } + } + + private CSharpFunctionParameter FunctionParameter( + CSharpMapperContext context, + CFunctionParameter functionParameter, + string parameterName) + { + var name = SanitizeIdentifier(parameterName); + var originalCodeLocationComment = OriginalCodeLocationComment(functionParameter); + var typeC = CType(context, functionParameter.Type); + var typeCSharp = Type(context, typeC); + + var functionParameterCSharp = new CSharpFunctionParameter( + context.Platform, + name, + originalCodeLocationComment, + typeC.SizeOf, + typeCSharp); + + return functionParameterCSharp; + } + + private ImmutableArray FunctionPointers( + CSharpMapperContext context, + ImmutableArray functionPointers) + { + var builder = ImmutableArray.CreateBuilder(functionPointers.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var functionPointer in functionPointers) + { + var functionPointerCSharp = FunctionPointer(context, functionPointer); + builder.Add(functionPointerCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpFunctionPointer FunctionPointer( + CSharpMapperContext context, + CFunctionPointer functionPointer) + { + var typeName = string.IsNullOrEmpty(functionPointer.Name) ? functionPointer.Type : functionPointer.Name; + var typeC = CType(context, typeName); + var typeNameCSharp = TypeNameMapFunctionPointer(context, typeC); + + var originalCodeLocationComment = OriginalCodeLocationComment(functionPointer); + var returnTypeC = CType(context, functionPointer.ReturnType); + var returnTypeCSharp = Type(context, returnTypeC); + var parameters = FunctionPointerParameters(context, functionPointer.Parameters); + + var result = new CSharpFunctionPointer( + context.Platform, + typeNameCSharp, + originalCodeLocationComment, + typeC.SizeOf, + returnTypeCSharp, + parameters); + + return result; + } + + private ImmutableArray FunctionPointerParameters( + CSharpMapperContext context, + ImmutableArray functionPointerParameters) + { + var builder = + ImmutableArray.CreateBuilder(functionPointerParameters.Length); + var parameterNames = new List(); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var functionPointerParameterC in functionPointerParameters) + { + var parameterName = CSharpUniqueParameterName(functionPointerParameterC.Name, parameterNames); + parameterNames.Add(parameterName); + var functionExternParameterCSharp = + FunctionPointerParameter(context, functionPointerParameterC, parameterName); + builder.Add(functionExternParameterCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpFunctionPointerParameter FunctionPointerParameter( + CSharpMapperContext context, + CFunctionPointerParameter functionPointerParameter, + string parameterName) + { + var name = SanitizeIdentifier(parameterName); + var originalCodeLocationComment = OriginalCodeLocationComment(functionPointerParameter); + var typeC = CType(context, functionPointerParameter.Type); + var typeCSharp = Type(context, typeC); + + var result = new CSharpFunctionPointerParameter( + context.Platform, + name, + originalCodeLocationComment, + typeC.SizeOf, + typeCSharp); + + return result; + } + + private ImmutableArray Structs( + CSharpMapperContext context, + ImmutableArray records) + { + var builder = ImmutableArray.CreateBuilder(records.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var record in records) + { + if (_builtinAliases.Contains(record.Name) || + _ignoredNames.Contains(record.Name)) + { + // short circuit, prevents emitting the type + continue; + } + + var structCSharp = Struct(context, record); + if (_ignoredNames.Contains(structCSharp.Name)) + { + continue; + } + + builder.Add(structCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpStruct Struct( + CSharpMapperContext context, + CRecord record) + { + var originalCodeLocationComment = OriginalCodeLocationComment(record); + var typeC = CType(context, record.Name); + var typeCSharp = Type(context, typeC); + var fields = StructFields(context, record.Fields); + var nestedStructs = NestedStructs(context, record.NestedRecords); + + return new CSharpStruct( + context.Platform, + originalCodeLocationComment, + typeC.SizeOf, + typeCSharp, + fields, + nestedStructs); + } + + private ImmutableArray StructFields( + CSharpMapperContext context, + ImmutableArray recordFields) + { + var builder = ImmutableArray.CreateBuilder(recordFields.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var recordField in recordFields) + { + var structFieldCSharp = StructField(context, recordField); + builder.Add(structFieldCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpStructField StructField( + CSharpMapperContext context, + CRecordField recordField) + { + var name = SanitizeIdentifier(recordField.Name); + var codeLocationComment = OriginalCodeLocationComment(recordField); + var typeC = CType(context, recordField.Type); + + CSharpType typeCSharp; + if (typeC.Kind == CKind.FunctionPointer) + { + var functionPointerName = TypeNameMapFunctionPointer(context, typeC); + typeCSharp = Type(context, typeC, functionPointerName); + } + else + { + typeCSharp = Type(context, typeC); + } + + var offset = recordField.Offset; + var padding = recordField.Padding; + var isWrapped = typeCSharp.IsArray && !IsValidFixedBufferType(typeCSharp.Name ?? string.Empty); + + var result = new CSharpStructField( + context.Platform, + name, + codeLocationComment, + typeC.SizeOf, + typeCSharp, + offset, + padding, + isWrapped); + + return result; + } + + private ImmutableArray NestedStructs( + CSharpMapperContext context, + ImmutableArray records) + { + var builder = ImmutableArray.CreateBuilder(records.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var record in records) + { + var structCSharp = Struct(context, record); + if (_ignoredNames.Contains(structCSharp.Name)) + { + continue; + } + + builder.Add(structCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private ImmutableArray OpaqueDataTypes( + CSharpMapperContext context, + ImmutableArray opaqueDataTypes) + { + var builder = ImmutableArray.CreateBuilder(opaqueDataTypes.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var opaqueDataTypeC in opaqueDataTypes) + { + var opaqueDataTypeCSharp = OpaqueDataStruct(context, opaqueDataTypeC); + + if (_ignoredNames.Contains(opaqueDataTypeCSharp.Name)) + { + continue; + } + + builder.Add(opaqueDataTypeCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpOpaqueStruct OpaqueDataStruct( + CSharpMapperContext context, + COpaqueType opaqueType) + { + var typeC = CType(context, opaqueType.Name); + var typeCSharp = Type(context, typeC); + var name = typeCSharp.Name!; + var originalCodeLocationComment = OriginalCodeLocationComment(opaqueType); + + var opaqueTypeCSharp = new CSharpOpaqueStruct( + context.Platform, + name, + originalCodeLocationComment, + typeC.SizeOf); + + return opaqueTypeCSharp; + } + + private ImmutableArray AliasStructs( + CSharpMapperContext context, + ImmutableArray typedefs) + { + var builder = ImmutableArray.CreateBuilder(typedefs.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var typedef in typedefs) + { + if (_builtinAliases.Contains(typedef.Name) || + _ignoredNames.Contains(typedef.Name)) + { + continue; + } + + var aliasStruct = AliasStruct(context, typedef); + if (_ignoredNames.Contains(aliasStruct.Name)) + { + continue; + } + + builder.Add(aliasStruct); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpAliasStruct AliasStruct( + CSharpMapperContext context, + CTypedef typedef) + { + var name = typedef.Name; + var originalCodeLocationComment = OriginalCodeLocationComment(typedef); + var underlyingTypeC = CType(context, typedef.UnderlyingType); + var typeC = CType(context, typedef.Name); + if (typeC.Location.IsSystem && underlyingTypeC.Location.IsSystem) + { + var diagnostic = new SystemTypedefDiagnostic(name, typedef.Location, underlyingTypeC.Name); + _parameters.DiagnosticsSink.Add(diagnostic); + } + + var underlyingTypeCSharp = Type(context, underlyingTypeC); + + var result = new CSharpAliasStruct( + context.Platform, + name, + originalCodeLocationComment, + typeC.SizeOf, + underlyingTypeCSharp); + + return result; + } + + private ImmutableArray Enums( + CSharpMapperContext context, + ImmutableArray enums) + { + var builder = ImmutableArray.CreateBuilder(enums.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var enumC in enums) + { + var enumCSharp = Enum(context, enumC); + + if (_ignoredNames.Contains(enumCSharp.Name)) + { + continue; + } + + builder.Add(enumCSharp); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpEnum Enum( + CSharpMapperContext context, + CEnum @enum) + { + var name = @enum.Name; + var originalCodeLocationComment = OriginalCodeLocationComment(@enum); + var cIntegerType = CType(context, @enum.IntegerType); + var integerType = Type(context, cIntegerType); + var values = EnumValues(context, @enum.Values); + + var result = new CSharpEnum( + context.Platform, + name, + originalCodeLocationComment, + cIntegerType.SizeOf, + integerType, + values); + return result; + } + + private ImmutableArray EnumValues( + CSharpMapperContext context, ImmutableArray enumValues) + { + var builder = ImmutableArray.CreateBuilder(enumValues.Length); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var enumValue in enumValues) + { + var @enum = EnumValue(context, enumValue); + builder.Add(@enum); + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpEnumValue EnumValue( + CSharpMapperContext context, CEnumValue enumValue) + { + var name = enumValue.Name; + var originalCodeLocationComment = OriginalCodeLocationComment(enumValue); + var value = enumValue.Value; + + var result = new CSharpEnumValue( + context.Platform, + name, + originalCodeLocationComment, + null, + value); + + return result; + } + + private ImmutableArray Constants( + CSharpMapperContext context, ImmutableArray constants) + { + var builder = ImmutableArray.CreateBuilder(constants.Length); + + var lookup = new Dictionary(); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var macroObject in constants) + { + if (_ignoredNames.Contains(macroObject.Name)) + { + continue; + } + + var constant = Constant(context, macroObject, lookup); + if (constant == null) + { + var diagnostic = new TranspileMacroObjectFailureDiagnostic(macroObject.Name, macroObject.Location); + _parameters.DiagnosticsSink.Add(diagnostic); + } + else + { + builder.Add(constant); + lookup.Add(constant.Name, constant); + } + } + + var result = builder.ToImmutable(); + return result; + } + + private CSharpConstant? Constant( + CSharpMapperContext context, + CMacroDefinition macroDefinition, + Dictionary lookup) + { + var originalCodeLocationComment = OriginalCodeLocationComment(macroDefinition); + var tokens = macroDefinition.Tokens.ToArray(); + + for (var i = 0; i < tokens.Length; i++) + { + var token = tokens[i]; + + foreach (var (typeName, typeNameAlias) in _parameters.SystemTypeNameAliases) + { + if (token == typeName) + { + token = tokens[i] = typeNameAlias; + } + } + + foreach (var (typeName, typeNameAlias) in _userTypeNameAliases) + { + if (token == typeName) + { + token = tokens[i] = typeNameAlias; + } + } + + if (token == "size_t") + { + token = tokens[i] = "ulong"; + } + + if (token.ToUpper(CultureInfo.InvariantCulture).EndsWith("ULL", StringComparison.InvariantCulture)) + { + var possibleIntegerToken = token[..^3]; + + if (possibleIntegerToken.StartsWith("0x", StringComparison.InvariantCulture)) + { + possibleIntegerToken = possibleIntegerToken[2..]; + if (ulong.TryParse( + possibleIntegerToken, + NumberStyles.HexNumber, + CultureInfo.InvariantCulture, + out _)) + { + token = tokens[i] = $"0x{possibleIntegerToken}UL"; + } + else + { + return null; + } + } + else + { + if (ulong.TryParse(possibleIntegerToken, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) + { + token = tokens[i] = $"{possibleIntegerToken}UL"; + } + else + { + return null; + } + } + } + } + + var typeValue = GetMacroExpressionTypeAndValue(tokens.ToImmutableArray(), lookup); + if (typeValue == null) + { + return null; + } + + var (type, value) = typeValue.Value; + if (type == "?") + { + return null; + } + + var result = new CSharpConstant( + context.Platform, + macroDefinition.Name, + originalCodeLocationComment, + null, + type, + value); + return result; + } + + private (string Type, string Value)? GetMacroExpressionTypeAndValue( + ImmutableArray tokens, IReadOnlyDictionary lookup) + { + var dependentMacros = new List(); + foreach (var token in tokens) + { + if (lookup.TryGetValue(token, out var dependentMacro)) + { + dependentMacros.Add($"var {dependentMacro.Name} = {dependentMacro.Value};"); + } + } + + var value = string.Join(string.Empty, tokens); + var code = @$" +using System; +{string.Join("\n", dependentMacros)} +var x = {value}; +".Trim(); + + var dotNetPath = Terminal.DotNetPath(); + var syntaxTree = CSharpSyntaxTree.ParseText(code); + var variableDeclarations = syntaxTree.GetRoot().DescendantNodesAndSelf().OfType(); + var variables = variableDeclarations.Last().Variables; + if (variables.Count > 1) + { + // something is wrong with the macro; it's probably not an object-like macro + return null; + } + + var variable = variables.Single(); + var variableInitializer = variable.Initializer; + if (variableInitializer == null) + { + return null; + } + + var expression = variableInitializer.Value; + var mscorlib = MetadataReference.CreateFromFile(Path.Combine(dotNetPath, "mscorlib.dll")); + var privateCoreLib = + MetadataReference.CreateFromFile(Path.Combine(dotNetPath, "System.Private.CoreLib.dll")); + var compilation = CSharpCompilation.Create("Assembly") + .AddReferences(mscorlib, privateCoreLib) + .AddSyntaxTrees(syntaxTree); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + var typeInfo = semanticModel.GetTypeInfo(expression); + + if (typeInfo.ConvertedType == null) + { + return null; + } + + var type = typeInfo.ConvertedType!.ToString()!; + + if (value.StartsWith("(uint)-", StringComparison.InvariantCulture) || + value.StartsWith("(ulong)-", StringComparison.InvariantCulture)) + { + value = $"unchecked({value})"; + } + + return (type, value); + } + + private CSharpType Type( + CSharpMapperContext context, + CType cType, + string? typeName = null) + { + var typeName2 = typeName ?? TypeName(context, cType); + var sizeOf = cType.SizeOf; + var alignOf = cType.AlignOf ?? 0; + var fixedBufferSize = cType.ArraySize ?? 0; + + var result = new CSharpType + { + Name = typeName2, + OriginalName = cType.Name, + SizeOf = sizeOf, + AlignOf = alignOf, + ArraySize = fixedBufferSize + }; + + return result; + } + + private string TypeName( + CSharpMapperContext context, + CType type) + { + if (type.Kind == CKind.FunctionPointer) + { + return TypeNameMapFunctionPointer(context, type); + } + + var name = type.Name; + string typeName; + + if (name.EndsWith("*", StringComparison.InvariantCulture) || + name.EndsWith("]", StringComparison.InvariantCulture)) + { + typeName = TypeNameMapPointer(context, type); + } + else + { + typeName = TypeNameMapElement(name, type.SizeOf); + } + + // TODO: https://github.com/lithiumtoast/c2cs/issues/15 + if (typeName == "va_list") + { + typeName = "nint"; + } + + return typeName; + } + + private string TypeNameMapFunctionPointer( + CSharpMapperContext context, + CType typeC) + { + if (typeC.Kind == CKind.Typedef) + { + return typeC.Name; + } + + if (typeC.Kind != CKind.FunctionPointer) + { + var up = new UseCaseException($"Expected type to be function pointer but type is '{typeC.Kind}'."); + throw up; + } + + if (_generatedFunctionPointersNamesByCNames.TryGetValue(typeC.Name, out var functionPointerNameCSharp)) + { + return functionPointerNameCSharp; + } + + var indexOfFirstParentheses = typeC.Name.IndexOf('(', StringComparison.InvariantCulture); + var returnTypeStringC = typeC.Name.Substring(0, indexOfFirstParentheses) + .Replace(" *", "*", StringComparison.InvariantCulture).Trim(); + var returnTypeC = CType(context, returnTypeStringC); + var returnTypeCSharp = Type(context, returnTypeC); + var returnTypeNameCSharpOriginal = returnTypeCSharp.Name ?? string.Empty; + var returnTypeNameCSharp = returnTypeNameCSharpOriginal.Replace("*", "Ptr", StringComparison.InvariantCulture); + var returnTypeStringCapitalized = char.ToUpper(returnTypeNameCSharp[0], CultureInfo.InvariantCulture) + + returnTypeNameCSharp.Substring(1); + + var parameterStringsCSharp = new List(); + var parameterStringsC = typeC.Name.Substring(indexOfFirstParentheses) + .Trim('(', ')').Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Replace(" *", "*", StringComparison.InvariantCulture)) + .Select(x => x.Trim()).ToArray(); + foreach (var typeNameC in parameterStringsC) + { + var parameterTypeC = CType(context, typeNameC); + var parameterTypeCSharp = Type(context, parameterTypeC); + + if (parameterTypeC.Name == "void" && parameterTypeC.Location.IsSystem) + { + continue; + } + + var typeNameCSharpOriginal = parameterTypeCSharp.Name ?? string.Empty; + var typeNameCSharp = typeNameCSharpOriginal.Replace("*", "Ptr", StringComparison.InvariantCulture); + var typeNameCSharpCapitalized = + char.ToUpper(typeNameCSharp[0], CultureInfo.InvariantCulture) + typeNameCSharp[1..]; + parameterStringsCSharp.Add(typeNameCSharpCapitalized); + } + + var parameterStringsCSharpJoined = string.Join('_', parameterStringsCSharp); + functionPointerNameCSharp = + $"FnPtr_{parameterStringsCSharpJoined}_{returnTypeStringCapitalized}" + .Replace("__", "_", StringComparison.InvariantCulture); + _generatedFunctionPointersNamesByCNames.Add(typeC.Name, functionPointerNameCSharp); + + return functionPointerNameCSharp; + } + + private string TypeNameMapPointer(CSharpMapperContext context, CType type) + { + var pointerTypeName = type.Name; + + // Replace [] with * + while (true) + { + var x = pointerTypeName.IndexOf('[', StringComparison.InvariantCulture); + + if (x == -1) + { + break; + } + + var y = pointerTypeName.IndexOf(']', x); + + pointerTypeName = pointerTypeName[..x] + "*" + pointerTypeName[(y + 1)..]; + } + + if (pointerTypeName.StartsWith("char*", StringComparison.InvariantCulture)) + { + return pointerTypeName.Replace("char*", "CString", StringComparison.InvariantCulture); + } + + if (pointerTypeName.StartsWith("wchar_t*", StringComparison.InvariantCulture)) + { + return pointerTypeName.Replace("wchar_t*", "CStringWide", StringComparison.InvariantCulture); + } + + if (pointerTypeName.StartsWith("FILE*", StringComparison.InvariantCulture)) + { + return pointerTypeName.Replace("FILE*", "nint", StringComparison.InvariantCulture); + } + + if (pointerTypeName.StartsWith("DIR*", StringComparison.InvariantCulture)) + { + return pointerTypeName.Replace("DIR*", "nint", StringComparison.InvariantCulture); + } + + var elementTypeName = pointerTypeName.TrimEnd('*'); + var pointersTypeName = pointerTypeName[elementTypeName.Length..]; + var elementType = CType(context, elementTypeName); + var mappedElementTypeName = TypeNameMapElement(elementType.Name, elementType.SizeOf); + pointerTypeName = mappedElementTypeName + pointersTypeName; + + return pointerTypeName; + } + + private string TypeNameMapElement(string typeName, int sizeOf) + { + if (_userTypeNameAliases.TryGetValue(typeName, out var aliasName)) + { + return aliasName; + } + + if (_parameters.SystemTypeNameAliases.TryGetValue(typeName, out var mappedSystemTypeName)) + { + return mappedSystemTypeName; + } + + switch (typeName) + { + case "char": + return "CChar"; + case "wchar_t": + return "CWideChar"; + case "bool": + case "_Bool": + return "CBool"; + case "int8_t": + return "sbyte"; + case "uint8_t": + return "byte"; + case "int16_t": + return "short"; + case "uint16_t": + return "ushort"; + case "int32_t": + return "int"; + case "uint32_t": + return "uint"; + case "int64_t": + return "long"; + case "uint64_t": + return "ulong"; + case "uintptr_t": + return "UIntPtr"; + case "intptr_t": + return "IntPtr"; + case "unsigned char": + case "unsigned short": + case "unsigned short int": + case "unsigned": + case "unsigned int": + case "unsigned long": + case "unsigned long int": + case "unsigned long long": + case "unsigned long long int": + case "size_t": + return TypeNameMapUnsignedInteger(sizeOf); + case "signed char": + case "short": + case "short int": + case "signed short": + case "signed short int": + case "int": + case "signed": + case "signed int": + case "long": + case "long int": + case "signed long": + case "signed long int": + case "long long": + case "long long int": + case "signed long long int": + case "ssize_t": + return TypeNameMapSignedInteger(sizeOf); + default: + return typeName; + } + } + + private static string TypeNameMapUnsignedInteger(int sizeOf) + { + return sizeOf switch + { + 1 => "byte", + 2 => "ushort", + 4 => "uint", + 8 => "ulong", + _ => throw new InvalidOperationException() + }; + } + + private static string TypeNameMapSignedInteger(int sizeOf) + { + return sizeOf switch + { + 1 => "sbyte", + 2 => "short", + 4 => "int", + 8 => "long", + _ => throw new InvalidOperationException() + }; + } + + private static string OriginalCodeLocationComment(CNode node) + { + string kindString; + if (node is CRecord record) + { + kindString = record.IsUnion ? "Union" : "Struct"; + } + else + { + kindString = node.Kind.ToString(); + } + + if (node is not CNodeWithLocation nodeWithLocation) + { + return $"// {kindString}"; + } + + var location = nodeWithLocation.Location; + return $"// {kindString} @ " + location; + } + + private static string SanitizeIdentifier(string name) + { + var result = name; + + switch (name) + { + case "abstract": + case "as": + case "base": + case "bool": + case "break": + case "byte": + case "case": + case "catch": + case "char": + case "checked": + case "class": + case "const": + case "continue": + case "decimal": + case "default": + case "delegate": + case "do": + case "double": + case "else": + case "enum": + case "event": + case "explicit": + case "extern": + case "false": + case "finally": + case "fixed": + case "float": + case "for": + case "foreach": + case "goto": + case "if": + case "implicit": + case "in": + case "int": + case "interface": + case "internal": + case "is": + case "lock": + case "long": + case "namespace": + case "new": + case "null": + case "object": + case "operator": + case "out": + case "override": + case "params": + case "private": + case "protected": + case "public": + case "readonly": + case "record": + case "ref": + case "return": + case "sbyte": + case "sealed": + case "short": + case "sizeof": + case "stackalloc": + case "static": + case "string": + case "struct": + case "switch": + case "this": + case "throw": + case "true": + case "try": + case "typeof": + case "uint": + case "ulong": + case "unchecked": + case "unsafe": + case "ushort": + case "using": + case "virtual": + case "void": + case "volatile": + case "while": + result = $"@{name}"; + break; + } + + return result; + } + + private static bool IsValidFixedBufferType(string typeString) + { + return typeString switch + { + "bool" => true, + "byte" => true, + "char" => true, + "short" => true, + "int" => true, + "long" => true, + "sbyte" => true, + "ushort" => true, + "uint" => true, + "ulong" => true, + "float" => true, + "double" => true, + _ => false + }; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperContext.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperContext.cs new file mode 100644 index 00000000..d69280dd --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperContext.cs @@ -0,0 +1,27 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Data; + +namespace C2CS.Feature.WriteCodeCSharp.Domain.Mapper; + +public class CSharpMapperContext +{ + public readonly TargetPlatform Platform; + + public readonly ImmutableDictionary TypesByName; + + public CSharpMapperContext(TargetPlatform platform, ImmutableArray types) + { + Platform = platform; + + var typesByNameBuilder = ImmutableDictionary.CreateBuilder(); + foreach (var type in types) + { + typesByNameBuilder.Add(type.Name, type); + } + + TypesByName = typesByNameBuilder.ToImmutable(); + } +} diff --git a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpMapperParameters.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperParameters.cs similarity index 61% rename from src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpMapperParameters.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperParameters.cs index a4d6942d..5ef7cfa2 100644 --- a/src/cs/production/C2CS.Feature.BindgenCSharp/Logic/CSharpMapperParameters.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/CSharpMapperParameters.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; -using C2CS.Feature.BindgenCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Foundation.Diagnostics; -namespace C2CS.Feature.BindgenCSharp.Logic; +namespace C2CS.Feature.WriteCodeCSharp.Domain.Mapper; public sealed class CSharpMapperParameters { @@ -12,8 +14,6 @@ public sealed class CSharpMapperParameters public ImmutableArray IgnoredTypeNames { get; } - public int Bitness { get; } - public DiagnosticsSink DiagnosticsSink { get; } public ImmutableDictionary SystemTypeNameAliases { get; } @@ -21,59 +21,31 @@ public sealed class CSharpMapperParameters public CSharpMapperParameters( ImmutableArray typeAliases, ImmutableArray ignoredTypeNames, - int bitness, DiagnosticsSink diagnostics) { TypeAliases = typeAliases; IgnoredTypeNames = ignoredTypeNames; - Bitness = bitness is 32 or 64 ? bitness : throw new NotImplementedException($"{bitness}-bit is not implemented."); DiagnosticsSink = diagnostics; - SystemTypeNameAliases = GetSystemTypeNameAliases(bitness).ToImmutableDictionary(); + SystemTypeNameAliases = GetSystemTypeNameAliases().ToImmutableDictionary(); } - private static Dictionary GetSystemTypeNameAliases(int bitness) + private static Dictionary GetSystemTypeNameAliases() { var aliases = new Dictionary(); - var operatingSystem = Platform.HostOperatingSystem; - AddSystemTypes(bitness, operatingSystem, aliases); + AddSystemTypes(aliases); return aliases; } - private static void AddSystemTypes( - int bitness, - RuntimeOperatingSystem operatingSystem, - Dictionary aliases) + private static void AddSystemTypes(IDictionary aliases) { aliases.Add("wchar_t", string.Empty); // remove - switch (operatingSystem) - { - case RuntimeOperatingSystem.Windows: - AddSystemTypesWindows(aliases); - break; - case RuntimeOperatingSystem.Linux: - AddSystemTypesLinux(bitness, aliases); - break; - case RuntimeOperatingSystem.macOS: - case RuntimeOperatingSystem.iOS: - case RuntimeOperatingSystem.tvOS: - AddSystemTypesDarwin(bitness, aliases); - break; - case RuntimeOperatingSystem.Unknown: - throw new PlatformNotSupportedException(); - case RuntimeOperatingSystem.FreeBSD: - case RuntimeOperatingSystem.Android: - case RuntimeOperatingSystem.Browser: - case RuntimeOperatingSystem.PlayStation: - case RuntimeOperatingSystem.Xbox: - case RuntimeOperatingSystem.Switch: - throw new NotImplementedException(); - default: - throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null); - } + AddSystemTypesWindows(aliases); + AddSystemTypesLinux(aliases); + AddSystemTypesDarwin(aliases); } - private static void AddSystemTypesDarwin(int bitness, Dictionary aliases) + private static void AddSystemTypesDarwin(IDictionary aliases) { aliases.Add("__uint32_t", "uint"); aliases.Add("__uint16_t", "ushort"); @@ -88,34 +60,16 @@ private static void AddSystemTypesDarwin(int bitness, Dictionary aliases.Add("_opaque_pthread_t", string.Empty); // remove aliases.Add("__darwin_pthread_handler_rec", string.Empty); // remove aliases.Add("__darwin_wchar_t", string.Empty); // remove - - switch (bitness) - { - case 32: - aliases.Add("__darwin_time_t", "int"); - break; - case 64: - aliases.Add("__darwin_time_t", "long"); - break; - } + aliases.Add("__darwin_time_t", "nint"); } - private static void AddSystemTypesLinux(int bitness, Dictionary aliases) + private static void AddSystemTypesLinux(IDictionary aliases) { aliases.Add("__gid_t", "uint"); aliases.Add("__uid_t", "uint"); aliases.Add("__pid_t", "int"); aliases.Add("__socklen_t", "uint"); - - switch (bitness) - { - case 32: - aliases.Add("__time_t", "int"); - break; - case 64: - aliases.Add("__time_t", "long"); - break; - } + aliases.Add("__time_t", "nint"); } private static void AddSystemTypesWindows(IDictionary aliases) diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/SystemTypedefDiagnostic.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/SystemTypedefDiagnostic.cs new file mode 100644 index 00000000..97be2c82 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/SystemTypedefDiagnostic.cs @@ -0,0 +1,20 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.WriteCodeCSharp.Domain.Mapper.Diagnostics; + +public sealed class SystemTypedefDiagnostic : Diagnostic +{ + public SystemTypedefDiagnostic(string typeName, CLocation location, string underlyingTypeName) + : base(DiagnosticSeverity.Warning, CreateMessage(typeName, location, underlyingTypeName)) + { + } + + private static string CreateMessage(string typeName, CLocation location, string underlyingTypeName) + { + return $"The typedef '{typeName}' at {location.FilePath}:{location.LineNumber}:{location.LineColumn} is a system alias to the system type '{underlyingTypeName}'. If you intend to have cross-platform bindings this is a problem; please create an issue on GitHub."; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/TranspileMacroObjectFailureDiagnostic.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/TranspileMacroObjectFailureDiagnostic.cs new file mode 100644 index 00000000..eae7a711 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/Mapper/Diagnostics/TranspileMacroObjectFailureDiagnostic.cs @@ -0,0 +1,20 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Foundation.Diagnostics; + +namespace C2CS.Feature.WriteCodeCSharp.Domain.Mapper.Diagnostics; + +public sealed class TranspileMacroObjectFailureDiagnostic : Diagnostic +{ + public TranspileMacroObjectFailureDiagnostic(string name, CLocation location) + : base(DiagnosticSeverity.Warning, CreateMessage(name, location)) + { + } + + private static string CreateMessage(string name, CLocation location) + { + return $"The object-like macro '{name}' at {location.FilePath}:{location.LineNumber}:{location.LineColumn} failed to be transpiled."; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpInput.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpInput.cs new file mode 100644 index 00000000..adb615c5 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpInput.cs @@ -0,0 +1,29 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; + +namespace C2CS.Feature.WriteCodeCSharp.Domain; + +public sealed class WriteCodeCSharpInput +{ + public ImmutableArray InputFilePaths { get; init; } + + public string OutputFilePath { get; init; } = string.Empty; + + public ImmutableArray TypeAliases { get; init; } + + public ImmutableArray IgnoredNames { get; init; } + + public string LibraryName { get; init; } = string.Empty; + + public string ClassName { get; init; } = string.Empty; + + public string NamespaceName { get; init; } = string.Empty; + + public string HeaderCodeRegion { get; init; } = string.Empty; + + public string FooterCodeRegion { get; init; } = string.Empty; +} diff --git a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Output.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpOutput.cs similarity index 58% rename from src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Output.cs rename to src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpOutput.cs index f71ee9d8..bfc951ee 100644 --- a/src/cs/production/C2CS.Feature.ExtractAbstractSyntaxTreeC/Output.cs +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpOutput.cs @@ -1,8 +1,10 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS.Feature.ExtractAbstractSyntaxTreeC; +using C2CS.Foundation.UseCases; -public class Output : UseCaseOutput +namespace C2CS.Feature.WriteCodeCSharp.Domain; + +public sealed class WriteCodeCSharpOutput : UseCaseOutput { } diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpValidator.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpValidator.cs new file mode 100644 index 00000000..1470bdac --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Domain/WriteCodeCSharpValidator.cs @@ -0,0 +1,175 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using System.IO.Abstractions; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Foundation.UseCases; +using C2CS.Foundation.UseCases.Exceptions; + +namespace C2CS.Feature.WriteCodeCSharp.Domain; + +public sealed class WriteCodeCSharpValidator : UseCaseValidator +{ + private readonly IFileSystem _fileSystem; + + public WriteCodeCSharpValidator(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public override WriteCodeCSharpInput Validate(WriteCodeCSharpConfiguration configuration) + { + var inputFilePaths = InputFilePaths(configuration.InputFileDirectory); + var outputFilePath = OutputFilePath(configuration.OutputFilePath); + var className = ClassName(configuration.ClassName, outputFilePath); + var libraryName = LibraryName(configuration.LibraryName, className); + var namespaceName = Namespace(configuration.NamespaceName, libraryName); + var typeAliases = TypeAliases(configuration.MappedTypeNames); + var ignoredNames = IgnoredTypeNames(configuration.IgnoredNames); + var headerCodeRegion = HeaderCodeRegion(configuration.HeaderCodeRegionFilePath); + var footerCodeRegion = FooterCodeRegion(configuration.FooterCodeRegionFilePath); + + return new WriteCodeCSharpInput + { + InputFilePaths = inputFilePaths, + OutputFilePath = outputFilePath, + ClassName = className, + LibraryName = libraryName, + NamespaceName = namespaceName, + TypeAliases = typeAliases, + IgnoredNames = ignoredNames, + HeaderCodeRegion = headerCodeRegion, + FooterCodeRegion = footerCodeRegion + }; + } + + private ImmutableArray InputFilePaths(string? inputDirectoryPath) + { + string directoryPath; + if (string.IsNullOrWhiteSpace(inputDirectoryPath)) + { + directoryPath = _fileSystem.Path.Combine(Environment.CurrentDirectory, "ast"); + } + else + { + if (!_fileSystem.Directory.Exists(inputDirectoryPath)) + { + throw new UseCaseException($"The abstract syntax tree input directory '{inputDirectoryPath}' does not exist."); + } + + directoryPath = inputDirectoryPath; + } + + var builder = ImmutableArray.CreateBuilder(); + var filePaths = _fileSystem.Directory.EnumerateFiles(directoryPath); + foreach (var filePath in filePaths) + { + var fileName = _fileSystem.Path.GetFileName(filePath); + var platformString = fileName.Replace(".json", string.Empty, StringComparison.InvariantCulture); + var platform = new TargetPlatform(platformString); + if (platform == TargetPlatform.Unknown) + { + throw new UseCaseException($"Unknown platform '{platform}' for abstract syntax tree."); + } + + builder.Add(filePath); + } + + return builder.ToImmutable(); + } + + private string OutputFilePath(string? outputFilePath) + { + if (string.IsNullOrEmpty(outputFilePath)) + { + throw new UseCaseException($"The output file path can not be an empty or null string."); + } + + var result = _fileSystem.Path.GetFullPath(outputFilePath); + var directoryPath = _fileSystem.Path.GetDirectoryName(outputFilePath); + _fileSystem.Directory.CreateDirectory(directoryPath); + return result; + } + + private static ImmutableArray TypeAliases(ImmutableArray? mappedTypeNames) + { + if (mappedTypeNames == null || mappedTypeNames.Value.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var typeAlias in mappedTypeNames) + { + builder.Add(typeAlias); + } + + return builder.ToImmutable(); + } + + private static ImmutableArray IgnoredTypeNames(ImmutableArray? ignoredTypeNames) + { + if (ignoredTypeNames == null || ignoredTypeNames.Value.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var array = ignoredTypeNames.Value + .Where(x => !string.IsNullOrEmpty(x)) + .Cast(); + return array.ToImmutableArray(); + } + + private static string LibraryName(string? libraryName, string className) + { + return !string.IsNullOrEmpty(libraryName) ? libraryName : className; + } + + private static string Namespace(string? @namespace, string libraryName) + { + return !string.IsNullOrEmpty(@namespace) ? @namespace : libraryName; + } + + private static string ClassName(string? className, string outputFilePath) + { + string result; + if (string.IsNullOrEmpty(className)) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputFilePath); + var firstIndexOfPeriod = fileNameWithoutExtension.IndexOf('.', StringComparison.InvariantCulture); + result = firstIndexOfPeriod == -1 + ? fileNameWithoutExtension + : fileNameWithoutExtension[..firstIndexOfPeriod]; + } + else + { + result = className; + } + + return result; + } + + private static string HeaderCodeRegion(string? headerCodeRegionFilePath) + { + if (string.IsNullOrEmpty(headerCodeRegionFilePath)) + { + return string.Empty; + } + + var code = File.ReadAllText(headerCodeRegionFilePath); + return code; + } + + private static string FooterCodeRegion(string? footerCodeRegionFilePath) + { + if (string.IsNullOrEmpty(footerCodeRegionFilePath)) + { + return string.Empty; + } + + var code = File.ReadAllText(footerCodeRegionFilePath); + return code; + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Startup.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Startup.cs new file mode 100644 index 00000000..91cba917 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/Startup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Feature.WriteCodeCSharp.Domain; +using Microsoft.Extensions.DependencyInjection; + +namespace C2CS.Feature.WriteCodeCSharp; + +public static class Startup +{ + public static void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/WriteCodeCSharpUseCase.cs b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/WriteCodeCSharpUseCase.cs new file mode 100644 index 00000000..16819ba2 --- /dev/null +++ b/src/cs/production/features/C2CS.Feature.WriteCodeCSharp/WriteCodeCSharpUseCase.cs @@ -0,0 +1,132 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Collections.Immutable; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.ReadCodeC.Data.Serialization; +using C2CS.Feature.WriteCodeCSharp.Data; +using C2CS.Feature.WriteCodeCSharp.Data.Model; +using C2CS.Feature.WriteCodeCSharp.Domain; +using C2CS.Feature.WriteCodeCSharp.Domain.CodeGenerator; +using C2CS.Feature.WriteCodeCSharp.Domain.Mapper; +using C2CS.Foundation.Diagnostics; +using C2CS.Foundation.UseCases; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace C2CS.Feature.WriteCodeCSharp; + +public sealed class WriteCodeCSharpUseCase : UseCase +{ + public override string Name => "Bindgen C#"; + + public WriteCodeCSharpUseCase(ILogger logger, IServiceProvider services, WriteCodeCSharpValidator validator) + : base(logger, services, validator) + { + } + + protected override void Execute(WriteCodeCSharpInput input, WriteCodeCSharpOutput output) + { + var abstractSyntaxTreesC = LoadCAbstractSyntaxTrees(input.InputFilePaths); + + var nodesPerPlatform = MapCNodesToCSharpNodes( + abstractSyntaxTreesC, + input.TypeAliases, + input.IgnoredNames, + Diagnostics); + + var abstractSyntaxTreeCSharp = AbstractSyntaxTree(nodesPerPlatform); + + var code = GenerateCSharpCode( + abstractSyntaxTreeCSharp, + input.ClassName, + input.LibraryName, + input.NamespaceName, + input.HeaderCodeRegion, + input.FooterCodeRegion); + + WriteCSharpCodeToFileStorage(input.OutputFilePath, code); + } + + private ImmutableArray LoadCAbstractSyntaxTrees(ImmutableArray filePaths) + { + BeginStep("Load"); + + var cJsonSerializer = Services.GetService()!; + + var builder = ImmutableArray.CreateBuilder(); + foreach (var filePath in filePaths) + { + var ast = cJsonSerializer.Read(filePath); + builder.Add(ast); + } + + EndStep(); + + return builder.ToImmutable(); + } + + private ImmutableDictionary MapCNodesToCSharpNodes( + ImmutableArray abstractSyntaxTrees, + ImmutableArray typeAliases, + ImmutableArray ignoredTypeNames, + DiagnosticsSink diagnostics) + { + BeginStep("Map platform specific nodes"); + + var mapperParameters = new CSharpMapperParameters(typeAliases, ignoredTypeNames, diagnostics); + var mapper = new CSharpMapper(mapperParameters); + var result = mapper.Map(abstractSyntaxTrees); + + EndStep(); + + return result; + } + + private CSharpAbstractSyntaxTree AbstractSyntaxTree(ImmutableDictionary nodesByPlatform) + { + BeginStep("Split/flatten platform specific nodes"); + + var abstractSyntaxTreeBuilder = new BuilderCSharpAbstractSyntaxTree(); + foreach (var (platform, nodes) in nodesByPlatform) + { + abstractSyntaxTreeBuilder.Add(platform, nodes); + } + + var result = abstractSyntaxTreeBuilder.Build(); + + EndStep(); + + return result; + } + + private string GenerateCSharpCode( + CSharpAbstractSyntaxTree abstractSyntaxTree, + string className, + string libraryName, + string namespaceName, + string headerCodeRegion, + string footerCodeRegion) + { + BeginStep("Generate code"); + + var codeGenerator = new GeneratorCSharpCode( + className, libraryName, namespaceName, headerCodeRegion, footerCodeRegion); + var result = codeGenerator.EmitCode(abstractSyntaxTree); + + EndStep(); + + return result; + } + + private void WriteCSharpCodeToFileStorage( + string outputFilePath, string codeCSharp) + { + BeginStep("Write file"); + + File.WriteAllText(outputFilePath, codeCSharp); + Console.WriteLine(outputFilePath); + + EndStep(); + } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj b/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj new file mode 100644 index 00000000..076ef79b --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj @@ -0,0 +1,20 @@ + + + + + net6.0 + enable + C2CS + false + + + + + false + + + + + + + diff --git a/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj.DotSettings b/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj.DotSettings new file mode 100644 index 00000000..e5fa4cad --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/C2CS.Common.csproj.DotSettings @@ -0,0 +1,11 @@ + + False + False + False + False + False + True + True + True + True + True \ No newline at end of file diff --git a/src/cs/production/C2CS.Common/Foundation/ArrayDeque.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/ArrayDeque.cs similarity index 99% rename from src/cs/production/C2CS.Common/Foundation/ArrayDeque.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/ArrayDeque.cs index 75a156c8..bfde6186 100644 --- a/src/cs/production/C2CS.Common/Foundation/ArrayDeque.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/ArrayDeque.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using JetBrains.Annotations; -namespace C2CS; +namespace C2CS.Foundation; // TODO: Add unit tests. // TODO: Allow for a custom resize function. // TODO: Use a struct enumerator. diff --git a/src/cs/production/C2CS.Common/Foundation/ConfigurationException.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/ConfigurationException.cs similarity index 95% rename from src/cs/production/C2CS.Common/Foundation/ConfigurationException.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/ConfigurationException.cs index 6e277ca9..1360259a 100644 --- a/src/cs/production/C2CS.Common/Foundation/ConfigurationException.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/ConfigurationException.cs @@ -3,7 +3,7 @@ using System; -namespace C2CS; +namespace C2CS.Foundation; public class ConfigurationException : Exception { diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/Data/Serialization/SnakeCaseNamingPolicy.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Data/Serialization/SnakeCaseNamingPolicy.cs new file mode 100644 index 00000000..02ccf1b4 --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Data/Serialization/SnakeCaseNamingPolicy.cs @@ -0,0 +1,26 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System.Linq; +using System.Text.Json; + +namespace C2CS.Foundation.Data.Serialization; + +public class SnakeCaseNamingPolicy : JsonNamingPolicy +{ + public static SnakeCaseNamingPolicy Instance { get; } = new(); + + public override string ConvertName(string name) + { + // Conversion to other naming convention goes here. Like SnakeCase, KebabCase etc. + return ToSnakeCase(name); + } + + private static string ToSnakeCase(string str) + { +#pragma warning disable CA1308 + return string.Concat( + str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x : x.ToString())).ToLowerInvariant(); +#pragma warning restore CA1308 + } +} diff --git a/src/cs/production/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs similarity index 53% rename from src/cs/production/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs index 8d6da6c7..595eaf61 100644 --- a/src/cs/production/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/Diagnostic.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System; +using C2CS.Foundation.Logging; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; -namespace C2CS; +namespace C2CS.Foundation.Diagnostics; /// /// Program runtime feedback that is not necessarily a run-time exception. @@ -12,14 +14,7 @@ namespace C2CS; [PublicAPI] public abstract class Diagnostic { - /// - /// Initializes a new instance of the class. - /// - /// The severity of the . - protected Diagnostic(DiagnosticSeverity severity) - { - Severity = severity; - } + private readonly Action _actionLogDiagnostic; /// /// The severity of the . @@ -27,28 +22,33 @@ protected Diagnostic(DiagnosticSeverity severity) public DiagnosticSeverity Severity { get; } /// - /// The short, one sentence long, description of the program's runtime feedback. + /// The message of the . /// - public string? Summary { get; protected set; } + public string Message { get; } = string.Empty; - protected string DiagnosticSeverityShortString + /// + /// Initializes a new instance of the class. + /// + /// The severity of the . + /// The message of the . + protected Diagnostic(DiagnosticSeverity severity, string message) { - get + Severity = severity; + + var logLevel = severity switch { - return Severity switch - { - DiagnosticSeverity.Information => "INFO", - DiagnosticSeverity.Error => "ERROR", - DiagnosticSeverity.Warning => "WARN", - DiagnosticSeverity.Panic => "PANIC", - _ => string.Empty - }; - } - } + DiagnosticSeverity.Information => LogLevel.Information, + DiagnosticSeverity.Warning => LogLevel.Warning, + DiagnosticSeverity.Error => LogLevel.Error, + DiagnosticSeverity.Panic => LogLevel.Critical, + _ => LogLevel.None + }; - public override string ToString() - { - return $"{DiagnosticSeverityShortString}: [{GetName()}] {Summary}"; + var name = GetName(); + _actionLogDiagnostic = LoggerMessage.Define( + logLevel, + LoggingEventRegistry.CreateEventIdentifier(name), + $"- {name} {message}"); } protected string GetName() @@ -62,4 +62,9 @@ protected string GetName() return typeName.Replace("Diagnostic", string.Empty, StringComparison.InvariantCulture); } + + internal void Log(ILogger logger) + { + _actionLogDiagnostic(logger, null!); + } } diff --git a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs similarity index 52% rename from src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs index e91adcbe..e32a94ee 100644 --- a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticPanic.cs @@ -4,14 +4,18 @@ using System; using JetBrains.Annotations; -namespace C2CS; +namespace C2CS.Foundation.Diagnostics; [PublicAPI] -public class DiagnosticPanic : Diagnostic +public sealed class DiagnosticPanic : Diagnostic { public DiagnosticPanic(Exception exception) - : base(DiagnosticSeverity.Panic) + : base(DiagnosticSeverity.Panic, CreateMessage(exception)) { - Summary = $"{exception.Message}{Environment.NewLine}{exception.StackTrace}"; + } + + private static string CreateMessage(Exception exception) + { + return $"{exception.Message}{Environment.NewLine}{exception.StackTrace}"; } } diff --git a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs similarity index 95% rename from src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs index 80318c7f..3ac6bcdc 100644 --- a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticSeverity.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; -namespace C2CS; +namespace C2CS.Foundation.Diagnostics; /// /// Defines different levels of program runtime feedback. diff --git a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs similarity index 83% rename from src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs index 15b0026f..c696a286 100644 --- a/src/cs/production/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Diagnostics/DiagnosticsSink.cs @@ -6,14 +6,14 @@ using System.Linq; using JetBrains.Annotations; -namespace C2CS; +namespace C2CS.Foundation.Diagnostics; [PublicAPI] public sealed class DiagnosticsSink { private readonly List _diagnostics = new(); - public bool HasError => _diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error); + public bool HasFaulted => _diagnostics.Any(x => x.Severity is DiagnosticSeverity.Error or DiagnosticSeverity.Panic); public void Add(Diagnostic diagnostic) { diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/Logging/LoggingEventRegistry.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/Logging/LoggingEventRegistry.cs new file mode 100644 index 00000000..92e58037 --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/Logging/LoggingEventRegistry.cs @@ -0,0 +1,16 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using Microsoft.Extensions.Logging; + +namespace C2CS.Foundation.Logging; + +public static class LoggingEventRegistry +{ + private static int _count; + + public static EventId CreateEventIdentifier(string name) + { + return new EventId(_count++, name); + } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseException.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseException.cs new file mode 100644 index 00000000..776a249b --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseException.cs @@ -0,0 +1,67 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Diagnostics; + +namespace C2CS.Foundation.UseCases.Exceptions; + +public sealed class UseCaseException : Exception +{ + public UseCaseException() + { + } + + public UseCaseException(string message, Exception innerException) + : base(message, innerException) + { + } + + public UseCaseException(string message) + : base(CreateMessage(message)) + { + } + + private static string CreateMessage(string message) + { + var featureName = FeatureName(); + if (string.IsNullOrEmpty(message)) + { + return featureName; + } + + return featureName + Environment.NewLine + message; + } + + private static string FeatureName() + { + var skipFrames = 0; + var featureNamespace = typeof(UseCaseException).Namespace! + ".Feature"; + + while (true) + { + var stackFrame = new StackFrame(skipFrames, false); + var method = stackFrame.GetMethod(); + if (method == null) + { + return string.Empty; + } + + var declaringType = method.DeclaringType; + var typeNamespace = declaringType?.Namespace!; + if (string.IsNullOrEmpty(typeNamespace)) + { + skipFrames++; + continue; + } + + if (!typeNamespace.StartsWith(featureNamespace, StringComparison.InvariantCulture)) + { + skipFrames++; + continue; + } + + return typeNamespace; + } + } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseStepFailedException.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseStepFailedException.cs new file mode 100644 index 00000000..3c7713c4 --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/Exceptions/UseCaseStepFailedException.cs @@ -0,0 +1,13 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; + +namespace C2CS.Foundation.UseCases.Exceptions; + +#pragma warning disable CA1032 +#pragma warning disable CA1064 + +internal sealed class UseCaseStepFailedException : Exception +{ +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCase.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCase.cs new file mode 100644 index 00000000..1551765b --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCase.cs @@ -0,0 +1,170 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Diagnostics; +using C2CS.Foundation.Diagnostics; +using C2CS.Foundation.UseCases.Exceptions; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace C2CS.Foundation.UseCases; + +[PublicAPI] +public abstract class UseCase + where TConfiguration : UseCaseConfiguration + where TOutput : UseCaseOutput, new() +{ + public readonly ILogger Logger; + public readonly IServiceProvider Services; + + private IDisposable? _loggerScope; + private IDisposable? _loggerScopeStep; + private readonly string _name; + private readonly Stopwatch _stopwatch; + private readonly Stopwatch _stepStopwatch; + private readonly UseCaseValidator _validator; + + public abstract string Name { get; } + + protected DiagnosticsSink Diagnostics { get; } = new(); + + protected UseCase( + ILogger logger, + IServiceProvider services, + UseCaseValidator validator) + { + Logger = logger; + Services = services; + + // ReSharper disable once VirtualMemberCallInConstructor + _name = Name; + + _stopwatch = new Stopwatch(); + _stepStopwatch = new Stopwatch(); + _validator = validator; + } + + [DebuggerHidden] + public TOutput Execute(TConfiguration configuration) + { + var output = new TOutput(); + + var previousCurrentDirectory = Environment.CurrentDirectory; + Environment.CurrentDirectory = configuration.WorkingDirectory ?? Environment.CurrentDirectory; + + Begin(); + try + { + output.Input = _validator.Validate(configuration); + Execute(output.Input, output); + } + catch (UseCaseStepFailedException) + { + // used as a way to exit the control flow of the current use case execution and end immediately + } + catch (Exception e) + { + if (Debugger.IsAttached) + { + throw; + } + else + { + Panic(e); + } + } + finally + { + Environment.CurrentDirectory = previousCurrentDirectory; + } + + End(output); + return output; + } + + protected abstract void Execute(TInput input, TOutput output); + + private void Begin() + { + _loggerScope = Logger.BeginScope(_name); + _stopwatch.Reset(); + _stepStopwatch.Reset(); + GarbageCollect(); + Logger.UseCaseStarted(); + _stopwatch.Start(); + } + + private void End(TOutput response) + { + _stopwatch.Stop(); + var timeSpan = _stopwatch.Elapsed; + + response.Complete(Diagnostics.GetAll()); + + if (response.IsSuccessful) + { + Logger.UseCaseSucceeded(timeSpan); + } + else + { + Logger.UseCaseFailed(timeSpan); + } + + foreach (var diagnostic in response.Diagnostics) + { + diagnostic.Log(Logger); + } + + _loggerScope?.Dispose(); + _loggerScope = null; + GarbageCollect(); + } + + private void Panic(Exception e) + { + var diagnostic = new DiagnosticPanic(e); + Diagnostics.Add(diagnostic); + } + + protected void BeginStep(string stepName) + { + _stepStopwatch.Reset(); + _loggerScopeStep = Logger.BeginScope(stepName); + GarbageCollect(); + Logger.UseCaseStepStarted(); + _stepStopwatch.Start(); + } + + protected void EndStep() + { + _stepStopwatch.Stop(); + var timeSpan = _stepStopwatch.Elapsed; + + var isSuccess = !Diagnostics.HasFaulted; + if (isSuccess) + { + Logger.UseCaseStepSucceeded(timeSpan); + } + else + { + Logger.UseCaseStepFailed(timeSpan); + } + + _loggerScopeStep?.Dispose(); + _loggerScopeStep = null; + GarbageCollect(); + + if (!isSuccess) + { + throw new UseCaseStepFailedException(); + } + } + + private static void GarbageCollect() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseConfiguration.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseConfiguration.cs new file mode 100644 index 00000000..71045bcd --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseConfiguration.cs @@ -0,0 +1,15 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +namespace C2CS.Foundation.UseCases; + +/// +/// Represents un-sanitized input for execution of a . +/// +public class UseCaseConfiguration +{ + /// + /// The working directory to use. Default is null. If null, the current directory is used. + /// + public string? WorkingDirectory { get; set; } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseLogging.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseLogging.cs new file mode 100644 index 00000000..ce7f059e --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseLogging.cs @@ -0,0 +1,71 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using C2CS.Foundation.Logging; +using Microsoft.Extensions.Logging; + +namespace C2CS.Foundation.UseCases; + +internal static class UseCaseLogging +{ + private static readonly Action ActionUseCaseStarted = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Use case started"), + "- Use case started"); + + private static readonly Action ActionUseCaseSucceeded = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Use case success"), + "- Use case success in {Elapsed:s\\.fff} seconds"); + + private static readonly Action ActionUseCaseFailed = LoggerMessage.Define( + LogLevel.Error, + LoggingEventRegistry.CreateEventIdentifier("Use case failed"), + "- Use case failed in {Elapsed:s\\.fff} seconds"); + + private static readonly Action ActionUseCaseStepStarted = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Use case step started"), + "- Use case step started"); + + private static readonly Action ActionUseCaseStepSucceeded = LoggerMessage.Define( + LogLevel.Information, + LoggingEventRegistry.CreateEventIdentifier("Use case step success"), + "- Use case step success in {Elapsed:s\\.fff} seconds"); + + private static readonly Action ActionUseCaseStepFailed = LoggerMessage.Define( + LogLevel.Error, + LoggingEventRegistry.CreateEventIdentifier("Use case step failed"), + "- Use case step failed in {Elapsed:s\\.fff} seconds"); + + public static void UseCaseStarted(this ILogger logger) + { + ActionUseCaseStarted(logger, null!); + } + + public static void UseCaseSucceeded(this ILogger logger, TimeSpan timeSpan) + { + ActionUseCaseSucceeded(logger, timeSpan, null!); + } + + public static void UseCaseFailed(this ILogger logger, TimeSpan timeSpan) + { + ActionUseCaseFailed(logger, timeSpan, null!); + } + + public static void UseCaseStepStarted(this ILogger logger) + { + ActionUseCaseStepStarted(logger, null!); + } + + public static void UseCaseStepSucceeded(this ILogger logger, TimeSpan timeSpan) + { + ActionUseCaseStepSucceeded(logger, timeSpan, null!); + } + + public static void UseCaseStepFailed(this ILogger logger, TimeSpan timeSpan) + { + ActionUseCaseStepFailed(logger, timeSpan, null!); + } +} diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs similarity index 58% rename from src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs index 61205cf1..8a9ba153 100644 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseOutput.cs @@ -2,22 +2,25 @@ // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. using System.Collections.Immutable; +using C2CS.Foundation.Diagnostics; -namespace C2CS; +namespace C2CS.Foundation.UseCases; -public abstract class UseCaseOutput +public abstract class UseCaseOutput { - public UseCaseOutputStatus Status { get; private set; } + public bool IsSuccessful { get; private set; } + + public TInput Input { get; internal set; } = default!; public ImmutableArray Diagnostics { get; private set; } - internal void WithDiagnostics(ImmutableArray diagnostics) + internal void Complete(ImmutableArray diagnostics) { Diagnostics = diagnostics; - Status = CalculateStatus(diagnostics); + IsSuccessful = CalculateIsSuccessful(diagnostics); } - private static UseCaseOutputStatus CalculateStatus(ImmutableArray diagnostics) + private static bool CalculateIsSuccessful(ImmutableArray diagnostics) { // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator foreach (var diagnostic in diagnostics) @@ -26,10 +29,10 @@ private static UseCaseOutputStatus CalculateStatus(ImmutableArray di DiagnosticSeverity.Error or DiagnosticSeverity.Panic) { - return UseCaseOutputStatus.Failure; + return false; } } - return UseCaseOutputStatus.Success; + return true; } } diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/IBaseUseCaseRequest.cs b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseValidator.cs similarity index 50% rename from src/cs/production/C2CS.Common/Foundation/UseCases/IBaseUseCaseRequest.cs rename to src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseValidator.cs index 92aa02e1..69b330da 100644 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/IBaseUseCaseRequest.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Foundation/UseCases/UseCaseValidator.cs @@ -1,11 +1,10 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -using System.Diagnostics.CodeAnalysis; +namespace C2CS.Foundation.UseCases; -namespace C2CS; - -[SuppressMessage("Design", "CA1040:Avoid empty interfaces", Justification = "Marker interface.")] -public interface IBaseUseCaseRequest +public abstract class UseCaseValidator + where TConfiguration : UseCaseConfiguration { + public abstract TInput Validate(TConfiguration configuration); } diff --git a/src/cs/production/infrastructure/C2CS.Common/Native/Native.cs b/src/cs/production/infrastructure/C2CS.Common/Native/Native.cs new file mode 100644 index 00000000..2a7d361e --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Native/Native.cs @@ -0,0 +1,166 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Runtime.InteropServices; + +namespace C2CS; + +public static class Native +{ + public static NativeOperatingSystem OperatingSystem + { + get + { + if (System.OperatingSystem.IsWindows()) + { + return NativeOperatingSystem.Windows; + } + + if (System.OperatingSystem.IsMacOS()) + { + return NativeOperatingSystem.macOS; + } + + if (System.OperatingSystem.IsLinux()) + { + return NativeOperatingSystem.Linux; + } + + if (System.OperatingSystem.IsAndroid()) + { + return NativeOperatingSystem.Android; + } + + if (System.OperatingSystem.IsIOS()) + { + return NativeOperatingSystem.iOS; + } + + if (System.OperatingSystem.IsTvOS()) + { + return NativeOperatingSystem.tvOS; + } + + if (System.OperatingSystem.IsBrowser()) + { + return NativeOperatingSystem.Browser; + } + + return NativeOperatingSystem.Unknown; + } + } + + public static NativeArchitecture Architecture + { + get + { + return RuntimeInformation.OSArchitecture switch + { + System.Runtime.InteropServices.Architecture.Arm64 => NativeArchitecture.ARM64, + System.Runtime.InteropServices.Architecture.Arm => NativeArchitecture.ARM32, + System.Runtime.InteropServices.Architecture.X86 => NativeArchitecture.X86, + System.Runtime.InteropServices.Architecture.X64 => NativeArchitecture.X64, + System.Runtime.InteropServices.Architecture.Wasm => NativeArchitecture.WASM32, + System.Runtime.InteropServices.Architecture.S390x => NativeArchitecture.Unknown, + _ => NativeArchitecture.Unknown + }; + } + } + + public static TargetPlatform Platform + { + get + { + var operatingSystem = OperatingSystem; + var architecture = Architecture; + + return operatingSystem switch + { + NativeOperatingSystem.Windows when architecture == NativeArchitecture.X64 => TargetPlatform + .x86_64_pc_windows_gnu, + NativeOperatingSystem.Windows when architecture == NativeArchitecture.X86 => TargetPlatform + .i686_pc_windows_gnu, + NativeOperatingSystem.Windows when architecture == NativeArchitecture.ARM64 => TargetPlatform + .aarch64_pc_windows_gnu, + NativeOperatingSystem.macOS when architecture == NativeArchitecture.ARM64 => TargetPlatform + .aarch64_apple_ios, + NativeOperatingSystem.macOS when architecture == NativeArchitecture.X64 => TargetPlatform + .x86_64_apple_darwin, + NativeOperatingSystem.Linux when architecture == NativeArchitecture.X64 => TargetPlatform + .x86_64_unknown_linux_gnu, + NativeOperatingSystem.Linux when architecture == NativeArchitecture.X86 => TargetPlatform + .i686_unknown_linux_gnu, + NativeOperatingSystem.Linux when architecture == NativeArchitecture.ARM64 => TargetPlatform + .aarch64_unknown_linux_gnu, + _ => throw new InvalidOperationException("Unknown platform host.") + }; + } + } + + /// + /// Gets the library file name extension given a . + /// + /// The runtime platform. + /// + /// A containing the library file name extension for the + /// . + /// + /// is not available yet with .NET 5. + /// is not a known valid value. + public static string LibraryFileNameExtension(NativeOperatingSystem operatingSystem) + { + return operatingSystem switch + { + NativeOperatingSystem.Windows => ".dll", + NativeOperatingSystem.Xbox => ".dll", + NativeOperatingSystem.macOS => ".dylib", + NativeOperatingSystem.tvOS => ".dylib", + NativeOperatingSystem.iOS => ".dylib", + NativeOperatingSystem.Linux => ".so", + NativeOperatingSystem.FreeBSD => ".so", + NativeOperatingSystem.Android => ".so", + NativeOperatingSystem.PlayStation4 => ".so", + NativeOperatingSystem.Browser => throw new NotImplementedException(), + NativeOperatingSystem.Switch => throw new NotImplementedException(), + NativeOperatingSystem.DualScreen3D => throw new NotImplementedException(), + NativeOperatingSystem.Unknown => throw new NotImplementedException(), + _ => throw new ArgumentOutOfRangeException(nameof(operatingSystem), operatingSystem, null) + }; + } + + /// + /// Gets the library file name prefix for a . + /// + /// The runtime platform. + /// + /// A containing the library file name prefix for the + /// . + /// + /// is not available yet with .NET 5. + /// is not a known valid value. + public static string LibraryFileNamePrefix(NativeOperatingSystem nativeOperatingSystem) + { + switch (nativeOperatingSystem) + { + case NativeOperatingSystem.Windows: + case NativeOperatingSystem.Xbox: + return string.Empty; + case NativeOperatingSystem.macOS: + case NativeOperatingSystem.tvOS: + case NativeOperatingSystem.iOS: + case NativeOperatingSystem.Linux: + case NativeOperatingSystem.FreeBSD: + case NativeOperatingSystem.Android: + case NativeOperatingSystem.PlayStation4: + return "lib"; + case NativeOperatingSystem.Browser: + case NativeOperatingSystem.Switch: + case NativeOperatingSystem.DualScreen3D: + case NativeOperatingSystem.Unknown: + throw new NotImplementedException(); + default: + throw new ArgumentOutOfRangeException(nameof(nativeOperatingSystem), nativeOperatingSystem, null); + } + } +} diff --git a/src/cs/production/C2CS.Common/Platform/RuntimeArchitecture.cs b/src/cs/production/infrastructure/C2CS.Common/Native/NativeArchitecture.cs similarity index 59% rename from src/cs/production/C2CS.Common/Platform/RuntimeArchitecture.cs rename to src/cs/production/infrastructure/C2CS.Common/Native/NativeArchitecture.cs index b7e3ca47..f828ed75 100644 --- a/src/cs/production/C2CS.Common/Platform/RuntimeArchitecture.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Native/NativeArchitecture.cs @@ -10,7 +10,7 @@ namespace C2CS; /// Defines the native computer architectures. /// [PublicAPI] -public enum RuntimeArchitecture +public enum NativeArchitecture { /// /// Unknown computer architecture. @@ -18,20 +18,22 @@ public enum RuntimeArchitecture Unknown = 0, /// - /// Intel x86_x64 64-bit computing architecture. Commonly found in modern desktop platforms such as Windows - /// 10. Also commonly found in some eighth generation consoles such as Xbox One and PlayStation 4. + /// Intel or AMD (Advanced Micro Devices) x86_x64 64-bit computing architecture. Commonly found in modern + /// desktop platforms such as Windows 10. Also commonly found in some eighth generation consoles such as Xbox + /// One and PlayStation 4. /// X64 = 1, /// - /// Intel x86 32-bit computing architecture. Commonly found in legacy desktop platforms. + /// Intel or AMD (Advanced Micro Devices) x86 32-bit computing architecture. Commonly found in legacy desktop + /// platforms. /// X86 = 2, /// /// ARM (Advanced RISC (Reduced Instruction Set Computer) Machines) 64-bit computing architecture. Commonly /// found in modern mobile or some modern console platforms such as iOS, Nintendo Switch, etc. Also observed - /// in some modern laptops such as the M1 from Apple. + /// in some modern laptops such as Apple Silicon. /// [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Product name.")] ARM64, @@ -41,5 +43,17 @@ public enum RuntimeArchitecture /// found in legacy mobile or legacy console platforms. /// [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Product name.")] - ARM32 = 4 + ARM32 = 4, + + /// + /// WebAssembly 64-bit. + /// + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Product name.")] + WASM64 = 5, + + /// + /// WebAssembly 32-bit. + /// + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Product name.")] + WASM32 = 6, } diff --git a/src/cs/production/C2CS.Common/Platform/RuntimeOperatingSystem.cs b/src/cs/production/infrastructure/C2CS.Common/Native/NativeOperatingSystem.cs similarity index 89% rename from src/cs/production/C2CS.Common/Platform/RuntimeOperatingSystem.cs rename to src/cs/production/infrastructure/C2CS.Common/Native/NativeOperatingSystem.cs index b483eaf2..a44e76b3 100644 --- a/src/cs/production/C2CS.Common/Platform/RuntimeOperatingSystem.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Native/NativeOperatingSystem.cs @@ -7,10 +7,10 @@ namespace C2CS; /// -/// Defines the native runtime operating systems. +/// Defines the native operating systems. /// [PublicAPI] -public enum RuntimeOperatingSystem +public enum NativeOperatingSystem { /// /// Unknown operating system. @@ -81,7 +81,7 @@ public enum RuntimeOperatingSystem /// /// Versions of the PlayStation operating system. Otherwise known as "Orbis OS". Based on . /// - PlayStation = 9, + PlayStation4 = 9, /// /// Versions of the Xbox operating system. @@ -89,7 +89,12 @@ public enum RuntimeOperatingSystem Xbox = 10, /// - /// TODO. Subject to change. + /// Versions of the Nintendo Switch operating system. /// - Switch = 11 + Switch = 11, + + /// + /// Versions of the Nintendo 3DS operating system. + /// + DualScreen3D = 12 } diff --git a/src/cs/production/infrastructure/C2CS.Common/Native/Serialization/NativePlatformJsonConverter.cs b/src/cs/production/infrastructure/C2CS.Common/Native/Serialization/NativePlatformJsonConverter.cs new file mode 100644 index 00000000..13c20e52 --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Native/Serialization/NativePlatformJsonConverter.cs @@ -0,0 +1,34 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace C2CS.Serialization; + +public class NativePlatformJsonConverter : JsonConverter +{ + public override TargetPlatform Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var value = reader.GetString(); + if (string.IsNullOrEmpty(value)) + { + return TargetPlatform.Unknown; + } + + var result = new TargetPlatform(value); + return result; + } + + public override void Write( + Utf8JsonWriter writer, + TargetPlatform value, + JsonSerializerOptions options) + { + writer.WriteStringValue(value.TargetName); + } +} diff --git a/src/cs/production/infrastructure/C2CS.Common/Native/TargetPlatform.cs b/src/cs/production/infrastructure/C2CS.Common/Native/TargetPlatform.cs new file mode 100644 index 00000000..20a1c75c --- /dev/null +++ b/src/cs/production/infrastructure/C2CS.Common/Native/TargetPlatform.cs @@ -0,0 +1,274 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Text.Json.Serialization; +using C2CS.Serialization; +using JetBrains.Annotations; + +namespace C2CS; + +// ReSharper disable InconsistentNaming +#pragma warning disable SA1124 +#pragma warning disable SA1300 +#pragma warning disable SA1307 +#pragma warning disable SA1310 +#pragma warning disable SA1311 +#pragma warning disable SA1313 +#pragma warning disable CA2211 +#pragma warning disable CA1707 +#pragma warning disable IDE1006 + +/// +/// Defines the native platforms. +/// +[PublicAPI] +[JsonConverter(typeof(NativePlatformJsonConverter))] +public record struct TargetPlatform(string TargetName) +{ + internal string TargetName = TargetName; + + /// + /// The runtime operating system. + /// + public NativeOperatingSystem OperatingSystem = ParseTargetOperatingSystem(TargetName); + + /// + /// The runtime computer architecture. + /// + public NativeArchitecture Architecture = ParseTargetArchitecture(TargetName); + + /// + /// Unknown runtime platform. + /// + public static readonly TargetPlatform Unknown = new("unknown-unknown-unknown"); + + #region Windows + + /// + /// x86 Windows (32-bit, Windows 7+) using Microsoft's compiler and linker. + /// + public static readonly TargetPlatform i686_pc_windows_msvc = new("i686-pc-windows-msvc"); + + /// + /// x64 Windows (64-bit, Windows 7+) using Microsoft's compiler and linker. + /// + public static readonly TargetPlatform x86_64_pc_windows_msvc = new("x86_64-pc-windows-msvc"); + + /// + /// ARM64 Windows (64-bit) using Microsoft's compiler and linker. + /// + public static readonly TargetPlatform aarch64_pc_windows_msvc = new("aarch64-pc-windows-msvc"); + + /// + /// x86 Windows (32-bit, Windows 7+) using GNU's Compiler Collection (GCC). + /// + public static readonly TargetPlatform i686_pc_windows_gnu = new("i686-pc-windows-gnu"); + + /// + /// x64 Windows (64-bit, Windows 7+) using GNU's Compiler Collection (GCC). + /// + public static readonly TargetPlatform x86_64_pc_windows_gnu = new("x86_64-pc-windows-gnu"); + + /// + /// ARM64 Windows (64-bit) using GNU's Compiler Collection (GCC). + /// + public static readonly TargetPlatform aarch64_pc_windows_gnu = new("aarch64-pc-windows-gnu"); + + #endregion + + #region Linux + + /// + /// x86 Linux (32-bit, kernel 2.6.32+, glibc 2.11+). + /// + public static readonly TargetPlatform i686_unknown_linux_gnu = new("i686-unknown-linux-gnu"); + + /// + /// x64 Linux (64-bit, kernel 2.6.32+, glibc 2.11+). + /// + public static readonly TargetPlatform x86_64_unknown_linux_gnu = new("x86_64-unknown-linux-gnu"); + + /// + /// ARM64 Linux (64-bit, kernel 4.2, glibc 2.17+). + /// + public static readonly TargetPlatform aarch64_unknown_linux_gnu = new("aarch64-unknown-linux-gnu"); + + #endregion + + #region macOS + + /// + /// x86 macOS (32-bit, 10.7+, Lion+). + /// + public static readonly TargetPlatform i686_apple_darwin = new("i686-apple-darwin"); + + /// + /// x64 macOS (64-bit, 10.7+, Lion+). + /// + public static readonly TargetPlatform x86_64_apple_darwin = new("x86_64-apple-darwin"); + + /// + /// ARM64 macOS (64-bit, 11.0+, Big Sur+). + /// + public static readonly TargetPlatform aarch64_apple_darwin = new("aarch64-apple-darwin"); + + #endregion + + #region iOS + + /// + /// ARM64 iOS (64-bit). + /// + public static readonly TargetPlatform aarch64_apple_ios = new("aarch64-apple-ios"); + + /// + /// ARM64 iOS simulator (64-bit). + /// + public static readonly TargetPlatform aarch64_apple_ios_sim = new("aarch64-apple-ios-sim"); + + /// + /// x64 iOS (64-bit). + /// + public static readonly TargetPlatform x86_64_apple_ios = new("x86_64-apple-ios"); + + #endregion + + #region Android + + /// + /// ARM64 Android (64-bit). + /// + public static readonly TargetPlatform aarch64_linux_android = new("aarch64-linux-android"); + + #endregion + + #region Browser + + /// + /// WebAssembly (32-bit). + /// + public static readonly TargetPlatform wasm32_unknown_unknown = new("wasm32-unknown-unknown"); + + /// + /// WebAssembly via Emscripten (32-bit). + /// + public static readonly TargetPlatform wasm32_unknown_emscripten = new("wasm32-unknown-emscripten"); + + #endregion + + #region Sony + + /// + /// PlayStation 4 (64-bit). + /// + public static readonly TargetPlatform x86_64_scei_ps4 = new("x86_64-scei-ps4"); + + #endregion + + #region Nintendo + + /// + /// Nintendo 3DS (32-bit). + /// + public static readonly TargetPlatform armv6k_nintendo_3ds = new("armv6k-nintendo-3ds"); + + #endregion + + private static NativeArchitecture ParseTargetArchitecture(string targetTriple) + { + if (targetTriple.StartsWith("aarch64-", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.ARM64; + } + + if (targetTriple.StartsWith("x86_64-", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.X64; + } + + if (targetTriple.StartsWith("i686-", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.X86; + } + + if (targetTriple.StartsWith("arm-", StringComparison.InvariantCultureIgnoreCase) || + targetTriple.StartsWith("armv7-", StringComparison.InvariantCultureIgnoreCase) || + targetTriple.StartsWith("armv6k-", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.ARM32; + } + + if (targetTriple.StartsWith("wasm64", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.WASM64; + } + + if (targetTriple.StartsWith("wasm32", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeArchitecture.WASM32; + } + + return NativeArchitecture.Unknown; + } + + private static NativeOperatingSystem ParseTargetOperatingSystem(string targetTriple) + { + if (targetTriple.Contains("-pc-windows", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.Windows; + } + + if (targetTriple.Contains("-unknown-linux", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.Linux; + } + + if (targetTriple.Contains("-apple-darwin", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.macOS; + } + + if (targetTriple.Contains("-apple-ios", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.iOS; + } + + if (targetTriple.Contains("-apple-tvos", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.tvOS; + } + + if (targetTriple.Contains("-unknown-freebsd", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.FreeBSD; + } + + if (targetTriple.Contains("-linux-android", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.Android; + } + + if (targetTriple.Contains("-scei-ps4", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.PlayStation4; + } + + if (targetTriple.Contains("-nintendo_3ds", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.DualScreen3D; + } + + if (targetTriple.StartsWith("wasm", StringComparison.InvariantCultureIgnoreCase)) + { + return NativeOperatingSystem.Browser; + } + + return NativeOperatingSystem.Unknown; + } + + public override string ToString() + { + return TargetName; + } +} diff --git a/src/cs/production/C2CS.Common/Platform/Terminal.cs b/src/cs/production/infrastructure/C2CS.Common/Native/Terminal.cs similarity index 96% rename from src/cs/production/C2CS.Common/Platform/Terminal.cs rename to src/cs/production/infrastructure/C2CS.Common/Native/Terminal.cs index d51087cd..5ca9475b 100644 --- a/src/cs/production/C2CS.Common/Platform/Terminal.cs +++ b/src/cs/production/infrastructure/C2CS.Common/Native/Terminal.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Threading; using JetBrains.Annotations; // ReSharper disable once EmptyNamespace @@ -89,8 +88,8 @@ private static Process CreateShellProcess(string command, string? workingDirecto } else { - var platform = Platform.HostOperatingSystem; - if (platform == RuntimeOperatingSystem.Windows) + var operatingSystem = Native.OperatingSystem; + if (operatingSystem == NativeOperatingSystem.Windows) { if (windowsUsePowerShell) { @@ -188,8 +187,8 @@ public static bool CMake(string rootDirectory, string cMakeDirectoryPath, string return false; } - var runtimePlatform = Platform.HostOperatingSystem; - var libraryFileNameExtension = Platform.LibraryFileNameExtension(runtimePlatform); + var operatingSystem = Native.OperatingSystem; + var libraryFileNameExtension = Native.LibraryFileNameExtension(operatingSystem); var outputFilePaths = Directory.EnumerateFiles( outputDirectoryPath, $"*{libraryFileNameExtension}", SearchOption.AllDirectories); foreach (var outputFilePath in outputFilePaths) @@ -198,7 +197,7 @@ public static bool CMake(string rootDirectory, string cMakeDirectoryPath, string outputDirectoryPath, libraryOutputDirectoryPath, StringComparison.InvariantCulture); var targetFileName = Path.GetFileName(targetFilePath); - if (runtimePlatform == RuntimeOperatingSystem.Windows) + if (operatingSystem == NativeOperatingSystem.Windows) { if (targetFileName.StartsWith("lib", StringComparison.InvariantCulture)) { diff --git a/src/cs/production/C2CS.Runtime/C2CS.Runtime.csproj b/src/cs/production/infrastructure/C2CS.Runtime/C2CS.Runtime.csproj similarity index 89% rename from src/cs/production/C2CS.Runtime/C2CS.Runtime.csproj rename to src/cs/production/infrastructure/C2CS.Runtime/C2CS.Runtime.csproj index cfecbae2..3af171d4 100644 --- a/src/cs/production/C2CS.Runtime/C2CS.Runtime.csproj +++ b/src/cs/production/infrastructure/C2CS.Runtime/C2CS.Runtime.csproj @@ -2,7 +2,7 @@ - net5.0 + net6.0 diff --git a/src/cs/production/C2CS.Runtime/C2CS.Runtime.csproj.DotSettings b/src/cs/production/infrastructure/C2CS.Runtime/C2CS.Runtime.csproj.DotSettings similarity index 100% rename from src/cs/production/C2CS.Runtime/C2CS.Runtime.csproj.DotSettings rename to src/cs/production/infrastructure/C2CS.Runtime/C2CS.Runtime.csproj.DotSettings diff --git a/src/cs/production/C2CS.Runtime/CBool.cs b/src/cs/production/infrastructure/C2CS.Runtime/CBool.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CBool.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CBool.cs diff --git a/src/cs/production/C2CS.Runtime/CChar.cs b/src/cs/production/infrastructure/C2CS.Runtime/CChar.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CChar.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CChar.cs diff --git a/src/cs/production/C2CS.Runtime/CCharWide.cs b/src/cs/production/infrastructure/C2CS.Runtime/CCharWide.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CCharWide.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CCharWide.cs diff --git a/src/cs/production/C2CS.Runtime/CString.cs b/src/cs/production/infrastructure/C2CS.Runtime/CString.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CString.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CString.cs diff --git a/src/cs/production/C2CS.Runtime/CStringWide.cs b/src/cs/production/infrastructure/C2CS.Runtime/CStringWide.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CStringWide.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CStringWide.cs diff --git a/src/cs/production/C2CS.Runtime/CStrings.cs b/src/cs/production/infrastructure/C2CS.Runtime/CStrings.cs similarity index 100% rename from src/cs/production/C2CS.Runtime/CStrings.cs rename to src/cs/production/infrastructure/C2CS.Runtime/CStrings.cs diff --git a/src/cs/production/clang-c/Program.cs b/src/cs/production/infrastructure/clang-c/Program.cs similarity index 81% rename from src/cs/production/clang-c/Program.cs rename to src/cs/production/infrastructure/clang-c/Program.cs index 779451a2..63177556 100644 --- a/src/cs/production/clang-c/Program.cs +++ b/src/cs/production/infrastructure/clang-c/Program.cs @@ -1,10 +1,12 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. +using System; + internal static class Program { private static void Main() { - C2CS.Program.Main(); + C2CS.Program.Main(Array.Empty()); } } diff --git a/src/cs/production/clang-c/api.txt b/src/cs/production/infrastructure/clang-c/api.txt similarity index 100% rename from src/cs/production/clang-c/api.txt rename to src/cs/production/infrastructure/clang-c/api.txt diff --git a/src/cs/production/infrastructure/clang-c/clang-c.csproj b/src/cs/production/infrastructure/clang-c/clang-c.csproj new file mode 100644 index 00000000..aa33cb5d --- /dev/null +++ b/src/cs/production/infrastructure/clang-c/clang-c.csproj @@ -0,0 +1,27 @@ + + + + + net6.0 + Exe + + + + + + false + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/cs/production/clang-c/config.json b/src/cs/production/infrastructure/clang-c/config.json similarity index 88% rename from src/cs/production/clang-c/config.json rename to src/cs/production/infrastructure/clang-c/config.json index 47915f90..1d731608 100644 --- a/src/cs/production/clang-c/config.json +++ b/src/cs/production/infrastructure/clang-c/config.json @@ -1,10 +1,9 @@ { + "ast": { "inputFilePath": "../../../../ext/clang/include/clang-c/Index.h", "includeDirectories": [ "../../../../ext/clang/include" ], - "outputFilePath": "../../../../src/cs/production/clang-cs/clang.cs", - "namespaceName": "bottlenoselabs", "functionNamesWhitelist": [ "clang_Cursor_getOffsetOfField", "clang_Cursor_getTranslationUnit", @@ -53,4 +52,13 @@ "clang_tokenize", "clang_visitChildren" ] - } \ No newline at end of file + }, + "cs": { + "outputFilePath": "../../../../src/cs/production/infrastructure/clang-cs/clang.cs", + "namespaceName": "bottlenoselabs", + "ignoredNames": [ + "LLVM_CLANG_C_STRICT_PROTOTYPES_BEGIN", + "LLVM_CLANG_C_STRICT_PROTOTYPES_END" + ] + } +} \ No newline at end of file diff --git a/src/cs/production/clang-cs/clang-cs.csproj b/src/cs/production/infrastructure/clang-cs/clang-cs.csproj similarity index 74% rename from src/cs/production/clang-cs/clang-cs.csproj rename to src/cs/production/infrastructure/clang-cs/clang-cs.csproj index 152c8879..79ba4b8a 100644 --- a/src/cs/production/clang-cs/clang-cs.csproj +++ b/src/cs/production/infrastructure/clang-cs/clang-cs.csproj @@ -7,6 +7,11 @@ + + + false + + diff --git a/src/cs/production/clang-cs/clang.cs b/src/cs/production/infrastructure/clang-cs/clang.cs similarity index 78% rename from src/cs/production/clang-cs/clang.cs rename to src/cs/production/infrastructure/clang-cs/clang.cs index 88409cfe..3bd45245 100644 --- a/src/cs/production/clang-cs/clang.cs +++ b/src/cs/production/infrastructure/clang-cs/clang.cs @@ -25,175 +25,175 @@ public static unsafe partial class clang private const string LibraryName = "clang"; // Function @ CXString.h:50:28 (clang-c/CXString.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CString clang_getCString(CXString @string); // Function @ Index.h:266:24 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXIndex clang_createIndex(int excludeDeclarationsFromPCH, int displayDiagnostics); // Function @ Index.h:358:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXString clang_getFileName(CXFile SFile); // Function @ Index.h:507:20 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern int clang_Location_isInSystemHeader(CXSourceLocation location); // Function @ Index.h:674:21 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void clang_getFileLocation(CXSourceLocation location, CXFile* file, ulong* line, ulong* column, ulong* offset); // Function @ Index.h:861:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_getNumDiagnostics(CXTranslationUnit Unit); // Function @ Index.h:872:29 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXDiagnostic clang_getDiagnostic(CXTranslationUnit Unit, uint Index); // Function @ Index.h:972:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXString clang_formatDiagnostic(CXDiagnostic Diagnostic, uint Options); // Function @ Index.h:982:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_defaultDiagnosticDisplayOptions(); // Function @ Index.h:988:5 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXDiagnosticSeverity clang_getDiagnosticSeverity(CXDiagnostic param); // Function @ Index.h:1418:33 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXErrorCode clang_parseTranslationUnit2(CXIndex CIdx, CString source_filename, CString* command_line_args, int num_command_line_args, CXUnsavedFile* unsaved_files, uint num_unsaved_files, uint options, CXTranslationUnit* out_TU); // Function @ Index.h:2705:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXCursor clang_getTranslationUnitCursor(CXTranslationUnit param); // Function @ Index.h:2725:34 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXCursorKind clang_getCursorKind(CXCursor param); // Function @ Index.h:2820:35 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXLinkageKind clang_getCursorLinkage(CXCursor cursor); // Function @ Index.h:2993:34 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXTranslationUnit clang_Cursor_getTranslationUnit(CXCursor param); // Function @ Index.h:3198:33 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXSourceLocation clang_getCursorLocation(CXCursor param); // Function @ Index.h:3211:30 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXSourceRange clang_getCursorExtent(CXCursor param); // Function @ Index.h:3414:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getCursorType(CXCursor C); // Function @ Index.h:3422:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXString clang_getTypeSpelling(CXType CT); // Function @ Index.h:3430:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getTypedefDeclUnderlyingType(CXCursor C); // Function @ Index.h:3438:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getEnumDeclIntegerType(CXCursor C); // Function @ Index.h:3448:26 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern long clang_getEnumConstantDeclValue(CXCursor C); // Function @ Index.h:3633:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_Cursor_isMacroFunctionLike(CXCursor C); // Function @ Index.h:3639:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_Cursor_isMacroBuiltin(CXCursor C); // Function @ Index.h:3674:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getPointeeType(CXType T); // Function @ Index.h:3679:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXCursor clang_getTypeDeclaration(CXType T); // Function @ Index.h:3701:35 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXCallingConv clang_getFunctionTypeCallingConv(CXType T); // Function @ Index.h:3708:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getResultType(CXType T); // Function @ Index.h:3781:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getCursorResultType(CXCursor C); // Function @ Index.h:3804:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getElementType(CXType T); // Function @ Index.h:3819:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_getArrayElementType(CXType T); // Function @ Index.h:3826:26 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern long clang_getArraySize(CXType T); // Function @ Index.h:3833:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_Type_getNamedType(CXType T); // Function @ Index.h:3927:26 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern long clang_Type_getAlignOf(CXType T); // Function @ Index.h:3945:26 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern long clang_Type_getSizeOf(CXType T); // Function @ Index.h:3967:23 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXType clang_Type_getModifiedType(CXType T); // Function @ Index.h:3989:26 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern long clang_Cursor_getOffsetOfField(CXCursor C); // Function @ Index.h:3995:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_Cursor_isAnonymous(CXCursor C); // Function @ Index.h:4217:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern uint clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data); // Function @ Index.h:4312:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXString clang_getCursorSpelling(CXCursor param); // Function @ Index.h:5001:25 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern CXString clang_getTokenSpelling(CXTranslationUnit param, CXToken param2); // Function @ Index.h:5031:21 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void clang_tokenize(CXTranslationUnit TU, CXSourceRange Range, CXToken** Tokens, ulong* NumTokens); // Function @ Index.h:5070:21 (clang-c/Index.h) - [DllImport(LibraryName)] + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] public static extern void clang_disposeTokens(CXTranslationUnit TU, CXToken* Tokens, uint NumTokens); // FunctionPointer @ Index.h:4191:35 (clang-c/Index.h) @@ -415,6 +415,49 @@ public struct CXClientData public static implicit operator CXClientData(void* data) => new() { Data = data }; } + // Enum @ CXErrorCode.h:28:6 (clang-c/CXErrorCode.h) + public enum CXErrorCode : int + { + CXError_Success = 0, + CXError_Failure = 1, + CXError_Crashed = 2, + CXError_InvalidArguments = 3, + CXError_ASTReadError = 4 + } + + // Enum @ Index.h:125:6 (clang-c/Index.h) + public enum CXAvailabilityKind : int + { + CXAvailability_Available = 0, + CXAvailability_Deprecated = 1, + CXAvailability_NotAvailable = 2, + CXAvailability_NotAccessible = 3 + } + + // Enum @ Index.h:174:6 (clang-c/Index.h) + public enum CXCursor_ExceptionSpecificationKind : int + { + CXCursor_ExceptionSpecificationKind_None = 0, + CXCursor_ExceptionSpecificationKind_DynamicNone = 1, + CXCursor_ExceptionSpecificationKind_Dynamic = 2, + CXCursor_ExceptionSpecificationKind_MSAny = 3, + CXCursor_ExceptionSpecificationKind_BasicNoexcept = 4, + CXCursor_ExceptionSpecificationKind_ComputedNoexcept = 5, + CXCursor_ExceptionSpecificationKind_Unevaluated = 6, + CXCursor_ExceptionSpecificationKind_Uninstantiated = 7, + CXCursor_ExceptionSpecificationKind_Unparsed = 8, + CXCursor_ExceptionSpecificationKind_NoThrow = 9 + } + + // Enum @ Index.h:277:9 (clang-c/Index.h) + public enum CXGlobalOptFlags : int + { + CXGlobalOpt_None = 0, + CXGlobalOpt_ThreadBackgroundPriorityForIndexing = 1, + CXGlobalOpt_ThreadBackgroundPriorityForEditing = 2, + CXGlobalOpt_ThreadBackgroundPriorityForAll = 3 + } + // Enum @ Index.h:739:6 (clang-c/Index.h) public enum CXDiagnosticSeverity : int { @@ -425,14 +468,90 @@ public enum CXDiagnosticSeverity : int CXDiagnostic_Fatal = 4 } - // Enum @ CXErrorCode.h:28:6 (clang-c/CXErrorCode.h) - public enum CXErrorCode : int + // Enum @ Index.h:803:6 (clang-c/Index.h) + public enum CXLoadDiag_Error : int { - CXError_Success = 0, - CXError_Failure = 1, - CXError_Crashed = 2, - CXError_InvalidArguments = 3, - CXError_ASTReadError = 4 + CXLoadDiag_None = 0, + CXLoadDiag_Unknown = 1, + CXLoadDiag_CannotLoad = 2, + CXLoadDiag_InvalidFile = 3 + } + + // Enum @ Index.h:895:6 (clang-c/Index.h) + public enum CXDiagnosticDisplayOptions : int + { + CXDiagnostic_DisplaySourceLocation = 1, + CXDiagnostic_DisplayColumn = 2, + CXDiagnostic_DisplaySourceRanges = 4, + CXDiagnostic_DisplayOption = 8, + CXDiagnostic_DisplayCategoryId = 16, + CXDiagnostic_DisplayCategoryName = 32 + } + + // Enum @ Index.h:1199:6 (clang-c/Index.h) + public enum CXTranslationUnit_Flags : int + { + CXTranslationUnit_None = 0, + CXTranslationUnit_DetailedPreprocessingRecord = 1, + CXTranslationUnit_Incomplete = 2, + CXTranslationUnit_PrecompiledPreamble = 4, + CXTranslationUnit_CacheCompletionResults = 8, + CXTranslationUnit_ForSerialization = 16, + CXTranslationUnit_CXXChainedPCH = 32, + CXTranslationUnit_SkipFunctionBodies = 64, + CXTranslationUnit_IncludeBriefCommentsInCodeCompletion = 128, + CXTranslationUnit_CreatePreambleOnFirstParse = 256, + CXTranslationUnit_KeepGoing = 512, + CXTranslationUnit_SingleFileParse = 1024, + CXTranslationUnit_LimitSkipFunctionBodiesToPreamble = 2048, + CXTranslationUnit_IncludeAttributedTypes = 4096, + CXTranslationUnit_VisitImplicitAttributes = 8192, + CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles = 16384, + CXTranslationUnit_RetainExcludedConditionalBlocks = 32768 + } + + // Enum @ Index.h:1442:6 (clang-c/Index.h) + public enum CXSaveTranslationUnit_Flags : int + { + CXSaveTranslationUnit_None = 0 + } + + // Enum @ Index.h:1464:6 (clang-c/Index.h) + public enum CXSaveError : int + { + CXSaveError_None = 0, + CXSaveError_Unknown = 1, + CXSaveError_TranslationErrors = 2, + CXSaveError_InvalidTU = 3 + } + + // Enum @ Index.h:1543:6 (clang-c/Index.h) + public enum CXReparse_Flags : int + { + CXReparse_None = 0 + } + + // Enum @ Index.h:1609:6 (clang-c/Index.h) + public enum CXTUResourceUsageKind : int + { + CXTUResourceUsage_AST = 1, + CXTUResourceUsage_Identifiers = 2, + CXTUResourceUsage_Selectors = 3, + CXTUResourceUsage_GlobalCompletionResults = 4, + CXTUResourceUsage_SourceManagerContentCache = 5, + CXTUResourceUsage_AST_SideTables = 6, + CXTUResourceUsage_SourceManager_Membuffer_Malloc = 7, + CXTUResourceUsage_SourceManager_Membuffer_MMap = 8, + CXTUResourceUsage_ExternalASTSource_Membuffer_Malloc = 9, + CXTUResourceUsage_ExternalASTSource_Membuffer_MMap = 10, + CXTUResourceUsage_Preprocessor = 11, + CXTUResourceUsage_PreprocessingRecord = 12, + CXTUResourceUsage_SourceManager_DataStructures = 13, + CXTUResourceUsage_Preprocessor_HeaderSearch = 14, + CXTUResourceUsage_MEMORY_IN_BYTES_BEGIN = 1, + CXTUResourceUsage_MEMORY_IN_BYTES_END = 14, + CXTUResourceUsage_First = 1, + CXTUResourceUsage_Last = 14 } // Enum @ Index.h:1706:6 (clang-c/Index.h) @@ -715,29 +834,30 @@ public enum CXLinkageKind : int CXLinkage_External = 4 } - // Enum @ Index.h:3377:6 (clang-c/Index.h) - public enum CXCallingConv : int + // Enum @ Index.h:2822:6 (clang-c/Index.h) + public enum CXVisibilityKind : int { - CXCallingConv_Default = 0, - CXCallingConv_C = 1, - CXCallingConv_X86StdCall = 2, - CXCallingConv_X86FastCall = 3, - CXCallingConv_X86ThisCall = 4, - CXCallingConv_X86Pascal = 5, - CXCallingConv_AAPCS = 6, - CXCallingConv_AAPCS_VFP = 7, - CXCallingConv_X86RegCall = 8, - CXCallingConv_IntelOclBicc = 9, - CXCallingConv_Win64 = 10, - CXCallingConv_X86_64Win64 = 10, - CXCallingConv_X86_64SysV = 11, - CXCallingConv_X86VectorCall = 12, - CXCallingConv_Swift = 13, - CXCallingConv_PreserveMost = 14, - CXCallingConv_PreserveAll = 15, - CXCallingConv_AArch64VectorCall = 16, - CXCallingConv_Invalid = 100, - CXCallingConv_Unexposed = 200 + CXVisibility_Invalid = 0, + CXVisibility_Hidden = 1, + CXVisibility_Protected = 2, + CXVisibility_Default = 3 + } + + // Enum @ Index.h:2966:6 (clang-c/Index.h) + public enum CXLanguageKind : int + { + CXLanguage_Invalid = 0, + CXLanguage_C = 1, + CXLanguage_ObjC = 2, + CXLanguage_CPlusPlus = 3 + } + + // Enum @ Index.h:2982:6 (clang-c/Index.h) + public enum CXTLSKind : int + { + CXTLS_None = 0, + CXTLS_Dynamic = 1, + CXTLS_Static = 2 } // Enum @ Index.h:3226:6 (clang-c/Index.h) @@ -865,6 +985,97 @@ public enum CXTypeKind : int CXType_Atomic = 177 } + // Enum @ Index.h:3377:6 (clang-c/Index.h) + public enum CXCallingConv : int + { + CXCallingConv_Default = 0, + CXCallingConv_C = 1, + CXCallingConv_X86StdCall = 2, + CXCallingConv_X86FastCall = 3, + CXCallingConv_X86ThisCall = 4, + CXCallingConv_X86Pascal = 5, + CXCallingConv_AAPCS = 6, + CXCallingConv_AAPCS_VFP = 7, + CXCallingConv_X86RegCall = 8, + CXCallingConv_IntelOclBicc = 9, + CXCallingConv_Win64 = 10, + CXCallingConv_X86_64Win64 = 10, + CXCallingConv_X86_64SysV = 11, + CXCallingConv_X86VectorCall = 12, + CXCallingConv_Swift = 13, + CXCallingConv_PreserveMost = 14, + CXCallingConv_PreserveAll = 15, + CXCallingConv_AArch64VectorCall = 16, + CXCallingConv_Invalid = 100, + CXCallingConv_Unexposed = 200 + } + + // Enum @ Index.h:3492:6 (clang-c/Index.h) + public enum CXTemplateArgumentKind : int + { + CXTemplateArgumentKind_Null = 0, + CXTemplateArgumentKind_Type = 1, + CXTemplateArgumentKind_Declaration = 2, + CXTemplateArgumentKind_NullPtr = 3, + CXTemplateArgumentKind_Integral = 4, + CXTemplateArgumentKind_Template = 5, + CXTemplateArgumentKind_TemplateExpansion = 6, + CXTemplateArgumentKind_Expression = 7, + CXTemplateArgumentKind_Pack = 8, + CXTemplateArgumentKind_Invalid = 9 + } + + // Enum @ Index.h:3845:6 (clang-c/Index.h) + public enum CXTypeNullabilityKind : int + { + CXTypeNullability_NonNull = 0, + CXTypeNullability_Nullable = 1, + CXTypeNullability_Unspecified = 2, + CXTypeNullability_Invalid = 3, + CXTypeNullability_NullableResult = 4 + } + + // Enum @ Index.h:3888:6 (clang-c/Index.h) + public enum CXTypeLayoutError : int + { + CXTypeLayoutError_Invalid = -1, + CXTypeLayoutError_Incomplete = -2, + CXTypeLayoutError_Dependent = -3, + CXTypeLayoutError_NotConstantSize = -4, + CXTypeLayoutError_InvalidFieldName = -5, + CXTypeLayoutError_Undeduced = -6 + } + + // Enum @ Index.h:4009:6 (clang-c/Index.h) + public enum CXRefQualifierKind : int + { + CXRefQualifier_None = 0, + CXRefQualifier_LValue = 1, + CXRefQualifier_RValue = 2 + } + + // Enum @ Index.h:4058:6 (clang-c/Index.h) + public enum CX_CXXAccessSpecifier : int + { + CX_CXXInvalidAccessSpecifier = 0, + CX_CXXPublic = 1, + CX_CXXProtected = 2, + CX_CXXPrivate = 3 + } + + // Enum @ Index.h:4078:6 (clang-c/Index.h) + public enum CX_StorageClass : int + { + CX_SC_Invalid = 0, + CX_SC_None = 1, + CX_SC_Extern = 2, + CX_SC_Static = 3, + CX_SC_PrivateExtern = 4, + CX_SC_OpenCLWorkGroupLocal = 5, + CX_SC_Auto = 6, + CX_SC_Register = 7 + } + // Enum @ Index.h:4162:6 (clang-c/Index.h) public enum CXChildVisitResult : int { @@ -873,380 +1084,286 @@ public enum CXChildVisitResult : int CXChildVisit_Recurse = 2 } - // Pseudo enum 'CXAvailabilityKind' @ Index.h:125:6 (clang-c/Index.h) - public const int CXAvailability_Available = 0; - public const int CXAvailability_Deprecated = 1; - public const int CXAvailability_NotAvailable = 2; - public const int CXAvailability_NotAccessible = 3; - - // Pseudo enum 'CXCursor_ExceptionSpecificationKind' @ Index.h:174:6 (clang-c/Index.h) - public const int CXCursor_ExceptionSpecificationKind_None = 0; - public const int CXCursor_ExceptionSpecificationKind_DynamicNone = 1; - public const int CXCursor_ExceptionSpecificationKind_Dynamic = 2; - public const int CXCursor_ExceptionSpecificationKind_MSAny = 3; - public const int CXCursor_ExceptionSpecificationKind_BasicNoexcept = 4; - public const int CXCursor_ExceptionSpecificationKind_ComputedNoexcept = 5; - public const int CXCursor_ExceptionSpecificationKind_Unevaluated = 6; - public const int CXCursor_ExceptionSpecificationKind_Uninstantiated = 7; - public const int CXCursor_ExceptionSpecificationKind_Unparsed = 8; - public const int CXCursor_ExceptionSpecificationKind_NoThrow = 9; - - // Pseudo enum 'CXGlobalOptFlags' @ Index.h:277:9 (clang-c/Index.h) - public const int CXGlobalOpt_None = 0; - public const int CXGlobalOpt_ThreadBackgroundPriorityForIndexing = 1; - public const int CXGlobalOpt_ThreadBackgroundPriorityForEditing = 2; - public const int CXGlobalOpt_ThreadBackgroundPriorityForAll = 3; - - // Pseudo enum 'CXLoadDiag_Error' @ Index.h:803:6 (clang-c/Index.h) - public const int CXLoadDiag_None = 0; - public const int CXLoadDiag_Unknown = 1; - public const int CXLoadDiag_CannotLoad = 2; - public const int CXLoadDiag_InvalidFile = 3; - - // Pseudo enum 'CXDiagnosticDisplayOptions' @ Index.h:895:6 (clang-c/Index.h) - public const int CXDiagnostic_DisplaySourceLocation = 1; - public const int CXDiagnostic_DisplayColumn = 2; - public const int CXDiagnostic_DisplaySourceRanges = 4; - public const int CXDiagnostic_DisplayOption = 8; - public const int CXDiagnostic_DisplayCategoryId = 16; - public const int CXDiagnostic_DisplayCategoryName = 32; - - // Pseudo enum 'CXTranslationUnit_Flags' @ Index.h:1199:6 (clang-c/Index.h) - public const int CXTranslationUnit_None = 0; - public const int CXTranslationUnit_DetailedPreprocessingRecord = 1; - public const int CXTranslationUnit_Incomplete = 2; - public const int CXTranslationUnit_PrecompiledPreamble = 4; - public const int CXTranslationUnit_CacheCompletionResults = 8; - public const int CXTranslationUnit_ForSerialization = 16; - public const int CXTranslationUnit_CXXChainedPCH = 32; - public const int CXTranslationUnit_SkipFunctionBodies = 64; - public const int CXTranslationUnit_IncludeBriefCommentsInCodeCompletion = 128; - public const int CXTranslationUnit_CreatePreambleOnFirstParse = 256; - public const int CXTranslationUnit_KeepGoing = 512; - public const int CXTranslationUnit_SingleFileParse = 1024; - public const int CXTranslationUnit_LimitSkipFunctionBodiesToPreamble = 2048; - public const int CXTranslationUnit_IncludeAttributedTypes = 4096; - public const int CXTranslationUnit_VisitImplicitAttributes = 8192; - public const int CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles = 16384; - public const int CXTranslationUnit_RetainExcludedConditionalBlocks = 32768; - - // Pseudo enum 'CXSaveTranslationUnit_Flags' @ Index.h:1442:6 (clang-c/Index.h) - public const int CXSaveTranslationUnit_None = 0; - - // Pseudo enum 'CXSaveError' @ Index.h:1464:6 (clang-c/Index.h) - public const int CXSaveError_None = 0; - public const int CXSaveError_Unknown = 1; - public const int CXSaveError_TranslationErrors = 2; - public const int CXSaveError_InvalidTU = 3; - - // Pseudo enum 'CXReparse_Flags' @ Index.h:1543:6 (clang-c/Index.h) - public const int CXReparse_None = 0; - - // Pseudo enum 'CXTUResourceUsageKind' @ Index.h:1609:6 (clang-c/Index.h) - public const int CXTUResourceUsage_AST = 1; - public const int CXTUResourceUsage_Identifiers = 2; - public const int CXTUResourceUsage_Selectors = 3; - public const int CXTUResourceUsage_GlobalCompletionResults = 4; - public const int CXTUResourceUsage_SourceManagerContentCache = 5; - public const int CXTUResourceUsage_AST_SideTables = 6; - public const int CXTUResourceUsage_SourceManager_Membuffer_Malloc = 7; - public const int CXTUResourceUsage_SourceManager_Membuffer_MMap = 8; - public const int CXTUResourceUsage_ExternalASTSource_Membuffer_Malloc = 9; - public const int CXTUResourceUsage_ExternalASTSource_Membuffer_MMap = 10; - public const int CXTUResourceUsage_Preprocessor = 11; - public const int CXTUResourceUsage_PreprocessingRecord = 12; - public const int CXTUResourceUsage_SourceManager_DataStructures = 13; - public const int CXTUResourceUsage_Preprocessor_HeaderSearch = 14; - public const int CXTUResourceUsage_MEMORY_IN_BYTES_BEGIN = 1; - public const int CXTUResourceUsage_MEMORY_IN_BYTES_END = 14; - public const int CXTUResourceUsage_First = 1; - public const int CXTUResourceUsage_Last = 14; - - // Pseudo enum 'CXVisibilityKind' @ Index.h:2822:6 (clang-c/Index.h) - public const int CXVisibility_Invalid = 0; - public const int CXVisibility_Hidden = 1; - public const int CXVisibility_Protected = 2; - public const int CXVisibility_Default = 3; - - // Pseudo enum 'CXLanguageKind' @ Index.h:2966:6 (clang-c/Index.h) - public const int CXLanguage_Invalid = 0; - public const int CXLanguage_C = 1; - public const int CXLanguage_ObjC = 2; - public const int CXLanguage_CPlusPlus = 3; - - // Pseudo enum 'CXTLSKind' @ Index.h:2982:6 (clang-c/Index.h) - public const int CXTLS_None = 0; - public const int CXTLS_Dynamic = 1; - public const int CXTLS_Static = 2; - - // Pseudo enum 'CXTemplateArgumentKind' @ Index.h:3492:6 (clang-c/Index.h) - public const int CXTemplateArgumentKind_Null = 0; - public const int CXTemplateArgumentKind_Type = 1; - public const int CXTemplateArgumentKind_Declaration = 2; - public const int CXTemplateArgumentKind_NullPtr = 3; - public const int CXTemplateArgumentKind_Integral = 4; - public const int CXTemplateArgumentKind_Template = 5; - public const int CXTemplateArgumentKind_TemplateExpansion = 6; - public const int CXTemplateArgumentKind_Expression = 7; - public const int CXTemplateArgumentKind_Pack = 8; - public const int CXTemplateArgumentKind_Invalid = 9; - - // Pseudo enum 'CXTypeNullabilityKind' @ Index.h:3845:6 (clang-c/Index.h) - public const int CXTypeNullability_NonNull = 0; - public const int CXTypeNullability_Nullable = 1; - public const int CXTypeNullability_Unspecified = 2; - public const int CXTypeNullability_Invalid = 3; - public const int CXTypeNullability_NullableResult = 4; - - // Pseudo enum 'CXTypeLayoutError' @ Index.h:3888:6 (clang-c/Index.h) - public const int CXTypeLayoutError_Invalid = -1; - public const int CXTypeLayoutError_Incomplete = -2; - public const int CXTypeLayoutError_Dependent = -3; - public const int CXTypeLayoutError_NotConstantSize = -4; - public const int CXTypeLayoutError_InvalidFieldName = -5; - public const int CXTypeLayoutError_Undeduced = -6; - - // Pseudo enum 'CXRefQualifierKind' @ Index.h:4009:6 (clang-c/Index.h) - public const int CXRefQualifier_None = 0; - public const int CXRefQualifier_LValue = 1; - public const int CXRefQualifier_RValue = 2; - - // Pseudo enum 'CX_CXXAccessSpecifier' @ Index.h:4058:6 (clang-c/Index.h) - public const int CX_CXXInvalidAccessSpecifier = 0; - public const int CX_CXXPublic = 1; - public const int CX_CXXProtected = 2; - public const int CX_CXXPrivate = 3; - - // Pseudo enum 'CX_StorageClass' @ Index.h:4078:6 (clang-c/Index.h) - public const int CX_SC_Invalid = 0; - public const int CX_SC_None = 1; - public const int CX_SC_Extern = 2; - public const int CX_SC_Static = 3; - public const int CX_SC_PrivateExtern = 4; - public const int CX_SC_OpenCLWorkGroupLocal = 5; - public const int CX_SC_Auto = 6; - public const int CX_SC_Register = 7; - - // Pseudo enum 'CXPrintingPolicyProperty' @ Index.h:4339:6 (clang-c/Index.h) - public const int CXPrintingPolicy_Indentation = 0; - public const int CXPrintingPolicy_SuppressSpecifiers = 1; - public const int CXPrintingPolicy_SuppressTagKeyword = 2; - public const int CXPrintingPolicy_IncludeTagDefinition = 3; - public const int CXPrintingPolicy_SuppressScope = 4; - public const int CXPrintingPolicy_SuppressUnwrittenScope = 5; - public const int CXPrintingPolicy_SuppressInitializers = 6; - public const int CXPrintingPolicy_ConstantArraySizeAsWritten = 7; - public const int CXPrintingPolicy_AnonymousTagLocations = 8; - public const int CXPrintingPolicy_SuppressStrongLifetime = 9; - public const int CXPrintingPolicy_SuppressLifetimeQualifiers = 10; - public const int CXPrintingPolicy_SuppressTemplateArgsInCXXConstructors = 11; - public const int CXPrintingPolicy_Bool = 12; - public const int CXPrintingPolicy_Restrict = 13; - public const int CXPrintingPolicy_Alignof = 14; - public const int CXPrintingPolicy_UnderscoreAlignof = 15; - public const int CXPrintingPolicy_UseVoidForZeroParams = 16; - public const int CXPrintingPolicy_TerseOutput = 17; - public const int CXPrintingPolicy_PolishForDeclaration = 18; - public const int CXPrintingPolicy_Half = 19; - public const int CXPrintingPolicy_MSWChar = 20; - public const int CXPrintingPolicy_IncludeNewlines = 21; - public const int CXPrintingPolicy_MSVCFormatting = 22; - public const int CXPrintingPolicy_ConstantsAsWritten = 23; - public const int CXPrintingPolicy_SuppressImplicitBase = 24; - public const int CXPrintingPolicy_FullyQualifiedName = 25; - public const int CXPrintingPolicy_LastProperty = 25; - - // Pseudo enum 'CXObjCPropertyAttrKind' @ Index.h:4530:9 (clang-c/Index.h) - public const int CXObjCPropertyAttr_noattr = 0; - public const int CXObjCPropertyAttr_readonly = 1; - public const int CXObjCPropertyAttr_getter = 2; - public const int CXObjCPropertyAttr_assign = 4; - public const int CXObjCPropertyAttr_readwrite = 8; - public const int CXObjCPropertyAttr_retain = 16; - public const int CXObjCPropertyAttr_copy = 32; - public const int CXObjCPropertyAttr_nonatomic = 64; - public const int CXObjCPropertyAttr_setter = 128; - public const int CXObjCPropertyAttr_atomic = 256; - public const int CXObjCPropertyAttr_weak = 512; - public const int CXObjCPropertyAttr_strong = 1024; - public const int CXObjCPropertyAttr_unsafe_unretained = 2048; - public const int CXObjCPropertyAttr_class = 4096; - - // Pseudo enum 'CXObjCDeclQualifierKind' @ Index.h:4573:9 (clang-c/Index.h) - public const int CXObjCDeclQualifier_None = 0; - public const int CXObjCDeclQualifier_In = 1; - public const int CXObjCDeclQualifier_Inout = 2; - public const int CXObjCDeclQualifier_Out = 4; - public const int CXObjCDeclQualifier_Bycopy = 8; - public const int CXObjCDeclQualifier_Byref = 16; - public const int CXObjCDeclQualifier_Oneway = 32; - - // Pseudo enum 'CXNameRefFlags' @ Index.h:4898:6 (clang-c/Index.h) - public const int CXNameRange_WantQualifier = 1; - public const int CXNameRange_WantTemplateArgs = 2; - public const int CXNameRange_WantSinglePiece = 4; - - // Pseudo enum 'CXTokenKind' @ Index.h:4941:14 (clang-c/Index.h) - public const int CXToken_Punctuation = 0; - public const int CXToken_Keyword = 1; - public const int CXToken_Identifier = 2; - public const int CXToken_Literal = 3; - public const int CXToken_Comment = 4; - - // Pseudo enum 'CXCompletionChunkKind' @ Index.h:5156:6 (clang-c/Index.h) - public const int CXCompletionChunk_Optional = 0; - public const int CXCompletionChunk_TypedText = 1; - public const int CXCompletionChunk_Text = 2; - public const int CXCompletionChunk_Placeholder = 3; - public const int CXCompletionChunk_Informative = 4; - public const int CXCompletionChunk_CurrentParameter = 5; - public const int CXCompletionChunk_LeftParen = 6; - public const int CXCompletionChunk_RightParen = 7; - public const int CXCompletionChunk_LeftBracket = 8; - public const int CXCompletionChunk_RightBracket = 9; - public const int CXCompletionChunk_LeftBrace = 10; - public const int CXCompletionChunk_RightBrace = 11; - public const int CXCompletionChunk_LeftAngle = 12; - public const int CXCompletionChunk_RightAngle = 13; - public const int CXCompletionChunk_Comma = 14; - public const int CXCompletionChunk_ResultType = 15; - public const int CXCompletionChunk_Colon = 16; - public const int CXCompletionChunk_SemiColon = 17; - public const int CXCompletionChunk_Equal = 18; - public const int CXCompletionChunk_HorizontalSpace = 19; - public const int CXCompletionChunk_VerticalSpace = 20; - - // Pseudo enum 'CXCodeComplete_Flags' @ Index.h:5547:6 (clang-c/Index.h) - public const int CXCodeComplete_IncludeMacros = 1; - public const int CXCodeComplete_IncludeCodePatterns = 2; - public const int CXCodeComplete_IncludeBriefComments = 4; - public const int CXCodeComplete_SkipPreamble = 8; - public const int CXCodeComplete_IncludeCompletionsWithFixIts = 16; - - // Pseudo enum 'CXCompletionContext' @ Index.h:5586:6 (clang-c/Index.h) - public const int CXCompletionContext_Unexposed = 0; - public const int CXCompletionContext_AnyType = 1; - public const int CXCompletionContext_AnyValue = 2; - public const int CXCompletionContext_ObjCObjectValue = 4; - public const int CXCompletionContext_ObjCSelectorValue = 8; - public const int CXCompletionContext_CXXClassTypeValue = 16; - public const int CXCompletionContext_DotMemberAccess = 32; - public const int CXCompletionContext_ArrowMemberAccess = 64; - public const int CXCompletionContext_ObjCPropertyAccess = 128; - public const int CXCompletionContext_EnumTag = 256; - public const int CXCompletionContext_UnionTag = 512; - public const int CXCompletionContext_StructTag = 1024; - public const int CXCompletionContext_ClassTag = 2048; - public const int CXCompletionContext_Namespace = 4096; - public const int CXCompletionContext_NestedNameSpecifier = 8192; - public const int CXCompletionContext_ObjCInterface = 16384; - public const int CXCompletionContext_ObjCProtocol = 32768; - public const int CXCompletionContext_ObjCCategory = 65536; - public const int CXCompletionContext_ObjCInstanceMessage = 131072; - public const int CXCompletionContext_ObjCClassMessage = 262144; - public const int CXCompletionContext_ObjCSelectorName = 524288; - public const int CXCompletionContext_MacroName = 1048576; - public const int CXCompletionContext_NaturalLanguage = 2097152; - public const int CXCompletionContext_IncludedFile = 4194304; - public const int CXCompletionContext_Unknown = 8388607; - - // Pseudo enum 'CXEvalResultKind' @ Index.h:5944:9 (clang-c/Index.h) - public const int CXEval_Int = 1; - public const int CXEval_Float = 2; - public const int CXEval_ObjCStrLiteral = 3; - public const int CXEval_StrLiteral = 4; - public const int CXEval_CFStr = 5; - public const int CXEval_Other = 6; - public const int CXEval_UnExposed = 0; - - // Pseudo enum 'CXVisitorResult' @ Index.h:6087:6 (clang-c/Index.h) - public const int CXVisit_Break = 0; - public const int CXVisit_Continue = 1; - - // Pseudo enum 'CXResult' @ Index.h:6094:9 (clang-c/Index.h) - public const int CXResult_Success = 0; - public const int CXResult_Invalid = 1; - public const int CXResult_VisitBreak = 2; - - // Pseudo enum 'CXIdxEntityKind' @ Index.h:6239:9 (clang-c/Index.h) - public const int CXIdxEntity_Unexposed = 0; - public const int CXIdxEntity_Typedef = 1; - public const int CXIdxEntity_Function = 2; - public const int CXIdxEntity_Variable = 3; - public const int CXIdxEntity_Field = 4; - public const int CXIdxEntity_EnumConstant = 5; - public const int CXIdxEntity_ObjCClass = 6; - public const int CXIdxEntity_ObjCProtocol = 7; - public const int CXIdxEntity_ObjCCategory = 8; - public const int CXIdxEntity_ObjCInstanceMethod = 9; - public const int CXIdxEntity_ObjCClassMethod = 10; - public const int CXIdxEntity_ObjCProperty = 11; - public const int CXIdxEntity_ObjCIvar = 12; - public const int CXIdxEntity_Enum = 13; - public const int CXIdxEntity_Struct = 14; - public const int CXIdxEntity_Union = 15; - public const int CXIdxEntity_CXXClass = 16; - public const int CXIdxEntity_CXXNamespace = 17; - public const int CXIdxEntity_CXXNamespaceAlias = 18; - public const int CXIdxEntity_CXXStaticVariable = 19; - public const int CXIdxEntity_CXXStaticMethod = 20; - public const int CXIdxEntity_CXXInstanceMethod = 21; - public const int CXIdxEntity_CXXConstructor = 22; - public const int CXIdxEntity_CXXDestructor = 23; - public const int CXIdxEntity_CXXConversionFunction = 24; - public const int CXIdxEntity_CXXTypeAlias = 25; - public const int CXIdxEntity_CXXInterface = 26; - - // Pseudo enum 'CXIdxEntityLanguage' @ Index.h:6274:9 (clang-c/Index.h) - public const int CXIdxEntityLang_None = 0; - public const int CXIdxEntityLang_C = 1; - public const int CXIdxEntityLang_ObjC = 2; - public const int CXIdxEntityLang_CXX = 3; - public const int CXIdxEntityLang_Swift = 4; - - // Pseudo enum 'CXIdxEntityCXXTemplateKind' @ Index.h:6292:9 (clang-c/Index.h) - public const int CXIdxEntity_NonTemplate = 0; - public const int CXIdxEntity_Template = 1; - public const int CXIdxEntity_TemplatePartialSpecialization = 2; - public const int CXIdxEntity_TemplateSpecialization = 3; - - // Pseudo enum 'CXIdxAttrKind' @ Index.h:6299:9 (clang-c/Index.h) - public const int CXIdxAttr_Unexposed = 0; - public const int CXIdxAttr_IBAction = 1; - public const int CXIdxAttr_IBOutlet = 2; - public const int CXIdxAttr_IBOutletCollection = 3; - - // Pseudo enum 'CXIdxDeclInfoFlags' @ Index.h:6334:9 (clang-c/Index.h) - public const int CXIdxDeclFlag_Skipped = 1; - - // Pseudo enum 'CXIdxObjCContainerKind' @ Index.h:6362:9 (clang-c/Index.h) - public const int CXIdxObjCContainer_ForwardRef = 0; - public const int CXIdxObjCContainer_Interface = 1; - public const int CXIdxObjCContainer_Implementation = 2; - - // Pseudo enum 'CXIdxEntityRefKind' @ Index.h:6422:9 (clang-c/Index.h) - public const int CXIdxEntityRef_Direct = 1; - public const int CXIdxEntityRef_Implicit = 2; - - // Pseudo enum 'CXSymbolRole' @ Index.h:6440:9 (clang-c/Index.h) - public const int CXSymbolRole_None = 0; - public const int CXSymbolRole_Declaration = 1; - public const int CXSymbolRole_Definition = 2; - public const int CXSymbolRole_Reference = 4; - public const int CXSymbolRole_Read = 8; - public const int CXSymbolRole_Write = 16; - public const int CXSymbolRole_Call = 32; - public const int CXSymbolRole_Dynamic = 64; - public const int CXSymbolRole_AddressOf = 128; - public const int CXSymbolRole_Implicit = 256; - - // Pseudo enum 'CXIndexOptFlags' @ Index.h:6612:9 (clang-c/Index.h) - public const int CXIndexOpt_None = 0; - public const int CXIndexOpt_SuppressRedundantRefs = 1; - public const int CXIndexOpt_IndexFunctionLocalSymbols = 2; - public const int CXIndexOpt_IndexImplicitTemplateInstantiations = 4; - public const int CXIndexOpt_SuppressWarnings = 8; - public const int CXIndexOpt_SkipParsedBodiesInSession = 16; + // Enum @ Index.h:4339:6 (clang-c/Index.h) + public enum CXPrintingPolicyProperty : int + { + CXPrintingPolicy_Indentation = 0, + CXPrintingPolicy_SuppressSpecifiers = 1, + CXPrintingPolicy_SuppressTagKeyword = 2, + CXPrintingPolicy_IncludeTagDefinition = 3, + CXPrintingPolicy_SuppressScope = 4, + CXPrintingPolicy_SuppressUnwrittenScope = 5, + CXPrintingPolicy_SuppressInitializers = 6, + CXPrintingPolicy_ConstantArraySizeAsWritten = 7, + CXPrintingPolicy_AnonymousTagLocations = 8, + CXPrintingPolicy_SuppressStrongLifetime = 9, + CXPrintingPolicy_SuppressLifetimeQualifiers = 10, + CXPrintingPolicy_SuppressTemplateArgsInCXXConstructors = 11, + CXPrintingPolicy_Bool = 12, + CXPrintingPolicy_Restrict = 13, + CXPrintingPolicy_Alignof = 14, + CXPrintingPolicy_UnderscoreAlignof = 15, + CXPrintingPolicy_UseVoidForZeroParams = 16, + CXPrintingPolicy_TerseOutput = 17, + CXPrintingPolicy_PolishForDeclaration = 18, + CXPrintingPolicy_Half = 19, + CXPrintingPolicy_MSWChar = 20, + CXPrintingPolicy_IncludeNewlines = 21, + CXPrintingPolicy_MSVCFormatting = 22, + CXPrintingPolicy_ConstantsAsWritten = 23, + CXPrintingPolicy_SuppressImplicitBase = 24, + CXPrintingPolicy_FullyQualifiedName = 25, + CXPrintingPolicy_LastProperty = 25 + } + + // Enum @ Index.h:4530:9 (clang-c/Index.h) + public enum CXObjCPropertyAttrKind : int + { + CXObjCPropertyAttr_noattr = 0, + CXObjCPropertyAttr_readonly = 1, + CXObjCPropertyAttr_getter = 2, + CXObjCPropertyAttr_assign = 4, + CXObjCPropertyAttr_readwrite = 8, + CXObjCPropertyAttr_retain = 16, + CXObjCPropertyAttr_copy = 32, + CXObjCPropertyAttr_nonatomic = 64, + CXObjCPropertyAttr_setter = 128, + CXObjCPropertyAttr_atomic = 256, + CXObjCPropertyAttr_weak = 512, + CXObjCPropertyAttr_strong = 1024, + CXObjCPropertyAttr_unsafe_unretained = 2048, + CXObjCPropertyAttr_class = 4096 + } + + // Enum @ Index.h:4573:9 (clang-c/Index.h) + public enum CXObjCDeclQualifierKind : int + { + CXObjCDeclQualifier_None = 0, + CXObjCDeclQualifier_In = 1, + CXObjCDeclQualifier_Inout = 2, + CXObjCDeclQualifier_Out = 4, + CXObjCDeclQualifier_Bycopy = 8, + CXObjCDeclQualifier_Byref = 16, + CXObjCDeclQualifier_Oneway = 32 + } + + // Enum @ Index.h:4898:6 (clang-c/Index.h) + public enum CXNameRefFlags : int + { + CXNameRange_WantQualifier = 1, + CXNameRange_WantTemplateArgs = 2, + CXNameRange_WantSinglePiece = 4 + } + + // Enum @ Index.h:4941:14 (clang-c/Index.h) + public enum CXTokenKind : int + { + CXToken_Punctuation = 0, + CXToken_Keyword = 1, + CXToken_Identifier = 2, + CXToken_Literal = 3, + CXToken_Comment = 4 + } + + // Enum @ Index.h:5156:6 (clang-c/Index.h) + public enum CXCompletionChunkKind : int + { + CXCompletionChunk_Optional = 0, + CXCompletionChunk_TypedText = 1, + CXCompletionChunk_Text = 2, + CXCompletionChunk_Placeholder = 3, + CXCompletionChunk_Informative = 4, + CXCompletionChunk_CurrentParameter = 5, + CXCompletionChunk_LeftParen = 6, + CXCompletionChunk_RightParen = 7, + CXCompletionChunk_LeftBracket = 8, + CXCompletionChunk_RightBracket = 9, + CXCompletionChunk_LeftBrace = 10, + CXCompletionChunk_RightBrace = 11, + CXCompletionChunk_LeftAngle = 12, + CXCompletionChunk_RightAngle = 13, + CXCompletionChunk_Comma = 14, + CXCompletionChunk_ResultType = 15, + CXCompletionChunk_Colon = 16, + CXCompletionChunk_SemiColon = 17, + CXCompletionChunk_Equal = 18, + CXCompletionChunk_HorizontalSpace = 19, + CXCompletionChunk_VerticalSpace = 20 + } + + // Enum @ Index.h:5547:6 (clang-c/Index.h) + public enum CXCodeComplete_Flags : int + { + CXCodeComplete_IncludeMacros = 1, + CXCodeComplete_IncludeCodePatterns = 2, + CXCodeComplete_IncludeBriefComments = 4, + CXCodeComplete_SkipPreamble = 8, + CXCodeComplete_IncludeCompletionsWithFixIts = 16 + } + + // Enum @ Index.h:5586:6 (clang-c/Index.h) + public enum CXCompletionContext : int + { + CXCompletionContext_Unexposed = 0, + CXCompletionContext_AnyType = 1, + CXCompletionContext_AnyValue = 2, + CXCompletionContext_ObjCObjectValue = 4, + CXCompletionContext_ObjCSelectorValue = 8, + CXCompletionContext_CXXClassTypeValue = 16, + CXCompletionContext_DotMemberAccess = 32, + CXCompletionContext_ArrowMemberAccess = 64, + CXCompletionContext_ObjCPropertyAccess = 128, + CXCompletionContext_EnumTag = 256, + CXCompletionContext_UnionTag = 512, + CXCompletionContext_StructTag = 1024, + CXCompletionContext_ClassTag = 2048, + CXCompletionContext_Namespace = 4096, + CXCompletionContext_NestedNameSpecifier = 8192, + CXCompletionContext_ObjCInterface = 16384, + CXCompletionContext_ObjCProtocol = 32768, + CXCompletionContext_ObjCCategory = 65536, + CXCompletionContext_ObjCInstanceMessage = 131072, + CXCompletionContext_ObjCClassMessage = 262144, + CXCompletionContext_ObjCSelectorName = 524288, + CXCompletionContext_MacroName = 1048576, + CXCompletionContext_NaturalLanguage = 2097152, + CXCompletionContext_IncludedFile = 4194304, + CXCompletionContext_Unknown = 8388607 + } + + // Enum @ Index.h:5944:9 (clang-c/Index.h) + public enum CXEvalResultKind : int + { + CXEval_Int = 1, + CXEval_Float = 2, + CXEval_ObjCStrLiteral = 3, + CXEval_StrLiteral = 4, + CXEval_CFStr = 5, + CXEval_Other = 6, + CXEval_UnExposed = 0 + } + + // Enum @ Index.h:6087:6 (clang-c/Index.h) + public enum CXVisitorResult : int + { + CXVisit_Break = 0, + CXVisit_Continue = 1 + } + + // Enum @ Index.h:6094:9 (clang-c/Index.h) + public enum CXResult : int + { + CXResult_Success = 0, + CXResult_Invalid = 1, + CXResult_VisitBreak = 2 + } + + // Enum @ Index.h:6239:9 (clang-c/Index.h) + public enum CXIdxEntityKind : int + { + CXIdxEntity_Unexposed = 0, + CXIdxEntity_Typedef = 1, + CXIdxEntity_Function = 2, + CXIdxEntity_Variable = 3, + CXIdxEntity_Field = 4, + CXIdxEntity_EnumConstant = 5, + CXIdxEntity_ObjCClass = 6, + CXIdxEntity_ObjCProtocol = 7, + CXIdxEntity_ObjCCategory = 8, + CXIdxEntity_ObjCInstanceMethod = 9, + CXIdxEntity_ObjCClassMethod = 10, + CXIdxEntity_ObjCProperty = 11, + CXIdxEntity_ObjCIvar = 12, + CXIdxEntity_Enum = 13, + CXIdxEntity_Struct = 14, + CXIdxEntity_Union = 15, + CXIdxEntity_CXXClass = 16, + CXIdxEntity_CXXNamespace = 17, + CXIdxEntity_CXXNamespaceAlias = 18, + CXIdxEntity_CXXStaticVariable = 19, + CXIdxEntity_CXXStaticMethod = 20, + CXIdxEntity_CXXInstanceMethod = 21, + CXIdxEntity_CXXConstructor = 22, + CXIdxEntity_CXXDestructor = 23, + CXIdxEntity_CXXConversionFunction = 24, + CXIdxEntity_CXXTypeAlias = 25, + CXIdxEntity_CXXInterface = 26 + } + + // Enum @ Index.h:6274:9 (clang-c/Index.h) + public enum CXIdxEntityLanguage : int + { + CXIdxEntityLang_None = 0, + CXIdxEntityLang_C = 1, + CXIdxEntityLang_ObjC = 2, + CXIdxEntityLang_CXX = 3, + CXIdxEntityLang_Swift = 4 + } + + // Enum @ Index.h:6292:9 (clang-c/Index.h) + public enum CXIdxEntityCXXTemplateKind : int + { + CXIdxEntity_NonTemplate = 0, + CXIdxEntity_Template = 1, + CXIdxEntity_TemplatePartialSpecialization = 2, + CXIdxEntity_TemplateSpecialization = 3 + } + + // Enum @ Index.h:6299:9 (clang-c/Index.h) + public enum CXIdxAttrKind : int + { + CXIdxAttr_Unexposed = 0, + CXIdxAttr_IBAction = 1, + CXIdxAttr_IBOutlet = 2, + CXIdxAttr_IBOutletCollection = 3 + } + + // Enum @ Index.h:6334:9 (clang-c/Index.h) + public enum CXIdxDeclInfoFlags : int + { + CXIdxDeclFlag_Skipped = 1 + } + + // Enum @ Index.h:6362:9 (clang-c/Index.h) + public enum CXIdxObjCContainerKind : int + { + CXIdxObjCContainer_ForwardRef = 0, + CXIdxObjCContainer_Interface = 1, + CXIdxObjCContainer_Implementation = 2 + } + + // Enum @ Index.h:6422:9 (clang-c/Index.h) + public enum CXIdxEntityRefKind : int + { + CXIdxEntityRef_Direct = 1, + CXIdxEntityRef_Implicit = 2 + } + + // Enum @ Index.h:6440:9 (clang-c/Index.h) + public enum CXSymbolRole : int + { + CXSymbolRole_None = 0, + CXSymbolRole_Declaration = 1, + CXSymbolRole_Definition = 2, + CXSymbolRole_Reference = 4, + CXSymbolRole_Read = 8, + CXSymbolRole_Write = 16, + CXSymbolRole_Call = 32, + CXSymbolRole_Dynamic = 64, + CXSymbolRole_AddressOf = 128, + CXSymbolRole_Implicit = 256 + } + + // Enum @ Index.h:6612:9 (clang-c/Index.h) + public enum CXIndexOptFlags : int + { + CXIndexOpt_None = 0, + CXIndexOpt_SuppressRedundantRefs = 1, + CXIndexOpt_IndexFunctionLocalSymbols = 2, + CXIndexOpt_IndexImplicitTemplateInstantiations = 4, + CXIndexOpt_SuppressWarnings = 8, + CXIndexOpt_SkipParsedBodiesInSession = 16 + } // MacroDefinition @ Index.h:35:9 (clang-c/Index.h) public const int CINDEX_VERSION_MAJOR = 0; diff --git a/src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj b/src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj new file mode 100644 index 00000000..f84b4e18 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj @@ -0,0 +1,53 @@ + + + + + net6.0 + enable + false + C2CS.IntegrationTests + $(NoWarn);CA1707;CA2000;SA1300;IDE1006;CA1034;CA1051;CA1062;CA1724 + + + + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + %(RecursiveDir) + Always + false + + + + diff --git a/src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings b/src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj.DotSettings similarity index 75% rename from src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings rename to src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj.DotSettings index d3e2c780..a4699b72 100644 --- a/src/cs/production/C2CS.Feature.BuildLibraryC/C2CS.Feature.BuildLibraryC.csproj.DotSettings +++ b/src/cs/tests/C2CS.Tests.Integration/C2CS.Tests.Integration.csproj.DotSettings @@ -1,2 +1,2 @@  - False \ No newline at end of file + True \ No newline at end of file diff --git a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepMetaData.cs b/src/cs/tests/C2CS.Tests.Integration/IntegrationTest.cs similarity index 58% rename from src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepMetaData.cs rename to src/cs/tests/C2CS.Tests.Integration/IntegrationTest.cs index 2232d24d..b8aa3e98 100644 --- a/src/cs/production/C2CS.Common/Foundation/UseCases/UseCaseStepMetaData.cs +++ b/src/cs/tests/C2CS.Tests.Integration/IntegrationTest.cs @@ -1,9 +1,11 @@ // Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. // Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. -namespace C2CS; +using System; -public class UseCaseStepMetaData +namespace C2CS.IntegrationTests; + +public abstract class IntegrationTest { - public string Name { get; init; } = string.Empty; + protected static IServiceProvider Services => TestHost.Services; } diff --git a/src/cs/tests/C2CS.Tests.Integration/Startup.cs b/src/cs/tests/C2CS.Tests.Integration/Startup.cs new file mode 100644 index 00000000..ad264398 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/Startup.cs @@ -0,0 +1,14 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using Microsoft.Extensions.DependencyInjection; + +namespace C2CS.IntegrationTests; + +public static class Startup +{ + public static void ConfigureServices(IServiceCollection services) + { + my_c_library.Startup.ConfigureServices(services); + } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/TestHost.cs b/src/cs/tests/C2CS.Tests.Integration/TestHost.cs new file mode 100644 index 00000000..36cba9a6 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/TestHost.cs @@ -0,0 +1,15 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using Microsoft.Extensions.Hosting; + +namespace C2CS.IntegrationTests; + +public static class TestHost +{ + public static IServiceProvider Services => Host.Services; + + private static readonly IHost Host = new HostBuilder() + .BuildHostCommon().ConfigureServices(Startup.ConfigureServices).Build(); +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/CMakeLists.txt b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/CMakeLists.txt new file mode 100644 index 00000000..33cb9997 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/CMakeLists.txt @@ -0,0 +1,21 @@ +# Minimum required version of CMake to be installed in order to build this project. +cmake_minimum_required(VERSION 3.16) + +# The project name followed by the language. +project(my_c_library C) + +# The C standard whose features are requested to build this target. Supported values are "90" for 1990, "99" for 1990 and "11" for 2011. +# If you don't care use the latest, 11. +set(CMAKE_C_STANDARD 11) + +# Change the output directories for artifacts. +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) + +# Add directories so header files can be found +include_directories(my_c_library PRIVATE include) + +# 1. Specify that the project is a dynamic link library (SHARED). +# 2. Specify what source code files are to be included. +add_library(my_c_library SHARED src/my_c_library.c include/my_c_library.h) \ No newline at end of file diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/c2cs_helper.h b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/c2cs_helper.h new file mode 100644 index 00000000..16081307 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/c2cs_helper.h @@ -0,0 +1,79 @@ +#if defined(_WIN32) && defined(__GNUC__) + #if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-pc-windows-gnu" + #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "i686-pc-windows-gnu" + #elif defined(__aarch64__) || defined(_M_ARM64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-pc-windows-gnu" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for Windows." + #endif +#elif defined(_WIN32) + #if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-pc-windows-msvc" + #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "i686-pc-windows-msvc" + #elif defined(__aarch64__) || defined(_M_ARM64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-pc-windows-msvc" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for Windows." + #endif +#elif defined(__linux__) + #if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-unknown-linux-gnu" + #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "i686-unknown-linux-gnu" + #elif defined(__aarch64__) || defined(_M_ARM64) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-unknown-linux-gnu" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for Linux." + #endif +#elif defined(__APPLE__) && defined(__MACH__) + #if __has_include("TargetConditionals.h") + #include + #if TARGET_OS_MAC + #if TARGET_CPU_X86 + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "i686-apple-darwin" + #elif TARGET_CPU_X86_64 + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-apple-darwin" + #elif TARGET_CPU_ARM64 + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-apple-darwin" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for macOS." + #endif + #elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE + #if TARGET_CPU_X86_64 + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-apple-ios" + #elif TARGET_CPU_ARM64 + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-apple-ios" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for iOS." + #endif + #else + #error "Failed to determine runtime platform name: Unknown operating system." + #endif + #else + #if defined(__i386__) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "i686-apple-darwin" + #elif defined(__x86_64__) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "x86_64-apple-darwin" + #elif defined(__arm64__) + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME "aarch64-apple-darwin" + #else + #error "Failed to determine runtime platform name: Unknown computer architecture for macOS." + #endif + #endif +#else + #define C2CS_RUNTIME_TARGET_PLATFORM_NAME NULL +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) + #define C2CS_API_DECL __declspec(dllexport) +#else + #define C2CS_API_DECL extern +#endif + +C2CS_API_DECL const char* c2cs_get_runtime_platform_name() +{ + return (C2CS_RUNTIME_TARGET_PLATFORM_NAME == NULL) ? "" : C2CS_RUNTIME_TARGET_PLATFORM_NAME; +} \ No newline at end of file diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/my_c_library.h b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/my_c_library.h new file mode 100644 index 00000000..b96d5db5 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/include/my_c_library.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "c2cs_helper.h" + +typedef enum enum_force_uint32 { + ENUM_FORCE_UINT32_DAY_UNKNOWN, + ENUM_FORCE_UINT32_DAY_MONDAY, + ENUM_FORCE_UINT32_DAY_TUESDAY, + ENUM_FORCE_UINT32_DAY_WEDNESDAY, + ENUM_FORCE_UINT32_DAY_THURSDAY, + ENUM_FORCE_UINT32_DAY_FRIDAY, + _ENUM_FORCE_UINT32 = 0x7FFFFFFF +} enum_force_uint32; + +C2CS_API_DECL void function_void_void(void); +C2CS_API_DECL void function_void_string(const char* s); +C2CS_API_DECL void function_void_uint16_int32_uint64(uint16_t a, int32_t b, uint64_t c); +C2CS_API_DECL void function_void_uint16ptr_int32ptr_uint64ptr(const uint16_t* a, const int32_t* b, const uint64_t* c); +C2CS_API_DECL void function_void_enum(const enum_force_uint32 e); diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/src/my_c_library.c b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/src/my_c_library.c new file mode 100644 index 00000000..01461014 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/c/src/my_c_library.c @@ -0,0 +1,45 @@ +#include "my_c_library.h" + +#include +#include + +void function_void_void(void) +{ + printf("function_void_void\n"); +} + +void function_void_string(const char* s) +{ + printf("function_void_string: %s\n", s); +} + +void function_void_uint16_int32_uint64(uint16_t a, int32_t b, uint64_t c) +{ + uint64_t sum = a + b + c; + printf("function_void_uint16_int32_uint64: %lu\n", sum); +} + +void function_void_uint16ptr_int32ptr_uint64ptr(const uint16_t* a, const int32_t* b, const uint64_t* c) +{ + uint64_t sum = *a + *b + *c; + printf("function_void_uint16ptr_int32ptr_uint64ptr: %lu\n", sum); +} + +void function_void_enum(const enum_force_uint32 e) +{ + printf("function_void_enum: "); + + switch (e) + { + case ENUM_FORCE_UINT32_DAY_UNKNOWN: + printf("UNKNOWN"); + break; + case ENUM_FORCE_UINT32_DAY_MONDAY: + printf("MONDAY"); + break; + default: + printf("???"); + } + + printf("\n"); +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_linux.json b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_linux.json new file mode 100644 index 00000000..67f8f160 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_linux.json @@ -0,0 +1,14 @@ +{ + "directory": "my_c_library/c/ast", + "ast": { + "input_file": "my_c_library/c/include/my_c_library.h", + "platforms": { + "aarch64-unknown-linux-gnu": {}, + "x86_64-unknown-linux-gnu": {} + } + }, + "cs": { + "output_file": "my_c_library/c/my_c_library.cs", + "namespace": "bottlenoselabs" + } +} \ No newline at end of file diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_macos.json b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_macos.json new file mode 100644 index 00000000..9db8ed8a --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_macos.json @@ -0,0 +1,14 @@ +{ + "directory": "my_c_library/c/ast", + "ast": { + "input_file": "my_c_library/c/include/my_c_library.h", + "platforms": { + "aarch64-apple-darwin": {}, + "x86_64-apple-darwin": {} + } + }, + "cs": { + "output_file": "my_c_library/c/my_c_library.cs", + "namespace": "bottlenoselabs" + } +} \ No newline at end of file diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_windows.json b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_windows.json new file mode 100644 index 00000000..5b041e6f --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/config_windows.json @@ -0,0 +1,14 @@ +{ + "directory": "my_c_library/c/ast", + "ast": { + "input_file": "my_c_library/c/include/my_c_library.h", + "platforms": { + "aarch64-pc-windows-msvc": {}, + "x86_64-pc-windows-msvc": {} + } + }, + "cs": { + "output_file": "my_c_library/c/my_c_library.cs", + "namespace": "bottlenoselabs" + } +} \ No newline at end of file diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BindgenCSharpTests.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BindgenCSharpTests.cs new file mode 100644 index 00000000..bdb38aeb --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BindgenCSharpTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.IntegrationTests.my_c_library.Fixtures; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace C2CS.IntegrationTests.my_c_library; + +[Trait("Integration", "my_c_library")] +public class BindgenCSharpTests : IntegrationTest +{ + private readonly BindgenCSharpFixture _fixture; + + public BindgenCSharpTests() + { + _fixture = Services.GetService()!; + } + + [Fact] + public void function_void_void() + { + Assert.True(_fixture.FunctionsByName.TryGetValue(nameof(function_void_void), out var value)); + + Assert.True(value!.ReturnType.ToString() == "void"); + Assert.True(value.ParameterList.Parameters.Count == 0); + } + + [Fact] + public void function_void_string() + { + Assert.True(_fixture.FunctionsByName.TryGetValue(nameof(function_void_string), out var value)); + + Assert.True(value!.ReturnType.ToString() == "void"); + + Assert.True(value.ParameterList.Parameters.Count == 1); + + var parameter = value.ParameterList.Parameters[0]; + Assert.True(parameter.Type!.ToString() == "CString"); + } + + [Fact] + public void function_void_uint16_int32_uint64() + { + Assert.True(_fixture.FunctionsByName.TryGetValue(nameof(function_void_uint16_int32_uint64), out var value)); + + Assert.True(value!.ReturnType.ToString() == "void"); + + Assert.True(value.ParameterList.Parameters.Count == 3); + + var firstParameter = value.ParameterList.Parameters[0]; + Assert.True(firstParameter.Type!.ToString() == "ushort"); + + var secondParameter = value.ParameterList.Parameters[1]; + Assert.True(secondParameter.Type!.ToString() == "int"); + + var thirdParameter = value.ParameterList.Parameters[2]; + Assert.True(thirdParameter.Type!.ToString() == "ulong"); + } + + [Fact] + public void function_void_uint16ptr_int32ptr_uint64ptr() + { + Assert.True(_fixture.FunctionsByName.TryGetValue(nameof(function_void_uint16ptr_int32ptr_uint64ptr), out var value)); + + Assert.True(value!.ReturnType.ToString() == "void"); + + Assert.True(value.ParameterList.Parameters.Count == 3); + + var firstParameter = value.ParameterList.Parameters[0]; + Assert.True(firstParameter.Type!.ToString() == "ushort*"); + + var secondParameter = value.ParameterList.Parameters[1]; + Assert.True(secondParameter.Type!.ToString() == "int*"); + + var thirdParameter = value.ParameterList.Parameters[2]; + Assert.True(thirdParameter.Type!.ToString() == "ulong*"); + } + + [Fact] + public void function_void_enum() + { + Assert.True(_fixture.FunctionsByName.TryGetValue(nameof(function_void_enum), out var value)); + + Assert.True(value!.ReturnType.ToString() == "void"); + + Assert.True(value.ParameterList.Parameters.Count == 1); + + var firstParameter = value.ParameterList.Parameters[0]; + Assert.True(firstParameter.Type!.ToString() == "enum_force_uint32"); + } + + [Fact] + public void enum_force_uint32() + { + Assert.True(_fixture.EnumsByName.TryGetValue(nameof(enum_force_uint32), out var value)); + Assert.True(value!.BaseList!.Types[0].Type.ToString() == "int"); + Assert.True(value.Members.Count > 0); + } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BuildLibraryCTests.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BuildLibraryCTests.cs new file mode 100644 index 00000000..db62e93f --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/BuildLibraryCTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using Xunit; + +namespace C2CS.IntegrationTests.my_c_library; + +[Trait("Integration", "my_c_library")] +public class BuildLibraryCTests +{ + // [Fact] + // public void Build() + // { + // var buildProject = new BuildProjectCMake(); + // + // var request = new Input(buildProject); + // var useCase = new UseCase(); + // var response = useCase.Execute(request); + // + // Assert.True(response.Status == UseCaseOutputStatus.Success); + // Assert.True(response.Diagnostics.Length == 0); + // Assert.True(!response.BuildTargetResults.IsDefaultOrEmpty); + // } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/ExtractAbstractSyntaxTreeCIntegrationTests.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/ExtractAbstractSyntaxTreeCIntegrationTests.cs new file mode 100644 index 00000000..bf7676fb --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/ExtractAbstractSyntaxTreeCIntegrationTests.cs @@ -0,0 +1,137 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.Feature.ReadCodeC.Data; +using C2CS.IntegrationTests.my_c_library.Fixtures; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace C2CS.IntegrationTests.my_c_library; + +[Trait("Integration", "my_c_library")] +public class ExtractAbstractSyntaxTreeCIntegrationTests : IntegrationTest +{ + private readonly ExtractAbstractSyntaxTreeCFixture _fixture; + + public ExtractAbstractSyntaxTreeCIntegrationTests() + { + _fixture = Services.GetService()!; + _fixture.AssertPlatform(); + } + + [Fact] + public void function_void_void() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.FunctionsByName.TryGetValue(nameof(function_void_void), out var value)); + + Assert.True(value!.CallingConvention == CFunctionCallingConvention.Cdecl); + Assert.True(value.ReturnType == "void"); + Assert.True(value.Parameters.IsDefaultOrEmpty); + } + } + + [Fact] + public void function_void_string() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.FunctionsByName.TryGetValue(nameof(function_void_string), out var value)); + + Assert.True(value!.CallingConvention == CFunctionCallingConvention.Cdecl); + Assert.True(value.ReturnType == "void"); + + Assert.True(!value.Parameters.IsDefaultOrEmpty); + Assert.True(value.Parameters.Length == 1); + + var parameter = value.Parameters[0]; + Assert.True(parameter.Type == "char*"); + } + } + + [Fact] + public void function_void_uint16_int32_uint64() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.FunctionsByName.TryGetValue(nameof(function_void_uint16_int32_uint64), out var value)); + + Assert.True(value!.CallingConvention == CFunctionCallingConvention.Cdecl); + Assert.True(value.ReturnType == "void"); + + Assert.True(!value.Parameters.IsDefaultOrEmpty); + Assert.True(value.Parameters.Length == 3); + + var firstParameter = value.Parameters[0]; + Assert.True(firstParameter.Type == "uint16_t"); + + var secondParameter = value.Parameters[1]; + Assert.True(secondParameter.Type == "int32_t"); + + var thirdParameter = value.Parameters[2]; + Assert.True(thirdParameter.Type == "uint64_t"); + } + } + + [Fact] + public void function_void_uint16ptr_int32ptr_uint64ptr() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.FunctionsByName.TryGetValue( + nameof(function_void_uint16ptr_int32ptr_uint64ptr), + out var value)); + + Assert.True(value!.CallingConvention == CFunctionCallingConvention.Cdecl); + Assert.True(value.ReturnType == "void"); + + Assert.True(!value.Parameters.IsDefaultOrEmpty); + Assert.True(value.Parameters.Length == 3); + + var firstParameter = value.Parameters[0]; + Assert.True(firstParameter.Type == "uint16_t*"); + + var secondParameter = value.Parameters[1]; + Assert.True(secondParameter.Type == "int32_t*"); + + var thirdParameter = value.Parameters[2]; + Assert.True(thirdParameter.Type == "uint64_t*"); + } + } + + [Fact] + public void function_void_enum() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.FunctionsByName.TryGetValue(nameof(function_void_enum), out var value)); + + Assert.True(value!.CallingConvention == CFunctionCallingConvention.Cdecl); + Assert.True(value.ReturnType == "void"); + + Assert.True(!value.Parameters.IsDefaultOrEmpty); + Assert.True(value.Parameters.Length == 1); + + var firstParameter = value.Parameters[0]; + Assert.True(firstParameter.Type == "enum_force_uint32"); + } + } + + [Fact] + public void enum_force_uint32() + { + Assert.True(_fixture.AbstractSyntaxTrees.Length > 0); + foreach (var ast in _fixture.AbstractSyntaxTrees) + { + Assert.True(ast.EnumsByName.TryGetValue(nameof(enum_force_uint32), out var value)); + Assert.True(value!.IntegerType == "unsigned int" || value.IntegerType == "int"); + Assert.True(!value.Values.IsDefaultOrEmpty); + } + } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/BindgenCSharpFixture.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/BindgenCSharpFixture.cs new file mode 100644 index 00000000..acdfabfd --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/BindgenCSharpFixture.cs @@ -0,0 +1,80 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Collections.Immutable; +using System.IO.Abstractions; +using C2CS.Data.Serialization; +using C2CS.Feature.WriteCodeCSharp; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; + +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local + +namespace C2CS.IntegrationTests.my_c_library.Fixtures; + +public sealed class BindgenCSharpFixture +{ + public readonly ImmutableDictionary FunctionsByName; + public readonly ImmutableDictionary EnumsByName; + + public BindgenCSharpFixture( + WriteCodeCSharpUseCase useCase, + IFileSystem fileSystem, + ConfigurationJsonSerializer configurationJsonSerializer, + ExtractAbstractSyntaxTreeCFixture ast) + { + Assert.True(!ast.AbstractSyntaxTrees.IsDefaultOrEmpty); + + var configurationFilePath = Native.OperatingSystem switch + { + NativeOperatingSystem.Windows => "my_c_library/config_windows.json", + NativeOperatingSystem.Linux => "my_c_library/config_linux.json", + NativeOperatingSystem.macOS => "my_c_library/config_macos.json", + _ => throw new InvalidOperationException() + }; + var configuration = configurationJsonSerializer.Read(configurationFilePath); + var configurationWriteCSharp = configuration.WriteCSharp; + Assert.True(configurationWriteCSharp != null); + + var output = useCase.Execute(configurationWriteCSharp!); + Assert.True(output != null); + var input = output!.Input; + + Assert.True(output.IsSuccessful); + Assert.True(output.Diagnostics.Length == 0); + + var code = fileSystem.File.ReadAllText(input.OutputFilePath); + var compilationUnitSyntax = CSharpSyntaxTree.ParseText(code).GetCompilationUnitRoot(); + + Assert.True(compilationUnitSyntax.Members.Count == 1); + var @namespace = compilationUnitSyntax.Members[0] as NamespaceDeclarationSyntax; + Assert.True(@namespace != null); + Assert.True(@namespace!.Name.ToString() == input.NamespaceName); + + Assert.True(@namespace.Members.Count == 1); + var @class = @namespace.Members[0] as ClassDeclarationSyntax; + Assert.True(@class != null); + Assert.True(@class!.Identifier.ToString() == input.ClassName); + + var methodsByNameBuilder = ImmutableDictionary.CreateBuilder(); + var enumsByNameBuilder = ImmutableDictionary.CreateBuilder(); + + foreach (var member in @class.Members) + { + switch (member) + { + case MethodDeclarationSyntax method: + methodsByNameBuilder.Add(method.Identifier.Text, method); + break; + case EnumDeclarationSyntax @enum: + enumsByNameBuilder.Add(@enum.Identifier.Text, @enum); + break; + } + } + + FunctionsByName = methodsByNameBuilder.ToImmutable(); + EnumsByName = enumsByNameBuilder.ToImmutable(); + } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/ExtractAbstractSyntaxTreeCFixture.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/ExtractAbstractSyntaxTreeCFixture.cs new file mode 100644 index 00000000..128edd07 --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Fixtures/ExtractAbstractSyntaxTreeCFixture.cs @@ -0,0 +1,90 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using System; +using System.Collections.Immutable; +using C2CS.Data.Serialization; +using C2CS.Feature.ReadCodeC; +using C2CS.Feature.ReadCodeC.Data; +using C2CS.Feature.ReadCodeC.Data.Serialization; +using C2CS.Feature.ReadCodeC.Domain; +using C2CS.Serialization; +using Xunit; + +namespace C2CS.IntegrationTests.my_c_library.Fixtures; + +public sealed class ExtractAbstractSyntaxTreeCFixture +{ + public readonly ImmutableArray AbstractSyntaxTrees; + + public sealed class AbstractSyntaxTreeFixtureData + { + public ImmutableDictionary FunctionsByName { get; init; } = ImmutableDictionary.Empty; + + public ImmutableDictionary EnumsByName { get; init; } = ImmutableDictionary.Empty; + } + + public ExtractAbstractSyntaxTreeCFixture( + ReadCodeCUseCase useCase, + CJsonSerializer cJsonSerializer, + ConfigurationJsonSerializer configurationJsonSerializer) + { + var configurationFilePath = Native.OperatingSystem switch + { + NativeOperatingSystem.Windows => "my_c_library/config_windows.json", + NativeOperatingSystem.Linux => "my_c_library/config_linux.json", + NativeOperatingSystem.macOS => "my_c_library/config_macos.json", + _ => throw new InvalidOperationException() + }; + var configuration = configurationJsonSerializer.Read(configurationFilePath); + var configurationReadC = configuration.ReadC; + var output = useCase.Execute(configurationReadC!); + AbstractSyntaxTrees = ParseAbstractSyntaxTrees(output, cJsonSerializer); + } + + private ImmutableArray ParseAbstractSyntaxTrees( + ReadCodeCOutput output, CJsonSerializer cJsonSerializer) + { + if (!output.IsSuccessful || output.Diagnostics.Length != 0) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + + foreach (var options in output.AbstractSyntaxTreesOptions) + { + if (string.IsNullOrEmpty(options.OutputFilePath)) + { + continue; + } + + var ast = cJsonSerializer.Read(options.OutputFilePath); + var functionsByName = ast.Functions.ToImmutableDictionary(x => x.Name, x => x); + var enumsByName = ast.Enums.ToImmutableDictionary(x => x.Name, x => x); + + var data = new AbstractSyntaxTreeFixtureData + { + FunctionsByName = functionsByName, + EnumsByName = enumsByName + }; + + builder.Add(data); + } + + return builder.ToImmutable(); + } + + public void AssertPlatform() + { + Assert.True(AbstractSyntaxTrees.Length > 0); + + foreach (var abstractSyntaxTree in AbstractSyntaxTrees) + { + Assert.True(abstractSyntaxTree.FunctionsByName.TryGetValue("c2cs_get_runtime_platform_name", out var function)); + Assert.Equal(CFunctionCallingConvention.Cdecl, function!.CallingConvention); + Assert.Equal("char*", function.ReturnType); + Assert.True(function.Parameters.IsDefaultOrEmpty); + } + } +} diff --git a/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Startup.cs b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Startup.cs new file mode 100644 index 00000000..37f0a69d --- /dev/null +++ b/src/cs/tests/C2CS.Tests.Integration/my_c_library/cs/Startup.cs @@ -0,0 +1,16 @@ +// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved. +// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information. + +using C2CS.IntegrationTests.my_c_library.Fixtures; +using Microsoft.Extensions.DependencyInjection; + +namespace C2CS.IntegrationTests.my_c_library; + +public static class Startup +{ + public static void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/src/cs/tests/Directory.Build.props b/src/cs/tests/Directory.Build.props new file mode 100644 index 00000000..777ef218 --- /dev/null +++ b/src/cs/tests/Directory.Build.props @@ -0,0 +1,18 @@ + + + + + + + net5.0 + true + latest + + + + + true + true + + + \ No newline at end of file diff --git a/src/cs/tests/Directory.Build.targets b/src/cs/tests/Directory.Build.targets new file mode 100644 index 00000000..72a6208f --- /dev/null +++ b/src/cs/tests/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file