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

z00m128 / sjasmplus / 1476

02 Feb 2025 10:37PM UTC coverage: 96.426% (-0.002%) from 96.428%
1476

push

cirrus-ci

ped7g
.cleanup TODO/comments

LGTM is no more and I haven't seen any output from the github built-in
code quality check, so let's see if removing the obsolete comment does
produce any change.

1 of 1 new or added line in 1 file covered. (100.0%)

20 existing lines in 2 files now uncovered.

9550 of 9904 relevant lines covered (96.43%)

171073.45 hits per line

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

97.69
/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
static const std::filesystem::path DOUBLE_DOT_PARENT{".."};
37
static constexpr char pathBadSlash = '\\';
38
static constexpr char pathGoodSlash = '/';
39

40
std::filesystem::path LaunchDirectory {};
41

42
int ListAddress;
43

44
static constexpr int LIST_EMIT_BYTES_BUFFER_SIZE = 1024 * 64;
45
static constexpr int DESTBUFLEN = 8192;
46

47
// ReadLine buffer and variables around
48
static char rlbuf[LINEMAX2 * 2];
49
static char * rlpbuf, * rlpbuf_end, * rlppos;
50
static bool colonSubline;
51
static int blockComment;
52

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

63
static void CloseBreakpointsFile();
64

65
std::string SInputFile::InitStr() {
693✔
66
        auto canonical = std::filesystem::exists(full) ? std::filesystem::canonical(full) : full.lexically_normal();
693✔
67
        switch(Options::FileVerbosity) {
693✔
68
                case Options::FNAME_LAUNCH_REL:
682✔
69
                        {        // try relative to LaunchDirectory, if outside (starts with "../") then keep absolute
70
                                auto relative = canonical.lexically_proximate(LaunchDirectory);
682✔
71
                                if (relative.begin()->compare(DOUBLE_DOT_PARENT)) canonical = std::move(relative);
682✔
72
                        }
682✔
73
                        [[fallthrough]];
74
                case Options::FNAME_ABSOLUTE:
684✔
75
                        return SJ_force_slash(canonical).string();
684✔
76
                case Options::FNAME_BASE:
9✔
77
                default:
78
                        return canonical.filename().string();
9✔
79
        }
80
}
693✔
81

82
std::filesystem::path GetOutputFileName(char*& p) {
931✔
83
        auto str_name = GetDelimitedStringEx(p);        // read delimited filename string
931✔
84
        SJ_FixSlashes(str_name);                                        // convert backslashes *with* warning
931✔
85
        // prefix with output path and force slashes again (without warning)
86
        return SJ_force_slash(Options::OutPrefix / str_name.first);
1,862✔
87
}
931✔
88

89
static bool isAnySlash(const char c) {
2✔
90
        return pathGoodSlash == c || pathBadSlash == c;
2✔
91
}
92

93
/**
94
 * @brief Check if the path does start with MS windows drive-letter and colon, but accepts
95
 * only absolute form with slash after colon, otherwise warns about relative way not supported.
96
 *
97
 * @param filePath p_filePath: filename to check
98
 * @return bool true if the filename contains drive-letter with ABSOLUTE path
99
 */
100
static bool isWindowsDrivePathStart(const char* filePath) {
667✔
101
        if (!filePath || !filePath[0] || ':' != filePath[1]) return false;
667✔
102
        const char driveLetter = toupper(filePath[0]);
2✔
103
        if (driveLetter < 'A' || 'Z' < driveLetter) return false;
2✔
104
        if (!isAnySlash(filePath[2])) {
2✔
105
                Warning("Relative file path with drive letter detected (not supported)", filePath, W_EARLY);
1✔
106
        }
107
        return true;
2✔
108
}
109

110
fullpath_ref_t GetInputFile(delim_string_t && in) {
3,621✔
111

112
        static dirs_in_map_t allArchivedInputFiles;
3,621✔
113
        static const SInputFile INPUT_FILE_STDIN(1);        // fake "<stdin>" string and empty path
3,621✔
114

115
        // check for special "empty" input value signalling <stdin>, return that instantly
116
        if (in.first.empty() && DT_COUNT == in.second) return INPUT_FILE_STDIN;
3,621✔
117

118
        // get current file's directory as base (use LaunchDirectory as fallback for stdin or zero level)
119
        std::filesystem::path CurrentDirectory = (fileNameFull && !fileNameFull->full.empty()) ? fileNameFull->full : LaunchDirectory;
3,606✔
120
        if (!std::filesystem::is_directory(CurrentDirectory)) CurrentDirectory.remove_filename();
3,606✔
121
        // make current directory canonical+relative to launchdir (avoid "..//"-like dupes in allArchivedInputFiles)
122
        CurrentDirectory = std::filesystem::canonical(CurrentDirectory).lexically_proximate(LaunchDirectory);
3,606✔
123

124
        // archive of all input files opened so far in the current directory (search results differ per current dir)
125
        files_in_map_t & archivedInputFiles = allArchivedInputFiles[CurrentDirectory];
3,606✔
126

127
        // if already archived, return archived full path
128
        SJ_FixSlashes(in);
3,606✔
129
        const auto lb = archivedInputFiles.lower_bound(in);
3,606✔
130
        if (archivedInputFiles.cend() != lb && lb->first == in) return lb->second;
3,606✔
131

132
        // !!! any warnings after this point must be W_EARLY, 2nd+ pass should use archived path = no warning !!!
133

134
        // not archived yet, look for the file somewhere...
135
        const std::filesystem::path name_in{ in.first };
693✔
136
        // no filename - return it as is (it's not valid for open)
137
        if (!name_in.has_filename()) {
693✔
138
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(name_in))->second;
12✔
139
        }
140
        // absolute path or windows drive letter oddities - use it as is (even if not valid)
141
        if (name_in.is_absolute() || isWindowsDrivePathStart(in.first.c_str())) {
681✔
142
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(name_in))->second;
16✔
143
        }
144
        // search include paths depending on delimiter and filename - first try current dir (except for "<name>")
145
        const auto current_dir_file = SJ_force_slash(CurrentDirectory / name_in);
665✔
146
        if (DT_ANGLE != in.second) {
665✔
147
                // force this as result for DT_COUNT delimiter (CLI argument filename => no searching)
148
                if (DT_COUNT == in.second || FileExists(current_dir_file)) {        // or if the file exists
637✔
149
                        return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(current_dir_file))->second;
620✔
150
                }
151
        }
152
        // search all include paths now
153
        for (auto incPath = Options::IncludeDirsList.crbegin(); incPath != Options::IncludeDirsList.crend(); ++incPath) {
77✔
154
                const auto dir_file = SJ_force_slash(*incPath / name_in);
55✔
155
                if (!FileExists(dir_file)) continue;
55✔
156
                return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(dir_file))->second;
23✔
157
        }
55✔
158
        // still not found. Found or not, return current dir path, it's either that or missing
159
        // do NOT return it "as input was", because that's enforcing LaunchDir as include path,
160
        // even if explicitly removed by `--inc`
161
        return archivedInputFiles.emplace_hint(lb, std::move(in), std::move(current_dir_file))->second;
22✔
162
}
3,606✔
163

164
fullpath_ref_t GetInputFile(char*& p) {
923✔
165
        auto name_in = GetDelimitedStringEx(p);
923✔
166
        return GetInputFile(std::move(name_in));
1,846✔
167
}
923✔
168

169
void ConstructDefaultFilename(std::filesystem::path & dest, const char* ext, bool checkIfDestIsEmpty) {
524✔
170
        if (nullptr == ext || !ext[0]) exit(1);        // invalid arguments
524✔
171
        // if the destination buffer has already some content and check is requested, exit
172
        if (checkIfDestIsEmpty && !dest.empty()) return;
524✔
173
        // construct the new default name - search for explicit name in sourcefiles
174
        dest = "asm";                // use "asm" base if no explicit filename available
522✔
175
        for (const SSource & src : sourceFiles) {
530✔
176
                if (!src.fname[0]) continue;
522✔
177
                dest = src.fname;
514✔
178
                break;
514✔
179
        }
180
        dest.replace_extension(ext);
522✔
181
}
182

183
void CheckRamLimitExceeded() {
619,023✔
184
        if (Options::IsLongPtr) return;                // in "longptr" mode with no device keep the address as is
619,023✔
185
        static bool notWarnedCurAdr = true;
186
        static bool notWarnedDisp = true;
187
        char buf[64];
188
        if (CurAddress >= 0x10000) {
618,936✔
189
                if (LASTPASS == pass && notWarnedCurAdr) {
76,435✔
190
                        SPRINTF2(buf, 64, "RAM limit exceeded 0x%X by %s",
22✔
191
                                         (unsigned int)CurAddress, DISP_NONE != PseudoORG ? "DISP":"ORG");
192
                        Warning(buf);
22✔
193
                        notWarnedCurAdr = false;
22✔
194
                }
195
                if (DISP_NONE != PseudoORG) CurAddress &= 0xFFFF;        // fake DISP address gets auto-wrapped FFFF->0
76,435✔
196
        } else notWarnedCurAdr = true;
542,501✔
197
        if (DISP_NONE != PseudoORG && adrdisp >= 0x10000) {
618,936✔
198
                if (LASTPASS == pass && notWarnedDisp) {
834✔
199
                        SPRINTF1(buf, 64, "RAM limit exceeded 0x%X by ORG", (unsigned int)adrdisp);
4✔
200
                        Warning(buf);
4✔
201
                        notWarnedDisp = false;
4✔
202
                }
203
        } else notWarnedDisp = true;
618,102✔
204
}
205

