diff --git a/Directory.Build.targets b/Directory.Build.targets
index 749f92a9..3bd7f0ac 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,19 +1,37 @@
+
+
+
+ clang-debug
+ clang-release
+ $(SolutionRoot)cmake_build/$(NativeCMakePreset)
+ <_NativeCMakeOutputDir>$([System.String]::Copy('$(NativeBuildDir)').Replace('\', '/'))
+
+
+
+
+
+
+ Condition="'$(DisablePostBuildCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
- <_OutputFiles Include="$(OutputPath)**\*.*"
- Exclude="$(OutputPath)**\*.user" />
+
+ <_OutputFiles Include="$(TargetDir)**\*.*"
+ Exclude="$(TargetDir)**\*.user;$(TargetDir)**\$(NativeLibName).*;$(TargetDir)**\lib$(NativeLibName).*;$(TargetDir)**\StgSharp.Native.*;$(TargetDir)**\libStgSharp.Native.*" />
+ BeforeTargets="CopyOutputToBuildDir"
+ Condition="'$(DisableNativeCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
+
+
@@ -45,12 +67,12 @@
value)
{
- fixed (GMatrix44* __value = &value)
- {
- __context->glUniformMatrix4fv(location, 1, transpose ? (byte)1 : (byte)0, (float*)__value);
- }
+ __context->glUniformMatrix4fv(location, 1, transpose ? (byte)1 : (byte)0, (float*)&value);
}
+
///
/// Forwards to the native OpenGL entry point.
diff --git a/src/Nitload.Graphics/OpenGL/Generated/OpenGLFunction.v4_1.g.cs b/src/Nitload.Graphics/OpenGL/Generated/OpenGLFunction.v4_1.g.cs
index 512d07be..997c6b9d 100644
--- a/src/Nitload.Graphics/OpenGL/Generated/OpenGLFunction.v4_1.g.cs
+++ b/src/Nitload.Graphics/OpenGL/Generated/OpenGLFunction.v4_1.g.cs
@@ -633,11 +633,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ProgramUniform(GlHandle program, int location, bool transpose, GMatrix44 value)
{
- fixed (GMatrix44* __value = &value)
- {
- __context->glProgramUniformMatrix4fv(program.Value, location, 1, transpose ? (byte)1 : (byte)0, (float*)__value);
+ __context->glProgramUniformMatrix4fv(program.Value, location, 1, transpose ? (byte)1 : (byte)0, (float*)&value);
}
- }
+
///
/// Forwards to the native OpenGL entry point.
diff --git a/src/Nitload.Graphics/OpenGL/glRenderStream.cs b/src/Nitload.Graphics/OpenGL/glRenderStream.cs
index c2ab1c90..0f505671 100644
--- a/src/Nitload.Graphics/OpenGL/glRenderStream.cs
+++ b/src/Nitload.Graphics/OpenGL/glRenderStream.cs
@@ -42,10 +42,11 @@ namespace Nitload.Graphics.OpenGL
{
ContextHandle = Marshal.AllocHGlobal(Marshal.SizeOf());
OpenglContext* contextID = (OpenglContext*)ContextHandle;
- contextID->glGetString = (delegate*)IntPtr.Zero;
+ contextID->glGetString = (delegate* unmanaged[Cdecl])IntPtr.Zero;
}
- if (GL == null) {
- _context = OpenGLFunction.BuildGlFunctionPackage(ContextHandle);
+ if (GL == null)
+ {
+ // _context = OpenGLFunction.BuildGlFunctionPackage(ContextHandle);
}
}
@@ -60,7 +61,8 @@ namespace Nitload.Graphics.OpenGL
public unsafe void MakeAsCurrentContext()
{
GraphicFramework.glfwMakeContextCurrent(CanvasHandle);
- OpenGLFunction.CurrentGL = this.GL;
+
+ // OpenGLFunction.CurrentGL = this.GL;
}
///
@@ -73,7 +75,8 @@ namespace Nitload.Graphics.OpenGL
SwapBuffers();
renderToThread[this] = ThreadHelper.EmptyThread;
GraphicFramework.glfwMakeContextCurrent(IntPtr.Zero);
- OpenGLFunction.CurrentGL = null;
+
+ // OpenGLFunction.CurrentGL = null;
}
///
@@ -121,7 +124,7 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected sealed override void NativeCameraViewRange(
Radius fieldOfRange,
- Vec2 offset,
+ Vec2 offset,
(float frontDepth, float backDepth) viewDepth
)
{
@@ -130,9 +133,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected sealed override void NativeCameraViewTarget(
- Vec3 position,
- Vec3 direction,
- Vec3 up
+ Vec3 position,
+ Vec3 direction,
+ Vec3 up
)
{
NativeCamera.SetViewDirection(position, direction, up);
diff --git a/src/Nitload.Graphics/PlainGeometryMesh.cs b/src/Nitload.Graphics/PlainGeometryMesh.cs
index c2ebf751..213c9974 100644
--- a/src/Nitload.Graphics/PlainGeometryMesh.cs
+++ b/src/Nitload.Graphics/PlainGeometryMesh.cs
@@ -16,14 +16,15 @@ namespace Nitload.Graphics
public class PlainGeometryMesh
{
- private readonly PlainGeometry _shape;
- private readonly Vec2[] _textureCoordinates;
+ private readonly Vec2[] _textureCoordinates;
private readonly Image _textureImage;
+ private readonly PlainGeometry _shape;
+
public PlainGeometryMesh(
PlainGeometry shape,
Image texture,
- Vec2[] texCoord
+ Vec2[] texCoord
)
{
ArgumentNullException.ThrowIfNull(shape);
@@ -40,14 +41,13 @@ namespace Nitload.Graphics
_textureCoordinates = texCoord;
}
- public Vec4[] VertexArray => _shape.VertexStream;
+ public Vec3[] VertexArray => _shape.VertexStream;
public Image TextureImage => _textureImage;
public ReadOnlySpan MeshIndices => _shape.VertexIndices;
- public ReadOnlySpan TextureCoord => MemoryMarshal.Cast(
- _textureCoordinates);
+ public ReadOnlySpan> TextureCoord => _textureCoordinates;
public PlainGeometryMesh Resize(
PlainGeometry newShape
diff --git a/src/Nitload.Graphics/RenderStream.cs b/src/Nitload.Graphics/RenderStream.cs
index 5aec9383..ae515e43 100644
--- a/src/Nitload.Graphics/RenderStream.cs
+++ b/src/Nitload.Graphics/RenderStream.cs
@@ -5,10 +5,10 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
+using Nitload.Common.Timing;
using Nitload.Mathematics;
using Nitload.Mathematics.Numeric.Graphics;
using Nitload.Mathematics.Numeric.Graphics;
-using Nitload.Common.Timing;
using System;
using System.Collections.Generic;
@@ -123,14 +123,14 @@ namespace Nitload.Graphics
protected abstract void NativeCameraViewRange(
Radius fieldOfRange,
- Vec2 offset,
+ Vec2 offset,
(float frontDepth, float backDepth) viewDepth
);
protected abstract void NativeCameraViewTarget(
- Vec3 position,
- Vec3 direction,
- Vec3 up
+ Vec3 position,
+ Vec3 direction,
+ Vec3 up
);
#endregion
diff --git a/src/Nitload.Mathematics/Numeric/Graphics/GMatrix44_T.cs b/src/Nitload.Mathematics/Numeric/Graphics/GMatrix44_T.cs
index 4824e1e2..9db36e62 100644
--- a/src/Nitload.Mathematics/Numeric/Graphics/GMatrix44_T.cs
+++ b/src/Nitload.Mathematics/Numeric/Graphics/GMatrix44_T.cs
@@ -24,12 +24,11 @@ namespace Nitload.Mathematics.Numeric.Graphics
private Buffer _buffer;
- public Vec4 this[
- int x
+ internal unsafe ref Vec4 this[
+ int x
]
{
- readonly get => _buffer[x];
- set => _buffer[x] = value;
+ get => ref Unsafe.Add(ref Unsafe.As, Vec4>(ref this), x);
}
public T this[
@@ -178,6 +177,75 @@ namespace Nitload.Mathematics.Numeric.Graphics
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GMatrix44 operator -(
+ GMatrix44 left,
+ GMatrix44 right
+ )
+ {
+ Unsafe.SkipInit(out GMatrix44 result);
+ result._buffer[0] = left._buffer[0] - right._buffer[0];
+ result._buffer[1] = left._buffer[1] - right._buffer[1];
+ result._buffer[2] = left._buffer[2] - right._buffer[2];
+ result._buffer[3] = left._buffer[3] - right._buffer[3];
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GMatrix44 operator *(
+ GMatrix44 left,
+ T right
+ )
+ {
+ Unsafe.SkipInit(out GMatrix44 result);
+ result._buffer[0] = left._buffer[0] * right;
+ result._buffer[1] = left._buffer[1] * right;
+ result._buffer[2] = left._buffer[2] * right;
+ result._buffer[3] = left._buffer[3] * right;
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GMatrix44 operator *(
+ T left,
+ GMatrix44 right
+ )
+ {
+ return right * left;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GMatrix44 operator *(
+ GMatrix44 left,
+ GMatrix44 right
+ )
+ {
+ Unsafe.SkipInit(out GMatrix44 result);
+ result._buffer[0] = left._buffer[0] + right._buffer[0];
+ result._buffer[1] = left._buffer[1] + right._buffer[1];
+ result._buffer[2] = left._buffer[2] + right._buffer[2];
+ result._buffer[3] = left._buffer[3] + right._buffer[3];
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GMatrix44 operator +(
+ GMatrix44 left,
+ GMatrix44 right
+ )
+ {
+ Unsafe.SkipInit(out GMatrix44 result);
+ result._buffer[0] = left._buffer[0] + right._buffer[0];
+ result._buffer[1] = left._buffer[1] + right._buffer[1];
+ result._buffer[2] = left._buffer[2] + right._buffer[2];
+ result._buffer[3] = left._buffer[3] + right._buffer[3];
+
+ return result;
+ }
+
[InlineArray(4)]
private struct Buffer
{
diff --git a/src/Nitload.Native/CMakeLists.txt b/src/Nitload.Native/CMakeLists.txt
index 223b0059..aeea2cc5 100644
--- a/src/Nitload.Native/CMakeLists.txt
+++ b/src/Nitload.Native/CMakeLists.txt
@@ -70,7 +70,13 @@ else()
set(NIF_ARCH_TAG ${NIF_RESOLVED_ARCH_NAME})
endif()
-set(NIF_BUILD_OUTPUT_DIR ${NIF_SOLUTION_ROOT}/bin/${NIF_OS_TAG}-${NIF_ARCH_TAG})
+set(NIF_EXPECTED_RID "" CACHE STRING "Optional managed target RID to validate before deployment")
+if(NIF_EXPECTED_RID AND NOT NIF_EXPECTED_RID STREQUAL "${NIF_OS_TAG}-${NIF_ARCH_TAG}")
+ message(FATAL_ERROR "Native target ${NIF_OS_TAG}-${NIF_ARCH_TAG} does not match managed target ${NIF_EXPECTED_RID}. Use a matching compiler/toolchain and a separate build directory.")
+endif()
+
+set(NIF_BUILD_OUTPUT_DIR "${NIF_SOLUTION_ROOT}/bin/${NIF_OS_TAG}-${NIF_ARCH_TAG}"
+ CACHE PATH "Native artifact directory shared with managed build deployment")
# Compile-time intermediates (native PDB / .obj side files) live inside the CMake
# build tree (cmake_build/) so they get cleaned up with it.
set(NIF_OBJECT_ROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/obj/${PROJECT_NAME})
@@ -86,9 +92,21 @@ source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${NIF_NATIVE_PROJECT_FILES})
set_target_properties(${PROJECT_NAME} PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
- RUNTIME_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR}
- LIBRARY_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR}
- ARCHIVE_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR})
+ OUTPUT_NAME "Nitload.Native"
+ PDB_NAME "Nitload.Native"
+ DEBUG_POSTFIX ""
+ RELEASE_POSTFIX ""
+ RELWITHDEBINFO_POSTFIX ""
+ MINSIZEREL_POSTFIX ""
+ RUNTIME_OUTPUT_DIRECTORY "${NIF_BUILD_OUTPUT_DIR}"
+ LIBRARY_OUTPUT_DIRECTORY "${NIF_BUILD_OUTPUT_DIR}"
+ ARCHIVE_OUTPUT_DIRECTORY "${NIF_BUILD_OUTPUT_DIR}"
+ PDB_OUTPUT_DIRECTORY "${NIF_BUILD_OUTPUT_DIR}")
+
+# P/Invoke loads Nitload.Native.dll on Windows, including GNU-style Clang toolchains.
+if(WIN32)
+ set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+endif()
foreach(_config Debug Release RelWithDebInfo MinSizeRel)
string(TOUPPER ${_config} _config_upper)
diff --git a/src/Nitload.Native/Nitload.Native.vcxproj b/src/Nitload.Native/Nitload.Native.vcxproj
index ec4f2119..abe5b157 100644
--- a/src/Nitload.Native/Nitload.Native.vcxproj
+++ b/src/Nitload.Native/Nitload.Native.vcxproj
@@ -85,9 +85,10 @@
<_ArchTag Condition="'$(Platform)'=='x64'">x64
<_ArchTag Condition="'$(Platform)'=='ARM64'">arm64
-
- $(SolutionDir)cmake_build\win_$(_ArchTag)\
- $(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\
+
+ Nitload.Native
+ $(NativeBuildDir)
+ $(SolutionRoot)obj\$(ProjectName)\$(Configuration)\$(Platform)\
true
diff --git a/src/Nitload.Native/README.md b/src/Nitload.Native/README.md
index 1b19e6fe..cd6e5b1f 100644
--- a/src/Nitload.Native/README.md
+++ b/src/Nitload.Native/README.md
@@ -6,6 +6,32 @@ Though this project do has a .vcxproj file,
you can only edit but not compile in visul studio.
Currently you can only compile this lib via Clang and cmake.
+## Build output and managed deployment
+
+From the repository root:
+
+```powershell
+cmake --preset clang-debug -S src/Nitload.Native
+cmake --build cmake_build/clang-debug --target Nitload.Native
+```
+
+CMake writes `Nitload.Native.dll` and its linker PDB to `bin/win-x64` on Windows x64
+(Linux uses `bin/linux-x64/libNitload.Native.so`). Debug and release presets currently
+share this platform directory; the most recent native build is the deployed version.
+`NIF_BUILD_OUTPUT_DIR` can override the destination. Managed builds pass their expected
+RID as `NIF_EXPECTED_RID` so an incompatible native target is rejected before deployment.
+
+`StgSharpDebug.csproj` enables `BuildNativeBeforeManaged` and `RequireNativeLibrary`.
+Its build runs the incremental CMake build (`clang-debug` for Debug, `clang-release`
+otherwise), then deploys the native library to the executable's actual `TargetDir`.
+Other managed projects copy an existing native artifact without invoking CMake.
+Managed-output staging never copies deployed native files back over the CMake artifact.
+
+To use a prebuilt native artifact, pass `-p:BuildNativeBeforeManaged=false`. A matching
+Clang/Ninja environment and the local GLFW static library are required for native builds.
+Cross-target builds require a matching toolchain/preset; changing a managed platform alone
+does not cross-compile the C library.
+
## Code Layout
Conventions:
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
index 26da0a18..39047e19 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenClass.cs
@@ -39,13 +39,13 @@ namespace Nitload.RegularAnalysis.Text
GenerateConcat(root, sc, func_list, context, exit_label);
break;
case RegexElementLabel.COUNT:
- GenerateCount(root, sc, func_list, context);
+ GenerateCount(root, sc, func_list, context, exit_label);
break;
case RegexElementLabel.GROUP_BEGIN:
- GenerateGroup(root, sc, func_list, context);
+ GenerateGroup(root, sc, func_list, context, exit_label);
break;
case RegexElementLabel.ALT:
- GenerateAlt(root, sc, func_list, context);
+ GenerateAlt(root, sc, func_list, context, exit_label);
break;
default:
@@ -167,17 +167,20 @@ namespace Nitload.RegularAnalysis.Text
)
{
RegexCharSetPayload payload = value.PayloadAs();
- string[] rules = new string[payload.Set.Count];
- for (int i = 0; i < rules.Length; i++) {
- rules[i] = GenerateCharSetExpression(payload.Set[i]);
- }
-
// Combine rule-level complements before applying the enclosing charset complement.
// An empty union accepts nothing; its complement accepts any available character.
- string predicate = rules.Length == 0 ? "false" : string.Join(" || ", rules);
- string mismatch = payload.Accept ? $"!({predicate})" : $"({predicate})";
string remain_span = context.RemainSpan;
- _ = sc.AppendLine($"if ({remain_span}.IsEmpty || {mismatch})")
+ _ = sc.AppendLine($"if ({remain_span}.IsEmpty ||")
+ .AppendLine(payload.Accept ? " !(" : " (");
+ if (payload.Set.Count == 0) {
+ _ = sc.AppendLine(" false");
+ }
+ for (int i = 0; i < payload.Set.Count; i++)
+ {
+ string suffix = i + 1 < payload.Set.Count ? " ||" : string.Empty;
+ _ = sc.AppendLine($" {GenerateCharSetExpression(payload.Set[i])}{suffix}");
+ }
+ _ = sc.AppendLine(" ))")
.AppendLine("{")
.AppendLine($"goto {exit.GetLabelName()};")
.AppendLine("}")
@@ -244,9 +247,6 @@ namespace Nitload.RegularAnalysis.Text
string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}";
string additional_count = $"__additional_count_{scratch_scope_index}";
- // TODO(NGRA): Replace this local COUNT exit with the inherited failure ExitLabel.
- // The temporary goto preserves the former local exit but still lets siblings execute.
-
if (min <= flattened_unit_count_threshold)
{
if (min > 0)
@@ -428,9 +428,6 @@ namespace Nitload.RegularAnalysis.Text
string additional_count = $"__additional_count_{scratch_scope_index}";
string starts_with = $"{nameof(MemoryExtensions.StartsWith)}";
- // TODO(NGRA): Replace this local COUNT exit with the inherited failure ExitLabel.
- // The temporary goto preserves the former local exit but still lets siblings execute.
-
if (literal.Length > flattened_unit_count_threshold)
{
// One repetition is already larger than the flattening threshold. Match the
@@ -551,93 +548,28 @@ namespace Nitload.RegularAnalysis.Text
RegexAstNode node,
[NotNull] in SequenceEmitter sc,
in List> func_list,
- in SourceGenContext context
+ in SourceGenContext context,
+ in ExitLabel exit_label
)
{
+ ExitLabel success = context.RequestExitLabel(ExitMode.Goto);
RegexAstNode current = node.Right;
-
- // TODO(NGRA-NAMING): Reserve exclusive names for future ALT cursor/capture snapshots;
- // their lifetime extends through retry labels, not just the candidate's emitted block.
- ExitLabel exit_label = context.RequestExitLabel(ExitMode.Goto);
-
- // TODO(NGRA): Walk every normalized ALT candidate exactly once. The final traversal
- // shape and failure continuation are not wired yet.
- while (RegexAstNode.IsNullOrEmpty(current))
+ while (!RegexAstNode.IsNullOrEmpty(current))
{
- RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE;
- if (op != 0)
- {
- string code = current.SourceCode;
- switch (op)
- {
- case RegexElementLabel.UNIT:
-
- // TODO(NGRA): Guard against an empty remaining span before indexing it.
- _ = sc.AppendLine(@$"if({context.RemainSpan}[0] == {FormatCharLiteral(code[0])})")
- .AppendLine("{")
- .AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
- .AppendLine($@"goto {exit_label};")
- .AppendLine("}");
- break;
- case RegexElementLabel.UNIT_SET:
-
- // TODO(NGRA): Guard the remaining span, handle empty/Any sets, and
- // validate negated-set composition before emitting this predicate.
- RegexCharSetPayload payload = current.PayloadAs();
- IReadOnlyList split = payload.Set;
- bool accept = payload.Accept;
- RegexCharSet _set = split[0];
- _ = sc.AppendLine($"if({(accept ? string.Empty : "!(")} {GenerateCharSetExpression(_set)} ||");
- for (int i = 1; i < split.Count - 1; i++)
- {
- _set = split[i];
- _ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ||");
- }
- _set = split[^1];
- _ = sc.AppendLine($" {GenerateCharSetExpression(_set)} ){(accept ? string.Empty : ')')}")
- .AppendLine("{")
- .AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
- .AppendLine($@"goto {exit_label};")
- .AppendLine("}");
- break;
- case RegexElementLabel.UNIT_SPAN:
-
- // TODO(NGRA): Advance by code.Length rather than one character and
- // preserve the ALT rollback cursor on failure.
- string literal = FormatStringLiteral(code);
- _ = sc.AppendLine(@$"if({context.RemainSpan}.{MemoryExtensions.StartsWith}({literal}))")
- .AppendLine("{")
- .AppendLine($@"{context.RemainSpan} = {context.RemainSpan}.Slice({code.Length});")
- .AppendLine($@"goto {exit_label};")
- .AppendLine("}");
-
- break;
- default:
-
- // TODO(NGRA): Route unsupported ALT candidates through the recursive
- // matcher or report a source-generation diagnostic.
- break;
- }
-
- // TODO(NGRA): Advance to the next ALT candidate after a scalar candidate
- // misses; the current sequence branch does not update current.
- } else
- {
- GenerateMethodSource(current, sc, func_list, context);
- current = current.Next;
- }
+ GenerateAltCandidate(current, sc, func_list, context, success);
+ current = current.Next;
}
- // TODO(NGRA): Emit the all-alternatives-failed continuation before the shared success
- // label, including restoration of the entry cursor and capture state.
- _ = sc.AppendLine($@"{exit_label}: {{ }}");
+ _ = sc.AppendLine($"goto {exit_label.GetLabelName()};")
+ .AppendLine($"{success.GetLabelName()}: ;");
}
private static void GenerateGroup(
RegexAstNode node,
[NotNull] in SequenceEmitter sc,
in List> func_list,
- in SourceGenContext context
+ in SourceGenContext context,
+ in ExitLabel exit_label
)
{
RegexAstNode left = node.Left;
@@ -648,14 +580,13 @@ namespace Nitload.RegularAnalysis.Text
// TODO(NGRA-NAMING): Reserve exclusive names for generated group snapshots and capture
// temporaries. A capture's user-provided name is not a unique generated local name.
- // TODO(NGRA): Select the owned group body using Empty-sentinel checks, snapshot its
- // start cursor, and restore it when the child matcher fails.
- if (left is not null)
+ // TODO(NGRA): Snapshot the group start cursor and restore it when the child fails.
+ if (!RegexAstNode.IsNullOrEmpty(left))
{
- GenerateMethodSource(left, sc, func_list, context);
+ GenerateMethodSource(left, sc, func_list, context, exit_label);
} else
- if (right is not null) {
- GenerateMethodSource(right, sc, func_list, context);
+ if (!RegexAstNode.IsNullOrEmpty(right)) {
+ GenerateMethodSource(right, sc, func_list, context, exit_label);
}
string group_name = config.Name;
@@ -685,7 +616,8 @@ namespace Nitload.RegularAnalysis.Text
RegexAstNode node,
[NotNull] in SequenceEmitter sc,
in List> func_list,
- in SourceGenContext context
+ in SourceGenContext context,
+ in ExitLabel exit_label
)
{
RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left;
@@ -699,12 +631,11 @@ namespace Nitload.RegularAnalysis.Text
// TODO(NGRA-NAMING): Reserve exclusive names for future repetition/rollback state
// across complex COUNT retries; recursive children must use the same method pool.
- ExitLabel exit_label = context.RequestExitLabel(ExitMode.Goto);
if ((value.Label & RegexElementLabel.SEQUENCE) == 0)
{
// TODO(NGRA): Emit min/max repetition, zero-width progress protection, greedy vs.
// lazy ordering, and per-attempt rollback for a complex COUNT body.
- GenerateMethodSource(value, sc, func_list, context);
+ GenerateMethodSource(value, sc, func_list, context, exit_label);
} else
{
switch (value.Label & RegexElementLabel.SEQUENCE)
@@ -726,9 +657,6 @@ namespace Nitload.RegularAnalysis.Text
}
}
- // TODO(NGRA): Connect COUNT success/failure continuations to the surrounding rollback
- // scope; the label is currently only a placeholder.
- _ = sc.AppendLine($@"{exit_label}: {{ }}");
}
#endregion
diff --git a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
index 1fbeb591..9deefd61 100644
--- a/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
+++ b/src/Nitload.RegularAnalysis/Text/TextRegexSourceGen.GenSpecial.cs
@@ -1,6 +1,6 @@
// -----------------------------------------------------------------------------
// file="TextRegexSourceGen.GenSpecial"
-// Project: Nitload
+// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
@@ -24,11 +24,162 @@ namespace Nitload.RegularAnalysis.Text
// to the general path; returning true prevents that subtree from being emitted twice.
return node.Label switch
{
- RegexElementLabel.CONCAT => TryGenerateFindConcat(node, sc, func_list, context, exit_label),
+ RegexElementLabel.CONCAT => TryGenerateFindConcat(node, sc, func_list, context,
+ exit_label),
_ => false,
};
}
+ #region ALT candidate rules
+
+ private static bool TryGetSimpleAltNode(
+ RegexAstNode node,
+ out RegexAstNode simple
+ )
+ {
+ simple = node;
+ while (simple.Label == RegexElementLabel.CONCAT)
+ {
+ bool left_empty = RegexAstNode.IsNullOrEmpty(simple.Left);
+ bool right_empty = RegexAstNode.IsNullOrEmpty(simple.Right);
+ if (left_empty == right_empty) {
+ return false;
+ }
+ simple = left_empty ? simple.Right : simple.Left;
+ }
+
+ // Inspect transparent wrappers without changing ownership or the candidate Next link.
+ // COUNT and GROUP are never peeled: repetition and capture semantics must survive.
+ return simple.Label is RegexElementLabel.UNIT or RegexElementLabel.UNIT_SET or
+ RegexElementLabel.UNIT_SPAN;
+ }
+
+ private static void GenerateAltCandidate(
+ RegexAstNode node,
+ in SequenceEmitter sc,
+ in List> func_list,
+ in SourceGenContext context,
+ in ExitLabel success
+ )
+ {
+ if (TryGetSimpleAltNode(node, out RegexAstNode simple)) {
+ node = simple;
+ }
+
+ switch (node.Label)
+ {
+ case RegexElementLabel.UNIT:
+ GenerateAltUnit(node, sc, context, success);
+ break;
+ case RegexElementLabel.UNIT_SET:
+ GenerateAltUnitSet(node, sc, context, success);
+ break;
+ case RegexElementLabel.UNIT_SPAN:
+ GenerateAltUnitSpan(node, sc, context, success);
+ break;
+ case RegexElementLabel.CONCAT when RegexAstNode.IsNullOrEmpty(node.Left) &&
+ RegexAstNode.IsNullOrEmpty(node.Right):
+ case RegexElementLabel.EPSILON:
+
+ // An empty expression succeeds without consuming input.
+ _ = sc.AppendLine($"goto {success.GetLabelName()};");
+ break;
+ case RegexElementLabel.CONCAT:
+ case RegexElementLabel.COUNT:
+ case RegexElementLabel.GROUP_BEGIN:
+ case RegexElementLabel.ALT:
+ GenerateAltOperator(node, sc, func_list, context, success);
+ break;
+ default:
+ throw new NotSupportedException($"Unsupported ALT candidate: {node.Label}.");
+ }
+ }
+
+ private static void GenerateAltUnit(
+ RegexAstNode node,
+ in SequenceEmitter sc,
+ in SourceGenContext context,
+ in ExitLabel success
+ )
+ {
+ string literal = node.Value;
+ if (literal.Length != 1) {
+ throw new InvalidOperationException("ALT UNIT requires one decoded character.");
+ }
+
+ _ = sc.AppendLine($"if (!{context.RemainSpan}.IsEmpty && {context.RemainSpan}[0] == {FormatCharLiteral(literal[0])})")
+ .AppendLine("{")
+ .AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
+ .AppendLine($"goto {success.GetLabelName()};")
+ .AppendLine("}");
+ }
+
+ private static void GenerateAltUnitSet(
+ RegexAstNode node,
+ in SequenceEmitter sc,
+ in SourceGenContext context,
+ in ExitLabel success
+ )
+ {
+ RegexCharSetPayload payload = node.PayloadAs();
+ _ = sc.AppendLine($"if (!{context.RemainSpan}.IsEmpty &&")
+ .AppendLine(payload.Accept ? " (" : " !(");
+ if (payload.Set.Count == 0) {
+ _ = sc.AppendLine(" false");
+ }
+ for (int i = 0; i < payload.Set.Count; i++)
+ {
+ string suffix = i + 1 < payload.Set.Count ? " ||" : string.Empty;
+ _ = sc.AppendLine($" {GenerateCharSetExpression(payload.Set[i])}{suffix}");
+ }
+ _ = sc.AppendLine(" ))")
+ .AppendLine("{")
+ .AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice(1);")
+ .AppendLine($"goto {success.GetLabelName()};")
+ .AppendLine("}");
+ }
+
+ private static void GenerateAltUnitSpan(
+ RegexAstNode node,
+ in SequenceEmitter sc,
+ in SourceGenContext context,
+ in ExitLabel success
+ )
+ {
+ string literal = node.Value;
+ if (literal.Length == 0) {
+ throw new InvalidOperationException("ALT UNIT_SPAN requires a non-empty literal.");
+ }
+ string predicate = $"{context.RemainSpan}.{nameof(MemoryExtensions.StartsWith)}({FormatStringLiteral(literal)})";
+ _ = sc.AppendLine($"if ({predicate})")
+ .AppendLine("{")
+ .AppendLine($"{context.RemainSpan} = {context.RemainSpan}.Slice({literal.Length});")
+ .AppendLine($"goto {success.GetLabelName()};")
+ .AppendLine("}");
+ }
+
+ private static void GenerateAltOperator(
+ RegexAstNode node,
+ in SequenceEmitter sc,
+ in List> func_list,
+ in SourceGenContext context,
+ in ExitLabel success
+ )
+ {
+ ExitLabel failure = context.RequestExitLabel(ExitMode.Goto);
+ string entry = $"__alt_entry_{context.RequestScratchScopeIndex()}";
+ _ = sc.AppendLine($"{ROS_char} {entry} = {context.RemainSpan};");
+ GenerateMethodSource(node, sc, func_list, context, failure);
+ _ = sc.AppendLine($"goto {success.GetLabelName()};")
+ .AppendLine($"{failure.GetLabelName()}:")
+ .AppendLine($"{context.RemainSpan} = {entry};");
+
+ // TODO(NGRA): Restore capture state once GROUP emission supports captures.
+ // Retrying an already successful candidate after a later continuation fails is separate.
+ }
+
+ #endregion
+
#region FIND concatenation
private static bool TryGenerateFindConcat(
@@ -78,7 +229,8 @@ namespace Nitload.RegularAnalysis.Text
{
// (?.*)abc
- if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT) {
+ if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT)
+ {
return false;
}
RegexAstNode group = node.Left;
@@ -106,6 +258,5 @@ namespace Nitload.RegularAnalysis.Text
// An escaped star is a literal and must not be classified as dot-star repetition.
#endregion
-
}
}
diff --git a/src/README.md b/src/README.md
index c18375f8..2960c206 100644
--- a/src/README.md
+++ b/src/README.md
@@ -28,4 +28,4 @@ cmake --build cmake_build/clang-release
dotnet build Nitload.sln
```
-Managed builds copy an existing native binary when one is available; they do not invoke CMake automatically. Generated OpenGL files under `Nitload.Graphics/OpenGL/Generated` should be updated through the generator in `tools/Nitload.GenerateGL`, not edited by hand.
+Managed library builds copy an existing native binary when one is available; they do not invoke CMake automatically. The `test/StgSharpDebug` executable explicitly enables the CMake build before managed compilation. Generated OpenGL files under `Nitload.Graphics/OpenGL/Generated` should be updated through the generator in `tools/Nitload.GenerateGL`, not edited by hand.
diff --git a/test/README.md b/test/README.md
index fa1aa32c..167dcd3e 100644
--- a/test/README.md
+++ b/test/README.md
@@ -16,4 +16,36 @@ Run the active debug harness directly when investigating a subsystem:
dotnet run --project test/StgSharpDebug/StgSharpDebug.csproj
```
+## NGRA generation preview
+
+`StgSharpDebug/TestRegexGenerate.cs` contains a standalone debugging routine that calls the
+existing NGRA emitter without Roslyn attribute binding. Select it from the debug `Main`:
+
+```powershell
+dotnet run --project test/StgSharpDebug/StgSharpDebug.csproj -- --regex-preview
+```
+
+This branch returns before `World` or native-memory initialization. Without the switch, the
+existing debug flow is unchanged. You can also call `TestRegexGenerate.Run()` directly from
+`Main` while debugging; pass a pattern to `Run("abc")` for a single preview.
+The switch only selects runtime behavior; building the debug project still builds its normal
+project references, including Graphics and Mathematics, and prepares the native library with
+CMake. `BuildNativeBeforeManaged` defaults to true; Debug selects `clang-debug`, and other
+configurations select `clang-release`. `RequireNativeLibrary` reports a missing DLL at build
+time instead of allowing the default runtime path to fail later.
+
+For managed-only preview work without a native toolchain or DLL, add
+`-p:BuildNativeBeforeManaged=false -p:RequireNativeLibrary=false` before `--` in the run command.
+
+Enter raw regular expressions without C# string delimiters. The routine prints only the emitted
+code, without status headers or file output. Exceptions propagate directly to the caller/debugger.
+Use `:exit` or end-of-input to leave the loop. Empty input is an empty regex. For one-shot mode:
+
+```powershell
+dotnet run --project test/StgSharpDebug/StgSharpDebug.csproj -- --regex-preview 'a{2,3}'
+```
+
+Set breakpoints in `TestRegexGenerate.cs` or any existing `Generate*` method. The output is a
+method-body fragment; this routine does not compile or execute it.
+
New repeatable tests should use a test project and avoid machine-specific paths, external mutable data, and dependencies on previous test execution. Keep one-off investigations in `SingleFile` or the manual debug harness until their expected behavior is stable enough for automation.
diff --git a/test/StgSharpDebug/Program.cs b/test/StgSharpDebug/Program.cs
index 0160e0e6..0ff354d0 100644
--- a/test/StgSharpDebug/Program.cs
+++ b/test/StgSharpDebug/Program.cs
@@ -5,13 +5,13 @@
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
-using Nitload.Graphics;
using Nitload.Common.HighPerformance.Memory;
using Nitload.Common.Internal;
+using Nitload.Common.Timing;
+using Nitload.Common.Timing;
+using Nitload.Graphics;
using Nitload.Mathematics;
using Nitload.Mathematics.Numeric;
-using Nitload.Common.Timing;
-using Nitload.Common.Timing;
using System;
using System.Runtime.InteropServices;
@@ -28,6 +28,13 @@ namespace Nitload
string[] args
)
{
+ // Select the managed-only emitter preview before initializing the normal runtime.
+ if (args.Length > 0 && args[0] == "--regex-preview")
+ {
+ Environment.ExitCode = TestRegexGenerate.Run(args[1..]);
+ return;
+ }
+
try
{
World.Initialize(new()
@@ -39,13 +46,8 @@ namespace Nitload
// .UseModule()
.InitializeModule();
- _ = SlabAllocator.FromBuffer(
- (nuint)NativeMemory.AlignedAlloc(65536 * 4, 4),
- 65536,
- buffer => NativeMemory.AlignedFree((void*)buffer)
- );
-
- TestRegexAnalyse.Run();
+ // TestRegexAnalyse.Run();
+ _ = TestRegexGenerate.Run("abc|123");
// TestHlsf();
diff --git a/test/StgSharpDebug/StgSharpDebug.csproj b/test/StgSharpDebug/StgSharpDebug.csproj
index 3cd98ede..70048fe6 100644
--- a/test/StgSharpDebug/StgSharpDebug.csproj
+++ b/test/StgSharpDebug/StgSharpDebug.csproj
@@ -2,6 +2,10 @@
Exe
+ true
+ true
+
+ true
@@ -9,6 +13,11 @@
+
+
+
+
+
@@ -18,4 +27,3 @@
-
diff --git a/test/StgSharpDebug/TestRegexGenerate.cs b/test/StgSharpDebug/TestRegexGenerate.cs
new file mode 100644
index 00000000..6a53a4a1
--- /dev/null
+++ b/test/StgSharpDebug/TestRegexGenerate.cs
@@ -0,0 +1,65 @@
+// -----------------------------------------------------------------------------
+// file="TestRegexGenerate"
+// Project: Nitload
+// Copyright (c) Nitload.
+// SPDX-License-Identifier: MIT
+// -----------------------------------------------------------------------------
+
+using Nitload.RegularAnalysis.Abstraction;
+using Nitload.RegularAnalysis.Text;
+using System;
+using System.Collections.Generic;
+using System.Runtime.ExceptionServices;
+using static Nitload.RegularAnalysis.Text.TextRegexSourceGen;
+
+namespace Nitload
+{
+ internal static class TestRegexGenerate
+ {
+ public static int Run(params string[] args)
+ {
+ if (args.Length > 1) {
+ throw new ArgumentException("Expected at most one regex pattern.", nameof(args));
+ }
+ if (args.Length == 1)
+ {
+ Print(args[0]);
+ return 0;
+ }
+
+ while (true)
+ {
+ Console.Write("regex> ");
+ string? pattern = Console.ReadLine();
+ if (pattern is null or ":exit") {
+ return 0;
+ }
+ Print(pattern);
+ }
+ }
+
+ private static void Print(string pattern)
+ {
+ TextRegexSource analysis = RegexAnalyzer.Analyze(pattern);
+ if (analysis.AnalyseException is not null) {
+ ExceptionDispatchInfo.Throw(analysis.AnalyseException);
+ }
+
+ SequenceEmitter body = new();
+ List> functions = [];
+ SourceGenContext context = new();
+ ExitLabel exit = context.RequestExitLabel(ExitMode.Goto);
+ GenerateMethodSource(analysis.Ast.Root, body, functions, context, exit);
+
+ foreach (string line in body.ToList()) {
+ Console.WriteLine(line);
+ }
+ foreach (SequenceEmitter function in functions)
+ {
+ foreach (string line in function.ToList()) {
+ Console.WriteLine(line);
+ }
+ }
+ }
+ }
+}