%mathpiper,def="TraceExp;Debug;Profile;DebugRun;DebugStep;DebugStepOver;DebugBreakAt;DebugRemoveBreakAt;DebugStop;DebugVerbose;DebugAddBreakpoint;BreakpointsClear;DebugCallstack;DebugBreakIf;DebugLocals;EchoTime;DebugShowCode"

/* def file definitions
TraceExp
Debug
Profile
DebugRun
DebugStep
DebugStepOver
DebugBreakAt
DebugRemoveBreakAt
DebugStop
DebugVerbose
DebugAddBreakpoint
BreakpointsClear
DebugCallstack
DebugBreakIf
DebugLocals
EchoTime
DebugShowCode
*/

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(), "Mode", "DebugBreakAt only supported in the debug build of MathPiper");
  If(filebreakpoints[file] = Empty,filebreakpoints[file]:={});
  DestructiveAppend(filebreakpoints[file],line);
];
DebugRemoveBreakAt(file,line):=
[
  Check(InDebugMode(), "Mode", "DebugRemoveBreakAt only supported in the debug build of MathPiper");
  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("DebugResult: ",Eval(PipeFromString(ReadCmdLineString("Debug> "):";")Read()));
    //      If(debugging,Echo("DebugResult: ",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 := Rest(debugcallstack);
  If(debugverbose,Echo(CustomEval'Result()," <-- ",CustomEval'Expression()));
];
Macro(Debug,{expression})
PipeToStdout()
[
   DebugStart();
   CustomEval(DebugEnter(),DebugLeave(),If(debugstopped,Check(False, "Debug", ""),[debugging:=True;debugcallstack := Rest(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(EchoTime,{expression})
[
	Local(result);
	Echo(Time()Bind(result, @expression), "seconds taken.");
	result;
];



// ClearScreenString : the ascii escape codes to clear the screen
ClearScreenString := UnicodeToString(27):"[2J":UnicodeToString(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


%/mathpiper




%mathpiper_docs,name="TraceExp",categories="User Functions;Control Flow",access="private"
*CMD TraceExp --- evaluate with tracing enabled
*CORE
*CALL
	TraceExp(expr)

*PARMS

{expr} -- expression to trace

*DESC

The expression "expr" is evaluated with the tracing facility turned
on. This means that every subexpression, which is evaluated, is shown
before and after evaluation. Before evaluation, it is shown in the
form {TrEnter(x)}, where {x}
denotes the subexpression being evaluated. After the evaluation the
line {TrLeave(x,y)} is printed, where {y} is the result of the evaluation. The indentation
shows the nesting level.

Note that this command usually generates huge amounts of output. A
more specific form of tracing (eg. {TraceRule}) is
probably more useful  for all but very simple expressions.

*E.G. notest

In> TraceExp(2+3);
	  TrEnter(2+3);
	    TrEnter(2);
	    TrLeave(2, 2);
	    TrEnter(3);
	    TrLeave(3, 3);
	    TrEnter(IsNumber(x));
	      TrEnter(x);
	      TrLeave(x, 2);
	    TrLeave(IsNumber(x),True);
	    TrEnter(IsNumber(y));
	      TrEnter(y);
	      TrLeave(y, 3);
	    TrLeave(IsNumber(y),True);
	    TrEnter(True);
	    TrLeave(True, True);
	    TrEnter(MathAdd(x,y));
	      TrEnter(x);
	      TrLeave(x, 2);
	      TrEnter(y);
	      TrLeave(y, 3);
	    TrLeave(MathAdd(x,y),5);
	  TrLeave(2+3, 5);
Result: 5;

*SEE TraceStack, TraceRule
%/mathpiper_docs




%mathpiper_docs,name="EchoTime",categories="User Functions;Input/Output"
*CMD EchoTime --- measure the time taken by a function and echos it
*STD
*CALL
	EchoTime()expr
*PARMS
{expr} -- any expression
*DESC

The function {EchoTime()expr} evaluates the expression {expr} and prints the time in seconds needed for the evaluation.
The time is printed to the current output stream.
The built-in function {Time} is used for timing.

The result is the "user time" as reported by the OS, not the real ("wall clock") time.
Therefore, any CPU-intensive processes running alongside MathPiper will not significantly affect the result of {EchoTime}.

*E.G. notest
In> EchoTime() N(MathLog(1000),40)
	0.34 seconds taken
Result: 6.9077552789821370520539743640530926228033;

*SEE Time, SystemTimer
%/mathpiper_docs