diff --git a/x/logic/interpreter/bootstrap/bootstrap.pl b/x/logic/interpreter/bootstrap/bootstrap.pl index af745ed2..9a73979e 100644 --- a/x/logic/interpreter/bootstrap/bootstrap.pl +++ b/x/logic/interpreter/bootstrap/bootstrap.pl @@ -80,9 +80,6 @@ % Stream selection and control -open(Filename, Mode, Stream) :- - open(Filename, Mode, Stream, []). - close(Stream) :- close(Stream, []). flush_output :- diff --git a/x/logic/interpreter/registry.go b/x/logic/interpreter/registry.go index 2f18c975..0741927f 100644 --- a/x/logic/interpreter/registry.go +++ b/x/logic/interpreter/registry.go @@ -54,6 +54,7 @@ var registry = map[string]any{ "set_input/1": engine.SetInput, "set_output/1": engine.SetOutput, "open/4": predicate.Open, + "open/3": predicate.Open3, "close/2": engine.Close, "flush_output/1": engine.FlushOutput, "stream_property/2": engine.StreamProperty, diff --git a/x/logic/keeper/features/consult_1.feature b/x/logic/keeper/features/consult_1.feature index 713d5a1a..8dc27c78 100644 --- a/x/logic/keeper/features/consult_1.feature +++ b/x/logic/keeper/features/consult_1.feature @@ -41,6 +41,7 @@ Feature: consult/1 expression: "['W',o,r,l,d,!]" """ + @great_for_documentation Scenario: Consult a Prolog program which also consults another Prolog program This scenario demonstrates the capability of a Prolog program to consult another Prolog program. This is useful for @@ -56,8 +57,7 @@ Feature: consult/1 } response: | :- consult('cosmwasm:storage:okp412ssv28mzr02jffvy4x39akrpky9ykfafzyjzmvgsqqdw78yjevpqgmqnmk?query=%7B%22object_data%22%3A%7B%22id%22%3A%20%225d3933430d0a12794fae719e0db87b6ec5f549b2%22%7D%7D&base64Decode=false'). - - program(a). + a. """ Given the CosmWasm smart contract "okp412ssv28mzr02jffvy4x39akrpky9ykfafzyjzmvgsqqdw78yjevpqgmqnmk" and the behavior: """ yaml @@ -68,24 +68,20 @@ Feature: consult/1 } } response: | - program(b). + b. """ Given the query: """ prolog consult('cosmwasm:storage:okp415ekvz3qdter33mdnk98v8whv5qdr53yusksnfgc08xd26fpdn3ts8gddht?query=%7B%22object_data%22%3A%7B%22id%22%3A%20%224cbe36399aabfcc7158ee7a66cbfffa525bb0ceab33d1ff2cff08759fe0a9b05%22%7D%7D&base64Decode=false'), - program(Name). + a, b. """ When the query is run (limited to 2 solutions) Then the answer we get is: """ yaml has_more: false - variables: ["Name"] + variables: results: - substitutions: - - variable: Name - expression: "a" - - variable: Name - expression: "b" """ @great_for_documentation @@ -123,17 +119,18 @@ Feature: consult/1 """ Given the query: """ prolog - program(Name). + source_file(File). """ When the query is run (limited to 2 solutions) Then the answer we get is: """ yaml has_more: false - variables: ["Name"] + variables: ["File"] results: - substitutions: - - variable: Name - expression: "a" - - variable: Name - expression: "b" + - variable: File + expression: "'cosmwasm:storage:okp412ssv28mzr02jffvy4x39akrpky9ykfafzyjzmvgsqqdw78yjevpqgmqnmk?query=%7B%22object_data%22%3A%7B%22id%22%3A%20%225d3933430d0a12794fae719e0db87b6ec5f549b2%22%7D%7D&base64Decode=false'" + - substitutions: + - variable: File + expression: "'cosmwasm:storage:okp415ekvz3qdter33mdnk98v8whv5qdr53yusksnfgc08xd26fpdn3ts8gddht?query=%7B%22object_data%22%3A%7B%22id%22%3A%20%224cbe36399aabfcc7158ee7a66cbfffa525bb0ceab33d1ff2cff08759fe0a9b05%22%7D%7D&base64Decode=false'" """ diff --git a/x/logic/keeper/features_test.go b/x/logic/keeper/features_test.go index 1856a174..7df3ac7e 100644 --- a/x/logic/keeper/features_test.go +++ b/x/logic/keeper/features_test.go @@ -182,11 +182,13 @@ func whenTheQueryIsRun(ctx context.Context) error { return nil } -func whenTheQueryIsRunLimitedToNSolutions(ctx context.Context, n uint64) error { - tc := testCaseFromContext(ctx).request +func whenTheQueryIsRunLimitedToNSolutions(ctx context.Context, n int) error { + request := testCaseFromContext(ctx).request - limit := sdkmath.NewUint(n) - tc.Limit = &limit + limit := sdkmath.NewUint(uint64(n)) + request.Limit = &limit + + testCaseFromContext(ctx).request = request return whenTheQueryIsRun(ctx) } @@ -249,8 +251,8 @@ func initializeScenario(t *testing.T) func(ctx *godog.ScenarioContext) { ctx.Given(`the CosmWasm smart contract "([^"]+)" and the behavior:`, givenASmartContractWithAddress) ctx.Given(`the query:`, givenTheQuery) ctx.Given(`the program:`, givenTheProgram) - ctx.When(`$the query is run^`, whenTheQueryIsRun) - ctx.When(`the query is run (limited to (\d+) solutions)`, whenTheQueryIsRunLimitedToNSolutions) + ctx.When(`^the query is run$`, whenTheQueryIsRun) + ctx.When(`^the query is run \(limited to (\d+) solutions\)$`, whenTheQueryIsRunLimitedToNSolutions) ctx.Then(`the answer we get is:`, theAnswerWeGetIs) } } @@ -272,7 +274,7 @@ func newQueryClient(ctx context.Context) (types.QueryServiceClient, error) { }, ) - if err := logicKeeper.SetParams(tc.ctx.Ctx, types.DefaultParams()); err != nil { + if err := logicKeeper.SetParams(tc.ctx.Ctx, logicKeeperParams()); err != nil { return nil, err } @@ -282,6 +284,15 @@ func newQueryClient(ctx context.Context) (types.QueryServiceClient, error) { return queryClient, nil } +func logicKeeperParams() types.Params { + params := types.DefaultParams() + limits := params.Limits + maxResultCount := sdkmath.NewUint(10) + limits.MaxResultCount = &maxResultCount + params.Limits = limits + return params +} + func atoi64(s string) (int64, error) { i, err := strconv.ParseInt(s, 10, 64) if err != nil { diff --git a/x/logic/predicate/file.go b/x/logic/predicate/file.go index 9f87f206..76fa7c00 100644 --- a/x/logic/predicate/file.go +++ b/x/logic/predicate/file.go @@ -106,7 +106,7 @@ func (m ioMode) Term() engine.Term { // - Stream is the stream to be opened. // - Options is a list of options. No options are currently defined, so the list should be empty. // -// open/4 gives True when SourceSink can be opened in Mode with the given Options. +// open/4 gives True when SourceSink can be opened in Mode with the given Options. // // # Virtual File System (VFS) // @@ -190,6 +190,23 @@ func Open(vm *engine.VM, sourceSink, mode, stream, options engine.Term, k engine return engine.Unify(vm, stream, s, k, env) } +// Open3 is a predicate which opens a stream to a source or sink. +// This predicate is a shorthand for open/4 with an empty list of options. +// +// # Signature +// +// open(+SourceSink, +Mode, -Stream) +// +// where: +// - SourceSink is an atom representing the source or sink of the stream, which is typically a URI. +// - Mode is an atom representing the mode of the stream to be opened. It can be one of "read", "write", or "append". +// - Stream is the stream to be opened. +// +// open/3 gives True when SourceSink can be opened in Mode. +func Open3(vm *engine.VM, sourceSink, mode, stream engine.Term, k engine.Cont, env *engine.Env) *engine.Promise { + return Open(vm, sourceSink, mode, stream, prolog.AtomEmptyList, k, env) +} + func getLoadedSources(vm *engine.VM) map[string]struct{} { loadedField := reflect.ValueOf(vm).Elem().FieldByName("loaded").MapKeys() loaded := make(map[string]struct{}, len(loadedField)) diff --git a/x/logic/predicate/file_test.go b/x/logic/predicate/file_test.go deleted file mode 100644 index a99074a2..00000000 --- a/x/logic/predicate/file_test.go +++ /dev/null @@ -1,188 +0,0 @@ -//nolint:gocognit -package predicate - -import ( - goctx "context" - "fmt" - "net/url" - "testing" - "time" - - dbm "github.com/cosmos/cosmos-db" - "github.com/golang/mock/gomock" - "github.com/ichiban/prolog" - "github.com/ichiban/prolog/engine" - - . "github.com/smartystreets/goconvey/convey" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - - "cosmossdk.io/log" - "cosmossdk.io/store" - "cosmossdk.io/store/metrics" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/okp4/okp4d/v7/x/logic/fs" - "github.com/okp4/okp4d/v7/x/logic/testutil" -) - -func TestSourceFile(t *testing.T) { - Convey("Given test cases", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - cases := []struct { - interpreter func(ctx goctx.Context) (i *prolog.Interpreter) - query string - wantResult []testutil.TermResults - wantError error - wantSuccess bool - }{ - { - interpreter: testutil.NewLightInterpreterMust, - query: "source_file(file).", - wantSuccess: false, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "consult(file1), consult(file2), source_file(file1).", - wantResult: []testutil.TermResults{{}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "consult(file1), consult(file2), consult(file3), source_file(file2).", - wantResult: []testutil.TermResults{{}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "consult(file1), consult(file2), source_file(file3).", - wantSuccess: false, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "source_file(X).", - wantSuccess: false, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "consult(file1), consult(file2), source_file(X).", - wantResult: []testutil.TermResults{{"X": "file1"}, {"X": "file2"}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "consult(file2), consult(file3), consult(file1), source_file(X).", - wantResult: []testutil.TermResults{{"X": "file1"}, {"X": "file2"}, {"X": "file3"}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewLightInterpreterMust, - query: "source_file(foo(bar)).", - wantResult: []testutil.TermResults{}, - wantError: fmt.Errorf("error(type_error(atom,foo(bar)),source_file/1)"), - }, - - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "source_files([file]).", - wantSuccess: false, - }, - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "consult(file1), consult(file2), source_files([file1, file2]).", - wantResult: []testutil.TermResults{{}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "consult(file1), consult(file2), source_files([file1, file2, file3]).", - wantSuccess: false, - }, - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "source_files(X).", - wantSuccess: false, - }, - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "consult(file2), consult(file1), source_files(X).", - wantResult: []testutil.TermResults{{"X": "[file1,file2]"}}, - wantSuccess: true, - }, - { - interpreter: testutil.NewComprehensiveInterpreterMust, - query: "consult(file2), consult(file1), source_files([file1, X]).", - wantResult: []testutil.TermResults{{"X": "file2"}}, - wantSuccess: true, - }, - } - - for nc, tc := range cases { - Convey(fmt.Sprintf("Given the query #%d: %s", nc, tc.query), func() { - Convey("and a mocked file system", func() { - uri, _ := url.Parse("file://dump.pl") - mockedFS := testutil.NewMockFS(ctrl) - mockedFS.EXPECT().Open(gomock.Any()).AnyTimes().Return(fs.NewVirtualFile( - []byte("dumb(dumber)."), - uri, - time.Now(), - ), nil) - - Convey("and a context", func() { - db := dbm.NewMemDB() - stateStore := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) - ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) - - Convey("and a vm", func() { - interpreter := tc.interpreter(ctx) - interpreter.FS = mockedFS - interpreter.Register1(engine.NewAtom("source_file"), SourceFile) - - Convey("When the predicate is called", func() { - sols, err := interpreter.QueryContext(ctx, tc.query) - - Convey("Then the error should be nil", func() { - So(err, ShouldBeNil) - So(sols, ShouldNotBeNil) - - Convey("and the bindings should be as expected", func() { - var got []testutil.TermResults - for sols.Next() { - m := testutil.TermResults{} - err := sols.Scan(m) - So(err, ShouldBeNil) - - got = append(got, m) - } - - if tc.wantError != nil { - So(sols.Err(), ShouldNotBeNil) - So(sols.Err().Error(), ShouldEqual, tc.wantError.Error()) - } else { - 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 { - So(testutil.ReindexUnknownVariables(termGot), ShouldEqual, tc.wantResult[iGot][varGot]) - } - } - } else { - So(len(got), ShouldEqual, 0) - } - } - }) - }) - }) - }) - }) - }) - }) - } - }) -}