ID:1288032
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
Kind of similar to ID:1283286, but a completely different feature.
Currently, we have __LINE__, __FILE__, CRASH() and ASSERT() as some debugging tools. CRASH() and ASSERT() generate a runtime error - which outputs a stack trace.

Basically, the ability to generate a stack trace from the point in the proc it's called up to the original/topmost call without crashing the procedure would be nice.

Whether it to be a macro like __STACK__, or some kind of command like STACK()/TRACE()
YES
This would be helpful.
++
++
Bumping this request as it would be useful for hunting down a few bugs.
This would be great. It's entirely possible for a proc to hit a state where it's in error, but said error is not "fatal". Being able to report the call stack without crashing would be nice.
CRASH() includes a provision for printing out the current stack trace when a proc crashes, however this isn't available to DM code. It would be nice to have a way to get the call stack as text in softcode without crashing a proc, so that if an error condition that is non-fatal is met, the condition could be logged. This would be very useful for debugging, especially in more complex games (i.e. SS13) where errors can only happen in specific odd circumstances, making them hard to replicate.
It would be better just to implement exceptions. Although it takes longer to implement (so I've heard) it's one of my most desired language improvements.

Runtime errors can be very confusing at times, since code just continues to be executed with variables replaced by null.

Implementation

try
var/x = 5 / 0
catch (e)
world.log << "ERROR: [e.message]\nSTACK: [dd_list2text(e.stack, "\n")]"
// e.stack would be a list for the stack trace,
// dd_list2text borrowed from Deadron.TextHandling

The purpose of this adaptation of try/catch would be to catch any errors occurring in the current proc. When the proc would normally crash it would jump to the catch block instead.



The following would not work as intended:

/proc/MyFunction()
var/x = 5 / 0

/world/New()
try
MyFunction()
catch (e)
world.log << "[e.message]" //this is never called


The MyFunction proc does not have a try/catch statement by itself, so it does not magically fall into the catch of the caller.

So unless you explicitly write try/catch statements everywhere they're not going to be caught.

Since older projects have no try/catch statements this feature is now backwards compatible.



The following should work:

/proc/MyFunction()
try
var/x = 5 / 0
catch (e)
throw e

/world/New()
try
MyFunction()
catch (e)
world.log << "[e.message]" //this is never called


By using the "throw" statement you can throw an error to the calling proc. This will stop execution of the calling proc immediately and jump to the catch block of the parent.

If there is no parent procedure then /world/OnError should be called with the error object as its first argument.



The last bit of this feature should be introducing a new compiler flag that you can include in your DME and is included in the Build Preferences modal.

When this flag is set, the compiler wraps try/catch statements around all procs/verbs which throw their errors upward.



The "e" object I've referenced in the catch block is an internal /DMError (or some such type) object by default, and (like usr) is already typecasted.

Variables include:
e.message: The message body of the error.
e.stack: A list of text strings for each level in the stack trace.
e.src: The value of 'src' at the time of the error.
e.usr: The value of 'usr' at the time of the error. (AFAIK the same as usr as it bubbles up, but good to include nonetheless to avoid confusion)

The line number and file would not be strictly necessary, as these may be derived from the stack trace.

The first item in the stack trace is always where the error occurred (within the try). The last item in the list is the proc/verb that triggered the chain of events that led to this error.





Is the above implementation doable and feasible?
In response to Sir Lazarus
Sir Lazarus wrote:
It would be better just to implement exceptions. Although it takes longer to implement (so I've heard) it's one of my most desired language improvements.
...
Is the above implementation doable and feasible?

Short answer: I don't know, but possibly.

Essentially I would see this as hijacking BYOND's existing exception setup (runtime errors are literally throwing exceptions), so when a try block was encountered it would take note of where the catch was and include a jump after the end of the block. I've looked at the compiler code before for for/while loops, and I think the potential is there. I'd have to review how the exceptions are currently handled and such, and I'd have to review that compiler code all over again, but the gist of it is I think it's possible.
Wasn't there another topic with a pretty lengthy discussion on try/catch?


Lummox JR wrote:
Sir Lazarus wrote:
It would be better just to implement exceptions. Although it takes longer to implement (so I've heard) it's one of my most desired language improvements.
...
Is the above implementation doable and feasible?

Short answer: I don't know, but possibly.

Essentially I would see this as hijacking BYOND's existing exception setup (runtime errors are literally throwing exceptions), so when a try block was encountered it would take note of where the catch was and include a jump after the end of the block. I've looked at the compiler code before for for/while loops, and I think the potential is there. I'd have to review how the exceptions are currently handled and such, and I'd have to review that compiler code all over again, but the gist of it is I think it's possible.

"You'd pretty much have to drag Dan back to do this. It's such a fundamental language issue that there'd be no handling it otherwise." - Lummox JR, August 28, 2007.