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

z00m128 / sjasmplus / 1487

10 Mar 2025 02:05AM UTC coverage: 96.445% (+0.005%) from 96.44%
1487

push

cirrus-ci

ped7g
SLD: fix labels export in special cases

Some of the new prefix control chars were not handled by sld export.
Not sure if this is fixing 100% of possible combinations, but certainly
supports more than before.

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

40 existing lines in 2 files now uncovered.

9603 of 9957 relevant lines covered (96.44%)

340238.26 hits per line

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

98.44
/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)
487,796✔
37
{
38
        char* olp = lp;
487,796✔
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"))) {
487,796✔
42
                lp = olp;
36✔
43
                return 0;
36✔
44
        }
45

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

54
        if (DirectivesTable.zoek(n)) return 1;
487,448✔
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]);
241,868✔
58
        const bool isExprDot = ('.' == *n) && (0 == n[1]) && ('(' == *lp);
241,868✔
59
        if ((beginningOfLine && !Options::syx.IsPseudoOpBOF) || (!isDigitDot && !isExprDot)) {
241,868✔
60
                lp = olp;                // alone "." must be followed by digit, or math expression in parentheses
241,676✔
61
                return 0;                // otherwise just return
241,676✔
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++)) {
192✔
66
                lp = olp; Error("Dot-repeater must be followed by number or parentheses", olp, SUPPRESS);
18✔
67
                return 0;
18✔
68
        }
69
        if (val < 1) {
174✔
70
                lp = olp; ErrorInt(".N must be positive integer", val, SUPPRESS);
24✔
71
                return 0;
24✔
72
        }
73

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

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

109

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

121
static void dirBYTE() {
140,634✔
122
        getBytesWithCheck();
140,634✔
123
}
140,634✔
124

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

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

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

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

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

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

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

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

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

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

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

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

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

281
static void dirORG() {
5,148✔
282
        aint val;
283
        if (!ParseExpressionNoSyntaxError(lp, val)) {
5,148✔
284
                Error("[ORG] Syntax error in <address>", lp, SUPPRESS);
6✔
285
                return;
4,848✔
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;
5,142✔
289
        CurAddress = val;
5,142✔
290
        if (DISP_NONE != PseudoORG) WarningById(W_DISPLACED_ORG);
5,142✔
291
        if (!DeviceID) return;
5,142✔
292
        if (!comma(lp)) {
3,982✔
293
                Device->CheckPage(CDevice::CHECK_RESET);
3,682✔
294
                return;
3,682✔
295
        }
296
        // emit warning when current slot does not cover address used for ORG
297
        auto slot = Device->GetCurrentSlot();
300✔
298
        if ((CurAddress < slot->Address || slot->Address + slot->Size <= CurAddress)) {
300✔
299
                char warnTxt[LINEMAX];
300
                SPRINTF4(warnTxt, LINEMAX,
114✔
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);
114✔
304
        }
305
        dirPageImpl("ORG");
300✔
306
}
307

308
static void dirDISP() {
408✔
309
        if (DISP_NONE != PseudoORG) {
408✔
310
                Warning("[DISP] displacement inside another displacement block, ignoring it.");
12✔
311
                SkipToEol(lp);
12✔
312
                return;
66✔
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;
396✔
317
        if (!ParseExpressionNoSyntaxError(lp, valAdr)) {
396✔
318
                Error("[DISP] Syntax error in <address>", lp, SUPPRESS);
6✔
319
                return;
6✔
320
        }
321
        // the expression of the DISP shouldn't be affected by relocation (even when starting inside relocation block)
322
        if (Relocation::checkAndWarn(true)) {
390✔
323
                SkipToEol(lp);
6✔
324
                return;                // report it as error and exit early
6✔
325
        }
326
        if (comma(lp)) {
384✔
327
                if (!ParseExpressionNoSyntaxError(lp, valPageNum)) {
78✔
328
                        Error("[DISP] Syntax error in <page number>", lp);
18✔
329
                        return;
18✔
330
                }
331
                if (!DeviceID) {
60✔
332
                        Error("[DISP] <page number> is accepted only in device mode", line);
6✔
333
                        return;
6✔
334
                }
335
                if (valPageNum < 0 || Device->PagesCount <= valPageNum) {
54✔
336
                        ErrorInt("[DISP] <page number> is out of range", valPageNum);
18✔
337
                        return;
18✔
338
                }
339
                dispPageNum = valPageNum;
36✔
340
        } else {
341
                dispPageNum = LABEL_PAGE_UNDEFINED;
306✔
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;
342✔
345
        // everything is valid, switch to DISP mode (dispPageNum is already set above)
346
        adrdisp = CurAddress;
342✔
347
        CurAddress = valAdr;
342✔
348
        PseudoORG = Relocation::type ? DISP_INSIDE_RELOCATE : DISP_ACTIVE;
342✔
349
}
350

351
static void dirENT() {
360✔
352
        if (DISP_NONE == PseudoORG) {
360✔
353
                Error("ENT should be after DISP");
12✔
354
                return;
12✔
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) {
348✔
358
                Error("The DISP block did start outside of relocation block, can't end inside it");
12✔
359
                return;
12✔
360
        }
361
        if (DISP_INSIDE_RELOCATE == PseudoORG && !Relocation::type) {
336✔
362
                Error("The DISP block did start inside of relocation block, can't end outside of it");
×
363
                return;
×
364
        }
365
        CurAddress = adrdisp;
336✔
366
        PseudoORG = DISP_NONE;
336✔
367
        dispPageNum = LABEL_PAGE_UNDEFINED;
336✔
368
}
369

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

379
static void dirMMU() {
912✔
380
        if (!DeviceID) {
912✔
381
                Warning("MMU is allowed only in real device emulation mode (See DEVICE)");
12✔
382
                SkipToEol(lp);
12✔
383
                return;
138✔
384
        }
385
        aint slot1, slot2, pageN = -1, address = -1;
900✔
386
        CDeviceSlot::ESlotOptions slotOpt = CDeviceSlot::O_NONE;
900✔
387
        if (!ParseExpression(lp, slot1)) {
900✔
388
                Error("[MMU] First slot number parsing failed", bp, SUPPRESS);
6✔
389
                return;
6✔
390
        }
391
        slot2 = slot1;
894✔
392
        if (!comma(lp)) {        // second slot or slot-option should follow (if not comma)
894✔
393
                // see if there is slot1-only with option-char (e/w/n options)
394
                const char slotOptChar = (*lp)|0x20;        // primitive ASCII tolower
354✔
395
                if ('a' <= slotOptChar && slotOptChar <= 'z' && (',' == lp[1] || White(lp[1]))) {
354✔
396
                        if ('e' == slotOptChar) slotOpt = CDeviceSlot::O_ERROR;
168✔
397
                        else if ('w' == slotOptChar) slotOpt = CDeviceSlot::O_WARNING;
120✔
398
                        else if ('n' == slotOptChar) slotOpt = CDeviceSlot::O_NEXT;
90✔
399
                        else {
400
                                Warning("[MMU] Unknown slot option (legal: e, w, n)", lp);
12✔
401
                        }
402
                        ++lp;
168✔
403
                } else {        // there was no option char, check if there was slot2 number to define range
404
                        if (!ParseExpression(lp, slot2)) {
186✔
405
                                Error("[MMU] Second slot number parsing failed", bp, SUPPRESS);
12✔
406
                                return;
12✔
407
                        }
408
                }
409
                if (!comma(lp)) {
342✔
410
                        Error("[MMU] Comma and page number expected after slot info", bp, SUPPRESS);
12✔
411
                        return;
12✔
412
                }
413
        }
414
        if (!ParseExpression(lp, pageN)) {
870✔
415
                Error("[MMU] Page number parsing failed", bp, SUPPRESS);
30✔
416
                return;
30✔
417
        }
418
        if (comma(lp)) {
840✔
419
                if (!ParseExpressionNoSyntaxError(lp, address)) {
474✔
420
                        Error("[MMU] address parsing failed", bp, SUPPRESS);
12✔
421
                        return;
12✔
422
                }
423
                check16(address);
462✔
424
                address &= 0xFFFF;
462✔
425
        }
426
        // convert slot entered as addresses into slot numbering (must be precise start address of slot)
427
        slot1 = Device->SlotNumberFromPreciseAddress(slot1);
828✔
428
        slot2 = Device->SlotNumberFromPreciseAddress(slot2);
828✔
429
        // validate argument values
430
        if (slot1 < 0 || slot2 < slot1 || Device->SlotsCount <= slot2) {
828✔
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",
30✔
433
                                 Device->SlotsCount - 1);
434
                Error(buf, NULL, SUPPRESS);
30✔
435
                return;
30✔
436
        }
437
        if (pageN < 0 || Device->PagesCount <= pageN + (slot2 - slot1)) {
798✔
438
                char buf[LINEMAX];
439
                SPRINTF1(buf, LINEMAX, "[MMU] Requested page(s) must be in range 0..%u", Device->PagesCount - 1);
24✔
440
                Error(buf, NULL, SUPPRESS);
24✔
441
                return;
24✔
442
        }
443
        // all valid, set it up
444
        for (aint slotN = slot1; slotN <= slot2; ++slotN, ++pageN) {
2,034✔
445
                Device->GetSlot(slotN)->Page = Device->GetPage(pageN);
1,260✔
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
1,260✔
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;
774✔
451
        // set explicit ORG address if the third argument was provided
452
        if (0 <= address) {
774✔
453
                CurAddress = address;
462✔
454
                if (DISP_NONE != PseudoORG) {
462✔
455
                        WarningById(W_DISPLACED_ORG);
12✔
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);
462✔
459
                const CDeviceSlot & check_s2 = *Device->GetSlot(slot2);
462✔
460
                if ((address < check_s1.Address) || (check_s2.Address + check_s2.Size <= address)) {
462✔
461
                        char buf[LINEMAX];
462
                        SPRINTF3(buf, LINEMAX, "[MMU] Requested ORG address 0x%04X is out of range 0x%04X..0x%04X",
60✔
463
                                         address, check_s1.Address, check_s2.Address + check_s2.Size - 1);
464
                        Warning(buf);
60✔
465
                }
466
        }
467
        Device->CheckPage(CDevice::CHECK_RESET);
774✔
468
}
469

470
static void dirSLOT() {
672✔
471
        aint val;
472
        if (!DeviceID) {
672✔
473
                Warning("SLOT only allowed in real device emulation mode (See DEVICE)");
12✔
474
                SkipParam(lp);
12✔
475
                return;
18✔
476
        }
477
        if (!ParseExpressionNoSyntaxError(lp, val)) {
660✔
478
                Error("[SLOT] Syntax error in <slot_number>", lp, SUPPRESS);
6✔
479
                return;
6✔
480
        }
481
        val = Device->SlotNumberFromPreciseAddress(val);
654✔
482
        if (!Device->SetSlot(val)) {
654✔
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);
84✔
485
                Error(buf, NULL, IF_FIRST);
84✔
486
        }
487
}
488

489
static void dirALIGN() {
864✔
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);
864✔
493
        if (-1 == val) val = 4;
864✔
494
        // calculate how many bytes has to be filled to reach desired alignment
495
        aint len = (~CurAddress + 1) & (val - 1);
864✔
496
        if (len < 1) return;                // nothing to fill, already aligned
864✔
497
        if (-1 == fill) EmitBlock(0, len, true);
638✔
498
        else                        EmitBlock(fill, len, false);
168✔
499
}
500

501
static void dirMODULE() {
426✔
502
        char* n = GetID(lp);
426✔
503
        if (n && (nullptr == STRCHR(n, '.'))) {
426✔
504
                if (*ModuleName) STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), ".");
408✔
505
                STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), n);
