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

z00m128 / sjasmplus / 1452

02 Jan 2025 07:59PM UTC coverage: 96.286% (+0.007%) from 96.279%
1452

Pull #254

cirrus-ci

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

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

This get still degraded to std::string/const char* when include paths
are involved (GetPath, ArchiveFilename, Open/Include anything).

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

241 of 263 new or added lines in 13 files covered. (91.63%)

2 existing lines in 2 files now uncovered.

9671 of 10044 relevant lines covered (96.29%)

168685.62 hits per line

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

97.81
/sjasm/directives.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
// direct.cpp
30

31
#include "sjdefs.h"
32

33
CFunctionTable DirectivesTable;
34
CFunctionTable DirectivesTable_dup;
35

36
int ParseDirective(bool beginningOfLine)
243,307✔
37
{
38
        char* olp = lp;
243,307✔
39

40
        // if END/.END directive is at the beginning of line = ignore them (even with "--dirbol")
41
        if (beginningOfLine && (cmphstr(lp, "end") || cmphstr(lp, ".end"))) {
243,307✔
42
                lp = olp;
18✔
43
                return 0;
18✔
44
        }
45

46
        bp = lp;
243,289✔
47
        char* n;
48
        aint val;
49
        if (!(n = getinstr(lp))) {        // will also reject any instruction followed by colon char (label)
243,289✔
50
                lp = olp;
156✔
51
                return 0;
156✔
52
        }
53

54
        if (DirectivesTable.zoek(n)) return 1;
243,133✔
55

56
        // Only "." repeat directive remains, but that one can't start at beginning of line (without --dirbol)
57
        const bool isDigitDot = ('.' == *n) && isdigit((byte)n[1]);
120,844✔
58
        const bool isExprDot = ('.' == *n) && (0 == n[1]) && ('(' == *lp);
120,844✔
59
        if ((beginningOfLine && !Options::syx.IsPseudoOpBOF) || (!isDigitDot && !isExprDot)) {
120,844✔
60
                lp = olp;                // alone "." must be followed by digit, or math expression in parentheses
120,748✔
61
                return 0;                // otherwise just return
120,748✔
62
        }
63

64
        // parse repeat-count either from n+1 (digits) or lp (parentheses) (if syntax is valid)
65
        if ((isDigitDot && !White(*lp)) || !ParseExpression(isDigitDot ? ++n : ++lp, val) || (isExprDot && ')' != *lp++)) {
96✔
66
                lp = olp; Error("Dot-repeater must be followed by number or parentheses", olp, SUPPRESS);
9✔
67
                return 0;
9✔
68
        }
69
        if (val < 1) {
87✔
70
                lp = olp; ErrorInt(".N must be positive integer", val, SUPPRESS);
12✔
71
                return 0;
12✔
72
        }
73

74
        // preserve original line buffer, and also the line to be repeated (at `lp`)
75
        char* ml = STRDUP(line);
75✔
76
        SkipBlanks();
75✔
77
        char* pp = STRDUP(lp);
75✔
78
        // create new copy of eolComment because original "line" content will be destroyed
79
        char* eolCommCopy = eolComment ? STRDUP(eolComment) : nullptr;
75✔
80
        eolComment = eolCommCopy;
75✔
81
        if (NULL == ml || NULL == pp) ErrorOOM();
75✔
82
        ++listmacro;
75✔
83
        do {
84
                line[0] = ' ';
288✔
85
                STRCPY(line+1, LINEMAX-1, pp);        // reset `line` to the content which should be repeated
288✔
86
                ParseLineSafe();                        // and parse it
288✔
87
                eolComment = NULL;                        // switch OFF EOL-comment after first line
288✔
88
        } while (--val);
288✔
89
        // restore everything
90
        STRCPY(line, LINEMAX, ml);
75✔
91
        --listmacro;
75✔
92
        donotlist = 1;
75✔
93
        if (eolCommCopy) free(eolCommCopy);
75✔
94
        free(pp);
75✔
95
        free(ml);
75✔
96
        // make lp point at \0, as the repeating line was processed fully
97
        lp = sline;
75✔
98
        *sline = 0;
75✔
99
        return 1;
75✔
100
}
101

102
int ParseDirective_REPT() {
24,400✔
103
        char* olp = bp = lp, * n;
24,400✔
104
        if ((n = getinstr(lp)) && DirectivesTable_dup.zoek(n)) return 1;
24,400✔
105
        lp = olp;
19,655✔
106
        return 0;
19,655✔
107
}
108

109

110
static void getBytesWithCheck(int add = 0, int dc = 0, bool dz = false) {
71,124✔
111
        check8(add); add &= 255;
71,124✔
112
        int dirDx[130];
113
        if (GetBytes(lp, dirDx, add, dc)) {
71,124✔
114
                EmitBytes(dirDx);
70,926✔
115
                if (dz) EmitByte(0);
70,926✔
116
        } else {
117
                Error("no arguments");
198✔
118
        }
119
}
71,124✔
120

121
static void dirBYTE() {
70,155✔
122
        getBytesWithCheck();
70,155✔
123
}
70,155✔
124

125
static void dirDC() {
816✔
126
        getBytesWithCheck(0, 1);
816✔
127
}
816✔
128

129
static void dirDZ() {
123✔
130
        getBytesWithCheck(0, 0, true);
123✔
131
}
123✔
132

133
static void dirABYTE() {
18✔
134
        aint add;
135
        if (ParseExpressionNoSyntaxError(lp, add)) {
18✔
136
                Relocation::checkAndWarn();
15✔
137
                getBytesWithCheck(add);
15✔
138
        } else {
139
                Error("ABYTE <offset> <bytes>: parsing <offset> failed", bp, SUPPRESS);
3✔
140
        }
141
}
18✔
142

143
static void dirABYTEC() {
6✔
144
        aint add;
145
        if (ParseExpressionNoSyntaxError(lp, add)) {
6✔
146
                Relocation::checkAndWarn();
3✔
147
                getBytesWithCheck(add, 1);
3✔
148
        } else {
149
                Error("ABYTEC <offset> <bytes>: parsing <offset> failed", bp, SUPPRESS);
3✔
150
        }
151
}
6✔
152

153
static void dirABYTEZ() {
15✔
154
        aint add;
155
        if (ParseExpressionNoSyntaxError(lp, add)) {
15✔
156
                Relocation::checkAndWarn();
12✔
157
                getBytesWithCheck(add, 0, true);
12✔
158
        } else {
159
                Error("ABYTEZ <offset> <bytes>: parsing <offset> failed", bp, SUPPRESS);
3✔
160
        }
161
}
15✔
162

163
static void dirWORD() {
4,579✔
164
        aint val;
165
        int teller = 0, e[130];
4,579✔
166
        do {
167
                // reset alternate result flag in ParseExpression part of code
168
                Relocation::isResultAffected = false;
8,326✔
169
                if (SkipBlanks()) {
8,326✔
170
                        Error("Expression expected", NULL, SUPPRESS);
6✔
171
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
8,320✔
172
                        check16(val);
8,281✔
173
                        e[teller] = val & 65535;
8,281✔
174
                        Relocation::resolveRelocationAffected(teller * 2);
8,281✔
175
                        ++teller;
8,281✔
176
                } else {
177
                        Error("[DW/DEFW/WORD] Syntax error", lp, SUPPRESS);
39✔
178
                        break;
39✔
179
                }
180
        } while (comma(lp) && teller < 128);
8,287✔
181
        e[teller] = -1;
4,579✔
182
        if (teller == 128 && *lp) Error("Over 128 values in DW/DEFW/WORD. Values over", lp, SUPPRESS);
4,579✔
183
        if (teller) EmitWords(e);
4,579✔
184
        else                Error("DW/DEFW/WORD with no arguments");
45✔
185
}
4,579✔
186

187
static void dirDWORD() {
102✔
188
        aint val;
189
        int teller = 0, e[130 * 2];
102✔
190
        do {
191
                if (SkipBlanks()) {
879✔
192
                        Error("Expression expected", NULL, SUPPRESS);
3✔
193
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
876✔
194
                        e[teller * 2] = val & 65535; e[teller * 2 + 1] = (val >> 16) & 0xFFFF; ++teller;
828✔
195
                } else {
196
                        Error("[DWORD] Syntax error", lp, SUPPRESS);
48✔
197
                        break;
48✔
198
                }
199
        } while (comma(lp) && teller < 128);
831✔
200
        e[teller * 2] = -1;
102✔
201
        if (teller == 128 && *lp) Error("Over 128 values in DWORD. Values over", lp, SUPPRESS);
102✔
202
        if (teller) EmitWords(e);
102✔
203
        else                Error("DWORD with no arguments");
51✔
204
}
102✔
205

206
static void dirD24() {
30✔
207
        aint val;
208
        int teller = 0, e[130 * 3];
30✔
209
        do {
210
                if (SkipBlanks()) {
804✔
211
                        Error("Expression expected", NULL, SUPPRESS);
3✔
212
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
801✔
213
                        check24(val);
798✔
214
                        e[teller++] = val & 255; e[teller++] = (val >> 8) & 255; e[teller++] = (val >> 16) & 255;
798✔
215
                } else {
216
                        Error("[D24] Syntax error", lp, SUPPRESS);
3✔
217
                        break;
3✔
218
                }
219
        } while (comma(lp) && teller < 128*3);
801✔
220
        e[teller] = -1;
30✔
221
        if (teller == 128*3 && *lp) Error("Over 128 values in D24. Values over", lp, SUPPRESS);
30✔
222
        if (teller) EmitBytes(e);
30✔
223
        else                Error("D24 with no arguments");
6✔
224
}
30✔
225

226
static void dirDG() {
132✔
227
        int dirDx[130];
228
        if (GetBits(lp, dirDx)) {
132✔
229
                EmitBytes(dirDx);
123✔
230
        } else {
231
                Error("no arguments");
9✔
232
        }
233
}
132✔
234

235
static void dirDH() {
72✔
236
        int dirDx[130];
237
        if (GetBytesHexaText(lp, dirDx)) {
72✔
238
                EmitBytes(dirDx);
51✔
239
        } else {
240
                Error("no arguments");
21✔
241
        }
242
}
72✔
243

244
static void dirBLOCK() {
883✔
245
        aint teller,val = 0;
883✔
246
        if (ParseExpressionNoSyntaxError(lp, teller)) {
883✔
247
                if (teller < 0) {
880✔
248
                        Warning("Negative BLOCK?");
24✔
249
                }
250
                if (comma(lp)) {
880✔
251
                        if (ParseExpression(lp, val)) check8(val);
745✔
252
                }
253
                EmitBlock(val, teller);
880✔
254
        } else {
255
                Error("[BLOCK] Syntax Error in <length>", lp, SUPPRESS);
3✔
256
        }
257
}
883✔
258

259
bool dirPageImpl(const char* const dirName, int pageNumber) {
1,227✔
260
        if (!Device) return false;
1,227✔
261
        if (pageNumber < 0 || Device->PagesCount <= pageNumber) {
1,227✔
262
                char buf[LINEMAX];
263
                SPRINTF2(buf, LINEMAX, "[%s] Page number must be in range 0..%d", dirName, Device->PagesCount - 1);
54✔
264
                ErrorInt(buf, pageNumber);
54✔
265
                return false;
54✔
266
        }
267
        Device->GetCurrentSlot()->Page = Device->GetPage(pageNumber);
1,173✔
268
        Device->CheckPage(CDevice::CHECK_RESET);
1,173✔
269
        return true;
1,173✔
270
}
271

272
static void dirPageImpl(const char* const dirName) {
1,229✔
273
        aint pageNum;
274
        if (ParseExpressionNoSyntaxError(lp, pageNum)) {
1,229✔
275
                dirPageImpl(dirName, pageNum);
1,223✔
276
        } else {
277
                Error("Syntax error in <page_number>", lp, SUPPRESS);
6✔
278
        }
279
}
1,229✔
280

281
static void dirORG() {
2,562✔
282
        aint val;
283
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,562✔
284
                Error("[ORG] Syntax error in <address>", lp, SUPPRESS);
3✔
285
                return;
2,412✔
286
        }
287
        // crop (with warning) address in device or non-longptr mode to 16bit address range
288
        if ((DeviceID || !Options::IsLongPtr) && !check16u(val)) val &= 0xFFFF;
2,559✔
289
        CurAddress = val;
2,559✔
290
        if (DISP_NONE != PseudoORG) WarningById(W_DISPLACED_ORG);
2,559✔
291
        if (!DeviceID) return;
2,559✔
292
        if (!comma(lp)) {
1,988✔
293
                Device->CheckPage(CDevice::CHECK_RESET);
1,838✔
294
                return;
1,838✔
295
        }
296
        // emit warning when current slot does not cover address used for ORG
297
        auto slot = Device->GetCurrentSlot();
150✔
298
        if ((CurAddress < slot->Address || slot->Address + slot->Size <= CurAddress)) {
150✔
299
                char warnTxt[LINEMAX];
300
                SPRINTF4(warnTxt, LINEMAX,
57✔
301
                                        "address 0x%04X vs slot %d range 0x%04X..0x%04X",
302
                                        CurAddress, Device->GetCurrentSlotNum(), slot->Address, slot->Address + slot->Size - 1);
303
                WarningById(W_ORG_PAGE, warnTxt);
57✔
304
        }
305
        dirPageImpl("ORG");
150✔
306
}
307

308
static void dirDISP() {
204✔
309
        if (DISP_NONE != PseudoORG) {
204✔
310
                Warning("[DISP] displacement inside another displacement block, ignoring it.");
6✔
311
                SkipToEol(lp);
6✔
312
                return;
33✔
313
        }
314
        aint valAdr, valPageNum;
315
        // parse+validate values first, don't even switch into DISP mode in case of any error
316
        Relocation::isResultAffected = false;
198✔
317
        if (!ParseExpressionNoSyntaxError(lp, valAdr)) {
198✔
318
                Error("[DISP] Syntax error in <address>", lp, SUPPRESS);
3✔
319
                return;
3✔
320
        }
321
        // the expression of the DISP shouldn't be affected by relocation (even when starting inside relocation block)
322
        if (Relocation::checkAndWarn(true)) {
195✔
323
                SkipToEol(lp);
3✔
324
                return;                // report it as error and exit early
3✔
325
        }
326
        if (comma(lp)) {
192✔
327
                if (!ParseExpressionNoSyntaxError(lp, valPageNum)) {
39✔
328
                        Error("[DISP] Syntax error in <page number>", lp);
9✔
329
                        return;
9✔
330
                }
331
                if (!DeviceID) {
30✔
332
                        Error("[DISP] <page number> is accepted only in device mode", line);
3✔
333
                        return;
3✔
334
                }
335
                if (valPageNum < 0 || Device->PagesCount <= valPageNum) {
27✔
336
                        ErrorInt("[DISP] <page number> is out of range", valPageNum);
9✔
337
                        return;
9✔
338
                }
339
                dispPageNum = valPageNum;
18✔
340
        } else {
341
                dispPageNum = LABEL_PAGE_UNDEFINED;
153✔
342
        }
343
        // crop (with warning) address in device or non-longptr mode to 16bit address range
344
        if ((DeviceID || !Options::IsLongPtr) && !check16u(valAdr)) valAdr &= 0xFFFF;
171✔
345
        // everything is valid, switch to DISP mode (dispPageNum is already set above)
346
        adrdisp = CurAddress;
171✔
347
        CurAddress = valAdr;
171✔
348
        PseudoORG = Relocation::type ? DISP_INSIDE_RELOCATE : DISP_ACTIVE;
171✔
349
}
350

