The STRUCT keywords are missing on the CON sPoint and sLine entries in the "StructureExample.spin2" example file for PNut v45, here is a corrected version.
The STRUCT keywords are missing on the CON sPoint and sLine entries in the "StructureExample.spin2" example file for PNut v45, here is a corrected version.
@cgracey said:
Here is how I see this, and it's very simple:
[ptr] := @variable
ptr[++]
When it comes to pointers, what is inside the brackets has to do with the pointer, itself.
This all stems from making pointers first-class, just like regular variables are.
Well, seems that there is nothing I can say to convince you that disrupting the operators effect is a bad idea, as well there is nothing you can say to convince me of the opposite.
@cgracey said:
Here is how I see this, and it's very simple:
[ptr] := @variable
ptr[++]
When it comes to pointers, what is inside the brackets has to do with the pointer, itself.
This all stems from making pointers first-class, just like regular variables are.
This change makes pointers second-class. Making them first class would mean making it possible to use normal variable operations on them, just like any other first-class value.
@cgracey said:
Here is how I see this, and it's very simple:
[ptr] := @variable
ptr[++]
When it comes to pointers, what is inside the brackets has to do with the pointer, itself.
This all stems from making pointers first-class, just like regular variables are.
This change makes pointers second-class. Making them first class would mean making it possible to use normal variable operations on them, just like any other first-class value.
My use> @macca said:
@cgracey said:
Here is how I see this, and it's very simple:
[ptr] := @variable
ptr[++]
When it comes to pointers, what is inside the brackets has to do with the pointer, itself.
This all stems from making pointers first-class, just like regular variables are.
Well, seems that there is nothing I can say to convince you that disrupting the operators effect is a bad idea, as well there is nothing you can say to convince me of the opposite.
I have thought a lot about how to do this and it's the only way that feels right to me. I don't want to disappoint anyone, but I can't bring myself to do what feels wrong. I've been thinking about this for nearly a year. I would have had it done a long time ago if I had not been talked out of it. A lot of time was lost due to doubt and uncertainty. Now that it's done, I feel good about it and I can move forward again.
Chip can you perhaps show us some more real-world examples of how this hybrid pointer/reference approach can be used. Maybe that will help alleviate some of our concerns. For example is this going to be the potential sort of syntax for how you could implement a method for finding a character in a string when passing in one of these new pointers you have come up with?
PUB findchar(^BYTE string, BYTE char) : result
repeat while string
if string==char
return [string]
string[++]
If you declare a pointer to some structure what happens when you add something directly to it?
PUB test(^STRUCTNAME structure) : result
structure+=3
What does this do? Does it try to add a long or a byte or a word value of 3 to the first element of the passed in structure? Seems like this would be a somewhat limited use of addition operations to only add to the first element.
Also can you do this?
PUB extract(^BYTE stringptr, offset) : result
return stringptr[offset] ' returns the element requested at some offset pos
I'm hoping we don't end up with too many nested brackets in lots of places doing pointer operations given you've sort of overloaded the array index operator in order to do pointer arithmetic.
@rogloh said:
Chip can you perhaps show us some more real-world examples of how this hybrid pointer/reference approach can be used. Maybe that will help alleviate some of our concerns. For example is this going to be the potential sort of syntax for how you could implement a method for finding a character in a string when passing in one of these new pointers you have come up with?
PUB findchar(^BYTE string, BYTE char) : result
repeat while string
if string==char
return [string]
string[++]
The only type overrides allowed for parameters and return values are pointers and small structures (15 longs, max). So, you can't do BYTE, since all parameters must be longs. Pointers are longs and small structures are passed/returned as zero-padded sets of longs.
That last string[++] would just be a lone variable without an operator, causing a compile-time error. You would need to code it as [string]++.
You could code that like this:
PUB findchar(^BYTE string, char) : result
repeat while string
if string[++] == char
return --[string]
If you declare a pointer to some structure what happens when you add something directly to it?
PUB test(^STRUCTNAME structure) : result
structure+=3
What does this do? Does it try to add a long or a byte or a word value of 3 to the first element of the passed in structure? Seems like this would be a somewhat limited use of addition operations to only add to the first element.
The only operators allowed on structures are := to assign, :=: to swap contents with a same-sized structure, ~ to clear it, and ~~ to set it to all $FF's.
If structure had a byte/word/long member 'x', then you could do structure.x += 3.
Also can you do this?
PUB extract(^BYTE stringptr, offset) : result
return stringptr[offset] ' returns the element requested at some offset pos
Yes, that would work as you stated.
I'm hoping we don't end up with too many nested brackets in lots of places doing pointer operations given you've sort of overloaded the array index operator in order to do pointer arithmetic.
I don't thing there'd be many occassions for nested brackets.
@cgracey said:
Here is how I see this, and it's very simple:
[ptr] := @variable
ptr[++]
When it comes to pointers, what is inside the brackets has to do with the pointer, itself.
This all stems from making pointers first-class, just like regular variables are.
This change makes pointers second-class. Making them first class would mean making it possible to use normal variable operations on them, just like any other first-class value.
I was thinking that the pointer, in operation, is first-class, where it IS the variable pointed to. Then, you must use [pointer] notation to operate on the pointer, itself.
@pointer is equivalent to [pointer], whereas @[pointer] is the address of the pointer, itself.
[pointer] must be used for assigning the pointer value, though pointer[++] modifies the pointer value and presents the variable being pointed to for read/modify/write purposes.
I think of the new bracket usage as dealing with the pointer's internal value:
[pointer]
pointer[++]
[--]pointer
What is INSIDE the brackets and related to the pointer variable is affecting the pointer INSIDE, not the pointed-to variable which is its OUTSIDE character.
PUB extract(^BYTE stringptr, offset) : result
return stringptr[offset] ' returns the element requested at some offset pos
Yes, that would work as you stated.
Ok thanks for the reply Chip. This part is probably about the only thing I like about it so far - probably because it works the same as C in this example. In C a char pointer can also be accessed as an array of chars, with the array operator dereferencing the pointer at the element offset provided by the array index. Your SPIN2 code works the same there even though the other way of dereferencing by name alone is automatic (like C references instead of pointers). So stringptr on it's own returns the first byte element (instead of the address itself) which is also the same as accessing stringptr[0]. You could say stringptr == stringptr[0] .
char extract(char *stringptr, int offset)
{
return stringptr[offset];
}
Hopefully we can still advance the pointer by some integer multiples of the structured size by doing this: [stringptr]+=3
@rogloh said:
So stringptr on it's own returns the first byte element (instead of the address itself) which is also the same as accessing stringptr[0]. You could say stringptr == stringptr[0] .
Notice that this is true for all variables. There's no difference between an array and a single variable.
@cgracey said:
That last string[++] would just be a lone variable without an operator, causing a compile-time error. You would need to code it as [string]++.
That should really be valid. If a normal increment is valid as a standalone statement, the pointer increment should be, too.
@cgracey said:
I posted a new PNut_v46 at the top of this thread.
DEBUG gating is added. Constant DEBUG_MASK defines 32 bits which can be used to gate DEBUG commands via DEBUG[MaskBitNumber]{(parameters...)}
DEBUG disabling is added. If constant DEBUG_DISABLE is defined as non-0, all DEBUGs will be ignored in that file.
Automatic prepending of the clock-setter program for PASM-only programs can now be inhibited by defining _AUTOCLK as 0.
VAR blocks can now switch type declarations on each line: VAR BYTE a,b,c, WORD d,e,f, LONG g,h,i
New DEBUG command C_Z will output the states of the C and Z flags, such as "C=0 Z=1".
I just downloaded PNut v46 and it appears that the latest changes has broken the ability to read the DEBUG mouse and keyboard.
If you run your example program DEBUG_Mouse_and_Keyboard.spin2 the program just freezes the first time the
debug(`myplot `pc_mouse(@xpos))
statement is executed. I tried adding DEBUG_DISABLE with both zero and non-zero values. The flag did as expected and suppressed running the debug statements when the value was non-zero, but it didn't make any difference on the DEBUG mouse and keyboard issue.
The original program works fine when using PNut v45.
I just downloaded PNut v46 and it appears that the latest changes has broken the ability to read the DEBUG mouse and keyboard.
If you run your example program DEBUG_Mouse_and_Keyboard.spin2 the program just freezes the first time the
debug(`myplot `pc_mouse(@xpos))
statement is executed. I tried adding DEBUG_DISABLE with both zero and non-zero values. The flag did as expected and suppressed running the debug statements when the value was non-zero, but it didn't make any difference on the DEBUG mouse and keyboard issue.
The original program works fine when using PNut v45.
Thanks for pointing this out, Francis. I will fix it.
It is now fixed and if you download v46 again, you will have the new one.
I had inserted a new "C=0 Z=0" string between two strings in the debugger that needed to be contiguous:
I just downloaded PNut v46 and it appears that the latest changes has broken the ability to read the DEBUG mouse and keyboard.
If you run your example program DEBUG_Mouse_and_Keyboard.spin2 the program just freezes the first time the
debug(`myplot `pc_mouse(@xpos))
statement is executed. I tried adding DEBUG_DISABLE with both zero and non-zero values. The flag did as expected and suppressed running the debug statements when the value was non-zero, but it didn't make any difference on the DEBUG mouse and keyboard issue.
The original program works fine when using PNut v45.
Thanks for pointing this out, Francis. I will fix it.
It is now fixed and if you download v46 again, you will have the new one.
I had inserted a new "C=0 Z=0" string between two strings in the debugger that needed to be contiguous:
I just downloaded PNut v46 and it appears that the latest changes has broken the ability to read the DEBUG mouse and keyboard.
If you run your example program DEBUG_Mouse_and_Keyboard.spin2 the program just freezes the first time the
debug(`myplot `pc_mouse(@xpos))
statement is executed. I tried adding DEBUG_DISABLE with both zero and non-zero values. The flag did as expected and suppressed running the debug statements when the value was non-zero, but it didn't make any difference on the DEBUG mouse and keyboard issue.
The original program works fine when using PNut v45.
Thanks for pointing this out, Francis. I will fix it.
It is now fixed and if you download v46 again, you will have the new one.
I had inserted a new "C=0 Z=0" string between two strings in the debugger that needed to be contiguous:
Francis Bauer found a bug that wound up being a problem in the interpreter. FIELD usage was causing the interpreter to crash by doing an unwanted RFVAR instruction.
The bug is fixed now. A new v46 is on the OBEX, plus the P2_PNut_Public is updated.
Chip,
It just dawned on me that there is now a situation where we have multiple device driver objects to use under a single filesystem object. Potentially at the same time. Namely for SD cards. There is the possibility of wanting to access two SD cards using different interfaces and therefore using different drivers ... but both still FAT32 formatted.
Is there, can there be, a way to specify different driver objects without incurring duplication of the filesystem object?
EDIT: I suppose the way to do it is have both driver objects specified in the one filesystem handler object and then add a mechanism in the handler to distinguish between devices ... supporting multiple mounted volumes that way.
Hehe, DOS level functionality. Not something I ever planned on.
Hmm, I guess this is where method pointers would be used effectively. It's a small set of interfacing methods to each driver. So it wouldn't be a big deal to runtime map them ... I'm hopefully guessing is the case since I've not used them before ...
EDIT: Nice, added a CASE selector for assigning the runtime mappings, then a quick round of text replacement in the sources and it works easy. I like it.
EDIT2: Oh, Flexspin doesn't care about specifying number of return arguments. More work to make it compile in Pnut.
I am adding round-robin cooperative multitasking to the Spin2 interpreter. This way, a single Spin2 cog can run many Spin2 programs by splitting its time among them.
Should I implement a duty value for each task, so rather than always executing it in the round-robin loop, it can be set to execute (1..64)/64ths of the time when its turn comes up?
Maybe 64ths is not the way to go and some exponential would be better, but do you see a value in some kind of duty control for each task? The point would be to allow critical tasks to run more frequently than uncritical tasks.
And does anyone have any opinion on how many tasks should be supported? I've got 16 working now, but I could make it unlimited. Do you see any value in that?
Here are the new instructions associated with this feature:
TASKINIT(method(params), stack_address) 'get a new task started
TASKNEXT() 'switch to the next task and eventually return to the next instruction - every task must do this periodically to keep things running
TASKSTOP() 'stop this task - same thing as returning from the method launched by TASKINIT
I am adding round-robin cooperative multitasking to the Spin2 interpreter. This way, a single Spin2 cog can run many Spin2 programs by splitting its time among them.
Should I implement a duty value for each task, so rather than always executing it in the round-robin loop, it can be set to execute (1..64)/64ths of the time when its turn comes up?
Maybe 64ths is not the way to go and some exponential would be better, but do you see a value in some kind of duty control for each task? The point would be to allow critical tasks to run more frequently than uncritical tasks.
And does anyone have any opinion on how many tasks should be supported? I've got 16 working now, but I could make it unlimited. Do you see any value in that?
Here are the new instructions associated with this feature:
TASKINIT(method(params), stack_address) 'get a new task started
TASKNEXT() 'switch to the next task and eventually return to the next instruction - every task must do this periodically to keep things running
TASKSTOP() 'stop this task - same thing as returning from the method launched by TASKINIT
I could add duty into TASKINIT like this:
TASKINIT(duty, method(params), stack_address)
I would use this! and 64ths would be a good granularity. 16 tasks might be quite a bit more than I would need, but I once wondered what the heck I would do with a 1MB hard drive...
Does TASKNEXT() give back the rest of the timeslice? If there is some hangup in the code and TASKNEXT() doesn't get executed I presume all tasks will hang?
@"R Baggett" said:
Does TASKNEXT() give back the rest of the timeslice? If there is some hangup in the code and TASKNEXT() doesn't get executed I presume all tasks will hang?
That's right. TASKNEXT() needs to execute periodically in each task to get to the next task. There is no timeslicing, just task switching.
You just have to sprinkle TASKNEXT() instructions throughout your code.
Cool and useful feature. I've been thinking about building something like that for flexspin.
TASKSTOP should take a task ID/handle to be able to stop a task from the outside (should perhaps also have a TASKID to grab the handle of the current task).
Ideally it should also be possible to force a switch to a particular task. That would go well with being able to set zero priority, where the task never gets activated by the normal round-robin scheduling.
If you used the top of the stack area to store per-task data in some sort of link list structure, you could have infinite tasks. In that case the handle would just be the initial stack pointer.
With so much processing power provided by each cog, this would be a very useful, tidy way of using a SPIN2 cog to it's full extent. I have written multitasked flashforth applications in the past and found the modularity very readable, especially when coming back to it years later. I would favour unlimited tasks if there's a choice.
I would favour a handle, so that a task may be stopped by external code, not just itself.
When a task is terminated, it would be nice to allow the task to tidy up before shutting down, rather than a 'crash' termination. Might need another keyword for that, though?
How would the user determine the required stack depth for each task?
How about setting the task repeat time in mS?
Comments
Oh, yeah. Duh! I wasn't putting it all together.
@cgracey ,
The STRUCT keywords are missing on the CON sPoint and sLine entries in the "StructureExample.spin2" example file for PNut v45, here is a corrected version.
Ah, thanks. I will fix this.
Well, seems that there is nothing I can say to convince you that disrupting the operators effect is a bad idea, as well there is nothing you can say to convince me of the opposite.
This change makes pointers second-class. Making them first class would mean making it possible to use normal variable operations on them, just like any other first-class value.
My use> @macca said:
I have thought a lot about how to do this and it's the only way that feels right to me. I don't want to disappoint anyone, but I can't bring myself to do what feels wrong. I've been thinking about this for nearly a year. I would have had it done a long time ago if I had not been talked out of it. A lot of time was lost due to doubt and uncertainty. Now that it's done, I feel good about it and I can move forward again.
Chip can you perhaps show us some more real-world examples of how this hybrid pointer/reference approach can be used. Maybe that will help alleviate some of our concerns. For example is this going to be the potential sort of syntax for how you could implement a method for finding a character in a string when passing in one of these new pointers you have come up with?
If you declare a pointer to some structure what happens when you add something directly to it?
What does this do? Does it try to add a long or a byte or a word value of 3 to the first element of the passed in structure? Seems like this would be a somewhat limited use of addition operations to only add to the first element.
Also can you do this?
I'm hoping we don't end up with too many nested brackets in lots of places doing pointer operations given you've sort of overloaded the array index operator in order to do pointer arithmetic.
The only type overrides allowed for parameters and return values are pointers and small structures (15 longs, max). So, you can't do BYTE, since all parameters must be longs. Pointers are longs and small structures are passed/returned as zero-padded sets of longs.
That last string[++] would just be a lone variable without an operator, causing a compile-time error. You would need to code it as [string]++.
You could code that like this:
The only operators allowed on structures are := to assign, :=: to swap contents with a same-sized structure, ~ to clear it, and ~~ to set it to all $FF's.
If structure had a byte/word/long member 'x', then you could do structure.x += 3.
Yes, that would work as you stated.
I don't thing there'd be many occassions for nested brackets.
I was thinking that the pointer, in operation, is first-class, where it IS the variable pointed to. Then, you must use [pointer] notation to operate on the pointer, itself.
@pointer is equivalent to [pointer], whereas @[pointer] is the address of the pointer, itself.
[pointer] must be used for assigning the pointer value, though pointer[++] modifies the pointer value and presents the variable being pointed to for read/modify/write purposes.
I think of the new bracket usage as dealing with the pointer's internal value:
[pointer]
pointer[++]
[--]pointer
What is INSIDE the brackets and related to the pointer variable is affecting the pointer INSIDE, not the pointed-to variable which is its OUTSIDE character.
I posted a new PNut_v46 at the top of this thread.
Ok thanks for the reply Chip. This part is probably about the only thing I like about it so far - probably because it works the same as C in this example. In C a char pointer can also be accessed as an array of chars, with the array operator dereferencing the pointer at the element offset provided by the array index. Your SPIN2 code works the same there even though the other way of dereferencing by name alone is automatic (like C references instead of pointers). So stringptr on it's own returns the first byte element (instead of the address itself) which is also the same as accessing stringptr[0]. You could say stringptr == stringptr[0] .
Hopefully we can still advance the pointer by some integer multiples of the structured size by doing this:
[stringptr]+=3
Notice that this is true for all variables. There's no difference between an array and a single variable.
That should really be valid. If a normal increment is valid as a standalone statement, the pointer increment should be, too.
@cgracey,
I just downloaded PNut v46 and it appears that the latest changes has broken the ability to read the DEBUG mouse and keyboard.
If you run your example program DEBUG_Mouse_and_Keyboard.spin2 the program just freezes the first time the
debug(`myplot `pc_mouse(@xpos))
statement is executed. I tried adding DEBUG_DISABLE with both zero and non-zero values. The flag did as expected and suppressed running the debug statements when the value was non-zero, but it didn't make any difference on the DEBUG mouse and keyboard issue.
The original program works fine when using PNut v45.
Thanks for pointing this out, Francis. I will fix it.
It is now fixed and if you download v46 again, you will have the new one.
I had inserted a new "C=0 Z=0" string between two strings in the debugger that needed to be contiguous:
I changed it to this and PC_MOUSE started working again:
@cgracey,
Thank you for the quick fix, the updated v46 allows the DEBUG Mouse and Keyboard to work.
Do you have an example of how to use the new DEBUG C_Z command?
Here is what it does. It's really only useful for PASM code, so I made an inline PASM routine to do C_Z's.
I updated the PNut_public repository to v46:
https://github.com/parallaxinc/P2_PNut_Public
Francis Bauer found a bug that wound up being a problem in the interpreter. FIELD usage was causing the interpreter to crash by doing an unwanted RFVAR instruction.
The bug is fixed now. A new v46 is on the OBEX, plus the P2_PNut_Public is updated.
Chip,
It just dawned on me that there is now a situation where we have multiple device driver objects to use under a single filesystem object. Potentially at the same time. Namely for SD cards. There is the possibility of wanting to access two SD cards using different interfaces and therefore using different drivers ... but both still FAT32 formatted.
Is there, can there be, a way to specify different driver objects without incurring duplication of the filesystem object?
EDIT: I suppose the way to do it is have both driver objects specified in the one filesystem handler object and then add a mechanism in the handler to distinguish between devices ... supporting multiple mounted volumes that way.
Hehe, DOS level functionality. Not something I ever planned on.
Evan, that sound like a way to handle it, to me.
Hmm, I guess this is where method pointers would be used effectively. It's a small set of interfacing methods to each driver. So it wouldn't be a big deal to runtime map them ... I'm hopefully guessing is the case since I've not used them before ...
EDIT: Nice, added a CASE selector for assigning the runtime mappings, then a quick round of text replacement in the sources and it works easy. I like it.
EDIT2: Oh, Flexspin doesn't care about specifying number of return arguments. More work to make it compile in Pnut.
Chip,
Looks like you've got strong demand for adding a C-like preprocessor to Spin2 - https://forums.parallax.com/discussion/176050/preprocessor/p1
I know I could make good use for it right now.
Stephen Moraco and I need to update PNut_TS as soon as I finish v47. Stephen wrote a pre-processor for it, already.
Question for you all:
I am adding round-robin cooperative multitasking to the Spin2 interpreter. This way, a single Spin2 cog can run many Spin2 programs by splitting its time among them.
Should I implement a duty value for each task, so rather than always executing it in the round-robin loop, it can be set to execute (1..64)/64ths of the time when its turn comes up?
Maybe 64ths is not the way to go and some exponential would be better, but do you see a value in some kind of duty control for each task? The point would be to allow critical tasks to run more frequently than uncritical tasks.
And does anyone have any opinion on how many tasks should be supported? I've got 16 working now, but I could make it unlimited. Do you see any value in that?
Here are the new instructions associated with this feature:
TASKINIT(method(params), stack_address) 'get a new task started
TASKNEXT() 'switch to the next task and eventually return to the next instruction - every task must do this periodically to keep things running
TASKSTOP() 'stop this task - same thing as returning from the method launched by TASKINIT
I could add duty into TASKINIT like this:
TASKINIT(duty, method(params), stack_address)
I would use this! and 64ths would be a good granularity. 16 tasks might be quite a bit more than I would need, but I once wondered what the heck I would do with a 1MB hard drive...
Does TASKNEXT() give back the rest of the timeslice? If there is some hangup in the code and TASKNEXT() doesn't get executed I presume all tasks will hang?
That's right. TASKNEXT() needs to execute periodically in each task to get to the next task. There is no timeslicing, just task switching.
You just have to sprinkle TASKNEXT() instructions throughout your code.
Cool and useful feature. I've been thinking about building something like that for flexspin.
TASKSTOP should take a task ID/handle to be able to stop a task from the outside (should perhaps also have a TASKID to grab the handle of the current task).
Ideally it should also be possible to force a switch to a particular task. That would go well with being able to set zero priority, where the task never gets activated by the normal round-robin scheduling.
If you used the top of the stack area to store per-task data in some sort of link list structure, you could have infinite tasks. In that case the handle would just be the initial stack pointer.
Ah, what's the plan for PropTool then? Is it officially depreciated?
With so much processing power provided by each cog, this would be a very useful, tidy way of using a SPIN2 cog to it's full extent. I have written multitasked flashforth applications in the past and found the modularity very readable, especially when coming back to it years later. I would favour unlimited tasks if there's a choice.
I would favour a handle, so that a task may be stopped by external code, not just itself.
When a task is terminated, it would be nice to allow the task to tidy up before shutting down, rather than a 'crash' termination. Might need another keyword for that, though?
How would the user determine the required stack depth for each task?
How about setting the task repeat time in mS?