Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
| `protoc-gen-cpp-tableau-loader` | C++17 | `*.pc.h` / `*.pc.cc` |
| `protoc-gen-csharp-tableau-loader` | C# (Unity 2022.3 LTS / .NET 8) | `*.pc.cs` |

The Go plugin accepts the following options (via the `opt` field of `buf.gen.yaml`):

- `pkg=<name>` — package name of the generated loader code (default `tableau`).
- `const=true` — generate a const API that exposes [goconst](https://github.com/Kybxd/goconst) read-only `Foo_Const` views through `Messager.Data()` and the typed `Get*` / `Find*` accessors. Mutation through those APIs is a compile error. `Message()` remains the mutable escape hatch; the runtime `MutableCheck` still applies to it. Requires also running `protoc-gen-go-const` on the same protos (so `Foo_Const` types live in the protoconf package) and a dependency on `github.com/Kybxd/goconst`.

## Quick start

Use [`make.py`](./make.py) (Python 3.10+, stdlib only):
Expand Down
7 changes: 7 additions & 0 deletions cmd/protoc-gen-go-tableau-loader/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ func FindMessageGoIdent(gen *protogen.Plugin, md protoreflect.MessageDescriptor)
return msg.GoIdent
}

// ConstViewType returns the goconst read-only view type name (e.g.
// "protoconf.Item_Const") for the given message descriptor. The returned value
// is already fully qualified, so it can be emitted directly via g.P.
func ConstViewType(g *protogen.GeneratedFile, gen *protogen.Plugin, md protoreflect.MessageDescriptor) string {
return g.QualifiedGoIdent(FindMessageGoIdent(gen, md)) + "_Const"
}

func FindEnum(gen *protogen.Plugin, ed protoreflect.EnumDescriptor) *protogen.Enum {
if file, ok := gen.FilesByPath[ed.ParentFile().Path()]; ok {
if enum := FindEnumByDescriptor(file.Enums, ed); enum != nil {
Expand Down
38 changes: 29 additions & 9 deletions cmd/protoc-gen-go-tableau-loader/indexes/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ import (
)

type Generator struct {
gen *protogen.Plugin
g *protogen.GeneratedFile
descriptor *index.IndexDescriptor
message *protogen.Message
gen *protogen.Plugin
g *protogen.GeneratedFile
descriptor *index.IndexDescriptor
message *protogen.Message
constEnabled bool

// level message
keys helper.MapKeySlice
}

func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message) *Generator {
func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message, constEnabled bool) *Generator {
generator := &Generator{
gen: gen,
g: g,
descriptor: descriptor,
message: message,
gen: gen,
g: g,
descriptor: descriptor,
message: message,
constEnabled: constEnabled,
}
generator.initLevelMessage()
return generator
Expand Down Expand Up @@ -64,6 +66,24 @@ func (x *Generator) mapValueType(index *index.LevelIndex) protogen.GoIdent {
return helper.FindMessageGoIdent(x.gen, index.MD)
}

// indexValueElem returns the element type used inside the index's slice/map
// containers for the indexed values. In non-const mode it is the mutable
// pointer type "*protoconf.Item"; in const mode it is the read-only value type
// "protoconf.Item_Const".
func (x *Generator) indexValueElem(index *index.LevelIndex) string {
if x.constEnabled {
return helper.ConstViewType(x.g, x.gen, index.MD)
}
return "*" + x.g.QualifiedGoIdent(x.mapValueType(index))
}

func (x *Generator) findFirstMissComment() string {
if x.constEnabled {
return "or a zero value if no value found."
}
return "or nil if no value found."
}

func (x *Generator) fieldGetter(fd protoreflect.FieldDescriptor) string {
return fmt.Sprintf(".Get%s()", helper.ParseIndexFieldName(x.gen, fd))
}
Expand Down
38 changes: 25 additions & 13 deletions cmd/protoc-gen-go-tableau-loader/indexes/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (x *Generator) genIndexTypeDef() {
}
x.g.P("}")
}
x.g.P("type ", x.indexMapType(index), " = map[", x.indexMapKeyType(index), "][]*", x.mapValueType(index))
x.g.P("type ", x.indexMapType(index), " = map[", x.indexMapKeyType(index), "][]", x.indexValueElem(index))
x.g.P()
}
}
Expand Down Expand Up @@ -187,14 +187,18 @@ func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *i

