[howto] I/O functionality

Started by Sebastian, Jul 29, 2018, 10:35 AM

Previous topic - Next topic


// this was posted long time ago by SLC; had to be stickied even since

Quote from: . on Dec 08, 2015, 12:47 PMThe Squirrel standard library offers IO functionality. It's just a little more complicated for new users that don't know to process files in binary mode.

To open a file we use the file type constructor and pass the file name and desired flags/modes that we want to use when opening that file. Flags/modes are the similar to the C modes from fopen.

local myfile = file("text.txt","rb+");
In this case 'b' tells us that we open the file on binary mode and 'r+' allows us to both read and write/update to the file. The file must exist!

Then we use the readn(type); member function to read each byte from that file.

local my_str = "";

while (!myfile.eos())
    my_str += format(@"%c", myfile.readn('b'));


We begin by creating a string variable `my_str` where we store the resulted string. Then we use the `eos()` (end of stream) member function to know when we reached the end of the file. If we reached the end of the file then `eos()` will return something different from null.

Then we read a single byte and since ASCII characters occupy a single byte that means we read a single character from the file. But a byte is nothing but a number. So we use the format function to convert that byte into a character which becomes a string. And we add the returned string which contains a single character to the actual string.

The list of values that you can read are as follows:
  • 'i' 32bits number returns an integer
  • 's' 16bits signed integer returns an integer
  • 'w' 16bits unsigned integer returns an integer
  • 'c' 8bits signed integer returns an integer
  • 'b' 8bits unsigned integer returns an integer
  • 'f' 32bits float returns an float
  • 'd' 64bits float returns an float

After that we print the retrieved text from the file. To write to a file we use the writen(type); member function which works just like the read function except it writes values.

local my_text = "Appended text goes here";

myfile.seek(0, 'e');

foreach (c in my_text)
    myfile.writen(c, 'b');

We begin by creating a dummy text string that will be appended to our current text. Then we move the cursor to the end of the file. We do that by seeking 0 bytes past the endpoint.

Then we use a foreach loop to process each character in our string. And since one ASCII character is one byte then we write them each as one byte.

The list of values that you can write are as follows:

  • 'l' processor dependent, 32bits on 32bits processors, 64bits on 64bits prcessors
  • returns an integer 'i'
  • 32bits number 's'
  • 16bits signed integer 'w'
  • 16bits unsigned integer 'c'
  • 8bits signed integer 'b'
  • 8bits unsigned integer 'f'
  • 32bits float 'd'
  • 64bits float

After we're done with the file we use the `close()` member function to close it and that's it.


I suppose the whole code can be summarized into two simple read/write functions:
function ReadTextFromFile(path)
    local f = file(path,"rb"), s = "";

    while (!f.eos())
        s += format(@"%c", f.readn('b'));


    return s;

function WriteTextToFile(path, text)
    local f = file(path,"rb+"), s = "";

    f.seek(0, 'e');

    foreach (c in text)
        f.writen(c, 'b');


And used as:
print( ReadTextFromFile("test.txt") );

WriteTextToFile("test.txt", "Appended text goes here");

print( ReadTextFromFile("test.txt") );

I suppose the whole read function can also be optimized with a blob to avoid a ton of IO operations for a single byte.

function ReadTextFromFile(path)
    local f = file(path,"rb"), s = "", n = 0;
    f.seek(0, 'e');
    n = f.tell();
    if (n == 0)
        return s;
    f.seek(0, 'b');
    local b = f.readblob(n+1);
    for (local i = 0; i < n; ++i)
        s += format(@"%c", b.readn('b'));
    return s;

Also, SLC is sharing a function for reading between strings of a text:
// Assuming `text`, `begin_str` and `end_str` are all strings. no error checking

function GetTextBetween(text, begin_str, end_str, must_end = true) {
  // See where `begin_str` starts in the `text`
  local start_idx = text.find(begin_str, 0);
  // if `start_idx` is `null` then it wasn't found
  if (start_idx == null) {
    return null;
  // Exclude `end_str` from the returned string
  start_idx += begin_str.len();
  // See where `end_str` starts in the `text`
  // Starting the search after where `begin_str` was located
  local end_idx = text.find(end_str, start_idx);
  // if `start_idx` is `null` then it wasn't found
  if (end_idx == null) {
    // If the end string must be present
    if (must_end) {
      return null; // Then return null
    // If the end string doesn't have to be present
    } else {
      // Return everything after `begin_str` (excluding `begin_str`)
      return text.slice(start_idx);
  // We have a beginning and an ending, so get that slice of text
  return text.slice(start_idx, end_idx);