try blocks can transform an abort into a return value?

fronobulax

Developer
Staff member
I've been following this closely as a script writer without realizing that this fix might being an end to pounding escape over an over and over trying to get a script to stop.

Me too, although I have not yet been motivated to see whether the scripts that are hostile to the escape key use finally or not. I think there is one which I did not write that will try something ten times regardless of whether the user tries to stop sooner or not.
 

Veracity

Developer
Staff member
I think there is one which I did not write that will try something ten times regardless of whether the user tries to stop sooner or not.
I'm very curious about how it is able to continue after KoLmafia is in an ABORT state. Care to share?
 

fronobulax

Developer
Staff member
I'm very curious about how it is able to continue after KoLmafia is in an ABORT state. Care to share?

To get the full impact of this you have to picture me cowering in full awareness of the uselessness of anecdotes and well aware of my own tendency to look at effect and jump to the wrong conclusion as to cause.

There is a relay script (not Guide) that generates a clickable task list and clicking on a task executes it. I would sometimes click on a task and realize it was a mistake. Sometimes the ESC key would abort, sometimes it would appear that the task aborted and restarted and once or twice I felt I had to kill the Kolmafia process. That said, I have not run the script recently so I have not seen it recently.

I looked at the code and it appears that the task is executed in a loop. The loop stops when there are no adventures, the task conditions are satisfied or the loop has run 10 times. Some tasks have subtasks that are other functions or builtin ASH functions. Some tasks are executed by cli_execute(...).

If I had to reduce this to something repeatable and testable I would look at the possibility that button mashing stopped a cli_execute() action but not the relay script. Otherwise anecdote.
 

chown

Member
So, do I understand correctly that not capturing a return value (by which I mean using it like a "void" function) from the various Mafia functions that can "fail" propagates an exception-like condition (not technically an abort?) up the call chain, until it reaches the innermost function call that returns a value? And then returns from that function with a default-initialized (or whatever the Mafia equivalent is) value of the appropriate type? Although I'm, overall, pleased that "abort" no longer does that, I'm still a bit curious as to why we would ever want that to happen.
 

Theraze

Active member
If you are adventuring somewhere for some reason, and your adventuring fails, and you aren't trapping it, then something probably went wrong. And you should go fix that. Before you waste 300 adventures doing exactly the same thing that just failed.
 

chown

Member
If you are adventuring somewhere for some reason, and your adventuring fails, and you aren't trapping it, then something probably went wrong. And you should go fix that. Before you waste 300 adventures doing exactly the same thing that just failed.

But, in order to get that behavior, our code needs to either explicitly check every return value, and abort as appropriate, or else always terminate when any function that calls these functions (possibly indirectly) returns zero. That non-local aspect really has me puzzled. Why are we allowed to do:

Code:
void prepare() {  // supposed to either work, or abort...
    create(1, $item[badass belt]);  // but, oops, this could fail and we forgot to abort!
        // (note that I have carefully checked every other return value....)
    if (35 <= my_basestat($stat[Mysticality])) abort("not enough mys");
    if (!equip($item[badass belt])) abort("equip failed");
    if (!use_familiar($familiar[green pixie])) abort("could not equip pixie");
}

int getAbsinthe() {
    prepare();
    if (!adv1($location[Noob Cave], -1, "")) abort("yet another abort");
    int result = item_amount($item[tiny bottle of absinthe]);
    if (!put_closet(result, $item[tiny bottle of absinthe])) abort("...");
    return result;  // n.b., a zero return will get misinterpreted as count of zero!
}

void main() {
    int total;
    while (total < 5) total += getAbsinthe();
}

Are we expected to always design our scripts so that every function call that returns zero can be understood as indicating a need to stop execution?
 

Veracity

Developer
Staff member
No. You are expected to look at each and every library function and understand what its return value means.
Just like any other computer language.

A large number of ASH functions return what is internally called the "continue value". It should be a boolean - not a "zero vs. non-zero", although that would be the equivalent - which indicates whether the underlying request succeeded or failed.

Something you have to keep in mind is that the CLI has a "scripting language" - and if a command fails, it aborts the script - and ASH is the "Advanced Script Handler" and is, at heart, also a "scripting language". Any number of function calls will, by default, abort your script. One of the first things I did when I took over ASH was to regularize that - and allow your script to "capture" the return value, rather than just automatically aborting, when a "failure" occurred. But, since it is a "scripting language", if you fail to capture the value, your script will abort.

If you want to "check every return value" so that you can give a nice error message explaining why it failed, cool. Although, you'd be better off doing those checks BEFORE making the function call. That would mean that the function would never fail- unless there was some other extreme error that you could not anticipate.

You are not "expected to design your scripts" to do that. You are "expected" to recognize that you are, in fact, coding in a scripting language which will abort your program on an error unless you take care to make it not do that.

That is, apparently, an unfamiliar programming paradigm to you. That is not a moral failure. ;)
 

chown

Member
Any number of function calls will, by default, abort your script.

if you fail to capture the value, your script will abort

I'm seriously banging my head against a wall here.

How is that the "default"? By "default", functions are only called in contexts where no calling function has a return value?? Because otherwise, they certainly don't abort! (Or, not without the script author explicitly adding an abort().)

[...]some other extreme error that you could not anticipate.

KoL is a changing thing; I can't possibly anticipate every reason why a call might fail. They just might, and as far as I'm concerned, that's a fact of life. The best I can ever do is write code that is robust to a wide array of changes that might happen. This language feature -- despite everyone's claims to the contrary -- does nothing to help me in that effort.
 

Veracity

Developer
Staff member
Any number of function calls will, by default, abort your script when they are unable to perform the action you requested.
 

fronobulax

Developer
Staff member
Can you call a function and then deal with failure? Sure, but if calling the function is expensive or you want to use a more standard and robust coding practice you first check everything you already know might cause the failure and deal with those preconditions before you make the call. In that case if the function fails you have learned something you did not know. What you do with than knowledge is up to you but trying to continue in the presence of an unanticipated error doesn't seem to be the correct response.

It should be noted that one source of Feature Requests is for the functions people need to actually check the preconditions and that generally helps the community at large.
 

xKiv

Active member
Any number of function calls will, by default, abort your script when they are unable to perform the action you requested.

And the abort[1] will itself get "aborted" if it happened (inside a library, for example) inside a function whose return value is captured. Even if that return value has nothing to do with mafia's continuation state.

Example pseudocode:
Code:
int count_particles() {
  int cnt=0;
  foreach particle in universe {
     if (zlib_is_valid_particle(particle)) {
      cnt += 1;
    }
  }
  return cnt;
}

void main() {
  int cnt = count_particles();
  print("there are " + cnt + " valid particles in the universe");
  // more code
}

if zlib_is_valid_particle (named so to suggest that it comes from a library) calls something else that also calls something else that errors out, you get action at distance (which tends to be frowned upon).

I think the only way to get into ERROR state is to call mafia's functions (user scripts can't put themselves into ERROR state intentionally).
Perhaps capturing should only stop "immediate" ERRORs? I.e. only when capturing mafia's internal functions, not user functions.

That would leave error handling to the scriptwriter who is actually calling the function, should know why, and presumably also knows if the error is lethal or ignorable.

[1] not an actual ABORT, just ERROR


ETA:
note that you *can't* write error checking robust enough to prevent all errors - KoL can change state without any interaction from us (pvp steals items, mall sells items, somebody in chat throws curse effects, ...), for one.
 
Last edited:
Top