diff --git a/include/dap/protocol.h b/include/dap/protocol.h index 0d13547..1781ec0 100644 --- a/include/dap/protocol.h +++ b/include/dap/protocol.h @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.43.0 +// DAP version 1.46.0 #ifndef dap_protocol_h #define dap_protocol_h @@ -56,14 +56,15 @@ struct AttachRequest : public Request { DAP_DECLARE_STRUCT_TYPEINFO(AttachRequest); // Names of checksum algorithms that may be supported by a debug adapter. -struct ChecksumAlgorithm {}; - -DAP_DECLARE_STRUCT_TYPEINFO(ChecksumAlgorithm); +// +// Must be one of the following enumeration values: +// 'MD5', 'SHA1', 'SHA256', 'timestamp' +using ChecksumAlgorithm = string; // The checksum of an item calculated by the specified algorithm. struct Checksum { // The algorithm used to calculate this checksum. - ChecksumAlgorithm algorithm; + ChecksumAlgorithm algorithm = "MD5"; // Value of the checksum. string checksum; }; @@ -272,9 +273,16 @@ DAP_DECLARE_STRUCT_TYPEINFO(ColumnDescriptor); // An ExceptionBreakpointsFilter is shown in the UI as an filter option for // configuring how exceptions are dealt with. 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 conditionDescription; // Initial value of the filter option. If not specified a value 'false' is // assumed. optional 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 description; // The internal ID of the filter option. This value is passed to the // 'setExceptionBreakpoints' request. string filter; @@ -318,7 +326,7 @@ struct Capabilities { // The debug adapter supports data breakpoints. optional supportsDataBreakpoints; // 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. optional supportsDelayedStackTraceLoading; // 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 // clients have specific icons for all of them. -struct CompletionItemType {}; - -DAP_DECLARE_STRUCT_TYPEINFO(CompletionItemType); +// +// Must be one of the following enumeration values: +// '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. struct CompletionItem { @@ -519,9 +530,10 @@ struct ContinuedEvent : public Event { DAP_DECLARE_STRUCT_TYPEINFO(ContinuedEvent); // This enumeration defines all possible access types for data breakpoints. -struct DataBreakpointAccessType {}; - -DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointAccessType); +// +// Must be one of the following enumeration values: +// 'read', 'write', 'readWrite' +using DataBreakpointAccessType = string; // Response to 'dataBreakpointInfo' request. struct DataBreakpointInfoResponse : public Response { @@ -548,7 +560,7 @@ DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoResponse); struct DataBreakpointInfoRequest : public Request { using Response = DataBreakpointInfoResponse; // The name of the Variable's child to obtain data breakpoint information for. - // If variableReference isn’t provided, this can be an expression. + // If variablesReference isn’t provided, this can be an expression. string name; // Reference to the Variable container if the data breakpoint is requested for // 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, // unhandled: breaks when exception unhandled, // userUnhandled: breaks if the exception is not handled by user code. -struct ExceptionBreakMode {}; - -DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakMode); +// +// Must be one of the following enumeration values: +// 'never', 'always', 'unhandled', 'userUnhandled' +using ExceptionBreakMode = string; // Detailed information about an exception that has occurred. struct ExceptionDetails { @@ -803,7 +816,7 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionDetails); // Response to 'exceptionInfo' request. struct ExceptionInfoResponse : public Response { // Mode that caused the exception notification to be raised. - ExceptionBreakMode breakMode; + ExceptionBreakMode breakMode = "never"; // Descriptive text for the exception provided by the debug adapter. optional description; // Detailed information about the exception. @@ -934,7 +947,7 @@ struct InitializeResponse : public Response { // The debug adapter supports data breakpoints. optional supportsDataBreakpoints; // 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. optional supportsDelayedStackTraceLoading; // The debug adapter supports the 'disassemble' request. @@ -1064,9 +1077,7 @@ struct InitializedEvent : public Event {}; DAP_DECLARE_STRUCT_TYPEINFO(InitializedEvent); // Logical areas that can be invalidated by the 'invalidated' event. -struct InvalidatedAreas {}; - -DAP_DECLARE_STRUCT_TYPEINFO(InvalidatedAreas); +using InvalidatedAreas = string; // 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 @@ -1239,9 +1250,10 @@ DAP_DECLARE_STRUCT_TYPEINFO(NextResponse); // The granularity of one 'step' in the stepping requests 'next', 'stepIn', // 'stepOut', and 'stepBack'. -struct SteppingGranularity {}; - -DAP_DECLARE_STRUCT_TYPEINFO(SteppingGranularity); +// +// Must be one of the following enumeration values: +// 'statement', 'line', 'instruction' +using SteppingGranularity = string; // The request starts the debuggee to run again for one step. // 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. struct ExceptionOptions { // 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 // 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. @@ -1724,7 +1736,9 @@ DAP_DECLARE_STRUCT_TYPEINFO(ExceptionFilterOptions); // The request configures the debuggers response to thrown exceptions. // If an exception is configured to break, a 'stopped' event is fired (with // 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 { using Response = SetExceptionBreakpointsResponse; // Configuration options for selected exceptions. @@ -1948,6 +1962,10 @@ DAP_DECLARE_STRUCT_TYPEINFO(SourceRequest); // A Stackframe contains the source location. 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 canRestart; // The column within the line. If source is null or doesn't exist, column is 0 // and must be ignored. integer column; @@ -2023,10 +2041,11 @@ DAP_DECLARE_STRUCT_TYPEINFO(StackFrameFormat); // 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 -// levels arguments. For performance conscious clients stack frames can be -// retrieved in a piecemeal way with the startFrame and levels arguments. 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 +// levels arguments. For performance conscious clients and if the debug +// adapter's 'supportsDelayedStackTraceLoading' capability is true, stack frames +// can be retrieved in a piecemeal way with the startFrame and levels arguments. +// 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 // 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 @@ -2162,6 +2181,15 @@ struct StoppedEvent : public Event { // The full reason for the event, e.g. 'Paused on exception'. This string is // shown in the UI as is and must be translated. optional 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> hitBreakpointIds; // A value of true hints to the frontend that this event should not change the // focus. optional preserveFocusHint; diff --git a/src/protocol_events.cpp b/src/protocol_events.cpp index 3743481..1fbcb3e 100644 --- a/src/protocol_events.cpp +++ b/src/protocol_events.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.43.0 +// DAP version 1.46.0 #include "dap/protocol.h" @@ -102,6 +102,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(StoppedEvent, "stopped", DAP_FIELD(allThreadsStopped, "allThreadsStopped"), DAP_FIELD(description, "description"), + DAP_FIELD(hitBreakpointIds, "hitBreakpointIds"), DAP_FIELD(preserveFocusHint, "preserveFocusHint"), DAP_FIELD(reason, "reason"), DAP_FIELD(text, "text"), diff --git a/src/protocol_requests.cpp b/src/protocol_requests.cpp index c76b5ff..dc3e10a 100644 --- a/src/protocol_requests.cpp +++ b/src/protocol_requests.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.43.0 +// DAP version 1.46.0 #include "dap/protocol.h" diff --git a/src/protocol_response.cpp b/src/protocol_response.cpp index f96c3cf..038c69d 100644 --- a/src/protocol_response.cpp +++ b/src/protocol_response.cpp @@ -15,7 +15,7 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.43.0 +// DAP version 1.46.0 #include "dap/protocol.h" diff --git a/src/protocol_types.cpp b/src/protocol_types.cpp index a9e5355..2adaf97 100644 --- a/src/protocol_types.cpp +++ b/src/protocol_types.cpp @@ -15,14 +15,12 @@ // Generated with protocol_gen.go -- do not edit this file. // go run scripts/protocol_gen/protocol_gen.go // -// DAP version 1.43.0 +// DAP version 1.46.0 #include "dap/protocol.h" namespace dap { -DAP_IMPLEMENT_STRUCT_TYPEINFO(ChecksumAlgorithm, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(Checksum, "", DAP_FIELD(algorithm, "algorithm"), @@ -70,7 +68,10 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(ColumnDescriptor, DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter, "", + DAP_FIELD(conditionDescription, + "conditionDescription"), DAP_FIELD(def, "default"), + DAP_FIELD(description, "description"), DAP_FIELD(filter, "filter"), DAP_FIELD(label, "label"), DAP_FIELD(supportsCondition, @@ -122,8 +123,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO( DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions")); -DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItemType, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem, "", DAP_FIELD(label, "label"), @@ -135,8 +134,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem, DAP_FIELD(text, "text"), DAP_FIELD(type, "type")); -DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointAccessType, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction, "", 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(ExceptionBreakMode, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionDetails, "", DAP_FIELD(evaluateName, "evaluateName"), @@ -189,8 +184,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTarget, DAP_FIELD(label, "label"), DAP_FIELD(line, "line")); -DAP_IMPLEMENT_STRUCT_TYPEINFO(InvalidatedAreas, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(Module, "", DAP_FIELD(addressRange, "addressRange"), @@ -204,8 +197,6 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(Module, DAP_FIELD(symbolStatus, "symbolStatus"), DAP_FIELD(version, "version")); -DAP_IMPLEMENT_STRUCT_TYPEINFO(SteppingGranularity, ""); - DAP_IMPLEMENT_STRUCT_TYPEINFO(Scope, "", DAP_FIELD(column, "column"), @@ -267,6 +258,7 @@ DAP_IMPLEMENT_STRUCT_TYPEINFO(InstructionBreakpoint, DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame, "", + DAP_FIELD(canRestart, "canRestart"), DAP_FIELD(column, "column"), DAP_FIELD(endColumn, "endColumn"), DAP_FIELD(endLine, "endLine"), diff --git a/tools/protocol_gen/protocol_gen.bkp.go b/tools/protocol_gen/protocol_gen.bkp.go new file mode 100644 index 0000000..a2f6270 --- /dev/null +++ b/tools/protocol_gen/protocol_gen.bkp.go @@ -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 +#include +#include + +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", 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 +} diff --git a/tools/protocol_gen/protocol_gen.go b/tools/protocol_gen/protocol_gen.go index b23b622..624a76b 100644 --- a/tools/protocol_gen/protocol_gen.go +++ b/tools/protocol_gen/protocol_gen.go @@ -104,14 +104,16 @@ func main() { } } +// root object of the parsed schema 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"` + Schema string `json:"$schema"` + Title string `json:"title"` + Description string `json:"description"` + Ty string `json:"type"` + Definitions map[string]*definition `json:"definitions"` } +// definitions() returns a lexicographically-stored list of named definitions func (r *root) definitions() []namedDefinition { sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions)) for name, def := range r.Definitions { @@ -121,6 +123,8 @@ func (r *root) definitions() []namedDefinition { return sortedDefinitions } +// getRef() returns the namedDefinition with the given reference string +// References have the form '#/definitions/' func (r *root) getRef(ref string) (namedDefinition, error) { if !strings.HasPrefix(ref, "#/definitions/") { 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 } +// namedDefinition is a [name, definition] pair type namedDefinition struct { - name string - def definition + name string // name as defined in the schema + def *definition // definition node } +// definition is the core JSON object type in the schema, describing requests, +// responses, events, properties and more. 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"` + Ty interface{} `json:"type"` + Title string `json:"title"` + Items *definition `json:"items"` + Description string `json:"description"` + Properties properties `json:"properties"` + Required []string `json:"required"` + 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 { - type namedProperty struct { - name string - property property - } - sorted := make([]namedProperty, 0, len(*p)) +// foreach() calls cb for each property in the map. cb is called in +// lexicographically-stored order for deterministic processing. +func (p *properties) foreach(cb func(string, *definition) error) error { + sorted := make([]namedDefinition, 0, len(*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 }) 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 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", 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)) -} - +// cppField describes a single C++ field of a C++ structure type cppField struct { - desc string - ty string - name string - defaultValue string - optional bool -} - -type cppStruct struct { desc string + ty cppType name string - typename string - base string - fields []cppField - deps []string - emit bool - typedefs []cppTypedef - ty structType + optional bool } -type cppTypedef struct { - from string - to string +// cppType is an interface for all C++ generated types +type cppType interface { + // 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 { - s = strings.Trim(s, "_") - switch s { - case "default": - return "def" - default: - return s - } +// cppStruct implements the cppType interface, describing a C++ structure +type cppStruct struct { + name string // C++ type name + protoname string // DAP name + desc string // Description + base string // Base class name + 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 != "" { io.WriteString(w, "// ") 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, t.from) io.WriteString(w, " = ") - io.WriteString(w, t.to) + io.WriteString(w, t.to.Name()) io.WriteString(w, ";") } @@ -321,16 +256,16 @@ func (s *cppStruct) writeHeader(w io.Writer) { io.WriteString(w, "\n ") if f.optional { io.WriteString(w, "optional<") - io.WriteString(w, f.ty) + io.WriteString(w, f.ty.Name()) io.WriteString(w, ">") } else { - io.WriteString(w, f.ty) + io.WriteString(w, f.ty.Name()) } io.WriteString(w, " ") io.WriteString(w, sanitize(f.name)) - if !f.optional && f.defaultValue != "" { + if !f.optional && f.ty.DefaultValue() != "" { io.WriteString(w, " = ") - io.WriteString(w, f.defaultValue) + io.WriteString(w, f.ty.DefaultValue()) } io.WriteString(w, ";") } @@ -341,13 +276,12 @@ func (s *cppStruct) writeHeader(w io.Writer) { io.WriteString(w, s.name) io.WriteString(w, ");\n\n") } - -func (s *cppStruct) writeCPP(w io.Writer) { +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, s.protoname) io.WriteString(w, "\"") for _, f := range s.fields { io.WriteString(w, ",\n ") @@ -360,7 +294,277 @@ func (s *cppStruct) writeCPP(w io.Writer) { 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" + 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{ // These are handled internally. "ProtocolMessage": true, @@ -369,166 +573,104 @@ func buildStructs(r *root) ([]*cppStruct, error) { "Response": true, } - out := []*cppStruct{} + // Step 1: Categorize all the named definitions by type. + structDefs := []namedDefinition{} + enumDefs := []namedDefinition{} for _, entry := range r.definitions() { - defName, def := entry.name, entry.def - if ignore[defName] { + if ignore[entry.name] { continue } - - 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{"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 + switch entry.def.Ty { + case nil, "object": + structDefs = append(structDefs, entry) + case "string": + enumDefs = append(enumDefs, entry) default: - props = def.Properties - required = def.Required - s.ty = types + return nil, fmt.Errorf("Unhandled top-level definition type: %v", entry.def.Ty) } + } + + // 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 { 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 { - 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 := 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 { + // Step 4: Resolve all the structure types + for _, s := range structDefs { + if err := r.buildRootStruct(s.def.cppType.(*cppStruct), s.def); err != nil { return nil, err } - - out = append(out, &s) } return out, nil } -type structType string +// cppTargetFile is an enumerator of target files that types should be written +// to. +type cppTargetFile string const ( - request = structType("request") - response = structType("response") - event = structType("event") - types = structType("types") + request = cppTargetFile("request") // protocol_request.cpp + response = cppTargetFile("response") // protocol_response.cpp + event = cppTargetFile("event") // protocol_events.cpp + 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 { pkg := struct { Version string `json:"version"` }{} if err := loadJSONFile(packageURL, &pkg); err != nil { - return err + return fmt.Errorf("Failed to load JSON file from '%v': %w", packageURL, err) } protocol := root{} 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() 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 err := exec.Command(clangfmt, "-i", hPath).Run(); err != nil { - return err + 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 err := exec.Command(clangfmt, "-i", p).Run(); err != nil { - return err + 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 { @@ -538,13 +680,16 @@ func run() error { 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) if err != nil { return err } defer h.Close() - cppFiles := map[structType]*os.File{} + cppFiles := map[cppTargetFile]*os.File{} for ty, p := range cppPaths { f, err := os.Create(p) 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)) } - structs, err := buildStructs(r) + types, err := r.buildTypes() if err != nil { return err } - structsByName := map[string]*cppStruct{} - for _, s := range structs { - structsByName[s.name] = s + typesByName := map[string]cppType{} + for _, s := range types { + typesByName[s.Name()] = s } seen := map[string]bool{} - var emit func(*cppStruct) - emit = func(s *cppStruct) { - if seen[s.name] { - return + var emit func(cppType) error + emit = func(ty cppType) error { + name := ty.Name() + if seen[name] { + return nil } - seen[s.name] = true - for _, dep := range s.deps { - emit(structsByName[dep]) + seen[name] = true + for _, dep := range ty.Dependencies() { + if err := emit(dep); err != nil { + return err + } } - s.writeHeader(h) - s.writeCPP(cppFiles[s.ty]) + ty.WriteHeader(h) + ty.WriteCPP(cppFiles[ty.File()]) + return nil } // emit message types. - // Referenced structs will be transitively emitted. - for _, s := range structs { - switch s.ty { + // Referenced types will be transitively emitted. + for _, s := range types { + switch s.File() { 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 } +// loadJSONFile() loads the JSON file from the given URL using a HTTP GET +// request. func loadJSONFile(url string, obj interface{}) error { resp, err := http.Get(url) if err != nil { @@ -615,11 +768,12 @@ func loadJSONFile(url string, obj interface{}) error { 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) thisDir := path.Dir(thisFile) h := path.Join(thisDir, "../../include/dap/protocol.h") - cpp := cppFilePaths{ + cpp := cppTargetFilePaths{ request: path.Join(thisDir, "../../src/protocol_requests.cpp"), response: path.Join(thisDir, "../../src/protocol_response.cpp"), event: path.Join(thisDir, "../../src/protocol_events.cpp"),