File access: Difference between revisions

From MZXWiki
Jump to navigation Jump to search
(→‎Opening and Navigating Files: Note about using fread_pos = -1 to test for file existence)
(→‎Reading and Writing Binary Data: MZX was updated a long time ago.)
 
Line 16: Line 16:
This would read four bytes stored in little-endian format from the file, and combine them together to form a single 32-bit value.  (See the articles on [[expressions]] and [[bitwise math]] for more information on this code.)
This would read four bytes stored in little-endian format from the file, and combine them together to form a single 32-bit value.  (See the articles on [[expressions]] and [[bitwise math]] for more information on this code.)


This is a somewhat contrived application, since another way to read from and write to a file is to use "fread_counter" and "fwrite_counter".  These were originally intended to conveniently read and write full counter values, instead of just bytes. Currently they are out of date, and work on 16-bit values instead of the 32-bit values MZX uses now.  However, there are plans to update them to work on 32-bit values in the near future.
This is a somewhat contrived application, since another way to read from and write to a file is to use "fread_counter" and "fwrite_counter".  These read and write full 32-bit counter values, instead of just bytes.
 
===Reading and Writing Strings===
===Reading and Writing Strings===
Finally, MZX provides reading and writing of arbitrary length strings.  Originally, the method for doing this was very limited.  The "fread" and "fwrite" counters were overloaded to work with string values.  A quirk with the way strings are handled forced a nonstandard syntax here, however: instead of
Finally, MZX provides reading and writing of arbitrary length strings.  Originally, the method for doing this was very limited.  The "fread" and "fwrite" counters were overloaded to work with string values.  A quirk with the way strings are handled forced a nonstandard syntax here, however: instead of

Latest revision as of 16:21, 19 January 2010

MegaZeux has supported reading and writing data to files since version 2.60, and since then this capability has been augmented and improved to allow for the use of external resources in a number of different ways. Apart from counter interfaces to directly manipulate the contents of a file, file access encompasses specific interfaces for reading and writing robots and Robotic, MZMs, and entire MZX worlds.

General Purpose File Access

MZX supports basic file reading and writing through the use of specific keyword counters, which fall into two obvious categories: "fread" counters for reading, and "fwrite" counters for writing.

Opening and Navigating Files

First, a file must be opened for reading/writing, using the following syntax:

set "filename.ext" to "fread_open"
set "filename.ext" to "fwrite_open"

Directory paths can also be used (MZX is OS agnostic and interprets both '\\' and '/' as separators), but for security reasons MZX will not allow access to directories above the one the .mzx file is running from. It is also perfectly acceptable to have a file open for both reading and writing at the same time. However, "fwrite_open" clears the contents of whatever file it opens and starts editing a new, blank file. So MZX also provides the "fwrite_modify" and "fwrite_append" functions, which preserve the contents of the file being opened. "fwrite_modify" places the write cursor at the beginning of the file, while "fwrite_append" starts writing at the end of the file.

The cursor, or in other words the place in the file currently be read from or written to, is maintained in the counters "fread_pos" and "fwrite_pos". These are separate values that do not interfere with one another, since it is after all quite common to have one file open for reading and another for writing. The cursors are automatically advanced when read/write operations are performed on the file, but they can also be set manually to navigate around the file. Before the port, when counters were only 16 bits, there existed "fread_page" and "fwrite_page" counters for working with files larger than 64KB in size. Since counters became 32-bit, however, these have been rendered obsolete, and fread_pos/fwrite_pos rendered absolute. Additionally, this has allowed for a convenient way to seek to the end of the file, by setting the appropriate "_pos" counter to -1. It is also possible to use this to check if a file exists, by opening it for reading and attempting to seek to the end this way. If the file doesn't exist, fread_pos will still be -1 when you check it, instead of the length of the file.

Reading and Writing Binary Data

Reading and writing actual data can be done in a few different ways. The simplest of these is simply to use the counters "fread" and "fwrite" as placeholders for data to be read from or written to the open file. "fwrite" must always be assigned TO (except for certain syntax related to strings, see below), but "fread" can be interpolated into robotic code just like any other counter, and will be evaluated each time it is encountered. Evaluation, in this case, entails reading one byte from the file, and advancing fread_pos by one. So for example:

