Feature Construct to swallow errors in ASH

Ryo_Sangnoir

Developer
Staff member
I'd like to request an ASH construct to swallow errors (run commands, and continue even if an error occurs).

My use-case is part of my farm script which wants to try drinking something (e.g. dirt julep) to get a buff but will bomb out if it's too expensive (as determined by the autoBuyPriceLimit). I could check with mall_price() before, but this doesn't check whether the ingredients are cheaper as well, unlike drink.

Currently I'm using a CLI construct that does the same thing with CLI commands:

Code:
cli_execute("try; drink dirt julep");
 

Veracity

Developer
Staff member
How does this differ from "capturing" the value of a function?

Many functions return a "continue value". That will be true if there will no errors and false if your script should abort. If you "capture" that value, your script is declaring that it wants to handle (or ignore) the error. If you don't the script will dutifully abort.

Code:
drink( $item[ mint juley ], 1 );

does not capture the return value.

Code:
if ( !drink( $item[ mint juley ], 1 ) ) { do something ....}
or 
if ( !drink( $item[ mint juley ], 1 ) );
or
boolean ignored = drink( $item[ mint juley ], 1 )

do capture the return value of the function and will not abort the script.

Isn't this what you are asking for?
 

Veracity

Developer
Staff member
Since we have try/finally, it's been proposed that we have try/catch/finally.

Code:
try {
    // Do a bunch of things, any of which can cause an error
    statement;
    ...;
}
catch {
    // Code to execute only if an error happened
    statement;
    ...;
}
finally {
    // Code to execute whether or not an error happened
    statement;
    ...;
}
The error state would remain in effect and propagate up - potentially triggering multiple finally blocks - until/unless it reaches a catch block.

What if we allowed standalone "catch" bocks?

Code:
catch {
    // Code which can cause an error
    statement;
    ...;
}
// Continue here whether or not an error happened
And since a single-line statement is interchangeable with a block,

Code:
catch statement;
Would be a way to say "ignore any error thrown by my statement".
I.e., capture and discard the value.

Interesting. In Java, you'd do that with a "try" block and an empty "catch".

Would this be confusing?

I wonder how many scripts would break were I to add "catch" as a reserved word. :)

Reopening this Feature since it worth adding a more obvious way to capture values...
 

Veracity

Developer
Staff member
Here is a suggestion for Exception handling in ASH, proposing a "catch".

I might submit "catch" as a reserved word, just to see how many scripts break and prompt the authors to fix them, and then, by and by, implement a "catch" facility.
 

MCroft

Developer
Staff member
Could we get more info on the error by assigning the caught error in the standalone case?

e.g. Error Err = catch statement?

or in the try/catch/finally block model, the more java-ish
catch(Error Err)
{
// Do your thing here, but Err goes out of scope...
}


I'd suggest success and failure consumers for things that could use a callback, but I can't think of any cases where that's the pattern I want.
 

Veracity

Developer
Staff member
I was unable to find any .ash files in my relay or script directory using "catch" as a variable name, so revision 20429 marks "catch" as a reserved word.
Not clear exactly how we'll use it, but I'm definitely warming to it, so lets, well, reserve it.
 

Veracity

Developer
Staff member
Here's a little experiment:

Code:
[color=green]> set autoSatisfyWithCloset=false[/color]

autoSatisfyWithCloset => false

[color=green]> closet put Fourth of May Cosplay Saber[/color]

Placing items into closet...
Requests complete.

[color=green]> outfit Current[/color]

[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]

[color=green]> ash catch outfit( "Current" )[/color]

[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]
Returned: Unable to wear outfit Current.

[color=green]> ash catch { print( "hello" ); outfit( "current" ); print( "goodbye" ); }[/color]

hello
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]
Returned: Unable to wear outfit Current.

[color=green]> ash catch { print( "hello" ); catch outfit( "current" ); print( "goodbye" ); }[/color]

hello
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]
goodbye
Returned:
This probably completes the standalone catch block/statement. The return value is the last message printed in the status line - which will be red, if it was an error, which will trigger the catch. As you can see, multiple lines can be red - the "outfit" command attempts to retrieve all the equipment pieces before donning the outfit (unlike KoL) and the first message is from retrieve_item() and the second from the outfit command itself.

I may commit this and then think about:

Code:
throw "message";  // throw an ERROR (not an ABORT) which can be caught

// catch all errors in a try block
try {
}
catch {
}
finally {
}

// catch errors that match PATTERN in a try block

try {
}
catch (string PATTERN ) {
}
finally {
}
 

Veracity

Developer
Staff member
Revision 20454 has the simple standalone catch. Give it a try and catch some errors, before finally telling me what you think.
 

Veracity

