From a7f2b8e43e19d4bf5a3c2eb3e82766e2085ccfdd Mon Sep 17 00:00:00 2001 From: sukus Date: Wed, 4 Sep 2024 22:32:04 +0200 Subject: [PATCH 1/4] Implement `INCLUDE_ONCE` directive --- include/asm/fstack.hpp | 1 + man/rgbasm.5 | 9 +++++++++ src/asm/fstack.cpp | 17 ++++++++++++++++- src/asm/lexer.cpp | 1 + src/asm/parser.y | 10 ++++++++++ test/asm/include-once.asm | 2 ++ test/asm/include-once.inc | 1 + 7 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/asm/include-once.asm create mode 100644 test/asm/include-once.inc diff --git a/include/asm/fstack.hpp b/include/asm/fstack.hpp index 433652e3b..8a8cb1940 100644 --- a/include/asm/fstack.hpp +++ b/include/asm/fstack.hpp @@ -65,6 +65,7 @@ std::optional fstk_FindFile(std::string const &path); bool yywrap(); void fstk_RunInclude(std::string const &path, bool updateStateNow); +void fstk_RunIncludeOnce(std::string const &path); void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs); void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span); void fstk_RunFor( diff --git a/man/rgbasm.5 b/man/rgbasm.5 index d59bbdad9..67beb125f 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -2158,6 +2158,15 @@ calls infinitely (or until you run out of memory, whichever comes first). INCLUDE "irq.inc" .Ed .Pp +You may also ensure a file only gets included once by using +.Ic INCLUDE_ONCE +instead of +.Ic INCLUDE . +This will skip including a file if its exact path has already been included before (with +.Ic INCLUDE +or +.Ic INCLUDE_ONCE ) . +.Pp You may also implicitly .Ic INCLUDE a file before the source file with the diff --git a/src/asm/fstack.cpp b/src/asm/fstack.cpp index 551d25340..ae1ab7d51 100644 --- a/src/asm/fstack.cpp +++ b/src/asm/fstack.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "error.hpp" #include "helpers.hpp" @@ -45,8 +46,8 @@ size_t maxRecursionDepth; // The first include path for `fstk_FindFile` to try is none at all static std::vector includePaths = {""}; - static std::string preIncludeName; +static std::unordered_set includedFiles; std::string const &FileStackNode::dump(uint32_t curLineNo) const { if (data.holds>()) { @@ -305,10 +306,24 @@ void fstk_RunInclude(std::string const &path, bool preInclude) { return; } + // Remember this path as being INCLUDEd at least once + includedFiles.insert(path); + if (!newFileContext(*fullPath, false)) fatalerror("Failed to set up lexer for file include\n"); } +void fstk_RunIncludeOnce(std::string const &path) { + if (includedFiles.find(path) != includedFiles.end()) { + if (verbose) { + printf("File '%s' already included, skipping INCLUDE_ONCE", path.c_str()); + } + return; + } + + fstk_RunInclude(path, false); +} + void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs) { Symbol *macro = sym_FindExactSymbol(macroName); diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 0ddc42a0f..69f48b55a 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -256,6 +256,7 @@ static std::unordered_map ke {"INCHARMAP", T_(OP_INCHARMAP) }, {"INCLUDE", T_(POP_INCLUDE) }, + {"INCLUDE_ONCE", T_(POP_INCLUDE_ONCE) }, {"PRINT", T_(POP_PRINT) }, {"PRINTLN", T_(POP_PRINTLN) }, {"EXPORT", T_(POP_EXPORT) }, diff --git a/src/asm/parser.y b/src/asm/parser.y index f3d961249..fe8d350a9 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -236,6 +236,7 @@ %token POP_IF "IF" %token POP_INCBIN "INCBIN" %token POP_INCLUDE "INCLUDE" +%token POP_INCLUDE_ONCE "INCLUDE_ONCE" %token POP_LOAD "LOAD" %token POP_MACRO "MACRO" %token POP_NEWCHARMAP "NEWCHARMAP" @@ -464,6 +465,7 @@ line_directive: | for | break | include + | include_once | if // It's important that all of these require being at line start for `skipIfBlock` | elif @@ -1146,6 +1148,14 @@ include: } ; +include_once: + label POP_INCLUDE_ONCE string endofline { + fstk_RunIncludeOnce($3); + if (failedOnMissingInclude) + YYACCEPT; + } +; + incbin: POP_INCBIN string { sect_BinaryFile($2, 0); diff --git a/test/asm/include-once.asm b/test/asm/include-once.asm new file mode 100644 index 000000000..c9053634a --- /dev/null +++ b/test/asm/include-once.asm @@ -0,0 +1,2 @@ +INCLUDE_ONCE "include-once.inc" +INCLUDE_ONCE "include-once.inc" diff --git a/test/asm/include-once.inc b/test/asm/include-once.inc new file mode 100644 index 000000000..fc2da9c2b --- /dev/null +++ b/test/asm/include-once.inc @@ -0,0 +1 @@ +DEF HELLO EQU 1 From 75a37468c8a1f340e391c0b1f00e82d321a7fe0e Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Sat, 7 Sep 2024 23:18:15 -0400 Subject: [PATCH 2/4] Identify files by (device, inode), not by path --- include/asm/fstack.hpp | 9 +++++++-- src/asm/fstack.cpp | 33 +++++++++++++++++++-------------- src/asm/parser.y | 4 ++-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/include/asm/fstack.hpp b/include/asm/fstack.hpp index 8a8cb1940..a85e98bc4 100644 --- a/include/asm/fstack.hpp +++ b/include/asm/fstack.hpp @@ -17,6 +17,12 @@ #include "asm/lexer.hpp" +enum IncludeType { + INCLUDE_NORMAL, + INCLUDE_PRE, + INCLUDE_ONCE +}; + struct FileStackNode { FileStackNodeType type; Either< @@ -64,8 +70,7 @@ void fstk_SetPreIncludeFile(std::string const &path); std::optional fstk_FindFile(std::string const &path); bool yywrap(); -void fstk_RunInclude(std::string const &path, bool updateStateNow); -void fstk_RunIncludeOnce(std::string const &path); +void fstk_RunInclude(std::string const &path, IncludeType type); void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs); void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span); void fstk_RunFor( diff --git a/src/asm/fstack.cpp b/src/asm/fstack.cpp index ae1ab7d51..b9fe272d8 100644 --- a/src/asm/fstack.cpp +++ b/src/asm/fstack.cpp @@ -6,10 +6,11 @@ #include #include #include +#include #include #include #include -#include +#include #include "error.hpp" #include "helpers.hpp" @@ -47,7 +48,7 @@ size_t maxRecursionDepth; // The first include path for `fstk_FindFile` to try is none at all static std::vector includePaths = {""}; static std::string preIncludeName; -static std::unordered_set includedFiles; +static std::set> includedFiles; std::string const &FileStackNode::dump(uint32_t curLineNo) const { if (data.holds>()) { @@ -292,11 +293,11 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint return context; } -void fstk_RunInclude(std::string const &path, bool preInclude) { +void fstk_RunInclude(std::string const &path, IncludeType type) { std::optional fullPath = fstk_FindFile(path); if (!fullPath) { - if (generatedMissingIncludes && !preInclude) { + if (generatedMissingIncludes && type != INCLUDE_PRE) { if (verbose) printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno)); failedOnMissingInclude = true; @@ -306,22 +307,26 @@ void fstk_RunInclude(std::string const &path, bool preInclude) { return; } - // Remember this path as being INCLUDEd at least once - includedFiles.insert(path); - - if (!newFileContext(*fullPath, false)) - fatalerror("Failed to set up lexer for file include\n"); -} + // The pair of device ID and serial number uniquely identify a file, with `stat()` + // following symbolic links to identify the actual file. + struct stat statBuf; + if (stat(fullPath->c_str(), &statBuf) != 0) { + error("Failed to stat file '%s': %s\n", fullPath->c_str(), strerror(errno)); + return; + } + std::pair inode{statBuf.st_dev, statBuf.st_ino}; -void fstk_RunIncludeOnce(std::string const &path) { - if (includedFiles.find(path) != includedFiles.end()) { + if (type == INCLUDE_ONCE && includedFiles.find(inode) != includedFiles.end()) { if (verbose) { printf("File '%s' already included, skipping INCLUDE_ONCE", path.c_str()); } return; } - fstk_RunInclude(path, false); + includedFiles.insert(inode); + + if (!newFileContext(*fullPath, false)) + fatalerror("Failed to set up lexer for file include\n"); } void fstk_RunMacro(std::string const ¯oName, std::shared_ptr macroArgs) { @@ -410,5 +415,5 @@ void fstk_Init(std::string const &mainPath, size_t maxDepth) { maxRecursionDepth = maxDepth; if (!preIncludeName.empty()) - fstk_RunInclude(preIncludeName, true); + fstk_RunInclude(preIncludeName, INCLUDE_PRE); } diff --git a/src/asm/parser.y b/src/asm/parser.y index fe8d350a9..2c6dcabf9 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -1142,7 +1142,7 @@ export_def: include: label POP_INCLUDE string endofline { - fstk_RunInclude($3, false); + fstk_RunInclude($3, INCLUDE_NORMAL); if (failedOnMissingInclude) YYACCEPT; } @@ -1150,7 +1150,7 @@ include: include_once: label POP_INCLUDE_ONCE string endofline { - fstk_RunIncludeOnce($3); + fstk_RunInclude($3, INCLUDE_ONCE); if (failedOnMissingInclude) YYACCEPT; } From ef4f9b8ef0e99c8458301a22d6a0d91c5c4b6a3b Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Sat, 7 Sep 2024 23:27:25 -0400 Subject: [PATCH 3/4] Actually test symbolic links --- test/asm/include-once.asm | 1 + test/asm/test.sh | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/asm/include-once.asm b/test/asm/include-once.asm index c9053634a..db946d300 100644 --- a/test/asm/include-once.asm +++ b/test/asm/include-once.asm @@ -1,2 +1,3 @@ INCLUDE_ONCE "include-once.inc" INCLUDE_ONCE "include-once.inc" +INCLUDE_ONCE "include-link.inc" diff --git a/test/asm/test.sh b/test/asm/test.sh index 33edcd205..b9b220a62 100755 --- a/test/asm/test.sh +++ b/test/asm/test.sh @@ -11,9 +11,12 @@ input="$(mktemp)" output="$(mktemp)" errput="$(mktemp)" +# Create a symbolic link for the `include-once.asm` test case. +ln include-once.inc include-link.inc + # Immediate expansion is the desired behavior. # shellcheck disable=SC2064 -trap "rm -f ${o@Q} ${gb@Q} ${input@Q} ${output@Q} ${errput@Q}" EXIT +trap "rm -f ${o@Q} ${gb@Q} ${input@Q} ${output@Q} ${errput@Q} include-link.inc" EXIT tests=0 failed=0 From a1500e81e02630e9c6975e757f59fc54290b37ca Mon Sep 17 00:00:00 2001 From: Rangi42 Date: Sat, 7 Sep 2024 23:45:49 -0400 Subject: [PATCH 4/4] Correct docs --- man/rgbasm.5 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 67beb125f..a0edd31d6 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -2162,12 +2162,13 @@ You may also ensure a file only gets included once by using .Ic INCLUDE_ONCE instead of .Ic INCLUDE . -This will skip including a file if its exact path has already been included before (with -.Ic INCLUDE +This will skip including a file if it has already been included before (with +.Ic INCLUDE , +.Ic INCLUDE_ONCE , or -.Ic INCLUDE_ONCE ) . +.Fl P ) . .Pp -You may also implicitly +You can implicitly .Ic INCLUDE a file before the source file with the .Fl P