206
void resolveRelocationAndSmartSmc(const aint immediateOffset, Relocation::EType minType) {
33,031✔
207
        // call relocation data generator to do its own errands
208
        Relocation::resolveRelocationAffected(immediateOffset, minType);
33,031✔
209
        // check smart-SMC functionality, if there is unresolved record to be set up
210
        if (INT_MAX == immediateOffset || sourcePosStack.empty() || 0 == smartSmcIndex) return;
33,031✔
211
        if (smartSmcLines.size() < smartSmcIndex) return;
236✔
212
        auto & smartSmc = smartSmcLines.at(smartSmcIndex - 1);
234✔
213
        if (~0U != smartSmc.colBegin || smartSmc != sourcePosStack.back()) return;
234✔
214
        if (1 < sourcePosStack.back().colBegin) return;                // only first segment belongs to SMC label
214✔
215
        // record does match current line, resolve the smart offset
216
        smartSmc.colBegin = immediateOffset;
211✔
217
}
218

219
void WriteDest() {
1,263✔
220
        if (!WBLength) {
1,263✔
221
                return;
723✔
222
        }
223
        destlen += WBLength;
540✔
224
        if (FP_Output != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_Output) != WBLength) {
540✔
UNCOV
225
                Error("Write error (disk full?)", NULL, FATAL);
×
226
        }
227
        if (FP_RAW != NULL && (aint) fwrite(WriteBuffer, 1, WBLength, FP_RAW) != WBLength) {
540✔
228
                Error("Write error (disk full?)", NULL, FATAL);
×
229
        }
230

231
        if (FP_tapout)
540✔
232
        {
233
                int write_length = tape_length + WBLength > 65535 ? 65535 - tape_length : WBLength;
14✔
234

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

237
                for (int i = 0; i < write_length; i++) tape_parity ^= WriteBuffer[i];
65,693✔
238
                tape_length += write_length;
14✔
239

240
                if (write_length < WBLength)
14✔
241
                {
242
                        WBLength = 0;
1✔
243
                        CloseTapFile();
1✔
244
                        Error("Tape block exceeds maximal size");
1✔
245
                }
246
        }
247
        WBLength = 0;
540✔
248
}
249

250
void PrintHex(char* & dest, aint value, int nibbles) {
82✔
251
        if (nibbles < 1 || 8 < nibbles) ExitASM(33);        // invalid argument
82✔
252
        const char oldChAfter = dest[nibbles];
82✔
253
        const aint mask = (int(sizeof(aint)*2) <= nibbles) ? ~0L : (1L<<(nibbles*4))-1L;
82✔
254
        if (nibbles != SPRINTF2(dest, 16, "%0*X", nibbles, value&mask)) ExitASM(33);
82✔
255
        dest += nibbles;
82✔
256
        *dest = oldChAfter;
82✔
257
}
82✔
258

259
void PrintHex32(char*& dest, aint value) {
73✔
260
        PrintHex(dest, value, 8);
73✔
261
}
73✔
262

263
void PrintHexAlt(char*& dest, aint value)
6,104✔
264
{
265
        char buffer[24] = { 0 }, * bp = buffer;
6,104✔
266
        SPRINTF1(buffer, 24, "%04X", value);
6,104✔
267
        while (*bp) *dest++ = *bp++;
38,223✔
268
}
6,104✔
269

270
static char pline[4*LINEMAX];
271

272
// buffer must be at least 4*LINEMAX chars long
273
void PrepareListLine(char* buffer, aint hexadd)
31,033✔
274
{
275
        ////////////////////////////////////////////////////
276
        // Line numbers to 1 to 99999 are supported only  //
277
        // For more lines, then first char is incremented //
278
        ////////////////////////////////////////////////////
279

280
        int digit = ' ';
31,033✔
281
        int linewidth = reglenwidth;
31,033✔
282
        uint32_t currentLine = sourcePosStack.at(IncludeLevel).line;
31,033✔
283
        aint linenumber = currentLine % 10000;
31,033✔
284
        if (5 <= linewidth) {                // five-digit number, calculate the leading "digit"
31,033✔
285
                linewidth = 5;
13✔
286
                digit = currentLine / 10000 + '0';
13✔
287
                if (digit > '~') digit = '~';
13✔
288
                if (currentLine >= 10000) linenumber += 10000;
13✔
289
        }
290
        memset(buffer, ' ', 24);
31,033✔
291
        if (listmacro) buffer[23] = '>';
31,033✔
292
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType) buffer[23] = '{';
31,033✔
293
        SPRINTF2(buffer, LINEMAX, "%*u", linewidth, linenumber); buffer[linewidth] = ' ';
31,033✔
294
        memcpy(buffer + linewidth, "++++++", IncludeLevel > 6 - linewidth ? 6 - linewidth : IncludeLevel);
31,033✔
295
        SPRINTF1(buffer + 6, LINEMAX, "%04X", hexadd & 0xFFFF); buffer[10] = ' ';
31,033✔
296
        if (digit > '0') *buffer = digit & 0xFF;
31,033✔
297
        // if substitutedLine is completely empty, list rather source line any way
298
        if (!*substitutedLine) substitutedLine = line;
31,033✔
299
        STRCPY(buffer + 24, LINEMAX2-24, substitutedLine);
31,033✔
300
        // add EOL comment if substituted was used and EOL comment is available
301
        if (substitutedLine != line && eolComment) STRCAT(buffer, LINEMAX2, eolComment);
31,033✔
302
}
31,033✔
303

304
static void ListFileStringRtrim() {
31,017✔
305
        // find end of currently prepared line
306
        char* beyondLine = pline+24;
31,017✔
307
        while (*beyondLine) ++beyondLine;
699,780✔
308
        // and remove trailing white space (space, tab, newline, carriage return, etc..)
309
        while (pline < beyondLine && White(beyondLine[-1])) --beyondLine;
87,900✔
310
        // set new line and new string terminator after
311
        *beyondLine++ = '\n';
31,017✔
312
        *beyondLine = 0;
31,017✔
313
}
31,017✔
314

315
// returns FILE* handle to either actual file defined by --lst=xxx, or stderr if --msg=lst, or NULL
316
// ! do not fclose this handle, for fclose logic use the FP_ListingFile variable itself !
317
FILE* GetListingFile() {
287,262✔
318
        if (NULL != FP_ListingFile) return FP_ListingFile;
287,262✔
319
        if (OV_LST == Options::OutputVerbosity) return stderr;
122,138✔
320
        return NULL;
119,987✔
321
}
322

323
static aint lastListedLine = -1;
324

325
void ListFile(bool showAsSkipped) {
738,991✔
326
        if (LASTPASS != pass || NULL == GetListingFile() || donotlist || Options::syx.IsListingSuspended) {
738,991✔
327
                donotlist = nListBytes = 0;
610,961✔
328
                return;
610,961✔
329
        }
330
        if (showAsSkipped && Options::LST_T_ACTIVE == Options::syx.ListingType) {
128,030✔
331
                assert(nListBytes <= 0);        // inactive line should not produce any machine code?!
5✔
332
                nListBytes = 0;
5✔
333
                return;                // filter out all "inactive" lines
5✔
334
        }
335
        if (Options::LST_T_MC_ONLY == Options::syx.ListingType && nListBytes <= 0) {
128,025✔
336
                return;                // filter out all lines without machine-code bytes
100,012✔
337
        }
338
        int pos = 0;
28,013✔
339
        do {
340
                if (showAsSkipped) substitutedLine = line;        // override substituted lines in skipped mode
31,017✔
341
                PrepareListLine(pline, ListAddress);
31,017✔
342
                const bool hideSource = !showAsSkipped && (lastListedLine == CompiledCurrentLine);
31,017✔
343
                if (hideSource) pline[24] = 0;                                // hide *same* source line on sub-sequent list-lines
31,017✔
344
                lastListedLine = CompiledCurrentLine;                // remember this line as listed
31,017✔
345
                char* pp = pline + 10;
31,017✔
346
                int BtoList = (nListBytes < 4) ? nListBytes : 4;
31,017✔
347
                for (int i = 0; i < BtoList; ++i) {
65,338✔
348
                        if (-2 == ListEmittedBytes[i + pos]) pp += (memcpy(pp, "...", 3), 3);
34,321✔
349
                        else pp += SPRINTF1(pp, 4, " %02X", ListEmittedBytes[i + pos]);
34,211✔
350
                }
351
                *pp = ' ';
31,017✔
352
                if (showAsSkipped) pline[11] = '~';
31,017✔
353
                ListFileStringRtrim();
31,017✔
354
                fputs(pline, GetListingFile());
31,017✔
355
                nListBytes -= BtoList;
31,017✔
356
                ListAddress += BtoList;
31,017✔
357
                pos += BtoList;
31,017✔
358
        } while (0 < nListBytes);
31,017✔
359
        nListBytes = 0;
28,013✔
360
        ListAddress = CurAddress;                        // move ListAddress also beyond unlisted but emitted bytes
28,013✔
361
}
362

363
void ListSilentOrExternalEmits() {
674,393✔
364
        // catch silent/external emits like "sj.add_byte(0x123)" from Lua script
365
        if (0 == nListBytes) return;                // no silent/external emit happened
674,393✔
366
        ++CompiledCurrentLine;
45✔
367
        char silentOrExternalBytes[] = "; these bytes were emitted silently/externally (lua script?)";
45✔
368
        substitutedLine = silentOrExternalBytes;
45✔
369
        eolComment = nullptr;
45✔
370
        ListFile();
45✔
371
        substitutedLine = line;
45✔
372
}
373

374
static bool someByteEmitted = false;
375

376
bool DidEmitByte() {        // returns true if some byte was emitted since last call to this function
3,111✔
377
        bool didEmit = someByteEmitted;                // value to return
3,111✔
378
        someByteEmitted = false;                        // reset the flag
3,111✔
379
        return didEmit;
3,111✔
380
}
381

