// protoc-gen-tarantool is a protoc plugin that generates Lua code targeting
// Tarantool's LuaJIT runtime, paired with the runtime/protobuf Lua package.
//
// Usage:
// protoc --tarantool_out=./out --plugin=./protoc-gen-tarantool foo.proto
//
// File option (in your .proto):
// import "tarantool/tarantool.proto";
// option (tarantool.lua_package) = "myapp.proto.foo";
package main
import (
"flag"
"fmt"
"io"
"os"
"strings"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
"sourcecraft.dev/bigbes/tarantool-protobuf/cmd/protoc-gen-tarantool/internal/gen"
)
func main() {
in, err := io.ReadAll(os.Stdin)
if err != nil {
fail("read stdin: %v", err)
}
req := &pluginpb.CodeGeneratorRequest{}
if err := proto.Unmarshal(in, req); err != nil {
fail("parse CodeGeneratorRequest: %v", err)
}
// protogen requires a go_package on every input file even when we are not
// generating Go. Inject a synthetic value when it's missing — it's never
// surfaced to the generated Lua.
for _, f := range req.ProtoFile {
if f.Options == nil {
f.Options = &descriptorpb.FileOptions{}
}
if f.Options.GoPackage == nil {
stub := "tarantoolpb_synthetic/" + strings.TrimSuffix(f.GetName(), ".proto")
f.Options.GoPackage = proto.String(stub)
}
}
var flags flag.FlagSet
modeFlag := flags.String("mode", "full", "codegen mode: full | runtime")
prefixFlag := flags.String("prefix", "",
"prefix prepended to every generated module's Lua require path "+
"(useful for side-by-side generation in tests)")
int64AsNumberFlag := flags.Bool("int64_as_number", false,
"opt-in (mode=full only): emit decoders that return a Lua number "+
"for int64/uint64/sint64/fixed64/sfixed64 values that fit in "+
"[-2^53, 2^53), falling back to cdata for values outside that "+
"range. Skips the per-call cdata allocation on the common case "+
"(IDs, timestamps in seconds/ms, byte counts). Decoded type is "+
"unstable (number-or-cdata); arithmetic works transparently. "+
"Default false (always cdata).")
plugin, err := protogen.Options{ParamFunc: flags.Set}.New(req)
if err != nil {
fail("init protogen: %v", err)
}
mode, err := gen.ParseMode(*modeFlag)
if err != nil {
fail("%v", err)
}
if *int64AsNumberFlag && mode != gen.ModeFull {
fail("int64_as_number is currently mode=full only " +
"(runtime mode would require a descriptor flag wired through pb.codec)")
}
cfg := gen.Config{Mode: mode, Prefix: *prefixFlag, Int64AsNumber: *int64AsNumberFlag}
// Advertise proto3 optional support so protoc lets us see those fields.
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
for _, file := range plugin.Files {
if !file.Generate {
continue
}
if err := gen.GenerateFile(plugin, file, cfg); err != nil {
plugin.Error(err)
}
}
out, err := proto.Marshal(plugin.Response())
if err != nil {
fail("marshal CodeGeneratorResponse: %v", err)
}
if _, err := os.Stdout.Write(out); err != nil {
fail("write stdout: %v", err)
}
}
func fail(format string, args ...any) {
fmt.Fprintf(os.Stderr, "protoc-gen-tarantool: "+format+"\n", args...)
os.Exit(1)
}