Feature - Rejected cli_execute can cause an unskippable error [Propose try-catch]

Jamesernator

New member
Basically
Code:
cli_execute
can for some commands immediately end the ASH script, this is observable with the following script:

Code:
void main() {
    cli_execute("Briefcase collect");
    cli_execute("terminal extrude booze");
    cli_execute("terminal extrude booze");
    cli_execute("terminal extrude booze");
    cli_execute("cheat ancestral recall");
    cli_execute("cheat 1952 mickey mantle");
    cli_execute("cheat island");
    cli_execute("breakfast");
    cli_execute("genie pocket wish");
    cli_execute("genie pocket wish");
    cli_execute("genie pocket wish");
    cli_execute("teatree shake");
    print("Dailies done");
}

If this code is run after using for example a single
Code:
terminal extrude
then it won't execute the rest of the script.

Now there is an (awful) workaround for this, by abusing `try-finally` you can ensure that some code will always run so this will still work:

Code:
void main() {
    try {
        cli_execute("Briefcase collect");
    } finally try {
        cli_execute("terminal extrude booze");
    } finally try {
        cli_execute("terminal extrude booze");
    } finally try {
        cli_execute("terminal extrude booze");   
    } finally try {
        cli_execute("cheat ancestral recall");
    } finally try {    
        cli_execute("cheat 1952 mickey mantle");
    } finally try {
        cli_execute("cheat island");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("teatree shake");
    } finally {
        print("Dailies done");
    }
}

However this doesn't seem a great solution as there's no way to wrap any of that logic in another function without having to still wrap `try-finally` around that e.g.:

Code:
void terminal_booze() {
     try {
        cli_execute("terminal extrude booze");
    } finally try {
        cli_execute("terminal extrude booze");
    } finally try {
        cli_execute("terminal extrude booze");   
    } finally {
        return;
    }
}

void main() {
    try {
        cli_execute("Briefcase collect");
    } finally try {
        terminal_booze();
    } finally try {
        cli_execute("cheat ancestral recall");
    } finally try {    
        cli_execute("cheat 1952 mickey mantle");
    } finally try {
        cli_execute("cheat island");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("genie pocket wish");
    } finally try {
        cli_execute("teatree shake");
    } finally {
        print("Dailies done");
    }
}

This means you can't create generic functions that rely on executing an arbitrary CLI command.

---

Now it seems to be the case that `cli_execute` creates some weird "exception"-like thing, so I think the best addition for this would be to add a `catch` statement to `try` e.g.:

Code:
void execute_safe(string command) {
    try {
        cli_execute(command);
    } catch {
        return;
    }
}

void main() {
    execute_safe("Briefcase collect");
    execute_safe("terminal extrude booze");
    execute_safe("terminal extrude booze");
    execute_safe("terminal extrude booze");   
    execute_safe("cheat ancestral recall");
    execute_safe("cheat 1952 mickey mantle");
    execute_safe("cheat island");
    execute_safe("genie pocket wish");
    execute_safe("genie pocket wish");
    execute_safe("genie pocket wish");
    execute_safe("teatree shake");
    print("Dailies done");
}
 

heeheehee

Developer
Staff member
Is this necessary, given that there exists a try command for CLI scripting?

> help try

else ; commands - do commands if preceding if/while/try didn't execute.
try ; commands - do commands, and continue even if an error occurs.

> ashq cli_execute("cheat gift card"); print("done")

You don't have enough draws left from the deck to do that today

> ashq cli_execute("try; cheat gift card"); print("done")

You don't have enough draws left from the deck to do that today
done
 

xKiv

Active member
I also use cli_execute("try; whatever").

Alternatively - won't the abort condition be trapped by (syntactically) capturing a return value of a function anywhere on the way up the call stack?
So ...
Code:
function int try_cli_execute(string command) {
  cli_execute(command);
  return 1;
}
...
int trap;
trap = try_cli_execute("whatever");
print(trap==0?"execute failed":"done");

(not tested)


Or is this a different type of abort?
 
Using exceptions to handle control flow like this is kind of gross.

Rant aside, doesn't capturing the return value from cli_execute cause it to not abort?

Furthermore, for some of those things, you can validate they are possible before you even do them. I only say this because if you are willing to consider a set of try/catch wrappers, why not actually check for feasibility rather than invoke whatever is necessary for exception-based code?
 

xKiv

Active member
Rant aside, doesn't capturing the return value from cli_execute cause it to not abort?

I thought it doesn't have one, but turns out it returns true when it didn't abort, and the default for boolean (which is what you capture when aborting) is false, so
Code:
if (cli_execute("whatever")) {
 print("done");
} else {
 print("a failure is you");
}
should work
 

lostcalpolydude

Developer
Staff member
So, rewriting that first block of code as
Code:
void main() {
    boolean temp;
    temp = cli_execute("Briefcase collect");
    temp = cli_execute("terminal extrude booze");
    temp = cli_execute("terminal extrude booze");
    temp = cli_execute("terminal extrude booze");
    temp = cli_execute("cheat ancestral recall");
    temp = cli_execute("cheat 1952 mickey mantle");
    temp = cli_execute("cheat island");
    temp = cli_execute("breakfast");
    temp = cli_execute("genie pocket wish");
    temp = cli_execute("genie pocket wish");
    temp = cli_execute("genie pocket wish");
    temp = cli_execute("teatree shake");
    print("Dailies done");
}
works fine. That's if you don't want to actually check whether things are possible first, or are worried about actions being done outside of mafia.
 

Jamesernator

New member
I wasn't aware of either the "try;" thing or the capturing the return value to prevent dark magic happening as neither of those are documented in the function's explanation: http://wiki.kolmafia.us/index.php?title=Cli_execute which is what I was following to try to write that script. Based on the fact it returned a boolean for success I expected that it would simply work to ignore the return value as is the case in almost every other programming language but discovered that this was not the case and some weird "exception"-like thing was happening so I tested `try-finally` so see that that would work and it did which is why I believed that `try-catch` would be a good idea as if `try-finally` worked it was clearly an "exception"-thing. Given that it appears to be a one-off special thing for `cli_execute` I don't really care about the `try-catch` thing anymore though.

Regarding cheesecookie's point, the reason I proposed `try-catch` is because it's generic and doesn't consider the reason for failure (which in the case of doing excess dailies is exactly what I want given that failure is just a non-operation) but as I said in the previous paragraph, as `cli_execute` seems to be a one off special case I don't think its necessary.
 

Veracity

Developer
Staff member
"Almost every other programming language" is not a "scripting language". You want scripts to abort if they are unable to do something you asked, unless you indicate that a failure is OK. The way you so-indicate in ASH is to capture the return value, which gives you the opportunity to examine it and act based on the result.

I guess that wasn't described in the specific function you mentioned because it's a design feature of the whole language.

It's especially visible in the interface to the CLI scripting language, which, itself, will automatically abort on failure. Various other ASH functions are wrappers for CLI commands. If the underlying CLI command will abort on error, so will the ASH function. If the underlying commands do not abort on failure, neither will the ASH function.

Which CLI functions abort on error - and therefore, which ASH functions that wrap them - do not come immediately to mind, but I don't think cli_execute is a "one-of special case". It is just completely explicit about wrapping a CLI command.
 
Last edited:
Top