pengine

package module
v0.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2022 License: BSD-2-Clause Imports: 16 Imported by: 0

README

pengine GoDoc

import "github.com/guregu/pengine"

Pengines: Prolog Engines client for Go. Pengines's motto is "Web Logic Programming Made Easy". This library lets you query SWI-Prolog from Go and from ichiban/prolog, a Prolog interpreter written in Go.

Development Status: beta. Feedback and contributions welcome!

Usage

This library supports both the JSON and Prolog format APIs. Additionally, an RPC predicate for ichiban/prolog is provided.

Configuration
client := pengine.Client{
    // URL of the pengines server, required.
    URL: "http://localhost:4242/pengine",

    // Application name, optional.
    Application: "pengines_sandbox",
    // Chunk is the number of query results to accumulate in one response. 1 by default.
    Chunk: 10,
    // SourceText is Prolog source code to load (optional, currently only supported for the JSON format).
    SourceText: "awesome(prolog).\n",
    // SourceURL specifies a URL of Prolog source for the pengine to load (optional).
    SourceURL: "https://example.com/script.pl",
}
JSON API

client.Ask returns an Answers[Solutions] iterator, but you can use the generic pengine.Ask[T] function to use your own types.

answers, err := client.Ask(ctx, "between(1,6,X)")
if err != nil {
	panic(err)
}
var got []json.Number
for as.Next() {
	cur := as.Current()
	x := cur["X"]
	got = append(got, x.Number)
}
// got = {"1", "2", "3", ...}
if err := answers.Error(); err != nil {
	panic(err)
}

You can also use client.Create to create a pengine and Ask it later. If you need to stop a query early or destroy a pengine whose automatic destruction was disabled, you can call client.Close.

Prolog API

client.AskProlog returns ichiban/prolog/engine.Term objects. This uses the ichiban/prolog parser to handle results in the Prolog format. Use this for the most accurate representation of Prolog terms, but be aware that the parser does not support all of SWI's bells and whistles.

You can also call pengine.Term.Prolog() to get Prolog terms from the JSON results, but they might be lossy in terms of Prolog typing.

Warning about Unicode atoms

SWI-Prolog's defaults around Unicode cause errors for our Prolog parser at the moment, so we need to tweak the configuration.

  • We can't handle SWI's \uXXXX Unicode escapes.
    • This can interfere with unification of query results and cause unexpected "no solutions found" errors.

Luckily, Pengines makes it easy for us to customize the result formatter and fix this. Just define a pengines:write_result/3 hook for the prolog format on your Pengines server.

Here's an example of a working Pengines configuration:

pengines:write_result(prolog, Event, _) :-
    format('Content-type: text/x-prolog; charset=UTF-8~n~n'),
    write_term(Event,
               [ quoted(true),
                 quote_non_ascii(true),            % 👈
                 character_escapes_unicode(false), % 👈
                 ignore_ops(true),
                 fullstop(true),
                 blobs(portray),
                 portray_goal(pengines:portray_blob),
                 nl(true)
               ]).
RPC for ichiban/prolog

You can call remote pengines from ichiban/prolog, a Go Prolog, as well.

pengine_rpc/3 mostly works like its SWI-Prolog counterpart. Not all the options are implemented yet, but it seems to work OK!

interpreter.Register3("pengine_rpc", pengine.RPC)
:- pengine_rpc('http://localhost:4242/pengine', between(1,50,X), [chunk(10)]), write(X), nl.
% 1 2 3 4 ...

Tests

Currently the tests are rather manual:

% from swipl
consult(example_server).
# from OS terminal
go test -v

Change the pengines server URL used by the tests with the --pengines-server command line flag.

Other languages

Check out these pengines clients for other languages.

Thanks

  • Torbjörn Lager, Jan Wielemaker, and everyone else who has contributed to SWI-Prolog and Pengines.
  • @ian-andrich for the Python implementation, which was a handy reference for making this.
  • @ichiban for the awesome Prolog interpreter in Go.
  • Markus Triska for the wonderful tutorial series The Power of Prolog, which started me on my logic programming journey.

License

BSD 2-clause.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrDead is an error returned when the pengine has died.
	ErrDead = fmt.Errorf("pengine: died")
	// ErrFailed is an error returned when a query failed (returned no results).
	ErrFailed = fmt.Errorf("pengine: query failed")
)

Functions

func RPC

