Skip to content

Commit

Permalink
ipsl: add support for load-builtin-scope
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorropo committed Jan 27, 2023
1 parent 7969ec1 commit 523edb1
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 35 deletions.
2 changes: 1 addition & 1 deletion ipsl/builtins.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ipsl

var defaultBuiltinFrame = frame{scope: map[string]NodeCompiler{
var defaultBuiltinFrame = frame{scope: ScopeMapping{
"all": CompileAll,
"empty": CompileEmpty,
}}
60 changes: 42 additions & 18 deletions ipsl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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()
}

Expand Down
2 changes: 1 addition & 1 deletion ipsl/ipsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 30 additions & 14 deletions ipsl/ipsl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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"),
}},
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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(""))
Expand Down
4 changes: 3 additions & 1 deletion ipsl/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package ipsl
import "strings"

type frame struct {
scope map[string]NodeCompiler
scope ScopeMapping
prefix string

next *frame
Expand Down Expand Up @@ -38,3 +38,5 @@ Unmatch:
}

type NodeCompiler func(scopeName string, arguments ...SomeNode) (SomeNode, error)

type ScopeMapping map[string]NodeCompiler

0 comments on commit 523edb1

Please sign in to comment.