382
static void EmitByteNoListing(int byte, bool preserveDeviceMemory = false) {
1,982,641✔
383
        someByteEmitted = true;
1,982,641✔
384
        if (LASTPASS == pass) {
1,982,641✔
385
                WriteBuffer[WBLength++] = (char)byte;
828,230✔
386
                if (DESTBUFLEN == WBLength) WriteDest();
828,230✔
387
        }
388
        // the page-checking in device mode must be done in all passes, the slot can have "wrap" option
389
        if (DeviceID) {
1,982,641✔
390
                Device->CheckPage(CDevice::CHECK_EMIT);
1,363,636✔
391
                if (MemoryPointer) {
1,363,636✔
392
                        if (LASTPASS == pass && !preserveDeviceMemory) *MemoryPointer = (char)byte;
1,363,172✔
393
                        ++MemoryPointer;
1,363,172✔
394
                }
395
        } else {
396
                CheckRamLimitExceeded();
619,005✔
397
        }
398
        ++CurAddress;
1,982,641✔
399
        if (DISP_NONE != PseudoORG) ++adrdisp;
1,982,641✔
400
}
1,982,641✔
401

402
static bool PageDiffersWarningShown = false;
403

404
void EmitByte(int byte, bool isInstructionStart) {
667,600✔
405
        if (isInstructionStart) {
667,600✔
406
                // SLD (Source Level Debugging) tracing-data logging
407
                if (IsSldExportActive()) {
115,356✔
408
                        int pageNum = Page->Number;
681✔
409
                        if (DISP_NONE != PseudoORG) {
681✔
410
                                int mappingPageNum = Device->GetPageOfA16(CurAddress);
5✔
411
                                if (LABEL_PAGE_UNDEFINED == dispPageNum) {        // special DISP page is not set, use mapped
5✔
412
                                        pageNum = mappingPageNum;
1✔
413
                                } else {
414
                                        pageNum = dispPageNum;                                        // special DISP page is set, use it instead
4✔
415
                                        if (pageNum != mappingPageNum && !PageDiffersWarningShown) {
4✔
416
                                                WarningById(W_DISP_MEM_PAGE);
1✔
417
                                                PageDiffersWarningShown = true;                // show warning about different mapping only once
1✔
418
                                        }
419
                                }
420
                        }
421
                        WriteToSldFile(pageNum, CurAddress);
681✔
422
                }
423
        }
424
        byte &= 0xFF;
667,600✔
425
        if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE-1) {
667,600✔
426
                ListEmittedBytes[nListBytes++] = byte;                // write also into listing
666,202✔
427
        } else {
428
                if (nListBytes < LIST_EMIT_BYTES_BUFFER_SIZE) {
1,398✔
429
                        // too many bytes, show it in listing as "..."
430
                        ListEmittedBytes[nListBytes++] = -2;
3✔
431
                }
432
        }
433
        EmitByteNoListing(byte);
667,600✔
434
}
667,600✔
435

436
void EmitWord(int word, bool isInstructionStart) {
14,960✔
437
        EmitByte(word % 256, isInstructionStart);
14,960✔
438
        EmitByte(word / 256, false);
14,960✔
439
}
14,960✔
440

441
void EmitBytes(const int* bytes, bool isInstructionStart) {
174,248✔
442
        if (BYTES_END_MARKER == *bytes) {
174,248✔
443
                Error("Illegal instruction", line, IF_FIRST);
4,203✔
444
                SkipToEol(lp);
4,203✔
445
        }
446
        while (BYTES_END_MARKER != *bytes) {
591,369✔
447
                EmitByte(*bytes++, isInstructionStart);
417,121✔
448
                isInstructionStart = (INSTRUCTION_START_MARKER == *bytes);        // only true for first byte, or when marker
417,121✔
449
                if (isInstructionStart) ++bytes;
417,121✔
450
        }
451
}
174,248✔
452

453
void EmitWords(const int* words, bool isInstructionStart) {
4,585✔
454
        while (BYTES_END_MARKER != *words) {
14,522✔
455
                EmitWord(*words++, isInstructionStart);
9,937✔
456
                isInstructionStart = false;                // only true for first word
9,937✔
457
        }
458
}
4,585✔
459

460
void EmitBlock(aint byte, aint len, bool preserveDeviceMemory, int emitMaxToListing) {
2,020✔
461
        if (len <= 0) {
2,020✔
462
                const aint adrMask = Options::IsLongPtr ? ~0 : 0xFFFF;
36✔
463
                CurAddress = (CurAddress + len) & adrMask;
36✔
464
                if (DISP_NONE != PseudoORG) adrdisp = (adrdisp + len) & adrMask;
36✔
465
                if (DeviceID)        Device->CheckPage(CDevice::CHECK_NO_EMIT);
36✔
466
                else                        CheckRamLimitExceeded();
18✔
467
                return;
36✔
468
        }
469
        if (LIST_EMIT_BYTES_BUFFER_SIZE <= nListBytes + emitMaxToListing) {        // clamp emit to list buffer
1,984✔
UNCOV
470
                emitMaxToListing = LIST_EMIT_BYTES_BUFFER_SIZE - nListBytes;
×
471
        }
472
        while (len--) {
1,067,524✔
473
                int dVal = (preserveDeviceMemory && DeviceID && MemoryPointer) ? MemoryPointer[0] : byte;
1,065,540✔
474
                EmitByteNoListing(byte, preserveDeviceMemory);
1,065,540✔
475
                if (LASTPASS == pass && emitMaxToListing) {
1,065,540✔
476
                        // put "..." marker into listing if some more bytes are emitted after last listed
477
                        if ((0 == --emitMaxToListing) && len) ListEmittedBytes[nListBytes++] = -2;
2,062✔
478
                        else ListEmittedBytes[nListBytes++] = dVal&0xFF;
1,872✔
479
                }
480
        }
481
}
482

483
// if offset is negative, it functions as "how many bytes from end of file"
484
// if length is negative, it functions as "how many bytes from end of file to not load"
485
void BinIncFile(fullpath_ref_t file, aint offset, aint length) {
174✔
486
        // open the desired file
487
        FILE* bif;
488
        if (!FOPEN_ISOK(bif, file.full, "rb")) Error("opening file", file.str.c_str());
174✔
489

490
        // Get length of file
491
        int totlen = 0, advanceLength;
174✔
492
        if (bif && (fseek(bif, 0, SEEK_END) || (totlen = ftell(bif)) < 0)) Error("telling file length", file.str.c_str(), FATAL);
174✔
493

494
        // process arguments (extra features like negative offset/length or INT_MAX length)
495
        // negative offset means "from the end of file"
496
        if (offset < 0) offset += totlen;
174✔
497
        // negative length means "except that many from end of file"
498
        if (length < 0) length += totlen - offset;
174✔
499
        // default length INT_MAX is "till the end of file"
500
        if (INT_MAX == length) length = totlen - offset;
174✔
501
        // verbose output of final values (before validation may terminate assembler)
502
        if (LASTPASS == pass && Options::OutputVerbosity <= OV_ALL) {
174✔
503
                char diagnosticTxt[MAX_PATH];
504
                SPRINTF4(diagnosticTxt, MAX_PATH, "include data: name=%s (%d bytes) Offset=%d  Len=%d", file.str.c_str(), totlen, offset, length);
3✔
505
                _CERR diagnosticTxt _ENDL;
3✔
506
        }
507
        // validate the resulting [offset, length]
508
        if (offset < 0 || length < 0 || totlen < offset + length) {
174✔
509
                Error("file too short", file.str.c_str());
12✔
510
                offset = std::clamp(offset, 0, totlen);
12✔
511
                length = std::clamp(length, 0, totlen - offset);
12✔
512
                assert((0 <= offset) && (offset + length <= totlen));
12✔
513
        }
514
        if (0 == length) {
174✔
515
                Warning("include data: requested to include no data (length=0)");
18✔
516
                if (bif) fclose(bif);
18✔
517
                return;
18✔
518
        }
519
        assert(nullptr != bif);                                // otherwise it was handled by 0 == length case above
156✔
520

521
        if (pass != LASTPASS) {
156✔
522
                while (length) {
246✔
523
                        advanceLength = length;                // maximum possible to advance in address space
142✔
524
                        if (DeviceID) {                                // particular device may adjust that to less
142✔
525
                                Device->CheckPage(CDevice::CHECK_EMIT);
60✔
526
                                if (MemoryPointer) {        // fill up current memory page if possible
60✔
527
                                        advanceLength = Page->RAM + Page->Size - MemoryPointer;
56✔
528
                                        if (length < advanceLength) advanceLength = length;
56✔
529
                                        MemoryPointer += advanceLength;                // also update it! Doh!
56✔
530
                                }
531
                        }
532
                        length -= advanceLength;
142✔
533
                        if (length <= 0 && 0 == advanceLength) Error("BinIncFile internal error", NULL, FATAL);
142✔
534
                        if (DISP_NONE != PseudoORG) adrdisp = adrdisp + advanceLength;
142✔
535
                        CurAddress = CurAddress + advanceLength;
142✔
536
                }
537
        } else {
538
                // Seek to the beginning of part to include
539
                if (fseek(bif, offset, SEEK_SET) || ftell(bif) != offset) {
52✔
UNCOV
540
                        Error("seeking in file to offset", file.str.c_str(), FATAL);
×
541
                }
542

543
                // Reading data from file
544
                char* data = new char[length + 1], * bp = data;
52✔
545
                if (NULL == data) ErrorOOM();
52✔
546
                size_t res = fread(bp, 1, length, bif);
52✔
547
                if (res != (size_t)length) Error("reading data from file failed", file.str.c_str(), FATAL);
52✔
548
                while (length--) EmitByteNoListing(*bp++);
249,553✔
549
                delete[] data;
52✔
550
        }
551
        fclose(bif);
156✔
552
}
553

554
static void OpenDefaultList(fullpath_ref_t inputFile);
555

556
static stdin_log_t::const_iterator stdin_read_it;
557
static stdin_log_t* stdin_log = nullptr;
558

