AIWiki RecentChanges Index

AiDebugger

Preliminary Remarks

On the HoopyIdeas page we did talk about the AiDebugger being able to verify that a program was working correctly by checking its performance against a specification. Now, that sounds a bit difficult because we have to decide what a specification is. So let’s draw back from that to start with.

We also said that the AiDebugger would look at the source code debug trace, but we can do better than that by making the AiDebugger an interpreter, i.e. it is the debugger - as its name suggests. This makes it more powerful because not only can it run the target program, it can modify variables, change the control flow and generally explore the behaviour of the target program more fully.

It just so happens that Prolog is a wonderful language for writing interpreters, so I will explore the AiDebugger idea further using Prolog. I will be using WinProlog? from LPA. This is a standard Edinburg Syntax Prolog, but it costs money. However, there are many free prologs around which will the code I will present. I would recommend SWI-Prolog because I’ve had good experience with it - and its keeper, Jan Wielemaker, is a good chap !!

Introducing the trivial AiDebugger version 0.0

OK, let’s write version 0.0 of our AiDebugger:

	'AiDebuggerV0.0'(Program) :- 
	  Program.

There, finished !! I knew you’d be impressed.

Trivial though the above interpreter is it actually does a bit of work. What we need next is a simple test program to run our interpreter on. How about a program that sums the numbers in a list:

	sum_list([],0).
	sum_list([Head|Tail],Sum) :-
	  sum_list(Tail,RemainingSum),
	  Sum is Head + RemainingSum.

Now run the AiDebugger on the sum_list predicate with a list of numbers:

	| ?- 'AiDebuggerV0.0'(sum_list([1,2,3],S)).
	S = 6

Note that

  | ?-

is a prompt by the Prolog environment, I typed in

  'AiDebugger0'(sum_list([1,2,3],S)).

and the Prolog environment responded with

  S = 6

We could now put in some extra gubbins to our AiDebugger so that it could prompt the user for the input parameters to the program, in our example this would be the list [1,2,3], and it could tell you that the answer was 6. I don’t think is very interesting at this stage, what is interesting is what happens if things go wrong. For example what happens if you give the program the list [a,2,3]. In WinProlog? the AiDebugger loses control and the WinProlog? system printed out an error message complaining that it can’t do the arithmetic a+2.

And now the slightly less trivial AiDebugger version 0.1

This is where you find out how reflective/introspective your programming language is. In most self-respecting Prologs you can catch such errors using the predicate catch/3. So let’s write a slightly more sophisticated AiDebugger - version 0.1:

	'AiDebuggerV0.1'(Program) :- 
	  catch(ErrorCode,(Program,!),Culprit),
	  report(ErrorCode,Culprit).
	report(0,_) :-
	  write(`Your program succeeded `),
	  nl,
	  !.
	report(-1,_) :-
	  write(`Your program failed`),
	  nl,
	  !.
	report(ErrorCode,Culprit) :-
	  write(`Your program got an error `),
	  write(ErrorCode),
	  write(` while running the predicate `),
	  write(Culprit),
	  nl.

This program has got a bit of verbiage to make it seem more intelligent. So lets see it in action:

	| ?- 'AiDebuggerV0.1'(sum_list([1,2,3,4],Sum)).
	Your program succeeded Sum = 10 

And perhaps slightly more impressive:

	| ?- 'AiDebuggerV0.1'(sum_list([a,2,3,4],Sum)).
	Your program got an error 23 while running the predicate (is) / 2
	Sum = _

Well, it’s a start, now we need to actually trace through the target program rather than just calling the program.

AiDebugger Version 0.2: with a trace facility for “pure” Prolog

We can start by augmenting our AiDebugger to trace through pure prolog, i.e. prolog with no cuts, no disjunction, no “not” and no meta-predicates like bagof/3, setof/3, findfall/3. For this we will need to know which predicates are built-in. Let’s define a predicate system/1 which succeeds if its argument is a built-in. In WinProlog? system/1 is defined by:

	system(Predicate) :-
	  predicate_property(Predicate,built-in).

Line by line interpreting is achieved with:

	trace(true) :-
	  !.
	trace((Goal1,Goal2)) :-
	  !,
	  trace(Goal1),
	  trace(Goal2).
	trace(Goal) :-
	  system(Goal),
	  !,
	  write('system call':Goal),
	  nl,
	  Goal.
	trace(Goal) :-
	  clause(Goal,Body),
	  write('calling':Goal),
	  nl,
	  trace(Body).
	
	system(Predicate) :-	
	  predicate_property(Predicate,built_in).

although we do need to make the target program completely dynamic for clause/2 to work.

	:- dynamic sum_list/2.

so now if we run our test program using trace we get:

	| ?- trace(sum_list([1,2,3],Sum)).
	calling : sum_list([1,2,3],_291474)	
	calling : sum_list([2,3],_292128)
	calling : sum_list([3],_292764)
	calling : sum_list([],0)
	system call : (_292764 is 3 + 0)
	system call : (_292128 is 2 + 3)
	system call : (_291474 is 1 + 5)
	Sum = 6

and our program is clearly on the way to full enlightenment !!