func (x *Generator) genIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) {
indexContainerName := x.indexContainerName(index, 0)
x.g.P("x.", indexContainerName, "[key] = append(x.", indexContainerName, "[key], ", parentDataName, ")")
appender := parentDataName
if x.constEnabled {
appender = parentDataName + ".AsConst()"
}
x.g.P("x.", indexContainerName, "[key] = append(x.", indexContainerName, "[key], ", appender, ")")
for i := 1; i < lm.LeveledContainerDepth(); i++ {
indexContainerName := x.indexContainerName(index, i)
if i == 1 {
x.g.P("if x.", indexContainerName, "[k1] == nil {")
x.g.P("x.", indexContainerName, "[k1] = make(", x.indexMapType(index), ")")
x.g.P("}")
x.g.P("x.", indexContainerName, "[k1][key] = append(x.", indexContainerName, "[k1][key], ", parentDataName, ")")
x.g.P("x.", indexContainerName, "[k1][key] = append(x.", indexContainerName, "[k1][key], ", appender, ")")
} else {
var fields []string
for j := 1; j <= i; j++ {
Expand All @@ -206,7 +210,7 @@ func (x *Generator) genIndexLoaderCommon(lm *index.LevelMessage, index *index.Le
x.g.P("if x.", indexContainerName, "[", keyName, "] == nil {")
x.g.P("x.", indexContainerName, "[", keyName, "] = make(", x.indexMapType(index), ")")
x.g.P("}")
x.g.P("x.", indexContainerName, "[", keyName, "][key] = append(x.", indexContainerName, "[", keyName, "][key], ", parentDataName, ")")
x.g.P("x.", indexContainerName, "[", keyName, "][key] = append(x.", indexContainerName, "[", keyName, "][key], ", appender, ")")
}
}
}
Expand All @@ -217,7 +221,7 @@ func (x *Generator) genIndexSorter() {
if len(index.SortedColFields) != 0 {
x.g.P("// Index(sort): ", index.Index)
indexContainerName := x.indexContainerName(index, 0)
x.g.P(indexContainerName, "Sorter := func(itemList []*", x.mapValueType(index), ") func(i, j int) bool {")
x.g.P(indexContainerName, "Sorter := func(itemList []", x.indexValueElem(index), ") func(i, j int) bool {")
x.g.P("return func(i, j int) bool {")
for i, field := range index.SortedColFields {
fieldName, _ := x.parseKeyFieldNameAndSuffix(field)
Expand Down Expand Up @@ -269,7 +273,7 @@ func (x *Generator) genIndexFinders() {
params := keys.GenGetParams()
args := keys.GenGetArguments()
x.g.P("// Find", index.Name(), " finds a slice of all values of the given key(s).")
x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []*", x.mapValueType(index), " {")
x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []", x.indexValueElem(index), " {")
if len(index.ColFields) == 1 {
x.g.P("return x.", indexContainerName, "[", args, "]")
} else {
Expand All @@ -279,13 +283,17 @@ func (x *Generator) genIndexFinders() {
x.g.P()

x.g.P("// FindFirst", index.Name(), " finds the first value of the given key(s),")
x.g.P("// or nil if no value found.")
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") *", x.mapValueType(index), " {")
x.g.P("// ", x.findFirstMissComment())
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") ", x.indexValueElem(index), " {")
x.g.P("val := x.Find", index.Name(), "(", args, ")")
x.g.P("if len(val) > 0 {")
x.g.P("return val[0]")
x.g.P("}")
x.g.P("return nil")
if x.constEnabled {
x.g.P("return ", x.indexValueElem(index), "{}")
} else {
x.g.P("return nil")
}
x.g.P("}")
x.g.P()

Expand All @@ -310,7 +318,7 @@ func (x *Generator) genIndexFinders() {

x.g.P("// Find", index.Name(), i, " finds a slice of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map")
x.g.P("// specified by (", partArgs, ").")
x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []*", x.mapValueType(index), " {")
x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []", x.indexValueElem(index), " {")
if len(index.ColFields) == 1 {
x.g.P("return x.Find", index.Name(), "Map", i, "(", partArgs, ")[", args, "]")
} else {
Expand All @@ -320,13 +328,17 @@ func (x *Generator) genIndexFinders() {
x.g.P()

x.g.P("// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map")
x.g.P("// specified by (", partArgs, "), or nil if no value found.")
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") *", x.mapValueType(index), " {")
x.g.P("// specified by (", partArgs, "), ", x.findFirstMissComment())
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") ", x.indexValueElem(index), " {")
x.g.P("val := x.Find", index.Name(), i, "(", partArgs, ", ", args, ")")
x.g.P("if len(val) > 0 {")
x.g.P("return val[0]")
x.g.P("}")
x.g.P("return nil")
if x.constEnabled {
x.g.P("return ", x.indexValueElem(index), "{}")
} else {
x.g.P("return nil")
}
x.g.P("}")
x.g.P()
}
Expand Down
48 changes: 30 additions & 18 deletions cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (x *Generator) genOrderedIndexTypeDef() {
x.g.P("}")
x.g.P()
}
x.g.P("type ", x.orderedIndexMapType(index), " = ", helper.TreeMapPackage.Ident("TreeMap"), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]")
x.g.P("type ", x.orderedIndexMapType(index), " = ", helper.TreeMapPackage.Ident("TreeMap"), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]")
x.g.P()
}
}
Expand Down Expand Up @@ -127,7 +127,7 @@ func (x *Generator) genOrderedIndexLoader() {
x.g.P("// OrderedIndex init.")
for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel {
for _, index := range lm.OrderedIndexes {
x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()")
x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()")
for i := 1; i < lm.LeveledContainerDepth(); i++ {
if i == 1 {
x.g.P("x.", x.orderedIndexContainerName(index, i), " = make(map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index), ")")
Expand Down Expand Up @@ -211,18 +211,22 @@ func (x *Generator) generateOneMulticolumnOrderedIndex(lm *index.LevelMessage, i
}

func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) {
appender := parentDataName
if x.constEnabled {
appender = parentDataName + ".AsConst()"
}
indexContainerName := x.orderedIndexContainerName(index, 0)
x.g.P("value, _ := x.", indexContainerName, ".Get(key)")
x.g.P("x.", indexContainerName, ".Put(key, append(value, ", parentDataName, "))")
x.g.P("x.", indexContainerName, ".Put(key, append(value, ", appender, "))")
for i := 1; i < lm.LeveledContainerDepth(); i++ {
orderedIndexContainerName := x.orderedIndexContainerName(index, i)
valueName := orderedIndexContainerName + "Value"
if i == 1 {
x.g.P("if x.", orderedIndexContainerName, "[k1] == nil {")
x.g.P("x.", orderedIndexContainerName, "[k1] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()")
x.g.P("x.", orderedIndexContainerName, "[k1] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()")
x.g.P("}")
x.g.P(valueName, ", _ := x.", orderedIndexContainerName, "[k1].Get(key)")
x.g.P("x.", orderedIndexContainerName, "[k1].Put(key, append(", valueName, ", ", parentDataName, "))")
x.g.P("x.", orderedIndexContainerName, "[k1].Put(key, append(", valueName, ", ", appender, "))")
} else {
var fields []string
for j := 1; j <= i; j++ {
Expand All @@ -232,10 +236,10 @@ func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *i
keyName := orderedIndexContainerName + "Keys"
x.g.P(keyName, " := ", levelIndexKeyType, "{", strings.Join(fields, ", "), "}")
x.g.P("if x.", orderedIndexContainerName, "[", keyName, "] == nil {")
x.g.P("x.", orderedIndexContainerName, "[", keyName, "] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()")
x.g.P("x.", orderedIndexContainerName, "[", keyName, "] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()")
x.g.P("}")
x.g.P(valueName, ", _ := x.", orderedIndexContainerName, "[", keyName, "].Get(key)")
x.g.P("x.", orderedIndexContainerName, "[", keyName, "].Put(key, append(", valueName, ", ", parentDataName, "))")
x.g.P("x.", orderedIndexContainerName, "[", keyName, "].Put(key, append(", valueName, ", ", appender, "))")
}
}
}
Expand All @@ -246,7 +250,7 @@ func (x *Generator) genOrderedIndexSorter() {
if len(index.SortedColFields) != 0 {
x.g.P("// OrderedIndex(sort): ", index.Index)
indexContainerName := x.orderedIndexContainerName(index, 0)
x.g.P(indexContainerName, "Sorter := func(itemList []*", x.mapValueType(index), ") func(i, j int) bool {")
x.g.P(indexContainerName, "Sorter := func(itemList []", x.indexValueElem(index), ") func(i, j int) bool {")
x.g.P("return func(i, j int) bool {")
for i, field := range index.SortedColFields {
fieldName, _ := x.parseKeyFieldNameAndSuffix(field)
Expand All @@ -260,14 +264,14 @@ func (x *Generator) genOrderedIndexSorter() {
}
x.g.P("}")
x.g.P("}")
x.g.P("x.", x.orderedIndexContainerName(index, 0), ".Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []*", x.mapValueType(index), ") bool {")
x.g.P("x.", x.orderedIndexContainerName(index, 0), ".Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []", x.indexValueElem(index), ") bool {")
x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))")
x.g.P("return true")
x.g.P("})")
// Iterate all leveled containers.
for i := 1; i < lm.LeveledContainerDepth(); i++ {
x.g.P("for _, itemMap := range x.", x.orderedIndexContainerName(index, i), " {")
x.g.P("itemMap.Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []*", x.mapValueType(index), ") bool {")
x.g.P("itemMap.Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []", x.indexValueElem(index), ") bool {")
x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))")
x.g.P("return true")
x.g.P("})")
Expand Down Expand Up @@ -300,7 +304,7 @@ func (x *Generator) genOrderedIndexFinders() {
params := keys.GenGetParams()
args := keys.GenGetArguments()
x.g.P("// Find", index.Name(), " finds a slice of all values of the given key(s).")
x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []*", x.mapValueType(index), " {")
x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []", x.indexValueElem(index), " {")
if len(index.ColFields) == 1 {
x.g.P("val, _ := x.", indexContainerName, ".Get(", args, ")")
} else {
Expand All @@ -311,13 +315,17 @@ func (x *Generator) genOrderedIndexFinders() {
x.g.P()

x.g.P("// FindFirst", index.Name(), " finds the first value of the given key(s),")
x.g.P("// or nil if no value found.")
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") *", x.mapValueType(index), " {")
x.g.P("// ", x.findFirstMissComment())
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") ", x.indexValueElem(index), " {")
x.g.P("val := x.Find", index.Name(), "(", args, ")")
x.g.P("if len(val) > 0 {")
x.g.P("return val[0]")
x.g.P("}")
x.g.P("return nil")
if x.constEnabled {
x.g.P("return ", x.indexValueElem(index), "{}")
} else {
x.g.P("return nil")
}
x.g.P("}")
x.g.P()

Expand All @@ -342,7 +350,7 @@ func (x *Generator) genOrderedIndexFinders() {

x.g.P("// Find", index.Name(), i, " finds a slice of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level treemap")
x.g.P("// specified by (", partArgs, ").")
x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []*", x.mapValueType(index), " {")
x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []", x.indexValueElem(index), " {")
x.g.P("m := x.Find", index.Name(), "Map", i, "(", partArgs, ")")
x.g.P("if m == nil {")
x.g.P("return nil")
Expand All @@ -357,13 +365,17 @@ func (x *Generator) genOrderedIndexFinders() {
x.g.P()

x.g.P("// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level treemap")
x.g.P("// specified by (", partArgs, "), or nil if no value found.")
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") *", x.mapValueType(index), " {")
x.g.P("// specified by (", partArgs, "), ", x.findFirstMissComment())
x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") ", x.indexValueElem(index), " {")
x.g.P("val := x.Find", index.Name(), i, "(", partArgs, ", ", args, ")")
x.g.P("if len(val) > 0 {")
x.g.P("return val[0]")
x.g.P("}")
x.g.P("return nil")
if x.constEnabled {
x.g.P("return ", x.indexValueElem(index), "{}")
} else {
x.g.P("return nil")
}
x.g.P("}")
x.g.P()
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/protoc-gen-go-tableau-loader/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (

const version = "0.12.0"

var pkg *string
var (
pkg *string
constFlag *bool
)

func main() {
showVersion := flag.Bool("version", false, "print the version and exit")
Expand All @@ -24,6 +27,7 @@ func main() {

var flags flag.FlagSet
pkg = flags.String("pkg", "tableau", "tableau package name")
constFlag = flags.Bool("const", false, "generate const API (read-only views via goconst) for syntax-level immutability on Data()/Get*/Find*")

protogen.Options{
ParamFunc: flags.Set,
Expand Down
Loading
Loading