What happens if I call a function that returns two values but my statement only receives one? For example, if foo(x) returns two values, what happens with this statement? Is the second value just ignored?
a := foo(x)
In fastspin it just ignores the second value, but it sounds like Chip's interpreter gives an error, so it's probably best to match up the return values (I may change fastspin to match Chip's Spin2 for this).
It might be nice in some cases to be able to just ignore the second value. Say I write a function that divides one integer by another and returns both the quotient and the remainder. I might want just the quotient and it would be too bad to have to have two different functions for this. I guess that's not a very strong argument though.
I think the Go language handles this by letting you write "_" for a value to be ignored, e.g.:
q, _ := mydiv(a, b)
_, r := mydiv(a, b)
Perhaps Spin2 could adopt that?
Yes, that could be done.
Now that I think of it, a little compiler support would probably help. It would be useful if you didn't have to declared the "_" symbol and also if it it didn't take any space. That will probably happen as part of code optimization in FastSpin but might require explicit support in Chip's Spin2 if it doesn't optimize out dead code.
Rather than a variable write (which pops the stack), just a pop would be done. A pop is a single bytecode.
What happens if I call a function that returns two values but my statement only receives one? For example, if foo(x) returns two values, what happens with this statement? Is the second value just ignored?
a := foo(x)
In fastspin it just ignores the second value, but it sounds like Chip's interpreter gives an error, so it's probably best to match up the return values (I may change fastspin to match Chip's Spin2 for this).
It might be nice in some cases to be able to just ignore the second value. Say I write a function that divides one integer by another and returns both the quotient and the remainder. I might want just the quotient and it would be too bad to have to have two different functions for this. I guess that's not a very strong argument though.
I think the Go language handles this by letting you write "_" for a value to be ignored, e.g.:
q, _ := mydiv(a, b)
_, r := mydiv(a, b)
Perhaps Spin2 could adopt that?
Yes, that could be done.
Now that I think of it, a little compiler support would probably help. It would be useful if you didn't have to declared the "_" symbol and also if it it didn't take any space. That will probably happen as part of code optimization in FastSpin but might require explicit support in Chip's Spin2 if it doesn't optimize out dead code.
Rather than a variable write (which pops the stack), just a pop would be done. A pop is a single bytecode.
Here's another random suggestion that may be nice to have in Spin2: keyword parameters / named parameters / whatever one calls them.
Basically, when you have a function that takes a lot of parameters, it is easy to forget which is which (especially in an untyped language like Spin)
PUBstart(clk_pin,mosi_pin,miso_pin,select_pin,mode=1,some_pointer)' note the proposed default parameter syntax
do_something(mode)
PUBmain
start(40,11,13,12,12,@something) ' WHAT DOES IT MEAN?? AAAAAAA
With keyword parameters, the parameters have to be named at the call site (but can appear in any order), so confusing them is impossible.
Here's another random suggestion that may be nice to have in Spin2: keyword parameters / named parameters / whatever one calls them.
Basically, when you have a function that takes a lot of parameters, it is easy to forget which is which (especially in an untyped language like Spin)
PUBstart(clk_pin,mosi_pin,miso_pin,select_pin,mode=1,some_pointer)' note the proposed default parameter syntax
do_something(mode)
PUBmain
start(40,11,13,12,12,@something) ' WHAT DOES IT MEAN?? AAAAAAA
With keyword parameters, the parameters have to be named at the call site (but can appear in any order), so confusing them is impossible.
New Request: Variable aliases. Parallax did this back the BASIC Stamp 2 days and, IMO, can make code easier to read.
Today, for example, I am updating my variant of FDS so that I can use external RX and TX buffers. I wanted to keep the original variable set so I am using the lower and upper words of some values to pass that information to the cog.
pubstartx(rxp, txp, mode, baudrate, rxlen, p_rxbuf, txlen, p_txbuf)
stop ' stop if runninglongfill(@rxhead, 0, 4) ' clear buffer indexeslongmove(@rxpin, @rxp, 3) ' copy pins and mode
rxpin.word[1] := rxlen ' set buffer lengths
txpin.word[1] := txlen
bitticks := clkfreq / baudrate ' system ticks per bit
bufpntrs.word[0] := p_rxbuf ' point to rx & tx buffers
bufpntrs.word[1] := p_txbuf
cog := cognew(@fdsuart, @rxhead) + 1' start the fds uart cogreturn cog ' return 1..8 if started, 0 if not
It would be nice if I could do this at the end of the var section.
word rxbuflen = rxpin.word[1]
...which would allow code like this:
pubrxcheck : b'' Check if byte received (never waits)'' -- returns -1 if no byte received, $00..$FF if byte
b := -1' assume no byte readyif (rxtail <> rxhead) ' if byte(s) in buffer
b := rxbuffer[rxtail] ' get next availableif (++rxtail == rxbuflen) ' advance and wrap if needed
rxtail := 0
This is cleaner than using rxpin.word[1] in the comparison.
Here's another random suggestion that may be nice to have in Spin2: keyword parameters / named parameters / whatever one calls them.
Basically, when you have a function that takes a lot of parameters, it is easy to forget which is which (especially in an untyped language like Spin)
PUBstart(clk_pin,mosi_pin,miso_pin,select_pin,mode=1,some_pointer)' note the proposed default parameter syntax
do_something(mode)
PUBmain
start(40,11,13,12,12,@something) ' WHAT DOES IT MEAN?? AAAAAAA
With keyword parameters, the parameters have to be named at the call site (but can appear in any order), so confusing them is impossible.
This an entirely compiler-side feature that changes nothing about the generated code.
+100 ...really neat idea. I've forgotten parameter order in my own code sometimes, only to have to look it up in the object
Remembering the symbol name is far easier
I've got the multiple-return-values scheme working now, as you can see with the POLXY instruction. You can nest multiple return values, too, within parameter lists.
The last big thing I need to do is get the method pointer scheme worked out. I'm going to have the first VAR long in each object point to the object's code base, so that method pointers can be a single long. A method pointer's 20 LSB's will point to the VAR base, which in turn points to the OBJ base, giving both the VAR and OBJ bases from just 20 bits. The top 12 bits of the method pointer will be the method index within the OBJ. This will keep things very sane, as method pointers will just be single longs. I'll have a little bit of PASM code before the interpreter starts that initializes the VAR structures with pointers to their related OBJ bases.
Here's a Spin2 program that generates rotating sines and cosines and outputs them to 16-bit PWM-dithered DACs. At 320MHz, the repeat loop takes ~1.6us/iteration, which is ~512 clocks. In PASM, you could go 8x faster. If you stuffed the CORDIC's pipeline, you could go 32x faster:
CON
pinx = 56
piny = 57
pins = pinx + 1<<6PUB go : x,y,z
wrpin(pins, %10110_00000000_01_00011_0)
wxpin(pins, 256)
pinl(pins)
repeat
x,y := polxy($7FFF, z += $01000000)
wypin(pinx, x ^ $8000)
wypin(piny, y ^ $8000)
I've got the multiple-return-values scheme working now, as you can see with the POLXY instruction. You can nest multiple return values, too, within parameter lists.
The last big thing I need to do is get the method pointer scheme worked out. I'm going to have the first VAR long in each object point to the object's code base, so that method pointers can be a single long. A method pointer's 20 LSB's will point to the VAR base, which in turn points to the OBJ base, giving both the VAR and OBJ bases from just 20 bits. The top 12 bits of the method pointer will be the method index within the OBJ. This will keep things very sane, as method pointers will just be single longs. I'll have a little bit of PASM code before the interpreter starts that initializes the VAR structures with pointers to their related OBJ bases.
Here's a Spin2 program that generates rotating sines and cosines and outputs them to 16-bit PWM-dithered DACs. At 320MHz, the repeat loop takes ~1.6us/iteration, which is ~512 clocks. In PASM, you could go 8x faster. If you stuffed the CORDIC's pipeline, you could go 32x faster:
CON
pinx = 56
piny = 57
pins = pinx + 1<<6PUB go : x,y,z
wrpin(pins, %10110_00000000_01_00011_0)
wxpin(pins, 256)
pinl(pins)
repeat
x,y := polxy($7FFF, z += $01000000)
wypin(pinx, x ^ $8000)
wypin(piny, y ^ $8000)
fastspin lets you do that, if the "str" method is declared to take a string, e.g. if it has a string as its default value:
I am aware. I am hoping, though, that this feature makes it into the Parallax compiler as well because my preferred editor is Propeller Tool. Hopefully, the update to Propeller Tool will allow for the connection to an external compiler (I've asked Jeff Martin to consider that).
Hi @cgracey , a while ago there was this discussion of being able to call SPIN from other languages. I think the idea was raised of using the stack to setup args and return values at the end of SPIN functions back to the caller instead of simply stopping the COG and I think you mentioned that this regular stack approach would solve a lot of problems. Is this something that is going to be part of SPIN2? That is, allowing other languages to make calls and return values to/from SPIN code? Or will SPIN2 require itself to be in full control of the P2 from boot up onwards making it become an overall P2 environment, instead of also a callable language, pretty much like the P1 was?
The reason I ask is that It would be nice for us to still be able to call shared SPIN2 code from MicroPython, C, perhaps Forth etc, where possible. I appreciate this adds more complexity and may constrain memory allocation etc.
Hi @cgracey , a while ago there was this discussion of being able to call SPIN from other languages. I think the idea was raised of using the stack to setup args and return values at the end of SPIN functions back to the caller instead of simply stopping the COG and I think you mentioned that this regular stack approach would solve a lot of problems. Is this something that is going to be part of SPIN2? That is, allowing other languages to make calls and return values to/from SPIN code? Or will SPIN2 require itself to be in full control of the P2 from boot up onwards making it become an overall P2 environment, instead of also a callable language, pretty much like the P1 was?
The reason I ask is that It would be nice for us to still be able to call shared SPIN2 code from MicroPython, C, perhaps Forth etc, where possible. I appreciate this adds more complexity and may constrain memory allocation etc.
Rogloh, sorry I didn't see this earlier.
We should be able to set up enough context that Spin2 can be called from other languages. Spin2 doesn't need to be in control. If other stuff is running, that should be fine. It's hard to work out the specifics, yet, of how Spin2 will handle this.
PUBgo | x,y,z
x := mptr(m0) 'assign method pointers to x,y,z
y := mptr(m1)
z := mptr(m2)
repeat'call methods pointed to by x,y,z
~~x
~~y
~~z
PRIm0
pinnot(16)
PRIm1
pinnot(17)
PRIm2
pinnot(18)
Here's what it compiles to in bytecodes:
00000- 14000080 'pointer to PUB go
00004- 2D 000080 'pointer to PRI m000008- 32000080 'pointer to PRI m10000C- 37000080 'pointer to PRI m200010- 3C 000000 'size of code
00014- 0C 22 D201 E022 D202 'PUB go
E122 D203 E200 D00E
00 D10E 00 D20E 0F 76040002D- 003E 103104 'PRI m000032- 003E 113104 'PRI m100037- 003E 123104 'PRI m2
This is an ultra simple example. Parameters can also be used, of course, and ':'+<number> after the pointer variable can express the number of results (default is zero results).
Oh, I just noticed: "~~expr" has a different meaning in Spin1 (it sign extends a word to a long). Any chance we could use a different combination of characters instead? "~*" is free, for example. Or, if Spin2 is moving more to keywords, maybe something like "MCALL(func)" which could be extended to "MCALL(func, arg1)", "MCALL(func, arg1, arg2)" etc.
What is advantage or purpose of using method pointers?
To allow the user to specify a function to be called when an event happens. For example something like:
' whenever the input value on "pin" changes, call the function "func"PUBmonitorpin(pin, func) | curval, oldval
oldval := INA[pin]
repeat
curval := INA[pin]
if curval <> oldval
~~func ' calls the provided function
oldval := curval
What is advantage or purpose of using method pointers?
To allow the user to specify a function to be called when an event happens. For example something like:
' whenever the input value on "pin" changes, call the function "func"PUBmonitorpin(pin, func) | curval, oldval
oldval := INA[pin]
repeat
curval := INA[pin]
if curval <> oldval
~~func ' calls the provided function
oldval := curval
I still think it would be better to be able to pass an object reference. That way you could call any of the methods of that object not just one. You'd probably need to add inheritance to Spin2 as well though to allow interfaces to be defined that are shared between multiple subclasses. That way you could have an I/O interface that defines methods like getc, putc, flush, close, etc. Each of these methods could be implemented differently for each type of I/O object.
What is advantage or purpose of using method pointers?
To allow the user to specify a function to be called when an event happens. For example something like:
' whenever the input value on "pin" changes, call the function "func"PUBmonitorpin(pin, func) | curval, oldval
oldval := INA[pin]
repeat
curval := INA[pin]
if curval <> oldval
~~func ' calls the provided function
oldval := curval
I still think it would be better to be able to pass an object reference. That way you could call any of the methods of that object not just one.
I think both object pointers and method pointers are useful. Both are natural solutions to problems in different circumstances.
You'd probably need to add inheritance to Spin2 as well though to allow interfaces to be defined that are shared between multiple subclasses.
That's the tricky part I think -- finding a nice way to do interfaces and inheritance while keeping the Spin2 language and compiler simple.
fastspin's Spin dialect has object pointers, but no formal inheritance. You can do a kind of cheap inheritance by starting object B off with an instance of object A; then the object pointer can function as either an A or a B. But there are no virtual methods, so this isn't as useful as it could be.
I don't have any way to call a method pointer in fastspin's Spin yet, but @ applied to a method produces a method pointer which can be passed to BASIC or C.
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 ).
Comments
Rather than a variable write (which pops the stack), just a pop would be done. A pop is a single bytecode.
Basically, when you have a function that takes a lot of parameters, it is easy to forget which is which (especially in an untyped language like Spin)
PUB start(clk_pin,mosi_pin,miso_pin,select_pin,mode=1,some_pointer) ' note the proposed default parameter syntax do_something(mode) PUB main start(40,11,13,12,12,@something) ' WHAT DOES IT MEAN?? AAAAAAA
With keyword parameters, the parameters have to be named at the call site (but can appear in any order), so confusing them is impossible.
' Proposed syntax, up for debate PUB start(mosi_pin:,miso_pin:,clk_pin:,select_pin:,mode:1,some_pointer) do_something(mode) PUB main start(@something, mode: 12, mosi_pin: 11, miso_pin: 13, select_pin: 12, clk_pin: 40) 'Ahhh yes
This an entirely compiler-side feature that changes nothing about the generated code.
That could even be turned off and on with a button, so it needn't cloud up the source code in every case.
Today, for example, I am updating my variant of FDS so that I can use external RX and TX buffers. I wanted to keep the original variable set so I am using the lower and upper words of some values to pass that information to the cog.
pub startx(rxp, txp, mode, baudrate, rxlen, p_rxbuf, txlen, p_txbuf) stop ' stop if running longfill(@rxhead, 0, 4) ' clear buffer indexes longmove(@rxpin, @rxp, 3) ' copy pins and mode rxpin.word[1] := rxlen ' set buffer lengths txpin.word[1] := txlen bitticks := clkfreq / baudrate ' system ticks per bit bufpntrs.word[0] := p_rxbuf ' point to rx & tx buffers bufpntrs.word[1] := p_txbuf cog := cognew(@fdsuart, @rxhead) + 1 ' start the fds uart cog return cog ' return 1..8 if started, 0 if not
It would be nice if I could do this at the end of the var section.
word rxbuflen = rxpin.word[1]
...which would allow code like this:pub rxcheck : b '' Check if byte received (never waits) '' -- returns -1 if no byte received, $00..$FF if byte b := -1 ' assume no byte ready if (rxtail <> rxhead) ' if byte(s) in buffer b := rxbuffer[rxtail] ' get next available if (++rxtail == rxbuflen) ' advance and wrap if needed rxtail := 0
This is cleaner than using rxpin.word[1] in the comparison.+100 ...really neat idea. I've forgotten parameter order in my own code sometimes, only to have to look it up in the object
Remembering the symbol name is far easier
term.str(string("Looking for Pixy.", 13))
with something like this...term.str("Looking for Pixy.\r\n")
fastspin lets you do that, if the "str" method is declared to take a string, e.g. if it has a string as its default value:
PUB str(x = "") ' do stuff
That does this too...
The last big thing I need to do is get the method pointer scheme worked out. I'm going to have the first VAR long in each object point to the object's code base, so that method pointers can be a single long. A method pointer's 20 LSB's will point to the VAR base, which in turn points to the OBJ base, giving both the VAR and OBJ bases from just 20 bits. The top 12 bits of the method pointer will be the method index within the OBJ. This will keep things very sane, as method pointers will just be single longs. I'll have a little bit of PASM code before the interpreter starts that initializes the VAR structures with pointers to their related OBJ bases.
Here's a Spin2 program that generates rotating sines and cosines and outputs them to 16-bit PWM-dithered DACs. At 320MHz, the repeat loop takes ~1.6us/iteration, which is ~512 clocks. In PASM, you could go 8x faster. If you stuffed the CORDIC's pipeline, you could go 32x faster:
CON pinx = 56 piny = 57 pins = pinx + 1<<6 PUB go : x,y,z wrpin(pins, %10110_00000000_01_00011_0) wxpin(pins, 256) pinl(pins) repeat x,y := polxy($7FFF, z += $01000000) wypin(pinx, x ^ $8000) wypin(piny, y ^ $8000)
Here is what the PUB compiles to:
00010- 00 3E 78 42 46 00 16 00 35 3E 78 43 08 36 3E 78 00020- 2F 45 0E 43 18 F2 22 EE 6D E1 E0 3E 38 D0 43 0F 00030- A0 37 3E 39 D1 43 0F A0 37 0F 67 04
And here's the output:
PUB doit(x,y) : j,k j,k := addsub(muldiv(x,y)) 'muldiv returns 2 results to satisfy addsub PRI addsub(a,b) : c,d return a+b,a-b 'set results on return PRI muldiv(a,b) : c,d c,d := a*b,a/b 'set up results for return
x,_ := 1,2 'x=1 _,y := 3,4 'y=4
(j,k) := myfunc(args) and return (a+b, a-b)
As a "general" rule the parentheses are used as a lexicographic designation of a tuple in code.
Perl, PHP, and Python implement tuples in a similar fashion.
If you use the parentheses approach:
(x,) := (1,2) (,y) := (3,4)
The reason I ask is that It would be nice for us to still be able to call shared SPIN2 code from MicroPython, C, perhaps Forth etc, where possible. I appreciate this adds more complexity and may constrain memory allocation etc.
That's sounding simple, and already established, so has a lot of appeal.
Yes.
Rogloh, sorry I didn't see this earlier.
We should be able to set up enough context that Spin2 can be called from other languages. Spin2 doesn't need to be in control. If other stuff is running, that should be fine. It's hard to work out the specifics, yet, of how Spin2 will handle this.
PUB go | x,y,z x := mptr(m0) 'assign method pointers to x,y,z y := mptr(m1) z := mptr(m2) repeat 'call methods pointed to by x,y,z ~~x ~~y ~~z PRI m0 pinnot(16) PRI m1 pinnot(17) PRI m2 pinnot(18)
Here's what it compiles to in bytecodes:
00000- 14 00 00 80 'pointer to PUB go 00004- 2D 00 00 80 'pointer to PRI m0 00008- 32 00 00 80 'pointer to PRI m1 0000C- 37 00 00 80 'pointer to PRI m2 00010- 3C 00 00 00 'size of code 00014- 0C 22 D2 01 E0 22 D2 02 'PUB go E1 22 D2 03 E2 00 D0 0E 00 D1 0E 00 D2 0E 0F 76 04 0002D- 00 3E 10 31 04 'PRI m0 00032- 00 3E 11 31 04 'PRI m1 00037- 00 3E 12 31 04 'PRI m2
This is an ultra simple example. Parameters can also be used, of course, and ':'+<number> after the pointer variable can express the number of results (default is zero results).
To allow the user to specify a function to be called when an event happens. For example something like:
' whenever the input value on "pin" changes, call the function "func" PUB monitorpin(pin, func) | curval, oldval oldval := INA[pin] repeat curval := INA[pin] if curval <> oldval ~~func ' calls the provided function oldval := curval
sub@varname to access the HUB address of a var/dat in a sub-object, alike sub#constant
Mike
That's the tricky part I think -- finding a nice way to do interfaces and inheritance while keeping the Spin2 language and compiler simple.
fastspin's Spin dialect has object pointers, but no formal inheritance. You can do a kind of cheap inheritance by starting object B off with an instance of object A; then the object pointer can function as either an A or a B. But there are no virtual methods, so this isn't as useful as it could be.
I don't have any way to call a method pointer in fastspin's Spin yet, but @ applied to a method produces a method pointer which can be passed to BASIC or C.
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