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

z00m128 / sjasmplus / 1460

21 Jan 2025 03:16AM UTC coverage: 96.3% (-0.2%) from 96.454%
1460

push

cirrus-ci

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

Adding GetInputFileName using std::filesystem::path and making it work
for main OpenFile and IncludeFile.

TODO:
- convert other input file situations like INCBIN, etc..
- get rid of obsolete functions later

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.
(and to verify all CI platforms to pass tests)

100 of 105 new or added lines in 6 files covered. (95.24%)

12 existing lines in 2 files now uncovered.

9631 of 10001 relevant lines covered (96.3%)

169323.02 hits per line

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

97.38
/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
static const std::filesystem::path EMPTY_PATH{""};
36

37
int ListAddress;
38
std::vector<const char*> archivedFileNames;        // archive of filename strings (Lua scripts only?)
39

40
static constexpr int LIST_EMIT_BYTES_BUFFER_SIZE = 1024 * 64;
41
static constexpr int DESTBUFLEN = 8192;
42

43
// ReadLine buffer and variables around
44
static char rlbuf[LINEMAX2 * 2];
45
static char * rlpbuf, * rlpbuf_end, * rlppos;
46
static bool colonSubline;
47
static int blockComment;
48

49
static int ListEmittedBytes[LIST_EMIT_BYTES_BUFFER_SIZE], nListBytes = 0;
50
static char WriteBuffer[DESTBUFLEN];
51
static int tape_seek = 0;
52
static int tape_length = 0;
53
static int tape_parity = 0x55;
54
static FILE* FP_tapout = NULL;
55
static FILE* FP_Input = NULL, * FP_Output = NULL, * FP_RAW = NULL;
56
static FILE* FP_ListingFile = NULL,* FP_ExportFile = NULL;
57
static aint WBLength = 0;
58

59
static void CloseBreakpointsFile();
60

61
// convert backslash and report them with warning
62
static void FixFileBackslashes(delim_string_t & str) {
3,988✔
63
        if (std::string::npos == str.first.find('\\')) return;
3,988✔
64
        WarningById(W_BACKSLASH, str.first.c_str());
13✔
65
        std::replace(str.first.begin(), str.first.end(), '\\', '/');
13✔
66
}
67

68
//FIXME prune these functions after everything will be refactored to GetInput/OutputFileName
69
static EDelimiterType delimiterOfLastFileName = DT_NONE;
70

71
// do: remember delimiter for GetDelimiterOfLastFileName, convert backslashes, prepend prefix
72
std::filesystem::path GetFileName(delim_string_t & str_name, const std::filesystem::path & pathPrefix) {
1,197✔
73
        FixFileBackslashes(str_name);
1,197✔
74
        delimiterOfLastFileName = str_name.second;        // remember delimiter for GetDelimiterOfLastFileName
1,197✔
75
        // return prefixed path (or just path if prefix is empty, "/" is not applied then)
76
        return pathPrefix / str_name.first;
1,197✔
77
}
78

79
std::filesystem::path GetFileName(char*& p, const std::filesystem::path & pathPrefix) {
1,197✔
80
        auto str_name = GetDelimitedStringEx(p);
1,197✔
81
        return GetFileName(str_name, pathPrefix);
2,394✔
82
}
1,197✔
83

84
std::filesystem::path GetOutputFileName(char*& p) {
930✔
85
        return GetFileName(p, Options::OutPrefix);
930✔
86
}
87

88
EDelimiterType GetDelimiterOfLastFileName() {
231✔
89
        // DT_NONE if no GetFileName was called
90
        return delimiterOfLastFileName;
231✔
91
}
92

93
//FIXME duplicate from support - get rid of SJ_SearchPath, GetPath, ... (whole chain)
94
static bool isAnySlash(const char c) {
2✔
95
        return pathGoodSlash == c || pathBadSlash == c;
2✔
96
}
97

98
//FIXME duplicate from support - get rid of SJ_SearchPath, GetPath, ... (whole chain)
99
static bool isWindowsDrivePathStart(const char* filePath) {
582✔
100
        if (!filePath || !filePath[0] || ':' != filePath[1]) return false;
582✔
101
        const char driveLetter = toupper(filePath[0]);
2✔
102
        if (driveLetter < 'A' || 'Z' < driveLetter) return false;
2✔
103
        if (!isAnySlash(filePath[2])) {
2✔
104
                Warning("Relative file path with drive letter detected (not supported)", filePath, W_EARLY);
1✔
105
        }
106
        return true;
2✔
107
}
108

109
fullpath_ref_t GetInputFile(delim_string_t && in) {
2,791✔
110

111
        static files_in_map_t archivedInputFiles;        // archive of all input files opened so far
2,791✔
112

113
        FixFileBackslashes(in);
2,791✔
114
        // check if already archived, just return full path
115
        const auto lb = archivedInputFiles.lower_bound(in);
2,791✔
116
        if (archivedInputFiles.cend() != lb && lb->first == in) return lb->second;
2,791✔
117

118
        // !!! any warnings after this point must be W_EARLY, 2nd+ pass does use archived path = no warning !!!
119

120
        // not archived yet, look for the file somewhere...
121
        const std::filesystem::path name_in{ in.first };
601✔
122
        // completely empty -> special value to signal stdin
123
        if (name_in.empty()) {                // use special SInputFile constructor to have fake name "<stdin>"
601✔
124
                return archivedInputFiles.emplace_hint(lb, std::move(in), 1)->second;
5✔
125
        }
126
        // no filename - return it as is (it's not valid for open)
127
        if (!name_in.has_filename()) {
596✔
NEW
128
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(name_in))->second;
×
129
        }
130
        // absolute path or windows drive letter oddities - use it as is (even if not valid)
131
        if (name_in.is_absolute() || isWindowsDrivePathStart(in.first.c_str())) {
596✔
132
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(name_in))->second;
16✔
133
        }
134
        // search include paths depending on delimiter and filename - first try current dir (except for "<name>")
135
        const auto current_dir_file = CurrentDirectory / name_in;
580✔
136
        if (DT_ANGLE != in.second) {
580✔
137
                // force this as result for DT_COUNT delimiter (CLI argument filename => no searching)
138
                if (DT_COUNT == in.second || FileExists(current_dir_file)) {        // or if the file exists
561✔
139
                        return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(current_dir_file))->second;
558✔
140
                }
141
        }
142
        // search all include paths now
143
        CStringsList* dir = Options::IncludeDirsList;        // include-paths to search
22✔
144
        while (dir) {
41✔
145
                const auto dir_file = dir->string / name_in;
33✔
146
                if (FileExists(dir_file)) {
33✔
147
                        return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(dir_file))->second;
14✔
148
                }
149
                dir = dir->next;
19✔
150
        }
33✔
151
        // still not found, check current directory for system-paths-first case
152
        if (DT_ANGLE == in.second && FileExists(current_dir_file)) {
8✔
153
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(current_dir_file))->second;
6✔
154
        }
155
        // not found, return it "as is"
156
        return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(name_in))->second;
2✔
157
}
601✔
158

159
fullpath_ref_t GetInputFile(char*& p) {
488✔
160
        auto name_in = GetDelimitedStringEx(p);
488✔
161
        return GetInputFile(std::move(name_in));
976✔
162
}
488✔
163

164
// returns permanent C-string pointer to the fullpathname (if new, it is added to archive)
165
const char* ArchiveFilename(const char* fullpathname) {
368✔
166
        for (auto fname : archivedFileNames) {                // search whole archive for identical full name
383✔
167
                if (!strcmp(fname, fullpathname)) return fname;
370✔
168
        }
169
        archivedFileNames.push_back(STRDUP(fullpathname));
13✔
170
        return archivedFileNames.back();
13✔
171
}
172

173
// does release all archived filenames, making all pointers (and archive itself) invalid
174
void ReleaseArchivedFilenames() {
508✔
175
        for (auto filename : archivedFileNames) free((void*)filename);
521✔
176
        archivedFileNames.clear();
508✔
177
}
508✔
178

179
static const char* FilenameBasePos(const char* fullname) {
32✔
180
        const char* const filenameEnd = fullname + strlen(fullname);
32✔
181
        const char* baseName = filenameEnd;
32✔
182
        while (fullname < baseName && '/' != baseName[-1] && '\\' != baseName[-1]) --baseName;
214✔
183
        return baseName;
32✔
184
}
185

186
// find position of extension in filename (points at dot char or beyond filename if no extension)
187
// filename is pointer to writeable format containing file name (can be full path) (NOT NULL)
188
// if initWithName and filenameBufferSize are explicitly provided, filename will be first overwritten with those
189
char* FilenameExtPos(char* filename, const char* initWithName, size_t initNameMaxLength) {
32✔
190
        // if the init value is provided with positive buffer size, init the buffer first
191
        if (0 < initNameMaxLength && initWithName) {
32✔
192
                STRCPY(filename, initNameMaxLength, initWithName);
16✔
193
        }
194
        // find start of the base filename
195
        const char* baseName = FilenameBasePos(filename);
32✔
196
        // find extension of the filename and return position of it
197
        char* const filenameEnd = filename + strlen(filename);
32✔
198
        char* extPos = filenameEnd;
32✔
199
        while (baseName < extPos && '.' != *extPos) --extPos;
139✔
200
        if (baseName < extPos) return extPos;
32✔
201
        // no extension found (empty filename, or "name", or ".name"), return end of filename
202
        return filenameEnd;
19✔
203
}
204

205
void ConstructDefaultFilename(std::filesystem::path & dest, const char* ext, bool checkIfDestIsEmpty) {
515✔
206
        if (nullptr == ext || !ext[0]) exit(1);        // invalid arguments
515✔
207
        // if the destination buffer has already some content and check is requested, exit
208
        if (checkIfDestIsEmpty && !dest.empty()) return;
515✔
209
        // construct the new default name - search for explicit name in sourcefiles
210
        dest = "asm";                // use "asm" base if no explicit filename available
513✔
211
        for (const SSource & src : sourceFiles) {
520✔
212
                if (!src.fname[0]) continue;
513✔
213
                dest = src.fname;
506✔
214
                break;
506✔
215
        }
216
        dest.replace_extension(ext);
513✔
217
}
218

