diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-11-18 15:58:12 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-18 15:58:12 -0500 |
| commit | 1d5f815b3964edee8a2d701e1a6cc078c89d677f (patch) | |
| tree | aa5b4b1473344e635d7ce1d2159fc57eeb40b841 | |
| parent | b482844b689eb109ee1d70c527e098400ac6d409 (diff) | |
RTTI/JSON (#2021)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Use 'Process' to communicate with an command line tool.
* Remove slang-win-stream
* Tidy up windows ProcessUtil.
* First version of BufferedReadStream.
* Windows working IPC for steams.
* Test proxy count option.
* Split Process/ProcessUtil. Process is platform dependant. ProcessUtil are functions that are platform independent.
* First implementation of Unix Process interface.
* Unix process compiles on cygwin.
* Fix typo in unix process.
* Separate unix pipe stream error of invalid access, from pipe availability.
* Fix in standard line extraction.
* Make fd non blocking.
* Fix issues with Windows Process streams.
* Added UnixPipe.
* Some fixes around UnixPipeStream.
* Make a unix stream closed explicit.
* Hack to debug linux process/stream.
* Revert to old linux pipe handling.
* Pass executable path for unit tests.
Split out CommandLine into own source.
* Small improvements in process/command line.
* Check process behavior with crash.
* Make stderr and stdout unbuffered for crash testing.
* Only turn disable buffering in crash test.
* Disable crash test on CI.
* Fix crash on clang/linux.
* Enable crash test.
Remove _appendBuffer as can use StreamUtil functionality.
* Added inital processing for http headers.
* Small improvements to HttpHeader.
* First pass HTTPPacketConnection working on windows.
* Enable other Process communication tests.
* Update comments.
* WIP JSON RPC.
* Add terminate to Process.
Made JSONRPC a Util.
* Small tidy up around HTTPPacketConnection.
* Improve process termination options.
* WIP for test-server.
* Add diagnostics error handling to test-server.
* Improved JSON support.
Parsing/creating JSON-RPC messages.
* WIP JSONRPC parsing.
* First pass RttiInfo support.
* WIP converting between JSON/native types.
* Project files.
* Split out RttiUtil.
Made RttiInfo constuction thread safe.
* WIP RTTI<->JSON.
* Add diagnostics to JSON<->native conversions.
* Make RttiInfo for structs globals. Avoids problem around derived types (like pointers), being able to cause an abort.
* Add pointer support to RTTI.
Fixed some compilation issues on linux.
* Add fixed array support.
* Added Rtti unit test.
* Add rtti unit test.
* Split out quoted/unquoted key handling.
Fix bugs in JSON value/container.
Added JSON native test.
* Make default array allocator use malloc/free.
Remove the new[] handler (doesn't work on visuals studio).
* Fix for linux warning.
* Remove some test code.
* Fix issues on x86 win.
* Fix warning on aarch64.
32 files changed, 3169 insertions, 27 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj index 46603abc8..5e70d59f7 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj @@ -286,6 +286,7 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-diagnostic-defs.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-diagnostics.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-lexer.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-native.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h" />
@@ -314,6 +315,7 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-include-system.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-diagnostics.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-lexer.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-native.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp" />
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters index 91ee4c5d9..c392612f6 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters @@ -45,6 +45,9 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-lexer.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-native.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -125,6 +128,9 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-lexer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-native.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj index 2a23f0116..817dca55d 100644 --- a/build/visual-studio/core/core.vcxproj +++ b/build/visual-studio/core/core.vcxproj @@ -311,6 +311,8 @@ <ClInclude Include="..\..\..\source\core\slang-render-api-util.h" /> <ClInclude Include="..\..\..\source\core\slang-riff-file-system.h" /> <ClInclude Include="..\..\..\source\core\slang-riff.h" /> + <ClInclude Include="..\..\..\source\core\slang-rtti-info.h" /> + <ClInclude Include="..\..\..\source\core\slang-rtti-util.h" /> <ClInclude Include="..\..\..\source\core\slang-secure-crt.h" /> <ClInclude Include="..\..\..\source\core\slang-semantic-version.h" /> <ClInclude Include="..\..\..\source\core\slang-shared-library.h" /> @@ -356,6 +358,8 @@ <ClCompile Include="..\..\..\source\core\slang-render-api-util.cpp" /> <ClCompile Include="..\..\..\source\core\slang-riff-file-system.cpp" /> <ClCompile Include="..\..\..\source\core\slang-riff.cpp" /> + <ClCompile Include="..\..\..\source\core\slang-rtti-info.cpp" /> + <ClCompile Include="..\..\..\source\core\slang-rtti-util.cpp" /> <ClCompile Include="..\..\..\source\core\slang-semantic-version.cpp" /> <ClCompile Include="..\..\..\source\core\slang-shared-library.cpp" /> <ClCompile Include="..\..\..\source\core\slang-signal.cpp" /> diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters index e526a558b..51b0313f6 100644 --- a/build/visual-studio/core/core.vcxproj.filters +++ b/build/visual-studio/core/core.vcxproj.filters @@ -120,6 +120,12 @@ <ClInclude Include="..\..\..\source\core\slang-riff.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\core\slang-rtti-info.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\source\core\slang-rtti-util.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\source\core\slang-secure-crt.h"> <Filter>Header Files</Filter> </ClInclude> @@ -251,6 +257,12 @@ <ClCompile Include="..\..\..\source\core\slang-riff.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\core\slang-rtti-info.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\source\core\slang-rtti-util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\..\..\source\core\slang-semantic-version.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj index c8eadc484..4869c220f 100644 --- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj +++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj @@ -277,11 +277,13 @@ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-compression.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-find-type-by-name.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-free-list.cpp" />
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-json-native.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-json.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-memory-arena.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-path.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-process.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-riff.cpp" />
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-rtti.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-short-list.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-string.cpp" />
<ClCompile Include="..\..\..\tools\unit-test\slang-unit-test.cpp" />
diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters index 2578a8736..9db9bee4c 100644 --- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters +++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters @@ -32,6 +32,9 @@ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-free-list.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-json-native.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-json.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -47,6 +50,9 @@ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-riff.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-rtti.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-short-list.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/build/visual-studio/test-server/test-server.vcxproj b/build/visual-studio/test-server/test-server.vcxproj new file mode 100644 index 000000000..756d1954b --- /dev/null +++ b/build/visual-studio/test-server/test-server.vcxproj @@ -0,0 +1,286 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM"> + <Configuration>Debug</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug aarch64|Win32"> + <Configuration>Debug aarch64</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug aarch64|x64"> + <Configuration>Debug aarch64</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug aarch64|ARM"> + <Configuration>Debug aarch64</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM"> + <Configuration>Release</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release aarch64|Win32"> + <Configuration>Release aarch64</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release aarch64|x64"> + <Configuration>Release aarch64</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release aarch64|ARM"> + <Configuration>Release aarch64</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{23149706-C12F-4329-B6AA-8266407C32D3}</ProjectGuid> + <IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename> + <Keyword>Win32Proj</Keyword> + <RootNamespace>test-server</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug aarch64|ARM'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release aarch64|ARM'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + <PlatformToolset>v142</PlatformToolset> + <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug aarch64|ARM'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release aarch64|ARM'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>..\..\..\bin\windows-x86\debug\</OutDir> + <IntDir>..\..\..\intermediate\windows-x86\debug\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>..\..\..\bin\windows-x64\debug\</OutDir> + <IntDir>..\..\..\intermediate\windows-x64\debug\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug aarch64|ARM'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>..\..\..\bin\windows-aarch64\debug\</OutDir> + <IntDir>..\..\..\intermediate\windows-aarch64\debug\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>..\..\..\bin\windows-x86\release\</OutDir> + <IntDir>..\..\..\intermediate\windows-x86\release\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>..\..\..\bin\windows-x64\release\</OutDir> + <IntDir>..\..\..\intermediate\windows-x64\release\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release aarch64|ARM'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>..\..\..\bin\windows-aarch64\release\</OutDir> + <IntDir>..\..\..\intermediate\windows-aarch64\release\test-server\</IntDir> + <TargetName>test-server</TargetName> + <TargetExt>.exe</TargetExt> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <Optimization>Disabled</Optimization> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + <Optimization>Disabled</Optimization> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug aarch64|ARM'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <Optimization>Disabled</Optimization> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <Optimization>Full</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <MinimalRebuild>false</MinimalRebuild> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <Optimization>Full</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <MinimalRebuild>false</MinimalRebuild> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release aarch64|ARM'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <Optimization>Full</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <MinimalRebuild>false</MinimalRebuild> + <StringPooling>true</StringPooling> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="..\..\..\tools\test-server\test-server-diagnostic-defs.h" /> + <ClInclude Include="..\..\..\tools\test-server\test-server-diagnostics.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\tools\test-server\test-server-diagnostics.cpp" /> + <ClCompile Include="..\..\..\tools\test-server\test-server-main.cpp" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\compiler-core\compiler-core.vcxproj"> + <Project>{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}</Project> + </ProjectReference> + <ProjectReference Include="..\core\core.vcxproj"> + <Project>{F9BE7957-8399-899E-0C49-E714FDDD4B65}</Project> + </ProjectReference> + <ProjectReference Include="..\slang\slang.vcxproj"> + <Project>{DB00DA62-0533-4AFD-B59F-A67D5B3A0808}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/build/visual-studio/test-server/test-server.vcxproj.filters b/build/visual-studio/test-server/test-server.vcxproj.filters new file mode 100644 index 000000000..2a5d2b9d3 --- /dev/null +++ b/build/visual-studio/test-server/test-server.vcxproj.filters @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{21EB8090-0D4E-1035-B6D3-48EBA215DCB7}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{E9C7FDCE-D52A-8D73-7EB0-C5296AF258F6}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\tools\test-server\test-server-diagnostic-defs.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\tools\test-server\test-server-diagnostics.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\tools\test-server\test-server-diagnostics.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\tools\test-server\test-server-main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/premake5.lua b/premake5.lua index c8c02ff09..13fee9e22 100644 --- a/premake5.lua +++ b/premake5.lua @@ -763,6 +763,12 @@ end links { "compiler-core", "core", "slang" } + tool "test-server" + uuid "23149706-C12F-4329-B6AA-8266407C32D3" + includedirs { "." } + + links { "compiler-core", "core", "slang" } + -- -- `slang-generate` is a tool we use for source code generation on -- the compiler. It depends on the `core` library, so we need to @@ -19,6 +19,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-generate", "build\vis EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-proxy", "build\visual-studio\test-proxy\test-proxy.vcxproj", "{BE412850-4BB9-429A-877C-BFBC4B34186C}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test-server", "build\visual-studio\test-server\test-server.vcxproj", "{23149706-C12F-4329-B6AA-8266407C32D3}"
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "compiler-core", "build\visual-studio\compiler-core\compiler-core.vcxproj", "{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core", "build\visual-studio\core\core.vcxproj", "{F9BE7957-8399-899E-0C49-E714FDDD4B65}"
@@ -181,6 +183,18 @@ Global {BE412850-4BB9-429A-877C-BFBC4B34186C}.Release|Win32.Build.0 = Release|Win32
{BE412850-4BB9-429A-877C-BFBC4B34186C}.Release|x64.ActiveCfg = Release|x64
{BE412850-4BB9-429A-877C-BFBC4B34186C}.Release|x64.Build.0 = Release|x64
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|aarch64.ActiveCfg = Debug aarch64|ARM
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|aarch64.Build.0 = Debug aarch64|ARM
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|Win32.ActiveCfg = Debug|Win32
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|Win32.Build.0 = Debug|Win32
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|x64.ActiveCfg = Debug|x64
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Debug|x64.Build.0 = Debug|x64
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|aarch64.ActiveCfg = Release aarch64|ARM
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|aarch64.Build.0 = Release aarch64|ARM
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|Win32.ActiveCfg = Release|Win32
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|Win32.Build.0 = Release|Win32
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|x64.ActiveCfg = Release|x64
+ {23149706-C12F-4329-B6AA-8266407C32D3}.Release|x64.Build.0 = Release|x64
{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}.Debug|aarch64.ActiveCfg = Debug aarch64|ARM
{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}.Debug|aarch64.Build.0 = Debug aarch64|ARM
{12C1E89D-F5D0-41D3-8E8D-FB3F358F8126}.Debug|Win32.ActiveCfg = Debug|Win32
@@ -446,6 +460,7 @@ Global {7F773DD9-EB8F-2403-B43C-B49C2014B99C} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13}
{66174227-8541-41FC-A6DF-4764FC66F78E} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13}
{BE412850-4BB9-429A-877C-BFBC4B34186C} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13}
+ {23149706-C12F-4329-B6AA-8266407C32D3} = {FD47AE19-69FD-260F-F2F1-20E65EA61D13}
{4B47A364-37C4-96A7-6041-97BB4C1D333B} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231}
{37BED5B5-23FA-D81F-8C0C-F1167867813A} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231}
{57C81DD3-4304-213D-AC16-39349871C957} = {EB5FC2C6-D72D-B6CC-C0C1-26F3AC2E9231}
diff --git a/source/compiler-core/slang-json-diagnostic-defs.h b/source/compiler-core/slang-json-diagnostic-defs.h index da3b2a28c..23bea9b79 100644 --- a/source/compiler-core/slang-json-diagnostic-defs.h +++ b/source/compiler-core/slang-json-diagnostic-defs.h @@ -36,4 +36,9 @@ DIAGNOSTIC(20006, Error, expectingValueName, "expecting value name [null, true, DIAGNOSTIC(20007, Error, unexpectedTokenExpectedTokenType, "unexpected '$0', expected '$1'") DIAGNOSTIC(20008, Error, unexpectedToken, "unexpected '$0'") +DIAGNOSTIC(20009, Error, unableToConvertField, "unable to convert field '$0' in type '$1'") +DIAGNOSTIC(20010, Error, fieldNotFound, "field '$0' not found in type '$1'") +DIAGNOSTIC(20011, Error, fieldNotDefinedOnType, "field '$0' not defined on type '$1'") +DIAGNOSTIC(20011, Error, fieldRequiredOnType, "field '$0' required on '$1'") + #undef DIAGNOSTIC diff --git a/source/compiler-core/slang-json-native.cpp b/source/compiler-core/slang-json-native.cpp new file mode 100644 index 000000000..5b2fb5db4 --- /dev/null +++ b/source/compiler-core/slang-json-native.cpp @@ -0,0 +1,377 @@ +#include "slang-json-native.h" + +#include "../../slang-com-helper.h" + +#include "../core/slang-rtti-util.h" + +#include "slang-json-diagnostics.h" + +namespace Slang { + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! JSONToNativeConverter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +/* static */Index JSONToNativeConverter::_getFieldCount(const StructRttiInfo* structRttiInfo) +{ + if (structRttiInfo->m_super) + { + return _getFieldCount(structRttiInfo->m_super) + structRttiInfo->m_fieldCount; + } + else + { + return structRttiInfo->m_fieldCount; + } +} + +/* static */Index JSONToNativeConverter::_findFieldIndex(const StructRttiInfo* structRttiInfo, const UnownedStringSlice& fieldName) +{ + if (structRttiInfo->m_super) + { + const Index index = _findFieldIndex(structRttiInfo->m_super, fieldName); + if (index >= 0) + { + return index + _getFieldCount(structRttiInfo->m_super); + } + } + + ConstArrayView<StructRttiInfo::Field> fields(structRttiInfo->m_fields, structRttiInfo->m_fieldCount); + + Index index = fields.findFirstIndex([fieldName](const StructRttiInfo::Field& field) ->bool { return fieldName == field.m_name; }); + if (index >= 0 && structRttiInfo->m_super) + { + index += _getFieldCount(structRttiInfo->m_super); + } + + return index; +} + +SlangResult JSONToNativeConverter::_structToNative(const ConstArrayView<JSONKeyValue>& pairs, const StructRttiInfo* structRttiInfo, void* out, Index& outFieldCount) +{ + Index fieldCount = 0; + + if (structRttiInfo->m_super) + { + SLANG_RETURN_ON_FAIL(_structToNative(pairs, structRttiInfo->m_super, out, fieldCount)); + } + + Byte* dst = (Byte*)out; + + const Index count = structRttiInfo->m_fieldCount; + + for (Index i = 0; i < count; ++i) + { + const auto& field = structRttiInfo->m_fields[i]; + + auto key = m_container->findKey(UnownedStringSlice(field.m_name)); + + if (key == 0) + { + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + continue; + } + + m_sink->diagnose(SourceLoc(), JSONDiagnostics::fieldRequiredOnType, field.m_name, structRttiInfo->m_name); + + // Unable to find this key + return SLANG_FAIL; + } + + // If there are any of the pairs, that are not in the type.. it's an error + const Index index = pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); + if (index < 0) + { + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + continue; + } + + m_sink->diagnose(SourceLoc(), JSONDiagnostics::fieldRequiredOnType, field.m_name, structRttiInfo->m_name); + + // Unable to find this key + return SLANG_FAIL; + } + + auto& pair = pairs[index]; + + // Copy the field over + SLANG_RETURN_ON_FAIL(convert(pair.value, field.m_type, dst + field.m_offset)); + + // Field was handled + ++fieldCount; + } + + // Write off the amount of fields converted/handled. + outFieldCount = fieldCount; + return SLANG_OK; +} + +SlangResult JSONToNativeConverter::convert(const JSONValue& in, const RttiInfo* rttiInfo, void* out) +{ + if (rttiInfo->isIntegral()) + { + return RttiUtil::setInt(m_container->asInteger(in), rttiInfo, out); + } + else if (rttiInfo->isFloat()) + { + return RttiUtil::setFromDouble(m_container->asFloat(in), rttiInfo, out); + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::Bool: + { + *(bool*)out = m_container->asBool(in); + return SLANG_OK; + } + case RttiInfo::Kind::Struct: + { + if (in.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + auto pairs = m_container->getObject(in); + const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo); + + Index fieldCount = 0; + SLANG_RETURN_ON_FAIL(_structToNative(pairs, structRttiInfo, out, fieldCount)); + + if (fieldCount != pairs.getCount()) + { + // We want to find the fields not found in the type + + for (auto& pair : pairs) + { + UnownedStringSlice fieldName = m_container->getStringFromKey(pair.key); + const Index index = _findFieldIndex(structRttiInfo, UnownedStringSlice(fieldName)); + + if (index < 0) + { + m_sink->diagnose(pair.keyLoc, JSONDiagnostics::fieldNotDefinedOnType, fieldName, structRttiInfo->m_name); + } + } + + // If these are different then there are fields defined in the object that are *not* defined in class definition + return SLANG_FAIL; + } + + return SLANG_OK; + } + case RttiInfo::Kind::Enum: + { + return SLANG_E_NOT_IMPLEMENTED; + } + case RttiInfo::Kind::String: + { + *(String*)out = m_container->getTransientString(in); + return SLANG_OK; + } + case RttiInfo::Kind::UnownedStringSlice: + { + // Problem -> if the slice is a lexeme, then when we decode with getString, it will lose scope. + // So we do something a bit odd and place the decoding string + + *(UnownedStringSlice*)out = m_container->getString(in); + return SLANG_OK; + } + case RttiInfo::Kind::List: + { + if (in.getKind() != JSONValue::Kind::Array) + { + return SLANG_FAIL; + } + + typedef List<Byte> Type; + Type& list = *(Type*)out; + + auto arr = m_container->getArray(in); + + const Index count = arr.getCount(); + + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + auto elementType = listRttiInfo->m_elementType; + + SLANG_RETURN_ON_FAIL(RttiUtil::setListCount(elementType, out, arr.getCount())); + + // Okay, we need to copy over one by one + + Byte* dstEles = list.getBuffer(); + for (Index i = 0; i < count; ++i, dstEles += elementType->m_size) + { + SLANG_RETURN_ON_FAIL(convert(arr[i], elementType, dstEles)); + } + + return SLANG_OK; + } + case RttiInfo::Kind::Dictionary: + { + // We can *only* serialize this into a straight JSON object iff the key is a string-like type + // We could turn into (say) an array of keys and values + break; + } + case RttiInfo::Kind::Other: + { + if (rttiInfo == GetRttiInfo<JSONValue>::get()) + { + // Do we need to copy into the container? + // As it stands we have to assume src is stored in container. + *(JSONValue*)out = in; + return SLANG_OK; + } + return SLANG_FAIL; + } + default: break; + } + return SLANG_FAIL; +} + + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! NativeToJSONConverter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +SlangResult NativeToJSONConverter::_structToJSON(const StructRttiInfo* structRttiInfo, const void* src, List<JSONKeyValue>& outPairs) +{ + // Do the super class first + if (structRttiInfo->m_super) + { + SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, src, outPairs)); + } + + const Byte* base = (const Byte*)src; + const Index count = structRttiInfo->m_fieldCount; + + for (Index i = 0; i < count; ++i) + { + const auto& field = structRttiInfo->m_fields[i]; + + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + const RttiDefaultValue defaultValue = RttiDefaultValue(field.m_flags & uint8_t(RttiDefaultValue::Mask)); + if (RttiUtil::isDefault(defaultValue, field.m_type, base + field.m_offset)) + { + // If it's a default, we don't bother writing it + continue; + } + } + + JSONKeyValue pair; + pair.key = m_container->getKey(UnownedStringSlice(field.m_name)); + auto res = convert(field.m_type, base + field.m_offset, pair.value); + + if (SLANG_FAILED(res)) + { + m_sink->diagnose(SourceLoc(), JSONDiagnostics::unableToConvertField, field.m_name, structRttiInfo->m_name); + return res; + } + + outPairs.add(pair); + } + + return SLANG_OK; +} + + +SlangResult NativeToJSONConverter::convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out) +{ + if (rttiInfo->isIntegral()) + { + out = JSONValue::makeInt(RttiUtil::getInt64(rttiInfo, in)); + return SLANG_OK; + } + else if (rttiInfo->isFloat()) + { + out = JSONValue::makeFloat(RttiUtil::asDouble(rttiInfo, in)); + return SLANG_OK; + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::Invalid: return SLANG_FAIL; + case RttiInfo::Kind::Bool: + { + out = JSONValue::makeBool(RttiUtil::asBool(rttiInfo, in)); + return SLANG_OK; + } + case RttiInfo::Kind::String: + { + const String& str = *(const String*)in; + out = m_container->createString(str.getUnownedSlice()); + return SLANG_OK; + } + case RttiInfo::Kind::UnownedStringSlice: + { + const UnownedStringSlice& slice = *(const UnownedStringSlice*)in; + out = m_container->createString(slice); + return SLANG_OK; + } + case RttiInfo::Kind::Struct: + { + const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo); + + List<JSONKeyValue> pairs; + SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, in, pairs)); + out = m_container->createObject(pairs.getBuffer(), pairs.getCount()); + return SLANG_OK; + } + case RttiInfo::Kind::Enum: + { + return SLANG_E_NOT_IMPLEMENTED; + } + case RttiInfo::Kind::List: + { + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + const auto elementRttiInfo = listRttiInfo->m_elementType; + + // The src probably *doesn't* contain bytes, but can cast like this because + // we only need the count (which doesn't depend on <T>), and the backing buffer + const List<Byte>& srcValuesList = *(const List<Byte>*)in; + + const Index count = srcValuesList.getCount(); + const Byte* srcValues = srcValuesList.getBuffer(); + + List<JSONValue> dstValues; + dstValues.setCount(count); + + const size_t elementStride = elementRttiInfo->m_size; + + for (Index i = 0; i < count; ++i, srcValues += elementStride) + { + SLANG_RETURN_ON_FAIL(convert(elementRttiInfo, srcValues, dstValues[i])); + } + + out = m_container->createArray(dstValues.getBuffer(), count); + return SLANG_OK; + } + case RttiInfo::Kind::Dictionary: + { + const DictionaryRttiInfo* listRttiInfo = static_cast<const DictionaryRttiInfo*>(rttiInfo); + const auto keyRttiInfo = listRttiInfo->m_keyType; + const auto valueRttiInfo = listRttiInfo->m_valueType; + + SLANG_UNUSED(keyRttiInfo); + SLANG_UNUSED(valueRttiInfo); + + // We can *only* serialize this into a straight JSON object iff the key is a string-like type + // We could turn into (say) an array of keys and values + + break; + } + case RttiInfo::Kind::Other: + { + if (rttiInfo == GetRttiInfo<JSONValue>::get()) + { + // Do we need to copy into the container? + // As it stands we have to assume src is stored in container. + const JSONValue& src = *(const JSONValue*)in; + + out = src; + return SLANG_OK; + } + break; + } + default: break; + } + + return SLANG_E_NOT_IMPLEMENTED; +} + +} // namespace Slang diff --git a/source/compiler-core/slang-json-native.h b/source/compiler-core/slang-json-native.h new file mode 100644 index 000000000..66f20aafb --- /dev/null +++ b/source/compiler-core/slang-json-native.h @@ -0,0 +1,50 @@ +#ifndef SLANG_COMPILER_CORE_JSON_NATIVE_H +#define SLANG_COMPILER_CORE_JSON_NATIVE_H + +#include "../../slang.h" +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" + +#include "slang-json-value.h" + +namespace Slang { + +struct JSONToNativeConverter +{ + SlangResult convert(const JSONValue& value, const RttiInfo* rttiInfo, void* out); + + JSONToNativeConverter(JSONContainer* container, DiagnosticSink* sink): + m_container(container), + m_sink(sink) + {} + +protected: + static Index _getFieldCount(const StructRttiInfo* structRttiInfo); + static Index _findFieldIndex(const StructRttiInfo* structRttiInfo, const UnownedStringSlice& fieldName); + + SlangResult _structToNative(const ConstArrayView<JSONKeyValue>& pairs, const StructRttiInfo* structRttiInfo, void* out, Index& outFieldCount); + + DiagnosticSink* m_sink; + JSONContainer* m_container; +}; + +struct NativeToJSONConverter +{ + SlangResult convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out); + + NativeToJSONConverter(JSONContainer* container, DiagnosticSink* sink) : + m_container(container), + m_sink(sink) + {} + +protected: + SlangResult _structToJSON(const StructRttiInfo* structRttiInfo, const void* src, List<JSONKeyValue>& outPairs); + + DiagnosticSink* m_sink; + JSONContainer* m_container; +}; + + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_JSON_NATIVE_H diff --git a/source/compiler-core/slang-json-parser.cpp b/source/compiler-core/slang-json-parser.cpp index a38afc418..fe9a0f580 100644 --- a/source/compiler-core/slang-json-parser.cpp +++ b/source/compiler-core/slang-json-parser.cpp @@ -32,7 +32,7 @@ SlangResult JSONParser::_parseObject() { JSONToken keyToken; SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::StringLiteral, keyToken)); - m_listener->addKey(m_lexer->getLexeme(keyToken), keyToken.loc); + m_listener->addQuotedKey(m_lexer->getLexeme(keyToken), keyToken.loc); SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::Colon)); @@ -345,18 +345,39 @@ void JSONWriter::endArray(SourceLoc loc) m_stack.removeLast(); } -void JSONWriter::addKey(const UnownedStringSlice& key, SourceLoc loc) +void JSONWriter::addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) { SLANG_UNUSED(loc); SLANG_ASSERT(m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0); _maybeEmitFieldComma(); + _maybeEmitIndent(); + + // Output the key quoted + StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); + StringEscapeUtil::appendQuoted(handler, key, m_builder); + + m_builder << " : "; + + m_state.m_flags |= State::Flag::HasKey; + // We don't want it to emit a , after the : + m_state.m_flags &= ~State::Flag::HasPrevious; +} + +void JSONWriter::addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) +{ + SLANG_UNUSED(loc); + SLANG_ASSERT(m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0); // It should be quoted SLANG_ASSERT(key.getLength() >= 2 && key[0] == '"' && key[key.getLength() - 1] == '"'); + _maybeEmitFieldComma(); _maybeEmitIndent(); - m_builder << key << " : "; + + m_builder << key; + + m_builder << " : "; m_state.m_flags |= State::Flag::HasKey; // We don't want it to emit a , after the : diff --git a/source/compiler-core/slang-json-parser.h b/source/compiler-core/slang-json-parser.h index 96531aee9..2391ea0d2 100644 --- a/source/compiler-core/slang-json-parser.h +++ b/source/compiler-core/slang-json-parser.h @@ -21,7 +21,8 @@ public: /// Add the key. Must be followed by addXXXValue. - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) = 0; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) = 0; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) = 0; /// Can be performed in an array or after an addLexemeKey in an object virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) = 0; @@ -93,7 +94,8 @@ public: virtual void endObject(SourceLoc loc) SLANG_OVERRIDE; virtual void startArray(SourceLoc loc) SLANG_OVERRIDE; virtual void endArray(SourceLoc loc) SLANG_OVERRIDE; - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) SLANG_OVERRIDE; virtual void addIntegerValue(int64_t value, SourceLoc loc) SLANG_OVERRIDE; virtual void addFloatValue(double value, SourceLoc loc) SLANG_OVERRIDE; diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp index 15da92ff0..1ca55e9eb 100644 --- a/source/compiler-core/slang-json-rpc.cpp +++ b/source/compiler-core/slang-json-rpc.cpp @@ -1,5 +1,7 @@ #include "slang-json-rpc.h" +#include "../../slang-com-helper.h" + namespace Slang { // https://www.jsonrpc.org/specification @@ -7,6 +9,406 @@ namespace Slang { // m_sourceManager.initialize(nullptr, nullptr); // m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); +static const auto g_jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc"); +static const auto g_jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0"); +static const auto g_method = UnownedStringSlice::fromLiteral("method"); +static const auto g_id = UnownedStringSlice::fromLiteral("id"); +static const auto g_params = UnownedStringSlice::fromLiteral("params"); +static const auto g_code = UnownedStringSlice::fromLiteral("code"); +static const auto g_error = UnownedStringSlice::fromLiteral("error"); +static const auto g_message = UnownedStringSlice::fromLiteral("message"); +static const auto g_result = UnownedStringSlice::fromLiteral("result"); +static const auto g_data = UnownedStringSlice::fromLiteral("data"); + +// Add the fields. +// TODO(JS): This is a little verbose, and could be improved on with something like +// * Tool that automatically generated from C++ (say via the C++ extractor) +// * Macro magic to simplify the construction +static const StructRttiInfo _makeJSONRPCErrorResponse_ErrorRtti() +{ + JSONRPCErrorResponse::Error obj; + StructRttiBuilder builder(&obj, "JSONRPCErrorResponse::Error", nullptr); + builder.addField("code", &obj.code); + builder.addField("message", &obj.message); + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCErrorResponse::Error::g_rttiInfo = _makeJSONRPCErrorResponse_ErrorRtti(); + +static const StructRttiInfo _makeJSONRPCErrorResponseRtti() +{ + JSONRPCErrorResponse obj; + StructRttiBuilder builder(&obj, "JSONRPCErrorResponse", nullptr); + + builder.addField("error", &obj.error); + builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCErrorResponse::g_rttiInfo = _makeJSONRPCErrorResponseRtti(); + +static const StructRttiInfo _makeJSONRPCCallResponseRtti() +{ + JSONRPCCall obj; + StructRttiBuilder builder(&obj, "JSONRPCCall", nullptr); + + builder.addField("method", &obj.method); + builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCCall::g_rttiInfo = _makeJSONRPCCallResponseRtti(); + +static const StructRttiInfo _makeJSONResultResponseResponseRtti() +{ + JSONResultResponse obj; + StructRttiBuilder builder(&obj, "JSONResultResponse", nullptr); + + builder.addField("result", &obj.result); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONResultResponse::g_rttiInfo = _makeJSONResultResponseResponseRtti(); + + +/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id) +{ + const Index maxPairs = 4; + JSONKeyValue pairs[maxPairs]; + + Index i = 0; + + // Version number is a string + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_params), params); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, Int id) +{ + const Index maxPairs = 3; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + // Version number is a string + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data, Int id) +{ + // Set up the error value + JSONValue errorValue; + { + const Index maxPairs = 2; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + if (code != 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_code), JSONValue::makeInt(code)); + } + if (message.getLength() > 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_message), container->createString(message)); + } + errorValue = container->createObject(pairs, i); + } + + const Index maxPairs = 4; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_error), errorValue); + + if (data.isValid()) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_data), data); + } + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + + +/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data, Int id) +{ + return createErrorResponse(container, Index(code), message, data, id); +} + +/* static */JSONValue JSONRPCUtil::createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id) +{ + const Index maxPairs = 3; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_result), resultValue); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */ JSONRPCUtil::ResponseType JSONRPCUtil::getResponseType(JSONContainer* container, const JSONValue& response) +{ + if (response.getKind() == JSONValue::Kind::Object) + { + const JSONKey resultKey = container->findKey(g_result); + const JSONKey errorKey = container->findKey(g_error); + + auto pairs = container->getObject(response); + + for (const auto& pair : pairs) + { + if (pair.key == resultKey) + { + return ResponseType::Result; + } + else if (pair.key == errorKey) + { + return ResponseType::Error; + } + } + } + + return ResponseType::Error; +} + +static SlangResult _parseError(JSONContainer* container, const JSONValue& error, JSONRPCUtil::ErrorResponse& out) +{ + if (error.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + const auto pairs = container->getObject(error); + + const JSONKey messageKey = container->findKey(g_message); + const JSONKey codeKey = container->findKey(g_code); + const JSONKey dataKey = container->findKey(g_data); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == messageKey) + { + if (pair.value.getKind() != JSONValue::Kind::String) + { + return SLANG_FAIL; + } + out.message = container->getString(pair.value); + fieldBits |= 0x1; + } + else if (pair.key == codeKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.code = Index(pair.value.asInteger()); + fieldBits |= 0x2; + } + else if (pair.key == dataKey) + { + out.data = pair.value; + fieldBits |= 0x4; + } + else + { + return SLANG_FAIL; + } + } + + // Check all required fields are set + return (fieldBits & 0x3) == 0x3 ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out) +{ + if (response.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(response); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey errorKey = container->findKey(g_error); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == errorKey) + { + // We need to decode the error + SLANG_RETURN_ON_FAIL(_parseError(container, pair.value, out)); + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out) +{ + if (response.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(response); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey resultKey = container->findKey(g_result); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == resultKey) + { + out.result = pair.value; + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseCall(JSONContainer* container, const JSONValue& value, Call& out) +{ + if (value.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(value); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey methodKey = container->findKey(g_method); + const JSONKey paramsKey = container->findKey(g_params); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == methodKey) + { + if (pair.value.getKind() != JSONValue::Kind::String) + { + return SLANG_FAIL; + } + out.method = container->getString(pair.value); + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else if (pair.key == paramsKey) + { + out.params = pair.value; + fieldBits |= 0x8; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + + /* static */SlangResult JSONRPCUtil::parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue) { SourceManager* sourceManager = sink->getSourceManager(); diff --git a/source/compiler-core/slang-json-rpc.h b/source/compiler-core/slang-json-rpc.h index 3ed3e5fee..e85664ceb 100644 --- a/source/compiler-core/slang-json-rpc.h +++ b/source/compiler-core/slang-json-rpc.h @@ -12,11 +12,110 @@ namespace Slang { +struct JSONRPCErrorResponse +{ + struct Error + { + Index code = 0; ///< Value from ErrorCode + UnownedStringSlice message; ///< Error message + + static const StructRttiInfo g_rttiInfo; + }; + + Error error; + JSONValue data; + Int id = -1; ///< Id of initiating method or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + +struct JSONRPCCall +{ + UnownedStringSlice method; ///< The name of the method + JSONValue params; ///< Can be invalid/array/object + Int id = -1; ///< Id associated with this request, or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + +struct JSONResultResponse +{ + JSONValue result; ///< The result value + Int id = -1; ///< Id of initiating method or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + /// Send and receive messages as JSON +/// +/// Strictly speaking should support ids, as strings or ids. Currently just supports with integer ids. +/// One way of dealing with this would be to just use JSONValue for ids, would allow invalid/string/integer and +/// a mechanism to compare/display etc. class JSONRPCUtil { public: - + + enum class ErrorCode + { + ParseError = -32700, ///< Invalid JSON was received by the server. + InvalidRequest = -32600, ///< The JSON sent is not a valid Request object. + MethodNotFound = -32601, ///< The method does not exist / is not available. + InvalidParams = -32602, ///< Invalid method parameter(s). + InternalError = -32603, ///< Internal JSON - RPC error. + + ServerImplStart = -32000, ///< Server implementation defined error range + ServerImplEnd = -32099, + }; + + enum class ResponseType + { + Invalid, + Error, + Result + }; + + struct ErrorResponse + { + Index code = 0; ///< Value from ErrorCode + UnownedStringSlice message; ///< Error message + JSONValue data; + Int id = -1; ///< Id of initiating method or -1 if not set + }; + + struct ResultResponse + { + JSONValue result; ///< The result value + Int id = -1; ///< Id of initiating method or -1 if not set + }; + + struct Call + { + UnownedStringSlice method; ///< The name of the method + JSONValue params; ///< Can be invalid/array/object + Int id = -1; ///< Id associated with this request, or -1 if not set + }; + + /// Parameters can be either named or via index. + static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id = -1); + /// Parameters can be either named or via index. + static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, Int id = -1); + + /// Create an error response + /// Code should typically be something in the ErrorCode range + static JSONValue createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); + static JSONValue createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); + /// Create a result response + static JSONValue createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id = -1); + + /// Determine the response type + static ResponseType getResponseType(JSONContainer* container, const JSONValue& response); + + static SlangResult parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out); + + static SlangResult parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out); + + static SlangResult parseCall(JSONContainer* container, const JSONValue& value, Call& out); + /// Parse slice into JSONContainer. outValue is the root of the hierarchy. static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp index 9a2bb37f4..1e18bee39 100644 --- a/source/compiler-core/slang-json-value.cpp +++ b/source/compiler-core/slang-json-value.cpp @@ -26,6 +26,24 @@ namespace Slang { JSONValue::Kind::Object, // Object, }; +static bool _isDefault(const RttiInfo* type, const void* in) +{ + SLANG_UNUSED(type) + const JSONValue& value = *(const JSONValue*)in; + return value.getKind() == JSONValue::Kind::Invalid; +} + +static OtherRttiInfo _getJSONValueRttiInfo() +{ + OtherRttiInfo info; + info.init<JSONValue>(RttiInfo::Kind::Other); + info.m_name = "JSONValue"; + info.m_isDefaultFunc = _isDefault; + info.m_typeFuncs = GetRttiTypeFuncs<JSONValue>::getFuncs(); + return info; +} +/* static */const OtherRttiInfo JSONValue::g_rttiInfo = _getJSONValueRttiInfo(); + static JSONKeyValue _makeInvalidKeyValue() { JSONKeyValue keyValue; @@ -204,7 +222,7 @@ JSONValue JSONContainer::createArray(const JSONValue* values, Index valuesCount, JSONValue value; value.type = JSONValue::Type::Array; value.loc = loc; - value.rangeIndex = _addRange(Range::Type::Array, m_objectValues.getCount(), valuesCount); + value.rangeIndex = _addRange(Range::Type::Array, m_arrayValues.getCount(), valuesCount); m_arrayValues.addRange(values, valuesCount); return value; @@ -240,6 +258,12 @@ JSONKey JSONContainer::getKey(const UnownedStringSlice& slice) return JSONKey(m_slicePool.add(slice)); } +JSONKey JSONContainer::findKey(const UnownedStringSlice& slice) const +{ + const Index index = m_slicePool.findIndex(slice); + return (index < 0) ? JSONKey(0) : JSONKey(index); +} + ConstArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in) const { SLANG_ASSERT(in.type == JSONValue::Type::Array); @@ -271,6 +295,8 @@ ArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in) return ArrayView<JSONValue>((JSONValue*)nullptr, 0); } const Range& range = m_ranges[in.rangeIndex]; + SLANG_ASSERT(range.startIndex <= m_arrayValues.getCount() && range.startIndex + range.count <= m_arrayValues.getCount()); + return ArrayView<JSONValue>(m_arrayValues.getBuffer() + range.startIndex, range.count); } @@ -311,9 +337,29 @@ UnownedStringSlice JSONContainer::getLexeme(const JSONValue& in) UnownedStringSlice JSONContainer::getString(const JSONValue& in) { + if (in.type == JSONValue::Type::StringValue) + { + return getStringFromKey(in.stringKey); + } + else if (in.type == JSONValue::Type::StringLexeme) + { + auto slice = getTransientString(in); + auto handle = m_slicePool.add(slice); + return m_slicePool.getSlice(handle); + } + + SLANG_ASSERT(!"Not a string type"); + return UnownedStringSlice(); +} + +UnownedStringSlice JSONContainer::getTransientString(const JSONValue& in) +{ switch (in.type) { - case JSONValue::Type::StringValue: return getStringFromKey(in.stringKey); + case JSONValue::Type::StringValue: + { + return getStringFromKey(in.stringKey); + } case JSONValue::Type::StringLexeme: { StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); @@ -340,7 +386,7 @@ UnownedStringSlice JSONContainer::getString(const JSONValue& in) JSONKey JSONContainer::getStringKey(const JSONValue& in) { - return (in.type == JSONValue::Type::StringValue) ? in.stringKey : getKey(getString(in)); + return (in.type == JSONValue::Type::StringValue) ? in.stringKey : getKey(getTransientString(in)); } bool JSONContainer::asBool(const JSONValue& value) @@ -393,6 +439,19 @@ double JSONContainer::asFloat(const JSONValue& value) } } +Index JSONContainer::findObjectIndex(const JSONValue& obj, JSONKey key) const +{ + auto pairs = getObject(obj); + return pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); +} + +JSONValue JSONContainer::findObjectValue(const JSONValue& obj, JSONKey key) const +{ + auto pairs = getObject(obj); + const Index index = pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); + return (index >= 0) ? pairs[index].value : JSONValue::makeInvalid(); +} + JSONValue& JSONContainer::getAt(const JSONValue& array, Index index) { SLANG_ASSERT(array.type == JSONValue::Type::Array); @@ -742,6 +801,11 @@ bool JSONContainer::areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index } } +bool JSONContainer::areEqual(const JSONValue& a, const UnownedStringSlice& slice) +{ + return a.getKind() == JSONValue::Kind::String && getTransientString(a) == slice; +} + bool JSONContainer::areEqual(const JSONValue& a, const JSONValue& b) { if (&a == &b) @@ -864,7 +928,7 @@ void JSONContainer::traverseRecursively(const JSONValue& value, JSONListener* li { // Emit the key const auto keyString = getStringFromKey(objKeyValue.key); - listener->addKey(keyString, objKeyValue.keyLoc); + listener->addUnquotedKey(keyString, objKeyValue.keyLoc); // Emit the value associated with the key traverseRecursively(objKeyValue.value, listener); @@ -1035,7 +1099,16 @@ void JSONBuilder::endArray(SourceLoc loc) _add(value); } -void JSONBuilder::addKey(const UnownedStringSlice& key, SourceLoc loc) +void JSONBuilder::addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) +{ + // We need to decode + m_work.Clear(); + StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); + StringEscapeUtil::appendUnquoted(handler, key, m_work); + addUnquotedKey(m_work.getUnownedSlice(), loc); +} + +void JSONBuilder::addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) { SLANG_ASSERT(m_keyValue.key == JSONKey(0)); m_keyValue.key = m_container->getKey(key); diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h index d008d5f18..2eae4ae08 100644 --- a/source/compiler-core/slang-json-value.h +++ b/source/compiler-core/slang-json-value.h @@ -9,6 +9,8 @@ #include "slang-json-parser.h" +#include "../core/slang-rtti-info.h" + namespace Slang { typedef uint32_t JSONKey; @@ -111,8 +113,13 @@ struct JSONValue }; static const Kind g_typeToKind[Index(Type::CountOf)]; + + static const OtherRttiInfo g_rttiInfo; }; +template <> +struct GetRttiInfo<JSONValue> { static const RttiInfo* get() { return &JSONValue::g_rttiInfo; } }; + struct JSONKeyValue { /// True if it's valid @@ -129,6 +136,11 @@ struct JSONKeyValue SourceLoc keyLoc; JSONValue value; + static JSONKeyValue make(JSONKey inKey, JSONValue inValue, SourceLoc inKeyLoc = SourceLoc()) + { + return JSONKeyValue{ inKey, inKeyLoc, inValue }; + } + static JSONKeyValue g_invalid; }; @@ -155,6 +167,11 @@ public: /// Get the value at the index in the array JSONValue& getAt(const JSONValue& array, Index index); + /// Returns the index of key in obj, or -1 if not found + Index findObjectIndex(const JSONValue& obj, JSONKey key) const; + /// Get the value in the object at key. REturns invalid if not found. + JSONValue findObjectValue(const JSONValue& obj, JSONKey key) const; + /// Returns the index Index findKeyGlobalIndex(const JSONValue& obj, JSONKey key); Index findKeyGlobalIndex(const JSONValue& obj, const UnownedStringSlice& slice); @@ -176,14 +193,20 @@ public: /// Returns string as a key JSONKey getStringKey(const JSONValue& in); - /// Get as a string. + /// Get as a string. The slice may used backing lexeme (ie will only last + /// as long as the backing JSON text, or be decoded and be transitory). + UnownedStringSlice getTransientString(const JSONValue& in); + + /// Get as a string. The contents will stay in scope as long as the container UnownedStringSlice getString(const JSONValue& in); - /// Gets the lexeme + /// Gets the lexeme UnownedStringSlice getLexeme(const JSONValue& in); /// Get a key for a name JSONKey getKey(const UnownedStringSlice& slice); + /// Returns JSONKey(0) if not found + JSONKey findKey(const UnownedStringSlice& slice) const; /// Get the string from the key UnownedStringSlice getStringFromKey(JSONKey key) const { return m_slicePool.getSlice(StringSlicePool::Handle(key)); } @@ -194,6 +217,8 @@ public: bool areEqual(const JSONValue* a, const JSONValue* b, Index count); bool areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + bool areEqual(const JSONValue& a, const UnownedStringSlice& slice); + /// Destroy value void destroy(JSONValue& value); /// Destroy recursively from value @@ -282,7 +307,8 @@ public: virtual void endObject(SourceLoc loc) SLANG_OVERRIDE; virtual void startArray(SourceLoc loc) SLANG_OVERRIDE; virtual void endArray(SourceLoc loc) SLANG_OVERRIDE; - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) SLANG_OVERRIDE; virtual void addIntegerValue(int64_t value, SourceLoc loc) SLANG_OVERRIDE; virtual void addFloatValue(double value, SourceLoc loc) SLANG_OVERRIDE; @@ -330,6 +356,8 @@ protected: JSONKeyValue m_keyValue; JSONValue m_rootValue; + + StringBuilder m_work; }; } // namespace Slang diff --git a/source/core/slang-allocator.h b/source/core/slang-allocator.h index f25fd92c9..bc1b880f8 100644 --- a/source/core/slang-allocator.h +++ b/source/core/slang-allocator.h @@ -19,11 +19,9 @@ namespace Slang #elif defined(__CYGWIN__) return aligned_alloc(alignment, size); #else - void * rs = 0; + void* rs = nullptr; int succ = posix_memalign(&rs, alignment, size); - if (succ!=0) - rs = 0; - return rs; + return (succ == 0) ? rs : nullptr; #endif } @@ -66,18 +64,15 @@ namespace Slang // Helper utilties for calling allocators. template<typename T, int isPOD> - class Initializer - { - - }; + class Initializer; template<typename T> class Initializer<T, 0> { public: - static void initialize(T* buffer, int size) + static void initialize(T* buffer, Index size) { - for (int i = 0; i < size; i++) + for (Index i = 0; i < size; i++) new (buffer + i) T(); } }; @@ -85,8 +80,10 @@ namespace Slang class Initializer<T, 1> { public: - static void initialize(T* buffer, int size) + static void initialize(T* buffer, Index size) { + SLANG_UNUSED(buffer); + SLANG_UNUSED(size); // It's pod so no initialization required //for (int i = 0; i < size; i++) // new (buffer + i) T; @@ -116,6 +113,7 @@ namespace Slang } }; +#if 0 template<typename T> class AllocateMethod<T, StandardAllocator> { @@ -129,6 +127,7 @@ namespace Slang delete[] ptr; } }; +#endif } #endif diff --git a/source/core/slang-common.h b/source/core/slang-common.h index eb6502b41..6f91da873 100644 --- a/source/core/slang-common.h +++ b/source/core/slang-common.h @@ -33,8 +33,9 @@ namespace Slang // TODO(JS): It looks like Index is actually 64 bit on 64 bit targets(!) // Previous discussions landed on Index being int32_t. - // Type used for indexing, in arrays/views etc + // Type used for indexing, in arrays/views etc. Signed. typedef Int Index; + typedef UInt UIndex; static const Index kMaxIndex = kMaxInt; diff --git a/source/core/slang-list.h b/source/core/slang-list.h index 4420cc084..25687d129 100644 --- a/source/core/slang-list.h +++ b/source/core/slang-list.h @@ -145,6 +145,15 @@ namespace Slang m_capacity = 0; return rs; } + void attachBuffer(T* buffer, Index count, Index capacity) + { + // Can only attach a buffer if there isn't a buffer already associated + SLANG_ASSERT(m_buffer == nullptr); + SLANG_ASSERT(count <= capacity); + m_buffer = buffer; + m_count = count; + m_capacity = capacity; + } inline ArrayView<T> getArrayView() const { @@ -324,7 +333,9 @@ namespace Slang void reserve(Index size) { - if(size > m_capacity) + // The cast for this comparison is needed, otherwise some compilers erroneously detect + // the possiblity of a zero sized allocation (possible if m_capacity is assumed to be negative). + if(UIndex(size) > UIndex(m_capacity)) { T* newBuffer = _allocate(size); if (m_capacity) diff --git a/source/core/slang-rtti-info.cpp b/source/core/slang-rtti-info.cpp new file mode 100644 index 000000000..f53cf742f --- /dev/null +++ b/source/core/slang-rtti-info.cpp @@ -0,0 +1,184 @@ +#include "slang-rtti-info.h" + +#include "../../slang-com-helper.h" + +#include <mutex> + +namespace Slang { + +#define SLANG_RTTI_INFO_INVALID(name) RttiInfo{RttiInfo::Kind::Invalid, 0, 0} +#define SLANG_RTTI_INFO_BASIC(name, type) \ + RttiInfo{RttiInfo::Kind::name, RttiInfo::AlignmentType(SLANG_ALIGN_OF(type)), RttiInfo::SizeType(sizeof(type))} + +/* static */const RttiInfo RttiInfo::g_basicTypes[Index(Kind::CountOf)] = +{ + SLANG_RTTI_INFO_INVALID(Invalid), + SLANG_RTTI_INFO_BASIC(I32, int32_t), + SLANG_RTTI_INFO_BASIC(U32, uint32_t), + SLANG_RTTI_INFO_BASIC(I64, int64_t), + SLANG_RTTI_INFO_BASIC(U64, uint64_t), + SLANG_RTTI_INFO_BASIC(F32, float), + SLANG_RTTI_INFO_BASIC(F64, double), + SLANG_RTTI_INFO_BASIC(Bool, bool), + SLANG_RTTI_INFO_BASIC(String, String), + SLANG_RTTI_INFO_BASIC(UnownedStringSlice, UnownedStringSlice), + SLANG_RTTI_INFO_BASIC(Ptr, void*), + SLANG_RTTI_INFO_BASIC(RefPtr, RefPtr<StringRepresentation>), + SLANG_RTTI_INFO_INVALID(FixedArray), + SLANG_RTTI_INFO_INVALID(Struct), + SLANG_RTTI_INFO_INVALID(Other), + SLANG_RTTI_INFO_INVALID(Enum), + SLANG_RTTI_INFO_INVALID(List), + SLANG_RTTI_INFO_INVALID(Dictionary), +}; + +struct RttiInfoManager +{ + void* allocate(size_t size) + { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + return m_arena.allocate(size); + } + + static RttiInfoManager& getSingleton() + { + static RttiInfoManager g_manager; + return g_manager; + } + +protected: + RttiInfoManager() : + m_arena(1024) + { + } + + std::recursive_mutex m_mutex; ///< We need a mutex to guard access to m_arena + MemoryArena m_arena; +}; + +/* static */void* RttiInfo::allocate(size_t size) +{ + return RttiInfoManager::getSingleton().allocate(size); +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StructRttiBuilder !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +static void _appendFixedArray(const FixedArrayRttiInfo* inFixedArray, StringBuilder& out) +{ + List<const FixedArrayRttiInfo*> fixedArrays; + fixedArrays.add(inFixedArray); + + const RttiInfo* cur = inFixedArray->m_elementType; + while (cur->m_kind == RttiInfo::Kind::FixedArray) + { + const FixedArrayRttiInfo* curArray = static_cast<const FixedArrayRttiInfo*>(cur); + fixedArrays.add(curArray); + cur = curArray->m_elementType; + } + + // Append the 'target' which is in cur + RttiInfo::append(cur, out); + // Now all the fixed array values, in order + for (auto fixedArray : fixedArrays) + { + out << "[" << int32_t(fixedArray->m_elementCount) << "]"; + } +} + +/* static */void RttiInfo::append(const RttiInfo* info, StringBuilder& out) +{ + switch (info->m_kind) + { + case RttiInfo::Kind::I32: out << "int32_t"; break; + case RttiInfo::Kind::U32: out << "uint32_t"; break; + case RttiInfo::Kind::I64: out << "int64_t"; break; + case RttiInfo::Kind::U64: out << "uint64_t"; break; + case RttiInfo::Kind::F32: out << "float"; break; + case RttiInfo::Kind::F64: out << "double"; break; + case RttiInfo::Kind::Bool: out << "bool"; break; + case RttiInfo::Kind::String: out << "String"; break; + case RttiInfo::Kind::UnownedStringSlice: out << "UnownedStringSlice"; break; + case RttiInfo::Kind::Ptr: + { + const PtrRttiInfo* ptrRttiInfo = static_cast<const PtrRttiInfo*>(info); + append(ptrRttiInfo->m_targetType, out); + out << "*"; + break; + } + case RttiInfo::Kind::RefPtr: + { + const RefPtrRttiInfo* ptrRttiInfo = static_cast<const RefPtrRttiInfo*>(info); + out << "RefPtr<"; + append(ptrRttiInfo->m_targetType, out); + out << ">"; + break; + } + case RttiInfo::Kind::FixedArray: + { + const FixedArrayRttiInfo* arrayRttiInfo = static_cast<const FixedArrayRttiInfo*>(info); + _appendFixedArray(arrayRttiInfo, out); + break; + } + case RttiInfo::Kind::List: + { + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(info); + out << "List<"; + append(listRttiInfo->m_elementType, out); + out << ">"; + break; + } + case RttiInfo::Kind::Dictionary: + { + const DictionaryRttiInfo* dictionaryRttiInfo = static_cast<const DictionaryRttiInfo*>(info); + + out << "Dictionary<"; + append(dictionaryRttiInfo->m_keyType, out); + out << ","; + append(dictionaryRttiInfo->m_valueType, out); + out << ">"; + break; + } + default: + { + if (info->isNamed()) + { + const NamedRttiInfo* namedRttiInfo = static_cast<const NamedRttiInfo*>(info); + out << namedRttiInfo->m_name; + break; + } + + out << "%Unknown%"; + break; + } + } +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! StructRttiBuilder !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +void StructRttiBuilder::_init(const char* name, const StructRttiInfo* super, const Byte* base) +{ + m_rttiInfo.m_name = name; + m_rttiInfo.m_super = super; + m_base = base; + + m_rttiInfo.m_fieldCount = 0; + m_rttiInfo.m_fields = nullptr; +} + +StructRttiInfo StructRttiBuilder::make() +{ + const Index fieldCount = m_fields.getCount(); + + if (fieldCount) + { + StructRttiInfo::Field* dstFields = (StructRttiInfo::Field*)RttiInfo::allocate(sizeof(StructRttiInfo::Field) * fieldCount); + ::memcpy(dstFields, m_fields.getBuffer(), sizeof(StructRttiInfo::Field) * fieldCount); + + m_rttiInfo.m_fields = dstFields; + m_rttiInfo.m_fieldCount = fieldCount; + } + + return m_rttiInfo; +} + +} // namespace Slang diff --git a/source/core/slang-rtti-info.h b/source/core/slang-rtti-info.h new file mode 100644 index 000000000..c1fe1810f --- /dev/null +++ b/source/core/slang-rtti-info.h @@ -0,0 +1,341 @@ +#ifndef SLANG_CORE_RTTI_INFO_H +#define SLANG_CORE_RTTI_INFO_H + +#include "slang-basic.h" +#include "slang-memory-arena.h" + +#include "slang-list.h" +#include "slang-dictionary.h" + +namespace Slang { + +struct RttiInfo; + +struct RttiTypeFuncs +{ + typedef void (*CtorArray)(const RttiInfo* rttiInfo, void* dst, Index count); + typedef void (*DtorArray)(const RttiInfo* rttiInfo, void* dst, Index count); + typedef void (*CopyArray)(const RttiInfo* rttiInfo, void* dst, const void* src, Index count); + + bool isValid() const { return ctorArray && dtorArray && copyArray; } + + static RttiTypeFuncs makeEmpty() { return RttiTypeFuncs{ nullptr, nullptr, nullptr }; } + + CtorArray ctorArray; + DtorArray dtorArray; + CopyArray copyArray; +}; + +template <typename T> +struct GetRttiTypeFuncs +{ + static void ctorArray(const RttiInfo* rttiInfo, void* in, Index count) + { + SLANG_UNUSED(rttiInfo); + T* dst = (T*)in; + for (Index i = 0; i < count; ++i) + { + new (dst + i) T; + } + } + static void dtorArray(const RttiInfo* rttiInfo, void* in, Index count) + { + SLANG_UNUSED(rttiInfo); + T* dst = (T*)in; + for (Index i = 0; i < count; ++i) + { + (dst + i)->~T(); + } + } + static void copyArray(const RttiInfo* rttiInfo, void* inDst, const void* inSrc, Index count) + { + SLANG_UNUSED(rttiInfo); + T* dst = (T*)inDst; + const T* src = (T*)inSrc; + for (Index i = 0; i < count; ++i) + { + dst[i] = src[i]; + } + } + static RttiTypeFuncs getFuncs() + { + RttiTypeFuncs funcs; + funcs.copyArray = ©Array; + funcs.dtorArray = &dtorArray; + funcs.ctorArray = &ctorArray; + return funcs; + } +}; + +struct RttiInfo +{ + typedef uint8_t AlignmentType; + typedef uint16_t SizeType; + + enum class Kind : uint8_t + { + Invalid, + I32, + U32, + I64, + U64, + F32, + F64, + Bool, + String, + UnownedStringSlice, + Ptr, + RefPtr, + FixedArray, + Struct, + Other, + Enum, + List, + Dictionary, + + CountOf, + }; + + Kind m_kind; + AlignmentType m_alignment; + SizeType m_size; + + void init(Kind kind, size_t alignment, size_t size) { m_kind = kind; m_alignment = AlignmentType(alignment); m_size = SizeType(size); } + + template <typename T> + void init(Kind kind) { init(kind, SLANG_ALIGN_OF(T), sizeof(T)); } + + /// Allocate memory for RttiInfo types. + /// Is thread safe, and doesn't require the memory to be freed explicitly + /// Will be freed at shutdown (via global dtor) + static void* allocate(size_t size); + + static bool isIntegral(RttiInfo::Kind kind) { return Index(kind) >= Index(RttiInfo::Kind::I32) && Index(kind) <= Index(RttiInfo::Kind::U64); } + static bool isFloat(RttiInfo::Kind kind) { return kind == RttiInfo::Kind::F32 || kind == RttiInfo::Kind::F64; } + static bool isBuiltIn(RttiInfo::Kind kind) { return kind == RttiInfo::Kind::I32 || kind == RttiInfo::Kind::Bool; } + static bool isNamed(RttiInfo::Kind kind) { return Index(kind) >= Index(RttiInfo::Kind::Struct) && Index(kind) <= Index(RttiInfo::Kind::Enum); } + + bool isIntegral() const { return isIntegral(m_kind); } + bool isFloat() const { return isFloat(m_kind); } + bool isBuiltIn() const { return isBuiltIn(m_kind); } + bool isNamed() const { return isNamed(m_kind); } + + static void append(const RttiInfo* info, StringBuilder& out); + + static const RttiInfo g_basicTypes[Index(Kind::CountOf)]; +}; + +// Can combine into flags on a field. Could store default value with a field, +// but this works fine for most purposes +enum class RttiDefaultValue : uint8_t +{ + Normal, ///< Zero for integral/float types/false for bool + One, + MinusOne, + + Mask = 0x7, +}; + +struct NamedRttiInfo : public RttiInfo +{ + const char* m_name; ///< Name +}; + +struct StructRttiInfo : public NamedRttiInfo +{ + typedef uint8_t Flags; + struct Flag + { + enum Enum : Flags + { + // We use low bits for 'RttiDefaultValue' value + Optional = 0x8, + }; + }; + + struct Field + { + const char* m_name; ///< Name of this field + const RttiInfo* m_type; ///< The type of this field + uint32_t m_offset; ///< Offset from object type in bytes + Flags m_flags; ///< Field flags + }; + + const StructRttiInfo* m_super; ///< Super class or nullptr if not defined + + Index m_fieldCount; ///< Amount of fields + const Field* m_fields; ///< Fields +}; + +struct EnumRttiInfo : public NamedRttiInfo +{ + // TODO(JS): +}; + +SLANG_FORCE_INLINE StructRttiInfo::Flags combine(StructRttiInfo::Flags flags, RttiDefaultValue defaultValue) +{ + return StructRttiInfo::Flags(defaultValue) | flags; +} + +struct ListRttiInfo : public RttiInfo +{ + const RttiInfo* m_elementType; +}; + +struct DictionaryRttiInfo : public RttiInfo +{ + const RttiInfo* m_keyType; + const RttiInfo* m_valueType; +}; + +struct PtrRttiInfo : public RttiInfo +{ + const RttiInfo* m_targetType; +}; + +struct RefPtrRttiInfo : public RttiInfo +{ + const RttiInfo* m_targetType; +}; + +struct FixedArrayRttiInfo : public RttiInfo +{ + const RttiInfo* m_elementType; + size_t m_elementCount; +}; + +struct OtherRttiInfo : public NamedRttiInfo +{ + typedef bool (*IsDefaultFunc)(const RttiInfo* rttiInfo, const void* in); + IsDefaultFunc m_isDefaultFunc; + RttiTypeFuncs m_typeFuncs; +}; + +// The default is to just get the info from a global held inside the type. +template <typename T> +struct GetRttiInfo +{ + SLANG_FORCE_INLINE static const RttiInfo* get() { return &T::g_rttiInfo; } +}; + +template <> struct GetRttiInfo<bool> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::Bool)];} }; +template <> struct GetRttiInfo<int32_t> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::I32)]; } }; +template <> struct GetRttiInfo<int64_t> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::I64)]; } }; +template <> struct GetRttiInfo<uint32_t> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::U32)]; } }; +template <> struct GetRttiInfo<uint64_t> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::U64)]; } }; +template <> struct GetRttiInfo<float> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::F32)]; } }; +template <> struct GetRttiInfo<double> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::F64)]; } }; +template <> struct GetRttiInfo<String> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::String)]; } }; +template <> struct GetRttiInfo<UnownedStringSlice> { static const RttiInfo* get() { return &RttiInfo::g_basicTypes[Index(RttiInfo::Kind::UnownedStringSlice)]; } }; + +template <typename T> +struct GetRttiInfo<List<T>> +{ + static const ListRttiInfo _make() + { + ListRttiInfo info; + info.init<List<Byte>>(RttiInfo::Kind::List); + info.m_elementType = GetRttiInfo<T>::get(); + return info; + } + static const RttiInfo* get() { static const ListRttiInfo g_info = _make(); return &g_info; } +}; + +// Strip const +template <typename T> +struct GetRttiInfo<const T> +{ + static const RttiInfo* get() { return GetRttiInfo<T>::get(); } +}; + +template <typename K, typename V> +struct GetRttiInfo<Dictionary<K, V>> +{ + static const DictionaryRttiInfo _make() + { + DictionaryRttiInfo info; + info.init<Dictionary<Byte, Byte>>(RttiInfo::Kind::Dictionary); + info.m_keyType = GetRttiInfo<K>::get(); + info.m_valueType = GetRttiInfo<V>::get(); + return info; + } + static const RttiInfo* get() { static const DictionaryRttiInfo g_info = _make(); return &g_info; } +}; + +template <typename TARGET> +struct GetRttiInfo<TARGET*> +{ + static const PtrRttiInfo _make() + { + PtrRttiInfo info; + info.init<void*>(RttiInfo::Kind::Ptr); + info.m_targetType = GetRttiInfo<TARGET>::get(); + return info; + } + static const RttiInfo* get() { static const PtrRttiInfo g_info = _make(); return &g_info; } +}; + +template <typename TARGET> +struct GetRttiInfo<RefPtr<TARGET>> +{ + static const RefPtrRttiInfo _make() + { + RefPtrRttiInfo info; + info.init<RefPtr<StringRepresentation>>(RttiInfo::Kind::RefPtr); + info.m_targetType = GetRttiInfo<TARGET>::get(); + return info; + } + static const RttiInfo* get() { static const RefPtrRttiInfo g_info = _make(); return &g_info; } +}; + +template <typename T, size_t COUNT> +struct GetRttiInfo<T[COUNT]> +{ + static const FixedArrayRttiInfo _make() + { + FixedArrayRttiInfo info; + info.m_kind = RttiInfo::Kind::FixedArray; + info.m_alignment = RttiInfo::AlignmentType(SLANG_ALIGN_OF(T)); + info.m_size = RttiInfo::SizeType(sizeof(T) * COUNT); + info.m_elementType = GetRttiInfo<T>::get(); + info.m_elementCount = COUNT; + return info; + } + static const RttiInfo* get() { static const FixedArrayRttiInfo g_info = _make(); return &g_info; } +}; + +struct StructRttiBuilder +{ + template <typename T> + StructRttiBuilder(T* obj, const char* name, const StructRttiInfo* super) + { + m_rttiInfo.init<T>(RttiInfo::Kind::Struct); + _init(name, super, (const Byte*)obj); + } + + template <typename T> + void addField(const char* name, const T* fieldPtr, StructRttiInfo::Flags flags = 0) + { + StructRttiInfo::Field field; + + field.m_name = name; + field.m_type = GetRttiInfo<T>::get(); + field.m_offset = uint32_t(ptrdiff_t((const Byte*)fieldPtr - m_base)); + field.m_flags = flags; + m_fields.add(field); + } + + StructRttiInfo make(); + + void _init(const char* name, const StructRttiInfo* super, const Byte* base); + + StructRttiInfo m_rttiInfo; + + List<StructRttiInfo::Field> m_fields; + const Byte* m_base; +}; + + +} // namespace Slang + +#endif // SLANG_CORE_RTTI_INFO_H diff --git a/source/core/slang-rtti-util.cpp b/source/core/slang-rtti-util.cpp new file mode 100644 index 000000000..ed4c32e58 --- /dev/null +++ b/source/core/slang-rtti-util.cpp @@ -0,0 +1,392 @@ +#include "slang-rtti-util.h" + +namespace Slang { + +/* static */SlangResult RttiUtil::setInt(int64_t value, const RttiInfo* rttiInfo, void* dst) +{ + SLANG_ASSERT(rttiInfo->isIntegral()); + + // We could check ranges are appropriate, but for now we just write. + // Passing in rttiInfo allows for other more complex types to be econverted + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::I32: *(int32_t*)dst = int32_t(value); break; + case RttiInfo::Kind::U32: *(uint32_t*)dst = uint32_t(value); break; + case RttiInfo::Kind::I64: *(int64_t*)dst = int64_t(value); break; + case RttiInfo::Kind::U64: *(uint64_t*)dst = uint64_t(value); break; + default: return SLANG_FAIL; + } + return SLANG_OK; +} + +/* static */int64_t RttiUtil::getInt64(const RttiInfo* rttiInfo, const void* src) +{ + SLANG_ASSERT(rttiInfo->isIntegral()); + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::I32: return *(const int32_t*)src; + case RttiInfo::Kind::U32: return *(const uint32_t*)src; + case RttiInfo::Kind::I64: return *(const int64_t*)src; + case RttiInfo::Kind::U64: return *(const uint64_t*)src; + default: break; + } + + SLANG_ASSERT(!"Not integral!"); + return -1; +} + +/* static */double RttiUtil::asDouble(const RttiInfo* rttiInfo, const void* src) +{ + if (rttiInfo->isIntegral()) + { + return (double)getInt64(rttiInfo, src); + } + else if (rttiInfo->isFloat()) + { + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::F32: return *(const float*)src; + case RttiInfo::Kind::F64: return *(const double*)src; + default: break; + } + } + + SLANG_ASSERT(!"Cannot convert to float"); + return 0.0; +} + +/* static */SlangResult RttiUtil::setFromDouble(double v, const RttiInfo* rttiInfo, void* dst) +{ + if (rttiInfo->isIntegral()) + { + return setInt(int64_t(v), rttiInfo, dst); + } + else if (rttiInfo->isFloat()) + { + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::F32: *(float*)dst = float(v); return SLANG_OK; + case RttiInfo::Kind::F64: *(double*)dst = v; return SLANG_OK; + default: break; + } + } + + return SLANG_FAIL; +} + +/* static */bool RttiUtil::asBool(const RttiInfo* rttiInfo, const void* src) +{ + if (rttiInfo->m_kind == RttiInfo::Kind::Bool) + { + return *(const bool*)src; + } + + if (rttiInfo->isIntegral()) + { + return getInt64(rttiInfo, src) != 0; + } + else if (rttiInfo->isFloat()) + { + return asDouble(rttiInfo, src) != 0.0; + } + + SLANG_ASSERT(!"Cannot convert to bool"); + return false; +} + +static int64_t _getIntDefaultValue(RttiDefaultValue value) +{ + switch (value) + { + default: + case RttiDefaultValue::Normal: return 0; + case RttiDefaultValue::One: return 1; + case RttiDefaultValue::MinusOne: return -1; + } +} + +static bool _isStructDefault(const StructRttiInfo* type, const void* src) +{ + if (type->m_super) + { + if (!_isStructDefault(type->m_super, src)) + { + return false; + } + } + + const Byte* base = (const Byte*)src; + + const Index count = type->m_fieldCount; + for (Index i = 0; i < count; ++i) + { + const auto& field = type->m_fields[i]; + + const RttiDefaultValue defaultValue = RttiDefaultValue(field.m_flags & uint8_t(RttiDefaultValue::Mask)); + + if (!RttiUtil::isDefault(defaultValue, field.m_type, base + field.m_offset)) + { + return false; + } + } + + return true; +} + +/* static */bool RttiUtil::isDefault(RttiDefaultValue defaultValue, const RttiInfo* rttiInfo, const void* src) +{ + if (rttiInfo->isIntegral()) + { + const auto value = getInt64(rttiInfo, src); + return _getIntDefaultValue(defaultValue) == value; + } + else if (rttiInfo->isFloat()) + { + const auto value = asDouble(rttiInfo, src); + return _getIntDefaultValue(defaultValue) == value; + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::Invalid: return true; + case RttiInfo::Kind::Bool: return *(const bool*)src == (_getIntDefaultValue(defaultValue) != 0); + case RttiInfo::Kind::String: + { + return ((const String*)src)->getLength() == 0; + } + case RttiInfo::Kind::UnownedStringSlice: + { + return ((const UnownedStringSlice*)src)->getLength() == 0; + } + case RttiInfo::Kind::Struct: + { + return _isStructDefault(static_cast<const StructRttiInfo*>(rttiInfo), src); + } + case RttiInfo::Kind::Enum: + { + SLANG_ASSERT(!"Not implemented yet"); + return false; + } + case RttiInfo::Kind::List: + { + const auto& v = *(const List<Byte>*)src; + return v.getCount() == 0; + } + case RttiInfo::Kind::Dictionary: + { + const auto& v = *(const Dictionary<Byte, Byte>*)src; + return v.Count() == 0; + } + case RttiInfo::Kind::Other: + { + const OtherRttiInfo* otherRttiInfo = static_cast<const OtherRttiInfo*>(rttiInfo); + return otherRttiInfo->m_isDefaultFunc && otherRttiInfo->m_isDefaultFunc(rttiInfo, src); + } + default: + { + return false; + } + } +} + +template <typename T> +struct GetRttiTypeFuncsForBuiltIn +{ + static void ctorArray(const RttiInfo* rttiInfo, void* dst, Index count) { SLANG_UNUSED(rttiInfo); ::memset(dst, 0, sizeof(T) * count); } + static void dtorArray(const RttiInfo* rttiInfo, void* dst, Index count) { SLANG_UNUSED(rttiInfo); SLANG_UNUSED(dst); SLANG_UNUSED(count); } + static void copyArray(const RttiInfo* rttiInfo, void* dst, const void* src, Index count) { SLANG_UNUSED(rttiInfo); ::memcpy(dst, src, sizeof(T) * count); } + + static RttiTypeFuncs getFuncs() + { + RttiTypeFuncs funcs; + funcs.copyArray = ©Array; + funcs.dtorArray = &dtorArray; + funcs.ctorArray = &ctorArray; + return funcs; + } +}; + +struct ListFuncs +{ + static void ctorArray(const RttiInfo* rttiInfo, void* inDst, Index count) + { + SLANG_UNUSED(rttiInfo); + SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List); + + // We don't care about the element type, as we can just initialize them all as List<Byte> + //const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + typedef List<Byte> Type; + + Type* dst = (Type*)inDst; + + for (Index i = 0; i < count; ++i) + { + new (dst + i) Type; + } + } + static void copyArray(const RttiInfo* rttiInfo, void* inDst, const void* inSrc, Index count) + { + SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List); + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + const auto elementType = listRttiInfo->m_elementType; + + // We need to get the type funcs + auto typeFuncs = RttiUtil::getTypeFuncs(elementType); + SLANG_ASSERT(typeFuncs.isValid()); + + // We need a type that we can get information from the list from - List<Byte> gives us the functions we need. + typedef List<Byte> Type; + + Type* dst = (Type*)inDst; + const Type* src = (const Type*)inSrc; + + for (Index i = 0; i < count; ++i) + { + auto& dstList = dst[i]; + auto& srcList = src[i]; + + const Index srcCount = srcList.getCount(); + + if (srcCount > dstList.getCount()) + { + // Allocate new memory + const Index dstCapacity = dstList.getCapacity(); + void* oldBuffer = dstList.detachBuffer(); + + void* newBuffer = ::malloc(count * elementType->m_size); + // Initialize it all first + typeFuncs.ctorArray(elementType, newBuffer, count); + typeFuncs.copyArray(elementType, newBuffer, oldBuffer, count); + + // Attach the new buffer + dstList.attachBuffer((Byte*)newBuffer, count, count); + + // Free the old buffer + if (oldBuffer) + { + typeFuncs.dtorArray(elementType, oldBuffer, dstCapacity); + + ::free(oldBuffer); + } + } + else + { + typeFuncs.copyArray(elementType, dstList.getBuffer(), srcList.getBuffer(), srcCount); + dstList.unsafeShrinkToCount(srcCount); + } + } + } + + static void dtorArray(const RttiInfo* rttiInfo, void* inDst, Index count) + { + SLANG_ASSERT(rttiInfo->m_kind == RttiInfo::Kind::List); + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + + const auto elementType = listRttiInfo->m_elementType; + + // We need to get the type funcs + auto typeFuncs = RttiUtil::getTypeFuncs(elementType); + SLANG_ASSERT(typeFuncs.isValid()); + + typedef List<Byte> Type; + Type* dst = (Type*)inDst; + + for (Index i = 0; i < count; ++i) + { + auto& dstList = dst[i]; + + const Index capacity = dstList.getCapacity(); + Byte* buffer = dstList.detachBuffer(); + + if (buffer) + { + typeFuncs.dtorArray(elementType, buffer, capacity); + ::free(buffer); + } + } + } + + static RttiTypeFuncs getFuncs() + { + RttiTypeFuncs funcs; + funcs.copyArray = ©Array; + funcs.dtorArray = &dtorArray; + funcs.ctorArray = &ctorArray; + return funcs; + } +}; + +RttiTypeFuncs RttiUtil::getTypeFuncs(const RttiInfo* rttiInfo) +{ + if (rttiInfo->isBuiltIn()) + { + switch (rttiInfo->m_size) + { + case 1: return GetRttiTypeFuncsForBuiltIn<uint8_t>::getFuncs(); + case 2: return GetRttiTypeFuncsForBuiltIn<uint16_t>::getFuncs(); + case 4: return GetRttiTypeFuncsForBuiltIn<uint32_t>::getFuncs(); + case 8: return GetRttiTypeFuncsForBuiltIn<uint64_t>::getFuncs(); + } + return RttiTypeFuncs::makeEmpty(); + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::String: return GetRttiTypeFuncs<String>::getFuncs(); + case RttiInfo::Kind::UnownedStringSlice: return GetRttiTypeFuncs<UnownedStringSlice>::getFuncs(); + case RttiInfo::Kind::List: return ListFuncs::getFuncs(); + default: break; + } + + return RttiTypeFuncs::makeEmpty(); +} + +/* static */SlangResult RttiUtil::setListCount(const RttiInfo* elementType, void* dst, Index count) +{ + // NOTE! The following only works because List<T> has capacity initialized members, and + // setting the count if it is <= capacity just sets the count (ie things aren't released(!)). + + List<Byte>& dstList = *(List<Byte>*)dst; + const Index oldCount = dstList.getCount(); + if (oldCount == count) + { + return SLANG_OK; + } + if (count < oldCount) + { + dstList.unsafeShrinkToCount(count); + return SLANG_OK; + } + + // Get funcs needed + const auto typeFuncs = RttiUtil::getTypeFuncs(elementType); + + if (!typeFuncs.isValid()) + { + return SLANG_FAIL; + } + + const Index dstCapacity = dstList.getCapacity(); + void* oldBuffer = dstList.detachBuffer(); + + void* newBuffer = ::malloc(count * elementType->m_size); + // Initialize it all first + typeFuncs.ctorArray(elementType, newBuffer, count); + + typeFuncs.copyArray(elementType, newBuffer, oldBuffer, oldCount); + + // Attach the new buffer + dstList.attachBuffer((Byte*)newBuffer, count, count); + + // Free the old buffer + if (oldBuffer) + { + typeFuncs.dtorArray(elementType, oldBuffer, dstCapacity); + ::free(oldBuffer); + } + + return SLANG_OK; +} + +} // namespace Slang diff --git a/source/core/slang-rtti-util.h b/source/core/slang-rtti-util.h new file mode 100644 index 000000000..d514d1980 --- /dev/null +++ b/source/core/slang-rtti-util.h @@ -0,0 +1,30 @@ +#ifndef SLANG_CORE_RTTI_UTIL_H +#define SLANG_CORE_RTTI_UTIL_H + +#include "slang-rtti-info.h" + +namespace Slang { +struct RttiUtil +{ + + static SlangResult setInt(int64_t value, const RttiInfo* rttiInfo, void* dst); + static int64_t getInt64(const RttiInfo* rttiInfo, const void* src); + + static double asDouble(const RttiInfo* rttiInfo, const void* src); + + static SlangResult setFromDouble(double v, const RttiInfo* rttiInfo, void* dst); + + static bool asBool(const RttiInfo* rttiInfo, const void* src); + + static bool isDefault(RttiDefaultValue defaultValue, const RttiInfo* rttiInfo, const void* src); + + static RttiTypeFuncs getTypeFuncs(const RttiInfo* rttiInfo); + + /// Set a list count + static SlangResult setListCount(const RttiInfo* elementType, void* dst, Index count); + +}; + +} // namespace Slang + +#endif // SLANG_CORE_RTTI_UTIL_H diff --git a/tools/slang-unit-test/unit-test-json-native.cpp b/tools/slang-unit-test/unit-test-json-native.cpp new file mode 100644 index 000000000..1d2085751 --- /dev/null +++ b/tools/slang-unit-test/unit-test-json-native.cpp @@ -0,0 +1,117 @@ +// unit-test-json-native.cpp + +#include "../../source/core/slang-rtti-info.h" + +#include "../../source/compiler-core/slang-json-native.h" +#include "../../source/compiler-core/slang-json-parser.h" + +#include "tools/unit-test/slang-unit-test.h" + +using namespace Slang; + +namespace { // anonymous + +struct SomeStruct +{ + typedef SomeStruct ThisType; + + bool operator==(const ThisType& rhs) const + { + return a == rhs.a && b == rhs.b && s == rhs.s && list == rhs.list; + } + bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + + int a = 0; + float b = 2.0f; + String s; + List<String> list; + + static const StructRttiInfo g_rttiInfo; +}; + +} // anonymous + +static const StructRttiInfo _makeSomeStructRtti() +{ + SomeStruct obj; + StructRttiBuilder builder(&obj, "SomeStruct", nullptr); + + builder.addField("a", &obj.a); + builder.addField("b", &obj.b); + builder.addField("s", &obj.s); + builder.addField("list", &obj.list); + + return builder.make(); +} +/* static */const StructRttiInfo SomeStruct::g_rttiInfo = _makeSomeStructRtti(); + +static SlangResult _check() +{ + // Convert into a JSON string + + SomeStruct s; + s.list.add("Hello!"); + s.s = "There"; + + // Try serializing it out + + SourceManager sourceManager; + sourceManager.initialize(nullptr, nullptr); + + DiagnosticSink sink(&sourceManager, &JSONLexer::calcLexemeLocation); + + RefPtr<JSONContainer> container(new JSONContainer(&sourceManager)); + + String json; + { + NativeToJSONConverter converter(container, &sink); + + JSONValue value; + SLANG_RETURN_ON_FAIL(converter.convert(GetRttiInfo<SomeStruct>::get(), &s, value)); + + // Convert into a string + JSONWriter writer(JSONWriter::IndentationStyle::Allman); + container->traverseRecursively(value, &writer); + + json = writer.getBuilder(); + } + + JSONValue readValue; + { + // Now need to parse as JSON + String contents(json); + SourceFile* sourceFile = sourceManager.createSourceFileWithString(PathInfo::makeUnknown(), contents); + SourceView* sourceView = sourceManager.createSourceView(sourceFile, nullptr, SourceLoc()); + + JSONLexer lexer; + lexer.init(sourceView, &sink); + + JSONBuilder builder(container); + + JSONParser parser; + SLANG_RETURN_ON_FAIL(parser.parse(&lexer, sourceView, &builder, &sink)); + + readValue = builder.getRootValue(); + } + + // Convert back to native + { + JSONToNativeConverter converter(container, &sink); + + { + SomeStruct readS; + SLANG_RETURN_ON_FAIL(converter.convert(readValue, GetRttiInfo<SomeStruct>::get(), &readS)); + + // Should be equal + SLANG_CHECK(readS == s); + } + } + + return SLANG_OK; +} + +SLANG_UNIT_TEST(JSONNative) +{ + SLANG_CHECK(SLANG_SUCCEEDED(_check())); + +} diff --git a/tools/slang-unit-test/unit-test-rtti.cpp b/tools/slang-unit-test/unit-test-rtti.cpp new file mode 100644 index 000000000..f72cfbde2 --- /dev/null +++ b/tools/slang-unit-test/unit-test-rtti.cpp @@ -0,0 +1,74 @@ +// unit-test-rtti.cpp + +#include "../../source/core/slang-rtti-info.h" + +#include "tools/unit-test/slang-unit-test.h" + +using namespace Slang; + +namespace { // anonymous + +struct SomeStruct +{ + int a = 0; + float b = 2.0f; + String s; + List<String> list; + + static const StructRttiInfo g_rttiInfo; +}; + +} // anonymous + +static const StructRttiInfo _makeSomeStructRtti() +{ + SomeStruct obj; + StructRttiBuilder builder(&obj, "SomeStruct", nullptr); + + builder.addField("a", &obj.a); + builder.addField("b", &obj.b); + builder.addField("s", &obj.s); + builder.addField("list", &obj.list); + + return builder.make(); +} +/* static */const StructRttiInfo SomeStruct::g_rttiInfo = _makeSomeStructRtti(); + +SLANG_UNIT_TEST(Rtti) +{ + using namespace Slang; + + const RttiInfo* types[] = + { + GetRttiInfo<int32_t>::get(), + GetRttiInfo<int32_t[10]>::get(), + GetRttiInfo<String>::get(), + GetRttiInfo<List<String>>::get(), + GetRttiInfo<List<List<String>>>::get(), + GetRttiInfo<int32_t[2][3]>::get(), + GetRttiInfo<SomeStruct>::get(), + GetRttiInfo<SomeStruct*>::get(), + GetRttiInfo<const float*const>::get(), + }; + + StringBuilder buf; + + for (auto type : types) + { + RttiInfo::append(type, buf); + buf << "\n"; + } + + const char expected[] = + "int32_t\n" + "int32_t[10]\n" + "String\n" + "List<String>\n" + "List<List<String>>\n" + "int32_t[2][3]\n" + "SomeStruct\n" + "SomeStruct*\n" + "float*\n"; + + SLANG_CHECK(buf == expected) +} diff --git a/tools/test-server/test-server-diagnostic-defs.h b/tools/test-server/test-server-diagnostic-defs.h new file mode 100644 index 000000000..ef0b280e8 --- /dev/null +++ b/tools/test-server/test-server-diagnostic-defs.h @@ -0,0 +1,26 @@ +// + +// The file is meant to be included multiple times, to produce different +// pieces of declaration/definition code related to diagnostic messages +// +// Each diagnostic is declared here with: +// +// DIAGNOSTIC(id, severity, name, messageFormat) +// +// Where `id` is the unique diagnostic ID, `severity` is the default +// severity (from the `Severity` enum), `name` is a name used to refer +// to this diagnostic from code, and `messageFormat` is the default +// (non-localized) message for the diagnostic, with placeholders +// for any arguments. + +#ifndef DIAGNOSTIC +#error Need to #define DIAGNOSTIC(...) before including "test-server-diagnostics-defs.h" +#define DIAGNOSTIC(id, severity, name, messageFormat) /* */ +#endif + +DIAGNOSTIC(100000, Error, unableToLoadSharedLibrary, "Unable to load shared library '$0'") +DIAGNOSTIC(100001, Error, unableToFindFunctionInSharedLibrary, "Unable to find function '$0' in shared library") +DIAGNOSTIC(100002, Error, unableToGetUnitTestModule, "Unable to get unit test module") +DIAGNOSTIC(100003, Error, unableToFindTest, "Unable to find test '$0'") + +#undef DIAGNOSTIC diff --git a/tools/test-server/test-server-diagnostics.cpp b/tools/test-server/test-server-diagnostics.cpp new file mode 100644 index 000000000..ddefc53d3 --- /dev/null +++ b/tools/test-server/test-server-diagnostics.cpp @@ -0,0 +1,13 @@ +#include "test-server-diagnostics.h" + +namespace TestServer { + +namespace ServerDiagnostics +{ +using namespace Slang; + +#define DIAGNOSTIC(id, severity, name, messageFormat) const DiagnosticInfo name = { id, Severity::severity, #name, messageFormat }; +#include "test-server-diagnostic-defs.h" +} + +} // namespace TestServer diff --git a/tools/test-server/test-server-diagnostics.h b/tools/test-server/test-server-diagnostics.h new file mode 100644 index 000000000..be816135a --- /dev/null +++ b/tools/test-server/test-server-diagnostics.h @@ -0,0 +1,21 @@ +#ifndef TEST_SERVER_DIAGNOSTICS_H +#define TEST_SERVER_DIAGNOSTICS_H + +#include "../../source/core/slang-basic.h" +#include "../../source/core/slang-writer.h" + +#include "../../source/compiler-core/slang-source-loc.h" +#include "../../source/compiler-core/slang-diagnostic-sink.h" + +namespace TestServer { +using namespace Slang; + +namespace ServerDiagnostics { + +#define DIAGNOSTIC(id, severity, name, messageFormat) extern const DiagnosticInfo name; +#include "test-server-diagnostic-defs.h" + +} // ServerDiagnostics +} // TestServer + +#endif diff --git a/tools/test-server/test-server-main.cpp b/tools/test-server/test-server-main.cpp new file mode 100644 index 000000000..7e4180441 --- /dev/null +++ b/tools/test-server/test-server-main.cpp @@ -0,0 +1,510 @@ +// test-server.cpp + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../../source/core/slang-secure-crt.h" + +#include "../../slang-com-helper.h" + +#include "../../source/core/slang-string.h" +#include "../../source/core/slang-io.h" +#include "../../source/core/slang-writer.h" +#include "../../source/core/slang-string-util.h" +#include "../../source/core/slang-process-util.h" + +#include "../../source/core/slang-shared-library.h" + +#include "../../source/core/slang-test-tool-util.h" +#include "../../source/core/slang-http.h" + +#include "../../source/compiler-core/slang-source-loc.h" +#include "../../source/compiler-core/slang-diagnostic-sink.h" + +#include "../../source/compiler-core/slang-json-parser.h" +#include "../../source/compiler-core/slang-json-rpc.h" +#include "../../source/compiler-core/slang-json-value.h" + +#include "test-server-diagnostics.h" + +#include "tools/unit-test/slang-unit-test.h" + +namespace TestServer +{ +using namespace Slang; + +class TestReporter : public ITestReporter +{ +public: + // ITestReporter + virtual SLANG_NO_THROW void SLANG_MCALL startTest(const char* testName) SLANG_OVERRIDE { } + virtual SLANG_NO_THROW void SLANG_MCALL addResult(TestResult result)SLANG_OVERRIDE; + virtual SLANG_NO_THROW void SLANG_MCALL addResultWithLocation(TestResult result, const char* testText, const char* file, int line) SLANG_OVERRIDE; + virtual SLANG_NO_THROW void SLANG_MCALL addResultWithLocation(bool testSucceeded, const char* testText, const char* file, int line) SLANG_OVERRIDE; + virtual SLANG_NO_THROW void SLANG_MCALL addExecutionTime(double time) SLANG_OVERRIDE { } + virtual SLANG_NO_THROW void SLANG_MCALL message(TestMessageType type, const char* message) SLANG_OVERRIDE; + virtual SLANG_NO_THROW void SLANG_MCALL endTest() SLANG_OVERRIDE { } + + StringBuilder m_buf; + Index m_failCount = 0; + Index m_testCount = 0; +}; + +class TestServer +{ +public: + typedef Slang::TestToolUtil::InnerMainFunc InnerMainFunc; + + SlangResult init(int argc, const char* const* argv); + + /// Can return nullptr if cannot create the session + slang::IGlobalSession* getGlobalSession(); + + /// Can return nullptr if cannot load the tool + ISlangSharedLibrary* loadSharedLibrary(const String& name, DiagnosticSink* sink = nullptr); + + /// Get a unit test module. Returns nullptr if not found. + IUnitTestModule* getUnitTestModule(const String& name, DiagnosticSink* sink = nullptr); + + /// Given a tool name return it's function pointer. Or nullptr on failure. + InnerMainFunc getToolFunction(const String& name, DiagnosticSink* sink = nullptr); + + /// Execute the server + SlangResult execute(); + + /// Dtor + ~TestServer(); + +protected: + SlangResult _executeSingle(); + SlangResult _executeUnitTest(JSONContainer* container, const JSONValue& root); + SlangResult _executeTool(JSONContainer* container, const JSONValue& root); + SlangResult _writeResponse(JSONContainer* containers, const JSONValue& root); + + bool m_quit = false; + + ComPtr<slang::IGlobalSession> m_session; + + Dictionary<String, ComPtr<ISlangSharedLibrary>> m_sharedLibraryMap; ///< Maps tool names to the dll + Dictionary<String, IUnitTestModule*> m_unitTestModules; + + String m_exePath; ///< Path to executable + + DiagnosticSink m_diagnosticSink; + SourceManager m_sourceManager; + + RefPtr<HTTPPacketConnection> m_connection; +}; + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +SlangResult TestServer::init(int argc, const char* const* argv) +{ + m_exePath = Path::getParentDirectory(argv[0]); + + RefPtr<Stream> stdinStream, stdoutStream; + + Process::getStdStream(Process::StreamType::StdIn, stdinStream); + Process::getStdStream(Process::StreamType::StdOut, stdoutStream); + + RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream)); + + m_connection = new HTTPPacketConnection(readStream, stdoutStream); + + m_sourceManager.initialize(nullptr, nullptr); + m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); + + return SLANG_OK; +} + +TestServer::~TestServer() +{ + for (auto& pair : m_unitTestModules) + { + pair.Value->destroy(); + } +} + +slang::IGlobalSession* TestServer::getGlobalSession() +{ + if (!m_session) + { + // Just create the global session in the regular way if there isn't one set + if (SLANG_FAILED(slang_createGlobalSession(SLANG_API_VERSION, m_session.writeRef()))) + { + return nullptr; + } + TestToolUtil::setSessionDefaultPreludeFromExePath(m_exePath.getBuffer(), m_session); + } + + return m_session; +} + +ISlangSharedLibrary* TestServer::loadSharedLibrary(const String& name, DiagnosticSink* sink) +{ + ComPtr<ISlangSharedLibrary> lib; + if (m_sharedLibraryMap.TryGetValue(name, lib)) + { + return lib; + } + + auto loader = DefaultSharedLibraryLoader::getSingleton(); + + auto toolPath = Path::combine(m_exePath, name); + + ComPtr<ISlangSharedLibrary> sharedLibrary; + if (SLANG_FAILED(loader->loadSharedLibrary(toolPath.getBuffer(), sharedLibrary.writeRef()))) + { + if (sink) + { + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToLoadSharedLibrary, name); + } + + return nullptr; + } + + m_sharedLibraryMap.Add(name, sharedLibrary); + return sharedLibrary; +} + +IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSink* sink) +{ + auto unitTestModulePtr = m_unitTestModules.TryGetValue(name); + if (unitTestModulePtr) + { + return *unitTestModulePtr; + } + + ISlangSharedLibrary* sharedLibrary = loadSharedLibrary(name, sink); + if (!sharedLibrary) + { + return nullptr; + } + + UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("slangUnitTestGetModule"); + + // get the unit test export name + UnitTestGetModuleFunc getModuleFunc = (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName.begin()); + if (!getModuleFunc) + { + if (sink) + { + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindFunctionInSharedLibrary, funcName); + } + return nullptr; + } + + IUnitTestModule* testModule = getModuleFunc(); + if (!testModule) + { + if (sink) + { + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToGetUnitTestModule); + } + return nullptr; + } + + m_unitTestModules.Add(name, testModule); + return testModule; +} + +TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, DiagnosticSink* sink) +{ + StringBuilder sharedLibToolBuilder; + sharedLibToolBuilder.append(name); + sharedLibToolBuilder.append("-tool"); + + ISlangSharedLibrary* sharedLibrary = loadSharedLibrary(sharedLibToolBuilder, sink); + if (!sharedLibrary) + { + return nullptr; + } + + UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("innerMain"); + + auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName.begin()); + if (!func && sink) + { + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindFunctionInSharedLibrary, funcName); + } + + return func; +} + +SlangResult TestServer::_writeResponse(JSONContainer* container, const JSONValue& root) +{ + // TODO(JS): We may want a non indented style, to reduce size + JSONWriter writer(JSONWriter::IndentationStyle::Allman); + container->traverseRecursively(root, &writer); + const StringBuilder& builder = writer.getBuilder(); + return m_connection->write(builder.getBuffer(), builder.getLength()); +} + +SlangResult TestServer::_executeSingle() +{ + // Block waiting for content (or error/closed) + SLANG_RETURN_ON_FAIL(m_connection->waitForResult()); + + // If we don't have content, we can quit for now + if (!m_connection->hasContent()) + { + return SLANG_OK; + } + + auto content = m_connection->getContent(); + + UnownedStringSlice slice((const char*)content.begin(), content.getCount()); + + // Reset for parse + m_sourceManager.reset(); + m_diagnosticSink.reset(); + + JSONContainer container(&m_sourceManager); + + // Parse as RPC JSON + JSONValue root; + + { + SlangResult res = JSONRPCUtil::parseJSON(slice, &container, &m_diagnosticSink, root); + // Consume that content/packet + m_connection->consumeContent(); + + if (SLANG_FAILED(res)) + { + return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, JSONRPCUtil::ErrorCode::InvalidRequest, UnownedStringSlice::fromLiteral("Unable to parse JSON"))); + } + } + + JSONRPCUtil::Call call; + { + SlangResult res = JSONRPCUtil::parseCall(&container, root, call); + if (SLANG_FAILED(res)) + { + return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, Index(JSONRPCUtil::ErrorCode::InvalidRequest), UnownedStringSlice::fromLiteral("Cannot parse call"))); + } + } + + const auto& method = call.method; + + // Do different things + if (method == "quit") + { + m_quit = true; + return SLANG_OK; + } + else if (method == "unitTest") + { + SLANG_RETURN_ON_FAIL(_executeUnitTest(&container, root)); + return SLANG_OK; + } + else if (method == "tool") + { + SLANG_RETURN_ON_FAIL(_executeTool(&container, root)); + return SLANG_OK; + } + + return SLANG_FAIL; +} + +static Index _findTestIndex(IUnitTestModule* testModule, const String& name) +{ + const auto testCount = testModule->getTestCount(); + for (SlangInt i = 0; i < testCount; ++i) + { + auto testName = testModule->getTestName(i); + + if (name == testName) + { + return Index(i); + } + } + return -1; +} + +SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONValue& root) +{ + String moduleName; + String testName; + Int enabledApis = 0; + + IUnitTestModule* testModule = getUnitTestModule(moduleName, &m_diagnosticSink); + if (!testModule) + { + return SLANG_FAIL; + } + + Index testIndex = _findTestIndex(testModule, moduleName); + if (testIndex < 0) + { + m_diagnosticSink.diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, testName); + return SLANG_FAIL; + } + + TestReporter testReporter; + + testModule->setTestReporter(&testReporter); + + // Assume we will used the shared session + slang::IGlobalSession* session = getGlobalSession(); + if (!session) + { + return SLANG_FAIL; + } + + UnitTestContext unitTestContext; + unitTestContext.slangGlobalSession = session; + unitTestContext.workDirectory = ""; + unitTestContext.enabledApis = RenderApiFlags(enabledApis); + unitTestContext.executableDirectory = m_exePath.getBuffer(); + + auto testCount = testModule->getTestCount(); + SLANG_ASSERT(testIndex >= 0 && testIndex < testCount); + + UnitTestFunc testFunc = testModule->getTestFunc(testIndex); + + try + { + testFunc(&unitTestContext); + } + catch (...) + { + testReporter.m_failCount++; + } + + if (testReporter.m_failCount > 0) + { + // Write out to stderr... + auto writers = StdWriters::createDefault(); + writers->getError().put(testReporter.m_buf.getUnownedSlice()); + return SLANG_FAIL; + } + + if (testReporter.m_testCount == 0) + { + return SLANG_E_NOT_AVAILABLE; + } + + return SLANG_OK; +} + +SlangResult TestServer::_executeTool(JSONContainer* container, const JSONValue& root) +{ + String toolName; + + auto func = getToolFunction(toolName, &m_diagnosticSink); + if (!func) + { + // Write out to diagnostics + return SLANG_FAIL; + } + + // Assume we will used the shared session + slang::IGlobalSession* session = getGlobalSession(); + if (!session) + { + return SLANG_FAIL; + } + + // Get the args list + + // Work out the args sent to the shared library + List<const char*> args; + + + RefPtr<StdWriters> stdWriters = StdWriters::createDefault(); + + const SlangResult res = func(stdWriters, session, int(args.getCount()), args.begin()); + if (SLANG_FAILED(res)) + { + return res; + } + return res; +} + + +SlangResult TestServer::execute() +{ + DiagnosticSink diagnosticSink; + + while (m_connection->isActive() && !m_quit) + { + SlangResult res = _executeSingle(); + if (m_quit) + { + break; + } + + if (SLANG_FAILED(res)) + { + // Return a result + } + } + + return SLANG_OK; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TestReporter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +void TestReporter::message(TestMessageType type, const char* message) +{ + if (type == TestMessageType::RunError || + type == TestMessageType::TestFailure) + { + m_failCount++; + } + + m_buf << message << "\n"; +} + +void TestReporter::addResultWithLocation(TestResult result, const char* testText, const char* file, int line) +{ + if (result == TestResult::Fail) + { + addResultWithLocation(false, testText, file, line); + } + else + { + m_testCount++; + } +} + +void TestReporter::addResultWithLocation(bool testSucceeded, const char* testText, const char* file, int line) +{ + m_testCount++; + + if (testSucceeded) + { + return; + } + + m_buf << "[Failed]: " << testText << "\n"; + m_buf << file << ":" << line << "\n"; + + m_failCount++; +} + +void TestReporter::addResult(TestResult result) +{ + if (result == TestResult::Fail) + { + m_failCount++; + } +} + + +SlangResult _execute(int argc, const char* const* argv) +{ + TestServer server; + SLANG_RETURN_ON_FAIL(server.init(argc, argv)); + SLANG_RETURN_ON_FAIL(server.execute()); + + return SLANG_OK; +} + +} // namespace TestServer + +int main(int argc, const char* const* argv) +{ + return (int)Slang::TestToolUtil::getReturnCode(TestServer:: _execute(argc, argv)); +} |
