prolog icon indicating copy to clipboard operation
prolog copied to clipboard

convertAssignAny doesn't work for json([...]) Variables

Open sdemjanenko opened this issue 1 year ago • 1 comments

I have found that sol.Scan doesn't serialize JSON Variables. The issue seems to come from the convertAssignAny method's case engine.Compound branch.

When I add this logging

if err := iter.Err(); err != nil {
  fmt.Println("convertAssignAny compound iteration err", err)
  return errConversion
}

I get this output

convertAssignAny compound iteration err error(type_error(list,json([=(id,someid),=(message,foobar)])),put_char/2)

Here is an example that hits this error

results(ResultJSON) :-
  ...
  ResultJSON = json([
    id = ResultID,
    message = Message
  ]).
type Solution struct {
  Result any `prolog:"ResultJSON"`
}
// Also fails with
// type Solution struct {
//   Result struct {
//     ID string `json:"id"`
//     Message string `json:"message"`
//   } `prolog:"ResultJSON"`
// }

sols, err := p.Query(`results(ResultJSON).`)
...
for sols.Next() {
  var s Solution
  if err := sols.Scan(&s); err != nil {
     ...
  }
  ...
}

The Go code returns this error failed to run prolog eval: conversion failed from sols.Scan(&s)

Is there a way to get this Scan working? Or does does the library code need to change internally to support this?

sdemjanenko avatar Dec 17 '24 15:12 sdemjanenko

Hi! This library has nothing to do with JSON. So, it doesn't know how to convert json(_), Key=Value, nor any other compound terms other than lists.

We can flatten the results so that we can address each field by a variable:
package main

import (
	"encoding/json"
	"fmt"

	"github.com/ichiban/prolog"
)

func main() {
	p := prolog.New(nil, nil)
	if err := p.Exec(`
		result(ID, Message) :-
			results(json(KVs)),
			member(id=ID, KVs),
			member(message=Message, KVs).
	
		results(json([id=someid, message=foobar])).
		results(json([id=anotherid, message=hello])).
	`); err != nil {
		panic(err)
	}

	sols, err := p.Query(`result(ID, Message).`)
	if err != nil {
		panic(err)
	}
	for sols.Next() {
		var s struct {
			ID      string `json:"id"`
			Message string `json:"message"`
		}
		if err := sols.Scan(&s); err != nil {
			panic(err)
		}
		b, err := json.Marshal(&s)
		if err != nil {
			panic(err)
		}
		fmt.Printf("%s\n", string(b))
	}
	if err := sols.Err(); err != nil {
		panic(err)
	}
}
{"id":"someid","message":"foobar"}
{"id":"anotherid","message":"hello"}

Program exited.

https://go.dev/play/p/3VWBa-sz1b3

Alternatively, we can define our custom `prolog.Scanner` but it'll be unnecessarily hard:
package main

import (
	"fmt"

	"github.com/ichiban/prolog"
	"github.com/ichiban/prolog/engine"
)

type JSONObject map[string]any

var _ prolog.Scanner = JSONObject{}

func (j JSONObject) Scan(vm *engine.VM, term engine.Term, env *engine.Env) error {
	return nil // TODO: the hard work.
}

func main() {
	p := prolog.New(nil, nil)
	if err := p.Exec(`
		results(json([id=someid, message=foobar])).
		results(json([id=anotherid, message=hello])).
	`); err != nil {
		panic(err)
	}

	sols, err := p.Query(`results(ResultJSON).`)
	if err != nil {
		panic(err)
	}
	for sols.Next() {
		var s struct {
			ResultJSON JSONObject
		}
		if err := sols.Scan(&s); err != nil {
			panic(err)
		}
		fmt.Printf("%#v\n", &s)
	}
	if err := sols.Err(); err != nil {
		panic(err)
	}
}

ichiban avatar Dec 18 '24 09:12 ichiban