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
395 changes: 395 additions & 0 deletions cmd/blocksetter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,395 @@
package main

import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"log"
"os"
"path/filepath"
"sort"
)

func main() {
out := flag.String("o", "", "output file for facing interface and methods")
flag.Parse()

if len(flag.Args()) != 1 {
log.Fatalln("must pass one package to produce facing interface methods for")
}
dir := flag.Args()[0]
if !filepath.IsAbs(dir) {
abs, err := filepath.Abs(dir)
if err != nil {
log.Fatalln(err)
}
dir = abs
}

fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
name := info.Name()
return filepath.Ext(name) == ".go" && filepath.Base(name) != *out
}, parser.ParseComments)
if err != nil {
log.Fatalln(err)
}
if len(pkgs) != 1 {
log.Fatalln("expected exactly one package in directory")
}

f, err := os.OpenFile(*out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatalln(err)
}
for _, pkg := range pkgs {
files := make([]*ast.File, 0, len(pkg.Files))
for _, file := range pkg.Files {
files = append(files, file)
}
procPackage(pkg.Name, files, f)
}
_ = f.Close()
}

func procPackage(pkgName string, files []*ast.File, w io.Writer) {
b := &builder{
pkgName: pkgName,
files: files,
fields: make(map[string][]*ast.Field),
aliases: make(map[string]string),
handled: map[string]struct{}{},
funcs: map[string]struct{}{},
}
b.readStructFields()
b.readFuncs()
b.resolveBlocks()
b.sortNames()

b.writePackage(w)
b.writeInterfaces(w)
b.writeMethods(w)
}

type builder struct {
pkgName string
files []*ast.File
fields map[string][]*ast.Field
funcs map[string]struct{}
aliases map[string]string
handled map[string]struct{}
facingBlocks []string
axisBlocks []string
colourBlocks []string
}

func (b *builder) sortNames() {
sort.Strings(b.facingBlocks)
sort.Strings(b.axisBlocks)
sort.Strings(b.colourBlocks)
}

func (b *builder) writePackage(w io.Writer) {
if _, err := fmt.Fprintf(w, "// Code generated by cmd/blocksetter; DO NOT EDIT.\n\npackage %v\n\n", b.pkgName); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "import ("); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\t\"github.com/df-mc/dragonfly/server/block/cube\""); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\t\"github.com/df-mc/dragonfly/server/item\""); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\t\"github.com/df-mc/dragonfly/server/world\""); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, ")"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
}