351
static void dirENT() {
180✔
352
        if (DISP_NONE == PseudoORG) {
180✔
353
                Error("ENT should be after DISP");
6✔
354
                return;
6✔
355
        }
356
        // check if the DISP..ENT block is either fully inside relocation block, or engulfing it fully.
357
        if (DISP_ACTIVE == PseudoORG && Relocation::type) {
174✔
358
                Error("The DISP block did start outside of relocation block, can't end inside it");
6✔
359
                return;
6✔
360
        }
361
        if (DISP_INSIDE_RELOCATE == PseudoORG && !Relocation::type) {
168✔
362
                Error("The DISP block did start inside of relocation block, can't end outside of it");
×
363
                return;
×
364
        }
365
        CurAddress = adrdisp;
168✔
366
        PseudoORG = DISP_NONE;
168✔
367
        dispPageNum = LABEL_PAGE_UNDEFINED;
168✔
368
}
369

370
static void dirPAGE() {
1,083✔
371
        if (!DeviceID) {
1,083✔
372
                Warning("PAGE only allowed in real device emulation mode (See DEVICE)");
4✔
373
                SkipParam(lp);
4✔
374
        } else {
375
                dirPageImpl("PAGE");
1,079✔
376
        }
377
}
1,083✔
378

379
static void dirMMU() {
456✔
380
        if (!DeviceID) {
456✔
381
                Warning("MMU is allowed only in real device emulation mode (See DEVICE)");
6✔
382
                SkipToEol(lp);
6✔
383
                return;
69✔
384
        }
385
        aint slot1, slot2, pageN = -1, address = -1;
450✔
386
        CDeviceSlot::ESlotOptions slotOpt = CDeviceSlot::O_NONE;
450✔
387
        if (!ParseExpression(lp, slot1)) {
450✔
388
                Error("[MMU] First slot number parsing failed", bp, SUPPRESS);
3✔
389
                return;
3✔
390
        }
391
        slot2 = slot1;
447✔
392
        if (!comma(lp)) {        // second slot or slot-option should follow (if not comma)
447✔
393
                // see if there is slot1-only with option-char (e/w/n options)
394
                const char slotOptChar = (*lp)|0x20;        // primitive ASCII tolower
177✔
395
                if ('a' <= slotOptChar && slotOptChar <= 'z' && (',' == lp[1] || White(lp[1]))) {
177✔
396
                        if ('e' == slotOptChar) slotOpt = CDeviceSlot::O_ERROR;
84✔
397
                        else if ('w' == slotOptChar) slotOpt = CDeviceSlot::O_WARNING;
60✔
398
                        else if ('n' == slotOptChar) slotOpt = CDeviceSlot::O_NEXT;
45✔
399
                        else {
400
                                Warning("[MMU] Unknown slot option (legal: e, w, n)", lp);
6✔
401
                        }
402
                        ++lp;
84✔
403
                } else {        // there was no option char, check if there was slot2 number to define range
404
                        if (!ParseExpression(lp, slot2)) {
93✔
405
                                Error("[MMU] Second slot number parsing failed", bp, SUPPRESS);
6✔
406
                                return;
6✔
407
                        }
408
                }
409
                if (!comma(lp)) {
171✔
410
                        Error("[MMU] Comma and page number expected after slot info", bp, SUPPRESS);
6✔
411
                        return;
6✔
412
                }
413
        }
414
        if (!ParseExpression(lp, pageN)) {
435✔
415
                Error("[MMU] Page number parsing failed", bp, SUPPRESS);
15✔
416
                return;
15✔
417
        }
418
        if (comma(lp)) {
420✔
419
                if (!ParseExpressionNoSyntaxError(lp, address)) {
237✔
420
                        Error("[MMU] address parsing failed", bp, SUPPRESS);
6✔
421
                        return;
6✔
422
                }
423
                check16(address);
231✔
424
                address &= 0xFFFF;
231✔
425
        }
426
        // convert slot entered as addresses into slot numbering (must be precise start address of slot)
427
        slot1 = Device->SlotNumberFromPreciseAddress(slot1);
414✔
428
        slot2 = Device->SlotNumberFromPreciseAddress(slot2);
414✔
429
        // validate argument values
430
        if (slot1 < 0 || slot2 < slot1 || Device->SlotsCount <= slot2) {
414✔
431
                char buf[LINEMAX];
432
                SPRINTF1(buf, LINEMAX, "[MMU] Slot number(s) must be in range 0..%u (or exact starting address of slot) and form a range",
15✔
433
                                 Device->SlotsCount - 1);
434
                Error(buf, NULL, SUPPRESS);
15✔
435
                return;
15✔
436
        }
437
        if (pageN < 0 || Device->PagesCount <= pageN + (slot2 - slot1)) {
399✔
438
                char buf[LINEMAX];
439
                SPRINTF1(buf, LINEMAX, "[MMU] Requested page(s) must be in range 0..%u", Device->PagesCount - 1);
12✔
440
                Error(buf, NULL, SUPPRESS);
12✔
441
                return;
12✔
442
        }
443
        // all valid, set it up
444
        for (aint slotN = slot1; slotN <= slot2; ++slotN, ++pageN) {
1,017✔
445
                Device->GetSlot(slotN)->Page = Device->GetPage(pageN);
630✔
446
                // this ^ is also enough to keep global "Slot" up to date (it's a pointer)
447
                Device->GetSlot(slotN)->Option = slotOpt;        // resets whole range to NONE when range
630✔
448
        }
449
        // wrap output addresses back into 64ki address space, it's essential for MMU functionality
450
        if (DISP_NONE != PseudoORG) adrdisp &= 0xFFFF; else CurAddress &= 0xFFFF;
387✔
451
        // set explicit ORG address if the third argument was provided
452
        if (0 <= address) {
387✔
453
                CurAddress = address;
231✔
454
                if (DISP_NONE != PseudoORG) {
231✔
455
                        WarningById(W_DISPLACED_ORG);
6✔
456
                }
457
                // check if explicit ORG address is outside of the slots affected by MMU, warn about it
458
                const CDeviceSlot & check_s1 = *Device->GetSlot(slot1);
231✔
459
                const CDeviceSlot & check_s2 = *Device->GetSlot(slot2);
231✔
460
                if ((address < check_s1.Address) || (check_s2.Address + check_s2.Size <= address)) {
231✔
461
                        char buf[LINEMAX];
462
                        SPRINTF3(buf, LINEMAX, "[MMU] Requested ORG address 0x%04X is out of range 0x%04X..0x%04X",
30✔
463
                                         address, check_s1.Address, check_s2.Address + check_s2.Size - 1);
464
                        Warning(buf);
30✔
465
                }
466
        }
467
        Device->CheckPage(CDevice::CHECK_RESET);
387✔
468
}
469

470
static void dirSLOT() {
336✔
471
        aint val;
472
        if (!DeviceID) {
336✔
473
                Warning("SLOT only allowed in real device emulation mode (See DEVICE)");
6✔
474
                SkipParam(lp);
6✔
475
                return;
9✔
476
        }
477
        if (!ParseExpressionNoSyntaxError(lp, val)) {
330✔
478
                Error("[SLOT] Syntax error in <slot_number>", lp, SUPPRESS);
3✔
479
                return;
3✔
480
        }
481
        val = Device->SlotNumberFromPreciseAddress(val);
327✔
482
        if (!Device->SetSlot(val)) {
327✔
483
                char buf[LINEMAX];
484
                SPRINTF1(buf, LINEMAX, "[SLOT] Slot number must be in range 0..%u, or exact starting address of slot", Device->SlotsCount - 1);
42✔
485
                Error(buf, NULL, IF_FIRST);
42✔
486
        }
487
}
488

489
static void dirALIGN() {
432✔
490
        // default alignment is 4, default filler is "0/none" (if not specified in directive explicitly)
491
        aint val, fill;
492
        ParseAlignArguments(lp, val, fill);
432✔
493
        if (-1 == val) val = 4;
432✔
494
        // calculate how many bytes has to be filled to reach desired alignment
495
        aint len = (~CurAddress + 1) & (val - 1);
432✔
496
        if (len < 1) return;                // nothing to fill, already aligned
432✔
497
        if (-1 == fill) EmitBlock(0, len, true);
319✔
498
        else                        EmitBlock(fill, len, false);
84✔
499
}
500

501
static void dirMODULE() {
210✔
502
        char* n = GetID(lp);
210✔
503
        if (n && (nullptr == STRCHR(n, '.'))) {
210✔
504
                if (*ModuleName) STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), ".");
201✔
505
                STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), n);
201✔
506
                // reset non-local label to default "_"
507
                if (vorlabp) free(vorlabp);
201✔
508
                vorlabp = STRDUP("_");
201✔
509
                if (IsSldExportActive()) {
205✔
510
                        WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld());
4✔
511
                }
512
        } else {
513
                if (n) {
9✔
514
                        Error("[MODULE] Dots not allowed in <module_name>", n, SUPPRESS);
6✔
515
                } else {
516
                        Error("[MODULE] Syntax error in <name>", bp, SUPPRESS);
3✔
517
                }
518
        }
519
}
210✔
520

521
static void dirENDMODULE() {
204✔
522
        if (! *ModuleName) {
204✔
523
                Error("ENDMODULE without MODULE");
9✔
524
                return;
9✔
525
        }
526
        if (IsSldExportActive()) {
195✔
527
                WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld(true));
4✔
528
        }
529
        // remove last part of composite modules name
530
        char* lastDot = strrchr(ModuleName, '.');
195✔
531
        if (lastDot)        *lastDot = 0;
195✔
532
        else                        *ModuleName = 0;
156✔
533
        // reset non-local label to default "_"
534
        if (vorlabp) free(vorlabp);
195✔
535
        vorlabp = STRDUP("_");
195✔
536
}
537

538
static void dirEND() {
48✔
539
        char* p = lp;
48✔
540
        aint val;
541
        if (ParseExpression(lp, val)) {
48✔
542
                if (val > 65535 || val < 0) ErrorInt("[END] Invalid address", IF_FIRST);
9✔
543
                else                                                 StartAddress = val;
9✔
544
        } else {
545
                lp = p;
39✔
546
        }
547

548
        IsRunning = 0;
48✔
549
}
48✔
550

551
static void dirSIZE() {
24✔
552
        aint val;
553
        if (!ParseExpressionNoSyntaxError(lp, val)) {
24✔
554
                Error("[SIZE] Syntax error in <filesize>", bp, SUPPRESS);
3✔
555
                return;
17✔
556
        }
557
        if (LASTPASS != pass) return;        // only active during final pass
21✔
558
        if (-1L == size) size = val;        // first time set
7✔
559
        else if (size != val) ErrorInt("[SIZE] Different size than previous", size);        // just check it's same
2✔
560
}
561

562
static void dirINCBIN() {
123✔
563
        int offset = 0, length = INT_MAX;
123✔
564
        const std::filesystem::path fnaam = GetFileName(lp);
123✔
565
        const bool system_paths_first = (DT_ANGLE == GetDelimiterOfLastFileName());
123✔
566
        if (anyComma(lp)) {
123✔
567
                aint val;
568
                if (!anyComma(lp)) {
87✔
569
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
75✔
570
                                Error("[INCBIN] Syntax error in <offset>", bp, SUPPRESS);
6✔
571
                                return;
15✔
572
                        }
573
                        offset = val;
69✔
574
                } else --lp;                // there was second comma right after, reread it
12✔
575
                if (anyComma(lp)) {
81✔
576
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
66✔
577
                                Error("[INCBIN] Syntax error in <length>", bp, SUPPRESS);
9✔
578
                                return;
9✔
579
                        }
580
                        length = val;
57✔
581
                }
582
        }
583
        BinIncFile(fnaam.string().c_str(), offset, length, system_paths_first);        //FIXME path idea ready with refactoring
108✔
584
}
123✔
585

586
static void dirINCHOB() {
30✔
587
        aint val;
588
        char* fnaamh;
589
        unsigned char len[2];
590
        int offset = 0,length = -1;
30✔
591
        FILE* ff;
592

593
        const std::filesystem::path fnaam = GetFileName(lp);
30✔
594
        const bool system_paths_first = (DT_ANGLE == GetDelimiterOfLastFileName());
30✔
595
        if (anyComma(lp)) {
30✔
596
                if (!anyComma(lp)) {
24✔
597
                        if (!ParseExpression(lp, val)) {
18✔
598
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
599
                        }
600
                        if (val < 0) {
18✔
601
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
602
                        }
603
                        offset += val;
18✔
604
                } else --lp;                // there was second comma right after, reread it
6✔
605
                if (anyComma(lp)) {
24✔
606
                        if (!ParseExpression(lp, val)) {
18✔
607
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
608
                        }
609
                        if (val < 0) {
18✔
610
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
611
                        }
612
                        length = val;
18✔
613
                }
614
        }
615

616
        fnaamh = GetPath(fnaam.string().c_str(), nullptr, system_paths_first);        //FIXME path idea ready
30✔
617
        if (!FOPEN_ISOK(ff, fnaamh, "rb")) {
30✔
NEW
618
                Error("[INCHOB] Error opening file", fnaam.string().c_str(), FATAL);
×
619
        }
620
        if (fseek(ff, 0x0b, 0) || 2 != fread(len, 1, 2, ff)) {
30✔
NEW
621
                Error("[INCHOB] Hobeta file has wrong format", fnaam.string().c_str(), FATAL);
×
622
        }
623
        fclose(ff);
30✔
624
        if (length == -1) {
30✔
625
                // calculate remaining length of the file (from the specified offset)
626
                length = len[0] + (len[1] << 8) - offset;
12✔
627
        }
628
        offset += 17;                // adjust offset (skip HOB header)
30✔
629
        BinIncFile(fnaam.string().c_str(), offset, length, system_paths_first);        //FIXME path idea ready w/ refactoring
30✔
630
        free(fnaamh);
30✔
631
}
30✔
632