559
void OpenFile(fullpath_ref_t nfilename, stdin_log_t* fStdinLog)
2,909✔
560
{
561
        if (++IncludeLevel > 20) {
2,909✔
562
                Error("Over 20 files nested", NULL, ALL);
6✔
563
                --IncludeLevel;
6✔
564
                return;
6✔
565
        }
566
        assert(!fStdinLog || nfilename.full.empty());
2,903✔
567
        if (fStdinLog) {
2,903✔
568
                FP_Input = stdin;
15✔
569
                stdin_log = fStdinLog;
15✔
570
                stdin_read_it = stdin_log->cbegin();        // reset read iterator (for 2nd+ pass)
15✔
571
        } else {
572
                if (!FOPEN_ISOK(FP_Input, nfilename.full, "rb")) {
2,888✔
573
                        Error("opening file", nfilename.str.c_str(), ALL);
9✔
574
                        --IncludeLevel;
9✔
575
                        return;
9✔
576
                }
577
        }
578

579
        fullpath_p_t oFileNameFull = fileNameFull;
2,894✔
580

581
        // archive the filename (for referencing it in SLD tracing data or listing/errors)
582
        fileNameFull = &nfilename;
2,894✔
583
        sourcePosStack.emplace_back(nfilename.str.c_str());
2,894✔
584

585
        // refresh pre-defined values related to file/include
586
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,894✔
587
        DefineTable.Replace("__FILE__", nfilename.str.c_str());
2,894✔
588
        if (0 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", nfilename.str.c_str());
2,894✔
589

590
        // open default listing file for each new source file (if default listing is ON) / explicit listing is already opened
591
        if (LASTPASS == pass && 0 == IncludeLevel && Options::IsDefaultListingName) OpenDefaultList(nfilename);
2,894✔
592
        // show in listing file which file was opened
593
        FILE* listFile = GetListingFile();
2,894✔
594
        if (LASTPASS == pass && listFile) {
2,894✔
595
                fputs("# file opened: ", listFile);
505✔
596
                fputs(nfilename.str.c_str(), listFile);
505✔
597
                fputs("\n", listFile);
505✔
598
        }
599

600
        rlpbuf = rlpbuf_end = rlbuf;
2,894✔
601
        colonSubline = false;
2,894✔
602
        blockComment = 0;
2,894✔
603

604
        ReadBufLine();
2,894✔
605

606
        if (stdin != FP_Input) fclose(FP_Input);
2,885✔
607
        else {
608
                if (1 == pass) {
15✔
609
                        stdin_log->push_back(0);        // add extra zero terminator
5✔
610
                        clearerr(stdin);                        // reset EOF on the stdin for another round of input
5✔
611
                }
612
        }
613

614
        // show in listing file which file was closed
615
        if (LASTPASS == pass && listFile) {
2,885✔
616
                fputs("# file closed: ", listFile);
505✔
617
                fputs(nfilename.str.c_str(), listFile);
505✔
618
                fputs("\n", listFile);
505✔
619

620
                // close listing file (if "default" listing filename is used)
621
                if (FP_ListingFile && 0 == IncludeLevel && Options::IsDefaultListingName) {
505✔
622
                        if (Options::AddLabelListing) LabelTable.Dump();
1✔
623
                        fclose(FP_ListingFile);
1✔
624
                        FP_ListingFile = NULL;
1✔
625
                }
626
        }
627

628
        --IncludeLevel;
2,885✔
629

630
        maxlin = std::max(maxlin, sourcePosStack.back().line);
2,885✔
631
        sourcePosStack.pop_back();
2,885✔
632
        fileNameFull = oFileNameFull;
2,885✔
633

634
        // refresh pre-defined values related to file/include
635
        DefineTable.Replace("__INCLUDE_LEVEL__", IncludeLevel);
2,885✔
636
        DefineTable.Replace("__FILE__", fileNameFull ? fileNameFull->str.c_str() : "<none>");
2,885✔
637
        if (-1 == IncludeLevel) DefineTable.Replace("__BASE_FILE__", "<none>");
2,885✔
638
}
639

640
void IncludeFile(fullpath_ref_t nfilename)
579✔
641
{
642
        auto oStdin_log = stdin_log;
579✔
643
        auto oStdin_read_it = stdin_read_it;
579✔
644
        FILE* oFP_Input = FP_Input;
579✔
645
        FP_Input = 0;
579✔
646

647
        char* pbuf = rlpbuf, * pbuf_end = rlpbuf_end, * buf = STRDUP(rlbuf);
579✔
648
        if (buf == NULL) ErrorOOM();
579✔
649
        bool oColonSubline = colonSubline;
579✔
650
        if (blockComment) Error("Internal error 'block comment'", NULL, FATAL);        // comment can't INCLUDE
579✔
651

652
        OpenFile(nfilename);
579✔
653

654
        colonSubline = oColonSubline;
579✔
655
        rlpbuf = pbuf, rlpbuf_end = pbuf_end;
579✔
656
        STRCPY(rlbuf, 8192, buf);
579✔
657
        free(buf);
579✔
658

659
        FP_Input = oFP_Input;
579✔
660
        stdin_log = oStdin_log;
579✔
661
        stdin_read_it = oStdin_read_it;
579✔
662
}
579✔
663

664
typedef struct {
665
        char        name[12];
666
        size_t        length;
667
        byte        marker[16];
668
} BOMmarkerDef;
669

670
const BOMmarkerDef UtfBomMarkers[] = {
671
        { { "UTF8" }, 3, { 0xEF, 0xBB, 0xBF } },
672
        { { "UTF32BE" }, 4, { 0, 0, 0xFE, 0xFF } },
673
        { { "UTF32LE" }, 4, { 0xFF, 0xFE, 0, 0 } },                // must be detected *BEFORE* UTF16LE
674
        { { "UTF16BE" }, 2, { 0xFE, 0xFF } },
675
        { { "UTF16LE" }, 2, { 0xFF, 0xFE } }
676
};
677

678
static bool ReadBufData() {
7,273,358✔
679
        // check here also if `line` buffer is not full
680
        if ((LINEMAX-2) <= (rlppos - line)) Error("Line too long", NULL, FATAL);
7,273,358✔
681
        // now check for read data
682
        if (rlpbuf < rlpbuf_end) return 1;                // some data still in buffer
7,273,358✔
683
        // check EOF on files in every pass, stdin only in first, following will starve the stdin_log
684
        if ((stdin != FP_Input || 1 == pass) && feof(FP_Input)) return 0;        // no more data in file
17,602✔
685
        // read next block of data
686
        rlpbuf = rlbuf;
3,945✔
687
        // handle STDIN file differently (pass1 = read it, pass2+ replay "log" variable)
688
        if (1 == pass || stdin != FP_Input) {        // ordinary file is re-read every pass normally
3,945✔
689
                rlpbuf_end = rlbuf + fread(rlbuf, 1, 4096, FP_Input);
3,895✔
690
                *rlpbuf_end = 0;                                        // add zero terminator after new block
3,895✔
691
        }
692
        if (stdin == FP_Input) {
3,945✔
693
                // store copy of stdin into stdin_log during pass 1
694
                if (1 == pass && rlpbuf < rlpbuf_end) {
55✔
695
                        stdin_log->insert(stdin_log->end(), rlpbuf, rlpbuf_end);
5✔
696
                }
697
                // replay the log in 2nd+ pass
698
                if (1 < pass) {
55✔
699
                        rlpbuf_end = rlpbuf;
50✔
700
                        long toCopy = std::min(8000L, (long)std::distance(stdin_read_it, stdin_log->cend()));
100✔
701
                        if (0 < toCopy) {
50✔
702
                                memcpy(rlbuf, &(*stdin_read_it), toCopy);
10✔
703
                                stdin_read_it += toCopy;
10✔
704
                                rlpbuf_end += toCopy;
10✔
705
                        }
706
                        *rlpbuf_end = 0;                                // add zero terminator after new block
50✔
707
                }
708
        }
709
        // check UTF BOM markers only at the beginning of the file (source line == 0)
710
        assert(!sourcePosStack.empty());
3,945✔
711
        if (sourcePosStack.back().line) {
3,945✔
712
                return (rlpbuf < rlpbuf_end);                // return true if some data were read
1,051✔
713
        }
714
        //UTF BOM markers detector
715
        for (const auto & bomMarkerData : UtfBomMarkers) {
17,354✔
716
                if (rlpbuf_end < (rlpbuf + bomMarkerData.length)) continue;        // not enough bytes in buffer
14,464✔
717
                if (memcmp(rlpbuf, bomMarkerData.marker, bomMarkerData.length)) continue;        // marker not found
14,464✔
718
                if (&bomMarkerData != UtfBomMarkers) {        // UTF8 is first in the array, other markers show error
7✔
719
                        Error("Invalid UTF encoding detected (only ASCII and UTF8 works)", bomMarkerData.name, FATAL);
4✔
720
                }
721
                rlpbuf += bomMarkerData.length;        // skip the UTF8 BOM marker
3✔
722
        }
723
        return (rlpbuf < rlpbuf_end);                        // return true if some data were read
2,890✔
724
}
725

726
void ReadBufLine(bool Parse, bool SplitByColon) {
21,857✔
727
        // if everything else fails (no data, not running, etc), return empty line
728
        *line = 0;
21,857✔
729
        bool IsLabel = true;
21,857✔
730
        // try to read through the buffer and produce new line from it
731
        while (IsRunning && ReadBufData()) {
238,949✔
732
                // start of new line (or fake "line" by colon)
733
                rlppos = line;
236,060✔
734
                substitutedLine = line;                // also reset "substituted" line to the raw new one
236,060✔
735
                eolComment = NULL;
236,060✔
736
                if (colonSubline) {                        // starting from colon (creating new fake "line")
236,060✔
737
                        colonSubline = false;        // (can't happen inside block comment)
10,854✔
738
                        *(rlppos++) = ' ';
10,854✔
739
                        IsLabel = false;
10,854✔
740
                } else {                                        // starting real new line
741
                        IsLabel = (0 == blockComment);
225,206✔
742
                }
743
                bool afterNonAlphaNum, afterNonAlphaNumNext = true;
236,060✔
744
                // copy data from read buffer into `line` buffer until EOL/colon is found
745
                while (
236,060✔
746
                                ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf &&        // split by EOL
6,541,239✔
747
                                // split by colon only on 2nd+ char && SplitByColon && not inside block comment
748
                                (blockComment || !SplitByColon || rlppos == line || ':' != *rlpbuf)) {
3,158,024✔
749
                        // copy the new character to new line
750
                        *rlppos = *rlpbuf++;
3,147,155✔
751
                        afterNonAlphaNum = afterNonAlphaNumNext;
3,147,155✔
752
                        afterNonAlphaNumNext = !isalnum((byte)*rlppos);
3,147,155✔
753
                        // handle EOL escaping, limited implementation, usage not recommended
754
                        if ('\\' == *rlppos && ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf))  {
3,147,155✔
755
                                char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
42✔
756
                                if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
42✔
757
                                sourcePosStack.back().nextSegment();        // mark last line in errors/etc
42✔
758
                                continue;                                                                // continue with chars from next line
42✔
759
                        }
42✔
760
                        // Block comments logic first (anything serious may happen only "outside" of block comment
761
                        if ('*' == *rlppos && ReadBufData() && '/' == *rlpbuf) {
3,147,113✔
762
                                if (0 < blockComment) --blockComment;        // block comment ends here, -1 from nesting
438✔
763
                                ++rlppos;        *rlppos++ = *rlpbuf++;                // copy the second char too
438✔
764
                                continue;
438✔
765
                        }
766
                        if ('/' == *rlppos && ReadBufData() && '*' == *rlpbuf) {
3,146,675✔
767
                                ++rlppos, ++blockComment;                                // block comment starts here, nest +1 more
435✔
768
                                *rlppos++ = *rlpbuf++;                                        // copy the second char too
435✔
769
                                continue;
435✔
770
                        }
771
                        if (blockComment) {                                                        // inside block comment just copy chars
3,146,240✔
772
                                ++rlppos;
5,574✔
773
                                continue;
5,574✔
774
                        }
775
                        // check if still in label area, if yes, copy the finishing colon as char (don't split by it)
776
                        if ((IsLabel = (IsLabel && islabchar(*rlppos)))) {
3,140,666✔
777
                                ++rlppos;                                        // label character
206,166✔
778
                                //SMC offset handling
779
                                if (ReadBufData() && '+' == *rlpbuf) {        // '+' after label, add it as SMC_offset syntax
206,166✔
780
                                        IsLabel = false;
483✔
781
                                        *rlppos++ = *rlpbuf++;
483✔
782
                                        if (ReadBufData() && (isdigit(byte(*rlpbuf)) || '*' == *rlpbuf)) *rlppos++ = *rlpbuf++;
483✔
783
                                }
784
                                if (ReadBufData() && ':' == *rlpbuf) {        // colon after label, add it
206,166✔
785
                                        *rlppos++ = *rlpbuf++;
13,293✔
786
                                        IsLabel = false;
13,293✔
787
                                }
788
                                continue;
206,166✔
789
                        }
790
                        // not in label any more, check for EOL comments ";" or "//"
791
                        if ((';' == *rlppos) || ('/' == *rlppos && ReadBufData() && '/' == *rlpbuf)) {
2,934,500✔
792
                                eolComment = rlppos;
78,047✔
793
                                ++rlppos;                                        // EOL comment ";"
78,047✔
794
                                while (ReadBufData() && '\n' != *rlpbuf && '\r' != *rlpbuf) *rlppos++ = *rlpbuf++;
2,390,802✔
795
                                continue;
78,047✔
796
                        }
797
                        // check for string literals - double/single quotes
798
                        if (afterNonAlphaNum && ('"' == *rlppos || '\'' == *rlppos)) {
2,856,453✔
799
                                const bool quotes = '"' == *rlppos;
14,448✔
800
                                int escaped = 0;
14,448✔
801
                                do {
802
                                        if (escaped) --escaped;
128,391✔
803
                                        ++rlppos;                                // previous char confirmed
128,391✔
804
                                        *rlppos = ReadBufData() ? *rlpbuf : 0;        // copy next char (if available)
128,391✔
805
                                        if (!*rlppos || '\r' == *rlppos || '\n' == *rlppos) *rlppos = 0;        // not valid
128,391✔
806
                                        else ++rlpbuf;                        // buffer char read (accepted)
128,310✔
807
                                        if (quotes && !escaped && '\\' == *rlppos) escaped = 2;        // escape sequence detected
128,391✔
808
                                } while (*rlppos && (escaped || (quotes ? '"' : '\'') != *rlppos));
128,391✔
809
                                if (*rlppos) ++rlppos;                // there should be ending "/' in line buffer, confirm it
14,448✔
810
                                continue;
14,448✔
811
                        }
14,448✔
812
                        // anything else just copy
813
                        ++rlppos;                                // previous char confirmed
2,842,005✔
814
                } // while "some char in buffer, and it's not line delimiter"
815
                // line interrupted somehow, may be correctly finished, check + finalize line and process it
816
                *rlppos = 0;
236,060✔
817
                // skip <EOL> char sequence in read buffer
818
                if (ReadBufData() && ('\r' == *rlpbuf || '\n' == *rlpbuf)) {
236,060✔
819
                        char CRLFtest = (*rlpbuf++) ^ ('\r'^'\n');        // flip CR->LF || LF->CR (and eats first)
224,953✔
820
                        if (ReadBufData() && CRLFtest == *rlpbuf) ++rlpbuf;        // if CRLF/LFCR pair, eat also second
224,953✔
821
                        // if this was very last <EOL> in file (on non-empty line), add one more fake empty line
822
                        if (!ReadBufData() && *line) *rlpbuf_end++ = '\n';        // to make listing files "as before"
224,953✔
823
                } else {
824
                        // advance over single colon if that was the reason to terminate line parsing
825
                        colonSubline = SplitByColon && ReadBufData() && (':' == *rlpbuf) && ++rlpbuf;
11,107✔
826
                }
827
                // do +1 for very first colon-segment only (rest is +1 due to artificial space at beginning)
828
                assert(!sourcePosStack.empty());
236,060✔
829
                size_t advanceColumns = colonSubline ? (0 == sourcePosStack.back().colEnd) + strlen(line) : 0;
236,060✔
830
                sourcePosStack.back().nextSegment(colonSubline, advanceColumns);
236,060✔
831
                // line is parsed and ready to be processed
832
                if (Parse)         ParseLine();        // processed here in loop
236,060✔
833
                else                 return;                        // processed externally
18,963✔
834
        } // while (IsRunning && ReadBufData())
835
}
836

