JoeStrout / miniscript

source code of both C# and C++ implementations of the MiniScript scripting language
MIT License
275 stars 64 forks source link

[Feature request][cmd] Let Ctrl+C interrupt the running code, instead of aborting MiniScript #119

Open Withered-Flower-0422 opened 7 months ago

Withered-Flower-0422 commented 7 months ago

When pressing Ctrl+C, you quit from MiniScript environment. It seems there's no way to interrupt the running code simply. Ctrl+C is more suitable to do a BREAK thing, not an ABORT thing. So just let Ctrl+C interrupt the running code, instead of aborting MiniScript. If you want to abort MiniScript, type Ctrl+Z. (This is already available.)

This is how python does. I feel it more reasonable. Python won't abort until you type Ctrl+Z.

Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> while 1:
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt // after pressing Ctrl+C, the code is interrupted, but not aborted.
>>>

See on Discord

JoeStrout commented 7 months ago

Properly breaking out of the code (without exiting the environment) is more involved than it sounds, but it's certainly doable. For reference, here is the C# code that handles a break in Mini Micro:

    public bool Break(bool silent=false) {
        //Debug.Log($"Break({silent})");
        if (!silent) {
            var opts = env.GetMap("bootOpts");
            allowControlCBreak = opts.GetBool("controlC", true);
            if (!allowControlCBreak) return false;
        }

        // grab the full stack and tuck it away for future reference
        ValList stack = stackAtLastErr;
        if (stack == null) stack = Intrinsics.StackList(interpreter.vm);
        //Debug.Log($"Break found stack: {stack.ToString()}");

        // also find the first non-null entry, to display right away
        SourceLoc loc = null;
        if (interpreter.vm != null) {
            foreach (var stackLoc in interpreter.vm.GetStack()) {
                loc = stackLoc;
                if (loc != null) break;
            }
        }
        interpreter.Stop();
        console.AbortInput();
        console.keyBuffer.Clear();
        if (!silent) {
            string msg = "BREAK";
            if (loc != null) {
                msg += " at ";
                if (loc.context != null) msg += loc.context + " ";
                msg += "line " + loc.lineNum;
            }
            textDisplay.Print(msg + "\n");
            //Debug.Log("printed: " + msg);
        }
        // Reset the interpreter (creating a new VM), but copy the globals
        // and various type maps out of the old one.
        var oldVM = interpreter.vm;
        interpreter.Reset();
        interpreter.REPL("");   // (forces creation of a VM)
        CreateVersionMap();
        interpreter.vm.globalContext.variables = oldVM.globalContext.variables;
        interpreter.vm.listType = oldVM.listType;
        interpreter.vm.mapType = oldVM.mapType;
        interpreter.vm.numberType = oldVM.numberType;
        interpreter.vm.stringType = oldVM.stringType;
        interpreter.vm.versionMap = oldVM.versionMap;
        oldVM = null;

        // and set the _stackAtBreak global to our stack
        interpreter.vm.globalContext.variables.SetElem(_stackAtBreak, stack);
        //Debug.Log("Rebuilt VM and restored " + globals.Count + " globals");
        doLaunchShell = false;
        isRunningShell = false;
        runProgram = false;
        return true;
    }
BibleClinger commented 7 months ago

For C++, specifically for Windows, the handler would be set like this:

SetConsoleCtrlHandler(CtrlHandler, TRUE);

A sample handler:

#include <windows.h>

BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
    switch (fdwCtrlType)
    {
    case CTRL_C_EVENT:
        // MiniScript break here
        return TRUE; // We are handling this event.
    default:
        return FALSE; // We want other handlers to consume the event.
}

For *nix, the C++ implementation would need to deal with signals.

For the handler in C#, I haven't done this yet myself, but quick web-searching seems to indicate you set it like this:

Console.CancelKeyPress += new ConsoleCancelEventHandler(consoleBreakHandler)

A sample handler:

protected static void consoleBreakHandler(object sender, ConsoleCancelEventArgs args)
{
    // MiniScript break here
    args.Cancel = true; // We don't want the program to quit.
}

References: