Skip to content

Commit

Permalink
refactor(logic)!: adopt unstructured expression for substitutions
Browse files Browse the repository at this point in the history
Adopt expression for the values substitued represented as a prolog term.
Previous format was an incorrect mix of structured/unstructured.
  • Loading branch information
ccamel committed Feb 16, 2024
1 parent cf3a66a commit 50f9d7f
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 430 deletions.
20 changes: 2 additions & 18 deletions proto/logic/v1beta2/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,14 @@ import "gogoproto/gogo.proto";

option go_package = "github.com/okp4/okp4d/x/logic/types";

// Term is the representation of a piece of data and can be a constant, a variable, or an atom.
message Term {
option (gogoproto.goproto_stringer) = true;

// name is the name of the term.
string name = 1 [(gogoproto.moretags) = "yaml:\"name\",omitempty"];
// arguments are the arguments of the term, which can be constants, variables, or atoms.
repeated Term arguments = 2 [
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"arguments\",omitempty"
];
}

// Substitution represents a substitution made to the variables in the query to obtain the answer.
message Substitution {
option (gogoproto.goproto_stringer) = true;

// variable is the name of the variable.
string variable = 1 [(gogoproto.moretags) = "yaml:\"variable\",omitempty"];
// term is the term that the variable is substituted with.
Term term = 2 [
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"term\",omitempty"
];
// expression is the value substituted for the variable, represented directly as a Prolog term (e.g., atom, number, compound).
string expression = 2 [(gogoproto.moretags) = "yaml:\"expression\",omitempty"];
}

// Result represents the result of a query.
Expand Down
126 changes: 75 additions & 51 deletions x/logic/keeper/interpreter.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package keeper

