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

z00m128 / sjasmplus / 1455

02 Jan 2025 10:38PM UTC coverage: 96.455% (+0.2%) from 96.279%
1455

Pull #254

cirrus-ci

ped7g
test coverage: try to fix reader.cpp mismatching

or at least move it to end of file... :D
Pull Request #254: migrating some of the file stuff to std::filesystem, in the naive hope of getting better cross-platform compatibility

274 of 297 new or added lines in 13 files covered. (92.26%)

2 existing lines in 1 file now uncovered.

9606 of 9959 relevant lines covered (96.46%)

170053.62 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)
243,328✔
37
{
38
        char* olp = lp;
243,328✔
39

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

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

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

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

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

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

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

109

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1128
        const std::filesystem::path fnaam = GetOutputFileName(lp);
196✔
1129

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

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

1146
        if (anyComma(lp)) {
192✔
1147
                if (addplace) {
14✔
1148
                        Error("[SAVETRD] Autostart is not used here", bp, PASS3); return;
1✔
1149
                } else {
1150
                        if (!ParseExpression(lp, val)) {
13✔
1151
                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1152
                        }
1153
                        if (val < 0) {
12✔
NEW
1154
                                Error("[SAVETRD] Negative values are not allowed", bp, PASS3); return;
×
1155
                        }
1156
                        autostart = val;
12✔
1157
                        // optional length of BASIC without variables
1158
                        if (anyComma(lp)) {
12✔
1159
                                if (!ParseExpression(lp, val)) {
5✔
1160
                                        Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1161
                                }
1162
                                lengthMinusVars = val;
4✔
1163
                        }
1164
                }
1165
        }
1166

1167
        if (exec) TRD_AddFile(fnaam, fnaamh.c_str(), start, length, autostart, replace, addplace, lengthMinusVars);
189✔
1168
}
203✔
1169

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

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

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

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

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

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

1326
/*void dirTEXTAREA() {
1327

1328
}*/
1329

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

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

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

1388
static void dirIF() {
20,669✔
1389
        aint val;
1390
        if (dirIfIfn(val)) dirIfInternal("IF", val);
20,669✔
1391
}
20,669✔
1392

1393
static void dirIFN() {
2,346✔
1394
        aint val;
1395
        if (dirIfIfn(val)) dirIfInternal("IFN", !val);
2,346✔
1396
}
2,346✔
1397

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

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

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

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

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

1450
static void dirElseCheckLiveDup() {
12✔
1451
        if (RepeatStack.empty()) return;
12✔
1452
        if (!RepeatStack.top().IsInWork) return;
3✔
1453

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

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

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

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

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

1481
static void dirINCLUDE() {
471✔
1482
        const std::filesystem::path fnaam = GetFileName(lp);
471✔
1483
        if (fnaam.has_filename()) {
471✔
1484
                EDelimiterType dt = GetDelimiterOfLastFileName();
465✔
1485
                ListFile();
465✔
1486
                IncludeFile(fnaam.string().c_str(), DT_ANGLE == dt);        //FIXME path idea possible with refactoring^2
465✔
1487
                donotlist = 1;
465✔
1488
        } else {
1489
                Error("[INCLUDE] empty filename", bp);
6✔
1490
        }
1491
}
471✔
1492

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

1516
static void dirOUTEND()
54✔
1517
{
1518
        if (pass == LASTPASS) CloseDest();
54✔
1519
}
54✔
1520

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

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

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

1558
        if (replaceEnabled) {
282✔
1559
                DefineTable.Replace(id, lp);
9✔
1560
        } else {
1561
                DefineTable.Add(id, lp, nullptr);
273✔
1562
        }
1563
        SkipToEol(lp);
282✔
1564
        substitutedLine = line;                // override substituted listing for DEFINE
282✔
1565
}
1566

