GitRoot

Craft your forge, Build your project, Grow your community freely
  1// SPDX-FileCopyrightText: 2026 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package main
  6
  7import (
  8	"fmt"
  9	"go/types"
 10	"path/filepath"
 11	"slices"
 12	"strings"
 13
 14	"github.com/gobeam/stringy"
 15)
 16
 17type Zig struct{}
 18
 19func (Zig) Extension() string {
 20	return ".zig"
 21}
 22
 23func (t Zig) TemplateStruct(g *Generate, f *FileToGen, st Struct) (string, []string) {
 24	fieldStrings := []string{}
 25	imports := []string{}
 26	for _, field := range st.Fields {
 27		typeStr, imp := t.TypeToLang(field.Type(), f.OriginFilePath, g.structLocations)
 28		if imp != "" && !slices.Contains(imports, imp) {
 29			imports = append(imports, imp)
 30		}
 31		fieldName := stringy.New(field.Name()).SnakeCase().CamelCase().Get()
 32		fieldStrings = append(fieldStrings, fmt.Sprintf("    %s: %s,", fieldName, typeStr))
 33	}
 34	structName := stringy.New(st.TypeName).PascalCase().Get()
 35
 36	return fmt.Sprintf(`pub const %s = struct {
 37%s
 38};
 39`, structName, strings.Join(fieldStrings, "\n")), imports
 40}
 41
 42func (Zig) TemplateEnum(en Enum, variants []string) string {
 43	enumName := stringy.New(en.TypeName)
 44	enumBuilder := strings.Builder{}
 45
 46	enumBuilder.WriteString(fmt.Sprintf("pub const %s = enum {\n", enumName.PascalCase().Get()))
 47	for _, e := range variants {
 48		estringy := stringy.New(e)
 49		enumBuilder.WriteString(fmt.Sprintf("  %s,\n", estringy.ToLower()))
 50	}
 51	enumBuilder.WriteString("};\n")
 52
 53	return enumBuilder.String()
 54}
 55
 56func (ts Zig) FormatFuncName(fn Func) string {
 57	return stringy.New(fn.Name).SnakeCase().CamelCase().Get()
 58}
 59
 60func (ts Zig) FormatParams(sig *types.Signature, isMethod bool, currentFile string, locations map[string]string) (string, []string) {
 61	var params []string
 62	var imports []string
 63
 64	p := sig.Params()
 65	for i := 0; i < p.Len(); i++ {
 66		v := p.At(i)
 67		typeStr, imp := ts.TypeToLang(v.Type(), currentFile, locations)
 68		if imp != "" {
 69			imports = append(imports, imp)
 70		}
 71
 72		name := v.Name()
 73		if name == "" {
 74			name = fmt.Sprintf("arg%d", i)
 75		} else {
 76			name = stringy.New(name).PascalCase().Get()
 77		}
 78
 79		params = append(params, fmt.Sprintf("%s: %s", name, typeStr))
 80	}
 81
 82	return strings.Join(params, ", "), imports
 83}
 84
 85func (ts Zig) FormatReturnType(sig *types.Signature, currentFile string, locations map[string]string) string {
 86	res := sig.Results()
 87	if res.Len() == 0 {
 88		return " void "
 89	} else {
 90		typeStr, _ := ts.TypeToLang(res.At(0).Type(), currentFile, locations)
 91		return fmt.Sprintf(" %s ", typeStr)
 92	}
 93}
 94
 95func (ts Zig) FormatExportFunc(wasmName string, funcName string) string {
 96	return fmt.Sprintf(`pub export fn %s(message: [*]const u8, size: u32): void {
 97  ??
 98}
 99
100`, funcName)
101}
102
103func (Zig) FormatImportFunc(wasmName string, funcName string, paramsList string, returnType string) string {
104	return fmt.Sprintf(`pub extern "gitroot" fn %s(%s) %s;
105`, funcName, paramsList, returnType)
106}
107
108func (ts Zig) TypeToLang(t types.Type, currentFile string, locations map[string]string) (string, string) {
109	if sig, ok := t.(*types.Signature); ok && sig.Recv() == nil {
110		var cbParams []string
111		var cbImports []string
112
113		p := sig.Params()
114		for i := 0; i < p.Len(); i++ {
115			pType, pImp := ts.TypeToLang(p.At(i).Type(), currentFile, locations)
116			if pImp != "" {
117				cbImports = append(cbImports, pImp)
118			}
119			cbParams = append(cbParams, pType)
120		}
121
122		callbackType := fmt.Sprintf("*const fn(%s) void", strings.Join(cbParams, ", "))
123		return callbackType, strings.Join(cbImports, "\n")
124	}
125
126	if slice, ok := t.(*types.Slice); ok {
127		elemType, imp := ts.TypeToLang(slice.Elem(), currentFile, locations)
128		return fmt.Sprintf("[]%s", elemType), imp
129	}
130
131	if ptr, ok := t.(*types.Pointer); ok {
132		return ts.TypeToLang(ptr.Elem(), currentFile, locations)
133	}
134
135	if named, ok := t.(*types.Named); ok {
136		obj := named.Obj()
137		typeName := stringy.New(obj.Name()).PascalCase().Get()
138
139		originFile, exists := locations[obj.Name()]
140		if exists {
141			if originFile == currentFile {
142				return typeName, ""
143			}
144
145			moduleName := strings.TrimSuffix(filepath.Base(originFile), ".go")
146			rustImport := fmt.Sprintf(`const %s = @import("%s.zig");`, typeName, moduleName)
147			return typeName, rustImport
148		}
149	}
150
151	switch t.String() {
152	case "string":
153		return "[]const u8", ""
154	case "map[string]string":
155		return "std.StringHashMap([]const u8)", "const std = @import(\"std\");"
156	case "map[string]any":
157		return "std.StringHashMap([]const u8)", "const std = @import(\"std\");"
158	case "uint64":
159		return "u64", ""
160	case "uint32":
161		return "u32", ""
162	case "int32":
163		return "u32", ""
164	case "int":
165		return "u32", ""
166	case "bool":
167		return "bool", ""
168	case "time.Time":
169		return "datetime.DateTime", "const datetime = @import(\"datetime\");"
170	}
171
172	fmt.Printf("🐞 Type not found %s\n", t.String())
173
174	return t.String(), ""
175}