go-hep.org/x/hep@v0.38.1/cmd/podio-gen/generator.go (about)

     1  // Copyright ©2020 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/format"
    11  	"io"
    12  	"os"
    13  	"regexp"
    14  	"strings"
    15  
    16  	"golang.org/x/text/cases"
    17  	"golang.org/x/text/language"
    18  	"gopkg.in/yaml.v3"
    19  )
    20  
    21  func toTitle(s string) string {
    22  	return cases.Title(language.Und, cases.NoLower).String(s)
    23  }
    24  
    25  type document struct {
    26  	Options struct {
    27  		Syntax           bool `yaml:"getSyntax"`
    28  		ExposePODMembers bool `yaml:"exposePODMembers"`
    29  	} `yaml:"options"`
    30  
    31  	Components yaml.Node `yaml:"components"`
    32  	DataTypes  yaml.Node `yaml:"datatypes"`
    33  }
    34  
    35  type dtype struct {
    36  	descr            string
    37  	author           string `yaml:"Author"`
    38  	members          []member
    39  	vecmbrs          []member
    40  	one2oneRels      []member
    41  	one2manyRels     []member
    42  	TransientMembers []string  `yaml:"TransientMembers"`
    43  	Typedefs         []string  `yaml:"Typedefs"`
    44  	extraCode        extraCode `yaml:"ExtraCode"`
    45  	constCode        extraCode `yaml:"ConstExtraCode"`
    46  }
    47  
    48  type member struct {
    49  	Name string
    50  	Type string
    51  	Doc  string
    52  }
    53  
    54  type extraCode struct {
    55  	Includes  string `yaml:"includes"`
    56  	ConstDecl string `yaml:"const_declaration"`
    57  	Decl      string `yaml:"declaration"`
    58  	Impl      string `yaml:"implementation"`
    59  }
    60  
    61  var (
    62  	builtins = map[string]string{
    63  		"bool":               "bool",
    64  		"short":              "int16",
    65  		"int":                "int32",
    66  		"long":               "int64",
    67  		"long long":          "int64",
    68  		"unsigned int":       "uint32",
    69  		"unsigned":           "uint32",
    70  		"unsigned long":      "uint64",
    71  		"unsigned long long": "uint64",
    72  		"float":              "float32",
    73  		"double":             "float64",
    74  		"std::string":        "string",
    75  	}
    76  
    77  	cxxMangle = strings.NewReplacer(
    78  		":", "_",
    79  		"<", "_",
    80  		">", "_",
    81  	)
    82  
    83  	stdArrayRe    = regexp.MustCompile(" *std::array *<([a-zA-Z0-9:]+) *, *([0-9]+)> *")
    84  	stdArrayDocRe = regexp.MustCompile(` *std::array *<([a-zA-Z0-9:]+) *, *([0-9]+)> *(\S+) *// *(.+)`)
    85  )
    86  
    87  type generator struct {
    88  	w io.Writer
    89  
    90  	buf *bytes.Buffer
    91  	pkg string
    92  	doc document
    93  
    94  	comps map[string]string
    95  	types map[string]string
    96  	rules map[string]string
    97  }
    98  
    99  func newGenerator(w io.Writer, pkg, fname, rules string) (*generator, error) {
   100  	var (
   101  		err error
   102  		gen = &generator{
   103  			w:   w,
   104  			buf: new(bytes.Buffer),
   105  			pkg: pkg,
   106  
   107  			comps: make(map[string]string),
   108  			types: make(map[string]string),
   109  			rules: make(map[string]string),
   110  		}
   111  	)
   112  
   113  	raw, err := os.ReadFile(fname)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("could not read input YAML file %q: %w", fname, err)
   116  	}
   117  
   118  	gen.doc.Options.Syntax = false
   119  	gen.doc.Options.ExposePODMembers = true
   120  
   121  	err = yaml.Unmarshal(raw, &gen.doc)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("could not unmarshal YAML file %q: %w", fname, err)
   124  	}
   125  
   126  	for _, rule := range strings.Split(rules, ",") {
   127  		toks := strings.Split(rule, "->")
   128  		if len(toks) != 2 {
   129  			continue
   130  		}
   131  		gen.rules[toks[0]] = toks[1]
   132  	}
   133  
   134  	gen.printf(`// Automatically generated. DO NOT EDIT.
   135  
   136  package %s
   137  
   138  `, pkg)
   139  
   140  	return gen, nil
   141  }
   142  
   143  func (g *generator) generate() error {
   144  	var err error
   145  
   146  	for i := 0; i < len(g.doc.Components.Content); i += 2 {
   147  		key := g.doc.Components.Content[i]
   148  		val := g.doc.Components.Content[i+1]
   149  		err = g.genComponent(key, val)
   150  		if err != nil {
   151  			return fmt.Errorf("could not handle component %q: %w", key.Value, err)
   152  		}
   153  
   154  	}
   155  
   156  	for i := 0; i < len(g.doc.DataTypes.Content); i += 2 {
   157  		key := g.doc.DataTypes.Content[i]
   158  		val := g.doc.DataTypes.Content[i+1]
   159  		err = g.genDataType(key, val)
   160  		if err != nil {
   161  			return fmt.Errorf("could not handle datatype %q: %w", key.Value, err)
   162  		}
   163  	}
   164  
   165  	out, err := format.Source(g.buf.Bytes())
   166  	if err != nil {
   167  		return fmt.Errorf("could not go/format generated code: %w", err)
   168  	}
   169  
   170  	_, err = g.w.Write(out)
   171  	if err != nil {
   172  		return fmt.Errorf("could not write generated code: %w", err)
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  func (g *generator) printf(format string, args ...any) {
   179  	fmt.Fprintf(g.buf, format, args...)
   180  }
   181  
   182  func (g *generator) genTypeName(typ string) string {
   183  	for k, v := range g.rules {
   184  		typ = strings.Replace(typ, k, v, 1)
   185  	}
   186  
   187  	return g.cxx2go(typ)
   188  }
   189  
   190  func (g *generator) cxx2go(typ string) string {
   191  	if v, ok := builtins[typ]; ok {
   192  		return v
   193  	}
   194  
   195  	if grps := stdArrayRe.FindStringSubmatch(typ); grps != nil {
   196  		return fmt.Sprintf("[%s]%s", grps[2], g.genTypeName(grps[1]))
   197  	}
   198  
   199  	return cxxMangle.Replace(typ)
   200  }
   201  
   202  func (g *generator) genComponent(knode, vnode *yaml.Node) error {
   203  	var (
   204  		err  error
   205  		name = knode.Value
   206  		typ  = g.genTypeName(name)
   207  		doc  = strings.TrimSpace(knode.HeadComment)
   208  	)
   209  
   210  	handleDoc := func(doc string) string {
   211  		return strings.TrimSpace(strings.Replace(doc, "#", "", 1))
   212  	}
   213  
   214  	doc = handleDoc(doc)
   215  	if doc != "" {
   216  		doc = "\n// " + doc
   217  	}
   218  
   219  	g.comps[name] = typ
   220  	g.printf(`// %s%s
   221  type %s struct {
   222  `, name, doc, typ,
   223  	)
   224  	for i := 0; i < len(vnode.Content); i += 2 {
   225  		key := vnode.Content[i]
   226  		val := vnode.Content[i+1]
   227  		if key.Value == "ExtraCode" {
   228  			continue
   229  		}
   230  		doc := handleDoc(val.LineComment)
   231  		if doc != "" {
   232  			doc = "// " + doc
   233  		}
   234  		g.printf("\t%s %s%s\n", toTitle(key.Value), g.genTypeName(val.Value), doc)
   235  	}
   236  
   237  	g.printf("}\n\n")
   238  
   239  	return err
   240  }
   241  
   242  func (g *generator) genDataType(knode, vnode *yaml.Node) error {
   243  	var (
   244  		err  error
   245  		name = knode.Value
   246  		typ  = g.genTypeName(name)
   247  	)
   248  
   249  	g.types[name] = typ
   250  
   251  	var dt dtype
   252  
   253  	for i := 0; i < len(vnode.Content); i += 2 {
   254  		key := vnode.Content[i]
   255  		val := vnode.Content[i+1]
   256  		switch key.Value {
   257  		case "Description":
   258  			dt.descr = val.Value
   259  		case "Author":
   260  			dt.author = val.Value
   261  		case "Members":
   262  			dt.members, err = g.genMembers(val)
   263  			if err != nil {
   264  				return fmt.Errorf("could not handle data type members of %q: %w",
   265  					name, err,
   266  				)
   267  			}
   268  		case "OneToOneRelations":
   269  			dt.one2oneRels, err = g.genMembers(val)
   270  			if err != nil {
   271  				return fmt.Errorf("could not handle data type 1-to-1 relations of %q: %w",
   272  					name, err,
   273  				)
   274  			}
   275  		case "OneToManyRelations":
   276  			dt.one2manyRels, err = g.genMembers(val)
   277  			if err != nil {
   278  				return fmt.Errorf("could not handle data type 1-to-n relations of %q: %w",
   279  					name, err,
   280  				)
   281  			}
   282  		case "ExtraCode":
   283  			err = val.Decode(&dt.extraCode)
   284  			if err != nil {
   285  				return fmt.Errorf("could not handle data type extracode of %q: %w",
   286  					name, err,
   287  				)
   288  			}
   289  		case "ConstExtraCode":
   290  			err = val.Decode(&dt.constCode)
   291  			if err != nil {
   292  				return fmt.Errorf("could not handle data type const-extracode of %q: %w",
   293  					name, err,
   294  				)
   295  			}
   296  		case "VectorMembers":
   297  			dt.vecmbrs, err = g.genMembers(val)
   298  			if err != nil {
   299  				return fmt.Errorf("could not handle data type vec-members of %q: %w",
   300  					name, err,
   301  				)
   302  			}
   303  		default:
   304  			return fmt.Errorf("unknown data type field %q", key.Value)
   305  		}
   306  	}
   307  
   308  	doc := dt.descr
   309  	if doc != "" {
   310  		doc = "\n// " + dt.descr
   311  	}
   312  	g.printf(`// %s%s
   313  type %s struct {
   314  `, name, doc, typ,
   315  	)
   316  	for _, m := range dt.members {
   317  		doc := ""
   318  		if m.Doc != "" {
   319  			doc = " // " + m.Doc
   320  		}
   321  		g.printf("\t%s %s%s\n", m.Name, m.Type, doc)
   322  	}
   323  
   324  	for _, m := range dt.one2oneRels {
   325  		doc := ""
   326  		if m.Doc != "" {
   327  			doc = " // " + m.Doc
   328  		}
   329  		g.printf("\t%s *%s%s\n", m.Name, m.Type, doc)
   330  	}
   331  
   332  	for _, m := range dt.one2manyRels {
   333  		doc := ""
   334  		if m.Doc != "" {
   335  			doc = " // " + m.Doc
   336  		}
   337  		g.printf("\t%s []*%s%s\n", m.Name, m.Type, doc)
   338  	}
   339  
   340  	for _, m := range dt.vecmbrs {
   341  		doc := ""
   342  		if m.Doc != "" {
   343  			doc = " // " + m.Doc
   344  		}
   345  		g.printf("\t%s []%s%s\n", m.Name, m.Type, doc)
   346  	}
   347  	g.printf("}\n\n")
   348  
   349  	return err
   350  }
   351  
   352  func (g *generator) genMembers(node *yaml.Node) ([]member, error) {
   353  	mbrs := make([]member, 0, len(node.Content))
   354  	for _, elem := range node.Content {
   355  		mbr := g.genMember(strings.TrimSpace(elem.Value))
   356  		mbr.Name = toTitle(mbr.Name)
   357  		mbrs = append(mbrs, mbr)
   358  	}
   359  	return mbrs, nil
   360  }
   361  
   362  func (g *generator) genMember(v string) member {
   363  	var mbr member
   364  	handleDoc := func(doc string) string {
   365  		return strings.TrimSpace(strings.Replace(doc, "//", "", 1))
   366  	}
   367  
   368  	// remove spaces coming from builtins...
   369  	for _, cxx := range []struct {
   370  		k, v string
   371  	}{
   372  		{"unsigned long long", "uint64"},
   373  		{"unsigned long", "uint64"},
   374  		{"long long", "int64"},
   375  		{"unsigned int", "uint32"},
   376  		{"unsigned", "uint32"},
   377  	} {
   378  		if !strings.HasPrefix(v, cxx.k) {
   379  			continue
   380  		}
   381  		v = strings.Replace(v, cxx.k, cxx.v, 1)
   382  	}
   383  
   384  	for k, cxx := range builtins {
   385  		if !strings.HasPrefix(v, k) {
   386  			continue
   387  		}
   388  		mbr.Type = cxx
   389  		v = strings.TrimSpace(strings.Replace(v, k, "", 1))
   390  		idx := strings.Index(v, " ")
   391  		mbr.Name = strings.TrimSpace(v[:idx])
   392  		mbr.Doc = handleDoc(strings.TrimSpace(v[idx:]))
   393  		return mbr
   394  	}
   395  
   396  	if grps := stdArrayDocRe.FindStringSubmatch(v); grps != nil {
   397  		mbr.Type = fmt.Sprintf("[%s]%s", grps[2], g.genTypeName(grps[1]))
   398  		mbr.Name = grps[3]
   399  		mbr.Doc = handleDoc(grps[4])
   400  		return mbr
   401  	}
   402  
   403  	idx := strings.Index(v, " ")
   404  	mbr.Type = g.genTypeName(strings.TrimSpace(v[:idx]))
   405  	v = strings.TrimSpace(v[idx:])
   406  	idx = strings.Index(v, "//")
   407  	mbr.Name = strings.TrimSpace(v[:idx])
   408  	mbr.Doc = handleDoc(strings.TrimSpace(v[idx:]))
   409  
   410  	return mbr
   411  }