From d1f0ee02bf825765aec68ce5df437bb6a783bbe0 Mon Sep 17 00:00:00 2001 From: ccamel Date: Fri, 23 Feb 2024 14:28:13 +0100 Subject: [PATCH] fix(logic): fix variable resolution --- x/logic/keeper/interpreter.go | 65 +------------ x/logic/predicate/address.go | 6 +- x/logic/predicate/crypto.go | 4 +- x/logic/predicate/did.go | 2 +- x/logic/predicate/file.go | 4 +- x/logic/predicate/string.go | 2 +- x/logic/predicate/uri.go | 4 +- x/logic/prolog/assert.go | 171 +++++++++++++++------------------- x/logic/prolog/byte.go | 2 +- x/logic/prolog/hex.go | 2 +- x/logic/prolog/list.go | 2 +- x/logic/prolog/option.go | 15 +-- x/logic/prolog/text.go | 18 ++-- 13 files changed, 103 insertions(+), 194 deletions(-) diff --git a/x/logic/keeper/interpreter.go b/x/logic/keeper/interpreter.go index 641cbd71..37d3f4ce 100644 --- a/x/logic/keeper/interpreter.go +++ b/x/logic/keeper/interpreter.go @@ -3,10 +3,8 @@ package keeper import ( "context" "math" - "strings" "github.com/ichiban/prolog" - "github.com/ichiban/prolog/engine" "github.com/samber/lo" errorsmod "cosmossdk.io/errors" @@ -73,41 +71,7 @@ func (k Keeper) execute(ctx context.Context, program, query string) (*types.Quer // 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 - } - - 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) - } - 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: success, - Error: answerErr, - HasMore: hasMore, - Variables: vars, - Results: results, - }, nil + return util.QueryInterpreter(ctx, i, query, limit) } // newInterpreter creates a new interpreter properly configured. @@ -193,30 +157,3 @@ 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 -} diff --git a/x/logic/predicate/address.go b/x/logic/predicate/address.go index 4f9a30e3..d8f7066a 100644 --- a/x/logic/predicate/address.go +++ b/x/logic/predicate/address.go @@ -35,7 +35,7 @@ import ( // [base64]: https://fr.wikipedia.org/wiki/Base64 func Bech32Address(_ *engine.VM, address, bech32 engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { forwardConverter := func(value []engine.Term, _ engine.Term, env *engine.Env) ([]engine.Term, error) { - hrpTerm, dataTerm, err := prolog.AssertPair(env, value[0]) + hrpTerm, dataTerm, err := prolog.AssertPair(value[0], env) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func Bech32Address(_ *engine.VM, address, bech32 engine.Term, cont engine.Cont, if err != nil { return nil, err } - hrp, err := prolog.AssertAtom(env, hrpTerm) + hrp, err := prolog.AssertAtom(hrpTerm, env) if err != nil { return nil, err } @@ -56,7 +56,7 @@ func Bech32Address(_ *engine.VM, address, bech32 engine.Term, cont engine.Cont, return []engine.Term{engine.NewAtom(b)}, nil } backwardConverter := func(value []engine.Term, _ engine.Term, env *engine.Env) ([]engine.Term, error) { - b, err := prolog.AssertAtom(env, value[0]) + b, err := prolog.AssertAtom(value[0], env) if err != nil { return nil, err } diff --git a/x/logic/predicate/crypto.go b/x/logic/predicate/crypto.go index d907ee52..0d277fa1 100644 --- a/x/logic/predicate/crypto.go +++ b/x/logic/predicate/crypto.go @@ -167,7 +167,7 @@ func xVerify(key, data, sig, options engine.Term, defaultAlgo util.KeyAlg, if err != nil { return engine.Error(err) } - typeAtom, err := prolog.AssertAtom(env, typeTerm) + typeAtom, err := prolog.AssertAtom(typeTerm, env) if err != nil { return engine.Error(err) } @@ -211,7 +211,7 @@ func termToBytes(term, options, defaultEncoding engine.Term, env *engine.Env) ([ if err != nil { return nil, err } - encodingAtom, err := prolog.AssertAtom(env, encodingTerm) + encodingAtom, err := prolog.AssertAtom(encodingTerm, env) if err != nil { return nil, err } diff --git a/x/logic/predicate/did.go b/x/logic/predicate/did.go index 6dc95ef6..25659e85 100644 --- a/x/logic/predicate/did.go +++ b/x/logic/predicate/did.go @@ -87,7 +87,7 @@ func DIDComponents(vm *engine.VM, did, components engine.Term, cont engine.Cont, switch segment := env.Resolve(t2.Arg(i)).(type) { case engine.Variable: default: - atom, err := prolog.AssertAtom(env, segment) + atom, err := prolog.AssertAtom(segment, env) if err != nil { return engine.Error(err) } diff --git a/x/logic/predicate/file.go b/x/logic/predicate/file.go index d7c96f44..145ff7df 100644 --- a/x/logic/predicate/file.go +++ b/x/logic/predicate/file.go @@ -150,11 +150,11 @@ func Open(vm *engine.VM, sourceSink, mode, stream, options engine.Term, k engine s := engine.NewInputTextStream(f) if prolog.IsGround(options, env) { - _, err = prolog.AssertList(env, options) + _, err = prolog.AssertList(options, env) switch { case err != nil: return engine.Error(err) - case !prolog.IsEmptyList(options): + case !prolog.IsEmptyList(options, env): return engine.Error(engine.DomainError(prolog.ValidEmptyList(), options, env)) } } diff --git a/x/logic/predicate/string.go b/x/logic/predicate/string.go index 035dab16..97f6f775 100644 --- a/x/logic/predicate/string.go +++ b/x/logic/predicate/string.go @@ -106,7 +106,7 @@ func ReadString(vm *engine.VM, stream, length, result engine.Term, cont engine.C func StringBytes( _ *engine.VM, str, bts, encodingTerm engine.Term, cont engine.Cont, env *engine.Env, ) *engine.Promise { - encoding, err := prolog.AssertAtom(env, encodingTerm) + encoding, err := prolog.AssertAtom(encodingTerm, env) if err != nil { return engine.Error(err) } diff --git a/x/logic/predicate/uri.go b/x/logic/predicate/uri.go index 68d7e0a6..e4e1848b 100644 --- a/x/logic/predicate/uri.go +++ b/x/logic/predicate/uri.go @@ -31,11 +31,11 @@ import ( // // [RFC 3986]: https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 func URIEncoded(_ *engine.VM, component, decoded, encoded engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { - _, err := prolog.AssertIsGround(env, component) + _, err := prolog.AssertIsGround(component, env) if err != nil { return engine.Error(err) } - uriComponent, err := prolog.AssertURIComponent(env, component) + uriComponent, err := prolog.AssertURIComponent(component, env) if err != nil { return engine.Error(err) } diff --git a/x/logic/prolog/assert.go b/x/logic/prolog/assert.go index 9b66c9c6..9fe44c78 100644 --- a/x/logic/prolog/assert.go +++ b/x/logic/prolog/assert.go @@ -30,58 +30,23 @@ func PredicateMatches(this string) func(string) bool { } // IsList returns true if the given term is a list. -func IsList(term engine.Term) bool { - switch v := term.(type) { - case engine.Compound: - return v.Functor() == AtomDot && v.Arity() == 2 - case engine.Atom: - return v == AtomEmptyList - } - - return false +func IsList(term engine.Term, env *engine.Env) bool { + _, err := AssertList(term, env) + return err == nil } // IsEmptyList returns true if the given term is an empty list. -func IsEmptyList(term engine.Term) bool { - if v, ok := term.(engine.Atom); ok { +func IsEmptyList(term engine.Term, env *engine.Env) bool { + if v, ok := env.Resolve(term).(engine.Atom); ok { return v == AtomEmptyList } return false } -// IsVariable returns true if the given term is a variable. -func IsVariable(term engine.Term) bool { - _, ok := term.(engine.Variable) - return ok -} - -// IsAtom returns true if the given term is an atom. -func IsAtom(term engine.Term) bool { - _, ok := term.(engine.Atom) - return ok -} - -// IsCompound returns true if the given term is a compound. -func IsCompound(term engine.Term) bool { - _, ok := term.(engine.Compound) - return ok -} - // IsGround returns true if the given term holds no free variables. func IsGround(term engine.Term, env *engine.Env) bool { - switch term := env.Resolve(term).(type) { - case engine.Variable: - return false - case engine.Compound: - for i := 0; i < term.Arity(); i++ { - if !IsGround(term.Arg(i), env) { - return false - } - } - return true - default: - return true - } + _, err := AssertIsGround(term, env) + return err == nil } func AreGround(terms []engine.Term, env *engine.Env) bool { @@ -92,105 +57,117 @@ func AreGround(terms []engine.Term, env *engine.Env) bool { // AssertIsGround resolves a term and returns it if it is ground. // If the term is not ground, the function returns nil and the instantiation error. -func AssertIsGround(env *engine.Env, t engine.Term) (engine.Term, error) { - if IsGround(t, env) { - return t, nil +func AssertIsGround(term engine.Term, env *engine.Env) (engine.Term, error) { + switch term := env.Resolve(term).(type) { + case engine.Variable: + return nil, engine.InstantiationError(env) + case engine.Compound: + args := make([]engine.Term, term.Arity()) + for i := 0; i < term.Arity(); i++ { + arg, err := AssertIsGround(term.Arg(i), env) + if err != nil { + return nil, err + } + args[i] = arg + } + return term.Functor().Apply(args...), nil + default: + return term, nil } - return nil, engine.InstantiationError(env) } // AssertAtom resolves a term and attempts to convert it into an engine.Atom if possible. // If conversion fails, the function returns the empty atom and the error. -func AssertAtom(env *engine.Env, t engine.Term) (engine.Atom, error) { - _, err := AssertIsGround(env, t) - if err != nil { - return AtomEmpty, err - } - if t, ok := t.(engine.Atom); ok { - return t, nil +func AssertAtom(term engine.Term, env *engine.Env) (engine.Atom, error) { + switch term := env.Resolve(term).(type) { + case engine.Atom: + return term, nil + case engine.Variable: + return AtomEmpty, engine.InstantiationError(env) + default: + return AtomEmpty, engine.TypeError(AtomTypeAtom, term, env) } - return AtomEmpty, engine.TypeError(AtomTypeAtom, t, env) } // AssertCharacterCode resolves a term and attempts to convert it into a rune if possible. // If conversion fails, the function returns the zero value and the error. -func AssertCharacterCode(env *engine.Env, t engine.Term) (rune, error) { - _, err := AssertIsGround(env, t) - if err != nil { - return 0, err - } - - if t, ok := t.(engine.Integer); ok { - if t >= 0 && t <= utf8.MaxRune { - return rune(t), nil +func AssertCharacterCode(term engine.Term, env *engine.Env) (rune, error) { + switch term := env.Resolve(term).(type) { + case engine.Integer: + if term >= 0 && term <= utf8.MaxRune { + return rune(term), nil } + case engine.Variable: + return utf8.RuneError, engine.InstantiationError(env) } - return 0, engine.TypeError(AtomTypeCharacterCode, t, env) + return utf8.RuneError, engine.TypeError(AtomTypeCharacterCode, term, env) } // AssertCharacter resolves a term and attempts to convert it into an engine.Atom if possible. // If conversion fails, the function returns the empty atom and the error. -func AssertCharacter(env *engine.Env, t engine.Term) (rune, error) { - _, err := AssertIsGround(env, t) - if err != nil { - return utf8.RuneError, err - } - if t, ok := t.(engine.Atom); ok { - runes := []rune(t.String()) +func AssertCharacter(term engine.Term, env *engine.Env) (rune, error) { + switch term := env.Resolve(term).(type) { + case engine.Atom: + runes := []rune(term.String()) if len(runes) == 1 { return runes[0], nil } + case engine.Variable: + return utf8.RuneError, engine.InstantiationError(env) } - return utf8.RuneError, engine.TypeError(AtomTypeCharacter, t, env) + + return utf8.RuneError, engine.TypeError(AtomTypeCharacter, term, env) } // AssertByte resolves a term and attempts to convert it into a byte if possible. // If conversion fails, the function returns the zero value and the error. -func AssertByte(env *engine.Env, t engine.Term) (byte, error) { - _, err := AssertIsGround(env, t) - if err != nil { - return 0, err - } - if t, ok := t.(engine.Integer); ok { - if t >= 0 && t <= 255 { - return byte(t), nil +func AssertByte(term engine.Term, env *engine.Env) (byte, error) { + switch term := env.Resolve(term).(type) { + case engine.Integer: + if term >= 0 && term <= 255 { + return byte(term), nil } + case engine.Variable: + return 0, engine.InstantiationError(env) } - return 0, engine.TypeError(AtomTypeByte, t, env) + return 0, engine.TypeError(AtomTypeByte, term, env) } // AssertList resolves a term as a list and returns it as a engine.Compound. // If conversion fails, the function returns nil and the error. -func AssertList(env *engine.Env, t engine.Term) (engine.Term, error) { - _, err := AssertIsGround(env, t) - if err != nil { - return nil, err - } - if IsList(t) { - return t, nil +func AssertList(term engine.Term, env *engine.Env) (engine.Term, error) { + switch term := env.Resolve(term).(type) { + case engine.Compound: + if term.Functor() == AtomDot && term.Arity() == 2 { + return term, nil + } + case engine.Atom: + if term == AtomEmptyList { + return term, nil + } } - return nil, engine.TypeError(AtomTypeList, t, env) + return nil, engine.TypeError(AtomTypeList, term, env) } // AssertPair resolves a term as a pair and returns the pair components. // If conversion fails, the function returns nil and the error. -func AssertPair(env *engine.Env, t engine.Term) (engine.Term, engine.Term, error) { - _, err := AssertIsGround(env, t) +func AssertPair(term engine.Term, env *engine.Env) (engine.Term, engine.Term, error) { + term, err := AssertIsGround(term, env) if err != nil { return nil, nil, err } - if t, ok := t.(engine.Compound); ok && t.Functor() == AtomPair && t.Arity() == 2 { - return t.Arg(0), t.Arg(1), nil + if term, ok := term.(engine.Compound); ok && term.Functor() == AtomPair && term.Arity() == 2 { + return term.Arg(0), term.Arg(1), nil } - return nil, nil, engine.TypeError(AtomTypePair, t, env) + return nil, nil, engine.TypeError(AtomTypePair, term, env) } // AssertURIComponent resolves a term as a URI component and returns it as an URIComponent. -func AssertURIComponent(env *engine.Env, t engine.Term) (util.URIComponent, error) { - switch v := env.Resolve(t); v { +func AssertURIComponent(term engine.Term, env *engine.Env) (util.URIComponent, error) { + switch v := env.Resolve(term); v { case AtomQueryValue: return util.QueryValueComponent, nil case AtomFragment: @@ -200,6 +177,6 @@ func AssertURIComponent(env *engine.Env, t engine.Term) (util.URIComponent, erro case AtomSegment: return util.SegmentComponent, nil default: - return 0, engine.TypeError(AtomTypeURIComponent, t, env) + return 0, engine.TypeError(AtomTypeURIComponent, term, env) } } diff --git a/x/logic/prolog/byte.go b/x/logic/prolog/byte.go index 6e40bc1a..08f52580 100644 --- a/x/logic/prolog/byte.go +++ b/x/logic/prolog/byte.go @@ -13,7 +13,7 @@ func ByteListTermToBytes(term engine.Term, env *engine.Env) ([]byte, error) { var bs []byte for iter.Next() { - b, err := AssertByte(env, iter.Current()) + b, err := AssertByte(iter.Current(), env) if err != nil { return nil, err } diff --git a/x/logic/prolog/hex.go b/x/logic/prolog/hex.go index 263ae6ba..5f7d65eb 100644 --- a/x/logic/prolog/hex.go +++ b/x/logic/prolog/hex.go @@ -8,7 +8,7 @@ import ( // TermHexToBytes try to convert an hexadecimal encoded atom to native golang []byte. func TermHexToBytes(term engine.Term, env *engine.Env) ([]byte, error) { - v, err := AssertAtom(env, term) + v, err := AssertAtom(term, env) if err != nil { return nil, err } diff --git a/x/logic/prolog/list.go b/x/logic/prolog/list.go index 1327fcda..5691ef17 100644 --- a/x/logic/prolog/list.go +++ b/x/logic/prolog/list.go @@ -13,7 +13,7 @@ func ListHead(list engine.Term, env *engine.Env) engine.Term { // ListIterator returns a list iterator. func ListIterator(list engine.Term, env *engine.Env) (engine.ListIterator, error) { - if !IsList(env.Resolve(list)) { + if !IsList(list, env) { return engine.ListIterator{}, engine.TypeError(AtomTypeList, list, env) } return engine.ListIterator{List: list, Env: env}, nil diff --git a/x/logic/prolog/option.go b/x/logic/prolog/option.go index 9bfcaffe..83442b99 100644 --- a/x/logic/prolog/option.go +++ b/x/logic/prolog/option.go @@ -11,7 +11,7 @@ import ( // If no option is found nil is returned. func GetOption(name engine.Atom, options engine.Term, env *engine.Env) (engine.Term, error) { extractOption := func(opt engine.Term) (engine.Term, error) { - switch v := opt.(type) { + switch v := env.Resolve(opt).(type) { case engine.Compound: if v.Functor() == name { if v.Arity() != 1 { @@ -31,13 +31,8 @@ func GetOption(name engine.Atom, options engine.Term, env *engine.Env) (engine.T return nil, engine.TypeError(AtomTypeOption, opt, env) } - resolvedTerm := env.Resolve(options) - if resolvedTerm == nil { - return nil, nil - } - - if IsList(resolvedTerm) { - iter, err := ListIterator(resolvedTerm, env) + if IsList(options, env) { + iter, err := ListIterator(options, env) if err != nil { return nil, err } @@ -56,7 +51,7 @@ func GetOption(name engine.Atom, options engine.Term, env *engine.Env) (engine.T } } - return extractOption(resolvedTerm) + return extractOption(options) } // GetOptionWithDefault returns the value of the first option with the given name in the given options, or the given @@ -81,7 +76,7 @@ func GetOptionAsAtomWithDefault( if err != nil { return AtomEmpty, err } - atom, err := AssertAtom(env, term) + atom, err := AssertAtom(term, env) if err != nil { return AtomEmpty, err } diff --git a/x/logic/prolog/text.go b/x/logic/prolog/text.go index 19a3dd6c..c13d9e75 100644 --- a/x/logic/prolog/text.go +++ b/x/logic/prolog/text.go @@ -8,12 +8,12 @@ import ( ) // AtomToString try to convert a given atom to a string. -func AtomToString(atom engine.Term, env *engine.Env) (string, error) { - v, err := AssertAtom(env, atom) +func AtomToString(term engine.Term, env *engine.Env) (string, error) { + atom, err := AssertAtom(term, env) if err != nil { return "", err } - return v.String(), nil + return atom.String(), nil } // listTermToString try to convert a given list to a string using the provided @@ -22,7 +22,7 @@ func AtomToString(atom engine.Term, env *engine.Env) (string, error) { // to return a rune. func listTermToString( term engine.Term, - converter func(*engine.Env, engine.Term) (rune, error), + converter func(engine.Term, *engine.Env) (rune, error), env *engine.Env, ) (string, error) { iter, err := ListIterator(term, env) @@ -32,7 +32,7 @@ func listTermToString( var sb strings.Builder for iter.Next() { - r, err := converter(env, iter.Current()) + r, err := converter(iter.Current(), env) if err != nil { return sb.String(), err } @@ -57,8 +57,8 @@ func CharacterCodeListTermToString(term engine.Term, env *engine.Env) (string, e // It's the same as CharacterCodeListTermToString, but expects the list to contain bytes. // It's equivalent to the prolog encoding 'octet'. func OctetListTermToString(term engine.Term, env *engine.Env) (string, error) { - return listTermToString(term, func(env *engine.Env, term engine.Term) (rune, error) { - b, err := AssertByte(env, term) + return listTermToString(term, func(term engine.Term, env *engine.Env) (rune, error) { + b, err := AssertByte(term, env) if err != nil { return utf8.RuneError, err } @@ -73,7 +73,7 @@ func TextTermToString(term engine.Term, env *engine.Env) (string, error) { case engine.Atom: return AtomToString(v, env) case engine.Compound: - if IsList(v) { + if IsList(v, env) { head := ListHead(v, env) if head == nil { return "", nil @@ -93,7 +93,7 @@ func TextTermToString(term engine.Term, env *engine.Env) (string, error) { } // StringToAtom converts a string to an atom. -func StringToAtom(s string) engine.Term { +func StringToAtom(s string) engine.Atom { return engine.NewAtom(s) }