Implement READFILE function (#1759)

This commit is contained in:
Rangi
2025-07-18 18:27:52 -04:00
committed by GitHub
parent 4a2f9fc744
commit 53c39d01d4
12 changed files with 102 additions and 9 deletions

View File

@@ -599,6 +599,7 @@ with its corresponding argument in
.Pq So %% Sc is replaced by the So % Sc character .
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
.It Fn READFILE name max Ta Returns the contents of the file Ar name No as a string. Reads up to Ar max No bytes, or the entire contents if Ar max No is not specified. If the file isn't found in the current directory, the include-path list passed to Xr rgbasm 1 Ap s Fl I No option on the command line will be searched.
.El
.Pp
The following functions operate on string expressions, but return integers.
@@ -1814,10 +1815,9 @@ Use
.Ic INCBIN
to include a raw binary file as it is.
If the file isn't found in the current directory, the include-path list passed to
.Xr rgbasm 1
(see the
.Xr rgbasm 1 Ap s
.Fl I
option) on the command line will be searched.
option on the command line will be searched.
.Bd -literal -offset indent
INCBIN "titlepic.bin"
INCBIN "sprites/hero.bin"
@@ -2370,11 +2370,10 @@ block, all of them but the first one are ignored.
Use
.Ic INCLUDE
to process another assembler file and then return to the current file when done.
If the file isn't found in the current directory, the include path list (see the
If the file isn't found in the current directory, the include-path list passed to
.Xr rgbasm 1 Ap s
.Fl I
option in
.Xr rgbasm 1 )
will be searched.
option on the command line will be searched.
You may nest
.Ic INCLUDE
calls infinitely (or until you run out of memory, whichever comes first).

View File

@@ -239,6 +239,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"TZCOUNT", T_(OP_TZCOUNT) },
{"BYTELEN", T_(OP_BYTELEN) },
{"READFILE", T_(OP_READFILE) },
{"STRBYTE", T_(OP_STRBYTE) },
{"STRCAT", T_(OP_STRCAT) },
{"STRCHAR", T_(OP_STRCHAR) },

View File

@@ -38,6 +38,7 @@
#include <algorithm>
#include <ctype.h>
#include <inttypes.h>
#include <optional>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -61,6 +62,7 @@
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
static std::optional<std::string> readFile(std::string const &name, uint32_t maxLen);
static uint32_t strToNum(std::vector<int32_t> const &s);
static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName);
static size_t strlenUTF8(std::string const &str, bool printErrors);
@@ -303,6 +305,7 @@
%token OP_LOG "LOG"
%token OP_LOW "LOW"
%token OP_POW "POW"
%token OP_READFILE "READFILE"
%token OP_REVCHAR "REVCHAR"
%token OP_ROUND "ROUND"
%token OP_SIN "SIN"
@@ -1687,6 +1690,20 @@ string_literal:
$$ = std::move($1);
$$.append($3);
}
| OP_READFILE LPAREN string RPAREN {
if (std::optional<std::string> contents = readFile($3, UINT32_MAX); contents) {
$$ = std::move(*contents);
} else {
YYACCEPT;
}
}
| OP_READFILE LPAREN string COMMA uconst RPAREN {
if (std::optional<std::string> contents = readFile($3, $5); contents) {
$$ = std::move(*contents);
} else {
YYACCEPT;
}
}
| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
size_t len = strlenUTF8($3, false);
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");
@@ -2728,6 +2745,44 @@ void yy::parser::error(std::string const &str) {
::error("%s", str.c_str());
}
static std::optional<std::string> readFile(std::string const &name, uint32_t maxLen) {
FILE *file = nullptr;
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
file = fopen(fullPath->c_str(), "rb");
}
if (!file) {
if (fstk_FileError(name, "READFILE")) {
// If `fstk_FileError` returned true due to `-MG`, we should abort due to a
// missing file, so return `std::nullopt`, which tells the caller to `YYACCEPT`
return std::nullopt;
}
return "";
}
Defer closeFile{[&] { fclose(file); }};
size_t readSize = maxLen;
if (fseek(file, 0, SEEK_END) == 0) {
// If the file is seekable and shorter than the max length,
// just read as many bytes as there are
if (long fileSize = ftell(file); static_cast<size_t>(fileSize) < readSize) {
readSize = fileSize;
}
fseek(file, 0, SEEK_SET);
} else if (errno != ESPIPE) {
error("Error determining size of READFILE file '%s': %s", name.c_str(), strerror(errno));
}
std::string contents;
contents.resize(readSize);
if (fread(&contents[0], 1, readSize, file) < readSize || ferror(file)) {
error("Error reading READFILE file '%s': %s", name.c_str(), strerror(errno));
return "";
}
return contents;
}
static uint32_t strToNum(std::vector<int32_t> const &s) {
uint32_t length = s.size();

View File

@@ -888,7 +888,7 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
}
Defer closeFile{[&] { fclose(file); }};
if (fseek(file, 0, SEEK_END) != -1) {
if (fseek(file, 0, SEEK_END) == 0) {
if (startPos > ftell(file)) {
error("Specified start position is greater than length of file '%s'", name.c_str());
return false;
@@ -935,7 +935,7 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
}
Defer closeFile{[&] { fclose(file); }};
if (fseek(file, 0, SEEK_END) != -1) {
if (fseek(file, 0, SEEK_END) == 0) {
if (long fsize = ftell(file); startPos > fsize) {
error("Specified start position is greater than length of file '%s'", name.c_str());
return false;

View File

@@ -0,0 +1,24 @@
section "tilemap", rom0
/*
input:
$20
$01 $03 $05 $07 $09
$02 $04 $06 $08 $10
$00 $de $01 $df $80
5
*/
def tilemap equs readfile("readfile-binary.inc.bin")
def area = bytelen(#tilemap) - 2
def offset = strbyte(#tilemap, 0)
def width = strbyte(#tilemap, area + 1)
db width, area / width
for idx, area
db strbyte(#tilemap, idx + 1) + offset
endr
/*
output:
5, 3
$21 $23 $25 $27 $29
$22 $24 $26 $28 $30
$20 $fe $21 $ff $a0
*/

Binary file not shown.

View File

@@ -0,0 +1 @@
!#%')"$&(0 <20>!<21><>

View File

@@ -0,0 +1,2 @@
def s equs readfile("readfile-nonexist.inc")
assert strlen(#s) == 0

View File

@@ -0,0 +1,3 @@
error: readfile-nonexist.asm(1):
Error opening READFILE file 'readfile-nonexist.inc': No such file or directory
Assembly aborted with 1 error!

5
test/asm/readfile.asm Normal file
View File

@@ -0,0 +1,5 @@
def s equs readfile("readfile.inc")
println strupr(#s) ++ "!"
redef s equs readfile("readfile.inc", 5)
println strrpl(#s, "l", "w") ++ "?"

1
test/asm/readfile.inc Normal file
View File

@@ -0,0 +1 @@
hello world

2
test/asm/readfile.out Normal file
View File

@@ -0,0 +1,2 @@
HELLO WORLD!
hewwo?