Actually, there are 4 possible positions that REXX remembers for a file. There is the current read character position. That is the position in the file at which CHARIN reads when you do not pass your own position arg. There is the current write character position. That is the position in the file at which CHAROUT writes when you do not pass your own position arg. There is the current read line position. That is the position in the file at which LINEIN reads when you do not pass your own position arg. And finally, there is the current write line position. That is the position in the file at which LINEOUT writes when you do not pass your own position arg.
The positions for CHARIN and CHAROUT (ie, the read character and write character positions) are expressed in terms of bytes. For example, if the current write character position is 121, then if you call CHAROUT without passing a position arg, it will start writing characters beginning at the 121st byte of the file.
The positions for LINEIN and LINEOUT (ie, the read line and write line positions) are expressed in terms of lines. For example, if the current write line is 12, then if you call LINEOUT without passing a position arg, it will write the line starting after the 11th line in the file (ie, however many bytes in that happens to be).
When you pass a line position arg to LINEOUT, REXX seeks to that place in the file in order to write out the line there. REXX implicitly (ie, transparent to your script) updates both the current write line position as well as the current write character position. In other words, LINEOUT affects both the position used in subsequent calls to CHAROUT as well as LINEOUT (when you make subsequent calls without passing a particular position). One interesting and useful aspect of this is that you can actually query the current write character position after a call to LINEOUT and find out exactly how many bytes you are into the file (ie, the character position where the line you just wrote ends).
When you pass a character position arg to CHAROUT, REXX seeks to that place in the file in order to write out characters there. REXX implicitly updates the current write character position. REXX doesn't really update the current write line position, although it does sort of "change" it.
In conclusion, LINEOUT and CHAROUT can affect each others' current position.
When you pass a line position arg to LINEIN, REXX seeks to that place in the file in order to read in the line there. REXX implicitly updates both the current read line position as well as the current read character position. In other words, LINEIN affects both the position used in subsequent calls to CHARIN as well as LINEIN (when you make subsequent calls without passing a particular position). One interesting and useful aspect of this is that you can actually query the current read character position after a call to LINEIN and find out at exactly which byte you will next read from the file.
When you pass a character position arg to CHARIN, REXX seeks to that place in the file in order to read in characters. REXX doesn't really update the current read line position, although it does sort of "change" it.
In conclusion, LINEIN and CHARIN can affect each others' current position.
Note:
Changing a file's position removes any error state on it (unless there is an error in changing its position).But it should be noted that calls to LINEIN and CHARIN do not affect the current positions of CHAROUT nor LINEOUT, and vice versa. So changing the current read positions do not affect the current write positions, and vice versa. One interesting aspect of this is that you can intersperse calls to read and write to the same file, and REXX will keep track of the separate read and write positions (although you may have to specify the desired position for the first read or first write to the file).
Here, we write 2 lines to a file named "mytext.txt", and then we read the first line of that same file. We then follow this by writing another line to the file. And then we finish by reading another line. Read the comments carefully and study the example to understand how REXX maintains the read and write positions separately. (Error checking has been removed just to make the example simpler).
/* Write the first line to a text file named "mytext.txt". Note * that we specify a position of 1, so if the file already exists * and has some text in it, that original text is erased. */ err = LINEOUT("mytext.txt", "This is line 1", 1) /* Write the second line to the same text file. Note that * we omit the position, so this line gets written immediately * after the line we just wrote above. */ err = LINEOUT("mytext.txt", "This is line 2") /* Read and display the first line of a text file named * "mytext.txt". Note that we pass the position * because this is the first call to read from * the file, and since we have already written * to it, we need to establish the read position. * This will display "This is line 1". */ SAY LINEIN("mytext.txt", 1) /* Write another line to the same text file. Note that * we omit the position, so this line gets written immediately * after the second line we wrote above, but not at where we last * read from the file (ie, not immediately after line 1). */ err = LINEOUT("mytext.txt", "This is line 3") /* Read and display the next line of the file. Note * that we omit the position, so we read from * where we last read, not from where we last * wrote to the file. This will display "This * is line 2". */ SAY LINEIN("mytext.txt")
Seeking to a position
As you know, you can pass a position arg to LINEOUT, CHAROUT, LINEIN, or CHARIN to specify the position where to perform some write or read. What you may not know is that you can also use these functions to specify a position without actually doing any write or read to the file. In other words, you can simply locate to a particular position. We refer to this as "seeking" to a particular position.
For example, here we set our current read character position to the second character in the file, without actually reading any characters. A subsequent call to CHAROUT to read some characters, without passing a position arg, will then start reading at that position.
/* Set the current read char position of "mytext.txt" * to the second char. Note that if there isn't actually * a second char or there is some problem seeking to it, * this will raise NOTREADY and return an empty string. * If there is no problem, it will not raise NOTREADY, * and still return an empty string. In conclusion, trapping * NOTREADY is the only sure way to error-check this call * (although for some interpreter's such as Reginald, using * STREAM's "D" command will work also). */ CHARIN("mytext.txt", 2)Here's an example of locating the read line position to the second line:
/* Set the current read line position of "mytext.txt" * to the second line. Note that if there isn't actually * a second line or there is some problem seeking to it, * this will raise NOTREADY and return a 0. If there is no * problem, it will not raise NOTREADY, and still * return 0. In conclusion, trapping NOTREADY is the only * sure way to error-check this call (although for some * interpreter's such as Reginald, using STREAM's "D" * command will work also). */ LINEOUT("mytext.txt", , 2)For some REXX interpreters such as Reginald, you can also use the STREAM Function's SEEK commands to set any one of a file's 4 positions. There are 4 such SEEK commands for the 4 positions. You specify the actual, desired position after the word "SEEK", and then finally indicate which of the 4 positions you wish to set.
One advantage of STREAM's SEEK commands is that they return more informative error indications. If the SEEK command works, STREAM returns the new current position referenced to how many characters this position is from the start of the file (where 0 is the very start of the file). If an error, STREAM returns a non-numeric string. (Reginald returns ERROR: xxx where xxx is an operating system error number. Other interpreters may return an empty string. DATATYPE can be used to check STREAM's return).
Another advantage is that you can reference the new position from the start of the file, the end of the file, or even whatever the current position is. For example, you can say "move one character forward from wherever I am right now in the file" (ie, skip over the next character). Or you could say "move to the end of the file". Or you could say "move two lines backward from wherever I am right now in the file". Etc. To reference your new position from the start of the file, put a = or > character before it. To reference your new position from the end of the file, put a < character before it. To reference your new position from the current position, put a + or - character before it (depending upon whether you're moving forward or backward, respectively). Here's an example of locating the read line (and character) position to the second line (from the start of the file):
/* Set the current read line position of "mytext.txt" * to the second line. Note that if there isn't actually * a second line or there is some problem seeking to it, * this will raise NOTREADY and return a non-numeric string. */ offset = STREAM("mytext.txt", 'C', 'SEEK =2 READ LINE') /* Check for an error */ IF DATATYPE(offset) \== "NUM" THEN SAY "There was an error seeking."Here's an example of locating the read character (and line) position to the end of the file:
/* Set the current read char position of "mytext.txt" * to the end of the file. Note that if this succeeds, * what is returned will be how many characters are in * the file. */ offset = STREAM("mytext.txt", 'C', 'SEEK <0 READ CHAR') /* Check for an error */ IF DATATYPE(offset) \== "NUM" THEN SAY "There was an error seeking."Note: You can replace READ CHAR by just READ if desired. The latter is an acceptable "shorthand" for the former.
Here's an example of locating the read character (and line) position back two characters from the current position:
/* Set the current read line position of "mytext.txt" * to two characters backward from the current position. * Note that if this succeeds, what is returned will be * how many characters we are now from the start of the * file. */ offset = STREAM("mytext.txt", 'C', 'SEEK -2 READ CHAR') /* Check for an error */ IF DATATYPE(offset) \== "NUM" THEN SAY "There was an error seeking."
Query the current position
Sometimes you need to know the current read or write position without actually setting it to a new place. We call this "querying the position".
STREAM has some more SEEK commands to query a position. There are 4 such SEEK commands to query the 4 positions. After the words "SEEK POSITION", you indicate which of the 4 positions you wish to query.
Here's an example of querying the current read character (CHARIN) position of a file named "mytext.txt":
position = STREAM("mytext.txt", 'C', 'QUERY POSITION READ CHAR')Here's an example of querying the current write character (CHAROUT) position:
position = STREAM("mytext.txt", 'C', 'QUERY POSITION WRITE CHAR')Here's an example of querying the current read line (LINEIN) position:
position = STREAM("mytext.txt", 'C', 'QUERY POSITION READ LINE')Here's an example of querying the current write line (LINEOUT) position:
position = STREAM("mytext.txt", 'C', 'QUERY POSITION WRITE LINE')So, for example, if you want to save the current read character position, and then restore it for a later call to CHARIN, you would do something like this:
filename = "mytext.txt" /* Get current CHARIN position */ read_position = STREAM(filename, 'C', 'QUERY POSITION READ CHAR') /* Check for an error */ IF DATATYPE(read_position) \== "NUM" THEN SAY "There was an error querying the position." ELSE DO /* Here you may do some more calls to CHARIN() which will change * the current read character position. So too, calls to LINEIN() will * change that position. */ /* Read the character at the earlier, saved position again */ CHARIN(filename, read_position) ENDNote: You can replace READ CHAR by just READ if desired. The latter is an acceptable "shorthand" for the former.