From 523edb14c4c2f83819d91f0ba3d0ebc758a9003f Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 27 Jan 2023 21:16:35 +0100 Subject: [PATCH] ipsl: add support for load-builtin-scope --- ipsl/builtins.go | 2 +- ipsl/compile.go | 60 +++++++++++++++++++++++++++++++++-------------- ipsl/ipsl.go | 2 +- ipsl/ipsl_test.go | 44 +++++++++++++++++++++++----------- ipsl/scope.go | 4 +++- 5 files changed, 77 insertions(+), 35 deletions(-) diff --git a/ipsl/builtins.go b/ipsl/builtins.go index 84e0fc4b1..744ff736c 100644 --- a/ipsl/builtins.go +++ b/ipsl/builtins.go @@ -1,6 +1,6 @@ package ipsl -var defaultBuiltinFrame = frame{scope: map[string]NodeCompiler{ +var defaultBuiltinFrame = frame{scope: ScopeMapping{ "all": CompileAll, "empty": CompileEmpty, }} diff --git a/ipsl/compile.go b/ipsl/compile.go index 3f4c01ba4..28a450599 100644 --- a/ipsl/compile.go +++ b/ipsl/compile.go @@ -35,41 +35,65 @@ func (e ErrSyntaxError) Error() string { return e.msg } -// CompileToTraversal returns a Traversal that has been compiled, the number of bytes red and an error. -// It compiles with the default builtin scope. -func CompileToTraversal(rr UnreadableRuneReader) (Traversal, int, error) { - return (&Compiler{}).CompileToTraversal(rr) -} - -// Compile returns some node that has been compiled, the number of bytes red and an error. -// It compiles with the default builtin scope. -func Compile(rr UnreadableRuneReader) (SomeNode, int, error) { - return (&Compiler{}).Compile(rr) -} - // 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 = make(map[string]NodeCompiler) + 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 } -// CompileToTraversal returns a Traversal that has been compiled, the number of bytes red and an error. -// It is thread safe to do multiple compilations at once on the same Compiler. But not with AddBuiltin. +// 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(scopeName string, arguments ...SomeNode) (SomeNode, error) { + if scopeName != "" { + panic(fmt.Sprintf("called with a non empty scope %q", scopeName)) + } + + 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 { @@ -82,8 +106,8 @@ func (c *Compiler) CompileToTraversal(rr UnreadableRuneReader) (Traversal, int, return traversal, n, nil } -// Compile returns some node that has been compiled, the number of bytes red and an error. -// It is thread safe to do multiple compilations at once on the same Compiler. But not with AddBuiltin. +// 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}.compileNextNodeWithoutClosure(&c.builtinFrame) @@ -412,7 +436,7 @@ type scopeScopeNode struct { scopeNode } -func (n scopeScopeNode) GetScope() (map[string]NodeCompiler, error) { +func (n scopeScopeNode) GetScope() (ScopeMapping, error) { return n.scopeNode.result.(Scope).GetScope() } diff --git a/ipsl/ipsl.go b/ipsl/ipsl.go index 2f1e6e79f..b1aa61c00 100644 --- a/ipsl/ipsl.go +++ b/ipsl/ipsl.go @@ -32,7 +32,7 @@ type Node interface { type Scope interface { Node - GetScope() (map[string]NodeCompiler, error) + GetScope() (ScopeMapping, error) } // An AllNode traverse all the traversals with the same cid it is given to. diff --git a/ipsl/ipsl_test.go b/ipsl/ipsl_test.go index d1b7056e2..c07a17d11 100644 --- a/ipsl/ipsl_test.go +++ b/ipsl/ipsl_test.go @@ -73,22 +73,13 @@ func TestBasic2CompileWithBuiltin(t *testing.T) { } type mockScopeNode struct { - scope map[string]NodeCompiler -} - -func (n mockScopeNode) Serialize() (AstNode, error) { - return AstNode{ - Type: SyntaxTypeValueNode, - Args: []AstNode{{ - Type: SyntaxTypeToken, - Literal: "load-test-scope", - }}, - }, nil + scope ScopeMapping } +func (n mockScopeNode) Serialize() (AstNode, error) { panic("MOCK!") } func (n mockScopeNode) SerializeForNetwork() (AstNode, error) { return n.Serialize() } -func (n mockScopeNode) GetScope() (map[string]NodeCompiler, error) { +func (n mockScopeNode) GetScope() (ScopeMapping, error) { return n.scope, nil } @@ -104,7 +95,7 @@ func TestScopeCompileWithBuiltin(t *testing.T) { } return SomeNode{ - Node: mockScopeNode{map[string]NodeCompiler{ + Node: mockScopeNode{ScopeMapping{ "reflect": reflect("test-scope"), "reflect.cursed.name": reflect("test-scope"), }}, @@ -132,7 +123,7 @@ func TestScopeCompileWithBuiltin(t *testing.T) { func TestEmpty(t *testing.T) { const code = `(empty)` - trav, n, err := CompileToTraversal(strings.NewReader(code)) + trav, n, err := (&Compiler{}).CompileToTraversal(strings.NewReader(code)) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } @@ -244,6 +235,31 @@ func TestNone(t *testing.T) { } } +func TestLoadingCustomBuiltinScopes(t *testing.T) { + var c Compiler + c.SetBuiltinScope("/mock/scope", mockScopeNode{ScopeMapping{ + "reflect": reflect("test-scope"), + }}) + + 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("")) diff --git a/ipsl/scope.go b/ipsl/scope.go index 2f4f1a0d6..a525cd493 100644 --- a/ipsl/scope.go +++ b/ipsl/scope.go @@ -3,7 +3,7 @@ package ipsl import "strings" type frame struct { - scope map[string]NodeCompiler + scope ScopeMapping prefix string next *frame @@ -38,3 +38,5 @@ Unmatch: } type NodeCompiler func(scopeName string, arguments ...SomeNode) (SomeNode, error) + +type ScopeMapping map[string]NodeCompiler