837
static void OpenListImp(const std::filesystem::path & listFilename) {
473✔
838
        // if STDERR is configured to contain listing, disable other listing files
839
        if (OV_LST == Options::OutputVerbosity) return;
473✔
840
        if (listFilename.empty()) return;
473✔
841
        if (!FOPEN_ISOK(FP_ListingFile, listFilename, "w")) {
314✔
UNCOV
842
                Error("opening file for write", listFilename.string().c_str(), FATAL);
×
843
        }
844
}
845

846
void OpenList() {
514✔
847
        // if STDERR is configured to contain listing, disable other listing files
848
        if (OV_LST == Options::OutputVerbosity) return;
514✔
849
        // check if listing file is already opened, or it is set to "default" file names
850
        if (Options::IsDefaultListingName || NULL != FP_ListingFile) return;
474✔
851
        // Only explicit listing files are opened here
852
        OpenListImp(Options::ListingFName);
472✔
853
}
854

855
static void OpenDefaultList(fullpath_ref_t inputFile) {
2✔
856
        // if STDERR is configured to contain listing, disable other listing files
857
        if (OV_LST == Options::OutputVerbosity) return;
3✔
858
        // check if listing file is already opened, or it is set to explicit file name
859
        if (!Options::IsDefaultListingName || NULL != FP_ListingFile) return;
2✔
860
        if (inputFile.full.empty()) return;                // no filename provided
2✔
861
        // Create default listing name, and try to open it
862
        std::filesystem::path listName { inputFile.full };
1✔
863
        listName.replace_extension("lst");
1✔
864
        OpenListImp(listName);
1✔
865
}
1✔
866

867
void CloseDest() {
642✔
868
        // Flush buffer before any other operations
869
        WriteDest();
642✔
870
        // does main output file exist? (to close it)
871
        if (FP_Output == NULL) return;
642✔
872
        // pad to desired size (and check for exceed of it)
873
        if (size != -1L) {
117✔
874
                if (destlen > size) {
4✔
875
                        ErrorInt("File exceeds 'size' by", destlen - size);
1✔
876
                }
877
                memset(WriteBuffer, 0, DESTBUFLEN);
4✔
878
                while (destlen < size) {
7✔
879
                        WBLength = std::min(aint(DESTBUFLEN), size-destlen);
3✔
880
                        WriteDest();
3✔
881
                }
882
                size = -1L;
4✔
883
        }
884
        fclose(FP_Output);
117✔
885
        FP_Output = NULL;
117✔
886
}
887

888
void SeekDest(long offset, int method) {
5✔
889
        WriteDest();
5✔
890
        if (FP_Output != NULL && fseek(FP_Output, offset, method)) {
5✔
UNCOV
891
                Error("File seek error (FPOS)", NULL, FATAL);
×
892
        }
893
}
5✔
894

895
void NewDest(const std::filesystem::path & newfilename, int mode) {
117✔
896
        // close previous output file
897
        CloseDest();
117✔
898

899
        // and open new file (keep previous/default name, if no explicit was provided)
900
        if (!newfilename.empty()) Options::DestinationFName = newfilename;
117✔
901
        OpenDest(mode);
117✔
902
}
117✔
903

