// 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 }