408✔
506
                // reset non-local label to default "<modules>._"
507
                if (vorlabp) free(vorlabp);
408✔
508
                auto modNameSz = strlen(ModuleName);
408✔
509
                vorlabp = (char*)malloc(modNameSz + 3);
408✔
510
                memcpy(vorlabp, ModuleName, modNameSz);
408✔
511
                vorlabp[modNameSz++] = '.';
408✔
512
                vorlabp[modNameSz++] = '_';
408✔
513
                vorlabp[modNameSz] = 0;
408✔
514

515
                if (IsSldExportActive()) {
408✔
516
                        WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld());
8✔
517
                }
518
        } else {
408✔
519
                if (n) {
18✔
520
                        Error("[MODULE] Dots not allowed in <module_name>", n, SUPPRESS);
12✔
521
                } else {
522
                        Error("[MODULE] Syntax error in <name>", bp, SUPPRESS);
6✔
523
                }
524
        }
525
}
426✔
526

527
static void dirENDMODULE() {
414✔
528
        if (! *ModuleName) {
414✔
529
                Error("ENDMODULE without MODULE");
18✔
530
                return;
18✔
531
        }
532
        if (IsSldExportActive()) {
396✔
533
                WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld(true));
8✔
534
        }
535
        // remove last part of composite modules name
536
        char* lastDot = strrchr(ModuleName, '.');
396✔
537
        if (lastDot)        *lastDot = 0;
396✔
538
        else                        *ModuleName = 0;
318✔
539
        // reset non-local label to default "_"
540
        if (vorlabp) free(vorlabp);
396✔
541
        vorlabp = STRDUP("_");
396✔
542
}
543

544
static void dirEND() {
96✔
545
        char* p = lp;
96✔
546
        aint val;
547
        if (ParseExpression(lp, val)) {
96✔
548
                if (val > 65535 || val < 0) ErrorInt("[END] Invalid address", IF_FIRST);
18✔
549
                else                                                 StartAddress = val;
18✔
550
        } else {
551
                lp = p;
78✔
552
        }
553

554
        IsRunning = 0;
96✔
555
}
96✔
556

557
static void dirSIZE() {
48✔
558
        aint val;
559
        if (!ParseExpressionNoSyntaxError(lp, val)) {
48✔
560
                Error("[SIZE] Syntax error in <filesize>", bp, SUPPRESS);
6✔
561
                return;
34✔
562
        }
563
        if (LASTPASS != pass) return;        // only active during final pass
42✔
564
        if (-1L == size) size = val;        // first time set
14✔
565
        else if (size != val) ErrorInt("[SIZE] Different size than previous", size);        // just check it's same
4✔
566
}
567

568
static void dirINCBIN() {
246✔
569
        int offset = 0, length = INT_MAX;
246✔
570
        fullpath_ref_t fnaam = GetInputFile(lp);
246✔
571
        if (anyComma(lp)) {
246✔
572
                aint val;
573
                if (!anyComma(lp)) {
174✔
574
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
150✔
575
                                Error("[INCBIN] Syntax error in <offset>", bp, SUPPRESS);
12✔
576
                                return;
30✔
577
                        }
578
                        offset = val;
138✔
579
                } else --lp;                // there was second comma right after, reread it
24✔
580
                if (anyComma(lp)) {
162✔
581
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
132✔
582
                                Error("[INCBIN] Syntax error in <length>", bp, SUPPRESS);
18✔
583
                                return;
18✔
584
                        }
585
                        length = val;
114✔
586
                }
587
        }
588
        BinIncFile(fnaam, offset, length);
216✔
589
}
590

591
static void dirINCHOB() {
60✔
592
        unsigned char len[2];
593
        int offset = 0,length = -1;
60✔
594
        fullpath_ref_t fnaam = GetInputFile(lp);
60✔
595
        if (anyComma(lp)) {
60✔
596
                aint val;
597
                if (!anyComma(lp)) {
48✔
598
                        if (!ParseExpression(lp, val)) {
36✔
UNCOV
599
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
600
                        }
601
                        if (val < 0) {
36✔
602
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
603
                        }
604
                        offset += val;
36✔
605
                } else --lp;                // there was second comma right after, reread it
12✔
606
                if (anyComma(lp)) {
48✔
607
                        if (!ParseExpression(lp, val)) {
36✔
UNCOV
608
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
609
                        }
610
                        if (val < 0) {
36✔
UNCOV
611
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
612
                        }
613
                        length = val;
36✔
614
                }
615
        }
616

617
        FILE* ff;
618
        if (!FOPEN_ISOK(ff, fnaam.full, "rb")) {
60✔
UNCOV
619
                Error("[INCHOB] Error opening file", fnaam.str.c_str(), FATAL);
×
620
        }
621
        if (fseek(ff, 0x0b, 0) || 2 != fread(len, 1, 2, ff)) {
60✔
UNCOV
622
                Error("[INCHOB] Hobeta file has wrong format", fnaam.str.c_str(), FATAL);
×
623
        }
624
        fclose(ff);
60✔
625
        if (length == -1) {
60✔
626
                // calculate remaining length of the file (from the specified offset)
627
                length = len[0] + (len[1] << 8) - offset;
24✔
628
        }
629
        offset += 17;                // adjust offset (skip HOB header)
60✔
630
        BinIncFile(fnaam, offset, length);
60✔
631
}
632

633
static void dirINCTRD() {
156✔
634
        aint val, offset = 0, length = INT_MAX;
156✔
635
        fullpath_ref_t trdfile = GetInputFile(lp);
156✔
636
        std::string filename {""};
156✔
637
        if (anyComma(lp) && !anyComma(lp)) filename = GetDelimitedString(lp);
156✔
638
        if (filename.empty()) {
156✔
639
                // file-in-disk syntax error
640
                Error("[INCTRD] Syntax error", bp, IF_FIRST);
18✔
641
                SkipToEol(lp);
18✔
642
                return;
18✔
643
        }
644
        if (anyComma(lp)) {
138✔
645
                if (!anyComma(lp)) {
102✔
646
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
96✔
647
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
12✔
648
                                SkipToEol(lp);
12✔
649
                                return;
12✔
650
                        }
651
                        if (val < 0) {
84✔
652
                                ErrorInt("[INCTRD] Negative offset value is not allowed", val);
6✔
653
                                SkipToEol(lp);
6✔
654
                                return;
6✔
655
                        }
656
                        offset = val;
78✔
657
                } else --lp;                // there was second comma right after, reread it
6✔
658
                if (anyComma(lp)) {
84✔
659
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
66✔
660
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
12✔
661
                                SkipToEol(lp);
12✔
662
                                return;
12✔
663
                        }
664
                        if (val < 0) {
54✔
665
                                ErrorInt("[INCTRD] Negative length value is not allowed", val);
6✔
666
                                SkipToEol(lp);
6✔
667
                                return;
6✔
668
                        }
669
                        length = val;
48✔
670
                }
671
        }
672
        if (TRD_PrepareIncFile(trdfile, filename.c_str(), offset, length)) {
102✔
673
                BinIncFile(trdfile, offset, length);
72✔
674
        }
675
}
156✔
676

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

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

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

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

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

