v0.1.0 602baa7

resolve.go RawHistoryFolder

1package main
2
3import (
4	"encoding/json"
5	"fmt"
6	"os"
7	"path/filepath"
8	"strconv"
9	"strings"
10)
11
12var builtin = map[string]*Type{}
13
14func init() {
15	for _, b := range []struct {
16		xml, goType string
17		size        int
18	}{
19		{"CARD8", "uint8", 1}, {"CARD16", "uint16", 2}, {"CARD32", "uint32", 4}, {"CARD64", "uint64", 8},
20		{"INT8", "int8", 1}, {"INT16", "int16", 2}, {"INT32", "int32", 4}, {"INT64", "int64", 8},
21		{"BOOL", "bool", 1}, {"BOOL32", "bool", 4}, {"char", "byte", 1}, {"void", "byte", 1}, {"BYTE", "byte", 1},
22	} {
23		builtin[b.xml] = &Type{Name: b.xml, GoType: b.goType, Base: b.goType, Size: b.size, Kind: "scalar", Resolved: true}
24	}
25}
26func loadModules() ([]*Module, error) {
27	var manifest struct {
28		Files []struct {
29			Path string `json:"path"`
30		} `json:"files"`
31	}
32	b, err := os.ReadFile("spec/manifest.json")
33	if err != nil {
34		return nil, err
35	}
36	if err = json.Unmarshal(b, &manifest); err != nil {
37		return nil, err
38	}
39	var nodes []*Node
40	for _, f := range manifest.Files {
41		if filepath.Ext(f.Path) != ".xml" {
42			continue
43		}
44		n, err := parseFile(f.Path)
45		if err != nil {
46			return nil, err
47		}
48		nodes = append(nodes, n)
49	}
50	return resolveModules(nodes)
51}
52func (m *Module) claim(name string, n *Node) error {
53	if old := m.Names[name]; old != nil {
54		return n.fail("Go name collision %s (previously %s:%d)", name, old.File, old.Line)
55	}
56	m.Names[name] = n
57	return nil
58}
59func resolveModules(nodes []*Node) ([]*Module, error) {
60	var modules []*Module
61	byHeader := map[string]*Module{}
62	for _, n := range nodes {
63		m := &Module{Header: n.Attr["header"], Pkg: strings.ReplaceAll(n.Attr["header"], "_", ""), XName: n.Attr["extension-xname"], ExtName: n.Attr["extension-name"], Root: n, Symbols: map[string]*Type{}, Names: map[string]*Node{}}
64		if byHeader[m.Header] != nil {
65			return nil, n.fail("duplicate module %s", m.Header)
66		}
67		byHeader[m.Header] = m
68		modules = append(modules, m)
69		for _, c := range n.Children {
70			switch c.Kind {
71			case "xidtype", "xidunion", "typedef", "struct", "union":
72				raw := c.Attr["name"]
73				if c.Kind == "typedef" {
74					raw = c.Attr["newname"]
75				}
76				name := goName(raw)
77				if err := m.claim(name, c); err != nil {
78					return nil, err
79				}
80				typ := &Type{Name: raw, GoType: name, Kind: c.Kind, Node: c, Module: m}
81				m.Types = append(m.Types, typ)
82				m.Symbols[raw] = typ
83			}
84		}
85	}
86	for _, m := range modules {
87		for _, n := range m.Root.Children {
88			if n.Kind == "import" {
89				dep := byHeader[n.Text]
90				if dep == nil {
91					return nil, n.fail("unknown import %s", n.Text)
92				}
93				m.Imports = append(m.Imports, dep)
94			}
95		}
96	}
97	for _, m := range modules {
98		for _, typ := range m.Types {
99			if err := resolveType(typ); err != nil {
100				return nil, err
101			}
102		}
103	}
104	for _, m := range modules {
105		if err := resolveEnums(m); err != nil {
106			return nil, err
107		}
108	}
109	// Re-resolve enum annotations after widths are known, without changing wire sizes.
110	for _, m := range modules {
111		for _, typ := range m.Types {
112			for i := range typ.Fields {
113				f := &typ.Fields[i]
114				if f.Kind != "field" {
115					continue
116				}
117				if enum := f.Node.Attr["enum"]; enum != "" {
118					if e, owner := findEnum(m, enum); e != nil {
119						f.GoType = qualified(m, owner, e.Name)
120					}
121				}
122				if mask := f.Node.Attr["mask"]; mask != "" {
123					if e, owner := findEnum(m, mask); e != nil {
124						f.GoType = qualified(m, owner, e.Name)
125					}
126				}
127			}
128		}
129	}
130	return modules, nil
131}
132func lookup(m *Module, name string) (*Type, error) {
133	if t := builtin[name]; t != nil {
134		return t, nil
135	}
136	if strings.Contains(name, ":") {
137		parts := strings.SplitN(name, ":", 2)
138		for _, dep := range m.Imports {
139			if dep.Header == parts[0] {
140				return lookup(dep, parts[1])
141			}
142		}
143		return nil, m.Root.fail("unknown qualified type %s", name)
144	}
145	if t := m.Symbols[name]; t != nil {
146		return t, nil
147	}
148	var found *Type
149	for _, dep := range m.Imports {
150		if t := dep.Symbols[name]; t != nil {
151			if found != nil && found != t {
152				return nil, m.Root.fail("ambiguous type %s", name)
153			}
154			found = t
155		}
156	}
157	if found != nil {
158		return found, nil
159	}
160	return nil, m.Root.fail("unknown type %s", name)
161}
162func qualified(m, owner *Module, name string) string {
163	if owner == nil || owner == m {
164		return name
165	}
166	return owner.Pkg + "." + name
167}
168func resolveType(t *Type) error {
169	if t.Resolved {
170		return nil
171	}
172	if t.Resolving {
173		return t.Node.fail("recursive type %s", t.Name)
174	}
175	t.Resolving = true
176	switch t.Kind {
177	case "xidtype", "xidunion":
178		t.Size = 4
179		t.Base = "uint32"
180	case "typedef":
181		base, err := lookup(t.Module, t.Node.Attr["oldname"])
182		if err != nil {
183			return err
184		}
185		if err := resolveType(base); err != nil {
186			return err
187		}
188		t.Size = base.Size
189		t.Base = base.Base
190		t.Variable = base.Variable
191	default:
192		off := 0
193		for _, n := range t.Node.Children {
194			if n.Kind == "doc" {
195				continue
196			}
197			f := Field{Name: goName(n.Attr["name"]), Param: paramName(n.Attr["name"]), Kind: n.Kind, Node: n, Off: off}
198			if t.Kind == "union" {
199				f.Off = 0
200			}
201			switch n.Kind {
202			case "pad":
203				if n.Attr["align"] != "" {
204					f.Align, _ = strconv.Atoi(n.Attr["align"])
205					f.Size = (-off) & (f.Align - 1)
206				} else {
207					f.Size, _ = strconv.Atoi(n.Attr["bytes"])
208				}
209			case "field", "list":
210				elem, err := lookup(t.Module, n.Attr["type"])
211				if err != nil {
212					return n.fail("%v", err)
213				}
214				if err := resolveType(elem); err != nil {
215					return err
216				}
217				f.Type = elem
218				f.GoType = qualified(t.Module, elem.Module, elem.GoType)
219				f.Size = elem.Size
220				if n.Kind == "list" {
221					if len(n.Children) > 0 {
222						f.Expr = n.Children[0]
223					}
224					if f.Expr != nil && f.Expr.Kind == "value" && !elem.Variable {
225						count, err := strconv.Atoi(f.Expr.Text)
226						if err != nil || count < 0 {
227							return n.fail("invalid constant list size")
228						}
229						f.Count = count
230						f.Size *= count
231						f.GoType = fmt.Sprintf("[%d]%s", count, f.GoType)
232					} else {
233						t.Variable = true
234						f.Size = 0
235						f.GoType = "[]" + f.GoType
236						if n.Attr["type"] == "char" {
237							f.GoType = "string"
238						}
239					}
240				}
241				if elem.Variable {
242					t.Variable = true
243				}
244			default:
245				return n.fail("unsupported type member %s", n.Kind)
246			}
247			t.Fields = append(t.Fields, f)
248			if t.Kind == "union" {
249				if f.Size > off {
250					off = f.Size
251				}
252			} else if !t.Variable {
253				off += f.Size
254			}
255		}
256		t.Size = off
257	}
258	t.Resolving = false
259	t.Resolved = true
260	return nil
261}
262func findEnum(m *Module, name string) (*Enum, *Module) {
263	if strings.Contains(name, ":") {
264		p := strings.SplitN(name, ":", 2)
265		for _, dep := range m.Imports {
266			if dep.Header == p[0] {
267				return findEnum(dep, p[1])
268			}
269		}
270		return nil, nil
271	}
272	for i := range m.Enums {
273		if m.Enums[i].Node.Attr["name"] == name {
274			return &m.Enums[i], m
275		}
276	}
277	for _, dep := range m.Imports {
278		for i := range dep.Enums {
279			if dep.Enums[i].Node.Attr["name"] == name {
280				return &dep.Enums[i], dep
281			}
282		}
283	}
284	return nil, nil
285}
286func resolveEnums(m *Module) error {
287	for _, n := range m.Root.Children {
288		if n.Kind != "enum" {
289			continue
290		}
291		e := Enum{Name: goName(n.Attr["name"]), Base: "uint32", Node: n}
292		if old := m.Names[e.Name]; old != nil {
293			if !contains("xidtype xidunion typedef", old.Kind) {
294				return n.fail("Go name collision %s", e.Name)
295			}
296			e.Existing = true
297		} else if err := m.claim(e.Name, n); err != nil {
298			return err
299		}
300		var max int64
301		for _, item := range n.Children {
302			if item.Kind == "doc" {
303				continue
304			}
305			ex := item.Children[0]
306			v, err := strconv.ParseInt(ex.Text, 0, 64)
307			if err != nil {
308				return ex.fail("invalid enum value")
309			}
310			if ex.Kind == "bit" {
311				if v < 0 || v > 31 {
312					return ex.fail("enum bit out of range")
313				}
314				v = 1 << uint(v)
315			}
316			if v < 0 || v > 0xffffffff {
317				return ex.fail("enum value out of range")
318			}
319			if v > max {
320				max = v
321			}
322			name := e.Name + goName(item.Attr["name"])
323			if err := m.claim(name, item); err != nil {
324				return err
325			}
326			e.Items = append(e.Items, EnumItem{name, v})
327		}
328		// Narrow only when every wire use has the same narrower width.
329		width := 0
330		mixed := false
331		var scan func(*Node)
332		scan = func(x *Node) {
333			if x.Attr["enum"] == n.Attr["name"] || x.Attr["mask"] == n.Attr["name"] || x.Attr["altenum"] == n.Attr["name"] {
334				typ, err := lookup(m, x.Attr["type"])
335				if err == nil {
336					if width != 0 && width != typ.Size {
337						mixed = true
338					}
339					width = typ.Size
340				}
341			}
342			for _, c := range x.Children {
343				scan(c)
344			}
345		}
346		scan(m.Root)
347		if !mixed && width == 1 && max <= 255 {
348			e.Base = "uint8"
349		}
350		if !mixed && width == 2 && max <= 65535 {
351			e.Base = "uint16"
352		}
353		m.Enums = append(m.Enums, e)
354	}
355	return nil
356}

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