Skip to content

Commit

Permalink
gotypes,build: add Implement (#58)
Browse files Browse the repository at this point in the history
By using Implement you can provide a definition of a function, taking the signature from a stub in the package. One major benefit of this approach is it makes it easy to handle external types in the function signature.

Updates #55
  • Loading branch information
mmcloughlin committed Jan 23, 2019
1 parent 9c913ee commit eb225e9
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -171,8 +171,9 @@ For demonstrations of `avo` features:

* **[args](examples/args):** Loading function arguments.
* **[returns](examples/returns):** Building return values.
* **[data](examples/data):** Defining `DATA` sections.
* **[complex](examples/complex):** Working with `complex{64,128}` types.
* **[data](examples/data):** Defining `DATA` sections.
* **[ext](examples/ext):** Interacting with types from external packages.

### Real Examples

Expand Down
17 changes: 17 additions & 0 deletions build/context.go
Expand Up @@ -111,6 +111,23 @@ func (c *Context) SignatureExpr(expr string) {
c.Signature(s)
}

// Implement starts building a function of the given name, whose type is
// specified by a stub in the containing package.
func (c *Context) Implement(name string) {
pkg := c.types()
if pkg == nil {
c.adderrormessage("no package specified")
return
}
s, err := gotypes.LookupSignature(pkg, name)
if err != nil {
c.adderror(err)
return
}
c.Function(name)
c.Signature(s)
}

func (c *Context) types() *types.Package {
if c.pkg == nil {
return nil
Expand Down
4 changes: 4 additions & 0 deletions build/global.go
Expand Up @@ -118,6 +118,10 @@ func Doc(lines ...string) { ctx.Doc(lines...) }
// Attributes sets function attributes for the currently active function.
func Attributes(a attr.Attribute) { ctx.Attributes(a) }

// Implement starts building a function of the given name, whose type is
// specified by a stub in the containing package.
func Implement(name string) { ctx.Implement(name) }

// AllocLocal allocates size bytes in the stack of the currently active function.
// Returns a reference to the base pointer for the newly allocated region.
func AllocLocal(size int) operand.Mem { return ctx.AllocLocal(size) }
Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Expand Up @@ -11,6 +11,8 @@ Features:
* **[returns](returns):** Building return values.
* **[complex](complex):** Working with `complex{64,128}` types.
* **[data](data):** Defining `DATA` sections.
* **[ext](ext):** Interacting with types from external packages.


"Real" examples:

Expand Down
33 changes: 33 additions & 0 deletions examples/ext/README.md
@@ -0,0 +1,33 @@
# ext

Demonstrates how to use external types in an `avo` function signature.

In this case, you will need to write the function stub yourself.

[embedmd]:# (stub.go /package/ $)
```go
package ext

import "github.com/mmcloughlin/avo/examples/ext/ext"

// StructFieldB returns field B.
func StructFieldB(e ext.Struct) byte
```

Then in place of the usual `TEXT` declaration we use `Implement` to state that we are beginning the definition of a function already declared in the package stub file.

[embedmd]:# (asm.go go /.*Package.*/ /RET.*/)
```go
Package("github.com/mmcloughlin/avo/examples/ext")
Implement("StructFieldB")
b := Load(Param("e").Field("B"), GP8())
Store(b, ReturnIndex(0))
RET()
```

Finally, in this case the `go:generate` line is different since we do not need to generate the stub file.

[embedmd]:# (ext_test.go go /.*go:generate.*/)
```go
//go:generate go run asm.go -out ext.s
```
16 changes: 16 additions & 0 deletions examples/ext/asm.go
@@ -0,0 +1,16 @@
// +build ignore

package main

import (
. "github.com/mmcloughlin/avo/build"
)

func main() {
Package("github.com/mmcloughlin/avo/examples/ext")
Implement("StructFieldB")
b := Load(Param("e").Field("B"), GP8())
Store(b, ReturnIndex(0))
RET()
Generate()
}
7 changes: 7 additions & 0 deletions examples/ext/ext.s
@@ -0,0 +1,7 @@
// Code generated by command: go run asm.go -out ext.s. DO NOT EDIT.

// func StructFieldB(e ext.Struct) byte
TEXT ·StructFieldB(SB), $0-25
MOVB e_B+6(FP), AL
MOVB AL, ret+24(FP)
RET
9 changes: 9 additions & 0 deletions examples/ext/ext/ext.go
@@ -0,0 +1,9 @@
// Package ext is used as a target package in the external types example.
package ext

// Struct is the target type in the external types example.
type Struct struct {
A [3]uint16
B byte
C string
}
17 changes: 17 additions & 0 deletions examples/ext/ext_test.go
@@ -0,0 +1,17 @@
package ext

import (
"testing"
"testing/quick"

"github.com/mmcloughlin/avo/examples/ext/ext"
)

//go:generate go run asm.go -out ext.s

func TestFunc(t *testing.T) {
expect := func(e ext.Struct) byte { return e.B }
if err := quick.CheckEqual(StructFieldB, expect, nil); err != nil {
t.Fatal(err)
}
}
7 changes: 7 additions & 0 deletions examples/ext/stub.go
@@ -0,0 +1,7 @@
// Package ext demonstrates how to interact with external types.
package ext

import "github.com/mmcloughlin/avo/examples/ext/ext"

// StructFieldB returns field B.
func StructFieldB(e ext.Struct) byte
22 changes: 21 additions & 1 deletion gotypes/signature.go
Expand Up @@ -3,6 +3,7 @@ package gotypes
import (
"bytes"
"errors"
"fmt"
"go/token"
"go/types"
"strconv"
Expand Down Expand Up @@ -31,6 +32,20 @@ func NewSignatureVoid() *Signature {
return NewSignature(nil, types.NewSignature(nil, nil, nil, false))
}

// LookupSignature returns the signature of the named function in the provided package.
func LookupSignature(pkg *types.Package, name string) (*Signature, error) {
scope := pkg.Scope()
obj := scope.Lookup(name)
if obj == nil {
return nil, fmt.Errorf("could not find function \"%s\"", name)
}
s, ok := obj.Type().(*types.Signature)
if !ok {
return nil, fmt.Errorf("object \"%s\" does not have signature type", name)
}
return NewSignature(pkg, s), nil
}

// ParseSignature builds a Signature by parsing a Go function type expression.
// The function type must reference builtin types only; see
// ParseSignatureInPackage if custom types are required.
Expand Down Expand Up @@ -67,7 +82,12 @@ func (s *Signature) Bytes() int { return s.Params().Bytes() + s.Results().Bytes(
// String writes Signature as a string. This does not include the "func" keyword.
func (s *Signature) String() string {
var buf bytes.Buffer
types.WriteSignature(&buf, s.sig, types.RelativeTo(s.pkg))
types.WriteSignature(&buf, s.sig, func(pkg *types.Package) string {
if pkg == s.pkg {
return ""
}
return pkg.Name()
})
return buf.String()
}

Expand Down
56 changes: 56 additions & 0 deletions gotypes/signature_test.go
Expand Up @@ -5,8 +5,64 @@ import (
"go/types"
"strings"
"testing"

"golang.org/x/tools/go/packages"
)

func TestLookupSignature(t *testing.T) {
pkg := LoadPackageTypes(t, "math")
s, err := LookupSignature(pkg, "Frexp")
if err != nil {
t.Fatal(err)
}

expect, err := ParseSignature("func(f float64) (frac float64, exp int)")
if err != nil {
t.Fatal(err)
}

if s.String() != expect.String() {
t.Errorf("\n got: %s\nexpect: %s\n", s, expect)
}
}

func TestLookupSignatureErrors(t *testing.T) {
cases := []struct {
PackagePath string
FunctionName string
ExpectedError string
}{
{"runtime", "HmmIdk", "could not find function \"HmmIdk\""},
{"crypto", "Decrypter", "object \"Decrypter\" does not have signature type"},
{"encoding/base64", "StdEncoding", "object \"StdEncoding\" does not have signature type"},
}
for _, c := range cases {
pkg := LoadPackageTypes(t, c.PackagePath)
_, err := LookupSignature(pkg, c.FunctionName)
if err == nil {
t.Fatalf("expected error looking up '%s' in package '%s'", c.FunctionName, c.PackagePath)
}
if err.Error() != c.ExpectedError {
t.Fatalf("wrong error message\n got: %q\nexpect: %q", err.Error(), c.ExpectedError)
}
}
}

func LoadPackageTypes(t *testing.T, path string) *types.Package {
t.Helper()
cfg := &packages.Config{
Mode: packages.LoadTypes,
}
pkgs, err := packages.Load(cfg, path)
if err != nil {
t.Fatal(err)
}
if len(pkgs) != 1 {
t.Fatal("expected to load exactly one package")
}
return pkgs[0].Types
}

func TestParseSignature(t *testing.T) {
cases := []struct {
Expr string
Expand Down

0 comments on commit eb225e9

Please sign in to comment.