Fix generation of named enumerator types

Schema top-level definitions, such as `InvalidatedAreas` and `SteppingGranularity` were being emitted as empty structures, when they were actually enumerators.

Re-work protocol_gen.go to emit these correctly.

Also bumps the protocol to DAP version 1.46.0
This commit is contained in:
Ben Clayton 2021-04-12 13:08:58 +01:00
parent ad185ee762
commit be5b677c7b
7 changed files with 1233 additions and 333 deletions

View File

@ -15,7 +15,7 @@
// Generated with protocol_gen.go -- do not edit this file. // Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go // go run scripts/protocol_gen/protocol_gen.go
// //
// DAP version 1.43.0 // DAP version 1.46.0
#ifndef dap_protocol_h #ifndef dap_protocol_h
#define dap_protocol_h #define dap_protocol_h
@ -56,14 +56,15 @@ struct AttachRequest : public Request {
DAP_DECLARE_STRUCT_TYPEINFO(AttachRequest); DAP_DECLARE_STRUCT_TYPEINFO(AttachRequest);
// Names of checksum algorithms that may be supported by a debug adapter. // Names of checksum algorithms that may be supported by a debug adapter.
struct ChecksumAlgorithm {}; //
// Must be one of the following enumeration values:
DAP_DECLARE_STRUCT_TYPEINFO(ChecksumAlgorithm); // 'MD5', 'SHA1', 'SHA256', 'timestamp'
using ChecksumAlgorithm = string;
// The checksum of an item calculated by the specified algorithm. // The checksum of an item calculated by the specified algorithm.
struct Checksum { struct Checksum {
// The algorithm used to calculate this checksum. // The algorithm used to calculate this checksum.
ChecksumAlgorithm algorithm; ChecksumAlgorithm algorithm = "MD5";
// Value of the checksum. // Value of the checksum.
string checksum; string checksum;
}; };
@ -272,9 +273,16 @@ DAP_DECLARE_STRUCT_TYPEINFO(ColumnDescriptor);
// An ExceptionBreakpointsFilter is shown in the UI as an filter option for // An ExceptionBreakpointsFilter is shown in the UI as an filter option for
// configuring how exceptions are dealt with. // configuring how exceptions are dealt with.
struct ExceptionBreakpointsFilter { struct ExceptionBreakpointsFilter {
// An optional help text providing information about the condition. This
// string is shown as the placeholder text for a text box and must be
// translated.
optional<string> conditionDescription;
// Initial value of the filter option. If not specified a value 'false' is // Initial value of the filter option. If not specified a value 'false' is
// assumed. // assumed.
optional<boolean> def; optional<boolean> def;
// An optional help text providing additional information about the exception
// filter. This string is typically shown as a hover and must be translated.
optional<string> description;
// The internal ID of the filter option. This value is passed to the // The internal ID of the filter option. This value is passed to the
// 'setExceptionBreakpoints' request. // 'setExceptionBreakpoints' request.
string filter; string filter;
@ -318,7 +326,7 @@ struct Capabilities {
// The debug adapter supports data breakpoints. // The debug adapter supports data breakpoints.
optional<boolean> supportsDataBreakpoints; optional<boolean> supportsDataBreakpoints;
// The debug adapter supports the delayed loading of parts of the stack, which // The debug adapter supports the delayed loading of parts of the stack, which
// requires that both the 'startFrame' and 'levels' arguments and the // requires that both the 'startFrame' and 'levels' arguments and an optional
// 'totalFrames' result of the 'StackTrace' request are supported. // 'totalFrames' result of the 'StackTrace' request are supported.
optional<boolean> supportsDelayedStackTraceLoading; optional<boolean> supportsDelayedStackTraceLoading;
// The debug adapter supports the 'disassemble' request. // The debug adapter supports the 'disassemble' request.
@ -398,9 +406,12 @@ DAP_DECLARE_STRUCT_TYPEINFO(CapabilitiesEvent);
// Some predefined types for the CompletionItem. Please note that not all // Some predefined types for the CompletionItem. Please note that not all
// clients have specific icons for all of them. // clients have specific icons for all of them.
struct CompletionItemType {}; //
// Must be one of the following enumeration values:
DAP_DECLARE_STRUCT_TYPEINFO(CompletionItemType); // 'method', 'function', 'constructor', 'field', 'variable', 'class',
// 'interface', 'module', 'property', 'unit', 'value', 'enum', 'keyword',
// 'snippet', 'text', 'color', 'file', 'reference', 'customcolor'
using CompletionItemType = string;
// CompletionItems are the suggestions returned from the CompletionsRequest. // CompletionItems are the suggestions returned from the CompletionsRequest.
struct CompletionItem { struct CompletionItem {
@ -519,9 +530,10 @@ struct ContinuedEvent : public Event {
DAP_DECLARE_STRUCT_TYPEINFO(ContinuedEvent); DAP_DECLARE_STRUCT_TYPEINFO(ContinuedEvent);
// This enumeration defines all possible access types for data breakpoints. // This enumeration defines all possible access types for data breakpoints.
struct DataBreakpointAccessType {}; //
// Must be one of the following enumeration values:
DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointAccessType); // 'read', 'write', 'readWrite'
using DataBreakpointAccessType = string;
// Response to 'dataBreakpointInfo' request. // Response to 'dataBreakpointInfo' request.
struct DataBreakpointInfoResponse : public Response { struct DataBreakpointInfoResponse : public Response {
@ -548,7 +560,7 @@ DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoResponse);
struct DataBreakpointInfoRequest : public Request { struct DataBreakpointInfoRequest : public Request {
using Response = DataBreakpointInfoResponse; using Response = DataBreakpointInfoResponse;
// The name of the Variable's child to obtain data breakpoint information for. // The name of the Variable's child to obtain data breakpoint information for.
// If variableReference isnt provided, this can be an expression. // If variablesReference isnt provided, this can be an expression.
string name; string name;
// Reference to the Variable container if the data breakpoint is requested for // Reference to the Variable container if the data breakpoint is requested for
// a child of the container. // a child of the container.
@ -777,9 +789,10 @@ DAP_DECLARE_STRUCT_TYPEINFO(EvaluateRequest);
// should result in a break. never: never breaks, always: always breaks, // should result in a break. never: never breaks, always: always breaks,
// unhandled: breaks when exception unhandled, // unhandled: breaks when exception unhandled,
// userUnhandled: breaks if the exception is not handled by user code. // userUnhandled: breaks if the exception is not handled by user code.
struct ExceptionBreakMode {}; //
// Must be one of the following enumeration values:
DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakMode); // 'never', 'always', 'unhandled', 'userUnhandled'
using ExceptionBreakMode = string;
// Detailed information about an exception that has occurred. // Detailed information about an exception that has occurred.
struct ExceptionDetails { struct ExceptionDetails {
@ -803,7 +816,7 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionDetails);
// Response to 'exceptionInfo' request. // Response to 'exceptionInfo' request.
struct ExceptionInfoResponse : public Response { struct ExceptionInfoResponse : public Response {
// Mode that caused the exception notification to be raised. // Mode that caused the exception notification to be raised.
ExceptionBreakMode breakMode; ExceptionBreakMode breakMode = "never";
// Descriptive text for the exception provided by the debug adapter. // Descriptive text for the exception provided by the debug adapter.
optional<string> description; optional<string> description;
// Detailed information about the exception. // Detailed information about the exception.
@ -934,7 +947,7 @@ struct InitializeResponse : public Response {
// The debug adapter supports data breakpoints. // The debug adapter supports data breakpoints.
optional<boolean> supportsDataBreakpoints; optional<boolean> supportsDataBreakpoints;
// The debug adapter supports the delayed loading of parts of the stack, which // The debug adapter supports the delayed loading of parts of the stack, which
// requires that both the 'startFrame' and 'levels' arguments and the // requires that both the 'startFrame' and 'levels' arguments and an optional
// 'totalFrames' result of the 'StackTrace' request are supported. // 'totalFrames' result of the 'StackTrace' request are supported.
optional<boolean> supportsDelayedStackTraceLoading; optional<boolean> supportsDelayedStackTraceLoading;
// The debug adapter supports the 'disassemble' request. // The debug adapter supports the 'disassemble' request.
@ -1064,9 +1077,7 @@ struct InitializedEvent : public Event {};
DAP_DECLARE_STRUCT_TYPEINFO(InitializedEvent); DAP_DECLARE_STRUCT_TYPEINFO(InitializedEvent);
// Logical areas that can be invalidated by the 'invalidated' event. // Logical areas that can be invalidated by the 'invalidated' event.
struct InvalidatedAreas {}; using InvalidatedAreas = string;
DAP_DECLARE_STRUCT_TYPEINFO(InvalidatedAreas);
// This event signals that some state in the debug adapter has changed and // This event signals that some state in the debug adapter has changed and
// requires that the client needs to re-render the data snapshot previously // requires that the client needs to re-render the data snapshot previously
@ -1239,9 +1250,10 @@ DAP_DECLARE_STRUCT_TYPEINFO(NextResponse);
// The granularity of one 'step' in the stepping requests 'next', 'stepIn', // The granularity of one 'step' in the stepping requests 'next', 'stepIn',
// 'stepOut', and 'stepBack'. // 'stepOut', and 'stepBack'.
struct SteppingGranularity {}; //
// Must be one of the following enumeration values:
DAP_DECLARE_STRUCT_TYPEINFO(SteppingGranularity); // 'statement', 'line', 'instruction'
using SteppingGranularity = string;
// The request starts the debuggee to run again for one step. // The request starts the debuggee to run again for one step.
// The debug adapter first sends the response and then a 'stopped' event (with // The debug adapter first sends the response and then a 'stopped' event (with
@ -1698,7 +1710,7 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionPathSegment);
// An ExceptionOptions assigns configuration options to a set of exceptions. // An ExceptionOptions assigns configuration options to a set of exceptions.
struct ExceptionOptions { struct ExceptionOptions {
// Condition when a thrown exception should result in a break. // Condition when a thrown exception should result in a break.
ExceptionBreakMode breakMode; ExceptionBreakMode breakMode = "never";
// A path that selects a single or multiple exceptions in a tree. If 'path' is // A path that selects a single or multiple exceptions in a tree. If 'path' is
// missing, the whole tree is selected. By convention the first segment of the // missing, the whole tree is selected. By convention the first segment of the
// path is a category that is used to group exceptions in the UI. // path is a category that is used to group exceptions in the UI.
@ -1724,7 +1736,9 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionFilterOptions);
// The request configures the debuggers response to thrown exceptions. // The request configures the debuggers response to thrown exceptions.
// If an exception is configured to break, a 'stopped' event is fired (with // If an exception is configured to break, a 'stopped' event is fired (with
// reason 'exception'). Clients should only call this request if the capability // reason 'exception'). Clients should only call this request if the capability
// 'exceptionBreakpointFilters' returns one or more filters. // 'exceptionBreakpointFilters' returns one or more filters. If a filter or
// filter option is invalid (e.g. due to an invalid 'condition'), the request
// should fail with an 'ErrorResponse' explaining the problem(s).
struct SetExceptionBreakpointsRequest : public Request { struct SetExceptionBreakpointsRequest : public Request {
using Response = SetExceptionBreakpointsResponse; using Response = SetExceptionBreakpointsResponse;
// Configuration options for selected exceptions. // Configuration options for selected exceptions.
@ -1948,6 +1962,10 @@ DAP_DECLARE_STRUCT_TYPEINFO(SourceRequest);
// A Stackframe contains the source location. // A Stackframe contains the source location.
struct StackFrame { struct StackFrame {
// Indicates whether this frame can be restarted with the 'restart' request.
// Clients should only use this if the debug adapter supports the 'restart'
// request (capability 'supportsRestartRequest' is true).
optional<boolean> canRestart;
// The column within the line. If source is null or doesn't exist, column is 0 // The column within the line. If source is null or doesn't exist, column is 0
// and must be ignored. // and must be ignored.
integer column; integer column;
@ -2023,10 +2041,11 @@ DAP_DECLARE_STRUCT_TYPEINFO(StackFrameFormat);
// The request returns a stacktrace from the current execution state of a given // The request returns a stacktrace from the current execution state of a given
// thread. A client can request all stack frames by omitting the startFrame and // thread. A client can request all stack frames by omitting the startFrame and
// levels arguments. For performance conscious clients stack frames can be // levels arguments. For performance conscious clients and if the debug
// retrieved in a piecemeal way with the startFrame and levels arguments. The // adapter's 'supportsDelayedStackTraceLoading' capability is true, stack frames
// response of the stackTrace request may contain a totalFrames property that // can be retrieved in a piecemeal way with the startFrame and levels arguments.
// hints at the total number of frames in the stack. If a client needs this // The response of the stackTrace request may contain a totalFrames property
// that hints at the total number of frames in the stack. If a client needs this
// total number upfront, it can issue a request for a single (first) frame and // total number upfront, it can issue a request for a single (first) frame and
// depending on the value of totalFrames decide how to proceed. In any case a // depending on the value of totalFrames decide how to proceed. In any case a
// client should be prepared to receive less frames than requested, which is an // client should be prepared to receive less frames than requested, which is an
@ -2162,6 +2181,15 @@ struct StoppedEvent : public Event {
// The full reason for the event, e.g. 'Paused on exception'. This string is // The full reason for the event, e.g. 'Paused on exception'. This string is
// shown in the UI as is and must be translated. // shown in the UI as is and must be translated.
optional<string> description; optional<string> description;
// Ids of the breakpoints that triggered the event. In most cases there will
// be only a single breakpoint but here are some examples for multiple
// breakpoints:
// - Different types of breakpoints map to the same location.
// - Multiple source breakpoints get collapsed to the same instruction by the
// compiler/runtime.
// - Multiple function breakpoints with different function names map to the
// same location.
optional<array<integer>> hitBreakpointIds;
// A value of true hints to the frontend that this event should not change the // A value of true hints to the frontend that this event should not change the
// focus. // focus.
optional<boolean> preserveFocusHint; optional<boolean> preserveFocusHint;

View File

@ -15,7 +15,7 @@
// Generated with protocol_gen.go -- do not edit this file. // Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go // go run scripts/protocol_gen/protocol_gen.go
// //
// DAP version 1.43.0 // DAP version 1.46.0
#include "dap/protocol.h" #include "dap/protocol.h"
@ -102,6 +102,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(StoppedEvent,
"stopped", "stopped",
DAP_FIELD(allThreadsStopped, "allThreadsStopped"), DAP_FIELD(allThreadsStopped, "allThreadsStopped"),
DAP_FIELD(description, "description"), DAP_FIELD(description, "description"),
DAP_FIELD(hitBreakpointIds, "hitBreakpointIds"),
DAP_FIELD(preserveFocusHint, "preserveFocusHint"), DAP_FIELD(preserveFocusHint, "preserveFocusHint"),
DAP_FIELD(reason, "reason"), DAP_FIELD(reason, "reason"),
DAP_FIELD(text, "text"), DAP_FIELD(text, "text"),

View File

@ -15,7 +15,7 @@
// Generated with protocol_gen.go -- do not edit this file. // Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go // go run scripts/protocol_gen/protocol_gen.go
// //
// DAP version 1.43.0 // DAP version 1.46.0
#include "dap/protocol.h" #include "dap/protocol.h"

View File

@ -15,7 +15,7 @@
// Generated with protocol_gen.go -- do not edit this file. // Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go // go run scripts/protocol_gen/protocol_gen.go
// //
// DAP version 1.43.0 // DAP version 1.46.0
#include "dap/protocol.h" #include "dap/protocol.h"

View File

@ -15,14 +15,12 @@
// Generated with protocol_gen.go -- do not edit this file. // Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go // go run scripts/protocol_gen/protocol_gen.go
// //
// DAP version 1.43.0 // DAP version 1.46.0
#include "dap/protocol.h" #include "dap/protocol.h"
namespace dap { namespace dap {
DAP_IMPLEMENT_STRUCT_TYPEINFO(ChecksumAlgorithm, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(Checksum, DAP_IMPLEMENT_STRUCT_TYPEINFO(Checksum,
"", "",
DAP_FIELD(algorithm, "algorithm"), DAP_FIELD(algorithm, "algorithm"),
@ -70,7 +68,10 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(ColumnDescriptor,
DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter, DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter,
"", "",
DAP_FIELD(conditionDescription,
"conditionDescription"),
DAP_FIELD(def, "default"), DAP_FIELD(def, "default"),
DAP_FIELD(description, "description"),
DAP_FIELD(filter, "filter"), DAP_FIELD(filter, "filter"),
DAP_FIELD(label, "label"), DAP_FIELD(label, "label"),
DAP_FIELD(supportsCondition, DAP_FIELD(supportsCondition,
@ -122,8 +123,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(
DAP_FIELD(supportsValueFormattingOptions, DAP_FIELD(supportsValueFormattingOptions,
"supportsValueFormattingOptions")); "supportsValueFormattingOptions"));
DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItemType, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem, DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem,
"", "",
DAP_FIELD(label, "label"), DAP_FIELD(label, "label"),
@ -135,8 +134,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem,
DAP_FIELD(text, "text"), DAP_FIELD(text, "text"),
DAP_FIELD(type, "type")); DAP_FIELD(type, "type"));
DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointAccessType, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction, DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction,
"", "",
DAP_FIELD(address, "address"), DAP_FIELD(address, "address"),
@ -167,8 +164,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablePresentationHint,
DAP_IMPLEMENT_STRUCT_TYPEINFO(ValueFormat, "", DAP_FIELD(hex, "hex")); DAP_IMPLEMENT_STRUCT_TYPEINFO(ValueFormat, "", DAP_FIELD(hex, "hex"));
DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakMode, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionDetails, DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionDetails,
"", "",
DAP_FIELD(evaluateName, "evaluateName"), DAP_FIELD(evaluateName, "evaluateName"),
@ -189,8 +184,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTarget,
DAP_FIELD(label, "label"), DAP_FIELD(label, "label"),
DAP_FIELD(line, "line")); DAP_FIELD(line, "line"));
DAP_IMPLEMENT_STRUCT_TYPEINFO(InvalidatedAreas, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(Module, DAP_IMPLEMENT_STRUCT_TYPEINFO(Module,
"", "",
DAP_FIELD(addressRange, "addressRange"), DAP_FIELD(addressRange, "addressRange"),
@ -204,8 +197,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(Module,
DAP_FIELD(symbolStatus, "symbolStatus"), DAP_FIELD(symbolStatus, "symbolStatus"),
DAP_FIELD(version, "version")); DAP_FIELD(version, "version"));
DAP_IMPLEMENT_STRUCT_TYPEINFO(SteppingGranularity, "");
DAP_IMPLEMENT_STRUCT_TYPEINFO(Scope, DAP_IMPLEMENT_STRUCT_TYPEINFO(Scope,
"", "",
DAP_FIELD(column, "column"), DAP_FIELD(column, "column"),
@ -267,6 +258,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(InstructionBreakpoint,
DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame, DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame,
"", "",
DAP_FIELD(canRestart, "canRestart"),
DAP_FIELD(column, "column"), DAP_FIELD(column, "column"),
DAP_FIELD(endColumn, "endColumn"), DAP_FIELD(endColumn, "endColumn"),
DAP_FIELD(endLine, "endLine"), DAP_FIELD(endLine, "endLine"),

View File

@ -0,0 +1,725 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// protocol_gen (re)generates the cppdap .h and .cpp files that describe the
// DAP protocol.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"reflect"
"runtime"
"sort"
"strings"
)
const (
protocolURL = "https://raw.githubusercontent.com/microsoft/vscode-debugadapter-node/master/debugProtocol.json"
packageURL = "https://raw.githubusercontent.com/microsoft/vscode-debugadapter-node/master/protocol/package.json"
versionTag = "${version}"
commonPrologue = `// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go
//
// DAP version ${version}
`
headerPrologue = commonPrologue + `
#ifndef dap_protocol_h
#define dap_protocol_h
#include "optional.h"
#include "typeinfo.h"
#include "typeof.h"
#include "variant.h"
#include <string>
#include <type_traits>
#include <vector>
namespace dap {
struct Request {};
struct Response {};
struct Event {};
`
headerEpilogue = `} // namespace dap
#endif // dap_protocol_h
`
cppPrologue = commonPrologue + `
#include "dap/protocol.h"
namespace dap {
`
cppEpilogue = `} // namespace dap
`
)
func main() {
flag.Parse()
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
type root struct {
Schema string `json:"$schema"`
Title string `json:"title"`
Description string `json:"description"`
Ty string `json:"type"`
Definitions map[string]definition `json:"definitions"`
}
func (r *root) definitions() []namedDefinition {
sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions))
for name, def := range r.Definitions {
sortedDefinitions = append(sortedDefinitions, namedDefinition{name, def})
}
sort.Slice(sortedDefinitions, func(i, j int) bool { return sortedDefinitions[i].name < sortedDefinitions[j].name })
return sortedDefinitions
}
func (r *root) getRef(ref string) (namedDefinition, error) {
if !strings.HasPrefix(ref, "#/definitions/") {
return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref)
}
name := strings.TrimPrefix(ref, "#/definitions/")
def, ok := r.Definitions[name]
if !ok {
return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref)
}
return namedDefinition{name, def}, nil
}
type namedDefinition struct {
name string
def definition
}
type definition struct {
Ty string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
Properties properties `json:"properties"`
Required []string `json:"required"`
AllOf []definition `json:"allOf"`
Ref string `json:"$ref"`
ClosedEnum []string `json:"enum"`
}
type properties map[string]property
func (p *properties) foreach(cb func(string, property) error) error {
type namedProperty struct {
name string
property property
}
sorted := make([]namedProperty, 0, len(*p))
for name, property := range *p {
sorted = append(sorted, namedProperty{name, property})
}
sort.Slice(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name })
for _, entry := range sorted {
if err := cb(entry.name, entry.property); err != nil {
return err
}
}
return nil
}
type property struct {
typed
Description string `json:"description"`
}
func (p *property) properties(r *root) (properties, []string, error) {
if p.Ref == "" {
return p.Properties, p.Required, nil
}
d, err := r.getRef(p.Ref)
if err != nil {
return nil, nil, err
}
return d.def.Properties, d.def.Required, nil
}
type typed struct {
Ty interface{} `json:"type"`
Items *typed `json:"items"`
Ref string `json:"$ref"`
Properties properties `json:"properties"`
Required []string `json:"required"`
ClosedEnum []string `json:"enum"`
OpenEnum []string `json:"_enum"`
}
func (t typed) typename(r *root, refs *[]string) (string, error) {
if t.Ref != "" {
d, err := r.getRef(t.Ref)
if err != nil {
return "", err
}
*refs = append(*refs, d.name)
return d.name, nil
}
if t.Ty == nil {
return "", fmt.Errorf("No type specified")
}
var typeof func(v reflect.Value) (string, error)
typeof = func(v reflect.Value) (string, error) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.String:
ty := v.Interface().(string)
switch ty {
case "boolean", "string", "integer", "number", "object", "null":
return ty, nil
case "array":
if t.Items != nil {
el, err := t.Items.typename(r, refs)
if err != nil {
return "", err
}
return fmt.Sprintf("array<%s>", el), nil
}
return "array<any>", nil
default:
return "", fmt.Errorf("Unhandled property type '%v'", ty)
}
case reflect.Slice, reflect.Array:
ty := "variant<"
for i := 0; i < v.Len(); i++ {
if i > 0 {
ty += ", "
}
el, err := typeof(v.Index(i))
if err != nil {
return "", err
}
ty += el
}
ty += ">"
return ty, nil
}
return "", fmt.Errorf("Unsupported type '%v' kind: %v", v.Interface(), v.Kind())
}
return typeof(reflect.ValueOf(t.Ty))
}
type cppField struct {
desc string
ty string
name string
defaultValue string
optional bool
}
type cppType interface {
Name() string
Dependencies() []string
File() cppFile
DefaultValue() string
WriteHeader(w io.Writer)
WriteCPP(w io.Writer)
}
type cppStruct struct {
desc string
name string
typename string
base string
fields []cppField
deps []string
emit bool
typedefs []cppTypedef
file cppFile
}
func (s *cppStruct) Name() string {
return s.name
}
func (s *cppStruct) Dependencies() []string {
return s.deps
}
func (s *cppStruct) File() cppFile {
return s.file
}
func (s *cppStruct) DefaultValue() string { return "" }
func (s *cppStruct) WriteHeader(w io.Writer) {
if s.desc != "" {
io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(s.desc, "\n", "\n// "))
io.WriteString(w, "\n")
}
io.WriteString(w, "struct ")
io.WriteString(w, s.name)
if s.base != "" {
io.WriteString(w, " : public ")
io.WriteString(w, s.base)
}
io.WriteString(w, " {")
// typedefs
for _, t := range s.typedefs {
io.WriteString(w, "\n using ")
io.WriteString(w, t.from)
io.WriteString(w, " = ")
io.WriteString(w, t.to)
io.WriteString(w, ";")
}
for _, f := range s.fields {
if f.desc != "" {
io.WriteString(w, "\n // ")
io.WriteString(w, strings.ReplaceAll(f.desc, "\n", "\n // "))
}
io.WriteString(w, "\n ")
if f.optional {
io.WriteString(w, "optional<")
io.WriteString(w, f.ty)
io.WriteString(w, ">")
} else {
io.WriteString(w, f.ty)
}
io.WriteString(w, " ")
io.WriteString(w, sanitize(f.name))
if !f.optional && f.defaultValue != "" {
io.WriteString(w, " = ")
io.WriteString(w, f.defaultValue)
}
io.WriteString(w, ";")
}
io.WriteString(w, "\n};\n\n")
io.WriteString(w, "DAP_DECLARE_STRUCT_TYPEINFO(")
io.WriteString(w, s.name)
io.WriteString(w, ");\n\n")
}
func (s *cppStruct) WriteCPP(w io.Writer) {
// typeinfo
io.WriteString(w, "DAP_IMPLEMENT_STRUCT_TYPEINFO(")
io.WriteString(w, s.name)
io.WriteString(w, ",\n \"")
io.WriteString(w, s.typename)
io.WriteString(w, "\"")
for _, f := range s.fields {
io.WriteString(w, ",\n ")
io.WriteString(w, "DAP_FIELD(")
io.WriteString(w, sanitize(f.name))
io.WriteString(w, ", \"")
io.WriteString(w, f.name)
io.WriteString(w, "\")")
}
io.WriteString(w, ");\n\n")
}
type cppTypedef struct {
from string
to string
deps []string
desc string
defaultValue string
}
func (ty *cppTypedef) Name() string {
return ty.from
}
func (ty *cppTypedef) Dependencies() []string {
return ty.deps
}
func (ty *cppTypedef) File() cppFile {
return types
}
func (ty *cppTypedef) DefaultValue() string { return ty.defaultValue }
func (ty *cppTypedef) WriteHeader(w io.Writer) {
if ty.desc != "" {
io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(ty.desc, "\n", "\n// "))
io.WriteString(w, "\n")
}
io.WriteString(w, "using ")
io.WriteString(w, ty.from)
io.WriteString(w, " = ")
io.WriteString(w, ty.to)
io.WriteString(w, ";\n\n")
}
func (ty *cppTypedef) WriteCPP(w io.Writer) {
}
func sanitize(s string) string {
s = strings.Trim(s, "_")
switch s {
case "default":
return "def"
default:
return s
}
}
func appendEnumDetails(desc string, openEnum []string, closedEnum []string) string {
if len(closedEnum) > 0 {
desc += "\n\nMust be one of the following enumeration values:\n"
for i, enum := range closedEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
if len(openEnum) > 0 {
desc += "\n\nMay be one of the following enumeration values:\n"
for i, enum := range openEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
return desc
}
func (r *root) buildObject(entry namedDefinition) (*cppStruct, error) {
defName := entry.name
def := entry.def
base := ""
if len(def.AllOf) > 1 && def.AllOf[0].Ref != "" {
ref, err := r.getRef(def.AllOf[0].Ref)
if err != nil {
return nil, err
}
base = ref.name
if len(def.AllOf) > 2 {
return nil, fmt.Errorf("Cannot handle allOf with more than 2 entries")
}
def = def.AllOf[1]
}
s := &cppStruct{
desc: def.Description,
name: defName,
base: base,
}
var props properties
var required []string
var err error
switch base {
case "Request":
if arguments, ok := def.Properties["arguments"]; ok {
props, required, err = arguments.properties(r)
}
if command, ok := def.Properties["command"]; ok {
s.typename = command.ClosedEnum[0]
}
response := strings.TrimSuffix(s.name, "Request") + "Response"
s.deps = append(s.deps, response)
s.typedefs = append(s.typedefs, cppTypedef{from: "Response", to: response})
s.emit = true
s.file = request
case "Response":
if body, ok := def.Properties["body"]; ok {
props, required, err = body.properties(r)
}
s.emit = true
s.file = response
case "Event":
if body, ok := def.Properties["body"]; ok {
props, required, err = body.properties(r)
}
if command, ok := def.Properties["event"]; ok {
s.typename = command.ClosedEnum[0]
}
s.emit = true
s.file = event
default:
props = def.Properties
required = def.Required
s.file = types
}
if err != nil {
return nil, err
}
if err = props.foreach(func(propName string, property property) error {
ty, err := property.typename(r, &s.deps)
if err != nil {
return fmt.Errorf("While processing %v.%v: %v", defName, propName, err)
}
optional := true
for _, r := range required {
if propName == r {
optional = false
}
}
desc := appendEnumDetails(property.Description, property.OpenEnum, property.ClosedEnum)
defaultValue := ""
if len(property.ClosedEnum) > 0 {
defaultValue = `"` + property.ClosedEnum[0] + `"`
}
s.fields = append(s.fields, cppField{
desc: desc,
defaultValue: defaultValue,
ty: ty,
name: propName,
optional: optional,
})
return nil
}); err != nil {
return nil, err
}
return s, nil
}
func (r *root) buildTypes() ([]cppType, error) {
ignore := map[string]bool{
// These are handled internally.
"ProtocolMessage": true,
"Request": true,
"Event": true,
"Response": true,
}
out := []cppType{}
for _, entry := range r.definitions() {
if ignore[entry.name] {
continue
}
switch entry.def.Ty {
case "", "object":
ty, err := r.buildObject(entry)
if err != nil {
return nil, err
}
out = append(out, ty)
case "string":
desc := appendEnumDetails(entry.def.Description, nil, entry.def.ClosedEnum)
defaultValue := ""
if len(entry.def.ClosedEnum) > 0 {
defaultValue = entry.def.ClosedEnum[0]
}
ty := &cppTypedef{from: entry.name, to: "std::string", desc: desc, defaultValue: defaultValue}
out = append(out, ty)
default:
return nil, fmt.Errorf("Unhandled type '%v' for '%v'", entry.def.Ty, entry.name)
}
}
return out, nil
}
type cppFile string
const (
request = cppFile("request")
response = cppFile("response")
event = cppFile("event")
types = cppFile("types")
)
type cppFilePaths map[cppFile]string
type cppFiles map[cppFile]*os.File
func run() error {
pkg := struct {
Version string `json:"version"`
}{}
if err := loadJSONFile(packageURL, &pkg); err != nil {
return fmt.Errorf("Failed to load JSON file from '%v': %w", packageURL, err)
}
protocol := root{}
if err := loadJSONFile(protocolURL, &protocol); err != nil {
return fmt.Errorf("Failed to load JSON file from '%v': %w", protocolURL, err)
}
hPath, cppPaths := outputPaths()
if err := emitFiles(&protocol, hPath, cppPaths, pkg.Version); err != nil {
return fmt.Errorf("Failed to emit files: %w", err)
}
if clangfmt, err := exec.LookPath("clang-format"); err == nil {
if out, err := exec.Command(clangfmt, "-i", hPath).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", hPath, string(out), err)
}
for _, p := range cppPaths {
if out, err := exec.Command(clangfmt, "-i", p).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", p, string(out), err)
}
}
} else {
fmt.Printf("clang-format not found on PATH. Please format before committing.")
}
return nil
}
func emitFiles(r *root, hPath string, cppPaths map[cppFile]string, version string) error {
h, err := os.Create(hPath)
if err != nil {
return err
}
defer h.Close()
cppFiles := map[cppFile]*os.File{}
for ty, p := range cppPaths {
f, err := os.Create(p)
if err != nil {
return err
}
cppFiles[ty] = f
defer f.Close()
}
h.WriteString(strings.ReplaceAll(headerPrologue, versionTag, version))
for _, f := range cppFiles {
f.WriteString(strings.ReplaceAll(cppPrologue, versionTag, version))
}
types, err := r.buildTypes()
if err != nil {
return err
}
typesByName := map[string]cppType{}
for _, s := range types {
typesByName[s.Name()] = s
}
seen := map[string]bool{}
var emit func(cppType) error
emit = func(ty cppType) error {
name := ty.Name()
if seen[name] {
return nil
}
seen[name] = true
for _, depName := range ty.Dependencies() {
dep, ok := typesByName[depName]
if !ok {
return fmt.Errorf("'%v' depends on unknown type '%v'", name, depName)
}
if err := emit(dep); err != nil {
return err
}
}
ty.WriteHeader(h)
ty.WriteCPP(cppFiles[ty.File()])
return nil
}
// emit message types.
// Referenced types will be transitively emitted.
for _, s := range types {
switch s.File() {
case request, response, event:
if err := emit(s); err != nil {
return err
}
}
}
h.WriteString(headerEpilogue)
for _, f := range cppFiles {
f.WriteString(cppEpilogue)
}
return nil
}
func loadJSONFile(url string, obj interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.NewDecoder(bytes.NewReader(data)).Decode(obj); err != nil {
return err
}
return nil
}
func outputPaths() (string, cppFilePaths) {
_, thisFile, _, _ := runtime.Caller(1)
thisDir := path.Dir(thisFile)
h := path.Join(thisDir, "../../include/dap/protocol.h")
cpp := cppFilePaths{
request: path.Join(thisDir, "../../src/protocol_requests.cpp"),
response: path.Join(thisDir, "../../src/protocol_response.cpp"),
event: path.Join(thisDir, "../../src/protocol_events.cpp"),
types: path.Join(thisDir, "../../src/protocol_types.cpp"),
}
return h, cpp
}

View File

@ -104,14 +104,16 @@ func main() {
} }
} }
// root object of the parsed schema
type root struct { type root struct {
Schema string `json:"$schema"` Schema string `json:"$schema"`
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Description string `json:"description"`
Ty string `json:"type"` Ty string `json:"type"`
Definitions map[string]definition `json:"definitions"` Definitions map[string]*definition `json:"definitions"`
} }
// definitions() returns a lexicographically-stored list of named definitions
func (r *root) definitions() []namedDefinition { func (r *root) definitions() []namedDefinition {
sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions)) sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions))
for name, def := range r.Definitions { for name, def := range r.Definitions {
@ -121,6 +123,8 @@ func (r *root) definitions() []namedDefinition {
return sortedDefinitions return sortedDefinitions
} }
// getRef() returns the namedDefinition with the given reference string
// References have the form '#/definitions/<name>'
func (r *root) getRef(ref string) (namedDefinition, error) { func (r *root) getRef(ref string) (namedDefinition, error) {
if !strings.HasPrefix(ref, "#/definitions/") { if !strings.HasPrefix(ref, "#/definitions/") {
return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref) return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref)
@ -133,164 +137,95 @@ func (r *root) getRef(ref string) (namedDefinition, error) {
return namedDefinition{name, def}, nil return namedDefinition{name, def}, nil
} }
// namedDefinition is a [name, definition] pair
type namedDefinition struct { type namedDefinition struct {
name string name string // name as defined in the schema
def definition def *definition // definition node
} }
// definition is the core JSON object type in the schema, describing requests,
// responses, events, properties and more.
type definition struct { type definition struct {
Ty string `json:"type"` Ty interface{} `json:"type"`
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Items *definition `json:"items"`
Properties properties `json:"properties"` Description string `json:"description"`
Required []string `json:"required"` Properties properties `json:"properties"`
AllOf []definition `json:"allOf"` Required []string `json:"required"`
Ref string `json:"$ref"` AllOf []*definition `json:"allOf"`
Ref string `json:"$ref"`
OpenEnum []string `json:"_enum"`
ClosedEnum []string `json:"enum"`
// The resolved C++ type of the definition
cppType cppType
} }
type properties map[string]property // properties is a map of property name to the property definition
type properties map[string]*definition
func (p *properties) foreach(cb func(string, property) error) error { // foreach() calls cb for each property in the map. cb is called in
type namedProperty struct { // lexicographically-stored order for deterministic processing.
name string func (p *properties) foreach(cb func(string, *definition) error) error {
property property sorted := make([]namedDefinition, 0, len(*p))
}
sorted := make([]namedProperty, 0, len(*p))
for name, property := range *p { for name, property := range *p {
sorted = append(sorted, namedProperty{name, property}) sorted = append(sorted, namedDefinition{name, property})
} }
sort.Slice(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name }) sort.Slice(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name })
for _, entry := range sorted { for _, entry := range sorted {
if err := cb(entry.name, entry.property); err != nil { if err := cb(entry.name, entry.def); err != nil {
return err return err
} }
} }
return nil return nil
} }
type property struct { // cppField describes a single C++ field of a C++ structure
typed
Description string `json:"description"`
}
func (p *property) properties(r *root) (properties, []string, error) {
if p.Ref == "" {
return p.Properties, p.Required, nil
}
d, err := r.getRef(p.Ref)
if err != nil {
return nil, nil, err
}
return d.def.Properties, d.def.Required, nil
}
type typed struct {
Ty interface{} `json:"type"`
Items *typed `json:"items"`
Ref string `json:"$ref"`
Properties properties `json:"properties"`
Required []string `json:"required"`
ClosedEnum []string `json:"enum"`
OpenEnum []string `json:"_enum"`
}
func (t typed) typename(r *root, refs *[]string) (string, error) {
if t.Ref != "" {
d, err := r.getRef(t.Ref)
if err != nil {
return "", err
}
*refs = append(*refs, d.name)
return d.name, nil
}
if t.Ty == nil {
return "", fmt.Errorf("No type specified")
}
var typeof func(v reflect.Value) (string, error)
typeof = func(v reflect.Value) (string, error) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.String:
ty := v.Interface().(string)
switch ty {
case "boolean", "string", "integer", "number", "object", "null":
return ty, nil
case "array":
if t.Items != nil {
el, err := t.Items.typename(r, refs)
if err != nil {
return "", err
}
return fmt.Sprintf("array<%s>", el), nil
}
return "array<any>", nil
default:
return "", fmt.Errorf("Unhandled property type '%v'", ty)
}
case reflect.Slice, reflect.Array:
ty := "variant<"
for i := 0; i < v.Len(); i++ {
if i > 0 {
ty += ", "
}
el, err := typeof(v.Index(i))
if err != nil {
return "", err
}
ty += el
}
ty += ">"
return ty, nil
}
return "", fmt.Errorf("Unsupported type '%v' kind: %v", v.Interface(), v.Kind())
}
return typeof(reflect.ValueOf(t.Ty))
}
type cppField struct { type cppField struct {
desc string
ty string
name string
defaultValue string
optional bool
}
type cppStruct struct {
desc string desc string
ty cppType
name string name string
typename string optional bool
base string
fields []cppField
deps []string
emit bool
typedefs []cppTypedef
ty structType
} }
type cppTypedef struct { // cppType is an interface for all C++ generated types
from string type cppType interface {
to string // Name() returns the type name, used to refer to the type
Name() string
// Dependencies() returns a list of dependent types, which must be emitted
// before this type
Dependencies() []cppType
// File() returns the cppTargetFile that this type should be written to
File() cppTargetFile
// Description() returns the type description as parsed from the schema
Description() string
// DefaultValue() returns the default value that should be used for any
// fields of this type
DefaultValue() string
// WriteHeader() writes the type definition to the given .h file writer
WriteHeader(w io.Writer)
// WriteHeader() writes the type definition to the given .cpp file writer
WriteCPP(w io.Writer)
} }
func sanitize(s string) string { // cppStruct implements the cppType interface, describing a C++ structure
s = strings.Trim(s, "_") type cppStruct struct {
switch s { name string // C++ type name
case "default": protoname string // DAP name
return "def" desc string // Description
default: base string // Base class name
return s fields []cppField // All fields of the structure
} deps []cppType // Types this structure depends on
typedefs []cppTypedef // All nested typedefs
file cppTargetFile // The files this type should be written to
} }
func (s *cppStruct) writeHeader(w io.Writer) { func (s *cppStruct) Name() string { return s.name }
func (s *cppStruct) Dependencies() []cppType { return s.deps }
func (s *cppStruct) File() cppTargetFile { return s.file }
func (s *cppStruct) Description() string { return s.desc }
func (s *cppStruct) DefaultValue() string { return "" }
func (s *cppStruct) WriteHeader(w io.Writer) {
if s.desc != "" { if s.desc != "" {
io.WriteString(w, "// ") io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(s.desc, "\n", "\n// ")) io.WriteString(w, strings.ReplaceAll(s.desc, "\n", "\n// "))
@ -309,7 +244,7 @@ func (s *cppStruct) writeHeader(w io.Writer) {
io.WriteString(w, "\n using ") io.WriteString(w, "\n using ")
io.WriteString(w, t.from) io.WriteString(w, t.from)
io.WriteString(w, " = ") io.WriteString(w, " = ")
io.WriteString(w, t.to) io.WriteString(w, t.to.Name())
io.WriteString(w, ";") io.WriteString(w, ";")
} }
@ -321,16 +256,16 @@ func (s *cppStruct) writeHeader(w io.Writer) {
io.WriteString(w, "\n ") io.WriteString(w, "\n ")
if f.optional { if f.optional {
io.WriteString(w, "optional<") io.WriteString(w, "optional<")
io.WriteString(w, f.ty) io.WriteString(w, f.ty.Name())
io.WriteString(w, ">") io.WriteString(w, ">")
} else { } else {
io.WriteString(w, f.ty) io.WriteString(w, f.ty.Name())
} }
io.WriteString(w, " ") io.WriteString(w, " ")
io.WriteString(w, sanitize(f.name)) io.WriteString(w, sanitize(f.name))
if !f.optional && f.defaultValue != "" { if !f.optional && f.ty.DefaultValue() != "" {
io.WriteString(w, " = ") io.WriteString(w, " = ")
io.WriteString(w, f.defaultValue) io.WriteString(w, f.ty.DefaultValue())
} }
io.WriteString(w, ";") io.WriteString(w, ";")
} }
@ -341,13 +276,12 @@ func (s *cppStruct) writeHeader(w io.Writer) {
io.WriteString(w, s.name) io.WriteString(w, s.name)
io.WriteString(w, ");\n\n") io.WriteString(w, ");\n\n")
} }
func (s *cppStruct) WriteCPP(w io.Writer) {
func (s *cppStruct) writeCPP(w io.Writer) {
// typeinfo // typeinfo
io.WriteString(w, "DAP_IMPLEMENT_STRUCT_TYPEINFO(") io.WriteString(w, "DAP_IMPLEMENT_STRUCT_TYPEINFO(")
io.WriteString(w, s.name) io.WriteString(w, s.name)
io.WriteString(w, ",\n \"") io.WriteString(w, ",\n \"")
io.WriteString(w, s.typename) io.WriteString(w, s.protoname)
io.WriteString(w, "\"") io.WriteString(w, "\"")
for _, f := range s.fields { for _, f := range s.fields {
io.WriteString(w, ",\n ") io.WriteString(w, ",\n ")
@ -360,7 +294,277 @@ func (s *cppStruct) writeCPP(w io.Writer) {
io.WriteString(w, ");\n\n") io.WriteString(w, ");\n\n")
} }
func buildStructs(r *root) ([]*cppStruct, error) { // cppStruct implements the cppType interface, describing a C++ typedef
type cppTypedef struct {
from string // Name of the typedef
to cppType // Target of the typedef
desc string // Description
}
func (ty *cppTypedef) Name() string { return ty.from }
func (ty *cppTypedef) Dependencies() []cppType { return []cppType{ty.to} }
func (ty *cppTypedef) File() cppTargetFile { return types }
func (ty *cppTypedef) Description() string { return ty.desc }
func (ty *cppTypedef) DefaultValue() string { return ty.to.DefaultValue() }
func (ty *cppTypedef) WriteHeader(w io.Writer) {
if ty.desc != "" {
io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(ty.desc, "\n", "\n// "))
io.WriteString(w, "\n")
}
io.WriteString(w, "using ")
io.WriteString(w, ty.from)
io.WriteString(w, " = ")
io.WriteString(w, ty.to.Name())
io.WriteString(w, ";\n\n")
}
func (ty *cppTypedef) WriteCPP(w io.Writer) {}
// cppStruct implements the cppType interface, describing a basic C++ type
type cppBasicType struct {
name string // Type name
desc string // Description
deps []cppType // Types this type depends on
defaultValue string // Default value for fields of this type
}
func (ty *cppBasicType) Name() string { return ty.name }
func (ty *cppBasicType) Dependencies() []cppType { return ty.deps }
func (ty *cppBasicType) File() cppTargetFile { return types }
func (ty *cppBasicType) Description() string { return ty.desc }
func (ty *cppBasicType) DefaultValue() string { return ty.defaultValue }
func (ty *cppBasicType) WriteHeader(w io.Writer) {}
func (ty *cppBasicType) WriteCPP(w io.Writer) {}
// sanitize() returns the given identifier transformed into a legal C++ identifier
func sanitize(s string) string {
s = strings.Trim(s, "_")
switch s {
case "default":
return "def"
default:
return s
}
}
// appendEnumDetails() appends any enumerator details to the given description string.
func appendEnumDetails(desc string, openEnum []string, closedEnum []string) string {
if len(closedEnum) > 0 {
desc += "\n\nMust be one of the following enumeration values:\n"
for i, enum := range closedEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
if len(openEnum) > 0 {
desc += "\n\nMay be one of the following enumeration values:\n"
for i, enum := range openEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
return desc
}
// buildRootStruct() populates the cppStruct type with information found in def.
// buildRootStruct() must only be called after all the root definitions have had
// a type constructed (however, not necessarily fully populated)
func (r *root) buildRootStruct(ty *cppStruct, def *definition) error {
if len(def.AllOf) > 1 && def.AllOf[0].Ref != "" {
ref, err := r.getRef(def.AllOf[0].Ref)
if err != nil {
return err
}
ty.base = ref.name
if len(def.AllOf) > 2 {
return fmt.Errorf("Cannot handle allOf with more than 2 entries")
}
def = def.AllOf[1]
}
if def.Ty != "object" {
return fmt.Errorf("Definion '%v' was of unexpected type '%v'", ty.name, def.Ty)
}
ty.desc = def.Description
var body *definition
var err error
switch ty.base {
case "Request":
if arguments, ok := def.Properties["arguments"]; ok {
body = arguments
}
if command, ok := def.Properties["command"]; ok {
ty.protoname = command.ClosedEnum[0]
}
responseName := strings.TrimSuffix(ty.name, "Request") + "Response"
responseDef := r.Definitions[responseName]
responseTy := responseDef.cppType
if responseTy == nil {
return fmt.Errorf("Failed to find response type '%v'", responseName)
}
ty.deps = append(ty.deps, responseTy)
ty.typedefs = append(ty.typedefs, cppTypedef{from: "Response", to: responseTy})
ty.file = request
case "Response":
body = def.Properties["body"]
ty.file = response
case "Event":
body = def.Properties["body"]
if command, ok := def.Properties["event"]; ok {
ty.protoname = command.ClosedEnum[0]
}
ty.file = event
default:
body = def
ty.file = types
}
if err != nil {
return err
}
if body == nil {
return nil
}
if body.Ref != "" {
ref, err := r.getRef(body.Ref)
if err != nil {
return err
}
body = ref.def
}
required := make(map[string]bool, len(body.Required))
for _, r := range body.Required {
required[r] = true
}
if err = body.Properties.foreach(func(propName string, property *definition) error {
propTy, err := r.getType(property)
if err != nil {
return fmt.Errorf("While processing %v.%v: %v", ty.name, propName, err)
}
optional := !required[propName]
desc := appendEnumDetails(property.Description, property.OpenEnum, property.ClosedEnum)
ty.fields = append(ty.fields, cppField{
desc: desc,
ty: propTy,
name: propName,
optional: optional,
})
ty.deps = append(ty.deps, propTy)
return nil
}); err != nil {
return err
}
return nil
}
// getType() returns the cppType for the given definition
func (r *root) getType(def *definition) (builtType cppType, err error) {
if def.cppType != nil {
return def.cppType, nil
}
defer func() { def.cppType = builtType }()
if def.Ref != "" {
ref, err := r.getRef(def.Ref)
if err != nil {
return nil, err
}
return ref.def.cppType, nil
}
v := reflect.ValueOf(def.Ty)
if v.Kind() == reflect.Interface {
v = v.Elem()
}
var typeof func(reflect.Value) (cppType, error)
typeof = func(v reflect.Value) (cppType, error) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.String:
ty := v.Interface().(string)
switch ty {
case "string":
desc := appendEnumDetails(def.Description, nil, def.ClosedEnum)
defaultValue := ""
if len(def.ClosedEnum) > 0 {
defaultValue = `"` + def.ClosedEnum[0] + `"`
}
ty := &cppBasicType{
name: ty,
defaultValue: defaultValue,
desc: desc,
}
return ty, nil
case "object", "boolean", "integer", "number", "null":
ty := &cppBasicType{
name: ty,
desc: def.Description,
}
return ty, nil
case "array":
name := "array<any>"
deps := []cppType{}
if def.Items != nil {
elTy, err := r.getType(def.Items)
if err != nil {
return nil, err
}
name = fmt.Sprintf("array<%s>", elTy.Name())
deps = append(deps, elTy)
}
return &cppBasicType{
name: name,
desc: def.Description,
deps: deps,
}, nil
default:
return nil, fmt.Errorf("Unhandled property type '%v'", ty)
}
case reflect.Slice, reflect.Array:
args := []string{}
deps := []cppType{}
for i := 0; i < v.Len(); i++ {
elTy, err := typeof(v.Index(i))
if err != nil {
return nil, err
}
deps = append(deps, elTy)
args = append(args, elTy.Name())
}
return &cppBasicType{
name: "variant<" + strings.Join(args, ", ") + ">",
desc: def.Description,
deps: deps,
}, nil
}
return nil, fmt.Errorf("Unsupported type '%v' kind: %v", v.Interface(), v.Kind())
}
return typeof(v)
}
// buildTypes() builds all the reachable types found in the schema, returning
// all the root, named definition types.
func (r *root) buildTypes() ([]cppType, error) {
ignore := map[string]bool{ ignore := map[string]bool{
// These are handled internally. // These are handled internally.
"ProtocolMessage": true, "ProtocolMessage": true,
@ -369,166 +573,104 @@ func buildStructs(r *root) ([]*cppStruct, error) {
"Response": true, "Response": true,
} }
out := []*cppStruct{} // Step 1: Categorize all the named definitions by type.
structDefs := []namedDefinition{}
enumDefs := []namedDefinition{}
for _, entry := range r.definitions() { for _, entry := range r.definitions() {
defName, def := entry.name, entry.def if ignore[entry.name] {
if ignore[defName] {
continue continue
} }
switch entry.def.Ty {
base := "" case nil, "object":
if len(def.AllOf) > 1 && def.AllOf[0].Ref != "" { structDefs = append(structDefs, entry)
ref, err := r.getRef(def.AllOf[0].Ref) case "string":
if err != nil { enumDefs = append(enumDefs, entry)
return nil, err
}
base = ref.name
if len(def.AllOf) > 2 {
return nil, fmt.Errorf("Cannot handle allOf with more than 2 entries")
}
def = def.AllOf[1]
}
s := cppStruct{
desc: def.Description,
name: defName,
base: base,
}
var props properties
var required []string
var err error
switch base {
case "Request":
if arguments, ok := def.Properties["arguments"]; ok {
props, required, err = arguments.properties(r)
}
if command, ok := def.Properties["command"]; ok {
s.typename = command.ClosedEnum[0]
}
response := strings.TrimSuffix(s.name, "Request") + "Response"
s.deps = append(s.deps, response)
s.typedefs = append(s.typedefs, cppTypedef{"Response", response})
s.emit = true
s.ty = request
case "Response":
if body, ok := def.Properties["body"]; ok {
props, required, err = body.properties(r)
}
s.emit = true
s.ty = response
case "Event":
if body, ok := def.Properties["body"]; ok {
props, required, err = body.properties(r)
}
if command, ok := def.Properties["event"]; ok {
s.typename = command.ClosedEnum[0]
}
s.emit = true
s.ty = event
default: default:
props = def.Properties return nil, fmt.Errorf("Unhandled top-level definition type: %v", entry.def.Ty)
required = def.Required
s.ty = types
} }
}
// Step 2: Construct, but do not build all the named object types (yet).
// This allows the getType() function to resolve to the cppStruct types,
// even if they're not built yet.
out := []cppType{}
for _, entry := range structDefs {
entry.def.cppType = &cppStruct{
name: entry.name,
}
out = append(out, entry.def.cppType)
}
// Step 3: Resolve all the enum types
for _, entry := range enumDefs {
enumTy, err := r.getType(entry.def)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ty := &cppTypedef{
from: entry.name,
to: enumTy,
desc: enumTy.Description(),
}
entry.def.cppType = ty
out = append(out, entry.def.cppType)
}
if err = props.foreach(func(propName string, property property) error { // Step 4: Resolve all the structure types
ty, err := property.typename(r, &s.deps) for _, s := range structDefs {
if err != nil { if err := r.buildRootStruct(s.def.cppType.(*cppStruct), s.def); err != nil {
return fmt.Errorf("While processing %v.%v: %v", defName, propName, err)
}
optional := true
for _, r := range required {
if propName == r {
optional = false
}
}
desc := property.Description
defaultValue := ""
if len(property.ClosedEnum) > 0 {
desc += "\n\nMust be one of the following enumeration values:\n"
for i, enum := range property.ClosedEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
defaultValue = `"` + property.ClosedEnum[0] + `"`
}
if len(property.OpenEnum) > 0 {
desc += "\n\nMay be one of the following enumeration values:\n"
for i, enum := range property.OpenEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
s.fields = append(s.fields, cppField{
desc: desc,
defaultValue: defaultValue,
ty: ty,
name: propName,
optional: optional,
})
return nil
}); err != nil {
return nil, err return nil, err
} }
out = append(out, &s)
} }
return out, nil return out, nil
} }
type structType string // cppTargetFile is an enumerator of target files that types should be written
// to.
type cppTargetFile string
const ( const (
request = structType("request") request = cppTargetFile("request") // protocol_request.cpp
response = structType("response") response = cppTargetFile("response") // protocol_response.cpp
event = structType("event") event = cppTargetFile("event") // protocol_events.cpp
types = structType("types") types = cppTargetFile("types") // protocol_types.cpp
) )
type cppFilePaths map[structType]string // cppTargetFilePaths is a map of cppTargetFile to the target file path
type cppTargetFilePaths map[cppTargetFile]string
type cppFiles map[structType]*os.File // cppFiles is a map of cppTargetFile to the open file
type cppFiles map[cppTargetFile]*os.File
// run() loads and parses the package and protocol JSON files, generates the
// protocol types from the schema, writes the types to the C++ files, then runs
// clang-format on each.
func run() error { func run() error {
pkg := struct { pkg := struct {
Version string `json:"version"` Version string `json:"version"`
}{} }{}
if err := loadJSONFile(packageURL, &pkg); err != nil { if err := loadJSONFile(packageURL, &pkg); err != nil {
return err return fmt.Errorf("Failed to load JSON file from '%v': %w", packageURL, err)
} }
protocol := root{} protocol := root{}
if err := loadJSONFile(protocolURL, &protocol); err != nil { if err := loadJSONFile(protocolURL, &protocol); err != nil {
return err return fmt.Errorf("Failed to load JSON file from '%v': %w", protocolURL, err)
} }
hPath, cppPaths := outputPaths() hPath, cppPaths := outputPaths()
if err := emitFiles(&protocol, hPath, cppPaths, pkg.Version); err != nil { if err := emitFiles(&protocol, hPath, cppPaths, pkg.Version); err != nil {
return err return fmt.Errorf("Failed to emit files: %w", err)
} }
if clangfmt, err := exec.LookPath("clang-format"); err == nil { if clangfmt, err := exec.LookPath("clang-format"); err == nil {
if err := exec.Command(clangfmt, "-i", hPath).Run(); err != nil { if out, err := exec.Command(clangfmt, "-i", hPath).CombinedOutput(); err != nil {
return err return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", hPath, string(out), err)
} }
for _, p := range cppPaths { for _, p := range cppPaths {
if err := exec.Command(clangfmt, "-i", p).Run(); err != nil { if out, err := exec.Command(clangfmt, "-i", p).CombinedOutput(); err != nil {
return err return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", p, string(out), err)
} }
} }
} else { } else {
@ -538,13 +680,16 @@ func run() error {
return nil return nil
} }
func emitFiles(r *root, hPath string, cppPaths map[structType]string, version string) error { // emitFiles() opens each of the C++ files, generates the cppType definitions
// from the schema root, then writes the types to the C++ files in dependency
// order.
func emitFiles(r *root, hPath string, cppPaths map[cppTargetFile]string, version string) error {
h, err := os.Create(hPath) h, err := os.Create(hPath)
if err != nil { if err != nil {
return err return err
} }
defer h.Close() defer h.Close()
cppFiles := map[structType]*os.File{} cppFiles := map[cppTargetFile]*os.File{}
for ty, p := range cppPaths { for ty, p := range cppPaths {
f, err := os.Create(p) f, err := os.Create(p)
if err != nil { if err != nil {
@ -559,36 +704,42 @@ func emitFiles(r *root, hPath string, cppPaths map[structType]string, version st
f.WriteString(strings.ReplaceAll(cppPrologue, versionTag, version)) f.WriteString(strings.ReplaceAll(cppPrologue, versionTag, version))
} }
structs, err := buildStructs(r) types, err := r.buildTypes()
if err != nil { if err != nil {
return err return err
} }
structsByName := map[string]*cppStruct{} typesByName := map[string]cppType{}
for _, s := range structs { for _, s := range types {
structsByName[s.name] = s typesByName[s.Name()] = s
} }
seen := map[string]bool{} seen := map[string]bool{}
var emit func(*cppStruct) var emit func(cppType) error
emit = func(s *cppStruct) { emit = func(ty cppType) error {
if seen[s.name] { name := ty.Name()
return if seen[name] {
return nil
} }
seen[s.name] = true seen[name] = true
for _, dep := range s.deps { for _, dep := range ty.Dependencies() {
emit(structsByName[dep]) if err := emit(dep); err != nil {
return err
}
} }
s.writeHeader(h) ty.WriteHeader(h)
s.writeCPP(cppFiles[s.ty]) ty.WriteCPP(cppFiles[ty.File()])
return nil
} }
// emit message types. // emit message types.
// Referenced structs will be transitively emitted. // Referenced types will be transitively emitted.
for _, s := range structs { for _, s := range types {
switch s.ty { switch s.File() {
case request, response, event: case request, response, event:
emit(s) if err := emit(s); err != nil {
return err
}
} }
} }
@ -600,6 +751,8 @@ func emitFiles(r *root, hPath string, cppPaths map[structType]string, version st
return nil return nil
} }
// loadJSONFile() loads the JSON file from the given URL using a HTTP GET
// request.
func loadJSONFile(url string, obj interface{}) error { func loadJSONFile(url string, obj interface{}) error {
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
@ -615,11 +768,12 @@ func loadJSONFile(url string, obj interface{}) error {
return nil return nil
} }
func outputPaths() (string, cppFilePaths) { // outputPaths() returns a path to the target C++ .h file and .cpp files
func outputPaths() (string, cppTargetFilePaths) {
_, thisFile, _, _ := runtime.Caller(1) _, thisFile, _, _ := runtime.Caller(1)
thisDir := path.Dir(thisFile) thisDir := path.Dir(thisFile)
h := path.Join(thisDir, "../../include/dap/protocol.h") h := path.Join(thisDir, "../../include/dap/protocol.h")
cpp := cppFilePaths{ cpp := cppTargetFilePaths{
request: path.Join(thisDir, "../../src/protocol_requests.cpp"), request: path.Join(thisDir, "../../src/protocol_requests.cpp"),
response: path.Join(thisDir, "../../src/protocol_response.cpp"), response: path.Join(thisDir, "../../src/protocol_response.cpp"),
event: path.Join(thisDir, "../../src/protocol_events.cpp"), event: path.Join(thisDir, "../../src/protocol_events.cpp"),