904
void OpenDest(int mode) {
623✔
905
        destlen = 0;
623✔
906
        if (mode != OUTPUT_TRUNCATE && !FileExists(Options::DestinationFName)) {
623✔
907
                mode = OUTPUT_TRUNCATE;
1✔
908
        }
909
        if (!Options::NoDestinationFile && !FOPEN_ISOK(FP_Output, Options::DestinationFName, mode == OUTPUT_TRUNCATE ? "wb" : "r+b")) {
623✔
UNCOV
910
                Error("opening file for write", Options::DestinationFName.string().c_str(), FATAL);
×
911
        }
912
        Options::NoDestinationFile = false;
623✔
913
        if (NULL == FP_RAW && "-" == Options::RAWFName) {
623✔
914
                FP_RAW = stdout;
1✔
915
                fflush(stdout);
1✔
916
                switchStdOutIntoBinaryMode();
1✔
917
        }
918
        if (FP_RAW == NULL && Options::RAWFName.has_filename() && !FOPEN_ISOK(FP_RAW, Options::RAWFName, "wb")) {
623✔
UNCOV
919
                Error("opening file for write", Options::RAWFName.string().c_str());
×
920
        }
921
        if (FP_Output != NULL && mode != OUTPUT_TRUNCATE) {
623✔
922
                if (fseek(FP_Output, 0, mode == OUTPUT_REWIND ? SEEK_SET : SEEK_END)) {
3✔
UNCOV
923
                        Error("File seek error (OUTPUT)", NULL, FATAL);
×
924
                }
925
        }
926
}
623✔
927

928
void CloseTapFile()
524✔
929
{
930
        char tap_data[2];
931

932
        WriteDest();
524✔
933
        if (FP_tapout == NULL) return;
524✔
934

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

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

940
        tap_data[0] =  tape_length     & 0xFF;
7✔
941
        tap_data[1] = (tape_length>>8) & 0xFF;
7✔
942
        if (fwrite(tap_data, 1, 2, FP_tapout) != 2) Error("Write error (disk full?)", NULL, FATAL);
7✔
943

944
        fclose(FP_tapout);
7✔
945
        FP_tapout = NULL;
7✔
946
}
947

948
void OpenTapFile(const std::filesystem::path & tapename, int flagbyte)
9✔
949
{
950
        CloseTapFile();
9✔
951

952
        if (!FOPEN_ISOK(FP_tapout,tapename, "r+b")) {
9✔
953
                Error( "opening file for write", tapename.string().c_str());
2✔
954
                return;
2✔
955
        }
956
        if (fseek(FP_tapout, 0, SEEK_END)) Error("File seek end error in TAPOUT", tapename.string().c_str(), FATAL);
7✔
957

958
        tape_seek = ftell(FP_tapout);
7✔
959
        tape_parity = flagbyte;
7✔
960
        tape_length = 2;
7✔
961

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

964
        if (sizeof(tap_data) != fwrite(tap_data, 1, sizeof(tap_data), FP_tapout)) {
7✔
UNCOV
965
                fclose(FP_tapout);
×
UNCOV
966
                Error("Write error (disk full?)", NULL, FATAL);
×
967
        }
968
}
969

970
// check if file exists and can be read for content
971
bool FileExists(const std::filesystem::path & file_name) {
213✔
972
        return        std::filesystem::exists(file_name) && (
367✔
973
                std::filesystem::is_regular_file(file_name) ||
154✔
UNCOV
974
                std::filesystem::is_character_file(file_name)        // true for very rare files like /dev/null
×
975
        );
213✔
976
}
977

978
bool FileExistsCstr(const char* file_name) {
17✔
979
        if (nullptr == file_name) return false;
17✔
980
        return FileExists(std::filesystem::path(file_name));
16✔
981
}
982

983
void Close() {
506✔
984
        if (*ModuleName) {
506✔
985
                Warning("ENDMODULE missing for module", ModuleName, W_ALL);
2✔
986
        }
987

988
        CloseDest();
506✔
989
        CloseTapFile();
506✔
990
        if (FP_ExportFile != NULL) {
506✔
991
                fclose(FP_ExportFile);
5✔
992
                FP_ExportFile = NULL;
5✔
993
        }
994
        if (FP_RAW != NULL) {
506✔
995
                if (stdout != FP_RAW) fclose(FP_RAW);
4✔
996
                FP_RAW = NULL;
4✔
997
        }
998
        if (FP_ListingFile != NULL) {
506✔
999
                fclose(FP_ListingFile);
305✔
1000
                FP_ListingFile = NULL;
305✔
1001
        }
1002
        CloseSld();
506✔
1003
        CloseBreakpointsFile();
506✔
1004
}
506✔
1005

1006
int SaveRAM(FILE* ff, int start, int length) {
202✔
1007
        //unsigned int addadr = 0,save = 0;
1008
        aint save = 0;
202✔
1009
        if (!DeviceID) return 0;                // unreachable currently
202✔
1010
        if (length + start > 0x10000) {
202✔
1011
                length = -1;
1✔
1012
        }
1013
        if (length <= 0) {
202✔
1014
                length = 0x10000 - start;
2✔
1015
        }
1016

1017
        CDeviceSlot* S;
1018
        for (int i=0;i<Device->SlotsCount;i++) {
466✔
1019
                S = Device->GetSlot(i);
466✔
1020
                if (start >= (int)S->Address  && start < (int)(S->Address + S->Size)) {
466✔
1021
                        if (length < (int)(S->Size - (start - S->Address))) {
214✔
1022
                                save = length;
189✔
1023
                        } else {
1024
                                save = S->Size - (start - S->Address);
25✔
1025
                        }
1026
                        if ((aint) fwrite(S->Page->RAM + (start - S->Address), 1, save, ff) != save) {
214✔
UNCOV
1027
                                return 0;
×
1028
                        }
1029
                        length -= save;
214✔
1030
                        start += save;
214✔
1031
                        if (length <= 0) {
214✔
1032
                                return 1;
202✔
1033
                        }
1034
                }
1035
        }
UNCOV
1036
        return 0;                // unreachable (with current devices)
×
1037
}
1038

1039
unsigned int MemGetWord(unsigned int address) {
10✔
1040
        return MemGetByte(address) + (MemGetByte(address+1)<<8);
10✔
1041
}
1042

1043
unsigned char MemGetByte(unsigned int address) {
22,304✔
1044
        if (!DeviceID || pass != LASTPASS) {
22,304✔
1045
                return 0;
14,864✔
1046
        }
1047

1048
        CDeviceSlot* S;
1049
        for (int i=0;i<Device->SlotsCount;i++) {
8,974✔
1050
                S = Device->GetSlot(i);
8,971✔
1051
                if (address >= (unsigned int)S->Address  && address < (unsigned int)S->Address + (unsigned int)S->Size) {
8,971✔
1052
                        return S->Page->RAM[address - S->Address];
7,437✔
1053
                }
1054
        }
1055

1056
        ErrorInt("MemGetByte: Error reading address", address);
3✔
1057
        return 0;
3✔
1058
}
1059

1060

1061
int SaveBinary(const std::filesystem::path & fname, aint start, aint length) {
17✔
1062
        FILE* ff;
1063
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
17✔
1064
        int result = SaveRAM(ff, start, length);
17✔
1065
        fclose(ff);
17✔
1066
        return result;
17✔
1067
}
1068

1069

