forked from Mirror/wren
Make ip an actual pointer.
This commit is contained in:
@ -1,55 +0,0 @@
|
||||
class Base {
|
||||
foo {
|
||||
io.write("a")
|
||||
inner
|
||||
io.write("b")
|
||||
}
|
||||
}
|
||||
|
||||
class Mid is Base {}
|
||||
|
||||
class Derived is Mid {
|
||||
foo {
|
||||
io.write("c")
|
||||
}
|
||||
}
|
||||
|
||||
var d = Derived.new
|
||||
d.foo
|
||||
|
||||
// find base-most method "foo" (on Base)
|
||||
// invoke it
|
||||
// when inner is hit, walk down inheritance chain to find nearest override
|
||||
// (in Derived)
|
||||
// invoke it
|
||||
|
||||
|
||||
for every class, store two lists:
|
||||
|
||||
1. list of methods this class defines.
|
||||
2. mapping of method name to which method body should be invoked first for
|
||||
that method. this will be the base-most class's definition of a given
|
||||
method name.
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The method body to invoke. This will be the base-most definition of a
|
||||
// method for a given name.
|
||||
Method* method;
|
||||
|
||||
// If [method] calls `inner`, this is the class whose inner method is invoked.
|
||||
// If *that* method in turn calls `inner`, we look up the [inner] property of
|
||||
// this method on that class's dispatch table to chain to the next one.
|
||||
//
|
||||
// This is `NULL` if there is no inner method to call.
|
||||
ClassObj* inner;
|
||||
} Dispatch;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Dispatch dispatchTable[MAX_SYMBOLS];
|
||||
} ClassObj;
|
||||
|
||||
with normal overridding, can unify those. with inner(), need both.
|
||||
|
||||
whenever a method is invoked, look it up in 2, then call that method body.
|
||||
80
doc/error-handling.txt
Normal file
80
doc/error-handling.txt
Normal file
@ -0,0 +1,80 @@
|
||||
Q: Can we use fibers for error-handling?
|
||||
|
||||
The goal here is to avoid adding support for exception handling if we're already
|
||||
going to support fibers. A potential bonus would be being able to have
|
||||
restartable error-handling.
|
||||
|
||||
The general idea is that instead of putting code in a "try" block, you throw it
|
||||
onto a new fiber. If an error occurs, that fiber is paused, and returns control
|
||||
back to the spawning fiber. The parent fiber can then decipher the error and
|
||||
either abandon the fiber, or try to fix the error and resume somehow.
|
||||
|
||||
The first question is what kinds of errors is this useful for. For things like
|
||||
parsing strings where failure is common and error-handling needs to be
|
||||
lightweight, I think using fibers is too heavy, both in performance and code.
|
||||
A better answer there is to lean on dynamic typing and return null on parse
|
||||
failure.
|
||||
|
||||
On the other hand, it might be nice to be able to resume here if the code that
|
||||
provided the string is far away and you don't want to have to manually propagate
|
||||
the error out.
|
||||
|
||||
Programmatic errors like unvalid argument types should halt the fiber but the
|
||||
programmer will not want to resume that at runtime. Using the mechanism here is
|
||||
fine since it would then dump a stack trace, etc. But it won't take advantage
|
||||
of resuming.
|
||||
|
||||
Resuming is probably useful for things like IO errors where the error can't be
|
||||
easily predicted beforehand but where you may want to handle it gracefully. For
|
||||
example, if a file can't be opened, the caller may want to wait a while and
|
||||
try again.
|
||||
|
||||
--
|
||||
|
||||
After thinking about it, maybe resuming is a bridge too far. Erlang's model is
|
||||
that a failure just kills the process. I'll note that Erlang does have try and
|
||||
catch, though.
|
||||
|
||||
The goals for error-handling in a scripting language are:
|
||||
|
||||
0. Have simple semantics and implementation.
|
||||
|
||||
1. Make it easy for developers to track down programmatic errors so they can
|
||||
fix them. This means bugs like wrong argument types should fail immediately
|
||||
and loudly, and should provide context (a callstack) about where the error
|
||||
occurred.
|
||||
|
||||
2. For runtime errors like parsing an invalid string or opening a missing file,
|
||||
the program should be able to easily detect the error at handle it.
|
||||
|
||||
3. It *may* be useful for programmers to be able to trap all errors and try to
|
||||
keep the program alive, or at least log the error in a meaningful way. When
|
||||
you have user-defined scripts, or a lot of code, or code authored by
|
||||
non-technical people, it's nice if a failure in one part can be reported but
|
||||
not take down the entire system.
|
||||
|
||||
Two close-at-hand examples:
|
||||
|
||||
- The REPL. A bug in code in the REPL shouldn't kill the whole REPL session.
|
||||
|
||||
- The test framework. In order to write tests in Wren that test programmatic
|
||||
runtime errors, we need to be able to detect them and output something.
|
||||
The test runner could just parse the error output when the entire process
|
||||
dies, but that means you can only have one error test per test file.
|
||||
|
||||
Given those, I'm thinking:
|
||||
|
||||
1. Programmatic errors take down the entire fiber and dump a callstack.
|
||||
Normally, they will also take down the parent fiber and so on until the
|
||||
entire program goes down.
|
||||
|
||||
2. Runtime errors return error codes (or null). Things like parsing a string to
|
||||
a number, etc. should just return an error that you are responsible for
|
||||
handling.
|
||||
|
||||
3. When handing off control to a fiber, there is a "guarded run" method that
|
||||
will run the fiber. If it fails with a programmatic error, the invoked fiber
|
||||
dies, but the parent does not. It gets the callstack and error as some sort
|
||||
of object it can poke at.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user