219
void CheckRamLimitExceeded() {
618,600✔
220
        if (Options::IsLongPtr) return;                // in "longptr" mode with no device keep the address as is
618,600✔
221
        static bool notWarnedCurAdr = true;
222
        static bool notWarnedDisp = true;
223
        char buf[64];
224
        if (CurAddress >= 0x10000) {
618,513✔
225
                if (LASTPASS == pass && notWarnedCurAdr) {
76,435✔
226
                        SPRINTF2(buf, 64, "RAM limit exceeded 0x%X by %s",
22✔
227
                                         (unsigned int)CurAddress, DISP_NONE != PseudoORG ? "DISP":"ORG");
228
                        Warning(buf);
22✔
229
                        notWarnedCurAdr = false;
22✔
230
                }
231
                if (DISP_NONE != PseudoORG) CurAddress &= 0xFFFF;        // fake DISP address gets auto-wrapped FFFF->0
76,435✔
232
        } else notWarnedCurAdr = true;
542,078✔
233
        if (DISP_NONE != PseudoORG && adrdisp >= 0x10000) {
618,513✔
234
                if (LASTPASS == pass && notWarnedDisp) {
834✔
235
                        SPRINTF1(buf, 64, "RAM limit exceeded 0x%X by ORG", (unsigned int)adrdisp);
4✔
236
                        Warning(buf);
4✔
237
                        notWarnedDisp = false;
4✔
238
                }
239
        } else notWarnedDisp = true;
617,679✔
240
}
241

242
void resolveRelocationAndSmartSmc(const aint immediateOffset, Relocation::EType minType) {
33,028✔
243
        // call relocation data generator to do its own errands
244
        Relocation::resolveRelocationAffected(immediateOffset, minType);
33,028✔
245
        // check smart-SMC functionality, if there is unresolved record to be set up
246
        if (INT_MAX == immediateOffset || sourcePosStack.empty() || 0 == smartSmcIndex) return;
33,028✔
247
        if (smartSmcLines.size() < smartSmcIndex) return;
236✔
248
        auto & smartSmc = smartSmcLines.at(smartSmcIndex - 1);
234✔
249
        if (~0U != smartSmc.colBegin || smartSmc != sourcePosStack.back()) return;
234✔
250
        if (1 < sourcePosStack.back().colBegin) return;                // only first segment belongs to SMC label
214✔
251
        // record does match current line, resolve the smart offset
252
        smartSmc.colBegin = immediateOffset;
211✔
253
}
254

255
void WriteDest() {
1,243✔
256
        if (!WBLength) {
1,243✔
257
                return;
711✔
258
        }
259
        destlen += WBLength;
532✔
260
        if (FP_Output != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_Output) != WBLength) {
532✔
261
                Error("Write error (disk full?)", NULL, FATAL);
×
262
        }
263
        if (FP_RAW != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_RAW) != WBLength) {
532✔
264
                Error("Write error (disk full?)", NULL, FATAL);
×
265
        }
266

267
        if (FP_tapout)
532✔
268
        {
269
                int write_length = tape_length + WBLength > 65535 ? 65535 - tape_length : WBLength;
14✔
270

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

273
                for (int i = 0; i < write_length; i++) tape_parity ^= WriteBuffer[i];
65,693✔
274
                tape_length += write_length;
14✔
275

276
                if (write_length < WBLength)
14✔
277
                {
278
                        WBLength = 0;
1✔
279
                        CloseTapFile();
1✔
280
                        Error("Tape block exceeds maximal size");
1✔
281
                }
282
        }
283
        WBLength = 0;
532✔
284
}
285

286
void PrintHex(char* & dest, aint value, int nibbles) {
82✔
287
        if (nibbles < 1 || 8 < nibbles) ExitASM(33);        // invalid argument
82✔
288
        const char oldChAfter = dest[nibbles];
82✔
289
        const aint mask = (int(sizeof(aint)*2) <= nibbles) ? ~0L : (1L<<(nibbles*4))-1L;
82✔
290
        if (nibbles != sprintf(dest, "%0*X", nibbles, value&mask)) ExitASM(33);
82✔
291
        dest += nibbles;
82✔
292
        *dest = oldChAfter;
82✔
293
}
82✔
294

295
void PrintHex32(char*& dest, aint value) {
73✔
296
        PrintHex(dest, value, 8);
73✔
297
}
73✔
298

299
void PrintHexAlt(char*& dest, aint value)
6,104✔
300
{
301
        char buffer[24] = { 0 }, * bp = buffer;
6,104✔
302
        sprintf(buffer, "%04X", value);
6,104✔
303
        while (*bp) *dest++ = *bp++;
38,223✔
304
}
6,104✔
305

306
static char pline[4*LINEMAX];
307

308
// buffer must be at least 4*LINEMAX chars long
309
void PrepareListLine(char* buffer, aint hexadd)
30,787✔
310
{
311
        ////////////////////////////////////////////////////
312
        // Line numbers to 1 to 99999 are supported only  //
313
        // For more lines, then first char is incremented //
314
        ////////////////////////////////////////////////////
315

316
        int digit = ' ';
30,787✔
317
        int linewidth = reglenwidth;
30,787✔
318
        uint32_t currentLine = sourcePosStack.at(IncludeLevel).line;
30,787✔
319
        aint linenumber = currentLine % 10000;
30,787✔
320
        if (5 <= linewidth) {                // five-digit number, calculate the leading "digit"
30,787✔
321
                linewidth = 5;
13✔
322
                digit = currentLine / 10000 + '0';
13✔
323
                if (digit > '~') digit = '~';
13✔
324
                if (currentLine >= 10000) linenumber += 10000;
13✔
325
        }
326
        memset(buffer, ' ', 24);
30,787✔
327
        if (listmacro) buffer[23] = '>';
30,787✔
328
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType) buffer[23] = '{';
30,787✔
329
        sprintf(buffer, "%*u", linewidth, linenumber); buffer[linewidth] = ' ';
30,787✔
330
        memcpy(buffer + linewidth, "++++++", IncludeLevel > 6 - linewidth ? 6 - linewidth : IncludeLevel);
30,787✔
331
        sprintf(buffer + 6, "%04X", hexadd & 0xFFFF); buffer[10] = ' ';
30,787✔
332
        if (digit > '0') *buffer = digit & 0xFF;
30,787✔
333
        // if substitutedLine is completely empty, list rather source line any way
334
        if (!*substitutedLine) substitutedLine = line;
30,787✔
335
        STRCPY(buffer + 24, LINEMAX2-24, substitutedLine);
30,787✔
336
        // add EOL comment if substituted was used and EOL comment is available
337
        if (substitutedLine != line && eolComment) STRCAT(buffer, LINEMAX2, eolComment);
30,787✔
338
}
30,787✔
339

340
static void ListFileStringRtrim() {
30,771✔
341
        // find end of currently prepared line
342
        char* beyondLine = pline+24;
30,771✔
343
        while (*beyondLine) ++beyondLine;
691,673✔
344
        // and remove trailing white space (space, tab, newline, carriage return, etc..)
345
        while (pline < beyondLine && White(beyondLine[-1])) --beyondLine;
87,276✔
346
        // set new line and new string terminator after
347
        *beyondLine++ = '\n';
30,771✔
348
        *beyondLine = 0;
30,771✔
349
}
30,771✔
350

351
// returns FILE* handle to either actual file defined by --lst=xxx, or stderr if --msg=lst, or NULL
352
// ! do not fclose this handle, for fclose logic use the FP_ListingFile variable itself !
353
FILE* GetListingFile() {
286,565✔
354
        if (NULL != FP_ListingFile) return FP_ListingFile;
286,565✔
355
        if (OV_LST == Options::OutputVerbosity) return stderr;
121,985✔
356
        return NULL;
119,979✔
357
}
358

359
static aint lastListedLine = -1;
360

361
void ListFile(bool showAsSkipped) {
738,130✔
362
        if (LASTPASS != pass || NULL == GetListingFile() || donotlist || Options::syx.IsListingSuspended) {
738,130✔
363
                donotlist = nListBytes = 0;
610,346✔
364
                return;
610,346✔
365
        }
366
        if (showAsSkipped && Options::LST_T_ACTIVE == Options::syx.ListingType) {
127,784✔
367
                assert(nListBytes <= 0);        // inactive line should not produce any machine code?!
5✔
368
                nListBytes = 0;
5✔
369
                return;                // filter out all "inactive" lines
5✔
370
        }
371
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType && nListBytes <= 0) {
127,779✔
372
                return;                // filter out all lines without machine-code bytes
100,012✔
373
        }
374
        int pos = 0;
27,767✔
375
        do {
376
                if (showAsSkipped) substitutedLine = line;        // override substituted lines in skipped mode
30,771✔
377
                PrepareListLine(pline, ListAddress);
30,771✔
378
                const bool hideSource = !showAsSkipped && (lastListedLine == CompiledCurrentLine);
30,771✔
379
                if (hideSource) pline[24] = 0;                                // hide *same* source line on sub-sequent list-lines
30,771✔
380
                lastListedLine = CompiledCurrentLine;                // remember this line as listed
30,771✔
381
                char* pp = pline + 10;
30,771✔
382
                int BtoList = (nListBytes < 4) ? nListBytes : 4;
30,771✔
383
                for (int i = 0; i < BtoList; ++i) {
64,954✔
384
                        if (-2 == ListEmittedBytes[i + pos]) pp += sprintf(pp, "...");
34,183✔
385
                        else pp += sprintf(pp, " %02X", ListEmittedBytes[i + pos]);
34,073✔
386
                }
387
                *pp = ' ';
30,771✔
388
                if (showAsSkipped) pline[11] = '~';
30,771✔
389
                ListFileStringRtrim();
30,771✔
390
                fputs(pline, GetListingFile());
30,771✔
391
                nListBytes -= BtoList;
30,771✔
392
                ListAddress += BtoList;
30,771✔
393
                pos += BtoList;
30,771✔
394
        } while (0 < nListBytes);
30,771✔
395
        nListBytes = 0;
27,767✔
396
        ListAddress = CurAddress;                        // move ListAddress also beyond unlisted but emitted bytes
27,767✔
397
}
398

399
void ListSilentOrExternalEmits() {
673,880✔
400
        // catch silent/external emits like "sj.add_byte(0x123)" from Lua script
401
        if (0 == nListBytes) return;                // no silent/external emit happened
673,880✔
402
        ++CompiledCurrentLine;
45✔
403
        char silentOrExternalBytes[] = "; these bytes were emitted silently/externally (lua script?)";
45✔
404
        substitutedLine = silentOrExternalBytes;
45✔
405
        eolComment = nullptr;
45✔
406
        ListFile();
45✔
407
        substitutedLine = line;
45✔
408
}
409

410
static bool someByteEmitted = false;
411

412
bool DidEmitByte() {        // returns true if some byte was emitted since last call to this function
3,084✔
413
        bool didEmit = someByteEmitted;                // value to return
3,084✔
414
        someByteEmitted = false;                        // reset the flag
3,084✔
415
        return didEmit;
3,084✔
416
}
417