func RPC(url, query, options engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise

RPC is like pengine_rpc/3 from SWI, provided for as a native predicate for ichiban/prolog. This is a native predicate for Prolog. To use the API from Go, use AskProlog.

Supports the following options: application(Atom), chunk(Integer), src_text(Atom), src_url(Atom), debug(Boolean).

See: https://www.swi-prolog.org/pldoc/man?predicate=pengine_rpc/3

func RPCWithInterpreter added in v0.2.3

func RPCWithInterpreter(p *prolog.Interpreter) func(url, query, options engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise

RPCWithInterpreter is like RPC but allows you to specify an interpreter to use for parsing results. Useful for handling custom operators.

Types

type Answers

type Answers[T any] interface {
	// Next prepares the next query result and returns true if there is a result.
	Next(context.Context) bool
	// Current returns the current query result.
	Current() T
	// Close kills this query (in pengine terms, stops it).
	// It is not necessary to call Close if all results were iterated through unless the pengine is configured otherwise.
	Close() error
	// Cumulative returns the cumulative time taken by this query, as reported by pengines.
	Cumulative() time.Duration
	// Engine returns this query's underlying Engine.
	Engine() *Engine
	// Err returns the error encountered by this query.
	// This should always be checked after iteration finishes.
	// Returns ErrFailed if the query failed at least once without succeeding at least once.
	Err() error
}

Answers is an iterator of query results. Use Next to prepare a result of type T and then Current to obtain it. Make sure to check Err after you finish iterating. Err will return ErrFailed if the query failed at least once without succeeding at least once.

answers, err := client.Ask(ctx, "between(1,6,X)")
if err != nil {
	panic(err)
}
var got []json.Number
for as.Next() {
	cur := as.Current()
	x := cur["X"]
	got = append(got, x.Number)
}
// got = {"1", "2", "3", ...}
if err := answers.Err(); err != nil {
	panic(err)
}

func Ask

func Ask[T any](ctx context.Context, c Client, query string) (Answers[T], error)

Ask creates a new pengine with the given initial query, executing it and returning an answers iterator. Queries must be in Prolog syntax without the terminating period or linebreak, such as:

between(1,3,X)

This uses the JSON format, so T can be anything that can unmarshal from the pengine result data. This package provides a Solutions type that can handle most results in a general manner.

func AskProlog

func AskProlog(ctx context.Context, c Client, query string) (Answers[engine.Term], error)

AskProlog creates a new pengine with the given initial query, executing it and returning an answers iterator. Queries must be in Prolog syntax without the terminating period or linebreak, such as:

between(1,3,X)

This uses the Prolog format and answers are ichiban/prolog terms. Because ichiban/prolog is used to interpret results, using SWI's nonstandard syntax extensions like dictionaries may break it.

type Client

type Client struct {
	// URL of the pengines server, required.
	URL string
	// Application name, optional. (example: "pengines_sandbox")
	Application string
	// Chunk is the number of query results to accumulate in one response. 1 by default.
	Chunk int

	// SourceText is Prolog source code to load (optional).
	SourceText string
	// SourceURL specifies a URL of Prolog source for the pengine to load (optional).
	SourceURL string

	// HTTP is the HTTP client used to make API requests.
	// If nil, http.DefaultClient is used.
	HTTP *http.Client

	// Interpreter is the Prolog interpreter used for decoding Prolog-format responses.
	// This is useful for handling custom operators.
	// If nil, a default interpreter will be used.
	Interpreter *prolog.Interpreter

	// If true, prints debug logs.
	Debug bool
}

Client is a Pengines endpoint.

func (Client) Ask

func (c Client) Ask(ctx context.Context, query string) (Answers[Solution], error)

Ask creates a new engine with the given initial query and executes it, returning the answers iterator.

func (Client) Create

func (c Client) Create(ctx context.Context, destroy bool) (*Engine, error)

Create creates a new pengine. Call Engine's Ask method to query it. If destroy is true, the pengine will be automatically destroyed when a query completes. If destroy is false, it is the caller's responsibility to destroy the pengine with Engine.Close.

type Compound

type Compound struct {
	Functor string `json:"functor"`
	Args    []Term `json:"args"`
}

Compound is a Prolog compound: functor(args0, args1, ...).

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine is a pengine.

func (*Engine) Ask

func (e *Engine) Ask(ctx context.Context, query string) (Answers[Solution], error)

Ask queries the pengine, returning an answers iterator.

func (*Engine) AskProlog

func (e *Engine) AskProlog(ctx context.Context, query string) (Answers[engine.Term], error)

AskProlog queries this engine with Prolog format results. Queries must be in Prolog syntax without the terminating period or linebreak, such as:

between(1,3,X)

Because ichiban/prolog is used to interpret results, using SWI's nonstandard syntax extensions like dictionaries may break it.

func (*Engine) Close

func (e *Engine) Close() error

Close destroys this engine. It is usually not necessary to do this as pengines will destroy themselves automatically unless configured differently.

func (*Engine) ID

func (e *Engine) ID() string

ID return this pengine's ID.

func (*Engine) Ping

func (e *Engine) Ping(ctx context.Context) error

Pings this pengine, returning ErrDead if it is dead.

type Error

type Error struct {
	Code string
	Data string
}

Error is an error from the pengines API.

func (Error) Error

func (err Error) Error() string

Error implements the error interface.

type Solution

type Solution map[string]Term

Solution is a mapping of variable names to values.

answers, err := Ask[Solution](ctx, "between(1,6,X)")
// ...
for as.Next() {
	cur := as.Current()
	// grab variables by name
	x := cur["X"]
}

type Term

type Term struct {
	Atom       *string
	Number     *json.Number
	Compound   *Compound
	Variable   *string
	Boolean    *bool
	List       []Term
	Dictionary map[string]Term
	Null       bool
}

Term represents a Prolog term. One of the fields should be "truthy". This can be handy for parsing query results in JSON format.

func (Term) Prolog

func (t Term) Prolog() engine.Term

Prolog converts this term to an ichiban/prolog term. Because pengine's JSON format is lossy in terms of Prolog types, this might not always be accurate. There is ambiguity between atoms, strings, and variables. If you are mainly dealing with Prolog terms, use AskProlog to use the Prolog format instead.

func (*Term) UnmarshalJSON

func (t *Term) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL