sub@varname to access the HUB address of a var/dat in a sub-object, alike sub#constant
Ugh -- then there would be three different syntaxes for accessing things in sub-objects (sub.method, sub#const, and sub@var). How about if we use "sub.x" for all 3 of those, like most languages do?
I agree. That " like most languages do" is a very important bit, that should always be the 'first port of call' for a new feature. use "sub.x" for all 3 of those, sounds great.
Spin needs to be more like the languages everyone already uses, and less like character chaff, or users will simply turn away...
sub@varname to access the HUB address of a var/dat in a sub-object, alike sub#constant
Mike
Ugh -- then there would be three different syntaxes for accessing things in sub-objects (sub.method, sub#const, and sub@var). How about if we use "sub.x" for all 3 of those, like most languages do?
(Disclaimer: fastspin already allows "sub.var" in Spin, so I'm biased towards that syntax ).
I just thought of something: Has anything changed about the way ABORT (or rather, catching an ABORT) works?
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
I just thought of something: Has anything changed about the way ABORT (or rather, catching an ABORT) works?
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
[/quote]
ABORT works differently now:
ABORT 'abort and return 0 to \ caller
ABORT value 'abort and return 'value' to \ caller
\method 'receives ABORT value (default=0) or 0 if no ABORT (ignores RETURN data)
So, there are effectively two different result channels now. ABORT always returns a single value, while RETURN can return 0..15 values.
@cgracey : How would you feel about changing ~~: to MCALL? That is, to call a function pointer with 1 result we'd do something like:
x := MCALL(1, func, x)
instead of your existing
x := ~~:1 func (x)
(at least I'm guessing that's your existing syntax? I don't recall an example of passing parameters to a method.)
I think it'd be a little bit easier to read, and would leave ~~ alone so that Spin1 programs that used it for sign extension would either work (if it's implemented in Spin2) or produce an error.
I suppose we could also follow the COGINIT example and do:
Why would you need to specify the number of return values? It seems like that should be included in the method pointer structure. Also, why use ~~ or MCALL? Couldn't you just use the method pointer directly, such as:
PUB test1 | x, y
MPTR func
func := @func1
x, y := func(1, 2, 3)
PUB func1(a, b, c) : result1, result2
result1 := a + b
result2 := b + c
EDIT: For a method with no parameters you would have to use () with the function call, such as func()
Would it be easy to implement "default values", like in C++?
I.e., could you do this (borrowing from Dave's example):
PUB test1 | x, y
MPTR func
func := @func1
x, y := func(1, 2)
PUB func1(a, b, c=3) : result1, result2
result1 := a + b
result2 := b + c
I can't speak for Chip, but I would find it very hard to do that in fastspin. Not the default values (that's already in) but default values for functions called through a pointer. That would require some major baggage to be carried around in the method pointer, and I think it's a bad idea. Default values for functions called directly are relatively easy (since we can look up the function arguments directly). But what would happen in a case like:
PUB test1(funcptr) | x
x := MCALL(1, funcptr(1, 2))
PUB func1(a, b, c=3)
return a+b+c
PUB func2(a, b, c=4)
return a*b*c
PUB func3(a, b)
return a/b
if "funcptr" is func1 then the default 3rd argument should be 3; if it's func2 then the default should be 4; if it's func3 then it shouldn't have a third argument at all. It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
...It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
...It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
The other way to handle the number of return values is to (a) limit the number of possible return values to some small value (like 4 or 8) and (b) always return the values in registers rather than on the stack; that way the caller doesn't need to know how many values were actually returned, they just use the first N register values. If the caller uses more than the function provides then they may get garbage, but that's true anyway.
Actually, I'm wondering if it's worth all this trouble to return multiple values...
Can't you just return a pointer to some VAR longs where the result was stored?
Also, could you just return a pointer to local variables in the function being called? Or, are they already deleted when you return from that function?
Is the qrotate thing the only driver for all of this?
I just thought of something: Has anything changed about the way ABORT (or rather, catching an ABORT) works?
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
ABORT works differently now:
ABORT 'abort and return 0 to \ caller
ABORT value 'abort and return 'value' to \ caller
\method 'receives ABORT value (default=0) or 0 if no ABORT (ignores RETURN data)
So, there are effectively two different result channels now. ABORT always returns a single value, while RETURN can return 0..15 values.
Hmm.
I was thinking of the usual TRY/CATCH/FINALLY sort of thing most newer languages have.
Here's an example of what I'm thinking about, if anyone for some reason isn't familiar with the construct. (Also, assume ERRNO is set to the abort value and is cleared before FINALLY is executed)
PUB foo | i
try
sd.start(1,2,3,4)
sd.mount
repeat i from 1 to 9
try
sd.openFile(i) ' pretend this opens a numbered file.
acquire_lock ' acquire lock for @buffer
sd.read(@buffer,123)
catch
sd#FILE_NOT_FOUND_ERR :
' do nothing and continue
other:
abort ERRNO ' rethrow
finally
release_lock
catch
sd#NO_CARD_ERR :
pst.str("Please insert a Card!")
result := -1
sd#ERR_first..sd#ERR_last :
pst.str(sd.err2str(ERRNO)) ' get string from relevant object
result := -2
other:
pst.str("Other error")
abort ERRNO ' rethrow
Also, there should be a way to define unique constants (so every exception can get it's own ID).
@cgracey : How would you feel about changing ~~: to MCALL? That is, to call a function pointer with 1 result we'd do something like:
x := MCALL(1, func, x)
instead of your existing
x := ~~:1 func (x)
(at least I'm guessing that's your existing syntax? I don't recall an example of passing parameters to a method.)
I think it'd be a little bit easier to read, and would leave ~~ alone so that Spin1 programs that used it for sign extension would either work (if it's implemented in Spin2) or produce an error.
I suppose we could also follow the COGINIT example and do:
x := MCALL(1, func(x))
although that might be a bit harder to parse.
I don't know what I want to do, yet, about all this. I just realized, I think because someone here pointed it out, that 'somelong := @method' is sufficient to establish a function pointer. A function pointer is a long that points to a VAR base for an object and provides the method index in the top 12 bits (bottom 20 point to VAR). Meanwhile, every object has the first long of its VAR space reserved as a pointer to its associated OBJ base, which contains the code. This way, all method pointers are just LONGs. No special method-pointer type is creeping into the scene here. It's just a long, which could even be an indexed array element.
About changing to 'MCALL', I just don't know, yet. The way it is now is actually like this:
If there are no parameters, no need for '(Parameters,...)'. If there are no return values, no need for ':ReturnValues'. I don't like ~~ much, but it is terse. I suppose '^^' could be used, if no name is used.
I just thought of something: Has anything changed about the way ABORT (or rather, catching an ABORT) works?
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
ABORT works differently now:
ABORT 'abort and return 0 to \ caller
ABORT value 'abort and return 'value' to \ caller
\method 'receives ABORT value (default=0) or 0 if no ABORT (ignores RETURN data)
So, there are effectively two different result channels now. ABORT always returns a single value, while RETURN can return 0..15 values.
Hmm.
I was thinking of the usual TRY/CATCH/FINALLY sort of thing most newer languages have.
Here's an example of what I'm thinking about, if anyone for some reason isn't familiar with the construct. (Also, assume ERRNO is set to the abort value and is cleared before FINALLY is executed)
PUB foo | i
try
sd.start(1,2,3,4)
sd.mount
repeat i from 1 to 9
try
sd.openFile(i) ' pretend this opens a numbered file.
acquire_lock ' acquire lock for @buffer
sd.read(@buffer,123)
catch
sd#FILE_NOT_FOUND_ERR :
' do nothing and continue
other:
abort ERRNO ' rethrow
finally
release_lock
catch
sd#NO_CARD_ERR :
pst.str("Please insert a Card!")
result := -1
sd#ERR_first..sd#ERR_last
pst.str(sd.err2str(ERRNO)) ' get string from relevant object
result := -2
other:
pst.str("Other error")
abort ERRNO ' rethrow
Also, there should be a way to define unique constants (so every exception can get it's own ID).
I was struggling with that the other day in Delphi (Pascal). It seems tricky to know what to put in the 'finally' block, especially. In your code, if sd.openFile blew up, you would never get to acquire_lock, while you'd be releasing some lock in 'finally'. This is the same type of thing I was having trouble with in Delphi. My gut feeling is that I don't like it, probably because of the invitation for too many things to get mis-mixed together in the three blocks. I think code like that needs to be broken down into more discrete chunks. One of the most important disciplines in programming, to me, is to carefully avoid mixing things together that could cause bad interactions. Better to keep things discrete. New programmers know maybe nothing about this and are not realizing that everything comes down to discrete decisions that are best arranged discretely. This man I knew who wrote C compilers a while back said that some features were 'a cannon pointed at the user's feet'.
ABORT values could be whatever you declare them as, and we could have some default values that are universally usable.
It seems tricky to know what to put in the 'finally' block. In your code, if sd.openFile blew up, you would never get to acquire_lock, while you'd be releasing some lock in 'finally'. This is the same type of thing I was having trouble with in Delphi. My gut feeling is that I don't like it, probably because of the potential for too many things to get mis-mixed together in the three blocks. I think code like that needs to be broken down into more discrete chunks.
I guess that's true to some degree (altough ideally release_lock would be programmed to do nothing or ABORT if the lock wasn't aquired first)
I'm not sure terseness is a virtue here. When we need method pointers we really need them, so they are definitely useful, but judging by past experience that need doesn't arise often. So (a) method pointer calls probably won't be typed frequently, which also means that (b) it'll be hard to remember what magic characters are used to invoke them. Spin has (perhaps unfairly) been criticized for its large number of operators with cryptic symbols. It looks like Spin2 will reduce the number of weird character combinations by using keywords like REV and ABS, so perhaps doing the same for method calls would be good?
Another alternative would be to borrow the syntax for regular pointer dereference, something like:
MPTR[funcptr] ' call funcptr, no parameters needed, no return value
x := MPTR:1[funcptr](a, b, c)
x,y := MPTR:2[funcptr](x, y)
I'm not sure terseness is a virtue here. When we need method pointers we really need them, so they are definitely useful, but judging by past experience that need doesn't arise often. So (a) method pointer calls probably won't be typed frequently, which also means that (b) it'll be hard to remember what magic characters are used to invoke them. Spin has (perhaps unfairly) been criticized for its large number of operators with cryptic symbols. It looks like Spin2 will reduce the number of weird character combinations by using keywords like REV and ABS, so perhaps doing the same for method calls would be good?
Another alternative would be to borrow the syntax for regular pointer dereference, something like:
MPTR[funcptr] ' call funcptr, no parameters needed, no return value
x := MPTR:1[funcptr](a, b, c)
x,y := MPTR:2[funcptr](x, y)
Yes, it's a rare thing, probably. I understand what you are getting at, but it seems like all those alternatives are mainly lex- and yacc-friendly, and less human-friendly. It's funny how far existing compiler tools have driven language design, and how they seem to have the long-term consequence of making all languages so similar that many people start to get more annoyed by the remaining differences than the benefits, until we are all demanding C or Python. Still not sure what to do.
P.S. I accidentally edited your last post, instead of quoting it. I think I got it straightened out, though.
...It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
I don't understand why knowing the number of return values is impossible. Can't this be determined at compile time, and then stored in the function ptr structure? How does that defeat the whole purpose?
EDIT: After thinking about it, it seems that the function ptr structure doesn't need to store the number of return values. It's just implicit in the calling syntax. As an example:
x, y, z = funcptr()
clearly returns 3 values. It's up to the programmer to make sure this matches the called method. Just like it's up to the programmer to make sure the number of calling parameters match.
...It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
I don't understand why knowing the number of return values is impossible. Can't this be determined at compile time, and then stored in the function ptr structure? How does that defeat the whole purpose?
The purpose of a pointer is to be flexible.
Ah, I see what you are getting at. The number of return values (and parameters) might as well be known at compile-time to ensure that things match up with the code that uses them. That could be accomplished by a special type of long variable, which has some additional attributes that the compiler tracks, like associated return value and parameter counts. That long variable would always have to be used as such. That would impinge on flexibility somewhat, but not totally. You'd have to have different long variables for pointers to differently-sized methods.
I'm not sure terseness is a virtue here. When we need method pointers we really need them, so they are definitely useful, but judging by past experience that need doesn't arise often. So (a) method pointer calls probably won't be typed frequently, which also means that (b) it'll be hard to remember what magic characters are used to invoke them. Spin has (perhaps unfairly) been criticized for its large number of operators with cryptic symbols. It looks like Spin2 will reduce the number of weird character combinations by using keywords like REV and ABS, so perhaps doing the same for method calls would be good?
Another alternative would be to borrow the syntax for regular pointer dereference, something like:
MPTR[funcptr] ' call funcptr, no parameters needed, no return value
x := MPTR:1[funcptr](a, b, c)
x,y := MPTR:2[funcptr](x, y)
Yes, it's a rare thing, probably. I understand what you are getting at, but it seems like all those alternatives are mainly lex- and yacc-friendly, and less human-friendly. It's funny how far existing compiler tools have driven language design, and how they seem to have the long-term consequence of making all languages so similar that many people start to get more annoyed by the remaining differences than the benefits, until we are all demanding C or Python. Still not sure what to do.
I wasn't thinking of the ease of parsing in those alternatives, I was thinking of how easy it would be for a programmer to remember the syntax. "MCALL(1, funcptr(x,y))" is like COGINIT, so it's kind of easy to remember. "MPTR[ptr]" is just like "LONG[ptr]", it dereferences ptr and produces a result with a particular type (either a method or a long). Maybe "METHOD[ptr]" would be a better syntax. Anyway, I feel like either of these would be a more natural fit with Spin.
It's no biggie either way, it's certainly easy enough to parse "~~ptr:N" with whatever tools one uses. I personally don't like "~~" both because it's already used in Spin1 and because it's another entry in the symbol soup. But maybe that's just me.
I'm assuming that the function pointer points to a structure that contains descriptive information about the method. In the MethodPointer object that I wrote 8 years ago, the method pointer structure consisted of two longs that contained the object base address, variable base address, stack variable size and starting address of a method.
...It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
I don't understand why knowing the number of return values is impossible. Can't this be determined at compile time, and then stored in the function ptr structure? How does that defeat the whole purpose?
Chip isn't using a structure to hold the function pointer, he's using a 32 bit value (20 bit base pointer + 12 bit index). So there's no room for number of return values, although I suppose the number of entries permitted per object could be reduced so we get a 9 bit index plus 3 bit return value count. But then what about number of parameters? It all starts to get a bit hairy.
I think he needs to know the number of return values because space for them is pre-allocated on the stack at the call point. The number of parameters is not such an issue because they're just pushed on to the stack and the caller is responsible for cleaning up, if I understand correctly.
EDIT: After thinking about it, it seems that the function ptr structure doesn't need to store the number of return values. It's just implicit in the calling syntax. As an example:
x, y, z = funcptr()
clearly returns 3 values. It's up to the programmer to make sure this matches the called method. Just like it's up to the programmer to make sure the number of calling parameters match.
That's a reasonable approach too. It does put a bit more work on the compiler's parser, but shifting the burden from programmer to compiler is probably not a bad thing. The one place where this might fall down is when a function with a return value is called but the return value is never assigned, e.g.:
PUB setval(newx) : oldx
oldx := x
x := oldx
...
funcptr := @setval
...
METHOD[funcptr](10) 'set x to 10, we do not care what its old value was
But I think that could be handled by always having space allocated for (at least) one return value, as I believe is done in the Spin1 interpreter.
ersmith,
I agree with you about ~~. I'm not a fan at all. I prefer any of your suggestions.
Chip:
I would expected a function pointer to be for a specific number of params and a specific number of return values. That's what I am used to from other languages. While your current plan is more flexible, it also opens the door to more hard to find obscure bugs.
I'm not sure terseness is a virtue here. When we need method pointers we really need them, so they are definitely useful, but judging by past experience that need doesn't arise often. So (a) method pointer calls probably won't be typed frequently, which also means that (b) it'll be hard to remember what magic characters are used to invoke them. Spin has (perhaps unfairly) been criticized for its large number of operators with cryptic symbols. It looks like Spin2 will reduce the number of weird character combinations by using keywords like REV and ABS, so perhaps doing the same for method calls would be good?
Another alternative would be to borrow the syntax for regular pointer dereference, something like:
MPTR[funcptr] ' call funcptr, no parameters needed, no return value
x := MPTR:1[funcptr](a, b, c)
x,y := MPTR:2[funcptr](x, y)
Yes, it's a rare thing, probably. I understand what you are getting at, but it seems like all those alternatives are mainly lex- and yacc-friendly, and less human-friendly. It's funny how far existing compiler tools have driven language design, and how they seem to have the long-term consequence of making all languages so similar that many people start to get more annoyed by the remaining differences than the benefits, until we are all demanding C or Python. Still not sure what to do.
I wasn't thinking of the ease of parsing in those alternatives, I was thinking of how easy it would be for a programmer to remember the syntax. "MCALL(1, funcptr(x,y))" is like COGINIT, so it's kind of easy to remember. "MPTR[ptr]" is just like "LONG[ptr]", it dereferences ptr and produces a result with a particular type (either a method or a long). Maybe "METHOD[ptr]" would be a better syntax. Anyway, I feel like either of these would be a more natural fit with Spin.
It's no biggie either way, it's certainly easy enough to parse "~~ptr:N" with whatever tools one uses. I personally don't like "~~" both because it's already used in Spin1 and because it's another entry in the symbol soup. But maybe that's just me.
I'm mulling over all the possibilities that I can think of.
If we make method pointers contain extra data which only the compiler tracks, we could stave off lots of user errors:
VAR MPTR w(1), x(3):1, y:2, z 'these are longs with a special 'method pointer' type
'w' would be a method pointer that had one parameter and no return value.
'x' would be a method pointer that had three parameters and one return value.
'y' would be a method pointer that had no parameters and two return values.
'z' would be a method pointer that had no parameters and no return values.
To assign method pointers:
MPTR(VarMethodPtr, {obj{[]}}.method{[]}) 'VarMethodPtr would be assigned method, compiler can check for match-up
To use method pointers:
w(param1)
i := x(param1,param2,param3)
i,j := y
z
z[index]
The compiler would know that occurrence of the method pointer implies a call to a method.
The limitation would be that all method pointers would have to be declared in VAR blocks and you couldn't reuse method pointers for differently-sized methods.
What about something like that? It gets rid of a lot of jiggery-pokery. I think it makes things air-tight, too.
Requiring that all method pointers be declared in VAR blocks would complicate one of the most common use cases, which is passing a callback as a parameter to a function.
I'm also curious about zero return value versus one return value functions. Is:
PUB usecallback(funcptr)
x := METHOD(2:1)[funcptr](a, b) ' two arguments, 1 return value
or
PUB usecallback(funcptr)
x := METHOD(x,y):z[funcptr](a, b)
Although to be honest I like @"Dave Hein" 's suggestion that the compiler should just examine the context and deduce the number of parameters and return values from that.
It is true that none of these methods prevent the programmer from shooting themselves in the foot. For that we'd need to add actual types to Spin, which would be a big change to the language but would bring other benefits (and costs, too!)
Requiring that all method pointers be declared in VAR blocks would complicate one of the most common use cases, which is passing a callback as a parameter to a function.
I'm also curious about zero return value versus one return value functions. Is:
PUB foo
return 0
foo ' ignore the return value
legal in Spin2, or do you have to write
_ := foo ' ignore the return value
You can use methods as instructions. The return values are not passed in that case.
So, no need for a blank assignment.
P.S. Ah, good point about pointers not being passable. I wasn't thinking enough.
Comments
I agree. That " like most languages do" is a very important bit, that should always be the 'first port of call' for a new feature.
use "sub.x" for all 3 of those, sounds great.
Spin needs to be more like the languages everyone already uses, and less like character chaff, or users will simply turn away...
+1
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
I just thought of something: Has anything changed about the way ABORT (or rather, catching an ABORT) works?
The way it works in Spin1 is really, really terrible. (how would it even work with multiple return values?)
You can't tell if there even was an ABORT. (this is the big one)
You don't really know what the ABORT value means (especially if it was already caught deeper down the stack and is being re-thrown)
If nothing has been done about this yet, I could post how I would solve the issue.
[/quote]
ABORT works differently now:
ABORT 'abort and return 0 to \ caller
ABORT value 'abort and return 'value' to \ caller
\method 'receives ABORT value (default=0) or 0 if no ABORT (ignores RETURN data)
So, there are effectively two different result channels now. ABORT always returns a single value, while RETURN can return 0..15 values.
I don't think it's used in any of the Propeller Tool's library examples or demos (or am I wrong?).
I think FSRW examples might be the only place I see it...
I think it'd be a little bit easier to read, and would leave ~~ alone so that Spin1 programs that used it for sign extension would either work (if it's implemented in Spin2) or produce an error.
I suppose we could also follow the COGINIT example and do: although that might be a bit harder to parse.
I.e., could you do this (borrowing from Dave's example):
I can't speak for Chip, but I would find it very hard to do that in fastspin. Not the default values (that's already in) but default values for functions called through a pointer. That would require some major baggage to be carried around in the method pointer, and I think it's a bad idea. Default values for functions called directly are relatively easy (since we can look up the function arguments directly). But what would happen in a case like: if "funcptr" is func1 then the default 3rd argument should be 3; if it's func2 then the default should be 4; if it's func3 then it shouldn't have a third argument at all. It's impossible (even in theory) to determine which function is funcptr at compile time, so it would all have to be done at run time. This would impose an overhead on all indirect method calls, even if optional arguments are never used.
Knowing the number of RETURN values is likewise impossible.
To get around this, you would have to constrain function pointers to compile-time definitions, which would defeat the whole purpose.
The other way to handle the number of return values is to (a) limit the number of possible return values to some small value (like 4 or 8) and (b) always return the values in registers rather than on the stack; that way the caller doesn't need to know how many values were actually returned, they just use the first N register values. If the caller uses more than the function provides then they may get garbage, but that's true anyway.
Can't you just return a pointer to some VAR longs where the result was stored?
Also, could you just return a pointer to local variables in the function being called? Or, are they already deleted when you return from that function?
Is the qrotate thing the only driver for all of this?
Hmm.
I was thinking of the usual TRY/CATCH/FINALLY sort of thing most newer languages have.
Here's an example of what I'm thinking about, if anyone for some reason isn't familiar with the construct. (Also, assume ERRNO is set to the abort value and is cleared before FINALLY is executed)
Also, there should be a way to define unique constants (so every exception can get it's own ID).
I don't know what I want to do, yet, about all this. I just realized, I think because someone here pointed it out, that 'somelong := @method' is sufficient to establish a function pointer. A function pointer is a long that points to a VAR base for an object and provides the method index in the top 12 bits (bottom 20 point to VAR). Meanwhile, every object has the first long of its VAR space reserved as a pointer to its associated OBJ base, which contains the code. This way, all method pointers are just LONGs. No special method-pointer type is creeping into the scene here. It's just a long, which could even be an indexed array element.
About changing to 'MCALL', I just don't know, yet. The way it is now is actually like this:
~~SomeMethodPtr:ReturnValues(Parameters,...)
There is some economy in this:
~~SomeMethodPtr
~~SomeMethodPtr(Parameters,...)
~~SomeMethodPtr:1
~~SomeMethodPtr:2(Parameters,...)
If there are no parameters, no need for '(Parameters,...)'. If there are no return values, no need for ':ReturnValues'. I don't like ~~ much, but it is terse. I suppose '^^' could be used, if no name is used.
~~SomeMethodPtr
~~SomeMethodPtr(Parameters,...)
~~SomeMethodPtr:1
~~SomeMethodPtr(Parameters,...):2
That would coincide nicely with how methods are declared.
I was struggling with that the other day in Delphi (Pascal). It seems tricky to know what to put in the 'finally' block, especially. In your code, if sd.openFile blew up, you would never get to acquire_lock, while you'd be releasing some lock in 'finally'. This is the same type of thing I was having trouble with in Delphi. My gut feeling is that I don't like it, probably because of the invitation for too many things to get mis-mixed together in the three blocks. I think code like that needs to be broken down into more discrete chunks. One of the most important disciplines in programming, to me, is to carefully avoid mixing things together that could cause bad interactions. Better to keep things discrete. New programmers know maybe nothing about this and are not realizing that everything comes down to discrete decisions that are best arranged discretely. This man I knew who wrote C compilers a while back said that some features were 'a cannon pointed at the user's feet'.
ABORT values could be whatever you declare them as, and we could have some default values that are universally usable.
I guess that's true to some degree (altough ideally release_lock would be programmed to do nothing or ABORT if the lock wasn't aquired first)
Another alternative would be to borrow the syntax for regular pointer dereference, something like:
Yes, it's a rare thing, probably. I understand what you are getting at, but it seems like all those alternatives are mainly lex- and yacc-friendly, and less human-friendly. It's funny how far existing compiler tools have driven language design, and how they seem to have the long-term consequence of making all languages so similar that many people start to get more annoyed by the remaining differences than the benefits, until we are all demanding C or Python. Still not sure what to do.
P.S. I accidentally edited your last post, instead of quoting it. I think I got it straightened out, though.
I don't understand why knowing the number of return values is impossible. Can't this be determined at compile time, and then stored in the function ptr structure? How does that defeat the whole purpose?
EDIT: After thinking about it, it seems that the function ptr structure doesn't need to store the number of return values. It's just implicit in the calling syntax. As an example: clearly returns 3 values. It's up to the programmer to make sure this matches the called method. Just like it's up to the programmer to make sure the number of calling parameters match.
The purpose of a pointer is to be flexible.
Ah, I see what you are getting at. The number of return values (and parameters) might as well be known at compile-time to ensure that things match up with the code that uses them. That could be accomplished by a special type of long variable, which has some additional attributes that the compiler tracks, like associated return value and parameter counts. That long variable would always have to be used as such. That would impinge on flexibility somewhat, but not totally. You'd have to have different long variables for pointers to differently-sized methods.
I wasn't thinking of the ease of parsing in those alternatives, I was thinking of how easy it would be for a programmer to remember the syntax. "MCALL(1, funcptr(x,y))" is like COGINIT, so it's kind of easy to remember. "MPTR[ptr]" is just like "LONG[ptr]", it dereferences ptr and produces a result with a particular type (either a method or a long). Maybe "METHOD[ptr]" would be a better syntax. Anyway, I feel like either of these would be a more natural fit with Spin.
It's no biggie either way, it's certainly easy enough to parse "~~ptr:N" with whatever tools one uses. I personally don't like "~~" both because it's already used in Spin1 and because it's another entry in the symbol soup. But maybe that's just me.
I think he needs to know the number of return values because space for them is pre-allocated on the stack at the call point. The number of parameters is not such an issue because they're just pushed on to the stack and the caller is responsible for cleaning up, if I understand correctly.
That's a reasonable approach too. It does put a bit more work on the compiler's parser, but shifting the burden from programmer to compiler is probably not a bad thing. The one place where this might fall down is when a function with a return value is called but the return value is never assigned, e.g.:
But I think that could be handled by always having space allocated for (at least) one return value, as I believe is done in the Spin1 interpreter.
I agree with you about ~~. I'm not a fan at all. I prefer any of your suggestions.
Chip:
I would expected a function pointer to be for a specific number of params and a specific number of return values. That's what I am used to from other languages. While your current plan is more flexible, it also opens the door to more hard to find obscure bugs.
I'm mulling over all the possibilities that I can think of.
If we make method pointers contain extra data which only the compiler tracks, we could stave off lots of user errors:
VAR MPTR w(1), x(3):1, y:2, z 'these are longs with a special 'method pointer' type
'w' would be a method pointer that had one parameter and no return value.
'x' would be a method pointer that had three parameters and one return value.
'y' would be a method pointer that had no parameters and two return values.
'z' would be a method pointer that had no parameters and no return values.
To assign method pointers:
MPTR(VarMethodPtr, {obj{[]}}.method{[]}) 'VarMethodPtr would be assigned method, compiler can check for match-up
To use method pointers:
w(param1)
i := x(param1,param2,param3)
i,j := y
z
z[index]
The compiler would know that occurrence of the method pointer implies a call to a method.
The limitation would be that all method pointers would have to be declared in VAR blocks and you couldn't reuse method pointers for differently-sized methods.
What about something like that? It gets rid of a lot of jiggery-pokery. I think it makes things air-tight, too.
I'm also curious about zero return value versus one return value functions. Is: legal in Spin2, or do you have to write
Although to be honest I like @"Dave Hein" 's suggestion that the compiler should just examine the context and deduce the number of parameters and return values from that.
It is true that none of these methods prevent the programmer from shooting themselves in the foot. For that we'd need to add actual types to Spin, which would be a big change to the language but would bring other benefits (and costs, too!)
You can use methods as instructions. The return values are not passed in that case.
So, no need for a blank assignment.
P.S. Ah, good point about pointers not being passable. I wasn't thinking enough.