The value is a string if it comes from a regular DM exception. (I'm not averse to fancifying that however, if people deem that important. The filename and line number, if available, certainly could be helpful.) The throw keyword, which I'll be adding soon, can throw any value.
For some reason, I really don't like the idea of it just being a string. There is essential data that needs to be avaliable (stack trace, line number/file). I'd personally like to have it as a datum as well so you can define your own exceptions (and provide extra things, like ex.name, so you can have different types of 'errors', while ex.message would tell you the actual error). Or give the error as a number value and define a constant for each type of error, so you don't have to do string comparisons.
A ten year old feature request addition is announced, but not even available yet, and we're already trying to get it expanded. I love us. ;)
Stack trace would be a lot harder to manage; I don't think it's viable, at least not for a first run. But the file and number would be useful for the default case, so I see value in having a special datum used by default.

But you can absolutely throw a number or an obj or whatever you like.
This syntax meets the standards of exception handling used in other languages, so that's good. However, this leaves the door open for "unhandled" exceptions, because you can't go around trying everything. I think that what is needed is an actual global proc that is called either for all exceptions or just the ones that are not handled by try-catch statements. Without this, you will still end up with ugly surprises in the output control.

I will probably abuse this and trigger some benign exceptions on purpose, to collect information that would otherwise be unavailable. The best part is that nobody will ever know! This opens up so many possibilities for debugging. You can write an AI metaprogram that makes the program get hurt once so that it "learns" what is bad and it never does it again.
In response to Multiverse7
I'm not sure there's a great need for a global handler, but I do see the point. It's not in my current design plan but it'd be feasible to add later if it was warranted.

What sort of information are you looking to get, though? If I were to add filename and line number info to the exception, that doesn't really tell you anything special.
The only global handler for errors I need applies to everything even when not using try/catch statements.

This would allow me to route error information myself when not using an output control.

All I need from a try/catch would be line number, file, it should also show the code on that line(This is useful especially when you've been modifying the code and that line may not be in the same place anymore.)

And obviously the error message/stack that you get from a normal error that you didn't use try/catch on.
In response to Superbike32
Superbike32 wrote:
All I need from a try/catch would be line number, file, it should also show the code on that line(This is useful especially when you've been modifying the code and that line may not be in the same place anymore.)

Showing the code is not possible. The code is compiled, never stored raw in the .dmb. A .dmb with debug info just stores file and line info along with the other code.

And obviously the error message/stack that you get from a normal error that you didn't use try/catch on.

I'm not sure about the feasibility of grabbing the call stack but I can look into it. I suppose it would be useful.

Here's a question: What would a global handler be called and how would you set it up? I have no syntactic answer for this but it sounds like it would be helpful for people, so I'd like to solicit suggestions.

Bear in mind that no global handler will ever be able to send a return value, i.e. to say "This error isn't bad, so keep going." A catch is a catch; it'll always be forced to bail out of at least the current proc.

And with a global handler, the expected behavior would be to only crash the current proc, yes? That is, just as it currently does. (In the cases that crash all callers--exceptions like infinite recursion--I have no intention of applying try/catch to those since those can cause stability issues. Likewise with infinite loops. These sorts of things just shouldn't happen.)
I'm think client.Error() or client.OnError() which would be called only when defined, otherwise it'd do the normal output on the client to the output window.

..() to do the default and display the error in the output, and a variable like client.Error(ex)

And "ex" would just be a text string that's identical to what output would show.

***And things would work like they do now no matter what as far as exceptions go even with a global handler of any sort where it crashes the current proc.

As far as try/catch is concerned though the current proc should continue to execute after the catch statement has finished executing. So any code after the statement that's not part of the try/catch should finish executing as well.

I don't know if you've thought about errors occurring inside the catch statement of a try/catch but those should crash the current proc entirely so that even if there's code after the catch statement in the same proc, since the catch crashed, the whole proc should crash/end.
In response to Superbike32
Superbike32 wrote:
..() to do the default and display the error in the output, and a variable like client.Error(ex)

I'm not sure that's at all feasible in a global handler. I do think maybe a return value could at least say "Hey, pretend there wasn't a handler for this" if it returned a certain way.

As far as try/catch is concerned though the current proc should continue to execute after the catch statement has finished executing. So any code after the statement that's not part of the try/catch should finish executing as well.

Yes, that's how the try/catch works. Except that it isn't the current proc necessarily, but whichever one had the try/catch. If you have a try block in proc A that calls proc B, and B crashes, control goes to the catch statement in A and then A carries on after the catch; B is done. Likewise if B calls C and it's C that crashes, B and C are both done.

This is a departure from the behavior without try/catch, in that when a proc crashes it goes back to the caller immediately where the caller left off. But having a try/catch that only operated on the current proc seemed pretty dumb, since no other languages do that--except maybe Visual Basic.

I don't know if you've thought about errors occurring inside the catch statement of a try/catch but those should crash the current proc entirely so that even if there's code after the catch statement in the same proc, since the catch crashed, the whole proc should crash/end.

Here's how it works, for some clarity: When a try instruction is encountered, the proc data is told there's an active try block. If it leaves the try block (via goto, break, continue, return, or just the end of the block), that block info is erased. If there's an exception while the current proc or any of its callers has an active try, control jumps to the catch that goes with that block.

That means if there's an error inside a catch block, it will act like normal code. So unless the try/catch is itself nested in another try/catch (I have not tested this case yet but it's allowable), or there's a global handler, the proc will crash the way it typically would.
In response to Lummox JR
Yea, I think if it returned a certain way then it should pretend there's no handler for it basically.

And try/catch seems to work as one would expect except that I didn't think about try/catch being put into another try/catch, even if it weren't possible though it could've been worked around just by saving "ex" to a variable and using another try/catch right after the previous one.

Anyways I know programming languages, even VB, and it works the same as C++ and C# do and if you use "Try InsertProcHere() Catch() it will catch any errors thrown by the proc that was called.

***Anyways as far as the stack is concerned, what limitations are there for grabbing the stack, because it will still error just as before, so the stack should be generated, but after it's generated but before it exits the proc it should see it was part of a try/catch and return execution to the catch block of execution and instead continue execution rather than stop it.

Is this not how it essentially works? Or does it bypass the current error handling all together when it's part of a try/catch?
In response to Lummox JR
Lummox JR wrote:
What sort of information are you looking to get, though? If I were to add filename and line number info to the exception, that doesn't really tell you anything special.

The filename and line number info should already be available if you have debugging enabled, so I don't know why that would be an issue. I think it's something special to know where to look for an error.

That's not really the kind of info I was referring to though. For example, you can use runtime errors to gather the command usage and argument types for verbs that have an unmodified description setting. Actually, you can still get the original argument types for verbs with custom descriptions by creating a new verb of the same "type", but with an empty description, and testing the new version instead. There is no other way to obtain this info, and it could be useful for designing custom command-line interfaces, or user-defined macros.


As for the error handling, everything sounds mostly ideal so far. Getting the call stack would be nice too. I would assume that it would at least be available to the global handler, since it could probably reuse the existing functionality.
In response to Multiverse7
Multiverse7 wrote:
Lummox JR wrote:
What sort of information are you looking to get, though? If I were to add filename and line number info to the exception, that doesn't really tell you anything special.

The filename and line number info should already be available if you have debugging enabled, so I don't know why that would be an issue. I think it's something special to know where to look for an error.

What I mean is, that info is fairly mundane. Useful, but not in the way you implied.

That's not really the kind of info I was referring to though. For example, you can use runtime errors to gather the command usage and argument types for verbs that have an unmodified description setting. Actually, you can still get the original argument types for verbs with custom descriptions by creating a new verb of the same "type", but with an empty description, and testing the new version instead. There is no other way to obtain this info, and it could be useful for designing custom command-line interfaces, or user-defined macros.

I don't really understand what you mean by this. If you wrote the game, you should already know how the verb usage works. Why would you need to obtain info you already have? Unlike, say, detecting the number of states in an icon, I don't see how verb usage info would be important to know at runtime rather than at compile-time.
In response to Lummox JR
Lummox JR wrote:
I don't see how verb usage info would be important to know at runtime rather than at compile-time.

It's ironic how funny that would be in any other context.

The info wouldn't be something I would need to know, but for the game to make use of. It's possible that I might dynamically generate a lot of verbs with potentially random input types, and need a way to automate a command syntax help system, so that users know how to enter any given command. This is all really hypothetical and it isn't all that important.
In response to Multiverse7
You can't dynamically generate verbs at runtime. You can copy existing ones, but you can't create brand new ones.
In response to Lummox JR
Copying existing ones is what I meant. I don't expect to redefine a verb's type. That is set in stone. Essentially what I'm talking about is allowing the user to give new names to a predefined pool of verbs. Such verbs might contain conditions that switch their functionality based on the presence of certain verbs with certain names in a verbs list. It's even possible to allow a verb to gain a reference to itself and know its own name! It's all about the collective interaction that can give rise to something new.
Hrm, well, it seems pretty outside the scope of what's needed for exception handling, I'd have to say.
I assume if the default crash becomes a datum, it would be a true datum that we can sub-type, and add procs to, like /mob?

That would be very helpful, I think, as it allows the developer to reason over the exception/error as an object. So for instance, my personal first port of call would be do to:

// The runtime actually constructs default errors of this type, passing in line and file to New().
error
var/line
var/file

New(var/line as num, var/file as text)
src.line = line
src.file = file

// I, as a developer, then add the following:
proc
line_number()
return src.line

file_name()
return src.file

print(var/target)
target << "[src.type] thrown in [src.file]:[src.line]"

// And I sub-type it, to allow wrapping of errors:
error/wrappederror
var/error/cause

New(var/error/cause, var/line as num, var/file as text)
..(line, file)
src.cause = cause

line_number()
return src.cause.line_number()

file_name()
return src.cause.file_name()

print(var/target)
cause.print(target)
..(target)

// Then for a (contrived) use-case:
proc
add_two(var/input)
. = 2 + input

silly_proc(var/input)
try
. = add_two("[input]")
catch(var/error/ex)
throw new/error/wrappederror(ex, __LINE__, __FILE__)

client
verb
perform_calculation(var/input as num)
try
world << silly_proc(input)
catch(var/error/ex)
world << "The calculation failed at [ex.file_name()]:[ex.line_number()], the trace is:"
ex.print(world)


I now have the ability for wrapped errors, that when interrogated directly will provide the file and line of the source error it originated from (handy for hiding the fact I wrapped the error at some point to annotate it with extra information), and can provide some manner of trace. Not a full stack-trace, sure, but some sense of calling context.

Similarly, I could then sub-type errors to capture the usr, the src, and other stuff as is appropriate for the scenario where I am throwing that sub-type.
In response to Stephen001
I suppose there's something to be said for making the proc path (when available), src, and usr available too, although I might try to keep it fairly simple and see how that works out.
It would be nice in the default errors, but not essential.
Page: 1 2 3