Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧠 Logic: 🧱 implement json_prolog/2 #350

Merged
merged 20 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b260e21
test(logic): json_prolog object and string test
bdeneux Apr 26, 2023
e43010d
feat(logic): convert json string to terms
bdeneux Apr 26, 2023
310459a
feat(logic): convert basic json object into prolog
bdeneux Apr 26, 2023
a60f332
feat(logic): json_prolog/2 handle integer number
bdeneux Apr 26, 2023
7679f94
feat(logic): json_prolog/2 handle boolean
bdeneux Apr 26, 2023
94e9c5b
feat(logic): json_prolog/2 handle null json value
bdeneux Apr 26, 2023
ff1f248
feat(logic): json_prolog/2 handle json array
bdeneux Apr 27, 2023
c0b5a6c
feat(logic): json_prolog/2 handle string term to json string
bdeneux Apr 27, 2023
9b58497
feat(logic): add util func to extract json object attribute
bdeneux Apr 28, 2023
4bb3f9a
feat(logic): json_prolog/2 handle json term to json object
bdeneux Apr 28, 2023
4e2b8b6
feat(logic): json_prolog/2 handle list term to json array
bdeneux Apr 28, 2023
9c3b7f8
feat(logic): json_prolog/2 handle boolean and null
bdeneux Apr 28, 2023
0307ab5
test(logic): add bidirectinnal test for json_prolog/2
bdeneux Apr 28, 2023
5b85987
feat(logic): include json_prolog/2 into the registry
bdeneux Apr 28, 2023
62df94e
style: refactor for linter
bdeneux Apr 28, 2023
356c15b
docs: add docs on jsonProlog func
bdeneux May 1, 2023
3e01dfa
test(logic): add test for json_prolog matching checking
bdeneux May 1, 2023
3ac7399
ci: fix dockerfile
bdeneux May 1, 2023
b04b56e
style(logic): fix linter
bdeneux May 2, 2023
e41ea03
style(logic): clean some comment and tests
bdeneux May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.2.1/libwasmvm_muslc.

# hadolint ignore=DL4006
RUN set -eux \
&& apk add --no-cache ca-certificates=20220614-r0 build-base=0.5-r3 git=2.36.5-r0 linux-headers=5.16.7-r1 \
&& apk add --no-cache ca-certificates=20220614-r0 build-base=0.5-r3 git=2.36.6-r0 linux-headers=5.16.7-r1 \
&& sha256sum /lib/libwasmvm_muslc.aarch64.a | grep 86bc5fdc0f01201481c36e17cd3dfed6e9650d22e1c5c8983a5b78c231789ee0 \
&& sha256sum /lib/libwasmvm_muslc.x86_64.a | grep a00700aa19f5bfe0f46290ddf69bf51eb03a6dfcd88b905e1081af2e42dbbafc \
&& cp "/lib/libwasmvm_muslc.$(uname -m).a" /lib/libwasmvm_muslc.a
Expand Down
1 change: 1 addition & 0 deletions x/logic/interpreter/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ var registry = map[string]any{
"hex_bytes/2": predicate.HexBytes,
"bech32_address/2": predicate.Bech32Address,
"source_file/1": predicate.SourceFile,
"json_prolog/2": predicate.JSONProlog,
}

// RegistryNames is the list of the predicate names in the Registry.
Expand Down
182 changes: 182 additions & 0 deletions x/logic/predicate/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package predicate

import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ichiban/prolog/engine"
"github.com/okp4/okp4d/x/logic/util"
"github.com/samber/lo"
)

// AtomJSON is a term which represents a json as a compound term `json([Pair])`.
var AtomJSON = engine.NewAtom("json")