1070
int SaveBinary3dos(const std::filesystem::path & fname, aint start, aint length, byte type, word w2, word w3) {
6✔
1071
        FILE* ff;
1072
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
6✔
1073
        // prepare +3DOS 128 byte header content
1074
        constexpr aint hsize = 128;
6✔
1075
        const aint full_length = hsize + length;
6✔
1076
        byte sum = 0, p3dos_header[hsize] { "PLUS3DOS\032\001" };
6✔
1077
        p3dos_header[11] = byte(full_length>>0);
6✔
1078
        p3dos_header[12] = byte(full_length>>8);
6✔
1079
        p3dos_header[13] = byte(full_length>>16);
6✔
1080
        p3dos_header[14] = byte(full_length>>24);
6✔
1081
        // +3 BASIC 8 byte header filled with "relevant values"
1082
        p3dos_header[15+0] = type;
6✔
1083
        p3dos_header[15+1] = byte(length>>0);
6✔
1084
        p3dos_header[15+2] = byte(length>>8);
6✔
1085
        p3dos_header[15+3] = byte(w2>>0);
6✔
1086
        p3dos_header[15+4] = byte(w2>>8);
6✔
1087
        p3dos_header[15+5] = byte(w3>>0);
6✔
1088
        p3dos_header[15+6] = byte(w3>>8);
6✔
1089
        // calculat checksum of the header
1090
        for (const byte v : p3dos_header) sum += v;
774✔
1091
        p3dos_header[hsize-1] = sum;
6✔
1092
        // write header and data
1093
        int result = (hsize == (aint) fwrite(p3dos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1094
        fclose(ff);
6✔
1095
        return result;
6✔
1096
}
1097

1098

1099
int SaveBinaryAmsdos(const std::filesystem::path & fname, aint start, aint length, word start_adr, byte type) {
7✔
1100
        FILE* ff;
1101
        if (!FOPEN_ISOK(ff, fname, "wb")) {
7✔
1102
                Error("opening file for write", fname.string().c_str(), SUPPRESS);
1✔
1103
                return 0;
1✔
1104
        }
1105
        // prepare AMSDOS 128 byte header content
1106
        constexpr aint hsize = 128;
6✔
1107
        byte amsdos_header[hsize] {};        // all zeroed (user_number and filename stay like that, just zeroes)
6✔
1108
        amsdos_header[0x12] = type;
6✔
1109
        amsdos_header[0x15] = byte(start>>0);
6✔
1110
        amsdos_header[0x16] = byte(start>>8);
6✔
1111
        amsdos_header[0x18] = amsdos_header[0x40] = byte(length>>0);
6✔
1112
        amsdos_header[0x19] = amsdos_header[0x41] = byte(length>>8);
6✔
1113
        amsdos_header[0x1A] = byte(start_adr>>0);
6✔
1114
        amsdos_header[0x1B] = byte(start_adr>>8);
6✔
1115
        // calculat checksum of the header
1116
        word sum = 0;
6✔
1117
        for (int ii = 0x43; ii--; ) sum += amsdos_header[ii];
408✔
1118
        amsdos_header[0x43] = byte(sum>>0);
6✔
1119
        amsdos_header[0x44] = byte(sum>>8);
6✔
1120
        // write header and data
1121
        int result = (hsize == (aint) fwrite(amsdos_header, 1, hsize, ff)) ? SaveRAM(ff, start, length) : 0;
6✔
1122
        fclose(ff);
6✔
1123
        return result;
6✔
1124
}
1125

1126

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

1132

1133
// start and length must be sanitized by caller
1134
bool SaveDeviceMemory(const std::filesystem::path & fname, const size_t start, const size_t length) {
10✔
1135
        FILE* ff;
1136
        if (!FOPEN_ISOK(ff, fname, "wb")) Error("opening file for write", fname.string().c_str(), FATAL);
10✔
1137
        bool res = SaveDeviceMemory(ff, start, length);
10✔
1138
        fclose(ff);
10✔
1139
        return res;
10✔
1140
}
1141

1142

1143
int SaveHobeta(const std::filesystem::path & fname, const char* fhobname, aint start, aint length) {
1✔
1144
        unsigned char header[0x11];
1145
        int i;
1146

1147
        if (length + start > 0x10000) {
1✔
UNCOV
1148
                length = -1;
×
1149
        }
1150
        if (length <= 0) {
1✔
1151
                length = 0x10000 - start;
×
1152
        }
1153

1154
        memset(header,' ',9);
1✔
1155
        i = strlen(fhobname);
1✔
1156
        if (i > 1)
1✔
1157
        {
1158
                const char *ext = strrchr(fhobname, '.');
1✔
1159
                if (ext && ext[1])
1✔
1160
                {
1161
                        header[8] = ext[1];
1✔
1162
                        i = ext-fhobname;
1✔
1163
                }
1164
        }
1165
        memcpy(header, fhobname, std::min(i,8));
1✔
1166

1167
        if (header[8] == 'B')        {
1✔
UNCOV
1168
                header[0x09] = (unsigned char)(length & 0xff);
×
UNCOV
1169
                header[0x0a] = (unsigned char)(length >> 8);
×
1170
        } else        {
1171
                header[0x09] = (unsigned char)(start & 0xff);
1✔
1172
                header[0x0a] = (unsigned char)(start >> 8);
1✔
1173
        }
1174

1175
        header[0x0b] = (unsigned char)(length & 0xff);
1✔
1176
        header[0x0c] = (unsigned char)(length >> 8);
1✔
1177
        header[0x0d] = 0;
1✔
1178
        if (header[0x0b] == 0) {
1✔
UNCOV
1179
                header[0x0e] = header[0x0c];
×
1180
        } else {
1181
                header[0x0e] = header[0x0c] + 1;
1✔
1182
        }
1183
        length = header[0x0e] * 0x100;
1✔
1184
        int chk = 0;
1✔
1185
        for (i = 0; i <= 14; chk = chk + (header[i] * 257) + i,i++) {
16✔
1186
                ;
1187
        }
1188
        header[0x0f] = (unsigned char)(chk & 0xff);
1✔
1189
        header[0x10] = (unsigned char)(chk >> 8);
1✔
1190

1191
        FILE* ff;
1192
        if (!FOPEN_ISOK(ff, fname, "wb")) {
1✔
UNCOV
1193
                Error("opening file for write", fname.string().c_str(), FATAL);
×
1194
        }
1195

1196
        int result = (17 == fwrite(header, 1, 17, ff)) && SaveRAM(ff, start, length);
1✔
1197
        fclose(ff);
1✔
1198
        return result;
1✔
1199
}
1200

1201
EReturn ReadFile() {
20,459✔
1202
        while (ReadLine()) {
42,838✔
1203
                const bool isInsideDupCollectingLines = !RepeatStack.empty() && !RepeatStack.top().IsInWork;
42,832✔
1204
                if (!isInsideDupCollectingLines) {
42,832✔
1205
                        // check for ending of IF/IFN/... block (keywords: ENDIF, ELSE and ELSEIF)
1206
                        char* p = line;
42,592✔
1207
                        SkipBlanks(p);
42,592✔
1208
                        if ('.' == *p) ++p;
42,592✔
1209
                        EReturn retVal = END;
42,592✔
1210
                        if (cmphstr(p, "elseif")) retVal = ELSEIF;
42,592✔
1211
                        if (cmphstr(p, "else")) retVal = ELSE;
42,592✔
1212
                        if (cmphstr(p, "endif")) retVal = ENDIF;
42,592✔
1213
                        if (END != retVal) {
42,592✔
1214
                                // one of the end-block keywords was found, don't parse it as regular line
1215
                                // but just substitute the rest of it and return end value of the keyword
1216
                                ++CompiledCurrentLine;
20,453✔
1217
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
20,453✔
1218
                                substitutedLine = line;                // for listing override substituted line with source
20,453✔
1219
                                if (ENDIF != retVal) ListFile();        // do the listing for ELSE and ELSEIF
20,453✔
1220
                                return retVal;
20,453✔
1221
                        }
1222
                }
1223
                ParseLineSafe();
22,379✔
1224
        }
1225
        return END;
6✔
1226
}
1227

1228

1229
EReturn SkipFile() {
23,430✔
1230
        int iflevel = 0;
23,430✔
1231
        while (ReadLine()) {
52,920✔
1232
                char* p = line;
52,917✔
1233
                if (isLabelStart(p) && !Options::syx.IsPseudoOpBOF) {
52,917✔
1234
                        // this could be label, skip it (the --dirbol users can't use label + IF/... inside block)
1235
                        while (islabchar(*p)) ++p;
4,670✔
1236
                        if (':' == *p) ++p;
1,043✔
1237
                }
1238
                SkipBlanks(p);
52,917✔
1239
                if ('.' == *p) ++p;
52,917✔
1240
                if (cmphstr(p, "if") || cmphstr(p, "ifn") || cmphstr(p, "ifused") ||
105,630✔
1241
                        cmphstr(p, "ifnused") || cmphstr(p, "ifdef") || cmphstr(p, "ifndef")) {
105,630✔
1242
                        ++iflevel;
240✔
1243
                } else if (cmphstr(p, "endif")) {
52,677✔
1244
                        if (iflevel) {
5,561✔
1245
                                --iflevel;
240✔
1246
                        } else {
1247
                                ++CompiledCurrentLine;
5,321✔
1248
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
5,321✔
1249
                                substitutedLine = line;                // override substituted listing for ENDIF
5,321✔
1250
                                return ENDIF;
23,427✔
1251
                        }
1252
                } else if (cmphstr(p, "else")) {
47,116✔
1253
                        if (!iflevel) {
18,211✔
1254
                                ++CompiledCurrentLine;
17,989✔
1255
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
17,989✔
1256
                                substitutedLine = line;                // override substituted listing for ELSE
17,989✔
1257
                                ListFile();
17,989✔
1258
                                return ELSE;
17,989✔
1259
                        }
1260
                } else if (cmphstr(p, "elseif")) {
28,905✔
1261
                        if (!iflevel) {
189✔
1262
                                ++CompiledCurrentLine;
117✔
1263
                                lp = ReplaceDefine(p);                // skip any empty substitutions and comments
117✔
1264
                                substitutedLine = line;                // override substituted listing for ELSEIF
117✔
1265
                                ListFile();
117✔
1266
                                return ELSEIF;
117✔
1267
                        }
1268
                } else if (cmphstr(p, "lua")) {                // lua script block detected, skip it whole
28,716✔
1269
                        // with extra custom while loop, to avoid confusion by `if/...` inside lua scripts
1270
                        ListFile(true);
84✔
1271
                        while (ReadLine()) {
189✔
1272
                                p = line;
189✔
1273
                                SkipBlanks(p);
189✔
1274
                                if (cmphstr(p, "endlua")) break;
189✔
1275
                                ListFile(true);
105✔
1276
                        }
1277
                }
1278
                ListFile(true);
29,490✔
1279
        }
1280
        return END;
3✔
1281
}
1282

1283
int ReadLineNoMacro(bool SplitByColon) {
18,981✔
1284
        if (!IsRunning || !ReadBufData()) return 0;
18,981✔
1285
        ReadBufLine(false, SplitByColon);
18,963✔
1286
        return 1;
18,963✔
1287
}
1288

1289
int ReadLine(bool SplitByColon) {
101,860✔
1290
        if (IsRunning && lijst) {                // read MACRO lines, if macro is being emitted
101,860✔
1291
                if (!lijstp || !lijstp->string) return 0;
86,368✔
1292
                assert(!sourcePosStack.empty());
86,368✔
1293
                sourcePosStack.back() = lijstp->source;
86,368✔
1294
                STRCPY(line, LINEMAX, lijstp->string);
86,368✔
1295
                substitutedLine = line;                // reset substituted listing
86,368✔
1296
                eolComment = NULL;                        // reset end of line comment
86,368✔
1297
                lijstp = lijstp->next;
86,368✔
1298
                return 1;
86,368✔
1299
        }
1300
        return ReadLineNoMacro(SplitByColon);
15,492✔
1301
}
1302

1303
int ReadFileToCStringsList(CStringsList*& f, const char* end) {
495✔
1304
        // f itself should be already NULL, not resetting it here
1305
        CStringsList** s = &f;
495✔
1306
        while (ReadLineNoMacro()) {
3,489✔
1307
                ++CompiledCurrentLine;
3,486✔
1308
                char* p = line;
3,486✔
1309
                SkipBlanks(p);
3,486✔
1310
                if ('.' == *p) ++p;
3,486✔
1311
                if (cmphstr(p, end)) {                // finished, read rest after end marker into line buffers
3,486✔
1312
                        lp = ReplaceDefine(p);
492✔
1313
                        return 1;
492✔
1314
                }
1315
                *s = new CStringsList(line);
2,994✔
1316
                s = &((*s)->next);
2,994✔
1317
                ListFile(true);
2,994✔
1318
        }
1319
        return 0;
3✔
1320
}
1321

1322
void OpenExpFile() {
506✔
1323
        assert(nullptr == FP_ExportFile);                        // this should be the first and only call to open it
506✔
1324
        if (!Options::ExportFName.has_filename()) return;        // no export file name provided, skip opening
506✔
1325
        if (FOPEN_ISOK(FP_ExportFile, Options::ExportFName, "w")) return;
6✔
1326
        Error("opening file for write", Options::ExportFName.string().c_str(), ALL);
1✔
1327
}
1328

1329
void WriteLabelEquValue(const char* name, aint value, FILE* f) {
73✔
1330
        if (nullptr == f) return;
73✔
1331
        char lnrs[16],* l = lnrs;
73✔
1332
        STRCPY(temp, LINEMAX-2, name);
73✔
1333
        STRCAT(temp, LINEMAX-1, ": EQU ");
73✔
1334
        STRCAT(temp, LINEMAX-1, "0x");
73✔
1335
        PrintHex32(l, value); *l = 0;
73✔
1336
        STRCAT(temp, LINEMAX-1, lnrs);
73✔
1337
        STRCAT(temp, LINEMAX-1, "\n");
73✔
1338
        fputs(temp, f);
73✔
1339
}
1340

1341
void WriteExp(const char* n, aint v) {
12✔
1342
        WriteLabelEquValue(n, v, FP_ExportFile);
12✔
1343
}
12✔
1344

1345
/////// source-level-debugging support by Ckirby
1346

1347
static FILE* FP_SourceLevelDebugging = NULL;
1348
static char sldMessage[LINEMAX2];
1349
static const char* WriteToSld_noSymbol = "";
1350
static char sldMessage_sourcePos[1024];
1351
static char sldMessage_definitionPos[1024];
1352
static const char* sldMessage_posFormat = "%d:%d:%d";        // at +3 is "%d:%d" and at +6 is "%d"
1353
static std::vector<std::string> sldCommentKeywords;
1354

1355
static void WriteToSldFile_TextFilePos(char* buffer, const TextFilePos & pos) {
2,214✔
1356
        int offsetFormat = !pos.colBegin ? 6 : !pos.colEnd ? 3 : 0;
2,214✔
1357
        snprintf(buffer, 1024-1, sldMessage_posFormat + offsetFormat, pos.line, pos.colBegin, pos.colEnd);
2,214✔
1358
}
2,214✔
1359

1360
static void OpenSldImp(const std::filesystem::path & sldFilename) {
506✔
1361
        if (!sldFilename.has_filename()) return;
506✔
1362
        if (!FOPEN_ISOK(FP_SourceLevelDebugging, sldFilename, "w")) {
16✔
UNCOV
1363
                Error("opening file for write", sldFilename.string().c_str(), FATAL);
×
1364
        }
1365
        fputs("|SLD.data.version|1\n", FP_SourceLevelDebugging);
16✔
1366
        if (0 < sldCommentKeywords.size()) {
16✔
1367
                fputs("||K|KEYWORDS|", FP_SourceLevelDebugging);
2✔
1368
                bool notFirst = false;
2✔
1369
                for (auto keyword : sldCommentKeywords) {
6✔
1370
                        if (notFirst) fputs(",", FP_SourceLevelDebugging);
4✔
1371
                        notFirst = true;
4✔
1372
                        fputs(keyword.c_str(), FP_SourceLevelDebugging);
4✔
1373
                }
4✔
1374
                fputs("\n", FP_SourceLevelDebugging);
2✔
1375
        }
1376
}
1377

1378
// will write result directly into Options::SourceLevelDebugFName
1379
static void OpenSld_buildDefaultNameIfNeeded() {
506✔
1380
        // check if SLD file name is already explicitly defined, or default is wanted
1381
        if (Options::SourceLevelDebugFName.has_filename() || !Options::IsDefaultSldName) return;
506✔
1382
        // name is still empty, and default is wanted, create one (start with "out" or first source name)
1383
        ConstructDefaultFilename(Options::SourceLevelDebugFName, "sld.txt", false);
3✔
1384
}
1385

1386
// returns true only in the LASTPASS and only when "sld" file was specified by user
1387
// and only when assembling is in "virtual DEVICE" mode (for "none" device no tracing is emitted)
1388
bool IsSldExportActive() {
203,904✔
1389
        return (nullptr != FP_SourceLevelDebugging && DeviceID);
203,904✔
1390
}
1391

1392
void OpenSld() {
506✔
1393
        // check if source-level-debug file is already opened
1394
        if (nullptr != FP_SourceLevelDebugging) return;
506✔
1395
        // build default filename if not explicitly provided, and default was requested
1396
        OpenSld_buildDefaultNameIfNeeded();
506✔
1397
        // try to open it if not opened yet
1398
        OpenSldImp(Options::SourceLevelDebugFName);
506✔
1399
}
1400

1401
void CloseSld() {
506✔
1402
        if (!FP_SourceLevelDebugging) return;
506✔
1403
        fclose(FP_SourceLevelDebugging);
16✔
1404
        FP_SourceLevelDebugging = nullptr;
16✔
1405
}
1406

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

1447
        assert(!sourcePosStack.empty());
1,107✔
1448
        const bool outside_source = (sourcePosStack.size() <= size_t(IncludeLevel));
1,107✔
1449
        const bool has_def_pos = !outside_source && (size_t(IncludeLevel + 1) < sourcePosStack.size());
1,107✔
1450
        const TextFilePos & curPos = outside_source ? sourcePosStack.back() : sourcePosStack.at(IncludeLevel);
1,107✔
1451
        const TextFilePos & defPos = has_def_pos ? sourcePosStack.back() : TextFilePos();
1,107✔
1452

1453
        const char* macroFN = defPos.filename && strcmp(defPos.filename, curPos.filename) ? defPos.filename : "";
1,107✔
1454
        WriteToSldFile_TextFilePos(sldMessage_sourcePos, curPos);
1,107✔
1455
        WriteToSldFile_TextFilePos(sldMessage_definitionPos, defPos);
1,107✔
1456
        snprintf(sldMessage, LINEMAX2, "%s|%s|%s|%s|%d|%d|%c|%s\n",
1,107✔
1457
                                curPos.filename, sldMessage_sourcePos, macroFN, sldMessage_definitionPos,
1,107✔
1458
                                pageNum, value, type, symbol);
1459
        fputs(sldMessage, FP_SourceLevelDebugging);
1,107✔
1460
}
1461

1462
void SldAddCommentKeyword(const char* keyword) {
21✔
1463
        if (nullptr == keyword || !keyword[0]) {
21✔
1464
                if (LASTPASS == pass) Error("[SLDOPT COMMENT] invalid keyword", lp, SUPPRESS);
6✔
1465
                return;
6✔
1466
        }
1467
        if (1 == pass) {
15✔
1468
                auto begin = sldCommentKeywords.cbegin();
5✔
1469
                auto end = sldCommentKeywords.cend();
5✔
1470
                // add keyword only if it is new (not included yet)
1471
                if (std::find(begin, end, keyword) == end) sldCommentKeywords.push_back(keyword);
13✔
1472
        }
1473
}
1474

1475
void SldTrackComments() {
288✔
1476
        assert(eolComment && IsSldExportActive());
288✔
1477
        if (!eolComment[0]) return;
288✔
1478
        for (auto keyword : sldCommentKeywords) {
337✔
1479
                if (strstr(eolComment, keyword.c_str())) {
67✔
1480
                        int pageNum = Page->Number;
18✔
1481
                        if (DISP_NONE != PseudoORG) {
18✔
1482
                                pageNum = LABEL_PAGE_UNDEFINED != dispPageNum ? dispPageNum : Device->GetPageOfA16(CurAddress);
1✔
1483
                        }
1484
                        WriteToSldFile(pageNum, CurAddress, 'K', eolComment);
18✔
1485
                        return;
18✔
1486
                }
1487
        }
67✔
1488
}
1489

1490
/////// Breakpoints list (for different emulators)
1491
static FILE* FP_BreakpointsFile = nullptr;
1492
static EBreakpointsFile breakpointsType;
1493
static int breakpointsCounter;
1494

1495
void OpenBreakpointsFile(const std::filesystem::path & filename, const EBreakpointsFile type) {
10✔
1496
        if (!filename.has_filename()) {
10✔
1497
                Error("empty filename", filename.string().c_str(), EARLY);
3✔
1498
                return;
3✔
1499
        }
1500
        if (FP_BreakpointsFile) {
7✔
1501
                Error("breakpoints file was already opened", nullptr, EARLY);
2✔
1502
                return;
2✔
1503
        }
1504
        if (!FOPEN_ISOK(FP_BreakpointsFile, filename, "w")) {
5✔
1505
                Error("opening file for write", filename.string().c_str(), EARLY);
1✔
1506
        }
1507
        breakpointsCounter = 0;
5✔
1508
        breakpointsType = type;
5✔
1509
}
1510

1511
static void CloseBreakpointsFile() {
506✔
1512
        if (!FP_BreakpointsFile) return;
506✔
1513
        fclose(FP_BreakpointsFile);
4✔
1514
        FP_BreakpointsFile = nullptr;
4✔
1515
}
1516

1517
void WriteBreakpoint(const aint val) {
120✔
1518
        if (!FP_BreakpointsFile) {
120✔
1519
                WarningById(W_BP_FILE);
8✔
1520
                return;
8✔
1521
        }
1522
        ++breakpointsCounter;
112✔
1523
        switch (breakpointsType) {
112✔
1524
                case BPSF_UNREAL:
5✔
1525
                        check16u(val);
5✔
1526
                        fprintf(FP_BreakpointsFile, "x0=0x%04X\n", val&0xFFFF);
5✔
1527
                        break;
5✔
1528
                case BPSF_ZESARUX:
107✔
1529
                        if (1 == breakpointsCounter) fputs(" --enable-breakpoints ", FP_BreakpointsFile);
107✔
1530
                        if (100 < breakpointsCounter) {
107✔
1531
                                Warning("Maximum amount of 100 breakpoints has been already reached, this one is ignored");
1✔
1532
                                break;
1✔
1533
                        }
1534
                        check16u(val);
106✔
1535
                        fprintf(FP_BreakpointsFile, "--set-breakpoint %d \"PC=%d\" ", breakpointsCounter, val&0xFFFF);
106✔
1536
                        break;
106✔
1537
        }
1538
}
1539

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