Developer
Staff member
I had the standalone catch command working, but not as a value. Fixed in revision 20456.

Code:
set_property( "autoSatisfyWithCloset", false );
put_closet( $item[ Fourth of May Cosplay Saber ] );

print();
print( "Standalone catch outfit" );
catch outfit( "Current" );

print();
print( "Standalone outfit in a block" );
catch {
    print( "hello" );
    outfit( "current" );
    print( "goodbye" );
}

print();
print( "Standalone outfit in a block with embedded catch" );
catch {
    print( "hello" );
    catch outfit( "current" );
    print( "goodbye" );
}

print();
print( "Captured outfit" );
boolean captured = outfit( "Current" );
print( "... returns " + captured );

print();
print( "Caught outfit" );
string caught = catch outfit( "Current" );
print( "... returns '" + caught + "'" );

print();
take_closet( $item[ Fourth of May Cosplay Saber ] );
set_property( "autoSatisfyWithCloset", true );
Does this:

Code:
[color=green]> catch.ash[/color]

autoSatisfyWithCloset => false
Placing items into closet...

Standalone catch outfit
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]

Standalone outfit in a block
hello
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]

Standalone outfit in a block with embedded catch
hello
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]
goodbye

Captured outfit
[color=red]You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.[/color]
... returns false

Caught outfit
You need 1 more Fourth of May Cosplay Saber to continue.
Unable to wear outfit Current.
... returns 'Unable to wear outfit Current.'

Removing items from closet...
You acquire an item: Fourth of May Cosplay Saber
autoSatisfyWithCloset => true
Requests complete.
 

Veracity

Developer
Staff member
Thinking more about catch blocks as part of try/finally

We could have 3 kinds:

catch {} - catch anything. You don't care about the error.
catch ( string message ) {} - catch anything. The message goes in the (scoped) variable
catch (string message, string pattern ) {} - catch something that matches the pattern, exact message is available to the block.

If we had a "pattern" type, we could also have

catch ( pattern p ) {} - catch something that matches the pattern. You don't care about the exact message.

but I see a lot of scripts - Guide, for example - that use variables named "pattern". Not going to add a new data type now.

I imagine that a try{} could be followed by multiple catch{} blocks.

If executing the try leaves you not in an error, it ignores the catch blocks and does the finally block.
If executing the try block leaves you in an error state, it looks at each catch in turn until it finds one willing to accept the error - either any error, or one whose pattern matches.
It executes that block as if it were a standalone catch block. If the block itself throws an error, it continues looking for a catch block.
If we are still in an error state (i.e. no matching catch is found or an error was thrown from within the catch), we execute the finally block and propagate the error up.
 

MCroft

Developer
Staff member
I have no content to add here, but I wanted to come by and appreciate your efforts on this project. It will definitely make it easier to help people give better defect reports about ash issues.
 

Veracity

Developer
Staff member
Here's what I'm thinking about errors. There are four types, of increeasing severity:

1) "THROWN" - script generated: throw "message" would result in an errorString of "THROWN: message";
2) "CAPTURE" - Runtime Library functions that returns a "continuation value" do so in response to a MafiaState.ERROR message which wwas displayed in red. If you do not "capture" (or catch) that, it will exit your script. The resulting errorString might be "CAPTURE: Transfer failed for 5 seal clubs"
3) "SCRIPT" - runtime errors (division by zero, array index out of bounds, modifying map inside foreach, whatever.). These are ScriptExceptions and errorString could be "SCRIPT: Division by zero (foo.ash, line 62)"
4) "JAVA" - Things that Should Not Happen. "JAVA: Null Pointer Exception".

If I add the ability to filter using a regexp, "THROWN: message1", or "CAPTURE" or whatever would be reasonable. I think that "SCRIPT" would be rare; they denote a coding error in your script and you probably want to be informed about them. And "JAVA" errors are always unexpected and would be used only if you are trying to debug the maximizer, say. :)
 

Veracity

Developer
Staff member
By the way - there are people who use "catch" and "throw" for flow control. That is a terrible coding practice and makes programs difficult to understand and debug. But, we can't stop you...
 

MCroft

Developer
Staff member
Would it be unreasonable/un-ASH-like to return an exception object? You could have a structure with error type, error text, error severity/logging level.

Would SCRIPT include things like a bad import <missingfile.ash> or undefined user functions, or is that something that shouldn't be thrown because it shouldn't be catchable?
 

fronobulax

Developer
Staff member
Would SCRIPT include things like a bad import <missingfile.ash> or undefined user functions, or is that something that shouldn't be thrown because it shouldn't be catchable?

I believe those are checked for, prior to execution and as such could not be "caught" in ash.
 

Veracity

Developer
Staff member
The proposed string is "<error type>: <error text>". Unless you are proposing that a future "throw" construct would contain type, message, severity.

