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

Fix/empty array #399

Merged
merged 3 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 41 additions & 0 deletions x/logic/predicate/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,44 @@ import (
// 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("-")

// AtomJSON are terms with principal functor json/1.
// It is used to represent json objects.
var 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("@")

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

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

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

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

// MakeNull returns the compound term @(null).
// It is used to represent the null value in json objects.
func MakeNull() engine.Term {
return AtomAt.Apply(AtomNull)
}

// MakeBool returns the compound term @(true) if b is true, otherwise @(false).
func MakeBool(b bool) engine.Term {
if b {
return AtomAt.Apply(AtomTrue)
}

return AtomAt.Apply(AtomFalse)
}

// MakeEmptyArray returns is the compound term @([]).
// It is used to represent the empty array in json objects.
func MakeEmptyArray() engine.Term {
return AtomAt.Apply(AtomEmptyArray)
}
19 changes: 11 additions & 8 deletions x/logic/predicate/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import (
"github.com/okp4/okp4d/x/logic/util"
)

// 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
Expand Down Expand Up @@ -125,11 +122,13 @@ func termsToJSON(term engine.Term, env *engine.Env) ([]byte, error) {
}

switch {
case AtomBool(true).Compare(t, env) == 0:
case MakeBool(true).Compare(t, env) == 0:
return json.Marshal(true)
case AtomBool(false).Compare(t, env) == 0:
case MakeBool(false).Compare(t, env) == 0:
return json.Marshal(false)
case AtomNull.Compare(t, env) == 0:
case MakeEmptyArray().Compare(t, env) == 0:
return json.Marshal([]json.RawMessage{})
case MakeNull().Compare(t, env) == 0:
return json.Marshal(nil)
}

Expand All @@ -153,9 +152,9 @@ func jsonToTerms(value any) (engine.Term, error) {
}
return engine.Integer(r.Int64()), nil
case bool:
return AtomBool(v), nil
return MakeBool(v), nil
case nil:
return AtomNull, nil
return MakeNull(), nil
case map[string]any:
keys := lo.Keys(v)
sort.Strings(keys)
Expand All @@ -171,6 +170,10 @@ func jsonToTerms(value any) (engine.Term, error) {
return AtomJSON.Apply(engine.List(attributes...)), nil
case []any:
elements := make([]engine.Term, 0, len(v))
if len(v) == 0 {
return MakeEmptyArray(), nil
}

for _, element := range v {
term, err := jsonToTerms(element)
if err != nil {
Expand Down
62 changes: 61 additions & 1 deletion x/logic/predicate/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ func TestJsonProlog(t *testing.T) {
},
// ** JSON -> Prolog **
// Array
{
description: "convert empty json array into prolog",
query: `json_prolog('[]', Term).`,
wantResult: []types.TermResults{{
"Term": "@([])",
}},
wantSuccess: true,
},
{
description: "convert json array into prolog",
query: `json_prolog('["foo", "bar"]', Term).`,
Expand All @@ -155,6 +163,14 @@ func TestJsonProlog(t *testing.T) {
}},
wantSuccess: true,
},
{
description: "convert json array with null element into prolog",
query: `json_prolog('[null]', Term).`,
wantResult: []types.TermResults{{
"Term": "[@(null)]",
}},
wantSuccess: true,
},
{
description: "convert json string array into prolog",
query: `json_prolog('["string with space", "bar"]', Term).`,
Expand All @@ -174,6 +190,22 @@ func TestJsonProlog(t *testing.T) {
}},
wantSuccess: true,
},
{
description: "convert atom term to json",
query: `json_prolog(Json, foo).`,
wantResult: []types.TermResults{{
"Json": "'\"foo\"'",
}},
wantSuccess: true,
},
{
description: "convert string with space to json",
query: `json_prolog(Json, 'foo bar').`,
wantResult: []types.TermResults{{
"Json": "'\"foo bar\"'",
}},
wantSuccess: true,
},
{
description: "convert string with space to json",
query: `json_prolog(Json, 'foo bar').`,
Expand All @@ -182,6 +214,14 @@ func TestJsonProlog(t *testing.T) {
}},
wantSuccess: true,
},
{
description: "convert empty-list atom term to json",
query: `json_prolog(Json, []).`,
wantResult: []types.TermResults{{
"Json": "'\"[]\"'",
}},
wantSuccess: true,
},
// ** Prolog -> JSON **
// Object
{
Expand Down Expand Up @@ -252,6 +292,14 @@ func TestJsonProlog(t *testing.T) {
},
// ** Prolog -> Json **
// Array
{
description: "convert empty json array from prolog",
query: `json_prolog(Json, @([])).`,
wantResult: []types.TermResults{{
"Json": "[]",
}},
wantSuccess: true,
},
{
description: "convert json array from prolog",
query: `json_prolog(Json, [foo,bar]).`,
Expand All @@ -260,6 +308,14 @@ func TestJsonProlog(t *testing.T) {
}},
wantSuccess: true,
},
{
description: "convert json array with null element from prolog",
query: `json_prolog(Json, [@(null)]).`,
wantResult: []types.TermResults{{
"Json": "'[null]'",
}},
wantSuccess: true,
},
{
description: "convert json string array from prolog",
query: `json_prolog(Json, ['string with space',bar]).`,
Expand Down Expand Up @@ -340,7 +396,6 @@ func TestJsonProlog(t *testing.T) {
So(sols.Err(), ShouldBeNil)

if tc.wantSuccess {
So(len(got), ShouldBeGreaterThan, 0)
So(len(got), ShouldEqual, len(tc.wantResult))
for iGot, resultGot := range got {
for varGot, termGot := range resultGot {
Expand Down Expand Up @@ -404,6 +459,11 @@ func TestJsonPrologWithMoreComplexStructBidirectional(t *testing.T) {
term: "json([a-b])",
wantSuccess: false,
},
{
json: `'{"key1":null,"key2":[],"key3":{"nestedKey1":null,"nestedKey2":[],"nestedKey3":["a",null,null]}}'`,
term: `json([key1- @(null),key2- @([]),key3-json([nestedKey1- @(null),nestedKey2- @([]),nestedKey3-[a,@(null),@(null)]])])`,
wantSuccess: true,
},
}
for nc, tc := range cases {
Convey(fmt.Sprintf("#%d : given the json: %s and the term %s", nc, tc.json, tc.term), func() {
Expand Down
12 changes: 0 additions & 12 deletions x/logic/predicate/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,6 @@ func ListToBytes(terms engine.ListIterator, env *engine.Env) ([]byte, error) {
return bt, nil
}

func AtomBool(b bool) engine.Term {
var r engine.Atom
if b {
r = engine.NewAtom("true")
} else {
r = engine.NewAtom("false")
}
return engine.NewAtom("@").Apply(r)
}

var AtomNull = engine.NewAtom("@").Apply(engine.NewAtom("null"))

// ExtractJSONTerm is an utility function that would extract all attribute of a JSON object
// that is represented in prolog with the `json` atom.
//
Expand Down