diff --git a/.gitignore b/.gitignore index 2b47f110..64e746cb 100644 --- a/.gitignore +++ b/.gitignore @@ -493,3 +493,4 @@ src/ChibiRuby.Unity/UserSettings src/ChibiRuby.Unity/Assets/Packages/* +.tools diff --git a/src/ChibiRuby.Debugger.Dap/Makefile b/src/ChibiRuby.Debugger.Dap/Makefile new file mode 100644 index 00000000..e92d7a04 --- /dev/null +++ b/src/ChibiRuby.Debugger.Dap/Makefile @@ -0,0 +1,54 @@ +# Regenerate the DAP protocol types + UTF-8 JSON serializer from the DAP JSON Schema. +# +# make schema # fetch upstream DAP schema + merge our overlay -> Schema/dap-schema.json +# make codegen # run NoJsonSchema over dap-schema.json -> Protocol/*.g.cs +# make regen # codegen only (schema is committed; no network) +# make test # run the DAP test suite +# make all # schema + codegen + test +# +# `codegen` uses the *released* NoJsonSchema.Cli dotnet tool from NuGet (pinned below), +# NOT the local ~/dev/NoJsonSchema source. Bump NOJSONSCHEMA_VERSION to adopt a newer release. + +NOJSONSCHEMA_VERSION := 1.2.0 + +NAMESPACE := ChibiRuby.Debugger.Dap.Protocol +SCHEMA := Schema/dap-schema.json +OUT := Protocol +TOOL_DIR := .tools +NOJSONSCHEMA := $(TOOL_DIR)/nojsonschema +TEST_PROJECT := ../../tests/ChibiRuby.Debugger.Dap.Tests/ChibiRuby.Debugger.Dap.Tests.csproj + +# $defs roots to generate. Synthesized *Body / inline-enum types come along transitively. +INCLUDE_TYPES := AttachRequest,AttachRequestArguments,AttachResponse,Breakpoint,BreakpointMode,Capabilities,Checksum,ChecksumAlgorithm,ColumnDescriptor,ConfigurationDoneArguments,ConfigurationDoneRequest,ConfigurationDoneResponse,ContinueArguments,ContinueRequest,ContinueResponse,ContinuedEvent,DisconnectArguments,DisconnectRequest,DisconnectResponse,ErrorResponse,EvaluateArguments,EvaluateRequest,EvaluateResponse,Event,ExceptionBreakpointsFilter,InitializeRequest,InitializeRequestArguments,InitializeResponse,InitializedEvent,LaunchRequest,LaunchRequestArguments,LaunchResponse,Message,NextArguments,NextRequest,NextResponse,OutputEvent,PauseArguments,PauseRequest,PauseResponse,ProtocolMessage,Request,Response,Scope,ScopesArguments,ScopesRequest,ScopesResponse,SetBreakpointsArguments,SetBreakpointsRequest,SetBreakpointsResponse,Source,SourceBreakpoint,StackFrame,StackFrameFormat,StackTraceArguments,StackTraceRequest,StackTraceResponse,StepInArguments,StepInRequest,StepInResponse,StepOutArguments,StepOutRequest,StepOutResponse,SteppingGranularity,StoppedEvent,TerminateArguments,TerminateRequest,TerminateResponse,TerminatedEvent,Thread,ThreadsRequest,ThreadsResponse,ValueFormat,Variable,VariablePresentationHint,VariablesArguments,VariablesRequest,VariablesResponse + +# $defs (and synthesized bodies) emitted as `readonly partial record struct` value objects. +VALUE_OBJECTS := Checksum,ConfigurationDoneArguments,ContinueArguments,ContinueResponseBody,ContinuedEventBody,DisconnectArguments,ErrorResponseBody,NextArguments,PauseArguments,ScopesArguments,ScopesResponseBody,SetBreakpointsResponseBody,StackTraceResponseBody,StepOutArguments,TerminateArguments,TerminatedEventBody,Thread,ThreadsResponseBody,VariablesResponseBody + +.PHONY: all schema codegen regen test tool clean-tool + +all: schema codegen test + +schema: + ./Schema/build-schema.sh + +# Install the pinned released tool into a local (gitignored) tool-path, idempotently. +tool: + @if [ ! -x "$(NOJSONSCHEMA)" ] || [ "$$($(NOJSONSCHEMA) --version 2>/dev/null)" != "$(NOJSONSCHEMA_VERSION)" ]; then \ + echo "installing NoJsonSchema.Cli $(NOJSONSCHEMA_VERSION) -> $(TOOL_DIR)"; \ + rm -rf "$(TOOL_DIR)"; \ + dotnet tool install NoJsonSchema.Cli --version $(NOJSONSCHEMA_VERSION) --tool-path $(TOOL_DIR); \ + fi + +codegen regen: tool + $(NOJSONSCHEMA) generate \ + -i $(SCHEMA) \ + -o $(OUT) \ + -n $(NAMESPACE) \ + --value-object "$(VALUE_OBJECTS)" \ + --include-type "$(INCLUDE_TYPES)" + +test: + dotnet test $(TEST_PROJECT) + +clean-tool: + rm -rf $(TOOL_DIR) diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/BreakpointModeFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/BreakpointModeFormatter.g.cs index 550b4a3b..2108f1a6 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/BreakpointModeFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/BreakpointModeFormatter.g.cs @@ -60,12 +60,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, BreakpointMode va { tokenizer.ReadStartArray(); - var list_AppliesTo = new global::System.Collections.Generic.List(); + var buf_AppliesTo = global::System.Array.Empty(); + var count_AppliesTo = 0; while (!tokenizer.TryReadEndArray()) { - list_AppliesTo.Add(tokenizer.ReadString()); + if (count_AppliesTo == buf_AppliesTo.Length) global::System.Array.Resize(ref buf_AppliesTo, buf_AppliesTo.Length == 0 ? 4 : buf_AppliesTo.Length * 2); + buf_AppliesTo[count_AppliesTo++] = tokenizer.ReadString(); } - value.AppliesTo = list_AppliesTo.ToArray(); + if (count_AppliesTo != buf_AppliesTo.Length) global::System.Array.Resize(ref buf_AppliesTo, count_AppliesTo); + value.AppliesTo = buf_AppliesTo; } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/CapabilitiesFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/CapabilitiesFormatter.g.cs index d9ec06b7..a86a5f44 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/CapabilitiesFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/CapabilitiesFormatter.g.cs @@ -79,15 +79,18 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Capabilities valu { tokenizer.ReadStartArray(); - var list_BreakpointModes = new global::System.Collections.Generic.List(); + var buf_BreakpointModes = global::System.Array.Empty(); + var count_BreakpointModes = 0; while (!tokenizer.TryReadEndArray()) { + if (count_BreakpointModes == buf_BreakpointModes.Length) global::System.Array.Resize(ref buf_BreakpointModes, buf_BreakpointModes.Length == 0 ? 4 : buf_BreakpointModes.Length * 2); tokenizer.ReadStartObject(); var elem = new BreakpointMode(); BreakpointModeFormatter.ReadInto(ref tokenizer, elem, options); - list_BreakpointModes.Add(elem); + buf_BreakpointModes[count_BreakpointModes++] = elem; } - value.BreakpointModes = list_BreakpointModes.ToArray(); + if (count_BreakpointModes != buf_BreakpointModes.Length) global::System.Array.Resize(ref buf_BreakpointModes, count_BreakpointModes); + value.BreakpointModes = buf_BreakpointModes; } } else @@ -287,15 +290,18 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Capabilities valu { tokenizer.ReadStartArray(); - var list_AdditionalModuleColumns = new global::System.Collections.Generic.List(); + var buf_AdditionalModuleColumns = global::System.Array.Empty(); + var count_AdditionalModuleColumns = 0; while (!tokenizer.TryReadEndArray()) { + if (count_AdditionalModuleColumns == buf_AdditionalModuleColumns.Length) global::System.Array.Resize(ref buf_AdditionalModuleColumns, buf_AdditionalModuleColumns.Length == 0 ? 4 : buf_AdditionalModuleColumns.Length * 2); tokenizer.ReadStartObject(); var elem = new ColumnDescriptor(); ColumnDescriptorFormatter.ReadInto(ref tokenizer, elem, options); - list_AdditionalModuleColumns.Add(elem); + buf_AdditionalModuleColumns[count_AdditionalModuleColumns++] = elem; } - value.AdditionalModuleColumns = list_AdditionalModuleColumns.ToArray(); + if (count_AdditionalModuleColumns != buf_AdditionalModuleColumns.Length) global::System.Array.Resize(ref buf_AdditionalModuleColumns, count_AdditionalModuleColumns); + value.AdditionalModuleColumns = buf_AdditionalModuleColumns; } } else if (__name.SequenceEqual("supportsDataBreakpoints"u8)) @@ -424,15 +430,18 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Capabilities valu { tokenizer.ReadStartArray(); - var list_ExceptionBreakpointFilters = new global::System.Collections.Generic.List(); + var buf_ExceptionBreakpointFilters = global::System.Array.Empty(); + var count_ExceptionBreakpointFilters = 0; while (!tokenizer.TryReadEndArray()) { + if (count_ExceptionBreakpointFilters == buf_ExceptionBreakpointFilters.Length) global::System.Array.Resize(ref buf_ExceptionBreakpointFilters, buf_ExceptionBreakpointFilters.Length == 0 ? 4 : buf_ExceptionBreakpointFilters.Length * 2); tokenizer.ReadStartObject(); var elem = new ExceptionBreakpointsFilter(); ExceptionBreakpointsFilterFormatter.ReadInto(ref tokenizer, elem, options); - list_ExceptionBreakpointFilters.Add(elem); + buf_ExceptionBreakpointFilters[count_ExceptionBreakpointFilters++] = elem; } - value.ExceptionBreakpointFilters = list_ExceptionBreakpointFilters.ToArray(); + if (count_ExceptionBreakpointFilters != buf_ExceptionBreakpointFilters.Length) global::System.Array.Resize(ref buf_ExceptionBreakpointFilters, count_ExceptionBreakpointFilters); + value.ExceptionBreakpointFilters = buf_ExceptionBreakpointFilters; } } else if (__name.SequenceEqual("supportsGotoTargetsRequest"u8)) @@ -519,12 +528,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Capabilities valu { tokenizer.ReadStartArray(); - var list_CompletionTriggerCharacters = new global::System.Collections.Generic.List(); + var buf_CompletionTriggerCharacters = global::System.Array.Empty(); + var count_CompletionTriggerCharacters = 0; while (!tokenizer.TryReadEndArray()) { - list_CompletionTriggerCharacters.Add(tokenizer.ReadString()); + if (count_CompletionTriggerCharacters == buf_CompletionTriggerCharacters.Length) global::System.Array.Resize(ref buf_CompletionTriggerCharacters, buf_CompletionTriggerCharacters.Length == 0 ? 4 : buf_CompletionTriggerCharacters.Length * 2); + buf_CompletionTriggerCharacters[count_CompletionTriggerCharacters++] = tokenizer.ReadString(); } - value.CompletionTriggerCharacters = list_CompletionTriggerCharacters.ToArray(); + if (count_CompletionTriggerCharacters != buf_CompletionTriggerCharacters.Length) global::System.Array.Resize(ref buf_CompletionTriggerCharacters, count_CompletionTriggerCharacters); + value.CompletionTriggerCharacters = buf_CompletionTriggerCharacters; } } else if (__name.SequenceEqual("supportedChecksumAlgorithms"u8)) @@ -538,12 +550,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Capabilities valu { tokenizer.ReadStartArray(); - var list_SupportedChecksumAlgorithms = new global::System.Collections.Generic.List(); + var buf_SupportedChecksumAlgorithms = global::System.Array.Empty(); + var count_SupportedChecksumAlgorithms = 0; while (!tokenizer.TryReadEndArray()) { - list_SupportedChecksumAlgorithms.Add(ChecksumAlgorithmFormatter.ReadValue(ref tokenizer)); + if (count_SupportedChecksumAlgorithms == buf_SupportedChecksumAlgorithms.Length) global::System.Array.Resize(ref buf_SupportedChecksumAlgorithms, buf_SupportedChecksumAlgorithms.Length == 0 ? 4 : buf_SupportedChecksumAlgorithms.Length * 2); + buf_SupportedChecksumAlgorithms[count_SupportedChecksumAlgorithms++] = ChecksumAlgorithmFormatter.ReadValue(ref tokenizer); } - value.SupportedChecksumAlgorithms = list_SupportedChecksumAlgorithms.ToArray(); + if (count_SupportedChecksumAlgorithms != buf_SupportedChecksumAlgorithms.Length) global::System.Array.Resize(ref buf_SupportedChecksumAlgorithms, count_SupportedChecksumAlgorithms); + value.SupportedChecksumAlgorithms = buf_SupportedChecksumAlgorithms; } } else if (__name.SequenceEqual("supportsSteppingGranularity"u8)) diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ScopesResponseBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ScopesResponseBodyFormatter.g.cs index a33904a2..407032bf 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ScopesResponseBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ScopesResponseBodyFormatter.g.cs @@ -30,15 +30,18 @@ internal static ScopesResponseBody ReadValue(ref Utf8JsonTokenizer tokenizer, No { tokenizer.ReadStartArray(); - var list_Scopes = new global::System.Collections.Generic.List(); + var buf_Scopes = global::System.Array.Empty(); + var count_Scopes = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Scopes == buf_Scopes.Length) global::System.Array.Resize(ref buf_Scopes, buf_Scopes.Length == 0 ? 4 : buf_Scopes.Length * 2); tokenizer.ReadStartObject(); var elem = new Scope(); ScopeFormatter.ReadInto(ref tokenizer, elem, options); - list_Scopes.Add(elem); + buf_Scopes[count_Scopes++] = elem; } - __v_Scopes = list_Scopes.ToArray(); + if (count_Scopes != buf_Scopes.Length) global::System.Array.Resize(ref buf_Scopes, count_Scopes); + __v_Scopes = buf_Scopes; } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsArgumentsFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsArgumentsFormatter.g.cs index 8a4c376b..ecd757bd 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsArgumentsFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsArgumentsFormatter.g.cs @@ -41,12 +41,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, SetBreakpointsArg { tokenizer.ReadStartArray(); - var list_Lines = new global::System.Collections.Generic.List(); + var buf_Lines = global::System.Array.Empty(); + var count_Lines = 0; while (!tokenizer.TryReadEndArray()) { - list_Lines.Add(tokenizer.ReadUInt64()); + if (count_Lines == buf_Lines.Length) global::System.Array.Resize(ref buf_Lines, buf_Lines.Length == 0 ? 4 : buf_Lines.Length * 2); + buf_Lines[count_Lines++] = tokenizer.ReadUInt64(); } - value.Lines = list_Lines.ToArray(); + if (count_Lines != buf_Lines.Length) global::System.Array.Resize(ref buf_Lines, count_Lines); + value.Lines = buf_Lines; } } else @@ -84,15 +87,18 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, SetBreakpointsArg { tokenizer.ReadStartArray(); - var list_Breakpoints = new global::System.Collections.Generic.List(); + var buf_Breakpoints = global::System.Array.Empty(); + var count_Breakpoints = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Breakpoints == buf_Breakpoints.Length) global::System.Array.Resize(ref buf_Breakpoints, buf_Breakpoints.Length == 0 ? 4 : buf_Breakpoints.Length * 2); tokenizer.ReadStartObject(); var elem = new SourceBreakpoint(); SourceBreakpointFormatter.ReadInto(ref tokenizer, elem, options); - list_Breakpoints.Add(elem); + buf_Breakpoints[count_Breakpoints++] = elem; } - value.Breakpoints = list_Breakpoints.ToArray(); + if (count_Breakpoints != buf_Breakpoints.Length) global::System.Array.Resize(ref buf_Breakpoints, count_Breakpoints); + value.Breakpoints = buf_Breakpoints; } } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsResponseBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsResponseBodyFormatter.g.cs index d419a342..17594485 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsResponseBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SetBreakpointsResponseBodyFormatter.g.cs @@ -30,15 +30,18 @@ internal static SetBreakpointsResponseBody ReadValue(ref Utf8JsonTokenizer token { tokenizer.ReadStartArray(); - var list_Breakpoints = new global::System.Collections.Generic.List(); + var buf_Breakpoints = global::System.Array.Empty(); + var count_Breakpoints = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Breakpoints == buf_Breakpoints.Length) global::System.Array.Resize(ref buf_Breakpoints, buf_Breakpoints.Length == 0 ? 4 : buf_Breakpoints.Length * 2); tokenizer.ReadStartObject(); var elem = new Breakpoint(); BreakpointFormatter.ReadInto(ref tokenizer, elem, options); - list_Breakpoints.Add(elem); + buf_Breakpoints[count_Breakpoints++] = elem; } - __v_Breakpoints = list_Breakpoints.ToArray(); + if (count_Breakpoints != buf_Breakpoints.Length) global::System.Array.Resize(ref buf_Breakpoints, count_Breakpoints); + __v_Breakpoints = buf_Breakpoints; } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SourceFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SourceFormatter.g.cs index 555b981a..b04bb343 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SourceFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/SourceFormatter.g.cs @@ -100,15 +100,18 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Source value, NoJ { tokenizer.ReadStartArray(); - var list_Sources = new global::System.Collections.Generic.List(); + var buf_Sources = global::System.Array.Empty(); + var count_Sources = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Sources == buf_Sources.Length) global::System.Array.Resize(ref buf_Sources, buf_Sources.Length == 0 ? 4 : buf_Sources.Length * 2); tokenizer.ReadStartObject(); var elem = new Source(); SourceFormatter.ReadInto(ref tokenizer, elem, options); - list_Sources.Add(elem); + buf_Sources[count_Sources++] = elem; } - value.Sources = list_Sources.ToArray(); + if (count_Sources != buf_Sources.Length) global::System.Array.Resize(ref buf_Sources, count_Sources); + value.Sources = buf_Sources; } } else @@ -130,13 +133,16 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, Source value, NoJ { tokenizer.ReadStartArray(); - var list_Checksums = new global::System.Collections.Generic.List(); + var buf_Checksums = global::System.Array.Empty(); + var count_Checksums = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Checksums == buf_Checksums.Length) global::System.Array.Resize(ref buf_Checksums, buf_Checksums.Length == 0 ? 4 : buf_Checksums.Length * 2); tokenizer.ReadStartObject(); - list_Checksums.Add(ChecksumFormatter.ReadValue(ref tokenizer, options)); + buf_Checksums[count_Checksums++] = ChecksumFormatter.ReadValue(ref tokenizer, options); } - value.Checksums = list_Checksums.ToArray(); + if (count_Checksums != buf_Checksums.Length) global::System.Array.Resize(ref buf_Checksums, count_Checksums); + value.Checksums = buf_Checksums; } } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StackTraceResponseBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StackTraceResponseBodyFormatter.g.cs index 8e4da80a..f32f9cc8 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StackTraceResponseBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StackTraceResponseBodyFormatter.g.cs @@ -32,15 +32,18 @@ internal static StackTraceResponseBody ReadValue(ref Utf8JsonTokenizer tokenizer { tokenizer.ReadStartArray(); - var list_StackFrames = new global::System.Collections.Generic.List(); + var buf_StackFrames = global::System.Array.Empty(); + var count_StackFrames = 0; while (!tokenizer.TryReadEndArray()) { + if (count_StackFrames == buf_StackFrames.Length) global::System.Array.Resize(ref buf_StackFrames, buf_StackFrames.Length == 0 ? 4 : buf_StackFrames.Length * 2); tokenizer.ReadStartObject(); var elem = new StackFrame(); StackFrameFormatter.ReadInto(ref tokenizer, elem, options); - list_StackFrames.Add(elem); + buf_StackFrames[count_StackFrames++] = elem; } - __v_StackFrames = list_StackFrames.ToArray(); + if (count_StackFrames != buf_StackFrames.Length) global::System.Array.Resize(ref buf_StackFrames, count_StackFrames); + __v_StackFrames = buf_StackFrames; } else if (__name.SequenceEqual("totalFrames"u8)) diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StoppedEventBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StoppedEventBodyFormatter.g.cs index 4a3d3d99..b4920326 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StoppedEventBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/StoppedEventBodyFormatter.g.cs @@ -120,12 +120,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, StoppedEventBody { tokenizer.ReadStartArray(); - var list_HitBreakpointIds = new global::System.Collections.Generic.List(); + var buf_HitBreakpointIds = global::System.Array.Empty(); + var count_HitBreakpointIds = 0; while (!tokenizer.TryReadEndArray()) { - list_HitBreakpointIds.Add(tokenizer.ReadInt32()); + if (count_HitBreakpointIds == buf_HitBreakpointIds.Length) global::System.Array.Resize(ref buf_HitBreakpointIds, buf_HitBreakpointIds.Length == 0 ? 4 : buf_HitBreakpointIds.Length * 2); + buf_HitBreakpointIds[count_HitBreakpointIds++] = tokenizer.ReadInt32(); } - value.HitBreakpointIds = list_HitBreakpointIds.ToArray(); + if (count_HitBreakpointIds != buf_HitBreakpointIds.Length) global::System.Array.Resize(ref buf_HitBreakpointIds, count_HitBreakpointIds); + value.HitBreakpointIds = buf_HitBreakpointIds; } } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ThreadsResponseBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ThreadsResponseBodyFormatter.g.cs index ab039e47..84a7a356 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ThreadsResponseBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/ThreadsResponseBodyFormatter.g.cs @@ -30,13 +30,16 @@ internal static ThreadsResponseBody ReadValue(ref Utf8JsonTokenizer tokenizer, N { tokenizer.ReadStartArray(); - var list_Threads = new global::System.Collections.Generic.List(); + var buf_Threads = global::System.Array.Empty(); + var count_Threads = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Threads == buf_Threads.Length) global::System.Array.Resize(ref buf_Threads, buf_Threads.Length == 0 ? 4 : buf_Threads.Length * 2); tokenizer.ReadStartObject(); - list_Threads.Add(ThreadFormatter.ReadValue(ref tokenizer, options)); + buf_Threads[count_Threads++] = ThreadFormatter.ReadValue(ref tokenizer, options); } - __v_Threads = list_Threads.ToArray(); + if (count_Threads != buf_Threads.Length) global::System.Array.Resize(ref buf_Threads, count_Threads); + __v_Threads = buf_Threads; } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablePresentationHintFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablePresentationHintFormatter.g.cs index 22396a13..7e17d940 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablePresentationHintFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablePresentationHintFormatter.g.cs @@ -75,12 +75,15 @@ internal static void ReadInto(ref Utf8JsonTokenizer tokenizer, VariablePresentat { tokenizer.ReadStartArray(); - var list_Attributes = new global::System.Collections.Generic.List(); + var buf_Attributes = global::System.Array.Empty(); + var count_Attributes = 0; while (!tokenizer.TryReadEndArray()) { - list_Attributes.Add(tokenizer.ReadString()); + if (count_Attributes == buf_Attributes.Length) global::System.Array.Resize(ref buf_Attributes, buf_Attributes.Length == 0 ? 4 : buf_Attributes.Length * 2); + buf_Attributes[count_Attributes++] = tokenizer.ReadString(); } - value.Attributes = list_Attributes.ToArray(); + if (count_Attributes != buf_Attributes.Length) global::System.Array.Resize(ref buf_Attributes, count_Attributes); + value.Attributes = buf_Attributes; } } else if (__name.SequenceEqual("visibility"u8)) diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablesResponseBodyFormatter.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablesResponseBodyFormatter.g.cs index 234bfe6b..65c7138b 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablesResponseBodyFormatter.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/Formatters/VariablesResponseBodyFormatter.g.cs @@ -30,15 +30,18 @@ internal static VariablesResponseBody ReadValue(ref Utf8JsonTokenizer tokenizer, { tokenizer.ReadStartArray(); - var list_Variables = new global::System.Collections.Generic.List(); + var buf_Variables = global::System.Array.Empty(); + var count_Variables = 0; while (!tokenizer.TryReadEndArray()) { + if (count_Variables == buf_Variables.Length) global::System.Array.Resize(ref buf_Variables, buf_Variables.Length == 0 ? 4 : buf_Variables.Length * 2); tokenizer.ReadStartObject(); var elem = new Variable(); VariableFormatter.ReadInto(ref tokenizer, elem, options); - list_Variables.Add(elem); + buf_Variables[count_Variables++] = elem; } - __v_Variables = list_Variables.ToArray(); + if (count_Variables != buf_Variables.Length) global::System.Array.Resize(ref buf_Variables, count_Variables); + __v_Variables = buf_Variables; } else diff --git a/src/ChibiRuby.Debugger.Dap/Protocol/ProtocolSerializer.g.cs b/src/ChibiRuby.Debugger.Dap/Protocol/ProtocolSerializer.g.cs index f535a4b0..e271f49f 100644 --- a/src/ChibiRuby.Debugger.Dap/Protocol/ProtocolSerializer.g.cs +++ b/src/ChibiRuby.Debugger.Dap/Protocol/ProtocolSerializer.g.cs @@ -26,13 +26,8 @@ public NoJsonFormatException(string message, int position, int line = 0, int col Column = column; } - /// Byte offset within the input where the failure was detected. public int Position { get; } - - /// 1-based line number; 0 when not computed (e.g. constructed directly without source span). public int Line { get; } - - /// 1-based column number on ; 0 when not computed. public int Column { get; } static string FormatMessage(string message, int position, int line, int column) => @@ -41,18 +36,11 @@ static string FormatMessage(string message, int position, int line, int column) : message + " (pos " + position + ")"; } -/// -/// Pull-style UTF-8 JSON tokenizer with a Read*/TryRead* only API surface: -/// the caller (generated formatters) always knows the expected next token from the schema. -/// ref struct Utf8JsonTokenizer { #if NET7_0_OR_GREATER - // C# 11 ref-byte field: zero indirection on hot reads. readonly ref byte head; #else - // Pre-net7 targets (netstandard 2.1) don't allow ref fields. Hold the span and recover the - // ref on-demand via MemoryMarshal.GetReference — JIT inlines `HeadRef()` to the same code. readonly global::System.ReadOnlySpan input; #endif readonly int length; @@ -75,7 +63,6 @@ public Utf8JsonTokenizer(global::System.ReadOnlySpan input) valueHasEscape = false; } - /// Recover a writable ref byte at index 0 of the input span. [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] ref byte HeadRef() => #if NET7_0_OR_GREATER @@ -90,8 +77,6 @@ public int Position get => pos; } - // ----- Structural ----------------------------------------------------------------------- - public void ReadStartObject() { SkipWhitespace(); @@ -108,10 +93,6 @@ public void ReadStartArray() pos++; } - /// - /// Inside an object: read the next property name into and the - /// trailing colon, or return false (and consume the closing '}') when the object ends. - /// public bool TryReadPropertyName(out global::System.ReadOnlySpan nameUtf8) { SkipWhitespace(); @@ -146,10 +127,6 @@ public bool TryReadPropertyName(out global::System.ReadOnlySpan nameUtf8) return true; } - /// - /// Inside an array: consume the optional ',' separator. Returns true and consumes the - /// closing ']' if the array ended; false when another element follows. - /// public bool TryReadEndArray() { SkipWhitespace(); @@ -168,9 +145,6 @@ public bool TryReadEndArray() return false; } - // ----- Optional/null -------------------------------------------------------------------- - - /// If the next token is JSON null, advance past it and return true. public bool TryReadNull() { SkipWhitespace(); @@ -183,8 +157,6 @@ public bool TryReadNull() return false; } - // ----- Value readers -------------------------------------------------------------------- - public string ReadString() { SkipWhitespace(); @@ -195,10 +167,7 @@ public string ReadString() return valueHasEscape ? DecodeString(span) : global::System.Text.Encoding.UTF8.GetString(span); } - /// - /// Read a JSON string and return its raw UTF-8 byte slice without allocating a string. - /// Returns false if the string contains backslash escapes — callers should then fall back to . - /// + /// Raw UTF-8 slice with no string allocation; returns false on escapes (caller falls back to ). public bool TryReadStringRaw(out global::System.ReadOnlySpan rawBytes) { SkipWhitespace(); @@ -230,7 +199,7 @@ public bool ReadBoolean() return false; } ThrowFormatException("Expected boolean, got 0x" + b.ToString("X2")); - return false; // unreachable — the C# flow analyser doesn't honour [DoesNotReturn] for CS0161. + return false; // unreachable; [DoesNotReturn] on ThrowFormatException isn't honoured for CS0161. } public sbyte ReadSByte() @@ -360,7 +329,6 @@ public double ReadDouble() public global::System.TimeSpan ReadTimeSpan() { var s = ReadString(); - // ISO 8601 duration ("PT1H30M") via XmlConvert — JSON Schema's `format: duration` mandates ISO 8601. try { return global::System.Xml.XmlConvert.ToTimeSpan(s); } catch (global::System.FormatException) { ThrowFormatException("Invalid duration '" + s + "'"); return default; } } @@ -380,15 +348,6 @@ public byte[] ReadByteArray() catch (global::System.FormatException) { ThrowFormatException("Invalid base64"); return []; } } - // ----- Discriminator peek (polymorphic dispatch) --------------------------------------- - - /// - /// Scan forward (without advancing the tokenizer state) for a top-level property named - /// on the current object, and return its raw UTF-8 string - /// value via . The caller must still be positioned just after the - /// { of the object (i.e. inside it). The scan does not validate the rest of the object; - /// it just hunts the discriminator so a polymorphic parser can dispatch to the right subtype. - /// public bool TryPeekDiscriminator( global::System.ReadOnlySpan discriminatorName, out global::System.ReadOnlySpan rawValue) @@ -410,7 +369,7 @@ public bool TryPeekDiscriminator( if (b == (byte)'{') { depth++; pos++; continue; } if (b == (byte)'}') { - if (depth == 0) return false; // current object ended + if (depth == 0) return false; depth--; pos++; continue; } if (b == (byte)'[') { SkipObjectOrArray((byte)'[', (byte)']'); continue; } @@ -418,7 +377,6 @@ public bool TryPeekDiscriminator( if (b != (byte)'"') { - // Skip scalar values quickly until we land on a property name or container. while (pos < length) { var bb = ByteAt(pos); @@ -429,14 +387,12 @@ public bool TryPeekDiscriminator( continue; } - // We're on a string. Inside the *current* object (depth == 0) it's a property name; - // anywhere deeper it's a string value to skip past. + // depth == 0: property name; deeper: a string value to skip past. ReadStringValue(); if (depth != 0) continue; var nameSpan = SliceFrom(valueStart, valueEnd - valueStart); - // After the name, expect ':'. SkipWhitespace(); if (pos >= length || ByteAt(pos) != (byte)':') return false; pos++; @@ -451,7 +407,6 @@ public bool TryPeekDiscriminator( return true; } - // Not the discriminator — skip the value and continue scanning. SkipWhitespace(); if (pos >= length) return false; byte valStart = ByteAt(pos); @@ -480,8 +435,6 @@ public bool TryPeekDiscriminator( } } - // ----- Skip an unknown value ----------------------------------------------------------- - public void SkipValue() { SkipWhitespace(); @@ -498,18 +451,10 @@ public void SkipValue() default: if (b == (byte)'-' || (b >= (byte)'0' && b <= (byte)'9')) { ReadNumberValue(); return; } ThrowFormatException("Unexpected byte 0x" + b.ToString("X2")); - return; // unreachable + return; } } - // ----- Internal helpers ---------------------------------------------------------------- - - /// - /// Throw a annotated with the current position plus the - /// 1-based line / column computed from the input span. Kept as a NoInlining void helper so the - /// callers stay JIT-inlineable — `throw new ...` expressions inline far worse than a single - /// method-call to a cold throw routine. - /// [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] public void ThrowFormatException(string message) @@ -539,25 +484,57 @@ public void ThrowFormatException(string message) ref global::System.Runtime.CompilerServices.Unsafe.Add(ref HeadRef(), offset), len); +#if NET8_0_OR_GREATER + // Vectorized membership scans via the BCL (AVX2/AVX-512 internally) beat a hand-rolled Vector128. + static readonly global::System.Buffers.SearchValues s_whitespace = + global::System.Buffers.SearchValues.Create(" \t\n\r"u8); + static readonly global::System.Buffers.SearchValues s_stringDelimiters = + global::System.Buffers.SearchValues.Create(StringDelimiterBytes()); + + static byte[] StringDelimiterBytes() + { + var bytes = new byte[34]; // control chars 0x00..0x1F, plus '"' and '\\' + for (int i = 0; i < 0x20; i++) bytes[i] = (byte)i; + bytes[0x20] = (byte)'"'; + bytes[0x21] = (byte)'\\'; + return bytes; + } +#endif + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] void SkipWhitespace() { + // Scalar fast-exit: pos usually already sits on non-whitespace (compact input, or an + // upstream peek already skipped), so avoid the IndexOfAnyExcept call in the common case. + if (pos >= length) return; + byte b0 = ByteAt(pos); + if (b0 != (byte)' ' && b0 != (byte)'\t' && b0 != (byte)'\n' && b0 != (byte)'\r') return; +#if NET8_0_OR_GREATER + int rel = global::System.MemoryExtensions.IndexOfAnyExcept(SliceFrom(pos, length - pos), s_whitespace); + pos = rel < 0 ? length : pos + rel; +#else while (pos < length) { byte b = ByteAt(pos); if (b == (byte)' ' || b == (byte)'\t' || b == (byte)'\n' || b == (byte)'\r') pos++; else break; } +#endif } - /// Consume "..." at pos (opening quote required), recording valueStart/valueEnd. void ReadStringValue() { - pos++; // skip opening quote + pos++; // opening quote valueStart = pos; valueHasEscape = false; while (pos < length) { +#if NET8_0_OR_GREATER + // Jump to the next closing quote / escape / control char; re-entered after each escape. + int rel = global::System.MemoryExtensions.IndexOfAny(SliceFrom(pos, length - pos), s_stringDelimiters); + if (rel < 0) break; + pos += rel; +#endif byte b = ByteAt(pos); if (b == (byte)'"') { @@ -602,7 +579,7 @@ void ConsumeKeyword(global::System.ReadOnlySpan keyword) void SkipObjectOrArray(byte open, byte close) { int depth = 1; - pos++; // consume the opening byte + pos++; // opening byte while (pos < length && depth > 0) { byte b = ByteAt(pos); @@ -668,10 +645,8 @@ ref struct Utf8JsonBufferWriter { readonly global::System.Buffers.IBufferWriter writer; #if NET7_0_OR_GREATER - // C# 11 ref-byte field — moves forward via `Unsafe.Add` on every Advance. ref byte bufferReference; #else - // Pre-net7: hold the rented span (no separate offset — slicing advances the view). global::System.Span bufferReference; #endif int spanRemaining; @@ -706,9 +681,7 @@ public void Flush() } } - /// Writable ref byte at past the current write head. - /// JIT-inlines to a single ADD on net7+, and to MemoryMarshal.GetReference + offset on - /// netstandard. Callers use it as the LHS of an assignment (e.g. GetByte(3) = b;). + // Writable ref byte at offset past the write head; used as an assignment LHS (`GetByte(3) = b;`). [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] ref byte GetByte(int offset) => #if NET7_0_OR_GREATER @@ -732,10 +705,7 @@ public void WritePropertyNameRaw(global::System.ReadOnlySpan nameWithQuote needsSeparator = false; } - /// - /// Writes as a JSON string value. The bytes must be valid UTF-8 - /// and contain no characters that would require escaping (used for enum members and other fixed literals). - /// + /// Writes pre-validated UTF-8 with no escaping (enum members, fixed literals). public void WriteRawStringValue(scoped global::System.ReadOnlySpan contentInsideQuotes) { MaybeSeparator(); @@ -774,7 +744,6 @@ public void WriteTimeOnly(global::System.TimeOnly v) public void WriteTimeSpan(global::System.TimeSpan v) { - // Emit ISO 8601 duration (`P…T…`) so it round-trips through `format: duration` consumers. WriteString(global::System.Xml.XmlConvert.ToString(v)); } @@ -954,7 +923,6 @@ void AppendAsciiRange(scoped global::System.ReadOnlySpan chars) #if NET7_0_OR_GREATER var destSpan = global::System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref bufferReference, chars.Length); #else - // Slice already-pinned span — bufferReference's start is already the write head. var destSpan = bufferReference.Slice(0, chars.Length); #endif int written = global::System.Text.Encoding.UTF8.GetBytes(chars, destSpan); @@ -1029,7 +997,6 @@ void Advance(int n) #if NET7_0_OR_GREATER bufferReference = ref global::System.Runtime.CompilerServices.Unsafe.Add(ref bufferReference, n); #else - // Span tracks the head itself via slicing — no separate offset to bump. bufferReference = bufferReference.Slice(n); #endif spanRemaining -= n;