From 6387355c5461aa048db922ad0a5ffaa6c1fae860 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Mon, 29 Dec 2025 23:06:56 +0100 Subject: [PATCH 1/8] Add restart frame --- src/org/rascalmpl/dap/RascalDebugAdapter.java | 11 +- .../dap/RascalDebugEventTrigger.java | 11 + .../AbstractInterpreterEventTrigger.java | 8 + src/org/rascalmpl/debug/DebugHandler.java | 28 +- .../rascalmpl/debug/DebugMessageFactory.java | 4 + src/org/rascalmpl/debug/IDebugMessage.java | 2 +- src/org/rascalmpl/debug/RascalEvent.java | 2 +- .../RestartFrameException.java | 31 +++ .../interpreter/result/RascalFunction.java | 247 ++++++++++-------- 9 files changed, 226 insertions(+), 118 deletions(-) create mode 100644 src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java diff --git a/src/org/rascalmpl/dap/RascalDebugAdapter.java b/src/org/rascalmpl/dap/RascalDebugAdapter.java index 7c8a0dbebb5..4a63cbb5014 100644 --- a/src/org/rascalmpl/dap/RascalDebugAdapter.java +++ b/src/org/rascalmpl/dap/RascalDebugAdapter.java @@ -154,7 +154,7 @@ public CompletableFuture initialize(InitializeRequestArguments arg capabilities.setSupportsConfigurationDoneRequest(true); capabilities.setExceptionBreakpointFilters(new ExceptionBreakpointsFilter[]{}); capabilities.setSupportsStepBack(false); - capabilities.setSupportsRestartFrame(false); + capabilities.setSupportsRestartFrame(true); capabilities.setSupportsSetVariable(false); capabilities.setSupportsRestartRequest(false); capabilities.setSupportsCompletionsRequest(true); @@ -740,6 +740,13 @@ public CompletableFuture completions(CompletionsArguments a return response; }, ownExecutor); } - + + @Override + public CompletableFuture restartFrame(RestartFrameArguments args) { + return CompletableFuture.supplyAsync(() -> { + debugHandler.processMessage(DebugMessageFactory.requestRestartFrame(args.getFrameId())); + return null; + }, ownExecutor); + } } diff --git a/src/org/rascalmpl/dap/RascalDebugEventTrigger.java b/src/org/rascalmpl/dap/RascalDebugEventTrigger.java index a5a208353c8..6fb731f6e32 100644 --- a/src/org/rascalmpl/dap/RascalDebugEventTrigger.java +++ b/src/org/rascalmpl/dap/RascalDebugEventTrigger.java @@ -132,6 +132,17 @@ public void fireSuspendByClientRequestEvent() { client.stopped(stoppedEventArguments); } + @Override + public void fireSuspendByRestartFrameEndEvent() { + suspendedState.suspended(); + + StoppedEventArguments stoppedEventArguments = new StoppedEventArguments(); + stoppedEventArguments.setThreadId(RascalDebugAdapter.mainThreadID); + stoppedEventArguments.setDescription("Paused after restarting frame."); + stoppedEventArguments.setReason("restart"); + client.stopped(stoppedEventArguments); + } + @Override public void fireSuspendEvent(RascalEvent.Detail detail) {} } diff --git a/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java b/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java index 40ec05ea4d5..b20cff251b2 100644 --- a/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java +++ b/src/org/rascalmpl/debug/AbstractInterpreterEventTrigger.java @@ -127,6 +127,14 @@ public void fireSuspendByStepEndEvent() { fireSuspendEvent(RascalEvent.Detail.STEP_END); } + /** + * Fires a resume event for this debug element with detail + * RascalEvent.Detail.CLIENT_REQUEST. + */ + public void fireSuspendByRestartFrameEndEvent() { + fireSuspendEvent(RascalEvent.Detail.RESTART_FRAME_END); + } + /** * Fires a suspend event for this debug element with detail * RascalEvent.Detail.STEP_END. diff --git a/src/org/rascalmpl/debug/DebugHandler.java b/src/org/rascalmpl/debug/DebugHandler.java index 1070ad1a138..94f1b728728 100644 --- a/src/org/rascalmpl/debug/DebugHandler.java +++ b/src/org/rascalmpl/debug/DebugHandler.java @@ -26,16 +26,13 @@ import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.control_exceptions.InterruptException; import org.rascalmpl.interpreter.control_exceptions.QuitException; +import org.rascalmpl.interpreter.control_exceptions.RestartFrameException; import org.rascalmpl.interpreter.env.Environment; import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.repl.output.ICommandOutput; import org.rascalmpl.repl.rascal.RascalValuePrinter; import org.rascalmpl.values.functions.IFunction; import java.util.function.Function; -import org.rascalmpl.semantics.dynamic.Statement.For; -import org.rascalmpl.semantics.dynamic.Statement.Switch; -import org.rascalmpl.semantics.dynamic.Statement.Visit; -import org.rascalmpl.semantics.dynamic.Statement.While; import org.rascalmpl.exceptions.RascalStackOverflowError; import org.rascalmpl.interpreter.staticErrors.StaticError; import org.rascalmpl.exceptions.Throw; @@ -98,6 +95,12 @@ private enum DebugStepMode { */ private Evaluator evaluator = null; + /** + * Flag indicating a frame restart has been requested. + * Set by the debugger thread, checked and used by the evaluated thread in suspended(). + */ + private int restartFrameId = -1; + /** * Create a new debug handler with its own interpreter event trigger. */ @@ -232,6 +235,13 @@ public void suspended(Object runtime, IntSupplier getCallStackSize, AbstractAST // Ignore } } + + // Check if a frame restart was requested while suspended + if (restartFrameId >= 0) { + int frameToRestart = restartFrameId; + restartFrameId = -1; // Reset the flag + throw new RestartFrameException(frameToRestart); + } } protected AbstractAST getReferenceAST() { @@ -419,6 +429,16 @@ public void processMessage(IDebugMessage message) { terminateAction.run(); } break; + + case RESTART_FRAME: + assert suspended: "Can only restart frame when suspended"; + int frameId = (int) message.getPayload(); + assert frameId >= 0 && frameId < evaluator.getCurrentStack().size(): "Frame id out of bounds: " + frameId; + // Set flag for the evaluated thread to handle the restart + restartFrameId = frameId; + // Unsuspend to let the evaluated thread continue and hit the restart exception + setSuspended(false); + break; } break; } diff --git a/src/org/rascalmpl/debug/DebugMessageFactory.java b/src/org/rascalmpl/debug/DebugMessageFactory.java index 9841c185638..6cdf11c09d7 100644 --- a/src/org/rascalmpl/debug/DebugMessageFactory.java +++ b/src/org/rascalmpl/debug/DebugMessageFactory.java @@ -44,6 +44,10 @@ public static IDebugMessage requestStepOut(){ public static IDebugMessage requestTermination() { return new DebugMessage(IDebugMessage.Action.TERMINATE, IDebugMessage.Subject.INTERPRETER, IDebugMessage.Detail.UNKNOWN); } + + public static IDebugMessage requestRestartFrame(int frameId) { + return new DebugMessage(IDebugMessage.Action.RESTART_FRAME, IDebugMessage.Subject.INTERPRETER, IDebugMessage.Detail.UNKNOWN, frameId); + } /* * Breakpoint requests. diff --git a/src/org/rascalmpl/debug/IDebugMessage.java b/src/org/rascalmpl/debug/IDebugMessage.java index 81309214cac..74976715cb3 100644 --- a/src/org/rascalmpl/debug/IDebugMessage.java +++ b/src/org/rascalmpl/debug/IDebugMessage.java @@ -22,7 +22,7 @@ public interface IDebugMessage { * UNKNOWN if not provided. */ enum Action { - UNKNOWN, SET, DELETE, TERMINATE, SUSPEND, RESUME + UNKNOWN, SET, DELETE, TERMINATE, SUSPEND, RESUME, RESTART_FRAME } /** diff --git a/src/org/rascalmpl/debug/RascalEvent.java b/src/org/rascalmpl/debug/RascalEvent.java index 705d7be6cbb..a826761ca51 100644 --- a/src/org/rascalmpl/debug/RascalEvent.java +++ b/src/org/rascalmpl/debug/RascalEvent.java @@ -31,7 +31,7 @@ public enum Kind { * Details additional to {@link Kind}. */ public enum Detail { - UNSPECIFIED, CLIENT_REQUEST, STEP_INTO, STEP_OVER, STEP_END, BREAKPOINT, STEP_OUT + UNSPECIFIED, CLIENT_REQUEST, STEP_INTO, STEP_OVER, STEP_END, BREAKPOINT, STEP_OUT, RESTART_FRAME_END } private final Kind kind; diff --git a/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java b/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java new file mode 100644 index 00000000000..b3cb010e93a --- /dev/null +++ b/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2009-2013 CWI + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + + * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI +*******************************************************************************/ +package org.rascalmpl.interpreter.control_exceptions; + +/** + * Exception thrown to request a frame restart in the debugger. + * This exception carries the target frame ID and propagates up the call stack + * until it reaches the appropriate frame that should be restarted. + */ +public class RestartFrameException extends ControlException { + private final static long serialVersionUID = 1L; + private final int targetFrameId; + + public RestartFrameException(int targetFrameId) { + super("Restart frame: " + targetFrameId); + this.targetFrameId = targetFrameId; + } + + public int getTargetFrameId() { + return targetFrameId; + } +} diff --git a/src/org/rascalmpl/interpreter/result/RascalFunction.java b/src/org/rascalmpl/interpreter/result/RascalFunction.java index 2c17fb52ae3..b34b6bb4d09 100644 --- a/src/org/rascalmpl/interpreter/result/RascalFunction.java +++ b/src/org/rascalmpl/interpreter/result/RascalFunction.java @@ -41,11 +41,13 @@ import org.rascalmpl.ast.Type.Structured; import org.rascalmpl.exceptions.ImplementationError; import org.rascalmpl.interpreter.Accumulator; +import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.IEvaluator; import org.rascalmpl.interpreter.IEvaluatorContext; import org.rascalmpl.interpreter.control_exceptions.Failure; import org.rascalmpl.interpreter.control_exceptions.InterruptException; import org.rascalmpl.interpreter.control_exceptions.MatchFailed; +import org.rascalmpl.interpreter.control_exceptions.RestartFrameException; import org.rascalmpl.interpreter.control_exceptions.Return; import org.rascalmpl.interpreter.env.Environment; import org.rascalmpl.interpreter.matching.IMatchingResult; @@ -248,136 +250,161 @@ public Result call(Type[] actualStaticTypes, IValue[] actuals, Map oldAccus = ctx.getAccumulators(); - Map renamings = new HashMap<>(); - Map dynamicRenamings = new HashMap<>(); - - try { - ctx.setCurrentAST(ast); - if (eval.getCallTracing()) { - printStartTrace(actuals); - } + // Outer loop to handle frame restarts + while (true) { + Environment old = ctx.getCurrentEnvt(); + AbstractAST currentAST = ctx.getCurrentAST(); + AbstractAST oldAST = currentAST; + Stack oldAccus = ctx.getAccumulators(); + Map renamings = new HashMap<>(); + Map dynamicRenamings = new HashMap<>(); + + try { + ctx.setCurrentAST(ast); + if (eval.getCallTracing()) { + printStartTrace(actuals); + } - String label = isAnonymous() ? "Anonymous Function" : name; - Environment environment = new Environment(declarationEnvironment, ctx.getCurrentEnvt(), currentAST != null ? currentAST.getLocation() : null, ast.getLocation(), label); - environment.markAsFunctionFrame(); - ctx.setCurrentEnvt(environment); + String label = isAnonymous() ? "Anonymous Function" : name; + Environment environment = new Environment(declarationEnvironment, ctx.getCurrentEnvt(), currentAST != null ? currentAST.getLocation() : null, ast.getLocation(), label); + environment.markAsFunctionFrame(); + ctx.setCurrentEnvt(environment); - IMatchingResult[] matchers = prepareFormals(ctx); - ctx.setAccumulators(accumulators); - ctx.pushEnv(); + IMatchingResult[] matchers = prepareFormals(ctx); + ctx.setAccumulators(accumulators); + ctx.pushEnv(); - Type actualStaticTypesTuple = TF.tupleType(actualStaticTypes); - if (hasVarArgs) { - if (!mayMatch(actualStaticTypesTuple)) { - throw new MatchFailed(); + Type actualStaticTypesTuple = TF.tupleType(actualStaticTypes); + if (hasVarArgs) { + if (!mayMatch(actualStaticTypesTuple)) { + throw new MatchFailed(); + } + actuals = computeVarArgsActuals(actuals, getFormals()); + actualStaticTypesTuple = computeVarArgsActualTypes(actualStaticTypes, getFormals()); } - actuals = computeVarArgsActuals(actuals, getFormals()); - actualStaticTypesTuple = computeVarArgsActualTypes(actualStaticTypes, getFormals()); - } - - int size = actuals.length; - Environment[] olds = new Environment[size]; - int i = 0; + + int size = actuals.length; + Environment[] olds = new Environment[size]; + int i = 0; - if (!hasVarArgs && size != this.formals.size()) { - throw new MatchFailed(); - } - - actualStaticTypesTuple = bindTypeParameters(actualStaticTypesTuple, actuals, getFormals(), renamings, dynamicRenamings, environment); - - if (size == 0) { - try { - bindKeywordArgs(keyArgValues); -// checkReturnTypeIsNotVoid(formals, actuals); - result = runBody(); - result = storeMemoizedResult(actuals,keyArgValues, result); - if (eval.getCallTracing()) { - printEndTrace(result.getValue()); - } - return result; + if (!hasVarArgs && size != this.formals.size()) { + throw new MatchFailed(); } - catch (Return e) { - checkReturnTypeIsNotVoid(formals, actuals, renamings); - result = computeReturn(e, renamings, dynamicRenamings); - storeMemoizedResult(actuals,keyArgValues, result); - return result; + + actualStaticTypesTuple = bindTypeParameters(actualStaticTypesTuple, actuals, getFormals(), renamings, dynamicRenamings, environment); + + if (size == 0) { + try { + bindKeywordArgs(keyArgValues); +// checkReturnTypeIsNotVoid(formals, actuals); + result = runBody(); + result = storeMemoizedResult(actuals,keyArgValues, result); + if (eval.getCallTracing()) { + printEndTrace(result.getValue()); + } + return result; + } + catch (Return e) { + checkReturnTypeIsNotVoid(formals, actuals, renamings); + result = computeReturn(e, renamings, dynamicRenamings); + storeMemoizedResult(actuals,keyArgValues, result); + return result; + } } - } - matchers[0].initMatch(makeResult(actualStaticTypesTuple.getFieldType(0), actuals[0], ctx)); - olds[0] = ctx.getCurrentEnvt(); - ctx.pushEnv(); + matchers[0].initMatch(makeResult(actualStaticTypesTuple.getFieldType(0), actuals[0], ctx)); + olds[0] = ctx.getCurrentEnvt(); + ctx.pushEnv(); - // pattern matching requires backtracking due to list, set and map matching and - // non-linear use of variables between formal parameters of a function... + // pattern matching requires backtracking due to list, set and map matching and + // non-linear use of variables between formal parameters of a function... - while (i >= 0 && i < size) { - if (ctx.isInterrupted()) { - throw new InterruptException(ctx.getStackTrace(), currentAST.getLocation()); - } - if (matchers[i].hasNext() && matchers[i].next()) { - if (i == size - 1) { - // formals are now bound by side effect of the pattern matcher - try { - bindKeywordArgs(keyArgValues); - - result = runBody(); - storeMemoizedResult(actuals, keyArgValues, result); - return result; - } - catch (Failure e) { - // backtrack current pattern assignment - if (!e.hasLabel() || e.hasLabel() && e.getLabel().equals(getName())) { - continue; + while (i >= 0 && i < size) { + if (ctx.isInterrupted()) { + throw new InterruptException(ctx.getStackTrace(), currentAST.getLocation()); + } + if (matchers[i].hasNext() && matchers[i].next()) { + if (i == size - 1) { + // formals are now bound by side effect of the pattern matcher + try { + bindKeywordArgs(keyArgValues); + + result = runBody(); + storeMemoizedResult(actuals, keyArgValues, result); + return result; } - else { - throw new UnguardedFail(getAst(), e); + catch (Failure e) { + // backtrack current pattern assignment + if (!e.hasLabel() || e.hasLabel() && e.getLabel().equals(getName())) { + continue; + } + else { + throw new UnguardedFail(getAst(), e); + } } } - } - else { - i++; - matchers[i].initMatch(makeResult(actualStaticTypesTuple.getFieldType(i), actuals[i], ctx)); - olds[i] = ctx.getCurrentEnvt(); + else { + i++; + matchers[i].initMatch(makeResult(actualStaticTypesTuple.getFieldType(i), actuals[i], ctx)); + olds[i] = ctx.getCurrentEnvt(); + ctx.pushEnv(); + } + } else { + ctx.unwind(olds[i]); + i--; ctx.pushEnv(); } - } else { - ctx.unwind(olds[i]); - i--; - ctx.pushEnv(); } - } - // backtrack to other function body - throw new MatchFailed(); - } - catch (Return e) { - checkReturnTypeIsNotVoid(formals, actuals, renamings); - - result = computeReturn(e, renamings, dynamicRenamings); - storeMemoizedResult(actuals, keyArgValues, result); - if (eval.getCallTracing()) { - printEndTrace(result.getValue()); + // backtrack to other function body + throw new MatchFailed(); } - return result; - } - catch (Throwable e) { - if (eval.getCallTracing()) { - printExcept(e); + catch (RestartFrameException rfe) { + // Handle frame restart at this level + // Restore environment state and retry execution + // Continue the while loop to retry the call + if(ctx instanceof Evaluator) { + Evaluator evaluator = (Evaluator) ctx; + var stackTrace = evaluator.getCurrentStack(); + // here we can check if the frame to restart is indeed this one + // we can just choose by length + if(stackTrace.size() == 1){ + throw new ImplementationError("Cannot restart frame of the REPL"); + } + if (rfe.getTargetFrameId() == stackTrace.size() -1) { + continue; // restart the frame by continuing the while loop + } + else { + // not the frame to restart, rethrow to let upper frames handle it + throw rfe; + } + } + throw new ImplementationError("Frame restart is only supported in Evaluator contexts"); } - throw e; - } - finally { - if (eval.getCallTracing()) { - eval.decCallNesting(); + catch (Return e) { + checkReturnTypeIsNotVoid(formals, actuals, renamings); + + result = computeReturn(e, renamings, dynamicRenamings); + storeMemoizedResult(actuals, keyArgValues, result); + if (eval.getCallTracing()) { + printEndTrace(result.getValue()); + } + return result; + } + catch (Throwable e) { + if (eval.getCallTracing()) { + printExcept(e); + } + throw e; + } + finally { + if (eval.getCallTracing()) { + eval.decCallNesting(); + } + ctx.setCurrentEnvt(old); + ctx.setAccumulators(oldAccus); + ctx.setCurrentAST(oldAST); } - ctx.setCurrentEnvt(old); - ctx.setAccumulators(oldAccus); - ctx.setCurrentAST(oldAST); } } From 10c6abb7975c91133bd124600f0db9d2143769f2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Tue, 30 Dec 2025 17:53:48 +0100 Subject: [PATCH 2/8] Refuse REPL frame restart --- src/org/rascalmpl/dap/RascalDebugAdapter.java | 12 +++++++++++- .../interpreter/result/RascalFunction.java | 15 ++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/dap/RascalDebugAdapter.java b/src/org/rascalmpl/dap/RascalDebugAdapter.java index 4a63cbb5014..3f5745eaa34 100644 --- a/src/org/rascalmpl/dap/RascalDebugAdapter.java +++ b/src/org/rascalmpl/dap/RascalDebugAdapter.java @@ -744,8 +744,18 @@ public CompletableFuture completions(CompletionsArguments a @Override public CompletableFuture restartFrame(RestartFrameArguments args) { return CompletableFuture.supplyAsync(() -> { - debugHandler.processMessage(DebugMessageFactory.requestRestartFrame(args.getFrameId())); + if(args.getFrameId() == 0){ + // From DAP spec, we must send a stopped event. Here we use this to indicate that the REPL frame cannot be restarted + StoppedEventArguments stoppedEventArguments = new StoppedEventArguments(); + stoppedEventArguments.setThreadId(RascalDebugAdapter.mainThreadID); + stoppedEventArguments.setDescription("Cannot restart the REPL frame"); + stoppedEventArguments.setReason("restart"); + client.stopped(stoppedEventArguments); + } else{ + debugHandler.processMessage(DebugMessageFactory.requestRestartFrame(args.getFrameId())); + } return null; + }, ownExecutor); } } diff --git a/src/org/rascalmpl/interpreter/result/RascalFunction.java b/src/org/rascalmpl/interpreter/result/RascalFunction.java index b34b6bb4d09..ee3553692cd 100644 --- a/src/org/rascalmpl/interpreter/result/RascalFunction.java +++ b/src/org/rascalmpl/interpreter/result/RascalFunction.java @@ -249,9 +249,11 @@ public Result call(Type[] actualStaticTypes, IValue[] actuals, Map call(Type[] actualStaticTypes, IValue[] actuals, Map call(Type[] actualStaticTypes, IValue[] actuals, Map runBody() { From 0f4af24c6de9295629b5e2e6a139ff6f9f77e2d9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Wed, 31 Dec 2025 11:29:33 +0100 Subject: [PATCH 3/8] Break on first possible location --- src/org/rascalmpl/debug/DebugHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/rascalmpl/debug/DebugHandler.java b/src/org/rascalmpl/debug/DebugHandler.java index 94f1b728728..05eae26c495 100644 --- a/src/org/rascalmpl/debug/DebugHandler.java +++ b/src/org/rascalmpl/debug/DebugHandler.java @@ -437,6 +437,7 @@ public void processMessage(IDebugMessage message) { // Set flag for the evaluated thread to handle the restart restartFrameId = frameId; // Unsuspend to let the evaluated thread continue and hit the restart exception + setSuspendRequested(true); // We use the suspension request to break on the first frame's breakable point setSuspended(false); break; } From f0f1812bef8185f37a0c62763609d15259d94dcc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Wed, 31 Dec 2025 11:39:11 +0100 Subject: [PATCH 4/8] Fix copyright --- .../control_exceptions/RestartFrameException.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java b/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java index b3cb010e93a..7b86aa9bd4d 100644 --- a/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java +++ b/src/org/rascalmpl/interpreter/control_exceptions/RestartFrameException.java @@ -1,14 +1,3 @@ -/******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI -*******************************************************************************/ package org.rascalmpl.interpreter.control_exceptions; /** From ab796a12bb5b399348b338420f2572b1831474f9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Fri, 9 Jan 2026 13:56:18 +0100 Subject: [PATCH 5/8] AtomicInteger as flag --- src/org/rascalmpl/debug/DebugHandler.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/debug/DebugHandler.java b/src/org/rascalmpl/debug/DebugHandler.java index 05eae26c495..9cfa52d8d37 100644 --- a/src/org/rascalmpl/debug/DebugHandler.java +++ b/src/org/rascalmpl/debug/DebugHandler.java @@ -19,6 +19,7 @@ import static org.rascalmpl.debug.AbstractInterpreterEventTrigger.newNullEventTrigger; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntSupplier; import org.rascalmpl.ast.AbstractAST; @@ -99,7 +100,7 @@ private enum DebugStepMode { * Flag indicating a frame restart has been requested. * Set by the debugger thread, checked and used by the evaluated thread in suspended(). */ - private int restartFrameId = -1; + private AtomicInteger restartFrameId = new AtomicInteger(-1); /** * Create a new debug handler with its own interpreter event trigger. @@ -237,9 +238,8 @@ public void suspended(Object runtime, IntSupplier getCallStackSize, AbstractAST } // Check if a frame restart was requested while suspended - if (restartFrameId >= 0) { - int frameToRestart = restartFrameId; - restartFrameId = -1; // Reset the flag + int frameToRestart = restartFrameId.getAndSet(-1); + if (frameToRestart >= 0) { throw new RestartFrameException(frameToRestart); } } @@ -435,10 +435,11 @@ public void processMessage(IDebugMessage message) { int frameId = (int) message.getPayload(); assert frameId >= 0 && frameId < evaluator.getCurrentStack().size(): "Frame id out of bounds: " + frameId; // Set flag for the evaluated thread to handle the restart - restartFrameId = frameId; - // Unsuspend to let the evaluated thread continue and hit the restart exception - setSuspendRequested(true); // We use the suspension request to break on the first frame's breakable point - setSuspended(false); + if (restartFrameId.compareAndSet(-1, frameId)) { + // Unsuspend to let the evaluated thread continue and hit the restart exception + setSuspendRequested(true); + setSuspended(false); + } break; } break; From 9b933b56d1b3c3236309d62ff75933506d2ca347 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Fri, 9 Jan 2026 14:14:31 +0100 Subject: [PATCH 6/8] Ensure suspended for RestartFrame Handling --- src/org/rascalmpl/debug/DebugHandler.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/org/rascalmpl/debug/DebugHandler.java b/src/org/rascalmpl/debug/DebugHandler.java index 9cfa52d8d37..0e96f434e0c 100644 --- a/src/org/rascalmpl/debug/DebugHandler.java +++ b/src/org/rascalmpl/debug/DebugHandler.java @@ -431,15 +431,16 @@ public void processMessage(IDebugMessage message) { break; case RESTART_FRAME: - assert suspended: "Can only restart frame when suspended"; - int frameId = (int) message.getPayload(); - assert frameId >= 0 && frameId < evaluator.getCurrentStack().size(): "Frame id out of bounds: " + frameId; - // Set flag for the evaluated thread to handle the restart - if (restartFrameId.compareAndSet(-1, frameId)) { - // Unsuspend to let the evaluated thread continue and hit the restart exception - setSuspendRequested(true); - setSuspended(false); - } + if(suspended) { + int frameId = (int) message.getPayload(); + assert frameId >= 0 && frameId < evaluator.getCurrentStack().size(): "Frame id out of bounds: " + frameId; + // Set flag for the evaluated thread to handle the restart + if (restartFrameId.compareAndSet(-1, frameId)) { + // Unsuspend to let the evaluated thread continue and hit the restart exception + setSuspendRequested(true); + setSuspended(false); + } + } break; } break; From 633d6b248e840db26491d2d42404e733d3677f01 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Tue, 20 Jan 2026 17:56:44 +0100 Subject: [PATCH 7/8] Allow frame restart on Exception breakpoint --- src/org/rascalmpl/interpreter/Evaluator.java | 11 +++-- .../interpreter/result/RascalFunction.java | 47 ++++++++++++------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index 0f70665b0d0..839a49cfd97 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -1608,12 +1608,15 @@ public void notifyAboutSuspension(AbstractAST currentAST) { @Override public void notifyAboutSuspensionException(Exception t) { currentException = t; - if (!suspendTriggerListeners.isEmpty()) { // remove the breakable condition since exception can happen anywhere - for (IRascalSuspendTriggerListener listener : suspendTriggerListeners) { - listener.suspended(this, () -> getCallStack().size(), currentAST); + try{ + if (!suspendTriggerListeners.isEmpty()) { // remove the breakable condition since exception can happen anywhere + for (IRascalSuspendTriggerListener listener : suspendTriggerListeners) { + listener.suspended(this, () -> getCallStack().size(), currentAST); + } } + } finally { // clear the exception after notifying listeners + currentException = null; } - currentException = null; } public AbstractInterpreterEventTrigger getEventTrigger() { diff --git a/src/org/rascalmpl/interpreter/result/RascalFunction.java b/src/org/rascalmpl/interpreter/result/RascalFunction.java index 2a985ede74a..2f4e3d31c93 100644 --- a/src/org/rascalmpl/interpreter/result/RascalFunction.java +++ b/src/org/rascalmpl/interpreter/result/RascalFunction.java @@ -243,6 +243,24 @@ public boolean isAnonymous() { return getName() == null; } + private boolean handleRestartFrame(RestartFrameException rfe) { + // Placeholder for any additional handling needed for frame restarts + if(ctx instanceof Evaluator) { + Evaluator evaluator = (Evaluator) ctx; + var stackTrace = evaluator.getCurrentStack(); + if(stackTrace.size() == 1){ // should not happen because this is the REPL frame + throw new ImplementationError("Cannot restart frame of the REPL"); + } + if (rfe.getTargetFrameId() == stackTrace.size() -1) { // the frame to restart + return true; // indicate to restart the frame + } + else { // not the frame to restart, rethrow to let upper frames handle it + throw rfe; + } + } + throw new ImplementationError("Frame restart is only supported in Evaluator contexts"); + } + @Override public Result call(Type[] actualStaticTypes, IValue[] actuals, Map keyArgValues) { Result result = getMemoizedResult(actuals, keyArgValues); @@ -365,21 +383,8 @@ public Result call(Type[] actualStaticTypes, IValue[] actuals, Map call(Type[] actualStaticTypes, IValue[] actuals, Map Date: Tue, 20 Jan 2026 17:58:49 +0100 Subject: [PATCH 8/8] Remove restart frame button on REPL frame --- src/org/rascalmpl/dap/RascalDebugAdapter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/rascalmpl/dap/RascalDebugAdapter.java b/src/org/rascalmpl/dap/RascalDebugAdapter.java index 9bef4c5caca..f80d5b0a18e 100644 --- a/src/org/rascalmpl/dap/RascalDebugAdapter.java +++ b/src/org/rascalmpl/dap/RascalDebugAdapter.java @@ -395,6 +395,7 @@ private StackFrame createStackFrame(int id, ISourceLocation loc, String name){ StackFrame frame = new StackFrame(); frame.setId(id); frame.setName(name); + frame.setCanRestart(false); if(loc != null && !loc.getScheme().equals(promptLocation.getScheme())) { var offsets = columns.get(loc); var line = shiftLine(loc.getBeginLine()); @@ -402,6 +403,7 @@ private StackFrame createStackFrame(int id, ISourceLocation loc, String name){ frame.setLine(line); frame.setColumn(column); frame.setSource(getSourceFromISourceLocation(loc)); + frame.setCanRestart(true); } return frame; }