ichiban / prolog

The only reasonable scripting engine for Go.
MIT License
564 stars 27 forks source link

Add implementation of trace/0 #323

Open Thihup opened 1 month ago

Thihup commented 1 month ago

Hi.

Thanks for this project!

I'm considering using it as the interpreter for the Bytecode verifier of the Java Virtual Machine Specification.

However, as this specification is very dense, sometimes it is hard to know why some predicates failed. So my request would be to add trace/0 like SWI-Prolog and GNU Prolog include.

I also tried to use :- set_prolog_flag(debug, on). but it didn't help in the tracing.

triska commented 1 month ago

I would suggest to instead include library(debug). For instance, it is provided by Scryer Prolog, here is its documentation:

https://www.scryer.pl/debug

With library(debug), you can understand the reason why a predicate fails, by producing maximal generalizations that still fail. In this way, you can show explanations that closely adhere to the actual program, instead of introducing a completely different formalism (a trace) that takes you away from the actual program you want to understand.

ichiban commented 1 month ago

HI, @Thihup!

Thank you for the request! Let me break down your request first. Let's say our version of trace/0 being implemented, what would you expect it to do when it reached to the ports? Just log Call: classIsTypeSafe(Class), Exit: classIsTypeSafe(Class), etc? Or do you want an interactive debugger?

If you want an interactive debugger, I'm afraid it's probably out of the scope of this project. Since it's an embeddable scripting engine decoupled from the UI, It's not always connected to the terminal or it might not have any user interactions at all. The best we can do along this line is to provide hooks like OnCall func(goal Term, env *Env), OnExit func(goalTerm, env *Env), etc to help you implement your own interactive debugger.

If you just want logs, why don't we go with library(debug) as @triska suggests? We lack many features scryer has but we can imitate a simplified version of library(debug) today!

package main

import (
    "os"

    "github.com/ichiban/prolog"
)

func main() {
    i := prolog.New(nil, os.Stdout)

    // Simplified version of https://github.com/mthom/scryer-prolog/blob/master/src/lib/debug.pl
    if err := i.Exec(`
        :- op(900, fx, $).
        :- op(900, fx, $-).
        :- op(950, fy, *).

        $-Goal :-
            catch(Goal, Exception, (portray_clause(exception:Exception:Goal), throw(Exception))).

        $Goal :-
            portray_clause(call:Goal),
            $-Goal,
            portray_clause(exit:Goal).

        * _.

        portray_clause(Clause) :-
            write(Clause),
            nl.
    `); err != nil {
        panic(err)
    }

    if err := i.Exec(`
        classIsTypeSafe(Class) :-
            $ classClassName(Class, Name), 
            $ classDefiningLoader(Class, L),
            $ superclassChain(Name, L, Chain),
            $ Chain \= [],
            $ classSuperClassName(Class, SuperclassName),
            $ loadedClass(SuperclassName, L, Superclass),
            $ classIsNotFinal(Superclass),   
            $ classMethods(Class, Methods), 
            $ checklist(methodIsTypeSafe(Class), Methods).

        classClassName(foo, 'com.example.foo.Foo').
    `); err != nil {
        panic(err)
    }

    _ = i.QuerySolution(`classIsTypeSafe(foo).`)
}
call:classClassName(foo,_73)
exit:classClassName(foo,com.example.foo.Foo)
call:classDefiningLoader(foo,_74)
exception:error(existence_error(procedure,classDefiningLoader/2),catch/3):classDefiningLoader(foo,_108)
exception:error(existence_error(procedure,classDefiningLoader/2),catch/3):classClassName(foo,_116)

Program exited.

https://go.dev/play/p/DKDRZyIeVZy

triska commented 1 month ago

I think the portray_clause/1 imitation could use writeq/1, and append . at the end so that the resulting term can be read with read/2.