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