set "counter" to "('fread'+('fread'<<8)+('fread'<<16)+('fread'<<24))"

This would read four bytes stored in little-endian format from the file, and combine them together to form a single 32-bit value. (See the articles on expressions and bitwise math for more information on this code.)

This is a somewhat contrived application, since another way to read from and write to a file is to use "fread_counter" and "fwrite_counter". These read and write full 32-bit counter values, instead of just bytes.

Reading and Writing Strings

Finally, MZX provides reading and writing of arbitrary length strings. Originally, the method for doing this was very limited. The "fread" and "fwrite" counters were overloaded to work with string values. A quirk with the way strings are handled forced a nonstandard syntax here, however: instead of

set "fwrite" to "$string"

it was (and is) necessary to use

set "$string" to "fwrite"

This method is limited in its usefulness because it requires the use of a terminating character, which is not user-definable. Instead of choosing something sensible and widely used, like a null byte (i.e. 0x00), Exophase chose to make the terminator an asterisk (*). As a result, the "fread#" and "fwrite#" function counters have become preferred for their accuracy and lack of side-effects. The number provided to the counter indicates how many characters to read or write. So for example, "fread20" reads 20 characters into a string. Be aware that these counters are specifically overloaded for string manipulation, and so it is not possible to interpolate "fread20" into an arbitrary string, as might be done with "fread". The syntax is very strictly:

set "$string" to "fread20"
set "$string" to "fwrite&$string.length&"

The second line demonstrates the use of counter interpolation to simply write the string in its entirety. It can of course be used to write only part of the string as well, and with string offsets to write an internal substring (see the article on strings for more details on this sort of manipulation).

Of course, being able to read in a string up to a terminating character is a useful function that is currently non-trivial (though still fairly simple) to implement. One way to do this would be to incrementally build the string byte by byte. Alternatively, one can seek ahead in the file for the terminating character, and count the number of character read from the starting point, reading the whole string in one command. The first method parses the file only once, but has to dynamically resize the string many times. The second method requires two passes over the portion of the file containing the string, but allocates space for the entire string only once. Since file operations are less costly than memory reallocations [A.N. citation/testing needed!], the second method is preferred, and is detailed in the following code:

set "local1" to 0
. "i.e. a null terminator"
set "$tmp" to "target_string" 
. "This sets up a string name to be passed by reference.  This is almost entirely a style thing in this case."
. "The only real benefit is that for a long string, there's less memory overhead since the string only has to be allocated once."
. "Still, it's a neat piece of code."
goto "#readstr"
...
: "#readstr"
set "local2" to "fread_pos"
: "readstr_loop"
set "local3" to "fread"
if "(('local3'='local1')o('local3'=-1))" = 0 then "readstr_loop"
. "This checks for the end of the file as well, since otherwise we could loop forever without an appropriate terminator."
set "local3" to "('fread_pos'-'local2'-1)"
. "The -1 accounts for the terminator itself, so that it won't be included in the string.  This can of course be changed."
set "fread_pos" to "local2"
set "$&$tmp&" to "fread&local3&"
. "This is the pass-by-reference trick.  The same thing can be accomplished by setting the target string to $tmp after the"
. "subroutine returns.  As mentioned before, this does cause the string data to be allocated twice."
set "local2" to "fread"
. "We need to read one more character to get past the terminator; this can be used as an extra return value to test whether"
. "the string terminated as expected, or at the end of the file."
goto "#return"

Of course depending on the size of the data stored in the file, another popular method is to simply read the entire file into a string and parse the contents internally as needed.

Closing Files

One final note: once you have finished working with a file, it is generally a good idea to close it and free up the handle for other programs to use. However, MZX does not provide an fread or fwrite_close function. Instead, and because only one file can be open for reading or writing at a time, the following trick is used to reset the handle:

set "" to "fread_open"
set "" to "fwrite_open"

Hackish though it may be, this closes the respective file by attempting to open a non-existent, empty string.

Other Types of File Access

MZX supports the reading and writing of select file formats specific to itself. Most of these follow the same basic concept as the general file access commands for opening a file. That is, set "filename.ext" to "command counter".