633
static void dirINCTRD() {
78✔
634
        aint val, offset = 0, length = INT_MAX;
78✔
635
        const std::filesystem::path trdname = GetFileName(lp);
78✔
636
        const bool system_paths_first = (DT_ANGLE == GetDelimiterOfLastFileName());
78✔
637
        std::string filename {""};
78✔
638
        if (anyComma(lp) && !anyComma(lp)) filename = GetDelimitedString(lp);
78✔
639
        if (filename.empty()) {
78✔
640
                // file-in-disk syntax error
641
                Error("[INCTRD] Syntax error", bp, IF_FIRST);
9✔
642
                SkipToEol(lp);
9✔
643
                return;
9✔
644
        }
645
        if (anyComma(lp)) {
69✔
646
                if (!anyComma(lp)) {
51✔
647
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
48✔
648
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
649
                                SkipToEol(lp);
6✔
650
                                return;
6✔
651
                        }
652
                        if (val < 0) {
42✔
653
                                ErrorInt("[INCTRD] Negative offset value is not allowed", val);
3✔
654
                                SkipToEol(lp);
3✔
655
                                return;
3✔
656
                        }
657
                        offset = val;
39✔
658
                } else --lp;                // there was second comma right after, reread it
3✔
659
                if (anyComma(lp)) {
42✔
660
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
33✔
661
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
662
                                SkipToEol(lp);
6✔
663
                                return;
6✔
664
                        }
665
                        if (val < 0) {
27✔
666
                                ErrorInt("[INCTRD] Negative length value is not allowed", val);
3✔
667
                                SkipToEol(lp);
3✔
668
                                return;
3✔
669
                        }
670
                        length = val;
24✔
671
                }
672
        }
673
        if (TRD_PrepareIncFile(trdname.string().c_str(), filename.c_str(), offset, length, system_paths_first)) {        //FIXME path idea ready w/ refactoring^2
51✔
674
                BinIncFile(trdname.string().c_str(), offset, length, system_paths_first);        //FIXME path idea ready w/ refactoring
36✔
675
        }
676
}
105✔
677

678
static void dirSAVESNA() {
90✔
679
        if (pass != LASTPASS) return;                // syntax error is not visible in early passes
99✔
680

681
        if (!DeviceID) {
30✔
682
                Error("SAVESNA only allowed in real device emulation mode (See DEVICE)", nullptr, SUPPRESS);
2✔
683
                return;
2✔
684
        } else if (!IsZXSpectrumDevice(DeviceID)) {
28✔
685
                Error("[SAVESNA] Device must be ZXSPECTRUM48 or ZXSPECTRUM128.", nullptr, SUPPRESS);
1✔
686
                return;
1✔
687
        }
688

689
        const std::filesystem::path fnaam = GetOutputFileName(lp);
27✔
690
        int start = StartAddress;
27✔
691
        if (anyComma(lp)) {
27✔
692
                aint val;
693
                if (!ParseExpression(lp, val)) return;
30✔
694
                if (0 <= start) Warning("[SAVESNA] Start address was also defined by END, SAVESNA argument used instead");
25✔
695
                if (0 <= val) {
25✔
696
                        start = val;
21✔
697
                } else {
698
                        Error("[SAVESNA] Negative values are not allowed", bp, SUPPRESS);
4✔
699
                        return;
4✔
700
                }
701
        }
702
        if (start < 0) {
22✔
703
                Error("[SAVESNA] No start address defined", bp, SUPPRESS);
1✔
704
                return;
1✔
705
        }
706

707
        if (!SaveSNA_ZX(fnaam, start)) Error("[SAVESNA] Error writing file (Disk full?)", bp, IF_FIRST);
21✔
708
}
27✔
709

710
static void dirEMPTYTAP() {
33✔
711
        if (pass != LASTPASS) {
33✔
712
                SkipParam(lp);
22✔
713
                return;
23✔
714
        }
715
        const std::filesystem::path fnaam = GetOutputFileName(lp);
11✔
716
        if (!fnaam.has_filename()) {
11✔
717
                Error("[EMPTYTAP] Syntax error", bp, IF_FIRST); return;
1✔
718
        }
719
        TAP_SaveEmpty(fnaam);
10✔
720
}
11✔
721

722
static void dirSAVETAP() {
192✔
723
        if (pass != LASTPASS) {
192✔
724
                SkipParam(lp);
128✔
725
                return;
158✔
726
        }
727

728
        bool exec = true, realtapeMode = false;
64✔
729
        int headerType = -1;
64✔
730
        aint val;
731
        int start = -1, length = -1, param2 = -1, param3 = -1;
64✔
732

733
        if (!DeviceID) {
64✔
734
                Error("SAVETAP only allowed in real device emulation mode (See DEVICE)");
1✔
735
                exec = false;
1✔
736
        }
737

738
        const std::filesystem::path fnaam = GetOutputFileName(lp);
64✔
739
        std::string fnaamh {""};
64✔
740
        if (anyComma(lp)) {
64✔
741
                if (!anyComma(lp)) {
62✔
742
                        char *tlp = lp;
61✔
743
                        char *id;
744

745
                        if ((id = GetID(lp)) && strlen(id) > 0) {
61✔
746
                                if (cmphstr(id, "basic")) {
59✔
747
                                        headerType = BASIC;
9✔
748
                                        realtapeMode = true;
9✔
749
                                } else if (cmphstr(id, "numbers")) {
50✔
750
                                        headerType = NUMBERS;
6✔
751
                                        realtapeMode = true;
6✔
752
                                } else if (cmphstr(id, "chars")) {
44✔
753
                                        headerType = CHARS;
2✔
754
                                        realtapeMode = true;
2✔
755
                                } else if (cmphstr(id, "code")) {
42✔
756
                                        headerType = CODE;
25✔
757
                                        realtapeMode = true;
25✔
758
                                } else if (cmphstr(id, "headless")) {
17✔
759
                                        headerType = HEADLESS;
16✔
760
                                        realtapeMode = true;
16✔
761
                                }
762
                        }
763

764
                        if (realtapeMode) {
61✔
765
                                if (anyComma(lp)) {
58✔
766
                                        if (headerType == HEADLESS) {
56✔
767
                                                if (!anyComma(lp)) {
14✔
768
                                                        if (!ParseExpression(lp, val)) {
13✔
769
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
27✔
770
                                                        }
771
                                                        if (val < 0) {
12✔
772
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
773
                                                        } else if (val > 0xFFFF) {
11✔
774
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
775
                                                        }
776
                                                        start = val;
10✔
777
                                                } else {
778
                                                        Error("[SAVETAP] Syntax error. Missing start address", bp, PASS3); return;
1✔
779
                                                }
780
                                                if (anyComma(lp)) {
10✔
781
                                                        if (!ParseExpression(lp, val)) {
10✔
782
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
1✔
783
                                                        }
784
                                                        if (val < 0) {
9✔
785
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
786
                                                        } else if (val > 0xFFFF) {
8✔
787
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
788
                                                        }
789
                                                        length = val;
7✔
790
                                                }
791
                                                if (anyComma(lp)) {
7✔
792
                                                        if (!ParseExpression(lp, val)) {
5✔
793
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
1✔
794
                                                        }
795
                                                        if (val < 0 || val > 255) {
4✔
796
                                                                Error("[SAVETAP] Invalid flag byte", bp, PASS3); return;
2✔
797
                                                        }
798
                                                        param3 = val;
2✔
799
                                                }
800
                                        } else if (!anyComma(lp)) {
42✔
801
                                                fnaamh = GetDelimitedString(lp);
41✔
802
                                                if (fnaamh.empty()) {
41✔
803
                                                        Error("[SAVETAP] Syntax error in tape file name", bp, PASS3);
1✔
804
                                                        return;
1✔
805
                                                } else if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
40✔
806
                                                        if (val < 0) {
39✔
807
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
808
                                                        } else if (val > 0xFFFF) {
38✔
809
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
810
                                                        }
811
                                                        start = val;
37✔
812

813
                                                        if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
37✔
814
                                                                if (val < 0) {
36✔
815
                                                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
816
                                                                } else if (val > 0xFFFF) {
35✔
817
                                                                        Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
818
                                                                }
819
                                                                length = val;
34✔
820

821
                                                                if (anyComma(lp)) {
34✔
822
                                                                        if (!ParseExpression(lp, val)) {
24✔
823
                                                                                Error("[SAVETAP] Syntax error", bp, IF_FIRST); return;
1✔
824
                                                                        }
825
                                                                        if (val < 0) {
23✔
826
                                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
827
                                                                        } else if (val > 0xFFFF) {
22✔
828
                                                                                Error("[SAVETAP] Values more than FFFFh are not allowed", bp, PASS3); return;
1✔
829
                                                                        }
830
                                                                        param2 = val;
21✔
831
                                                                }
832
                                                                if (anyComma(lp)) {
31✔
833
                                                                        if (!ParseExpression(lp, val)) {
5✔
834
                                                                                Error("[SAVETAP] Syntax error", bp, IF_FIRST); return;
1✔
835
                                                                        }
836
                                                                        if (val < 0) {
4✔
837
                                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
838
                                                                        } else if (val > 0xFFFF) {
3✔
839
                                                                                Error("[SAVETAP] Values more than FFFFh are not allowed", bp, PASS3); return;
1✔
840
                                                                        }
841
                                                                        param3 = val;
2✔
842
                                                                }
843
                                                        } else {
844
                                                                Error("[SAVETAP] Syntax error. Missing block length", bp, PASS3); return;
1✔
845
                                                        }
846
                                                } else {
847
                                                        Error("[SAVETAP] Syntax error. Missing start address", bp, PASS3); return;
1✔
848
                                                }
849
                                        } else {
850
                                                Error("[SAVETAP] Syntax error. Missing tape block file name", bp, PASS3); return;
1✔
851
                                        }
852
                                } else {
853
                                        realtapeMode = false;
2✔
854
                                }
855
                        }
856
                        if (!realtapeMode) {
37✔
857
                                lp = tlp;
5✔
858
                                IsLabelNotFound = false;
5✔
859
                                if (!ParseExpression(lp, val) || IsLabelNotFound) {
5✔
860
                                        Error("[SAVETAP] Syntax error", bp, PASS3); return;
2✔
861
                                }
862
                                if (val < 0) {
3✔
863
                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
864
                                }
865
                                start = val;
2✔
866
                        }
867
                } else {
868
                        Error("[SAVETAP] Syntax error. No parameters", bp, PASS3); return;
1✔
869
                }
870
        } else if (StartAddress < 0) {
2✔
871
                Error("[SAVETAP] Syntax error. No parameters", bp, PASS3); return;
2✔
872
        } else {
873
                start = StartAddress;
×
874
        }
875

876
        if (exec) {
34✔
877
                int done = 0;
33✔
878

879
                if (realtapeMode) {
33✔
880
                        done = TAP_SaveBlock(fnaam, headerType, fnaamh.c_str(), start, length, param2, param3);
32✔
881
                } else {
882
                        if (!IsZXSpectrumDevice(DeviceID)) {
1✔
883
                                Error("[SAVETAP snapshot] Device is not of ZX Spectrum type.", Device->ID, SUPPRESS);
1✔
884
                        } else {
NEW
885
                                done = TAP_SaveSnapshot(fnaam, start);
×
886
                        }
887
                }
888

889
                if (!done) {
33✔
890
                        Error("[SAVETAP] Error writing file", bp, IF_FIRST);
1✔
891
                }
892
        }
893
}
94✔
894

895
static void dirSAVEBIN() {
84✔
896
        if (!DeviceID) {
84✔
897
                Error("SAVEBIN only allowed in real device emulation mode (See DEVICE)");
7✔
898
                SkipToEol(lp);
7✔
899
                return;
34✔
900
        }
901
        bool exec = (LASTPASS == pass);
77✔
902
        aint val;
903
        int start = -1, length = -1;
77✔
904
        const std::filesystem::path fnaam = GetOutputFileName(lp);
77✔
905
        if (anyComma(lp)) {
77✔
906
                if (!anyComma(lp)) {
74✔
907
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
71✔
908
                                Error("[SAVEBIN] Syntax error", bp, SUPPRESS); return;
6✔
909
                        }
910
                        if (val < 0) {
65✔
911
                                Error("[SAVEBIN] Values less than 0000h are not allowed", bp); return;
3✔
912
                        } else if (val > 0xFFFF) {
62✔
913
                                  Error("[SAVEBIN] Values more than FFFFh are not allowed", bp); return;
3✔
914
                        }
915
                        start = val;
59✔
916
                } else {
917
                          Error("[SAVEBIN] Syntax error. No parameters", bp, PASS3); return;
3✔
918
                }
919
                if (anyComma(lp)) {
59✔
920
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
56✔
921
                                Error("[SAVEBIN] Syntax error", bp, SUPPRESS); return;
6✔
922
                        }
923
                        if (val < 0) {
50✔
924
                                Error("[SAVEBIN] Negative values are not allowed", bp); return;
3✔
925
                        }
926
                        length = val;
47✔
927
                }
928
        } else {
929
                Error("[SAVEBIN] Syntax error. No parameters", bp); return;
3✔
930
        }
931

932
        if (exec && !SaveBinary(fnaam, start, length)) {
50✔
933
                Error("[SAVEBIN] Error writing file (Disk full?)", bp, IF_FIRST);
×
934
        }
935
}
77✔
936

