diff --git a/Cesium.Compiler/stdlib/ctype.h b/Cesium.Compiler/stdlib/ctype.h new file mode 100644 index 00000000..57194c6f --- /dev/null +++ b/Cesium.Compiler/stdlib/ctype.h @@ -0,0 +1,4 @@ +#pragma once + +__cli_import("Cesium.Runtime.CTypeFunctions::IsSpace") +int isspace(int ch); diff --git a/Cesium.IntegrationTests/stdlib/string/byte/ctype.c b/Cesium.IntegrationTests/stdlib/string/byte/ctype.c new file mode 100644 index 00000000..65b76385 --- /dev/null +++ b/Cesium.IntegrationTests/stdlib/string/byte/ctype.c @@ -0,0 +1,12 @@ +#include +#include +#include + +int main(void) +{ + for (int ndx = 0; ndx <= UCHAR_MAX; ndx++) + if (isspace(ndx)) + printf("0x%02x ", ndx); + + return 42; +} diff --git a/Cesium.Runtime.Tests/StdIoFunctionTests.cs b/Cesium.Runtime.Tests/StdIoFunctionTests.cs new file mode 100644 index 00000000..25ff7ce7 --- /dev/null +++ b/Cesium.Runtime.Tests/StdIoFunctionTests.cs @@ -0,0 +1,40 @@ +using System.Text; + +namespace Cesium.Runtime.Tests; + +public class StdIoFunctionTests +{ + [Theory] + [InlineData(9, "0x09")] + [InlineData(32, "0x20")] + public unsafe void FPrintFHex(long input, string expectedResult) + { + var format = Encoding.UTF8.GetBytes("0x%02x"); + + using var buffer = new MemoryStream(); + var handleIndex = StdIoFunctions.Handles.Count; + try + { + using var writer = new StreamWriter(buffer); + var handle = new StdIoFunctions.StreamHandle + { + FileMode = "w", + // ReSharper disable once AccessToDisposedClosure + Writer = () => writer + }; + + StdIoFunctions.Handles.Add(handle); + fixed (byte* formatPtr = format) + { + Assert.Equal(4, StdIoFunctions.FPrintF((void*)handleIndex, formatPtr, &input)); + } + } + finally + { + StdIoFunctions.Handles.RemoveAt(handleIndex); + } + + var result = Encoding.UTF8.GetString(buffer.ToArray()); + Assert.Equal(expectedResult, result); + } +} diff --git a/Cesium.Runtime/CTypeFunctions.cs b/Cesium.Runtime/CTypeFunctions.cs new file mode 100644 index 00000000..6cafaedc --- /dev/null +++ b/Cesium.Runtime/CTypeFunctions.cs @@ -0,0 +1,21 @@ +namespace Cesium.Runtime; + +/// +/// Functions declared in the ctype.h +/// +public unsafe static class CTypeFunctions +{ + public static int IsSpace(int value) + { + return value switch + { + 0x20 => 1, + 0x0c => 1, + 0x0a => 1, + 0x0d => 1, + 0x09 => 1, + 0x0b => 1, + _ => 0, + }; + } +} diff --git a/Cesium.Runtime/Cesium.Runtime.csproj b/Cesium.Runtime/Cesium.Runtime.csproj index 8d34f79c..d8fe30c4 100644 --- a/Cesium.Runtime/Cesium.Runtime.csproj +++ b/Cesium.Runtime/Cesium.Runtime.csproj @@ -8,4 +8,8 @@ latest + + + + diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index b4e0d4d4..4d752560 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,4 +1,3 @@ -using System.IO; using System.Runtime.InteropServices; #if NETSTANDARD using System.Text; @@ -11,14 +10,14 @@ namespace Cesium.Runtime; /// public unsafe static class StdIoFunctions { - record class StreamHandle + internal record StreamHandle { public required string FileMode { get; set; } public Func? Reader { get; set; } public Func? Writer { get; set; } } - private static List handles = new(); + internal static List Handles = new(); private const int StdIn = 0; @@ -28,17 +27,17 @@ record class StreamHandle static StdIoFunctions() { - handles.Add(new StreamHandle() + Handles.Add(new StreamHandle() { FileMode = "r", Reader = () => Console.In, }); - handles.Add(new StreamHandle() + Handles.Add(new StreamHandle() { FileMode = "w", Writer = () => Console.Out, }); - handles.Add(new StreamHandle() + Handles.Add(new StreamHandle() { FileMode = "w", Writer = () => Console.Error, @@ -129,6 +128,18 @@ public static int FPrintF(void* stream, byte* str, void* varargs) streamWriter.Write(formatString.Substring(currentPosition, lengthTillPercent)); consumedBytes += lengthTillPercent; int addition = 1; + int width = 0; + if (formatString[formatStartPosition + addition] == '0') + { + addition++; + } + + while (formatString[formatStartPosition + addition] >= '0' && formatString[formatStartPosition + addition] <= '9') + { + width = width * 10 + (formatString[formatStartPosition + addition] - '0'); + addition++; + } + string formatSpecifier = formatString[formatStartPosition + addition].ToString(); if (formatString[formatStartPosition + addition] == 'l') { @@ -180,6 +191,19 @@ public static int FPrintF(void* stream, byte* str, void* varargs) consumedBytes += pointerValueString.Length; consumedArgs++; break; + case "x": + case "X": + nuint hexadecimalValue = ((nuint*)varargs)[consumedArgs]; + if (hexadecimalValue != 0) + { + var targetFormat = "{0:" + formatSpecifier + (width == 0 ? "" : width) + "}"; + // NOTE: without converting nuint to long, this was broken on .NET Framework + var hexadecimalValueString = string.Format(targetFormat, (long)hexadecimalValue); + streamWriter.Write(hexadecimalValueString); + consumedBytes += hexadecimalValueString.Length; + consumedArgs++; + } + break; case "%": streamWriter.Write('%'); consumedBytes += 1; @@ -225,7 +249,7 @@ public static int FPrintF(void* stream, byte* str, void* varargs) private static StreamHandle? GetStreamHandle(void* stream) { var handleIndex = (int)(IntPtr)stream; - var result = handles.ElementAtOrDefault(handleIndex); + var result = Handles.ElementAtOrDefault(handleIndex); return result; } }