main 602baa7

events.go RawHistoryFolder

1package main
2
3import (
4	"fmt"
5	"path/filepath"
6	"sort"
7	"strconv"
8	"strings"
9)
10
11// eventIR is one <event> or <eventcopy>. Copies point at their source IR and
12// share its container.
13type eventIR struct {
14	Name     string // Go base name, e.g. MapRequest
15	Code     int    // core: wire code; extension: relative code; XGE: evtype
16	XGE      bool
17	Copy     *eventIR
18	C        *container
19	Node     *Node
20	KindExpr string // "" when the event has no Kind (core XGE envelope)
21}
22
23const eventLifetime = "Getters read the copied Event.Raw, so a view stays valid as long as the Event value it was taken from."
24
25func eventCode(n *Node) (int, error) {
26	code, err := strconv.Atoi(n.Attr["number"])
27	if err != nil || code < 0 || code > 255 {
28		return 0, n.fail("invalid event number %q", n.Attr["number"])
29	}
30	return code, nil
31}
32
33func kindExpr(m *Module, e *eventIR) (string, error) {
34	switch {
35	case m.ExtName == "" && e.XGE:
36		// The core XGE envelope is not an event type: DecodeEvent resolves
37		// every XGE packet to its extension's kind or KindUnknown.
38		return "", nil
39	case m.ExtName == "":
40		if e.Code < 2 || e.Code > 34 {
41			return "", e.Node.fail("core event code %d outside 2..34", e.Code)
42		}
43		return fmt.Sprintf("xturbo.EventKind(%d)", e.Code), nil
44	case e.XGE:
45		return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s)<<8 | 0x80 | %d", m.ExtName, e.Code), nil
46	}
47	if e.Code > 127 {
48		return "", e.Node.fail("extension event number %d above 127", e.Code)
49	}
50	if e.Code == 0 {
51		// "| 0" is a no-op that staticcheck rejects (SA4016).
52		return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s) << 8", m.ExtName), nil
53	}
54	return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s)<<8 | %d", m.ExtName, e.Code), nil
55}
56
57// xgeReserved are the implicit XGE header getters.
58const xgeReserved = "Raw Extension Length EventType Tail"
59
60func resolveEvents(m *Module) ([]*eventIR, error) {
61	var events []*eventIR
62	byXName := map[string]*eventIR{}
63	for _, n := range m.Root.Children {
64		if n.Kind != "event" && n.Kind != "eventcopy" {
65			continue
66		}
67		code, err := eventCode(n)
68		if err != nil {
69			return nil, err
70		}
71		e := &eventIR{Name: goName(n.Attr["name"]), Code: code, Node: n}
72		if n.Kind == "eventcopy" {
73			src := byXName[n.Attr["ref"]]
74			if src == nil {
75				return nil, n.fail("eventcopy of unknown event %s", n.Attr["ref"])
76			}
77			if src.Copy != nil {
78				src = src.Copy
79			}
80			e.Copy, e.C, e.XGE = src, src.C, src.XGE
81		} else {
82			e.XGE = n.Attr["xge"] == "true"
83			lay, reserved := layoutEvent, "Raw"
84			switch {
85			case e.XGE:
86				lay, reserved = layoutXGE, xgeReserved
87				if code > 127 {
88					return nil, n.fail("xge evtype %d >= 128", code)
89				}
90			case n.Attr["no-sequence-number"] == "true":
91				lay = layoutEventNoSeq
92			}
93			c, err := buildContainer(m, e.Name+"Event", "e", n.Children, lay, reserved)
94			if err != nil {
95				return nil, err
96			}
97			if c.Dynamic >= 0 || c.Fixed > 32 {
98				return nil, n.fail("event members must lie within the first 32 bytes")
99			}
100			for _, mem := range c.Members {
101				if mem.Kind == "list" && mem.ListKind == "iter" {
102					return nil, n.fail("variable-size struct list in event")
103				}
104			}
105			e.C = c
106		}
107		if e.KindExpr, err = kindExpr(m, e); err != nil {
108			return nil, err
109		}
110		names := []string{e.Name + "Event", e.Name + "Of"}
111		if e.KindExpr != "" {
112			names = append(names, "Kind"+e.Name)
113		}
114		if m.ExtName == "" && !e.XGE {
115			names = append(names, e.Name+"EventData")
116		}
117		for _, s := range names {
118			if err := m.claim(s, n); err != nil {
119				return nil, err
120			}
121		}
122		byXName[n.Attr["name"]] = e
123		events = append(events, e)
124	}
125	return events, nil
126}
127
128// dataField renders one EventData struct field and its Put statement.
129func dataField(c *container, mem member) (field, put string, err error) {
130	name := mem.Method
131	if name == "Put" {
132		name += "_"
133	}
134	src := "d." + name
135	at := fmt.Sprintf("tmp[%d:]", mem.Static)
136	switch {
137	case mem.Kind == "field" && isScalar(mem.Type):
138		return name + " " + mem.GoType, putScalar(mem.Type, src, at), nil
139	case mem.Kind == "field":
140		return name + " " + mem.GoType, fmt.Sprintf("%s.Put(%s);", src, at), nil
141	case mem.Kind == "list" && mem.Count.Kind == "value":
142		count := mem.Size / mem.Type.Size
143		elem := mem.Elem
144		var b strings.Builder
145		fmt.Fprintf(&b, "for i := range %s {", src)
146		target := fmt.Sprintf("tmp[%d+i*%d:]", mem.Static, mem.Type.Size)
147		if isScalar(mem.Type) {
148			b.WriteString(putScalar(mem.Type, src+"[i]", target))
149		} else {
150			fmt.Fprintf(&b, "%s[i].Put(%s);", src, target)
151		}
152		b.WriteString("};")
153		return fmt.Sprintf("%s [%d]%s", name, count, elem), b.String(), nil
154	}
155	return "", "", c.Module.Root.fail("unsupported event member %s for EventData", mem.XName)
156}
157
158func eventDecl(m *Module, e *eventIR, lists map[string]string) (string, error) {
159	var b strings.Builder
160	if e.KindExpr != "" {
161		fmt.Fprintf(&b, "// Kind%s identifies %s events in xturbo.Event.Kind.\nconst Kind%s = %s;\n", e.Name, e.Name, e.Name, e.KindExpr)
162	}
163	view := e.Name + "Event"
164	if e.Copy != nil {
165		src := e.Copy.Name
166		fmt.Fprintf(&b, "// %s shares %s's layout (eventcopy).\ntype %s = %sEvent;\n", view, src, view, src)
167		fmt.Fprintf(&b, "// %sOf views ev as %s without checking ev.Kind.\nfunc %sOf(ev *xturbo.Event) %s { return %sOf(ev) };\n", e.Name, e.Name, e.Name, view, src)
168		if m.ExtName == "" && !e.XGE {
169			fmt.Fprintf(&b, "// %sEventData encodes a %s event for SendEvent with %s's fields.\ntype %sEventData %sEventData;\n", e.Name, e.Name, src, e.Name, src)
170			fmt.Fprintf(&b, "// Put writes the event (code %d, pads and sequence zeroed) into dst.\nfunc (d *%sEventData) Put(dst *[32]byte) { (*%sEventData)(d).Put(dst); dst[0] = %d };\n", e.Code, e.Name, src, e.Code)
171		}
172		return b.String(), nil
173	}
174	c := e.C
175	if e.XGE {
176		fmt.Fprintf(&b, "// %s is a view over a %s generic event: the copied Event.Raw plus the borrowed Event.Tail.\n// Tail is valid only until the Handler callback returns; copy what you keep.\n", view, e.Name)
177		fmt.Fprintf(&b, "type %s struct { raw *[32]byte; tail []byte };\n", view)
178		fmt.Fprintf(&b, "// %sOf views ev as %s; false when ev is not a generic event or Tail does not match its length.\n", e.Name, e.Name)
179		fmt.Fprintf(&b, "func %sOf(ev *xturbo.Event) (%s, bool) { if ev.Raw[0]&0x7f != 35 || len(ev.Tail) != 4*int(binary.LittleEndian.Uint32(ev.Raw[4:])) { return %s{}, false }; return %s{raw: &ev.Raw, tail: ev.Tail}, true };\n", e.Name, view, view, view)
180		fmt.Fprintf(&b, "func (e %s) Extension() uint8 { return e.raw[1] };\n", view)
181		fmt.Fprintf(&b, "func (e %s) Length() uint32 { return binary.LittleEndian.Uint32(e.raw[4:]) };\n", view)
182		fmt.Fprintf(&b, "func (e %s) EventType() uint16 { return binary.LittleEndian.Uint16(e.raw[8:]) };\n", view)
183		fmt.Fprintf(&b, "func (e %s) Tail() []byte { return e.tail };\n", view)
184	} else {
185		fmt.Fprintf(&b, "// %s is a view over a %s event's 32 bytes. %s\n", view, e.Name, eventLifetime)
186		fmt.Fprintf(&b, "type %s struct { raw *[32]byte };\n", view)
187		fmt.Fprintf(&b, "// %sOf views ev as %s without checking ev.Kind: a wrong kind yields garbage, never a panic.\n", e.Name, e.Name)
188		fmt.Fprintf(&b, "func %sOf(ev *xturbo.Event) %s { return %s{raw: &ev.Raw} };\n", e.Name, view, view)
189	}
190	fmt.Fprintf(&b, "func (e %s) Raw() *[32]byte { return e.raw };\n", view)
191	if err := c.getters(&b, view, lists); err != nil {
192		return "", err
193	}
194	if m.ExtName != "" || e.XGE {
195		return b.String(), nil
196	}
197	var fields, puts []string
198	for _, mem := range c.Members {
199		if mem.Kind == "pad" {
200			continue
201		}
202		f, p, err := dataField(c, mem)
203		if err != nil {
204			return "", err
205		}
206		fields = append(fields, f)
207		puts = append(puts, p)
208	}
209	fmt.Fprintf(&b, "// %sEventData encodes a %s event for SendEvent.\ntype %sEventData struct {\n%s\n};\n", e.Name, e.Name, e.Name, strings.Join(fields, "\n"))
210	// Encode into a local array and store it last: borrowed fields (union
211	// views such as ClientMessageData) may alias dst, e.g. d.Put(&ev.Raw).
212	fmt.Fprintf(&b, "// Put writes the event (code %d, pads and sequence zeroed) into dst. dst may alias\n// memory that d's borrowed fields view, such as the Event.Raw they were read from.\nfunc (d *%sEventData) Put(dst *[32]byte) { var tmp [32]byte; tmp[0] = %d; %s; *dst = tmp };\n", e.Code, e.Name, e.Code, strings.Join(puts, ""))
213	return b.String(), nil
214}
215
216// errorIR is one <error> or <errorcopy>.
217type errorIR struct {
218	Const, Name string
219	Code        int
220}
221
222func errorConst(xname string) string {
223	return "Bad" + goName(strings.TrimPrefix(xname, "Bad"))
224}
225
226func resolveErrors(m *Module) ([]errorIR, error) {
227	var errs []errorIR
228	seen := map[int]bool{}
229	for _, n := range m.Root.Children {
230		if n.Kind != "error" && n.Kind != "errorcopy" {
231			continue
232		}
233		code, err := strconv.Atoi(n.Attr["number"])
234		if err != nil || code < 0 || code > 255 || seen[code] {
235			return nil, n.fail("invalid or duplicate error number %q", n.Attr["number"])
236		}
237		seen[code] = true
238		e := errorIR{Const: errorConst(n.Attr["name"]), Code: code}
239		e.Name = e.Const
240		if err := m.claim(e.Const, n); err != nil {
241			return nil, err
242		}
243		errs = append(errs, e)
244	}
245	return errs, nil
246}
247
248// errorCount is one past the highest error number.
249func errorCount(m *Module) int {
250	count := 0
251	for _, n := range m.Root.Children {
252		if n.Kind == "error" || n.Kind == "errorcopy" {
253			code, _ := strconv.Atoi(n.Attr["number"])
254			count = max(count, code+1)
255		}
256	}
257	return count
258}
259
260// eventCount is one past the highest non-XGE event number.
261func eventCount(m *Module) (int, error) {
262	count := 0
263	for _, n := range m.Root.Children {
264		if (n.Kind == "event" || n.Kind == "eventcopy") && n.Attr["xge"] != "true" {
265			code, err := eventCode(n)
266			if err != nil {
267				return 0, err
268			}
269			count = max(count, code+1)
270		}
271	}
272	return count, nil
273}
274
275func errorsDecl(m *Module, errs []errorIR) string {
276	var b strings.Builder
277	what := "wire error codes"
278	if m.ExtName != "" {
279		what = "error codes relative to the extension's FirstError (XError.Rel)"
280	}
281	fmt.Fprintf(&b, "// Error codes: %s.\nconst (\n", what)
282	for _, e := range errs {
283		fmt.Fprintf(&b, "%s uint8 = %d\n", e.Const, e.Code)
284	}
285	b.WriteString(");\n")
286	names := make([]string, errorCount(m))
287	for _, e := range errs {
288		names[e.Code] = e.Name
289	}
290	b.WriteString("var errorNames = [...]string{")
291	for _, n := range names {
292		fmt.Fprintf(&b, "%q,", n)
293	}
294	b.WriteString("};\n")
295	fmt.Fprintf(&b, "// ErrorName returns the name of an error code (%s), or \"\" when unknown.\n", what)
296	b.WriteString("func ErrorName(code uint8) string { if int(code) < len(errorNames) { return errorNames[code] }; return \"\" };\n")
297	return b.String()
298}
299
300func emitEvents(modules []*Module) error {
301	for _, m := range modules {
302		events, err := resolveEvents(m)
303		if err != nil {
304			return err
305		}
306		lists := map[string]string{}
307		var decls []string
308		for _, e := range events {
309			d, err := eventDecl(m, e, lists)
310			if err != nil {
311				return err
312			}
313			decls = append(decls, d)
314		}
315		var names []string
316		for name := range lists {
317			// Reuse a list view already declared by replies_generated.go.
318			if old := m.Names[name]; old != nil && old == m.Root {
319				continue
320			}
321			if err := m.claim(name, m.Root); err != nil {
322				return err
323			}
324			names = append(names, name)
325		}
326		sort.Strings(names)
327		for _, name := range names {
328			decls = append(decls, lists[name])
329		}
330		errs, err := resolveErrors(m)
331		if err != nil {
332			return err
333		}
334		if len(errs) > 0 {
335			data := struct {
336				Pkg   string
337				Decls []string
338			}{Pkg: m.Pkg, Decls: []string{errorsDecl(m, errs)}}
339			if err := writeTemplate(filepath.Join(m.Pkg, "errors_generated.go"), "errors", data); err != nil {
340				return err
341			}
342		}
343		if len(decls) == 0 {
344			continue
345		}
346		imports := importsFor(m, strings.Join(decls, "\n")) + "\nimport \"bnema.dev/xturbo\""
347		decls = append([]string{imports}, decls...)
348		data := struct {
349			Pkg   string
350			Decls []string
351		}{Pkg: m.Pkg, Decls: decls}
352		if err := writeTemplate(filepath.Join(m.Pkg, "events_generated.go"), "events", data); err != nil {
353			return err
354		}
355		if err := emitEventFuzz(m, events); err != nil {
356			return err
357		}
358	}
359	return nil
360}
361
362// emitEventFuzz writes FuzzEventViews: every view getter runs on arbitrary
363// bytes, and for core events EventData built from the getters survives a
364// Put/view round trip. Seeds are the package's event goldens.
365func emitEventFuzz(m *Module, events []*eventIR) error {
366	var b strings.Builder
367	b.WriteString("func FuzzEventViews(f *testing.F) {\nf.Add(make([]byte, 32));\n")
368	goldens, _ := filepath.Glob(filepath.Join("testdata/golden", m.Pkg, "*.event.hex"))
369	sort.Strings(goldens)
370	for _, g := range goldens {
371		fmt.Fprintf(&b, "if raw, err := xtest.LoadHex(%q); err != nil { f.Fatal(err) } else { f.Add(raw) };\n", "../"+filepath.ToSlash(g))
372	}
373	b.WriteString("f.Fuzz(func(t *testing.T, raw []byte) {\nif len(raw) < 32 { return };\nvar ev xturbo.Event;\ncopy(ev.Raw[:], raw);\nev.Tail = raw[32:];\n")
374	for _, e := range events {
375		if e.Copy != nil {
376			fmt.Fprintf(&b, "_ = %sOf(&ev);\n", e.Name)
377			continue
378		}
379		v := "v" + e.Name
380		if e.XGE {
381			fmt.Fprintf(&b, "if %s, ok := %sOf(&ev); ok { _ = %s.Extension(); _ = %s.Length(); _ = %s.EventType(); _ = len(%s.Tail());", v, e.Name, v, v, v, v)
382		} else {
383			fmt.Fprintf(&b, "{ %s := %sOf(&ev);", v, e.Name)
384		}
385		touch(&b, e.C, v, 0, nil, `t.Fatal("iterator error")`)
386		if m.ExtName == "" && !e.XGE {
387			var fields, checks []string
388			for _, mem := range e.C.Members {
389				if mem.Kind == "pad" {
390					continue
391				}
392				name := mem.Method
393				if name == "Put" {
394					name += "_"
395				}
396				switch {
397				case mem.Kind == "list" && mem.ListKind == "bytes":
398					fields = append(fields, fmt.Sprintf("%s: [%d]%s(%s.%s())", name, mem.Size/mem.Type.Size, mem.Elem, v, mem.Method))
399					checks = append(checks, fmt.Sprintf("if !bytes.Equal(w.%s(), %s.%s()) { t.Fatal(%q) };", mem.Method, v, mem.Method, e.Name+"."+mem.Method))
400				case mem.Kind == "field" && isScalar(mem.Type):
401					fields = append(fields, fmt.Sprintf("%s: %s.%s()", name, v, mem.Method))
402					checks = append(checks, fmt.Sprintf("if w.%s() != %s.%s() { t.Fatal(%q) };", mem.Method, v, mem.Method, e.Name+"."+mem.Method))
403				case mem.Kind == "field":
404					fields = append(fields, fmt.Sprintf("%s: %s.%s()", name, v, mem.Method))
405					checks = append(checks, fmt.Sprintf("if !bytes.Equal(out[%d:%d], ev.Raw[%d:%d]) { t.Fatal(%q) };", mem.Static, mem.Static+mem.Size, mem.Static, mem.Static+mem.Size, e.Name+"."+mem.Method))
406				default:
407					return e.Node.fail("event member %s has no EventData round trip", mem.XName)
408				}
409			}
410			fmt.Fprintf(&b, "d := %sEventData{%s}; var out [32]byte; d.Put(&out);", e.Name, strings.Join(fields, ", "))
411			fmt.Fprintf(&b, "if out[0] != %d { t.Fatal(\"%s code\") };", e.Code, e.Name)
412			b.WriteString("back := xturbo.Event{Raw: out};")
413			fmt.Fprintf(&b, "w := %sOf(&back); %s", e.Name, strings.Join(checks, ""))
414			// In place: dst aliases the Raw that d's borrowed fields view.
415			fmt.Fprintf(&b, "in := xturbo.Event{Raw: ev.Raw}; vi := %sOf(&in); di := %sEventData{%s}; di.Put(&in.Raw); if in.Raw != out { t.Fatal(%q) };",
416				e.Name, e.Name, strings.ReplaceAll(strings.Join(fields, ", "), v+".", "vi."), e.Name+" in-place Put")
417		}
418		b.WriteString("};\n")
419	}
420	b.WriteString("})\n}\n")
421	body := b.String()
422	imports := "import \"testing\"\nimport \"bnema.dev/xturbo\""
423	if len(goldens) > 0 {
424		imports += "\nimport \"bnema.dev/xturbo/internal/xtest\""
425	}
426	if strings.Contains(body, "bytes.") {
427		imports += "\nimport \"bytes\""
428	}
429	data := struct {
430		Pkg   string
431		Decls []string
432	}{Pkg: m.Pkg, Decls: []string{imports, body}}
433	return writeTemplate(filepath.Join(m.Pkg, "events_fuzz_test.go"), "events", data)
434}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 1

↑↓ moveenter openesc close