v0.1.0 0e37717

gen.go RawHistoryFolder

1// Package gen generates the deliberately bounded libxkbcommon ABI subset.
2package gen
3
4import (
5	"fmt"
6	"go/format"
7	"os"
8	"regexp"
9	"strconv"
10	"strings"
11)
12
13var comments = regexp.MustCompile(`(?s)/\*.*?\*/|//[^\n]*`)
14
15func clean(s string) string { return comments.ReplaceAllString(s, "") }
16func declared(h, name string) bool {
17	re := regexp.MustCompile(`(?m)\b` + regexp.QuoteMeta(name) + `\s*\([^;{}]*\)\s*;`)
18	return re.MatchString(clean(h))
19}
20
21var def = regexp.MustCompile(`(?m)^\s*#define\s+(XKB_(?:KEY_|MOD_NAME_|VMOD_NAME_|LED_NAME_)[A-Za-z0-9_]+)\s+("[^"]*"|0x[0-9a-fA-F]+|[0-9]+)\b?`)
22var en = regexp.MustCompile(`(?s)enum\s+xkb_[a-z_]+\s*\{([^}]*)\}`)
23var item = regexp.MustCompile(`^\s*(XKB_[A-Z][A-Z0-9_]*)(?:\s*=\s*(.+))?$`)
24
25func constants(h string) string {
26	h = clean(h)
27	var b strings.Builder
28	for _, e := range en.FindAllStringSubmatch(h, -1) {
29		last := int64(-1)
30		for _, part := range strings.Split(e[1], ",") {
31			m := item.FindStringSubmatch(strings.TrimSpace(part))
32			if m == nil {
33				continue
34			}
35			expr := strings.TrimSpace(m[2])
36			if expr == "" {
37				last++
38				expr = strconv.FormatInt(last, 10)
39			} else if n, err := strconv.ParseInt(expr, 0, 64); err == nil {
40				last = n
41			} else if v := regexp.MustCompile(`^\(1 << (\d+)\)$`).FindStringSubmatch(expr); v != nil {
42				shift, _ := strconv.Atoi(v[1])
43				last = 1 << shift
44			} else {
45				continue
46			}
47			fmt.Fprintf(&b, "%s = %s\n", m[1], expr)
48		}
49	}
50	for _, m := range def.FindAllStringSubmatch(h, -1) {
51		fmt.Fprintf(&b, "%s = %s\n", m[1], m[2])
52	}
53	return b.String()
54}
55
56// Functions describes the bound ABI; each name is checked against the pinned headers.
57// Pointers are uintptr to avoid GC passing Go pointers to native code unexpectedly.
58var Functions = []struct{ Name, Signature string }{
59	{"xkb_context_new", "func(uint32) uintptr"}, {"xkb_context_ref", "func(uintptr) uintptr"}, {"xkb_context_unref", "func(uintptr)"},
60	{"xkb_keymap_new_from_names", "func(uintptr, uintptr, uint32) uintptr"}, {"xkb_keymap_new_from_string", "func(uintptr, uintptr, uint32, uint32) uintptr"}, {"xkb_keymap_new_from_buffer", "func(uintptr, uintptr, uintptr, uint32, uint32) uintptr"},
61	{"xkb_keymap_ref", "func(uintptr) uintptr"}, {"xkb_keymap_unref", "func(uintptr)"}, {"xkb_keymap_get_as_string", "func(uintptr,uint32) uintptr"},
62	{"xkb_keymap_mod_get_index", "func(uintptr,uintptr) uint32"}, {"xkb_keymap_num_layouts", "func(uintptr) uint32"},
63	{"xkb_state_new", "func(uintptr) uintptr"}, {"xkb_state_ref", "func(uintptr) uintptr"}, {"xkb_state_unref", "func(uintptr)"},
64	{"xkb_state_update_mask", "func(uintptr,uint32,uint32,uint32,uint32,uint32,uint32) uint32"},
65	{"xkb_state_serialize_layout", "func(uintptr,uint32) uint32"}, {"xkb_state_serialize_mods", "func(uintptr,uint32) uint32"},
66	{"xkb_state_key_get_one_sym", "func(uintptr,uint32) uint32"}, {"xkb_state_key_get_utf8", "func(uintptr,uint32,uintptr,uintptr) int32"},
67	{"xkb_keysym_get_name", "func(uint32,uintptr,uintptr) int32"}, {"xkb_keysym_from_name", "func(uintptr,uint32) uint32"},
68	{"xkb_compose_table_new_from_locale", "func(uintptr,uintptr,uint32) uintptr"}, {"xkb_compose_table_ref", "func(uintptr) uintptr"}, {"xkb_compose_table_unref", "func(uintptr)"},
69	{"xkb_compose_state_new", "func(uintptr,uint32) uintptr"}, {"xkb_compose_state_ref", "func(uintptr) uintptr"}, {"xkb_compose_state_unref", "func(uintptr)"},
70	{"xkb_compose_state_feed", "func(uintptr,uint32) uint32"}, {"xkb_compose_state_get_status", "func(uintptr) uint32"},
71	{"xkb_compose_state_reset", "func(uintptr)"}, {"xkb_compose_state_get_utf8", "func(uintptr,uintptr,uintptr) int32"}, {"xkb_compose_state_get_one_sym", "func(uintptr) uint32"},
72}
73
74func field(name string) string { return strings.ToUpper(name[:1]) + name[1:] }
75func Generate(root string) error {
76	h1, err := os.ReadFile(root + "/upstream/xkbcommon.h")
77	if err != nil {
78		return err
79	}
80	h2, err := os.ReadFile(root + "/upstream/xkbcommon-compose.h")
81	if err != nil {
82		return err
83	}
84	hdr := string(h1) + "\n" + string(h2)
85	var b strings.Builder
86	b.WriteString("// Code generated by go generate; DO NOT EDIT.\npackage raw\n\n")
87	b.WriteString("// Opaque C handles and scalar typedefs.\ntype Context uintptr\ntype Keymap uintptr\ntype State uintptr\ntype ComposeTable uintptr\ntype ComposeState uintptr\ntype Keycode uint32\ntype Keysym uint32\ntype LayoutIndex uint32\ntype ModIndex uint32\ntype ModMask uint32\ntype LayoutMask uint32\n")
88	b.WriteString("// RuleNames matches struct xkb_rule_names (five const char pointers).\ntype RuleNames struct { Rules, Model, Layout, Variant, Options uintptr }\n")
89	b.WriteString("const (\n" + constants(hdr))
90	for _, f := range []string{"xkbcommon-keysyms.h", "xkbcommon-names.h"} {
91		data, e := os.ReadFile(root + "/upstream/" + f)
92		if e != nil {
93			return e
94		}
95		b.WriteString(constants(string(data)))
96	}
97	b.WriteString(")\n\n// Funcs holds resolved library functions.\ntype Funcs struct {\n")
98	for _, f := range Functions {
99		if !declared(hdr, f.Name) {
100			return fmt.Errorf("missing pinned declaration: %s", f.Name)
101		}
102		fmt.Fprintf(&b, "%s %s\n", field(f.Name), f.Signature)
103	}
104	b.WriteString("}\n\n// Symbols is the complete required symbol set.\nvar Symbols = []string{\n")
105	for _, f := range Functions {
106		fmt.Fprintf(&b, "%q,\n", f.Name)
107	}
108	b.WriteString("}\n\n// Register registers prevalidated addresses in a Funcs value.\nfunc Register(f *Funcs, register func(any,uintptr)) {\n")
109	for _, f := range Functions {
110		fmt.Fprintf(&b, "register(&f.%s, addresses[%q])\n", field(f.Name), f.Name)
111	}
112	b.WriteString("}\n")
113	// address map is passed in, not a global mutable singleton.
114	s := strings.Replace(b.String(), "func Register(f *Funcs, register func(any,uintptr))", "func Register(f *Funcs, addresses map[string]uintptr, register func(any,uintptr))", 1)
115	formatted, err := format.Source([]byte(s))
116	if err != nil {
117		return fmt.Errorf("format generated file: %w", err)
118	}
119	return os.WriteFile(root+"/raw/generated.go", formatted, 0644)
120}

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 2

↑↓ moveenter openesc close