• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

z00m128 / sjasmplus / 1452

01 Jan 2025 12:13PM UTC coverage: 96.269% (-0.01%) from 96.279%
1452

Pull #254

cirrus-ci

ped7g
c++17: replacing custom code with std::filesystem[::path] where feasible

Making parser Get[Output]FileName to use std::filesystem::path hopefully
everywhere.

This get still degraded to std::string/const char* when include paths
are involved (GetPath, ArchiveFilename, Open/Include anything), and some
writing functions are not refactored yet either.

But committing this as intermittent step just in case I will be unable
to develop this further soon or if I need rollback to working version.
Pull Request #254: migrating some of the file stuff to std::filesystem, in the naive hope of getting better cross-platform compatibility

223 of 244 new or added lines in 12 files covered. (91.39%)

2 existing lines in 2 files now uncovered.

9677 of 10052 relevant lines covered (96.27%)

168552.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.68
/sjasm/sjio.cpp
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) {
3,138✔
61
        for (auto fname : archivedFileNames) {                // search whole archive for identical full name
4,088✔
62
                if (!strcmp(fname, fullpathname)) return fname;
3,502✔
63
        }
64
        const char* newName = STRDUP(fullpathname);
586✔
65
        archivedFileNames.push_back(newName);
586✔
66
        return newName;
586✔
67
}
68

69
// does release all archived filenames, making all pointers (and archive itself) invalid
70
void ReleaseArchivedFilenames() {
508✔
71
        for (auto filename : archivedFileNames) free((void*)filename);
1,094✔
72
        archivedFileNames.clear();
508✔
73
}
508✔
74

75
static const char* FilenameBasePos(const char* fullname) {
38✔
76
        const char* const filenameEnd = fullname + strlen(fullname);
38✔
77
        const char* baseName = filenameEnd;
38✔
78
        while (fullname < baseName && '/' != baseName[-1] && '\\' != baseName[-1]) --baseName;
373✔
79
        return baseName;
38✔
80
}
81

82
// find position of extension in filename (points at dot char or beyond filename if no extension)
83
// filename is pointer to writeable format containing file name (can be full path) (NOT NULL)
84
// if initWithName and filenameBufferSize are explicitly provided, filename will be first overwritten with those
85
char* FilenameExtPos(char* filename, const char* initWithName, size_t initNameMaxLength) {
32✔
86
        // if the init value is provided with positive buffer size, init the buffer first
87
        if (0 < initNameMaxLength && initWithName) {
32✔
88
                STRCPY(filename, initNameMaxLength, initWithName);
16✔
89
        }
90
        // find start of the base filename
91
        const char* baseName = FilenameBasePos(filename);
32✔
92
        // find extension of the filename and return position of it
93
        char* const filenameEnd = filename + strlen(filename);
32✔
94
        char* extPos = filenameEnd;
32✔
95
        while (baseName < extPos && '.' != *extPos) --extPos;
139✔
96
        if (baseName < extPos) return extPos;
32✔
97
        // no extension found (empty filename, or "name", or ".name"), return end of filename
98
        return filenameEnd;
19✔
99
}
100

101
void ConstructDefaultFilename(std::filesystem::path & dest, const char* ext, bool checkIfDestIsEmpty) {
515✔
102
        if (nullptr == ext || !ext[0]) exit(1);        // invalid arguments
515✔
103
        // if the destination buffer has already some content and check is requested, exit
104
        if (checkIfDestIsEmpty && !dest.empty()) return;
515✔
105
        // construct the new default name - search for explicit name in sourcefiles
106
        dest = "asm";                // use "asm" base if no explicit filename available
513✔
107
        for (const SSource & src : sourceFiles) {
520✔
108
                if (!src.fname[0]) continue;
513✔
109
                dest = src.fname;
506✔
110
                break;
506✔
111
        }
112
        dest.replace_extension(ext);
513✔
113
}
114

115
void CheckRamLimitExceeded() {
618,600✔
116
        if (Options::IsLongPtr) return;                // in "longptr" mode with no device keep the address as is
618,600✔
117
        static bool notWarnedCurAdr = true;
118
        static bool notWarnedDisp = true;
119
        char buf[64];
120
        if (CurAddress >= 0x10000) {
618,513✔
121
                if (LASTPASS == pass && notWarnedCurAdr) {
76,435✔
122
                        SPRINTF2(buf, 64, "RAM limit exceeded 0x%X by %s",
22✔
123
                                         (unsigned int)CurAddress, DISP_NONE != PseudoORG ? "DISP":"ORG");
124
                        Warning(buf);
22✔
125
                        notWarnedCurAdr = false;
22✔
126
                }
127
                if (DISP_NONE != PseudoORG) CurAddress &= 0xFFFF;        // fake DISP address gets auto-wrapped FFFF->0
76,435✔
128
        } else notWarnedCurAdr = true;
542,078✔
129
        if (DISP_NONE != PseudoORG && adrdisp >= 0x10000) {
618,513✔
130
                if (LASTPASS == pass && notWarnedDisp) {
834✔
131
                        SPRINTF1(buf, 64, "RAM limit exceeded 0x%X by ORG", (unsigned int)adrdisp);
4✔
132
                        Warning(buf);
4✔
133
                        notWarnedDisp = false;
4✔
134
                }
135
        } else notWarnedDisp = true;
617,679✔
136
}
137

138
void resolveRelocationAndSmartSmc(const aint immediateOffset, Relocation::EType minType) {
33,028✔
139
        // call relocation data generator to do its own errands
140
        Relocation::resolveRelocationAffected(immediateOffset, minType);
33,028✔
141
        // check smart-SMC functionality, if there is unresolved record to be set up
142
        if (INT_MAX == immediateOffset || sourcePosStack.empty() || 0 == smartSmcIndex) return;
33,028✔
143
        if (smartSmcLines.size() < smartSmcIndex) return;
236✔
144
        auto & smartSmc = smartSmcLines.at(smartSmcIndex - 1);
234✔
145
        if (~0U != smartSmc.colBegin || smartSmc != sourcePosStack.back()) return;
234✔
146
        if (1 < sourcePosStack.back().colBegin) return;                // only first segment belongs to SMC label
214✔
147
        // record does match current line, resolve the smart offset
148
        smartSmc.colBegin = immediateOffset;
211✔
149
}
150

151
void WriteDest() {
1,243✔
152
        if (!WBLength) {
1,243✔
153
                return;
711✔
154
        }
155
        destlen += WBLength;
532✔
156
        if (FP_Output != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_Output) != WBLength) {
532✔
157
                Error("Write error (disk full?)", NULL, FATAL);
×
158
        }
159
        if (FP_RAW != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_RAW) != WBLength) {
532✔
160
                Error("Write error (disk full?)", NULL, FATAL);
×
161
        }
162

163
        if (FP_tapout)
532✔
164
        {
165
                int write_length = tape_length + WBLength > 65535 ? 65535 - tape_length : WBLength;
14✔
166

167
                if ( (aint)fwrite(WriteBuffer, 1, write_length, FP_tapout) != write_length) Error("Write error (disk full?)", NULL, FATAL);
14✔
168

169
                for (int i = 0; i < write_length; i++) tape_parity ^= WriteBuffer[i];
65,693✔
170
                tape_length += write_length;
14✔
171

172
                if (write_length < WBLength)
14✔
173
                {
174
                        WBLength = 0;
1✔
175
                        CloseTapFile();
1✔
176
                        Error("Tape block exceeds maximal size");
1✔
177
                }
178
        }
179
        WBLength = 0;
532✔
180
}
181

182
void PrintHex(char* & dest, aint value, int nibbles) {
82✔
183
        if (nibbles < 1 || 8 < nibbles) ExitASM(33);        // invalid argument
82✔
184
        const char oldChAfter = dest[nibbles];
82✔
185
        const aint mask = (int(sizeof(aint)*2) <= nibbles) ? ~0L : (1L<<(nibbles*4))-1L;
82✔
186
        if (nibbles != sprintf(dest, "%0*X", nibbles, value&mask)) ExitASM(33);
82✔
187
        dest += nibbles;
82✔
188
        *dest = oldChAfter;
82✔
189
}
82✔
190

191
void PrintHex32(char*& dest, aint value) {
73✔
192
        PrintHex(dest, value, 8);
73✔
193
}
73✔
194

195
void PrintHexAlt(char*& dest, aint value)
6,104✔
196
{
197
        char buffer[24] = { 0 }, * bp = buffer;
6,104✔
198
        sprintf(buffer, "%04X", value);
6,104✔
199
        while (*bp) *dest++ = *bp++;
38,223✔
200
}
6,104✔
201

202
static char pline[4*LINEMAX];
203

204
// buffer must be at least 4*LINEMAX chars long
205
void PrepareListLine(char* buffer, aint hexadd)
30,779✔
206
{
207
        ////////////////////////////////////////////////////
208
        // Line numbers to 1 to 99999 are supported only  //
209
        // For more lines, then first char is incremented //
210
        ////////////////////////////////////////////////////
211

212
        int digit = ' ';
30,779✔
213
        int linewidth = reglenwidth;
30,779✔
214
        uint32_t currentLine = sourcePosStack.at(IncludeLevel).line;
30,779✔
215
        aint linenumber = currentLine % 10000;
30,779✔
216
        if (5 <= linewidth) {                // five-digit number, calculate the leading "digit"
30,779✔
217
                linewidth = 5;
13✔
218
                digit = currentLine / 10000 + '0';
13✔
219
                if (digit > '~') digit = '~';
13✔
220
                if (currentLine >= 10000) linenumber += 10000;
13✔
221
        }
222
        memset(buffer, ' ', 24);
30,779✔
223
        if (listmacro) buffer[23] = '>';
30,779✔
224
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType) buffer[23] = '{';
30,779✔
225
        sprintf(buffer, "%*u", linewidth, linenumber); buffer[linewidth] = ' ';
30,779✔
226
        memcpy(buffer + linewidth, "++++++", IncludeLevel > 6 - linewidth ? 6 - linewidth : IncludeLevel);
30,779✔
227
        sprintf(buffer + 6, "%04X", hexadd & 0xFFFF); buffer[10] = ' ';
