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
@@ -1785,11 +1785,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// <para>Forwards to the native OpenGL entry point.</para>
@@ -633,11 +633,9 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// <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>());
OpenglContext* contextID = (OpenglContext*)ContextHandle;
contextID->glGetString = (delegate*<uint,byte*>)IntPtr.Zero;
contextID->glGetString = (delegate* unmanaged[Cdecl]<uint,byte*>)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;
}
/// <inheritdoc />
@@ -73,7 +75,8 @@ namespace Nitload.Graphics.OpenGL
SwapBuffers();
renderToThread[this] = ThreadHelper.EmptyThread;
GraphicFramework.glfwMakeContextCurrent(IntPtr.Zero);
OpenGLFunction.CurrentGL = null;
// OpenGLFunction.CurrentGL = null;
}
/// <inheritdoc />
@@ -121,7 +124,7 @@ namespace Nitload.Graphics.OpenGL
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected sealed override void NativeCameraViewRange(
Radius fieldOfRange,
Vec2 offset,
Vec2<float> 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<float> position,
Vec3<float> direction,
Vec3<float> up
)
{
NativeCamera.SetViewDirection(position, direction, up);
+6 -6
View File
@@ -16,14 +16,15 @@ namespace Nitload.Graphics
public class PlainGeometryMesh
{
private readonly PlainGeometry _shape;
private readonly Vec2[] _textureCoordinates;
private readonly Vec2<float>[] _textureCoordinates;
private readonly Image<Rgba8> _textureImage;
private readonly PlainGeometry _shape;
public PlainGeometryMesh(
PlainGeometry shape,
Image<Rgba8> texture,
Vec2[] texCoord
Vec2<float>[] texCoord
)
{
ArgumentNullException.ThrowIfNull(shape);
@@ -40,14 +41,13 @@ namespace Nitload.Graphics
_textureCoordinates = texCoord;
}
public Vec4[] VertexArray => _shape.VertexStream;
public Vec3<float>[] VertexArray => _shape.VertexStream;
public Image<Rgba8> TextureImage => _textureImage;
public ReadOnlySpan<int> MeshIndices => _shape.VertexIndices;
public ReadOnlySpan<Vec4> TextureCoord => MemoryMarshal.Cast<Vec2, Vec4>(
_textureCoordinates);
public ReadOnlySpan<Vec2<float>> TextureCoord => _textureCoordinates;
public PlainGeometryMesh Resize(
PlainGeometry newShape
+5 -5
View File
@@ -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<float> offset,
(float frontDepth, float backDepth) viewDepth
);
protected abstract void NativeCameraViewTarget(
Vec3 position,
Vec3 direction,
Vec3 up
Vec3<float> position,
Vec3<float> direction,
Vec3<float> up
);
#endregion
@@ -24,12 +24,11 @@ namespace Nitload.Mathematics.Numeric.Graphics
private Buffer _buffer;
public Vec4<T> this[
int x
internal unsafe ref Vec4<T> this[
int x
]
{
readonly get => _buffer[x];
set => _buffer[x] = value;
get => ref Unsafe.Add(ref Unsafe.As<GMatrix44<T>, Vec4<T>>(ref this), x);
}
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)]
private struct Buffer
{
+22 -4
View File
@@ -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/<preset>) 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)
+4 -3
View File
@@ -85,9 +85,10 @@
<!-- 把 ARM64 → arm64,x64 保持 x64 -->
<_ArchTag Condition="'$(Platform)'=='x64'">x64</_ArchTag>
<_ArchTag Condition="'$(Platform)'=='ARM64'">arm64</_ArchTag>
<!-- 输出到 cmake_build/win_<arch>/,与 CMake 目录约定保持一致 -->
<OutDir>$(SolutionDir)cmake_build\win_$(_ArchTag)\</OutDir>
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
<!-- Keep the navigation project's artifact paths aligned with CMake and managed deployment. -->
<TargetName>Nitload.Native</TargetName>
<OutDir>$(NativeBuildDir)</OutDir>
<IntDir>$(SolutionRoot)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<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.
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:
@@ -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<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.
// 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<string> sc,
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;
// 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<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;
}
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<string> sc,
in List<SequenceEmitter<string>> 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<string> sc,
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;
@@ -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
@@ -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<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
private static bool TryGenerateFindConcat(
@@ -78,7 +229,8 @@ namespace Nitload.RegularAnalysis.Text
{
// (?<name>.*)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
}
}
+1 -1
View File
@@ -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.