Shop OBEX P1 Docs P2 Docs Learn Events
Adding a new command to FemtoBasic — Parallax Forums

Adding a new command to FemtoBasic

Duane C. JohnsonDuane C. Johnson Posts: 955
edited 2012-02-19 09:24 in Propeller 1
Hi Mike;
I am attempting to add a special command, OPENX, to the DongleBasic variation of FemtoBasic with an SD Card.
What I want to do is open a file with the file name represented by a numeric expression, <expr>, instead of an alpha string.

I figured out the method to add the command and it worked correctly in the normal way before I started modifying it.

I added a new primary scanFilenameX(f) that doesn't look for quotes, (I hope).

OPENX doesn't work and I don't know why. Actually I don't know what I'm doing.
Would you look this over and divine or edit this to make it work.
I show examples of the Basic code I'm using.

Duane J
pri scanFilename(f) | c, chars  ' Original scanFilename(f)
   result := f
   chars := 0
   tp++                         ' skip past initial quote
   c := byte[tp]              
   repeat while c <> quote and c <> 0
      tp++
      if chars++ < 31
         byte[f++] := c         ' keep up to 31 characters
      c := byte[tp]
   if c == quote                ' move past closing quote
      tp++
   byte[f] := 0

pri scanFilenameX(f) | c, chars ' ZZZZ New Added Primary Function without Quotes
   result := f
   chars := 0
   tp++                         ' skip past initial quote
   c := byte[tp]              
   repeat while c <> 0
      tp++
      if chars++ < 31
         byte[f++] := c         ' keep up to 31 characters
      c := byte[tp]
   byte[f] := 0

            140: ' OPEN " <file> ", R/W/A   ' Original OPEN Command
               if spaces <> quote
                  abort @syn
               scanFilename(@f0)
               if spaces <> ","
                  abort @syn
               case skipspaces
                  "A", "a": d := "a"
                  "W", "w": d := "w"
                  "R", "r": d := "r"
                  other: abort string("Invalid open file mode")
               tp++
               if \mass.mountSDVol(def#spiDO,def#spiClk,def#spiDI,def#spiCS) < 0
                  abort string("Can't mount SD card")
               if \mass.openFile(@f0,d)
                  abort string("Can't open file")
               fileOpened := true

            187: ' OPENX <expr>, R/W/A ' ZZZZ New Open a file based on the value of <expr>
               if spaces <> 0          ' At least one parameter
                  b := c := expr
               scanFilenameX(@f0)
               if spaces <> ","
                  abort @syn
               case skipspaces
                  "A", "a": d := "a"
                  "W", "w": d := "w"
                  "R", "r": d := "r"
                  other: abort string("Invalid open file mode")
               tp++
               if \mass.mountSDVol(def#spiDO,def#spiClk,def#spiDI,def#spiCS) < 0
                  abort string("Can't mount SD card")
               if \mass.openFile(@f0,d)
                  abort string("Can't open file")
               fileOpened := true
            

This writes data to a file with a Numeric Name
NEW
10 OPEN "201212", W
20 WRITE 2012, 12, 1
30 CLOSE
RUN
OK

This reads the file with an Alphabetic Name
NEW
20 OPEN "201212", R
30 READ B, C, D
40 CLOSE
50 PRINT B, C, D
RUN
2012    12      1
OK

This reads the file with a Numeric Name
NEW
10 A = 201212
20 OPENX A , R
30 READ B, C, D
40 CLOSE
50 PRINT B, C, D
RUN
IN LINE 20 Syntax Error
OK

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2012-02-07 08:36
    Did you add OPENX to the table of keywords near the beginning of the program? Remember to add an entry to "toks" as well.

    Your scanFilenameX routine doesn't have an appropriate stop condition. I think you're stopping the scan on a zero byte. That'll gobble up the rest of the OPENX statement. You'd want to stop on a comma, then backup the text pointer so the main OPENX code will see the comma again.

    You could also modify the existing OPEN statement. Currently it checks that the first non-blank after the OPEN is a quote. If it's not a quote, you could call "expr" to look for a numeric value, then convert the number to a zero-terminated string in "f0" instead of calling "scanFilename". The rest of the OPEN code should work fine for either case. Something like:
       if spaces == quote
          scanFilename(@f0)
       else
          convertToString(expr,@f0)
    
    The convertToString routine would convert the value of its first parameter into a zero-terminated string pointed to by the second parameter. You could use a modified version the decimal output routine in the PRINT statement code.
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-08 13:41
    Hi Mike;
    Did you add OPENX to the table of keywords near the beginning of the program? Remember to add an entry to "toks" as well.

    Yes, I did these things. My new OPENX works the same as OPEN when it has the same code. So I assume I have at least got this right.
    if spaces == quote
          scanFilename(@f0)
       else
          convertToString(expr,@f0)
    

    I 'm trying to use this.
    I made a PRIvate method named "convertToString" with "(expr,@f0)" as the passed parameters.
    However, expr is a PRIvate method. My "Propeller Manual" says parameters are escentially lomg variables.
    The spin tool doesn't like this.
    What am I doing wrong?

    I know I will have more questions later as I'm a spin newbie.

    Duane J
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-15 08:22
    I've been trying to add the OPENX command to DongleBasic, a variant of FemtoBasic, for over a week now with no luck.

    Adding commands has been easy, however, getting them to work has been much tougher.
    Mainly because I'm not a good programmer. Far from it.

    What I want to do is be able to open files on the SD-Card based on a changeable number as the name.

    Currently OPEN in DongleBasic works this way:
    OPEN "<file>", {R | W | A }
    "<file>" is a string surrounded by QUOTES that is coded within the command, essentially it is unchangeable.
    If other files are to be used a separate command is needed for each of them.
    This is very cumbersome as I need about 500 data files on the card.

    What I need is:
    OPENX <expr>, {R | W | A }
    <expr> is a numeric expression WITHOUT QUOTES, such as:
    1234 a number
    A a number in a variable
    12 + 34 an equation with numbers that evaluates to a number
    A + B an equation with variables that evaluates to a number.
    I assume there are restrictions to this as the resultant number must contain 8 or less characters without an extension.

    This would be an important advance to the usefulness of DongleBasic. I doesn't seem to me this will take much code to do this.

    I got OPENX to accept strings both with or without quotes to work.
    I can't get it to use an expression though.
    I keep getting spin compiler errors.

    Help, I'm over my head.
    Would anyone be willing to edit my version of DongleBasicX so the OPENX command accepts an <expr> instead of a "string" for use as the SD-Card file name?

    My project is for a computational based solar tracker. I use a program in my PC that pre calculates stepper motor movements years in advanced. Each file contains stepper movements for a week or so. The data has a granularity of about 15 minutes of time. My program moves the motors between these data points linearly over the time period. The SD-Card easily has enough space to have at least 500 files, probably a lot more.

    I can zip my version and send it to you?

    Duane C. Johnson
    redrok@redrok.com
    (651)426-4766
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-16 00:11
    Change the OPEN code as Mike suggested to:
    140: ' OPEN <expr>|"<file>", R/W/A
                   if spaces == quote
                     scanFilename(@f0)
                   else
                     convertToString(expr,@f0)
    
    then add a new private method convertToString like this:
    PRI convertToString(value,dst)
    
      repeat 8
        byte[dst++] := (lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
    
      byte[dst] := 0                ' string terminator
    
    Expressions will be converted into their 8-digit-hex equivalent, e.g. open 40+2,a will generate 0000002A as the filename.
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-16 05:36
    Hi kuroneko;

    Thanks for the reply.

    I may not have made it clear. I was looking for a way to mathematically evaluate the contents of the string of characters.
    The string may contain numbers, variable, or mathematical operators. The resultant number then is used as the file name to be opened.

    As an example, the print command does this:
    A = 4
    PRINT 1 + 2 * 3 + A ' results in:
    11

    Duane J
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-16 05:49
    I may not have made it clear. I was looking for a way to mathematically evaluate the contents of the string of characters.
    I'm aware of that. With the posted patch I get this (debug output highlighted):
    A = 4
    OK
    open 1+2*3+A,r
    [COLOR="red"]about to open 0000000B[/COLOR]
    Can't mount SD card
    OK
    
    Note that I picked hex for simplicity as it already comes with 8 digits (and you have an 8 character limit).
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-16 06:02
    Hi kuroneko;

    A simpler method might be that I do the mathematics and put the number in a variable.
    This variable is a single alpha character, A .. Z.

    As an example, the OPEN command does this:
    A = 4
    B = 1 + 2 * 3 + A
    OPEN B , W

    Note! If there are quotes the OPEN works in the normal way.
    In absence of the quotes OPEN uses the contents of the variable as the name.

    Thanks.

    Duane J
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-16 16:35
    @Duane: Apologies, I don't really know how to read your last post. Are you indicating that it now works as you expect it to or don't you believe that the patch does what you want?
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-17 20:18
    Hi kuroneko & Mike;

    I got it to work. Yey!!!

    Well not the way I originally wanted with numeric equations within the OPEN command.
    But this method seams acceptable and much easier to do.

    The OPEN statement accepts either a "<String>" with Quotes
    or a single character <var>, without Quotes, that points
    to a variable which contains a 32 bit number. This is converted
    to an 8 character name.

    The beauty of this is one can easily access any of many files
    that contain stepper motor data.

    Mu intention is to have files that contain a weeks worth of
    stepper movements. The numeric name can be calculate from
    the clock/calendar.

    BTW, there can be 511 files in the root directory. I can
    have over 9 years of movement data on one SD card.

    Cool huh!!

    Things to do:
    1. The var name must use upper case letters. Lower case doesn't
    work. I have to convert lower case to upper case.
    2. I have to add syntax aborts.
    3. The DELETE statement should have this same also.
    4. I added a bunch of test code that needs to be removed.
    5. Learn to program better.

    This is some clips of my working copy of DongleBasic.

    I can send you a zip of the full thing if you want.

    Duane J
    redrok@redrok.com
    ----------------------
    CON
       version   = 3                       ' Major version
       release   = 008                     ' Minor release
       testLevel = 0                       ' Test change level
       
       _clkmode  = def#clockMode
       _xinfreq  = def#xtalClock
       _free     = (def#endMemory - def#endFree + 3) / 4
    
       bootTx    = 30                      ' Boot serial port transmit pin
       bootRx    = 31                      ' Boot serial port receive pin
    
       bspKey    = 8                       ' Input backspace key
       fEof      = -1                      ' End of file value
          
       maxstack  = 20                      ' Maximum stack depth
       linelen   = 256                     ' Maximum input line length
       space     = 32                      ' Space character
       quote     = 34                      ' Double quote
       comma     = 44                      ' Comma
       dot       = 46                      ' Dot
       colin     = 58                      ' Colin
       semi      = 59                      ' Semi
       caseBit   = !32                     ' Uppercase/Lowercase bit
       saveProg  = $7FF0                   ' End of saved program in EEPROM
       callStack = 512                     ' Minimum Spin call stack depth (longs)
    ----------------------
    var
       long sp, tp, eop, nextlineloc, curlineno, pauseTime
       BYTE sdname[9]
       long vars[26], stack[maxstack], control[2], progsize
       long forStep[26], forLimit[26], forLoop[26]
       word outputs
       byte tline[linelen], tailLine[linelen], inVars[26], fileOpened
    
       tok12 byte "OPEN",    0 ' 140 OPEN "<file>" , <mode>  
                               '          "<file>" is a string File Name, with quotes, of the form "aaaaaaaa.xxx"
                               '     OPEN  <var>   , <mode> 
    			   '            var is a pointer, A..Z, without quotes to a variable that contains a
    			   '            number to be converted to a string of the form nnnnnnnn, a 32 bit number     ' Uses the HEX number pointed to by <var> Variable, no Quotes
    ----------------------
       syn      byte "Syntax Error"                , 0
       syn1     byte "Syntax Error 1"              , 0
       syn2     byte "Syntax Error 2"              , 0
       syn3     byte "Syntax Error 3"              , 0
       synexpr  byte "Syntax Error No Expr"        , 0
       syncomma byte "Syntax Error No Comma"       , 0
       synother byte "Syntax Error Other"          , 0
       synquote byte "Syntax Error No Quote"       , 0
       synvar   byte "Syntax Error Not A Variable" , 0
       ln       byte "Invalid Line Number"         , 0
    ----------------------
    pri scanFilename(f) | c, chars  ' Filemame with quotes
       result := f
       chars  := 0
       tp++                         ' skip past initial quote
       c := byte[tp]
       repeat while c <> quote and c <> 0
          tp++
          if chars++ < 31
             byte[f++] := c         ' keep up to 31 characters
          c := byte[tp]
       if c == quote                ' move past closing quote
          tp++
       byte[f] := 0
    ----------------------
    pri scanFilenameQ(f) | c, chars  ' Filename with no quotes
       result := f
       chars  := 0
       c := byte[tp]
       repeat while c <> comma and c <> 0
          tp++
          if chars++ < 31
             byte[f++] := c         ' keep up to 31 characters
          c := byte[tp]
       byte[f] := 0
    ----------------------
                140: ' OPEN <var>, R/W/A ' ZZZZ Uses the HEX number pointed to by <var> Variable, no Quotes
                   if spaces == quote
                      scanFilename(@sdname)
                   else   
                      if spaces > space ' Any Characters > Space  i.e 33 or greater
                      scanFilenameQ(@f0)
    
                      ser.str(string("@f0 Variable Name",def#Cr,def#Lf))
                      ser.str(string(quote        ))
                      ser.str(@f0)                  ' Print a String
                      ser.str(string(quote,space  ))
                      ser.str(string("d"          ))
                      ser.dec(@f0)                  ' Print a Decimal
                '     ser.str(string(      space  ))
                '     ser.str(string("h"          ))
                '     ser.hex(@f0,8)                ' Print a Hex
                      ser.str(string(      space  ))
                      ser.str(string(def#Cr,def#Lf))
    
                      e := byte[@f0] - "A" 
    {{
                      if e > 26                     ' ???? Capitalize it
                         e := e - 32
    }}
                      ser.str(string("e Variable Name",def#Cr,def#Lf))
                      ser.str(string(quote        ))
                      ser.str(e)                  ' Print a String
                      ser.str(string(quote,space  ))
                      ser.str(string("d"          ))
                      ser.dec(e)                  ' Print a Decimal
                '     ser.str(string(      space  ))
                '     ser.str(string("h"          ))
                '     ser.hex(e,8)                ' Print a Hex
                      ser.str(string(      space  ))
                      ser.str(string(def#Cr,def#Lf))
    
    {{		  if e > 26                     ' ???? Capitalize it
    		     e := dst - 32            
                      if e < 0 or dst > 26          ' Make sure it really is a var
    		     abort @synvar              
    }}
                      dst := vars[byte[@f0]- "A"]                ' Change from a Character to a var
    
                      ser.str(string("dst Content of Variable",def#Cr,def#Lf))
                '     ser.str(string(quote        ))
                '     ser.str(dst)                  ' Print a String
                '     ser.str(string(quote,space  ))
                      ser.str(string("d"          ))
                      ser.dec(dst)                  ' Print a Decimal
                      ser.str(string(      space  ))
                      ser.str(string("h"          ))
                      ser.hex(dst,8)                ' Print a Hex
                      ser.str(string(      space  ))
                      ser.str(string(def#Cr,def#Lf))
    
                      e := 0                        ' Convert var value to a hex string
                      repeat 8
                         sdname[e++] := (lookupz((dst <-= 4) & $F : "0".."9", "A".."F"))
                      sdname[8] := 0                ' Add string terminator
    
                      ser.str(string("@sdname Variable String",def#Cr,def#Lf))
                      ser.str(string(quote        ))
                      ser.str(@sdname)              ' Print a String
                      ser.str(string(quote,space  ))
                '     ser.str(string("d"          ))
                '     ser.dec(@sdname)              ' Print a Decimal
                '     ser.str(string(      space  ))
                '     ser.str(string("h"          ))
                '     ser.hex(@sdname,8)            ' Print a Hex
                '     ser.str(string(      space  ))
                      ser.str(string(def#Cr,def#Lf))
    
                   if spaces <> comma               ' Find the comma
                      abort @syncomma
    
                   case skipspaces                  ' File action
                      "A", "a": d := "a"            ' Append
                      "W", "w": d := "w"            ' Write
                      "R", "r": d := "r"            ' Read
                      other: abort string("Invalid open file mode")
                   tp++
                   if \mass.mountSDVol(def#spiDO,def#spiClk,def#spiDI,def#spiCS) < 0
                      abort string("Can't mount SD card")
                   if \mass.openFile(@sdname,d)
                      abort string("Can't open file")
                   fileOpened := true
    ----------------------
    
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-18 07:05
    Hi kuroneko & Mike;

    I got it to work. Yey!!!

    Here's the DELETE statement.

    The DELETE statement accepts either a "<String>" with Quotes
    or a single character <var>, without Quotes, that points
    to a variable which contains a 32 bit number. This is converted
    to an 8 character name.

    ----------------------
                144: ' DELETE " <file> "
                   if spaces == quote
                      scanFilename(@sdname)
                   else   
                      if spaces > space ' Any Characters > Space  i.e 33 or greater
                      scanFilenameQ(@f0)
                      dst := vars[byte[@f0]- "A"]                ' Change from a Character to a var
                      e := 0                        ' Convert var value to a hex string
                      repeat 8
                         sdname[e++] := (lookupz((dst <-= 4) & $F : "0".."9", "A".."F"))
                      sdname[8] := 0                ' Add string terminator
    
                   if \mass.mountSDVol(def#spiDO,def#spiClk,def#spiDI,def#spiCS) < 0
                      abort string("Can't mount SD card")
                   if \mass.openFile(@sdname,"d")
                      abort string("Can't delete file")
                   if \mass.closeFile < 0
                      abort string("Error deleting file")
                   \mass.unmountSDVol
    ----------------------
    

    Thanks.

    Duane J
  • Oldbitcollector (Jeff)Oldbitcollector (Jeff) Posts: 8,091
    edited 2012-02-18 07:25
    That's Slick!

    If you added a "Are you sure?" to this, it would make a nifty addition to the standard Femto package.

    OBC
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-18 08:40
    That's Slick!
    Thanks, but I couldn't have done it by myself.
    If you added a "Are you sure?" to this, it would make a nifty addition to the standard Femto package.
    OBC

    Hmmmm?
    That would get in the way of software controlled OPEN and DELETE functions.
    Maybe the "Are you sure?" should work only when in "edit" mode and not in "running" mode.

    Next, I need to add a Clock/Calendar statement.
    I'm using the DS1307

    I have several variations of FemtoBasic I regularly use:
    [HTML]<pre>
    DongleBasic with SD card, can autostart a Basic program on the card after reset
    DongleBasicX with SD card, with the new OPEN statements, can autostart a Basic program on the card after reset
    StickBasic no SD card, no autostart
    StickBasicProm no SD card but will autostart a preveiosly saved Basic program after reset.
    </pre>[/HTML]
    Here are a couple of test FemtoBasic programs to demonstrate the added commands.
    NEW
    10 FOR A = 0 TO 16
    20   OPEN  A , W
    30   WRITE  A, 2222, 3333
    40   CLOSE
    50   OPEN  A , R
    60   READ  B, C, D
    70   PRINT A,B,C,D
    80   CLOSE
    90 NEXT A
    100 FILES
    RUN
    
    NEW
    10 FOR A = 0 TO 16
    20   DELETE  A
    90 NEXT A
    100 FILES
    RUN
    
    Duane J
    <redrok@redrok.com>
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-18 17:00
    I may sound like a broken record but why do you avoid the (working) solution in [post=1074993]post #5[/post]? Expression evaluation code is already implemented, all you have to add is the string conversion.
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-18 20:57
    Hi kuronekol

    I didn't want to just convert a number in the OPEN statement into a name.

    I wanted to convert a number contained in a variable into a name.

    I can use math to store the result of a mathematical equation in a variable that results in a file name.

    Does this make sense?

    Duane J.
  • kuronekokuroneko Posts: 3,623
    edited 2012-02-19 00:50
    10 FOR a = 10 TO 40 STEP 10
    20  OPEN a*256+42,w
    25   WRITE a, 2222, 3333
    30  CLOSE
    40 NEXT a
    
  • Duane C. JohnsonDuane C. Johnson Posts: 955
    edited 2012-02-19 09:24
    Hi kuroneko;

    Thank You, Thank You, Thank You!!!!!!

    That worked perfectly. And much better than my crude solution.

    To answer question:
    I may sound like a broken record but why do you avoid the (working) solution in post #5? Expression evaluation code is already implemented, all you have to add is the string conversion.

    I just couldn't get it to work. Mostly due to my incompetence.
    Having seen your "Working" version I can see my mistakes.
    The main one was the requirement for proper "Indentation" in spin. I just didn't get it.

    BTW, it was easy to do the same thing to the DELETE statement.

    For me, this has been a valuable, if not harrowing, learning experience.

    Do you recommend a book that would teach me the "Best Practices and Recommendations" of writing spin code? I really don't need the hardware (robotic) side of things as I am pretty good at this stuff already. The Propeller Manual just doesn't do this for me.

    On to implementing the DS1307 clock commands in FemtoBasic.

    You might ask why I keep focusing on FemtoBasic.
    For me, this is the perfect platform. I make many utility micros that I can put together in less than an hour. Then make them work very quickly by writing in the Femto tokenized interpreter. Sure the code runs slowly, but I rarely need the code to run very fast.

    I have made Tiny Basic interpreters for many years using 8080, Z80, 1802, 8052-basic, and others. The Prop is by far the nicest of all of them for ease of implementation and use.

    When introducing newcomers to micro controllers, seeing a bag of parts magically transform into a working unit and then programming in a simple language really hooks them in.

    Duane J
Sign In or Register to comment.