?login_element?

Subversion Repositories NedoOS

Rev

Rev 557 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. /*
  2.  
  3.   SjASMPlus Z80 Cross Compiler
  4.  
  5.   This is modified sources of SjASM by Aprisobal - aprisobal@tut.by
  6.  
  7.   Copyright (c) 2006 Sjoerd Mastijn
  8.  
  9.   This software is provided 'as-is', without any express or implied warranty.
  10.   In no event will the authors be held liable for any damages arising from the
  11.   use of this software.
  12.  
  13.   Permission is granted to anyone to use this software for any purpose,
  14.   including commercial applications, and to alter it and redistribute it freely,
  15.   subject to the following restrictions:
  16.  
  17.   1. The origin of this software must not be misrepresented; you must not claim
  18.          that you wrote the original software. If you use this software in a product,
  19.          an acknowledgment in the product documentation would be appreciated but is
  20.          not required.
  21.  
  22.   2. Altered source versions must be plainly marked as such, and must not be
  23.          misrepresented as being the original software.
  24.  
  25.   3. This notice may not be removed or altered from any source distribution.
  26.  
  27. */
  28.  
  29. // sjio.cpp
  30.  
  31. #include "sjdefs.h"
  32.  
  33. #include <fcntl.h>
  34.  
  35. int ListAddress;
  36. std::vector<const char*> archivedFileNames;     // archive of all files opened (also includes!) (fullname!)
  37.  
  38. static constexpr int LIST_EMIT_BYTES_BUFFER_SIZE = 1024 * 64;
  39. static constexpr int DESTBUFLEN = 8192;
  40.  
  41. // ReadLine buffer and variables around
  42. static char rlbuf[LINEMAX2 * 2];
  43. static char * rlpbuf, * rlpbuf_end, * rlppos;
  44. static bool colonSubline;
  45. static int blockComment;
  46.  
  47. static int ListEmittedBytes[LIST_EMIT_BYTES_BUFFER_SIZE], nListBytes = 0;
  48. static char WriteBuffer[DESTBUFLEN];
  49. static int tape_seek = 0;
  50. static int tape_length = 0;
  51. static int tape_parity = 0x55;
  52. static FILE* FP_tapout = NULL;
  53. static FILE* FP_Input = NULL, * FP_Output = NULL, * FP_RAW = NULL;
  54. static FILE* FP_ListingFile = NULL,* FP_ExportFile = NULL;
  55. static aint WBLength = 0;
  56.  
  57. static void CloseBreakpointsFile();
  58.  
  59. // returns permanent C-string pointer to the fullpathname (if new, it is added to archive)
  60. const char* ArchiveFilename(const char* fullpathname) {
  61.         for (auto fname : archivedFileNames) {          // search whole archive for identical full name
  62.                 if (!strcmp(fname, fullpathname)) return fname;
  63.         }
  64.         const char* newName = STRDUP(fullpathname);
  65.         archivedFileNames.push_back(newName);
  66.         return newName;
  67. }
  68.  
  69. // does release all archived filenames, making all pointers (and archive itself) invalid
  70. void ReleaseArchivedFilenames() {
  71.         for (auto filename : archivedFileNames) free((void*)filename);
  72.         archivedFileNames.clear();
  73. }
  74.  
  75. // find position of extension in filename (points at dot char or beyond filename if no extension)
  76. // filename is pointer to writeable format containing file name (can be full path) (NOT NULL)
  77. // if initWithName and filenameBufferSize are explicitly provided, filename will be first overwritten with those
  78. char* FilenameExtPos(char* filename, const char* initWithName, size_t initNameMaxLength) {
  79.         // if the init value is provided with positive buffer size, init the buffer first
  80.         if (0 < initNameMaxLength && initWithName) {
  81.                 STRCPY(filename, initNameMaxLength, initWithName);
  82.         }
  83.         // find start of the base filename
  84.         const char* baseName = FilenameBasePos(filename);
  85.         // find extension of the filename and return position of it
  86.         char* const filenameEnd = filename + strlen(filename);
  87.         char* extPos = filenameEnd;
  88.         while (baseName < extPos && '.' != *extPos) --extPos;
  89.         if (baseName < extPos) return extPos;
  90.         // no extension found (empty filename, or "name", or ".name"), return end of filename
  91.         return filenameEnd;
  92. }
  93.  
  94. const char* FilenameBasePos(const char* fullname) {
  95.         const char* const filenameEnd = fullname + strlen(fullname);
  96.         const char* baseName = filenameEnd;
  97.         while (fullname < baseName && '/' != baseName[-1] && '\\' != baseName[-1]) --baseName;
  98.         return baseName;
  99. }
  100.  
  101. void ConstructDefaultFilename(char* dest, size_t dest_size, const char* ext, bool checkIfDestIsEmpty) {
  102.         if (nullptr == dest || nullptr == ext || !ext[0]) exit(1);      // invalid arguments
  103.         // if the destination buffer has already some content and check is requested, exit
  104.         if (checkIfDestIsEmpty && dest[0]) return;
  105.         size_t extSz = strlen(ext);
  106.         dest[0] = 0;
  107.         // construct the new default name - search for explicit name in sourcefiles
  108.         for (SSource & src : sourceFiles) {
  109.                 if (!src.fname[0]) continue;
  110.                 STRNCPY(dest, dest_size, src.fname, dest_size-1-extSz);
  111.                 dest[dest_size-1-extSz] = 0;
  112.                 break;
  113.         }
  114.         if (!dest[0]) STRNCPY(dest, dest_size, "asm", dest_size-1);             // no explicit, use "asm" base
  115.         // replace the extension
  116.         STRCPY(FilenameExtPos(dest), dest_size, ext);
  117. }
  118.  
  119. void CheckRamLimitExceeded() {
  120.         if (Options::IsLongPtr) return;         // in "longptr" mode with no device keep the address as is
  121.         static bool notWarnedCurAdr = true;
  122.         static bool notWarnedDisp = true;
  123.         char buf[64];
  124.         if (CurAddress >= 0x10000) {
  125.                 if (LASTPASS == pass && notWarnedCurAdr) {
  126.                         SPRINTF2(buf, 64, "RAM limit exceeded 0x%X by %s",
  127.                                          (unsigned int)CurAddress, DISP_NONE != PseudoORG ? "DISP":"ORG");
  128.                         Warning(buf);
  129.                         notWarnedCurAdr = false;
  130.                 }
  131.                 if (DISP_NONE != PseudoORG) CurAddress &= 0xFFFF;       // fake DISP address gets auto-wrapped FFFF->0
  132.         } else notWarnedCurAdr = true;
  133.         if (DISP_NONE != PseudoORG && adrdisp >= 0x10000) {
  134.                 if (LASTPASS == pass && notWarnedDisp) {
  135.                         SPRINTF1(buf, 64, "RAM limit exceeded 0x%X by ORG", (unsigned int)adrdisp);
  136.                         Warning(buf);
  137.                         notWarnedDisp = false;
  138.                 }
  139.         } else notWarnedDisp = true;
  140. }
  141.  
  142. void resolveRelocationAndSmartSmc(const aint immediateOffset, Relocation::EType minType) {
  143.         // call relocation data generator to do its own errands
  144.         Relocation::resolveRelocationAffected(immediateOffset, minType);
  145.         // check smart-SMC functionality, if there is unresolved record to be set up
  146.         if (INT_MAX == immediateOffset || sourcePosStack.empty() || 0 == smartSmcIndex) return;
  147.         if (smartSmcLines.size() < smartSmcIndex) return;
  148.         auto & smartSmc = smartSmcLines.at(smartSmcIndex - 1);
  149.         if (~0U != smartSmc.colBegin || smartSmc != sourcePosStack.back()) return;
  150.         if (1 < sourcePosStack.back().colBegin) return;         // only first segment belongs to SMC label
  151.         // record does match current line, resolve the smart offset
  152.         smartSmc.colBegin = immediateOffset;
  153. }
  154.  
  155. void WriteDest() {
  156.         if (!WBLength) {
  157.                 return;
  158.         }
  159.         destlen += WBLength;
  160.         if (FP_Output != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_Output) != WBLength) {
  161.                 Error("Write error (disk full?)", NULL, FATAL);
  162.         }
  163.         if (FP_RAW != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_RAW) != WBLength) {
  164.                 Error("Write error (disk full?)", NULL, FATAL);
  165.         }
  166.  
  167.         if (FP_tapout)
  168.         {
  169.                 int write_length = tape_length + WBLength > 65535 ? 65535 - tape_length : WBLength;
  170.  
  171.                 if ( (aint)fwrite(WriteBuffer, 1, write_length, FP_tapout) != write_length) Error("Write error (disk full?)", NULL, FATAL);
  172.  
  173.                 for (int i = 0; i < write_length; i++) tape_parity ^= WriteBuffer[i];
  174.                 tape_length += write_length;
  175.  
  176.                 if (write_length < WBLength)
  177.                 {
  178.                         WBLength = 0;
  179.                         CloseTapFile();
  180.                         Error("Tape block exceeds maximal size");
  181.                 }
  182.         }
  183.         WBLength = 0;
  184. }
  185.  
  186. void PrintHex(char* & dest, aint value, int nibbles) {
  187.         if (nibbles < 1 || 8 < nibbles) ExitASM(33);    // invalid argument
  188.         const char oldChAfter = dest[nibbles];
  189.         const aint mask = (int(sizeof(aint)*2) <= nibbles) ? ~0L : (1L<<(nibbles*4))-1L;
  190.         if (nibbles != sprintf(dest, "%0*X", nibbles, value&mask)) ExitASM(33);
  191.         dest += nibbles;
  192.         *dest = oldChAfter;
  193. }
  194.  
  195. void PrintHex32(char*& dest, aint value) {
  196.         PrintHex(dest, value, 8);
  197. }
  198.  
  199. void PrintHexAlt(char*& dest, aint value)
  200. {
  201.         char buffer[24] = { 0 }, * bp = buffer;
  202.         sprintf(buffer, "%04X", value);
  203.         while (*bp) *dest++ = *bp++;
  204. }
  205.  
  206. static char pline[4*LINEMAX];
  207.  
  208. // buffer must be at least 4*LINEMAX chars long
  209. void PrepareListLine(char* buffer, aint hexadd)
  210. {
  211.         ////////////////////////////////////////////////////
  212.         // Line numbers to 1 to 99999 are supported only  //
  213.         // For more lines, then first char is incremented //
  214.         ////////////////////////////////////////////////////
  215.  
  216.         int digit = ' ';
  217.         int linewidth = reglenwidth;
  218.         uint32_t currentLine = sourcePosStack.at(IncludeLevel).line;
  219.         aint linenumber = currentLine % 10000;
  220.         if (5 <= linewidth) {           // five-digit number, calculate the leading "digit"
  221.                 linewidth = 5;
  222.                 digit = currentLine / 10000 + '0';
  223.                 if (digit > '~') digit = '~';
  224.                 if (currentLine >= 10000) linenumber += 10000;
  225.         }
  226.         memset(buffer, ' ', 24);
  227.         if (listmacro) buffer[23] = '>';
  228.         if (Options::LST_T_MC_ONLY == Options::syx.ListingType) buffer[23] = '{';
  229.         sprintf(buffer, "%*u", linewidth, linenumber); buffer[linewidth] = ' ';
  230.         memcpy(buffer + linewidth, "++++++", IncludeLevel > 6 - linewidth ? 6 - linewidth : IncludeLevel);
  231.         sprintf(buffer + 6, "%04X", hexadd & 0xFFFF); buffer[10] = ' ';
  232.         if (digit > '0') *buffer = digit & 0xFF;
  233.         // if substitutedLine is completely empty, list rather source line any way
  234.         if (!*substitutedLine) substitutedLine = line;
  235.         STRCPY(buffer + 24, LINEMAX2-24, substitutedLine);
  236.         // add EOL comment if substituted was used and EOL comment is available
  237.         if (substitutedLine != line && eolComment) STRCAT(buffer, LINEMAX2, eolComment);
  238. }
  239.  
  240. static void ListFileStringRtrim() {
  241.         // find end of currently prepared line
  242.         char* beyondLine = pline+24;
  243.         while (*beyondLine) ++beyondLine;
  244.         // and remove trailing white space (space, tab, newline, carriage return, etc..)
  245.         while (pline < beyondLine && White(beyondLine[-1])) --beyondLine;
  246.         // set new line and new string terminator after
  247.         *beyondLine++ = '\n';
  248.         *beyondLine = 0;
  249. }
  250.  
  251. // returns FILE* handle to either actual file defined by --lst=xxx, or stderr if --msg=lst, or NULL
  252. // ! do not fclose this handle, for fclose logic use the FP_ListingFile variable itself !
  253. FILE* GetListingFile() {
  254.         if (NULL != FP_ListingFile) return FP_ListingFile;
  255.         if (OV_LST == Options::OutputVerbosity) return stderr;
  256.         return NULL;
  257. }
  258.  
  259. static aint lastListedLine = -1;
  260.  
  261. void ListFile(bool showAsSkipped) {
  262.         if (LASTPASS != pass || NULL == GetListingFile() || donotlist || Options::syx.IsListingSuspended) {
  263.                 donotlist = nListBytes = 0;
  264.                 return;
  265.         }
  266.         if (showAsSkipped && Options::LST_T_ACTIVE == Options::syx.ListingType) {
  267.                 assert(nListBytes <= 0);        // inactive line should not produce any machine code?!
  268.                 nListBytes = 0;
  269.                 return;         // filter out all "inactive" lines
  270.         }
  271.         if (Options::LST_T_MC_ONLY == Options::syx.ListingType && nListBytes <= 0) {
  272.                 return;         // filter out all lines without machine-code bytes
  273.         }
  274.         int pos = 0;
  275.         do {
  276.                 if (showAsSkipped) substitutedLine = line;      // override substituted lines in skipped mode
  277.                 PrepareListLine(pline, ListAddress);
  278.                 const bool hideSource = !showAsSkipped && (lastListedLine == CompiledCurrentLine);
  279.                 if (hideSource) pline[24] = 0;                          // hide *same* source line on sub-sequent list-lines
  280.                 lastListedLine = CompiledCurrentLine;           // remember this line as listed
  281.                 char* pp = pline + 10;
  282.                 int BtoList = (nListBytes < 4) ? nListBytes : 4;
  283.                 for (int i = 0; i < BtoList; ++i) {
  284.                         if (-2 == ListEmittedBytes[i + pos]) pp += sprintf(pp, "...");
  285.                         else pp += sprintf(pp, " %02X", ListEmittedBytes[i + pos]);
  286.                 }
  287.                 *pp = ' ';
  288.                 if (showAsSkipped) pline[11] = '~';
  289.                 ListFileStringRtrim();
  290.                 fputs(pline, GetListingFile());
  291.                 nListBytes -= BtoList;
  292.                 ListAddress += BtoList;
  293.                 pos += BtoList;
  294.         } while (0 < nListBytes);
  295.         nListBytes = 0;
  296.         ListAddress = CurAddress;                       // move ListAddress also beyond unlisted but emitted bytes
  297. }
  298.  
  299. void ListSilentOrExternalEmits() {
  300.         // catch silent/external emits like "sj.add_byte(0x123)" from Lua script
  301.         if (0 == nListBytes) return;            // no silent/external emit happened
  302.         ++CompiledCurrentLine;
  303.         char silentOrExternalBytes[] = "; these bytes were emitted silently/externally (lua script?)";
  304.         substitutedLine = silentOrExternalBytes;
  305.         eolComment = nullptr;
  306.         ListFile();
  307.         substitutedLine = line;
  308. }
  309.  
  310. static bool someByteEmitted = false;
  311.  
  312. bool DidEmitByte() {    // returns true if some byte was emitted since last call to this function
  313.         bool didEmit = someByteEmitted;         // value to return
  314.         someByteEmitted = false;                        // reset the flag
  315.         return didEmit;
  316. }
  317.  
  318. static void EmitByteNoListing(int byte, bool preserveDeviceMemory = false) {
  319.         someByteEmitted = true;
  320.         if (LASTPASS == pass) {
  321.                 WriteBuffer[WBLength++] = (char)byte;
  322.                 if (DESTBUFLEN == WBLength) WriteDest();
  323.         }
  324.         // the page-checking in device mode must be done in all passes, the slot can have "wrap" option
  325.         if (DeviceID) {
  326.                 Device->CheckPage(CDevice::CHECK_EMIT);
  327.                 if (MemoryPointer) {
  328.                         if (LASTPASS == pass && !preserveDeviceMemory) *MemoryPointer = (char)byte;
  329.                         ++MemoryPointer;
  330.                 }
  331.         } else {
  332.                 CheckRamLimitExceeded();
  333.         }
  334.         ++CurAddress;
  335.         if (DISP_NONE != PseudoORG) ++adrdisp;
  336. }
  337.  
  338. static bool PageDiffersWarningShown = false;
  339.  
  340. void EmitByte(int byte, bool isInstructionStart) {
  341.         if (isInstructionStart) {
  342.                 // SLD (Source Level Debugging) tracing-data logging
  343.                 if (IsSldExportActive()) {
  344.                         int pageNum = Page->Number;
  345.                         if (DISP_NONE != PseudoORG) {
  346.                                 int mappingPageNum = Device->GetPageOfA16(CurAddress);
  347.                                 if (LABEL_PAGE_UNDEFINED == dispPageNum) {      // special DISP page is not set, use mapped
  348.                                         pageNum = mappingPageNum;
  349.                                 } else {
  350.                                         pageNum = dispPageNum;                                  // special DISP page is set, use it instead
  351.                                         if (pageNum != mappingPageNum && !PageDiffersWarningShown) {
  352.                                                 WarningById(W_DISP_MEM_PAGE);
  353.                                                 PageDiffersWarningShown = true;         // show warning about different mapping only once
  354.                                         }
  355.                                 }
  356.                         }
  357.                         WriteToSldFile(pageNum, CurAddress);
  358.                 }
  359.         }
  360.         byte &= 0xFF;
  361.         if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE-1) {
  362.                 ListEmittedBytes[nListBytes++] = byte;          // write also into listing
  363.         } else {
  364.                 if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE) {
  365.                         // too many bytes, show it in listing as "..."
  366.                         ListEmittedBytes[nListBytes++] = -2;
  367.                 }
  368.         }
  369.         EmitByteNoListing(byte);
  370. }
  371.  
  372. void EmitWord(int word, bool isInstructionStart) {
  373.         EmitByte(word % 256, isInstructionStart);
  374.         EmitByte(word / 256, false);
  375. }
  376.  
  377. void EmitBytes(const int* bytes, bool isInstructionStart) {
  378.         if (BYTES_END_MARKER == *bytes) {
  379.                 Error("Illegal instruction", line, IF_FIRST);
  380.                 SkipToEol(lp);
  381.         }
  382.         while (BYTES_END_MARKER != *bytes) {
  383.                 EmitByte(*bytes++, isInstructionStart);
  384.                 isInstructionStart = (INSTRUCTION_START_MARKER == *bytes);      // only true for first byte, or when marker
  385.                 if (isInstructionStart) ++bytes;
  386.         }
  387. }
  388.  
  389. void EmitWords(const int* words, bool isInstructionStart) {
  390.         while (BYTES_END_MARKER != *words) {
  391.                 EmitWord(*words++, isInstructionStart);
  392.                 isInstructionStart = false;             // only true for first word
  393.         }
  394. }
  395.  
  396. void EmitBlock(aint byte, aint len, bool preserveDeviceMemory, int emitMaxToListing) {
  397.         if (len <= 0) {
  398.                 const aint adrMask = Options::IsLongPtr ? ~0 : 0xFFFF;
  399.                 CurAddress = (CurAddress + len) & adrMask;
  400.                 if (DISP_NONE != PseudoORG) adrdisp = (adrdisp + len) & adrMask;
  401.                 if (DeviceID)   Device->CheckPage(CDevice::CHECK_NO_EMIT);
  402.                 else                    CheckRamLimitExceeded();
  403.                 return;
  404.         }
  405.         if (LIST_EMIT_BYTES_BUFFER_SIZE <= nListBytes + emitMaxToListing) {     // clamp emit to list buffer
  406.                 emitMaxToListing = LIST_EMIT_BYTES_BUFFER_SIZE - nListBytes;
  407.         }
  408.         while (len--) {
  409.                 int dVal = (preserveDeviceMemory && DeviceID && MemoryPointer) ? MemoryPointer[0] : byte;
  410.                 EmitByteNoListing(byte, preserveDeviceMemory);
  411.                 if (LASTPASS == pass && emitMaxToListing) {
  412.                         // put "..." marker into listing if some more bytes are emitted after last listed
  413.                         if ((0 == --emitMaxToListing) && len) ListEmittedBytes[nListBytes++] = -2;
  414.                         else ListEmittedBytes[nListBytes++] = dVal&0xFF;
  415.                 }
  416.         }
  417. }
  418.  
  419. char* GetPath(const char* fname, char** filenamebegin, bool systemPathsBeforeCurrent)
  420. {
  421.         char fullFilePath[MAX_PATH] = { 0 };
  422.         CStringsList* dir = Options::IncludeDirsList;   // include-paths to search
  423.         // search current directory first (unless "systemPathsBeforeCurrent")
  424.         if (!systemPathsBeforeCurrent) {
  425.                 // if found, just skip the `while (dir)` loop
  426.                 if (SJ_SearchPath(CurrentDirectory, fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) dir = nullptr;
  427.                 else fullFilePath[0] = 0;       // clear fullFilePath every time when not found
  428.         }
  429.         while (dir) {
  430.                 if (SJ_SearchPath(dir->string, fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) break;
  431.                 fullFilePath[0] = 0;    // clear fullFilePath every time when not found
  432.                 dir = dir->next;
  433.         }
  434.         // if the file was not found in the list, and current directory was not searched yet
  435.         if (!fullFilePath[0] && systemPathsBeforeCurrent) {
  436.                 //and the current directory was not searched yet, do it now, set empty string if nothing
  437.                 if (!SJ_SearchPath(CurrentDirectory, fname, NULL, MAX_PATH, fullFilePath, filenamebegin)) {
  438.                         fullFilePath[0] = 0;    // clear fullFilePath every time when not found
  439.                 }
  440.         }
  441.         if (!fullFilePath[0] && filenamebegin) {        // if still not found, reset also *filenamebegin
  442.                 *filenamebegin = fullFilePath;
  443.         }
  444.         // copy the result into new memory
  445.         char* kip = STRDUP(fullFilePath);
  446.         if (kip == NULL) ErrorOOM();
  447.         // convert filenamebegin pointer into the copied string (from temporary buffer pointer)
  448.         if (filenamebegin) *filenamebegin += (kip - fullFilePath);
  449.         return kip;
  450. }
  451.  
  452. // if offset is negative, it functions as "how many bytes from end of file"
  453. // if length is negative, it functions as "how many bytes from end of file to not load"
  454. void BinIncFile(const char* fname, aint offset, aint length) {
  455.         // open the desired file
  456.         FILE* bif;
  457.         char* fullFilePath = GetPath(fname);
  458.         if (!FOPEN_ISOK(bif, fullFilePath, "rb")) Error("opening file", fname);
  459.         free(fullFilePath);
  460.  
  461.         // Get length of file
  462.         int totlen = 0, advanceLength;
  463.         if (bif && (fseek(bif, 0, SEEK_END) || (totlen = ftell(bif)) < 0)) Error("telling file length", fname, FATAL);
  464.  
  465.         // process arguments (extra features like negative offset/length or INT_MAX length)
  466.         // negative offset means "from the end of file"
  467.         if (offset < 0) offset += totlen;
  468.         // negative length means "except that many from end of file"
  469.         if (length < 0) length += totlen - offset;
  470.         // default length INT_MAX is "till the end of file"
  471.         if (INT_MAX == length) length = totlen - offset;
  472.         // verbose output of final values (before validation may terminate assembler)
  473.         if (LASTPASS == pass && Options::OutputVerbosity <= OV_ALL) {
  474.                 char diagnosticTxt[MAX_PATH];
  475.                 SPRINTF4(diagnosticTxt, MAX_PATH, "include data: name=%s (%d bytes) Offset=%d  Len=%d", fname, totlen, offset, length);
  476.                 _CERR diagnosticTxt _ENDL;
  477.         }
  478.         // validate the resulting [offset, length]
  479.         if (offset < 0 || length < 0 || totlen < offset + length) {
  480.                 Error("file too short", fname);
  481.                 offset = std::min(std::max(0, offset), totlen);                 //TODO change to std::clamp when C++17 is used
  482.                 length = std::min(std::max(0, length), totlen-offset);  //TODO change to std::clamp when C++17 is used
  483.                 assert((0 <= offset) && (offset + length <= totlen));
  484.         }
  485.         if (0 == length) {
  486.                 Warning("include data: requested to include no data (length=0)");
  487.                 if (bif) fclose(bif);
  488.                 return;
  489.         }
  490.         assert(nullptr != bif);                         // otherwise it was handled by 0 == length case above
  491.  
  492.         if (pass != LASTPASS) {
  493.                 while (length) {
  494.                         advanceLength = length;         // maximum possible to advance in address space
  495.                         if (DeviceID) {                         // particular device may adjust that to less
  496.                                 Device->CheckPage(CDevice::CHECK_EMIT);
  497.                                 if (MemoryPointer) {    // fill up current memory page if possible
  498.                                         advanceLength = Page->RAM + Page->Size - MemoryPointer;
  499.                                         if (length < advanceLength) advanceLength = length;
  500.                                         MemoryPointer += advanceLength;         // also update it! Doh!
  501.                                 }
  502.                         }
  503.                         length -= advanceLength;
  504.                         if (length <= 0 && 0 == advanceLength) Error("BinIncFile internal error", NULL, FATAL);
  505.                         if (DISP_NONE != PseudoORG) adrdisp = adrdisp + advanceLength;
  506.                         CurAddress = CurAddress + advanceLength;
  507.                 }
  508.         } else {
  509.                 // Seek to the beginning of part to include
  510.                 if (fseek(bif, offset, SEEK_SET) || ftell(bif) != offset) {
  511.                         Error("seeking in file to offset", fname, FATAL);
  512.                 }
  513.  
  514.                 // Reading data from file
  515.                 char* data = new char[length + 1], * bp = data;
  516.                 if (NULL == data) ErrorOOM();
  517.                 size_t res = fread(bp, 1, length, bif);
  518.                 if (res != (size_t)length) Error("reading data from file failed", fname, FATAL);
  519.                 while (length--) EmitByteNoListing(*bp++);
  520.                 delete[] data;
  521.         }
  522.         fclose(bif);
  523. }
  524.  
  525. static void OpenDefaultList(const char *fullpath);
  526.  
  527. static stdin_log_t::const_iterator stdin_read_it;
  528. static stdin_log_t* stdin_log = nullptr;
  529.  
  530. void OpenFile(const char* nfilename, bool systemPathsBeforeCurrent, stdin_log_t* fStdinLog)
  531. {
  532.         if (++IncludeLevel > 20) {
  533.                 Error("Over 20 files nested", NULL, ALL);
  534.                 --IncludeLevel;
  535.                 return;
  536.         }
  537.         char* fullpath, * filenamebegin;
  538.         if (!*nfilename && fStdinLog) {
  539.                 fullpath = STRDUP("<stdin>");
  540.                 filenamebegin = fullpath;
  541.                 FP_Input = stdin;
  542.                 stdin_log = fStdinLog;
  543.                 stdin_read_it = stdin_log->cbegin();    // reset read iterator (for 2nd+ pass)
  544.         } else {
  545.                 fullpath = GetPath(nfilename, &filenamebegin, systemPathsBeforeCurrent);
  546.  
  547.                 if (!FOPEN_ISOK(FP_Input, fullpath, "rb")) {
  548.                         free(fullpath);
  549.                         Error("opening file", nfilename, ALL);
  550.                         --IncludeLevel;
  551.                         return;
  552.                 }
  553.         }
  554.  
  555.         const char* oFileNameFull = fileNameFull, * oCurrentDirectory = CurrentDirectory;
  556.  
  557.         // archive the filename (for referencing it in SLD tracing data or listing/errors)
  558.         fileNameFull = ArchiveFilename(fullpath);       // get const pointer into archive
  559.         sourcePosStack.emplace_back(Options::IsShowFullPath ? fileNameFull : FilenameBasePos(fileNameFull));
  560.  
  561.         // refresh pre-defined values related to file/include
  562.         DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
  563.         DefineTable.Replace("__FILE__", fileNameFull);
  564.         if (0 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", fileNameFull);
  565.  
  566.         // open default listing file for each new source file (if default listing is ON)
  567.         if (LASTPASS == pass && 0 == IncludeLevel && Options::IsDefaultListingName) {
  568.                 OpenDefaultList(fileNameFull);                  // explicit listing file is already opened
  569.         }
  570.         // show in listing file which file was opened
  571.         FILE* listFile = GetListingFile();
  572.         if (LASTPASS == pass && listFile) {
  573.                 fputs("# file opened: ", listFile);
  574.                 fputs(fileNameFull, listFile);
  575.                 fputs("\n", listFile);
  576.         }
  577.  
  578.         *filenamebegin = 0;                                     // shorten fullpath to only-path string
  579.         CurrentDirectory = fullpath;            // and use it as CurrentDirectory
  580.  
  581.         rlpbuf = rlpbuf_end = rlbuf;
  582.         colonSubline = false;
  583.         blockComment = 0;
  584.  
  585.         ReadBufLine();
  586.  
  587.         if (stdin != FP_Input) fclose(FP_Input);
  588.         else {
  589.                 if (1 == pass) {
  590.                         stdin_log->push_back(0);        // add extra zero terminator
  591.                         clearerr(stdin);                        // reset EOF on the stdin for another round of input
  592.                 }
  593.         }
  594.         CurrentDirectory = oCurrentDirectory;
  595.         free(fullpath);                                         // was used by CurrentDirectory till now
  596.         fullpath = nullptr;
  597.  
  598.         // show in listing file which file was closed
  599.         if (LASTPASS == pass && listFile) {
  600.                 fputs("# file closed: ", listFile);
  601.                 fputs(fileNameFull, listFile);
  602.                 fputs("\n", listFile);
  603.  
  604.                 // close listing file (if "default" listing filename is used)
  605.                 if (FP_ListingFile && 0 == IncludeLevel && Options::IsDefaultListingName) {
  606.                         if (Options::AddLabelListing) LabelTable.Dump();
  607.                         fclose(FP_ListingFile);
  608.                         FP_ListingFile = NULL;
  609.                 }
  610.         }
  611.  
  612.         --IncludeLevel;
  613.  
  614.         maxlin = std::max(maxlin, sourcePosStack.back().line);
  615.         sourcePosStack.pop_back();
  616.         fileNameFull = oFileNameFull;
  617.  
  618.         // refresh pre-defined values related to file/include
  619.         DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
  620.         DefineTable.Replace("__FILE__", fileNameFull ? fileNameFull : "<none>");
  621.         if (-1 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", "<none>");
  622. }
  623.  
  624. void IncludeFile(const char* nfilename, bool systemPathsBeforeCurrent)
  625. {
  626.         auto oStdin_log = stdin_log;
  627.         auto oStdin_read_it = stdin_read_it;
  628.         FILE* oFP_Input = FP_Input;
  629.         FP_Input = 0;
  630.  
  631.         char* pbuf = rlpbuf, * pbuf_end = rlpbuf_end, * buf = STRDUP(rlbuf);
  632.         if (buf == NULL) ErrorOOM();
  633.         bool oColonSubline = colonSubline;
  634.         if (blockComment) Error("Internal error 'block comment'", NULL, FATAL); // comment can't INCLUDE
  635.  
  636.         OpenFile(nfilename, systemPathsBeforeCurrent);
  637.  
  638.         colonSubline = oColonSubline;
  639.         rlpbuf = pbuf, rlpbuf_end = pbuf_end;
  640.         STRCPY(rlbuf, 8192, buf);
  641.         free(buf);
  642.  
  643.         FP_Input = oFP_Input;
  644.         stdin_log = oStdin_log;
  645.         stdin_read_it = oStdin_read_it;
  646. }
  647.  
  648. typedef struct {
  649.         char    name[12];
  650.         size_t  length;
  651.         byte    marker[16];
  652. } BOMmarkerDef;
  653.  
  654. const BOMmarkerDef UtfBomMarkers[] = {
  655.         { { "UTF8" }, 3, { 0xEF, 0xBB, 0xBF } },
  656.         { { "UTF32BE" }, 4, { 0, 0, 0xFE, 0xFF } },
  657.         { { "UTF32LE" }, 4, { 0xFF, 0xFE, 0, 0 } },             // must be detected *BEFORE* UTF16LE
  658.         { { "UTF16BE" }, 2, { 0xFE, 0xFF } },
  659.         { { "UTF16LE" }, 2, { 0xFF, 0xFE } }
  660. };
  661.  
  662. static bool ReadBufData() {
  663.         // check here also if `line` buffer is not full
  664.         if ((LINEMAX-2) <= (rlppos - line)) Error("Line too long", NULL, FATAL);
  665.         // now check for read data
  666.         if (rlpbuf < rlpbuf_end) return 1;              // some data still in buffer
  667.         // check EOF on files in every pass, stdin only in first, following will starve the stdin_log
  668.         if ((stdin != FP_Input || 1 == pass) && feof(FP_Input)) return 0;       // no more data in file
  669.         // read next block of data
  670.         rlpbuf = rlbuf;
  671.         // handle STDIN file differently (pass1 = read it, pass2+ replay "log" variable)
  672.         if (1 == pass || stdin != FP_Input) {   // ordinary file is re-read every pass normally
  673.                 rlpbuf_end = rlbuf + fread(rlbuf, 1, 4096, FP_Input);
  674.                 *rlpbuf_end = 0;                                        // add zero terminator after new block
  675.         }
  676.         if (stdin == FP_Input) {
  677.                 // store copy of stdin into stdin_log during pass 1
  678.                 if (1 == pass && rlpbuf < rlpbuf_end) {
  679.                         stdin_log->insert(stdin_log->end(), rlpbuf, rlpbuf_end);
  680.                 }
  681.                 // replay the log in 2nd+ pass
  682.                 if (1 < pass) {
  683.                         rlpbuf_end = rlpbuf;
  684.                         long toCopy = std::min(8000L, (long)std::distance(stdin_read_it, stdin_log->cend()));
  685.                         if (0 < toCopy) {
  686.                                 memcpy(rlbuf, &(*stdin_read_it), toCopy);
  687.                                 stdin_read_it += toCopy;
  688.                                 rlpbuf_end += toCopy;
  689.                         }
  690.                         *rlpbuf_end = 0;                                // add zero terminator after new block
  691.                 }
  692.         }
  693.         // check UTF BOM markers only at the beginning of the file (source line == 0)
  694.         assert(!sourcePosStack.empty());
  695.         if (sourcePosStack.back().line) {
  696.                 return (rlpbuf < rlpbuf_end);           // return true if some data were read
  697.         }
  698.         //UTF BOM markers detector
  699.         for (const auto & bomMarkerData : UtfBomMarkers) {
  700.                 if (rlpbuf_end < (rlpbuf + bomMarkerData.length)) continue;     // not enough bytes in buffer
  701.                 if (memcmp(rlpbuf, bomMarkerData.marker, bomMarkerData.length)) continue;       // marker not found
  702.                 if (&bomMarkerData != UtfBomMarkers) {  // UTF8 is first in the array, other markers show error
  703.                         Error("Invalid UTF encoding detected (only ASCII and UTF8 works)", bomMarkerData.name, FATAL);
  704.                 }
  705.                 rlpbuf += bomMarkerData.length; // skip the UTF8 BOM marker
  706.         }
  707.         return (rlpbuf < rlpbuf_end);                   // return true if some data were read
  708. }
  709.  
  710. void ReadBufLine(bool Parse, bool SplitByColon) {
  711.         // if everything else fails (no data, not running, etc), return empty line
  712.         *line = 0;
  713.         bool IsLabel = true;
  714.         // try to read through the buffer and produce new line from it
  715.         while (IsRunning && ReadBufData()) {
  716.                 // start of new line (or fake "line" by colon)
  717.                 rlppos = line;
  718.                 substitutedLine = line;         // also reset "substituted" line to the raw new one
  719.                 eolComment = NULL;
  720.                 if (colonSubline) {                     // starting from colon (creating new fake "line")
  721.                         colonSubline = false;   // (can't happen inside block comment)
  722.                         *(rlppos++) = ' ';
  723.                         IsLabel = false;
  724.                 } else {                                        // starting real new line
  725.                         IsLabel = (0 == blockComment);
  726.                 }
  727.                 bool afterNonAlphaNum, afterNonAlphaNumNext = true;
  728.                 // copy data from read buffer into `line` buffer until EOL/colon is found
  729.                 while (
  730.                                 ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf &&  // split by EOL
  731.                                 // split by colon only on 2nd+ char && SplitByColon && not inside block comment
  732.                                 (blockComment || !SplitByColon || rlppos == line || ':' != *rlpbuf)) {
  733.                         // copy the new character to new line
  734.                         *rlppos = *rlpbuf++;
  735.                         afterNonAlphaNum = afterNonAlphaNumNext;
  736.                         afterNonAlphaNumNext = !isalnum((byte)*rlppos);
  737.                         // Block comments logic first (anything serious may happen only "outside" of block comment
  738.                         if ('*' == *rlppos && ReadBufData() && '/' == *rlpbuf) {
  739.                                 if (0 < blockComment) --blockComment;   // block comment ends here, -1 from nesting
  740.                                 ++rlppos;       *rlppos++ = *rlpbuf++;          // copy the second char too
  741.                                 continue;
  742.                         }
  743.                         if ('/' == *rlppos && ReadBufData() && '*' == *rlpbuf) {
  744.                                 ++rlppos, ++blockComment;                               // block comment starts here, nest +1 more
  745.                                 *rlppos++ = *rlpbuf++;                                  // copy the second char too
  746.                                 continue;
  747.                         }
  748.                         if (blockComment) {                                                     // inside block comment just copy chars
  749.                                 ++rlppos;
  750.                                 continue;
  751.                         }
  752.                         // check if still in label area, if yes, copy the finishing colon as char (don't split by it)
  753.                         if ((IsLabel = (IsLabel && islabchar(*rlppos)))) {
  754.                                 ++rlppos;                                       // label character
  755.                                 //SMC offset handling
  756.                                 if (ReadBufData() && '+' == *rlpbuf) {  // '+' after label, add it as SMC_offset syntax
  757.                                         IsLabel = false;
  758.                                         *rlppos++ = *rlpbuf++;
  759.                                         if (ReadBufData() && (isdigit(byte(*rlpbuf)) || '*' == *rlpbuf)) *rlppos++ = *rlpbuf++;
  760.                                 }
  761.                                 if (ReadBufData() && ':' == *rlpbuf) {  // colon after label, add it
  762.                                         *rlppos++ = *rlpbuf++;
  763.                                         IsLabel = false;
  764.                                 }
  765.                                 continue;
  766.                         }
  767.                         // not in label any more, check for EOL comments ";" or "//"
  768.                         if ((';' == *rlppos) || ('/' == *rlppos && ReadBufData() && '/' == *rlpbuf)) {
  769.                                 eolComment = rlppos;
  770.                                 ++rlppos;                                       // EOL comment ";"
  771.                                 while (ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf) *rlppos++ = *rlpbuf++;
  772.                                 continue;
  773.                         }
  774.                         // check for string literals - double/single quotes
  775.                         if (afterNonAlphaNum && ('"' == *rlppos || '\'' == *rlppos)) {
  776.                                 const bool quotes = '"' == *rlppos;
  777.                                 int escaped = 0;
  778.                                 do {
  779.                                         if (escaped) --escaped;
  780.                                         ++rlppos;                               // previous char confirmed
  781.                                         *rlppos = ReadBufData() ? *rlpbuf : 0;  // copy next char (if available)
  782.                                         if (!*rlppos || '\r' == *rlppos || '\n' == *rlppos) *rlppos = 0;        // not valid
  783.                                         else ++rlpbuf;                  // buffer char read (accepted)
  784.                                         if (quotes && !escaped && '\\' == *rlppos) escaped = 2; // escape sequence detected
  785.                                 } while (*rlppos && (escaped || (quotes ? '"' : '\'') != *rlppos));
  786.                                 if (*rlppos) ++rlppos;          // there should be ending "/' in line buffer, confirm it
  787.                                 continue;
  788.                         }
  789.                         // anything else just copy
  790.                         ++rlppos;                               // previous char confirmed
  791.                 } // while "some char in buffer, and it's not line delimiter"
  792.                 // line interrupted somehow, may be correctly finished, check + finalize line and process it
  793.                 *rlppos = 0;
  794.                 // skip <EOL> char sequence in read buffer
  795.                 if (ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf)) {
  796.                         char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');      // flip CR->LF || LF->CR (and eats first)
  797.                         if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;     // if CRLF/LFCR pair, eat also second
  798.                         // if this was very last <EOL> in file (on non-empty line), add one more fake empty line
  799.                         if (!ReadBufData() && *line) *rlpbuf_end++ = '\n';      // to make listing files "as before"
  800.                 } else {
  801.                         // advance over single colon if that was the reason to terminate line parsing
  802.                         colonSubline = SplitByColon && ReadBufData() && (':' == *rlpbuf) && ++rlpbuf;
  803.                 }
  804.                 // do +1 for very first colon-segment only (rest is +1 due to artificial space at beginning)
  805.                 assert(!sourcePosStack.empty());
  806.                 size_t advanceColumns = colonSubline ? (0 == sourcePosStack.back().colEnd) + strlen(line) : 0;
  807.                 sourcePosStack.back().nextSegment(colonSubline, advanceColumns);
  808.                 // line is parsed and ready to be processed
  809.                 if (Parse)      ParseLine();    // processed here in loop
  810.                 else            return;                 // processed externally
  811.         } // while (IsRunning && ReadBufData())
  812. }
  813.  
  814. static void OpenListImp(const char* listFilename) {
  815.         // if STDERR is configured to contain listing, disable other listing files
  816.         if (OV_LST == Options::OutputVerbosity) return;
  817.         if (NULL == listFilename || !listFilename[0]) return;
  818.         if (!FOPEN_ISOK(FP_ListingFile, listFilename, "w")) {
  819.                 Error("opening file for write", listFilename, FATAL);
  820.         }
  821. }
  822.  
  823. void OpenList() {
  824.         // if STDERR is configured to contain listing, disable other listing files
  825.         if (OV_LST == Options::OutputVerbosity) return;
  826.         // check if listing file is already opened, or it is set to "default" file names
  827.         if (Options::IsDefaultListingName || NULL != FP_ListingFile) return;
  828.         // Only explicit listing files are opened here
  829.         OpenListImp(Options::ListingFName);
  830. }
  831.  
  832. static void OpenDefaultList(const char *fullpath) {
  833.         // if STDERR is configured to contain listing, disable other listing files
  834.         if (OV_LST == Options::OutputVerbosity) return;
  835.         // check if listing file is already opened, or it is set to explicit file name
  836.         if (!Options::IsDefaultListingName || NULL != FP_ListingFile) return;
  837.         if (NULL == fullpath || !*fullpath) return;             // no filename provided
  838.         // Create default listing name, and try to open it
  839.         char tempListName[LINEMAX+10];          // make sure there is enough room for new extension
  840.         char* extPos = FilenameExtPos(tempListName, fullpath, LINEMAX); // find extension position
  841.         STRCPY(extPos, 5, ".lst");                      // overwrite it with ".lst"
  842.         // list filename prepared, open it
  843.         OpenListImp(tempListName);
  844. }
  845.  
  846. void CloseDest() {
  847.         // Flush buffer before any other operations
  848.         WriteDest();
  849.         // does main output file exist? (to close it)
  850.         if (FP_Output == NULL) return;
  851.         // pad to desired size (and check for exceed of it)
  852.         if (size != -1L) {
  853.                 if (destlen > size) {
  854.                         ErrorInt("File exceeds 'size' by", destlen - size);
  855.                 }
  856.                 memset(WriteBuffer, 0, DESTBUFLEN);
  857.                 while (destlen < size) {
  858.                         WBLength = std::min(aint(DESTBUFLEN), size-destlen);
  859.                         WriteDest();
  860.                 }
  861.                 size = -1L;
  862.         }
  863.         fclose(FP_Output);
  864.         FP_Output = NULL;
  865. }
  866.  
  867. void SeekDest(long offset, int method) {
  868.         WriteDest();
  869.         if (FP_Output != NULL && fseek(FP_Output, offset, method)) {
  870.                 Error("File seek error (FPOS)", NULL, FATAL);
  871.         }
  872. }
  873.  
  874. void NewDest(const char* newfilename, int mode) {
  875.         // close previous output file
  876.         CloseDest();
  877.  
  878.         // and open new file (keep previous/default name, if no explicit was provided)
  879.         if (newfilename && *newfilename) STRCPY(Options::DestinationFName, LINEMAX, newfilename);
  880.         OpenDest(mode);
  881. }
  882.  
  883. void OpenDest(int mode) {
  884.         destlen = 0;
  885.         if (mode != OUTPUT_TRUNCATE && !FileExists(Options::DestinationFName)) {
  886.                 mode = OUTPUT_TRUNCATE;
  887.         }
  888.         if (!Options::NoDestinationFile && !FOPEN_ISOK(FP_Output, Options::DestinationFName, mode == OUTPUT_TRUNCATE ? "wb" : "r+b")) {
  889.                 Error("opening file for write", Options::DestinationFName, FATAL);
  890.         }
  891.         Options::NoDestinationFile = false;
  892.         if (NULL == FP_RAW && '-' == Options::RAWFName[0] && 0 == Options::RAWFName[1]) {
  893.                 FP_RAW = stdout;
  894.                 fflush(stdout);
  895.                 switchStdOutIntoBinaryMode();
  896.         }
  897.         if (FP_RAW == NULL && Options::RAWFName[0] && !FOPEN_ISOK(FP_RAW, Options::RAWFName, "wb")) {
  898.                 Error("opening file for write", Options::RAWFName);
  899.         }
  900.         if (FP_Output != NULL && mode != OUTPUT_TRUNCATE) {
  901.                 if (fseek(FP_Output, 0, mode == OUTPUT_REWIND ? SEEK_SET : SEEK_END)) {
  902.                         Error("File seek error (OUTPUT)", NULL, FATAL);
  903.                 }
  904.         }
  905. }
  906.  
  907. void CloseTapFile()
  908. {
  909.         char tap_data[2];
  910.  
  911.         WriteDest();
  912.         if (FP_tapout == NULL) return;
  913.  
  914.         tap_data[0] = tape_parity & 0xFF;
  915.         if (fwrite(tap_data, 1, 1, FP_tapout) != 1) Error("Write error (disk full?)", NULL, FATAL);
  916.  
  917.         if (fseek(FP_tapout, tape_seek, SEEK_SET)) Error("File seek end error in TAPOUT", NULL, FATAL);
  918.  
  919.         tap_data[0] =  tape_length     & 0xFF;
  920.         tap_data[1] = (tape_length>>8) & 0xFF;
  921.         if (fwrite(tap_data, 1, 2, FP_tapout) != 2) Error("Write error (disk full?)", NULL, FATAL);
  922.  
  923.         fclose(FP_tapout);
  924.         FP_tapout = NULL;
  925. }
  926.  
  927. void OpenTapFile(const char * tapename, int flagbyte)
  928. {
  929.         CloseTapFile();
  930.  
  931.         if (!FOPEN_ISOK(FP_tapout,tapename, "r+b")) {
  932.                 Error( "opening file for write", tapename);
  933.                 return;
  934.         }
  935.         if (fseek(FP_tapout, 0, SEEK_END)) Error("File seek end error in TAPOUT", tapename, FATAL);
  936.  
  937.         tape_seek = ftell(FP_tapout);
  938.         tape_parity = flagbyte;
  939.         tape_length = 2;
  940.  
  941.         byte tap_data[3] = { 0, 0, (byte)flagbyte };
  942.  
  943.         if (sizeof(tap_data) != fwrite(tap_data, 1, sizeof(tap_data), FP_tapout)) {
  944.                 fclose(FP_tapout);
  945.                 Error("Write error (disk full?)", NULL, FATAL);
  946.         }
  947. }
  948.  
  949. bool FileExists(const char* file_name) {
  950.         FILE* test;
  951.         bool exists = FOPEN_ISOK(test, file_name, "r");
  952.         exists && fclose(test);
  953.         return exists;
  954. }
  955.  
  956. void Close() {
  957.         if (*ModuleName) {
  958.                 Warning("ENDMODULE missing for module", ModuleName, W_ALL);
  959.         }
  960.  
  961.         CloseDest();
  962.         CloseTapFile();
  963.         if (FP_ExportFile != NULL) {
  964.                 fclose(FP_ExportFile);
  965.                 FP_ExportFile = NULL;
  966.         }
  967.         if (FP_RAW != NULL) {
  968.                 if (stdout != FP_RAW) fclose(FP_RAW);
  969.                 FP_RAW = NULL;
  970.         }
  971.         if (FP_ListingFile != NULL) {
  972.                 fclose(FP_ListingFile);
  973.                 FP_ListingFile = NULL;
  974.         }
  975.         CloseSld();
  976.         CloseBreakpointsFile();
  977. }
  978.  
  979. int SaveRAM(FILE* ff, int start, int length) {
  980.         //unsigned int addadr = 0,save = 0;
  981.         aint save = 0;
  982.         if (!DeviceID) return 0;                // unreachable currently
  983.         if (length + start > 0x10000) {
  984.                 length = -1;
  985.         }
  986.         if (length <= 0) {
  987.                 length = 0x10000 - start;
  988.         }
  989.  
  990.         CDeviceSlot* S;
  991.         for (int i=0;i<Device->SlotsCount;i++) {
  992.                 S = Device->GetSlot(i);
  993.                 if (start >= (int)S->Address  && start < (int)(S->Address + S->Size)) {
  994.                         if (length < (int)(S->Size - (start - S->Address))) {
  995.                                 save = length;
  996.                         } else {
  997.                                 save = S->Size - (start - S->Address);
  998.                         }
  999.                         if ((aint) fwrite(S->Page->RAM + (start - S->Address), 1, save, ff) != save) {
  1000.                                 return 0;
  1001.                         }
  1002.                         length -= save;
  1003.                         start += save;
  1004.                         if (length <= 0) {
  1005.                                 return 1;
  1006.                         }
  1007.                 }
  1008.         }
  1009.         return 0;               // unreachable (with current devices)
  1010. }
  1011.  
  1012. unsigned int MemGetWord(unsigned int address) {
  1013.         return MemGetByte(address) + (MemGetByte(address+1)<<8);
  1014. }
  1015.  
  1016. unsigned char MemGetByte(unsigned int address) {
  1017.         if (!DeviceID || pass != LASTPASS) {
  1018.                 return 0;
  1019.         }
  1020.  
  1021.         CDeviceSlot* S;
  1022.         for (int i=0;i<Device->SlotsCount;i++) {
  1023.                 S = Device->GetSlot(i);
  1024.                 if (address >= (unsigned int)S->Address  && address < (unsigned int)S->Address + (unsigned int)S->Size) {
  1025.                         return S->Page->RAM[address - S->Address];
  1026.                 }
  1027.         }
  1028.  
  1029.         ErrorInt("MemGetByte: Error reading address", address);
  1030.         return 0;
  1031. }
  1032.  
  1033.  
  1034. int SaveBinary(const char* fname, aint start, aint length) {
  1035.         FILE* ff;
  1036.         if (!FOPEN_ISOK(ff, fname, "wb")) {
  1037.                 Error("opening file for write", fname, FATAL);
  1038.         }
  1039.         int result = SaveRAM(ff, start, length);
  1040.         fclose(ff);
  1041.         return result;
  1042. }
  1043.  
  1044.  
  1045. int SaveBinary3dos(const char* fname, aint start, aint length, byte type, word w2, word w3) {
  1046.         FILE* ff;
  1047.         if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname, FATAL);
  1048.         // prepare +3DOS 128 byte header content
  1049.         constexpr aint hsize = 128;
  1050.         const aint full_length = hsize + length;
  1051.         byte sum = 0, p3dos_header[hsize] { "PLUS3DOS\032\001" };
  1052.         p3dos_header[11] = byte(full_length>>0);
  1053.         p3dos_header[12] = byte(full_length>>8);
  1054.         p3dos_header[13] = byte(full_length>>16);
  1055.         p3dos_header[14] = byte(full_length>>24);
  1056.         // +3 BASIC 8 byte header filled with "relevant values"
  1057.         p3dos_header[15+0] = type;
  1058.         p3dos_header[15+1] = byte(length>>0);
  1059.         p3dos_header[15+2] = byte(length>>8);
  1060.         p3dos_header[15+3] = byte(w2>>0);
  1061.         p3dos_header[15+4] = byte(w2>>8);
  1062.         p3dos_header[15+5] = byte(w3>>0);
  1063.         p3dos_header[15+6] = byte(w3>>8);
  1064.         // calculat checksum of the header
  1065.         for (const byte v : p3dos_header) sum += v;
  1066.         p3dos_header[hsize-1] = sum;
  1067.         // write header and data
  1068.         int result = (hsize == (aint) fwrite(p3dos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
  1069.         fclose(ff);
  1070.         return result;
  1071. }
  1072.  
  1073.  
  1074. int SaveBinaryAmsdos(const char* fname, aint start, aint length, word start_adr, byte type) {
  1075.         FILE* ff;
  1076.         if (!FOPEN_ISOK(ff, fname, "wb")) {
  1077.                 Error("opening file for write", fname, SUPPRESS);
  1078.                 return 0;
  1079.         }
  1080.         // prepare AMSDOS 128 byte header content
  1081.         constexpr aint hsize = 128;
  1082.         byte amsdos_header[hsize] {};   // all zeroed (user_number and filename stay like that, just zeroes)
  1083.         amsdos_header[0x12] = type;
  1084.         amsdos_header[0x15] = byte(start>>0);
  1085.         amsdos_header[0x16] = byte(start>>8);
  1086.         amsdos_header[0x18] = amsdos_header[0x40] = byte(length>>0);
  1087.         amsdos_header[0x19] = amsdos_header[0x41] = byte(length>>8);
  1088.         amsdos_header[0x1A] = byte(start_adr>>0);
  1089.         amsdos_header[0x1B] = byte(start_adr>>8);
  1090.         // calculat checksum of the header
  1091.         word sum = 0;
  1092.         for (int ii = 0x43; ii--; ) sum += amsdos_header[ii];
  1093.         amsdos_header[0x43] = byte(sum>>0);
  1094.         amsdos_header[0x44] = byte(sum>>8);
  1095.         // write header and data
  1096.         int result = (hsize == (aint) fwrite(amsdos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
  1097.         fclose(ff);
  1098.         return result;
  1099. }
  1100.  
  1101.  
  1102. // all arguments must be sanitized by caller (this just writes data block into opened file)
  1103. bool SaveDeviceMemory(FILE* file, const size_t start, const size_t length) {
  1104.         return (length == fwrite(Device->Memory + start, 1, length, file));
  1105. }
  1106.  
  1107.  
  1108. // start and length must be sanitized by caller
  1109. bool SaveDeviceMemory(const char* fname, const size_t start, const size_t length) {
  1110.         FILE* ff;
  1111.         if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname, FATAL);
  1112.         bool res = SaveDeviceMemory(ff, start, length);
  1113.         fclose(ff);
  1114.         return res;
  1115. }
  1116.  
  1117.  
  1118. int SaveHobeta(const char* fname, const char* fhobname, aint start, aint length) {
  1119.         unsigned char header[0x11];
  1120.         int i;
  1121.  
  1122.         if (length + start > 0x10000) {
  1123.                 length = -1;
  1124.         }
  1125.         if (length <= 0) {
  1126.                 length = 0x10000 - start;
  1127.         }
  1128.  
  1129.         memset(header,' ',9);
  1130.         i = strlen(fhobname);
  1131.         if (i > 1)
  1132.         {
  1133.                 const char *ext = strrchr(fhobname, '.');
  1134.                 if (ext && ext[1])
  1135.                 {
  1136.                         header[8] = ext[1];
  1137.                         i = ext-fhobname;
  1138.                 }
  1139.         }
  1140.         memcpy(header, fhobname, std::min(i,8));
  1141.  
  1142.         if (header[8] == 'B')   {
  1143.                 header[0x09] = (unsigned char)(length & 0xff);
  1144.                 header[0x0a] = (unsigned char)(length >> 8);
  1145.         } else  {
  1146.                 header[0x09] = (unsigned char)(start & 0xff);
  1147.                 header[0x0a] = (unsigned char)(start >> 8);
  1148.         }
  1149.  
  1150.         header[0x0b] = (unsigned char)(length & 0xff);
  1151.         header[0x0c] = (unsigned char)(length >> 8);
  1152.         header[0x0d] = 0;
  1153.         if (header[0x0b] == 0) {
  1154.                 header[0x0e] = header[0x0c];
  1155.         } else {
  1156.                 header[0x0e] = header[0x0c] + 1;
  1157.         }
  1158.         length = header[0x0e] * 0x100;
  1159.         int chk = 0;
  1160.         for (i = 0; i <= 14; chk = chk + (header[i] * 257) + i,i++) {
  1161.                 ;
  1162.         }
  1163.         header[0x0f] = (unsigned char)(chk & 0xff);
  1164.         header[0x10] = (unsigned char)(chk >> 8);
  1165.  
  1166.         FILE* ff;
  1167.         if (!FOPEN_ISOK(ff, fname, "wb")) {
  1168.                 Error("opening file for write", fname, FATAL);
  1169.         }
  1170.  
  1171.         int result = (17 == fwrite(header, 1, 17, ff)) && SaveRAM(ff, start, length);
  1172.         fclose(ff);
  1173.         return result;
  1174. }
  1175.  
  1176. EReturn ReadFile() {
  1177.         while (ReadLine()) {
  1178.                 const bool isInsideDupCollectingLines = !RepeatStack.empty() && !RepeatStack.top().IsInWork;
  1179.                 if (!isInsideDupCollectingLines) {
  1180.                         // check for ending of IF/IFN/... block (keywords: ENDIF, ELSE and ELSEIF)
  1181.                         char* p = line;
  1182.                         SkipBlanks(p);
  1183.                         if ('.' == *p) ++p;
  1184.                         EReturn retVal = END;
  1185.                         if (cmphstr(p, "elseif")) retVal = ELSEIF;
  1186.                         if (cmphstr(p, "else")) retVal = ELSE;
  1187.                         if (cmphstr(p, "endif")) retVal = ENDIF;
  1188.                         if (END != retVal) {
  1189.                                 // one of the end-block keywords was found, don't parse it as regular line
  1190.                                 // but just substitute the rest of it and return end value of the keyword
  1191.                                 ++CompiledCurrentLine;
  1192.                                 lp = ReplaceDefine(p);          // skip any empty substitutions and comments
  1193.                                 substitutedLine = line;         // for listing override substituted line with source
  1194.                                 if (ENDIF != retVal) ListFile();        // do the listing for ELSE and ELSEIF
  1195.                                 return retVal;
  1196.                         }
  1197.                 }
  1198.                 ParseLineSafe();
  1199.         }
  1200.         return END;
  1201. }
  1202.  
  1203.  
  1204. EReturn SkipFile() {
  1205.         int iflevel = 0;
  1206.         while (ReadLine()) {
  1207.                 char* p = line;
  1208.                 if (isLabelStart(p) && !Options::syx.IsPseudoOpBOF) {
  1209.                         // this could be label, skip it (the --dirbol users can't use label + IF/... inside block)
  1210.                         while (islabchar(*p)) ++p;
  1211.                         if (':' == *p) ++p;
  1212.                 }
  1213.                 SkipBlanks(p);
  1214.                 if ('.' == *p) ++p;
  1215.                 if (cmphstr(p, "if") || cmphstr(p, "ifn") || cmphstr(p, "ifused") ||
  1216.                         cmphstr(p, "ifnused") || cmphstr(p, "ifdef") || cmphstr(p, "ifndef")) {
  1217.                         ++iflevel;
  1218.                 } else if (cmphstr(p, "endif")) {
  1219.                         if (iflevel) {
  1220.                                 --iflevel;
  1221.                         } else {
  1222.                                 ++CompiledCurrentLine;
  1223.                                 lp = ReplaceDefine(p);          // skip any empty substitutions and comments
  1224.                                 substitutedLine = line;         // override substituted listing for ENDIF
  1225.                                 return ENDIF;
  1226.                         }
  1227.                 } else if (cmphstr(p, "else")) {
  1228.                         if (!iflevel) {
  1229.                                 ++CompiledCurrentLine;
  1230.                                 lp = ReplaceDefine(p);          // skip any empty substitutions and comments
  1231.                                 substitutedLine = line;         // override substituted listing for ELSE
  1232.                                 ListFile();
  1233.                                 return ELSE;
  1234.                         }
  1235.                 } else if (cmphstr(p, "elseif")) {
  1236.                         if (!iflevel) {
  1237.                                 ++CompiledCurrentLine;
  1238.                                 lp = ReplaceDefine(p);          // skip any empty substitutions and comments
  1239.                                 substitutedLine = line;         // override substituted listing for ELSEIF
  1240.                                 ListFile();
  1241.                                 return ELSEIF;
  1242.                         }
  1243.                 } else if (cmphstr(p, "lua")) {         // lua script block detected, skip it whole
  1244.                         // with extra custom while loop, to avoid confusion by `if/...` inside lua scripts
  1245.                         ListFile(true);
  1246.                         while (ReadLine()) {
  1247.                                 p = line;
  1248.                                 SkipBlanks(p);
  1249.                                 if (cmphstr(p, "endlua")) break;
  1250.                                 ListFile(true);
  1251.                         }
  1252.                 }
  1253.                 ListFile(true);
  1254.         }
  1255.         return END;
  1256. }
  1257.  
  1258. int ReadLineNoMacro(bool SplitByColon) {
  1259.         if (!IsRunning || !ReadBufData()) return 0;
  1260.         ReadBufLine(false, SplitByColon);
  1261.         return 1;
  1262. }
  1263.  
  1264. int ReadLine(bool SplitByColon) {
  1265.         if (IsRunning && lijst) {               // read MACRO lines, if macro is being emitted
  1266.                 if (!lijstp || !lijstp->string) return 0;
  1267.                 assert(!sourcePosStack.empty());
  1268.                 sourcePosStack.back() = lijstp->source;
  1269.                 STRCPY(line, LINEMAX, lijstp->string);
  1270.                 substitutedLine = line;         // reset substituted listing
  1271.                 eolComment = NULL;                      // reset end of line comment
  1272.                 lijstp = lijstp->next;
  1273.                 return 1;
  1274.         }
  1275.         return ReadLineNoMacro(SplitByColon);
  1276. }
  1277.  
  1278. int ReadFileToCStringsList(CStringsList*& f, const char* end) {
  1279.         // f itself should be already NULL, not resetting it here
  1280.         CStringsList** s = &f;
  1281.         while (ReadLineNoMacro()) {
  1282.                 ++CompiledCurrentLine;
  1283.                 char* p = line;
  1284.                 SkipBlanks(p);
  1285.                 if ('.' == *p) ++p;
  1286.                 if (cmphstr(p, end)) {          // finished, read rest after end marker into line buffers
  1287.                         lp = ReplaceDefine(p);
  1288.                         return 1;
  1289.                 }
  1290.                 *s = new CStringsList(line);
  1291.                 s = &((*s)->next);
  1292.                 ListFile(true);
  1293.         }
  1294.         return 0;
  1295. }
  1296.  
  1297. void WriteLabelEquValue(const char* name, aint value, FILE* f) {
  1298.         if (nullptr == f) return;
  1299.         char lnrs[16],* l = lnrs;
  1300.         STRCPY(temp, LINEMAX-2, name);
  1301.         STRCAT(temp, LINEMAX-1, ": EQU ");
  1302.         STRCAT(temp, LINEMAX-1, "0x");
  1303.         PrintHex32(l, value); *l = 0;
  1304.         STRCAT(temp, LINEMAX-1, lnrs);
  1305.         STRCAT(temp, LINEMAX-1, "\n");
  1306.         fputs(temp, f);
  1307. }
  1308.  
  1309. void WriteExp(const char* n, aint v) {
  1310.         if (FP_ExportFile == NULL) {
  1311.                 if (!FOPEN_ISOK(FP_ExportFile, Options::ExportFName, "w")) {
  1312.                         Error("opening file for write", Options::ExportFName, FATAL);
  1313.                 }
  1314.         }
  1315.         WriteLabelEquValue(n, v, FP_ExportFile);
  1316. }
  1317.  
  1318. /////// source-level-debugging support by Ckirby
  1319.  
  1320. static FILE* FP_SourceLevelDebugging = NULL;
  1321. static char sldMessage[LINEMAX2];
  1322. static const char* WriteToSld_noSymbol = "";
  1323. static char sldMessage_sourcePos[1024];
  1324. static char sldMessage_definitionPos[1024];
  1325. static const char* sldMessage_posFormat = "%d:%d:%d";   // at +3 is "%d:%d" and at +6 is "%d"
  1326. static std::vector<std::string> sldCommentKeywords;
  1327.  
  1328. static void WriteToSldFile_TextFilePos(char* buffer, const TextFilePos & pos) {
  1329.         int offsetFormat = !pos.colBegin ? 6 : !pos.colEnd ? 3 : 0;
  1330.         snprintf(buffer, 1024-1, sldMessage_posFormat + offsetFormat, pos.line, pos.colBegin, pos.colEnd);
  1331. }
  1332.  
  1333. static void OpenSldImp(const char* sldFilename) {
  1334.         if (nullptr == sldFilename || !sldFilename[0]) return;
  1335.         if (!FOPEN_ISOK(FP_SourceLevelDebugging, sldFilename, "w")) {
  1336.                 Error("opening file for write", sldFilename, FATAL);
  1337.         }
  1338.         fputs("|SLD.data.version|1\n", FP_SourceLevelDebugging);
  1339.         if (0 < sldCommentKeywords.size()) {
  1340.                 fputs("||K|KEYWORDS|", FP_SourceLevelDebugging);
  1341.                 bool notFirst = false;
  1342.                 for (auto keyword : sldCommentKeywords) {
  1343.                         if (notFirst) fputs(",", FP_SourceLevelDebugging);
  1344.                         notFirst = true;
  1345.                         fputs(keyword.c_str(), FP_SourceLevelDebugging);
  1346.                 }
  1347.                 fputs("\n", FP_SourceLevelDebugging);
  1348.         }
  1349. }
  1350.  
  1351. // will write directly into Options::SourceLevelDebugFName array
  1352. static void OpenSld_buildDefaultNameIfNeeded() {
  1353.         // check if SLD file name is already explicitly defined, or default is wanted
  1354.         if (Options::SourceLevelDebugFName[0] || !Options::IsDefaultSldName) return;
  1355.         // name is still empty, and default is wanted, create one (start with "out" or first source name)
  1356.         ConstructDefaultFilename(Options::SourceLevelDebugFName, LINEMAX, ".sld.txt", false);
  1357. }
  1358.  
  1359. // returns true only in the LASTPASS and only when "sld" file was specified by user
  1360. // and only when assembling is in "virtual DEVICE" mode (for "none" device no tracing is emitted)
  1361. bool IsSldExportActive() {
  1362.         return (nullptr != FP_SourceLevelDebugging && DeviceID);
  1363. }
  1364.  
  1365. void OpenSld() {
  1366.         // check if source-level-debug file is already opened
  1367.         if (nullptr != FP_SourceLevelDebugging) return;
  1368.         // build default filename if not explicitly provided, and default was requested
  1369.         OpenSld_buildDefaultNameIfNeeded();
  1370.         // try to open it if not opened yet
  1371.         OpenSldImp(Options::SourceLevelDebugFName);
  1372. }
  1373.  
  1374. void CloseSld() {
  1375.         if (!FP_SourceLevelDebugging) return;
  1376.         fclose(FP_SourceLevelDebugging);
  1377.         FP_SourceLevelDebugging = nullptr;
  1378. }
  1379.  
  1380. void WriteToSldFile(int pageNum, int value, char type, const char* symbol) {
  1381.         // SLD line format:
  1382.         // <file name>|<source line>|<definition file>|<definition line>|<page number>|<value>|<type>|<data>\n
  1383.         //
  1384.         // * string <file name> can't be empty (empty is for specific "control lines" with different format)
  1385.         //
  1386.         // * unsigned <source line> when <file name> is not empty, line number (in human way starting at 1)
  1387.         // The actual format is "%d[:%d[:%d]]", first number is always line. If second number is present,
  1388.         // that's the start column (in bytes), and if also third number is present, that's end column.
  1389.         //
  1390.         // * string <definition file> where the <definition line> was defined, if empty, it's equal to <file name>
  1391.         //
  1392.         // * unsigned <definition line> explicit zero value in regular source, but inside macros
  1393.         // the <source line> keeps pointing at line emitting the macro, while this value points
  1394.         // to source with actual definitions of instructions/etc (nested macro in macro <source line>
  1395.         // still points at the top level source which initiated it).
  1396.         // The format is again "%d[:%d[:%d]]" same as <source line>, optionally including the columns data.
  1397.         //
  1398.         // * int <value> is not truncated to page range, but full 16b Z80 address or even 32b value (equ)
  1399.         //
  1400.         // * string <data> content depends on char <type>:
  1401.         // 'T' = instruction Trace, empty data
  1402.         // 'D' = EQU symbol, <data> is the symbol name ("label")
  1403.         // 'F' = function label, <data> is the symbol name
  1404.         // 'Z' = device (memory model) changed, <data> has special custom formatting
  1405.         //
  1406.         // 'Z' device <data> format:
  1407.         // pages.size:<page size>,pages.count:<page count>,slots.count:<slots count>[,slots.adr:<slot0 adr>,...,<slotLast adr>]
  1408.         // unsigned <page size> is also any-slot size in current version.
  1409.         // unsigned <page count> and <slots count> define how many pages/slots there are
  1410.         // uint16_t <slotX adr> is starting address of slot memory region in Z80 16b addressing
  1411.         //
  1412.         // specific lines (<file name> string was empty):
  1413.         // |SLD.data.version|<version number>
  1414.         // <version number> is SLD file format version, currently should be 0
  1415.         // ||<anything till EOL>
  1416.         // comment line, not to be parsed
  1417.         if (nullptr == FP_SourceLevelDebugging || !type) return;
  1418.         if (nullptr == symbol) symbol = WriteToSld_noSymbol;
  1419.  
  1420.         assert(!sourcePosStack.empty());
  1421.         const bool outside_source = (sourcePosStack.size() <= size_t(IncludeLevel));
  1422.         const bool has_def_pos = !outside_source && (size_t(IncludeLevel + 1) < sourcePosStack.size());
  1423.         const TextFilePos & curPos = outside_source ? sourcePosStack.back() : sourcePosStack.at(IncludeLevel);
  1424.         const TextFilePos defPos = has_def_pos ? sourcePosStack.back() : TextFilePos();
  1425.  
  1426.         const char* macroFN = defPos.filename && strcmp(defPos.filename, curPos.filename) ? defPos.filename : "";
  1427.         WriteToSldFile_TextFilePos(sldMessage_sourcePos, curPos);
  1428.         WriteToSldFile_TextFilePos(sldMessage_definitionPos, defPos);
  1429.         snprintf(sldMessage, LINEMAX2, "%s|%s|%s|%s|%d|%d|%c|%s\n",
  1430.                                 curPos.filename, sldMessage_sourcePos, macroFN, sldMessage_definitionPos,
  1431.                                 pageNum, value, type, symbol);
  1432.         fputs(sldMessage, FP_SourceLevelDebugging);
  1433. }
  1434.  
  1435. void SldAddCommentKeyword(const char* keyword) {
  1436.         if (nullptr == keyword || !keyword[0]) {
  1437.                 if (LASTPASS == pass) Error("[SLDOPT COMMENT] invalid keyword", lp, SUPPRESS);
  1438.                 return;
  1439.         }
  1440.         if (1 == pass) {
  1441.                 auto begin = sldCommentKeywords.cbegin();
  1442.                 auto end = sldCommentKeywords.cend();
  1443.                 // add keyword only if it is new (not included yet)
  1444.                 if (std::find(begin, end, keyword) == end) sldCommentKeywords.push_back(keyword);
  1445.         }
  1446. }
  1447.  
  1448. void SldTrackComments() {
  1449.         assert(eolComment && IsSldExportActive());
  1450.         if (!eolComment[0]) return;
  1451.         for (auto keyword : sldCommentKeywords) {
  1452.                 if (strstr(eolComment, keyword.c_str())) {
  1453.                         int pageNum = Page->Number;
  1454.                         if (DISP_NONE != PseudoORG) {
  1455.                                 pageNum = LABEL_PAGE_UNDEFINED != dispPageNum ? dispPageNum : Device->GetPageOfA16(CurAddress);
  1456.                         }
  1457.                         WriteToSldFile(pageNum, CurAddress, 'K', eolComment);
  1458.                         return;
  1459.                 }
  1460.         }
  1461. }
  1462.  
  1463. /////// Breakpoints list (for different emulators)
  1464. static FILE* FP_BreakpointsFile = nullptr;
  1465. static EBreakpointsFile breakpointsType;
  1466. static int breakpointsCounter;
  1467.  
  1468. void OpenBreakpointsFile(const char* filename, const EBreakpointsFile type) {
  1469.         if (nullptr == filename || !filename[0]) {
  1470.                 Error("empty filename", filename, EARLY);
  1471.                 return;
  1472.         }
  1473.         if (FP_BreakpointsFile) {
  1474.                 Error("breakpoints file was already opened", nullptr, EARLY);
  1475.                 return;
  1476.         }
  1477.         if (!FOPEN_ISOK(FP_BreakpointsFile, filename, "w")) {
  1478.                 Error("opening file for write", filename, EARLY);
  1479.         }
  1480.         breakpointsCounter = 0;
  1481.         breakpointsType = type;
  1482. }
  1483.  
  1484. static void CloseBreakpointsFile() {
  1485.         if (!FP_BreakpointsFile) return;
  1486.         fclose(FP_BreakpointsFile);
  1487.         FP_BreakpointsFile = nullptr;
  1488. }
  1489.  
  1490. void WriteBreakpoint(const aint val) {
  1491.         if (!FP_BreakpointsFile) {
  1492.                 WarningById(W_BP_FILE);
  1493.                 return;
  1494.         }
  1495.         ++breakpointsCounter;
  1496.         switch (breakpointsType) {
  1497.                 case BPSF_UNREAL:
  1498.                         check16u(val);
  1499.                         fprintf(FP_BreakpointsFile, "x0=0x%04X\n", val&0xFFFF);
  1500.                         break;
  1501.                 case BPSF_ZESARUX:
  1502.                         if (1 == breakpointsCounter) fputs(" --enable-breakpoints ", FP_BreakpointsFile);
  1503.                         if (100 < breakpointsCounter) {
  1504.                                 Warning("Maximum amount of 100 breakpoints has been already reached, this one is ignored");
  1505.                                 break;
  1506.                         }
  1507.                         check16u(val);
  1508.                         fprintf(FP_BreakpointsFile, "--set-breakpoint %d \"PC=%d\" ", breakpointsCounter, val&0xFFFF);
  1509.                         break;
  1510.         }
  1511. }
  1512.  
  1513. //eof sjio.cpp
  1514.