1567
static void dirUNDEFINE() {
105✔
1568
        char* id;
1569

1570
        if (!(id = GetID(lp)) && *lp != '*') {
105✔
1571
                Error("[UNDEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1572
                return;
3✔
1573
        }
1574

1575
        if (*lp == '*') {
102✔
1576
                ++lp;
3✔
1577
                DefineTable.RemoveAll();
3✔
1578
        } else if (DefineTable.FindDuplicate(id)) {
99✔
1579
                DefineTable.Remove(id);
90✔
1580
        } else {
1581
                Warning("[UNDEFINE] Identifier not found", id);
9✔
1582
        }
1583
}
1584

1585
static void dirEXPORT() {
60✔
1586
        aint val;
1587
        char* n, * p;
1588

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

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

1696
        if (LASTPASS == pass && *e) {
87✔
1697
                _CERR "> " _CMDL Options::tcols->display _CMDL e _CMDL Options::tcols->end _ENDL;
27✔
1698
        }
1699
}
1700

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

1721
static void dirENDS() {
9✔
1722
        Error("[ENDS] End structure without structure");
9✔
1723
}
9✔
1724

1725
static void dirASSERT() {
2,240✔
1726
        char* p = lp;
2,240✔
1727
        aint val;
1728
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,240✔
1729
                Error("[ASSERT] Syntax error", p, SUPPRESS);
12✔
1730
                return;
12✔
1731
        }
1732
        if (pass == LASTPASS && !val) {
2,228✔
1733
                Error("[ASSERT] Assertion failed", p);
6✔
1734
        }
1735
        if (comma(lp)) SkipToEol(lp);
2,228✔
1736
}
1737

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

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

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

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

1829
// isWhile == false: DUP/REPT parsing
1830
// isWhile == true: WHILE parsing
1831
static void DupWhileImplementation(bool isWhile) {
4,496✔
1832
        aint val = 0;
4,496✔
1833
        CStringsList* condition = nullptr;
4,496✔
1834

1835
        if (!RepeatStack.empty()) {
4,496✔
1836
                SRepeatStack& dup = RepeatStack.top();
3,941✔
1837
                if (!dup.IsInWork) {
3,941✔
1838
                        SkipToEol(lp);                // Just skip the expression to the end of line, don't evaluate yet
264✔
1839
                        ++dup.Level;
264✔
1840
                        return;
276✔
1841
                }
1842
        }
1843

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

1892
        RepeatStack.emplace(val, condition, new CStringsList(indexVar ? indexVar : ""));
4,220✔
1893
        if (!SkipBlanks()) Error("[DUP] unexpected chars", lp, SUPPRESS);
4,220✔
1894
}
1895

1896
static void dirDUP() {
4,454✔
1897
        DupWhileImplementation(false);
4,454✔
1898
}
4,454✔
1899

1900
static void dirWHILE() {
42✔
1901
        DupWhileImplementation(true);
42✔
1902
}
42✔
1903

1904
static bool shouldRepeat(SRepeatStack& dup) {
360,476✔
1905
        if (nullptr == dup.RepeatCondition) {
360,476✔
1906
                return 0 <= --dup.RepeatCount;
60,279✔
1907
        } else {
1908
                if (!dup.RepeatCount--) {
300,197✔
1909
                        sourcePosStack.push_back(dup.RepeatCondition->source);
6✔
1910
                        Error("[WHILE] infinite loop? (reaching the guardian value, default 100k)");
6✔
1911
                        sourcePosStack.pop_back();
6✔
1912
                        return false;
6✔
1913
                }
1914
                aint val = 0;
300,191✔
1915
                IsLabelNotFound = false;
300,191✔
1916
                char* expressionSource = dup.RepeatCondition->string;
300,191✔
1917
                if (!ParseExpressionNoSyntaxError(expressionSource, val) || *expressionSource) {
300,191✔
1918
                        const TextFilePos oSourcePos = sourcePosStack.back();
3✔
1919
                        sourcePosStack.back() = dup.RepeatCondition->source;
3✔
1920
                        Error("[WHILE] Syntax error in <expression>", dup.RepeatCondition->string, SUPPRESS);
3✔
1921
                        sourcePosStack.back() = oSourcePos;
3✔
1922
                        return false;
3✔
1923
                }
1924
                if (IsLabelNotFound) {
300,188✔
1925
                        WarningById(W_FWD_REF, dup.RepeatCondition->string, W_EARLY);
1✔
1926
                        return false;
1✔
1927
                }
1928
                return val;
300,187✔
1929
        }
1930
}
1931

