Shop OBEX P1 Docs P2 Docs Learn Events
The CASE for less IFs (or maybe NOT!) — Parallax Forums

The CASE for less IFs (or maybe NOT!)

simonlsimonl Posts: 866
edited 2009-05-06 15:40 in Propeller 1
Not sure if this is widely known, but I thought I'd share it anyway:

There are times when we might want to see (for instance) which command we've received. In such times, I generally opted for:
if strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ):
    doCmdCLS
    done := 1

if strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ):
    doCmdHello
    done := 1

if strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ):
    doCmdToggle( @cmd[noparse][[/noparse] parStart ] )
    done := 1

if strcomp( string( "clear" ), @cmd[noparse][[/noparse] 0 ] ):
    doCmdClear
    done := 1

if strcomp( string( "Flash LED" ), @cmd[noparse][[/noparse] 0 ] ):
    FlashLED
    done := 1

' -------------------------------------------------
' Catch invalid commands.
' -------------------------------------------------
if( done <> 1 )
    ser.str( string( "Invalid command!") )
    done := 0




Nothing wrong with that! However, the Propeller has to do every IF test, even if the command has already been acted upon.

I've now found that it's possible to do:
case 1 == 1

    strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdCLS

    strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdHello

    strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdToggle( @cmd[noparse][[/noparse] parStart ] )

    strcomp( string( "clear" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdClear

    strcomp( string( "Flash LED" ), @cmd[noparse][[/noparse] 0 ] ):
        FlashLED

    ' -------------------------------------------------
    ' Catch invalid commands.
    ' -------------------------------------------------
    other:                                                               
        ser.str( string( "Invalid command!") )




Notice the case 1 == 1? This allows us to use a "case" structure, which means the Propeller jumps out of the structure as soon as the command has been acted upon (I believe!).

Even if I'm wrong about that, I think it looks "cleaner" anyway!

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Cheers,
Simon

www.norfolkhelicopterclub.com

“Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.

Post Edited (simonl) : 5/5/2009 11:44:03 PM GMT

Comments

  • Oldbitcollector (Jeff)Oldbitcollector (Jeff) Posts: 8,091
    edited 2009-05-05 21:11
    Gosh I hope you are right on this one..

    Wow.. Nice find.. Can someone verify this?

    OBC

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    New to the Propeller?

    Visit the: The Propeller Pages @ Warranty Void.
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-05-05 21:41
    Works!

    But you can simply write

    case -1

    instead of 1==1. Which simply is the same. 1==1 is simply a boolean expresion which will be resolved during runtime (needs runtime of course). True is represented as -1, false as 0. Strcomp also returns a boolean true or false.

    But this also ends in a bunch of string-compares if the command is at the end of the list. For many commands I prefere the solution where you create a hash value of the command string and use the case statement to compare numbers. This is much faster and you don't need to store the·text of all the commands. One long/word per command is enough.


    Post Edited (MagIO2) : 5/5/2009 9:51:54 PM GMT
  • simonlsimonl Posts: 866
    edited 2009-05-05 21:57
    OK, I've done some time testing, and I'm 60% correct!

    I say 60% because (as you'll see if you run my attached codes) IF is slower than CASE some of the time.

    The two attached codes are identical except for the CASE and IF structures. They're code for some testing I'm doing with serial comm's, and accept the following commands:
    1. cls
    2. hello
    3. toggle <pinNumber>
    4. clear
    5. flash

    Operation:
    * Send to Propeller
    * Start PST
    * Hit enter
    * Type command & hit enter

    Here are the counter readings that I got:
    CASE         IF  DIFFERENCE
    cls          92640      97648        5008
    hello      4042768    4046352        3584
    toggle 17  2025152    2029040        3888
    clear         8160       9152         992
    flash      2499600       6912    -2492688
    <CR>       1470432    1469728        -704*
    claer      1470688       7168    -1463520*
    
    * Where the last two commands were invalid.
    
    



    Strangley "flash" and "claer" (intentionally wrong!) sometimes give very different readings, and I can't figure-out why!

    I'm not sure what to make of this now - LOL.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.

    Post Edited (simonl) : 5/5/2009 10:26:34 PM GMT
  • simonlsimonl Posts: 866
    edited 2009-05-05 22:19
    @MagIO2: Wow! Using "case -1" trims 320 ticks - nice one smile.gif
    MagIO2 said...

    But this also ends in a bunch of string-compares if the command is at the end of the list. For many commands I prefere the solution where you create a hash value of the command string and use the case statement to compare numbers. This is much faster and you don't need to store the text of all the commands. One long/word per command is enough.

    I'm not sure I follow what you're saying there! Would you give an example?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
  • SRLMSRLM Posts: 5,045
    edited 2009-05-05 22:43
    A hash table is simply a table where the keys (in this case strings) are resolved into a hopefully unique number, and then the key is stored in the table at that point. Generally, a hash table has two main design criteria: the function used to translate the string into a number (possibly something ASCII based), and a collision resolution strategy (in case two different strings end up with the same number: what happens?).

    Ultimately, this is a data structures problem that has been studied immensely. Personally, I'd prefer binary trees (O(n log(n)) if I remember right) since they can be expanded easier, and don't take as much memory as hash tables. With this small amount of commands though, neither would be any significant improvement over simply listing them as you have done. It might be worth looking into though if you want lot's of commands (>100 maybe).
  • localrogerlocalroger Posts: 3,452
    edited 2009-05-05 22:52
    SRLM, I tried using the keycode/lookup table method for PropCMD, and on the Prop simply doing the string compares is faster. The reason is that Spin is interpreted, but string compares are done in PASM within the interpreter, much much faster than even integer math can be done in Spin. Compares of short strings are just as fast as a single integer compare for all practical purposes. In PropCMD, I used IF statements but I put it in a dedicated PRI and ended each case with RETURN to break out. I suspect in bytecode that's about equivalent to the clever CASE method.
  • mparkmpark Posts: 1,305
    edited 2009-05-05 23:06
    Suggestion: Use ELSEIF to avoid doing unnecessary comparisons after a match has been found:

    if strcomp( string( "cls" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdCLS
        done := 1
    
    [b]elseif[/b] strcomp( string( "hello" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdHello
        done := 1
    
    [b]elseif[/b] strcomp( string( "toggle" ), @cmd[noparse][[/noparse] 0 ] ):
        doCmdToggle( @cmd[noparse][[/noparse] parStart ] )
        done := 1
    [i]etc.[/i]
    
    
  • jazzedjazzed Posts: 11,803
    edited 2009-05-05 23:35
    Simon,

    You have revealed an interesting aspect of spin. Thanks for that! I never new a case of CASE could be an expression ... of course that means it could be a variable too. CASE implementation is kind of freaky though and actually uses more code bytes than IF ELSEIF ELSE.

    Cheers.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    --Steve


    Propalyzer: Propeller PC Logic Analyzer
    http://forums.parallax.com/showthread.php?p=788230
  • simonlsimonl Posts: 866
    edited 2009-05-05 23:38
    @mpark: D'oh! I completely forgot ELSEIF :-(

    Just for a laugh (hey, it's 0:23 here in UK!) I re-coded using elseif, and the readings are as follows:
                ELSEIF    IF DIFF   CASE DIFF*
    cls          92320      -5328           0
    hello      4042304        -4048        -144
    toggle 17  2026128      -2912       +1296
    clear         7392      -1760        -448
    flash      2500288   +2493376       +1008
    <CR>       1468944       -838       -1168
    claer      1470800   +1463632        +432
    
    * Compared with timings using "CASE -1"
    
    



    So, again, some bizarre results! And again, CASE is often quicker, but I still don't understand why.

    @SLRM: Thanks for the info smile.gif

    @localroger: Interesting findings. I like your use of RETURN - another option I'd not considered. I'm not sure if what I've shown with this CASE trick is so clever though - LOL

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
  • simonlsimonl Posts: 866
    edited 2009-05-05 23:41
    @jazzed: Uses more bytes freaked.gif - I think that alone tips me towards using ELSEIF from now on! Thanks for that.

    Strange; I looked at program usage and found:
    Case    486
    If      489
    EsleIf  490
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.

    Post Edited (simonl) : 5/5/2009 11:48:48 PM GMT
  • simonlsimonl Posts: 866
    edited 2009-05-06 00:14
    @jazzed: Actually, I just realised I'd left the "done := 1" lines in, which aren't needed. The up-shot is: "Case" and "ElseIf" both take 486 longs!

    Now I really must get some shut-eye - it's 01:16 aaargh!

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
  • localrogerlocalroger Posts: 3,452
    edited 2009-05-06 00:18
    Beware, ELSEIF is limited to 16 ELSEIFs. I was using ELSEIF when I switched to RETURN after each case because I had too many cases.
  • jazzedjazzed Posts: 11,803
    edited 2009-05-06 00:21
    Ok, i guess the constants make a difference .... Sorry to keep you up Simon.

    Limited to 16? Uuugh.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    --Steve


    Propalyzer: Propeller PC Logic Analyzer
    http://forums.parallax.com/showthread.php?p=788230
  • Bill DrummondBill Drummond Posts: 54
    edited 2009-05-06 01:56
    I should clean this up an put it in the OBEX.
    note the addition to Keyboard.spin.
    I don't think execution speed is a an issue for keyboard input, I prefer clarity and utility unless I run out of storage.
    !!!Not all the pins are the standard ones!
    {{ Hash Code Command Interpreter by Bill Drummond
    This program is a demonstration of using hash codes to parse keyboard input into commands.
    Using hash codes makes the case statement easy to use and to my eye code that's easier to follow than 'if else' code,
    especially since SPIN is not very string friendly. Note that I coded this without needing a DAT statement.
    Entering a uncoded Command will output "Hashcode 'Unrecognized Command'", you can then use that hash code for adding
    your new command. The IDE won't let you use the slash to define a constant, but the actual name doesn't matter
    as long as you assign the hash code for the command you wish to enter. ex: DIR_W for DIR/W
    [s]The Windows Program HashPrj.exe will output the Constants to a text file that you can cut and paste for the commands
    you wish to use.[/s]
    I've included code for 1 or 2 argument strings.
    Some of the code was borrowed from PropCMD by Michelli Scales
    ++++++++++++++++added this to Keyboard.spin+++++++++++++++++
    PUB LookNext : KeyCode
    '' LookNext (never waits)
    '' returns key and leaves it in the buffer(0 if buffer empty)
        if par_tail <> par_head
        keycode := par_keys.word[noparse][[/noparse]par_tail]
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    }}
    CON
            _clkmode        = xtal1 + pll16x
            _xinfreq        = 5_000_000
    
     CLS     = $00004813
     COLOR   = $00484142
     COPY    = $00048459
     COPYSER = $0845E7E2
     DEL     = $0000489C
     DIR     = $000048E2
     DIR_W   = $0048E547 ' this hashcode is for DIR/8 the IDE sees DIR div W
     DUMP    = $00049A20
     DUMP_S  = $049A2343 ' this hashcode is for DUMP/S
     GETSER  = $04BA97A2
     HELP    = $0004CA10
     KEYCODE = $0FAD83C5
     MORE    = $00052465
     MOUNT   = $00524A34
     SEROUT  = $057A74A4
     TYPE    = $00059E45
     TYPE_S  = $059E4843
     UNMOUNT = $0A324A64
     RENAME  = $056A2615
     'GETPINS = $0BA94E73
     
      bkspKey = $C8
      escKey  = $CB
      ctlcKey = $0263
      ctlzKey = $027A
      F1      = $D0
      LCDRows = 13
      BKSP    = $08
      LF      = $0A
      CR      = $0D
      CTLZ    = $1A
      ESC     = $1B
      SPC     = $20
      
      SDBase  = 8
      LCDBase = 12
      RXPin   = 31
      TXPin   = 30 
      KbData  = 26
      KbClock = 27  
          
    OBJ
            
            lcd     : "tv_text"
            fdx     : "FullDuplexSerial"
            kb      : "keyboard"
            SDCard   : "fsrw"
             
    VAR
     byte HashStr[noparse][[/noparse]36]
     byte Arg1[noparse][[/noparse]14]
     byte Arg2[noparse][[/noparse]13]
     byte HasArgs
     byte Has2Args
     byte Fnames[noparse][[/noparse]13]
     byte FBuf[noparse][[/noparse]160]
     byte Alt[noparse][[/noparse]2]
     byte Lcount
     long hc
     'Pins
     byte LCD_Base 
     byte SD_Base
     byte Kb_Data
     byte Kb_Clock
     byte RX_Pin
     byte TX_Pin
    PUB start | i
      SD_Base := 8
      ''lcd.str(string("Mounting SD Card.", CR))        
      SDCard.mount(SD_Base)
      ''lcd.str(string("Success.", CR))
      'start the tv lcdinal
       if getpinFile
         lcd.str(string("Pins Loaded From File",CR))
       '' Alternate Cursor Chars
       Alt[noparse][[/noparse]0] := SPC     
       Alt[noparse][[/noparse]1] := $5F 
       MainLoop
       
    PRI MainLoop | ch
      Lcount := 0
      HashStr[noparse][[/noparse]0] := 0 
      repeat
       
       lcd.str(string($B4,$07)) ''Prompt string
       blink 
       DoCmd(Hash(getCmd)) 
      
    PRI DoCmd(Cmd) | r     ' Parse Commands
      
      case Cmd
       
       CLS         : lcd.out(0)
              
       COLOR:      if HasArgs
                     GetArg1
                     lcd.out($C)
                     lcd.out(Arg1[noparse][[/noparse]0]-$30)
                     
       COPY:       CopyCmd
       COPYSER:    CopySerialCmd
       
       DEL:        if HasArgs
                    GetArg1
                    r := SDCard.popen(@Arg1,"d")
                    DirCmd
                    
       DIR:        DirCmd
       
       DIR_W :     DirWideCmd       ' enter DIR/8
       
       DUMP :      DumpCmd
       
       DUMP_S:     DumpSerialCmd    ' enter DUMP/S
       GETSER:     GetSerCmd
       'GETPINS:     getpinFile
       
       HELP,$3F:       HelpCmd  '$3f = "?"
       
       KEYCODE:    KeyCodeCmd
       MORE:       TypeCmd
       
       MOUNT:      SDCard.Mount(SD_Base)
       
       TYPE:       TypeCmd
       
       TYPE_S,SEROUT: SerialOutCmd
                           
       RENAME:     lcd.str(string(" = RENAME Not Yet coded",CR))
       
       
       OTHER :  
                   lcd.out("$")
                   lcd.Hex(Cmd,8)
                   lcd.str(string(" =  Unrecognized Command",CR))
                   
    PRI GetPinFile : result | ch,i,j
     ch := SDCard.popen(String("PINS.TXT"), "r")
      if ch < 0
        LCD_Base := LCDBase 
        RX_Pin   := RXPin
        TX_Pin   := TXPin 
        Kb_Data  := KbData
        Kb_Clock := KbClock
        result := 0
      else
       result := true
        HashStr[noparse][[/noparse]0] := 0
        j := 1
       Repeat until StrComp(String("END"),@arg1)
          i := 0
          ch := 0 
         repeat until ch == Lf
          ch := SDCard.PGetC  
           if (ch <> $0D)and (ch <> $0A) 
            'Lcd.Hex(ch,3)
            HashStr[noparse][[/noparse]i++] := ch
        HashStr[noparse][[/noparse]i] := 0 
       
        GetArg1
        GetArg2
        'lcd.str(@arg1)
        'lcd.str(string(" = "))
        'lcd.hex(numfromarg(@arg2),2)
        
        'Lcd.Out(Cr)
          
         if StrComp(@Arg1,String("LCD_Base"))
          Lcd_Base := numfromarg(@arg2)
         if StrComp(@Arg1,String("RX_Pin"))
          RX_Pin := numfromarg(@arg2)
         if StrComp(@Arg1,String("TX_Pin"))
          TX_Pin := numfromarg(@arg2)
        if StrComp(@Arg1,String("Kb_Data"))
          Kb_Data := numfromarg(@arg2)
        if StrComp(@Arg1,String("Kb_Clk"))
          Kb_Clock := numfromarg(@arg2)   
      lcd.start(LCD_Base)    
       'Start the Serial
      fdx.start(RX_Pin,TX_Pin,0,9600)  ' Rx,Tx, Mode, Baud
      fdx.str(String("Serial test "))
      'start the keyboard
      kb.start(Kb_Data, Kb_Clock)
      lcd.str(string("Hash Code Command Interpeter",CR))
      lcd.str(string("    by Bill Drummond",CR))
                
    {Command [noparse][[/noparse]Arg1 Arg2] }  
    PRI GetCmd : Str_Ptr| i,ch         
        i := 0
        repeat until ch == CR
         blink 
          if KB.looknext == F1+2                            ''Added this to "keyboard.spin"
            kb.getkey
            i := StrSize(@hashStr)
            lcd.str(@hashStr)
            blink
          else
            ch  := UpperCase(kb.getkey)
                     
            if ch == $C8                 'KB for backspace
             ch := BKSP                  ' TV_Text wants $08
             lcd.out(ch)                 ' send BS
             lcd.out(SPC)                 ' untype prev ch
             i--                         ' backup index
            lcd.out(ch)                  ' backover space
            if ch <> BKSP                ' ignore BS char
             HashStr[noparse][[/noparse]i++] := ch
        HashStr[noparse][[/noparse]i-1] := 0
       'lcd.str(@hashStr)
       
       return @HashStr
       
    PRI GetArg1 : Str_Ptr | j,i
        i := 0
        j := 0
        Has2Args := 0
        repeat until (HashStr[noparse][[/noparse]i] == SPC ) 
          i++
        i ++
        repeat until HashStr[noparse][[/noparse]i] == 0 
         Arg1[noparse][[/noparse]j] := HashStr[noparse][[/noparse]i]
          if HashStr[noparse][[/noparse]i] == SPC
           Has2Args := -1
           quit
          i++
          j++
        Arg1[noparse][[/noparse]j--] := 0
        Return @Arg1
        
    PRI GetArg2 : Str_Ptr | j,i
        i := 0
        j := 0
        repeat until HashStr[noparse][[/noparse]i] == SPC
          i++
        i ++
        repeat until HashStr[noparse][[/noparse]i] == SPC
          i++
        i ++
        repeat until HashStr[noparse][[/noparse]i] == 0 
         Arg2[noparse][[/noparse]j] := HashStr[noparse][[/noparse]i]
          i++
          j++
        Arg2[noparse][[/noparse]j] := 0
        Return @Arg2
        
    {Get Hash code for command}    
    PRI Hash(string_ptr):Result | x
      HasArgs := 0
      repeat while (byte[noparse][[/noparse]string_ptr] <> 0  ) 
        if  byte[noparse][[/noparse]string_ptr] == SPC
         HasArgs := -1
         quit
       Result := (Result << 4) + byte[noparse][[/noparse]string_ptr++] 
       x := Result & $F0_00_00_00
       if (x <> 0)
        Result := Result ^ (x >> 24)
       Result := result & (! x)
    PRI Blink | i                                           'Display Blinking Cursor, uses VAR Alt[noparse][[/noparse]2]
        i := 0
        repeat until kb.gotkey
            lcd.out(Alt[noparse][[/noparse]i++])           
            waitcnt(10_000_000 + cnt)
            lcd.out(BKSP)
            if i == 2
             i := 0
      lcd.out(SPC)     
      lcd.out(BKSP)
      
    PRI CopySerialCmd | r,ch
      if HasArgs
        GetArg1
        r := SDCard.popen(@Arg1, "w")
        fdx.str(String(CR,"Upload File Now, Hit esc when done",CR))
        fdx.rxflush
        repeat 
          if fdx.rxcheck
         ch := fdx.rx
         if ch <> ESC
          lcd.out(ch)
          SDCard.pputc(ch)   
         else
           r := SDCard.pClose
            quit
       fdx.str(String("Xfer Done ",CR))
       lcd.str(String("Xfer Done ",CR))
        
    PRI GetSerCmd | ch,k
     fdx.str(String("Serial Ready ",CR))
     fdx.rxflush 
     repeat 
      if fdx.rxcheck
       ch := fdx.rx
       if ch <> ESC
        lcd.out(ch)    
       else
        quit 
    fdx.str(String("Serial Done ",CR))
    lcd.str(String("Serial Done ",CR))
     
    PRI KeyCodeCmd | ch
       lcd.str(string("Type a key or Esc to exit",cr))  
       Repeat until ch == $CB
        blink
        ch := KB.getkey
        lcd.out(ch)
        lcd.str(string(" KEYCODE is $")) 
        lcd.hex(ch,2)
        lcd.out(cr)
        
        
    PRI CopyCmd | r,w,RCount
     Rcount := 160
     if HasArgs
      GetArg1
      if Has2Args
       GetArg2
       repeat while Rcount == 160
          r := SDCard.popen(@Arg1, "r")
          RCount := SDCard.pread(@Fbuf, 160)
          SDCard.pClose
          lcd.dec(Rcount)
          lcd.out(SPC)
          w := SDCard.popen(@Arg2, w)
          r := SDCard.pWrite(@Fbuf, Rcount)
          lcd.dec(r)
          SDCard.pClose 
    PRI DirCmd | r
         SDCard.opendir
         repeat while 0 == SDCard.nextfile(@Fnames)
            lcd.str(@Fnames)
            lcd.out(CR)
       
       
             
         
    PRI DirWideCmd | i
          i := 0
          SDCard.opendir 
          repeat while 0 == SDCard.nextfile(@Fnames)
           lcd.str(@Fnames)
            case  i 
             0 : lcd.out($0A)
                 lcd.out(14)
             1 : lcd.out($0A)
                 lcd.out(27)
             2 : lcd.out($0A)
                 lcd.out(1)
                  i := -1
            i++
           if not (i //= 3)
            lcd.out(CR)
          lcd.out(CR)
        
    PRI TypeCmd| ch
         lcd.out(0)
         lcount := 0 
         if HasArgs
           ch := SDCard.popen(GetArg1, "r")
           if ch < 0
            lcd.str(string("File : "))
            lcd.str(@arg1)
            lcd.str(string(" Not Found"))  
            lcd.out(cr)
            return  
           lcd.out(CR)
           lcd.out(0)
           repeat until ch < 0
             ch := SDCard.pgetc
             Display(ch)
         Lcd.out(BKSP)
         Lcd.out(SPC)
         Lcd.out(CR)
            
    PRI Display(ch)
        if ch == cr
          lmore(0,"t")
        else     
              if (ch <> $0A)                                 ' because TV_Text uses line feed for cursor position.
               lcd.out(ch)
                       
    PRI HelpCmd| ch
           lcd.out(0)
           lcount := 0 
           ch := SDCard.popen(String("HELP.TXT"), "r")
           lcd.out(CR)
           repeat until ch < 0
             ch := SDCard.pgetc
             Display(ch)
            
           Lcd.out(BKSP)
           Lcd.out(SPC)
           lcd.out(CR)
                       
    PRI SerialoutCmd | Ch
         if HasArgs
           ch := SDCard.popen(GetArg1, "r")
           fdx.tx(CR)
           repeat until ch < 0
             ch := SDCard.pgetc
            fdx.tx(ch)  
            
    PRI DumpCmd |  ch,c, lc[noparse][[/noparse]15], adx, atend             ' From DosCMD.spin
      ch := SDCard.popen(GetArg1, "r")
      if ch < 0
        lcd.str(string("File : "))
        lcd.str(@arg1)
        lcd.str(string(" Not Found: "))
        lcd.out(cr)
        return
     
      adx := 0
      atend := false
      repeat until atend
        lcd.out(spc)
        lcd.hex(adx,4)
        lcd.str(string(":"))
        c := 0
        repeat while c < 8  
          ch := SDCard.pgetc
          if ch < 0
            lcd.str(string("   "))
            atend := true
            lc[noparse][[/noparse]c] := -1
          else
            lcd.out(spc)
            lcd.hex(ch,2)
            lc[noparse][[/noparse]c] := ch
          c++
        c := 0
        lcd.out(spc)
        repeat while c < 8
          ch := lc[noparse][[/noparse]c]
          if ch =< 1 or (ch => bksp and ch =< cr)
            lcd.out(46)
          else
            lcd.out(ch)
          c++
        adx += 8
        if not lmore(0,"t")
          return  
                            
    PRI DumpSerialCmd  |  ch,c, lc[noparse][[/noparse]15], adx, atend             ' From DosCMD.spin
      ch := SDCard.popen(GetArg1, "r")
      if ch < 0
        lcd.str(string("File : "))
        lcd.str(@arg1)
        lcd.str(string(" Not Found: "))
        lcd.out(cr)
        return
      fdx.tx(lf)
      adx := 0
      atend := false
      repeat until atend
        fdx.tx(spc)
        fdx.hex(adx,4)
        fdx.str(string(":"))
        c := 0                     
        repeat while c < 8  
          ch := SDCard.pgetc
          if ch < 0
            fdx.str(string("   "))
            atend := true
            lc[noparse][[/noparse]c] := -1
          else
            fdx.tx(spc)
            fdx.hex(ch,2)
            lc[noparse][[/noparse]c] := ch
          c++
        c := 0
        fdx.tx(spc)
        repeat while c < 8
          ch := lc[noparse][[/noparse]c]
          if ch =< 1 or (ch => bksp and ch =< cr)
            fdx.tx(46)
          else
            fdx.tx(ch)
          c++
        adx += 8
        if not lmore(0,"s")
          return  
              
            
                                 
    PRI uppercase(c) : chr
      if lookdown(c: "a".."z")
        c -= SPC
      chr := c
    PRI numfromarg(s) | nbase, c
      nbase := 10
      result := 0
      repeat 
        c := byte[noparse][[/noparse]s]
        s++
        if c == 0
          return
        elseif c == "$" '$=hex
          nbase := 16
        elseif c == "#"
          nbase := 10
        else
          if c => "a"
            c := c - constant("a"-10)
          elseif c => "A"
            c := c - constant("A"-10)
          elseif c => "0"
            c := c - "0"
          result := result * nbase + c
          
    PRI lmore(nocr,d) | c                                     ''borrowed from DosCmd
      result := true
      if nocr == 0
        if d == "t"
         lcd.out(CR)
      if d == "s"
         fdx.tx(CR)
         fdx.tx(LF)
      c := kb.key
      if c == ctlckey
        abort
      lcount++
        if lcount > LCDRows - 2
          lcd.str(string("-more-"))
          c := kb.getkey
          if c == esckey or c == ctlckey
            lcd.str(string(" (aborted)",CR))
            return false
          repeat 6
            lcd.str(string(bksp,spc,bksp))
          lcount := 0
      {{
                                                       TERMS OF USE: MIT License                                                                                                              
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation     
    files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    
    modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
    is furnished to do so, subject to the following conditions:                                                                   
                                                                                                                                  
    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
                                                                                                                                  
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          
    WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         
    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   
    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         
    }}
    

    Post Edited (Bill Drummond) : 5/6/2009 2:06:30 AM GMT
  • John AbshierJohn Abshier Posts: 1,116
    edited 2009-05-06 02:06
    Interesting, but does it matter? 1300 clock ticks at 80,000,000 MHz is 16 microseconds, out of all the time it takes me to type in the input. Human input is not time critical.

    John Abshier
  • Beau SchwabeBeau Schwabe Posts: 6,568
    edited 2009-05-06 02:58
    simonl,

    I tried some timing as well, but I changed your code to keep every branch consistent and on the same playing field. Using case -1 does shave 320 clocks off of the end result, but something I noticed in my observations, is that the IF/THEN statements are more consistent if you want some kind of determinism, where as the CASE statements seem to progressively increase in clock time the further down in the chain you get. Also the IF/THEN actually produces shorter code by a few longs than the CASE statements.

    So in conclusion, if you want timing to be "more consistent" use the IF/THEN ... If slightly more speed is important and you only have 3 or 4 branches, use the CASE function. Anything beyond 5 branches you don't really gain anything in the way of faster execution speed. It actually starts counting against you.



    [b]CON[/b]
        [b]_clkmode[/b] = [b]xtal1[/b] + [b]pll16x[/b]                           
        [b]_xinfreq[/b] = 5_000_000
    
    [b]VAR[/b]
    [b]word[/b]    cmd,done
    
    [b]long[/b]    counter1,counter2
    
    
    [b]obj[/b]
    
    COMport       : "FullDuplexSerial.spin"
    
    [b]PUB[/b] TestLoop
    
    COMport.start(31, 30, 0, 2400)
    
    cmd := [b]string[/b]( "A" )            '<--- This value ranges from "A to "F" 
    done := 0
    [b]dira[/b][noparse][[/noparse]&shy;16..23]~~  
    counter1 := [b]cnt[/b]
    
    '----------------------------------------
    {
    [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "A" ), cmd )                        '6912 - A
       done := 1
    [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "B" ), cmd )                        '6960 - B
       done := 2
    [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "C" ), cmd )                        '6960 - C
       done := 3
    [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "D" ), cmd )                        '6960 - D
       done := 4
    [b]if[/b] [b]strcomp[/b]( [b]string[/b]( "E" ), cmd )                        '6928 - E
       done := 5
    
                                                            '6432 - F <-- This value falls through all conditions
    }
    '----------------------------------------
    {
    [b]case[/b] 1 == 1
    
        [b]strcomp[/b]( [b]string[/b]( "A" ), cmd ): done := 1            '3824 - A
        [b]strcomp[/b]( [b]string[/b]( "B" ), cmd ): done := 2            '5008 - B
        [b]strcomp[/b]( [b]string[/b]( "C" ), cmd ): done := 3            '6144 - C
        [b]strcomp[/b]( [b]string[/b]( "D" ), cmd ): done := 4            '7280 - D
        [b]strcomp[/b]( [b]string[/b]( "E" ), cmd ): done := 5            '8384 - E
        
                                                            '7888 - F <-- This value falls through all conditions
    }
    '----------------------------------------
    '{
    [b]case[/b] -1
    
        [b]strcomp[/b]( [b]string[/b]( "A" ), cmd ): done := 1            '3504 - A
        [b]strcomp[/b]( [b]string[/b]( "B" ), cmd ): done := 2            '4688 - B
        [b]strcomp[/b]( [b]string[/b]( "C" ), cmd ): done := 3            '5824 - C
        [b]strcomp[/b]( [b]string[/b]( "D" ), cmd ): done := 4            '6960 - D
        [b]strcomp[/b]( [b]string[/b]( "E" ), cmd ): done := 5            '8064 - E
        
                                                            '7568 - F <-- This value falls through all conditions
    '}
    '----------------------------------------
    
    [b]outa[/b][noparse][[/noparse]&shy;16..23] := |<done
    counter2 := [b]cnt[/b]
    
    COMport.dec(counter2-counter1)
    
    [b]repeat[/b]
    
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Beau Schwabe

    IC Layout Engineer
    Parallax, Inc.

    Post Edited (Beau Schwabe (Parallax)) : 5/6/2009 8:52:16 PM GMT
  • localrogerlocalroger Posts: 3,452
    edited 2009-05-06 03:03
    @Bill -- it will still be slower than case/strcomp. It's counterintuitive, but the strcomps are much faster than experience in all other languages would leave you to believe and the hash algorithm is much more expensive by comparison. It will also take more code LONGs; to me that was the kicker, it's both slower AND bigger than the "slow stupid" way of doing it.
  • BradCBradC Posts: 2,601
    edited 2009-05-06 03:14
    Beau Schwabe (Parallax) said...
    simonl,

    I tried some timing as well, but I changed your code to keep every branch consistent and on the same playing field. Using case -1 does shave 320 clocks off of the end result, but something I noticed in my observations, is that the IF/THEN statements are more consistent if you want some kind of determinism, where as the CASE statements seem to progressively increase in clock time the further down in the chain you get. Also the IF/THEN actually produces shorter code by a few longs than the CASE statements.

    This is true. A CASE statement is a serial construct that progressively compares each statement as it works its way down the chain and jumps out as soon as the condition is met (somewhat like a S/A A/D converter staircase). Your series of IF statement run every comparison every time and the only difference is which condition code is run. In that case the first instance (done :=1) is going to be faster than the others as it requires one less byte of spin code than the others. (Pushing 1 on the stack is faster than the others due to the way the compiler/interpreter handles that literal)

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    "VOOM"?!? Mate, this bird wouldn't "voom" if you put four million volts through it! 'E's bleedin' demised!
  • Bill DrummondBill Drummond Posts: 54
    edited 2009-05-06 04:24
    A personal observation:
    Is there a modern computer language that doesn't have some form of a case statement?
    Who would want one?
    If execution speed was an issue, is SPIN a logical choice?
    Are there programmers who spend more time debugging their code than writing it?

    LONG LIVE CLAIRTY, waste the computers time not mine.
  • heaterheater Posts: 3,370
    edited 2009-05-06 05:34
    Bill Drummond: "Are there programmers who spend more time debugging their code than writing it?"

    Probably most. Especially if you count testing as debugging, after all you have to find the bugs first.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    For me, the past is not over yet.
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-05-06 07:03
    @Beau:

    Just like to mention a little bug in your code
    [b]byte[/b]    [color=red]cmd[noparse][[/noparse]&shy;16][/color],done
    
    [b]long[/b]    counter1,counter2
    
    
    [b]obj[/b]
    
    COMport       : "FullDuplexSerial.spin"
    
    [b]PUB[/b] TestLoop
    
    COMport.start(31, 30, 0, 2400)
    
    [color=red]cmd := [b]string[/b]( "A" )[/color]            '<--- This value ranges from "A to "F" 
    done := 0
    [b]dira[/b][noparse][[/noparse]&shy;16..23]~~  
    counter1 := [b]cnt[/b]
    
    

    The assignment is not putting the string into the byte-buffer. It's putting the long sized pointer to string "A" into the cmd buffer. This bug is 'fixed' by another bug:
    [b]strcomp[/b]( [b]string[/b]( "A" ),[color=red] cmd[/color] )
    

    As you pass the content of cmd to the stringcompare. Usually if you want to compare something with a buffer you'd write
    strcomp( string("xy"), @cmd )

    @ all

    I like the hashing version because it simply saves memory. Of course this comes along with an added runtime for creating the hash value. But I don't see a reason for code that deals with user input to be as fast as possible. And the hashing function could be optimized as well. If you simply·XOR the 2 first with the 2 last characters resulting in a word size hash value of the command-string this might be sufficient to distinguish·all the commands you use. You could build the hash value while you enter the command - that's where you have plenty of time.
    Say you have a complex controller with 30 commands with an average of 5 characters. You need 30 * 6 bytes (one additional byte for string terminator) = 180 bytes for storing the commands vs 60 bytes for hash-values. And of course it makes a difference if you use a strcomp in if or case instead of a constant. There you have additional savings.
    ·
  • hippyhippy Posts: 1,981
    edited 2009-05-06 11:59
    It was mainly interest in what bytecodes were generated for particulat Spin constructs which led me to write my bytecode isassembler ...

    ====                                  ; PUB IfTest( ch )
    ====                                  ; if strcomp( string( "A" ), ch )
    ====                                  ;   doCmdA
    ====                                  ; elseif strcomp( string( "B" ), ch )
    ====                                  ;   doCmdB
    ====                                  ; elseif strcomp( string( "C" ), ch )
    ====                                  ;   doCmdB
    
    0024         87 80 37        S5       PUSH     #B9.BYTE
    0027         64                       PUSH     VL1.LONG
    0028         17                       STRCOMP
    0029         0A 05                    JPF      N6
    002B         01 05 02                 CALLSUB  S12
    002E         04 16                    GOTO     J8
    0030         87 80 39        N6       PUSH     #B10.BYTE
    0033         64                       PUSH     VL1.LONG
    0034         17                       STRCOMP
    0035         0A 05                    JPF      N7
    0037         01 05 03                 CALLSUB  S13
    003A         04 0A                    GOTO     J8
    003C         87 80 3B        N7       PUSH     #B11.BYTE
    003F         64                       PUSH     VL1.LONG
    0040         17                       STRCOMP
    0041         0A 03                    JPF      J8
    0043         01 05 03                 CALLSUB  S13
    0046         32              J8       RETURN   
    
    ====                                  ; PUB CaseTest( ch )
    ====                                  ;   case true
    ====                                  ;     strcomp( string( "A" ), ch ) :
    ====                                  ;       doCmdA
    ====                                  ;     strcomp( string( "B" ), ch ) :
    ====                                  ;       doCmdB
    ====                                  ;     strcomp( string( "C" ), ch ) :
    ====                                  ;       doCmdB
    
    0024         38 39           S5       PUSH     57
    0026         34                       PUSH     -1
    0027         87 80 3A                 PUSH     #B10.BYTE
    002A         64                       PUSH     VL1.LONG
    002B         17                       STRCOMP
    002C         0D 0F                    CASE     J6
    002E         87 80 3C                 PUSH     #B11.BYTE
    0031         64                       PUSH     VL1.LONG
    0032         17                       STRCOMP
    0033         0D 0C                    CASE     J7
    0035         87 80 3E                 PUSH     #B12.BYTE
    0038         64                       PUSH     VL1.LONG
    0039         17                       STRCOMP
    003A         0D 09                    CASE     J8
    003C         0C                       GOTO[noparse][[/noparse]]
    003D         01 05 02        J6       CALLSUB  S13
    0040         0C                       GOTO[noparse][[/noparse]]
    0041         01 05 03        J7       CALLSUB  S14
    0044         0C                       GOTO[noparse][[/noparse]]
    0045         01 05 03        J8       CALLSUB  S14
    0048         0C                       GOTO[noparse][[/noparse]]
    0049         32              J9       RETURN
    
    
  • simonlsimonl Posts: 866
    edited 2009-05-06 12:16
    Hey hippy; great to "see" you around here again - we've missed ya!

    That's kind of strange - if I'm reading your output right - as it looks like CASE is therefore more memory hungry, but that contradicts what I found in the PropTool!

    I was in the middle of writing the following summary when I saw your post - I think it still holds?!

    So to summarise:

    * A "case" structure takes less memory

    * Use "case -1" instead of "case 1==1"

    * If you're after more deterministic timing, use a series of IF statements

    * An ElseIf structure is faster than straight IFs

    * The further down the "Case" and "ElseIf" structure that a matching condition is found, the longer the structure takes to process. This isn't the case in an "If" structure.

    * The timing differences are extremely small, so your choice of structure really depends on your coding "style"

    The up-shot is therefore: none of this really matters (!), but I've had fun exploring it, and learned some stuff along the way smile.gif

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Cheers,
    Simon

    www.norfolkhelicopterclub.com

    “Before you criticize someone, you should walk a mile in their shoes. That way when you criticize them, you are a mile away from them and you have their shoes.” - Jack Handey.
  • lonesocklonesock Posts: 917
    edited 2009-05-06 15:40
    simonl said...
    * Use "case -1" instead of "case 1==1"
    It might be more obvious to just use the constant "TRUE", which is defined as -1.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    lonesock
    Piranha are people too.
Sign In or Register to comment.