package main import ( "bytes" "embed" "fmt" "go/format" "os" "path/filepath" "sort" "strings" "text/template" ) //go:embed templates/*.tmpl var templates embed.FS func writeTemplate(path, name string, data any) error { t, err := template.ParseFS(templates, "templates/"+name+".tmpl") if err != nil { return err } var b bytes.Buffer if err = t.Execute(&b, data); err != nil { return err } code, err := format.Source(b.Bytes()) if err != nil { return fmt.Errorf("format %s: %w\n%s", path, err, b.String()) } if err = os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } return os.WriteFile(path, code, 0644) } func putScalar(t *Type, src, dst string) string { if t.Base == "bool" { if t.Size == 1 { return fmt.Sprintf("if %s { %s[0]=1 } else { %s[0]=0 };", src, dst, dst) } return fmt.Sprintf("if %s { binary.LittleEndian.PutUint32(%s,1) } else { binary.LittleEndian.PutUint32(%s,0) };", src, dst, dst) } if t.Size == 1 { return fmt.Sprintf("%s[0]=byte(%s);", dst, src) } return fmt.Sprintf("binary.LittleEndian.PutUint%d(%s,uint%d(%s));", 8*t.Size, dst, 8*t.Size, src) } func getScalar(t *Type, goType, src string) string { value := src + "[0]" if t.Size > 1 { value = fmt.Sprintf("binary.LittleEndian.Uint%d(%s)", 8*t.Size, src) } if t.Base == "bool" { return "(" + value + "!=0)" } return goType + "(" + value + ")" } func isScalar(t *Type) bool { return t.Base != "" } func typeDecl(t *Type) (string, error) { if isScalar(t) { return fmt.Sprintf("type %s %s", t.GoType, t.Base), nil } var b strings.Builder if t.Kind == "union" { fmt.Fprintf(&b, "type %s struct { raw []byte }; const %sSize=%d;\n", t.GoType, t.GoType, t.Size) fmt.Fprintf(&b, "func Get%s(p []byte) %s { return %s{raw:p[:%d]} };\n", t.GoType, t.GoType, t.GoType, t.Size) fmt.Fprintf(&b, "func (v %s) Put(p []byte) { copy(p[:%d],v.raw) };\n", t.GoType, t.Size) for _, f := range t.Fields { if f.Kind == "pad" { continue } if f.Kind == "list" { fmt.Fprintf(&b, "func (v %s) %s() (out %s) { for i:=range out { out[i]=%s }; return };\n", t.GoType, f.Name, f.GoType, getScalar(f.Type, qualified(t.Module, f.Type.Module, f.Type.GoType), fmt.Sprintf("v.raw[i*%d:]", f.Type.Size))) } else { fmt.Fprintf(&b, "func (v %s) %s() %s { return %s };\n", t.GoType, f.Name, f.GoType, getScalar(f.Type, f.GoType, "v.raw")) } } return b.String(), nil } fmt.Fprintf(&b, "type %s struct {\n", t.GoType) for _, f := range t.Fields { if f.Kind != "pad" { name := f.Name if name == "Size" || name == "Put" { name += "_" } fmt.Fprintf(&b, "%s %s\n", name, f.GoType) } } b.WriteString("}\n") fmt.Fprintf(&b, "const %sSize=%d;\n", t.GoType, t.Size) if t.Variable { fmt.Fprintf(&b, "func (v %s) Size() int { off:=0;", t.GoType) for _, f := range t.Fields { name := f.Name if name == "Size" || name == "Put" { name += "_" } if f.Align > 0 { fmt.Fprintf(&b, "off=(off+%d)&^%d;", f.Align-1, f.Align-1) } else if f.Kind == "list" && f.Count == 0 { if f.Type.Variable { fmt.Fprintf(&b, "for i:=range v.%s { off+=v.%s[i].Size() };", name, name) } else { fmt.Fprintf(&b, "off+=len(v.%s)*%d;", name, f.Type.Size) } } else { fmt.Fprintf(&b, "off+=%d;", f.Size) } } b.WriteString("return off };\n") } fmt.Fprintf(&b, "func (v %s) Put(p []byte) { ", t.GoType) if t.Variable { b.WriteString("off:=0;") } else { fmt.Fprintf(&b, "clear(p[:%d]);", t.Size) } for _, f := range t.Fields { name := f.Name if name == "Size" || name == "Put" { name += "_" } off := fmt.Sprint(f.Off) if t.Variable { off = "off" } if f.Kind == "pad" { if t.Variable { if f.Align > 0 { fmt.Fprintf(&b, "{ next:=(off+%d)&^%d; clear(p[off:next]); off=next };", f.Align-1, f.Align-1) } else { fmt.Fprintf(&b, "clear(p[off:off+%d]); off+=%d;", f.Size, f.Size) } } continue } src := "v." + name dst := "p[" + off + ":]" if f.Kind == "list" { fmt.Fprintf(&b, "for i:=range %s {", src) item := src + "[i]" target := fmt.Sprintf("p[%s+i*%d:]", off, f.Type.Size) if f.Type.Variable { target = "p[off:]" } if isScalar(f.Type) { b.WriteString(putScalar(f.Type, item, target)) } else { fmt.Fprintf(&b, "%s.Put(%s);", item, target) } if f.Type.Variable { fmt.Fprintf(&b, "off+=%s.Size();", item) } b.WriteString("};") if t.Variable && !f.Type.Variable { fmt.Fprintf(&b, "off+=len(%s)*%d;", src, f.Type.Size) } } else { if isScalar(f.Type) { b.WriteString(putScalar(f.Type, src, dst)) } else { fmt.Fprintf(&b, "%s.Put(%s);", src, dst) } if t.Variable { fmt.Fprintf(&b, "off+=%d;", f.Size) } } } b.WriteString("}\n") if !t.Variable { fmt.Fprintf(&b, "func Get%s(p []byte) (v %s) { _=p[%d];", t.GoType, t.GoType, t.Size-1) for _, f := range t.Fields { if f.Kind == "pad" { continue } dst := "v." + f.Name src := fmt.Sprintf("p[%d:]", f.Off) if f.Kind == "list" { fmt.Fprintf(&b, "for i:=range %s { %s[i]=%s };", dst, dst, getScalar(f.Type, qualified(t.Module, f.Type.Module, f.Type.GoType), fmt.Sprintf("p[%d+i*%d:]", f.Off, f.Type.Size))) } else if isScalar(f.Type) { fmt.Fprintf(&b, "%s=%s;", dst, getScalar(f.Type, f.GoType, src)) } else { fmt.Fprintf(&b, "%s=%s(%s);", dst, qualified(t.Module, f.Type.Module, "Get"+f.Type.GoType), src) } } b.WriteString("return };\n") } return b.String(), nil } func importsFor(m *Module, decls string) string { paths := map[string]string{"binary.": "encoding/binary", "bits.": "math/bits"} for _, dep := range m.Imports { paths[dep.Pkg+"."] = "bnema.dev/xturbo/" + dep.Pkg } var imports []string for prefix, path := range paths { if strings.Contains(decls, prefix) { imports = append(imports, fmt.Sprintf("import %q", path)) } } sort.Strings(imports) return strings.Join(imports, "\n") } func emitTypes(modules []*Module) error { for _, m := range modules { for _, t := range m.Types { decl, err := typeDecl(t) if err != nil { return err } m.Decls = append(m.Decls, decl) } for _, e := range m.Enums { var b strings.Builder if !e.Existing { fmt.Fprintf(&b, "type %s %s;\n", e.Name, e.Base) } for _, item := range e.Items { fmt.Fprintf(&b, "const %s %s = %d;\n", item.Name, e.Name, item.Value) } m.Decls = append(m.Decls, b.String()) } imp := importsFor(m, strings.Join(m.Decls, "\n")) m.Decls = append([]string{imp}, m.Decls...) if err := writeTemplate(filepath.Join(m.Pkg, "types_generated.go"), "types", m); err != nil { return err } for _, t := range m.Types { if t.Kind == "struct" || t.Kind == "union" { m.SizeChecks = append(m.SizeChecks, fmt.Sprintf("if %sSize != %d { t.Errorf(%q, %sSize) }", t.GoType, t.Size, t.GoType+" fixed prefix = %d", t.GoType)) } } if err := packetSizeChecks(m); err != nil { return err } if err := writeTemplate(filepath.Join(m.Pkg, "sizes_test.go"), "sizes", m); err != nil { return err } } var extensions []*Module for _, m := range modules { if m.ExtName != "" { count, err := eventCount(m) if err != nil { return err } m.NumEvents, m.NumErrors = count, errorCount(m) extensions = append(extensions, m) } } return writeTemplate("ext_generated.go", "ext", extensions) } func sizeCheck(name string, got, want int) string { return fmt.Sprintf("if got:= %d; got != %d { t.Errorf(%q,got) }", got, want, name+" fixed size = %d") }