


LocalSymbols(TraceStart,TraceEnter,TraceLeave,DebugStart,DebugEnter, 
             DebugLeave,ProfileStart,ProfileEnter,result,
             WriteLines,ClearScreenString,Debug'FileLoaded, Debug'FileLines, Debug'NrLines,
             debugstepoverfile, debugstepoverline) [

TraceStart() := [indent := 0;];
TraceEnter() :=
[
    indent++;
    Space(2*indent);
    Echo("Enter ",CustomEval'Expression());
];
TraceLeave() :=
[
    Space(2*indent);
    Echo("Leave ",CustomEval'Result());
    indent--;
];
Macro(TraceExp,{expression})
[
    TraceStart();
    CustomEval(TraceEnter(),TraceLeave(),CustomEval'Stop(),@expression);
];



DebugStart():=
[
   debugging:=True;
   debugstopdepth := -1;
   breakpoints:={};
   filebreakpoints := {};
   debugstopped:=False;
   debugverbose:=False;
   debugcallstack:={};
   breakpredicate:=False;
];
DebugRun():= [debugging:=False;True;];
DebugStep():=[debugging:=False;nextdebugging:=True;];

DebugStepOver():=
[
  debugging:=False;
  debugstepoverfile := DebugFile(CustomEval'Expression());
  debugstepoverline := DebugLine(CustomEval'Expression());
  debugstopdepth := Length(debugcallstack);
];
DebugBreakAt(file,line):=
[
  Check(InDebugMode(),"DebugBreakAt only supported in the debug build of Yacas");
  If(filebreakpoints[file] = Empty,filebreakpoints[file]:={});
  DestructiveAppend(filebreakpoints[file],line);
];
DebugRemoveBreakAt(file,line):=
[
  Check(InDebugMode(),"DebugRemoveBreakAt only supported in the debug build of Yacas");
  If(filebreakpoints[file] = Empty,filebreakpoints[file]:={});
  filebreakpoints[file] := Difference(filebreakpoints[file],{line});
];


DebugStop():=[debugging:=False;debugstopped:=True;CustomEval'Stop();];
DebugVerbose(verbose):=[debugverbose:=verbose;];
DebugAddBreakpoint(fname_IsString) <-- [ breakpoints := fname:breakpoints;];
Macro(DebugBreakIf,{predicate})
[
  breakpredicate:= Hold(@predicate);
];

BreakpointsClear() <--
[
  breakpredicate:=False;
  breakpoints := {};
];
Macro(DebugLocals,{})
[
  Echo("");
  Echo("*************** Current locals on the stack ****************");
  ForEach(item,CustomEval'Locals())
  [
    Echo("      ",item," : ",Eval(item));
  ];
  Echo("");
];
DebugCallstack() <--
[
  Echo("");
  Echo("*************** Function call stack ****************");
  ForEach(item,debugcallstack)
  [
    if(IsFunction(item))
      Echo("      Function ",Type(item)," : ",item)
    else
      Echo("      Variable ",item);
  ];
  Echo("");
];

Macro(DebugEnter,{})
[
  debugcallstack := CustomEval'Expression():debugcallstack;
  // custom breakpoint (custom predicate thought up by the programmer)
  If(debugging = False And
      Eval(breakpredicate) = True,
      [
        breakpredicate:=False;
        debugging:=True;
      ]);

  If(debugging = False And InDebugMode(),
  [  
    Local(file,line);
    file := DebugFile(CustomEval'Expression());
    If(filebreakpoints[file] != Empty,
    [
      line := DebugLine(CustomEval'Expression());
      If(Not(file = debugstepoverfile And line = debugstepoverline) And
         Contains(filebreakpoints[file],line),
         [
           debugging:=True;
         ]
        );
    ]);
  ]);


  // the standard breakpoint
  If(debugging = False And
      IsFunction(CustomEval'Expression()) And
      Contains(breakpoints,Type(CustomEval'Expression())),   debugging:=True);
   nextdebugging:=False;
   If (debugging,
   [
     If(InDebugMode(),DebugShowCode());
     Echo(">>> ",CustomEval'Expression());
     While(debugging)
     [
        Echo("DebugOut> ",Eval(FromString(ReadCmdLineString("Debug> "):";")Read()));
    //      If(debugging,Echo("DebugOut> ",debugRes));
       If(IsExitRequested(),debugging:=False);
     ];
    ]);
   debugging:=nextdebugging;

   If(IsExitRequested(),debugstopped:=True);

];
Macro(DebugLeave,{})
[
  If(debugging = False And debugstopdepth >= 0 And Length(debugcallstack) = debugstopdepth,
  [
    debugstepoverline := -1;
    debugging := True;
    debugstopdepth := -1;
  ]);

  debugcallstack := Tail(debugcallstack);
  If(debugverbose,Echo(CustomEval'Result()," <-- ",CustomEval'Expression()));
];
Macro(Debug,{expression})
ToStdout()
[
   DebugStart();
   CustomEval(DebugEnter(),DebugLeave(),If(debugstopped,Check(False,""),[debugging:=True;debugcallstack := Tail(debugcallstack);]),@expression);
];


ProfileStart():=
[
    profilefn:={};
];
10 # ProfileEnter()_(IsFunction(CustomEval'Expression())) <--
[
    Local(fname);
    fname:=Type(CustomEval'Expression());
    If(profilefn[fname]=Empty,profilefn[fname]:=0);
    profilefn[fname] := profilefn[fname]+1;
];
Macro(Profile,{expression})
[
    ProfileStart();
    CustomEval(ProfileEnter(),True,CustomEval'Stop(),@expression);
    ForEach(item,profilefn)
      Echo("Function ",item[1]," called ",item[2]," times");
];

/// Measure the time taken by evaluation and print results.
Macro(Time,{expression})
[
	Local(result);
	Echo(GetTime(Set(result, @expression)), "seconds taken");
	result;
];




// ClearScreenString : the ascii escape codes to clear the screen
ClearScreenString := CharString(27):"[2J":CharString(27):"[1;1H";

// WriteLines: do the actual outputting of lines of a file to screen
WriteLines(filename,lines,from,nrlines,breakpoints,current):=
[
  Local(i,nr);
  nr:=Length(lines);
  WriteString(ClearScreenString);
  Echo("File ",filename," at line ",current);
  For(i:=from,i<from+nrlines And i<nr,i++)
  [

    if (current = i) 
      WriteString(">")
    else
      WriteString(" ");
    if (Contains(breakpoints,i)) 
      WriteString("*")
    else
      WriteString(" ");
    WriteString("| ");
    Echo(lines[i][1]);
  ];
];
Debug'FileLoaded := "";
Debug'FileLines := {};
Debug'NrLines:=20;

//
// DebugShowCode: show the part of the file we are currently executing (based on the 
// value returned by CustomEval'Expression() ).
//
// Currently unimplemented, should we remove?
//
DebugShowCode():=
[
  False;
];

]; //LocalSymbols





