Skip to content

Commit

Permalink
refactor(logic): improve GetOption utility function
Browse files Browse the repository at this point in the history
  • Loading branch information
ccamel committed Oct 18, 2023
1 parent c8e8e7e commit a730bf3
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 151 deletions.
40 changes: 21 additions & 19 deletions x/logic/predicate/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,34 @@ import (
"github.com/ichiban/prolog/engine"
)

// AtomPair are terms with principal functor (-)/2.
// For example, the term -(A, B) denotes the pair of elements A and B.
var AtomPair = engine.NewAtom("-")
var (
// AtomPair are terms with principal functor (-)/2.
// For example, the term -(A, B) denotes the pair of elements A and B.
AtomPair = engine.NewAtom("-")

// AtomJSON are terms with principal functor json/1.
// It is used to represent json objects.
var AtomJSON = engine.NewAtom("json")
// AtomJSON are terms with principal functor json/1.
// It is used to represent json objects.
AtomJSON = engine.NewAtom("json")

// AtomAt are terms with principal functor (@)/1.
// It is used to represent special values in json objects.
var AtomAt = engine.NewAtom("@")
// AtomAt are terms with principal functor (@)/1.
// It is used to represent special values in json objects.
AtomAt = engine.NewAtom("@")

// AtomTrue is the term true.
var AtomTrue = engine.NewAtom("true")
// AtomTrue is the term true.
AtomTrue = engine.NewAtom("true")

// AtomFalse is the term false.
var AtomFalse = engine.NewAtom("false")
// AtomFalse is the term false.
AtomFalse = engine.NewAtom("false")

// AtomEmptyArray is the term [].
var AtomEmptyArray = engine.NewAtom("[]")
// AtomEmptyArray is the term [].
AtomEmptyArray = engine.NewAtom("[]")

// AtomNull is the term null.
var AtomNull = engine.NewAtom("null")
// AtomNull is the term null.
AtomNull = engine.NewAtom("null")

// AtomEncoding is the term used to indicate the encoding type option.
var AtomEncoding = engine.NewAtom("encoding")
// AtomEncoding is the term used to indicate the encoding type option.
AtomEncoding = engine.NewAtom("encoding")
)

// MakeNull returns the compound term @(null).
// It is used to represent the null value in json objects.
Expand Down
6 changes: 3 additions & 3 deletions x/logic/predicate/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/okp4/okp4d/x/logic/util"
)

// SHAHash is a predicate that computes the Hash of the given Data. The signature is as follow:
// SHAHash is a predicate that computes the Hash of the given Data. The signature is as follows:
//
// sha_hash(+Data, -Hash) is det
// sha_hash(+Data, +Hash) is det
Expand Down Expand Up @@ -45,7 +45,7 @@ func SHAHash(vm *engine.VM, data, hash engine.Term, cont engine.Cont, env *engin
})
}