937
static void dirSAVEDEV() {
90✔
938
        bool exec = DeviceID && LASTPASS == pass;
90✔
939
        if (!exec && LASTPASS == pass) Error("SAVEDEV only allowed in real device emulation mode (See DEVICE)");
90✔
940

941
        aint args[3]{-1, -1, -1};                // page, offset, length
90✔
942
        const std::filesystem::path fnaam = GetOutputFileName(lp);
90✔
943
        for (auto & arg : args) {
360✔
944
                if (!comma(lp) || !ParseExpression(lp, arg)) {
270✔
945
                        exec = false;
45✔
946
                        Error("Expected syntax SAVEDEV <filename>,<startPage>,<startOffset>,<length>", bp, SUPPRESS);
45✔
947
                }
948
        }
949
        if (exec) {
90✔
950
                // validate arguments
951
                if (args[0] < 0 || Device->PagesCount <= args[0]) {
22✔
952
                        exec = false; ErrorInt("[SAVEDEV] page number is out of range", args[0]);
2✔
953
                }
954
                const int32_t start = Device->GetMemoryOffset(args[0], args[1]);
22✔
955
                const int32_t totalRam = Device->GetMemoryOffset(Device->PagesCount, 0);
22✔
956
                if (exec && (start < 0 || totalRam <= start)) {
22✔
957
                        exec = false; ErrorInt("[SAVEDEV] calculated start address is out of range", start);
4✔
958
                }
959
                if (exec && (args[2] <= 0 || totalRam < start + args[2])) {
22✔
960
                        exec = false;
6✔
961
                        if (args[2]) ErrorInt("[SAVEDEV] invalid end address (bad length?)", start + args[2]);
6✔
962
                        else Warning("[SAVEDEV] zero length requested");
1✔
963
                }
964
                if (exec && !SaveDeviceMemory(fnaam, (size_t)start, (size_t)args[2])) {
22✔
965
                        Error("[SAVEDEV] Error writing file (Disk full?)", bp, IF_FIRST);
×
966
                }
967
        }
968
}
90✔
969

970
static void dirSAVE3DOS() {
63✔
971
        if (!DeviceID) {
63✔
972
                Error("SAVE3DOS works in real device emulation mode (See DEVICE)");
3✔
973
                SkipToEol(lp);
3✔
974
                return;
45✔
975
        }
976
        bool exec = (LASTPASS == pass);
60✔
977
        const std::filesystem::path fnaam = GetOutputFileName(lp);
60✔
978
        aint args[5] = { -1, -1, 3, -1, -1 };        // address, size, type, w2_line, w3
60✔
979
        const bool optional[] = {false, false, true, true, true};
60✔
980
        if (!anyComma(lp) || !getIntArguments<5>(lp, args, optional)) {
60✔
981
                Error("[SAVE3DOS] expected syntax is <filename>,<address>,<size>[,<type>[,<w2_line>[,<w3>]]]", bp, SUPPRESS);
27✔
982
                return;
27✔
983
        }
984
        aint &address = args[0], &size = args[1], &type = args[2], &w2_line = args[3], &w3 = args[4];
33✔
985
        if (address < 0 || size < 1 || 0x10000 < address + size) {
33✔
986
                Error("[SAVE3DOS] [address, size] region outside of 64ki", bp);
9✔
987
                return;
9✔
988
        }
989
        if (-1 == w3) w3 = size;        // default for w3 is size for all types, unless overridden
24✔
990
        switch (type) {
24✔
991
        case 0:                // type Program: default w2 = 0x8000
6✔
992
                if (-1 == w2_line) w2_line = 0x8000;
6✔
993
        case 1:                // type Numeric array: no idea what w2 actually should be for these
994
        case 2:                // type Character array:
995
                break;
12✔
996
        case 3:                // type Code: default w2 = load address
6✔
997
                if (-1 == w2_line) w2_line = address;
6✔
998
                break;
6✔
999
        default:
6✔
1000
                Error("[SAVE3DOS] expected type 0..3", bp);
6✔
1001
                return;
6✔
1002
        }
1003
        if (exec && !SaveBinary3dos(fnaam, address, size, type, w2_line, w3)) {
18✔
1004
                Error("[SAVE3DOS] Error writing file (Disk full?)", bp, IF_FIRST);
×
1005
        }
1006
}
60✔
1007

1008
static void dirSAVEAMSDOS() {
54✔
1009
        if (!DeviceID) {
54✔
1010
                Error("SAVEAMSDOS works in real device emulation mode (See DEVICE)");
3✔
1011
                SkipToEol(lp);
3✔
1012
                return;
33✔
1013
        }
1014
        bool exec = (LASTPASS == pass);
51✔
1015
        const std::filesystem::path fnaam = GetOutputFileName(lp);
51✔
1016
        aint args[] = { -1, -1, 0, 2 };        // address, size, start, type
51✔
1017
        const bool optional[] = {false, false, true, true};
51✔
1018
        if (!anyComma(lp) || !getIntArguments<4>(lp, args, optional)) {
51✔
1019
                Error("[SAVEAMSDOS] expected syntax is <filename>,<address>,<size>[,<start = 0>[,<type = 2>]", bp, SUPPRESS);
24✔
1020
                return;
24✔
1021
        }
1022
        aint &address = args[0], &size = args[1], &start = args[2], &type = args[3];
27✔
1023
        if (address < 0 || size < 1 || 0x10000 < address + size) {
27✔
1024
                Error("[SAVEAMSDOS] [address, size] region outside of 64ki", bp);
6✔
1025
                return;
6✔
1026
        }
1027
        check16u(start);
21✔
1028
        if (type < 0) type = -0x1000;                // check8 works for -256..+255 values, in this case just 0..255 is valid
21✔
1029
        check8(type);
21✔
1030
        if (exec && !SaveBinaryAmsdos(fnaam, address, size, start, type)) {
21✔
1031
                Error("[SAVEAMSDOS] Error writing file (Disk full?)", bp, IF_FIRST);
1✔
1032
        }
1033
}
51✔
1034

1035
static void dirSAVEHOB() {
15✔
1036
        if (!DeviceID || pass != LASTPASS) {
15✔
1037
                if (!DeviceID) Error("SAVEHOB only allowed in real device emulation mode (See DEVICE)");
11✔
1038
                SkipToEol(lp);
11✔
1039
                return;
14✔
1040
        }
1041
        aint val;
1042
        int start = -1,length = -1;
4✔
1043
        bool exec = true;
4✔
1044

1045
        const std::filesystem::path fnaam = GetOutputFileName(lp);
4✔
1046
        std::string fnaamh {""};
4✔
1047
        if (anyComma(lp)) {
4✔
1048
                if (!anyComma(lp)) {
3✔
1049
                        fnaamh = GetDelimitedString(lp);
2✔
1050
                        if (fnaamh.empty()) {
2✔
1051
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
1✔
1052
                        }
1053
                } else {
1054
                          Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
1✔
1055
                }
1056
        } else {
1057
                Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return; //is this ok?
1✔
1058
        }
1059

1060
        if (anyComma(lp)) {
1✔
1061
                if (!anyComma(lp)) {
1✔
1062
                        if (!ParseExpression(lp, val)) {
1✔
1063
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
×
1064
                        }
1065
                        if (val < 0x4000) {
1✔
1066
                                Error("[SAVEHOB] Values less than 4000h are not allowed", bp, PASS3); return;
×
1067
                        } else if (val > 0xFFFF) {
1✔
1068
                                  Error("[SAVEHOB] Values more than FFFFh are not allowed", bp, PASS3); return;
×
1069
                        }
1070
                        start = val;
1✔
1071
                } else {
1072
                          Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
×
1073
                }
1074
                if (anyComma(lp)) {
1✔
1075
                        if (!ParseExpression(lp, val)) {
1✔
1076
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
×
1077
                        }
1078
                        if (val < 0) {
1✔
1079
                                Error("[SAVEHOB] Negative values are not allowed", bp, PASS3); return;
×
1080
                        }
1081
                        length = val;
1✔
1082
                }
1083
        } else {
1084
                Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
×
1085
        }
1086
        if (exec && !SaveHobeta(fnaam, fnaamh.c_str(), start, length)) {
1✔
1087
                Error("[SAVEHOB] Error writing file (Disk full?)", bp, IF_FIRST); return;
×
1088
        }
1089
}
7✔
1090

1091
static void dirEMPTYTRD() {
39✔
1092
        if (pass != LASTPASS) {
39✔
1093
                SkipToEol(lp);
26✔
1094
                return;
28✔
1095
        }
1096
        char diskLabel[9] = "        ";
13✔
1097

1098
        const std::filesystem::path fnaam = GetOutputFileName(lp);
13✔
1099
        if (!fnaam.has_filename()) {
13✔
1100
                Error("[EMPTYTRD] Syntax error", bp, IF_FIRST);
2✔
1101
                return;
2✔
1102
        }
1103
        if (anyComma(lp)) {
11✔
1104
                const std::string srcLabel = GetDelimitedString(lp);
5✔
1105
                if (srcLabel.empty()) {
5✔
1106
                        Error("[EMPTYTRD] Syntax error, empty label", bp, IF_FIRST);
1✔
1107
                } else {
1108
                        memcpy(diskLabel, srcLabel.data(), std::min(static_cast<std::string::size_type>(8), srcLabel.size()));
4✔
1109
                        if (8 < srcLabel.size()) {
4✔
1110
                                Warning("[EMPTYTRD] label will be truncated to 8 characters", diskLabel);
1✔
1111
                        }
1112
                }
1113
        }
5✔
1114
        TRD_SaveEmpty(fnaam, diskLabel);
11✔
1115
}
13✔
1116

1117
static void dirSAVETRD() {
570✔
1118
        if (!DeviceID || pass != LASTPASS) {
570✔
1119
                if (!DeviceID) Error("SAVETRD only allowed in real device emulation mode (See DEVICE)");
381✔
1120
                SkipToEol(lp);
381✔
1121
                return;
384✔
1122
        }
1123

1124
        bool exec = true, replace = false, addplace = false;
189✔
1125
        aint val;
1126
        int start = -1, length = -1, autostart = -1, lengthMinusVars = -1;
189✔
1127

1128
        const std::filesystem::path fnaam = GetOutputFileName(lp);
189✔
1129
        std::string fnaamh {""};
189✔
1130
        if (anyComma(lp)) {
189✔
1131
                if (!anyComma(lp)) {
189✔
1132
                        if ((replace = ('|' == *lp))) SkipBlanks(++lp);        // detect "|" for "replace" feature
189✔
1133
                        else if ((addplace = ('&' == *lp))) SkipBlanks(++lp); // detect "&" for "addplace" feature
182✔
1134
                        fnaamh = GetDelimitedString(lp);
189✔
1135
                        if (fnaamh.empty()) {
189✔
UNCOV
1136
                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
×
1137
                        }
1138
                } else {
1139
                          Error("[SAVETRD] Syntax error. No parameters", bp, PASS3); return;
×
1140
                }
1141
        } else {
1142
                Error("[SAVETRD] Syntax error. No parameters", bp, PASS3); return; //is this ok?
×
1143
        }
1144

1145
        if (anyComma(lp)) {
189✔
1146
                if (!anyComma(lp)) {
189✔
1147
                        if (!ParseExpression(lp, val)) {
189✔
1148
                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
×
1149
                        }
1150
                        if (val > 0xFFFF) {
189✔
1151
                                  Error("[SAVETRD] Values more than 0FFFFh are not allowed", bp, PASS3); return;
×
1152
                        }
1153
                        start = val;
189✔
1154
                } else {
1155
                          Error("[SAVETRD] Syntax error. No parameters", bp, PASS3); return;
×
1156
                }
1157
                if (anyComma(lp)) {
189✔
1158
                        if (!anyComma(lp)) {
189✔
1159
                                if (!ParseExpression(lp, val)) {
189✔
1160
                                        Error("[SAVETRD] Syntax error", bp, PASS3); return;
×
1161
                                }
1162
                                if (val < 0) {
189✔
1163
                                        Error("[SAVETRD] Negative values are not allowed", bp, PASS3); return;
×
1164
                                }
1165
                                length = val;
189✔
1166
                        } else {
1167
                                Error("[SAVETRD] Syntax error. No parameters", bp, PASS3); return;
×
1168
                        }
1169
                }
1170
                if (anyComma(lp)) {
189✔
1171
                        if (addplace) {
14✔
1172
                                Error("[SAVETRD] Autostart is not used here", bp, PASS3); return;
1✔
1173
                        } else {
1174
                                if (!ParseExpression(lp, val)) {
13✔
1175
                                        Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1176
                                }
1177
                                if (val < 0) {
12✔
1178
                                        Error("[SAVETRD] Negative values are not allowed", bp, PASS3); return;
×
1179
                                }
1180
                                autostart = val;
12✔
1181
                                // optional length of BASIC without variables
1182
                                if (anyComma(lp)) {
12✔
1183
                                        if (!ParseExpression(lp, val)) {
5✔
1184
                                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1185
                                        }
1186
                                        lengthMinusVars = val;
4✔
1187
                                }
1188
                        }
1189
                }
1190
        } else {
1191
                Error("[SAVETRD] Syntax error. No parameters", bp, PASS3); return;
×
1192
        }
1193

1194
        if (exec) TRD_AddFile(fnaam, fnaamh.c_str(), start, length, autostart, replace, addplace, lengthMinusVars);
186✔
1195
}
192✔
1196

1197
static void dirENCODING() {
30✔
1198
        auto arg = GetDelimitedString(lp);
30✔
1199
        char* aP = arg.data();
30✔
1200
        if (cmphstr(aP, "dos")) {
30✔
1201
                ConvertEncoding = ENCDOS;
6✔
1202
        } else if (cmphstr(aP, "win")) {
24✔
1203
                ConvertEncoding = ENCWIN;
6✔
1204
        } else {
1205
                Error("[ENCODING] Invalid argument (valid values: \"dos\" and \"win\")", aP, IF_FIRST);
18✔
1206
        }
1207
}
30✔
1208

