Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for specification overloads #107

Merged
merged 15 commits into from
Jun 3, 2020
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ wgl
# Vim
*.swp
*.swo

# GoLand
.idea/
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Features:
- Go functions that mirror the C specification using Go types.
- Support for multiple OpenGL APIs (GL/GLES/EGL/WGL/GLX/EGL), versions, and profiles.
- Support for extensions (including debug callbacks).
- Support for overloads to provide Go functions with different parameter signatures.

See the [open issues](https://github.com/go-gl/glow/issues) for caveats about the current state of the implementation.

Expand All @@ -15,6 +16,15 @@ Generated Packages

Generated OpenGL binding packages are available in the [go-gl/gl](https://github.com/go-gl/gl) repository.

Overloads
---------

See subdirectory `xml/overload` for examples. The motivation here is to provide Go functions with different parameter signatures of existing OpenGL functions.

For example, `glVertexAttribPointer(..., void *)` cannot be used with `gl.VertexAttribPointer(..., unsafe.Pointer)` when using arbitrary offset values. The `checkptr` safeguard will abort the program when doing so.
Overloads allow the creation of an additional `gl.VertexAttribPointerWithOffset(..., uintptr)`, which calls the original OpenGL function with appropriate casts.


Custom Packages
---------------

Expand Down
9 changes: 9 additions & 0 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ type Function struct {
GoName string // Go name of the function with the API prefix stripped
Parameters []Parameter
Return Type
Overloads []Overload
}

// An Overload describes an alternative signature for the same function.
type Overload struct {
GoName string // Go name of the original function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this value be pulled from the parent struct? similarly for Name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name turned out not to be necessary. As for GoName, I was not able to figure out an easy way to access GoName from the parent from the sub-template overloadCall. Hence I kept this field

OverloadName string // Go name of the overload
Parameters []Parameter
Return Type
}

// A Parameter to a Function.
Expand Down
12 changes: 11 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func performRestriction(pkg *Package, jsonPath string) {

func parseSpecifications(xmlDir string) []*Specification {
specDir := filepath.Join(xmlDir, "spec")
overloadDir := filepath.Join(xmlDir, "overload")
specFiles, err := ioutil.ReadDir(specDir)
if err != nil {
log.Fatalln("error reading spec file entries:", err)
Expand All @@ -124,7 +125,16 @@ func parseSpecifications(xmlDir string) []*Specification {
if !strings.HasSuffix(specFile.Name(), "xml") {
continue
}
spec, err := NewSpecification(filepath.Join(specDir, specFile.Name()))

registry, err := readSpecFile(filepath.Join(specDir, specFile.Name()))
if err != nil {
log.Fatalln("error reading XML spec file: ", specFile.Name(), err)
}
overloads, err := readOverloadFile(filepath.Join(overloadDir, specFile.Name()))
if err != nil {
log.Fatalln("error reading XML overload file: ", specFile.Name(), err)
}
spec, err := NewSpecification(*registry, overloads)
if err != nil {
log.Fatalln("error parsing specification:", specFile.Name(), err)
}
Expand Down
52 changes: 52 additions & 0 deletions overload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"encoding/xml"
"os"
)

type xmlOverloads struct {
Overloads []xmlOverload `xml:"overload"`
}

type xmlOverload struct {
Name string `xml:"name,attr"`
OverloadName string `xml:"overloadName,attr"`

ParameterChanges []xmlParameterChange `xml:"parameterChanges>change"`
}

type xmlParameterChange struct {
// Index is the zero-based index of the parameter list.
Index int `xml:"index,attr"`
// Name describes a change of the parameter name.
Name *xmlNameChange `xml:"name"`
// Type describes a change of the parameter type.
Type *xmlTypeChange `xml:"type"`
}

type xmlNameChange struct {
Value string `xml:"value,attr"`
}

type xmlTypeChange struct {
Signature string `xml:"signature,attr"`
}

func readOverloadFile(file string) (xmlOverloads, error) {
var overloads xmlOverloads

_, err := os.Stat(file)
if err != nil {
return overloads, nil
}

f, err := os.Open(file)
if err != nil {
return overloads, err
}
defer f.Close()

err = xml.NewDecoder(f).Decode(&overloads)
return overloads, err
}
64 changes: 60 additions & 4 deletions spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,53 @@ func parseFunctions(commands []xmlCommand) (specFunctions, error) {
return functions, nil
}

func parseOverloads(functions specFunctions, overloads xmlOverloads) (specFunctions, error) {
for _, overloadInfo := range overloads.Overloads {
function := functions.getByName(overloadInfo.Name)
if function == nil {
return nil, fmt.Errorf("function <%s> not found to overload", overloadInfo.Name)
}
err := overloadFunction(function, overloadInfo)
if err != nil {
return nil, err
}
}
return functions, nil
}

func overloadFunction(function *Function, info xmlOverload) error {
overload := Overload{
GoName: function.GoName,
OverloadName: info.OverloadName,
Parameters: make([]Parameter, len(function.Parameters)),
Return: function.Return,
}
copy(overload.Parameters, function.Parameters)
for _, change := range info.ParameterChanges {
if (change.Index < 0) || (change.Index >= len(function.Parameters)) {
return fmt.Errorf("overload for <%s> has invalid parameter index", info.Name)
}
param := &overload.Parameters[change.Index]

if change.Type != nil {
_, ctype, err := parseSignature(xmlSignature(change.Type.Signature))
if err != nil {
return fmt.Errorf("failed to parse signature of overload for <%s>: %v", info.Name, err)
}
// store original type definition as a cast, as this most likely will be needed.
param.Type.Cast = param.Type.CDefinition
param.Type.PointerLevel = ctype.PointerLevel
param.Type.Name = ctype.Name
param.Type.CDefinition = ctype.CDefinition
}
if change.Name != nil {
param.Name = change.Name.Value
}
}
function.Overloads = append(function.Overloads, overload)
return nil
}

func parseSignature(signature xmlSignature) (name string, ctype Type, err error) {
readingName := false
readingType := false
Expand Down Expand Up @@ -402,6 +449,15 @@ func (functions specFunctions) get(name, api string) *Function {
return functions[specRef{name, ""}]
}

func (functions specFunctions) getByName(name string) *Function {
for key, function := range functions {
if key.name == name {
return function
}
}
return nil
}

func (enums specEnums) get(name, api string) *Enum {
enum, ok := enums[specRef{name, api}]
if ok {
Expand Down Expand Up @@ -459,14 +515,14 @@ func (addRem *specAddRemSet) shouldInclude(pkgSpec *PackageSpec) bool {
return true
}

// NewSpecification creates a new specification based on an XML file.
func NewSpecification(file string) (*Specification, error) {
registry, err := readSpecFile(file)
// NewSpecification creates a new specification based on an XML registry.
func NewSpecification(registry xmlRegistry, overloads xmlOverloads) (*Specification, error) {
functions, err := parseFunctions(registry.Commands)
if err != nil {
return nil, err
}

functions, err := parseFunctions(registry.Commands)
functions, err = parseOverloads(functions, overloads)
if err != nil {
return nil, err
}
Expand Down
80 changes: 80 additions & 0 deletions spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import "testing"

func TestParseSignature(t *testing.T) {
tt := []struct {
input string
expectedName string
expectedType Type
}{
{
input: "const void *<name>pointer</name>",
expectedName: "pointer",
expectedType: Type{
Name: "void",
PointerLevel: 1,
CDefinition: "const void *",
},
},
{
input: "<ptype>GLsizei</ptype> <name>stride</name>",
expectedName: "stride",
expectedType: Type{
Name: "GLsizei",
PointerLevel: 0,
CDefinition: "GLsizei ",
},
},
{
input: "const <ptype>GLuint</ptype> *<name>value</name>",
expectedName: "value",
expectedType: Type{
Name: "GLuint",
PointerLevel: 1,
CDefinition: "const GLuint *",
},
},
{
input: "<ptype>GLuint</ptype> <name>baseAndCount</name>[2]",
expectedName: "baseAndCount",
expectedType: Type{
Name: "GLuint",
PointerLevel: 1,
CDefinition: "GLuint *",
},
},
{
input: "uintptr_t **",
expectedName: "",
expectedType: Type{
Name: "uintptr_t",
PointerLevel: 2,
CDefinition: "uintptr_t **",
},
},
}

for _, tc := range tt {
tc := tc
t.Run(tc.input, func(t *testing.T) {
name, ctype, err := parseSignature(xmlSignature(tc.input))
failed := false
if err != nil {
t.Logf("parseSignature returned error: %v", err)
failed = true
}
if name != tc.expectedName {
t.Logf("name [%s] does not match expected [%s]", name, tc.expectedName)
failed = true
}
if ctype != tc.expectedType {
t.Logf("type [%v] does not match expected [%v]", ctype, tc.expectedType)
failed = true
}
if failed {
t.Fail()
}
})
}
}
21 changes: 20 additions & 1 deletion tmpl/package.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package {{.Name}}
//glow:rmspace

{{define "paramsCDecl"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.Type.CType}} {{$p.CName}}{{end}}{{end}}
{{define "paramsCCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{if $p.Type.IsDebugProc}}glowCDebugCallback{{else}}{{$p.CName}}{{end}}{{end}}{{end}}
{{define "paramsCCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{if $p.Type.IsDebugProc}}glowCDebugCallback{{else}}{{if ge (len $p.Type.Cast) 1}}({{$p.Type.Cast}})({{end}}{{$p.CName}}{{if ge (len $p.Type.Cast) 1}}){{end}}{{end}}{{end}}{{end}}

{{define "paramsGoDecl"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.GoName}} {{$p.Type.GoType}}{{end}}{{end}}
{{define "paramsGoCall"}}{{range $i, $p := .}}{{if ne $i 0}}, {{end}}{{$p.Type.ConvertGoToC $p.GoName}}{{end}}{{end}}
Expand Down Expand Up @@ -66,6 +66,11 @@ package {{.Name}}
// static {{.Return.CType}} glow{{.GoName}}(GP{{toUpper .GoName}} fnptr{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsCDecl" .Parameters}}) {
// {{if not .Return.IsVoid}}return {{end}}(*fnptr)({{template "paramsCCall" .Parameters}});
// }
// {{range .Overloads}}
// static {{.Return.CType}} glow{{.OverloadName}}(GP{{toUpper .GoName}} fnptr{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsCDecl" .Parameters}}) {
// {{if not .Return.IsVoid}}return {{end}}(*fnptr)({{template "paramsCCall" .Parameters}});
// }
// {{end}}
// {{end}}
//
import "C"
Expand Down Expand Up @@ -95,6 +100,7 @@ func boolToInt(b bool) int {
}

{{define "bridgeCall"}}C.glow{{.GoName}}(gp{{.GoName}}{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsGoCall" .Parameters}}){{end}}
{{define "overloadCall"}}C.glow{{.OverloadName}}(gp{{.GoName}}{{if ge (len .Parameters) 1}}, {{end}}{{template "paramsGoCall" .Parameters}}){{end}}
{{range .Functions}}
{{.Comment}}
func {{.GoName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid}} {{.Return.GoType}}{{end}} {
Expand All @@ -107,6 +113,19 @@ func {{.GoName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid
return {{.Return.ConvertCToGo "ret"}}
{{end}}
}
{{range .Overloads}}

func {{.OverloadName}}({{template "paramsGoDecl" .Parameters}}){{if not .Return.IsVoid}} {{.Return.GoType}}{{end}} {
{{range .Parameters}}
{{if .Type.IsDebugProc}}userDebugCallback = {{.GoName}}{{end}}
{{end}}
{{if .Return.IsVoid}}{{template "overloadCall" .}}
{{else}}
ret := {{template "overloadCall" .}}
return {{.Return.ConvertCToGo "ret"}}
{{end}}
}
{{end}}
{{end}}

//glow:keepspace
Expand Down
3 changes: 3 additions & 0 deletions type.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Type struct {
Name string // Name of the type without modifiers
PointerLevel int // Number of levels of declared indirection to the type
CDefinition string // Raw C definition
Cast string // Raw C cast in case conversion is necessary
}

// A Typedef describes a C typedef statement.
Expand Down Expand Up @@ -112,6 +113,8 @@ func (t Type) GoType() string {
case "GLDEBUGPROC", "GLDEBUGPROCARB", "GLDEBUGPROCKHR":
// Special case mapping to the type defined in debug.tmpl
return "DebugProc"
case "uintptr_t":
return t.pointers() + "uintptr"
}
return "unsafe.Pointer"
}
Expand Down
30 changes: 30 additions & 0 deletions type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import "testing"

func TestGoType(t *testing.T) {
tt := []struct {
in Type
expected string
}{
{
in: Type{
Name: "uintptr_t",
PointerLevel: 1,
CDefinition: "uintptr_t*",
Cast: "void *",
},
expected: "*uintptr",
},
}

for _, tc := range tt {
tc := tc
t.Run(tc.in.String(), func(t *testing.T) {
goType := tc.in.GoType()
if goType != tc.expected {
t.Errorf("expected <%s>, got <%s>", tc.expected, goType)
}
})
}
}
Loading