From f31971919d6a2fcd56cea051b1eadc2996e0269b Mon Sep 17 00:00:00 2001 From: Louis Thibault Date: Thu, 20 Jul 2023 17:31:53 +0300 Subject: [PATCH 1/4] Remove guest/tinygo, since we are now targeting Go 1.21's WASI support. --- guest/tinygo/ww.go | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 guest/tinygo/ww.go diff --git a/guest/tinygo/ww.go b/guest/tinygo/ww.go deleted file mode 100644 index d160d811..00000000 --- a/guest/tinygo/ww.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package ww contains Wetware bindings for WASM guest-code. -package ww - -import ( - "context" - "unsafe" - - "capnproto.org/go/capnp/v3/rpc" - - api "github.com/wetware/ww/api/cluster" - - "github.com/stealthrocket/wazergo/types" -) - -var conn = rpc.NewConn(rpc.NewStreamTransport(hostPipe{}), nil) - -// Bootstrap returns the host capability exported by the Wetware -// runtime. -func Bootstrap(ctx context.Context) api.Host { - return api.Host(conn.Bootstrap(ctx)) -} - -type hostPipe struct{} - -func (hostPipe) Read(b []byte) (int, error) { - var n uint32 - err := hostRead(bytesToPointer(b), uint32(len(b)), &n) - return int(n), types.Errno(err) -} - -func (hostPipe) Write(b []byte) (int, error) { - var n uint32 - err := hostWrite(bytesToPointer(b), uint32(len(b)), &n) - return int(n), types.Errno(err) -} - -func (hostPipe) Close() error { - return types.Errno(hostClose()) -} - -//go:inline -func bytesToPointer(b []byte) uint32 { - return *(*uint32)(unsafe.Pointer(unsafe.SliceData(b))) -} From 40761df0fad9a1143ecca7414ea562cff697941f Mon Sep 17 00:00:00 2001 From: Louis Thibault Date: Thu, 20 Jul 2023 17:33:19 +0300 Subject: [PATCH 2/4] Fix missing body of main{} in system/internal/main.go. --- system/internal/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/internal/main.go b/system/internal/main.go index 14a9902f..b4a119fd 100644 --- a/system/internal/main.go +++ b/system/internal/main.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "unsafe" ) @@ -16,6 +17,7 @@ var hostbuf, guestbuf []byte // and guest. From here, we should hopefully be able to wrap it // in an rpc.Conn and have it "just work". func main() { + fmt.Println("Hello, Wetware!") // // DEMO // buf := make([]byte, 42) // n, err := pipe{}.Read(buf) From 365aa509c42316f7b92cd9c257bc87c2b33ea8b2 Mon Sep 17 00:00:00 2001 From: Louis Thibault Date: Thu, 20 Jul 2023 17:34:53 +0300 Subject: [PATCH 3/4] Comment unused code in system/internal/main.go (fixes remaining 'go test' errors). --- system/internal/main.go | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/system/internal/main.go b/system/internal/main.go index b4a119fd..07b7bef9 100644 --- a/system/internal/main.go +++ b/system/internal/main.go @@ -4,10 +4,9 @@ package main import ( "fmt" - "unsafe" ) -var hostbuf, guestbuf []byte +// var hostbuf, guestbuf []byte // main is just a demo right now. It is the counterpart to // the net.Conn that is the subject of ww.go's demo. It reads @@ -31,16 +30,16 @@ func main() { // // -- DEMO } -//go:export __init -func wwinit(host, guest uint32) { - hostbuf = make([]byte, int(host)) - guestbuf = make([]byte, int(guest)) - initBuffers(bytesToPointer(hostbuf), bytesToPointer(guestbuf)) -} +// //go:export __init +// func wwinit(host, guest uint32) { +// hostbuf = make([]byte, int(host)) +// guestbuf = make([]byte, int(guest)) +// initBuffers(bytesToPointer(hostbuf), bytesToPointer(guestbuf)) +// } -//go:wasm-module ww -//go:export __init_buffers -func initBuffers(guestBufPtr, hostBufPtr uintptr) +// //go:wasm-module ww +// //go:export __init_buffers +// func initBuffers(guestBufPtr, hostBufPtr uintptr) // //go:wasm-module ww // //go:export _recv @@ -85,14 +84,14 @@ func initBuffers(guestBufPtr, hostBufPtr uintptr) // return // } -//go:inline -func bytesToPointer(b []byte) uintptr { - pointer := unsafe.SliceData(b) - return uintptr(unsafe.Pointer(pointer)) -} +// //go:inline +// func bytesToPointer(b []byte) uintptr { +// pointer := unsafe.SliceData(b) +// return uintptr(unsafe.Pointer(pointer)) +// } -//go:inline -func stringToPointer(s string) uintptr { - pointer := unsafe.StringData(s) - return uintptr(unsafe.Pointer(pointer)) -} +// //go:inline +// func stringToPointer(s string) uintptr { +// pointer := unsafe.StringData(s) +// return uintptr(unsafe.Pointer(pointer)) +// } From 7ef4c939006939d88982564c8658d899ad909837 Mon Sep 17 00:00:00 2001 From: Louis Thibault Date: Thu, 20 Jul 2023 19:17:52 +0300 Subject: [PATCH 4/4] Simplify Path API; fixes test failure. --- pkg/anchor/anchor.go | 2 +- pkg/anchor/path.go | 164 +++++++-------------------------------- pkg/anchor/path_test.go | 166 ++-------------------------------------- 3 files changed, 34 insertions(+), 298 deletions(-) diff --git a/pkg/anchor/anchor.go b/pkg/anchor/anchor.go index f9631f9e..94cd6b56 100644 --- a/pkg/anchor/anchor.go +++ b/pkg/anchor/anchor.go @@ -28,7 +28,7 @@ func (a Anchor) Ls(ctx context.Context) (*Iterator, capnp.ReleaseFunc) { func (a Anchor) Walk(ctx context.Context, path string) (Anchor, capnp.ReleaseFunc) { p := NewPath(path) - if p.IsRoot() { + if p == (Path{}) { anchor := a.AddRef() return anchor, anchor.Release } diff --git a/pkg/anchor/path.go b/pkg/anchor/path.go index acb16492..2db90e1a 100644 --- a/pkg/anchor/path.go +++ b/pkg/anchor/path.go @@ -1,7 +1,6 @@ package anchor import ( - "errors" "fmt" "path" "strings" @@ -10,8 +9,6 @@ import ( "github.com/wetware/ww/pkg/internal/bounded" ) -var root = Path{}.bind(identity) - // Path represents the location of an anchor. It is a bounded value // that enforces the following constraints: paths are strings that // MAY contain any printable ASCII characters except the backslash. @@ -27,7 +24,9 @@ var root = Path{}.bind(identity) // the root path is the identity function over anchors, or that the // Anchor.Walk() method is a nop when the root path is passed in as // an argument. The empty string automatically resolves to the root -// path. +// path. Zero-value paths are also treated as root, but are used as +// as a safe default when the path contains an error, and therefore +// are NOT RECOMMENDED. type Path struct { value bounded.Type[string] } @@ -36,103 +35,60 @@ type Path struct { // the 'path' argument is valid, or an error if it is not. // // Callers SHOULD check Path.Err() before proceeding. -func NewPath(path string) Path { - if trimmed(path) == "" { - return root +func NewPath(path string) (p Path) { + if path = trimmed(path); path != "" { + p = Path{}.bind(func(s string) bounded.Type[string] { + return bounded.Value(path) + }) } - value := bounded.Value(path) - return Path{value: value}.bind(identity) // force validation + return } -// PathFromParts joins each element of 'parts' into a single path, +// JoinPath joins each element of 'parts' into a single path, // with each element separated by '/'. Each element of 'parts' is // validated before being joined. An element is valid if it is a // valid path, and does not contain the path separator. // // Callers SHOULD check Path.Err() before proceeding. -func PathFromParts(parts []string) Path { - if err := validateParts(parts); err != nil { - return failure(err) - } - +func JoinPath(parts []string) Path { return NewPath(path.Join(parts...)) } -func (p Path) Maybe() (string, error) { - return p.value.Maybe() -} - // Err returns a non-nil error if the path is malformed. func (p Path) Err() error { - _, err := p.Maybe() + _, err := p.value.Maybe() return err } // String returns the canonical path string. If Err() != nil, -// String() returns the root path. +// String() returns the zero-value string. func (p Path) String() string { - path, _ := p.Maybe() - return path -} - -// IsRoot returns true if the p is the root path. -func (p Path) IsRoot() bool { - return p.String() == "/" -} - -// IsZero reports whether p is a zero-value path, as distinct from -// the root path. If p.IsZero() == true, then p.IsRoot() == false. -// The converse may not be true. -func (p Path) IsZero() bool { s, err := p.value.Maybe() - return s == "" && err == nil -} - -// IsChild returns true if path is a direct child of p. -// See also: p.IsSubpath() -func (p Path) IsChild(path Path) bool { - parent := p.String() - candidate := path.String() - dir, _ := popright(candidate) - - return (parent == dir) != (candidate == "/") -} - -// Child binds the child's name to path. It fails if the -// child name contains invalid characters of a separator. -func (p Path) WithChild(name string) Path { - if validName(name) { - return p.bind(suffix(name)) + if err != nil { + return "" } - return failure(errors.New("invalid name")) -} - -// Returns true if path is a subpath of p. -func (p Path) IsSubpath(path Path) bool { - parent := p.String() - candidate := path.String() - diff := strings.TrimPrefix(candidate, parent) - - return diff != "" && (parent == "/" || diff[0] == '/') + return path.Clean(path.Join("/", s)) } +// Next splits the path into the tail and head components. It +// is used to iterate through each path component sequentially, +// using the following pattern: +// +// for path, name := path.Next(); name != ""; path, name = path.Next() { +// // do something... +// } func (p Path) Next() (Path, string) { name := p.bind(head).String() return p.bind(tail), trimmed(name) } -// func (p Path) index() []byte { -// path := p.String() -// return []byte(path) // TODO(performance): unsafe.Pointer -// } - func (p Path) bind(f func(string) bounded.Type[string]) Path { value := p.value. + Bind(f). Bind(clean). - Bind(validate). - Bind(f) + Bind(validate) return Path{ value: value, @@ -141,21 +97,6 @@ func (p Path) bind(f func(string) bounded.Type[string]) Path { // Bindable path functions. -// func subpath(path Path) func(string) bounded.Type[string] { -// return suffix(path.String()) -// } - -// func trimPrefix(path Path) func(string) bounded.Type[string] { -// return func(s string) bounded.Type[string] { -// suffix := strings.TrimPrefix(path.String(), s) -// return bounded.Value(suffix).Bind(clean) -// } -// } - -func identity(path string) bounded.Type[string] { - return bounded.Value(path) -} - func head(path string) bounded.Type[string] { path, _ = popleft(path) return bounded.Value(path) @@ -166,30 +107,11 @@ func tail(path string) bounded.Type[string] { return bounded.Value(path) } -// func last(path string) bounded.Type[string] { -// _, path = popright(path) -// return bounded.Value(path) -// } - -// func parent(path string) bounded.Type[string] { -// path, _ = popright(path) -// return bounded.Value(path) -// } - -func suffix(s string) func(string) bounded.Type[string] { - return func(prefix string) bounded.Type[string] { - return bounded.Value(path.Join(prefix, s)) - } -} - // clean the path through pure lexical analysis, and esure it has // exactly one leading separator. Cleaned paths are not guaranteed // to be valid, but are guaranteed to compose within the STM index. func clean(p string) bounded.Type[string] { - // Ensure the path begins with a "/", so that prefixes - // compose well in the STM index. - p = path.Clean(path.Join("/", p)) - return bounded.Value(p) + return bounded.Value(path.Clean(p)) } // validate returns the unmodified path if it contains only @@ -206,33 +128,12 @@ func validate(path string) bounded.Type[string] { return bounded.Value(path) } -func validateParts(path []string) error { - // ensure there are no path separators in the components. - for i, p := range path { - if !validName(p) { - return fmt.Errorf("segment %d: invalid name", i) - } - } - - return nil -} - // valid returns true if r is a legal character in a path. // The separator path '/' returns true. func valid(r rune) bool { return unicode.In(r, &pathChars) } -func validName(name string) bool { - for _, r := range name { - if r == '/' || !valid(r) { - return false - } - } - - return true -} - func failure(err error) Path { return Path{ value: bounded.Failure[string](err), @@ -254,10 +155,6 @@ func popleft(path string) (string, string) { return pop(next, path) } -func popright(path string) (string, string) { - return pop(prev, path) -} - func pop(index func(string) int, path string) (string, string) { if i := index(path); i > 0 { return path[:i], path[i:] @@ -270,15 +167,6 @@ func next(path string) int { return strings.IndexRune(trimmed(path), '/') + 1 } -func prev(path string) int { - i := strings.LastIndex(trimmed(path), "/") - if i < 0 { - i = 0 - } - - return i + 1 -} - func trimmed(path string) string { return strings.TrimPrefix(path, "/") } diff --git a/pkg/anchor/path_test.go b/pkg/anchor/path_test.go index d9bfae9f..4c3b2a14 100644 --- a/pkg/anchor/path_test.go +++ b/pkg/anchor/path_test.go @@ -50,14 +50,16 @@ func TestPath(t *testing.T) { } } -func TestPathFromParts(t *testing.T) { +func TestJoinPath(t *testing.T) { t.Parallel() - path := anchor.PathFromParts([]string{"foo", "bar"}) - assert.NoError(t, path.Err(), "should bind path from parts") + path := anchor.JoinPath([]string{"foo", "bar"}) + require.NoError(t, path.Err(), "should bind path from parts") + require.Equal(t, "/foo/bar", path.String()) - failed := anchor.PathFromParts([]string{"foo", "/fail"}) - assert.Error(t, failed.Err(), "should not bind invalid path segment") + path = anchor.JoinPath([]string{"/foo/", "/bar"}) + require.NoError(t, path.Err(), "should bind path from parts") + require.Equal(t, "/foo/bar", path.String()) } func TestPathIteration(t *testing.T) { @@ -90,160 +92,6 @@ func TestPathIteration(t *testing.T) { } } -func TestIsRoot(t *testing.T) { - t.Parallel() - - assert.True(t, anchor.NewPath("").IsRoot(), - "should be root") - assert.True(t, anchor.Path{}.IsRoot(), - "zero-value path should be root") - assert.False(t, anchor.NewPath("/foo/bar").IsRoot(), - "should not be root") - assert.False(t, anchor.NewPath("µ").IsRoot(), - "invalid path should not be root") -} - -func TestIsZero(t *testing.T) { - t.Parallel() - - assert.False(t, anchor.NewPath("").IsZero(), - `root path should not be zero-value`) - assert.True(t, anchor.Path{}.IsZero(), - "anchor.Path{} should be zero-value") - assert.False(t, anchor.NewPath("/foo").IsZero(), - "non-root path should not be zero-value") - assert.False(t, anchor.NewPath("µ").IsZero(), - "invalid path should not be zero-value") -} - -func TestWithChild(t *testing.T) { - t.Parallel() - - parent := anchor.NewPath("/foo") - - child := parent.WithChild("baz") - assert.NoError(t, child.Err(), "should bind child") - - fail := parent.WithChild("/baz") - assert.Error(t, fail.Err(), "should not bind invalid child") -} - -func TestIsSubpath(t *testing.T) { - t.Parallel() - - for _, tt := range []struct { - name string - parent, subpath anchor.Path - expect bool - }{ - { - name: "root", - expect: false, - }, - { - name: "child", - parent: anchor.NewPath("/foo"), - subpath: anchor.NewPath("/foo/bar"), - expect: true, - }, - { - name: "nonchild", - parent: anchor.NewPath("/foo"), - subpath: anchor.NewPath("/foobar"), - expect: false, - }, - { - name: "nonchild", - parent: anchor.NewPath("/foo"), - subpath: anchor.NewPath("/foobar/baz"), - expect: false, - }, - { - name: "childOfRoot", - parent: anchor.NewPath(""), - subpath: anchor.NewPath("/foo"), - expect: true, - }, - } { - t.Run(tt.name, func(t *testing.T) { - ok := tt.parent.IsSubpath(tt.subpath) - if tt.expect { - assert.True(t, ok, "%s should be a supbath of %s", - tt.subpath, - tt.parent) - } else { - assert.False(t, ok, "%s should NOT be a subpath of %s", - tt.subpath, - tt.parent) - } - }) - } -} - -func TestIsChild(t *testing.T) { - t.Parallel() - - for _, tt := range []struct { - name string - parent, child anchor.Path - expect bool - }{ - { - name: "root", - expect: false, - }, - { - name: "child", - parent: anchor.NewPath("/foo"), - child: anchor.NewPath("/foo/bar"), - expect: true, - }, - { - name: "deep", - parent: anchor.NewPath("/foo/bar/baz"), - child: anchor.NewPath("/foo/bar/baz/qux"), - expect: true, - }, - { - name: "samePrefix", - parent: anchor.NewPath("/foo"), - child: anchor.NewPath("/foobar"), - expect: false, - }, - { - name: "nonchild", - parent: anchor.NewPath("/foo"), - child: anchor.NewPath("/foobar/baz"), - expect: false, - }, - { - name: "child", - parent: anchor.NewPath("/foo"), - child: anchor.NewPath("/foo/bar/baz"), - expect: false, - }, - { - name: "childOfRoot", - parent: anchor.NewPath(""), - child: anchor.NewPath("/foo"), - expect: true, - }, - } { - t.Run(tt.name, func(t *testing.T) { - ok := tt.parent.IsChild(tt.child) - if tt.expect { - assert.True(t, ok, "%s should be a subpath of %s", - tt.child, - tt.parent) - } else { - assert.False(t, ok, "%s should NOT be a subpath of %s", - tt.child, - tt.parent) - } - }) - } -} - func trimmed(s string) string { return strings.Trim(s, "/") }