1package main
2
3import (
4 "encoding/xml"
5 "fmt"
6 "io"
7 "os"
8 "strconv"
9 "strings"
10)
11
12// Node retains XML order and source locations, including expressions. Resolution
13// turns this syntax tree into the flat, typed emission IR; parsing makes no Go
14// naming or layout choices.
15type Node struct {
16 Kind string
17 Attr map[string]string
18 Text string
19 Children []*Node
20 File string
21 Line int
22}
23
24func (n *Node) fail(format string, args ...any) error {
25 return fmt.Errorf("%s:%d: %s", n.File, n.Line, fmt.Sprintf(format, args...))
26}
27
28// Attribute and child whitelists are per element, not global: a known attribute
29// or element used in the wrong place is also an error. Documentation is ignored.
30var attributes = map[string]string{
31 "xcb": "header extension-name extension-xname extension-multiword major-version minor-version",
32 "import": "", "typedef": "oldname newname", "xidtype": "name", "xidunion": "name", "type": "",
33 "enum": "name", "item": "name", "value": "", "bit": "", "struct": "name", "union": "name",
34 "request": "name opcode combine-adjacent", "reply": "", "event": "name number no-sequence-number xge",
35 "eventcopy": "name number ref", "error": "name number type", "errorcopy": "name number ref",
36 "field": "name type enum altenum mask", "pad": "bytes align", "list": "name type enum",
37 "exprfield": "name type", "switch": "name", "bitcase": "", "fieldref": "", "enumref": "ref",
38 "op": "op", "unop": "op", "popcount": "", "doc": "",
39}
40var children = map[string]string{
41 "xcb": "import typedef xidtype xidunion enum struct union request event eventcopy error errorcopy doc",
42 "xidunion": "type", "enum": "item doc", "item": "value bit doc",
43 "struct": "field pad list doc", "union": "field pad list doc",
44 "request": "field pad list exprfield switch reply doc", "reply": "field pad list exprfield doc",
45 "event": "field pad list exprfield doc", "error": "field pad doc",
46 "list": "value fieldref op unop popcount enumref", "exprfield": "value fieldref op unop popcount enumref",
47 "switch": "fieldref op unop popcount enumref bitcase", "bitcase": "enumref value field pad doc",
48 "op": "value fieldref op unop popcount enumref", "unop": "value fieldref op unop popcount enumref",
49 "popcount": "value fieldref op unop popcount enumref",
50}
51
52func contains(words, word string) bool {
53 for _, s := range strings.Fields(words) {
54 if s == word {
55 return true
56 }
57 }
58 return false
59}
60
61func parseFile(path string) (*Node, error) {
62 f, err := os.Open(path)
63 if err != nil {
64 return nil, err
65 }
66 defer f.Close()
67 return parseXML(path, f)
68}
69func parseXML(file string, r io.Reader) (*Node, error) {
70 d := xml.NewDecoder(r)
71 var root *Node
72 var stack []*Node
73 for {
74 token, err := d.Token()
75 if err == io.EOF {
76 break
77 }
78 if err != nil {
79 line, _ := d.InputPos()
80 return nil, fmt.Errorf("%s:%d: %w", file, line, err)
81 }
82 switch t := token.(type) {
83 case xml.StartElement:
84 line, _ := d.InputPos()
85 n := &Node{Kind: t.Name.Local, Attr: make(map[string]string), File: file, Line: line}
86 allowed, ok := attributes[n.Kind]
87 if !ok || t.Name.Space != "" {
88 return nil, n.fail("unsupported element <%s>", t.Name.Local)
89 }
90 if len(stack) == 0 {
91 if root != nil || n.Kind != "xcb" {
92 return nil, n.fail("expected a single <xcb> root")
93 }
94 root = n
95 } else {
96 parent := stack[len(stack)-1]
97 if !contains(children[parent.Kind], n.Kind) {
98 return nil, n.fail("unsupported <%s> in <%s>", n.Kind, parent.Kind)
99 }
100 parent.Children = append(parent.Children, n)
101 }
102 if n.Kind == "doc" {
103 if err := d.Skip(); err != nil {
104 return nil, n.fail("invalid doc: %v", err)
105 }
106 continue
107 }
108 for _, a := range t.Attr {
109 if a.Name.Space != "" || !contains(allowed, a.Name.Local) {
110 return nil, n.fail("unknown attribute %q on <%s>", a.Name.Local, n.Kind)
111 }
112 if _, ok := n.Attr[a.Name.Local]; ok {
113 return nil, n.fail("duplicate attribute %q", a.Name.Local)
114 }
115 n.Attr[a.Name.Local] = a.Value
116 }
117 stack = append(stack, n)
118 case xml.EndElement:
119 if len(stack) == 0 {
120 return nil, fmt.Errorf("%s: unexpected closing element", file)
121 }
122 n := stack[len(stack)-1]
123 n.Text = strings.TrimSpace(n.Text)
124 if err := validateNode(n); err != nil {
125 return nil, err
126 }
127 stack = stack[:len(stack)-1]
128 case xml.CharData:
129 if len(stack) > 0 {
130 stack[len(stack)-1].Text += string(t)
131 } else if strings.TrimSpace(string(t)) != "" {
132 return nil, fmt.Errorf("%s: text outside root", file)
133 }
134 case xml.Directive:
135 return nil, fmt.Errorf("%s: XML directives are unsupported", file)
136 }
137 }
138 if root == nil {
139 return nil, fmt.Errorf("%s:1: missing <xcb> root", file)
140 }
141 return root, nil
142}
143func require(n *Node, names string) error {
144 for _, name := range strings.Fields(names) {
145 if n.Attr[name] == "" {
146 return n.fail("missing %s on <%s>", name, n.Kind)
147 }
148 }
149 return nil
150}
151func number(n *Node, attr string, bits int) error {
152 _, err := strconv.ParseUint(n.Attr[attr], 0, bits)
153 if err != nil {
154 return n.fail("invalid %s %q", attr, n.Attr[attr])
155 }
156 return nil
157}
158func exprCount(n *Node) int {
159 count := 0
160 for _, c := range n.Children {
161 if c.Kind != "doc" {
162 count++
163 }
164 }
165 return count
166}
167func validateNode(n *Node) error {
168 required := map[string]string{
169 "xcb": "header", "typedef": "oldname newname", "xidtype": "name", "xidunion": "name",
170 "enum": "name", "item": "name", "struct": "name", "union": "name", "request": "name opcode",
171 "event": "name number", "eventcopy": "name number ref", "error": "name number", "errorcopy": "name number ref",
172 "field": "name type", "list": "name type", "exprfield": "name type", "switch": "name", "enumref": "ref", "op": "op", "unop": "op",
173 }
174 if err := require(n, required[n.Kind]); err != nil {
175 return err
176 }
177 switch n.Kind {
178 case "import", "type", "fieldref", "enumref":
179 if n.Text == "" {
180 return n.fail("empty <%s>", n.Kind)
181 }
182 case "value", "bit":
183 if _, err := strconv.ParseInt(n.Text, 0, 64); err != nil {
184 return n.fail("invalid <%s> integer %q", n.Kind, n.Text)
185 }
186 case "pad":
187 _, b := n.Attr["bytes"]
188 _, a := n.Attr["align"]
189 if b == a {
190 return n.fail("pad requires exactly one of bytes or align")
191 }
192 key := "bytes"
193 if a {
194 key = "align"
195 }
196 if err := number(n, key, 31); err != nil {
197 return err
198 }
199 if a {
200 v, _ := strconv.Atoi(n.Attr[key])
201 if v == 0 || v&(v-1) != 0 {
202 return n.fail("pad alignment must be a positive power of two")
203 }
204 }
205 case "request":
206 if err := number(n, "opcode", 8); err != nil {
207 return err
208 }
209 case "event", "eventcopy", "error", "errorcopy":
210 if err := number(n, "number", 8); err != nil {
211 return err
212 }
213 if n.Attr["xge"] == "true" {
214 v, _ := strconv.Atoi(n.Attr["number"])
215 if v >= 128 {
216 return n.fail("xge evtype must be < 128")
217 }
218 }
219 case "op":
220 if !contains("+ - * / & | << >>", n.Attr["op"]) {
221 return n.fail("unsupported operator %q", n.Attr["op"])
222 }
223 if exprCount(n) != 2 {
224 return n.fail("op requires two operands")
225 }
226 case "unop":
227 if n.Attr["op"] != "~" {
228 return n.fail("unsupported unary operator %q", n.Attr["op"])
229 }
230 if exprCount(n) != 1 {
231 return n.fail("unop requires one operand")
232 }
233 case "popcount", "exprfield", "item":
234 if exprCount(n) != 1 {
235 return n.fail("<%s> requires one expression", n.Kind)
236 }
237 case "list":
238 if exprCount(n) > 1 {
239 return n.fail("list has multiple length expressions")
240 }
241 case "bitcase":
242 fields := 0
243 for _, c := range n.Children {
244 if c.Kind == "field" {
245 fields++
246 if contains("CARD8 CARD16 INT8 INT16 BOOL char BYTE void", c.Attr["type"]) {
247 return c.fail("value-list bitcase requires a 4-byte field")
248 }
249 }
250 }
251 if fields != 1 {
252 return n.fail("value-list bitcase requires exactly one field")
253 }
254 }
255 for _, key := range []string{"xge", "no-sequence-number", "combine-adjacent", "extension-multiword"} {
256 if v, ok := n.Attr[key]; ok && v != "true" && v != "false" {
257 return n.fail("invalid boolean %s=%q", key, v)
258 }
259 }
260 // Forward references cannot be laid out in one pass. Reject at the length
261 // expression's location rather than failing later while emitting Go.
262 declared := map[string]bool{}
263 for _, c := range n.Children {
264 if c.Kind == "field" || c.Kind == "exprfield" {
265 declared[c.Attr["name"]] = true
266 }
267 }
268 seen := map[string]bool{}
269 for _, c := range n.Children {
270 if c.Kind == "list" {
271 var visit func(*Node) error
272 visit = func(e *Node) error {
273 if e.Kind == "fieldref" && declared[e.Text] && !seen[e.Text] {
274 return e.fail("list length references later field %q", e.Text)
275 }
276 for _, child := range e.Children {
277 if err := visit(child); err != nil {
278 return err
279 }
280 }
281 return nil
282 }
283 if err := visit(c); err != nil {
284 return err
285 }
286 }
287 if c.Kind == "field" || c.Kind == "exprfield" {
288 seen[c.Attr["name"]] = true
289 }
290 }
291 return nil
292}