418
static void EmitByteNoListing(int byte, bool preserveDeviceMemory = false) {
1,982,218✔
419
        someByteEmitted = true;
1,982,218✔
420
        if (LASTPASS == pass) {
1,982,218✔
421
                WriteBuffer[WBLength++] = (char)byte;
828,089✔
422
                if (DESTBUFLEN == WBLength) WriteDest();
828,089✔
423
        }
424
        // the page-checking in device mode must be done in all passes, the slot can have "wrap" option
425
        if (DeviceID) {
1,982,218✔
426
                Device->CheckPage(CDevice::CHECK_EMIT);
1,363,636✔
427
                if (MemoryPointer) {
1,363,636✔
428
                        if (LASTPASS == pass && !preserveDeviceMemory) *MemoryPointer = (char)byte;
1,363,172✔
429
                        ++MemoryPointer;
1,363,172✔
430
                }
431
        } else {
432
                CheckRamLimitExceeded();
618,582✔
433
        }
434
        ++CurAddress;
1,982,218✔
435
        if (DISP_NONE != PseudoORG) ++adrdisp;
1,982,218✔
436
}
1,982,218✔
437

438
static bool PageDiffersWarningShown = false;
439

440
void EmitByte(int byte, bool isInstructionStart) {
667,177✔
441
        if (isInstructionStart) {
667,177✔
442
                // SLD (Source Level Debugging) tracing-data logging
443
                if (IsSldExportActive()) {
115,332✔
444
                        int pageNum = Page->Number;
681✔
445
                        if (DISP_NONE != PseudoORG) {
681✔
446
                                int mappingPageNum = Device->GetPageOfA16(CurAddress);
5✔
447
                                if (LABEL_PAGE_UNDEFINED == dispPageNum) {        // special DISP page is not set, use mapped
5✔
448
                                        pageNum = mappingPageNum;
1✔
449
                                } else {
450
                                        pageNum = dispPageNum;                                        // special DISP page is set, use it instead
4✔
451
                                        if (pageNum != mappingPageNum && !PageDiffersWarningShown) {
4✔
452
                                                WarningById(W_DISP_MEM_PAGE);
1✔
453
                                                PageDiffersWarningShown = true;                // show warning about different mapping only once
1✔
454
                                        }
455
                                }
456
                        }
457
                        WriteToSldFile(pageNum, CurAddress);
681✔
458
                }
459
        }
460
        byte &= 0xFF;
667,177✔
461
        if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE-1) {
667,177✔
462
                ListEmittedBytes[nListBytes++] = byte;                // write also into listing
665,779✔
463
        } else {
464
                if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE) {
1,398✔
465
                        // too many bytes, show it in listing as "..."
466
                        ListEmittedBytes[nListBytes++] = -2;
3✔
467
                }
468
        }
469
        EmitByteNoListing(byte);
667,177✔
470
}
667,177✔
471

472
void EmitWord(int word, bool isInstructionStart) {
14,960✔
473
        EmitByte(word % 256, isInstructionStart);
14,960✔
474
        EmitByte(word / 256, false);
14,960✔
475
}
14,960✔
476

477
void EmitBytes(const int* bytes, bool isInstructionStart) {
174,083✔
478
        if (BYTES_END_MARKER == *bytes) {
174,083✔
479
                Error("Illegal instruction", line, IF_FIRST);
4,203✔
480
                SkipToEol(lp);
4,203✔
481
        }
482
        while (BYTES_END_MARKER != *bytes) {
590,802✔
483
                EmitByte(*bytes++, isInstructionStart);
416,719✔
484
                isInstructionStart = (INSTRUCTION_START_MARKER == *bytes);        // only true for first byte, or when marker
416,719✔
485
                if (isInstructionStart) ++bytes;
416,719✔
486
        }
487
}
174,083✔
488

489
void EmitWords(const int* words, bool isInstructionStart) {
4,585✔
490
        while (BYTES_END_MARKER != *words) {
14,522✔
491
                EmitWord(*words++, isInstructionStart);
9,937✔
492
                isInstructionStart = false;                // only true for first word
9,937✔
493
        }
494
}
4,585✔
495

496
void EmitBlock(aint byte, aint len, bool preserveDeviceMemory, int emitMaxToListing) {
2,020✔
497
        if (len <= 0) {
2,020✔
498
                const aint adrMask = Options::IsLongPtr ? ~0 : 0xFFFF;
36✔
499
                CurAddress = (CurAddress + len) & adrMask;
36✔
500
                if (DISP_NONE != PseudoORG) adrdisp = (adrdisp + len) & adrMask;
36✔
501
                if (DeviceID)        Device->CheckPage(CDevice::CHECK_NO_EMIT);
36✔
502
                else                        CheckRamLimitExceeded();
18✔
503
                return;
36✔
504
        }
505
        if (LIST_EMIT_BYTES_BUFFER_SIZE <= nListBytes + emitMaxToListing) {        // clamp emit to list buffer
1,984✔
506
                emitMaxToListing = LIST_EMIT_BYTES_BUFFER_SIZE - nListBytes;
×
507
        }
508
        while (len--) {
1,067,524✔
509
                int dVal = (preserveDeviceMemory && DeviceID && MemoryPointer) ? MemoryPointer[0] : byte;
1,065,540✔
510
                EmitByteNoListing(byte, preserveDeviceMemory);
1,065,540✔
511
                if (LASTPASS == pass && emitMaxToListing) {
1,065,540✔
512
                        // put "..." marker into listing if some more bytes are emitted after last listed
513
                        if ((0 == --emitMaxToListing) && len) ListEmittedBytes[nListBytes++] = -2;
2,062✔
514
                        else ListEmittedBytes[nListBytes++] = dVal&0xFF;
1,872✔
515
                }
516
        }
517
}
518

