process cases of ALT as special rules

This commit is contained in:
Youmu Liu
2026-09-10 18:17:09 +08:00
parent 8755185e78
commit f372f83171
18 changed files with 486 additions and 166 deletions
+28 -6
View File
@@ -1,19 +1,37 @@
<Project> <Project>
<!-- Native builds are opt-in for executable hosts, not repeated by every library project. -->
<Target Name="BuildNativeLibrary"
BeforeTargets="PrepareForBuild"
Condition="'$(BuildNativeBeforeManaged)'=='true' And '$(DesignTimeBuild)'!='true'">
<PropertyGroup>
<NativeCMakePreset Condition="'$(NativeCMakePreset)'=='' And '$(Configuration)'=='Debug'">clang-debug</NativeCMakePreset>
<NativeCMakePreset Condition="'$(NativeCMakePreset)'==''">clang-release</NativeCMakePreset>
<NativeCMakeBuildDir Condition="'$(NativeCMakeBuildDir)'==''">$(SolutionRoot)cmake_build/$(NativeCMakePreset)</NativeCMakeBuildDir>
<_NativeCMakeOutputDir>$([System.String]::Copy('$(NativeBuildDir)').Replace('\', '/'))</_NativeCMakeOutputDir>
</PropertyGroup>
<Exec Command="cmake --preset &quot;$(NativeCMakePreset)&quot; -S &quot;$(SolutionRoot)src/Nitload.Native&quot; -B &quot;$(NativeCMakeBuildDir)&quot; &quot;-DNIF_BUILD_OUTPUT_DIR:PATH=$(_NativeCMakeOutputDir)&quot; &quot;-DNIF_EXPECTED_RID:STRING=$(BuildRid)&quot;" />
<Exec Command="cmake --build &quot;$(NativeCMakeBuildDir)&quot; --target &quot;$(NativeLibName)&quot;" />
<Error Condition="!Exists('$(NativeBuildDir)$(NativeFileName)')"
Text="CMake did not produce the expected native library: $(NativeBuildDir)$(NativeFileName). Check the target OS, architecture and output name." />
</Target>
<!-- ════════════════════════════════════════════════════════ --> <!-- ════════════════════════════════════════════════════════ -->
<!-- 编译后:把 C# 输出拷贝到 bin/<rid>/ 目录 --> <!-- 编译后:把 C# 输出拷贝到 bin/<rid>/ 目录 -->
<!-- 替代原来的 xcopy PostBuild --> <!-- 替代原来的 xcopy PostBuild -->
<!-- ════════════════════════════════════════════════════════ --> <!-- ════════════════════════════════════════════════════════ -->
<Target Name="CopyOutputToBuildDir" <Target Name="CopyOutputToBuildDir"
AfterTargets="Build" AfterTargets="Build"
Condition="'$(DisablePostBuildCopy)'!='true'"> Condition="'$(DisablePostBuildCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
<MakeDir Directories="$(ManagedBuildDir)" /> <MakeDir Directories="$(ManagedBuildDir)" />
<!-- 拷贝所有编译输出(DLL + PDB + XML + deps.json) --> <!-- 拷贝所有编译输出(DLL + PDB + XML + deps.json) -->
<ItemGroup> <ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*.*" <!-- Native artifacts flow from CMake to hosts only. Never copy an old deployed DLL
Exclude="$(OutputPath)**\*.user" /> back over the authoritative CMake output, including pre-rename artifacts. -->
<_OutputFiles Include="$(TargetDir)**\*.*"
Exclude="$(TargetDir)**\*.user;$(TargetDir)**\$(NativeLibName).*;$(TargetDir)**\lib$(NativeLibName).*;$(TargetDir)**\StgSharp.Native.*;$(TargetDir)**\libStgSharp.Native.*" />
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(_OutputFiles)" <Copy SourceFiles="@(_OutputFiles)"
@@ -30,7 +48,11 @@
<!-- ════════════════════════════════════════════════════════ --> <!-- ════════════════════════════════════════════════════════ -->
<Target Name="CopyNativeToProjectOutput" <Target Name="CopyNativeToProjectOutput"
AfterTargets="Build" AfterTargets="Build"
Condition="'$(DisableNativeCopy)'!='true'"> BeforeTargets="CopyOutputToBuildDir"
Condition="'$(DisableNativeCopy)'!='true' And '$(MSBuildProjectExtension)'=='.csproj' And '$(DesignTimeBuild)'!='true'">
<Error Text="Native library not found at $(NativeBuildDir)$(NativeFileName). Build Nitload.Native with CMake, or enable BuildNativeBeforeManaged."
Condition="'$(RequireNativeLibrary)'=='true' And !Exists('$(NativeBuildDir)$(NativeFileName)')" />
<!-- native 库文件 --> <!-- native 库文件 -->
<ItemGroup> <ItemGroup>
@@ -45,12 +67,12 @@
</ItemGroup> </ItemGroup>
<Copy SourceFiles="@(_NativeLibFile);@(_NativeSymFile)" <Copy SourceFiles="@(_NativeLibFile);@(_NativeSymFile)"
DestinationFolder="$(OutputPath)" DestinationFolder="$(TargetDir)"
SkipUnchangedFiles="true" SkipUnchangedFiles="true"
Condition="'@(_NativeLibFile)'!='' Or '@(_NativeSymFile)'!=''" /> Condition="'@(_NativeLibFile)'!='' Or '@(_NativeSymFile)'!=''" />
<Message Importance="high" <Message Importance="high"
Text="[Nitload] Native $(NativeFileName) ($(BuildRid)) → $(OutputPath)" Text="[Nitload] Native $(NativeFileName) ($(BuildRid)) → $(TargetDir)"
Condition="'@(_NativeLibFile)'!=''" /> Condition="'@(_NativeLibFile)'!=''" />
<Warning Text="[Nitload] Native library not found at $(NativeBuildDir)$(NativeFileName). Build Nitload.Native first!" <Warning Text="[Nitload] Native library not found at $(NativeBuildDir)$(NativeFileName). Build Nitload.Native first!"
+1 -1
View File
@@ -161,7 +161,7 @@ Nitload is currently in development and requires compilation from source. **NuGe
dotnet build Nitload.sln dotnet build Nitload.sln
``` ```
Managed builds copy an existing native binary into their output directory. They do not invoke CMake automatically, so build `Nitload.Native` first when native functionality is required. Managed library builds copy an existing native binary into their output directory. Build `Nitload.Native` first when native functionality is required. The `StgSharpDebug` executable is an exception: its project enables an incremental CMake build before managed compilation and requires the native DLL for the normal runtime path.
### Build Configuration ### Build Configuration
@@ -1785,11 +1785,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Uniform(int location, bool transpose, GMatrix44<float> value) public void Uniform(int location, bool transpose, GMatrix44<float> value)
{ {
fixed (GMatrix44<float>* __value = &value) __context->glUniformMatrix4fv(location, 1, transpose ? (byte)1 : (byte)0, (float*)&value);
{
__context->glUniformMatrix4fv(location, 1, transpose ? (byte)1 : (byte)0, (float*)__value);
}
} }
/// <summary> /// <summary>
/// <para>Forwards to the native OpenGL entry point.</para> /// <para>Forwards to the native OpenGL entry point.</para>
@@ -633,11 +633,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ProgramUniform(GlHandle program, int location, bool transpose, GMatrix44<float> value) public void ProgramUniform(GlHandle program, int location, bool transpose, GMatrix44<float> value)
{ {
fixed (GMatrix44<float>* __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);
} }
}
/// <summary> /// <summary>
/// <para>Forwards to the native OpenGL entry point.</para> /// <para>Forwards to the native OpenGL entry point.</para>
+12 -9
View File
@@ -42,10 +42,11 @@ namespace Nitload.Graphics.OpenGL
{ {
ContextHandle = Marshal.AllocHGlobal(Marshal.SizeOf<OpenglContext>()); ContextHandle = Marshal.AllocHGlobal(Marshal.SizeOf<OpenglContext>());
OpenglContext* contextID = (OpenglContext*)ContextHandle; OpenglContext* contextID = (OpenglContext*)ContextHandle;
contextID->glGetString = (delegate*<uint,byte*>)IntPtr.Zero; contextID->glGetString = (delegate* unmanaged[Cdecl]<uint,byte*>)IntPtr.Zero;
} }
if (GL == null) { if (GL == null)
_context = OpenGLFunction.BuildGlFunctionPackage(ContextHandle); {
// _context = OpenGLFunction.BuildGlFunctionPackage(ContextHandle);
} }
} }
@@ -60,7 +61,8 @@ namespace Nitload.Graphics.OpenGL
public unsafe void MakeAsCurrentContext() public unsafe void MakeAsCurrentContext()
{ {
GraphicFramework.glfwMakeContextCurrent(CanvasHandle); GraphicFramework.glfwMakeContextCurrent(CanvasHandle);
OpenGLFunction.CurrentGL = this.GL;
// OpenGLFunction.CurrentGL = this.GL;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -73,7 +75,8 @@ namespace Nitload.Graphics.OpenGL
SwapBuffers(); SwapBuffers();
renderToThread[this] = ThreadHelper.EmptyThread; renderToThread[this] = ThreadHelper.EmptyThread;
GraphicFramework.glfwMakeContextCurrent(IntPtr.Zero); GraphicFramework.glfwMakeContextCurrent(IntPtr.Zero);
OpenGLFunction.CurrentGL = null;
// OpenGLFunction.CurrentGL = null;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -121,7 +124,7 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected sealed override void NativeCameraViewRange( protected sealed override void NativeCameraViewRange(
Radius fieldOfRange, Radius fieldOfRange,
Vec2 offset, Vec2<float> offset,
(float frontDepth, float backDepth) viewDepth (float frontDepth, float backDepth) viewDepth
) )
{ {
@@ -130,9 +133,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected sealed override void NativeCameraViewTarget( protected sealed override void NativeCameraViewTarget(
Vec3 position, Vec3<float> position,
Vec3 direction, Vec3<float> direction,
Vec3 up Vec3<float> up
) )
{ {
NativeCamera.SetViewDirection(position, direction, up); NativeCamera.SetViewDirection(position, direction, up);
+6 -6
View File
@@ -16,14 +16,15 @@ namespace Nitload.Graphics
public class PlainGeometryMesh public class PlainGeometryMesh
{ {
private readonly PlainGeometry _shape; private readonly Vec2<float>[] _textureCoordinates;
private readonly Vec2[] _textureCoordinates;
private readonly Image<Rgba8> _textureImage; private readonly Image<Rgba8> _textureImage;
private readonly PlainGeometry _shape;
public PlainGeometryMesh( public PlainGeometryMesh(
PlainGeometry shape, PlainGeometry shape,
Image<Rgba8> texture, Image<Rgba8> texture,
Vec2[] texCoord Vec2<float>[] texCoord
) )
{ {
ArgumentNullException.ThrowIfNull(shape); ArgumentNullException.ThrowIfNull(shape);
@@ -40,14 +41,13 @@ namespace Nitload.Graphics
_textureCoordinates = texCoord; _textureCoordinates = texCoord;
} }
public Vec4[] VertexArray => _shape.VertexStream; public Vec3<float>[] VertexArray => _shape.VertexStream;
public Image<Rgba8> TextureImage => _textureImage; public Image<Rgba8> TextureImage => _textureImage;
public ReadOnlySpan<int> MeshIndices => _shape.VertexIndices; public ReadOnlySpan<int> MeshIndices => _shape.VertexIndices;
public ReadOnlySpan<Vec4> TextureCoord => MemoryMarshal.Cast<Vec2, Vec4>( public ReadOnlySpan<Vec2<float>> TextureCoord => _textureCoordinates;
_textureCoordinates);
public PlainGeometryMesh Resize( public PlainGeometryMesh Resize(
PlainGeometry newShape PlainGeometry newShape
+5 -5
View File
@@ -5,10 +5,10 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
using Nitload.Common.Timing;
using Nitload.Mathematics; using Nitload.Mathematics;
using Nitload.Mathematics.Numeric.Graphics; using Nitload.Mathematics.Numeric.Graphics;
using Nitload.Mathematics.Numeric.Graphics; using Nitload.Mathematics.Numeric.Graphics;
using Nitload.Common.Timing;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -123,14 +123,14 @@ namespace Nitload.Graphics
protected abstract void NativeCameraViewRange( protected abstract void NativeCameraViewRange(
Radius fieldOfRange, Radius fieldOfRange,
Vec2 offset, Vec2<float> offset,
(float frontDepth, float backDepth) viewDepth (float frontDepth, float backDepth) viewDepth
); );
protected abstract void NativeCameraViewTarget( protected abstract void NativeCameraViewTarget(
Vec3 position, Vec3<float> position,
Vec3 direction, Vec3<float> direction,
Vec3 up Vec3<float> up
); );
#endregion #endregion
@@ -24,12 +24,11 @@ namespace Nitload.Mathematics.Numeric.Graphics
private Buffer _buffer; private Buffer _buffer;
public Vec4<T> this[ internal unsafe ref Vec4<T> this[
int x int x
] ]
{ {
readonly get => _buffer[x]; get => ref Unsafe.Add(ref Unsafe.As<GMatrix44<T>, Vec4<T>>(ref this), x);
set => _buffer[x] = value;
} }
public T this[ public T this[
@@ -178,6 +177,75 @@ namespace Nitload.Mathematics.Numeric.Graphics
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GMatrix44<T> operator -(
GMatrix44<T> left,
GMatrix44<T> right
)
{
Unsafe.SkipInit(out GMatrix44<T> 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<T> operator *(
GMatrix44<T> left,
T right
)
{
Unsafe.SkipInit(out GMatrix44<T> 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<T> operator *(
T left,
GMatrix44<T> right
)
{
return right * left;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GMatrix44<T> operator *(
GMatrix44<T> left,
GMatrix44<T> right
)
{
Unsafe.SkipInit(out GMatrix44<T> 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<T> operator +(
GMatrix44<T> left,
GMatrix44<T> right
)
{
Unsafe.SkipInit(out GMatrix44<T> 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)] [InlineArray(4)]
private struct Buffer private struct Buffer
{ {
+22 -4
View File
@@ -70,7 +70,13 @@ else()
set(NIF_ARCH_TAG ${NIF_RESOLVED_ARCH_NAME}) set(NIF_ARCH_TAG ${NIF_RESOLVED_ARCH_NAME})
endif() 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 # Compile-time intermediates (native PDB / .obj side files) live inside the CMake
# build tree (cmake_build/<preset>) so they get cleaned up with it. # build tree (cmake_build/<preset>) so they get cleaned up with it.
set(NIF_OBJECT_ROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/obj/${PROJECT_NAME}) 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 set_target_properties(${PROJECT_NAME} PROPERTIES
C_STANDARD 11 C_STANDARD 11
C_STANDARD_REQUIRED ON C_STANDARD_REQUIRED ON
RUNTIME_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR} OUTPUT_NAME "Nitload.Native"
LIBRARY_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR} PDB_NAME "Nitload.Native"
ARCHIVE_OUTPUT_DIRECTORY ${NIF_BUILD_OUTPUT_DIR}) 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) foreach(_config Debug Release RelWithDebInfo MinSizeRel)
string(TOUPPER ${_config} _config_upper) string(TOUPPER ${_config} _config_upper)
+4 -3
View File
@@ -85,9 +85,10 @@
<!-- 把 ARM64 → arm64,x64 保持 x64 --> <!-- 把 ARM64 → arm64,x64 保持 x64 -->
<_ArchTag Condition="'$(Platform)'=='x64'">x64</_ArchTag> <_ArchTag Condition="'$(Platform)'=='x64'">x64</_ArchTag>
<_ArchTag Condition="'$(Platform)'=='ARM64'">arm64</_ArchTag> <_ArchTag Condition="'$(Platform)'=='ARM64'">arm64</_ArchTag>
<!-- 输出到 cmake_build/win_<arch>/,与 CMake 目录约定保持一致 --> <!-- Keep the navigation project's artifact paths aligned with CMake and managed deployment. -->
<OutDir>$(SolutionDir)cmake_build\win_$(_ArchTag)\</OutDir> <TargetName>Nitload.Native</TargetName>
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir> <OutDir>$(NativeBuildDir)</OutDir>
<IntDir>$(SolutionRoot)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> <PropertyGroup Condition="'$(Configuration)'=='Debug'">
<RunCodeAnalysis>true</RunCodeAnalysis> <RunCodeAnalysis>true</RunCodeAnalysis>
+26
View File
@@ -6,6 +6,32 @@ Though this project do has a .vcxproj file,
you can only edit but not compile in visul studio. you can only edit but not compile in visul studio.
Currently you can only compile this lib via Clang and cmake. 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 ## Code Layout
Conventions: Conventions:
@@ -39,13 +39,13 @@ namespace Nitload.RegularAnalysis.Text
GenerateConcat(root, sc, func_list, context, exit_label); GenerateConcat(root, sc, func_list, context, exit_label);
break; break;
case RegexElementLabel.COUNT: case RegexElementLabel.COUNT:
GenerateCount(root, sc, func_list, context); GenerateCount(root, sc, func_list, context, exit_label);
break; break;
case RegexElementLabel.GROUP_BEGIN: case RegexElementLabel.GROUP_BEGIN:
GenerateGroup(root, sc, func_list, context); GenerateGroup(root, sc, func_list, context, exit_label);
break; break;
case RegexElementLabel.ALT: case RegexElementLabel.ALT:
GenerateAlt(root, sc, func_list, context); GenerateAlt(root, sc, func_list, context, exit_label);
break; break;
default: default:
@@ -167,17 +167,20 @@ namespace Nitload.RegularAnalysis.Text
) )
{ {
RegexCharSetPayload payload = value.PayloadAs<RegexCharSetPayload>(); RegexCharSetPayload payload = value.PayloadAs<RegexCharSetPayload>();
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. // Combine rule-level complements before applying the enclosing charset complement.
// An empty union accepts nothing; its complement accepts any available character. // 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; 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("{")
.AppendLine($"goto {exit.GetLabelName()};") .AppendLine($"goto {exit.GetLabelName()};")
.AppendLine("}") .AppendLine("}")
@@ -244,9 +247,6 @@ namespace Nitload.RegularAnalysis.Text
string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}"; string required_chunk_index = $"__required_chunk_index_{scratch_scope_index}";
string additional_count = $"__additional_count_{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 <= flattened_unit_count_threshold)
{ {
if (min > 0) if (min > 0)
@@ -428,9 +428,6 @@ namespace Nitload.RegularAnalysis.Text
string additional_count = $"__additional_count_{scratch_scope_index}"; string additional_count = $"__additional_count_{scratch_scope_index}";
string starts_with = $"{nameof(MemoryExtensions.StartsWith)}"; 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) if (literal.Length > flattened_unit_count_threshold)
{ {
// One repetition is already larger than the flattening threshold. Match the // One repetition is already larger than the flattening threshold. Match the
@@ -551,93 +548,28 @@ namespace Nitload.RegularAnalysis.Text
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list, in List<SequenceEmitter<string>> func_list,
in SourceGenContext context in SourceGenContext context,
in ExitLabel exit_label
) )
{ {
ExitLabel success = context.RequestExitLabel(ExitMode.Goto);
RegexAstNode current = node.Right; RegexAstNode current = node.Right;
while (!RegexAstNode.IsNullOrEmpty(current))
// 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))
{ {
RegexElementLabel op = current.Label & RegexElementLabel.SEQUENCE; GenerateAltCandidate(current, sc, func_list, context, success);
if (op != 0) current = current.Next;
{
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<RegexCharSetPayload>();
IReadOnlyList<RegexCharSet> 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;
}
} }
// TODO(NGRA): Emit the all-alternatives-failed continuation before the shared success _ = sc.AppendLine($"goto {exit_label.GetLabelName()};")
// label, including restoration of the entry cursor and capture state. .AppendLine($"{success.GetLabelName()}: ;");
_ = sc.AppendLine($@"{exit_label}: {{ }}");
} }
private static void GenerateGroup( private static void GenerateGroup(
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list, in List<SequenceEmitter<string>> func_list,
in SourceGenContext context in SourceGenContext context,
in ExitLabel exit_label
) )
{ {
RegexAstNode left = node.Left; RegexAstNode left = node.Left;
@@ -648,14 +580,13 @@ namespace Nitload.RegularAnalysis.Text
// TODO(NGRA-NAMING): Reserve exclusive names for generated group snapshots and capture // 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. // 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 // TODO(NGRA): Snapshot the group start cursor and restore it when the child fails.
// start cursor, and restore it when the child matcher fails. if (!RegexAstNode.IsNullOrEmpty(left))
if (left is not null)
{ {
GenerateMethodSource(left, sc, func_list, context); GenerateMethodSource(left, sc, func_list, context, exit_label);
} else } else
if (right is not null) { if (!RegexAstNode.IsNullOrEmpty(right)) {
GenerateMethodSource(right, sc, func_list, context); GenerateMethodSource(right, sc, func_list, context, exit_label);
} }
string group_name = config.Name; string group_name = config.Name;
@@ -685,7 +616,8 @@ namespace Nitload.RegularAnalysis.Text
RegexAstNode node, RegexAstNode node,
[NotNull] in SequenceEmitter<string> sc, [NotNull] in SequenceEmitter<string> sc,
in List<SequenceEmitter<string>> func_list, in List<SequenceEmitter<string>> func_list,
in SourceGenContext context in SourceGenContext context,
in ExitLabel exit_label
) )
{ {
RegexAstNode value = RegexAstNode.IsNullOrEmpty(node.Left) ? node.Right : node.Left; 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 // TODO(NGRA-NAMING): Reserve exclusive names for future repetition/rollback state
// across complex COUNT retries; recursive children must use the same method pool. // 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) if ((value.Label & RegexElementLabel.SEQUENCE) == 0)
{ {
// TODO(NGRA): Emit min/max repetition, zero-width progress protection, greedy vs. // TODO(NGRA): Emit min/max repetition, zero-width progress protection, greedy vs.
// lazy ordering, and per-attempt rollback for a complex COUNT body. // 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 } else
{ {
switch (value.Label & RegexElementLabel.SEQUENCE) 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 #endregion
@@ -1,6 +1,6 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// file="TextRegexSourceGen.GenSpecial" // file="TextRegexSourceGen.GenSpecial"
// Project: Nitload // Project: StgSharp
// Copyright (c) Nitload. // Copyright (c) Nitload.
// SPDX-License-Identifier: MIT // 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. // to the general path; returning true prevents that subtree from being emitted twice.
return node.Label switch 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, _ => 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<string> sc,
in List<SequenceEmitter<string>> 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<string> 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<string> sc,
in SourceGenContext context,
in ExitLabel success
)
{
RegexCharSetPayload payload = node.PayloadAs<RegexCharSetPayload>();
_ = 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<string> 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<string> sc,
in List<SequenceEmitter<string>> 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 #region FIND concatenation
private static bool TryGenerateFindConcat( private static bool TryGenerateFindConcat(
@@ -78,7 +229,8 @@ namespace Nitload.RegularAnalysis.Text
{ {
// (?<name>.*)abc // (?<name>.*)abc
if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT) { if (RegexAstNode.IsNullOrEmpty(node) || node.Label != RegexElementLabel.CONCAT)
{
return false; return false;
} }
RegexAstNode group = node.Left; 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. // An escaped star is a literal and must not be classified as dot-star repetition.
#endregion #endregion
} }
} }
+1 -1
View File
@@ -28,4 +28,4 @@ cmake --build cmake_build/clang-release
dotnet build Nitload.sln 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.
+32
View File
@@ -16,4 +16,36 @@ Run the active debug harness directly when investigating a subsystem:
dotnet run --project test/StgSharpDebug/StgSharpDebug.csproj 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. 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.
+12 -10
View File
@@ -5,13 +5,13 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
using Nitload.Graphics;
using Nitload.Common.HighPerformance.Memory; using Nitload.Common.HighPerformance.Memory;
using Nitload.Common.Internal; using Nitload.Common.Internal;
using Nitload.Common.Timing;
using Nitload.Common.Timing;
using Nitload.Graphics;
using Nitload.Mathematics; using Nitload.Mathematics;
using Nitload.Mathematics.Numeric; using Nitload.Mathematics.Numeric;
using Nitload.Common.Timing;
using Nitload.Common.Timing;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -28,6 +28,13 @@ namespace Nitload
string[] args 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 try
{ {
World.Initialize(new() World.Initialize(new()
@@ -39,13 +46,8 @@ namespace Nitload
// .UseModule<GraphicModule>() // .UseModule<GraphicModule>()
.InitializeModule(); .InitializeModule();
_ = SlabAllocator.FromBuffer<int>( // TestRegexAnalyse.Run();
(nuint)NativeMemory.AlignedAlloc(65536 * 4, 4), _ = TestRegexGenerate.Run("abc|123");
65536,
buffer => NativeMemory.AlignedFree((void*)buffer)
);
TestRegexAnalyse.Run();
// TestHlsf(); // TestHlsf();
+9 -1
View File
@@ -2,6 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<BuildNativeBeforeManaged>true</BuildNativeBeforeManaged>
<RequireNativeLibrary>true</RequireNativeLibrary>
<!-- Let MSBuild run the incremental CMake build even when managed inputs are unchanged. -->
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup> </PropertyGroup>
@@ -9,6 +13,11 @@
<None Remove="SSG_errorlog.log" /> <None Remove="SSG_errorlog.log" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- The debug host calls the emitter directly and needs Roslyn at runtime. -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Nitload.Common\Nitload.Common.csproj" /> <ProjectReference Include="..\..\src\Nitload.Common\Nitload.Common.csproj" />
<ProjectReference Include="..\..\src\Nitload.Graphics\Nitload.Graphics.csproj" /> <ProjectReference Include="..\..\src\Nitload.Graphics\Nitload.Graphics.csproj" />
@@ -18,4 +27,3 @@
</ItemGroup> </ItemGroup>
</Project> </Project>
+65
View File
@@ -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<string> body = new();
List<SequenceEmitter<string>> 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<string> function in functions)
{
foreach (string line in function.ToList()) {
Console.WriteLine(line);
}
}
}
}
}