Skip to content

Commit

Permalink
add async Query iterators
Browse files Browse the repository at this point in the history
update Trealla to guregu/trealla 0.5.1
  • Loading branch information
guregu committed Sep 28, 2022
1 parent 74e20dc commit cbeab1b
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 171 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It's pretty fast. Not as fast as native Trealla, but pretty dang fast (2-5x slow
### Caveats

- Alpha status, API will change.
- Queries are findall'd and won't return answers until they terminate.
- ~~Queries are findall'd and won't return answers until they terminate.~~
- Doesn't work on Windows ([wasmer-go issue](https://github.com/wasmerio/wasmer-go/issues/69)).
- Works great on WSL.
- ~~Currently interpreters are ephemeral, so you have to reconsult everything each query (working on this)~~.
Expand All @@ -26,9 +26,17 @@ func main() {
// load the interpreter and (optionally) grant access to the current directory
pl := trealla.New(trealla.WithPreopen("."))
// run a query; cancel context to abort it
answer, err := pl.Query(ctx, "member(X, [1, foo(bar), c]).")
// get the second substitution (answer) for X
x := answer.Solutions[1]["X"] // trealla.Compound{Functor: "foo", Args: ["bar"]}
query := pl.Query(ctx, "member(X, [1, foo(bar), c]).")
// iterate through answers
for query.Next(ctx) {
answer := query.Current()
x := answer.Solution["X"]
fmt.Println(x) // 1, trealla.Compound{Functor: "foo", Args: ["bar"]}, "c"
}
// make sure to check the query for errors
if err := query.Err(); err != nil {
panic(err)
}
}
```

Expand Down
82 changes: 82 additions & 0 deletions trealla/answer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package trealla

import (
"encoding/json"
"fmt"
"strings"
)

// Answer is a query result.
type Answer struct {
// Query is the original query goal.
Query string
// Solution (substitutions) for a successful query.
// Indexed by variable name.
Solution Solution `json:"answer"`
// Output is captured stdout text from this query.
Output string
}

type response struct {
Answer
Result queryStatus
Error json.RawMessage // ball
}

func newAnswer(program, raw string) (Answer, error) {
if len(strings.TrimSpace(raw)) == 0 {
return Answer{}, ErrFailure
}

start := strings.IndexRune(raw, stx)
end := strings.IndexRune(raw, etx)
nl := strings.IndexRune(raw[end+1:], '\n') + end + 1
butt := len(raw)
if nl >= 0 {
butt = nl
}

output := raw[start+1 : end]
js := raw[end+1 : butt]

resp := response{
Answer: Answer{
Query: program,
Output: output,
},
}

dec := json.NewDecoder(strings.NewReader(js))
dec.UseNumber()
if err := dec.Decode(&resp); err != nil {
return resp.Answer, fmt.Errorf("trealla: decoding error: %w", err)
}

switch resp.Result {
case statusSuccess:
return resp.Answer, nil
case statusFailure:
return resp.Answer, ErrFailure
case statusError:
ball, err := unmarshalTerm(resp.Error)
if err != nil {
return resp.Answer, err
}
return resp.Answer, ErrThrow{Ball: ball}
default:
return resp.Answer, fmt.Errorf("trealla: unexpected query status: %v", resp.Result)
}
}

// queryStatus is the status of a query answer.
type queryStatus string

// Result values.
const (
// statusSuccess is for queries that succeed.
statusSuccess queryStatus = "success"
// statusFailure is for queries that fail (find no answers).
statusFailure queryStatus = "failure"
// statusError is for queries that throw an error.
statusError queryStatus = "error"
)
14 changes: 10 additions & 4 deletions trealla/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ func BenchmarkQuery(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = pl.Query(ctx, "X=1, write(X)")
if err != nil {
q := pl.Query(ctx, "X=1, write(X)")
if !q.Next(ctx) {
b.Fatal("no answer")
}
if q.Err() != nil {
b.Fatal(err)
}
}
Expand All @@ -28,8 +31,11 @@ func BenchmarkTak(b *testing.B) {
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := pl.Query(ctx, "consult('testdata/tak'), run")
if err != nil {
q := pl.Query(ctx, "consult('testdata/tak'), run")
if !q.Next(ctx) {
b.Fatal("no answer")
}
if q.Err() != nil {
b.Fatal(err)
}
}
Expand Down
Loading

0 comments on commit cbeab1b

Please sign in to comment.