Here's a proof of concept:

Code:
int div( int a, int b )
{
    return a / b;
}

print( "about to divide by zero" );
string e1 = catch div( 10, 0 );
print( e1 );

print( "about to put on outfit noway" );
string e2 = catch outfit( "noway" );
print( e2 );

print( "about to get a bad substring" );
string e3 = catch substring( "foo", -1, 5 );
print( e3 );

print( "about to throw an NPE" );
string e4 = catch substring( "foo", 1 );
print( e4 );
I put an IntelliJ breakpoint in the ASH Runtime library's substring and manually set the input string to null just before it was about to call Java substring.
Executing this gave me the following:

Code:
> testc.ash

about to divide by zero
SCRIPT: Division by zero (testc.ash, line 3)
about to put on outfit noway
[color=red]No outfit found matching: noway[/color]
CAPTURE: No outfit found matching: noway
about to get a bad substring
SCRIPT: Begin index -1 out of bounds (testc.ash, line 15)
about to throw an NPE
JAVA: java.lang.NullPointerException
I'll submit this. I have to improve the parsing of catch as a standalone keyword that takes a block vs. catch as a construct that takes a value and returns a value. FOr whatever reason, I have a class for each thing and the runtime code is essentially identical.

Which is to say, I need to have one class and improve the Parser to use the same thing in either case.
 

MCroft

Developer
Staff member
I don't think we've introduced the concept of error severity (since it's all new), and I don't know if there's a burning desire for it that can't be handled by parsing the error, so I'm a ways away from proposing an actual change. It's kibbitzing, I suppose.

What I'd imagine using severity for is things that end up being transient errors like an https timeout. Again, not sure if that is in scope of things that you'd use this construct for. If that's CAPTURE, then a severity might be a hint, so you could retry. Totally doable with <error type> : <error text> although I'd need to know what I was looking for.
 

Veracity

Developer
Staff member
Revision 20479 fixes catch to work with catch BLOCK returning a value correctly and with catch EXPRESSION rather than simply catch VALUE.

Code:
print( "*** tests for capturing error value" );
print();

print( "capture unknown outfit" );
boolean captured = outfit( "no-such-outfit" );
print( "... returns " + captured );

print();
print( "*** tests for single value catch" );
print();

print( "divide by zero..." );
string e1 = catch 10 / 0;
print( "..." + e1 );

print( "unknown outfit..." );
string e2 = catch outfit( "no-such-outfit" );
print( "..." + e2 );

print( " bad substring..." );
string e3 = catch substring( "foo", -1, 5 );
print( "..." + e3 );

print( "throw an NPE (requires manual manipulation in debugger)..." );
string e4 = catch substring( "foo", 1 );
print( "..." + e4 );

print();
print( "*** tests for standalone block catch" );
print();

print( "uncaptured outfit in standalone block" );
catch {
    print( "hello" );
    outfit( "no-such-outfit" );
    print( "goodbye" );
}

print();
print( "captured outfit in standalone block" );
catch {
    print( "hello" );
    catch outfit( "no-such-outfit" );
    print( "goodbye" );
}

print();
print( "*** tests for value block catch" );
print();

print( "uncaptured outfit in value block..." );
string e5 = catch {
    print( "hello" );
    outfit( "no-such-outfit" );
    print( "goodbye" );
};
print( "..." + e5 );

print();
print( "captured outfit in value block..." );
string e6 = catch {
    print( "hello" );
    catch outfit( "no-such-outfit" );
    print( "goodbye" );
};
print( "..." + e6 );
yields

Code:
[color=green]> catch.ash[/color]

*** tests for capturing error value

capture unknown outfit
[color=red]No outfit found matching: no-such-outfit[/color]
... returns false

*** tests for single value catch

divide by zero...
...SCRIPT: Division by zero (catch.ash, line 13)
unknown outfit...
[color=red]No outfit found matching: no-such-outfit[/color]
...CAPTURE: No outfit found matching: no-such-outfit
bad substring...
...SCRIPT: Begin index -1 out of bounds (catch.ash, line 21)
throw an NPE (requires manual manipulation in debugger)...
...

*** tests for standalone block catch

uncaptured outfit in standalone block
hello
[color=red]No outfit found matching: no-such-outfit[/color]

captured outfit in standalone block
hello
[color=red]No outfit found matching: no-such-outfit[/color]
goodbye

*** tests for value block catch

uncaptured outfit in value block...
hello
[color=red]No outfit found matching: no-such-outfit[/color]
...CAPTURE: No outfit found matching: no-such-outfit

captured outfit in value block...
hello
[color=red]No outfit found matching: no-such-outfit[/color]
goodbye
...
 
Top