func (b *builder) writeInterfaces(w io.Writer) {
if _, err := fmt.Fprintln(w, "// HasFacing represents a block with a horizontal facing direction."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "type HasFacing interface {"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tworld.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tWithFacing(facing cube.Direction) world.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "// HasAxis represents a block with an axis."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "type HasAxis interface {"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tworld.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tWithAxis(axis cube.Axis) world.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "// HasColour represents a block with a colour."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "type HasColour interface {"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tworld.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tWithColour(colour item.Colour) world.Block"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
}

func (b *builder) writeMethods(w io.Writer) {
for _, name := range b.facingBlocks {
if _, err := fmt.Fprintln(w, "// WithFacing returns the block with the facing set to facing."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintf(w, "func (b %v) WithFacing(facing cube.Direction) world.Block {\n", name); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tb.Facing = facing"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\treturn b"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
}
for _, name := range b.axisBlocks {
if _, err := fmt.Fprintln(w, "// WithAxis returns the block with the axis set to axis."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintf(w, "func (b %v) WithAxis(axis cube.Axis) world.Block {\n", name); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tb.Axis = axis"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\treturn b"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
}
for _, name := range b.colourBlocks {
if _, err := fmt.Fprintln(w, "// WithColour returns the block with the colour set to colour."); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintf(w, "func (b %v) WithColour(colour item.Colour) world.Block {\n", name); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\tb.Colour = colour"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "\treturn b"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w, "}"); err != nil {
log.Fatalln(err)
}
if _, err := fmt.Fprintln(w); err != nil {
log.Fatalln(err)
}
}
}

func (b *builder) resolveBlocks() {
for structName, fields := range b.fields {
if _, ok := b.funcs[structName]; !ok {
continue
}
if hasFacingDirectionField(fields) {
b.facingBlocks = append(b.facingBlocks, structName)
}
if hasAxisField(fields) {
b.axisBlocks = append(b.axisBlocks, structName)
}
if hasColourField(fields) {
b.colourBlocks = append(b.colourBlocks, structName)
}
}
}

func hasFacingDirectionField(fields []*ast.Field) bool {
for _, field := range fields {
if len(field.Names) == 0 {
continue
}
for _, name := range field.Names {
if name.Name != "Facing" {
continue
}
if s, ok := field.Type.(*ast.SelectorExpr); ok {
if x, ok := s.X.(*ast.Ident); ok && x.Name == "cube" && s.Sel.Name == "Direction" {
return true
}
}
}
}
return false
}

func hasAxisField(fields []*ast.Field) bool {
for _, field := range fields {
if len(field.Names) == 0 {
continue
}
for _, name := range field.Names {
if name.Name != "Axis" {
continue
}
if s, ok := field.Type.(*ast.SelectorExpr); ok {
if x, ok := s.X.(*ast.Ident); ok && x.Name == "cube" && s.Sel.Name == "Axis" {
return true
}
}
}
}
return false
}

func hasColourField(fields []*ast.Field) bool {
for _, field := range fields {
if len(field.Names) == 0 {
continue
}
for _, name := range field.Names {
if name.Name != "Colour" {
continue
}
if s, ok := field.Type.(*ast.SelectorExpr); ok {
if x, ok := s.X.(*ast.Ident); ok && x.Name == "item" && s.Sel.Name == "Colour" {
return true
}
}
}
}
return false
}

func (b *builder) readFuncs() {
for _, f := range b.files {
ast.Inspect(f, b.readFuncDecls)
}
}

func (b *builder) readFuncDecls(node ast.Node) bool {
fun, ok := node.(*ast.FuncDecl)
if !ok || fun.Name.Name != "EncodeBlock" || fun.Recv == nil {
return true
}
if ident, ok := fun.Recv.List[0].Type.(*ast.Ident); ok {
b.funcs[ident.Name] = struct{}{}
}
return true
}

func (b *builder) readStructFields() {
for _, f := range b.files {
ast.Inspect(f, b.readStructs)
}
b.resolveEmbedded()
b.resolveAliases()
}

func (b *builder) resolveAliases() {
for name, alias := range b.aliases {
b.fields[name] = b.findFields(alias)
}
}

func (b *builder) findFields(structName string) []*ast.Field {
for {
if fields, ok := b.fields[structName]; ok {
return fields
}
if nested, ok := b.aliases[structName]; ok {
structName = nested
continue
}
return nil
}
}

func (b *builder) resolveEmbedded() {
for name, fields := range b.fields {
if _, ok := b.handled[name]; ok {
continue
}
newFields := make([]*ast.Field, 0, len(fields))
for _, f := range fields {
if len(f.Names) == 0 {
if ident, ok := f.Type.(*ast.Ident); ok {
for _, af := range b.findFields(ident.Name) {
if len(af.Names) == 0 {
b.resolveEmbedded()
return
}
}
newFields = append(newFields, b.findFields(ident.Name)...)
}
} else {
newFields = append(newFields, f)
}
}
b.handled[name] = struct{}{}
b.fields[name] = newFields
}
}

func (b *builder) readStructs(node ast.Node) bool {
s, ok := node.(*ast.TypeSpec)
if !ok {
return true
}
switch t := s.Type.(type) {
case *ast.StructType:
b.fields[s.Name.Name] = t.Fields.List
case *ast.Ident:
b.aliases[s.Name.Name] = t.Name
}
return true
}
Loading