Saving and Loading Robots

Robots, or more specifically Robotic code, can be saved and loaded in a number of different ways. The most basic methods are:

set "filename.ext" to "save_robot"
set "filename.ext" to "load_robot"

The first command saves the source code of the current robot to a file as plain text, the same as you would see if you opened the robot in the editor. The second loads plaintext source code from a file, replacing the current source code of the robot and starting execution from the first command.

Another method which does effectively the same thing but in a different way, is this:

set "filename.ext" to "save_bc"
set "filename.ext" to "load_bc"

These are the same as the previous two commands, with the difference that the source code is saved and interpreted in bytecode form (hence "bc"), not plaintext. Bytecode is how robots are stored in the MZX world itself, and is several times smaller (in general) than the human-readable text. The trade-off is that it is not then human-readable, of course.

Finally, all of these command counters can take a parameter, like so:

set "filename.ext" to "save_robot20"
set "filename.ext" to "load_robot&target_robot&"
set "filename.ext" to "save_bc&ridPickles&"
set "filename.ext" to "load_bc&robot('loopcount')&"

This parameter, as implied, is a robot ID number, and instead of saving or loading code from/to the robot issuing the command, MZX will perform the operation on the robot with the specified ID.

Saving and Loading Games and Worlds

MZX also allows the saving and loading of MZX games in two distinctly different ways. The first is to save/load the game exactly as if the player had pressed F3 or F4 and selected a file.

set "filename.sav" to "save_game"
set "filename.sav" to "load_game"

These can be (and are) used to create custom save/load routines in MZX games. Naturally, this preserves counter values and robot execution states.

The other option is to save the current state of the MZX world. The following functions are provided:

set "filename.mzx" to "save_world"
set "filename.mzx" to "load_world"

However, currently the second one does not actually do anything, which may be an unintentional bug. For now, swap world "filename.mzx" can be used as an alternative. This method does NOT save counters (they are not stored in the world file since all counters are evaluated and initialized when the game starts), nor does it save robot execution state (for the same reason).

Saving and Loading MZMs

Unlike the previous applications, which all have very similar syntax, MZMs involve a number of different parameters that are not easily encapsulated by a simple function counter. Instead, other MZX commands have been overloaded to work with them. Saving an MZM works as follows:

copy (overlay) block at "xcoord" "ycoord" for "width" "height" to "@filename.mzm" "mzm_type"

While loading an MZM works like this:

put "@filename.mzm" Image_file "target_layer" at "xcoord" "ycoord"

The nuances of these commands are beyond the scope of this article, and are more fully covered in the article on MZMs.

Palettes and Character Sets

Palettes and character sets currently have no quick method of saving, though of course they have been easily loaded with load palette "filename.pal" and load char set "filename.chr" since version 2.0. It is not that difficult to use general fwrite commands to do this manually, however, as the formats for both filetypes are simple and easy to read and write. Below are some simple routines for doing just this:

: "#save_charset"
set "&$tmp&" to "fwrite_open"
. "To use this as a true subroutine, set $tmp to the name of the file you want to save beforehand."
set "char" to 0
set "byte" to 0
. "These are built-in counters for reading binary data out of a character."
: "sc_loop"
set "fwrite" to "char_byte"
inc "byte" by 1
if "byte" < 14 then "sc_loop"
set "byte" to 0
inc "char" by 1
if "char" < 256 then "sc_loop"
. "We have now iterated through each byte in each character, in order, and written them to the file."
. "This is all a .chr file actually is."
set "" to "fwrite_open"
goto "#return"

: "#save_palette"
set "&$tmp&" to "fwrite_open"
loop start
set "fwrite" to "SMZX_R&loopcount&"
set "fwrite" to "SMZX_G&loopcount&"
set "fwrite" to "SMZX_B&loopcount&"
loop for 15
. "Despite the name, the SMZX_* counters are not exclusively for SMZX mode, and are simply the easiest"
. "way of directly accessing the color values.  This loop sets each component (red, green, and blue) of each"
. "color in order, which is all a .pal file is.  The loop terminator can be set to 255 for an SMZX palette."
set "" to "fwrite_open"
goto "#return"