package main import ( "bufio" "bytes" "fmt" "io" "os" "path" "strings" ) const ( numbergenerics = "~int| ~uint32 | ~uint64 | ~uintptr" ) var ( typemap = map[string]string{ "char": "byte", "void*": "unsafe.Pointer", "const void*": "unsafe.Pointer", } ) var infhcache = make(map[string]string, 1024) func infh(name string) string { if s, ok := infhcache[name]; ok { return s } s := " [" + name + "]" infhcache[name] = s return s } func scanHeader(name string, scan *bufio.Scanner) { ln := 0 var regionfile *os.File symtab := symbolTable{ "ZE_APICALL": &symbol{symbolTypeConst, "ZE_APICALL", []string{""}}, "ZE_APIEXPORT": &symbol{symbolTypeConst, "ZE_APIEXPORT", []string{""}}, "ZE_DLLEXPORT": &symbol{symbolTypeConst, "ZE_DLLEXPORT", []string{""}}, } fileheadersb := strings.Builder{} region := "" _ = os.RemoveAll(name) err := os.MkdirAll(name, 0755) if err != nil { panic(fmt.Sprintf("%s L%d: cannot create region folder %s, err: %v", name, ln, region, err)) } for scan.Scan() { ln++ t := scan.Text() switch { // file headers case strings.HasPrefix(t, "/*") || strings.HasPrefix(t, " *") || strings.HasPrefix(t, " */"): fileheadersb.WriteString(t) fileheadersb.WriteString("\n") // pragma start case strings.HasPrefix(t, "#pragma region "): region = strings.TrimSpace(t[15:]) if region == "" { panic(fmt.Sprintf("%s L%d: unexpected empty region", name, ln)) } fmt.Println(infh(name), "scanning region", region) f, err := os.Create(path.Join(name, region+".go")) if err != nil { panic(fmt.Sprintf("%s L%d: cannot create region %s, err: %v", name, ln, region, err)) } f.WriteString("// Code generated by cmd/gen. DO NOT EDIT.\n\n") f.WriteString(fileheadersb.String()) f.WriteString("\n") f.WriteString("package ") f.WriteString(name) f.WriteString("\n\nimport (\n\t\"unsafe\"\n)\n\n") regionfile = f // block barrier case strings.HasPrefix(t, "///////////////////////////////////////////////////////////////////////////////"): fmt.Println(" [scan] enter", region, "block") ln = scanBlocks(name, scan, regionfile, ln, symtab) fmt.Println(" [scan] leave", region, "block") // pragma end case strings.HasPrefix(t, "#pragma endregion"): fmt.Println(infh(name), "close region", regionfile.Name()) _ = regionfile.Close() regionfile = nil // skip outer # case strings.HasPrefix(t, "#") || t == "" || strings.HasPrefix(t, "// ") || strings.HasPrefix(t, "extern "): fmt.Println(" [scan] skip", t) continue default: panic(fmt.Sprintf("%s L%d: unexpected line %s", name, ln, t)) } } } func checkSymbolName( symtab symbolTable, ln int, name, sname, goname string, sb *strings.Builder, f *os.File, eq func() *symbol) bool { if _, ok := symtab[sname]; ok { panic(fmt.Sprintf("%s L%d: func #define %s has been defined", name, ln, sname)) } enumsname := strings.TrimSuffix(sname, "_t") + "s_t" if _, ok := symtab[enumsname]; ok { fmt.Println("[warn] redirect name", sname, "to", enumsname) return true } symtab[sname] = eq() if sb.Len() == 0 { panic(fmt.Sprintf("%s L%d: unexpected non-comment for symbol %s", name, ln, sname)) } brief := " " + goname if goname != sname { brief = fmt.Sprint(brief, " (", sname, ")") } f.WriteString(strings.Replace(sb.String(), "/ @brief", brief, 1)) sb.Reset() return false } func scanBlocks( name string, scan *bufio.Scanner, f *os.File, ln int, symtab symbolTable, ) int { sb := strings.Builder{} skip2nextblk := false //TODO: check logic ifdepth := 0 isparsing := func() bool { //TODO: more condition return ifdepth > 0 } for scan.Scan() { ln++ t := scan.Text() switch { // block end case t == "": if isparsing() { continue } if sb.Len() != 0 { fmt.Printf("[warn] %s L%d: non-0 sb at block end: %s\n", name, ln, &sb) } return ln case skip2nextblk: continue // is definition's comment case strings.HasPrefix(t, "/// "): sb.WriteString(t) sb.WriteString("\n") case strings.HasPrefix(t, "#if"): if len(t) <= 8 { panic(fmt.Sprintf("%s L%d: unexpected short #if", name, ln)) } if t[3] == ' ' { // is platform related judgement skip2nextblk = true continue } if t[:8] != "#ifndef " { panic(fmt.Sprintf("%s L%d: unexpected #if type %s", name, ln, t)) } sname := strings.TrimSpace(t[8:]) _, ok := symtab[sname] if ok { ln = skip2endif(scan, ln) continue } ifdepth++ case strings.HasPrefix(t, "#endif"): ifdepth-- if ifdepth < 0 { panic(fmt.Sprintf("%s L%d: unexpected unpaired #endif", name, ln)) } case strings.HasPrefix(t, "#define "): if !strings.Contains(t, "(") { // is const define argseval := trimEmptyStringArray(strings.Split(t[8:], " ")) if len(argseval) != 2 { panic(fmt.Sprintf("%s L%d: unexpected const #define line %s", name, ln, t)) } sname := strings.TrimSpace(argseval[0]) val := strings.TrimSpace(argseval[1]) checkSymbolName(symtab, ln, name, sname, sname, &sb, f, func() *symbol { return newSymbolConst(sname, val) }) f.WriteString("const ") f.WriteString(sname) f.WriteString(" = ") f.WriteString(val) f.WriteString("\n\n") continue } sname, argseval, ok := strings.Cut(t[8:], "(") if !ok { panic(fmt.Sprintf("%s L%d: unexpected #define line %s", name, ln, t)) } args, n, err := getInsideRoundBrakets(argseval) if err != nil { panic(fmt.Sprintf("%s L%d: unexpected args bracket err: %v", name, ln, err)) } sname = strings.TrimSpace(sname) args = strings.TrimSpace(args) eval := strings.TrimSpace(argseval[n+1:]) checkSymbolName(symtab, ln, name, sname, sname, &sb, f, func() *symbol { return newSymbolFunc(sname, args, eval) }) f.WriteString("func ") f.WriteString(sname) f.WriteString("[T ") f.WriteString(numbergenerics) f.WriteString("](") f.WriteString(strings.Join(strings.Split(args, ","), " T,")) f.WriteString(" T) T {\n\treturn ") f.WriteString(eval) f.WriteString("\n}\n\n") case strings.HasPrefix(t, "typedef "): s, newln := get1sentence(t, scan, ln) if newln < 0 { panic(fmt.Sprintf("%s L%d: unexpected sentence end from", name, ln)) } ln = newln if strings.Contains(s, "\n") { // multi-line typedef lines := strings.Split(s, "\n") if len(lines) < 4 { panic(fmt.Sprintf("%s L%d: unexpected short multi typdef line %s", name, ln, t)) } if lines[1] != "{" { panic(fmt.Sprintf("%s L%d: unexpected non-{ multi typdef line %s", name, ln, t)) } switch { case strings.Contains(lines[0], " struct "): fsb := bytes.NewBuffer(make([]byte, 0, 256)) for i, stat := range lines[2:] { if strings.HasPrefix(stat, "}") { lines = lines[2+i:] break } fsb.WriteString("\n") if stat == "" { continue } stat = strings.TrimSpace(stat) vname := "" c := "" switch { case strings.HasPrefix(stat, "char ") || strings.HasPrefix(stat, "void* ") || strings.HasPrefix(stat, "const void*"): remains, sz := "", "" ok := false remains, c, ok = strings.Cut(stat, ";") if !ok { panic(fmt.Sprintf("%s L%d: unsupported non-end line %s", name, ln, stat)) } c = strings.TrimPrefix(strings.TrimSpace(c), "//") remains = strings.TrimSpace(remains) i := strings.LastIndex(remains, " ") vname = remains[i+1:] tn := strings.TrimSpace(remains[:i]) tname, ok := typemap[tn] if !ok { panic(fmt.Sprintf("%s L%d: unsupported type %s", name, ln, tname)) } vname, sz, ok = strings.Cut(vname, "[") vname = us2camel(strings.TrimSpace(vname)) fsb.WriteString("\t") if ok { // is array fsb.WriteString(vname) fsb.WriteString(" [") fsb.WriteString(sz) fsb.WriteString(tname) } else { fsb.WriteString(vname) fsb.WriteString(" ") fsb.WriteString(tname) } case strings.Contains(stat, "_t "): remains, sz := "", "" ok := false remains, c, ok = strings.Cut(stat, ";") if !ok { panic(fmt.Sprintf("%s L%d: unsupported non-end line %s", name, ln, stat)) } c = strings.TrimPrefix(strings.TrimSpace(c), "//") tname, remains, ok := strings.Cut(remains, "_t ") if !ok { panic(fmt.Sprintf("%s L%d: unexpected statement %s", name, ln, stat)) } tname = strings.TrimSpace(tname) k := tname + "_t" if symtab.contains(k) { tname = symtab[k].replace(k) } vname, sz, ok = strings.Cut(remains, "[") vname = us2camel(strings.TrimSpace(vname)) fsb.WriteString("\t") if ok { // is array fsb.WriteString(vname) fsb.WriteString(" [") fsb.WriteString(sz) fsb.WriteString(tname) } else { fsb.WriteString(vname) fsb.WriteString(" ") fsb.WriteString(tname) } case strings.HasPrefix(stat, "///< "): fsb.Truncate(fsb.Len() - 1) fsb.WriteString(stat[4:]) default: panic(fmt.Sprintf("%s L%d: unexpected statement %s", name, ln, stat)) } if c != "" { fsb.WriteString("\t// ") fsb.WriteString(strings.Replace(c, "/<", vname, 1)) } } if len(lines) != 1 { panic(fmt.Sprintf("%s L%d: unexpected len > 1 last lines %s", name, ln, strings.Join(lines, "\n"))) } if !strings.HasPrefix(lines[0], "}") { panic(fmt.Sprintf("%s L%d: unexpected last line %s", name, ln, lines[0])) } sname := strings.TrimSuffix(strings.TrimSpace(lines[0][1:]), ";") val := us2camel(strings.TrimSuffix(sname, "_t")) checkSymbolName(symtab, ln, name, sname, val, &sb, f, func() *symbol { return newSymbolConst(sname, val) }) f.WriteString("type ") f.WriteString(val) f.WriteString(" struct {") _, _ = io.Copy(f, fsb) f.WriteString("\n}\n\n") continue case strings.Contains(lines[0], " enum "): fsb := strings.Builder{} iscontinouscomment := false for i, stat := range lines[2:] { if strings.HasPrefix(stat, "}") { lines = lines[2+i:] break } fsb.WriteString("\n") if stat == "" { continue } stat = strings.TrimSpace(stat) if strings.HasPrefix(stat, "//") { if !iscontinouscomment { fsb.WriteString("\n") } fsb.WriteString("\t") fsb.WriteString(stat) iscontinouscomment = true continue } if iscontinouscomment { fsb.WriteString("\n") } iscontinouscomment = false constval, comment, ok := strings.Cut(stat, "//") if !ok { panic(fmt.Sprintf("%s L%d: unexpected enum line %s", name, ln, stat)) } cname, _, _ := strings.Cut(constval, "=") fsb.WriteString("\t") fsb.WriteString(strings.TrimSuffix(strings.TrimSpace(constval), ",")) fsb.WriteString("\t// ") fsb.WriteString(strings.Replace(comment, "/<", strings.TrimSpace(cname), 1)) } if len(lines) != 1 { panic(fmt.Sprintf("%s L%d: unexpected len > 1 last lines %s", name, ln, strings.Join(lines, "\n"))) } if !strings.HasPrefix(lines[0], "}") { panic(fmt.Sprintf("%s L%d: unexpected last line %s", name, ln, lines[0])) } sname := strings.TrimSuffix(strings.TrimSpace(lines[0][1:]), ";") val := us2camel(strings.TrimSuffix(sname, "_t")) redirect := checkSymbolName(symtab, ln, name, sname, val, &sb, f, func() *symbol { return newSymbolConst(sname, val) }) replaces := "" if !redirect { f.WriteString("type ") f.WriteString(val) f.WriteString(" uintptr\nconst (") replaces = " " + val + " =" } else { _, _ = f.Seek(-1, io.SeekCurrent) f.WriteString("const (") replaces = " " + val + "s =" } vars := strings.ReplaceAll(fsb.String(), " =", replaces) if strings.Contains(vars, "(") { vars = symtab.apply(vars) } f.WriteString(vars) f.WriteString("\n)\n\n") continue } } // single-line typedef, empty struct or type alias, replace with "type" statement typs := trimEmptyStringArray(strings.Split(s[8:], " ")) if len(typs) == 0 { panic(fmt.Sprintf("%s L%d: unexpected single typdef line %s", name, ln, t)) } if strings.TrimSpace(typs[0]) == "struct" { if len(typs) != 3 || !strings.Contains(typs[1], "handle_t") || !strings.Contains(typs[2], "*") { if symtab.contains(strings.TrimSuffix(typs[2], ";")) { fmt.Printf("[warn] %s L%d: skip duplicated single typdef %s\n", name, ln, strings.Join(typs, "_____")) } else { fmt.Printf("[warn] %s L%d: skip future expected single typdef %s\n", name, ln, strings.Join(typs, "_____")) } continue } typs = typs[1:] typs[0] = "uintptr" typs[1] = strings.TrimPrefix(strings.TrimSpace(typs[1]), "*") } if len(typs) != 2 { panic(fmt.Sprintf("%s L%d: unexpected typdef line %s", name, ln, t)) } sname := strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(typs[1]), ";")) val := us2camel(strings.TrimSuffix(sname, "_t")) origtyp := strings.TrimSuffix(strings.TrimSpace(typs[0]), "_t") checkSymbolName(symtab, ln, name, sname, val, &sb, f, func() *symbol { return newSymbolConst(sname, val) }) f.WriteString("type ") f.WriteString(val) f.WriteString(" ") f.WriteString(origtyp) f.WriteString("\n\n") default: panic(fmt.Sprintf("%s L%d: unexpected line %s", name, ln, t)) } } return ln }