// 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) } } // 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"` } // 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 { sortedDefinitions = append(sortedDefinitions, namedDefinition{name, def}) } sort.Slice(sortedDefinitions, func(i, j int) bool { return sortedDefinitions[i].name < sortedDefinitions[j].name }) 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) } 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 } // namedDefinition is a [name, definition] pair type namedDefinition struct { 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 interface{} `json:"type"` Title string `json:"title"` Items *definition `json:"items"` Description string `json:"description"` Properties properties `json:"properties"` Required []string `json:"required"` OneOf []*definition `json:"oneOf"` 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 } // properties is a map of property name to the property definition type properties map[string]*definition // 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, 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.def); err != nil { return err } } return nil } // cppField describes a single C++ field of a C++ structure type cppField struct { desc string ty cppType name string optional bool } // 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) } // 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) 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// ")) 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.Name()) 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.Name()) io.WriteString(w, ">") } else { io.WriteString(w, f.ty.Name()) } io.WriteString(w, " ") io.WriteString(w, sanitize(f.name)) if !f.optional && f.ty.DefaultValue() != "" { io.WriteString(w, " = ") io.WriteString(w, f.ty.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.protoname) 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") } // 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 } if len(def.OneOf) != 0 { args := []string{} deps := []cppType{} for i := 0; i < len(def.OneOf); i++ { if def.OneOf[i] == nil { return nil, fmt.Errorf("Item %d in oneOf is nil", i) } elTy, err := r.getType(def.OneOf[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 } 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, "Request": true, "Event": true, "Response": true, } // Step 1: Categorize all the named definitions by type. structDefs := []namedDefinition{} enumDefs := []namedDefinition{} for _, entry := range r.definitions() { if ignore[entry.name] { continue } switch entry.def.Ty { case nil, "object": structDefs = append(structDefs, entry) case "string": enumDefs = append(enumDefs, entry) default: 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) } // 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 } } return out, nil } // cppTargetFile is an enumerator of target files that types should be written // to. type cppTargetFile string const ( request = cppTargetFile("request") // protocol_request.cpp response = cppTargetFile("response") // protocol_response.cpp event = cppTargetFile("event") // protocol_events.cpp types = cppTargetFile("types") // protocol_types.cpp ) // cppTargetFilePaths is a map of cppTargetFile to the target file path type cppTargetFilePaths map[cppTargetFile]string // 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 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 } // 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[cppTargetFile]*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 _, dep := range ty.Dependencies() { 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 } // 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 { 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 } // 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 := 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"), types: path.Join(thisDir, "../../src/protocol_types.cpp"), } return h, cpp }