mirror of
https://github.com/zap-proto/go.git
synced 2026-10-30 15:05:00 +00:00
The parser moved to idl and the C++ backend was still holding the old one, so the two lines of work could not both be had. They meet here: emitcpp.go imports idl like emit.go does, and structSize moves to idl beside the Struct whose width it reports — a stated `@N` or where the last field ends is a property of the declaration, not of whoever is printing it. The grammar tests move with the grammar. Emission tests stay with the emitter. Every golden, Go and C++, is unchanged: the merge is a move, not a change of what the generator prints.
131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// zapgen reads a .zap schema file and emits per-struct accessor + builder
|
|
// code that calls a ZAP runtime.
|
|
//
|
|
// One front end, several back ends: the parser, the desugar and the schema
|
|
// model are shared, and -lang picks what gets printed. Go emits .go calling
|
|
// github.com/zap-proto/go; cpp emits .hpp calling github.com/zap-proto/cpp.
|
|
// Both carry the same offsets, the same ordinals and the same bytes, because
|
|
// both read one AST.
|
|
//
|
|
// Usage:
|
|
//
|
|
// zapgen vms/xvm/txs/schema.zap # emit Go into same dir as input
|
|
// zapgen -out ./gen schema.zap # emit into specified dir
|
|
// zapgen -lang cpp -out ./gen s.zap # emit C++ headers
|
|
//
|
|
// Author intent: drop a `//go:generate zapgen schema.zap` line at the
|
|
// top of each consuming package and run `go generate ./...`.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"github.com/zap-proto/go/idl"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func main() {
|
|
var (
|
|
outDir = flag.String("out", "", "output directory (default: input file's dir)")
|
|
single = flag.Bool("single", false, "emit one combined <schema>_zap.go instead of per-struct files")
|
|
suffix = flag.String("type-suffix", "", "append SUFFIX to every generated type name (e.g. -type-suffix=View)")
|
|
lang = flag.String("lang", "go", "output language: go | cpp")
|
|
)
|
|
flag.Usage = usage
|
|
flag.Parse()
|
|
|
|
if flag.NArg() != 1 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
input := flag.Arg(0)
|
|
|
|
if err := run(input, *outDir, *lang, *single, *suffix); err != nil {
|
|
fmt.Fprintf(os.Stderr, "zapgen: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, "usage: zapgen [-lang go|cpp] [-out OUTDIR] [-single] [-type-suffix SUFFIX] SCHEMA.zap")
|
|
fmt.Fprintln(os.Stderr, "")
|
|
fmt.Fprintln(os.Stderr, "Reads a .zap schema and emits one file per struct and per interface:")
|
|
fmt.Fprintln(os.Stderr, " -lang go <struct>_zap.go calling github.com/zap-proto/go")
|
|
fmt.Fprintln(os.Stderr, " -lang cpp <struct>_zap.hpp calling github.com/zap-proto/cpp")
|
|
fmt.Fprintln(os.Stderr, "With -single, emits one combined <SCHEMA>_zap file instead.")
|
|
fmt.Fprintln(os.Stderr, "With -type-suffix, appends SUFFIX to every generated type name.")
|
|
}
|
|
|
|
func run(input, outDir, lang string, single bool, typeSuffix string) error {
|
|
src, err := os.ReadFile(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
file, err := idl.Parse(input, src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if typeSuffix != "" {
|
|
for _, s := range file.Structs {
|
|
s.Name += typeSuffix
|
|
}
|
|
// Patch nested-struct references to the renamed types.
|
|
for _, s := range file.Structs {
|
|
for _, f := range s.Fields {
|
|
if f.Type.Kind == idl.KindStruct {
|
|
f.Type.StructName += typeSuffix
|
|
}
|
|
if f.Type.Kind == idl.KindList && f.Type.ListElem != nil &&
|
|
f.Type.ListElem.Kind == idl.KindStruct {
|
|
f.Type.ListElem.StructName += typeSuffix
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if outDir == "" {
|
|
outDir = filepath.Dir(input)
|
|
}
|
|
if err := os.MkdirAll(outDir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
emitSingle, emitAll, err := backend(lang)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if single {
|
|
name, body, err := emitSingle(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(outDir, name)
|
|
return os.WriteFile(path, body, 0o644)
|
|
}
|
|
files, err := emitAll(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for name, body := range files {
|
|
path := filepath.Join(outDir, name)
|
|
if err := os.WriteFile(path, body, 0o644); err != nil {
|
|
return fmt.Errorf("write %s: %w", path, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// backend picks the pair of emitters for a language. Adding a language is
|
|
// adding a case here and an emitter beside emit.go — never a second parser.
|
|
func backend(lang string) (func(*idl.File) (string, []byte, error), func(*idl.File) (map[string][]byte, error), error) {
|
|
switch lang {
|
|
case "go":
|
|
return EmitSingle, Emit, nil
|
|
case "cpp":
|
|
return EmitCPPSingle, EmitCPP, nil
|
|
}
|
|
return nil, nil, fmt.Errorf("unknown -lang %q (want go or cpp)", lang)
|
|
}
|