721
static void dirSAVETAP() {
384✔
722
        if (pass != LASTPASS) {
384✔
723
                SkipParam(lp);
256✔
724
                return;
316✔
725
        }
726

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

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

737
        const std::filesystem::path fnaam = GetOutputFileName(lp);
128✔
738
        std::string fnaamh {""};
128✔
739
        if (anyComma(lp)) {
128✔
740
                if (!anyComma(lp)) {
124✔
741
                        char *tlp = lp;
122✔
742
                        char *id;
743

744
                        if ((id = GetID(lp)) && strlen(id) > 0) {
122✔
745
                                if (cmphstr(id, "basic")) {
118✔
746
                                        headerType = BASIC;
18✔
747
                                        realtapeMode = true;
18✔
748
                                } else if (cmphstr(id, "numbers")) {
100✔
749
                                        headerType = NUMBERS;
12✔
750
                                        realtapeMode = true;
12✔
751
                                } else if (cmphstr(id, "chars")) {
88✔
752
                                        headerType = CHARS;
4✔
753
                                        realtapeMode = true;
4✔
754
                                } else if (cmphstr(id, "code")) {
84✔
755
                                        headerType = CODE;
50✔
756
                                        realtapeMode = true;
50✔
757
                                } else if (cmphstr(id, "headless")) {
34✔
758
                                        headerType = HEADLESS;
32✔
759
                                        realtapeMode = true;
32✔
760
                                }
761
                        }
762

763
                        if (realtapeMode) {
122✔
764
                                if (anyComma(lp)) {
116✔
765
                                        if (headerType == HEADLESS) {
112✔
766
                                                if (!anyComma(lp)) {
28✔
767
                                                        if (!ParseExpression(lp, val)) {
26✔
768
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
54✔
769
                                                        }
770
                                                        if (val < 0) {
24✔
771
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
2✔
772
                                                        } else if (val > 0xFFFF) {
22✔
773
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
2✔
774
                                                        }
775
                                                        start = val;
20✔
776
                                                } else {
777
                                                        Error("[SAVETAP] Syntax error. Missing start address", bp, PASS3); return;
2✔
778
                                                }
779
                                                if (anyComma(lp)) {
20✔
780
                                                        if (!ParseExpression(lp, val)) {
20✔
781
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
2✔
782
                                                        }
783
                                                        if (val < 0) {
18✔
784
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
2✔
785
                                                        } else if (val > 0xFFFF) {
16✔
786
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
2✔
787
                                                        }
788
                                                        length = val;
14✔
789
                                                }
790
                                                if (anyComma(lp)) {
14✔
791
                                                        if (!ParseExpression(lp, val)) {
10✔
792
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
2✔
793
                                                        }
794
                                                        if (val < 0 || val > 255) {
8✔
795
                                                                Error("[SAVETAP] Invalid flag byte", bp, PASS3); return;
4✔
796
                                                        }
797
                                                        param3 = val;
4✔
798
                                                }
799
                                        } else if (!anyComma(lp)) {
84✔
800
                                                fnaamh = GetDelimitedString(lp);
82✔
801
                                                if (fnaamh.empty()) {
82✔
802
                                                        Error("[SAVETAP] Syntax error in tape file name", bp, PASS3);
2✔
803
                                                        return;
2✔
804
                                                } else if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
80✔
805
                                                        if (val < 0) {
78✔
806
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
2✔
807
                                                        } else if (val > 0xFFFF) {
76✔
808
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
2✔
809
                                                        }
810
                                                        start = val;
74✔
811

812
                                                        if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
74✔
813
                                                                if (val < 0) {
72✔
814
                                                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
2✔
815
                                                                } else if (val > 0xFFFF) {
70✔
816
                                                                        Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
2✔
817
                                                                }
818
                                                                length = val;
68✔
819

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

875
        if (exec) {
68✔
876
                int done = 0;
66✔
877

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

888
                if (!done) {
66✔
889
                        Error("[SAVETAP] Error writing file", bp, IF_FIRST);
2✔
890
                }
891
        }
892
}
188✔
893

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

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

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

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

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

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

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

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

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

1090
static void dirEMPTYTRD() {
78✔
1091
        if (pass != LASTPASS) {
78✔
1092
                SkipToEol(lp);
52✔
1093
                return;
56✔
1094
        }
1095
        char diskLabel[9] = "        ";
26✔
1096

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

1116
static void dirSAVETRD() {
1,182✔
1117
        if (!DeviceID || pass != LASTPASS) {
1,182✔
1118
                if (!DeviceID) Error("SAVETRD only allowed in real device emulation mode (See DEVICE)");
790✔
1119
                SkipToEol(lp);
790✔
1120
                return;
804✔
1121
        }
1122

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

1127
        const std::filesystem::path fnaam = GetOutputFileName(lp);
392✔
1128

1129
        std::string fnaamh {""};
392✔
1130
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No parameters", bp, SUPPRESS);
392✔
1131
        if (!anyComma(lp)) {
392✔
1132
                if ((replace = ('|' == *lp))) SkipBlanks(++lp);        // detect "|" for "replace" feature
390✔
1133
                else if ((addplace = ('&' == *lp))) SkipBlanks(++lp); // detect "&" for "addplace" feature
376✔
1134
                fnaamh = GetDelimitedString(lp);
390✔
1135
        }
1136
        if (fnaamh.empty()) return Error("[SAVETRD] No filename", bp, SUPPRESS);
392✔
1137

1138
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No address", bp, SUPPRESS);
388✔
1139
        if (ParseExpressionNoSyntaxError(lp, val) && 0 <= val && val <= 0xFFFF) {
386✔
1140
                start = val;
384✔
1141
        }        // else start == -1 (syntax error or invalid value)
1142
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No length", bp, SUPPRESS);
386✔
1143
        ParseExpressionNoSyntaxError(lp, length);        // parse error/invalid value will be reported later
384✔
1144

1145
        if (anyComma(lp)) {
384✔
1146
                if (addplace) {
28✔
1147
                        Error("[SAVETRD] Autostart is not used here", bp, PASS3); return;
2✔
1148
                } else {
1149
                        if (!ParseExpression(lp, val)) {
26✔
1150
                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
2✔
1151
                        }
1152
                        if (val < 0) {
24✔
UNCOV
1153
                                Error("[SAVETRD] Negative values are not allowed", bp, PASS3); return;
×
1154
                        }
1155
                        autostart = val;
24✔
1156
                        // optional length of BASIC without variables
1157
                        if (anyComma(lp)) {
24✔
1158
                                if (!ParseExpression(lp, val)) {
10✔
1159
                                        Error("[SAVETRD] Syntax error", bp, PASS3); return;
2✔
1160
                                }
1161
                                lengthMinusVars = val;
8✔
1162
                        }
1163
                }
1164
        }
1165

1166
        if (exec) TRD_AddFile(fnaam, fnaamh.c_str(), start, length, autostart, replace, addplace, lengthMinusVars);
378✔
1167
}
406✔
1168

1169
static void dirENCODING() {
60✔
1170
        auto arg = GetDelimitedString(lp);
60✔
1171
        char* aP = arg.data();
60✔
1172
        if (cmphstr(aP, "dos")) {
60✔
1173
                ConvertEncoding = ENCDOS;
12✔
1174
        } else if (cmphstr(aP, "win")) {
48✔
1175
                ConvertEncoding = ENCWIN;
12✔
1176
        } else {
1177
                Error("[ENCODING] Invalid argument (valid values: \"dos\" and \"win\")", aP, IF_FIRST);
36✔
1178
        }
1179
}
60✔
1180

1181
static void dirOPT() {
906✔
1182
        // supported options: --zxnext[=cspect] --reversepop --dirbol --nofakes --syntax=<...> -W...
1183
        // process OPT specific command keywords first: {push, pop, reset, listoff, liston, listall, listact, listmc}
1184
        bool didReset = false, didList = Options::syx.IsListingSuspended;
906✔
1185
        while (!SkipBlanks(lp) && '-' != *lp) {
1,448✔
1186
                if (cmphstr(lp, "pop")) {        // "pop" previous syntax state
710✔
1187
                        if (!Options::SSyntax::popSyntax()) Warning("[OPT] no previous syntax found");
162✔
1188
                        return;
168✔
1189
                } else if (cmphstr(lp, "push")) {        // "push" previous syntax state
548✔
1190
                        if (didReset) Warning("[OPT] pushing syntax status after reset");
156✔
1191
                        // preserve current syntax status, before using arguments of OPT
1192
                        Options::SSyntax::pushCurrentSyntax();
156✔
1193
                } else if (cmphstr(lp, "reset")) {        // keep current syntax state
392✔
1194
                        Options::SSyntax::resetCurrentSyntax();
224✔
1195
                        didReset = true;
224✔
1196
                } else if (cmphstr(lp, "listoff")) {
168✔
1197
                        if (!didList) {
84✔
1198
                                ListFile();                // *list* the OPT line suspending the listing
78✔
1199
                                // show in listing file that some part was suspended
1200
                                FILE* listFile = GetListingFile();
78✔
1201
                                if (LASTPASS == pass && listFile) fputs("# listing file suspended...\n", listFile);
78✔
1202
                        }
1203
                        donotlist = 1;
84✔
1204
                        Options::syx.IsListingSuspended = didList = true;
84✔
1205
                } else if (cmphstr(lp, "liston")) {
84✔
1206
                        Options::syx.IsListingSuspended = false;
18✔
1207
                } else if (cmphstr(lp, "listall")) {
66✔
1208
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
6✔
1209
                        didList = true;
6✔
1210
                        donotlist = 1;
6✔
1211
                        Options::syx.ListingType = Options::LST_T_ALL;
6✔
1212
                } else if (cmphstr(lp, "listact")) {
60✔
1213
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
24✔
1214
                        didList = true;
24✔
1215
                        donotlist = 1;
24✔
1216
                        Options::syx.ListingType = Options::LST_T_ACTIVE;
24✔
1217
                } else if (cmphstr(lp, "listmc")) {
36✔
1218
                        if (!didList) ListFile();                // *list* the OPT line changing the filtering
30✔
1219
                        didList = true;
30✔
1220
                        donotlist = 1;
30✔
1221
                        Options::syx.ListingType = Options::LST_T_MC_ONLY;
30✔
1222
                } else {
1223
                        Error("[OPT] invalid command (valid commands: push, pop, reset, liston, listoff, listall, listact, listmc)", lp);
6✔
1224
                        SkipToEol(lp);
6✔
1225
                        return;
6✔
1226
                }
1227
        }
1228
        // split user arguments into "argc, argv" like variables (by white-space)
1229
        char parsedOpts[LINEMAX];
1230
        std::vector<char*> parsedOptsArray;
738✔
1231
        int charI = 0, errI;
738✔
1232
        while (!SkipBlanks(lp)) {
1,466✔
1233
                parsedOptsArray.push_back(parsedOpts + charI);
728✔
1234
                while (*lp && !White()) parsedOpts[charI++] = *lp++;
7,680✔
1235
                parsedOpts[charI++] = 0;
728✔
1236
        }
1237
        int optI = parsedOptsArray.size();
738✔
1238
        parsedOptsArray.push_back(nullptr);
738✔
1239
        // parse user arguments and adjust current syntax setup
1240
        if (optI != (errI = Options::parseSyntaxOptions(optI, parsedOptsArray.data()))) {
738✔
1241
                Error("[OPT] invalid/failed option", parsedOptsArray[errI]);
6✔
1242
        }
1243
        // init Z80N extensions if requested (the Init is safe to be called multiple times)
1244
        if (Options::syx.IsNextEnabled) Z80::InitNextExtensions();
734✔
1245
}
734✔
1246

1247
static void dirLABELSLIST() {
78✔
1248
        if (pass != 1 || !DeviceID) {
78✔
1249
                if (!DeviceID) Error("LABELSLIST only allowed in real device emulation mode (See DEVICE)");
54✔
1250
                SkipToEol(lp);
54✔
1251
                return;
56✔
1252
        }
1253
        const std::filesystem::path opt = GetOutputFileName(lp);
24✔
1254
        if (opt.has_filename()) {
24✔
1255
                Options::UnrealLabelListFName = opt;
22✔
1256
                Options::EmitVirtualLabels = false;
22✔
1257
                if (comma(lp)) {
22✔
1258
                        aint virtualLabelsArg;
1259
                        if (!ParseExpressionNoSyntaxError(lp, virtualLabelsArg)) {
4✔
1260
                                Error("[LABELSLIST] Syntax error in <virtual labels>", bp, EARLY);
2✔
1261
                                return;
2✔
1262
                        }
1263
                        Options::EmitVirtualLabels = (virtualLabelsArg != 0);
2✔
1264
                }
1265
        } else {
1266
                Error("[LABELSLIST] No filename", bp, EARLY);        // pass == 1 -> EARLY
2✔
1267
        }
1268
}
24✔
1269

1270
static void dirCSPECTMAP() {
78✔
1271
        if (LASTPASS != pass || !DeviceID) {
78✔
1272
                if (!DeviceID) Error("CSPECTMAP only allowed in real device emulation mode (See DEVICE)");
54✔
1273
                SkipParam(lp);
54✔
1274
                return;
54✔
1275
        }
1276
        const std::filesystem::path fName = GetOutputFileName(lp);
24✔
1277
        if (fName.has_filename()) {
24✔
1278
                Options::CSpectMapFName = fName;
22✔
1279
        } else {                // create default map file name from current source file name (appends ".map")
1280
                assert(!sourcePosStack.empty());
2✔
1281
                Options::CSpectMapFName = sourcePosStack.back().filename;
2✔
1282
                Options::CSpectMapFName += ".map";
2✔
1283
        }
1284
        // remember page size of current device (in case the source is multi-device later)
1285
        Options::CSpectMapPageSize = Device->GetPage(0)->Size;
24✔
1286
}
24✔
1287

1288
static void dirBPLIST() {
66✔
1289
        // breakpoint file is opened in second pass, and content is written through third pass
1290
        // so position of `BPLIST` directive in source does not matter
1291
        if (2 != pass || !DeviceID) {        // nothing to do in first or last pass, second will open the file
66✔
1292
                if (2 == pass) {        // !Device is true -> no device in second pass -> error
46✔
1293
                        Error("BPLIST only allowed in real device emulation mode (See DEVICE)", nullptr, EARLY);
2✔
1294
                }
1295
                SkipToEol(lp);
46✔
1296
                return;
46✔
1297
        }
1298
        const std::filesystem::path fName = GetOutputFileName(lp);
20✔
1299
        EBreakpointsFile type = BPSF_UNREAL;
20✔
1300
        if (cmphstr(lp, "unreal")) {
20✔
1301
                type = BPSF_UNREAL;
4✔
1302
        } else if (cmphstr(lp, "zesarux")) {
16✔
1303
                type = BPSF_ZESARUX;
6✔
1304
        } else if (!SkipBlanks()) {
10✔
1305
                Warning("[BPLIST] invalid breakpoints file type (use \"unreal\" or \"zesarux\")", lp, W_EARLY);
4✔
1306
        }
1307
        OpenBreakpointsFile(fName, type);
20✔
1308
}
20✔
1309

1310
static void dirSETBREAKPOINT() {
726✔
1311
        if (LASTPASS != pass) {
726✔
1312
                SkipToEol(lp);
484✔
1313
                return;
484✔
1314
        }
1315
        aint val = 0;
242✔
1316
        if (SkipBlanks(lp)) {                // without any expression do the "$" breakpoint
242✔
1317
                WriteBreakpoint(CurAddress);
18✔
1318
        } else if (ParseExpressionNoSyntaxError(lp, val)) {
224✔
1319
                WriteBreakpoint(val);
222✔
1320
        } else {
1321
                Error("[SETBREAKPOINT] Syntax error", bp, SUPPRESS);
2✔
1322
        }
1323
}
1324

1325
/*void dirTEXTAREA() {
1326

1327
}*/
1328

1329
// error message templates for IF**some** directives
1330
constexpr static size_t dirIfErrorsN = 2, dirIfErrorsSZ = 48;
1331
const static char dirIfErrorsTxtSrc[dirIfErrorsN][dirIfErrorsSZ] = {
1332
        { "[%s] No ENDIF" },
1333
        { "[%s] one ELSE only expected" }
1334
};
1335

1336
// IF and IFN internal helper, to evaluate expression
1337
static bool dirIfIfn(aint & val) {
46,330✔
1338
        IsLabelNotFound = false;
46,330✔
1339
        if (!ParseExpression(lp, val)) {
46,330✔
1340
                Error("[IF/IFN] Syntax error", lp, IF_FIRST);
18✔
1341
                return false;
18✔
1342
        }
1343
        if (IsLabelNotFound) {
46,312✔
1344
                WarningById(W_FWD_REF, bp, W_EARLY);
26✔
1345
        }
1346
        return true;
46,312✔
1347
}
1348

1349
// main IF implementation parsing/skipping part of source depending on "val", handling ELSE/ENDIF
1350
static void dirIfInternal(const char* dirName, aint val) {
48,370✔
1351
        // set up error messages for the particular pseudo-op
1352
        char errorsTxt[dirIfErrorsN][dirIfErrorsSZ];
1353
        for (size_t i = 0; i < dirIfErrorsN; ++i) {
145,110✔
1354
                SPRINTF1(errorsTxt[i], dirIfErrorsSZ, dirIfErrorsTxtSrc[i], dirName);
96,740✔
1355
        }
1356
        // do the IF**some** part
1357
        ListFile();
48,370✔
1358
        EReturn ret = END;
48,370✔
1359
        aint elseCounter = 0;
48,370✔
1360
        aint orVal = false;
48,370✔
1361
        while (ENDIF != ret) {
136,130✔
1362
                orVal |= val;
87,778✔
1363
                switch (ret = val ? ReadFile() : SkipFile()) {
87,778✔
1364
                        case ELSE:
39,090✔
1365
                                if (elseCounter++) Error(errorsTxt[1]);
39,090✔
1366
                                val = !val && !orVal;
39,090✔
1367
                                break;
39,090✔
1368
                        case ELSEIF:
318✔
1369
                                val = !val && !orVal;
318✔
1370
                                if (val) {                // active ELSEIF, evaluate expression
318✔
1371
                                        if (!dirIfIfn(val)) {
192✔
1372
                                                val = false;                // syntax error in expression
6✔
1373
                                                orVal = true;                // force remaining IF-blocks inactive
6✔
1374
                                        }
1375
                                }
1376
                                break;
318✔
1377
                        case ENDIF:
48,352✔
1378
                                break;
48,352✔
1379
                        default:
18✔
1380
                                if (IsRunning) Error(errorsTxt[0]);
18✔
1381
                                donotlist=!IsRunning;                // do the listing only if still running
18✔
1382
                                return;
18✔
1383
                }
1384
        }
1385
}
1386

1387
static void dirIF() {
41,446✔
1388
        aint val;
1389
        if (dirIfIfn(val)) dirIfInternal("IF", val);
41,446✔
1390
}
41,446✔
1391

1392
static void dirIFN() {
4,692✔
1393
        aint val;
1394
        if (dirIfIfn(val)) dirIfInternal("IFN", !val);
4,692✔
1395
}
4,692✔
1396

1397
// IFUSED and IFNUSED internal helper, to parse label
1398
static bool dirIfusedIfnused(char* & id) {
240✔
1399
        id = NULL;
240✔
1400
        if (SkipBlanks()) {                                                // no argument (use last parsed label)
240✔
1401
                if (LastParsedLabel) {
30✔
1402
                        id = STRDUP(LastParsedLabel);
18✔
1403
                } else {
1404
                        Error("[IFUSED/IFNUSED] no label defined ahead");
12✔
1405
                        return false;
12✔
1406
                }
1407
        } else {
1408
                std::unique_ptr<char[]> validLabel(ValidateLabel(lp, false, true));
210✔
1409
                if (validLabel) {
210✔
1410
                        id = STRDUP(validLabel.get());
192✔
1411
                        while (islabchar(*lp)) ++lp;        // advance lp beyond parsed label (valid chars only)
1,980✔
1412
                } else {
1413
                        SkipToEol(lp);                                        // ValidateLabel aready reported some error, skip rest
18✔
1414
                }
1415
        }
210✔
1416
        return id && SkipBlanks();                                // valid "id" and no extra characters = OK
228✔
1417
}
1418

1419
static void dirIFUSED() {
162✔
1420
        char* id;
1421
        if (dirIfusedIfnused(id)) dirIfInternal("IFUSED", LabelTable.IsUsed(id));
162✔
1422
        if (id) free(id);
162✔
1423
}
162✔
1424

1425
static void dirIFNUSED() {
78✔
1426
        char* id;
1427
        if (dirIfusedIfnused(id)) dirIfInternal("IFNUSED", !LabelTable.IsUsed(id));
78✔
1428
        if (id) free(id);
78✔
1429
}
78✔
1430

1431
static void dirIFDEF() {
228✔
1432
        char* id;
1433
        if ((id = GetID(lp)) && *id) {
228✔
1434
                dirIfInternal("IFDEF", DefineTable.FindDuplicate(id));
222✔
1435
        } else {
1436
                Error("[IFDEF] Illegal identifier", bp);
6✔
1437
        }
1438
}
228✔
1439

1440
static void dirIFNDEF() {
1,830✔
1441
        char* id;
1442
        if ((id = GetID(lp)) && *id) {
1,830✔
1443
                dirIfInternal("IFNDEF", !DefineTable.FindDuplicate(id));
1,824✔
1444
        } else {
1445
                Error("[IFNDEF] Illegal identifier", bp);
6✔
1446
        }
1447
}
1,830✔
1448

1449
static void dirElseCheckLiveDup() {
24✔
1450
        if (RepeatStack.empty()) return;
24✔
1451
        if (!RepeatStack.top().IsInWork) return;
6✔
1452

1453
        // Seems some ELSE/ELSEIF/ENDIF was encountered inside DUP->EDUP without starting IF
1454
        // -> probably IF was outside of DUP->EDUP block, which is not legal in sjasmplus
1455
        // terminate the current DUP->EDUP macro early and report the open ELSE/ELSEIF/ENDIF
1456
        Error("Conditional block must start and finish inside the repeat block, nested completely");
6✔
1457
        lijstp = nullptr;
6✔
1458
        RepeatStack.top().RepeatCount = 0;
6✔
1459
}
1460

1461
static void dirELSE() {
12✔
1462
        dirElseCheckLiveDup();
12✔
1463
        Error("ELSE without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
12✔
1464
}
12✔
1465

1466
static void dirELSEIF() {
6✔
1467
        dirElseCheckLiveDup();
6✔
1468
        Error("ELSEIF without IF/IFN");
6✔
1469
}
6✔
1470

1471
static void dirENDIF() {
6✔
1472
        dirElseCheckLiveDup();
6✔
1473
        Error("ENDIF without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
6✔
1474
}
6✔
1475

1476
/*void dirENDTEXTAREA() {
1477
  Error("ENDT without TEXTAREA",0);
1478
}*/
1479

1480
static void dirINCLUDE() {
1,170✔
1481
        fullpath_ref_t fnaam = GetInputFile(lp);
1,170✔
1482
        if (fnaam.full.has_filename()) {
1,170✔
1483
                ListFile();
1,158✔
1484
                IncludeFile(fnaam);
1,158✔
1485
                donotlist = 1;
1,158✔
1486
        } else {
1487
                Error("[INCLUDE] empty filename", bp);
12✔
1488
        }
1489
}
1,170✔
1490

1491
static void dirOUTPUT() {
720✔
1492
        if (LASTPASS != pass) {
720✔
1493
                SkipToEol(lp);
480✔
1494
                return;
486✔
1495
        }
1496
        const std::filesystem::path fnaam = GetOutputFileName(lp);
240✔
1497
        char modechar = 0;
240✔
1498
        int mode = OUTPUT_TRUNCATE;
240✔
1499
        if (comma(lp)) {
240✔
1500
                if (!SkipBlanks(lp)) modechar = (*lp++) | 0x20;
16✔
1501
                switch (modechar) {
16✔
1502
                        case 't': mode = OUTPUT_TRUNCATE;        break;
2✔
1503
                        case 'r': mode = OUTPUT_REWIND;                break;
6✔
1504
                        case 'a': mode = OUTPUT_APPEND;                break;
2✔
1505
                        default:
6✔
1506
                                Error("[OUTPUT] Invalid <mode> (valid modes: t, a, r)", bp);
6✔
1507
                                return;
6✔
1508
                }
1509
        }
1510
        //Options::NoDestinationFile = false;
1511
        NewDest(fnaam, mode);
234✔
1512
}
240✔
1513

1514
static void dirOUTEND()
114✔
1515
{
1516
        if (pass == LASTPASS) CloseDest();
114✔
1517
}
114✔
1518

1519
static void dirTAPOUT()
60✔
1520
{
1521
        aint val;
1522
        const std::filesystem::path fnaam = GetOutputFileName(lp);
60✔
1523
        int tape_flag = 255;
60✔
1524
        if (comma(lp))
60✔
1525
        {
1526
                if (!ParseExpression(lp, val))
24✔
1527
                {
1528
                        Error("[TAPOUT] Missing flagbyte value", bp, PASS3); return;
6✔
1529
                }
1530
                tape_flag = val;
18✔
1531
        }
1532
        if (pass == LASTPASS) OpenTapFile(fnaam, tape_flag);
54✔
1533
}
60✔
1534

1535
static void dirTAPEND()
48✔
1536
{
1537
        // if (!FP_tapout) {Error("TAPEND without TAPOUT", bp, PASS3); return;}
1538
        if (pass == LASTPASS) CloseTapFile();
48✔
1539
}
48✔
1540

1541
static void dirDEFINE() {
600✔
1542
        bool replaceEnabled = ('+' == *lp) ? ++lp, true : false;
600✔
1543
        char* id = GetID(lp);
600✔
1544
        if (nullptr == id) {
600✔
1545
                Error("[DEFINE] Illegal <id>", lp, SUPPRESS);
6✔
1546
                return;
6✔
1547
        }
1548
        if (White(*lp)) ++lp;                // skip one whitespace (not considered part of value) (others are)
594✔
1549
        // but trim trailing spaces of value, if there's eol-comment
1550
        if (eolComment) {
594✔
1551
                char *rtrim = lp + strlen(lp);
12✔
1552
                while (lp < rtrim && ' ' == rtrim[-1]) --rtrim;
96✔
1553
                *rtrim = 0;
12✔
1554
        }
1555

1556
        if (replaceEnabled) {
594✔
1557
                DefineTable.Replace(id, lp);
18✔
1558
        } else {
1559
                DefineTable.Add(id, lp, nullptr);
576✔
1560
        }
1561
        SkipToEol(lp);
594✔
1562
        substitutedLine = line;                // override substituted listing for DEFINE
594✔
1563
}
1564

1565
static void dirUNDEFINE() {
210✔
1566
        char* id;
1567

1568
        if (!(id = GetID(lp)) && *lp != '*') {
210✔
1569
                Error("[UNDEFINE] Illegal <id>", lp, SUPPRESS);
6✔
1570
                return;
6✔
1571
        }
1572

1573
        if (*lp == '*') {
204✔
1574
                ++lp;
6✔
1575
                DefineTable.RemoveAll();
6✔
1576
        } else if (DefineTable.FindDuplicate(id)) {
198✔
1577
                DefineTable.Remove(id);
180✔
1578
        } else {
1579
                Warning("[UNDEFINE] Identifier not found", id);
18✔
1580
        }
1581
}
1582

1583
static void dirEXPORT() {
120✔
1584
        aint val;
1585
        char* n, * p;
1586

1587
        if (Options::ExportFName.empty()) {
120✔
1588
                assert(!sourcePosStack.empty());
4✔
1589
                Options::ExportFName = sourcePosStack.back().filename;
4✔
1590
                Options::ExportFName.replace_extension(".exp");
4✔
1591
                Warning("[EXPORT] Filename for exportfile was not indicated. Output will be in", Options::ExportFName.string().c_str(), W_EARLY);
4✔
1592
        }
1593
        if (!(n = p = GetID(lp))) {
120✔
1594
                Error("[EXPORT] Syntax error", lp, SUPPRESS);
24✔
1595
                return;
88✔
1596
        }
1597
        if (pass != LASTPASS) return;
96✔
1598
        IsLabelNotFound = false;
32✔
1599
        GetLabelValue(n, val);
32✔
1600
        if (!IsLabelNotFound) WriteExp(p, val);
32✔
1601
}
1602

1603
static void dirDISPLAY() {
204✔
1604
        char decprint = 'H';
204✔
1605
        char e[LINEMAX + 32], optionChar;                // put extra buffer at end for particular H/A/D number printout
1606
        char* ep = e, * const endOfE = e + LINEMAX;
204✔
1607
        aint val;
1608
        do {
1609
                if (SkipBlanks()) {
4,812✔
1610
                        Error("[DISPLAY] Expression expected");
6✔
1611
                        break;
6✔
1612
                }
1613
                if (*lp == '/') {
4,806✔
1614
                        switch (optionChar = toupper((byte)lp[1])) {
228✔
1615
                        case 'A': case 'D': case 'H': case 'B': case 'C':
198✔
1616
                                // known options, switching hex+dec / dec / hex / binary mode / char mode
1617
                                decprint = optionChar;
198✔
1618
                                break;
198✔
1619
                        case 'L': case 'T':                                // silently ignored options (legacy compatibility)
24✔
1620
                                // in ALASM: 'L' is "concatenate to previous line" (as if there was no \r\n on it)
1621
                                // in ALASM: 'T' used ahead of expression will display first the expression itself, then value
1622
                                break ;
24✔
1623
                        default:
6✔
1624
                                Error("[DISPLAY] Syntax error, unknown option", lp, SUPPRESS);
6✔
1625
                                return;
30✔
1626
                        }
1627
                        lp += 2;
222✔
1628
                        continue;
222✔
1629
                }
1630
                // try to parse some string literal
1631
                const int remainingBufferSize = endOfE - ep;
4,578✔
1632
                if (remainingBufferSize <= 0) {
4,578✔
1633
                        Error("[DISPLAY] internal buffer overflow, resulting text is too long", line);
6✔
1634
                        return;
6✔
1635
                }
1636
                int ei = 0;
4,572✔
1637
                val = GetCharConstAsString(lp, ep, ei, remainingBufferSize);
4,572✔
1638
                if (-1 == val) {
4,572✔
1639
                        Error("[DISPLAY] Syntax error", line);
6✔
1640
                        return;
6✔
1641
                } else if (val) {
4,566✔
1642
                        ep += ei;                                // string literal successfuly parsed
530✔
1643
                } else {
1644
                        // string literal was not there, how about expression?
1645
                        if (ParseExpressionNoSyntaxError(lp, val)) {
4,036✔
1646
                                if (decprint == 'B') {        // 8-bit binary (doesn't care about higher bits)
4,030✔
1647
                                        *(ep++) = '%';
24✔
1648
                                        aint bitMask = 0x80;
24✔
1649
                                        while (bitMask) {
216✔
1650
                                                *(ep++) = (val & bitMask) ? '1' : '0';
192✔
1651
                                                if (0x10 == bitMask) *(ep++) = '\'';
192✔
1652
                                                bitMask >>= 1;
192✔
1653
                                        }
1654
                                }
1655
                                if (decprint == 'C') {
4,030✔
1656
                                        val &= 0xFF;        // truncate to 8bit value
24✔
1657
                                        if (' ' <= val && val < 127) {        // printable ASCII
24✔
1658
                                                *ep++ = '\'';
6✔
1659
                                                *ep++ = val;
6✔
1660
                                                *ep++ = '\'';
6✔
1661
                                        } else {                // non-printable char, do the \x?? form
1662
                                                *ep++ = '\'';
18✔
1663
                                                *ep++ = '\\';
18✔
1664
                                                *ep++ = 'x';
18✔
1665
                                                PrintHex(ep, val, 2);
18✔
1666
                                                *ep++ = '\'';
18✔
1667
                                        }
1668
                                }
1669
                                if (decprint == 'H' || decprint == 'A') {
4,030✔
1670
                                        *(ep++) = '0';
3,934✔
1671
                                        *(ep++) = 'x';
3,934✔
1672
                                        PrintHexAlt(ep, val);
3,934✔
1673
                                }
1674
                                if (decprint == 'D' || decprint == 'A') {
4,030✔
1675
                                        if (decprint == 'A') {
108✔
1676
                                                *(ep++) = ','; *(ep++) = ' ';
60✔
1677
                                        }
1678
                                        int charsToPrint = SPRINTF1(ep, remainingBufferSize, "%u", val);
108✔
1679
                                        if (remainingBufferSize <= charsToPrint) {
108✔
1680
                                                Error("[DISPLAY] internal buffer overflow, resulting text is too long", line);
6✔
1681
                                                return;
6✔
1682
                                        }
1683
                                        ep += charsToPrint;
102✔
1684
                                }
1685
                                decprint = 'H';
4,024✔
1686
                        } else {
1687
                                Error("[DISPLAY] Syntax error", line, SUPPRESS);
6✔
1688
                                return;
6✔
1689
                        }
1690
                }
1691
        } while(comma(lp));
4,776✔
1692
        *ep = 0; // end line
174✔
1693

1694
        if (LASTPASS == pass && *e) {
174✔
1695
                _CERR "> " _CMDL Options::tcols->display _CMDL e _CMDL Options::tcols->end _ENDL;
54✔
1696
        }
1697
}
1698

1699
static void dirMACRO() {
1,188✔
1700
        if (lijst) {
1,188✔
1701
                Error("[MACRO] No macro definitions allowed here", NULL, SUPPRESS);
120✔
1702
                return;
120✔
1703
        }
1704
        // check if the name of macro is defined at beginning of the line ("label" name)
1705
        const bool labelName = LastParsedLabelLine == CompiledCurrentLine;
1,068✔
1706
        assert(!labelName || LastParsedLabel);
1,068✔
1707
        char* lpLabel = labelName ? LastParsedLabel : lp;        // temporary pointer to advance by GetID
1,068✔
1708
        char* n = GetID(lpLabel);                                                        // get+validate macro name
1,068✔
1709
        if (*lpLabel && !White(*lpLabel)) n = nullptr;                // if there's unexpected trailing char, report illegal name
1,068✔
1710
        if (n) {
1,068✔
1711
                if (!labelName) lp = lpLabel;                                        // name was after MACRO keyword, advance global `lp` (to parse arguments)
1,014✔
1712
                MacroTable.Add(n, lp);
1,014✔
1713
        } else {
1714
                Error("[MACRO] Illegal macroname", labelName ? LastParsedLabel : lp);        // report what was fed into GetID
54✔
1715
                SkipToEol(lp);
54✔
1716
        }
1717
}
1718

1719
static void dirENDS() {
18✔
1720
        Error("[ENDS] End structure without structure");
18✔
1721
}
18✔
1722

1723
static void dirASSERT() {
4,594✔
1724
        char* p = lp;
4,594✔
1725
        aint val;
1726
        if (!ParseExpressionNoSyntaxError(lp, val)) {
4,594✔
1727
                Error("[ASSERT] Syntax error", p, SUPPRESS);
24✔
1728
                return;
24✔
1729
        }
1730
        if (pass == LASTPASS && !val) {
4,570✔
1731
                Error("[ASSERT] Assertion failed", p);
36✔
1732
        }
1733
        if (comma(lp)) SkipToEol(lp);
4,570✔
1734
}
1735

1736
static void dirSHELLEXEC() {
18✔
1737
        //TODO for v2.x change the "SHELLEXEC <command>[, <params>]" syntax to "SHELLEXEC <whatever>"
1738
        // (and add good examples how to deal with quotes/colons/long file names with spaces)
1739
        std::string command = GetDelimitedString(lp);
18✔
1740
        std::string parameters {""};
18✔
1741
        if (comma(lp)) {
18✔
1742
                parameters = GetDelimitedString(lp);
12✔
1743
        }
1744
        if (pass == LASTPASS) {
18✔
1745
                if (!system(nullptr)) {
6✔
UNCOV
1746
                        Error("[SHELLEXEC] clib command processor is not available on this platform!");
×
1747
                } else {
1748
                        if (!parameters.empty()) command += " " + parameters;
6✔
1749
                        if (Options::OutputVerbosity <= OV_ALL) {
6✔
UNCOV
1750
                                _CERR "Executing <" _CMDL command _CMDL ">" _ENDL;
×
1751
                        }
1752
                        // flush both stdout and stderr before trying to execute anything externally
1753
                        _COUT flush;
6✔
1754
                        _CERR flush;
6✔
1755
                        // execute the requested command
1756
                        int exitCode = system(command.c_str());
6✔
1757
                        if (exitCode) {
6✔
1758
                                ErrorInt("[SHELLEXEC] non-zero exit code", WEXITSTATUS(exitCode));
2✔
1759
                        }
1760
                }
1761
        }
1762
}
18✔
1763

1764
static void dirSTRUCT() {
492✔
1765
        CStructure* st;
1766
        int global = 0;
492✔
1767
        aint offset = 0;
492✔
1768
        char* naam;
1769
        SkipBlanks();
492✔
1770
        if (*lp == '@') {
492✔
1771
                ++lp; global = 1;
30✔
1772
        }
1773

1774
        if (!(naam = GetID(lp)) || !strlen(naam)) {
492✔
1775
                Error("[STRUCT] Illegal structure name", lp, SUPPRESS);
6✔
1776
                return;
486✔
1777
        }
1778
        if (comma(lp)) {
486✔
1779
                IsLabelNotFound = false;
90✔
1780
                if (!ParseExpressionNoSyntaxError(lp, offset)) {
90✔
1781
                        Error("[STRUCT] Offset syntax error", lp, SUPPRESS);
6✔
1782
                        return;
6✔
1783
                }
1784
                if (IsLabelNotFound) {
84✔
1785
                        Error("[STRUCT] Forward reference", NULL, EARLY);
10✔
1786
                }
1787
        }
1788
        if (!SkipBlanks()) {
480✔
1789
                Error("[STRUCT] syntax error, unexpected", lp);
6✔
1790
        }
1791
        st = StructureTable.Add(naam, offset, global);
480✔
1792
        ListFile();
480✔
1793
        while (ReadLine()) {
2,874✔
1794
                lp = line; /*if (White()) { SkipBlanks(lp); if (*lp=='.') ++lp; if (cmphstr(lp,"ends")) break; }*/
2,868✔
1795
                SkipBlanks(lp);
2,868✔
1796
                if (*lp == '.') {
2,868✔
1797
                        ++lp;
42✔
1798
                }
1799
                if (cmphstr(lp, "ends")) {
2,868✔
1800
                        ++CompiledCurrentLine;
474✔
1801
                        if (st) st->deflab();
474✔
1802
                        lp = ReplaceDefine(lp);                // skip any empty substitutions and comments
474✔
1803
                        substitutedLine = line;                // override substituted listing for ENDS
474✔
1804
                        return;
474✔
1805
                }
1806
                if (st) ParseStructLine(st);
2,394✔
1807
                ListFile(true);
2,394✔
1808
        }
1809
        Error("[STRUCT] Unexpected end of structure");
6✔
1810
        st->deflab();
6✔
1811
}
1812

1813
static void dirFPOS() {
36✔
1814
        aint val;
1815
        int method = SEEK_SET;
36✔
1816
        SkipBlanks(lp);
36✔
1817
        if ((*lp == '+') || (*lp == '-')) {
36✔
1818
                method = SEEK_CUR;
12✔
1819
        }
1820
        if (!ParseExpressionNoSyntaxError(lp, val)) {
36✔
1821
                Error("[FPOS] Syntax error", lp, SUPPRESS);
6✔
1822
        } else if (pass == LASTPASS) {
30✔
1823
                SeekDest(val, method);
10✔
1824
        }
1825
}
36✔
1826

1827
// isWhile == false: DUP/REPT parsing
1828
// isWhile == true: WHILE parsing
1829
static void DupWhileImplementation(bool isWhile) {
8,992✔
1830
        aint val = 0;
8,992✔
1831
        CStringsList* condition = nullptr;
8,992✔
1832

1833
        if (!RepeatStack.empty()) {
8,992✔
1834
                SRepeatStack& dup = RepeatStack.top();
7,882✔
1835
                if (!dup.IsInWork) {
7,882✔
1836
                        SkipToEol(lp);                // Just skip the expression to the end of line, don't evaluate yet
528✔
1837
                        ++dup.Level;
528✔
1838
                        return;
552✔
1839
                }
1840
        }
1841

1842
        const char* indexVar = nullptr;
8,464✔
1843
        if (isWhile) {
8,464✔
1844
                condition = new CStringsList(lp);
78✔
1845
                if (nullptr == condition) ErrorOOM();
78✔
1846
                lp += strlen(condition->string);
78✔
1847
                // scan condition string for extra guardian value, and split + parse it as needed
1848
                char* expressionSource = condition->string;
78✔
1849
                bool parseOk = ParseExpressionNoSyntaxError(expressionSource, val);
78✔
1850
                if (parseOk && *expressionSource && comma(expressionSource)) {
78✔
1851
                        // comma found, try to parse explicit guardian value
1852
                        char* guardianSource = expressionSource;
18✔
1853
                        parseOk = parseOk && ParseExpressionNoSyntaxError(guardianSource, val);
18✔
1854
                        // overwrite the comma to keep only condition string without guardian argument
1855
                        if (parseOk) {
18✔
1856
                                assert(',' == expressionSource[-1]);
12✔
1857
                                expressionSource[-1] = 0;
12✔
1858
                                ++val;                // +1 to explicit value to report error when WHILE does *over* that
12✔
1859
                        }
1860
                } else {
1861
                        val = 100001;        // default guardian value is 100k
60✔
1862
                }
1863
                if (!parseOk) {
78✔
1864
                        Error("[WHILE] Syntax error in <expression>", condition->string, SUPPRESS);
18✔
1865
                        free(condition->string);                        // release original string
18✔
1866
                        condition->string = STRDUP("0");        // force it to evaluate to zero
18✔
1867
                        val = 1;
18✔
1868
                }
1869
        } else {
1870
                IsLabelNotFound = false;
8,386✔
1871
                if (!ParseExpressionNoSyntaxError(lp, val)) {
8,386✔
1872
                        Error("[DUP/REPT] Syntax error in <count>", lp, SUPPRESS);
18✔
1873
                        return;
18✔
1874
                }
1875
                if (IsLabelNotFound) {
8,368✔
1876
                        Error("[DUP/REPT] Forward reference", NULL, ALL);
2✔
1877
                }
1878
                if ((int) val < 0) {
8,368✔
1879
                        ErrorInt("[DUP/REPT] Repeat value must be positive or zero", val, IF_FIRST); return;
6✔
1880
                }
1881
                if (comma(lp)) {
8,362✔
1882
                        indexVar = GetID(lp);
60✔
1883
                        if (nullptr == indexVar) {
60✔
1884
                                Error("[DUP/REPT] invalid index variable name", lp, IF_FIRST);
12✔
1885
                                SkipToEol(lp);
12✔
1886
                        }
1887
                }
1888
        }
1889

1890
        RepeatStack.emplace(val, condition, new CStringsList(indexVar ? indexVar : ""));
8,440✔
1891
        if (!SkipBlanks()) Error("[DUP] unexpected chars", lp, SUPPRESS);
8,440✔
1892
}
1893

1894
static void dirDUP() {
8,908✔
1895
        DupWhileImplementation(false);
8,908✔
1896
}
8,908✔
1897

1898
static void dirWHILE() {
84✔
1899
        DupWhileImplementation(true);
84✔
1900
}
84✔
1901

1902
static bool shouldRepeat(SRepeatStack& dup) {
720,952✔
1903
        if (nullptr == dup.RepeatCondition) {
720,952✔
1904
                return 0 <= --dup.RepeatCount;
120,558✔
1905
        } else {
1906
                if (!dup.RepeatCount--) {
600,394✔
1907
                        sourcePosStack.push_back(dup.RepeatCondition->source);
12✔
1908
                        Error("[WHILE] infinite loop? (reaching the guardian value, default 100k)");
12✔
1909
                        sourcePosStack.pop_back();
12✔
1910
                        return false;
12✔
1911
                }
1912
                aint val = 0;
600,382✔
1913
                IsLabelNotFound = false;
600,382✔
1914
                char* expressionSource = dup.RepeatCondition->string;
600,382✔
1915
                if (!ParseExpressionNoSyntaxError(expressionSource, val) || *expressionSource) {
600,382✔
1916
                        const TextFilePos oSourcePos = sourcePosStack.back();
6✔
1917
                        sourcePosStack.back() = dup.RepeatCondition->source;
6✔
1918
                        Error("[WHILE] Syntax error in <expression>", dup.RepeatCondition->string, SUPPRESS);
6✔
1919
                        sourcePosStack.back() = oSourcePos;
6✔
1920
                        return false;
6✔
1921
                }
1922
                if (IsLabelNotFound) {
600,376✔
1923
                        WarningById(W_FWD_REF, dup.RepeatCondition->string, W_EARLY);
2✔
1924
                        return false;
2✔
1925
                }
1926
                return val;
600,374✔
1927
        }
1928
}
1929

1930
static void dirEDUP() {
8,980✔
1931
        if (RepeatStack.empty() || RepeatStack.top().IsInWork) {
8,980✔
1932
                Error("[EDUP/ENDR/ENDW] End repeat without repeat");
18✔
1933
                return;
546✔
1934
        }
1935

1936
        SRepeatStack& dup = RepeatStack.top();
8,962✔
1937
        if (!dup.IsInWork && dup.Level) {
8,962✔
1938
                --dup.Level;
528✔
1939
                return;
528✔
1940
        }
1941
        dup.IsInWork = true;
8,434✔
1942
        // kill the "EDUP" inside DUP-list (+ works as "while (IsRunning && lijstp && lijstp->string)" terminator)
1943
        if (dup.Pointer->string) free(dup.Pointer->string);
8,434✔
1944
        dup.Pointer->string = NULL;
8,434✔
1945
        ++listmacro;
8,434✔
1946
        char* ml = STRDUP(line);        // copy the EDUP line for List purposes (after the DUP block emit)
8,434✔
1947
        if (ml == NULL) ErrorOOM();
8,434✔
1948

1949
        CStringsList* olijstp = lijstp;
8,434✔
1950
        ++lijst;
8,434✔
1951
        assert(!sourcePosStack.empty());
8,434✔
1952
        const TextFilePos oSourcePos = sourcePosStack.back();
8,434✔
1953
        aint currentRepeatIndex = 0;
8,434✔
1954
        while (IsRunning && dup.Lines && shouldRepeat(dup)) {
720,958✔
1955
                sourcePosStack.back() = dup.sourcePos;
712,524✔
1956
                lijstp = dup.Lines;
712,524✔
1957
                assert(lijstp);
712,524✔
1958
                if (*lijstp->string) {
712,524✔
1959
                        // if the DUP has index variable, the first "line" is the variable name, set it up to current index
1960
                        std::unique_ptr<char[]> indexVar(ValidateLabel(lijstp->string,  false));
276✔
1961
                        if (indexVar.get()) LabelTable.Insert(indexVar.get(), currentRepeatIndex++, LABEL_IS_DEFL);
276✔
1962
                }
276✔
1963
                lijstp = lijstp->next;        // skip first empty line / indexVar name
712,524✔
1964
                while (IsRunning && lijstp && lijstp->string) {        // the EDUP/REPT/ENDM line has string=NULL => ends loop
1,563,196✔
1965
                        if (lijstp->source.line) sourcePosStack.back() = lijstp->source;
850,672✔
1966
                        STRCPY(line, LINEMAX, lijstp->string);
850,672✔
1967
                        substitutedLine = line;                // reset substituted listing
850,672✔
1968
                        eolComment = NULL;                        // reset end of line comment
850,672✔
1969
                        lijstp = lijstp->next;
850,672✔
1970
                        ParseLineSafe();
850,672✔
1971
                        sourcePosStack.back().nextSegment();
850,672✔
1972
                }
1973
        }
1974
        sourcePosStack.back() = oSourcePos;
8,434✔
1975
        RepeatStack.pop();
8,434✔
1976
        lijstp = olijstp;
8,434✔
1977
        --lijst;
8,434✔
1978
        --listmacro;
8,434✔
1979
        STRCPY(line, LINEMAX,  ml);                // show EDUP line itself
8,434✔
1980
        free(ml);
8,434✔
1981
        ++CompiledCurrentLine;
8,434✔
1982
        substitutedLine = line;                        // override substituted list line for EDUP
8,434✔
1983
        ListFile();
8,434✔
1984
}
1985

1986
static void dirENDM() {
36✔
1987
        if (!RepeatStack.empty()) {
36✔
1988
                Warning("ENDM used as DUP/REPT block terminator, this is deprecated (and bugged when used inside macro), change to EDUP or ENDR");
6✔
1989
                dirEDUP();
6✔
1990
        } else {
1991
                Error("[ENDM] End macro without macro");
30✔
1992
        }
1993
}
36✔
1994

1995
static bool dirDEFARRAY_parseItems(CStringsList** nextPtr) {
408✔
1996
        char ml[LINEMAX];
1997
        do {
1998
                const char* const itemLp = lp;
2,394✔
1999
                char* n = ml;
2,394✔
2000
                if (!GetMacroArgumentValue(lp, n)) {
2,394✔
2001
                        Error("[DEFARRAY] Syntax error", itemLp, SUPPRESS);
36✔
2002
                        return false;
36✔
2003
                }
2004
                *nextPtr = new CStringsList(ml);
2,358✔
2005
                if ((*nextPtr)->string == NULL) ErrorOOM();
2,358✔
2006
                nextPtr = &((*nextPtr)->next);
2,358✔
2007
        } while (anyComma(lp));
2,358✔
2008
        return SkipBlanks();
372✔
2009
}
2010

2011
static void dirDEFARRAY_add(const char* id) {
42✔
2012
        DefineTable.Get(id);
42✔
2013
        if (NULL == DefineTable.DefArrayList) {
42✔
2014
                Error("[DEFARRAY+] unknown array <id>", id);
12✔
2015
                SkipToEol(lp);
12✔
2016
                return;
12✔
2017
        }
2018
        // array was already defined, seek to the last item in the list
2019
        while (DefineTable.DefArrayList->next) DefineTable.DefArrayList = DefineTable.DefArrayList->next;
168✔
2020
        dirDEFARRAY_parseItems(&DefineTable.DefArrayList->next);
30✔
2021
        return;
30✔
2022
}
2023

2024
static void dirDEFARRAY() {
522✔
2025
        bool plus = ('+' == *lp) ? ++lp, true : false;
522✔
2026
        const char* id = White() ? GetID(lp) : nullptr;
522✔
2027
        if (!id) {
522✔
2028
                Error("[DEFARRAY] Syntax error in <id>", lp);
36✔
2029
                SkipToEol(lp);
36✔
2030
                return;
36✔
2031
        }
2032
        if (!White() || SkipBlanks()) {        // enforce whitespace between ID and first item and detect empty ones
486✔
2033
                if (SkipBlanks()) Error("[DEFARRAY] must have at least one entry");
66✔
2034
                else Error("[DEFARRAY] missing space between <id> and first <item>", lp);
30✔
2035
                SkipToEol(lp);
66✔
2036
                return;
66✔
2037
        }
2038
        if (plus) {
420✔
2039
                dirDEFARRAY_add(id);
42✔
2040
        } else {
2041
                CStringsList* a = NULL;
378✔
2042
                if (!dirDEFARRAY_parseItems(&a) || NULL == a) {
378✔
2043
                        if (a) delete a;        // release already parsed items, if there was syntax error
30✔
2044
                        return;
30✔
2045
                }
2046
                DefineTable.Add(id, "", a);
348✔
2047
        }
2048
}
2049

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

2052
static void dirDEFDEVICE() {
106✔
2053
        //DEFDEVICE <deviceid>, <slot_size>, <page_count>[, <slot_0_initial_page>[, ...]]
2054
        const char* id = GetID(lp);
106✔
2055
        if (!id) {
106✔
2056
                Error(DEFDEVICE_SYNTAX_ERR, bp, SUPPRESS);
6✔
2057
                return;
90✔
2058
        }
2059

2060
        const bool is_defined = std::any_of(
100✔
2061
                DefDevices.begin(), DefDevices.end(),
2062
                [&](const CDeviceDef* el) { return 0 == strcasecmp(id, el->getID()); }
248✔
2063
        );
2064
        if (is_defined || 1 < pass) {
100✔
2065
                // same id defined twice during first pass?
2066
                if (pass <= 1) Error("[DEFDEVICE] device with such ID is already defined", id, EARLY);
70✔
2067
                // in later passes ignore the line, DEFDEVICE works only in first pass
2068
                SkipToEol(lp);
70✔
2069
                return;
70✔
2070
        }
2071

2072
        // add new definition if arguments are correct and this is first pass
2073
        aint args[2 + CDeviceDef::MAX_SLOT_N] = {};        // slot_size, page_count, initial pages, ...
30✔
2074
        bool optional[2 + CDeviceDef::MAX_SLOT_N] = {false, false};
30✔
2075
        aint &slot_size = args[0], &page_count = args[1], *initial_pages = args + 2;
30✔
2076
        for (size_t i = 2; i < CDeviceDef::MAX_SLOT_N; ++i) {
7,650✔
2077
                args[i] = -1;
7,620✔
2078
                optional[i] = true;
7,620✔
2079
        }
2080
        if (!anyComma(lp) || !getIntArguments<2 + CDeviceDef::MAX_SLOT_N>(lp, args, optional)) {
30✔
2081
                Error(DEFDEVICE_SYNTAX_ERR, bp, EARLY);
8✔
2082
                return;
8✔
2083
        }
2084
        if (slot_size < 256 || 0x10000 < slot_size || page_count <= 0) {
22✔
2085
                Error("[DEFDEVICE] valid slot_size: 256..64ki, page_count: 1 or more", bp, EARLY);
6✔
2086
                return;
6✔
2087
        }
2088
        DefDevices.push_back(new CDeviceDef(id, slot_size, page_count));
16✔
2089

2090
        // init "initialPages array by going 0, 1, 2, ..., page_count-1, page_count-1, ... or parsed explicit values
2091
        CDeviceDef & dev = *DefDevices.back();
16✔
2092
        int previous_page = -1;
16✔
2093
        for (int32_t i = 0; i < dev.SlotsCount; ++i) {
596✔
2094
                if (0 <= initial_pages[i] && initial_pages[i] < dev.PagesCount) {
580✔
2095
                        previous_page = initial_pages[i];
38✔
2096
                } else {
2097
                        if (-1 != initial_pages[i]) ErrorInt("[DEFDEVICE] invalid initial page", initial_pages[i], EARLY);
542✔
2098
                        if (previous_page < dev.PagesCount - 1) ++previous_page;
542✔
2099
                }
2100
                dev.initialPages[i] = previous_page;
580✔
2101
        }
2102
}
2103

2104
static void dirDEVICE() {
1,744✔
2105
        // refresh source position of first DEVICE directive
2106
        if (1 == ++deviceDirectivesCount) {
1,744✔
2107
                assert(!sourcePosStack.empty());
940✔
2108
                globalDeviceSourcePos = sourcePosStack.back();
940✔
2109
        }
2110

2111
        char* id = GetID(lp);
1,744✔
2112
        if (id) {
1,744✔
2113
                aint ramtop = 0;
1,738✔
2114
                if (anyComma(lp)) {
1,738✔
2115
                        if (!ParseExpressionNoSyntaxError(lp, ramtop)) {
270✔
2116
                                Error("[DEVICE] Syntax error", bp); return;
60✔
2117
                        }
2118
                        if (ramtop < 0x5D00 || 0xFFFF < ramtop) {
258✔
2119
                                  ErrorInt("[DEVICE] valid range for RAMTOP is $5D00..$FFFF", ramtop); return;
48✔
2120
                        }
2121
                }
2122
                // if (1 == deviceDirectivesCount && Device) -> device was already set globally, skip SetDevice
2123
                if (1 < deviceDirectivesCount || !Devices) {
1,678✔
2124
                        if (!SetDevice(id, ramtop)) {
1,248✔
2125
                                Error("[DEVICE] Invalid parameter", id, IF_FIRST);
20✔
2126
                        }
2127
                }
2128
        } else {
2129
                Error("[DEVICE] Syntax error in <deviceid>", lp, SUPPRESS);
6✔
2130
        }
2131
}
2132

2133
static void dirSLDOPT() {
42✔
2134
        SkipBlanks(lp);
42✔
2135
        if (cmphstr(lp, "COMMENT")) {
42✔
2136
                do {
2137
                        SldAddCommentKeyword(GetID(lp));
42✔
2138
                } while (!SkipBlanks(lp) && anyComma(lp));
42✔
2139
        } else {
2140
                Error("[SLDOPT] Syntax error in <type> (valid is only COMMENT)", lp, SUPPRESS);
12✔
2141
        }
2142
}
42✔
2143

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

2247
        DirectivesTable.insertd(".device", dirDEVICE);
1,034✔
2248
        DirectivesTable.insertd(".defdevice", dirDEFDEVICE);
1,034✔
2249

2250
        DirectivesTable.insertd(".bplist", dirBPLIST);
1,034✔
2251
        DirectivesTable.insertd(".setbreakpoint", dirSETBREAKPOINT);
1,034✔
2252
        DirectivesTable.insertd(".setbp", dirSETBREAKPOINT);
1,034✔
2253

2254
        DirectivesTable.insertd(".relocate_start", Relocation::dirRELOCATE_START);
1,034✔
2255
        DirectivesTable.insertd(".relocate_end", Relocation::dirRELOCATE_END);
1,034✔
2256
        DirectivesTable.insertd(".relocate_table", Relocation::dirRELOCATE_TABLE);
1,034✔
2257

2258
        DirectivesTable.insertd(".sldopt", dirSLDOPT);
1,034✔
2259

2260
#ifdef USE_LUA
2261
        DirectivesTable.insertd(".lua", dirLUA);
1,034✔
2262
        DirectivesTable.insertd(".endlua", dirENDLUA);
1,034✔
2263
        DirectivesTable.insertd(".includelua", dirINCLUDELUA);
1,034✔
2264
#endif //USE_LUA
2265

2266
        DirectivesTable_dup.insertd(".dup", dirDUP);
1,034✔
2267
        DirectivesTable_dup.insertd(".edup", dirEDUP);
1,034✔
2268
        DirectivesTable_dup.insertd(".endm", dirENDM);
1,034✔
2269
        DirectivesTable_dup.insertd(".endr", dirEDUP);
1,034✔
2270
        DirectivesTable_dup.insertd(".endw", dirEDUP);
1,034✔
2271
        DirectivesTable_dup.insertd(".rept", dirDUP);
1,034✔
2272
        DirectivesTable_dup.insertd(".while", dirWHILE);
1,034✔
2273
}
1,034✔
2274

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