519
char* GetPath(const char* fname, char** filenamebegin, bool systemPathsBeforeCurrent)
255✔
520
{
521
        char fullFilePath[MAX_PATH] = { 0 };
255✔
522
        CStringsList* dir = Options::IncludeDirsList;        // include-paths to search
255✔
523
        // search current directory first (unless "systemPathsBeforeCurrent")
524
        if (!systemPathsBeforeCurrent) {
255✔
525
                // if found, just skip the `while (dir)` loop
526
                if (SJ_SearchPath(CurrentDirectory.c_str(), fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) dir = nullptr;
240✔
527
                else fullFilePath[0] = 0;        // clear fullFilePath every time when not found
15✔
528
        }
529
        while (dir) {
267✔
530
                if (SJ_SearchPath(dir->string, fname, nullptr, MAX_PATH, fullFilePath, filenamebegin)) break;
30✔
531
                fullFilePath[0] = 0;        // clear fullFilePath every time when not found
12✔
532
                dir = dir->next;
12✔
533
        }
534
        // if the file was not found in the list, and current directory was not searched yet
535
        if (!fullFilePath[0] && systemPathsBeforeCurrent) {
255✔
536
                //and the current directory was not searched yet, do it now, set empty string if nothing
NEW
537
                if (!SJ_SearchPath(CurrentDirectory.c_str(), fname, NULL, MAX_PATH, fullFilePath, filenamebegin)) {
×
UNCOV
538
                        fullFilePath[0] = 0;        // clear fullFilePath every time when not found
×
539
                }
540
        }
541
        if (!fullFilePath[0] && filenamebegin) {        // if still not found, reset also *filenamebegin
255✔
UNCOV
542
                *filenamebegin = fullFilePath;
×
543
        }
544
        // copy the result into new memory
545
        char* kip = STRDUP(fullFilePath);
255✔
546
        if (kip == NULL) ErrorOOM();
255✔
547
        // convert filenamebegin pointer into the copied string (from temporary buffer pointer)
548
        if (filenamebegin) *filenamebegin += (kip - fullFilePath);
255✔
549
        return kip;
255✔
550
}
551

552
// if offset is negative, it functions as "how many bytes from end of file"
553
// if length is negative, it functions as "how many bytes from end of file to not load"
554
void BinIncFile(const char* fname, aint offset, aint length, const bool systemPathsFirst) {
174✔
555
        // open the desired file
556
        FILE* bif;
557
        char* fullFilePath = GetPath(fname, nullptr, systemPathsFirst);        //FIXME !research! path idea
174✔
558
        if (!FOPEN_ISOK(bif, fullFilePath, "rb")) Error("opening file", fname);
174✔
559
        free(fullFilePath);
174✔
560

561
        // Get length of file
562
        int totlen = 0, advanceLength;
174✔
563
        if (bif && (fseek(bif, 0, SEEK_END) || (totlen = ftell(bif)) < 0)) Error("telling file length", fname, FATAL);
174✔
564

565
        // process arguments (extra features like negative offset/length or INT_MAX length)
566
        // negative offset means "from the end of file"
567
        if (offset < 0) offset += totlen;
174✔
568
        // negative length means "except that many from end of file"
569
        if (length < 0) length += totlen - offset;
174✔
570
        // default length INT_MAX is "till the end of file"
571
        if (INT_MAX == length) length = totlen - offset;
174✔
572
        // verbose output of final values (before validation may terminate assembler)
573
        if (LASTPASS == pass && Options::OutputVerbosity <= OV_ALL) {
174✔
574
                char diagnosticTxt[MAX_PATH];
575
                SPRINTF4(diagnosticTxt, MAX_PATH, "include data: name=%s (%d bytes) Offset=%d  Len=%d", fname, totlen, offset, length);
3✔
576
                _CERR diagnosticTxt _ENDL;
3✔
577
        }
578
        // validate the resulting [offset, length]
579
        if (offset < 0 || length < 0 || totlen < offset + length) {
174✔
580
                Error("file too short", fname);
12✔
581
                offset = std::clamp(offset, 0, totlen);
12✔
582
                length = std::clamp(length, 0, totlen - offset);
12✔
583
                assert((0 <= offset) && (offset + length <= totlen));
12✔
584
        }
585
        if (0 == length) {
174✔
586
                Warning("include data: requested to include no data (length=0)");
18✔
587
                if (bif) fclose(bif);
18✔
588
                return;
18✔
589
        }
590
        assert(nullptr != bif);                                // otherwise it was handled by 0 == length case above
156✔
591

592
        if (pass != LASTPASS) {
156✔
593
                while (length) {
246✔
594
                        advanceLength = length;                // maximum possible to advance in address space
142✔
595
                        if (DeviceID) {                                // particular device may adjust that to less
142✔
596
                                Device->CheckPage(CDevice::CHECK_EMIT);
60✔
597
                                if (MemoryPointer) {        // fill up current memory page if possible
60✔
598
                                        advanceLength = Page->RAM + Page->Size - MemoryPointer;
56✔
599
                                        if (length < advanceLength) advanceLength = length;
56✔
600
                                        MemoryPointer += advanceLength;                // also update it! Doh!
56✔
601
                                }
602
                        }
603
                        length -= advanceLength;
142✔
604
                        if (length <= 0 && 0 == advanceLength) Error("BinIncFile internal error", NULL, FATAL);
142✔
605
                        if (DISP_NONE != PseudoORG) adrdisp = adrdisp + advanceLength;
142✔
606
                        CurAddress = CurAddress + advanceLength;
142✔
607
                }
608
        } else {
609
                // Seek to the beginning of part to include
610
                if (fseek(bif, offset, SEEK_SET) || ftell(bif) != offset) {
52✔
611
                        Error("seeking in file to offset", fname, FATAL);
×
612
                }
613

614
                // Reading data from file
615
                char* data = new char[length + 1], * bp = data;
52✔
616
                if (NULL == data) ErrorOOM();
52✔
617
                size_t res = fread(bp, 1, length, bif);
52✔
618
                if (res != (size_t)length) Error("reading data from file failed", fname, FATAL);
52✔
619
                while (length--) EmitByteNoListing(*bp++);
249,553✔
620
                delete[] data;
52✔
621
        }
622
        fclose(bif);
156✔
623
}
624

625
static void OpenDefaultList(const char *fullpath);
626

627
static stdin_log_t::const_iterator stdin_read_it;
628
static stdin_log_t* stdin_log = nullptr;
629

630
void OpenFile(fullpath_ref_t nfilename, stdin_log_t* fStdinLog)
2,768✔
631
{
632
        if (++IncludeLevel > 20) {
2,768✔
633
                Error("Over 20 files nested", NULL, ALL);
6✔
634
                --IncludeLevel;
6✔
635
                return;
12✔
636
        }
637
        assert(!fStdinLog || nfilename.full.empty());
2,762✔
638
        if (fStdinLog) {
2,762✔
639
                FP_Input = stdin;
12✔
640
                stdin_log = fStdinLog;
12✔
641
                stdin_read_it = stdin_log->cbegin();        // reset read iterator (for 2nd+ pass)
12✔
642
        } else {
643
                if (!FOPEN_ISOK(FP_Input, nfilename.full, "rb")) {
2,750✔
644
                        Error("opening file", nfilename.fullStr.c_str(), ALL);
6✔
645
                        --IncludeLevel;
6✔
646
                        return;
6✔
647
                }
648
        }
649

650
        fullpath_p_t oFileNameFull = fileNameFull;
2,756✔
651
        const std::filesystem::path oCurrentDirectory = CurrentDirectory;
2,756✔
652

653
        // archive the filename (for referencing it in SLD tracing data or listing/errors)
654
        fileNameFull = &nfilename;
2,756✔
655
        sourcePosStack.emplace_back(Options::IsShowFullPath ? nfilename.fullStr.c_str() : nfilename.baseStr.c_str());
2,756✔
656

657
        // refresh pre-defined values related to file/include
658
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,756✔
659
        DefineTable.Replace("__FILE__", nfilename.fullStr.c_str());
2,756✔
660
        if (0 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", nfilename.fullStr.c_str());
2,756✔
661

662
        // open default listing file for each new source file (if default listing is ON)
663
        if (LASTPASS == pass && 0 == IncludeLevel && Options::IsDefaultListingName) {
2,756✔
664
                OpenDefaultList(nfilename.full.c_str());//FIXME use path        // explicit listing file is already opened
1✔
665
        }
666
        // show in listing file which file was opened
667
        FILE* listFile = GetListingFile();
2,756✔
668
        if (LASTPASS == pass && listFile) {
2,756✔
669
                fputs("# file opened: ", listFile);
460✔
670
                fputs(nfilename.fullStr.c_str(), listFile);
460✔
671
                fputs("\n", listFile);
460✔
672
        }
673

674
        CurrentDirectory = fileNameFull->full.parent_path();                // use file's current directory
2,756✔
675

676
        rlpbuf = rlpbuf_end = rlbuf;
2,756✔
677
        colonSubline = false;
2,756✔
678
        blockComment = 0;
2,756✔
679

680
        ReadBufLine();
2,756✔
681

682
        if (stdin != FP_Input) fclose(FP_Input);
2,747✔
683
        else {
684
                if (1 == pass) {
12✔
685
                        stdin_log->push_back(0);        // add extra zero terminator
4✔
686
                        clearerr(stdin);                        // reset EOF on the stdin for another round of input
4✔
687
                }
688
        }
689
        CurrentDirectory = oCurrentDirectory;
2,747✔
690

691
        // show in listing file which file was closed
692
        if (LASTPASS == pass && listFile) {
2,747✔
693
                fputs("# file closed: ", listFile);
460✔
694
                fputs(nfilename.fullStr.c_str(), listFile);
460✔
695
                fputs("\n", listFile);
460✔
696

697
                // close listing file (if "default" listing filename is used)
698
                if (FP_ListingFile && 0 == IncludeLevel && Options::IsDefaultListingName) {
460✔
699
                        if (Options::AddLabelListing) LabelTable.Dump();
1✔
700
                        fclose(FP_ListingFile);
1✔
701
                        FP_ListingFile = NULL;
1✔
702
                }
703
        }
704

705
        --IncludeLevel;
2,747✔
706

707
        maxlin = std::max(maxlin, sourcePosStack.back().line);
2,747✔
708
        sourcePosStack.pop_back();
2,747✔
709
        fileNameFull = oFileNameFull;
2,747✔
710

711
        // refresh pre-defined values related to file/include
712
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,747✔
713
        DefineTable.Replace("__FILE__", fileNameFull ? fileNameFull->fullStr.c_str() : "<none>");
2,747✔
714
        //FIXME add in some include tests test of __FILE__ just to verify this has still valid c_str pointer
715
        if (-1 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", "<none>");
2,747✔
716
}
2,747✔
717

718
void IncludeFile(fullpath_ref_t nfilename)
465✔
719
{
720
        auto oStdin_log = stdin_log;
465✔
721
        auto oStdin_read_it = stdin_read_it;
465✔
722
        FILE* oFP_Input = FP_Input;
465✔
723
        FP_Input = 0;
465✔
724

725
        char* pbuf = rlpbuf, * pbuf_end = rlpbuf_end, * buf = STRDUP(rlbuf);
465✔
726
        if (buf == NULL) ErrorOOM();
465✔
727
        bool oColonSubline = colonSubline;
465✔
728
        if (blockComment) Error("Internal error 'block comment'", NULL, FATAL);        // comment can't INCLUDE
465✔
729

730
        OpenFile(nfilename);
465✔
731

732
        colonSubline = oColonSubline;
465✔
733
        rlpbuf = pbuf, rlpbuf_end = pbuf_end;
465✔
734
        STRCPY(rlbuf, 8192, buf);
465✔
735
        free(buf);
465✔
736

737
        FP_Input = oFP_Input;
465✔
738
        stdin_log = oStdin_log;
465✔
739
        stdin_read_it = oStdin_read_it;
465✔
740
}
465✔
741

742
typedef struct {
743
        char        name[12];
744
        size_t        length;
745
        byte        marker[16];
746
} BOMmarkerDef;
747

748
const BOMmarkerDef UtfBomMarkers[] = {
749
        { { "UTF8" }, 3, { 0xEF, 0xBB, 0xBF } },
750
        { { "UTF32BE" }, 4, { 0, 0, 0xFE, 0xFF } },
751
        { { "UTF32LE" }, 4, { 0xFF, 0xFE, 0, 0 } },                // must be detected *BEFORE* UTF16LE
752
        { { "UTF16BE" }, 2, { 0xFE, 0xFF } },
753
        { { "UTF16LE" }, 2, { 0xFF, 0xFE } }
754
};
755

756
static bool ReadBufData() {
7,244,273✔
757
        // check here also if `line` buffer is not full
758
        if ((LINEMAX-2) <= (rlppos - line)) Error("Line too long", NULL, FATAL);
7,244,273✔
759
        // now check for read data
760
        if (rlpbuf < rlpbuf_end) return 1;                // some data still in buffer
7,244,273✔
761
        // check EOF on files in every pass, stdin only in first, following will starve the stdin_log
762
        if ((stdin != FP_Input || 1 == pass) && feof(FP_Input)) return 0;        // no more data in file
16,830✔
763
        // read next block of data
764
        rlpbuf = rlbuf;
3,799✔
765
        // handle STDIN file differently (pass1 = read it, pass2+ replay "log" variable)
766
        if (1 == pass || stdin != FP_Input) {        // ordinary file is re-read every pass normally
3,799✔
767
                rlpbuf_end = rlbuf + fread(rlbuf, 1, 4096, FP_Input);
3,759✔
768
                *rlpbuf_end = 0;                                        // add zero terminator after new block
3,759✔
769
        }
770
        if (stdin == FP_Input) {
3,799✔
771
                // store copy of stdin into stdin_log during pass 1
772
                if (1 == pass && rlpbuf < rlpbuf_end) {
44✔
773
                        stdin_log->insert(stdin_log->end(), rlpbuf, rlpbuf_end);
4✔
774
                }
775
                // replay the log in 2nd+ pass
776
                if (1 < pass) {
44✔
777
                        rlpbuf_end = rlpbuf;
40✔
778
                        long toCopy = std::min(8000L, (long)std::distance(stdin_read_it, stdin_log->cend()));
80✔
779
                        if (0 < toCopy) {
40✔
780
                                memcpy(rlbuf, &(*stdin_read_it), toCopy);
8✔
781
                                stdin_read_it += toCopy;
8✔
782
                                rlpbuf_end += toCopy;
8✔
783
                        }
784
                        *rlpbuf_end = 0;                                // add zero terminator after new block
40✔
785
                }
786
        }
787
        // check UTF BOM markers only at the beginning of the file (source line == 0)
788
        assert(!sourcePosStack.empty());
3,799✔
789
        if (sourcePosStack.back().line) {
3,799✔
790
                return (rlpbuf < rlpbuf_end);                // return true if some data were read
1,043✔
791
        }
792
        //UTF BOM markers detector
793
        for (const auto & bomMarkerData : UtfBomMarkers) {
16,526✔
794
                if (rlpbuf_end < (rlpbuf + bomMarkerData.length)) continue;        // not enough bytes in buffer
13,774✔
795
                if (memcmp(rlpbuf, bomMarkerData.marker, bomMarkerData.length)) continue;        // marker not found
13,774✔
796
                if (&bomMarkerData != UtfBomMarkers) {        // UTF8 is first in the array, other markers show error
7✔
797
                        Error("Invalid UTF encoding detected (only ASCII and UTF8 works)", bomMarkerData.name, FATAL);
4✔
798
                }
799
                rlpbuf += bomMarkerData.length;        // skip the UTF8 BOM marker
3✔
800
        }
801
        return (rlpbuf < rlpbuf_end);                        // return true if some data were read
2,752✔
802
}
803

804
void ReadBufLine(bool Parse, bool SplitByColon) {
21,377✔
805
        // if everything else fails (no data, not running, etc), return empty line
806
        *line = 0;
21,377✔
807
        bool IsLabel = true;
21,377✔
808
        // try to read through the buffer and produce new line from it
809
        while (IsRunning && ReadBufData()) {
238,064✔
810
                // start of new line (or fake "line" by colon)
811
                rlppos = line;
235,313✔
812
                substitutedLine = line;                // also reset "substituted" line to the raw new one
235,313✔
813
                eolComment = NULL;
235,313✔
814
                if (colonSubline) {                        // starting from colon (creating new fake "line")
235,313✔
815
                        colonSubline = false;        // (can't happen inside block comment)
10,854✔
816
                        *(rlppos++) = ' ';
10,854✔
817
                        IsLabel = false;
10,854✔
818
                } else {                                        // starting real new line
819
                        IsLabel = (0 == blockComment);
224,459✔
820
                }
821
                bool afterNonAlphaNum, afterNonAlphaNumNext = true;
235,313✔
822
                // copy data from read buffer into `line` buffer until EOL/colon is found
823
                while (
235,313✔
824
                                ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf &&        // split by EOL
6,517,112✔
825
                                // split by colon only on 2nd+ char && SplitByColon && not inside block comment
826
                                (blockComment || !SplitByColon || rlppos == line || ':' != *rlpbuf)) {
3,146,334✔
827
                        // copy the new character to new line
828
                        *rlppos = *rlpbuf++;
3,135,465✔
829
                        afterNonAlphaNum = afterNonAlphaNumNext;
3,135,465✔
830
                        afterNonAlphaNumNext = !isalnum((byte)*rlppos);
3,135,465✔
831
                        // handle EOL escaping, limited implementation, usage not recommended
832
                        if ('\\' == *rlppos && ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf))  {
3,135,465✔
833
                                char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
42✔
834
                                if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
42✔
835
                                sourcePosStack.back().nextSegment();        // mark last line in errors/etc
42✔
836
                                continue;                                                                // continue with chars from next line
42✔
837
                        }
42✔
838
                        // Block comments logic first (anything serious may happen only "outside" of block comment
839
                        if ('*' == *rlppos && ReadBufData() && '/' == *rlpbuf) {
3,135,423✔
840
                                if (0 < blockComment) --blockComment;        // block comment ends here, -1 from nesting
438✔
841
                                ++rlppos;        *rlppos++ = *rlpbuf++;                // copy the second char too
438✔
842
                                continue;
438✔
843
                        }
844
                        if ('/' == *rlppos && ReadBufData() && '*' == *rlpbuf) {
3,134,985✔
845
                                ++rlppos, ++blockComment;                                // block comment starts here, nest +1 more
435✔
846
                                *rlppos++ = *rlpbuf++;                                        // copy the second char too
435✔
847
                                continue;
435✔
848
                        }
849
                        if (blockComment) {                                                        // inside block comment just copy chars
3,134,550✔
850
                                ++rlppos;
5,574✔
851
                                continue;
5,574✔
852
                        }
853
                        // check if still in label area, if yes, copy the finishing colon as char (don't split by it)
854
                        if ((IsLabel = (IsLabel && islabchar(*rlppos)))) {
3,128,976✔
855
                                ++rlppos;                                        // label character
206,160✔
856
                                //SMC offset handling
857
                                if (ReadBufData() && '+' == *rlpbuf) {        // '+' after label, add it as SMC_offset syntax
206,160✔
858
                                        IsLabel = false;
483✔
859
                                        *rlppos++ = *rlpbuf++;
483✔
860
                                        if (ReadBufData() && (isdigit(byte(*rlpbuf)) || '*' == *rlpbuf)) *rlppos++ = *rlpbuf++;
483✔
861
                                }
862
                                if (ReadBufData() && ':' == *rlpbuf) {        // colon after label, add it
206,160✔
863
                                        *rlppos++ = *rlpbuf++;
13,290✔
864
                                        IsLabel = false;
13,290✔
865
                                }
866
                                continue;
206,160✔
867
                        }
868
                        // not in label any more, check for EOL comments ";" or "//"
869
                        if ((';' == *rlppos) || ('/' == *rlppos && ReadBufData() && '/' == *rlpbuf)) {
2,922,816✔
870
                                eolComment = rlppos;
77,840✔
871
                                ++rlppos;                                        // EOL comment ";"
77,840✔
872
                                while (ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf) *rlppos++ = *rlpbuf++;
2,382,282✔
873
                                continue;
77,840✔
874
                        }
875
                        // check for string literals - double/single quotes
876
                        if (afterNonAlphaNum && ('"' == *rlppos || '\'' == *rlppos)) {
2,844,976✔
877
                                const bool quotes = '"' == *rlppos;
14,085✔
878
                                int escaped = 0;
14,085✔
879
                                do {
880
                                        if (escaped) --escaped;
123,867✔
881
                                        ++rlppos;                                // previous char confirmed
123,867✔
882
                                        *rlppos = ReadBufData() ? *rlpbuf : 0;        // copy next char (if available)
123,867✔
883
                                        if (!*rlppos || '\r' == *rlppos || '\n' == *rlppos) *rlppos = 0;        // not valid
123,867✔
884
                                        else ++rlpbuf;                        // buffer char read (accepted)
123,786✔
885
                                        if (quotes && !escaped && '\\' == *rlppos) escaped = 2;        // escape sequence detected
123,867✔
886
                                } while (*rlppos && (escaped || (quotes ? '"' : '\'') != *rlppos));
123,867✔
887
                                if (*rlppos) ++rlppos;                // there should be ending "/' in line buffer, confirm it
14,085✔
888
                                continue;
14,085✔
889
                        }
14,085✔
890
                        // anything else just copy
891
                        ++rlppos;                                // previous char confirmed
2,830,891✔
892
                } // while "some char in buffer, and it's not line delimiter"
893
                // line interrupted somehow, may be correctly finished, check + finalize line and process it
894
                *rlppos = 0;
235,313✔
895
                // skip <EOL> char sequence in read buffer
896
                if (ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf)) {
235,313✔
897
                        char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
224,262✔
898
                        if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
224,262✔
899
                        // if this was very last <EOL> in file (on non-empty line), add one more fake empty line
900
                        if (!ReadBufData() && *line) *rlpbuf_end++ = '\n';        // to make listing files "as before"
224,262✔
901
                } else {
902
                        // advance over single colon if that was the reason to terminate line parsing
903
                        colonSubline = SplitByColon && ReadBufData() && (':' == *rlpbuf) && ++rlpbuf;
11,051✔
904
                }
905
                // do +1 for very first colon-segment only (rest is +1 due to artificial space at beginning)
906
                assert(!sourcePosStack.empty());
235,313✔
907
                size_t advanceColumns = colonSubline ? (0 == sourcePosStack.back().colEnd) + strlen(line) : 0;
235,313✔
908
                sourcePosStack.back().nextSegment(colonSubline, advanceColumns);
235,313✔
909
                // line is parsed and ready to be processed
910
                if (Parse)         ParseLine();        // processed here in loop
235,313✔
911
                else                 return;                        // processed externally
18,621✔
912
        } // while (IsRunning && ReadBufData())
913
}
914

915
static void OpenListImp(const std::filesystem::path & listFilename) {
471✔
916
        // if STDERR is configured to contain listing, disable other listing files
917
        if (OV_LST == Options::OutputVerbosity) return;
471✔
918
        if (listFilename.empty()) return;
471✔
919
        if (!FOPEN_ISOK(FP_ListingFile, listFilename, "w")) {
312✔
920
                Error("opening file for write", listFilename.string().c_str(), FATAL);
×
921
        }
922
}
923

924
void OpenList() {
505✔
925
        // if STDERR is configured to contain listing, disable other listing files
926
        if (OV_LST == Options::OutputVerbosity) return;
505✔
927
        // check if listing file is already opened, or it is set to "default" file names
928
        if (Options::IsDefaultListingName || NULL != FP_ListingFile) return;
471✔
929
        // Only explicit listing files are opened here
930
        OpenListImp(Options::ListingFName);
470✔
931
}
932

933
static void OpenDefaultList(const char *fullpath) {        //FIXME take path instead?
1✔
934
        // if STDERR is configured to contain listing, disable other listing files
935
        if (OV_LST == Options::OutputVerbosity) return;
1✔
936
        // check if listing file is already opened, or it is set to explicit file name
937
        if (!Options::IsDefaultListingName || NULL != FP_ListingFile) return;
1✔
938
        if (NULL == fullpath || !*fullpath) return;                // no filename provided
1✔
939
        // Create default listing name, and try to open it
940
        char tempListName[LINEMAX+10];                // make sure there is enough room for new extension
941
        char* extPos = FilenameExtPos(tempListName, fullpath, LINEMAX);        // find extension position
1✔
942
        STRCPY(extPos, 5, ".lst");                        // overwrite it with ".lst"
1✔
943
        // list filename prepared, open it
944
        OpenListImp(tempListName);
1✔
945
}
946

947
void CloseDest() {
631✔
948
        // Flush buffer before any other operations
949
        WriteDest();
631✔
950
        // does main output file exist? (to close it)
951
        if (FP_Output == NULL) return;
631✔
952
        // pad to desired size (and check for exceed of it)
953
        if (size != -1L) {
116✔
954
                if (destlen > size) {
4✔
955
                        ErrorInt("File exceeds 'size' by", destlen - size);
1✔
956
                }
957
                memset(WriteBuffer, 0, DESTBUFLEN);
4✔
958
                while (destlen < size) {
7✔
959
                        WBLength = std::min(aint(DESTBUFLEN), size-destlen);
3✔
960
                        WriteDest();
3✔
961
                }
962
                size = -1L;
4✔
963
        }
964
        fclose(FP_Output);
116✔
965
        FP_Output = NULL;
116✔
966
}
967

968
void SeekDest(long offset, int method) {
5✔
969
        WriteDest();
5✔
970
        if (FP_Output != NULL && fseek(FP_Output, offset, method)) {
5✔
971
                Error("File seek error (FPOS)", NULL, FATAL);
×
972
        }
973
}
5✔
974

975
void NewDest(const std::filesystem::path & newfilename, int mode) {
116✔
976
        // close previous output file
977
        CloseDest();
116✔
978

979
        // and open new file (keep previous/default name, if no explicit was provided)
980
        if (!newfilename.empty()) Options::DestinationFName = newfilename;
116✔
981
        OpenDest(mode);
116✔
982
}
116✔
983

984
void OpenDest(int mode) {
613✔
985
        destlen = 0;
613✔
986
        if (mode != OUTPUT_TRUNCATE && !FileExists(Options::DestinationFName)) {
613✔
987
                mode = OUTPUT_TRUNCATE;
1✔
988
        }
989
        if (!Options::NoDestinationFile && !FOPEN_ISOK(FP_Output, Options::DestinationFName, mode == OUTPUT_TRUNCATE ? "wb" : "r+b")) {
613✔
990
                Error("opening file for write", Options::DestinationFName.string().c_str(), FATAL);
×
991
        }
992
        Options::NoDestinationFile = false;
613✔
993
        if (NULL == FP_RAW && "-" == Options::RAWFName) {
613✔
994
                FP_RAW = stdout;
1✔
995
                fflush(stdout);
1✔
996
                switchStdOutIntoBinaryMode();
1✔
997
        }
998
        if (FP_RAW == NULL && Options::RAWFName.has_filename() && !FOPEN_ISOK(FP_RAW, Options::RAWFName, "wb")) {
613✔
999
                Error("opening file for write", Options::RAWFName.string().c_str());
×
1000
        }
1001
        if (FP_Output != NULL && mode != OUTPUT_TRUNCATE) {
613✔
1002
                if (fseek(FP_Output, 0, mode == OUTPUT_REWIND ? SEEK_SET : SEEK_END)) {
3✔
1003
                        Error("File seek error (OUTPUT)", NULL, FATAL);
×
1004
                }
1005
        }
1006
}
613✔
1007

1008
void CloseTapFile()
515✔
1009
{
1010
        char tap_data[2];
1011

1012
        WriteDest();
515✔
1013
        if (FP_tapout == NULL) return;
515✔
1014

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

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

1020
        tap_data[0] =  tape_length     & 0xFF;
7✔
1021
        tap_data[1] = (tape_length>>8) & 0xFF;
7✔
1022
        if (fwrite(tap_data, 1, 2, FP_tapout) != 2) Error("Write error (disk full?)", NULL, FATAL);
7✔
1023

1024
        fclose(FP_tapout);
7✔
1025
        FP_tapout = NULL;
7✔
1026
}
1027

1028
void OpenTapFile(const std::filesystem::path & tapename, int flagbyte)
9✔
1029
{
1030
        CloseTapFile();
9✔
1031

1032
        if (!FOPEN_ISOK(FP_tapout,tapename, "r+b")) {
9✔
1033
                Error( "opening file for write", tapename.string().c_str());
2✔
1034
                return;
2✔
1035
        }
1036
        if (fseek(FP_tapout, 0, SEEK_END)) Error("File seek end error in TAPOUT", tapename.string().c_str(), FATAL);
7✔
1037

1038
        tape_seek = ftell(FP_tapout);
7✔
1039
        tape_parity = flagbyte;
7✔
1040
        tape_length = 2;
7✔
1041

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

1044
        if (sizeof(tap_data) != fwrite(tap_data, 1, sizeof(tap_data), FP_tapout)) {
7✔
1045
                fclose(FP_tapout);
×
1046
                Error("Write error (disk full?)", NULL, FATAL);
×
1047
        }
1048
}
1049

1050
// check if file exists and seems to be regular file (maybe character works too? stdin is what? FIXME)
1051
bool FileExists(const std::filesystem::path & file_name) {
143✔
1052
        return        std::filesystem::exists(file_name) && (
253✔
1053
                std::filesystem::is_regular_file(file_name) ||
110✔
NEW
1054
                std::filesystem::is_character_file(file_name)        //FIXME verify with stdin if this is needed
×
1055
        );
143✔
1056
}
1057

1058
bool FileExistsCstr(const char* file_name) {
17✔
1059
        if (nullptr == file_name) return false;
17✔
1060
        return FileExists(std::filesystem::path(file_name));
16✔
1061
}
1062

1063
void Close() {
497✔
1064
        if (*ModuleName) {
497✔
1065
                Warning("ENDMODULE missing for module", ModuleName, W_ALL);
2✔
1066
        }
1067

1068
        CloseDest();
497✔
1069
        CloseTapFile();
497✔
1070
        if (FP_ExportFile != NULL) {
497✔
1071
                fclose(FP_ExportFile);
5✔
1072
                FP_ExportFile = NULL;
5✔
1073
        }
1074
        if (FP_RAW != NULL) {
497✔
1075
                if (stdout != FP_RAW) fclose(FP_RAW);
4✔
1076
                FP_RAW = NULL;
4✔
1077
        }
1078
        if (FP_ListingFile != NULL) {
497✔
1079
                fclose(FP_ListingFile);
303✔
1080
                FP_ListingFile = NULL;
303✔
1081
        }
1082
        CloseSld();
497✔
1083
        CloseBreakpointsFile();
497✔
1084
}
497✔
1085

1086
int SaveRAM(FILE* ff, int start, int length) {
202✔
1087
        //unsigned int addadr = 0,save = 0;
1088
        aint save = 0;
202✔
1089
        if (!DeviceID) return 0;                // unreachable currently
202✔
1090
        if (length + start > 0x10000) {
202✔
1091
                length = -1;
1✔
1092
        }
1093
        if (length <= 0) {
202✔
1094
                length = 0x10000 - start;
2✔
1095
        }
1096

1097
        CDeviceSlot* S;
1098
        for (int i=0;i<Device->SlotsCount;i++) {
466✔
1099
                S = Device->GetSlot(i);
466✔
1100
                if (start >= (int)S->Address  && start < (int)(S->Address + S->Size)) {
466✔
1101
                        if (length < (int)(S->Size - (start - S->Address))) {
214✔
1102
                                save = length;
189✔
1103
                        } else {
1104
                                save = S->Size - (start - S->Address);
25✔
1105
                        }
1106
                        if ((aint) fwrite(S->Page->RAM + (start - S->Address), 1, save, ff) != save) {
214✔
1107
                                return 0;
×
1108
                        }
1109
                        length -= save;
214✔
1110
                        start += save;
214✔
1111
                        if (length <= 0) {
214✔
1112
                                return 1;
202✔
1113
                        }
1114
                }
1115
        }
1116
        return 0;                // unreachable (with current devices)
×
1117
}
1118

1119
unsigned int MemGetWord(unsigned int address) {
10✔
1120
        return MemGetByte(address) + (MemGetByte(address+1)<<8);
10✔
1121
}
1122

1123
unsigned char MemGetByte(unsigned int address) {
22,304✔
1124
        if (!DeviceID || pass != LASTPASS) {
22,304✔
1125
                return 0;
14,864✔
1126
        }
1127

1128
        CDeviceSlot* S;
1129
        for (int i=0;i<Device->SlotsCount;i++) {
8,974✔
1130
                S = Device->GetSlot(i);
8,971✔
1131
                if (address >= (unsigned int)S->Address  && address < (unsigned int)S->Address + (unsigned int)S->Size) {
8,971✔
1132
                        return S->Page->RAM[address - S->Address];
7,437✔
1133
                }
1134
        }
1135

1136
        ErrorInt("MemGetByte: Error reading address", address);
3✔
1137
        return 0;
3✔
1138
}
1139

1140

1141
int SaveBinary(const std::filesystem::path & fname, aint start, aint length) {
17✔
1142
        FILE* ff;
1143
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
17✔
1144
        int result = SaveRAM(ff, start, length);
17✔
1145
        fclose(ff);
17✔
1146
        return result;
17✔
1147
}
1148

1149

1150
int SaveBinary3dos(const std::filesystem::path & fname, aint start, aint length, byte type, word w2, word w3) {
6✔
1151
        FILE* ff;
1152
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
6✔
1153
        // prepare +3DOS 128 byte header content
1154
        constexpr aint hsize = 128;
6✔
1155
        const aint full_length = hsize + length;
6✔
1156
        byte sum = 0, p3dos_header[hsize] { "PLUS3DOS\032\001" };
6✔
1157
        p3dos_header[11] = byte(full_length>>0);
6✔
1158
        p3dos_header[12] = byte(full_length>>8);
6✔
1159
        p3dos_header[13] = byte(full_length>>16);
6✔
1160
        p3dos_header[14] = byte(full_length>>24);
6✔
1161
        // +3 BASIC 8 byte header filled with "relevant values"
1162
        p3dos_header[15+0] = type;
6✔
1163
        p3dos_header[15+1] = byte(length>>0);
6✔
1164
        p3dos_header[15+2] = byte(length>>8);
6✔
1165
        p3dos_header[15+3] = byte(w2>>0);
6✔
1166
        p3dos_header[15+4] = byte(w2>>8);
6✔
1167
        p3dos_header[15+5] = byte(w3>>0);
6✔
1168
        p3dos_header[15+6] = byte(w3>>8);
6✔
1169
        // calculat checksum of the header
1170
        for (const byte v : p3dos_header) sum += v;
774✔
1171
        p3dos_header[hsize-1] = sum;
6✔
1172
        // write header and data
1173
        int result = (hsize == (aint) fwrite(p3dos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1174
        fclose(ff);
6✔
1175
        return result;
6✔
1176
}
1177

1178

1179
int SaveBinaryAmsdos(const std::filesystem::path & fname, aint start, aint length, word start_adr, byte type) {
7✔
1180
        FILE* ff;
1181
        if (!FOPEN_ISOK(ff, fname, "wb")) {
7✔
1182
                Error("opening file for write", fname.string().c_str(), SUPPRESS);
1✔
1183
                return 0;
1✔
1184
        }
1185
        // prepare AMSDOS 128 byte header content
1186
        constexpr aint hsize = 128;
6✔
1187
        byte amsdos_header[hsize] {};        // all zeroed (user_number and filename stay like that, just zeroes)
6✔
1188
        amsdos_header[0x12] = type;
6✔
1189
        amsdos_header[0x15] = byte(start>>0);
6✔
1190
        amsdos_header[0x16] = byte(start>>8);
6✔
1191
        amsdos_header[0x18] = amsdos_header[0x40] = byte(length>>0);
6✔
1192
        amsdos_header[0x19] = amsdos_header[0x41] = byte(length>>8);
6✔
1193
        amsdos_header[0x1A] = byte(start_adr>>0);
6✔
1194
        amsdos_header[0x1B] = byte(start_adr>>8);
6✔
1195
        // calculat checksum of the header
1196
        word sum = 0;
6✔
1197
        for (int ii = 0x43; ii--; ) sum += amsdos_header[ii];
408✔
1198
        amsdos_header[0x43] = byte(sum>>0);
6✔
1199
        amsdos_header[0x44] = byte(sum>>8);
6✔
1200
        // write header and data
1201
        int result = (hsize == (aint) fwrite(amsdos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1202
        fclose(ff);
6✔
1203
        return result;
6✔
1204
}
1205

1206

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

1212

1213
// start and length must be sanitized by caller
1214
bool SaveDeviceMemory(const std::filesystem::path & fname, const size_t start, const size_t length) {
10✔
1215
        FILE* ff;
1216
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
10✔
1217
        bool res = SaveDeviceMemory(ff, start, length);
10✔
1218
        fclose(ff);
10✔
1219
        return res;
10✔
1220
}
1221

1222

1223
int SaveHobeta(const std::filesystem::path & fname, const char* fhobname, aint start, aint length) {
1✔
1224
        unsigned char header[0x11];
1225
        int i;
1226

1227
        if (length + start > 0x10000) {
1✔
1228
                length = -1;
×
1229
        }
1230
        if (length <= 0) {
1✔
1231
                length = 0x10000 - start;
×
1232
        }
1233

1234
        memset(header,' ',9);
1✔
1235
        i = strlen(fhobname);
1✔
1236
        if (i > 1)
1✔
1237
        {
1238
                const char *ext = strrchr(fhobname, '.');
1✔
1239
                if (ext && ext[1])
1✔
1240
                {
1241
                        header[8] = ext[1];
1✔
1242
                        i = ext-fhobname;
1✔
1243
                }
1244
        }
1245
        memcpy(header, fhobname, std::min(i,8));
1✔
1246

1247
        if (header[8] == 'B')        {
1✔
1248
                header[0x09] = (unsigned char)(length & 0xff);
×
1249
                header[0x0a] = (unsigned char)(length >> 8);
×
1250
        } else        {
1251
                header[0x09] = (unsigned char)(start & 0xff);
1✔
1252
                header[0x0a] = (unsigned char)(start >> 8);
1✔
1253
        }
1254

1255
        header[0x0b] = (unsigned char)(length & 0xff);
1✔
1256
        header[0x0c] = (unsigned char)(length >> 8);
1✔
1257
        header[0x0d] = 0;
1✔
1258
        if (header[0x0b] == 0) {
1✔
1259
                header[0x0e] = header[0x0c];
×
1260
        } else {
1261
                header[0x0e] = header[0x0c] + 1;
1✔
1262
        }
1263
        length = header[0x0e] * 0x100;
1✔
1264
        int chk = 0;
1✔
1265
        for (i = 0; i <= 14; chk = chk + (header[i] * 257) + i,i++) {
16✔
1266
                ;
1267
        }
1268
        header[0x0f] = (unsigned char)(chk & 0xff);
1✔
1269
        header[0x10] = (unsigned char)(chk >> 8);
1✔
1270

1271
        FILE* ff;
1272
        if (!FOPEN_ISOK(ff, fname, "wb")) {
1✔
1273
                Error("opening file for write", fname.string().c_str(), FATAL);
×
1274
        }
1275

1276
        int result = (17 == fwrite(header, 1, 17, ff)) && SaveRAM(ff, start, length);
1✔
1277
        fclose(ff);
1✔
1278
        return result;
1✔
1279
}
1280

1281
EReturn ReadFile() {
20,417✔
1282
        while (ReadLine()) {
42,688✔
1283
                const bool isInsideDupCollectingLines = !RepeatStack.empty() && !RepeatStack.top().IsInWork;
42,682✔
1284
                if (!isInsideDupCollectingLines) {
42,682✔
1285
                        // check for ending of IF/IFN/... block (keywords: ENDIF, ELSE and ELSEIF)
1286
                        char* p = line;
42,442✔
1287
                        SkipBlanks(p);
42,442✔
1288
                        if ('.' == *p) ++p;
42,442✔
1289
                        EReturn retVal = END;
42,442✔
1290
                        if (cmphstr(p, "elseif")) retVal = ELSEIF;
42,442✔
1291
                        if (cmphstr(p, "else")) retVal = ELSE;
42,442✔
1292
                        if (cmphstr(p, "endif")) retVal = ENDIF;
42,442✔
1293
                        if (END != retVal) {
42,442✔
1294
                                // one of the end-block keywords was found, don't parse it as regular line
1295
                                // but just substitute the rest of it and return end value of the keyword
1296
                                ++CompiledCurrentLine;
20,411✔
1297
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
20,411✔
1298
                                substitutedLine = line;                // for listing override substituted line with source
20,411✔
1299
                                if (ENDIF != retVal) ListFile();        // do the listing for ELSE and ELSEIF
20,411✔
1300
                                return retVal;
20,411✔
1301
                        }
1302
                }
1303
                ParseLineSafe();
22,271✔
1304
        }
1305
        return END;
6✔
1306
}
1307

1308

1309
EReturn SkipFile() {
23,382✔
1310
        int iflevel = 0;
23,382✔
1311
        while (ReadLine()) {
52,728✔
1312
                char* p = line;
52,725✔
1313
                if (isLabelStart(p) && !Options::syx.IsPseudoOpBOF) {
52,725✔
1314
                        // this could be label, skip it (the --dirbol users can't use label + IF/... inside block)
1315
                        while (islabchar(*p)) ++p;
4,670✔
1316
                        if (':' == *p) ++p;
1,043✔
1317
                }
1318
                SkipBlanks(p);
52,725✔
1319
                if ('.' == *p) ++p;
52,725✔
1320
                if (cmphstr(p, "if") || cmphstr(p, "ifn") || cmphstr(p, "ifused") ||
105,246✔
1321
                        cmphstr(p, "ifnused") || cmphstr(p, "ifdef") || cmphstr(p, "ifndef")) {
105,246✔
1322
                        ++iflevel;
240✔
1323
                } else if (cmphstr(p, "endif")) {
52,485✔
1324
                        if (iflevel) {
5,543✔
1325
                                --iflevel;
240✔
1326
                        } else {
1327
                                ++CompiledCurrentLine;
5,303✔
1328
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
5,303✔
1329
                                substitutedLine = line;                // override substituted listing for ENDIF
5,303✔
1330
                                return ENDIF;
23,379✔
1331
                        }
1332
                } else if (cmphstr(p, "else")) {
46,942✔
1333
                        if (!iflevel) {
18,181✔
1334
                                ++CompiledCurrentLine;
17,959✔
1335
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
17,959✔
1336
                                substitutedLine = line;                // override substituted listing for ELSE
17,959✔
1337
                                ListFile();
17,959✔
1338
                                return ELSE;
17,959✔
1339
                        }
1340
                } else if (cmphstr(p, "elseif")) {
28,761✔
1341
                        if (!iflevel) {
189✔
1342
                                ++CompiledCurrentLine;
117✔
1343
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
117✔
1344
                                substitutedLine = line;                // override substituted listing for ELSEIF
117✔
1345
                                ListFile();
117✔
1346
                                return ELSEIF;
117✔
1347
                        }
1348
                } else if (cmphstr(p, "lua")) {                // lua script block detected, skip it whole
28,572✔
1349
                        // with extra custom while loop, to avoid confusion by `if/...` inside lua scripts
1350
                        ListFile(true);
84✔
1351
                        while (ReadLine()) {
189✔
1352
                                p = line;
189✔
1353
                                SkipBlanks(p);
189✔
1354
                                if (cmphstr(p, "endlua")) break;
189✔
1355
                                ListFile(true);
105✔
1356
                        }
1357
                }
1358
                ListFile(true);
29,346✔
1359
        }
1360
        return END;
3✔
1361
}
1362

1363
int ReadLineNoMacro(bool SplitByColon) {
18,639✔
1364
        if (!IsRunning || !ReadBufData()) return 0;
18,639✔
1365
        ReadBufLine(false, SplitByColon);
18,621✔
1366
        return 1;
18,621✔
1367
}
1368

1369
int ReadLine(bool SplitByColon) {
101,518✔
1370
        if (IsRunning && lijst) {                // read MACRO lines, if macro is being emitted
101,518✔
1371
                if (!lijstp || !lijstp->string) return 0;
86,368✔
1372
                assert(!sourcePosStack.empty());
86,368✔
1373
                sourcePosStack.back() = lijstp->source;
86,368✔
1374
                STRCPY(line, LINEMAX, lijstp->string);
86,368✔
1375
                substitutedLine = line;                // reset substituted listing
86,368✔
1376
                eolComment = NULL;                        // reset end of line comment
86,368✔
1377
                lijstp = lijstp->next;
86,368✔
1378
                return 1;
86,368✔
1379
        }
1380
        return ReadLineNoMacro(SplitByColon);
15,150✔
1381
}
1382

1383
int ReadFileToCStringsList(CStringsList*& f, const char* end) {
495✔
1384
        // f itself should be already NULL, not resetting it here
1385
        CStringsList** s = &f;
495✔
1386
        while (ReadLineNoMacro()) {
3,489✔
1387
                ++CompiledCurrentLine;
3,486✔
1388
                char* p = line;
3,486✔
1389
                SkipBlanks(p);
3,486✔
1390
                if ('.' == *p) ++p;
3,486✔
1391
                if (cmphstr(p, end)) {                // finished, read rest after end marker into line buffers
3,486✔
1392
                        lp = ReplaceDefine(p);
492✔
1393
                        return 1;
492✔
1394
                }
1395
                *s = new CStringsList(line);
2,994✔
1396
                s = &((*s)->next);
2,994✔
1397
                ListFile(true);
2,994✔
1398
        }
1399
        return 0;
3✔
1400
}
1401

1402
void OpenExpFile() {
497✔
1403
        assert(nullptr == FP_ExportFile);                        // this should be the first and only call to open it
497✔
1404
        if (!Options::ExportFName.has_filename()) return;        // no export file name provided, skip opening
497✔
1405
        if (FOPEN_ISOK(FP_ExportFile, Options::ExportFName, "w")) return;
6✔
1406
        Error("opening file for write", Options::ExportFName.string().c_str(), ALL);
1✔
1407
}
1408

1409
void WriteLabelEquValue(const char* name, aint value, FILE* f) {
73✔
1410
        if (nullptr == f) return;
73✔
1411
        char lnrs[16],* l = lnrs;
73✔
1412
        STRCPY(temp, LINEMAX-2, name);
73✔
1413
        STRCAT(temp, LINEMAX-1, ": EQU ");
73✔
1414
        STRCAT(temp, LINEMAX-1, "0x");
73✔
1415
        PrintHex32(l, value); *l = 0;
73✔
1416
        STRCAT(temp, LINEMAX-1, lnrs);
73✔
1417
        STRCAT(temp, LINEMAX-1, "\n");
73✔
1418
        fputs(temp, f);
73✔
1419
}
1420

1421
void WriteExp(const char* n, aint v) {
12✔
1422
        WriteLabelEquValue(n, v, FP_ExportFile);
12✔
1423
}
12✔
1424

1425
/////// source-level-debugging support by Ckirby
1426

1427
static FILE* FP_SourceLevelDebugging = NULL;
1428
static char sldMessage[LINEMAX2];
1429
static const char* WriteToSld_noSymbol = "";
1430
static char sldMessage_sourcePos[1024];
1431
static char sldMessage_definitionPos[1024];
1432
static const char* sldMessage_posFormat = "%d:%d:%d";        // at +3 is "%d:%d" and at +6 is "%d"
1433
static std::vector<std::string> sldCommentKeywords;
1434

1435
static void WriteToSldFile_TextFilePos(char* buffer, const TextFilePos & pos) {
2,214✔
1436
        int offsetFormat = !pos.colBegin ? 6 : !pos.colEnd ? 3 : 0;
2,214✔
1437
        snprintf(buffer, 1024-1, sldMessage_posFormat + offsetFormat, pos.line, pos.colBegin, pos.colEnd);
2,214✔
1438
}
2,214✔
1439

1440
static void OpenSldImp(const std::filesystem::path & sldFilename) {
497✔
1441
        if (!sldFilename.has_filename()) return;
497✔
1442
        if (!FOPEN_ISOK(FP_SourceLevelDebugging, sldFilename, "w")) {
16✔
1443
                Error("opening file for write", sldFilename.string().c_str(), FATAL);
×
1444
        }
1445
        fputs("|SLD.data.version|1\n", FP_SourceLevelDebugging);
16✔
1446
        if (0 < sldCommentKeywords.size()) {
16✔
1447
                fputs("||K|KEYWORDS|", FP_SourceLevelDebugging);
2✔
1448
                bool notFirst = false;
2✔
1449
                for (auto keyword : sldCommentKeywords) {
6✔
1450
                        if (notFirst) fputs(",", FP_SourceLevelDebugging);
4✔
1451
                        notFirst = true;
4✔
1452
                        fputs(keyword.c_str(), FP_SourceLevelDebugging);
4✔
1453
                }
4✔
1454
                fputs("\n", FP_SourceLevelDebugging);
2✔
1455
        }
1456
}
1457

1458
// will write result directly into Options::SourceLevelDebugFName
1459
static void OpenSld_buildDefaultNameIfNeeded() {
497✔
1460
        // check if SLD file name is already explicitly defined, or default is wanted
1461
        if (Options::SourceLevelDebugFName.has_filename() || !Options::IsDefaultSldName) return;
497✔
1462
        // name is still empty, and default is wanted, create one (start with "out" or first source name)
1463
        ConstructDefaultFilename(Options::SourceLevelDebugFName, ".sld.txt", false);
3✔
1464
}
1465

1466
// returns true only in the LASTPASS and only when "sld" file was specified by user
1467
// and only when assembling is in "virtual DEVICE" mode (for "none" device no tracing is emitted)
1468
bool IsSldExportActive() {
203,756✔
1469
        return (nullptr != FP_SourceLevelDebugging && DeviceID);
203,756✔
1470
}
1471

1472
void OpenSld() {
497✔
1473
        // check if source-level-debug file is already opened
1474
        if (nullptr != FP_SourceLevelDebugging) return;
497✔
1475
        // build default filename if not explicitly provided, and default was requested
1476
        OpenSld_buildDefaultNameIfNeeded();
497✔
1477
        // try to open it if not opened yet
1478
        OpenSldImp(Options::SourceLevelDebugFName);
497✔
1479
}
1480

1481
void CloseSld() {
497✔
1482
        if (!FP_SourceLevelDebugging) return;
497✔
1483
        fclose(FP_SourceLevelDebugging);
16✔
1484
        FP_SourceLevelDebugging = nullptr;
16✔
1485
}
1486

1487
void WriteToSldFile(int pageNum, int value, char type, const char* symbol) {
1,107✔
1488
        // SLD line format:
1489
        // <file name>|<source line>|<definition file>|<definition line>|<page number>|<value>|<type>|<data>\n
1490
        //
1491
        // * string <file name> can't be empty (empty is for specific "control lines" with different format)
1492
        //
1493
        // * unsigned <source line> when <file name> is not empty, line number (in human way starting at 1)
1494
        // The actual format is "%d[:%d[:%d]]", first number is always line. If second number is present,
1495
        // that's the start column (in bytes), and if also third number is present, that's end column.
1496
        //
1497
        // * string <definition file> where the <definition line> was defined, if empty, it's equal to <file name>
1498
        //
1499
        // * unsigned <definition line> explicit zero value in regular source, but inside macros
1500
        // the <source line> keeps pointing at line emitting the macro, while this value points
1501
        // to source with actual definitions of instructions/etc (nested macro in macro <source line>
1502
        // still points at the top level source which initiated it).
1503
        // The format is again "%d[:%d[:%d]]" same as <source line>, optionally including the columns data.
1504
        //
1505
        // * int <value> is not truncated to page range, but full 16b Z80 address or even 32b value (equ)
1506
        //
1507
        // * string <data> content depends on char <type>:
1508
        // 'T' = instruction Trace, empty data
1509
        // 'D' = EQU symbol, <data> is the symbol name ("label")
1510
        // 'F' = function label, <data> is the symbol name
1511
        // 'Z' = device (memory model) changed, <data> has special custom formatting
1512
        //
1513
        // 'Z' device <data> format:
1514
        // pages.size:<page size>,pages.count:<page count>,slots.count:<slots count>[,slots.adr:<slot0 adr>,...,<slotLast adr>]
1515
        // unsigned <page size> is also any-slot size in current version.
1516
        // unsigned <page count> and <slots count> define how many pages/slots there are
1517
        // uint16_t <slotX adr> is starting address of slot memory region in Z80 16b addressing
1518
        //
1519
        // specific lines (<file name> string was empty):
1520
        // |SLD.data.version|<version number>
1521
        // <version number> is SLD file format version, currently should be 0
1522
        // ||<anything till EOL>
1523
        // comment line, not to be parsed
1524
        if (nullptr == FP_SourceLevelDebugging || !type) return;
1,107✔
1525
        if (nullptr == symbol) symbol = WriteToSld_noSymbol;
1,107✔
1526

1527
        assert(!sourcePosStack.empty());
1,107✔
1528
        const bool outside_source = (sourcePosStack.size() <= size_t(IncludeLevel));
1,107✔
1529
        const bool has_def_pos = !outside_source && (size_t(IncludeLevel + 1) < sourcePosStack.size());
1,107✔
1530
        const TextFilePos & curPos = outside_source ? sourcePosStack.back() : sourcePosStack.at(IncludeLevel);
1,107✔
1531
        const TextFilePos & defPos = has_def_pos ? sourcePosStack.back() : TextFilePos();
1,107✔
1532

1533
        const char* macroFN = defPos.filename && strcmp(defPos.filename, curPos.filename) ? defPos.filename : "";
1,107✔
1534
        WriteToSldFile_TextFilePos(sldMessage_sourcePos, curPos);
1,107✔
1535
        WriteToSldFile_TextFilePos(sldMessage_definitionPos, defPos);
1,107✔
1536
        snprintf(sldMessage, LINEMAX2, "%s|%s|%s|%s|%d|%d|%c|%s\n",
1,107✔
1537
                                curPos.filename, sldMessage_sourcePos, macroFN, sldMessage_definitionPos,
1,107✔
1538
                                pageNum, value, type, symbol);
1539
        fputs(sldMessage, FP_SourceLevelDebugging);
1,107✔
1540
}
1541

1542
void SldAddCommentKeyword(const char* keyword) {
21✔
1543
        if (nullptr == keyword || !keyword[0]) {
21✔
1544
                if (LASTPASS == pass) Error("[SLDOPT COMMENT] invalid keyword", lp, SUPPRESS);
6✔
1545
                return;
6✔
1546
        }
1547
        if (1 == pass) {
15✔
1548
                auto begin = sldCommentKeywords.cbegin();
5✔
1549
                auto end = sldCommentKeywords.cend();
5✔
1550
                // add keyword only if it is new (not included yet)
1551
                if (std::find(begin, end, keyword) == end) sldCommentKeywords.push_back(keyword);
13✔
1552
        }
1553
}
1554

1555
void SldTrackComments() {
288✔
1556
        assert(eolComment && IsSldExportActive());
288✔
1557
        if (!eolComment[0]) return;
288✔
1558
        for (auto keyword : sldCommentKeywords) {
337✔
1559
                if (strstr(eolComment, keyword.c_str())) {
67✔
1560
                        int pageNum = Page->Number;
18✔
1561
                        if (DISP_NONE != PseudoORG) {
18✔
1562
                                pageNum = LABEL_PAGE_UNDEFINED != dispPageNum ? dispPageNum : Device->GetPageOfA16(CurAddress);
1✔
1563
                        }
1564
                        WriteToSldFile(pageNum, CurAddress, 'K', eolComment);
18✔
1565
                        return;
18✔
1566
                }
1567
        }
67✔
1568
}
1569

1570
/////// Breakpoints list (for different emulators)
1571
static FILE* FP_BreakpointsFile = nullptr;
1572
static EBreakpointsFile breakpointsType;
1573
static int breakpointsCounter;
1574

1575
void OpenBreakpointsFile(const std::filesystem::path & filename, const EBreakpointsFile type) {
10✔
1576
        if (!filename.has_filename()) {
10✔
1577
                Error("empty filename", filename.string().c_str(), EARLY);
3✔
1578
                return;
3✔
1579
        }
1580
        if (FP_BreakpointsFile) {
7✔
1581
                Error("breakpoints file was already opened", nullptr, EARLY);
2✔
1582
                return;
2✔
1583
        }
1584
        if (!FOPEN_ISOK(FP_BreakpointsFile, filename, "w")) {
5✔
1585
                Error("opening file for write", filename.string().c_str(), EARLY);
1✔
1586
        }
1587
        breakpointsCounter = 0;
5✔
1588
        breakpointsType = type;
5✔
1589
}
1590

1591
static void CloseBreakpointsFile() {
497✔
1592
        if (!FP_BreakpointsFile) return;
497✔
1593
        fclose(FP_BreakpointsFile);
4✔
1594
        FP_BreakpointsFile = nullptr;
4✔
1595
}
1596

1597
void WriteBreakpoint(const aint val) {
120✔
1598
        if (!FP_BreakpointsFile) {
120✔
1599
                WarningById(W_BP_FILE);
8✔
1600
                return;
8✔
1601
        }
1602
        ++breakpointsCounter;
112✔
1603
        switch (breakpointsType) {
112✔
1604
                case BPSF_UNREAL:
5✔
1605
                        check16u(val);
5✔
1606
                        fprintf(FP_BreakpointsFile, "x0=0x%04X\n", val&0xFFFF);
5✔
1607
                        break;
5✔
1608
                case BPSF_ZESARUX:
107✔
1609
                        if (1 == breakpointsCounter) fputs(" --enable-breakpoints ", FP_BreakpointsFile);
107✔
1610
                        if (100 < breakpointsCounter) {
107✔
1611
                                Warning("Maximum amount of 100 breakpoints has been already reached, this one is ignored");
1✔
1612
                                break;
1✔
1613
                        }
1614
                        check16u(val);
106✔
1615
                        fprintf(FP_BreakpointsFile, "--set-breakpoint %d \"PC=%d\" ", breakpointsCounter, val&0xFFFF);
106✔
1616
                        break;
106✔
1617
        }
1618
}
1619

1620
//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