blob: 936accbf6706c2ae294b087f964a081fb00c0ee7 [file] [log] [blame]
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// The gendebug program takes gl.go and generates a version of it
// where each function includes tracing code that writes its arguments
// to the standard log.
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/printer"
"go/token"
"io/ioutil"
"log"
"os"
"strconv"
)
var outfile = flag.String("o", "", "result will be written to the file instead of stdout.")
var fset = new(token.FileSet)
func typeString(t ast.Expr) string {
buf := new(bytes.Buffer)
printer.Fprint(buf, fset, t)
return buf.String()
}
func typePrinter(t string) string {
switch t {
case "[]float32", "[]byte":
return "len(%d)"
}
return "%v"
}
func typePrinterArg(t, name string) string {
switch t {
case "[]float32", "[]byte":
return "len(" + name + ")"
}
return name
}
func die(err error) {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
func main() {
flag.Parse()
f, err := parser.ParseFile(fset, "consts.go", nil, parser.ParseComments)
if err != nil {
die(err)
}
entries := enum(f)
f, err = parser.ParseFile(fset, "gl.go", nil, parser.ParseComments)
if err != nil {
die(err)
}
buf := new(bytes.Buffer)
fmt.Fprint(buf, preamble)
fmt.Fprintf(buf, "func (v Enum) String() string {\n")
fmt.Fprintf(buf, "\tswitch v {\n")
for _, e := range dedup(entries) {
fmt.Fprintf(buf, "\tcase 0x%x: return %q\n", e.value, e.name)
}
fmt.Fprintf(buf, "\t%s\n", `default: return fmt.Sprintf("gl.Enum(0x%x)", uint32(v))`)
fmt.Fprintf(buf, "\t}\n")
fmt.Fprintf(buf, "}\n\n")
for _, d := range f.Decls {
// Before:
// func (ctx *context) StencilMask(mask uint32) {
// C.glStencilMask(C.GLuint(mask))
// }
//
// After:
// func (ctx *context) StencilMask(mask uint32) {
// defer func() {
// errstr := ctx.errDrain()
// log.Printf("gl.StencilMask(%v) %v", mask, errstr)
// }()
// C.glStencilMask(C.GLuint(mask))
// }
fn, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if fn.Recv == nil || fn.Recv.List[0].Names[0].Name != "ctx" {
continue
}
tname := "<unknown>"
t := fn.Recv.List[0].Type
if star, ok := t.(*ast.StarExpr); ok {
tname = "*" + star.X.(*ast.Ident).Name
} else if t, ok := t.(*ast.Ident); ok {
tname = t.Name
}
var (
params []string
paramTypes []string
results []string
resultTypes []string
)
// Print function signature.
fmt.Fprintf(buf, "func (ctx %s) %s(", tname, fn.Name.Name)
for i, p := range fn.Type.Params.List {
if i > 0 {
fmt.Fprint(buf, ", ")
}
ty := typeString(p.Type)
for i, n := range p.Names {
if i > 0 {
fmt.Fprint(buf, ", ")
}
fmt.Fprintf(buf, "%s ", n.Name)
params = append(params, n.Name)
paramTypes = append(paramTypes, ty)
}
fmt.Fprint(buf, ty)
}
fmt.Fprintf(buf, ") (")
if fn.Type.Results != nil {
for i, r := range fn.Type.Results.List {
if i > 0 {
fmt.Fprint(buf, ", ")
}
ty := typeString(r.Type)
if len(r.Names) == 0 {
name := fmt.Sprintf("r%d", i)
fmt.Fprintf(buf, "%s ", name)
results = append(results, name)
resultTypes = append(resultTypes, ty)
}
for i, n := range r.Names {
if i > 0 {
fmt.Fprint(buf, ", ")
}
fmt.Fprintf(buf, "%s ", n.Name)
results = append(results, n.Name)
resultTypes = append(resultTypes, ty)
}
fmt.Fprint(buf, ty)
}
}
fmt.Fprintf(buf, ") {\n")
// gl.GetError is used by errDrain, which will be made part of
// all functions. So do not apply it to gl.GetError to avoid
// infinite recursion.
skip := fn.Name.Name == "GetError"
if !skip {
// Insert a defer block for tracing.
fmt.Fprintf(buf, "defer func() {\n")
fmt.Fprintf(buf, "\terrstr := ctx.errDrain()\n")
switch fn.Name.Name {
case "GetUniformLocation", "GetAttribLocation":
fmt.Fprintf(buf, "\tr0.name = name\n")
}
fmt.Fprintf(buf, "\tlog.Printf(\"gl.%s(", fn.Name.Name)
for i, p := range paramTypes {
if i > 0 {
fmt.Fprint(buf, ", ")
}
fmt.Fprint(buf, typePrinter(p))
}
fmt.Fprintf(buf, ") ")
if len(resultTypes) > 1 {
fmt.Fprint(buf, "(")
}
for i, r := range resultTypes {
if i > 0 {
fmt.Fprint(buf, ", ")
}
fmt.Fprint(buf, typePrinter(r))
}
if len(resultTypes) > 1 {
fmt.Fprint(buf, ") ")
}
fmt.Fprintf(buf, "%%v\"")
for i, p := range paramTypes {
fmt.Fprintf(buf, ", %s", typePrinterArg(p, params[i]))
}
for i, r := range resultTypes {
fmt.Fprintf(buf, ", %s", typePrinterArg(r, results[i]))
}
fmt.Fprintf(buf, ", errstr)\n")
fmt.Fprintf(buf, "}()\n")
}
// Print original body of function.
for _, s := range fn.Body.List {
if c := enqueueCall(s); c != nil {
c.Fun.(*ast.SelectorExpr).Sel.Name = "enqueueDebug"
setEnqueueBlocking(c)
}
printer.Fprint(buf, fset, s)
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "}\n\n")
}
b, err := format.Source(buf.Bytes())
if err != nil {
os.Stdout.Write(buf.Bytes())
die(err)
}
if *outfile == "" {
os.Stdout.Write(b)
return
}
if err := ioutil.WriteFile(*outfile, b, 0666); err != nil {
die(err)
}
}
func enqueueCall(stmt ast.Stmt) *ast.CallExpr {
exprStmt, ok := stmt.(*ast.ExprStmt)
if !ok {
return nil
}
call, ok := exprStmt.X.(*ast.CallExpr)
if !ok {
return nil
}
fun, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return nil
}
if fun.Sel.Name != "enqueue" {
return nil
}
return call
}
func setEnqueueBlocking(c *ast.CallExpr) {
lit := c.Args[0].(*ast.CompositeLit)
for _, elt := range lit.Elts {
kv := elt.(*ast.KeyValueExpr)
if kv.Key.(*ast.Ident).Name == "blocking" {
kv.Value = &ast.Ident{Name: "true"}
return
}
}
lit.Elts = append(lit.Elts, &ast.KeyValueExpr{
Key: &ast.Ident{
NamePos: lit.Rbrace,
Name: "blocking",
},
Value: &ast.Ident{Name: "true"},
})
}
const preamble = `// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Generated from gl.go using go generate. DO NOT EDIT.
// See doc.go for details.
// +build linux darwin windows
// +build gldebug
package gl
import (
"fmt"
"log"
"math"
"sync/atomic"
"unsafe"
)
func (ctx *context) errDrain() string {
var errs []Enum
for {
e := ctx.GetError()
if e == 0 {
break
}
errs = append(errs, e)
}
if len(errs) > 0 {
return fmt.Sprintf(" error: %v", errs)
}
return ""
}
func (ctx *context) enqueueDebug(c call) uintptr {
numCalls := atomic.AddInt32(&ctx.debug, 1)
if numCalls > 1 {
panic("concurrent calls made to the same GL context")
}
defer func() {
if atomic.AddInt32(&ctx.debug, -1) > 0 {
select {} // block so you see us in the panic
}
}()
return ctx.enqueue(c)
}
`
type entry struct {
name string
value uint64
}
// enum builds a list of all GL constants that make up the gl.Enum type.
func enum(f *ast.File) []entry {
var entries []entry
for _, d := range f.Decls {
gendecl, ok := d.(*ast.GenDecl)
if !ok {
continue
}
if gendecl.Tok != token.CONST {
continue
}
for _, s := range gendecl.Specs {
v, ok := s.(*ast.ValueSpec)
if !ok {
continue
}
if len(v.Names) != 1 || len(v.Values) != 1 {
continue
}
val, err := strconv.ParseUint(v.Values[0].(*ast.BasicLit).Value, 0, 64)
if err != nil {
log.Fatalf("enum %s: %v", v.Names[0].Name, err)
}
entries = append(entries, entry{v.Names[0].Name, val})
}
}
return entries
}
func dedup(entries []entry) []entry {
// Find all duplicates. Use "%d" as the name of any value with duplicates.
seen := make(map[uint64]int)
for _, e := range entries {
seen[e.value]++
}
var dedup []entry
for _, e := range entries {
switch seen[e.value] {
case 0: // skip, already here
case 1:
dedup = append(dedup, e)
default:
// value is duplicated
dedup = append(dedup, entry{fmt.Sprintf("%d", e.value), e.value})
seen[e.value] = 0
}
}
return dedup
}