Skip to content

Commit

Permalink
feat(logic): implement support for user output
Browse files Browse the repository at this point in the history
  • Loading branch information
ccamel committed Apr 5, 2023
1 parent 143dc15 commit 0a65522
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 82 deletions.
71 changes: 50 additions & 21 deletions x/logic/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,72 @@ package interpreter
import (
goctx "context"
"fmt"
"io"
"io/fs"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
)

// Predicates is a map of predicate names to their execution costs.
type Predicates map[string]uint64

// New creates a new prolog.Interpreter with:
// - a list of predefined predicates (with their execution costs).
// - a compiled bootstrap script, that can be used to perform setup tasks.
// - a meter to track gas consumption.
// - a file system to load external files.
//
// Option is a function that configures an Interpreter.
type Option func(*prolog.Interpreter) error

// WithPredicates configures the interpreter to register the specified predicates.
// The predicates names must be present in the registry, otherwise the function will return an error.
// The bootstrap script can be an empty string if no bootstrap script is needed. If compilation of the bootstrap script
// fails, the function will return an error.
func WithPredicates(_ goctx.Context, predicates Predicates, meter sdk.GasMeter) Option {
return func(i *prolog.Interpreter) error {
for predicate, cost := range predicates {
if err := Register(i, predicate, cost, meter); err != nil {
return fmt.Errorf("error registering predicate '%s': %w", predicate, err)
}
}
return nil
}
}

// WithBootstrap configures the interpreter to compile the specified bootstrap script to serve as setup context.
// If compilation of the bootstrap script fails, the function will return an error.
func WithBootstrap(ctx goctx.Context, bootstrap string) Option {
return func(i *prolog.Interpreter) error {
if err := i.Compile(ctx, bootstrap); err != nil {
return fmt.Errorf("error compiling bootstrap script: %w", err)
}
return nil
}
}

// WithUserOutputWriter configures the interpreter to use the specified writer for user output.
func WithUserOutputWriter(w io.Writer) Option {
return func(i *prolog.Interpreter) error {
i.SetUserOutput(engine.NewOutputTextStream(w))

return nil
}
}

// WithFS configures the interpreter to use the specified file system.
func WithFS(fs fs.FS) Option {
return func(i *prolog.Interpreter) error {
i.FS = fs
return nil
}
}

// New creates a new prolog.Interpreter with the specified options.
func New(
ctx goctx.Context,
predicates Predicates,
bootstrap string,
meter sdk.GasMeter,
fs fs.FS,
opts ...Option,
) (*prolog.Interpreter, error) {
var i prolog.Interpreter
i.FS = fs

for predicate, cost := range predicates {
if err := Register(&i, predicate, cost, meter); err != nil {
return nil, fmt.Errorf("error registering predicate '%s': %w", predicate, err)
for _, opt := range opts {
if err := opt(&i); err != nil {
return nil, err
}
}

if err := i.Compile(ctx, bootstrap); err != nil {
return nil, fmt.Errorf("error compiling bootstrap script: %w", err)
}

return &i, nil
}
33 changes: 23 additions & 10 deletions x/logic/keeper/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryS
ctx = k.enhanceContext(ctx)
sdkCtx := sdk.UnwrapSDKContext(ctx)

i, err := k.newInterpreter(ctx)
i, userOutputBuffer, err := k.newInterpreter(ctx)
if err != nil {
return nil, sdkerrors.Wrapf(types.Internal, "error creating interpreter: %v", err.Error())
}
Expand Down Expand Up @@ -77,6 +77,11 @@ func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryS
panic(sdk.ErrorOutOfGas{Descriptor: "Prolog interpreter execution"})
}

var userOutput string
if userOutputBuffer != nil {
userOutput = userOutputBuffer.String()
}

return &types.QueryServiceAskResponse{
Height: uint64(sdkCtx.BlockHeight()),
GasUsed: sdkCtx.GasMeter().GasConsumed(),
Expand All @@ -86,6 +91,7 @@ func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryS
Variables: variables,
Results: results,
},
UserOutput: userOutput,
}, nil
}

Expand All @@ -100,12 +106,13 @@ func checkLimits(request *types.QueryServiceAskRequest, limits types.Limits) err
}

// newInterpreter creates a new interpreter properly configured.
func (k Keeper) newInterpreter(ctx goctx.Context) (*prolog.Interpreter, error) {
func (k Keeper) newInterpreter(ctx goctx.Context) (*prolog.Interpreter, *util.BoundedBuffer, error) {
sdkctx := sdk.UnwrapSDKContext(ctx)
params := k.GetParams(sdkctx)

interpreterParams := params.GetInterpreter()
gasPolicy := params.GetGasPolicy()
limits := params.GetLimits()

whitelist := util.NonZeroOrDefault(interpreterParams.PredicatesWhitelist, interpreter.RegistryNames)
blacklist := interpreterParams.GetPredicatesBlacklist()
Expand All @@ -125,15 +132,21 @@ func (k Keeper) newInterpreter(ctx goctx.Context) (*prolog.Interpreter, error) {
},
interpreter.Predicates{})

interpreted, err := interpreter.New(
ctx,
predicates,
util.NonZeroOrDefault(interpreterParams.GetBootstrap(), bootstrap.Bootstrap()),
gasMeter,
k.fsProvider(ctx),
)
options := []interpreter.Option{
interpreter.WithPredicates(ctx, predicates, gasMeter),
interpreter.WithBootstrap(ctx, util.NonZeroOrDefault(interpreterParams.GetBootstrap(), bootstrap.Bootstrap())),
interpreter.WithFS(k.fsProvider(ctx)),
}

var userOutputBuffer *util.BoundedBuffer
if limits.MaxUserOutputSize != nil && limits.MaxUserOutputSize.GT(sdkmath.ZeroUint()) {
userOutputBuffer = util.NewBoundedBufferMust(int(limits.MaxUserOutputSize.Uint64()))
options = append(options, interpreter.WithUserOutputWriter(userOutputBuffer))
}

i, err := interpreter.New(options...)

return interpreted, err
return i, userOutputBuffer, err
}

// filterPredicates filters the given predicate (with arity) according to the given whitelist and blacklist.
Expand Down
88 changes: 45 additions & 43 deletions x/logic/types/params.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 4 additions & 8 deletions x/logic/util/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ import (
"io"
)

var (
_ = io.Writer(&BoundedBuffer{})
)
var _ = io.Writer(&BoundedBuffer{})

var (
ErrInvalidSize = func(size int) error {
return fmt.Errorf("invalid buffer size %d", size)
}
)
var ErrInvalidSize = func(size int) error {
return fmt.Errorf("invalid buffer size %d", size)
}

// BoundedBuffer is a fixed size buffer that overwrites older data when the buffer is full.
type BoundedBuffer struct {
Expand Down

0 comments on commit 0a65522

Please sign in to comment.