package org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.repl.debug;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Attribute;
import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.Frame;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.RascalRuntimeException;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.Thrown;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.ideservices.IDEServices;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.repl.CommandExecutor;
import org.rascalmpl.library.lang.rascal.syntax.RascalParser;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.parser.Parser;
import org.rascalmpl.parser.gtd.io.InputConverter;
import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener;
import org.rascalmpl.parser.uptr.UPTRNodeFactory;
import org.rascalmpl.parser.uptr.action.NoActionExecutor;
import org.rascalmpl.repl.BaseREPL;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.value.IConstructor;
import org.rascalmpl.value.ISourceLocation;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IValueFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.uptr.ITree;
import org.rascalmpl.values.uptr.TreeAdapter;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class BreakPointManager {
enum DEBUG_MODE { STEP, SKIP, NEXT, BREAK, RETURN };
private List<BreakPoint> breakpoints;
int uid;
private boolean shouldQuit= false;
DEBUG_MODE mode = DEBUG_MODE.BREAK;
private final String FAINT_ON = Ansi.ansi().a(Attribute.INTENSITY_FAINT).toString();
private final String FAINT_OFF = Ansi.ansi().a(Attribute.INTENSITY_BOLD_OFF).toString();
private final String RED_ON = Ansi.ansi().fg(Ansi.Color.RED).toString();
private final String RED_OFF = Ansi.ansi().fg(Ansi.Color.BLACK).toString();
Frame currentFrame = null; // next mode, only break in current function
Frame returnFrame = null; // only break on return from this frame
private final PathConfig pcfg;
private PrintWriter stdout;
private IDEServices ideServices;
private final String listingIndent = "\t";
private boolean autoList = true;
private final int defaultListingDelta = 5;
//RascalHighlighter highlighter;
Cache<String, IValue> parsedModuleCache;
IValueFactory vf = ValueFactoryFactory.getValueFactory();
BreakPointManager(PathConfig pcfg, PrintWriter stdout, IDEServices ideServices){
this.stdout = stdout;
this.pcfg = pcfg;
this.ideServices = ideServices;
breakpoints = new ArrayList<>();
uid = 1;
// highlighter = new RascalHighlighter()
// .setKeywordMarkup(Ansi.ansi().bold().toString(),
// Ansi.ansi().boldOff().toString())
// .setCommentMarkup(Ansi.ansi().fg(Ansi.Color.GREEN).toString(),
// Ansi.ansi().fg(Ansi.Color.BLACK).toString());
parsedModuleCache = Caffeine.newBuilder()
.weakValues()
.maximumSize(5)
.build();
}
void reset(){
mode = DEBUG_MODE.BREAK;
currentFrame = null;
returnFrame = null;
}
void edit(Path file){
ideServices.edit(file);
}
private boolean isBlackListed(Frame frame){
return frame == null || frame.src.getPath().equals(CommandExecutor.consoleInputPath) ||
frame.function.getPrintableName().endsWith("_init") ||
frame.src.getPath().endsWith(".mu");
}
void setStdOut(PrintWriter stdout){
this.stdout = stdout;
}
void setAutoList(boolean autoList){
this.autoList = autoList;
}
void setBreakMode(Frame frame){
if(mode == DEBUG_MODE.RETURN){
return;
}
mode = DEBUG_MODE.BREAK;
this.currentFrame = null;
}
void setStepMode(Frame frame){
mode = isBlackListed(frame) ? DEBUG_MODE.SKIP : DEBUG_MODE.STEP;
this.currentFrame = null;
}
void setNextMode(Frame currentFrame){
mode = DEBUG_MODE.NEXT;
this.currentFrame = currentFrame;
}
void setReturnMode(Frame currentFrame){
mode = DEBUG_MODE.RETURN;
this.returnFrame = currentFrame;
}
boolean hasEnabledBreakPoints(){
for(BreakPoint breakpoint : breakpoints){
if(breakpoint.enabled){
return true;
}
}
return false;
}
boolean doAutoList(Frame frame){
if(autoList){
listingDirective(frame, defaultListingDelta);
}
return true;
}
BreakPoint getBreakPoint(int reqId){
for(BreakPoint breakpoint : breakpoints){
if(breakpoint.id == reqId){
return breakpoint;
}
}
return null;
}
void requestQuit(){
shouldQuit = true;
}
boolean shouldContinue(){
return !shouldQuit;
}
/****************************************************************/
/* Handle all frame events */
/****************************************************************/
boolean matchOnObserve(Frame frame){
if(!frame.src.hasLineColumn()){
return false;
}
switch(mode){
case STEP:
if(isBlackListed(frame)){
mode = DEBUG_MODE.SKIP;
return false;
}
return doAutoList(frame);
case SKIP:
return false;
case NEXT:
if(frame != currentFrame){
return false;
}
return doAutoList(frame);
case RETURN:
return false;
case BREAK:
for(BreakPoint bp : breakpoints){
if(bp.matchOnObserve(frame)){
return doAutoList(frame);
}
}
}
return false;
}
boolean matchOnEnter(Frame frame){
if(!frame.src.hasLineColumn()){
return false;
}
switch(mode){
case STEP:
if(isBlackListed(frame)){
mode = DEBUG_MODE.SKIP;
return false;
}
//return doAutoList(frame);
return true;
case SKIP:
if(!isBlackListed(frame)){
mode = DEBUG_MODE.STEP;
//return doAutoList(frame);
return true;
}
return false;
case NEXT:
return false;
case RETURN:
return false;
case BREAK:
for(BreakPoint bp : breakpoints){
if(bp.matchOnEnter(frame)){
mode = DEBUG_MODE.STEP;
//return doAutoList(frame);
return true;
}
}
}
return false;
}
boolean matchOnLeave(Frame frame, Object rval){
if(!frame.src.hasLineColumn()){
return false;
}
switch(mode){
case STEP:
if(isBlackListed(frame.previousCallFrame)){
mode = DEBUG_MODE.SKIP;
}
return doAutoList(frame);
case SKIP:
if(!isBlackListed(frame.previousCallFrame)){
mode = DEBUG_MODE.STEP;
}
return false;
case NEXT:
return false;
case RETURN:
if(returnFrame == frame){
returnFrame = null;
mode = DEBUG_MODE.BREAK;
if(autoList){
listingDirective(frame, defaultListingDelta);
}
stdout.println(BaseREPL.PRETTY_PROMPT_PREFIX + "Function " + frame.function.getPrintableName() + " will return: " + rval + BaseREPL.PRETTY_PROMPT_POSTFIX);
stdout.flush();
return true;
}
return false;
case BREAK:
for(BreakPoint bp : breakpoints){
if(bp.matchOnLeave(frame)){
return doAutoList(frame);
}
}
}
return false;
}
boolean matchOnException(Frame frame, Thrown thrown){
stdout.print(RED_ON);
thrown.printStackTrace(stdout);
stdout.print(RED_OFF);
return doAutoList(frame);
}
/****************************************************************/
/* Handle all debug directives */
/****************************************************************/
// break directive during debug mode
void breakDirective(Frame frame, String[] args) throws NumberFormatException {
if(args.length == 2 && args[1].matches("[0-9]+")){ // break <lino>
int lino = Integer.parseInt(args[1]);
String path = frame.src.getPath();
add(new LineBreakpoint(uid++, path, lino));
} else {
breakDirective(args);
}
}
// break directive outside debug mode
public void breakDirective(String[] args) throws NumberFormatException {
if(args.length == 1){ // break
printBreakPoints(stdout);
return;
}
if(args.length == 2){ // break <functionName>
add(new FunctionEnterBreakpoint(uid++, args[1]));
}
if(args.length == 3){ // break <moduleName> <lino>
ISourceLocation modSrc = pcfg.resolveModule(args[1]);
if(modSrc != null){
add(new LineBreakpoint(uid++, modSrc.getPath(), Integer.parseInt(args[2])));
} else {
stdout.println("Module " + args[1] + " not found");
stdout.flush();
}
}
}
public void ignoreDirective(String[] args) throws NumberFormatException {
int ignoreCnt = 0;
if(args.length == 3){
ignoreCnt = Integer.parseInt(args[2]);
}
if(args.length == 2 || args.length == 3){
int bkpt = Integer.parseInt(args[1]);
BreakPoint breakpoint = getBreakPoint(bkpt);
if(breakpoint != null){
breakpoint.setIgnore(ignoreCnt);
return;
}
stdout.println("Breakpoint #" + bkpt + " is not defined");
return;
}
stdout.println("ignore requires 1 or 2 arguments");
}
void returnDirective(Frame frame, String[] args){
setReturnMode(frame);
}
public void clearDirective(String[] args) throws NumberFormatException {
if(args.length == 1){
breakpoints = new ArrayList<BreakPoint>();
return;
}
ArrayList<BreakPoint> newBreakpoints = new ArrayList<BreakPoint>();
next:
for(BreakPoint breakpoint : breakpoints){
for(int i = 1; i < args.length; i++){
int bpno = Integer.parseInt(args[i]);
if(breakpoint.getId() == bpno){
continue next;
}
}
newBreakpoints.add(breakpoint);
}
breakpoints = newBreakpoints;
}
public void enableDirective(String[] args) throws NumberFormatException {
setEnabled(args, true);
}
public void disableDirective(String[] args) throws NumberFormatException {
setEnabled(args, false);
}
private void setEnabled(String[] args, boolean enabled) throws NumberFormatException {
if(args.length == 1){
for(BreakPoint breakpoint : breakpoints){
breakpoint.setEnabled(enabled);
}
return;
}
for(BreakPoint breakpoint : breakpoints){
for(int i = 1; i < args.length; i++){
int bpno = Integer.parseInt(args[i]);
if(breakpoint.getId() == bpno){
breakpoint.setEnabled(enabled);
}
}
}
}
void listingDirective(Frame frame, String[] args){
int delta = defaultListingDelta;
if(args.length > 1){
try {
delta = Integer.parseInt(args[1]);
} catch(NumberFormatException e){
// use the default value
}
}
listingDirective(frame, delta);
}
// private helper functions
private void add(BreakPoint breakpoint){
breakpoints.add(breakpoint);
}
protected void printBreakPointsHeader(PrintWriter stdout){
stdout.println("Id\tEnabled\tKind\tIgnore\tDetails");
}
private void printBreakPoints(PrintWriter stdout){
if(breakpoints.isEmpty()){
stdout.println("No breakpoints");
} else {
printBreakPointsHeader(stdout);
for(BreakPoint breakpoint : breakpoints){
breakpoint.println(stdout);
}
}
stdout.flush();
}
private void listingDirective(Frame frame, int delta){
ISourceLocation breakpointSrc = frame.src;
int breakpointSrcBegin = breakpointSrc.getBeginLine();
int breakpointSrcEnd = breakpointSrc.getEndLine();
int breakpointDelta = breakpointSrcEnd - breakpointSrcBegin;
int windowBegin;
int windowEnd;
int windowSize = 2 * delta + 1;
ISourceLocation functionSrc = frame.function.src;
if(functionSrc.getEndLine() - functionSrc.getBeginLine() <= windowSize){
windowBegin = functionSrc.getBeginLine();
windowEnd = functionSrc.getEndLine();
} else if(breakpointDelta <= windowSize){
windowBegin = Math.max(1, breakpointSrcBegin - delta);
windowEnd = breakpointSrcEnd + delta;
} else {
windowBegin = breakpointSrcBegin - 2;
windowEnd = breakpointSrcBegin + windowSize - 2;
}
IValueFactory vf = ValueFactoryFactory.getValueFactory();
try {
String[] lines;
ISourceLocation srcFile = vf.sourceLocation(breakpointSrc.getScheme(), "", breakpointSrc.getPath());
if(breakpointSrc.getPath().endsWith(".rsc")){
//A Rascal source file, parse and highlight
ITree parseTree = getParsedModule(srcFile);
StringWriter sw = new StringWriter();
TreeAdapter.unparseWithFocus(parseTree, sw, breakpointSrc);
lines = sw.toString().split("\n");
} else {
// Something else (muRascal), no highlighting
lines = getResourceContent(srcFile).toString().split("\n");
}
for(int lino = windowBegin; lino <= windowEnd; lino++){
stdout.println(FAINT_ON + String.format("%4d", lino) + FAINT_OFF + listingIndent + lines[lino - 1]);
}
} catch (URISyntaxException e){
stdout.println("Cannot create URI for source file");
} catch (IOException e) {
stdout.println("Cannot read source file");
}
stdout.flush();
}
private IValue lastModified(ISourceLocation sloc) {
try {
return vf.datetime(URIResolverRegistry.getInstance().lastModified(sloc));
} catch(FileNotFoundException e){
throw RuntimeExceptionFactory.pathNotFound(sloc, null, null);
}
catch (IOException e) {
throw RuntimeExceptionFactory.io(vf.string(e.getMessage()), null, null);
}
}
private ITree getParsedModule(ISourceLocation loc){
String key = loc.toString() + lastModified(loc).toString();
return (ITree) parsedModuleCache.get(key, k -> {
try {
ITree tree = new RascalParser().parse(Parser.START_MODULE, loc.getURI(), getResourceContent(loc), new NoActionExecutor() , new DefaultNodeFlattener<IConstructor, ITree, ISourceLocation>(), new UPTRNodeFactory(false));
return tree;
} catch (IOException e) {
throw RascalRuntimeException.io(vf.string(e.getMessage()), null);
}
});
}
private char[] getResourceContent(ISourceLocation location) throws IOException{
char[] data;
Reader textStream = null;
URIResolverRegistry resolverRegistry = URIResolverRegistry.getInstance();
try {
textStream = resolverRegistry.getCharacterReader(location);
data = InputConverter.toChar(textStream);
}
finally{
if(textStream != null){
textStream.close();
}
}
return data;
}
}