import (
goctx "context"
"context"
"math"
"strings"

"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
"github.com/samber/lo"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"

sdk "github.com/cosmos/cosmos-sdk/types"

Expand All @@ -27,21 +28,22 @@ const (
defaultWeightFactor = uint64(1)
)

func (k Keeper) limits(ctx goctx.Context) types.Limits {
func (k Keeper) limits(ctx context.Context) types.Limits {
params := k.GetParams(sdk.UnwrapSDKContext(ctx))
return params.GetLimits()
}

func (k Keeper) enhanceContext(ctx goctx.Context) goctx.Context {
func (k Keeper) enhanceContext(ctx context.Context) context.Context {
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithValue(types.AuthKeeperContextKey, k.authKeeper)
sdkCtx = sdkCtx.WithValue(types.BankKeeperContextKey, k.bankKeeper)
return sdkCtx
}

func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryServiceAskResponse, error) {
func (k Keeper) execute(ctx context.Context, program, query string) (*types.QueryServiceAskResponse, error) {
ctx = k.enhanceContext(ctx)
sdkCtx := sdk.UnwrapSDKContext(ctx)
limits := k.limits(sdkCtx)

i, userOutputBuffer, err := k.newInterpreter(ctx)
if err != nil {
Expand All @@ -51,22 +53,15 @@ func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryS
return nil, errorsmod.Wrapf(types.InvalidArgument, "error compiling query: %v", err.Error())
}

sols, err := i.QueryContext(ctx, query)
answer, err := k.queryInterpreter(ctx, i, query, *limits.MaxResultCount)
if err != nil {
return nil, errorsmod.Wrapf(types.InvalidArgument, "error executing query: %v", err.Error())
}
defer func() {
_ = sols.Close()
}()

var userOutput string
if userOutputBuffer != nil {
userOutput = userOutputBuffer.String()
}
answer, err := k.solsToAnswer(sdkCtx, sols)
if err != nil {
return nil, err
}

return &types.QueryServiceAskResponse{
Height: uint64(sdkCtx.BlockHeight()),
Expand All @@ -76,55 +71,47 @@ func (k Keeper) execute(ctx goctx.Context, program, query string) (*types.QueryS
}, nil
}

// solsToAnswer consumes the given prolog solutions and convert it to an answer.
func (k Keeper) solsToAnswer(sdkCtx sdk.Context, sols *prolog.Solutions) (*types.Answer, error) {
solSuccess := false
solError := ""
limits := k.limits(sdkCtx)
var variables []string
results := make([]types.Result, 0)
for nb := sdkmath.ZeroUint(); nb.LT(*limits.MaxResultCount) && sols.Next(); nb = nb.Incr() {
solSuccess = true

m := types.TermResults{}
if err := sols.Scan(m); err != nil {
return nil, errorsmod.Wrapf(types.Internal, "error scanning solution: %v", err.Error())
}
if nb.IsZero() {
variables = m.ToVariables()
}

results = append(results, types.Result{Substitutions: m.ToSubstitutions()})
// queryInterpreter executes the given query on the given interpreter and returns the answer.
func (k Keeper) queryInterpreter(ctx context.Context, i *prolog.Interpreter, query string, limit sdkmath.Uint) (*types.Answer, error) {
p := engine.NewParser(&i.VM, strings.NewReader(query))
t, err := p.Term()
if err != nil {
return nil, err
}

if err := sols.Err(); err != nil {
if sdkCtx.GasMeter().IsOutOfGas() {
panic(storetypes.ErrorOutOfGas{Descriptor: "Prolog interpreter execution"})
var env *engine.Env
count := sdkmath.ZeroUint()
envs := make([]*engine.Env, 0, limit.Uint64())
_, callErr := engine.Call(&i.VM, t, func(env *engine.Env) *engine.Promise {
if count.LT(limit) {
envs = append(envs, env)
}
solError = sols.Err().Error()
count = count.Incr()
return engine.Bool(count.GT(limit))
}, env).Force(ctx)

answerErr := lo.IfF(callErr != nil, func() string {
return callErr.Error()
}).Else("")
success := len(envs) > 0
hasMore := count.GT(limit)
vars := parsedVarsToVars(p.Vars)
results, err := envsToResults(envs, p.Vars, i)
if err != nil {
return nil, err
}

return &types.Answer{
Success: solSuccess,
Error: solError,
HasMore: sols.Next(),
Variables: variables,
Success: success,
Error: answerErr,
HasMore: hasMore,
Variables: vars,
Results: results,
}, nil
}

func checkLimits(request *types.QueryServiceAskRequest, limits types.Limits) error {
size := sdkmath.NewUint(uint64(len(request.GetQuery())))
limit := util.DerefOrDefault(limits.MaxSize, sdkmath.NewUint(math.MaxInt64))
if size.GT(limit) {
return errorsmod.Wrapf(types.LimitExceeded, "query: %d > MaxSize: %d", size, limit)
}

return nil
}

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

Expand Down Expand Up @@ -173,6 +160,16 @@ func (k Keeper) newInterpreter(ctx goctx.Context) (*prolog.Interpreter, *util.Bo
return i, userOutputBuffer, err
}

func checkLimits(request *types.QueryServiceAskRequest, limits types.Limits) error {
size := sdkmath.NewUint(uint64(len(request.GetQuery())))
limit := util.DerefOrDefault(limits.MaxSize, sdkmath.NewUint(math.MaxInt64))
if size.GT(limit) {
return errorsmod.Wrapf(types.LimitExceeded, "query: %d > MaxSize: %d", size, limit)
}

return nil
}

// toPredicate converts the given predicate costs to a function that returns the cost for the given predicate as
// a pair of predicate name and cost.
func toPredicate(defaultCost uint64, predicateCosts []types.PredicateCost) func(string, int) lo.Tuple2[string, uint64] {
Expand All @@ -196,3 +193,30 @@ func nonNilNorZeroOrDefaultUint64(v *sdkmath.Uint, defaultValue uint64) uint64 {

return v.Uint64()
}

func parsedVarsToVars(vars []engine.ParsedVariable) []string {
return lo.Map(vars, func(v engine.ParsedVariable, _ int) string {
return v.Name.String()
})
}

func envsToResults(envs []*engine.Env, vars []engine.ParsedVariable, i *prolog.Interpreter) ([]types.Result, error) {
results := make([]types.Result, 0, len(envs))
for _, rEnv := range envs {
substitutions := make([]types.Substitution, 0, len(vars))
for _, v := range vars {
var expression prolog.TermString
err := expression.Scan(&i.VM, v.Variable, rEnv)
if err != nil {
return nil, err
}
substitution := types.Substitution{
Variable: v.Name.String(),
Expression: string(expression),
}
substitutions = append(substitutions, substitution)
}
results = append(results, types.Result{Substitutions: substitutions})
}
return results, nil
}
43 changes: 0 additions & 43 deletions x/logic/types/logic.go

This file was deleted.

Loading

0 comments on commit 50f9d7f

Please sign in to comment.