1209
static void dirOPT() {
447✔
1210
        // supported options: --zxnext[=cspect] --reversepop --dirbol --nofakes --syntax=<...> -W...
1211
        // process OPT specific command keywords first: {push, pop, reset, listoff, liston, listall, listact, listmc}
1212
        bool didReset = false, didList = Options::syx.IsListingSuspended;
447✔
1213
        while (!SkipBlanks(lp) && '-' != *lp) {
715✔
1214
                if (cmphstr(lp, "pop")) {        // "pop" previous syntax state
352✔
1215
                        if (!Options::SSyntax::popSyntax()) Warning("[OPT] no previous syntax found");
81✔
1216
                        return;
84✔
1217
                } else if (cmphstr(lp, "push")) {        // "push" previous syntax state
271✔
1218
                        if (didReset) Warning("[OPT] pushing syntax status after reset");
78✔
1219
                        // preserve current syntax status, before using arguments of OPT
1220
                        Options::SSyntax::pushCurrentSyntax();
78✔
1221
                } else if (cmphstr(lp, "reset")) {        // keep current syntax state
193✔
1222
                        Options::SSyntax::resetCurrentSyntax();
109✔
1223
                        didReset = true;
109✔
1224
                } else if (cmphstr(lp, "listoff")) {
84✔
1225
                        if (!didList) {
42✔
1226
                                ListFile();                // *list* the OPT line suspending the listing
39✔
1227
                                // show in listing file that some part was suspended
1228
                                FILE* listFile = GetListingFile();
39✔
1229
                                if (LASTPASS == pass && listFile) fputs("# listing file suspended...\n", listFile);
39✔
1230
                        }
1231
                        donotlist = 1;
42✔
1232
                        Options::syx.IsListingSuspended = didList = true;
42✔
1233
                } else if (cmphstr(lp, "liston")) {
42✔
1234
                        Options::syx.IsListingSuspended = false;
9✔
1235
                } else if (cmphstr(lp, "listall")) {
33✔
1236
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
3✔
1237
                        didList = true;
3✔
1238
                        donotlist = 1;
3✔
1239
                        Options::syx.ListingType = Options::LST_T_ALL;
3✔
1240
                } else if (cmphstr(lp, "listact")) {
30✔
1241
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
12✔
1242
                        didList = true;
12✔
1243
                        donotlist = 1;
12✔
1244
                        Options::syx.ListingType = Options::LST_T_ACTIVE;
12✔
1245
                } else if (cmphstr(lp, "listmc")) {
18✔
1246
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
15✔
1247
                        didList = true;
15✔
1248
                        donotlist = 1;
15✔
1249
                        Options::syx.ListingType = Options::LST_T_MC_ONLY;
15✔
1250
                } else {
1251
                        Error("[OPT] invalid command (valid commands: push, pop, reset, liston, listoff, listall, listact, listmc)", lp);
3✔
1252
                        SkipToEol(lp);
3✔
1253
                        return;
3✔
1254
                }
1255
        }
1256
        // split user arguments into "argc, argv" like variables (by white-space)
1257
        char parsedOpts[LINEMAX];
1258
        std::vector<char*> parsedOptsArray;
363✔
1259
        int charI = 0, errI;
363✔
1260
        while (!SkipBlanks(lp)) {
718✔
1261
                parsedOptsArray.push_back(parsedOpts + charI);
355✔
1262
                while (*lp && !White()) parsedOpts[charI++] = *lp++;
3,705✔
1263
                parsedOpts[charI++] = 0;
355✔
1264
        }
1265
        int optI = parsedOptsArray.size();
363✔
1266
        parsedOptsArray.push_back(nullptr);
363✔
1267
        // parse user arguments and adjust current syntax setup
1268
        if (optI != (errI = Options::parseSyntaxOptions(optI, parsedOptsArray.data()))) {
363✔
1269
                Error("[OPT] invalid/failed option", parsedOptsArray[errI]);
3✔
1270
        }
1271
        // init Z80N extensions if requested (the Init is safe to be called multiple times)
1272
        if (Options::syx.IsNextEnabled) Z80::InitNextExtensions();
361✔
1273
}
361✔
1274

1275
static void dirLABELSLIST() {
39✔
1276
        if (pass != 1 || !DeviceID) {
39✔
1277
                if (!DeviceID) Error("LABELSLIST only allowed in real device emulation mode (See DEVICE)");
27✔
1278
                SkipToEol(lp);
27✔
1279
                return;
28✔
1280
        }
1281
        const std::filesystem::path opt = GetOutputFileName(lp);
12✔
1282
        if (opt.has_filename()) {
12✔
1283
                Options::UnrealLabelListFName = opt;
11✔
1284
                Options::EmitVirtualLabels = false;
11✔
1285
                if (comma(lp)) {
11✔
1286
                        aint virtualLabelsArg;
1287
                        if (!ParseExpressionNoSyntaxError(lp, virtualLabelsArg)) {
2✔
1288
                                Error("[LABELSLIST] Syntax error in <virtual labels>", bp, EARLY);
1✔
1289
                                return;
1✔
1290
                        }
1291
                        Options::EmitVirtualLabels = (virtualLabelsArg != 0);
1✔
1292
                }
1293
        } else {
1294
                Error("[LABELSLIST] No filename", bp, EARLY);        // pass == 1 -> EARLY
1✔
1295
        }
1296
}
12✔
1297

1298
static void dirCSPECTMAP() {
39✔
1299
        if (LASTPASS != pass || !DeviceID) {
39✔
1300
                if (!DeviceID) Error("CSPECTMAP only allowed in real device emulation mode (See DEVICE)");
27✔
1301
                SkipParam(lp);
27✔
1302
                return;
27✔
1303
        }
1304
        const std::filesystem::path fName = GetOutputFileName(lp);
12✔
1305
        if (fName.has_filename()) {
12✔
1306
                Options::CSpectMapFName = fName;
11✔
1307
        } else {                // create default map file name from current source file name (appends ".map")
1308
                assert(!sourcePosStack.empty());
1✔
1309
                Options::CSpectMapFName = sourcePosStack.back().filename;
1✔
1310
                Options::CSpectMapFName += ".map";
1✔
1311
        }
1312
        // remember page size of current device (in case the source is multi-device later)
1313
        Options::CSpectMapPageSize = Device->GetPage(0)->Size;
12✔
1314
}
12✔
1315

1316
static void dirBPLIST() {
33✔
1317
        // breakpoint file is opened in second pass, and content is written through third pass
1318
        // so position of `BPLIST` directive in source does not matter
1319
        if (2 != pass || !DeviceID) {        // nothing to do in first or last pass, second will open the file
33✔
1320
                if (2 == pass) {        // !Device is true -> no device in second pass -> error
23✔
1321
                        Error("BPLIST only allowed in real device emulation mode (See DEVICE)", nullptr, EARLY);
1✔
1322
                }
1323
                SkipToEol(lp);
23✔
1324
                return;
23✔
1325
        }
1326
        const std::filesystem::path fName = GetOutputFileName(lp);
10✔
1327
        EBreakpointsFile type = BPSF_UNREAL;
10✔
1328
        if (cmphstr(lp, "unreal")) {
10✔
1329
                type = BPSF_UNREAL;
2✔
1330
        } else if (cmphstr(lp, "zesarux")) {
8✔
1331
                type = BPSF_ZESARUX;
3✔
1332
        } else if (!SkipBlanks()) {
5✔
1333
                Warning("[BPLIST] invalid breakpoints file type (use \"unreal\" or \"zesarux\")", lp, W_EARLY);
2✔
1334
        }
1335
        OpenBreakpointsFile(fName, type);
10✔
1336
}
10✔
1337

1338
static void dirSETBREAKPOINT() {
363✔
1339
        if (LASTPASS != pass) {
363✔
1340
                SkipToEol(lp);
242✔
1341
                return;
242✔
1342
        }
1343
        aint val = 0;
121✔
1344
        if (SkipBlanks(lp)) {                // without any expression do the "$" breakpoint
121✔
1345
                WriteBreakpoint(CurAddress);
9✔
1346
        } else if (ParseExpressionNoSyntaxError(lp, val)) {
112✔
1347
                WriteBreakpoint(val);
111✔
1348
        } else {
1349
                Error("[SETBREAKPOINT] Syntax error", bp, SUPPRESS);
1✔
1350
        }
1351
}
1352

1353
/*void dirTEXTAREA() {
1354

1355
}*/
1356

1357
// error message templates for IF**some** directives
1358
constexpr static size_t dirIfErrorsN = 2, dirIfErrorsSZ = 48;
1359
const static char dirIfErrorsTxtSrc[dirIfErrorsN][dirIfErrorsSZ] = {
1360
        { "[%s] No ENDIF" },
1361
        { "[%s] one ELSE only expected" }
1362
};
1363

1364
// IF and IFN internal helper, to evaluate expression
1365
static bool dirIfIfn(aint & val) {
23,111✔
1366
        IsLabelNotFound = false;
23,111✔
1367
        if (!ParseExpression(lp, val)) {
23,111✔
1368
                Error("[IF/IFN] Syntax error", lp, IF_FIRST);
9✔
1369
                return false;
9✔
1370
        }
1371
        if (IsLabelNotFound) {
23,102✔
1372
                WarningById(W_FWD_REF, bp, W_EARLY);
13✔
1373
        }
1374
        return true;
23,102✔
1375
}
1376

1377
// main IF implementation parsing/skipping part of source depending on "val", handling ELSE/ENDIF
1378
static void dirIfInternal(const char* dirName, aint val) {
24,116✔
1379
        // set up error messages for the particular pseudo-op
1380
        char errorsTxt[dirIfErrorsN][dirIfErrorsSZ];
1381
        for (size_t i = 0; i < dirIfErrorsN; ++i) {
72,348✔
1382
                SPRINTF1(errorsTxt[i], dirIfErrorsSZ, dirIfErrorsTxtSrc[i], dirName);
48,232✔
1383
        }
1384
        // do the IF**some** part
1385
        ListFile();
24,116✔
1386
        EReturn ret = END;
24,116✔
1387
        aint elseCounter = 0;
24,116✔
1388
        aint orVal = false;
24,116✔
1389
        while (ENDIF != ret) {
67,891✔
1390
                orVal |= val;
43,784✔
1391
                switch (ret = val ? ReadFile() : SkipFile()) {
43,784✔
1392
                        case ELSE:
19,509✔
1393
                                if (elseCounter++) Error(errorsTxt[1]);
19,509✔
1394
                                val = !val && !orVal;
19,509✔
1395
                                break;
19,509✔
1396
                        case ELSEIF:
159✔
1397
                                val = !val && !orVal;
159✔
1398
                                if (val) {                // active ELSEIF, evaluate expression
159✔
1399
                                        if (!dirIfIfn(val)) {
96✔
1400
                                                val = false;                // syntax error in expression
3✔
1401
                                                orVal = true;                // force remaining IF-blocks inactive
3✔
1402
                                        }
1403
                                }
1404
                                break;
159✔
1405
                        case ENDIF:
24,107✔
1406
                                break;
24,107✔
1407
                        default:
9✔
1408
                                if (IsRunning) Error(errorsTxt[0]);
9✔
1409
                                donotlist=!IsRunning;                // do the listing only if still running
9✔
1410
                                return;
9✔
1411
                }
1412
        }
1413
}
1414

1415
static void dirIF() {
20,669✔
1416
        aint val;
1417
        if (dirIfIfn(val)) dirIfInternal("IF", val);
20,669✔
1418
}
20,669✔
1419

1420
static void dirIFN() {
2,346✔
1421
        aint val;
1422
        if (dirIfIfn(val)) dirIfInternal("IFN", !val);
2,346✔
1423
}
2,346✔
1424

1425
// IFUSED and IFNUSED internal helper, to parse label
1426
static bool dirIfusedIfnused(char* & id) {
120✔
1427
        id = NULL;
120✔
1428
        if (SkipBlanks()) {                                                // no argument (use last parsed label)
120✔
1429
                if (LastParsedLabel) {
15✔
1430
                        id = STRDUP(LastParsedLabel);
9✔
1431
                } else {
1432
                        Error("[IFUSED/IFNUSED] no label defined ahead");
6✔
1433
                        return false;
6✔
1434
                }
1435
        } else {
1436
                std::unique_ptr<char[]> validLabel(ValidateLabel(lp, false, true));
105✔
1437
                if (validLabel) {
105✔
1438
                        id = STRDUP(validLabel.get());
96✔
1439
                        while (islabchar(*lp)) ++lp;        // advance lp beyond parsed label (valid chars only)
990✔
1440
                } else {
1441
                        SkipToEol(lp);                                        // ValidateLabel aready reported some error, skip rest
9✔
1442
                }
1443
        }
105✔
1444
        return id && SkipBlanks();                                // valid "id" and no extra characters = OK
114✔
1445
}
1446

1447
static void dirIFUSED() {
81✔
1448
        char* id;
1449
        if (dirIfusedIfnused(id)) dirIfInternal("IFUSED", LabelTable.IsUsed(id));
81✔
1450
        if (id) free(id);
81✔
1451
}
81✔
1452

1453
static void dirIFNUSED() {
39✔
1454
        char* id;
1455
        if (dirIfusedIfnused(id)) dirIfInternal("IFNUSED", !LabelTable.IsUsed(id));
39✔
1456
        if (id) free(id);
39✔
1457
}
39✔
1458

1459
static void dirIFDEF() {
114✔
1460
        char* id;
1461
        if ((id = GetID(lp)) && *id) {
114✔
1462
                dirIfInternal("IFDEF", DefineTable.FindDuplicate(id));
111✔
1463
        } else {
1464
                Error("[IFDEF] Illegal identifier", bp);
3✔
1465
        }
1466
}
114✔
1467

1468
static void dirIFNDEF() {
900✔
1469
        char* id;
1470
        if ((id = GetID(lp)) && *id) {
900✔
1471
                dirIfInternal("IFNDEF", !DefineTable.FindDuplicate(id));
897✔
1472
        } else {
1473
                Error("[IFNDEF] Illegal identifier", bp);
3✔
1474
        }
1475
}
900✔
1476

1477
static void dirElseCheckLiveDup() {
12✔
1478
        if (RepeatStack.empty()) return;
12✔
1479
        if (!RepeatStack.top().IsInWork) return;
3✔
1480

1481
        // Seems some ELSE/ELSEIF/ENDIF was encountered inside DUP->EDUP without starting IF
1482
        // -> probably IF was outside of DUP->EDUP block, which is not legal in sjasmplus
1483
        // terminate the current DUP->EDUP macro early and report the open ELSE/ELSEIF/ENDIF
1484
        Error("Conditional block must start and finish inside the repeat block, nested completely");
3✔
1485
        lijstp = nullptr;
3✔
1486
        RepeatStack.top().RepeatCount = 0;
3✔
1487
}
1488