30,779✔
228
        if (digit > '0') *buffer = digit & 0xFF;
30,779✔
229
        // if substitutedLine is completely empty, list rather source line any way
230
        if (!*substitutedLine) substitutedLine = line;
30,779✔
231
        STRCPY(buffer + 24, LINEMAX2-24, substitutedLine);
30,779✔
232
        // add EOL comment if substituted was used and EOL comment is available
233
        if (substitutedLine != line && eolComment) STRCAT(buffer, LINEMAX2, eolComment);
30,779✔
234
}
30,779✔
235

236
static void ListFileStringRtrim() {
30,763✔
237
        // find end of currently prepared line
238
        char* beyondLine = pline+24;
30,763✔
239
        while (*beyondLine) ++beyondLine;
691,167✔
240
        // and remove trailing white space (space, tab, newline, carriage return, etc..)
241
        while (pline < beyondLine && White(beyondLine[-1])) --beyondLine;
87,254✔
242
        // set new line and new string terminator after
243
        *beyondLine++ = '\n';
30,763✔
244
        *beyondLine = 0;
30,763✔
245
}
30,763✔
246

247
// returns FILE* handle to either actual file defined by --lst=xxx, or stderr if --msg=lst, or NULL
248
// ! do not fclose this handle, for fclose logic use the FP_ListingFile variable itself !
249
FILE* GetListingFile() {
286,462✔
250
        if (NULL != FP_ListingFile) return FP_ListingFile;
286,462✔
251
        if (OV_LST == Options::OutputVerbosity) return stderr;
121,905✔
252
        return NULL;
119,898✔
253
}
254

255
static aint lastListedLine = -1;
256

257
void ListFile(bool showAsSkipped) {
737,866✔
258
        if (LASTPASS != pass || NULL == GetListingFile() || donotlist || Options::syx.IsListingSuspended) {
737,866✔
259
                donotlist = nListBytes = 0;
610,090✔
260
                return;
610,090✔
261
        }
262
        if (showAsSkipped && Options::LST_T_ACTIVE == Options::syx.ListingType) {
127,776✔
263
                assert(nListBytes <= 0);        // inactive line should not produce any machine code?!
5✔
264
                nListBytes = 0;
5✔
265
                return;                // filter out all "inactive" lines
5✔
266
        }
267
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType && nListBytes <= 0) {
127,771✔
268
                return;                // filter out all lines without machine-code bytes
100,012✔
269
        }
270
        int pos = 0;
27,759✔
271
        do {
272
                if (showAsSkipped) substitutedLine = line;        // override substituted lines in skipped mode
30,763✔
273
                PrepareListLine(pline, ListAddress);
30,763✔
274
                const bool hideSource = !showAsSkipped && (lastListedLine == CompiledCurrentLine);
30,763✔
275
                if (hideSource) pline[24] = 0;                                // hide *same* source line on sub-sequent list-lines
30,763✔
276
                lastListedLine = CompiledCurrentLine;                // remember this line as listed
30,763✔
277
                char* pp = pline + 10;
30,763✔
278
                int BtoList = (nListBytes < 4) ? nListBytes : 4;
30,763✔
279
                for (int i = 0; i < BtoList; ++i) {
64,946✔
280
                        if (-2 == ListEmittedBytes[i + pos]) pp += sprintf(pp, "...");
34,183✔
281
                        else pp += sprintf(pp, " %02X", ListEmittedBytes[i + pos]);
34,073✔
282
                }
283
                *pp = ' ';
30,763✔
284
                if (showAsSkipped) pline[11] = '~';
30,763✔
285
                ListFileStringRtrim();
30,763✔
286
                fputs(pline, GetListingFile());
30,763✔
287
                nListBytes -= BtoList;
30,763✔
288
                ListAddress += BtoList;
30,763✔
289
                pos += BtoList;
30,763✔
290
        } while (0 < nListBytes);
30,763✔
291
        nListBytes = 0;
27,759✔
292
        ListAddress = CurAddress;                        // move ListAddress also beyond unlisted but emitted bytes
27,759✔
293
}
294

295
void ListSilentOrExternalEmits() {
673,691✔
296
        // catch silent/external emits like "sj.add_byte(0x123)" from Lua script
297
        if (0 == nListBytes) return;                // no silent/external emit happened
673,691✔
298
        ++CompiledCurrentLine;
45✔
299
        char silentOrExternalBytes[] = "; these bytes were emitted silently/externally (lua script?)";
45✔
300
        substitutedLine = silentOrExternalBytes;
45✔
301
        eolComment = nullptr;
45✔
302
        ListFile();
45✔
303
        substitutedLine = line;
45✔
304
}
305

306
static bool someByteEmitted = false;
307

308
bool DidEmitByte() {        // returns true if some byte was emitted since last call to this function
3,084✔
309
        bool didEmit = someByteEmitted;                // value to return
3,084✔
310
        someByteEmitted = false;                        // reset the flag
3,084✔
311
        return didEmit;
3,084✔
312
}
313

314
static void EmitByteNoListing(int byte, bool preserveDeviceMemory = false) {
1,982,218✔
315
        someByteEmitted = true;
1,982,218✔
316
        if (LASTPASS == pass) {
1,982,218✔
317
                WriteBuffer[WBLength++] = (char)byte;
828,089✔
318
                if (DESTBUFLEN == WBLength) WriteDest();
828,089✔
319
        }
320
        // the page-checking in device mode must be done in all passes, the slot can have "wrap" option
321
        if (DeviceID) {
1,982,218✔
322
                Device->CheckPage(CDevice::CHECK_EMIT);
1,363,636✔
323
                if (MemoryPointer) {
1,363,636✔
324
                        if (LASTPASS == pass && !preserveDeviceMemory) *MemoryPointer = (char)byte;
1,363,172✔
325
                        ++MemoryPointer;
1,363,172✔
326
                }
327
        } else {
328
                CheckRamLimitExceeded();
618,582✔
329
        }
330
        ++CurAddress;
1,982,218✔
331
        if (DISP_NONE != PseudoORG) ++adrdisp;
1,982,218✔
332
}
1,982,218✔
333

334
static bool PageDiffersWarningShown = false;
335

336
void EmitByte(int byte, bool isInstructionStart) {
667,177✔
337
        if (isInstructionStart) {
667,177✔
338
                // SLD (Source Level Debugging) tracing-data logging
339
                if (IsSldExportActive()) {
115,332✔
340
                        int pageNum = Page->Number;
681✔
341
                        if (DISP_NONE != PseudoORG) {
681✔
342
                                int mappingPageNum = Device->GetPageOfA16(CurAddress);
5✔
343
                                if (LABEL_PAGE_UNDEFINED == dispPageNum) {        // special DISP page is not set, use mapped
5✔
344
                                        pageNum = mappingPageNum;
1✔
345
                                } else {
346
                                        pageNum = dispPageNum;                                        // special DISP page is set, use it instead
4✔
347
                                        if (pageNum != mappingPageNum && !PageDiffersWarningShown) {
4✔
348
                                                WarningById(W_DISP_MEM_PAGE);
1✔
349
                                                PageDiffersWarningShown = true;                // show warning about different mapping only once
1✔
350
                                        }
351
                                }
352
                        }
353
                        WriteToSldFile(pageNum, CurAddress);
681✔
354
                }
355
        }
356
        byte &= 0xFF;
667,177✔
357
        if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE-1) {
667,177✔
358
                ListEmittedBytes[nListBytes++] = byte;                // write also into listing
665,779✔
359
        } else {
360
                if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE) {
1,398✔
361
                        // too many bytes, show it in listing as "..."
362
                        ListEmittedBytes[nListBytes++] = -2;
3✔
363
                }
364
        }
365
        EmitByteNoListing(byte);
667,177✔
366
}
667,177✔
367

368
void EmitWord(int word, bool isInstructionStart) {
14,960✔
369
        EmitByte(word % 256, isInstructionStart);
14,960✔
370
        EmitByte(word / 256, false);
14,960✔
371
}
14,960✔
372

373
void EmitBytes(const int* bytes, bool isInstructionStart) {
174,083✔
374
        if (BYTES_END_MARKER == *bytes) {
174,083✔
375
                Error("Illegal instruction", line, IF_FIRST);
4,203✔
376
                SkipToEol(lp);
4,203✔
377
        }
378
        while (BYTES_END_MARKER != *bytes) {
590,802✔
379
                EmitByte(*bytes++, isInstructionStart);
416,719✔
380
                isInstructionStart = (INSTRUCTION_START_MARKER == *bytes);        // only true for first byte, or when marker
416,719✔
381
                if (isInstructionStart) ++bytes;
416,719✔
382
        }
383
}
174,083✔
384

385
void EmitWords(const int* words, bool isInstructionStart) {
4,585✔
386
        while (BYTES_END_MARKER != *words) {
14,522✔
387
                EmitWord(*words++, isInstructionStart);
9,937✔
388
                isInstructionStart = false;                // only true for first word
9,937✔
389
        }
390
}
4,585✔
391

392
void EmitBlock(aint byte, aint len, bool preserveDeviceMemory, int emitMaxToListing) {
2,020✔
393
        if (len <= 0) {
2,020✔
394
                const aint adrMask = Options::IsLongPtr ? ~0 : 0xFFFF;
36✔
395
                CurAddress = (CurAddress + len) & adrMask;
36✔
396
                if (DISP_NONE != PseudoORG) adrdisp = (adrdisp + len) & adrMask;
36✔
397
                if (DeviceID)        Device->CheckPage(CDevice::CHECK_NO_EMIT);
36✔
398
                else                        CheckRamLimitExceeded();
18✔
399
                return;
36✔
400
        }
401
        if (LIST_EMIT_BYTES_BUFFER_SIZE <= nListBytes + emitMaxToListing) {        // clamp emit to list buffer
1,984✔
402
                emitMaxToListing = LIST_EMIT_BYTES_BUFFER_SIZE - nListBytes;
×
403
        }
404
        while (len--) {
1,067,524✔
405
                int dVal = (preserveDeviceMemory && DeviceID && MemoryPointer) ? MemoryPointer[0] : byte;
1,065,540✔
406
                EmitByteNoListing(byte, preserveDeviceMemory);
1,065,540✔
407
                if (LASTPASS == pass && emitMaxToListing) {
1,065,540✔
408
                        // put "..." marker into listing if some more bytes are emitted after last listed
409
                        if ((0 == --emitMaxToListing) && len) ListEmittedBytes[nListBytes++] = -2;
2,062✔
410
                        else ListEmittedBytes[nListBytes++] = dVal&0xFF;
1,872✔
411
                }
412
        }
413
}
414