1932
static void dirEDUP() {
4,490✔
1933
        if (RepeatStack.empty() || RepeatStack.top().IsInWork) {
4,490✔
1934
                Error("[EDUP/ENDR/ENDW] End repeat without repeat");
9✔
1935
                return;
273✔
1936
        }
1937

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

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

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

1997
static bool dirDEFARRAY_parseItems(CStringsList** nextPtr) {
204✔
1998
        char ml[LINEMAX];
1999
        do {
2000
                const char* const itemLp = lp;
1,197✔
2001
                char* n = ml;
1,197✔
2002
                if (!GetMacroArgumentValue(lp, n)) {
1,197✔
2003
                        Error("[DEFARRAY] Syntax error", itemLp, SUPPRESS);
18✔
2004
                        return false;
18✔
2005
                }
2006
                *nextPtr = new CStringsList(ml);
1,179✔
2007
                if ((*nextPtr)->string == NULL) ErrorOOM();
1,179✔
2008
                nextPtr = &((*nextPtr)->next);
1,179✔
2009
        } while (anyComma(lp));
1,179✔
2010
        return SkipBlanks();
186✔
2011
}
2012

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

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

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

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

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

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

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

2106
static void dirDEVICE() {
869✔
2107
        // refresh source position of first DEVICE directive
2108
        if (1 == ++deviceDirectivesCount) {
869✔
2109
                assert(!sourcePosStack.empty());
467✔
2110
                globalDeviceSourcePos = sourcePosStack.back();
467✔
2111
        }
2112

2113
        char* id = GetID(lp);
869✔
2114
        if (id) {
869✔
2115
                aint ramtop = 0;
866✔
2116
                if (anyComma(lp)) {
866✔
2117
                        if (!ParseExpressionNoSyntaxError(lp, ramtop)) {
135✔
2118
                                Error("[DEVICE] Syntax error", bp); return;
30✔
2119
                        }
2120
                        if (ramtop < 0x5D00 || 0xFFFF < ramtop) {
129✔
2121
                                  ErrorInt("[DEVICE] valid range for RAMTOP is $5D00..$FFFF", ramtop); return;
24✔
2122
                        }
2123
                }
2124
                // if (1 == deviceDirectivesCount && Device) -> device was already set globally, skip SetDevice
2125
                if (1 < deviceDirectivesCount || !Devices) {
836✔
2126
                        if (!SetDevice(id, ramtop)) {
623✔
2127
                                Error("[DEVICE] Invalid parameter", id, IF_FIRST);
10✔
2128
                        }
2129
                }
2130
        } else {
2131
                Error("[DEVICE] Syntax error in <deviceid>", lp, SUPPRESS);
3✔
2132
        }
2133
}
2134

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

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

2249
        DirectivesTable.insertd(".device", dirDEVICE);
505✔
2250
        DirectivesTable.insertd(".defdevice", dirDEFDEVICE);
505✔
2251

2252
        DirectivesTable.insertd(".bplist", dirBPLIST);
505✔
2253
        DirectivesTable.insertd(".setbreakpoint", dirSETBREAKPOINT);
505✔
2254
        DirectivesTable.insertd(".setbp", dirSETBREAKPOINT);
505✔
2255

2256
        DirectivesTable.insertd(".relocate_start", Relocation::dirRELOCATE_START);
505✔
2257
        DirectivesTable.insertd(".relocate_end", Relocation::dirRELOCATE_END);
505✔
2258
        DirectivesTable.insertd(".relocate_table", Relocation::dirRELOCATE_TABLE);
505✔
2259

2260
        DirectivesTable.insertd(".sldopt", dirSLDOPT);
505✔
2261

2262
#ifdef USE_LUA
2263
        DirectivesTable.insertd(".lua", dirLUA);
505✔
2264
        DirectivesTable.insertd(".endlua", dirENDLUA);
505✔
2265
        DirectivesTable.insertd(".includelua", dirINCLUDELUA);
505✔
2266
#endif //USE_LUA
2267

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

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