Shop OBEX P1 Docs P2 Docs Learn Events
Spin2 - Page 3 — Parallax Forums

Spin2

1356718

Comments

  • cgraceycgracey Posts: 14,208
    David Betz wrote: »
    cgracey wrote: »
    ersmith wrote: »
    David Betz wrote: »
    ersmith wrote: »
    David Betz wrote: »
    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.
  • cgracey wrote: »
    David Betz wrote: »
    cgracey wrote: »
    ersmith wrote: »
    David Betz wrote: »
    ersmith wrote: »
    David Betz wrote: »
    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.
    Sounds good.

  • Wuerfel_21Wuerfel_21 Posts: 5,107
    edited 2019-12-30 19:25
    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)
    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.
  • cgraceycgracey Posts: 14,208
    Wuerfel_21 wrote: »
    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)
    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.
  • JonnyMacJonnyMac Posts: 9,159
    edited 2019-12-30 20:35
    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.
    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.
  • Wuerfel_21 wrote: »
    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)
    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.

    +100 ...really neat idea. I've forgotten parameter order in my own code sometimes, only to have to look it up in the object :blush:
    Remembering the symbol name is far easier
  • JonnyMacJonnyMac Posts: 9,159
    edited 2019-12-31 20:45
    I would love to replace...
      term.str(string("Looking for Pixy.", 13))
    
    with something like this...
      term.str("Looking for Pixy.\r\n")
    
  • JonnyMac wrote: »
    I would love to replace...
      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
    
  • RaymanRayman Posts: 14,762
    there's also the printf
    That does this too...
  • cgraceycgracey Posts: 14,208
    edited 2020-01-01 13:44
    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<<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:

    Spin2_polxy.jpg
    515 x 404 - 73K
  • cgracey wrote: »
    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<<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:

    Spin2_polxy.jpg
    Great progress! What does a method definition that returns multiple values look like?

  • 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).
  • cgraceycgracey Posts: 14,208
    Here is an example of returning and passing multiple values:
    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
    
  • cgraceycgracey Posts: 14,208
    edited 2020-01-01 23:05
    I just added '_' for use as a no-variable-write:
      x,_ := 1,2    'x=1
      _,y := 3,4    'y=4
    
  • Chip, the conventional approach for the "multiple return values" like what you have is to call them tuples and indicate them as such:
    (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.
  • pedwardpedward Posts: 1,642
    edited 2020-01-01 23:23
    cgracey wrote: »
    I just added '_' for use as a no-variable-write:
      x,_ := 1,2    'x=1
      _,y := 3,4    'y=4
    

    If you use the parentheses approach:
    
    (x,) := (1,2)
    (,y) := (3,4)
    
    
  • roglohrogloh Posts: 5,837
    edited 2020-01-02 03:02
    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.
  • jmgjmg Posts: 15,175
    pedward wrote: »
    Chip, the conventional approach for the "multiple return values" like what you have is to call them tuples and indicate them as such:
    (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.

    That's sounding simple, and already established, so has a lot of appeal.
  • Chip: so if you do this:
    X, Y := Y, X
    
    ... it is a swap?
  • cgraceycgracey Posts: 14,208
    JRoark wrote: »
    Chip: so if you do this:
    X, Y := Y, X
    
    ... it is a swap?

    Yes.
  • cgraceycgracey Posts: 14,208
    rogloh wrote: »
    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.
  • cgraceycgracey Posts: 14,208
    edited 2020-01-05 11:34
    I've got method pointers working now:
    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).
  • Instead of "mptr(m0)", could the compiler recognize "@m0"? "@" is the way other pointers are created, and it would save having to reserve a keyword
  • What is advantage or purpose of using method pointers?
  • ersmithersmith Posts: 6,068
    edited 2020-01-05 16:02
    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"
    PUB monitorpin(pin, func) | curval, oldval
      oldval := INA[pin]
      repeat
        curval := INA[pin]
        if curval <> oldval
            ~~func ' calls the provided function
            oldval := curval
    
  • ersmith wrote: »
    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"
    PUB monitorpin(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.

  • Another interesting syntax would be

    sub@varname to access the HUB address of a var/dat in a sub-object, alike sub#constant

    Mike
  • David Betz wrote: »
    ersmith wrote: »
    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"
    PUB monitorpin(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.
  • msrobots wrote: »
    Another interesting syntax would be

    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 :) ).
Sign In or Register to comment.