415
char* GetPath(const char* fname, char** filenamebegin, bool systemPathsBeforeCurrent)
3,022✔
416
{
417
        char fullFilePath[MAX_PATH] = { 0 };
3,022✔
418
        CStringsList* dir = Options::IncludeDirsList;        // include-paths to search
3,022✔
419
        // search current directory first (unless "systemPathsBeforeCurrent")
420
        if (!systemPathsBeforeCurrent) {
3,022✔
421
                // if found, just skip the `while (dir)` loop
422
                if (SJ_SearchPath(CurrentDirectory, fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) dir = nullptr;
2,960✔
423
                else fullFilePath[0] = 0;        // clear fullFilePath every time when not found
27✔
424
        }
425
        while (dir) {
3,094✔
426
                if (SJ_SearchPath(dir->string, fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) break;
122✔
427
                fullFilePath[0] = 0;        // clear fullFilePath every time when not found
72✔
428
                dir = dir->next;
72✔
429
        }
430
        // if the file was not found in the list, and current directory was not searched yet
431
        if (!fullFilePath[0] && systemPathsBeforeCurrent) {
3,022✔
432
                //and the current directory was not searched yet, do it now, set empty string if nothing
433
                if (!SJ_SearchPath(CurrentDirectory, fname, NULL, MAX_PATH, fullFilePath, filenamebegin)) {
21✔
434
                        fullFilePath[0] = 0;        // clear fullFilePath every time when not found
3✔
435
                }
436
        }
437
        if (!fullFilePath[0] && filenamebegin) {        // if still not found, reset also *filenamebegin
3,022✔
438
                *filenamebegin = fullFilePath;
6✔
439
        }
440
        // copy the result into new memory
441
        char* kip = STRDUP(fullFilePath);
3,022✔
442
        if (kip == NULL) ErrorOOM();
3,022✔
443
        // convert filenamebegin pointer into the copied string (from temporary buffer pointer)
444
        if (filenamebegin) *filenamebegin += (kip - fullFilePath);
3,022✔
445
        return kip;
3,022✔
446
}
447

448
// if offset is negative, it functions as "how many bytes from end of file"
449
// if length is negative, it functions as "how many bytes from end of file to not load"
450
void BinIncFile(const char* fname, aint offset, aint length, const bool systemPathsFirst) {
174✔
451
        // open the desired file
452
        FILE* bif;
453
        char* fullFilePath = GetPath(fname, nullptr, systemPathsFirst);
174✔
454
        if (!FOPEN_ISOK(bif, fullFilePath, "rb")) Error("opening file", fname);
174✔
455
        free(fullFilePath);
174✔
456

457
        // Get length of file
458
        int totlen = 0, advanceLength;
174✔
459
        if (bif && (fseek(bif, 0, SEEK_END) || (totlen = ftell(bif)) < 0)) Error("telling file length", fname, FATAL);
174✔
460

461
        // process arguments (extra features like negative offset/length or INT_MAX length)
462
        // negative offset means "from the end of file"
463
        if (offset < 0) offset += totlen;
174✔
464
        // negative length means "except that many from end of file"
465
        if (length < 0) length += totlen - offset;
174✔
466
        // default length INT_MAX is "till the end of file"
467
        if (INT_MAX == length) length = totlen - offset;
174✔
468
        // verbose output of final values (before validation may terminate assembler)
469
        if (LASTPASS == pass && Options::OutputVerbosity <= OV_ALL) {
174✔
470
                char diagnosticTxt[MAX_PATH];
471
                SPRINTF4(diagnosticTxt, MAX_PATH, "include data: name=%s (%d bytes) Offset=%d  Len=%d", fname, totlen, offset, length);
3✔
472
                _CERR diagnosticTxt _ENDL;
3✔
473
        }
474
        // validate the resulting [offset, length]
475
        if (offset < 0 || length < 0 || totlen < offset + length) {
174✔
476
                Error("file too short", fname);
12✔
477
                offset = std::clamp(offset, 0, totlen);
12✔
478
                length = std::clamp(length, 0, totlen - offset);
12✔
479
                assert((0 <= offset) && (offset + length <= totlen));
12✔
480
        }
481
        if (0 == length) {
174✔
482
                Warning("include data: requested to include no data (length=0)");
18✔
483
                if (bif) fclose(bif);
18✔
484
                return;
18✔
485
        }
486
        assert(nullptr != bif);                                // otherwise it was handled by 0 == length case above
156✔
487

488
        if (pass != LASTPASS) {
156✔
489
                while (length) {
246✔
490
                        advanceLength = length;                // maximum possible to advance in address space
142✔
491
                        if (DeviceID) {                                // particular device may adjust that to less
142✔
492
                                Device->CheckPage(CDevice::CHECK_EMIT);
60✔
493
                                if (MemoryPointer) {        // fill up current memory page if possible
60✔
494
                                        advanceLength = Page->RAM + Page->Size - MemoryPointer;
56✔
495
                                        if (length < advanceLength) advanceLength = length;
56✔
496
                                        MemoryPointer += advanceLength;                // also update it! Doh!
56✔
497
                                }
498
                        }
499
                        length -= advanceLength;
142✔
500
                        if (length <= 0 && 0 == advanceLength) Error("BinIncFile internal error", NULL, FATAL);
142✔
501
                        if (DISP_NONE != PseudoORG) adrdisp = adrdisp + advanceLength;
142✔
502
                        CurAddress = CurAddress + advanceLength;
142✔
503
                }
504
        } else {
505
                // Seek to the beginning of part to include
506
                if (fseek(bif, offset, SEEK_SET) || ftell(bif) != offset) {
52✔
507
                        Error("seeking in file to offset", fname, FATAL);
×
508
                }
509

510
                // Reading data from file
511
                char* data = new char[length + 1], * bp = data;
52✔
512
                if (NULL == data) ErrorOOM();
52✔
513
                size_t res = fread(bp, 1, length, bif);
52✔
514
                if (res != (size_t)length) Error("reading data from file failed", fname, FATAL);
52✔
515
                while (length--) EmitByteNoListing(*bp++);
249,553✔
516
                delete[] data;
52✔
517
        }
518
        fclose(bif);
156✔
519
}
520

521
static void OpenDefaultList(const char *fullpath);
522

523
static stdin_log_t::const_iterator stdin_read_it;
524
static stdin_log_t* stdin_log = nullptr;
525

526
void OpenFile(const char* nfilename, bool systemPathsBeforeCurrent, stdin_log_t* fStdinLog)
2,768✔
527
{
528
        if (++IncludeLevel > 20) {
2,768✔
529
                Error("Over 20 files nested", NULL, ALL);
6✔
530
                --IncludeLevel;
6✔
531
                return;
12✔
532
        }
533
        char* fullpath, * filenamebegin;
534
        if (!*nfilename && fStdinLog) {
2,762✔
535
                fullpath = STRDUP("<stdin>");
12✔
536
                filenamebegin = fullpath;
12✔
537
                FP_Input = stdin;
12✔
538
                stdin_log = fStdinLog;
12✔
539
                stdin_read_it = stdin_log->cbegin();        // reset read iterator (for 2nd+ pass)
12✔
540
        } else {
541
                fullpath = GetPath(nfilename, &filenamebegin, systemPathsBeforeCurrent);
2,750✔
542

543
                if (!FOPEN_ISOK(FP_Input, fullpath, "rb")) {
2,750✔
544
                        free(fullpath);
6✔
545
                        Error("opening file", nfilename, ALL);
6✔
546
                        --IncludeLevel;
6✔
547
                        return;
6✔
548
                }
549
        }
550

551
        const char* oFileNameFull = fileNameFull, * oCurrentDirectory = CurrentDirectory;
2,756✔
552

553
        // archive the filename (for referencing it in SLD tracing data or listing/errors)
554
        fileNameFull = ArchiveFilename(fullpath);        // get const pointer into archive
2,756✔
555
        sourcePosStack.emplace_back(Options::IsShowFullPath ? fileNameFull : FilenameBasePos(fileNameFull));
2,756✔
556
        //FIXME with paths it can be: sourcePosStack.emplace_back(Options::IsShowFullPath ? fileNameFull : fileNameFull.filename());
557
        //FIXME and remove FilenameBasePos
558

559
        // refresh pre-defined values related to file/include
560
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,756✔
561
        DefineTable.Replace("__FILE__", fileNameFull);
2,756✔
562
        if (0 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", fileNameFull);
2,756✔
563

564
        // open default listing file for each new source file (if default listing is ON)
565
        if (LASTPASS == pass && 0 == IncludeLevel && Options::IsDefaultListingName) {
2,756✔
566
                OpenDefaultList(fileNameFull);                        // explicit listing file is already opened
1✔
567
        }
568
        // show in listing file which file was opened
569
        FILE* listFile = GetListingFile();
2,756✔
570
        if (LASTPASS == pass && listFile) {
2,756✔
571
                fputs("# file opened: ", listFile);
460✔
572
                fputs(fileNameFull, listFile);
460✔
573
                fputs("\n", listFile);
460✔
574
        }
575

576
        *filenamebegin = 0;                                        // shorten fullpath to only-path string
2,756✔
577
        CurrentDirectory = fullpath;                // and use it as CurrentDirectory
2,756✔
578

579
        rlpbuf = rlpbuf_end = rlbuf;
2,756✔
580
        colonSubline = false;
2,756✔
581
        blockComment = 0;
2,756✔
582

583
        ReadBufLine();
2,756✔
584

585
        if (stdin != FP_Input) fclose(FP_Input);
2,747✔
586
        else {
587
                if (1 == pass) {
12✔
588
                        stdin_log->push_back(0);        // add extra zero terminator
4✔
589
                        clearerr(stdin);                        // reset EOF on the stdin for another round of input
4✔
590
                }
591
        }
592
        CurrentDirectory = oCurrentDirectory;
2,747✔
593
        free(fullpath);                                                // was used by CurrentDirectory till now
2,747✔
594
        fullpath = nullptr;
2,747✔
595

596
        // show in listing file which file was closed
597
        if (LASTPASS == pass && listFile) {
2,747✔
598
                fputs("# file closed: ", listFile);
460✔
599
                fputs(fileNameFull, listFile);
460✔
600
                fputs("\n", listFile);
460✔
601

602
                // close listing file (if "default" listing filename is used)
603
                if (FP_ListingFile && 0 == IncludeLevel && Options::IsDefaultListingName) {
460✔
604
                        if (Options::AddLabelListing) LabelTable.Dump();
1✔
605
                        fclose(FP_ListingFile);
1✔
606
                        FP_ListingFile = NULL;
1✔
607
                }
608
        }
609

610
        --IncludeLevel;
2,747✔
611

612
        maxlin = std::max(maxlin, sourcePosStack.back().line);
2,747✔
613
        sourcePosStack.pop_back();
2,747✔
614
        fileNameFull = oFileNameFull;
2,747✔
615

616
        // refresh pre-defined values related to file/include
617
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,747✔
618
        DefineTable.Replace("__FILE__", fileNameFull ? fileNameFull : "<none>");
2,747✔
619
        if (-1 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", "<none>");
2,747✔
620
}
621

622
void IncludeFile(const char* nfilename, bool systemPathsBeforeCurrent)
465✔
623
{
624
        auto oStdin_log = stdin_log;
465✔
625
        auto oStdin_read_it = stdin_read_it;
465✔
626
        FILE* oFP_Input = FP_Input;
465✔
627
        FP_Input = 0;
465✔
628

629
        char* pbuf = rlpbuf, * pbuf_end = rlpbuf_end, * buf = STRDUP(rlbuf);
465✔
630
        if (buf == NULL) ErrorOOM();
465✔
631
        bool oColonSubline = colonSubline;
465✔
632
        if (blockComment) Error("Internal error 'block comment'", NULL, FATAL);        // comment can't INCLUDE
465✔
633

634
        OpenFile(nfilename, systemPathsBeforeCurrent);
465✔
635

636
        colonSubline = oColonSubline;
465✔
637
        rlpbuf = pbuf, rlpbuf_end = pbuf_end;
465✔
638
        STRCPY(rlbuf, 8192, buf);
465✔
639
        free(buf);
465✔
640

641
        FP_Input = oFP_Input;
465✔
642
        stdin_log = oStdin_log;
465✔
643
        stdin_read_it = oStdin_read_it;
465✔
644
}
465✔
645

646
typedef struct {
647
        char        name[12];
648
        size_t        length;
649
        byte        marker[16];
650
} BOMmarkerDef;
651

652
const BOMmarkerDef UtfBomMarkers[] = {
653
        { { "UTF8" }, 3, { 0xEF, 0xBB, 0xBF } },
654
        { { "UTF32BE" }, 4, { 0, 0, 0xFE, 0xFF } },
655
        { { "UTF32LE" }, 4, { 0xFF, 0xFE, 0, 0 } },                // must be detected *BEFORE* UTF16LE
656
        { { "UTF16BE" }, 2, { 0xFE, 0xFF } },
657
        { { "UTF16LE" }, 2, { 0xFF, 0xFE } }
658
};
659

660
static bool ReadBufData() {
7,235,948✔
661
        // check here also if `line` buffer is not full
662
        if ((LINEMAX-2) <= (rlppos - line)) Error("Line too long", NULL, FATAL);
7,235,948✔
663
        // now check for read data
664
        if (rlpbuf < rlpbuf_end) return 1;                // some data still in buffer
7,235,948✔
665
        // check EOF on files in every pass, stdin only in first, following will starve the stdin_log
666
        if ((stdin != FP_Input || 1 == pass) && feof(FP_Input)) return 0;        // no more data in file
16,824✔
667
        // read next block of data
668
        rlpbuf = rlbuf;
3,799✔
669
        // handle STDIN file differently (pass1 = read it, pass2+ replay "log" variable)
670
        if (1 == pass || stdin != FP_Input) {        // ordinary file is re-read every pass normally
3,799✔
671
                rlpbuf_end = rlbuf + fread(rlbuf, 1, 4096, FP_Input);
3,759✔
672
                *rlpbuf_end = 0;                                        // add zero terminator after new block
3,759✔
673
        }
674
        if (stdin == FP_Input) {
3,799✔
675
                // store copy of stdin into stdin_log during pass 1
676
                if (1 == pass && rlpbuf < rlpbuf_end) {
44✔
677
                        stdin_log->insert(stdin_log->end(), rlpbuf, rlpbuf_end);
4✔
678
                }
679
                // replay the log in 2nd+ pass
680
                if (1 < pass) {
44✔
681
                        rlpbuf_end = rlpbuf;
40✔
682
                        long toCopy = std::min(8000L, (long)std::distance(stdin_read_it, stdin_log->cend()));
80✔
683
                        if (0 < toCopy) {
40✔
684
                                memcpy(rlbuf, &(*stdin_read_it), toCopy);
8✔
685
                                stdin_read_it += toCopy;
8✔
686
                                rlpbuf_end += toCopy;
8✔
687
                        }
688
                        *rlpbuf_end = 0;                                // add zero terminator after new block
40✔
689
                }
690
        }
691
        // check UTF BOM markers only at the beginning of the file (source line == 0)
692
        assert(!sourcePosStack.empty());
3,799✔
693
        if (sourcePosStack.back().line) {
3,799✔
694
                return (rlpbuf < rlpbuf_end);                // return true if some data were read
1,043✔
695
        }
696
        //UTF BOM markers detector
697
        for (const auto & bomMarkerData : UtfBomMarkers) {
16,526✔
698
                if (rlpbuf_end < (rlpbuf + bomMarkerData.length)) continue;        // not enough bytes in buffer
13,774✔
699
                if (memcmp(rlpbuf, bomMarkerData.marker, bomMarkerData.length)) continue;        // marker not found
13,774✔
700
                if (&bomMarkerData != UtfBomMarkers) {        // UTF8 is first in the array, other markers show error
7✔
701
                        Error("Invalid UTF encoding detected (only ASCII and UTF8 works)", bomMarkerData.name, FATAL);
4✔
702
                }
703
                rlpbuf += bomMarkerData.length;        // skip the UTF8 BOM marker
3✔
704
        }
705
        return (rlpbuf < rlpbuf_end);                        // return true if some data were read
2,752✔
706
}
707

708
void ReadBufLine(bool Parse, bool SplitByColon) {
21,032✔
709
        // if everything else fails (no data, not running, etc), return empty line
710
        *line = 0;
21,032✔
711
        bool IsLabel = true;
21,032✔
712
        // try to read through the buffer and produce new line from it
713
        while (IsRunning && ReadBufData()) {
237,800✔
714
                // start of new line (or fake "line" by colon)
715
                rlppos = line;
235,049✔
716
                substitutedLine = line;                // also reset "substituted" line to the raw new one
235,049✔
717
                eolComment = NULL;
235,049✔
718
                if (colonSubline) {                        // starting from colon (creating new fake "line")
235,049✔
719
                        colonSubline = false;        // (can't happen inside block comment)
10,854✔
720
                        *(rlppos++) = ' ';
10,854✔
721
                        IsLabel = false;
10,854✔
722
                } else {                                        // starting real new line
723
                        IsLabel = (0 == blockComment);
224,195✔
724
                }
725
                bool afterNonAlphaNum, afterNonAlphaNumNext = true;
235,049✔
726
                // copy data from read buffer into `line` buffer until EOL/colon is found
727
                while (
235,049✔
728
                                ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf &&        // split by EOL
6,510,824✔
729
                                // split by colon only on 2nd+ char && SplitByColon && not inside block comment
730
                                (blockComment || !SplitByColon || rlppos == line || ':' != *rlpbuf)) {
3,143,322✔
731
                        // copy the new character to new line
732
                        *rlppos = *rlpbuf++;
3,132,453✔
733
                        afterNonAlphaNum = afterNonAlphaNumNext;
3,132,453✔
734
                        afterNonAlphaNumNext = !isalnum((byte)*rlppos);
3,132,453✔
735
                        // handle EOL escaping, limited implementation, usage not recommended
736
                        if ('\\' == *rlppos && ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf))  {
3,132,453✔
737
                                char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
42✔
738
                                if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
42✔
739
                                sourcePosStack.back().nextSegment();        // mark last line in errors/etc
42✔
740
                                continue;                                                                // continue with chars from next line
42✔
741
                        }
42✔
742
                        // Block comments logic first (anything serious may happen only "outside" of block comment
743
                        if ('*' == *rlppos && ReadBufData() && '/' == *rlpbuf) {
3,132,411✔
744
                                if (0 < blockComment) --blockComment;        // block comment ends here, -1 from nesting
438✔
745
                                ++rlppos;        *rlppos++ = *rlpbuf++;                // copy the second char too
438✔
746
                                continue;
438✔
747
                        }
748
                        if ('/' == *rlppos && ReadBufData() && '*' == *rlpbuf) {
3,131,973✔
749
                                ++rlppos, ++blockComment;                                // block comment starts here, nest +1 more
435✔
750
                                *rlppos++ = *rlpbuf++;                                        // copy the second char too
435✔
751
                                continue;
435✔
752
                        }
753
                        if (blockComment) {                                                        // inside block comment just copy chars
3,131,538✔
754
                                ++rlppos;
5,574✔
755
                                continue;
5,574✔
756
                        }
757
                        // check if still in label area, if yes, copy the finishing colon as char (don't split by it)
758
                        if ((IsLabel = (IsLabel && islabchar(*rlppos)))) {
3,125,964✔
759
                                ++rlppos;                                        // label character
206,250✔
760
                                //SMC offset handling
761
                                if (ReadBufData() && '+' == *rlpbuf) {        // '+' after label, add it as SMC_offset syntax
206,250✔
762
                                        IsLabel = false;
483✔
763
                                        *rlppos++ = *rlpbuf++;
483✔
764
                                        if (ReadBufData() && (isdigit(byte(*rlpbuf)) || '*' == *rlpbuf)) *rlppos++ = *rlpbuf++;
483✔
765
                                }
766
                                if (ReadBufData() && ':' == *rlpbuf) {        // colon after label, add it
206,250✔
767
                                        *rlppos++ = *rlpbuf++;
13,290✔
768
                                        IsLabel = false;
13,290✔
769
                                }
770
                                continue;
206,250✔
771
                        }
772
                        // not in label any more, check for EOL comments ";" or "//"
773
                        if ((';' == *rlppos) || ('/' == *rlppos && ReadBufData() && '/' == *rlpbuf)) {
2,919,714✔
774
                                eolComment = rlppos;
77,729✔
775
                                ++rlppos;                                        // EOL comment ";"
77,729✔
776
                                while (ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf) *rlppos++ = *rlpbuf++;
2,379,075✔
777
                                continue;
77,729✔
778
                        }
779
                        // check for string literals - double/single quotes
780
                        if (afterNonAlphaNum && ('"' == *rlppos || '\'' == *rlppos)) {
2,841,985✔
781
                                const bool quotes = '"' == *rlppos;
14,001✔
782
                                int escaped = 0;
14,001✔
783
                                do {
784
                                        if (escaped) --escaped;
123,246✔
785
                                        ++rlppos;                                // previous char confirmed
123,246✔
786
                                        *rlppos = ReadBufData() ? *rlpbuf : 0;        // copy next char (if available)
123,246✔
787
                                        if (!*rlppos || '\r' == *rlppos || '\n' == *rlppos) *rlppos = 0;        // not valid
123,246✔
788
                                        else ++rlpbuf;                        // buffer char read (accepted)
123,165✔
789
                                        if (quotes && !escaped && '\\' == *rlppos) escaped = 2;        // escape sequence detected
123,246✔
790
                                } while (*rlppos && (escaped || (quotes ? '"' : '\'') != *rlppos));
123,246✔
791
                                if (*rlppos) ++rlppos;                // there should be ending "/' in line buffer, confirm it
14,001✔
792
                                continue;
14,001✔
793
                        }
14,001✔
794
                        // anything else just copy
795
                        ++rlppos;                                // previous char confirmed
2,827,984✔
796
                } // while "some char in buffer, and it's not line delimiter"
797
                // line interrupted somehow, may be correctly finished, check + finalize line and process it
798
                *rlppos = 0;
235,049✔
799
                // skip <EOL> char sequence in read buffer
800
                if (ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf)) {
235,049✔
801
                        char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
223,998✔
802
                        if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
223,998✔
803
                        // if this was very last <EOL> in file (on non-empty line), add one more fake empty line
804
                        if (!ReadBufData() && *line) *rlpbuf_end++ = '\n';        // to make listing files "as before"
223,998✔
805
                } else {
806
                        // advance over single colon if that was the reason to terminate line parsing
807
                        colonSubline = SplitByColon && ReadBufData() && (':' == *rlpbuf) && ++rlpbuf;
11,051✔
808
                }
809
                // do +1 for very first colon-segment only (rest is +1 due to artificial space at beginning)
810
                assert(!sourcePosStack.empty());
235,049✔
811
                size_t advanceColumns = colonSubline ? (0 == sourcePosStack.back().colEnd) + strlen(line) : 0;
235,049✔
812
                sourcePosStack.back().nextSegment(colonSubline, advanceColumns);
235,049✔
813
                // line is parsed and ready to be processed
814
                if (Parse)         ParseLine();        // processed here in loop
235,049✔
815
                else                 return;                        // processed externally
18,276✔
816
        } // while (IsRunning && ReadBufData())
817
}
818

819
static void OpenListImp(const std::filesystem::path & listFilename) {
471✔
820
        // if STDERR is configured to contain listing, disable other listing files
821
        if (OV_LST == Options::OutputVerbosity) return;
471✔
822
        if (listFilename.empty()) return;
471✔
823
        if (!FOPEN_ISOK(FP_ListingFile, listFilename, "w")) {
312✔
NEW
824
                Error("opening file for write", listFilename.string().c_str(), FATAL);
×
825
        }
826
}
827

828
void OpenList() {
505✔
829
        // if STDERR is configured to contain listing, disable other listing files
830
        if (OV_LST == Options::OutputVerbosity) return;
505✔
831
        // check if listing file is already opened, or it is set to "default" file names
832
        if (Options::IsDefaultListingName || NULL != FP_ListingFile) return;
471✔
833
        // Only explicit listing files are opened here
834
        OpenListImp(Options::ListingFName);
470✔
835
}
836

837
static void OpenDefaultList(const char *fullpath) {
1✔
838
        // if STDERR is configured to contain listing, disable other listing files
839
        if (OV_LST == Options::OutputVerbosity) return;
1✔
840
        // check if listing file is already opened, or it is set to explicit file name
841
        if (!Options::IsDefaultListingName || NULL != FP_ListingFile) return;
1✔
842
        if (NULL == fullpath || !*fullpath) return;                // no filename provided
1✔
843
        // Create default listing name, and try to open it
844
        char tempListName[LINEMAX+10];                // make sure there is enough room for new extension
845
        char* extPos = FilenameExtPos(tempListName, fullpath, LINEMAX);        // find extension position
1✔
846
        STRCPY(extPos, 5, ".lst");                        // overwrite it with ".lst"
1✔
847
        // list filename prepared, open it
848
        OpenListImp(tempListName);
1✔
849
}
850

851
void CloseDest() {
631✔
852
        // Flush buffer before any other operations
853
        WriteDest();
631✔
854
        // does main output file exist? (to close it)
855
        if (FP_Output == NULL) return;
631✔
856
        // pad to desired size (and check for exceed of it)
857
        if (size != -1L) {
116✔
858
                if (destlen > size) {
4✔
859
                        ErrorInt("File exceeds 'size' by", destlen - size);
1✔
860
                }
861
                memset(WriteBuffer, 0, DESTBUFLEN);
4✔
862
                while (destlen < size) {
7✔
863
                        WBLength = std::min(aint(DESTBUFLEN), size-destlen);
3✔
864
                        WriteDest();
3✔
865
                }
866
                size = -1L;
4✔
867
        }
868
        fclose(FP_Output);
116✔
869
        FP_Output = NULL;
116✔
870
}
871

872
void SeekDest(long offset, int method) {
5✔
873
        WriteDest();
5✔
874
        if (FP_Output != NULL && fseek(FP_Output, offset, method)) {
5✔
875
                Error("File seek error (FPOS)", NULL, FATAL);
×
876
        }
877
}
5✔
878

879
void NewDest(const std::filesystem::path & newfilename, int mode) {
116✔
880
        // close previous output file
881
        CloseDest();
116✔
882

883
        // and open new file (keep previous/default name, if no explicit was provided)
884
        if (!newfilename.empty()) Options::DestinationFName = newfilename;
116✔
885
        OpenDest(mode);
116✔
886
}
116✔
887

888
void OpenDest(int mode) {
613✔
889
        destlen = 0;
613✔
890
        if (mode != OUTPUT_TRUNCATE && !FileExists(Options::DestinationFName)) {
613✔
891
                mode = OUTPUT_TRUNCATE;
1✔
892
        }
893
        if (!Options::NoDestinationFile && !FOPEN_ISOK(FP_Output, Options::DestinationFName, mode == OUTPUT_TRUNCATE ? "wb" : "r+b")) {
613✔
NEW
894
                Error("opening file for write", Options::DestinationFName.string().c_str(), FATAL);
×
895
        }
896
        Options::NoDestinationFile = false;
613✔
897
        if (NULL == FP_RAW && "-" == Options::RAWFName) {
613✔
898
                FP_RAW = stdout;
1✔
899
                fflush(stdout);
1✔
900
                switchStdOutIntoBinaryMode();
1✔
901
        }
902
        if (FP_RAW == NULL && Options::RAWFName.has_filename() && !FOPEN_ISOK(FP_RAW, Options::RAWFName, "wb")) {
613✔
NEW
903
                Error("opening file for write", Options::RAWFName.string().c_str());
×
904
        }
905
        if (FP_Output != NULL && mode != OUTPUT_TRUNCATE) {
613✔
906
                if (fseek(FP_Output, 0, mode == OUTPUT_REWIND ? SEEK_SET : SEEK_END)) {
3✔
907
                        Error("File seek error (OUTPUT)", NULL, FATAL);
×
908
                }
909
        }
910
}
613✔
911

912
void CloseTapFile()
515✔
913
{
914
        char tap_data[2];
915

916
        WriteDest();
515✔
917
        if (FP_tapout == NULL) return;
515✔
918

919
        tap_data[0] = tape_parity & 0xFF;
7✔
920
        if (fwrite(tap_data, 1, 1, FP_tapout) != 1) Error("Write error (disk full?)", NULL, FATAL);
7✔
921

922
        if (fseek(FP_tapout, tape_seek, SEEK_SET)) Error("File seek end error in TAPOUT", NULL, FATAL);
7✔
923

924
        tap_data[0] =  tape_length     & 0xFF;
7✔
925
        tap_data[1] = (tape_length>>8) & 0xFF;
7✔
926
        if (fwrite(tap_data, 1, 2, FP_tapout) != 2) Error("Write error (disk full?)", NULL, FATAL);
7✔
927

928
        fclose(FP_tapout);
7✔
929
        FP_tapout = NULL;
7✔
930
}
931

932
void OpenTapFile(const std::filesystem::path & tapename, int flagbyte)
9✔
933
{
934
        CloseTapFile();
9✔
935

936
        if (!FOPEN_ISOK(FP_tapout,tapename, "r+b")) {
9✔
937
                Error( "opening file for write", tapename.string().c_str());
2✔
938
                return;
2✔
939
        }
940
        if (fseek(FP_tapout, 0, SEEK_END)) Error("File seek end error in TAPOUT", tapename.string().c_str(), FATAL);
7✔
941

942
        tape_seek = ftell(FP_tapout);
7✔
943
        tape_parity = flagbyte;
7✔
944
        tape_length = 2;
7✔
945

946
        byte tap_data[3] = { 0, 0, (byte)flagbyte };
7✔
947

948
        if (sizeof(tap_data) != fwrite(tap_data, 1, sizeof(tap_data), FP_tapout)) {
7✔
949
                fclose(FP_tapout);
×
950
                Error("Write error (disk full?)", NULL, FATAL);
×
951
        }
952
}
953

954
bool FileExists(const std::filesystem::path & file_name) {
20✔
955
        FILE* test;
956
        bool exists = FOPEN_ISOK(test, file_name, "r") && (0 == fclose(test));
20✔
957
        return exists;
20✔
958
}
959

960
bool FileExistsCstr(const char* file_name) {
17✔
961
        if (nullptr == file_name) return false;
17✔
962
        return FileExists(std::filesystem::path(file_name));
16✔
963
}
964

965
void Close() {
497✔
966
        if (*ModuleName) {
497✔
967
                Warning("ENDMODULE missing for module", ModuleName, W_ALL);
2✔
968
        }
969

970
        CloseDest();
497✔
971
        CloseTapFile();
497✔
972
        if (FP_ExportFile != NULL) {
497✔
973
                fclose(FP_ExportFile);
5✔
974
                FP_ExportFile = NULL;
5✔
975
        }
976
        if (FP_RAW != NULL) {
497✔
977
                if (stdout != FP_RAW) fclose(FP_RAW);
4✔
978
                FP_RAW = NULL;
4✔
979
        }
980
        if (FP_ListingFile != NULL) {
497✔
981
                fclose(FP_ListingFile);
303✔
982
                FP_ListingFile = NULL;
303✔
983
        }
984
        CloseSld();
497✔
985
        CloseBreakpointsFile();
497✔
986
}
497✔
987

988
int SaveRAM(FILE* ff, int start, int length) {
202✔
989
        //unsigned int addadr = 0,save = 0;
990
        aint save = 0;
202✔
991
        if (!DeviceID) return 0;                // unreachable currently
202✔
992
        if (length + start > 0x10000) {
202✔
993
                length = -1;
1✔
994
        }
995
        if (length <= 0) {
202✔
996
                length = 0x10000 - start;
2✔
997
        }
998

999
        CDeviceSlot* S;
1000
        for (int i=0;i<Device->SlotsCount;i++) {
466✔
1001
                S = Device->GetSlot(i);
466✔
1002
                if (start >= (int)S->Address  && start < (int)(S->Address + S->Size)) {
466✔
1003
                        if (length < (int)(S->Size - (start - S->Address))) {
214✔
1004
                                save = length;
189✔
1005
                        } else {
1006
                                save = S->Size - (start - S->Address);
25✔
1007
                        }
1008
                        if ((aint) fwrite(S->Page->RAM + (start - S->Address), 1, save, ff) != save) {
214✔
1009
                                return 0;
×
1010
                        }
1011
                        length -= save;
214✔
1012
                        start += save;
214✔
1013
                        if (length <= 0) {
214✔
1014
                                return 1;
202✔
1015
                        }
1016
                }
1017
        }
1018
        return 0;                // unreachable (with current devices)
×
1019
}
1020

1021
unsigned int MemGetWord(unsigned int address) {
10✔
1022
        return MemGetByte(address) + (MemGetByte(address+1)<<8);
10✔
1023
}
1024

1025
unsigned char MemGetByte(unsigned int address) {
22,304✔
1026
        if (!DeviceID || pass != LASTPASS) {
22,304✔
1027
                return 0;
14,864✔
1028
        }
1029

1030
        CDeviceSlot* S;
1031
        for (int i=0;i<Device->SlotsCount;i++) {
8,974✔
1032
                S = Device->GetSlot(i);
8,971✔
1033
                if (address >= (unsigned int)S->Address  && address < (unsigned int)S->Address + (unsigned int)S->Size) {
8,971✔
1034
                        return S->Page->RAM[address - S->Address];
7,437✔
1035
                }
1036
        }
1037

1038
        ErrorInt("MemGetByte: Error reading address", address);
3✔
1039
        return 0;
3✔
1040
}
1041

1042

1043
int SaveBinary(const char* fname, aint start, aint length) {
17✔
1044
        FILE* ff;
1045
        if (!FOPEN_ISOK(ff, fname, "wb")) {
17✔
1046
                Error("opening file for write", fname, FATAL);
×
1047
        }
1048
        int result = SaveRAM(ff, start, length);
17✔
1049
        fclose(ff);
17✔
1050
        return result;
17✔
1051
}
1052

1053

1054
int SaveBinary3dos(const char* fname, aint start, aint length, byte type, word w2, word w3) {
6✔
1055
        FILE* ff;
1056
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname, FATAL);
6✔
1057
        // prepare +3DOS 128 byte header content
1058
        constexpr aint hsize = 128;
6✔
1059
        const aint full_length = hsize + length;
6✔
1060
        byte sum = 0, p3dos_header[hsize] { "PLUS3DOS\032\001" };
6✔
1061
        p3dos_header[11] = byte(full_length>>0);
6✔
1062
        p3dos_header[12] = byte(full_length>>8);
6✔
1063
        p3dos_header[13] = byte(full_length>>16);
6✔
1064
        p3dos_header[14] = byte(full_length>>24);
6✔
1065
        // +3 BASIC 8 byte header filled with "relevant values"
1066
        p3dos_header[15+0] = type;
6✔
1067
        p3dos_header[15+1] = byte(length>>0);
6✔
1068
        p3dos_header[15+2] = byte(length>>8);
6✔
1069
        p3dos_header[15+3] = byte(w2>>0);
6✔
1070
        p3dos_header[15+4] = byte(w2>>8);
6✔
1071
        p3dos_header[15+5] = byte(w3>>0);
6✔
1072
        p3dos_header[15+6] = byte(w3>>8);
6✔
1073
        // calculat checksum of the header
1074
        for (const byte v : p3dos_header) sum += v;
774✔
1075
        p3dos_header[hsize-1] = sum;
6✔
1076
        // write header and data
1077
        int result = (hsize == (aint) fwrite(p3dos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1078
        fclose(ff);
6✔
1079
        return result;
6✔
1080
}
1081

1082

1083
int SaveBinaryAmsdos(const char* fname, aint start, aint length, word start_adr, byte type) {
7✔
1084
        FILE* ff;
1085
        if (!FOPEN_ISOK(ff, fname, "wb")) {
7✔
1086
                Error("opening file for write", fname, SUPPRESS);
1✔
1087
                return 0;
1✔
1088
        }
1089
        // prepare AMSDOS 128 byte header content
1090
        constexpr aint hsize = 128;
6✔
1091
        byte amsdos_header[hsize] {};        // all zeroed (user_number and filename stay like that, just zeroes)
6✔
1092
        amsdos_header[0x12] = type;
6✔
1093
        amsdos_header[0x15] = byte(start>>0);
6✔
1094
        amsdos_header[0x16] = byte(start>>8);
6✔
1095
        amsdos_header[0x18] = amsdos_header[0x40] = byte(length>>0);
6✔
1096
        amsdos_header[0x19] = amsdos_header[0x41] = byte(length>>8);
6✔
1097
        amsdos_header[0x1A] = byte(start_adr>>0);
6✔
1098
        amsdos_header[0x1B] = byte(start_adr>>8);
6✔
1099
        // calculat checksum of the header
1100
        word sum = 0;
6✔
1101
        for (int ii = 0x43; ii--; ) sum += amsdos_header[ii];
408✔
1102
        amsdos_header[0x43] = byte(sum>>0);
6✔
1103
        amsdos_header[0x44] = byte(sum>>8);
6✔
1104
        // write header and data
1105
        int result = (hsize == (aint) fwrite(amsdos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1106
        fclose(ff);
6✔
1107
        return result;
6✔
1108
}
1109

1110

1111
// all arguments must be sanitized by caller (this just writes data block into opened file)
1112
bool SaveDeviceMemory(FILE* file, const size_t start, const size_t length) {
10✔
1113
        return (length == fwrite(Device->Memory + start, 1, length, file));
10✔
1114
}
1115

1116

1117
// start and length must be sanitized by caller
1118
bool SaveDeviceMemory(const char* fname, const size_t start, const size_t length) {
10✔
1119
        FILE* ff;
1120
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname, FATAL);
10✔
1121
        bool res = SaveDeviceMemory(ff, start, length);
10✔
1122
        fclose(ff);
10✔
1123
        return res;
10✔
1124
}
1125

1126

1127
int SaveHobeta(const char* fname, const char* fhobname, aint start, aint length) {
1✔
1128
        unsigned char header[0x11];
1129
        int i;
1130

1131
        if (length + start > 0x10000) {
1✔
1132
                length = -1;
×
1133
        }
1134
        if (length <= 0) {
1✔
1135
                length = 0x10000 - start;
×
1136
        }
1137

1138
        memset(header,' ',9);
1✔
1139
        i = strlen(fhobname);
1✔
1140
        if (i > 1)
1✔
1141
        {
1142
                const char *ext = strrchr(fhobname, '.');
1✔
1143
                if (ext && ext[1])
1✔
1144
                {
1145
                        header[8] = ext[1];
1✔
1146
                        i = ext-fhobname;
1✔
1147
                }
1148
        }
1149
        memcpy(header, fhobname, std::min(i,8));
1✔
1150

1151
        if (header[8] == 'B')        {
1✔
1152
                header[0x09] = (unsigned char)(length & 0xff);
×
1153
                header[0x0a] = (unsigned char)(length >> 8);
×
1154
        } else        {
1155
                header[0x09] = (unsigned char)(start & 0xff);
1✔
1156
                header[0x0a] = (unsigned char)(start >> 8);
1✔
1157
        }
1158

1159
        header[0x0b] = (unsigned char)(length & 0xff);
1✔
1160
        header[0x0c] = (unsigned char)(length >> 8);
1✔
1161
        header[0x0d] = 0;
1✔
1162
        if (header[0x0b] == 0) {
1✔
1163
                header[0x0e] = header[0x0c];
×
1164
        } else {
1165
                header[0x0e] = header[0x0c] + 1;
1✔
1166
        }
1167
        length = header[0x0e] * 0x100;
1✔
1168
        int chk = 0;
1✔
1169
        for (i = 0; i <= 14; chk = chk + (header[i] * 257) + i,i++) {
16✔
1170
                ;
1171
        }
1172
        header[0x0f] = (unsigned char)(chk & 0xff);
1✔
1173
        header[0x10] = (unsigned char)(chk >> 8);
1✔
1174

1175
        FILE* ff;
1176
        if (!FOPEN_ISOK(ff, fname, "wb")) {
1✔
1177
                Error("opening file for write", fname, FATAL);
×
1178
        }
1179

1180
        int result = (17 == fwrite(header, 1, 17, ff)) && SaveRAM(ff, start, length);
1✔
1181
        fclose(ff);
1✔
1182
        return result;
1✔
1183
}
1184

1185
EReturn ReadFile() {
20,402✔
1186
        while (ReadLine()) {
42,403✔
1187
                const bool isInsideDupCollectingLines = !RepeatStack.empty() && !RepeatStack.top().IsInWork;
42,397✔
1188
                if (!isInsideDupCollectingLines) {
42,397✔
1189
                        // check for ending of IF/IFN/... block (keywords: ENDIF, ELSE and ELSEIF)
1190
                        char* p = line;
42,157✔
1191
                        SkipBlanks(p);
42,157✔
1192
                        if ('.' == *p) ++p;
42,157✔
1193
                        EReturn retVal = END;
42,157✔
1194
                        if (cmphstr(p, "elseif")) retVal = ELSEIF;
42,157✔
1195
                        if (cmphstr(p, "else")) retVal = ELSE;
42,157✔
1196
                        if (cmphstr(p, "endif")) retVal = ENDIF;
42,157✔
1197
                        if (END != retVal) {
42,157✔
1198
                                // one of the end-block keywords was found, don't parse it as regular line
1199
                                // but just substitute the rest of it and return end value of the keyword
1200
                                ++CompiledCurrentLine;
20,396✔
1201
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
20,396✔
1202
                                substitutedLine = line;                // for listing override substituted line with source
20,396✔
1203
                                if (ENDIF != retVal) ListFile();        // do the listing for ELSE and ELSEIF
20,396✔
1204
                                return retVal;
20,396✔
1205
                        }
1206
                }
1207
                ParseLineSafe();
22,001✔
1208
        }
1209
        return END;
6✔
1210
}
1211

1212

1213
EReturn SkipFile() {
23,382✔
1214
        int iflevel = 0;
23,382✔
1215
        while (ReadLine()) {
52,728✔
1216
                char* p = line;
52,725✔
1217
                if (isLabelStart(p) && !Options::syx.IsPseudoOpBOF) {
52,725✔
1218
                        // this could be label, skip it (the --dirbol users can't use label + IF/... inside block)
1219
                        while (islabchar(*p)) ++p;
4,670✔
1220
                        if (':' == *p) ++p;
1,043✔
1221
                }
1222
                SkipBlanks(p);
52,725✔
1223
                if ('.' == *p) ++p;
52,725✔
1224
                if (cmphstr(p, "if") || cmphstr(p, "ifn") || cmphstr(p, "ifused") ||
105,246✔
1225
                        cmphstr(p, "ifnused") || cmphstr(p, "ifdef") || cmphstr(p, "ifndef")) {
105,246✔
1226
                        ++iflevel;
240✔
1227
                } else if (cmphstr(p, "endif")) {
52,485✔
1228
                        if (iflevel) {
5,543✔
1229
                                --iflevel;
240✔
1230
                        } else {
1231
                                ++CompiledCurrentLine;
5,303✔
1232
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
5,303✔
1233
                                substitutedLine = line;                // override substituted listing for ENDIF
5,303✔
1234
                                return ENDIF;
23,379✔
1235
                        }
1236
                } else if (cmphstr(p, "else")) {
46,942✔
1237
                        if (!iflevel) {
18,181✔
1238
                                ++CompiledCurrentLine;
17,959✔
1239
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
17,959✔
1240
                                substitutedLine = line;                // override substituted listing for ELSE
17,959✔
1241
                                ListFile();
17,959✔
1242
                                return ELSE;
17,959✔
1243
                        }
1244
                } else if (cmphstr(p, "elseif")) {
28,761✔
1245
                        if (!iflevel) {
189✔
1246
                                ++CompiledCurrentLine;
117✔
1247
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
117✔
1248
                                substitutedLine = line;                // override substituted listing for ELSEIF
117✔
1249
                                ListFile();
117✔
1250
                                return ELSEIF;
117✔
1251
                        }
1252
                } else if (cmphstr(p, "lua")) {                // lua script block detected, skip it whole
28,572✔
1253
                        // with extra custom while loop, to avoid confusion by `if/...` inside lua scripts
1254
                        ListFile(true);
84✔
1255
                        while (ReadLine()) {
189✔
1256
                                p = line;
189✔
1257
                                SkipBlanks(p);
189✔
1258
                                if (cmphstr(p, "endlua")) break;
189✔
1259
                                ListFile(true);
105✔
1260
                        }
1261
                }
1262
                ListFile(true);
29,346✔
1263
        }
1264
        return END;
3✔
1265
}
1266

1267
int ReadLineNoMacro(bool SplitByColon) {
18,294✔
1268
        if (!IsRunning || !ReadBufData()) return 0;
18,294✔
1269
        ReadBufLine(false, SplitByColon);
18,276✔
1270
        return 1;
18,276✔
1271
}
1272

1273
int ReadLine(bool SplitByColon) {
101,233✔
1274
        if (IsRunning && lijst) {                // read MACRO lines, if macro is being emitted
101,233✔
1275
                if (!lijstp || !lijstp->string) return 0;
86,368✔
1276
                assert(!sourcePosStack.empty());
86,368✔
1277
                sourcePosStack.back() = lijstp->source;
86,368✔
1278
                STRCPY(line, LINEMAX, lijstp->string);
86,368✔
1279
                substitutedLine = line;                // reset substituted listing
86,368✔
1280
                eolComment = NULL;                        // reset end of line comment
86,368✔
1281
                lijstp = lijstp->next;
86,368✔
1282
                return 1;
86,368✔
1283
        }
1284
        return ReadLineNoMacro(SplitByColon);
14,865✔
1285
}
1286

1287
int ReadFileToCStringsList(CStringsList*& f, const char* end) {
480✔
1288
        // f itself should be already NULL, not resetting it here
1289
        CStringsList** s = &f;
480✔
1290
        while (ReadLineNoMacro()) {
3,429✔
1291
                ++CompiledCurrentLine;
3,426✔
1292
                char* p = line;
3,426✔
1293
                SkipBlanks(p);
3,426✔
1294
                if ('.' == *p) ++p;
3,426✔
1295
                if (cmphstr(p, end)) {                // finished, read rest after end marker into line buffers
3,426✔
1296
                        lp = ReplaceDefine(p);
477✔
1297
                        return 1;
477✔
1298
                }
1299
                *s = new CStringsList(line);
2,949✔
1300
                s = &((*s)->next);
2,949✔
1301
                ListFile(true);
2,949✔
1302
        }
1303
        return 0;
3✔
1304
}
1305

1306
void OpenExpFile() {
497✔
1307
        assert(nullptr == FP_ExportFile);                        // this should be the first and only call to open it
497✔
1308
        if (!Options::ExportFName.has_filename()) return;        // no export file name provided, skip opening
497✔
1309
        if (FOPEN_ISOK(FP_ExportFile, Options::ExportFName, "w")) return;
6✔
1310
        Error("opening file for write", Options::ExportFName.string().c_str(), ALL);
1✔
1311
}
1312

1313
void WriteLabelEquValue(const char* name, aint value, FILE* f) {
73✔
1314
        if (nullptr == f) return;
73✔
1315
        char lnrs[16],* l = lnrs;
73✔
1316
        STRCPY(temp, LINEMAX-2, name);
73✔
1317
        STRCAT(temp, LINEMAX-1, ": EQU ");
73✔
1318
        STRCAT(temp, LINEMAX-1, "0x");
73✔
1319
        PrintHex32(l, value); *l = 0;
73✔
1320
        STRCAT(temp, LINEMAX-1, lnrs);
73✔
1321
        STRCAT(temp, LINEMAX-1, "\n");
73✔
1322
        fputs(temp, f);
73✔
1323
}
1324

1325
void WriteExp(const char* n, aint v) {
12✔
1326
        WriteLabelEquValue(n, v, FP_ExportFile);
12✔
1327
}
12✔
1328

1329
/////// source-level-debugging support by Ckirby
1330

1331
static FILE* FP_SourceLevelDebugging = NULL;
1332
static char sldMessage[LINEMAX2];
1333
static const char* WriteToSld_noSymbol = "";
1334
static char sldMessage_sourcePos[1024];
1335
static char sldMessage_definitionPos[1024];
1336
static const char* sldMessage_posFormat = "%d:%d:%d";        // at +3 is "%d:%d" and at +6 is "%d"
1337
static std::vector<std::string> sldCommentKeywords;
1338

1339
static void WriteToSldFile_TextFilePos(char* buffer, const TextFilePos & pos) {
2,214✔
1340
        int offsetFormat = !pos.colBegin ? 6 : !pos.colEnd ? 3 : 0;
2,214✔
1341
        snprintf(buffer, 1024-1, sldMessage_posFormat + offsetFormat, pos.line, pos.colBegin, pos.colEnd);
2,214✔
1342
}
2,214✔
1343

1344
static void OpenSldImp(const std::filesystem::path & sldFilename) {
497✔
1345
        if (!sldFilename.has_filename()) return;
497✔
1346
        if (!FOPEN_ISOK(FP_SourceLevelDebugging, sldFilename, "w")) {
16✔
NEW
1347
                Error("opening file for write", sldFilename.string().c_str(), FATAL);
×
1348
        }
1349
        fputs("|SLD.data.version|1\n", FP_SourceLevelDebugging);
16✔
1350
        if (0 < sldCommentKeywords.size()) {
16✔
1351
                fputs("||K|KEYWORDS|", FP_SourceLevelDebugging);
2✔
1352
                bool notFirst = false;
2✔
1353
                for (auto keyword : sldCommentKeywords) {
6✔
1354
                        if (notFirst) fputs(",", FP_SourceLevelDebugging);
4✔
1355
                        notFirst = true;
4✔
1356
                        fputs(keyword.c_str(), FP_SourceLevelDebugging);
4✔
1357
                }
4✔
1358
                fputs("\n", FP_SourceLevelDebugging);
2✔
1359
        }
1360
}
1361

1362
// will write result directly into Options::SourceLevelDebugFName
1363
static void OpenSld_buildDefaultNameIfNeeded() {
497✔
1364
        // check if SLD file name is already explicitly defined, or default is wanted
1365
        if (Options::SourceLevelDebugFName.has_filename() || !Options::IsDefaultSldName) return;
497✔
1366
        // name is still empty, and default is wanted, create one (start with "out" or first source name)
1367
        ConstructDefaultFilename(Options::SourceLevelDebugFName, ".sld.txt", false);
3✔
1368
}
1369

1370
// returns true only in the LASTPASS and only when "sld" file was specified by user
1371
// and only when assembling is in "virtual DEVICE" mode (for "none" device no tracing is emitted)
1372
bool IsSldExportActive() {
203,665✔
1373
        return (nullptr != FP_SourceLevelDebugging && DeviceID);
203,665✔
1374
}
1375

1376
void OpenSld() {
497✔
1377
        // check if source-level-debug file is already opened
1378
        if (nullptr != FP_SourceLevelDebugging) return;
497✔
1379
        // build default filename if not explicitly provided, and default was requested
1380
        OpenSld_buildDefaultNameIfNeeded();
497✔
1381
        // try to open it if not opened yet
1382
        OpenSldImp(Options::SourceLevelDebugFName);
497✔
1383
}
1384

1385
void CloseSld() {
497✔
1386
        if (!FP_SourceLevelDebugging) return;
497✔
1387
        fclose(FP_SourceLevelDebugging);
16✔
1388
        FP_SourceLevelDebugging = nullptr;
16✔
1389
}
1390

1391
void WriteToSldFile(int pageNum, int value, char type, const char* symbol) {
1,107✔
1392
        // SLD line format:
1393
        // <file name>|<source line>|<definition file>|<definition line>|<page number>|<value>|<type>|<data>\n
1394
        //
1395
        // * string <file name> can't be empty (empty is for specific "control lines" with different format)
1396
        //
1397
        // * unsigned <source line> when <file name> is not empty, line number (in human way starting at 1)
1398
        // The actual format is "%d[:%d[:%d]]", first number is always line. If second number is present,
1399
        // that's the start column (in bytes), and if also third number is present, that's end column.
1400
        //
1401
        // * string <definition file> where the <definition line> was defined, if empty, it's equal to <file name>
1402
        //
1403
        // * unsigned <definition line> explicit zero value in regular source, but inside macros
1404
        // the <source line> keeps pointing at line emitting the macro, while this value points
1405
        // to source with actual definitions of instructions/etc (nested macro in macro <source line>
1406
        // still points at the top level source which initiated it).
1407
        // The format is again "%d[:%d[:%d]]" same as <source line>, optionally including the columns data.
1408
        //
1409
        // * int <value> is not truncated to page range, but full 16b Z80 address or even 32b value (equ)
1410
        //
1411
        // * string <data> content depends on char <type>:
1412
        // 'T' = instruction Trace, empty data
1413
        // 'D' = EQU symbol, <data> is the symbol name ("label")
1414
        // 'F' = function label, <data> is the symbol name
1415
        // 'Z' = device (memory model) changed, <data> has special custom formatting
1416
        //
1417
        // 'Z' device <data> format:
1418
        // pages.size:<page size>,pages.count:<page count>,slots.count:<slots count>[,slots.adr:<slot0 adr>,...,<slotLast adr>]
1419
        // unsigned <page size> is also any-slot size in current version.
1420
        // unsigned <page count> and <slots count> define how many pages/slots there are
1421
        // uint16_t <slotX adr> is starting address of slot memory region in Z80 16b addressing
1422
        //
1423
        // specific lines (<file name> string was empty):
1424
        // |SLD.data.version|<version number>
1425
        // <version number> is SLD file format version, currently should be 0
1426
        // ||<anything till EOL>
1427
        // comment line, not to be parsed
1428
        if (nullptr == FP_SourceLevelDebugging || !type) return;
1,107✔
1429
        if (nullptr == symbol) symbol = WriteToSld_noSymbol;
1,107✔
1430

1431
        assert(!sourcePosStack.empty());
1,107✔
1432
        const bool outside_source = (sourcePosStack.size() <= size_t(IncludeLevel));
1,107✔
1433
        const bool has_def_pos = !outside_source && (size_t(IncludeLevel + 1) < sourcePosStack.size());
1,107✔
1434
        const TextFilePos & curPos = outside_source ? sourcePosStack.back() : sourcePosStack.at(IncludeLevel);
1,107✔
1435
        const TextFilePos defPos = has_def_pos ? sourcePosStack.back() : TextFilePos();
1,107✔
1436

1437
        const char* macroFN = defPos.filename && strcmp(defPos.filename, curPos.filename) ? defPos.filename : "";
1,107✔
1438
        WriteToSldFile_TextFilePos(sldMessage_sourcePos, curPos);
1,107✔
1439
        WriteToSldFile_TextFilePos(sldMessage_definitionPos, defPos);
1,107✔
1440
        snprintf(sldMessage, LINEMAX2, "%s|%s|%s|%s|%d|%d|%c|%s\n",
1,107✔
1441
                                curPos.filename, sldMessage_sourcePos, macroFN, sldMessage_definitionPos,
1,107✔
1442
                                pageNum, value, type, symbol);
1443
        fputs(sldMessage, FP_SourceLevelDebugging);
1,107✔
1444
}
1445

1446
void SldAddCommentKeyword(const char* keyword) {
21✔
1447
        if (nullptr == keyword || !keyword[0]) {
21✔
1448
                if (LASTPASS == pass) Error("[SLDOPT COMMENT] invalid keyword", lp, SUPPRESS);
6✔
1449
                return;
6✔
1450
        }
1451
        if (1 == pass) {
15✔
1452
                auto begin = sldCommentKeywords.cbegin();
5✔
1453
                auto end = sldCommentKeywords.cend();
5✔
1454
                // add keyword only if it is new (not included yet)
1455
                if (std::find(begin, end, keyword) == end) sldCommentKeywords.push_back(keyword);
13✔
1456
        }
1457
}
1458

1459
void SldTrackComments() {
288✔
1460
        assert(eolComment && IsSldExportActive());
288✔
1461
        if (!eolComment[0]) return;
288✔
1462
        for (auto keyword : sldCommentKeywords) {
337✔
1463
                if (strstr(eolComment, keyword.c_str())) {
67✔
1464
                        int pageNum = Page->Number;
18✔
1465
                        if (DISP_NONE != PseudoORG) {
18✔
1466
                                pageNum = LABEL_PAGE_UNDEFINED != dispPageNum ? dispPageNum : Device->GetPageOfA16(CurAddress);
1✔
1467
                        }
1468
                        WriteToSldFile(pageNum, CurAddress, 'K', eolComment);
18✔
1469
                        return;
18✔
1470
                }
1471
        }
67✔
1472
}
1473

1474
/////// Breakpoints list (for different emulators)
1475
static FILE* FP_BreakpointsFile = nullptr;
1476
static EBreakpointsFile breakpointsType;
1477
static int breakpointsCounter;
1478

1479
void OpenBreakpointsFile(const char* filename, const EBreakpointsFile type) {
10✔
1480
        if (nullptr == filename || !filename[0]) {
10✔
1481
                Error("empty filename", filename, EARLY);
3✔
1482
                return;
3✔
1483
        }
1484
        if (FP_BreakpointsFile) {
7✔
1485
                Error("breakpoints file was already opened", nullptr, EARLY);
2✔
1486
                return;
2✔
1487
        }
1488
        if (!FOPEN_ISOK(FP_BreakpointsFile, filename, "w")) {
5✔
1489
                Error("opening file for write", filename, EARLY);
1✔
1490
        }
1491
        breakpointsCounter = 0;
5✔
1492
        breakpointsType = type;
5✔
1493
}
1494

1495
static void CloseBreakpointsFile() {
497✔
1496
        if (!FP_BreakpointsFile) return;
497✔
1497
        fclose(FP_BreakpointsFile);
4✔
1498
        FP_BreakpointsFile = nullptr;
4✔
1499
}
1500

1501
void WriteBreakpoint(const aint val) {
120✔
1502
        if (!FP_BreakpointsFile) {
120✔
1503
                WarningById(W_BP_FILE);
8✔
1504
                return;
8✔
1505
        }
1506
        ++breakpointsCounter;
112✔
1507
        switch (breakpointsType) {
112✔
1508
                case BPSF_UNREAL:
5✔
1509
                        check16u(val);
5✔
1510
                        fprintf(FP_BreakpointsFile, "x0=0x%04X\n", val&0xFFFF);
5✔
1511
                        break;
5✔
1512
                case BPSF_ZESARUX:
107✔
1513
                        if (1 == breakpointsCounter) fputs(" --enable-breakpoints ", FP_BreakpointsFile);
107✔
1514
                        if (100 < breakpointsCounter) {
107✔
1515
                                Warning("Maximum amount of 100 breakpoints has been already reached, this one is ignored");
1✔
1516
                                break;
1✔
1517
                        }
1518
                        check16u(val);
106✔
1519
                        fprintf(FP_BreakpointsFile, "--set-breakpoint %d \"PC=%d\" ", breakpointsCounter, val&0xFFFF);
106✔
1520
                        break;
106✔
1521
        }
1522
}
1523

1524
//eof sjio.cpp
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc