diff --git a/ipsl/builtins.go b/ipsl/builtins.go deleted file mode 100644 index a489e2ebea..0000000000 --- a/ipsl/builtins.go +++ /dev/null @@ -1,6 +0,0 @@ -package ipsl - -var defaultBuiltinFrame = frame{scope: ScopeMapping{ - "all": compileAll, - "empty": compileEmpty, -}} diff --git a/ipsl/compile.go b/ipsl/compile.go deleted file mode 100644 index 0f7fb9159f..0000000000 --- a/ipsl/compile.go +++ /dev/null @@ -1,560 +0,0 @@ -package ipsl - -import ( - "errors" - "fmt" - "io" - "strconv" - "strings" - "sync" - - "github.com/ipfs/go-cid" -) - -type UnreadableRuneReader interface { - io.RuneReader - - UnreadRune() error -} - -// ErrTypeError indicagtes a syntax error, this error will always be errors.Is able. -type ErrTypeError struct { - Msg string -} - -func (e ErrTypeError) Error() string { - return e.Msg -} - -// ErrSyntaxError indicates a syntax error, this error will always be errors.Is able. -type ErrSyntaxError struct { - msg string -} - -func (e ErrSyntaxError) Error() string { - return e.msg -} - -// Compiler allows to do multiple compilations and customise which builtins are available by default. -// The main point of this is for the testsuite because it allows to add mocked builtins nodes. -// You should most likely just use the CompileToTraversal and Compile functions. -// The zero value is valid and include the default builtin scope. -type Compiler struct { - builtinFrame frame - scopes map[string]Scope - initFrame sync.Once -} - -func (c *Compiler) initDefaultFrame() { - c.initFrame.Do(func() { - c.builtinFrame.scope = ScopeMapping{ - "load-builtin-scope": c.loadBuiltinScope, - } - c.builtinFrame.next = &defaultBuiltinFrame - c.scopes = make(map[string]Scope) - }) -} - -// SetBuiltin add a new builtin node to the compiler. -// It is not threadsafe with any other method of the Compiler. -func (c *Compiler) SetBuiltin(name string, nodeCompiler NodeCompiler) { - c.initDefaultFrame() - c.builtinFrame.scope[name] = nodeCompiler -} - -// SetBuiltinScope add a scope that will be loadable by the load-builtin-scope node. -// It is not threadsafe with any other method of the Compiler. -func (c *Compiler) SetBuiltinScope(name string, scope Scope) { - c.initDefaultFrame() - c.scopes[name] = scope -} - -func (c *Compiler) loadBuiltinScope(arguments ...SomeNode) (SomeNode, error) { - if len(arguments) != 1 { - return SomeNode{}, ErrTypeError{fmt.Sprintf("too many arguments: expected 1; got %d", len(arguments))} - } - - // TODO: replace string argument with a string matcher. - arg := arguments[0] - str, ok := arg.Node.(StringLiteral) - if !ok { - return SomeNode{}, ErrTypeError{fmt.Sprintf("wrong type passed in: expected String; got %s", PrettyNodeType(arg.Node))} - } - - scope, ok := c.scopes[str.Str] - if !ok { - return SomeNode{Node: None{}}, nil - } - return SomeNode{scope}, nil -} - -// CompileToTraversal returns a Traversal that has been compiled and the number of bytes red. -// It is thread safe to do multiple compilations at once on the same Compiler. -func (c *Compiler) CompileToTraversal(rr UnreadableRuneReader) (Traversal, int, error) { - node, n, err := c.Compile(rr) - if err != nil { - return nil, n, err - } - traversal, ok := node.Node.(Traversal) - if !ok { - return nil, n, ErrTypeError{fmt.Sprintf("expected type Traversal; got %q", PrettyNodeType(node.Node))} - } - return traversal, n, nil -} - -// Compile returns some node that has been compiled and the number of bytes red. -// It is thread safe to do multiple compilations at once on the same Compiler. -func (c *Compiler) Compile(rr UnreadableRuneReader) (SomeNode, int, error) { - c.initDefaultFrame() - return compiler{rr}.compileNextNodeWithoutTermination(&c.builtinFrame) -} - -// compiler represent a single pass compilation attempt -type compiler struct { - rr UnreadableRuneReader -} - -// compileNextNode compile the next node, it will return a non zero rune if a closing brace has been reached. -// It will never return a non zero SomeNode as well as a non zero rune. -func (c compiler) compileNextNode(f *frame) (SomeNode, rune, int, error) { - var sum int - for { - r, n, err := c.rr.ReadRune() - sum += n - switch err { - case nil: - case io.EOF: - err = io.ErrUnexpectedEOF - fallthrough - default: - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode ReadRune: %w", err) - } - - switch r { - case '(': - node, n, err := c.compileValueNode(f) - sum += n - if err != nil { - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode compileValueNode: %w", err) - } - return node, 0, sum, nil - case '[': - node, n, err := c.compileScopeNode(f) - sum += n - if err != nil { - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode compileScopeNode: %w", err) - } - return node, 0, sum, nil - case ')', ']': - // We cannot see a } in correct code because they are swalloed by skipComment. - return SomeNode{}, r, sum, nil - case '$': - node, n, err := c.compileCid() - sum += n - if err != nil { - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode compileCid: %w", err) - } - return node, 0, sum, nil - case '"': - node, n, err := c.compileString() - sum += n - if err != nil { - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode compileString: %w", err) - } - return node, 0, sum, nil - case '°': - return SomeNode{Node: None{}}, 0, sum, nil - // TODO: implement other literals - case '{': - n, err := c.skipComment() - sum += n - if err != nil { - return SomeNode{}, 0, sum, fmt.Errorf("compileNextNode skipComment: %w", err) - } - case ' ', '\n', '\t': - continue - default: - return SomeNode{}, 0, sum, fmt.Errorf("while parsing next node encountered unkown rune %q", string(r)) - } - } -} - -func (c compiler) compileNextNodeWithoutTermination(f *frame) (SomeNode, int, error) { - node, end, n, err := c.compileNextNode(f) - if err != nil { - return SomeNode{}, n, err - } - if end != 0 { - return SomeNode{}, n, ErrSyntaxError{fmt.Sprintf("unexpected node termination %q", string(end))} - } - - return node, n, err -} - -func (c compiler) compileCid() (SomeNode, int, error) { - token, n, err := c.readToken() - if err != nil { - return SomeNode{}, n, err - } - - cid, err := cid.Decode(string(token)) - if err != nil { - return SomeNode{}, n, err - } - - return SomeNode{ - Node: CidLiteral{cid}, - }, n, nil -} - -func (c compiler) compileString() (SomeNode, int, error) { - tokens := append([]rune(nil), '"') - var backslash bool // stores if the previous char was a backslash - var sum int - for { - r, n, err := c.rr.ReadRune() - sum += n - switch err { - case nil: - case io.EOF: - err = io.ErrUnexpectedEOF - fallthrough - default: - return SomeNode{}, sum, err - } - - tokens = append(tokens, r) - - switch r { - case '\\': - backslash = !backslash - case '"': - if backslash { - backslash = false - continue - } - - str, err := strconv.Unquote(string(tokens)) - if err != nil { - return SomeNode{}, sum, err - } - - return SomeNode{ - Node: StringLiteral{str}, - }, sum, nil - default: - backslash = false - } - } -} - -func (c compiler) compileValueNode(f *frame) (SomeNode, int, error) { - var sum int - tokenRunes, n, err := c.readToken() - sum += n - if err != nil { - return SomeNode{}, sum, err - } - - var arguments []SomeNode -argumentLoop: - for { - node, end, n, err := c.compileNextNode(f) - sum += n - if err != nil { - return SomeNode{}, sum, err - } - - switch end { - case ')': - break argumentLoop - case 0: - default: - return SomeNode{}, sum, ErrSyntaxError{fmt.Sprintf("incorrect termination for a value node: %q", string(end))} - } - - arguments = append(arguments, node) - } - - token := string(tokenRunes) - compiler, ok := f.get(token) - if !ok { - return SomeNode{}, sum, ErrTypeError{fmt.Sprintf("did not found token %s in current scope", token)} - } - - result, err := compiler(arguments...) - return result, sum, err -} - -func (c compiler) compileScopeNode(f *frame) (SomeNode, int, error) { - var sum int - token, n, err := c.readToken() - sum += n - if err != nil { - return SomeNode{}, sum, err - } - - scope, n, err := c.compileNextNodeWithoutTermination(f) - sum += n - if err != nil { - return SomeNode{}, sum, err - } - scopeObject, ok := scope.Node.(Scope) - if !ok { - return SomeNode{}, sum, ErrTypeError{fmt.Sprintf("expected Scope type node; got %s", PrettyNodeType(scope.Node))} - } - - boundToken := string(token) - - scopeMap, err := scopeObject.Scope(boundToken) - if err != nil { - return SomeNode{}, sum, err - } - - nextFrame := &frame{ - prefix: boundToken, - scope: scopeMap, - - next: f, - } - - result, n, err := c.compileNextNodeWithoutTermination(nextFrame) - sum += n - if err != nil { - return SomeNode{}, sum, err - } - - // checks that nothings follows after the scope - afterNode, end, n, err := c.compileNextNode(nextFrame) - sum += n - if err != nil { - return SomeNode{}, sum, err - } - - switch end { - case ']': - break - case 0: - ast, err := UncompileNode(afterNode.Node) - if err != nil { - // can't Serialize the node, don't include node in error message - return SomeNode{}, sum, ErrSyntaxError{fmt.Sprintf("unexpected node following value node inside scope node %q", ast.String())} - } - return SomeNode{}, sum, ErrSyntaxError{"unexpected node following value node inside scope node"} - default: - return SomeNode{}, sum, ErrSyntaxError{fmt.Sprintf("incorrect terminator for a scope node %q", string(end))} - } - - return result, sum, nil -} - -func (c compiler) skipComment() (int, error) { - var sum int - for { - r, n, err := c.rr.ReadRune() - sum += n - if err != nil { - return sum, err - } - - if r != '}' { - continue - } - - return sum, nil - } -} - -func (c compiler) readToken() ([]rune, int, error) { - var sum int - var token []rune - for { - r, n, err := c.rr.ReadRune() - sum += n - if err != nil { - return nil, sum, err - } - - switch r { - case '{': - n, err := c.skipComment() - sum += n - if err != nil { - return nil, sum, err - } - if len(token) == 0 { - continue - } - return token, sum, nil - case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '-', '.': - token = append(token, r) - continue - default: - // token break - err := c.rr.UnreadRune() - if err != nil { - return nil, sum, err - } - sum -= n - - return token, sum, nil - } - } -} - -// SomeNode is any ipsl expression, this is used for reflection within the compiler. -type SomeNode struct { - Node Node -} - -type AstNode struct { - Type SyntaxType - Args []AstNode - // Literals is set for literals types and tokens (to string) - // Literals must always be either a cid, a string, or a number (uint64) - Literal any -} - -func (a AstNode) String() string { - switch a.Type { - case SyntaxTypeToken: - return a.Literal.(string) - case SyntaxTypeValueNode: - r := make([]string, len(a.Args)) - for i, v := range a.Args { - r[i] = v.String() - } - return "(" + strings.Join(r, " ") + ")" - case SyntaxTypeScopeNode: - r := make([]string, len(a.Args)) - for i, v := range a.Args { - r[i] = v.String() - } - return "[" + strings.Join(r, " ") + "]" - case SyntaxTypeStringLiteral: - return strconv.Quote(a.Literal.(string)) - case SyntaxTypeCidLiteral: - return "$" + a.Literal.(cid.Cid).String() - case SyntaxTypeNumberLiteral: - return strconv.FormatUint(a.Literal.(uint64), 10) - case SyntaxTypeNone: - return "°" - default: - return a.Type.String() - } -} - -// UniqScopes is gonna Unique the scopes objects, trying to reuse the same backing array. -func UniqScopes(scopes []BoundScope) []BoundScope { - // Reuse the same backing array, that fine because the right most copies we can do is noops. - // Else scopes's indexes will shift when we find duplicates. - r := scopes[:0] -argLoop: - for _, s := range scopes { - // Insertion code because scopes are not certain to be comparable. - // We expect to have extremely few unique scopes anyway. - for _, v := range r { - if !s.Eq(v) { - continue - } - - continue argLoop // already in r - } - r = append(r, s) // new scope - } - - return r -} - -// uncompile return an AstNode that includes required scopping to be correct. -func uncompile(n Node, serialize func(Node) (AstNode, []BoundScope, error)) (AstNode, error) { - ast, scopes, err := serialize(n) - if err != nil { - return AstNode{}, err - } - - scopes = UniqScopes(scopes) - - // ensure bound names are unique - names := make(map[string]BoundScope, len(scopes)) - for _, s := range scopes { - b := s.Bound() - other, ok := names[b] - if ok { - return AstNode{}, fmt.Errorf("duplicated bound scope name %q for %q and %q", b, s, other) - } - names[b] = s - } - - for _, s := range scopes { - scopeAst, rscopes, err := serialize(s) - if err != nil { - return AstNode{}, err - } - if len(rscopes) != 0 { - return AstNode{}, errors.New("uncompile does not support scopped scopes") - } - - ast = AstNode{ - Type: SyntaxTypeScopeNode, - Args: []AstNode{ - { - Type: SyntaxTypeToken, - Literal: s.Bound(), - }, - scopeAst, - ast, - }, - } - } - - return ast, nil -} - -// UncompileNode return an AstNode adding required scopping nodes to be correct -func UncompileNode(n Node) (AstNode, error) { - return uncompile(n, func(n Node) (AstNode, []BoundScope, error) { - return n.Serialize() - }) -} - -// UncompileNodeForNetwork is like UncompileNode but it call SerializeForNetwork. -func UncompileNodeForNetwork(n Node) (AstNode, error) { - return uncompile(n, func(n Node) (AstNode, []BoundScope, error) { - return n.SerializeForNetwork() - }) -} - -type SyntaxType uint8 - -const ( - SyntaxTypeUnkown SyntaxType = iota - SyntaxTypeToken - SyntaxTypeValueNode - SyntaxTypeScopeNode - SyntaxTypeStringLiteral - SyntaxTypeCidLiteral - SyntaxTypeNumberLiteral - SyntaxTypeNone -) - -func (st SyntaxType) String() string { - switch st { - case SyntaxTypeToken: - return "Token" - case SyntaxTypeValueNode: - return "Value Node" - case SyntaxTypeScopeNode: - return "Scope Node" - case SyntaxTypeStringLiteral: - return "String Literal" - case SyntaxTypeCidLiteral: - return "Cid Literal" - case SyntaxTypeNumberLiteral: - return "Number Literal" - case SyntaxTypeNone: - return "None" - default: - return "Unknown syntax type of " + strconv.FormatUint(uint64(st), 10) - } -} diff --git a/ipsl/helpers/helpers_test.go b/ipsl/helpers/helpers_test.go index f8eea0cfee..99e3e8c789 100644 --- a/ipsl/helpers/helpers_test.go +++ b/ipsl/helpers/helpers_test.go @@ -21,13 +21,6 @@ type mockTraversal struct { results []ipsl.CidTraversalPair } -func (mockTraversal) Serialize() (ipsl.AstNode, []ipsl.BoundScope, error) { - panic("MOCK!") -} -func (n mockTraversal) SerializeForNetwork() (ipsl.AstNode, []ipsl.BoundScope, error) { - return n.Serialize() -} - func (n mockTraversal) Traverse(b blocks.Block) ([]ipsl.CidTraversalPair, error) { var bad bool if data := b.RawData(); !bytes.Equal(data, n.expectedData) { diff --git a/ipsl/ipsl.go b/ipsl/ipsl.go index 34eecce932..b9311e0c9f 100644 --- a/ipsl/ipsl.go +++ b/ipsl/ipsl.go @@ -1,8 +1,6 @@ package ipsl import ( - "fmt" - "github.com/ipfs/go-cid" "github.com/ipfs/go-libipfs/blocks" ) @@ -13,42 +11,11 @@ type CidTraversalPair struct { } type Traversal interface { - Node - // Traverse must never be called with bytes not matching the cid. // The bytes must never be modified by the implementations. Traverse(blocks.Block) ([]CidTraversalPair, error) } -type Node interface { - // Serialize returns the real representation of what is being executed. - // The []Scope must return the self Scope and the Scopes of all children nodes. - Serialize() (AstNode, []BoundScope, error) - - // SerializeForNetwork returns a representation that is very likely to make an other node produce the same traversal as the current one. - // The main point of this is for builtin polyfills, for example a builtin node can choose to make itself a pick node that select between the builtin or some wasm module. - // The Scope must return the self Scope and the Scopes of all children nodes. - SerializeForNetwork() (AstNode, []BoundScope, error) -} - -type Scope interface { - Node - - // GetScope returns a scope mapping, consumers are not allowed to modify the ScopeMapping. - Scope(bound string) (ScopeMapping, error) - - // Eq must return true if the two scopes are the same. - // Eq must not take name binding in consideration. - Eq(Scope) bool -} - -// BoundScope represent a Scope that have been named. -type BoundScope interface { - Scope - - Bound() string -} - // An AllNode traverse all the traversals with the same cid it is given to. type AllNode struct { Traversals []Traversal @@ -58,20 +25,6 @@ func All(traversals ...Traversal) Traversal { return AllNode{traversals} } -func compileAll(arguments ...SomeNode) (SomeNode, error) { - traversals := make([]Traversal, len(arguments)) - for i, c := range arguments { - trav, ok := c.Node.(Traversal) - if !ok { - return SomeNode{}, ErrTypeError{fmt.Sprintf("trying to build an all node but received wrong type as %T argument", PrettyNodeType(c.Node))} - } - - traversals[i] = trav - } - - return SomeNode{All(traversals...)}, nil -} - func (n AllNode) Traverse(b blocks.Block) ([]CidTraversalPair, error) { var results []CidTraversalPair for _, t := range n.Traversals { @@ -84,91 +37,6 @@ func (n AllNode) Traverse(b blocks.Block) ([]CidTraversalPair, error) { return results, nil } -func (n AllNode) serialize(serialize func(n Node) (AstNode, []BoundScope, error)) (AstNode, []BoundScope, error) { - args := make([]AstNode, len(n.Traversals)+1) - args[0] = AstNode{ - Type: SyntaxTypeToken, - Literal: "all", - } - - var scopes []BoundScope - argsTraversals := args[1:] - for i, child := range n.Traversals { - var err error - var lscopes []BoundScope - argsTraversals[i], lscopes, err = serialize(child) - scopes = UniqScopes(append(scopes, lscopes...)) - if err != nil { - return AstNode{}, nil, err - } - } - - return AstNode{ - Type: SyntaxTypeValueNode, - Args: args, - }, scopes, nil -} - -func (n AllNode) Serialize() (AstNode, []BoundScope, error) { - return n.serialize(func(n Node) (AstNode, []BoundScope, error) { - return n.Serialize() - }) -} - -func (n AllNode) SerializeForNetwork() (AstNode, []BoundScope, error) { - return n.serialize(func(n Node) (AstNode, []BoundScope, error) { - return n.SerializeForNetwork() - }) -} - -var _ Node = CidLiteral{} - -type CidLiteral struct { - Cid cid.Cid -} - -func (c CidLiteral) Serialize() (AstNode, []BoundScope, error) { - return AstNode{ - Type: SyntaxTypeCidLiteral, - Literal: c.Cid, - }, []BoundScope{}, nil -} - -func (c CidLiteral) SerializeForNetwork() (AstNode, []BoundScope, error) { - return c.Serialize() -} - -var _ Node = StringLiteral{} - -type StringLiteral struct { - Str string -} - -func (c StringLiteral) Serialize() (AstNode, []BoundScope, error) { - return AstNode{ - Type: SyntaxTypeStringLiteral, - Literal: c.Str, - }, []BoundScope{}, nil -} - -func (c StringLiteral) SerializeForNetwork() (AstNode, []BoundScope, error) { - return c.Serialize() -} - -var _ Node = None{} - -type None struct{} - -func (c None) Serialize() (AstNode, []BoundScope, error) { - return AstNode{ - Type: SyntaxTypeNone, - }, []BoundScope{}, nil -} - -func (c None) SerializeForNetwork() (AstNode, []BoundScope, error) { - return c.Serialize() -} - // EmptyTraversal is a traversal that always returns nothing type EmptyTraversal struct{} @@ -179,42 +47,3 @@ func Empty() Traversal { func (c EmptyTraversal) Traverse(_ blocks.Block) ([]CidTraversalPair, error) { return []CidTraversalPair{}, nil } - -func (c EmptyTraversal) Serialize() (AstNode, []BoundScope, error) { - return AstNode{ - Type: SyntaxTypeValueNode, - Args: []AstNode{{ - Type: SyntaxTypeToken, - Literal: "empty", - }}, - }, []BoundScope{}, nil -} - -func (c EmptyTraversal) SerializeForNetwork() (AstNode, []BoundScope, error) { - return c.Serialize() -} - -func compileEmpty(arguments ...SomeNode) (SomeNode, error) { - if len(arguments) != 0 { - return SomeNode{}, ErrTypeError{fmt.Sprintf("empty node called with %d arguments, empty does not take arguments", len(arguments))} - } - - return SomeNode{Empty()}, nil -} - -func PrettyNodeType(n Node) string { - switch n.(type) { - case Traversal: - return "Traversal" - case Scope: - return "Scope" - case CidLiteral: - return "Cid" - case StringLiteral: - return "String" - case None: - return "None" - default: - return fmt.Sprintf("unknown node type %T", n) - } -} diff --git a/ipsl/ipsl_test.go b/ipsl/ipsl_test.go deleted file mode 100644 index e8f9799e7d..0000000000 --- a/ipsl/ipsl_test.go +++ /dev/null @@ -1,281 +0,0 @@ -package ipsl_test - -import ( - "errors" - "fmt" - "io" - "strings" - "testing" - - "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/blocks" - . "github.com/ipfs/go-libipfs/ipsl" -) - -func reflect(expectedScope BoundScope) NodeCompiler { - return func(nodes ...SomeNode) (SomeNode, error) { - if len(nodes) != 1 { - return SomeNode{}, fmt.Errorf("got arguments list that is not one while compiling reflect: %#v", nodes) - } - - return nodes[0], nil - } -} - -func TestBasicCompileWithBuiltin(t *testing.T) { - var c Compiler - c.SetBuiltin("reflect", reflect(nil)) - - const code = `(reflect $bafkqaaa)` - node, n, err := c.Compile(strings.NewReader(code)) - if err != nil { - t.Fatalf("failed to compile: %s", err.Error()) - } - if n != len(code) { - t.Errorf("bytes red does not match code size") - } - - cidlit, ok := node.Node.(CidLiteral) - if !ok { - t.Fatalf("type does not match, expected Cid; got: %s", PrettyNodeType(node.Node)) - } - expected := cid.MustParse("bafkqaaa") - if !cidlit.Cid.Equals(expected) { - t.Errorf("cid does not match, expected: %s; got %s", expected, cidlit.Cid) - } -} - -func TestBasic2CompileWithBuiltin(t *testing.T) { - var c Compiler - c.SetBuiltin("reflect", reflect(nil)) - - const code = `(reflect{some comment to test ([)] comments}(reflect $bafkqaaa))` - node, n, err := c.Compile(strings.NewReader(code)) - if err != nil { - t.Fatalf("failed to compile: %s", err.Error()) - } - if n != len(code) { - t.Errorf("bytes red does not match code size") - } - - cidlit, ok := node.Node.(CidLiteral) - if !ok { - t.Fatalf("type does not match, expected Cid; got: %s", PrettyNodeType(node.Node)) - } - expected := cid.MustParse("bafkqaaa") - if !cidlit.Cid.Equals(expected) { - t.Errorf("cid does not match, expected: %s; got %s", expected, cidlit.Cid) - } -} - -type mockScopeNode struct { - scope map[string]func(BoundScope) NodeCompiler -} - -func (n *mockScopeNode) Serialize() (AstNode, []BoundScope, error) { panic("MOCK!") } -func (n *mockScopeNode) SerializeForNetwork() (AstNode, []BoundScope, error) { return n.Serialize() } - -func (n *mockScopeNode) Eq(s Scope) bool { - return n == s -} -func (n *mockScopeNode) Scope(bound string) (ScopeMapping, error) { - b := boundMockScopeNode{n, bound} - r := make(ScopeMapping, len(n.scope)) - for k, v := range n.scope { - r[k] = v(b) - } - return r, nil -} - -type boundMockScopeNode struct { - *mockScopeNode - - name string -} - -func (n boundMockScopeNode) Bound() string { - return n.name -} - -func TestScopeCompileWithBuiltin(t *testing.T) { - var c Compiler - c.SetBuiltin("load-test-scope", func(nodes ...SomeNode) (SomeNode, error) { - if len(nodes) != 0 { - return SomeNode{}, fmt.Errorf("got arguments list that is not empty: %#v", nodes) - } - - return SomeNode{ - Node: &mockScopeNode{map[string]func(BoundScope) NodeCompiler{ - "reflect": reflect, - "reflect.cursed.name": reflect, - }}, - }, nil - }) - - const code = `[test-scope (load-test-scope) (test-scope.reflect (test-scope.reflect.cursed.name $bafkqaaa))]` - node, n, err := c.Compile(strings.NewReader(code)) - if err != nil { - t.Fatalf("failed to compile: %s", err.Error()) - } - if n != len(code) { - t.Errorf("bytes red does not match code size") - } - - cidlit, ok := node.Node.(CidLiteral) - if !ok { - t.Fatalf("type does not match, expected Cid; got: %s", PrettyNodeType(node.Node)) - } - expected := cid.MustParse("bafkqaaa") - if !cidlit.Cid.Equals(expected) { - t.Errorf("cid does not match, expected: %s; got %s", expected, cidlit.Cid) - } -} - -func TestEmpty(t *testing.T) { - const code = `(empty)` - trav, n, err := (&Compiler{}).CompileToTraversal(strings.NewReader(code)) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if n != len(code) { - t.Errorf("unexpected code length returned: expected %d; got %d", len(code), n) - } - - ast, err := UncompileNode(trav) - if err != nil { - t.Errorf("unexpected error: %s", err.Error()) - } else { - rebuiltCode := ast.String() - if rebuiltCode != code { - t.Errorf("serialized code does not match: expected %q, got %q", code, rebuiltCode) - } - } - ast, err = UncompileNodeForNetwork(trav) - if err != nil { - t.Errorf("unexpected error: %s", err.Error()) - } else { - rebuiltCode := ast.String() - if rebuiltCode != code { - t.Errorf("serialized code does not match: expected %q, got %q", code, rebuiltCode) - } - } - - cids, err := trav.Traverse(blocks.NewBlock([]byte("some bytes"))) - if err != nil { - t.Fatalf("unexpected error: %s", err.Error()) - } - if len(cids) != 0 { - t.Errorf("unexpected traversal results matching empty") - } -} - -func TestUnexpectedEOFInBrokenNode(t *testing.T) { - _, _, err := (&Compiler{}).Compile(strings.NewReader(`(empty {this is some unterminated node}`)) - if err == nil { - t.Fatal("unexpected incorrect code does not return an error") - } - if !errors.Is(err, io.ErrUnexpectedEOF) { - t.Errorf("unexpected error: expected %q; got %q", io.ErrUnexpectedEOF, err) - } -} - -func TestStringLiterals(t *testing.T) { - var c Compiler - c.SetBuiltin("reflect", reflect(nil)) - - for _, tc := range [...]struct { - code string - result string - err error - }{ - {`"this is a string node"`, "this is a string node", nil}, - {`"this is a string \" with an escaped quote"`, "this is a string \" with an escaped quote", nil}, - {`"this is a string with an escaped backslash \\"`, "this is a string with an escaped backslash \\", nil}, - {`"this is an unterminated string`, "", io.ErrUnexpectedEOF}, - {`"this is an unterminated string because it escape the termination \"`, "", io.ErrUnexpectedEOF}, - {`(reflect "this is a string node")`, "this is a string node", nil}, - {`(reflect{comment in the middle}"this is a string \" with an escaped quote")`, "this is a string \" with an escaped quote", nil}, - {`(reflect "this is a string with an escaped backslash \\"{comment right after})`, "this is a string with an escaped backslash \\", nil}, - {`(reflect "this is an unterminated string`, "", io.ErrUnexpectedEOF}, - {`(reflect "this is an unterminated string because it escape the termination \"`, "", io.ErrUnexpectedEOF}, - } { - node, sum, err := c.Compile(strings.NewReader(tc.code)) - if !errors.Is(err, tc.err) { - t.Errorf("wrong error for input %q: expected %q; got %q", tc.code, tc.err, err) - } - if sum != len(tc.code) { - t.Errorf("wrong sum for input %q: expected %q; got %q", tc.code, len(tc.code), sum) - } - if tc.err != nil { - continue - } - - strNode, ok := node.Node.(StringLiteral) - if !ok { - t.Errorf("wrong node type for input %q: expected %T; got %T", tc.code, StringLiteral{}, node.Node) - continue - } - - if strNode.Str != tc.result { - t.Errorf("wrong result for input %q: expected %q; got %q", tc.code, tc.result, strNode.Str) - } - } -} - -func TestNone(t *testing.T) { - var c Compiler - c.SetBuiltin("reflect", reflect(nil)) - - for _, tc := range [...]string{ - `°`, - `(reflect °)`, - `(reflect (reflect °))`, - // TODO: add tests for pick - } { - node, sum, err := c.Compile(strings.NewReader(tc)) - if sum != len(tc) { - t.Errorf("wrong sum for input %q: expected %q; got %q", tc, len(tc), sum) - } - if err != nil { - t.Errorf("unexpected error for input %q: got %q", tc, err) - } - if node.Node != (None{}) { - t.Errorf("unexpected node for input %q: got %q", tc, node.Node) - } - } -} - -func TestLoadingCustomBuiltinScopes(t *testing.T) { - var c Compiler - c.SetBuiltinScope("/mock/scope", &mockScopeNode{map[string]func(BoundScope) NodeCompiler{ - "reflect": reflect, - }}) - - const code = `[test-scope (load-builtin-scope "/mock/scope") (test-scope.reflect $bafkqaaa)]` - node, n, err := c.Compile(strings.NewReader(code)) - if err != nil { - t.Fatalf("failed to compile: %s", err.Error()) - } - if n != len(code) { - t.Errorf("bytes red does not match code size") - } - - cidlit, ok := node.Node.(CidLiteral) - if !ok { - t.Fatalf("type does not match, expected Cid; got: %s", PrettyNodeType(node.Node)) - } - expected := cid.MustParse("bafkqaaa") - if !cidlit.Cid.Equals(expected) { - t.Errorf("cid does not match, expected: %s; got %s", expected, cidlit.Cid) - } -} - -func FuzzCompile(f *testing.F) { - var c Compiler - c.SetBuiltin("reflect", reflect(nil)) - - f.Add(`(reflect (reflect $bafkqaaa))`) - f.Fuzz(func(_ *testing.T, code string) { - c.Compile(strings.NewReader(code)) - }) -} diff --git a/ipsl/scope.go b/ipsl/scope.go deleted file mode 100644 index 260eebbcae..0000000000 --- a/ipsl/scope.go +++ /dev/null @@ -1,44 +0,0 @@ -package ipsl - -import "strings" - -type frame struct { - scope ScopeMapping - prefix string - - next *frame -} - -func (f *frame) get(s string) (NodeCompiler, bool) { - names := strings.SplitN(s, ".", 2) - first := names[0] - var second string - if len(names) > 1 { - second = names[1] - } else { - second = first - first = "" - } - if f.prefix != first { - goto Unmatch - } - { - r, ok := f.scope[second] - if ok { - return r, true - } - } - -Unmatch: - if f.next == nil { - return nil, false - } - - return f.next.get(s) -} - -// NodeCompiler is responsible for compiling arguments to a node. -// scope will be nil if this is called for the builtin scope. -type NodeCompiler func(arguments ...SomeNode) (SomeNode, error) - -type ScopeMapping map[string]NodeCompiler diff --git a/ipsl/unixfs/scope.go b/ipsl/unixfs/scope.go deleted file mode 100644 index 2ecb3e2f0c..0000000000 --- a/ipsl/unixfs/scope.go +++ /dev/null @@ -1,53 +0,0 @@ -package unixfs - -import ( - "github.com/ipfs/go-libipfs/ipsl" -) - -// Name is the builtin scope name for unixfs. -const Name = "/unixfs/v0.0.1" - -var _ ipsl.Scope = scope{} - -// scope provides the unixfs scope -type scope struct{} - -type boundScope struct { - scope - - boundName string -} - -func (s boundScope) Bound() string { - return s.boundName -} - -func Scope() ipsl.Scope { - return scope{} -} - -func (s scope) Eq(other ipsl.Scope) bool { - return s == other -} - -func (s scope) Scope(bound string) (ipsl.ScopeMapping, error) { - b := boundScope{s, bound} - return ipsl.ScopeMapping{ - "everything": compileEverything(b), - }, nil -} - -func (scope) Serialize() (ipsl.AstNode, []ipsl.BoundScope, error) { - return ipsl.AstNode{ - Type: ipsl.SyntaxTypeValueNode, - Args: []ipsl.AstNode{ - {Type: ipsl.SyntaxTypeToken, Literal: "load-builtin-scope"}, - {Type: ipsl.SyntaxTypeStringLiteral, Literal: string(Name)}, - }, - }, []ipsl.BoundScope{}, nil -} - -func (s scope) SerializeForNetwork() (ipsl.AstNode, []ipsl.BoundScope, error) { - // TODO: return a (pick (load-builtin-scope ...) (load-builtin-wasm ...)) when we have wasm support. - return s.Serialize() -} diff --git a/ipsl/unixfs/scope_test.go b/ipsl/unixfs/scope_test.go deleted file mode 100644 index d215825405..0000000000 --- a/ipsl/unixfs/scope_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package unixfs_test - -import ( - "context" - "strconv" - "strings" - "testing" - - "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/blocks" - "github.com/ipfs/go-libipfs/ipsl" - "github.com/ipfs/go-libipfs/ipsl/helpers" - . "github.com/ipfs/go-libipfs/ipsl/unixfs" - "golang.org/x/exp/slices" -) - -func TestEverythingThroughScope(t *testing.T) { - var c ipsl.Compiler - c.SetBuiltinScope(Name, Scope()) - - var code = `[unixfs (load-builtin-scope ` + strconv.Quote(string(Name)) + `) (unixfs.everything)]` - - traversal, n, err := c.CompileToTraversal(strings.NewReader(code)) - if err != nil { - t.Fatalf("CompileToTraversal: %s", err) - } - if n != len(code) { - t.Errorf("wrong code length, expected %d; got %d", len(code), n) - } - - bs, expectedOrder := getSmallTreeDatastore(t) - root := expectedOrder[0] - var result []cid.Cid - err = helpers.SyncDFS(context.Background(), root, traversal, bs, 10, func(b blocks.Block) error { - c := b.Cid() - hashedData, err := c.Prefix().Sum(b.RawData()) - if err != nil { - t.Errorf("error hashing data in callBack: %s", err) - } else { - if !hashedData.Equals(c) { - t.Errorf("got wrong bytes in callBack: cid %s; hashedBytes %s", c, hashedData) - } - } - - result = append(result, c) - - return nil - }) - if err != nil { - t.Fatalf("SyncDFS: %s", err) - } - - if !slices.Equal(result, expectedOrder) { - t.Errorf("bad traversal order: expected: %v; got %v", expectedOrder, result) - } - - ast, err := ipsl.UncompileNode(traversal) - if err != nil { - t.Errorf("Uncompile: %s", err) - } - - serialized := ast.String() - if serialized != code { - t.Errorf("seriliazed code does not match original code: expected %q; got %q", code, serialized) - } -} diff --git a/ipsl/unixfs/unixfs.go b/ipsl/unixfs/unixfs.go index 0709c0a2da..b543b1b57c 100644 --- a/ipsl/unixfs/unixfs.go +++ b/ipsl/unixfs/unixfs.go @@ -14,36 +14,10 @@ import ( // Everything is a Traversal that will match all the unixfs childs blocks, forever. func Everything() ipsl.Traversal { - return EverythingNode{boundScope{scope{}, "unixfs"}} + return EverythingNode{} } -func compileEverything(s boundScope) ipsl.NodeCompiler { - return func(arguments ...ipsl.SomeNode) (ipsl.SomeNode, error) { - if len(arguments) != 0 { - return ipsl.SomeNode{}, ipsl.ErrTypeError{Msg: fmt.Sprintf("empty node called with %d arguments, empty does not take arguments", len(arguments))} - } - - return ipsl.SomeNode{Node: EverythingNode{s}}, nil - } -} - -type EverythingNode struct { - scope boundScope -} - -func (n EverythingNode) Serialize() (ipsl.AstNode, []ipsl.BoundScope, error) { - return ipsl.AstNode{ - Type: ipsl.SyntaxTypeValueNode, - Args: []ipsl.AstNode{{ - Type: ipsl.SyntaxTypeToken, - Literal: n.scope.Bound() + ".everything", - }}, - }, []ipsl.BoundScope{n.scope}, nil -} - -func (n EverythingNode) SerializeForNetwork() (ipsl.AstNode, []ipsl.BoundScope, error) { - return n.Serialize() -} +type EverythingNode struct{} func (n EverythingNode) Traverse(b blocks.Block) ([]ipsl.CidTraversalPair, error) { switch codec := b.Cid().Prefix().Codec; codec { diff --git a/rapide/serverdriven_test.go b/rapide/serverdriven_test.go index 8aa8e4dcb7..870d3e66ef 100644 --- a/rapide/serverdriven_test.go +++ b/rapide/serverdriven_test.go @@ -65,14 +65,6 @@ func (bs *mockBlockstore) Traverse(b blocks.Block) ([]ipsl.CidTraversalPair, err return childrens, nil } -func (*mockBlockstore) Serialize() (ipsl.AstNode, []ipsl.BoundScope, error) { - panic("MOCK!") -} - -func (*mockBlockstore) SerializeForNetwork() (ipsl.AstNode, []ipsl.BoundScope, error) { - panic("MOCK!") -} - func (bs *mockBlockstore) Download(ctx context.Context, root cid.Cid, traversal ipsl.Traversal) (ClosableBlockIterator, error) { ctx, cancel := context.WithCancel(ctx) r := make(chan blocks.BlockOrError)