// HexBytes is a predicate that unifies hexadecimal encoded bytes to a list of bytes. The signature is as follow:
// HexBytes is a predicate that unifies hexadecimal encoded bytes to a list of bytes. The signature is as follows:
//
// hex_bytes(?Hex, ?Bytes) is det
//
Expand Down Expand Up @@ -125,7 +125,7 @@ const (
//
// # Verify the signature of given binary data.
// - ed25519_verify([127, ...], [56, 90, ..], [23, 56, ...], encoding(octet)).
func ED25519Verify(vm *engine.VM, key, data, sig, options engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise {
func ED25519Verify(_ *engine.VM, key, data, sig, options engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise {
return engine.Delay(func(ctx context.Context) *engine.Promise {
r, err := cryptoVerify(Ed25519, key, data, sig, options, env)
if err != nil {
Expand Down
36 changes: 3 additions & 33 deletions x/logic/predicate/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/okp4/okp4d/x/logic/types"
"github.com/okp4/okp4d/x/logic/util"
)

// SortBalances by coin denomination.
Expand Down Expand Up @@ -65,38 +66,11 @@ func BytesToList(bt []byte) engine.Term {
return engine.List(terms...)
}

func OptionsContains(atom engine.Atom, options engine.Term, env *engine.Env) (engine.Compound, error) {
switch opts := env.Resolve(options).(type) {
case engine.Compound:
if opts.Functor() == atom {
return opts, nil
} else if opts.Arity() == 2 && opts.Functor().String() == "." {
iter := engine.ListIterator{List: opts, Env: env}

for iter.Next() {
opt := env.Resolve(iter.Current())
term, err := OptionsContains(atom, opt, env)
if err != nil {
return nil, err
}
if term != nil {
return term, nil
}
}
}
return nil, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("invalid options term, should be compound, give %T", opts)
}
}

// TermToBytes try to convert a term to native golang []byte.
// By default, if no encoding options is given the term is considered as hexadecimal value.
// Available encoding option is `text`, `octet` and `hex` (default value).
func TermToBytes(term, options engine.Term, env *engine.Env) ([]byte, error) {
encoding, err := OptionsContains(AtomEncoding, options, env)
encoding, err := util.GetOption(AtomEncoding, options, env)
if err != nil {
return nil, err
}
Expand All @@ -105,11 +79,7 @@ func TermToBytes(term, options engine.Term, env *engine.Env) ([]byte, error) {
encoding = AtomEncoding.Apply(engine.NewAtom("hex")).(engine.Compound)
}

if encoding.Arity() != 1 {
return nil, fmt.Errorf("invalid arity for encoding option, should be 1")
}

switch enc := env.Resolve(encoding.Arg(0)).(type) {
switch enc := env.Resolve(encoding).(type) {
case engine.Atom:
switch enc.String() {
case "octet":
Expand Down
96 changes: 0 additions & 96 deletions x/logic/predicate/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,102 +81,6 @@ func TestExtractJsonTerm(t *testing.T) {
})
}

func TestOptionsContains(t *testing.T) {
Convey("Given a test cases", t, func() {
cases := []struct {
atom engine.Atom
options engine.Term
result engine.Compound
wantSuccess bool
wantError error
}{
{
atom: engine.NewAtom("foo"),
options: engine.NewAtom("foo").Apply(engine.NewAtom("bar")),
result: engine.NewAtom("foo").Apply(engine.NewAtom("bar")).(engine.Compound),
wantSuccess: true,
},
{
atom: engine.NewAtom("bar"),
options: engine.NewAtom("foo").Apply(engine.NewAtom("bar")),
result: nil,
wantSuccess: true,
},
{
atom: engine.NewAtom("foo"),
options: engine.List(engine.NewAtom("foo").Apply(engine.NewAtom("bar"))),
result: engine.NewAtom("foo").Apply(engine.NewAtom("bar")).(engine.Compound),
wantSuccess: true,
},
{
atom: engine.NewAtom("bar"),
options: engine.List(engine.NewAtom("foo").Apply(engine.NewAtom("bar"))),
result: nil,
wantSuccess: true,
},
{
atom: engine.NewAtom("foo"),
options: engine.List(
engine.NewAtom("jo").Apply(engine.NewAtom("bi")),
engine.NewAtom("hey").Apply(engine.NewAtom("hoo")),
engine.NewAtom("foo").Apply(engine.NewAtom("bar"))),
result: engine.NewAtom("foo").Apply(engine.NewAtom("bar")).(engine.Compound),
wantSuccess: true,
},
{
atom: engine.NewAtom("hey"),
options: engine.List(
engine.NewAtom("jo").Apply(engine.NewAtom("bi")),
engine.NewAtom("hey").Apply(engine.NewAtom("hoo")),
engine.NewAtom("foo").Apply(engine.NewAtom("bar"))),
result: engine.NewAtom("hey").Apply(engine.NewAtom("hoo")).(engine.Compound),
wantSuccess: true,
},
{
atom: engine.NewAtom("foo"),
options: engine.List(
engine.NewAtom("jo").Apply(engine.NewAtom("bi")),
engine.NewAtom("hey"),
engine.NewAtom("foo").Apply(engine.NewAtom("bar"))),
wantSuccess: false,
wantError: fmt.Errorf("invalid options term, should be compound, give engine.Atom"),
},
{
atom: engine.NewAtom("foo"),
options: nil,
wantSuccess: true,
result: nil,
},
}
for nc, tc := range cases {
Convey(fmt.Sprintf("Given the term option #%d: %s", nc, tc.atom), func() {
Convey("when check contains", func() {
env := engine.Env{}
result, err := OptionsContains(tc.atom, tc.options, &env)

if tc.wantSuccess {
Convey("then no error should be thrown", func() {
So(err, ShouldBeNil)

Convey("and result should be as expected", func() {
So(result, ShouldResemble, tc.result)
})
})
} else {
Convey("then error should occurs", func() {
So(err, ShouldNotBeNil)

Convey("and should be as expected", func() {
So(err, ShouldResemble, tc.wantError)
})
})
}
})
})
}
})
}

func TestTermToBytes(t *testing.T) {
Convey("Given a test cases", t, func() {
cases := []struct {
Expand Down
57 changes: 57 additions & 0 deletions x/logic/util/prolog.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package util

import (
"fmt"
"strings"

"github.com/ichiban/prolog/engine"
)

// AtomDot is the term used to represent the dot in a list.
var AtomDot = engine.NewAtom(".")

// StringToTerm converts a string to a term.
func StringToTerm(s string) engine.Term {
return engine.NewAtom(s)
Expand Down Expand Up @@ -39,3 +43,56 @@ func PredicateMatches(this string) func(string) bool {
return strings.Split(this, "/")[0] == that
}
}

// IsList returns true if the given compound is a list.
func IsList(compound engine.Compound) bool {
return compound.Functor() == AtomDot && compound.Arity() == 2
}

// GetOption returns the value of the first option with the given name in the given options.
// An option is a compound with the given name as functor and one argument which is
// a term, for instance `opt(v)`.
// The options are either a list of options or an option.
// If no option is found nil is returned.
func GetOption(name engine.Atom, options engine.Term, env *engine.Env) (engine.Term, error) {
extractOption := func(term engine.Term) (engine.Term, error) {
switch v := term.(type) {
case engine.Compound:
if v.Functor() == name {
if v.Arity() != 1 {
return nil, fmt.Errorf("invalid arity for compound '%s': %d but expected 1", name, v.Arity())
}

return v.Arg(0), nil
}
return nil, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("invalid term '%s' - expected engine.Compound but got %T", term, v)
}
}

resolvedTerm := env.Resolve(options)

if v, ok := resolvedTerm.(engine.Compound); ok {
if IsList(v) {
iter := engine.ListIterator{List: v, Env: env}

for iter.Next() {
opt := env.Resolve(iter.Current())

term, err := extractOption(opt)
if err != nil {
return nil, err
}

if term != nil {
return term, nil
}
}
return nil, nil
}
}
return extractOption(resolvedTerm)
}
Loading

0 comments on commit a730bf3

Please sign in to comment.