5.3. Reading text data from a stream file

We know what a line of text looks like now. We also know how to write lines to disk. What we don't know, yet, is how to read them.

The big difference between reading lines and writing lines is that when you write the data, you already know how long the line needs to be. But when you read, you don't. You won't know how long it is until you've found the CRLF sequence in the text!

Now, we could solve that by reading one byte from disk at a time, until one of them turned out to be the new line sequence, but that would not run efficiently, because disk hardware is designed to read from disk in larger chunks. So, what we'll do is read a whole buffer of data, and then parse that data looking for our new line sequence.

We'll save any characters in the buffer that occur after the new line, so that we can use them as the start of our next line of text.

The RPG code that I came up with looks like this:

     P readline        B                   export                   
     D readline        PI            10I 0                          
     D   fd                          10I 0 value                    
     D   text                          *   value                    
     D   maxlen                      10I 0 value                    
                                                                    
     D rdbuf           S           1024A   static                   
     D rdpos           S             10I 0 static                   
     D rdlen           S             10I 0 static                   
                                                                    
     D p_retstr        S               *                            
     D RetStr          S          32766A   based(p_retstr)          
     D len             S             10I 0                          
                                                                    
     c                   eval      len = 0                          
     c                   eval      p_retstr = text                  
     c                   eval      %subst(RetStr:1:MaxLen) = *blanks
                                                                       
     c                   dow       1 = 1                               
                                                                       
     C* Load the buffer                                                
     c                   if        rdpos>=rdlen                        
     c                   eval      rdpos = 0                           
     c                   eval      rdlen=read(fd:%addr(rdbuf):%size(rdbuf))
 
     c                   if        rdlen < 1                           
     c                   return    -1                                  
     c                   endif                                         
     c                   endif                                         
                                                                       
     C* Is this the end of the line?                                   
     c                   eval      rdpos = rdpos + 1                   
     c                   if        %subst(rdbuf:rdpos:1) = x'25'       
     c                   return    len                                 
     c                   endif                                         
                                                                       
     C* Otherwise, add it to the text string.                          
     c                   if        %subst(rdbuf:rdpos:1) <> x'0d'      
     c                               and len<>maxlen                    
     c                   eval      len = len + 1           
     c                   eval      %subst(retstr:len:1) =  
     c                               %subst(rdbuf:rdpos:1) 
     c                   endif                             
                                                           
     c                   enddo                             
                                                           
     c                   return    len                     
     P                 E                                   
   

Add that routine to the IFSTEXTR4 service program, and then add this code to the prototypes in the IFSTEXT_H member:

     D readline        PR            10I 0              
     D   fd                          10I 0 value        
     D   text                          *   value        
     D   maxlen                      10I 0 value        
   

One more thing: Because we're writing a service program, and we want our code to be as useful as possible, we're going to write binder source that tells the CRTSRVPGM how other programs can bind to us.

If you haven't done this before, don't worry about it. It's really, really simple. It's just a list of what gets exported. Here's the entire code of our binding source:

      STRPGMEXP                   
        EXPORT SYMBOL(WRITELINE)  
        EXPORT SYMBOL(READLINE)   
      ENDPGMEXP                   
   

See? Very simple. Put that code into a member called "IFSTEXTR4" in a source file called QSRVSRC. Now let's compile it:

      CRTRPGMOD IFSTEXTR4 SRCFILE(IFSEBOOK/QRPGLESRC) DBGVIEW(*LIST)
      CRTSRVPGM IFSTEXTR4 EXPORT(*SRCFILE) SRCFILE(IFSEBOOK/QSRVSRC) TEXT('IFS Text file service program')
   

Finally, to make it easy to compile the programs that use this service program, we'll create a binding directory. This only involves running two commands:

     CRTBNDDIR IFSEBOOK/IFSTEXT TEXT('IFS Text binding directory')
     ADDBNDDIRE BNDDIR(IFSEBOOK/IFSTEXT) OBJ((IFSTEXTR4))