// JSONProlog is a predicate that will unify a JSON string into prolog terms and vice versa.
//
// json_prolog(?Json, ?Term) is det
//
// Where
// - `Json` is the string representation of the json
// - `Term` is an Atom that would be unified by the JSON representation as Prolog terms.
//
// In addition, when passing Json and Term, this predicate return true if both result match.
//
// Example:
//
// # JSON conversion to Prolog.
// - json_prolog('{"foo": "bar"}', json([foo-bar])).
func JSONProlog(vm *engine.VM, j, term engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise {
return engine.Delay(func(ctx context.Context) *engine.Promise {
var result engine.Term

switch t1 := env.Resolve(j).(type) {
case engine.Variable:
case engine.Atom:
terms, err := jsonStringToTerms(t1.String())
if err != nil {
return engine.Error(fmt.Errorf("json_prolog/2: %w", err))
}
result = terms
default:
return engine.Error(fmt.Errorf("json_prolog/2: cannot unify json with %T", t1))
}

switch t2 := env.Resolve(term).(type) {
case engine.Variable:
if result == nil {
return engine.Error(fmt.Errorf("json_prolog/2: could not unify two variable"))
}
return engine.Unify(vm, term, result, cont, env)
default:
b, err := termsToJSON(t2, env)
if err != nil {
return engine.Error(fmt.Errorf("json_prolog/2: %w", err))
}

b, err = sdk.SortJSON(b)
if err != nil {
return engine.Error(fmt.Errorf("json_prolog/2: %w", err))
}
return engine.Unify(vm, j, util.StringToTerm(string(b)), cont, env)
}
})
}

func jsonStringToTerms(j string) (engine.Term, error) {
var values any
decoder := json.NewDecoder(strings.NewReader(j))
decoder.UseNumber() // unmarshal a number into an interface{} as a Number instead of as a float64

if err := decoder.Decode(&values); err != nil {
return nil, err
}

return jsonToTerms(values)
}

func termsToJSON(term engine.Term, env *engine.Env) ([]byte, error) {
switch t := term.(type) {
case engine.Atom:
return json.Marshal(t.String())
case engine.Integer:
return json.Marshal(t)
case engine.Compound:
switch t.Functor().String() {
case ".": // Represent an engine.List
if t.Arity() != 2 {
return nil, fmt.Errorf("wrong term arity for array, give %d, expected %d", t.Arity(), 2)
}

iter := engine.ListIterator{List: t, Env: env}

elements := make([]json.RawMessage, 0)
for iter.Next() {
element, err := termsToJSON(env.Resolve(iter.Current()), env)
if err != nil {
return nil, err
}
elements = append(elements, element)
}
return json.Marshal(elements)
case AtomJSON.String():
terms, err := ExtractJSONTerm(t, env)
if err != nil {
return nil, err
}

attributes := make(map[string]json.RawMessage, len(terms))
for key, term := range terms {
raw, err := termsToJSON(env.Resolve(term), env)
if err != nil {
return nil, err
}
attributes[key] = raw
}
return json.Marshal(attributes)
}

switch {
case AtomBool(true).Compare(t, env) == 0:
return json.Marshal(true)
case AtomBool(false).Compare(t, env) == 0:
return json.Marshal(false)
case AtomNull.Compare(t, env) == 0:
return json.Marshal(nil)
}

return nil, fmt.Errorf("invalid functor %s", t.Functor())
default:
return nil, fmt.Errorf("could not convert %s {%T} to json", t, t)
}
}

func jsonToTerms(value any) (engine.Term, error) {
switch v := value.(type) {
case string:
return util.StringToTerm(v), nil
case json.Number:
r, ok := math.NewIntFromString(string(v))
if !ok {
return nil, fmt.Errorf("could not convert number '%s' into integer term, decimal number is not handled yet", v)
}
if !r.IsInt64() {
return nil, fmt.Errorf("could not convert number '%s' into integer term, overflow", v)
}
return engine.Integer(r.Int64()), nil
case bool:
return AtomBool(v), nil
case nil:
return AtomNull, nil
case map[string]any:
keys := lo.Keys(v)
sort.Strings(keys)

attributes := make([]engine.Term, 0, len(v))
for _, key := range keys {
attributeValue, err := jsonToTerms(v[key])
if err != nil {
return nil, err
}
attributes = append(attributes, AtomPair.Apply(engine.NewAtom(key), attributeValue))
}
return AtomJSON.Apply(engine.List(attributes...)), nil
case []any:
elements := make([]engine.Term, 0, len(v))
for _, element := range v {
term, err := jsonToTerms(element)
if err != nil {
return nil, err
}
elements = append(elements, term)
}
return engine.List(elements...), nil
default:
return nil, fmt.Errorf("could not convert %s (%T) to a prolog term", v, v)
}
}
Loading