feat(gen): port server generator from purego-libwayland wlgen
- commit
- 7ad88b7bbf04b9ae7fc43e12e958921cf8318f58
- author
- brice · · 2026-10-01 03:26 UTC
- parent
- 4a364ab
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +558 -0
A internal/gen/server/equivalence_test.go +115 -0
| @@ -0,0 +1,115 @@ | |||
| 1 | + | package server | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bufio" | |
| 5 | + | "os" | |
| 6 | + | "path/filepath" | |
| 7 | + | "strings" | |
| 8 | + | "testing" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/go-wayland-bindings/spec" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // TestEquivalence regenerates every protocol listed in the //go:generate lines | |
| 14 | + | // of the baseline purego-libwayland protocol/gen.go and requires byte-identical | |
| 15 | + | // output. It runs only when WGB_OLD_PUREGO points at a baseline checkout. | |
| 16 | + | func TestEquivalence(t *testing.T) { | |
| 17 | + | root := os.Getenv("WGB_OLD_PUREGO") | |
| 18 | + | if root == "" { | |
| 19 | + | t.Skip("WGB_OLD_PUREGO not set") | |
| 20 | + | } | |
| 21 | + | protoDir := filepath.Join(root, "protocol") | |
| 22 | + | f, err := os.Open(filepath.Join(protoDir, "gen.go")) | |
| 23 | + | if err != nil { | |
| 24 | + | t.Fatal(err) | |
| 25 | + | } | |
| 26 | + | defer f.Close() | |
| 27 | + | ||
| 28 | + | const prefix = "//go:generate go run ../cmd/wlgen " | |
| 29 | + | n := 0 | |
| 30 | + | sc := bufio.NewScanner(f) | |
| 31 | + | sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) | |
| 32 | + | for sc.Scan() { | |
| 33 | + | line := sc.Text() | |
| 34 | + | if !strings.HasPrefix(line, prefix) { | |
| 35 | + | continue | |
| 36 | + | } | |
| 37 | + | args := strings.Fields(strings.TrimPrefix(line, prefix)) | |
| 38 | + | n++ | |
| 39 | + | t.Run(line[len(prefix):], func(t *testing.T) { checkLine(t, protoDir, args) }) | |
| 40 | + | } | |
| 41 | + | if err := sc.Err(); err != nil { | |
| 42 | + | t.Fatal(err) | |
| 43 | + | } | |
| 44 | + | if n == 0 { | |
| 45 | + | t.Fatal("no //go:generate lines found") | |
| 46 | + | } | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | func loadXML(t *testing.T, path string) *spec.Protocol { | |
| 50 | + | t.Helper() | |
| 51 | + | b, err := os.ReadFile(path) | |
| 52 | + | if err != nil { | |
| 53 | + | t.Fatal(err) | |
| 54 | + | } | |
| 55 | + | p, err := spec.Parse(b) | |
| 56 | + | if err != nil { | |
| 57 | + | t.Fatalf("%s: %v", path, err) | |
| 58 | + | } | |
| 59 | + | return p | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | func checkLine(t *testing.T, protoDir string, args []string) { | |
| 63 | + | opts := Options{Imports: map[string]string{}, Owners: map[string]string{}, Tool: "wlgen"} | |
| 64 | + | var out, xmlPath string | |
| 65 | + | var importXML []string | |
| 66 | + | for i := 0; i < len(args); i++ { | |
| 67 | + | switch args[i] { | |
| 68 | + | case "-package", "-out", "-import", "-import-xml": | |
| 69 | + | if i+1 >= len(args) { | |
| 70 | + | t.Fatalf("flag %s missing value", args[i]) | |
| 71 | + | } | |
| 72 | + | v := args[i+1] | |
| 73 | + | i++ | |
| 74 | + | switch args[i-1] { | |
| 75 | + | case "-package": | |
| 76 | + | opts.Package = v | |
| 77 | + | case "-out": | |
| 78 | + | out = v | |
| 79 | + | case "-import": | |
| 80 | + | a, path, ok := strings.Cut(v, "=") | |
| 81 | + | if !ok { | |
| 82 | + | t.Fatalf("invalid -import %q", v) | |
| 83 | + | } | |
| 84 | + | opts.Imports[a] = path | |
| 85 | + | case "-import-xml": | |
| 86 | + | importXML = append(importXML, v) | |
| 87 | + | } | |
| 88 | + | default: | |
| 89 | + | xmlPath = args[i] | |
| 90 | + | } | |
| 91 | + | } | |
| 92 | + | if opts.Package == "" || out == "" || xmlPath == "" { | |
| 93 | + | t.Fatalf("incomplete generate line: %v", args) | |
| 94 | + | } | |
| 95 | + | for _, s := range importXML { | |
| 96 | + | a, path, ok := strings.Cut(s, "=") | |
| 97 | + | if !ok || opts.Imports[a] == "" { | |
| 98 | + | t.Fatalf("invalid -import-xml %q", s) | |
| 99 | + | } | |
| 100 | + | for _, it := range loadXML(t, filepath.Join(protoDir, path)).Interfaces { | |
| 101 | + | opts.Owners[it.Name] = a | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | got, err := Generate(loadXML(t, filepath.Join(protoDir, xmlPath)), opts) | |
| 105 | + | if err != nil { | |
| 106 | + | t.Fatal(err) | |
| 107 | + | } | |
| 108 | + | want, err := os.ReadFile(filepath.Join(protoDir, out)) | |
| 109 | + | if err != nil { | |
| 110 | + | t.Fatal(err) | |
| 111 | + | } | |
| 112 | + | if string(got) != string(want) { | |
| 113 | + | t.Errorf("generated output differs from %s (got %d bytes, want %d)", out, len(got), len(want)) | |
| 114 | + | } | |
| 115 | + | } | |
| @@ -0,0 +1,115 @@ | |||||
| 1 | + | package server | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bufio" | |||
| 5 | + | "os" | |||
| 6 | + | "path/filepath" | |||
| 7 | + | "strings" | |||
| 8 | + | "testing" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/go-wayland-bindings/spec" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // TestEquivalence regenerates every protocol listed in the //go:generate lines | |||
| 14 | + | // of the baseline purego-libwayland protocol/gen.go and requires byte-identical | |||
| 15 | + | // output. It runs only when WGB_OLD_PUREGO points at a baseline checkout. | |||
| 16 | + | func TestEquivalence(t *testing.T) { | |||
| 17 | + | root := os.Getenv("WGB_OLD_PUREGO") | |||
| 18 | + | if root == "" { | |||
| 19 | + | t.Skip("WGB_OLD_PUREGO not set") | |||
| 20 | + | } | |||
| 21 | + | protoDir := filepath.Join(root, "protocol") | |||
| 22 | + | f, err := os.Open(filepath.Join(protoDir, "gen.go")) | |||
| 23 | + | if err != nil { | |||
| 24 | + | t.Fatal(err) | |||
| 25 | + | } | |||
| 26 | + | defer f.Close() | |||
| 27 | + | ||||
| 28 | + | const prefix = "//go:generate go run ../cmd/wlgen " | |||
| 29 | + | n := 0 | |||
| 30 | + | sc := bufio.NewScanner(f) | |||
| 31 | + | sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) | |||
| 32 | + | for sc.Scan() { | |||
| 33 | + | line := sc.Text() | |||
| 34 | + | if !strings.HasPrefix(line, prefix) { | |||
| 35 | + | continue | |||
| 36 | + | } | |||
| 37 | + | args := strings.Fields(strings.TrimPrefix(line, prefix)) | |||
| 38 | + | n++ | |||
| 39 | + | t.Run(line[len(prefix):], func(t *testing.T) { checkLine(t, protoDir, args) }) | |||
| 40 | + | } | |||
| 41 | + | if err := sc.Err(); err != nil { | |||
| 42 | + | t.Fatal(err) | |||
| 43 | + | } | |||
| 44 | + | if n == 0 { | |||
| 45 | + | t.Fatal("no //go:generate lines found") | |||
| 46 | + | } | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | func loadXML(t *testing.T, path string) *spec.Protocol { | |||
| 50 | + | t.Helper() | |||
| 51 | + | b, err := os.ReadFile(path) | |||
| 52 | + | if err != nil { | |||
| 53 | + | t.Fatal(err) | |||
| 54 | + | } | |||
| 55 | + | p, err := spec.Parse(b) | |||
| 56 | + | if err != nil { | |||
| 57 | + | t.Fatalf("%s: %v", path, err) | |||
| 58 | + | } | |||
| 59 | + | return p | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | func checkLine(t *testing.T, protoDir string, args []string) { | |||
| 63 | + | opts := Options{Imports: map[string]string{}, Owners: map[string]string{}, Tool: "wlgen"} | |||
| 64 | + | var out, xmlPath string | |||
| 65 | + | var importXML []string | |||
| 66 | + | for i := 0; i < len(args); i++ { | |||
| 67 | + | switch args[i] { | |||
| 68 | + | case "-package", "-out", "-import", "-import-xml": | |||
| 69 | + | if i+1 >= len(args) { | |||
| 70 | + | t.Fatalf("flag %s missing value", args[i]) | |||
| 71 | + | } | |||
| 72 | + | v := args[i+1] | |||
| 73 | + | i++ | |||
| 74 | + | switch args[i-1] { | |||
| 75 | + | case "-package": | |||
| 76 | + | opts.Package = v | |||
| 77 | + | case "-out": | |||
| 78 | + | out = v | |||
| 79 | + | case "-import": | |||
| 80 | + | a, path, ok := strings.Cut(v, "=") | |||
| 81 | + | if !ok { | |||
| 82 | + | t.Fatalf("invalid -import %q", v) | |||
| 83 | + | } | |||
| 84 | + | opts.Imports[a] = path | |||
| 85 | + | case "-import-xml": | |||
| 86 | + | importXML = append(importXML, v) | |||
| 87 | + | } | |||
| 88 | + | default: | |||
| 89 | + | xmlPath = args[i] | |||
| 90 | + | } | |||
| 91 | + | } | |||
| 92 | + | if opts.Package == "" || out == "" || xmlPath == "" { | |||
| 93 | + | t.Fatalf("incomplete generate line: %v", args) | |||
| 94 | + | } | |||
| 95 | + | for _, s := range importXML { | |||
| 96 | + | a, path, ok := strings.Cut(s, "=") | |||
| 97 | + | if !ok || opts.Imports[a] == "" { | |||
| 98 | + | t.Fatalf("invalid -import-xml %q", s) | |||
| 99 | + | } | |||
| 100 | + | for _, it := range loadXML(t, filepath.Join(protoDir, path)).Interfaces { | |||
| 101 | + | opts.Owners[it.Name] = a | |||
| 102 | + | } | |||
| 103 | + | } | |||
| 104 | + | got, err := Generate(loadXML(t, filepath.Join(protoDir, xmlPath)), opts) | |||
| 105 | + | if err != nil { | |||
| 106 | + | t.Fatal(err) | |||
| 107 | + | } | |||
| 108 | + | want, err := os.ReadFile(filepath.Join(protoDir, out)) | |||
| 109 | + | if err != nil { | |||
| 110 | + | t.Fatal(err) | |||
| 111 | + | } | |||
| 112 | + | if string(got) != string(want) { | |||
| 113 | + | t.Errorf("generated output differs from %s (got %d bytes, want %d)", out, len(got), len(want)) | |||
| 114 | + | } | |||
| 115 | + | } | |||
A internal/gen/server/server.go +386 -0
| @@ -0,0 +1,386 @@ | |||
| 1 | + | // Package server generates Go server-side bindings (for | |
| 2 | + | // github.com/bnema/purego-libwayland/server) from Wayland protocol XML. | |
| 3 | + | package server | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bytes" | |
| 7 | + | "fmt" | |
| 8 | + | "go/format" | |
| 9 | + | "sort" | |
| 10 | + | "strings" | |
| 11 | + | "unicode" | |
| 12 | + | ||
| 13 | + | "github.com/bnema/go-wayland-bindings/spec" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // Options configures Generate. | |
| 17 | + | type Options struct { | |
| 18 | + | // Package is the Go package name of the generated file. | |
| 19 | + | Package string | |
| 20 | + | // Imports maps an import alias to a Go import path. | |
| 21 | + | Imports map[string]string | |
| 22 | + | // Owners maps an interface name to the import alias of the package that | |
| 23 | + | // defines it; "" means the generated package itself. Interfaces defined | |
| 24 | + | // by the protocol being generated are always local. | |
| 25 | + | Owners map[string]string | |
| 26 | + | // Tool is the tool name in the generated-code header (default "wlbgen"). | |
| 27 | + | Tool string | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | // Generate returns the gofmt-formatted server bindings for p. | |
| 31 | + | func Generate(p *spec.Protocol, opts Options) ([]byte, error) { | |
| 32 | + | if p == nil { | |
| 33 | + | return nil, fmt.Errorf("server: nil protocol") | |
| 34 | + | } | |
| 35 | + | if opts.Package == "" { | |
| 36 | + | return nil, fmt.Errorf("server: empty package name") | |
| 37 | + | } | |
| 38 | + | tool := opts.Tool | |
| 39 | + | if tool == "" { | |
| 40 | + | tool = "wlbgen" | |
| 41 | + | } | |
| 42 | + | g := &gen{owners: map[string]string{}, imports: map[string]string{}} | |
| 43 | + | for k, v := range opts.Imports { | |
| 44 | + | g.imports[k] = v | |
| 45 | + | } | |
| 46 | + | for k, v := range opts.Owners { | |
| 47 | + | g.owners[k] = v | |
| 48 | + | } | |
| 49 | + | for _, it := range p.Interfaces { | |
| 50 | + | g.owners[it.Name] = "" | |
| 51 | + | } | |
| 52 | + | return g.generate(p, opts.Package, tool) | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | func summary(d *spec.Description) string { | |
| 56 | + | if d == nil { | |
| 57 | + | return "" | |
| 58 | + | } | |
| 59 | + | return d.Summary | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | func camel(s string) string { | |
| 63 | + | var b strings.Builder | |
| 64 | + | for _, part := range strings.Split(s, "_") { | |
| 65 | + | if part == "" { | |
| 66 | + | continue | |
| 67 | + | } | |
| 68 | + | r := []rune(part) | |
| 69 | + | b.WriteRune(unicode.ToUpper(r[0])) | |
| 70 | + | b.WriteString(string(r[1:])) | |
| 71 | + | } | |
| 72 | + | return b.String() | |
| 73 | + | } | |
| 74 | + | func name(s string) string { | |
| 75 | + | for _, prefix := range []string{"wl_", "xdg_"} { | |
| 76 | + | if strings.HasPrefix(s, prefix) { | |
| 77 | + | s = strings.TrimPrefix(s, prefix) | |
| 78 | + | break | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | return camel(s) | |
| 82 | + | } | |
| 83 | + | func signature(m spec.Message) string { | |
| 84 | + | var b strings.Builder | |
| 85 | + | if m.Since > 1 { | |
| 86 | + | fmt.Fprint(&b, m.Since) | |
| 87 | + | } | |
| 88 | + | for _, a := range m.Args { | |
| 89 | + | if a.Type == "new_id" && a.Interface == "" { | |
| 90 | + | b.WriteString("sun") | |
| 91 | + | continue | |
| 92 | + | } | |
| 93 | + | if a.AllowNull { | |
| 94 | + | b.WriteByte('?') | |
| 95 | + | } | |
| 96 | + | b.WriteString(map[string]string{"int": "i", "uint": "u", "fixed": "f", "string": "s", "array": "a", "object": "o", "new_id": "n", "fd": "h"}[a.Type]) | |
| 97 | + | } | |
| 98 | + | return b.String() | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | type gen struct { | |
| 102 | + | bytes.Buffer | |
| 103 | + | owners map[string]string | |
| 104 | + | imports map[string]string | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | func (g *gen) p(f string, a ...any) { fmt.Fprintf(&g.Buffer, f, a...) } | |
| 108 | + | func (g *gen) ref(s string) (string, error) { | |
| 109 | + | if s == "" { | |
| 110 | + | return "nil", nil | |
| 111 | + | } | |
| 112 | + | alias, ok := g.owners[s] | |
| 113 | + | if !ok { | |
| 114 | + | return "", fmt.Errorf("unknown interface %s", s) | |
| 115 | + | } | |
| 116 | + | if alias != "" { | |
| 117 | + | alias += "." | |
| 118 | + | } | |
| 119 | + | return alias + name(s) + "Interface", nil | |
| 120 | + | } | |
| 121 | + | func (g *gen) wrapper(s string) string { | |
| 122 | + | alias := g.owners[s] | |
| 123 | + | if alias != "" { | |
| 124 | + | alias += "." | |
| 125 | + | } | |
| 126 | + | return alias + name(s) | |
| 127 | + | } | |
| 128 | + | func (g *gen) table(m spec.Message) error { | |
| 129 | + | var ts []string | |
| 130 | + | for _, a := range m.Args { | |
| 131 | + | if a.Type == "new_id" && a.Interface == "" { | |
| 132 | + | ts = append(ts, "nil", "nil", "nil") | |
| 133 | + | continue | |
| 134 | + | } | |
| 135 | + | t := "nil" | |
| 136 | + | if a.Type == "object" || a.Type == "new_id" { | |
| 137 | + | var e error | |
| 138 | + | t, e = g.ref(a.Interface) | |
| 139 | + | if e != nil { | |
| 140 | + | return e | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | ts = append(ts, t) | |
| 144 | + | } | |
| 145 | + | g.p("{Name:%q,Signature:%q,Types:[]*server.Interface{%s}},\n", m.Name, signature(m), strings.Join(ts, ",")) | |
| 146 | + | return nil | |
| 147 | + | } | |
| 148 | + | func (g *gen) typ(a spec.Arg, event bool) string { | |
| 149 | + | switch a.Type { | |
| 150 | + | case "uint": | |
| 151 | + | return "uint32" | |
| 152 | + | case "int": | |
| 153 | + | return "int32" | |
| 154 | + | case "fixed": | |
| 155 | + | return "server.Fixed" | |
| 156 | + | case "string": | |
| 157 | + | return "string" | |
| 158 | + | case "array": | |
| 159 | + | return "[]byte" | |
| 160 | + | case "fd": | |
| 161 | + | return "int" | |
| 162 | + | case "new_id": | |
| 163 | + | if !event { | |
| 164 | + | return "uint32" | |
| 165 | + | } | |
| 166 | + | fallthrough | |
| 167 | + | case "object": | |
| 168 | + | if a.Interface == "" { | |
| 169 | + | return "*server.Resource" | |
| 170 | + | } | |
| 171 | + | return "*" + g.wrapper(a.Interface) | |
| 172 | + | } | |
| 173 | + | return "server.Arg" | |
| 174 | + | } | |
| 175 | + | func param(a spec.Arg) string { | |
| 176 | + | s := a.Name | |
| 177 | + | if parts := strings.Split(s, "_"); len(parts) > 1 { | |
| 178 | + | s = parts[0] | |
| 179 | + | for _, part := range parts[1:] { | |
| 180 | + | s += camel(part) | |
| 181 | + | } | |
| 182 | + | } | |
| 183 | + | switch s { | |
| 184 | + | case "interface", "type", "map", "range", "func", "var", "package", "select", "default", "go", "defer", "return", "chan", "import": | |
| 185 | + | return s + "Arg" | |
| 186 | + | } | |
| 187 | + | return s | |
| 188 | + | } | |
| 189 | + | func (g *gen) params(m spec.Message, event bool) { | |
| 190 | + | for _, a := range m.Args { | |
| 191 | + | if a.Type == "new_id" && a.Interface == "" && !event { | |
| 192 | + | g.p(", interfaceName string, version uint32, id uint32") | |
| 193 | + | continue | |
| 194 | + | } | |
| 195 | + | g.p(", %s %s", param(a), g.typ(a, event)) | |
| 196 | + | } | |
| 197 | + | } | |
| 198 | + | func (g *gen) generate(pr *spec.Protocol, pkg, tool string) ([]byte, error) { | |
| 199 | + | g.p("// Code generated by %s; DO NOT EDIT.\npackage %s\nimport (\"github.com/bnema/purego-libwayland/server\"\n", tool, pkg) | |
| 200 | + | pin := false | |
| 201 | + | for _, it := range pr.Interfaces { | |
| 202 | + | for _, m := range it.Events { | |
| 203 | + | for _, a := range m.Args { | |
| 204 | + | if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { | |
| 205 | + | pin = true | |
| 206 | + | } | |
| 207 | + | } | |
| 208 | + | } | |
| 209 | + | } | |
| 210 | + | if pin { | |
| 211 | + | g.p("\"runtime\"\n") | |
| 212 | + | } | |
| 213 | + | for _, it := range pr.Interfaces { | |
| 214 | + | if len(it.Requests) > 0 { | |
| 215 | + | g.p("\"fmt\"\n") | |
| 216 | + | break | |
| 217 | + | } | |
| 218 | + | } | |
| 219 | + | aliases := make([]string, 0, len(g.imports)) | |
| 220 | + | for alias := range g.imports { | |
| 221 | + | aliases = append(aliases, alias) | |
| 222 | + | } | |
| 223 | + | sort.Strings(aliases) | |
| 224 | + | for _, alias := range aliases { | |
| 225 | + | g.p("%s %q\n", alias, g.imports[alias]) | |
| 226 | + | } | |
| 227 | + | g.p(")\n") | |
| 228 | + | for _, it := range pr.Interfaces { | |
| 229 | + | g.p("var %sInterface *server.Interface\n", name(it.Name)) | |
| 230 | + | } | |
| 231 | + | g.p("func init(){\n") | |
| 232 | + | for _, it := range pr.Interfaces { | |
| 233 | + | g.p("%sInterface=&server.Interface{Name:%q,Version:%d}\n", name(it.Name), it.Name, it.Version) | |
| 234 | + | } | |
| 235 | + | for _, it := range pr.Interfaces { | |
| 236 | + | n := name(it.Name) | |
| 237 | + | g.p("%sInterface.Requests=[]server.Message{\n", n) | |
| 238 | + | for _, m := range it.Requests { | |
| 239 | + | if e := g.table(m); e != nil { | |
| 240 | + | return nil, e | |
| 241 | + | } | |
| 242 | + | } | |
| 243 | + | g.p("}\n%sInterface.Events=[]server.Message{\n", n) | |
| 244 | + | for _, m := range it.Events { | |
| 245 | + | if e := g.table(m); e != nil { | |
| 246 | + | return nil, e | |
| 247 | + | } | |
| 248 | + | } | |
| 249 | + | g.p("}\n") | |
| 250 | + | } | |
| 251 | + | g.p("if err:=server.NewInterfaces(") | |
| 252 | + | for _, it := range pr.Interfaces { | |
| 253 | + | g.p("%sInterface,", name(it.Name)) | |
| 254 | + | } | |
| 255 | + | g.p(");err!=nil{panic(err)}}\n") | |
| 256 | + | for _, it := range pr.Interfaces { | |
| 257 | + | n := name(it.Name) | |
| 258 | + | g.p("// %s %s.\ntype %s struct{*server.Resource}\nfunc Wrap%s(r *server.Resource)*%s{if r==nil{return nil};return &%s{r}}\n", n, summary(it.Description), n, n, n, n) | |
| 259 | + | for j, m := range it.Requests { | |
| 260 | + | g.p("const %sRequest%s uint32=%d\n", n, camel(m.Name), j) | |
| 261 | + | } | |
| 262 | + | for j, m := range it.Events { | |
| 263 | + | g.p("const %sEvent%s uint32=%d\n", n, camel(m.Name), j) | |
| 264 | + | } | |
| 265 | + | for _, en := range it.Enums { | |
| 266 | + | t := n + camel(en.Name) | |
| 267 | + | g.p("// %s %s", t, summary(en.Description)) | |
| 268 | + | if en.Bitfield { | |
| 269 | + | g.p(" (bitfield)") | |
| 270 | + | } | |
| 271 | + | g.p(".\ntype %s uint32\nconst(\n", t) | |
| 272 | + | for _, v := range en.Entries { | |
| 273 | + | g.p("%s%s %s=%s\n", t, camel(v.Name), t, v.Value) | |
| 274 | + | } | |
| 275 | + | g.p(")\n") | |
| 276 | + | } | |
| 277 | + | g.p("// %sHandler handles %s requests.\ntype %sHandler interface{\n", n, it.Name, n) | |
| 278 | + | for _, m := range it.Requests { | |
| 279 | + | g.p("%s(self *%s", camel(m.Name), n) | |
| 280 | + | g.params(m, false) | |
| 281 | + | g.p(")\n") | |
| 282 | + | } | |
| 283 | + | g.p("}\nfunc New%s(c server.Client,version int32,id uint32,h %sHandler)(*%s,error){", n, n, n) | |
| 284 | + | if len(it.Requests) > 0 { | |
| 285 | + | g.p("if h==nil{return nil,fmt.Errorf(%q)}\n", "purego-libwayland: nil handler for "+it.Name) | |
| 286 | + | } | |
| 287 | + | g.p("var self *%s\nr,err:=c.CreateResource(%sInterface,version,id,func(_ *server.Resource,opcode uint32,args []server.Arg){switch opcode{\n", n, n) | |
| 288 | + | for j, m := range it.Requests { | |
| 289 | + | g.p("case %d:h.%s(self", j, camel(m.Name)) | |
| 290 | + | idx := 0 | |
| 291 | + | for _, a := range m.Args { | |
| 292 | + | if a.Type == "new_id" && a.Interface == "" { | |
| 293 | + | g.p(",args[%d].String(),args[%d].Uint(),args[%d].NewID()", idx, idx+1, idx+2) | |
| 294 | + | idx += 3 | |
| 295 | + | continue | |
| 296 | + | } | |
| 297 | + | x := fmt.Sprintf("args[%d]", idx) | |
| 298 | + | idx++ | |
| 299 | + | switch a.Type { | |
| 300 | + | case "uint", "int", "fixed", "string", "array", "fd", "new_id": | |
| 301 | + | accessor := camel(a.Type) | |
| 302 | + | if a.Type == "new_id" { | |
| 303 | + | accessor = "NewID" | |
| 304 | + | } | |
| 305 | + | g.p(",%s.%s()", x, accessor) | |
| 306 | + | case "object": | |
| 307 | + | if a.Interface == "" { | |
| 308 | + | g.p(",%s.Resource()", x) | |
| 309 | + | } else { | |
| 310 | + | g.p(",%sWrap%s(%s.Resource())", func() string { | |
| 311 | + | if v := g.owners[a.Interface]; v != "" { | |
| 312 | + | return v + "." | |
| 313 | + | } | |
| 314 | + | return "" | |
| 315 | + | }(), name(a.Interface), x) | |
| 316 | + | } | |
| 317 | + | } | |
| 318 | + | } | |
| 319 | + | g.p(")") | |
| 320 | + | if m.Type == "destructor" { | |
| 321 | + | g.p(";self.Destroy()") | |
| 322 | + | } | |
| 323 | + | g.p("\n") | |
| 324 | + | } | |
| 325 | + | g.p("}});if err!=nil{return nil,err};self=Wrap%s(r);r.Data=self;return self,nil}\n", n) | |
| 326 | + | for _, m := range it.Events { | |
| 327 | + | g.p("// Send%s %s", camel(m.Name), summary(m.Description)) | |
| 328 | + | if m.Since > 1 { | |
| 329 | + | g.p(" (since version %d)", m.Since) | |
| 330 | + | } | |
| 331 | + | g.p(".\nfunc(self *%s)Send%s(", n, camel(m.Name)) | |
| 332 | + | for i, a := range m.Args { | |
| 333 | + | if i > 0 { | |
| 334 | + | g.p(",") | |
| 335 | + | } | |
| 336 | + | g.p("%s %s", param(a), g.typ(a, true)) | |
| 337 | + | } | |
| 338 | + | g.p("){\n") | |
| 339 | + | needsPin := false | |
| 340 | + | for _, a := range m.Args { | |
| 341 | + | if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { | |
| 342 | + | needsPin = true | |
| 343 | + | } | |
| 344 | + | } | |
| 345 | + | if needsPin { | |
| 346 | + | g.p("var p runtime.Pinner;defer p.Unpin()\n") | |
| 347 | + | } | |
| 348 | + | g.p("self.PostEvent(%sEvent%s", n, camel(m.Name)) | |
| 349 | + | for _, a := range m.Args { | |
| 350 | + | v := param(a) | |
| 351 | + | if a.Type == "new_id" && a.Interface == "" { | |
| 352 | + | g.p(",server.String(&p,%s.Iface().Name,false),server.Uint(uint32(%s.Version())),server.NewID(%s)", v, v, v) | |
| 353 | + | continue | |
| 354 | + | } | |
| 355 | + | switch a.Type { | |
| 356 | + | case "uint", "int", "fd": | |
| 357 | + | g.p(",server.%s(%s)", map[string]string{"uint": "Uint", "int": "Int", "fd": "Fd"}[a.Type], v) | |
| 358 | + | case "fixed": | |
| 359 | + | g.p(",server.FixedArg(%s)", v) | |
| 360 | + | case "string": | |
| 361 | + | g.p(",server.String(&p,%s,%t)", v, a.AllowNull) | |
| 362 | + | case "array": | |
| 363 | + | g.p(",server.Array(&p,%s)", v) | |
| 364 | + | case "object", "new_id": | |
| 365 | + | constructor := "Object" | |
| 366 | + | if a.Type == "new_id" { | |
| 367 | + | constructor = "NewID" | |
| 368 | + | } | |
| 369 | + | if a.Interface == "" { | |
| 370 | + | g.p(",server.%s(%s)", constructor, v) | |
| 371 | + | } else { | |
| 372 | + | g.p(",server.%s(resourceOf(%s))", constructor, v) | |
| 373 | + | } | |
| 374 | + | } | |
| 375 | + | } | |
| 376 | + | g.p(")\n}\n") | |
| 377 | + | } | |
| 378 | + | g.p("func New%sGlobal(d *server.Display,version int32,bind server.BindFunc)error{return d.CreateGlobal(%sInterface,version,bind)}\n", n, n) | |
| 379 | + | } | |
| 380 | + | g.p("func resourceOf(v interface{GetResource()*server.Resource})*server.Resource{if v==nil{return nil};return v.GetResource()}\n") | |
| 381 | + | for _, it := range pr.Interfaces { | |
| 382 | + | n := name(it.Name) | |
| 383 | + | g.p("func(self *%s)GetResource()*server.Resource{if self==nil{return nil};return self.Resource}\n", n) | |
| 384 | + | } | |
| 385 | + | return format.Source(g.Bytes()) | |
| 386 | + | } | |
| @@ -0,0 +1,386 @@ | |||||
| 1 | + | // Package server generates Go server-side bindings (for | |||
| 2 | + | // github.com/bnema/purego-libwayland/server) from Wayland protocol XML. | |||
| 3 | + | package server | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "bytes" | |||
| 7 | + | "fmt" | |||
| 8 | + | "go/format" | |||
| 9 | + | "sort" | |||
| 10 | + | "strings" | |||
| 11 | + | "unicode" | |||
| 12 | + | ||||
| 13 | + | "github.com/bnema/go-wayland-bindings/spec" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // Options configures Generate. | |||
| 17 | + | type Options struct { | |||
| 18 | + | // Package is the Go package name of the generated file. | |||
| 19 | + | Package string | |||
| 20 | + | // Imports maps an import alias to a Go import path. | |||
| 21 | + | Imports map[string]string | |||
| 22 | + | // Owners maps an interface name to the import alias of the package that | |||
| 23 | + | // defines it; "" means the generated package itself. Interfaces defined | |||
| 24 | + | // by the protocol being generated are always local. | |||
| 25 | + | Owners map[string]string | |||
| 26 | + | // Tool is the tool name in the generated-code header (default "wlbgen"). | |||
| 27 | + | Tool string | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | // Generate returns the gofmt-formatted server bindings for p. | |||
| 31 | + | func Generate(p *spec.Protocol, opts Options) ([]byte, error) { | |||
| 32 | + | if p == nil { | |||
| 33 | + | return nil, fmt.Errorf("server: nil protocol") | |||
| 34 | + | } | |||
| 35 | + | if opts.Package == "" { | |||
| 36 | + | return nil, fmt.Errorf("server: empty package name") | |||
| 37 | + | } | |||
| 38 | + | tool := opts.Tool | |||
| 39 | + | if tool == "" { | |||
| 40 | + | tool = "wlbgen" | |||
| 41 | + | } | |||
| 42 | + | g := &gen{owners: map[string]string{}, imports: map[string]string{}} | |||
| 43 | + | for k, v := range opts.Imports { | |||
| 44 | + | g.imports[k] = v | |||
| 45 | + | } | |||
| 46 | + | for k, v := range opts.Owners { | |||
| 47 | + | g.owners[k] = v | |||
| 48 | + | } | |||
| 49 | + | for _, it := range p.Interfaces { | |||
| 50 | + | g.owners[it.Name] = "" | |||
| 51 | + | } | |||
| 52 | + | return g.generate(p, opts.Package, tool) | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | func summary(d *spec.Description) string { | |||
| 56 | + | if d == nil { | |||
| 57 | + | return "" | |||
| 58 | + | } | |||
| 59 | + | return d.Summary | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | func camel(s string) string { | |||
| 63 | + | var b strings.Builder | |||
| 64 | + | for _, part := range strings.Split(s, "_") { | |||
| 65 | + | if part == "" { | |||
| 66 | + | continue | |||
| 67 | + | } | |||
| 68 | + | r := []rune(part) | |||
| 69 | + | b.WriteRune(unicode.ToUpper(r[0])) | |||
| 70 | + | b.WriteString(string(r[1:])) | |||
| 71 | + | } | |||
| 72 | + | return b.String() | |||
| 73 | + | } | |||
| 74 | + | func name(s string) string { | |||
| 75 | + | for _, prefix := range []string{"wl_", "xdg_"} { | |||
| 76 | + | if strings.HasPrefix(s, prefix) { | |||
| 77 | + | s = strings.TrimPrefix(s, prefix) | |||
| 78 | + | break | |||
| 79 | + | } | |||
| 80 | + | } | |||
| 81 | + | return camel(s) | |||
| 82 | + | } | |||
| 83 | + | func signature(m spec.Message) string { | |||
| 84 | + | var b strings.Builder | |||
| 85 | + | if m.Since > 1 { | |||
| 86 | + | fmt.Fprint(&b, m.Since) | |||
| 87 | + | } | |||
| 88 | + | for _, a := range m.Args { | |||
| 89 | + | if a.Type == "new_id" && a.Interface == "" { | |||
| 90 | + | b.WriteString("sun") | |||
| 91 | + | continue | |||
| 92 | + | } | |||
| 93 | + | if a.AllowNull { | |||
| 94 | + | b.WriteByte('?') | |||
| 95 | + | } | |||
| 96 | + | b.WriteString(map[string]string{"int": "i", "uint": "u", "fixed": "f", "string": "s", "array": "a", "object": "o", "new_id": "n", "fd": "h"}[a.Type]) | |||
| 97 | + | } | |||
| 98 | + | return b.String() | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | type gen struct { | |||
| 102 | + | bytes.Buffer | |||
| 103 | + | owners map[string]string | |||
| 104 | + | imports map[string]string | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | func (g *gen) p(f string, a ...any) { fmt.Fprintf(&g.Buffer, f, a...) } | |||
| 108 | + | func (g *gen) ref(s string) (string, error) { | |||
| 109 | + | if s == "" { | |||
| 110 | + | return "nil", nil | |||
| 111 | + | } | |||
| 112 | + | alias, ok := g.owners[s] | |||
| 113 | + | if !ok { | |||
| 114 | + | return "", fmt.Errorf("unknown interface %s", s) | |||
| 115 | + | } | |||
| 116 | + | if alias != "" { | |||
| 117 | + | alias += "." | |||
| 118 | + | } | |||
| 119 | + | return alias + name(s) + "Interface", nil | |||
| 120 | + | } | |||
| 121 | + | func (g *gen) wrapper(s string) string { | |||
| 122 | + | alias := g.owners[s] | |||
| 123 | + | if alias != "" { | |||
| 124 | + | alias += "." | |||
| 125 | + | } | |||
| 126 | + | return alias + name(s) | |||
| 127 | + | } | |||
| 128 | + | func (g *gen) table(m spec.Message) error { | |||
| 129 | + | var ts []string | |||
| 130 | + | for _, a := range m.Args { | |||
| 131 | + | if a.Type == "new_id" && a.Interface == "" { | |||
| 132 | + | ts = append(ts, "nil", "nil", "nil") | |||
| 133 | + | continue | |||
| 134 | + | } | |||
| 135 | + | t := "nil" | |||
| 136 | + | if a.Type == "object" || a.Type == "new_id" { | |||
| 137 | + | var e error | |||
| 138 | + | t, e = g.ref(a.Interface) | |||
| 139 | + | if e != nil { | |||
| 140 | + | return e | |||
| 141 | + | } | |||
| 142 | + | } | |||
| 143 | + | ts = append(ts, t) | |||
| 144 | + | } | |||
| 145 | + | g.p("{Name:%q,Signature:%q,Types:[]*server.Interface{%s}},\n", m.Name, signature(m), strings.Join(ts, ",")) | |||
| 146 | + | return nil | |||
| 147 | + | } | |||
| 148 | + | func (g *gen) typ(a spec.Arg, event bool) string { | |||
| 149 | + | switch a.Type { | |||
| 150 | + | case "uint": | |||
| 151 | + | return "uint32" | |||
| 152 | + | case "int": | |||
| 153 | + | return "int32" | |||
| 154 | + | case "fixed": | |||
| 155 | + | return "server.Fixed" | |||
| 156 | + | case "string": | |||
| 157 | + | return "string" | |||
| 158 | + | case "array": | |||
| 159 | + | return "[]byte" | |||
| 160 | + | case "fd": | |||
| 161 | + | return "int" | |||
| 162 | + | case "new_id": | |||
| 163 | + | if !event { | |||
| 164 | + | return "uint32" | |||
| 165 | + | } | |||
| 166 | + | fallthrough | |||
| 167 | + | case "object": | |||
| 168 | + | if a.Interface == "" { | |||
| 169 | + | return "*server.Resource" | |||
| 170 | + | } | |||
| 171 | + | return "*" + g.wrapper(a.Interface) | |||
| 172 | + | } | |||
| 173 | + | return "server.Arg" | |||
| 174 | + | } | |||
| 175 | + | func param(a spec.Arg) string { | |||
| 176 | + | s := a.Name | |||
| 177 | + | if parts := strings.Split(s, "_"); len(parts) > 1 { | |||
| 178 | + | s = parts[0] | |||
| 179 | + | for _, part := range parts[1:] { | |||
| 180 | + | s += camel(part) | |||
| 181 | + | } | |||
| 182 | + | } | |||
| 183 | + | switch s { | |||
| 184 | + | case "interface", "type", "map", "range", "func", "var", "package", "select", "default", "go", "defer", "return", "chan", "import": | |||
| 185 | + | return s + "Arg" | |||
| 186 | + | } | |||
| 187 | + | return s | |||
| 188 | + | } | |||
| 189 | + | func (g *gen) params(m spec.Message, event bool) { | |||
| 190 | + | for _, a := range m.Args { | |||
| 191 | + | if a.Type == "new_id" && a.Interface == "" && !event { | |||
| 192 | + | g.p(", interfaceName string, version uint32, id uint32") | |||
| 193 | + | continue | |||
| 194 | + | } | |||
| 195 | + | g.p(", %s %s", param(a), g.typ(a, event)) | |||
| 196 | + | } | |||
| 197 | + | } | |||
| 198 | + | func (g *gen) generate(pr *spec.Protocol, pkg, tool string) ([]byte, error) { | |||
| 199 | + | g.p("// Code generated by %s; DO NOT EDIT.\npackage %s\nimport (\"github.com/bnema/purego-libwayland/server\"\n", tool, pkg) | |||
| 200 | + | pin := false | |||
| 201 | + | for _, it := range pr.Interfaces { | |||
| 202 | + | for _, m := range it.Events { | |||
| 203 | + | for _, a := range m.Args { | |||
| 204 | + | if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { | |||
| 205 | + | pin = true | |||
| 206 | + | } | |||
| 207 | + | } | |||
| 208 | + | } | |||
| 209 | + | } | |||
| 210 | + | if pin { | |||
| 211 | + | g.p("\"runtime\"\n") | |||
| 212 | + | } | |||
| 213 | + | for _, it := range pr.Interfaces { | |||
| 214 | + | if len(it.Requests) > 0 { | |||
| 215 | + | g.p("\"fmt\"\n") | |||
| 216 | + | break | |||
| 217 | + | } | |||
| 218 | + | } | |||
| 219 | + | aliases := make([]string, 0, len(g.imports)) | |||
| 220 | + | for alias := range g.imports { | |||
| 221 | + | aliases = append(aliases, alias) | |||
| 222 | + | } | |||
| 223 | + | sort.Strings(aliases) | |||
| 224 | + | for _, alias := range aliases { | |||
| 225 | + | g.p("%s %q\n", alias, g.imports[alias]) | |||
| 226 | + | } | |||
| 227 | + | g.p(")\n") | |||
| 228 | + | for _, it := range pr.Interfaces { | |||
| 229 | + | g.p("var %sInterface *server.Interface\n", name(it.Name)) | |||
| 230 | + | } | |||
| 231 | + | g.p("func init(){\n") | |||
| 232 | + | for _, it := range pr.Interfaces { | |||
| 233 | + | g.p("%sInterface=&server.Interface{Name:%q,Version:%d}\n", name(it.Name), it.Name, it.Version) | |||
| 234 | + | } | |||
| 235 | + | for _, it := range pr.Interfaces { | |||
| 236 | + | n := name(it.Name) | |||
| 237 | + | g.p("%sInterface.Requests=[]server.Message{\n", n) | |||
| 238 | + | for _, m := range it.Requests { | |||
| 239 | + | if e := g.table(m); e != nil { | |||
| 240 | + | return nil, e | |||
| 241 | + | } | |||
| 242 | + | } | |||
| 243 | + | g.p("}\n%sInterface.Events=[]server.Message{\n", n) | |||
| 244 | + | for _, m := range it.Events { | |||
| 245 | + | if e := g.table(m); e != nil { | |||
| 246 | + | return nil, e | |||
| 247 | + | } | |||
| 248 | + | } | |||
| 249 | + | g.p("}\n") | |||
| 250 | + | } | |||
| 251 | + | g.p("if err:=server.NewInterfaces(") | |||
| 252 | + | for _, it := range pr.Interfaces { | |||
| 253 | + | g.p("%sInterface,", name(it.Name)) | |||
| 254 | + | } | |||
| 255 | + | g.p(");err!=nil{panic(err)}}\n") | |||
| 256 | + | for _, it := range pr.Interfaces { | |||
| 257 | + | n := name(it.Name) | |||
| 258 | + | g.p("// %s %s.\ntype %s struct{*server.Resource}\nfunc Wrap%s(r *server.Resource)*%s{if r==nil{return nil};return &%s{r}}\n", n, summary(it.Description), n, n, n, n) | |||
| 259 | + | for j, m := range it.Requests { | |||
| 260 | + | g.p("const %sRequest%s uint32=%d\n", n, camel(m.Name), j) | |||
| 261 | + | } | |||
| 262 | + | for j, m := range it.Events { | |||
| 263 | + | g.p("const %sEvent%s uint32=%d\n", n, camel(m.Name), j) | |||
| 264 | + | } | |||
| 265 | + | for _, en := range it.Enums { | |||
| 266 | + | t := n + camel(en.Name) | |||
| 267 | + | g.p("// %s %s", t, summary(en.Description)) | |||
| 268 | + | if en.Bitfield { | |||
| 269 | + | g.p(" (bitfield)") | |||
| 270 | + | } | |||
| 271 | + | g.p(".\ntype %s uint32\nconst(\n", t) | |||
| 272 | + | for _, v := range en.Entries { | |||
| 273 | + | g.p("%s%s %s=%s\n", t, camel(v.Name), t, v.Value) | |||
| 274 | + | } | |||
| 275 | + | g.p(")\n") | |||
| 276 | + | } | |||
| 277 | + | g.p("// %sHandler handles %s requests.\ntype %sHandler interface{\n", n, it.Name, n) | |||
| 278 | + | for _, m := range it.Requests { | |||
| 279 | + | g.p("%s(self *%s", camel(m.Name), n) | |||
| 280 | + | g.params(m, false) | |||
| 281 | + | g.p(")\n") | |||
| 282 | + | } | |||
| 283 | + | g.p("}\nfunc New%s(c server.Client,version int32,id uint32,h %sHandler)(*%s,error){", n, n, n) | |||
| 284 | + | if len(it.Requests) > 0 { | |||
| 285 | + | g.p("if h==nil{return nil,fmt.Errorf(%q)}\n", "purego-libwayland: nil handler for "+it.Name) | |||
| 286 | + | } | |||
| 287 | + | g.p("var self *%s\nr,err:=c.CreateResource(%sInterface,version,id,func(_ *server.Resource,opcode uint32,args []server.Arg){switch opcode{\n", n, n) | |||
| 288 | + | for j, m := range it.Requests { | |||
| 289 | + | g.p("case %d:h.%s(self", j, camel(m.Name)) | |||
| 290 | + | idx := 0 | |||
| 291 | + | for _, a := range m.Args { | |||
| 292 | + | if a.Type == "new_id" && a.Interface == "" { | |||
| 293 | + | g.p(",args[%d].String(),args[%d].Uint(),args[%d].NewID()", idx, idx+1, idx+2) | |||
| 294 | + | idx += 3 | |||
| 295 | + | continue | |||
| 296 | + | } | |||
| 297 | + | x := fmt.Sprintf("args[%d]", idx) | |||
| 298 | + | idx++ | |||
| 299 | + | switch a.Type { | |||
| 300 | + | case "uint", "int", "fixed", "string", "array", "fd", "new_id": | |||
| 301 | + | accessor := camel(a.Type) | |||
| 302 | + | if a.Type == "new_id" { | |||
| 303 | + | accessor = "NewID" | |||
| 304 | + | } | |||
| 305 | + | g.p(",%s.%s()", x, accessor) | |||
| 306 | + | case "object": | |||
| 307 | + | if a.Interface == "" { | |||
| 308 | + | g.p(",%s.Resource()", x) | |||
| 309 | + | } else { | |||
| 310 | + | g.p(",%sWrap%s(%s.Resource())", func() string { | |||
| 311 | + | if v := g.owners[a.Interface]; v != "" { | |||
| 312 | + | return v + "." | |||
| 313 | + | } | |||
| 314 | + | return "" | |||
| 315 | + | }(), name(a.Interface), x) | |||
| 316 | + | } | |||
| 317 | + | } | |||
| 318 | + | } | |||
| 319 | + | g.p(")") | |||
| 320 | + | if m.Type == "destructor" { | |||
| 321 | + | g.p(";self.Destroy()") | |||
| 322 | + | } | |||
| 323 | + | g.p("\n") | |||
| 324 | + | } | |||
| 325 | + | g.p("}});if err!=nil{return nil,err};self=Wrap%s(r);r.Data=self;return self,nil}\n", n) | |||
| 326 | + | for _, m := range it.Events { | |||
| 327 | + | g.p("// Send%s %s", camel(m.Name), summary(m.Description)) | |||
| 328 | + | if m.Since > 1 { | |||
| 329 | + | g.p(" (since version %d)", m.Since) | |||
| 330 | + | } | |||
| 331 | + | g.p(".\nfunc(self *%s)Send%s(", n, camel(m.Name)) | |||
| 332 | + | for i, a := range m.Args { | |||
| 333 | + | if i > 0 { | |||
| 334 | + | g.p(",") | |||
| 335 | + | } | |||
| 336 | + | g.p("%s %s", param(a), g.typ(a, true)) | |||
| 337 | + | } | |||
| 338 | + | g.p("){\n") | |||
| 339 | + | needsPin := false | |||
| 340 | + | for _, a := range m.Args { | |||
| 341 | + | if a.Type == "string" || a.Type == "array" || (a.Type == "new_id" && a.Interface == "") { | |||
| 342 | + | needsPin = true | |||
| 343 | + | } | |||
| 344 | + | } | |||
| 345 | + | if needsPin { | |||
| 346 | + | g.p("var p runtime.Pinner;defer p.Unpin()\n") | |||
| 347 | + | } | |||
| 348 | + | g.p("self.PostEvent(%sEvent%s", n, camel(m.Name)) | |||
| 349 | + | for _, a := range m.Args { | |||
| 350 | + | v := param(a) | |||
| 351 | + | if a.Type == "new_id" && a.Interface == "" { | |||
| 352 | + | g.p(",server.String(&p,%s.Iface().Name,false),server.Uint(uint32(%s.Version())),server.NewID(%s)", v, v, v) | |||
| 353 | + | continue | |||
| 354 | + | } | |||
| 355 | + | switch a.Type { | |||
| 356 | + | case "uint", "int", "fd": | |||
| 357 | + | g.p(",server.%s(%s)", map[string]string{"uint": "Uint", "int": "Int", "fd": "Fd"}[a.Type], v) | |||
| 358 | + | case "fixed": | |||
| 359 | + | g.p(",server.FixedArg(%s)", v) | |||
| 360 | + | case "string": | |||
| 361 | + | g.p(",server.String(&p,%s,%t)", v, a.AllowNull) | |||
| 362 | + | case "array": | |||
| 363 | + | g.p(",server.Array(&p,%s)", v) | |||
| 364 | + | case "object", "new_id": | |||
| 365 | + | constructor := "Object" | |||
| 366 | + | if a.Type == "new_id" { | |||
| 367 | + | constructor = "NewID" | |||
| 368 | + | } | |||
| 369 | + | if a.Interface == "" { | |||
| 370 | + | g.p(",server.%s(%s)", constructor, v) | |||
| 371 | + | } else { | |||
| 372 | + | g.p(",server.%s(resourceOf(%s))", constructor, v) | |||
| 373 | + | } | |||
| 374 | + | } | |||
| 375 | + | } | |||
| 376 | + | g.p(")\n}\n") | |||
| 377 | + | } | |||
| 378 | + | g.p("func New%sGlobal(d *server.Display,version int32,bind server.BindFunc)error{return d.CreateGlobal(%sInterface,version,bind)}\n", n, n) | |||
| 379 | + | } | |||
| 380 | + | g.p("func resourceOf(v interface{GetResource()*server.Resource})*server.Resource{if v==nil{return nil};return v.GetResource()}\n") | |||
| 381 | + | for _, it := range pr.Interfaces { | |||
| 382 | + | n := name(it.Name) | |||
| 383 | + | g.p("func(self *%s)GetResource()*server.Resource{if self==nil{return nil};return self.Resource}\n", n) | |||
| 384 | + | } | |||
| 385 | + | return format.Source(g.Bytes()) | |||
| 386 | + | } | |||
A internal/gen/server/server_test.go +57 -0
| @@ -0,0 +1,57 @@ | |||
| 1 | + | package server | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "strings" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/go-wayland-bindings/spec" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestUntypedNewIDEvent(t *testing.T) { | |
| 11 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "test_device", Version: 1, Events: []spec.Message{{Name: "created", Args: []spec.Arg{{Name: "id", Type: "new_id"}}}}}}} | |
| 12 | + | b, err := Generate(p, Options{Package: "test"}) | |
| 13 | + | if err != nil { | |
| 14 | + | t.Fatal(err) | |
| 15 | + | } | |
| 16 | + | s := string(b) | |
| 17 | + | if !strings.HasPrefix(s, "// Code generated by wlbgen; DO NOT EDIT.\n") { | |
| 18 | + | t.Fatalf("unexpected header:\n%s", s) | |
| 19 | + | } | |
| 20 | + | if !strings.Contains(s, `Signature: "sun"`) || !strings.Contains(s, `server.String(&p, id.Iface().Name, false), server.Uint(uint32(id.Version())), server.NewID(id)`) { | |
| 21 | + | t.Fatalf("untyped new_id event missing wire args:\n%s", s) | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func TestToolHeader(t *testing.T) { | |
| 26 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "test_device", Version: 1}}} | |
| 27 | + | b, err := Generate(p, Options{Package: "test", Tool: "wlgen"}) | |
| 28 | + | if err != nil { | |
| 29 | + | t.Fatal(err) | |
| 30 | + | } | |
| 31 | + | if !strings.HasPrefix(string(b), "// Code generated by wlgen; DO NOT EDIT.\n") { | |
| 32 | + | t.Fatalf("unexpected header:\n%s", b) | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func TestUnknownInterface(t *testing.T) { | |
| 37 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "a", Version: 1, Events: []spec.Message{{Name: "e", Args: []spec.Arg{{Name: "o", Type: "object", Interface: "missing"}}}}}}} | |
| 38 | + | if _, err := Generate(p, Options{Package: "test"}); err == nil { | |
| 39 | + | t.Fatal("expected error for unknown interface") | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func TestSignature(t *testing.T) { | |
| 44 | + | cases := []struct { | |
| 45 | + | m spec.Message | |
| 46 | + | want string | |
| 47 | + | }{ | |
| 48 | + | {spec.Message{Name: "bind", Args: []spec.Arg{{Type: "uint"}, {Type: "new_id"}}}, "usun"}, | |
| 49 | + | {spec.Message{Since: 2, Args: []spec.Arg{{Type: "object", AllowNull: true}, {Type: "fd"}, {Type: "fixed"}}}, "2?ohf"}, | |
| 50 | + | {spec.Message{Args: []spec.Arg{{Type: "new_id", Interface: "wl_surface"}}}, "n"}, | |
| 51 | + | } | |
| 52 | + | for _, c := range cases { | |
| 53 | + | if got := signature(c.m); got != c.want { | |
| 54 | + | t.Errorf("signature(%+v)=%q, want %q", c.m, got, c.want) | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | } | |
| @@ -0,0 +1,57 @@ | |||||
| 1 | + | package server | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "strings" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/go-wayland-bindings/spec" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestUntypedNewIDEvent(t *testing.T) { | |||
| 11 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "test_device", Version: 1, Events: []spec.Message{{Name: "created", Args: []spec.Arg{{Name: "id", Type: "new_id"}}}}}}} | |||
| 12 | + | b, err := Generate(p, Options{Package: "test"}) | |||
| 13 | + | if err != nil { | |||
| 14 | + | t.Fatal(err) | |||
| 15 | + | } | |||
| 16 | + | s := string(b) | |||
| 17 | + | if !strings.HasPrefix(s, "// Code generated by wlbgen; DO NOT EDIT.\n") { | |||
| 18 | + | t.Fatalf("unexpected header:\n%s", s) | |||
| 19 | + | } | |||
| 20 | + | if !strings.Contains(s, `Signature: "sun"`) || !strings.Contains(s, `server.String(&p, id.Iface().Name, false), server.Uint(uint32(id.Version())), server.NewID(id)`) { | |||
| 21 | + | t.Fatalf("untyped new_id event missing wire args:\n%s", s) | |||
| 22 | + | } | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func TestToolHeader(t *testing.T) { | |||
| 26 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "test_device", Version: 1}}} | |||
| 27 | + | b, err := Generate(p, Options{Package: "test", Tool: "wlgen"}) | |||
| 28 | + | if err != nil { | |||
| 29 | + | t.Fatal(err) | |||
| 30 | + | } | |||
| 31 | + | if !strings.HasPrefix(string(b), "// Code generated by wlgen; DO NOT EDIT.\n") { | |||
| 32 | + | t.Fatalf("unexpected header:\n%s", b) | |||
| 33 | + | } | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | func TestUnknownInterface(t *testing.T) { | |||
| 37 | + | p := &spec.Protocol{Interfaces: []spec.Interface{{Name: "a", Version: 1, Events: []spec.Message{{Name: "e", Args: []spec.Arg{{Name: "o", Type: "object", Interface: "missing"}}}}}}} | |||
| 38 | + | if _, err := Generate(p, Options{Package: "test"}); err == nil { | |||
| 39 | + | t.Fatal("expected error for unknown interface") | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | func TestSignature(t *testing.T) { | |||
| 44 | + | cases := []struct { | |||
| 45 | + | m spec.Message | |||
| 46 | + | want string | |||
| 47 | + | }{ | |||
| 48 | + | {spec.Message{Name: "bind", Args: []spec.Arg{{Type: "uint"}, {Type: "new_id"}}}, "usun"}, | |||
| 49 | + | {spec.Message{Since: 2, Args: []spec.Arg{{Type: "object", AllowNull: true}, {Type: "fd"}, {Type: "fixed"}}}, "2?ohf"}, | |||
| 50 | + | {spec.Message{Args: []spec.Arg{{Type: "new_id", Interface: "wl_surface"}}}, "n"}, | |||
| 51 | + | } | |||
| 52 | + | for _, c := range cases { | |||
| 53 | + | if got := signature(c.m); got != c.want { | |||
| 54 | + | t.Errorf("signature(%+v)=%q, want %q", c.m, got, c.want) | |||
| 55 | + | } | |||
| 56 | + | } | |||
| 57 | + | } | |||