1489
static void dirELSE() {
6✔
1490
        dirElseCheckLiveDup();
6✔
1491
        Error("ELSE without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
6✔
1492
}
6✔
1493

1494
static void dirELSEIF() {
3✔
1495
        dirElseCheckLiveDup();
3✔
1496
        Error("ELSEIF without IF/IFN");
3✔
1497
}
3✔
1498

1499
static void dirENDIF() {
3✔
1500
        dirElseCheckLiveDup();
3✔
1501
        Error("ENDIF without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
3✔
1502
}
3✔
1503

1504
/*void dirENDTEXTAREA() {
1505
  Error("ENDT without TEXTAREA",0);
1506
}*/
1507

1508
static void dirINCLUDE() {
471✔
1509
        const std::filesystem::path fnaam = GetFileName(lp);
471✔
1510
        if (fnaam.has_filename()) {
471✔
1511
                EDelimiterType dt = GetDelimiterOfLastFileName();
465✔
1512
                ListFile();
465✔
1513
                IncludeFile(fnaam.string().c_str(), DT_ANGLE == dt);        //FIXME path idea possible with refactoring^2
465✔
1514
                donotlist = 1;
465✔
1515
        } else {
1516
                Error("[INCLUDE] empty filename", bp);
6✔
1517
        }
1518
}
471✔
1519

1520
static void dirOUTPUT() {
357✔
1521
        if (LASTPASS != pass) {
357✔
1522
                SkipToEol(lp);
238✔
1523
                return;
241✔
1524
        }
1525
        const std::filesystem::path fnaam = GetOutputFileName(lp);
119✔
1526
        char modechar = 0;
119✔
1527
        int mode = OUTPUT_TRUNCATE;
119✔
1528
        if (comma(lp)) {
119✔
1529
                if (!SkipBlanks(lp)) modechar = (*lp++) | 0x20;
8✔
1530
                switch (modechar) {
8✔
1531
                        case 't': mode = OUTPUT_TRUNCATE;        break;
1✔
1532
                        case 'r': mode = OUTPUT_REWIND;                break;
3✔
1533
                        case 'a': mode = OUTPUT_APPEND;                break;
1✔
1534
                        default:
3✔
1535
                                Error("[OUTPUT] Invalid <mode> (valid modes: t, a, r)", bp);
3✔
1536
                                return;
3✔
1537
                }
1538
        }
1539
        //Options::NoDestinationFile = false;
1540
        NewDest(fnaam, mode);
116✔
1541
}
119✔
1542

1543
static void dirOUTEND()
54✔
1544
{
1545
        if (pass == LASTPASS) CloseDest();
54✔
1546
}
54✔
1547

1548
static void dirTAPOUT()
30✔
1549
{
1550
        aint val;
1551
        const std::filesystem::path fnaam = GetOutputFileName(lp);
30✔
1552
        int tape_flag = 255;
30✔
1553
        if (comma(lp))
30✔
1554
        {
1555
                if (!ParseExpression(lp, val))
12✔
1556
                {
1557
                        Error("[TAPOUT] Missing flagbyte value", bp, PASS3); return;
3✔
1558
                }
1559
                tape_flag = val;
9✔
1560
        }
1561
        if (pass == LASTPASS) OpenTapFile(fnaam, tape_flag);
27✔
1562
}
30✔
1563

1564
static void dirTAPEND()
24✔
1565
{
1566
        // if (!FP_tapout) {Error("TAPEND without TAPOUT", bp, PASS3); return;}
1567
        if (pass == LASTPASS) CloseTapFile();
24✔
1568
}
24✔
1569

1570
static void dirDEFINE() {
285✔
1571
        bool replaceEnabled = ('+' == *lp) ? ++lp, true : false;
285✔
1572
        char* id = GetID(lp);
285✔
1573
        if (nullptr == id) {
285✔
1574
                Error("[DEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1575
                return;
3✔
1576
        }
1577
        if (White(*lp)) ++lp;                // skip one whitespace (not considered part of value) (others are)
282✔
1578
        // but trim trailing spaces of value, if there's eol-comment
1579
        if (eolComment) {
282✔
1580
                char *rtrim = lp + strlen(lp);
6✔
1581
                while (lp < rtrim && ' ' == rtrim[-1]) --rtrim;
48✔
1582
                *rtrim = 0;
6✔
1583
        }
1584

1585
        if (replaceEnabled) {
282✔
1586
                DefineTable.Replace(id, lp);
9✔
1587
        } else {
1588
                DefineTable.Add(id, lp, nullptr);
273✔
1589
        }
1590
        SkipToEol(lp);
282✔
1591
        substitutedLine = line;                // override substituted listing for DEFINE
282✔
1592
}
1593

1594
static void dirUNDEFINE() {
105✔
1595
        char* id;
1596

1597
        if (!(id = GetID(lp)) && *lp != '*') {
105✔
1598
                Error("[UNDEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1599
                return;
3✔
1600
        }
1601

1602
        if (*lp == '*') {
102✔
1603
                ++lp;
3✔
1604
                DefineTable.RemoveAll();
3✔
1605
        } else if (DefineTable.FindDuplicate(id)) {
99✔
1606
                DefineTable.Remove(id);
90✔
1607
        } else {
1608
                Warning("[UNDEFINE] Identifier not found", id);
9✔
1609
        }
1610
}
1611

1612
static void dirEXPORT() {
60✔
1613
        aint val;
1614
        char* n, * p;
1615

1616
        if (Options::ExportFName.empty()) {
60✔
1617
                assert(!sourcePosStack.empty());
2✔
1618
                Options::ExportFName = sourcePosStack.back().filename;
2✔
1619
                Options::ExportFName.replace_extension(".exp");
2✔
1620
                Warning("[EXPORT] Filename for exportfile was not indicated. Output will be in", Options::ExportFName.string().c_str(), W_EARLY);
2✔
1621
        }
1622
        if (!(n = p = GetID(lp))) {
60✔
1623
                Error("[EXPORT] Syntax error", lp, SUPPRESS);
12✔
1624
                return;
44✔
1625
        }
1626
        if (pass != LASTPASS) return;
48✔
1627
        IsLabelNotFound = false;
16✔
1628
        GetLabelValue(n, val);
16✔
1629
        if (!IsLabelNotFound) WriteExp(p, val);
16✔
1630
}
1631

1632
static void dirDISPLAY() {
102✔
1633
        char decprint = 'H';
102✔
1634
        char e[LINEMAX + 32], optionChar;                // put extra buffer at end for particular H/A/D number printout
1635
        char* ep = e, * const endOfE = e + LINEMAX;
102✔
1636
        aint val;
1637
        do {
1638
                if (SkipBlanks()) {
2,406✔
1639
                        Error("[DISPLAY] Expression expected");
3✔
1640
                        break;
3✔
1641
                }
1642
                if (*lp == '/') {
2,403✔
1643
                        switch (optionChar = toupper((byte)lp[1])) {
114✔
1644
                        case 'A': case 'D': case 'H': case 'B': case 'C':
99✔
1645
                                // known options, switching hex+dec / dec / hex / binary mode / char mode
1646
                                decprint = optionChar;
99✔
1647
                                break;
99✔
1648
                        case 'L': case 'T':                                // silently ignored options (legacy compatibility)
12✔
1649
                                // in ALASM: 'L' is "concatenate to previous line" (as if there was no \r\n on it)
1650
                                // in ALASM: 'T' used ahead of expression will display first the expression itself, then value
1651
                                break ;
12✔
1652
                        default:
3✔
1653
                                Error("[DISPLAY] Syntax error, unknown option", lp, SUPPRESS);
3✔
1654
                                return;
15✔
1655
                        }
1656
                        lp += 2;
111✔
1657
                        continue;
111✔
1658
                }
1659
                // try to parse some string literal
1660
                const int remainingBufferSize = endOfE - ep;
2,289✔
1661
                if (remainingBufferSize <= 0) {
2,289✔
1662
                        Error("[DISPLAY] internal buffer overflow, resulting text is too long", line);
3✔
1663
                        return;
3✔
1664
                }
1665
                int ei = 0;
2,286✔
1666
                val = GetCharConstAsString(lp, ep, ei, remainingBufferSize);
2,286✔
1667
                if (-1 == val) {
2,286✔
1668
                        Error("[DISPLAY] Syntax error", line);
3✔
1669
                        return;
3✔
1670
                } else if (val) {
2,283✔
1671
                        ep += ei;                                // string literal successfuly parsed
265✔
1672
                } else {
1673
                        // string literal was not there, how about expression?
1674
                        if (ParseExpressionNoSyntaxError(lp, val)) {
2,018✔
1675
                                if (decprint == 'B') {        // 8-bit binary (doesn't care about higher bits)
2,015✔
1676
                                        *(ep++) = '%';
12✔
1677
                                        aint bitMask = 0x80;
12✔
1678
                                        while (bitMask) {
108✔
1679
                                                *(ep++) = (val & bitMask) ? '1' : '0';
96✔
1680
                                                if (0x10 == bitMask) *(ep++) = '\'';
96✔
1681
                                                bitMask >>= 1;
96✔
1682
                                        }
1683
                                }
1684
                                if (decprint == 'C') {
2,015✔
1685
                                        val &= 0xFF;        // truncate to 8bit value
12✔
1686
                                        if (' ' <= val && val < 127) {        // printable ASCII
12✔
1687
                                                *ep++ = '\'';
3✔
1688
                                                *ep++ = val;
3✔
1689
                                                *ep++ = '\'';
3✔
1690
                                        } else {                // non-printable char, do the \x?? form
1691
                                                *ep++ = '\'';
9✔
1692
                                                *ep++ = '\\';
9✔
1693
                                                *ep++ = 'x';
9✔
1694
                                                PrintHex(ep, val, 2);
9✔
1695
                                                *ep++ = '\'';
9✔
1696
                                        }
1697
                                }
1698
                                if (decprint == 'H' || decprint == 'A') {
2,015✔
1699
                                        *(ep++) = '0';
1,967✔
1700
                                        *(ep++) = 'x';
1,967✔
1701
                                        PrintHexAlt(ep, val);
1,967✔
1702
                                }
1703
                                if (decprint == 'D' || decprint == 'A') {
2,015✔
1704
                                        if (decprint == 'A') {
54✔
1705
                                                *(ep++) = ','; *(ep++) = ' ';
30✔
1706
                                        }
1707
                                        int charsToPrint = SPRINTF1(ep, remainingBufferSize, "%u", val);
54✔
1708
                                        if (remainingBufferSize <= charsToPrint) {
54✔
1709
                                                Error("[DISPLAY] internal buffer overflow, resulting text is too long", line);
3✔
1710
                                                return;
3✔
1711
                                        }
1712
                                        ep += charsToPrint;
51✔
1713
                                }
1714
                                decprint = 'H';
2,012✔
1715
                        } else {
1716
                                Error("[DISPLAY] Syntax error", line, SUPPRESS);
3✔
1717
                                return;
3✔
1718
                        }
1719
                }
1720
        } while(comma(lp));
2,388✔
1721
        *ep = 0; // end line
87✔
1722

1723
        if (LASTPASS == pass && *e) {
87✔
1724
                _CERR "> " _CMDL Options::tcols->display _CMDL e _CMDL Options::tcols->end _ENDL;
27✔
1725
        }
1726
}
1727

1728
static void dirMACRO() {
573✔
1729
        if (lijst) {
573✔
1730
                Error("[MACRO] No macro definitions allowed here", NULL, SUPPRESS);
60✔
1731
                return;
60✔
1732
        }
1733
        // check if the name of macro is defined at beginning of the line ("label" name)
1734
        const bool labelName = LastParsedLabelLine == CompiledCurrentLine;
513✔
1735
        assert(!labelName || LastParsedLabel);
513✔
1736
        char* lpLabel = labelName ? LastParsedLabel : lp;        // temporary pointer to advance by GetID
513✔
1737
        char* n = GetID(lpLabel);                                                        // get+validate macro name
513✔
1738
        if (*lpLabel && !White(*lpLabel)) n = nullptr;                // if there's unexpected trailing char, report illegal name
513✔
1739
        if (n) {
513✔
1740
                if (!labelName) lp = lpLabel;                                        // name was after MACRO keyword, advance global `lp` (to parse arguments)
486✔
1741
                MacroTable.Add(n, lp);
486✔
1742
        } else {
1743
                Error("[MACRO] Illegal macroname", labelName ? LastParsedLabel : lp);        // report what was fed into GetID
27✔
1744
                SkipToEol(lp);
27✔
1745
        }
1746
}
1747

1748
static void dirENDS() {
9✔
1749
        Error("[ENDS] End structure without structure");
9✔
1750
}
9✔
1751

1752
static void dirASSERT() {
2,240✔
1753
        char* p = lp;
2,240✔
1754
        aint val;
1755
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,240✔
1756
                Error("[ASSERT] Syntax error", p, SUPPRESS);
12✔
1757
                return;
12✔
1758
        }
1759
        if (pass == LASTPASS && !val) {
2,228✔
1760
                Error("[ASSERT] Assertion failed", p);
6✔
1761
        }
1762
        if (comma(lp)) SkipToEol(lp);
2,228✔
1763
}
1764

1765
static void dirSHELLEXEC() {
9✔
1766
        //TODO for v2.x change the "SHELLEXEC <command>[, <params>]" syntax to "SHELLEXEC <whatever>"
1767
        // (and add good examples how to deal with quotes/colons/long file names with spaces)
1768
        std::string command = GetDelimitedString(lp);
9✔
1769
        std::string parameters {""};
9✔
1770
        if (comma(lp)) {
9✔
1771
                parameters = GetDelimitedString(lp);
6✔
1772
        }
1773
        if (pass == LASTPASS) {
9✔
1774
                if (!system(nullptr)) {
3✔
1775
                        Error("[SHELLEXEC] clib command processor is not available on this platform!");
×
1776
                } else {
1777
                        if (!parameters.empty()) command += " " + parameters;
3✔
1778
                        if (Options::OutputVerbosity <= OV_ALL) {
3✔
NEW
1779
                                _CERR "Executing <" _CMDL command _CMDL ">" _ENDL;
×
1780
                        }
1781
                        // flush both stdout and stderr before trying to execute anything externally
1782
                        _COUT flush;
3✔
1783
                        _CERR flush;
3✔
1784
                        // execute the requested command
1785
                        int exitCode = system(command.c_str());
3✔
1786
                        if (exitCode) {
3✔
1787
                                ErrorInt("[SHELLEXEC] non-zero exit code", WEXITSTATUS(exitCode));
1✔
1788
                        }
1789
                }
1790
        }
1791
}
9✔
1792

1793
static void dirSTRUCT() {
246✔
1794
        CStructure* st;
1795
        int global = 0;
246✔
1796
        aint offset = 0;
246✔
1797
        char* naam;
1798
        SkipBlanks();
246✔
1799
        if (*lp == '@') {
246✔
1800
                ++lp; global = 1;
15✔
1801
        }
1802

1803
        if (!(naam = GetID(lp)) || !strlen(naam)) {
246✔
1804
                Error("[STRUCT] Illegal structure name", lp, SUPPRESS);
3✔
1805
                return;
243✔
1806
        }
1807
        if (comma(lp)) {
243✔
1808
                IsLabelNotFound = false;
45✔
1809
                if (!ParseExpressionNoSyntaxError(lp, offset)) {
45✔
1810
                        Error("[STRUCT] Offset syntax error", lp, SUPPRESS);
3✔
1811
                        return;
3✔
1812
                }
1813
                if (IsLabelNotFound) {
42✔
1814
                        Error("[STRUCT] Forward reference", NULL, EARLY);
5✔
1815
                }
1816
        }
1817
        if (!SkipBlanks()) {
240✔
1818
                Error("[STRUCT] syntax error, unexpected", lp);
3✔
1819
        }
1820
        st = StructureTable.Add(naam, offset, global);
240✔
1821
        ListFile();
240✔
1822
        while (ReadLine()) {
1,437✔
1823
                lp = line; /*if (White()) { SkipBlanks(lp); if (*lp=='.') ++lp; if (cmphstr(lp,"ends")) break; }*/
1,434✔
1824
                SkipBlanks(lp);
1,434✔
1825
                if (*lp == '.') {
1,434✔
1826
                        ++lp;
21✔
1827
                }
1828
                if (cmphstr(lp, "ends")) {
1,434✔
1829
                        ++CompiledCurrentLine;
237✔
1830
                        if (st) st->deflab();
237✔
1831
                        lp = ReplaceDefine(lp);                // skip any empty substitutions and comments
237✔
1832
                        substitutedLine = line;                // override substituted listing for ENDS
237✔
1833
                        return;
237✔
1834
                }
1835
                if (st) ParseStructLine(st);
1,197✔
1836
                ListFile(true);
1,197✔
1837
        }
1838
        Error("[STRUCT] Unexpected end of structure");
3✔
1839
        st->deflab();
3✔
1840
}
1841

1842
static void dirFPOS() {
18✔
1843
        aint val;
1844
        int method = SEEK_SET;
18✔
1845
        SkipBlanks(lp);
18✔
1846
        if ((*lp == '+') || (*lp == '-')) {
18✔
1847
                method = SEEK_CUR;
6✔
1848
        }
1849
        if (!ParseExpressionNoSyntaxError(lp, val)) {
18✔
1850
                Error("[FPOS] Syntax error", lp, SUPPRESS);
3✔
1851
        } else if (pass == LASTPASS) {
15✔
1852
                SeekDest(val, method);
5✔
1853
        }
1854
}
18✔
1855

1856
// isWhile == false: DUP/REPT parsing
1857
// isWhile == true: WHILE parsing
1858
static void DupWhileImplementation(bool isWhile) {
4,496✔
1859
        aint val = 0;
4,496✔
1860
        CStringsList* condition = nullptr;
4,496✔
1861

1862
        if (!RepeatStack.empty()) {
4,496✔
1863
                SRepeatStack& dup = RepeatStack.top();
3,941✔
1864
                if (!dup.IsInWork) {
3,941✔
1865
                        SkipToEol(lp);                // Just skip the expression to the end of line, don't evaluate yet
264✔
1866
                        ++dup.Level;
264✔
1867
                        return;
276✔
1868
                }
1869
        }
1870

1871
        const char* indexVar = nullptr;
4,232✔
1872
        if (isWhile) {
4,232✔
1873
                condition = new CStringsList(lp);
39✔
1874
                if (nullptr == condition) ErrorOOM();
39✔
1875
                lp += strlen(condition->string);
39✔
1876
                // scan condition string for extra guardian value, and split + parse it as needed
1877
                char* expressionSource = condition->string;
39✔
1878
                bool parseOk = ParseExpressionNoSyntaxError(expressionSource, val);
39✔
1879
                if (parseOk && *expressionSource && comma(expressionSource)) {
39✔
1880
                        // comma found, try to parse explicit guardian value
1881
                        char* guardianSource = expressionSource;
9✔
1882
                        parseOk = parseOk && ParseExpressionNoSyntaxError(guardianSource, val);
9✔
1883
                        // overwrite the comma to keep only condition string without guardian argument
1884
                        if (parseOk) {
9✔
1885
                                assert(',' == expressionSource[-1]);
6✔
1886
                                expressionSource[-1] = 0;
6✔
1887
                                ++val;                // +1 to explicit value to report error when WHILE does *over* that
6✔
1888
                        }
1889
                } else {
1890
                        val = 100001;        // default guardian value is 100k
30✔
1891
                }
1892
                if (!parseOk) {
39✔
1893
                        Error("[WHILE] Syntax error in <expression>", condition->string, SUPPRESS);
9✔
1894
                        free(condition->string);                        // release original string
9✔
1895
                        condition->string = STRDUP("0");        // force it to evaluate to zero
9✔
1896
                        val = 1;
9✔
1897
                }
1898
        } else {
1899
                IsLabelNotFound = false;
4,193✔
1900
                if (!ParseExpressionNoSyntaxError(lp, val)) {
4,193✔
1901
                        Error("[DUP/REPT] Syntax error in <count>", lp, SUPPRESS);
9✔
1902
                        return;
9✔
1903
                }
1904
                if (IsLabelNotFound) {
4,184✔
1905
                        Error("[DUP/REPT] Forward reference", NULL, ALL);
1✔
1906
                }
1907
                if ((int) val < 0) {
4,184✔
1908
                        ErrorInt("[DUP/REPT] Repeat value must be positive or zero", val, IF_FIRST); return;
3✔
1909
                }
1910
                if (comma(lp)) {
4,181✔
1911
                        indexVar = GetID(lp);
30✔
1912
                        if (nullptr == indexVar) {
30✔
1913
                                Error("[DUP/REPT] invalid index variable name", lp, IF_FIRST);
6✔
1914
                                SkipToEol(lp);
6✔
1915
                        }
1916
                }
1917
        }
1918

1919
        RepeatStack.emplace(val, condition, new CStringsList(indexVar ? indexVar : ""));
4,220✔
1920
        if (!SkipBlanks()) Error("[DUP] unexpected chars", lp, SUPPRESS);
4,220✔
1921
}
1922

1923
static void dirDUP() {
4,454✔
1924
        DupWhileImplementation(false);
4,454✔
1925
}
4,454✔
1926

1927
static void dirWHILE() {
42✔
1928
        DupWhileImplementation(true);
42✔
1929
}
42✔
1930

1931
static bool shouldRepeat(SRepeatStack& dup) {
360,476✔
1932
        if (nullptr == dup.RepeatCondition) {
360,476✔
1933
                return 0 <= --dup.RepeatCount;
60,279✔
1934
        } else {
1935
                if (!dup.RepeatCount--) {
300,197✔
1936
                        sourcePosStack.push_back(dup.RepeatCondition->source);
6✔
1937
                        Error("[WHILE] infinite loop? (reaching the guardian value, default 100k)");
6✔
1938
                        sourcePosStack.pop_back();
6✔
1939
                        return false;
6✔
1940
                }
1941
                aint val = 0;
300,191✔
1942
                IsLabelNotFound = false;
300,191✔
1943
                char* expressionSource = dup.RepeatCondition->string;
300,191✔
1944
                if (!ParseExpressionNoSyntaxError(expressionSource, val) || *expressionSource) {
300,191✔
1945
                        const TextFilePos oSourcePos = sourcePosStack.back();
3✔
1946
                        sourcePosStack.back() = dup.RepeatCondition->source;
3✔
1947
                        Error("[WHILE] Syntax error in <expression>", dup.RepeatCondition->string, SUPPRESS);
3✔
1948
                        sourcePosStack.back() = oSourcePos;
3✔
1949
                        return false;
3✔
1950
                }
1951
                if (IsLabelNotFound) {
300,188✔
1952
                        WarningById(W_FWD_REF, dup.RepeatCondition->string, W_EARLY);
1✔
1953
                        return false;
1✔
1954
                }
1955
                return val;
300,187✔
1956
        }
1957
}
1958

1959
static void dirEDUP() {
4,490✔
1960
        if (RepeatStack.empty() || RepeatStack.top().IsInWork) {
4,490✔
1961
                Error("[EDUP/ENDR/ENDW] End repeat without repeat");
9✔
1962
                return;
273✔
1963
        }
1964

1965
        SRepeatStack& dup = RepeatStack.top();
4,481✔
1966
        if (!dup.IsInWork && dup.Level) {
4,481✔
1967
                --dup.Level;
264✔
1968
                return;
264✔
1969
        }
1970
        dup.IsInWork = true;
4,217✔
1971
        // kill the "EDUP" inside DUP-list (+ works as "while (IsRunning && lijstp && lijstp->string)" terminator)
1972
        if (dup.Pointer->string) free(dup.Pointer->string);
4,217✔
1973
        dup.Pointer->string = NULL;
4,217✔
1974
        ++listmacro;
4,217✔
1975
        char* ml = STRDUP(line);        // copy the EDUP line for List purposes (after the DUP block emit)
4,217✔
1976
        if (ml == NULL) ErrorOOM();
4,217✔
1977

1978
        CStringsList* olijstp = lijstp;
4,217✔
1979
        ++lijst;
4,217✔
1980
        assert(!sourcePosStack.empty());
4,217✔
1981
        const TextFilePos oSourcePos = sourcePosStack.back();
4,217✔
1982
        aint currentRepeatIndex = 0;
4,217✔
1983
        while (IsRunning && dup.Lines && shouldRepeat(dup)) {
360,479✔
1984
                sourcePosStack.back() = dup.sourcePos;
356,262✔
1985
                lijstp = dup.Lines;
356,262✔
1986
                assert(lijstp);
356,262✔
1987
                if (*lijstp->string) {
356,262✔
1988
                        // if the DUP has index variable, the first "line" is the variable name, set it up to current index
1989
                        std::unique_ptr<char[]> indexVar(ValidateLabel(lijstp->string,  false));
138✔
1990
                        if (indexVar.get()) LabelTable.Insert(indexVar.get(), currentRepeatIndex++, LABEL_IS_DEFL);
138✔
1991
                }
138✔
1992
                lijstp = lijstp->next;        // skip first empty line / indexVar name
356,262✔
1993
                while (IsRunning && lijstp && lijstp->string) {        // the EDUP/REPT/ENDM line has string=NULL => ends loop
781,598✔
1994
                        if (lijstp->source.line) sourcePosStack.back() = lijstp->source;
425,336✔
1995
                        STRCPY(line, LINEMAX, lijstp->string);
425,336✔
1996
                        substitutedLine = line;                // reset substituted listing
425,336✔
1997
                        eolComment = NULL;                        // reset end of line comment
425,336✔
1998
                        lijstp = lijstp->next;
425,336✔
1999
                        ParseLineSafe();
425,336✔
2000
                        sourcePosStack.back().nextSegment();
425,336✔
2001
                }
2002
        }
2003
        sourcePosStack.back() = oSourcePos;
4,217✔
2004
        RepeatStack.pop();
4,217✔
2005
        lijstp = olijstp;
4,217✔
2006
        --lijst;
4,217✔
2007
        --listmacro;
4,217✔
2008
        STRCPY(line, LINEMAX,  ml);                // show EDUP line itself
4,217✔
2009
        free(ml);
4,217✔
2010
        ++CompiledCurrentLine;
4,217✔
2011
        substitutedLine = line;                        // override substituted list line for EDUP
4,217✔
2012
        ListFile();
4,217✔
2013
}
2014

2015
static void dirENDM() {
18✔
2016
        if (!RepeatStack.empty()) {
18✔
2017
                Warning("ENDM used as DUP/REPT block terminator, this is deprecated (and bugged when used inside macro), change to EDUP or ENDR");
3✔
2018
                dirEDUP();
3✔
2019
        } else {
2020
                Error("[ENDM] End macro without macro");
15✔
2021
        }
2022
}
18✔
2023

2024
static bool dirDEFARRAY_parseItems(CStringsList** nextPtr) {
204✔
2025
        char ml[LINEMAX];
2026
        do {
2027
                const char* const itemLp = lp;
1,197✔
2028
                char* n = ml;
1,197✔
2029
                if (!GetMacroArgumentValue(lp, n)) {
1,197✔
2030
                        Error("[DEFARRAY] Syntax error", itemLp, SUPPRESS);
18✔
2031
                        return false;
18✔
2032
                }
2033
                *nextPtr = new CStringsList(ml);
1,179✔
2034
                if ((*nextPtr)->string == NULL) ErrorOOM();
1,179✔
2035
                nextPtr = &((*nextPtr)->next);
1,179✔
2036
        } while (anyComma(lp));
1,179✔
2037
        return SkipBlanks();
186✔
2038
}
2039

2040
static void dirDEFARRAY_add(const char* id) {
21✔
2041
        DefineTable.Get(id);
21✔
2042
        if (NULL == DefineTable.DefArrayList) {
21✔
2043
                Error("[DEFARRAY+] unknown array <id>", id);
6✔
2044
                SkipToEol(lp);
6✔
2045
                return;
6✔
2046
        }
2047
        // array was already defined, seek to the last item in the list
2048
        while (DefineTable.DefArrayList->next) DefineTable.DefArrayList = DefineTable.DefArrayList->next;
84✔
2049
        dirDEFARRAY_parseItems(&DefineTable.DefArrayList->next);
15✔
2050
        return;
15✔
2051
}
2052

2053
static void dirDEFARRAY() {
261✔
2054
        bool plus = ('+' == *lp) ? ++lp, true : false;
261✔
2055
        const char* id = White() ? GetID(lp) : nullptr;
261✔
2056
        if (!id) {
261✔
2057
                Error("[DEFARRAY] Syntax error in <id>", lp);
18✔
2058
                SkipToEol(lp);
18✔
2059
                return;
18✔
2060
        }
2061
        if (!White() || SkipBlanks()) {        // enforce whitespace between ID and first item and detect empty ones
243✔
2062
                if (SkipBlanks()) Error("[DEFARRAY] must have at least one entry");
33✔
2063
                else Error("[DEFARRAY] missing space between <id> and first <item>", lp);
15✔
2064
                SkipToEol(lp);
33✔
2065
                return;
33✔
2066
        }
2067
        if (plus) {
210✔
2068
                dirDEFARRAY_add(id);
21✔
2069
        } else {
2070
                CStringsList* a = NULL;
189✔
2071
                if (!dirDEFARRAY_parseItems(&a) || NULL == a) {
189✔
2072
                        if (a) delete a;        // release already parsed items, if there was syntax error
15✔
2073
                        return;
15✔
2074
                }
2075
                DefineTable.Add(id, "", a);
174✔
2076
        }
2077
}
2078

2079
static const char* DEFDEVICE_SYNTAX_ERR = "[DEFDEVICE] expected syntax is <deviceid>, <slot_size>, <page_count>[, <slot_0_initial_page>[, ...]]";
2080

2081
static void dirDEFDEVICE() {
53✔
2082
        //DEFDEVICE <deviceid>, <slot_size>, <page_count>[, <slot_0_initial_page>[, ...]]
2083
        const char* id = GetID(lp);
53✔
2084
        if (!id) {
53✔
2085
                Error(DEFDEVICE_SYNTAX_ERR, bp, SUPPRESS);
3✔
2086
                return;
45✔
2087
        }
2088

2089
        const bool is_defined = std::any_of(
50✔
2090
                DefDevices.begin(), DefDevices.end(),
2091
                [&](const CDeviceDef* el) { return 0 == strcasecmp(id, el->getID()); }
124✔
2092
        );
2093
        if (is_defined || 1 < pass) {
50✔
2094
                // same id defined twice during first pass?
2095
                if (pass <= 1) Error("[DEFDEVICE] device with such ID is already defined", id, EARLY);
35✔
2096
                // in later passes ignore the line, DEFDEVICE works only in first pass
2097
                SkipToEol(lp);
35✔
2098
                return;
35✔
2099
        }
2100

2101
        // add new definition if arguments are correct and this is first pass
2102
        aint args[2 + CDeviceDef::MAX_SLOT_N] = {};        // slot_size, page_count, initial pages, ...
15✔
2103
        bool optional[2 + CDeviceDef::MAX_SLOT_N] = {false, false};
15✔
2104
        aint &slot_size = args[0], &page_count = args[1], *initial_pages = args + 2;
15✔
2105
        for (size_t i = 2; i < CDeviceDef::MAX_SLOT_N; ++i) {
3,825✔
2106
                args[i] = -1;
3,810✔
2107
                optional[i] = true;
3,810✔
2108
        }
2109
        if (!anyComma(lp) || !getIntArguments<2 + CDeviceDef::MAX_SLOT_N>(lp, args, optional)) {
15✔
2110
                Error(DEFDEVICE_SYNTAX_ERR, bp, EARLY);
4✔
2111
                return;
4✔
2112
        }
2113
        if (slot_size < 256 || 0x10000 < slot_size || page_count <= 0) {
11✔
2114
                Error("[DEFDEVICE] valid slot_size: 256..64ki, page_count: 1 or more", bp, EARLY);
3✔
2115
                return;
3✔
2116
        }
2117
        DefDevices.push_back(new CDeviceDef(id, slot_size, page_count));
8✔
2118

2119
        // init "initialPages array by going 0, 1, 2, ..., page_count-1, page_count-1, ... or parsed explicit values
2120
        CDeviceDef & dev = *DefDevices.back();
8✔
2121
        int previous_page = -1;
8✔
2122
        for (int32_t i = 0; i < dev.SlotsCount; ++i) {
298✔
2123
                if (0 <= initial_pages[i] && initial_pages[i] < dev.PagesCount) {
290✔
2124
                        previous_page = initial_pages[i];
19✔
2125
                } else {
2126
                        if (-1 != initial_pages[i]) ErrorInt("[DEFDEVICE] invalid initial page", initial_pages[i], EARLY);
271✔
2127
                        if (previous_page < dev.PagesCount - 1) ++previous_page;
271✔
2128
                }
2129
                dev.initialPages[i] = previous_page;
290✔
2130
        }
2131
}
2132

2133
static void dirDEVICE() {
869✔
2134
        // refresh source position of first DEVICE directive
2135
        if (1 == ++deviceDirectivesCount) {
869✔
2136
                assert(!sourcePosStack.empty());
467✔
2137
                globalDeviceSourcePos = sourcePosStack.back();
467✔
2138
        }
2139

2140
        char* id = GetID(lp);
869✔
2141
        if (id) {
869✔
2142
                aint ramtop = 0;
866✔
2143
                if (anyComma(lp)) {
866✔
2144
                        if (!ParseExpressionNoSyntaxError(lp, ramtop)) {
135✔
2145
                                Error("[DEVICE] Syntax error", bp); return;
30✔
2146
                        }
2147
                        if (ramtop < 0x5D00 || 0xFFFF < ramtop) {
129✔
2148
                                  ErrorInt("[DEVICE] valid range for RAMTOP is $5D00..$FFFF", ramtop); return;
24✔
2149
                        }
2150
                }
2151
                // if (1 == deviceDirectivesCount && Device) -> device was already set globally, skip SetDevice
2152
                if (1 < deviceDirectivesCount || !Devices) {
836✔
2153
                        if (!SetDevice(id, ramtop)) {
623✔
2154
                                Error("[DEVICE] Invalid parameter", id, IF_FIRST);
10✔
2155
                        }
2156
                }
2157
        } else {
2158
                Error("[DEVICE] Syntax error in <deviceid>", lp, SUPPRESS);
3✔
2159
        }
2160
}
2161

2162
static void dirSLDOPT() {
21✔
2163
        SkipBlanks(lp);
21✔
2164
        if (cmphstr(lp, "COMMENT")) {
21✔
2165
                do {
2166
                        SldAddCommentKeyword(GetID(lp));
21✔
2167
                } while (!SkipBlanks(lp) && anyComma(lp));
21✔
2168
        } else {
2169
                Error("[SLDOPT] Syntax error in <type> (valid is only COMMENT)", lp, SUPPRESS);
6✔
2170
        }
2171
}
21✔
2172

2173
void InsertDirectives() {
505✔
2174
        DirectivesTable.insertd(".assert", dirASSERT);
505✔
2175
        DirectivesTable.insertd(".byte", dirBYTE);
505✔
2176
        DirectivesTable.insertd(".abyte", dirABYTE);
505✔
2177
        DirectivesTable.insertd(".abytec", dirABYTEC);
505✔
2178
        DirectivesTable.insertd(".abytez", dirABYTEZ);
505✔
2179
        DirectivesTable.insertd(".word", dirWORD);
505✔
2180
        DirectivesTable.insertd(".block", dirBLOCK);
505✔
2181
        DirectivesTable.insertd(".dword", dirDWORD);
505✔
2182
        DirectivesTable.insertd(".d24", dirD24);
505✔
2183
        DirectivesTable.insertd(".dg", dirDG);
505✔
2184
        DirectivesTable.insertd(".defg", dirDG);
505✔
2185
        DirectivesTable.insertd(".dh", dirDH);
505✔
2186
        DirectivesTable.insertd(".defh", dirDH);
505✔
2187
        DirectivesTable.insertd(".hex", dirDH);
505✔
2188
        DirectivesTable.insertd(".org", dirORG);
505✔
2189
        DirectivesTable.insertd(".fpos",dirFPOS);
505✔
2190
        DirectivesTable.insertd(".align", dirALIGN);
505✔
2191
        DirectivesTable.insertd(".module", dirMODULE);
505✔
2192
        DirectivesTable.insertd(".size", dirSIZE);
505✔
2193
        //DirectivesTable.insertd(".textarea",dirTEXTAREA);
2194
        DirectivesTable.insertd(".textarea", dirDISP);
505✔
2195
        DirectivesTable.insertd(".else", dirELSE);
505✔
2196
        DirectivesTable.insertd(".elseif", dirELSEIF);
505✔
2197
        DirectivesTable.insertd(".export", dirEXPORT);
505✔
2198
        DirectivesTable.insertd(".display", dirDISPLAY);
505✔
2199
        DirectivesTable.insertd(".end", dirEND);
505✔
2200
        DirectivesTable.insertd(".include", dirINCLUDE);
505✔
2201
        DirectivesTable.insertd(".incbin", dirINCBIN);
505✔
2202
        DirectivesTable.insertd(".binary", dirINCBIN);
505✔
2203
        DirectivesTable.insertd(".inchob", dirINCHOB);
505✔
2204
        DirectivesTable.insertd(".inctrd", dirINCTRD);
505✔
2205
        DirectivesTable.insertd(".insert", dirINCBIN);
505✔
2206
        DirectivesTable.insertd(".savenex", dirSAVENEX);
505✔
2207
        DirectivesTable.insertd(".savesna", dirSAVESNA);
505✔
2208
        DirectivesTable.insertd(".savehob", dirSAVEHOB);
505✔
2209
        DirectivesTable.insertd(".savebin", dirSAVEBIN);
505✔
2210
        DirectivesTable.insertd(".savedev", dirSAVEDEV);
505✔
2211
        DirectivesTable.insertd(".emptytap", dirEMPTYTAP);
505✔
2212
        DirectivesTable.insertd(".savetap", dirSAVETAP);
505✔
2213
        DirectivesTable.insertd(".emptytrd", dirEMPTYTRD);
505✔
2214
        DirectivesTable.insertd(".savetrd", dirSAVETRD);
505✔
2215
        DirectivesTable.insertd(".savecpcsna", dirSAVECPCSNA);
505✔
2216
        DirectivesTable.insertd(".savecdt", dirSAVECDT);
505✔
2217
        DirectivesTable.insertd(".save3dos", dirSAVE3DOS);
505✔
2218
        DirectivesTable.insertd(".saveamsdos", dirSAVEAMSDOS);
505✔
2219
        DirectivesTable.insertd(".savecpr", dirSAVECPR);
505✔
2220
        DirectivesTable.insertd(".shellexec", dirSHELLEXEC);
505✔
2221
/*#ifdef WIN32
2222
        DirectivesTable.insertd(".winexec", dirWINEXEC);
2223
#endif*/
2224
        DirectivesTable.insertd(".if", dirIF);
505✔
2225
        DirectivesTable.insertd(".ifn", dirIFN);
505✔
2226
        DirectivesTable.insertd(".ifused", dirIFUSED);
505✔
2227
        DirectivesTable.insertd(".ifnused", dirIFNUSED);
505✔
2228
        DirectivesTable.insertd(".ifdef", dirIFDEF);
505✔
2229
        DirectivesTable.insertd(".ifndef", dirIFNDEF);
505✔
2230
        DirectivesTable.insertd(".output", dirOUTPUT);
505✔
2231
        DirectivesTable.insertd(".outend", dirOUTEND);
505✔
2232
        DirectivesTable.insertd(".tapout", dirTAPOUT);
505✔
2233
        DirectivesTable.insertd(".tapend", dirTAPEND);
505✔
2234
        DirectivesTable.insertd(".define", dirDEFINE);
505✔
2235
        DirectivesTable.insertd(".undefine", dirUNDEFINE);
505✔
2236
        DirectivesTable.insertd(".defarray", dirDEFARRAY);
505✔
2237
        DirectivesTable.insertd(".macro", dirMACRO);
505✔
2238
        DirectivesTable.insertd(".struct", dirSTRUCT);
505✔
2239
        DirectivesTable.insertd(".dc", dirDC);
505✔
2240
        DirectivesTable.insertd(".dz", dirDZ);
505✔
2241
        DirectivesTable.insertd(".db", dirBYTE);
505✔
2242
        DirectivesTable.insertd(".dm", dirBYTE);
505✔
2243
        DirectivesTable.insertd(".dw", dirWORD);
505✔
2244
        DirectivesTable.insertd(".ds", dirBLOCK);
505✔
2245
        DirectivesTable.insertd(".dd", dirDWORD);
505✔
2246
        DirectivesTable.insertd(".defb", dirBYTE);
505✔
2247
        DirectivesTable.insertd(".defw", dirWORD);
505✔
2248
        DirectivesTable.insertd(".defs", dirBLOCK);
505✔
2249
        DirectivesTable.insertd(".defd", dirDWORD);
505✔
2250
        DirectivesTable.insertd(".defm", dirBYTE);
505✔
2251
        DirectivesTable.insertd(".endmod", dirENDMODULE);
505✔
2252
        DirectivesTable.insertd(".endmodule", dirENDMODULE);
505✔
2253
        DirectivesTable.insertd(".rept", dirDUP);
505✔
2254
        DirectivesTable.insertd(".dup", dirDUP);
505✔
2255
        DirectivesTable.insertd(".while", dirWHILE);
505✔
2256
        DirectivesTable.insertd(".disp", dirDISP);
505✔
2257
        DirectivesTable.insertd(".phase", dirDISP);
505✔
2258
        DirectivesTable.insertd(".ent", dirENT);
505✔
2259
        DirectivesTable.insertd(".unphase", dirENT);
505✔
2260
        DirectivesTable.insertd(".dephase", dirENT);
505✔
2261
        DirectivesTable.insertd(".page", dirPAGE);
505✔
2262
        DirectivesTable.insertd(".slot", dirSLOT);
505✔
2263
        DirectivesTable.insertd(".mmu", dirMMU);
505✔
2264
        DirectivesTable.insertd(".encoding", dirENCODING);
505✔
2265
        DirectivesTable.insertd(".opt", dirOPT);
505✔
2266
        DirectivesTable.insertd(".labelslist", dirLABELSLIST);
505✔
2267
        DirectivesTable.insertd(".cspectmap", dirCSPECTMAP);
505✔
2268
        DirectivesTable.insertd(".endif", dirENDIF);
505✔
2269
        DirectivesTable.insertd(".endt", dirENT);
505✔
2270
        DirectivesTable.insertd(".endm", dirENDM);
505✔
2271
        DirectivesTable.insertd(".edup", dirEDUP);
505✔
2272
        DirectivesTable.insertd(".endr", dirEDUP);
505✔
2273
        DirectivesTable.insertd(".endw", dirEDUP);
505✔
2274
        DirectivesTable.insertd(".ends", dirENDS);
505✔
2275

2276
        DirectivesTable.insertd(".device", dirDEVICE);
505✔
2277
        DirectivesTable.insertd(".defdevice", dirDEFDEVICE);
505✔
2278

2279
        DirectivesTable.insertd(".bplist", dirBPLIST);
505✔
2280
        DirectivesTable.insertd(".setbreakpoint", dirSETBREAKPOINT);
505✔
2281
        DirectivesTable.insertd(".setbp", dirSETBREAKPOINT);
505✔
2282

2283
        DirectivesTable.insertd(".relocate_start", Relocation::dirRELOCATE_START);
505✔
2284
        DirectivesTable.insertd(".relocate_end", Relocation::dirRELOCATE_END);
505✔
2285
        DirectivesTable.insertd(".relocate_table", Relocation::dirRELOCATE_TABLE);
505✔
2286

2287
        DirectivesTable.insertd(".sldopt", dirSLDOPT);
505✔
2288

2289
#ifdef USE_LUA
2290
        DirectivesTable.insertd(".lua", dirLUA);
505✔
2291
        DirectivesTable.insertd(".endlua", dirENDLUA);
505✔
2292
        DirectivesTable.insertd(".includelua", dirINCLUDELUA);
505✔
2293
#endif //USE_LUA
2294

2295
        DirectivesTable_dup.insertd(".dup", dirDUP);
505✔
2296
        DirectivesTable_dup.insertd(".edup", dirEDUP);
505✔
2297
        DirectivesTable_dup.insertd(".endm", dirENDM);
505✔
2298
        DirectivesTable_dup.insertd(".endr", dirEDUP);
505✔
2299
        DirectivesTable_dup.insertd(".endw", dirEDUP);
505✔
2300
        DirectivesTable_dup.insertd(".rept", dirDUP);
505✔
2301
        DirectivesTable_dup.insertd(".while", dirWHILE);
505✔
2302
}
505✔
2303

2304
//eof direct.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