Shop OBEX P1 Docs P2 Docs Learn Events
In praise of SEND — Parallax Forums

In praise of SEND

I think SEND is an under-rated feature of Spin2. I've been working on some floating point code and needing to print test results. So I wrote a simple function SendFloat(x) to send a floating point number, and now I can print test results like:

  z := f.FSqrt(x)
  send("x = ", f.SendFloat(x), " sqrt(x) = ", f.SendFloat(z), 13, 10)

It's so nice not to have to wrap all the strings in string(), not having to worry about how many arguments there are to print, and not having to worry about buffers being overwritten. The closest we could come to that in Spin1 would be something like:

  ' ToString() uses an internal buffer, so only print one float at a time!
  ser.fmt1(string("x=%s "), f.ToString(x))
  ser.fmt1(string(" sqrt(x) = %s\r\n"), f.ToString(z))

Comments

  • Spin2 feature tierlist when?

    Yes, SEND really is a good one.

  • cgraceycgracey Posts: 14,133

    I'd like to find all kinds of neat things to put into Spin2 - simple little things that get us around huge headaches. ABORT is something really useful that may be getting underutilized.

  • Yes, ABORT probably is underutilized, but it was in Spin1 too so at least most people know about it.

    RECV also looks like a promisiing feature. I've been trying to think of ways to use RECV to let me share the same code for reading floats from the terminal and from strings. I guess I could write a string buffer object and do something like:

       ' read from terminal
       recv := @serial.rx
       floatval := floatObj.RecvFloat()
    
       ' read from string
       stringbuf.SetString(string("-1.2345E+20""))
       recv := @stringbuf.GetChar
       floatval := floatObj.RecvFloat()
    

    but that seems a little bit cumbersome.

    I wonder if there might be some way to allow recv to set up an internal buffer? I'm just kind of speculating here, not sure if it's a good idea, but maybe RECV with arguments could put the data somewhere to be read out by subsequent recv's, something like:

      recv("-1.2345E+20")                    ' pre buffer data to be read by following RECV calls
      floatval := floatObj.RecvFloat()  ' use the buffered data set up by earlier call
    

    or even:

       floatval := recv("-1.2345E+20", floatObj.RecvFloat())
    

    How hard do you think something like that would be to add? Does it even make sense?

  • @cgracey said:
    I'd like to find all kinds of neat things to put into Spin2 - simple little things that get us around huge headaches. ABORT is something really useful that may be getting underutilized.

    Well, as I think I already said once, abort just kinda sucks, because you can't discern between a normal return and an abort.

  • Cluso99Cluso99 Posts: 18,069

    @Wuerfel_21 said:

    @cgracey said:
    I'd like to find all kinds of neat things to put into Spin2 - simple little things that get us around huge headaches. ABORT is something really useful that may be getting underutilized.

    Well, as I think I already said once, abort just kinda sucks, because you can't discern between a normal return and an abort.

    IIRC you can, You just need to return a value.

  • cgraceycgracey Posts: 14,133
    edited 2021-03-08 03:16

    @Wuerfel_21 said:

    @cgracey said:
    I'd like to find all kinds of neat things to put into Spin2 - simple little things that get us around huge headaches. ABORT is something really useful that may be getting underutilized.

    Well, as I think I already said once, abort just kinda sucks, because you can't discern between a normal return and an abort.

    You operate at a dedicated level to catch an ABORT. In that context, you would only be anticipating a zero or an abort value that you programmed.

    \method ()

    If that (above) is your ABORT trap, you would never be looking for regular return values, only zero or an abort code. The regular return values would happen in deeper methods.

    ABORT is a disruption from normal business. It's like you're at the grocery store buying groceries and you go unconscious and wind up in the hospital. It doesn't matter anymore what aisle you were on when you went unconscious or what return values you might have gotten. You are now in the hospital. Grocery shopping is aborted.

    This is how I like to think about it, anyway.

    Remember that when an ABORT occurs, the return stack is popped all the way to the caller that used the backslash. That caller can only receive the default value of zero or the value expressed by the ABORT command, which might have occurred deep in the call chain.

  • @cgracey said:
    That caller can only receive the default value of zero or the value expressed by the ABORT command, which might have occurred deep in the call chain.

    Ah, so it changed from Spin1 in that regard. That's still not particularly useful (because if I do care about the return value from a potentially-aborting function, I now have to wrap it in a another function to get them and then pass them in some obnoxious other way).

  • Cluso99Cluso99 Posts: 18,069

    @Wuerfel_21 said:

    @cgracey said:
    That caller can only receive the default value of zero or the value expressed by the ABORT command, which might have occurred deep in the call chain.

    Ah, so it changed from Spin1 in that regard. That's still not particularly useful (because if I do care about the return value from a potentially-aborting function, I now have to wrap it in a another function to get them and then pass them in some obnoxious other way).

    AFAIK you just abort(someerrorcode) and it pops up the calls until it finds the call with err := \somecall(params) where you need to check the return value. By convention, a negative number is an error and often is an error number that you use to point to a string defining the errror.

    Possibly best to look at the spin docs for the P1 as IIRC it explains it fairly well.

  • cgraceycgracey Posts: 14,133
    edited 2021-03-08 14:32

    @Wuerfel_21 said:

    @cgracey said:
    That caller can only receive the default value of zero or the value expressed by the ABORT command, which might have occurred deep in the call chain.

    Ah, so it changed from Spin1 in that regard. That's still not particularly useful (because if I do care about the return value from a potentially-aborting function, I now have to wrap it in a another function to get them and then pass them in some obnoxious other way).

    If you want to do error handling with normal return values, you can do that. Abort is for when you want to hyperspace back out to some trap point. It's like calling 911. The 911 operator doesn't want to hear trivial details, just what the emergency is.

  • I think @Wuerfel_21 's point is that in order to distinguish the "emergency" values returned by ABORT from the normal return values, you have to set aside some part of the return value space for those emergency values. That is, if a function could return any 32 bit value there's no way for the caller to tell whether it returned normally or due to an ABORT.

    One way to ameleriorate this would be to have an extra return value that could indicate abort or normal return. So to call a function which might potentially abort you would do:

       value, isAbort := \func(a, b, c)
    

    "func" itself only returns one value, but the abort handling mechanism adds the extra boolean isAbort which is either -1 or 0. "value" would be the normal function result if isAbort is 0, and the ABORT value if isAbort is -1. If you know that the value indicates errors, you could just do:

       value, _ := \func(a,b,c)
    

    and then proceed as you do today.

  • Wuerfel_21Wuerfel_21 Posts: 4,461
    edited 2021-03-08 14:52

    @cgracey said:

    @Wuerfel_21 said:

    @cgracey said:
    That caller can only receive the default value of zero or the value expressed by the ABORT command, which might have occurred deep in the call chain.

    Ah, so it changed from Spin1 in that regard. That's still not particularly useful (because if I do care about the return value from a potentially-aborting function, I now have to wrap it in a another function to get them and then pass them in some obnoxious other way).

    If you want to do error handling with normal return values, you can do that. Abort is for when you want to hyperspace back out to some trap point. It's like calling 911. The 911 operator doesn't want to hear trivial details, just what the emergency is.

    Yes, but say I have a function to read 4 longs from a file or something, but the underlying code might abort, so, if I were to do this:

    OBJ 
      file: "ImaginaryObject"
    
    PUB mine() | a,b,c,d
      a,b,c,d := file.get4Longs()
      doStuff(a,b,c,d)
    

    then if get4Longs aborts it aborts past mine, which let's say I don't want. If I wanted to catch the error but also get the return values I'd have to do something like this obnoxious nonsense

    OBJ 
      file: "ImaginaryObject"
    
    PUB mine() | a,b,c,d
      if \get4LongsWrapper(@a)
        handleError()
      else
        doStuff(a,b,c,d)
    
    PRI get4LongsWrapper(ptr) | a,b,c,d
      a,b,c,d := file.get4Longs()
      longmove(ptr,@a,4)
    
  • cgraceycgracey Posts: 14,133

    @ersmith said:
    I think @Wuerfel_21 's point is that in order to distinguish the "emergency" values returned by ABORT from the normal return values, you have to set aside some part of the return value space for those emergency values. That is, if a function could return any 32 bit value there's no way for the caller to tell whether it returned normally or due to an ABORT.

    One way to ameleriorate this would be to have an extra return value that could indicate abort or normal return. So to call a function which might potentially abort you would do:

       value, isAbort := \func(a, b, c)
    

    "func" itself only returns one value, but the abort handling mechanism adds the extra boolean isAbort which is either -1 or 0. "value" would be the normal function result if isAbort is 0, and the ABORT value if isAbort is -1. If you know that the value indicates errors, you could just do:

       value, _ := \func(a,b,c)
    

    and then proceed as you do today.

    But if an ABORT occurs, the return values are likely meaningless. It's not business-as-usual, anymore. That's why ABORTs are meant to be trapped at a higher level.

    Because we have multiple return values, we can always implement error-handling with an extra value. ABORT is intended to be a catch-all without concern for what level the ABORT occurred at. When an ABORT occurs, there may be a lot of unfinished business in the call stack, rendering near-level return values meaningless.

  • cgraceycgracey Posts: 14,133

    Eric, I think what you are suggesting is that we append the ABORT value to the return parameters, to localize error handling.

    I'm not sure how to think about this, yet. I can't decide whether it's a net Improvement or complication. I kind of like the delineation of a trap level.

  • cgraceycgracey Posts: 14,133

    ```

    if \get4LongsWrapper()
    handleError()

    PRI get4LongsWrapper | a,b,c,d
    a,b,c,d := file.get4Longs()
    doStuff(a,b,c,d)

    ```

    I would do it like this.

  • @cgracey said:
    Eric, I think what you are suggesting is that we append the ABORT value to the return parameters, to localize error handling.

    Not quite, I'm suggesting an additional flag to indicate whether the function completed due to RETURN or due to an ABORT. This would allow users to use \ in front of functions that may return arbitrary values. But with multiple return values possible this makes another complication, which is that the "ABORT or RETURN" flag will have to be appended at the end of all of those values. I'm not sure if that additional complication is worth it. Using a wrapper function may be better.

    Perhaps Spin3 could have some kind of TRY/CATCH scheme to automate the production of the wrapper functions, so that:

    TRY
      a,b,c,d := file.get4Longs()
      doStuff(a, b, c, d)
    CATCH err
      handleError(err)
    

    gets automatically translated into (the equivalent of)

    err := try_wrapper()
    if err
      handleError(err)
    ...
    pri try_wrapper() | a, b, c, d
      a,b,c,d := file.get4Longs()
      doStuff(a, b, c, d)
    
  • @cgracey said:
    ```

    if \get4LongsWrapper()
    handleError()

    PRI get4LongsWrapper | a,b,c,d
    a,b,c,d := file.get4Longs()
    doStuff(a,b,c,d)

    ```

    I would do it like this.

    I guess that works for that simple example, but becomes annoying again when you need to return something from doStuff(a,b,c,d)

Sign In or Register to comment.