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

z00m128 / sjasmplus / 1540

29 Jan 2026 02:54PM UTC coverage: 96.511% (-0.02%) from 96.533%
1540

push

cirrus-ci

ped7g
Adjusting PoC code from ZXPrinter

newline in fuse export added also after last command - to be tested (and
reported as bug, if this doesn't work, but I strongly prefer to end
exported file with newline, unless it is proven to break something)

docs added

dirSETBREAKPOINT implementation reworked to not access freed memory of
temporary string during write and also to be more precise in syntax
errors reporting

tests updated for final state of the new options

14 of 14 new or added lines in 2 files covered. (100.0%)

5 existing lines in 2 files now uncovered.

9654 of 10003 relevant lines covered (96.51%)

171417.39 hits per line

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

98.48
/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)
244,727✔
37
{
38
        char* olp = lp;
244,727✔
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"))) {
244,727✔
42
                lp = olp;
18✔
43
                return 0;
18✔
44
        }
45

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

54
        if (DirectivesTable.zoek(n)) return 1;
244,553✔
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]);
121,113✔
58
        const bool isExprDot = ('.' == *n) && (0 == n[1]) && ('(' == *lp);
121,113✔
59
        if ((beginningOfLine && !Options::syx.IsPseudoOpBOF) || (!isDigitDot && !isExprDot)) {
121,113✔
60
                lp = olp;                // alone "." must be followed by digit, or math expression in parentheses
121,017✔
61
                return 0;                // otherwise just return
121,017✔
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,406✔
103
        char* olp = bp = lp, * n;
24,406✔
104
        if ((n = getinstr(lp)) && DirectivesTable_dup.zoek(n)) return 1;
24,406✔
105
        lp = olp;
19,658✔
106
        return 0;
19,658✔
107
}
108

109

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

121
static void dirBYTE() {
70,392✔
122
        getBytesWithCheck();
70,392✔
123
}
70,392✔
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 dirDP() {
33✔
134
        // calculate size of the string by subtracting current address + 1 from value of helper temporary label
135
        const auto endLabel = TemporaryLabelTable.seekForward(DP_HELPER_LABEL);
33✔
136
        const aint dpSize = (nullptr != endLabel) ? (endLabel->value - (CurAddress + 1)) : 0;
33✔
137
        if ((LASTPASS == pass) && (nullptr == endLabel)) Error("dirDP(): please, contact the author of this program.", nullptr, FATAL);
33✔
138
        EmitByte(dpSize);                                                                                        // emit the size byte
33✔
139
        getBytesWithCheck();                                                                                // use regular DB-like parsing for string body
33✔
140
        TemporaryLabelTable.InsertRefresh(DP_HELPER_LABEL);                        // insert the forward helper temporary label after it
33✔
141
}
33✔
142

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

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

163
static void dirABYTEZ() {
15✔
164
        aint add;
165
        if (ParseExpressionNoSyntaxError(lp, add)) {
15✔
166
                Relocation::checkAndWarn();
12✔
167
                getBytesWithCheck(add, 0, true);
12✔
168
        } else {
169
                Error("ABYTEZ <offset> <bytes>: parsing <offset> failed", bp, SUPPRESS);
3✔
170
        }
171
}
15✔
172

173
static void dirWORD() {
4,627✔
174
        aint val;
175
        int teller = 0, e[130];
4,627✔
176
        do {
177
                // reset alternate result flag in ParseExpression part of code
178
                Relocation::isResultAffected = false;
8,395✔
179
                if (SkipBlanks()) {
8,395✔
180
                        Error("Expression expected", NULL, SUPPRESS);
6✔
181
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
8,389✔
182
                        check16(val);
8,338✔
183
                        e[teller] = val & 65535;
8,338✔
184
                        Relocation::resolveRelocationAffected(teller * 2);
8,338✔
185
                        ++teller;
8,338✔
186
                } else {
187
                        Error("[DW/DEFW/WORD] Syntax error", lp, SUPPRESS);
51✔
188
                        break;
51✔
189
                }
190
        } while (comma(lp) && teller < 128);
8,344✔
191
        e[teller] = -1;
4,627✔
192
        if (teller == 128 && *lp) Error("Over 128 values in DW/DEFW/WORD. Values over", lp, SUPPRESS);
4,627✔
193
        if (teller) EmitWords(e);
4,627✔
194
        else                Error("DW/DEFW/WORD with no arguments");
57✔
195
}
4,627✔
196

197
static void dirDWORD() {
102✔
198
        aint val;
199
        int teller = 0, e[130 * 2];
102✔
200
        do {
201
                if (SkipBlanks()) {
879✔
202
                        Error("Expression expected", NULL, SUPPRESS);
3✔
203
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
876✔
204
                        e[teller * 2] = val & 65535; e[teller * 2 + 1] = (val >> 16) & 0xFFFF; ++teller;
828✔
205
                } else {
206
                        Error("[DWORD] Syntax error", lp, SUPPRESS);
48✔
207
                        break;
48✔
208
                }
209
        } while (comma(lp) && teller < 128);
831✔
210
        e[teller * 2] = -1;
102✔
211
        if (teller == 128 && *lp) Error("Over 128 values in DWORD. Values over", lp, SUPPRESS);
102✔
212
        if (teller) EmitWords(e);
102✔
213
        else                Error("DWORD with no arguments");
51✔
214
}
102✔
215

216
static void dirD24() {
30✔
217
        aint val;
218
        int teller = 0, e[130 * 3];
30✔
219
        do {
220
                if (SkipBlanks()) {
804✔
221
                        Error("Expression expected", NULL, SUPPRESS);
3✔
222
                } else if (ParseExpressionNoSyntaxError(lp, val)) {
801✔
223
                        check24(val);
798✔
224
                        e[teller++] = val & 255; e[teller++] = (val >> 8) & 255; e[teller++] = (val >> 16) & 255;
798✔
225
                } else {
226
                        Error("[D24] Syntax error", lp, SUPPRESS);
3✔
227
                        break;
3✔
228
                }
229
        } while (comma(lp) && teller < 128*3);
801✔
230
        e[teller] = -1;
30✔
231
        if (teller == 128*3 && *lp) Error("Over 128 values in D24. Values over", lp, SUPPRESS);
30✔
232
        if (teller) EmitBytes(e);
30✔
233
        else                Error("D24 with no arguments");
6✔
234
}
30✔
235

236
static void dirDG() {
132✔
237
        int dirDx[130];
238
        if (GetBits(lp, dirDx)) {
132✔
239
                EmitBytes(dirDx);
123✔
240
        } else {
241
                Error("no arguments");
9✔
242
        }
243
}
132✔
244

245
static void dirDH() {
77✔
246
        int dirDx[130];
247
        if (GetBytesHexaText(lp, dirDx)) {
77✔
248
                EmitBytes(dirDx);
56✔
249
        } else {
250
                Error("no arguments");
21✔
251
        }
252
}
77✔
253

254
static void dirBLOCK() {
943✔
255
        int dirDx[130];
256
        aint teller = 0, val = 0, initByteCount = 0, emitMaxToListing = 4;
943✔
257
        if (ParseExpressionNoSyntaxError(lp, teller)) {
943✔
258
                if (teller < 0) {
940✔
259
                        Warning("Negative BLOCK?");
24✔
260
                }
261
                if (comma(lp)) {
940✔
262
                        // Parse operand list using GetBytes (like DEFB does)
263
                        initByteCount = GetBytes(lp, dirDx, 0, 0);
799✔
264
                        if (teller < initByteCount) {                // Operands exceed DEFS size - truncate and error
799✔
265
                                WarningById(W_SHORT_BLOCK, teller);
48✔
266
                                initByteCount = teller;
48✔
267
                                if (initByteCount < 0) initByteCount = 0;
48✔
268
                                dirDx[initByteCount] = -1;                // truncate init data
48✔
269
                        }
270
                        if (0 < initByteCount) {                        // emit the explicit init bytes first (if any are available)
799✔
271
                                EmitBytes(dirDx);
763✔
272
                                val = dirDx[initByteCount - 1];        // last explicit byte is new filler value
763✔
273
                                emitMaxToListing -= (initByteCount & 3);
763✔
274
                        }
275
                }
276
                // emit remaining bytes set with filler
277
                EmitBlock(val, teller - initByteCount, false, emitMaxToListing);
940✔
278
        } else {
279
                Error("[BLOCK] Syntax Error in <length>", lp, SUPPRESS);
3✔
280
        }
281
}
943✔
282

283
bool dirPageImpl(const char* const dirName, int pageNumber) {
1,227✔
284
        if (!Device) return false;
1,227✔
285
        if (pageNumber < 0 || Device->PagesCount <= pageNumber) {
1,227✔
286
                char buf[LINEMAX];
287
                SPRINTF2(buf, LINEMAX, "[%s] Page number must be in range 0..%d", dirName, Device->PagesCount - 1);
54✔
288
                ErrorInt(buf, pageNumber);
54✔
289
                return false;
54✔
290
        }
291
        Device->GetCurrentSlot()->Page = Device->GetPage(pageNumber);
1,173✔
292
        Device->CheckPage(CDevice::CHECK_RESET);
1,173✔
293
        return true;
1,173✔
294
}
295

296
static void dirPageImpl(const char* const dirName) {
1,229✔
297
        aint pageNum;
298
        if (ParseExpressionNoSyntaxError(lp, pageNum)) {
1,229✔
299
                dirPageImpl(dirName, pageNum);
1,223✔
300
        } else {
301
                Error("Syntax error in <page_number>", lp, SUPPRESS);
6✔
302
        }
303
}
1,229✔
304

305
static void dirORG() {
2,622✔
306
        aint val;
307
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,622✔
308
                Error("[ORG] Syntax error in <address>", lp, SUPPRESS);
3✔
309
                return;
2,472✔
310
        }
311
        // crop (with warning) address in device or non-longptr mode to 16bit address range
312
        if ((DeviceID || !Options::IsLongPtr) && !check16u(val)) val &= 0xFFFF;
2,619✔
313
        // warn about jump in address in raw/output mode
314
        if (DidWriteOutputByte() && (CurAddress != val)) {
2,619✔
315
                WarningById(W_FILE_ORG, val - CurAddress);
22✔
316
        }
317
        CurAddress = val;
2,619✔
318
        if (DISP_NONE != PseudoORG) WarningById(W_DISPLACED_ORG);
2,619✔
319
        if (!DeviceID) return;
2,619✔
320
        if (!comma(lp)) {
2,006✔
321
                Device->CheckPage(CDevice::CHECK_RESET);
1,856✔
322
                return;
1,856✔
323
        }
324
        // emit warning when current slot does not cover address used for ORG
325
        auto slot = Device->GetCurrentSlot();
150✔
326
        if ((CurAddress < slot->Address || slot->Address + slot->Size <= CurAddress)) {
150✔
327
                char warnTxt[LINEMAX];
328
                SPRINTF4(warnTxt, LINEMAX,
57✔
329
                                        "address 0x%04X vs slot %d range 0x%04X..0x%04X",
330
                                        CurAddress, Device->GetCurrentSlotNum(), slot->Address, slot->Address + slot->Size - 1);
331
                WarningById(W_ORG_PAGE, warnTxt);
57✔
332
        }
333
        dirPageImpl("ORG");
150✔
334
}
335

336
// public API for lua setter
337
void dirOrgOnlyAddr(aint address) {
13✔
338
        // crop (with warning) address in device or non-longptr mode to 16bit address range
339
        if ((DeviceID || !Options::IsLongPtr) && !check16u(address)) address &= 0xFFFF;
13✔
340
        CurAddress = address;
13✔
341
        if (DISP_NONE != PseudoORG) WarningById(W_DISPLACED_ORG);
13✔
342
        if (DeviceID) Device->CheckPage(CDevice::CHECK_RESET);
13✔
343
}
13✔
344

345
static void dirDISP() {
204✔
346
        if (DISP_NONE != PseudoORG) {
204✔
347
                Warning("[DISP] displacement inside another displacement block, ignoring it.");
6✔
348
                SkipToEol(lp);
6✔
349
                return;
33✔
350
        }
351
        aint valAdr, valPageNum;
352
        // parse+validate values first, don't even switch into DISP mode in case of any error
353
        Relocation::isResultAffected = false;
198✔
354
        if (!ParseExpressionNoSyntaxError(lp, valAdr)) {
198✔
355
                Error("[DISP] Syntax error in <address>", lp, SUPPRESS);
3✔
356
                return;
3✔
357
        }
358
        // the expression of the DISP shouldn't be affected by relocation (even when starting inside relocation block)
359
        if (Relocation::checkAndWarn(true)) {
195✔
360
                SkipToEol(lp);
3✔
361
                return;                // report it as error and exit early
3✔
362
        }
363
        if (comma(lp)) {
192✔
364
                if (!ParseExpressionNoSyntaxError(lp, valPageNum)) {
39✔
365
                        Error("[DISP] Syntax error in <page number>", lp);
9✔
366
                        return;
9✔
367
                }
368
                if (!DeviceID) {
30✔
369
                        Error("[DISP] <page number> is accepted only in device mode", line);
3✔
370
                        return;
3✔
371
                }
372
                if (valPageNum < 0 || Device->PagesCount <= valPageNum) {
27✔
373
                        ErrorInt("[DISP] <page number> is out of range", valPageNum);
9✔
374
                        return;
9✔
375
                }
376
                dispPageNum = valPageNum;
18✔
377
        } else {
378
                dispPageNum = LABEL_PAGE_UNDEFINED;
153✔
379
        }
380
        // crop (with warning) address in device or non-longptr mode to 16bit address range
381
        if ((DeviceID || !Options::IsLongPtr) && !check16u(valAdr)) valAdr &= 0xFFFF;
171✔
382
        // everything is valid, switch to DISP mode (dispPageNum is already set above)
383
        adrdisp = CurAddress;
171✔
384
        CurAddress = valAdr;
171✔
385
        PseudoORG = Relocation::type ? DISP_INSIDE_RELOCATE : DISP_ACTIVE;
171✔
386
}
387

388
static void dirENT() {
180✔
389
        if (DISP_NONE == PseudoORG) {
180✔
390
                Error("ENT should be after DISP");
6✔
391
                return;
6✔
392
        }
393
        // check if the DISP..ENT block is either fully inside relocation block, or engulfing it fully.
394
        if (DISP_ACTIVE == PseudoORG && Relocation::type) {
174✔
395
                Error("The DISP block did start outside of relocation block, can't end inside it");
6✔
396
                return;
6✔
397
        }
398
        if (DISP_INSIDE_RELOCATE == PseudoORG && !Relocation::type) {
168✔
399
                Error("The DISP block did start inside of relocation block, can't end outside of it");
×
400
                return;
×
401
        }
402
        CurAddress = adrdisp;
168✔
403
        PseudoORG = DISP_NONE;
168✔
404
        dispPageNum = LABEL_PAGE_UNDEFINED;
168✔
405
}
406

407
static void dirPAGE() {
1,083✔
408
        if (!DeviceID) {
1,083✔
409
                Warning("PAGE only allowed in real device emulation mode (See DEVICE)");
4✔
410
                SkipParam(lp);
4✔
411
        } else {
412
                dirPageImpl("PAGE");
1,079✔
413
        }
414
}
1,083✔
415

416
static void dirMMU() {
456✔
417
        if (!DeviceID) {
456✔
418
                Warning("MMU is allowed only in real device emulation mode (See DEVICE)");
6✔
419
                SkipToEol(lp);
6✔
420
                return;
69✔
421
        }
422
        aint slot1, slot2, pageN = -1, address = -1;
450✔
423
        CDeviceSlot::ESlotOptions slotOpt = CDeviceSlot::O_NONE;
450✔
424
        if (!ParseExpression(lp, slot1)) {
450✔
425
                Error("[MMU] First slot number parsing failed", bp, SUPPRESS);
3✔
426
                return;
3✔
427
        }
428
        slot2 = slot1;
447✔
429
        if (!comma(lp)) {        // second slot or slot-option should follow (if not comma)
447✔
430
                // see if there is slot1-only with option-char (e/w/n options)
431
                const char slotOptChar = (*lp)|0x20;        // primitive ASCII tolower
177✔
432
                if ('a' <= slotOptChar && slotOptChar <= 'z' && (',' == lp[1] || White(lp[1]))) {
177✔
433
                        if ('e' == slotOptChar) slotOpt = CDeviceSlot::O_ERROR;
84✔
434
                        else if ('w' == slotOptChar) slotOpt = CDeviceSlot::O_WARNING;
60✔
435
                        else if ('n' == slotOptChar) slotOpt = CDeviceSlot::O_NEXT;
45✔
436
                        else {
437
                                Warning("[MMU] Unknown slot option (legal: e, w, n)", lp);
6✔
438
                        }
439
                        ++lp;
84✔
440
                } else {        // there was no option char, check if there was slot2 number to define range
441
                        if (!ParseExpression(lp, slot2)) {
93✔
442
                                Error("[MMU] Second slot number parsing failed", bp, SUPPRESS);
6✔
443
                                return;
6✔
444
                        }
445
                }
446
                if (!comma(lp)) {
171✔
447
                        Error("[MMU] Comma and page number expected after slot info", bp, SUPPRESS);
6✔
448
                        return;
6✔
449
                }
450
        }
451
        if (!ParseExpression(lp, pageN)) {
435✔
452
                Error("[MMU] Page number parsing failed", bp, SUPPRESS);
15✔
453
                return;
15✔
454
        }
455
        if (comma(lp)) {
420✔
456
                if (!ParseExpressionNoSyntaxError(lp, address)) {
237✔
457
                        Error("[MMU] address parsing failed", bp, SUPPRESS);
6✔
458
                        return;
6✔
459
                }
460
                check16(address);
231✔
461
                address &= 0xFFFF;
231✔
462
        }
463
        // convert slot entered as addresses into slot numbering (must be precise start address of slot)
464
        slot1 = Device->SlotNumberFromPreciseAddress(slot1);
414✔
465
        slot2 = Device->SlotNumberFromPreciseAddress(slot2);
414✔
466
        // validate argument values
467
        if (slot1 < 0 || slot2 < slot1 || Device->SlotsCount <= slot2) {
414✔
468
                char buf[LINEMAX];
469
                SPRINTF1(buf, LINEMAX, "[MMU] Slot number(s) must be in range 0..%u (or exact starting address of slot) and form a range",
15✔
470
                                 Device->SlotsCount - 1);
471
                Error(buf, NULL, SUPPRESS);
15✔
472
                return;
15✔
473
        }
474
        if (pageN < 0 || Device->PagesCount <= pageN + (slot2 - slot1)) {
399✔
475
                char buf[LINEMAX];
476
                SPRINTF1(buf, LINEMAX, "[MMU] Requested page(s) must be in range 0..%u", Device->PagesCount - 1);
12✔
477
                Error(buf, NULL, SUPPRESS);
12✔
478
                return;
12✔
479
        }
480
        // all valid, set it up
481
        for (aint slotN = slot1; slotN <= slot2; ++slotN, ++pageN) {
1,017✔
482
                Device->GetSlot(slotN)->Page = Device->GetPage(pageN);
630✔
483
                // this ^ is also enough to keep global "Slot" up to date (it's a pointer)
484
                Device->GetSlot(slotN)->Option = slotOpt;        // resets whole range to NONE when range
630✔
485
        }
486
        // wrap output addresses back into 64ki address space, it's essential for MMU functionality
487
        if (DISP_NONE != PseudoORG) adrdisp &= 0xFFFF; else CurAddress &= 0xFFFF;
387✔
488
        // set explicit ORG address if the third argument was provided
489
        if (0 <= address) {
387✔
490
                CurAddress = address;
231✔
491
                if (DISP_NONE != PseudoORG) {
231✔
492
                        WarningById(W_DISPLACED_ORG);
6✔
493
                }
494
                // check if explicit ORG address is outside of the slots affected by MMU, warn about it
495
                const CDeviceSlot & check_s1 = *Device->GetSlot(slot1);
231✔
496
                const CDeviceSlot & check_s2 = *Device->GetSlot(slot2);
231✔
497
                if ((address < check_s1.Address) || (check_s2.Address + check_s2.Size <= address)) {
231✔
498
                        char buf[LINEMAX];
499
                        SPRINTF3(buf, LINEMAX, "[MMU] Requested ORG address 0x%04X is out of range 0x%04X..0x%04X",
30✔
500
                                         address, check_s1.Address, check_s2.Address + check_s2.Size - 1);
501
                        Warning(buf);
30✔
502
                }
503
        }
504
        Device->CheckPage(CDevice::CHECK_RESET);
387✔
505
}
506

507
static void dirSLOT() {
336✔
508
        aint val;
509
        if (!DeviceID) {
336✔
510
                Warning("SLOT only allowed in real device emulation mode (See DEVICE)");
6✔
511
                SkipParam(lp);
6✔
512
                return;
9✔
513
        }
514
        if (!ParseExpressionNoSyntaxError(lp, val)) {
330✔
515
                Error("[SLOT] Syntax error in <slot_number>", lp, SUPPRESS);
3✔
516
                return;
3✔
517
        }
518
        val = Device->SlotNumberFromPreciseAddress(val);
327✔
519
        if (!Device->SetSlot(val)) {
327✔
520
                char buf[LINEMAX];
521
                SPRINTF1(buf, LINEMAX, "[SLOT] Slot number must be in range 0..%u, or exact starting address of slot", Device->SlotsCount - 1);
42✔
522
                Error(buf, NULL, IF_FIRST);
42✔
523
        }
524
}
525

526
static void dirALIGN() {
432✔
527
        // default alignment is 4, default filler is "0/none" (if not specified in directive explicitly)
528
        aint val, fill;
529
        ParseAlignArguments(lp, val, fill);
432✔
530
        if (-1 == val) val = 4;
432✔
531
        // calculate how many bytes has to be filled to reach desired alignment
532
        aint len = (~CurAddress + 1) & (val - 1);
432✔
533
        if (len < 1) return;                // nothing to fill, already aligned
432✔
534
        if (-1 == fill) EmitBlock(0, len, true);
319✔
535
        else                        EmitBlock(fill, len, false);
84✔
536
}
537

538
static void dirMODULE() {
225✔
539
        char* n = GetID(lp);
225✔
540
        if (n && (nullptr == STRCHR(n, '.'))) {
225✔
541
                if (*ModuleName) STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), ".");
216✔
542
                STRCAT(ModuleName, LINEMAX-1-strlen(ModuleName), n);
216✔
543
                InitVorlab();        // reset non-local label to default "<modules>._"
216✔
544
                if (IsSldExportActive()) {
221✔
545
                        WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld());
5✔
546
                }
547
        } else {
548
                if (n) {
9✔
549
                        Error("[MODULE] Dots not allowed in <module_name>", n, SUPPRESS);
6✔
550
                } else {
551
                        Error("[MODULE] Syntax error in <name>", bp, SUPPRESS);
3✔
552
                }
553
        }
554
}
225✔
555

556
static void dirENDMODULE() {
219✔
557
        if (! *ModuleName) {
219✔
558
                Error("ENDMODULE without MODULE");
9✔
559
                return;
9✔
560
        }
561
        if (IsSldExportActive()) {
210✔
562
                WriteToSldFile(-1, CurAddress, 'L', ExportModuleToSld(true));
5✔
563
        }
564
        // remove last part of composite modules name
565
        char* lastDot = strrchr(ModuleName, '.');
210✔
566
        if (lastDot)        *lastDot = 0;
210✔
567
        else                        *ModuleName = 0;
168✔
568
        InitVorlab();        // reset non-local label to default "<modules>._"
210✔
569
}
570

571
static void dirEND() {
48✔
572
        char* p = lp;
48✔
573
        aint val;
574
        if (ParseExpression(lp, val)) {
48✔
575
                if (val > 65535 || val < 0) ErrorInt("[END] Invalid address", IF_FIRST);
9✔
576
                else                                                 StartAddress = val;
9✔
577
        } else {
578
                lp = p;
39✔
579
        }
580

581
        IsRunning = 0;
48✔
582
}
48✔
583

584
static void dirSIZE() {
24✔
585
        aint val;
586
        if (!ParseExpressionNoSyntaxError(lp, val)) {
24✔
587
                Error("[SIZE] Syntax error in <filesize>", bp, SUPPRESS);
3✔
588
                return;
17✔
589
        }
590
        if (LASTPASS != pass) return;        // only active during final pass
21✔
591
        if (-1L == size) size = val;        // first time set
7✔
592
        else if (size != val) ErrorInt("[SIZE] Different size than previous", size);        // just check it's same
2✔
593
}
594

595
static void dirINCBIN() {
123✔
596
        int offset = 0, length = INT_MAX;
123✔
597
        fullpath_ref_t fnaam = GetInputFile(lp);
123✔
598
        if (anyComma(lp)) {
123✔
599
                aint val;
600
                if (!anyComma(lp)) {
87✔
601
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
75✔
602
                                Error("[INCBIN] Syntax error in <offset>", bp, SUPPRESS);
6✔
603
                                return;
15✔
604
                        }
605
                        offset = val;
69✔
606
                } else --lp;                // there was second comma right after, reread it
12✔
607
                if (anyComma(lp)) {
81✔
608
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
66✔
609
                                Error("[INCBIN] Syntax error in <length>", bp, SUPPRESS);
9✔
610
                                return;
9✔
611
                        }
612
                        length = val;
57✔
613
                }
614
        }
615
        BinIncFile(fnaam, offset, length);
108✔
616
}
617

618
static void dirINCHOB() {
30✔
619
        unsigned char len[2];
620
        int offset = 0,length = -1;
30✔
621
        fullpath_ref_t fnaam = GetInputFile(lp);
30✔
622
        if (anyComma(lp)) {
30✔
623
                aint val;
624
                if (!anyComma(lp)) {
24✔
625
                        if (!ParseExpression(lp, val)) {
18✔
626
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
627
                        }
628
                        if (val < 0) {
18✔
629
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
630
                        }
631
                        offset += val;
18✔
632
                } else --lp;                // there was second comma right after, reread it
6✔
633
                if (anyComma(lp)) {
24✔
634
                        if (!ParseExpression(lp, val)) {
18✔
635
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
636
                        }
637
                        if (val < 0) {
18✔
638
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
639
                        }
640
                        length = val;
18✔
641
                }
642
        }
643

644
        FILE* ff;
645
        if (!FOPEN_ISOK(ff, fnaam.full, "rb")) {
30✔
646
                Error("[INCHOB] Error opening file", fnaam.str.c_str(), FATAL);
×
647
        }
648
        if (fseek(ff, 0x0b, 0) || 2 != fread(len, 1, 2, ff)) {
30✔
649
                Error("[INCHOB] Hobeta file has wrong format", fnaam.str.c_str(), FATAL);
×
650
        }
651
        fclose(ff);
30✔
652
        if (length == -1) {
30✔
653
                // calculate remaining length of the file (from the specified offset)
654
                length = len[0] + (len[1] << 8) - offset;
12✔
655
        }
656
        offset += 17;                // adjust offset (skip HOB header)
30✔
657
        BinIncFile(fnaam, offset, length);
30✔
658
}
659

660
static void dirINCTRD() {
78✔
661
        aint val, offset = 0, length = INT_MAX;
78✔
662
        fullpath_ref_t trdfile = GetInputFile(lp);
78✔
663
        std::string filename {""};
78✔
664
        if (anyComma(lp) && !anyComma(lp)) filename = GetDelimitedString(lp);
78✔
665
        if (filename.empty()) {
78✔
666
                // file-in-disk syntax error
667
                Error("[INCTRD] Syntax error", bp, IF_FIRST);
9✔
668
                SkipToEol(lp);
9✔
669
                return;
9✔
670
        }
671
        if (anyComma(lp)) {
69✔
672
                if (!anyComma(lp)) {
51✔
673
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
48✔
674
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
675
                                SkipToEol(lp);
6✔
676
                                return;
6✔
677
                        }
678
                        if (val < 0) {
42✔
679
                                ErrorInt("[INCTRD] Negative offset value is not allowed", val);
3✔
680
                                SkipToEol(lp);
3✔
681
                                return;
3✔
682
                        }
683
                        offset = val;
39✔
684
                } else --lp;                // there was second comma right after, reread it
3✔
685
                if (anyComma(lp)) {
42✔
686
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
33✔
687
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
688
                                SkipToEol(lp);
6✔
689
                                return;
6✔
690
                        }
691
                        if (val < 0) {
27✔
692
                                ErrorInt("[INCTRD] Negative length value is not allowed", val);
3✔
693
                                SkipToEol(lp);
3✔
694
                                return;
3✔
695
                        }
696
                        length = val;
24✔
697
                }
698
        }
699
        if (TRD_PrepareIncFile(trdfile, filename.c_str(), offset, length)) {
51✔
700
                BinIncFile(trdfile, offset, length);
36✔
701
        }
702
}
78✔
703

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

707
        if (!DeviceID) {
30✔
708
                Error("SAVESNA only allowed in real device emulation mode (See DEVICE)", nullptr, SUPPRESS);
2✔
709
                return;
2✔
710
        } else if (!IsZXSpectrumDevice(DeviceID)) {
28✔
711
                Error("[SAVESNA] Device must be ZXSPECTRUM48 or ZXSPECTRUM128.", nullptr, SUPPRESS);
1✔
712
                return;
1✔
713
        }
714

715
        const std::filesystem::path fnaam = GetOutputFileName(lp);
27✔
716
        int start = StartAddress;
27✔
717
        if (anyComma(lp)) {
27✔
718
                aint val;
719
                if (!ParseExpression(lp, val)) return;
30✔
720
                if (0 <= start) Warning("[SAVESNA] Start address was also defined by END, SAVESNA argument used instead");
25✔
721
                if (0 <= val) {
25✔
722
                        start = val;
21✔
723
                } else {
724
                        Error("[SAVESNA] Negative values are not allowed", bp, SUPPRESS);
4✔
725
                        return;
4✔
726
                }
727
        }
728
        if (start < 0) {
22✔
729
                Error("[SAVESNA] No start address defined", bp, SUPPRESS);
1✔
730
                return;
1✔
731
        }
732

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

736
static void dirEMPTYTAP() {
33✔
737
        if (pass != LASTPASS) {
33✔
738
                SkipParam(lp);
22✔
739
                return;
23✔
740
        }
741
        const std::filesystem::path fnaam = GetOutputFileName(lp);
11✔
742
        if (!fnaam.has_filename()) {
11✔
743
                Error("[EMPTYTAP] Syntax error", bp, IF_FIRST); return;
1✔
744
        }
745
        TAP_SaveEmpty(fnaam);
10✔
746
}
11✔
747

748
static void dirSAVETAP() {
192✔
749
        if (pass != LASTPASS) {
192✔
750
                SkipParam(lp);
128✔
751
                return;
158✔
752
        }
753

754
        bool exec = true, realtapeMode = false;
64✔
755
        int headerType = -1;
64✔
756
        aint val;
757
        int start = -1, length = -1, param2 = -1, param3 = -1;
64✔
758

759
        if (!DeviceID) {
64✔
760
                Error("SAVETAP only allowed in real device emulation mode (See DEVICE)");
1✔
761
                exec = false;
1✔
762
        }
763

764
        const std::filesystem::path fnaam = GetOutputFileName(lp);
64✔
765
        std::string fnaamh {""};
64✔
766
        if (anyComma(lp)) {
64✔
767
                if (!anyComma(lp)) {
62✔
768
                        char *tlp = lp;
61✔
769
                        char *id;
770

771
                        if ((id = GetID(lp)) && strlen(id) > 0) {
61✔
772
                                if (cmphstr(id, "basic")) {
59✔
773
                                        headerType = BASIC;
9✔
774
                                        realtapeMode = true;
9✔
775
                                } else if (cmphstr(id, "numbers")) {
50✔
776
                                        headerType = NUMBERS;
6✔
777
                                        realtapeMode = true;
6✔
778
                                } else if (cmphstr(id, "chars")) {
44✔
779
                                        headerType = CHARS;
2✔
780
                                        realtapeMode = true;
2✔
781
                                } else if (cmphstr(id, "code")) {
42✔
782
                                        headerType = CODE;
25✔
783
                                        realtapeMode = true;
25✔
784
                                } else if (cmphstr(id, "headless")) {
17✔
785
                                        headerType = HEADLESS;
16✔
786
                                        realtapeMode = true;
16✔
787
                                }
788
                        }
789

790
                        if (realtapeMode) {
61✔
791
                                if (anyComma(lp)) {
58✔
792
                                        if (headerType == HEADLESS) {
56✔
793
                                                if (!anyComma(lp)) {
14✔
794
                                                        if (!ParseExpression(lp, val)) {
13✔
795
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
27✔
796
                                                        }
797
                                                        if (val < 0) {
12✔
798
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
799
                                                        } else if (val > 0xFFFF) {
11✔
800
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
801
                                                        }
802
                                                        start = val;
10✔
803
                                                } else {
804
                                                        Error("[SAVETAP] Syntax error. Missing start address", bp, PASS3); return;
1✔
805
                                                }
806
                                                if (anyComma(lp)) {
10✔
807
                                                        if (!ParseExpression(lp, val)) {
10✔
808
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
1✔
809
                                                        }
810
                                                        if (val < 0) {
9✔
811
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
812
                                                        } else if (val > 0xFFFF) {
8✔
813
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
814
                                                        }
815
                                                        length = val;
7✔
816
                                                }
817
                                                if (anyComma(lp)) {
7✔
818
                                                        if (!ParseExpression(lp, val)) {
5✔
819
                                                                Error("[SAVETAP] Syntax error", bp, PASS3); return;
1✔
820
                                                        }
821
                                                        if (val < 0 || val > 255) {
4✔
822
                                                                Error("[SAVETAP] Invalid flag byte", bp, PASS3); return;
2✔
823
                                                        }
824
                                                        param3 = val;
2✔
825
                                                }
826
                                        } else if (!anyComma(lp)) {
42✔
827
                                                fnaamh = GetDelimitedString(lp);
41✔
828
                                                if (fnaamh.empty()) {
41✔
829
                                                        Error("[SAVETAP] Syntax error in tape file name", bp, PASS3);
1✔
830
                                                        return;
1✔
831
                                                } else if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
40✔
832
                                                        if (val < 0) {
39✔
833
                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
834
                                                        } else if (val > 0xFFFF) {
38✔
835
                                                                Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
836
                                                        }
837
                                                        start = val;
37✔
838

839
                                                        if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
37✔
840
                                                                if (val < 0) {
36✔
841
                                                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
842
                                                                } else if (val > 0xFFFF) {
35✔
843
                                                                        Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
844
                                                                }
845
                                                                length = val;
34✔
846

847
                                                                if (anyComma(lp)) {
34✔
848
                                                                        if (!ParseExpression(lp, val)) {
24✔
849
                                                                                Error("[SAVETAP] Syntax error", bp, IF_FIRST); return;
1✔
850
                                                                        }
851
                                                                        if (val < 0) {
23✔
852
                                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
853
                                                                        } else if (val > 0xFFFF) {
22✔
854
                                                                                Error("[SAVETAP] Values more than FFFFh are not allowed", bp, PASS3); return;
1✔
855
                                                                        }
856
                                                                        param2 = val;
21✔
857
                                                                }
858
                                                                if (anyComma(lp)) {
31✔
859
                                                                        if (!ParseExpression(lp, val)) {
5✔
860
                                                                                Error("[SAVETAP] Syntax error", bp, IF_FIRST); return;
1✔
861
                                                                        }
862
                                                                        if (val < 0) {
4✔
863
                                                                                Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
864
                                                                        } else if (val > 0xFFFF) {
3✔
865
                                                                                Error("[SAVETAP] Values more than FFFFh are not allowed", bp, PASS3); return;
1✔
866
                                                                        }
867
                                                                        param3 = val;
2✔
868
                                                                }
869
                                                        } else {
870
                                                                Error("[SAVETAP] Syntax error. Missing block length", bp, PASS3); return;
1✔
871
                                                        }
872
                                                } else {
873
                                                        Error("[SAVETAP] Syntax error. Missing start address", bp, PASS3); return;
1✔
874
                                                }
875
                                        } else {
876
                                                Error("[SAVETAP] Syntax error. Missing tape block file name", bp, PASS3); return;
1✔
877
                                        }
878
                                } else {
879
                                        realtapeMode = false;
2✔
880
                                }
881
                        }
882
                        if (!realtapeMode) {
37✔
883
                                lp = tlp;
5✔
884
                                IsLabelNotFound = false;
5✔
885
                                if (!ParseExpression(lp, val) || IsLabelNotFound) {
5✔
886
                                        Error("[SAVETAP] Syntax error", bp, PASS3); return;
2✔
887
                                }
888
                                if (val < 0) {
3✔
889
                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
890
                                }
891
                                start = val;
2✔
892
                        }
893
                } else {
894
                        Error("[SAVETAP] Syntax error. No parameters", bp, PASS3); return;
1✔
895
                }
896
        } else if (StartAddress < 0) {
2✔
897
                Error("[SAVETAP] Syntax error. No parameters", bp, PASS3); return;
2✔
898
        } else {
899
                start = StartAddress;
×
900
        }
901

902
        if (exec) {
34✔
903
                int done = 0;
33✔
904

905
                if (realtapeMode) {
33✔
906
                        done = TAP_SaveBlock(fnaam, headerType, fnaamh.c_str(), start, length, param2, param3);
32✔
907
                } else {
908
                        if (!IsZXSpectrumDevice(DeviceID)) {
1✔
909
                                Error("[SAVETAP snapshot] Device is not of ZX Spectrum type.", Device->ID, SUPPRESS);
1✔
910
                        } else {
911
                                done = TAP_SaveSnapshot(fnaam, start);
×
912
                        }
913
                }
914

915
                if (!done) {
33✔
916
                        Error("[SAVETAP] Error writing file", bp, IF_FIRST);
1✔
917
                }
918
        }
919
}
94✔
920

921
static void dirSAVEBIN() {
90✔
922
        if (!DeviceID) {
90✔
923
                Error("SAVEBIN only allowed in real device emulation mode (See DEVICE)");
7✔
924
                SkipToEol(lp);
7✔
925
                return;
34✔
926
        }
927
        bool exec = (LASTPASS == pass);
83✔
928
        aint val;
929
        int start = -1, length = -1;
83✔
930
        const std::filesystem::path fnaam = GetOutputFileName(lp);
83✔
931
        if (anyComma(lp)) {
83✔
932
                if (!anyComma(lp)) {
80✔
933
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
77✔
934
                                Error("[SAVEBIN] Syntax error", bp, SUPPRESS); return;
6✔
935
                        }
936
                        if (val < 0) {
71✔
937
                                Error("[SAVEBIN] Values less than 0000h are not allowed", bp); return;
3✔
938
                        } else if (val > 0xFFFF) {
68✔
939
                                  Error("[SAVEBIN] Values more than FFFFh are not allowed", bp); return;
3✔
940
                        }
941
                        start = val;
65✔
942
                } else {
943
                          Error("[SAVEBIN] Syntax error. No parameters", bp, PASS3); return;
3✔
944
                }
945
                if (anyComma(lp)) {
65✔
946
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
62✔
947
                                Error("[SAVEBIN] Syntax error", bp, SUPPRESS); return;
6✔
948
                        }
949
                        if (val < 0) {
56✔
950
                                Error("[SAVEBIN] Negative values are not allowed", bp); return;
3✔
951
                        }
952
                        length = val;
53✔
953
                }
954
        } else {
955
                Error("[SAVEBIN] Syntax error. No parameters", bp); return;
3✔
956
        }
957

958
        if (exec && !SaveBinary(fnaam, start, length)) {
56✔
959
                Error("[SAVEBIN] Error writing file (Disk full?)", bp, IF_FIRST);
×
960
        }
961
}
83✔
962

963
static void dirSAVEDEV() {
90✔
964
        bool exec = DeviceID && LASTPASS == pass;
90✔
965
        if (!exec && LASTPASS == pass) Error("SAVEDEV only allowed in real device emulation mode (See DEVICE)");
90✔
966

967
        aint args[3]{-1, -1, -1};                // page, offset, length
90✔
968
        const std::filesystem::path fnaam = GetOutputFileName(lp);
90✔
969
        for (auto & arg : args) {
360✔
970
                if (!comma(lp) || !ParseExpression(lp, arg)) {
270✔
971
                        exec = false;
45✔
972
                        Error("Expected syntax SAVEDEV <filename>,<startPage>,<startOffset>,<length>", bp, SUPPRESS);
45✔
973
                }
974
        }
975
        if (exec) {
90✔
976
                // validate arguments
977
                if (args[0] < 0 || Device->PagesCount <= args[0]) {
22✔
978
                        exec = false; ErrorInt("[SAVEDEV] page number is out of range", args[0]);
2✔
979
                }
980
                const int32_t start = Device->GetMemoryOffset(args[0], args[1]);
22✔
981
                const int32_t totalRam = Device->GetMemoryOffset(Device->PagesCount, 0);
22✔
982
                if (exec && (start < 0 || totalRam <= start)) {
22✔
983
                        exec = false; ErrorInt("[SAVEDEV] calculated start address is out of range", start);
4✔
984
                }
985
                if (exec && (args[2] <= 0 || totalRam < start + args[2])) {
22✔
986
                        exec = false;
6✔
987
                        if (args[2]) ErrorInt("[SAVEDEV] invalid end address (bad length?)", start + args[2]);
6✔
988
                        else Warning("[SAVEDEV] zero length requested");
1✔
989
                }
990
                if (exec && !SaveDeviceMemory(fnaam, (size_t)start, (size_t)args[2])) {
22✔
991
                        Error("[SAVEDEV] Error writing file (Disk full?)", bp, IF_FIRST);
×
992
                }
993
        }
994
}
90✔
995

996
static void dirSAVE3DOS() {
63✔
997
        if (!DeviceID) {
63✔
998
                Error("SAVE3DOS works in real device emulation mode (See DEVICE)");
3✔
999
                SkipToEol(lp);
3✔
1000
                return;
45✔
1001
        }
1002
        bool exec = (LASTPASS == pass);
60✔
1003
        const std::filesystem::path fnaam = GetOutputFileName(lp);
60✔
1004
        aint args[5] = { -1, -1, 3, -1, -1 };        // address, size, type, w2_line, w3
60✔
1005
        const bool optional[] = {false, false, true, true, true};
60✔
1006
        if (!anyComma(lp) || !getIntArguments<5>(lp, args, optional)) {
60✔
1007
                Error("[SAVE3DOS] expected syntax is <filename>,<address>,<size>[,<type>[,<w2_line>[,<w3>]]]", bp, SUPPRESS);
27✔
1008
                return;
27✔
1009
        }
1010
        aint &address = args[0], &size = args[1], &type = args[2], &w2_line = args[3], &w3 = args[4];
33✔
1011
        if (address < 0 || size < 1 || 0x10000 < address + size) {
33✔
1012
                Error("[SAVE3DOS] [address, size] region outside of 64ki", bp);
9✔
1013
                return;
9✔
1014
        }
1015
        if (-1 == w3) w3 = size;        // default for w3 is size for all types, unless overridden
24✔
1016
        switch (type) {
24✔
1017
        case 0:                // type Program: default w2 = 0x8000
6✔
1018
                if (-1 == w2_line) w2_line = 0x8000;
6✔
1019
        case 1:                // type Numeric array: no idea what w2 actually should be for these
1020
        case 2:                // type Character array:
1021
                break;
12✔
1022
        case 3:                // type Code: default w2 = load address
6✔
1023
                if (-1 == w2_line) w2_line = address;
6✔
1024
                break;
6✔
1025
        default:
6✔
1026
                Error("[SAVE3DOS] expected type 0..3", bp);
6✔
1027
                return;
6✔
1028
        }
1029
        if (exec && !SaveBinary3dos(fnaam, address, size, type, w2_line, w3)) {
18✔
1030
                Error("[SAVE3DOS] Error writing file (Disk full?)", bp, IF_FIRST);
×
1031
        }
1032
}
60✔
1033

1034
static void dirSAVEAMSDOS() {
54✔
1035
        if (!DeviceID) {
54✔
1036
                Error("SAVEAMSDOS works in real device emulation mode (See DEVICE)");
3✔
1037
                SkipToEol(lp);
3✔
1038
                return;
33✔
1039
        }
1040
        bool exec = (LASTPASS == pass);
51✔
1041
        const std::filesystem::path fnaam = GetOutputFileName(lp);
51✔
1042
        aint args[] = { -1, -1, 0, 2 };        // address, size, start, type
51✔
1043
        const bool optional[] = {false, false, true, true};
51✔
1044
        if (!anyComma(lp) || !getIntArguments<4>(lp, args, optional)) {
51✔
1045
                Error("[SAVEAMSDOS] expected syntax is <filename>,<address>,<size>[,<start = 0>[,<type = 2>]", bp, SUPPRESS);
24✔
1046
                return;
24✔
1047
        }
1048
        aint &address = args[0], &size = args[1], &start = args[2], &type = args[3];
27✔
1049
        if (address < 0 || size < 1 || 0x10000 < address + size) {
27✔
1050
                Error("[SAVEAMSDOS] [address, size] region outside of 64ki", bp);
6✔
1051
                return;
6✔
1052
        }
1053
        check16u(start);
21✔
1054
        if (type < 0) type = -0x1000;                // check8 works for -256..+255 values, in this case just 0..255 is valid
21✔
1055
        check8(type);
21✔
1056
        if (exec && !SaveBinaryAmsdos(fnaam, address, size, start, type)) {
21✔
1057
                Error("[SAVEAMSDOS] Error writing file (Disk full?)", bp, IF_FIRST);
1✔
1058
        }
1059
}
51✔
1060

1061
static void dirSAVEHOB() {
15✔
1062
        if (!DeviceID || pass != LASTPASS) {
15✔
1063
                if (!DeviceID) Error("SAVEHOB only allowed in real device emulation mode (See DEVICE)");
11✔
1064
                SkipToEol(lp);
11✔
1065
                return;
14✔
1066
        }
1067
        aint val;
1068
        int start = -1,length = -1;
4✔
1069
        bool exec = true;
4✔
1070

1071
        const std::filesystem::path fnaam = GetOutputFileName(lp);
4✔
1072
        std::string fnaamh {""};
4✔
1073
        if (anyComma(lp)) {
4✔
1074
                if (!anyComma(lp)) {
3✔
1075
                        fnaamh = GetDelimitedString(lp);
2✔
1076
                        if (fnaamh.empty()) {
2✔
1077
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
1✔
1078
                        }
1079
                } else {
1080
                          Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
1✔
1081
                }
1082
        } else {
1083
                Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return; //is this ok?
1✔
1084
        }
1085

1086
        if (anyComma(lp)) {
1✔
1087
                if (!anyComma(lp)) {
1✔
1088
                        if (!ParseExpression(lp, val)) {
1✔
1089
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
×
1090
                        }
1091
                        if (val < 0x4000) {
1✔
1092
                                Error("[SAVEHOB] Values less than 4000h are not allowed", bp, PASS3); return;
×
1093
                        } else if (val > 0xFFFF) {
1✔
1094
                                  Error("[SAVEHOB] Values more than FFFFh are not allowed", bp, PASS3); return;
×
1095
                        }
1096
                        start = val;
1✔
1097
                } else {
1098
                          Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
×
1099
                }
1100
                if (anyComma(lp)) {
1✔
1101
                        if (!ParseExpression(lp, val)) {
1✔
1102
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
×
1103
                        }
1104
                        if (val < 0) {
1✔
1105
                                Error("[SAVEHOB] Negative values are not allowed", bp, PASS3); return;
×
1106
                        }
1107
                        length = val;
1✔
1108
                }
1109
        } else {
1110
                Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
×
1111
        }
1112
        if (exec && !SaveHobeta(fnaam, fnaamh.c_str(), start, length)) {
1✔
1113
                Error("[SAVEHOB] Error writing file (Disk full?)", bp, IF_FIRST); return;
×
1114
        }
1115
}
7✔
1116

1117
static void dirEMPTYTRD() {
39✔
1118
        if (pass != LASTPASS) {
39✔
1119
                SkipToEol(lp);
26✔
1120
                return;
28✔
1121
        }
1122
        char diskLabel[9] = "        ";
13✔
1123

1124
        const std::filesystem::path fnaam = GetOutputFileName(lp);
13✔
1125
        if (!fnaam.has_filename()) {
13✔
1126
                Error("[EMPTYTRD] Syntax error", bp, IF_FIRST);
2✔
1127
                return;
2✔
1128
        }
1129
        if (anyComma(lp)) {
11✔
1130
                const std::string srcLabel = GetDelimitedString(lp);
5✔
1131
                if (srcLabel.empty()) {
5✔
1132
                        Error("[EMPTYTRD] Syntax error, empty label", bp, IF_FIRST);
1✔
1133
                } else {
1134
                        memcpy(diskLabel, srcLabel.data(), std::min(static_cast<std::string::size_type>(8), srcLabel.size()));
4✔
1135
                        if (8 < srcLabel.size()) {
4✔
1136
                                Warning("[EMPTYTRD] label will be truncated to 8 characters", diskLabel);
1✔
1137
                        }
1138
                }
1139
        }
5✔
1140
        TRD_SaveEmpty(fnaam, diskLabel);
11✔
1141
}
13✔
1142

1143
static void dirSAVETRD() {
591✔
1144
        if (!DeviceID || pass != LASTPASS) {
591✔
1145
                if (!DeviceID) Error("SAVETRD only allowed in real device emulation mode (See DEVICE)");
395✔
1146
                SkipToEol(lp);
395✔
1147
                return;
402✔
1148
        }
1149

1150
        bool exec = true, replace = false, addplace = false;
196✔
1151
        aint val;
1152
        int start = -1, length = -1, autostart = -1, lengthMinusVars = -1;
196✔
1153

1154
        const std::filesystem::path fnaam = GetOutputFileName(lp);
196✔
1155

1156
        std::string fnaamh {""};
196✔
1157
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No parameters", bp, SUPPRESS);
196✔
1158
        if (!anyComma(lp)) {
196✔
1159
                if ((replace = ('|' == *lp))) SkipBlanks(++lp);        // detect "|" for "replace" feature
195✔
1160
                else if ((addplace = ('&' == *lp))) SkipBlanks(++lp); // detect "&" for "addplace" feature
188✔
1161
                fnaamh = GetDelimitedString(lp);
195✔
1162
        }
1163
        if (fnaamh.empty()) return Error("[SAVETRD] No filename", bp, SUPPRESS);
196✔
1164

1165
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No address", bp, SUPPRESS);
194✔
1166
        if (ParseExpressionNoSyntaxError(lp, val) && 0 <= val && val <= 0xFFFF) {
193✔
1167
                start = val;
192✔
1168
        }        // else start == -1 (syntax error or invalid value)
1169
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No length", bp, SUPPRESS);
193✔
1170
        ParseExpressionNoSyntaxError(lp, length);        // parse error/invalid value will be reported later
192✔
1171

1172
        if (anyComma(lp)) {
192✔
1173
                if (addplace) {
14✔
1174
                        Error("[SAVETRD] Autostart is not used here", bp, PASS3); return;
1✔
1175
                } else {
1176
                        if (!ParseExpression(lp, val)) {
13✔
1177
                                Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1178
                        }
1179
                        if (val < 0) {
12✔
1180
                                Error("[SAVETRD] Negative values are not allowed", bp, PASS3); return;
×
1181
                        }
1182
                        autostart = val;
12✔
1183
                        // optional length of BASIC without variables
1184
                        if (anyComma(lp)) {
12✔
1185
                                if (!ParseExpression(lp, val)) {
5✔
1186
                                        Error("[SAVETRD] Syntax error", bp, PASS3); return;
1✔
1187
                                }
1188
                                lengthMinusVars = val;
4✔
1189
                        }
1190
                }
1191
        }
1192

1193
        if (exec) TRD_AddFile(fnaam, fnaamh.c_str(), start, length, autostart, replace, addplace, lengthMinusVars);
189✔
1194
}
203✔
1195

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

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

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

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

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

1342
static void dirSETBREAKPOINT() {
414✔
1343
        if (LASTPASS != pass) {
414✔
1344
                SkipToEol(lp);
276✔
1345
                return;
282✔
1346
        }
1347
        aint val = CurAddress;
138✔
1348
        delim_string_t delimTxt = {};
138✔
1349
        if (!SkipBlanks(lp) && (DT_NONE == DelimiterAnyBegins(lp, false))) {        // not a condition, parse as address
138✔
1350
                if (!ParseExpressionNoSyntaxError(lp, val)) {
125✔
1351
                        Error("[SETBREAKPOINT] Syntax error", bp, SUPPRESS);
4✔
1352
                        return;
4✔
1353
                } else if (anyComma(lp) && !SkipBlanks(lp)) {                                                // condition can be second arg
121✔
1354
                        if (DT_NONE != DelimiterAnyBegins(lp, false)) delimTxt = GetDelimitedStringEx(lp);
4✔
1355
                }
1356
        } else {                                                                                                                                // condition delimited string follows
1357
                delimTxt = GetDelimitedStringEx(lp);
13✔
1358
        }
1359
        if (!SkipBlanks(lp)) {
134✔
1360
                Error("[SETBREAKPOINT] Syntax error", lp, SUPPRESS);
2✔
1361
                return;
2✔
1362
        }
1363
        WriteBreakpoint(val, (DT_NONE != delimTxt.second && !delimTxt.first.empty()) ? delimTxt.first.c_str() : nullptr);
132✔
1364
}
138✔
1365

1366
/*void dirTEXTAREA() {
1367

1368
}*/
1369

1370
// error message templates for IF**some** directives
1371
constexpr static size_t dirIfErrorsN = 2, dirIfErrorsSZ = 48;
1372
const static char dirIfErrorsTxtSrc[dirIfErrorsN][dirIfErrorsSZ] = {
1373
        { "[%s] No ENDIF" },
1374
        { "[%s] one ELSE only expected" }
1375
};
1376

1377
// IF and IFN internal helper, to evaluate expression
1378
static bool dirIfIfn(aint & val) {
23,189✔
1379
        IsLabelNotFound = false;
23,189✔
1380
        if (!ParseExpression(lp, val)) {
23,189✔
1381
                Error("[IF/IFN] Syntax error", lp, IF_FIRST);
9✔
1382
                return false;
9✔
1383
        }
1384
        if (IsLabelNotFound) {
23,180✔
1385
                WarningById(W_FWD_REF, bp, W_EARLY);
13✔
1386
        }
1387
        return true;
23,180✔
1388
}
1389

1390
// main IF implementation parsing/skipping part of source depending on "val", handling ELSE/ENDIF
1391
static void dirIfInternal(const char* dirName, aint val) {
24,212✔
1392
        // set up error messages for the particular pseudo-op
1393
        char errorsTxt[dirIfErrorsN][dirIfErrorsSZ];
1394
        for (size_t i = 0; i < dirIfErrorsN; ++i) {
72,636✔
1395
                SPRINTF1(errorsTxt[i], dirIfErrorsSZ, dirIfErrorsTxtSrc[i], dirName);
48,424✔
1396
        }
1397
        // do the IF**some** part
1398
        ListFile();
24,212✔
1399
        EReturn ret = END;
24,212✔
1400
        aint elseCounter = 0;
24,212✔
1401
        aint orVal = false;
24,212✔
1402
        while (ENDIF != ret) {
68,119✔
1403
                orVal |= val;
43,916✔
1404
                switch (ret = val ? ReadFile() : SkipFile()) {
43,916✔
1405
                        case ELSE:
19,545✔
1406
                                if (elseCounter++) Error(errorsTxt[1]);
19,545✔
1407
                                val = !val && !orVal;
19,545✔
1408
                                break;
19,545✔
1409
                        case ELSEIF:
159✔
1410
                                val = !val && !orVal;
159✔
1411
                                if (val) {                // active ELSEIF, evaluate expression
159✔
1412
                                        if (!dirIfIfn(val)) {
96✔
1413
                                                val = false;                // syntax error in expression
3✔
1414
                                                orVal = true;                // force remaining IF-blocks inactive
3✔
1415
                                        }
1416
                                }
1417
                                break;
159✔
1418
                        case ENDIF:
24,203✔
1419
                                break;
24,203✔
1420
                        default:
9✔
1421
                                if (IsRunning) Error(errorsTxt[0]);
9✔
1422
                                donotlist=!IsRunning;                // do the listing only if still running
9✔
1423
                                return;
9✔
1424
                }
1425
        }
1426
}
1427

1428
static void dirIF() {
20,747✔
1429
        aint val;
1430
        if (dirIfIfn(val)) dirIfInternal("IF", val);
20,747✔
1431
}
20,747✔
1432

1433
static void dirIFN() {
2,346✔
1434
        aint val;
1435
        if (dirIfIfn(val)) dirIfInternal("IFN", !val);
2,346✔
1436
}
2,346✔
1437

1438
// IFUSED and IFNUSED internal helper, to parse label
1439
static bool dirIfusedIfnused(char* & id) {
123✔
1440
        id = NULL;
123✔
1441
        if (SkipBlanks()) {                                                // no argument (use last parsed label)
123✔
1442
                if (LastParsedLabel) {
15✔
1443
                        id = STRDUP(LastParsedLabel);
9✔
1444
                } else {
1445
                        Error("[IFUSED/IFNUSED] no label defined ahead");
6✔
1446
                        return false;
6✔
1447
                }
1448
        } else {
1449
                std::unique_ptr<char[]> validLabel(ValidateLabel(lp, false, true));
108✔
1450
                if (validLabel) {
108✔
1451
                        id = STRDUP(validLabel.get());
99✔
1452
                        while (islabchar(*lp)) ++lp;        // advance lp beyond parsed label (valid chars only)
1,074✔
1453
                } else {
1454
                        SkipToEol(lp);                                        // ValidateLabel aready reported some error, skip rest
9✔
1455
                }
1456
        }
108✔
1457
        return id && SkipBlanks();                                // valid "id" and no extra characters = OK
117✔
1458
}
1459

1460
static void dirIFUSED() {
84✔
1461
        char* id;
1462
        if (dirIfusedIfnused(id)) dirIfInternal("IFUSED", LabelTable.IsUsed(id));
84✔
1463
        if (id) free(id);
84✔
1464
}
84✔
1465

1466
static void dirIFNUSED() {
39✔
1467
        char* id;
1468
        if (dirIfusedIfnused(id)) dirIfInternal("IFNUSED", !LabelTable.IsUsed(id));
39✔
1469
        if (id) free(id);
39✔
1470
}
39✔
1471

1472
static void dirIFDEF() {
114✔
1473
        char* id;
1474
        if ((id = GetID(lp)) && *id) {
114✔
1475
                dirIfInternal("IFDEF", DefineTable.FindDuplicate(id));
111✔
1476
        } else {
1477
                Error("[IFDEF] Illegal identifier", bp);
3✔
1478
        }
1479
}
114✔
1480

1481
static void dirIFNDEF() {
915✔
1482
        char* id;
1483
        if ((id = GetID(lp)) && *id) {
915✔
1484
                dirIfInternal("IFNDEF", !DefineTable.FindDuplicate(id));
912✔
1485
        } else {
1486
                Error("[IFNDEF] Illegal identifier", bp);
3✔
1487
        }
1488
}
915✔
1489

1490
static void dirElseCheckLiveDup() {
12✔
1491
        if (RepeatStack.empty()) return;
12✔
1492
        if (!RepeatStack.top().IsInWork) return;
3✔
1493

1494
        // Seems some ELSE/ELSEIF/ENDIF was encountered inside DUP->EDUP without starting IF
1495
        // -> probably IF was outside of DUP->EDUP block, which is not legal in sjasmplus
1496
        // terminate the current DUP->EDUP macro early and report the open ELSE/ELSEIF/ENDIF
1497
        Error("Conditional block must start and finish inside the repeat block, nested completely");
3✔
1498
        lijstp = nullptr;
3✔
1499
        RepeatStack.top().RepeatCount = 0;
3✔
1500
}
1501

1502
static void dirELSE() {
6✔
1503
        dirElseCheckLiveDup();
6✔
1504
        Error("ELSE without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
6✔
1505
}
6✔
1506

1507
static void dirELSEIF() {
3✔
1508
        dirElseCheckLiveDup();
3✔
1509
        Error("ELSEIF without IF/IFN");
3✔
1510
}
3✔
1511

1512
static void dirENDIF() {
3✔
1513
        dirElseCheckLiveDup();
3✔
1514
        Error("ENDIF without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
3✔
1515
}
3✔
1516

1517
/*void dirENDTEXTAREA() {
1518
  Error("ENDT without TEXTAREA",0);
1519
}*/
1520

1521
static void dirINCLUDE() {
588✔
1522
        fullpath_ref_t fnaam = GetInputFile(lp);
588✔
1523
        if (fnaam.full.has_filename()) {
588✔
1524
                ListFile();
582✔
1525
                IncludeFile(fnaam);
582✔
1526
                donotlist = 1;
582✔
1527
        } else {
1528
                Error("[INCLUDE] empty filename", bp);
6✔
1529
        }
1530
}
588✔
1531

1532
static void dirOUTPUT() {
366✔
1533
        if (LASTPASS != pass) {
366✔
1534
                SkipToEol(lp);
244✔
1535
                return;
247✔
1536
        }
1537
        const std::filesystem::path fnaam = GetOutputFileName(lp);
122✔
1538
        char modechar = 0;
122✔
1539
        int mode = OUTPUT_TRUNCATE;
122✔
1540
        if (comma(lp)) {
122✔
1541
                if (!SkipBlanks(lp)) modechar = (*lp++) | 0x20;
9✔
1542
                switch (modechar) {
9✔
1543
                        case 't': mode = OUTPUT_TRUNCATE;        break;
1✔
1544
                        case 'r': mode = OUTPUT_REWIND;                break;
3✔
1545
                        case 'a': mode = OUTPUT_APPEND;                break;
2✔
1546
                        default:
3✔
1547
                                Error("[OUTPUT] Invalid <mode> (valid modes: t, a, r)", bp);
3✔
1548
                                return;
3✔
1549
                }
1550
        }
1551
        //Options::NoDestinationFile = false;
1552
        NewDest(fnaam, mode);
119✔
1553
}
122✔
1554

1555
static void dirOUTEND()
63✔
1556
{
1557
        if (pass == LASTPASS) CloseDest();
63✔
1558
}
63✔
1559

1560
static void dirTAPOUT()
30✔
1561
{
1562
        aint val;
1563
        const std::filesystem::path fnaam = GetOutputFileName(lp);
30✔
1564
        int tape_flag = 255;
30✔
1565
        if (comma(lp))
30✔
1566
        {
1567
                if (!ParseExpression(lp, val))
12✔
1568
                {
1569
                        Error("[TAPOUT] Missing flagbyte value", bp, PASS3); return;
3✔
1570
                }
1571
                tape_flag = val;
9✔
1572
        }
1573
        if (pass == LASTPASS) OpenTapFile(fnaam, tape_flag);
27✔
1574
}
30✔
1575

1576
static void dirTAPEND()
24✔
1577
{
1578
        // if (!FP_tapout) {Error("TAPEND without TAPOUT", bp, PASS3); return;}
1579
        if (pass == LASTPASS) CloseTapFile();
24✔
1580
}
24✔
1581

1582
static void dirDEFINE() {
300✔
1583
        bool replaceEnabled = ('+' == *lp) ? ++lp, true : false;
300✔
1584
        char* id = GetID(lp);
300✔
1585
        if (nullptr == id) {
300✔
1586
                Error("[DEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1587
                return;
3✔
1588
        }
1589
        if (White(*lp)) ++lp;                // skip one whitespace (not considered part of value) (others are)
297✔
1590
        // but trim trailing spaces of value, if there's eol-comment
1591
        if (eolComment) {
297✔
1592
                char *rtrim = lp + strlen(lp);
6✔
1593
                while (lp < rtrim && ' ' == rtrim[-1]) --rtrim;
48✔
1594
                *rtrim = 0;
6✔
1595
        }
1596

1597
        if (replaceEnabled) {
297✔
1598
                DefineTable.Replace(id, lp);
9✔
1599
        } else {
1600
                DefineTable.Add(id, lp, nullptr);
288✔
1601
        }
1602
        SkipToEol(lp);
297✔
1603
        substitutedLine = line;                // override substituted listing for DEFINE
297✔
1604
}
1605

1606
static void dirUNDEFINE() {
105✔
1607
        char* id;
1608

1609
        if (!(id = GetID(lp)) && *lp != '*') {
105✔
1610
                Error("[UNDEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1611
                return;
3✔
1612
        }
1613

1614
        if (*lp == '*') {
102✔
1615
                ++lp;
3✔
1616
                DefineTable.RemoveAll();
3✔
1617
        } else if (DefineTable.FindDuplicate(id)) {
99✔
1618
                DefineTable.Remove(id);
90✔
1619
        } else {
1620
                Warning("[UNDEFINE] Identifier not found", id);
9✔
1621
        }
1622
}
1623

1624
static void dirEXPORT() {
60✔
1625
        aint val;
1626
        char* n, * p;
1627

1628
        if (Options::ExportFName.empty()) {
60✔
1629
                assert(!sourcePosStack.empty());
2✔
1630
                Options::ExportFName = sourcePosStack.back().filename;
2✔
1631
                Options::ExportFName.replace_extension(".exp");
2✔
1632
                Warning("[EXPORT] Filename for exportfile was not indicated. Output will be in", Options::ExportFName.string().c_str(), W_EARLY);
2✔
1633
        }
1634
        if (!(n = p = GetID(lp))) {
60✔
1635
                Error("[EXPORT] Syntax error", lp, SUPPRESS);
12✔
1636
                return;
44✔
1637
        }
1638
        if (pass != LASTPASS) return;
48✔
1639
        IsLabelNotFound = false;
16✔
1640
        GetLabelValue(n, val);
16✔
1641
        if (!IsLabelNotFound) WriteExp(p, val);
16✔
1642
}
1643

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

1735
        if (LASTPASS == pass && *e) {
87✔
1736
                _CERR "> " _CMDL Options::tcols->display _CMDL e _CMDL Options::tcols->end _ENDL;
27✔
1737
        }
1738
}
1739

1740
static void dirMACRO() {
627✔
1741
        if (lijst) {
627✔
1742
                Error("[MACRO] No macro definitions allowed here", NULL, SUPPRESS);
60✔
1743
                return;
60✔
1744
        }
1745
        // check if the name of macro is defined at beginning of the line ("label" name)
1746
        const bool labelName = LastParsedLabelLine == CompiledCurrentLine;
567✔
1747
        assert(!labelName || LastParsedLabel);
567✔
1748
        char* lpLabel = labelName ? LastParsedLabel : lp;        // temporary pointer to advance by GetID
567✔
1749
        char* n = GetID(lpLabel);                                                        // get+validate macro name
567✔
1750
        if (*lpLabel && !White(*lpLabel)) n = nullptr;                // if there's unexpected trailing char, report illegal name
567✔
1751
        if (n) {
567✔
1752
                if (!labelName) lp = lpLabel;                                        // name was after MACRO keyword, advance global `lp` (to parse arguments)
540✔
1753
                MacroTable.Add(n, lp);
540✔
1754
        } else {
1755
                Error("[MACRO] Illegal macroname", labelName ? LastParsedLabel : lp);        // report what was fed into GetID
27✔
1756
                SkipToEol(lp);
27✔
1757
        }
1758
}
1759

1760
static void dirENDS() {
15✔
1761
        Error("[ENDS] End structure without structure");
15✔
1762
}
15✔
1763

1764
static void dirASSERT() {
2,348✔
1765
        char* p = lp;
2,348✔
1766
        aint val;
1767
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,348✔
1768
                Error("[ASSERT] Syntax error", p, SUPPRESS);
12✔
1769
                return;
12✔
1770
        }
1771
        if (pass == LASTPASS && !val) {
2,336✔
1772
                Error("[ASSERT] Assertion failed", p);
18✔
1773
        }
1774
        if (comma(lp)) SkipToEol(lp);
2,336✔
1775
}
1776

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

1805
static void dirSTRUCT() {
270✔
1806
        CStructure* st;
1807
        aint offset = 0;
270✔
1808
        SkipBlanks();
270✔
1809
        std::unique_ptr<char[]> validLabel(ValidateLabel(lp, false, true));
270✔
1810
        if (!validLabel) {        // ValidateLabel() reports illegal name as "label" ... welp... good enough.
270✔
1811
                SkipToEol(lp);
9✔
1812
                return;
9✔
1813
        }
1814
        while (islabchar(*lp)) ++lp;        // advance lp beyond parsed label (valid chars only)
2,331✔
1815
        if (comma(lp)) {
261✔
1816
                IsLabelNotFound = false;
45✔
1817
                if (!ParseExpressionNoSyntaxError(lp, offset)) {
45✔
1818
                        Error("[STRUCT] Offset syntax error", lp, SUPPRESS);
3✔
1819
                        return;
3✔
1820
                }
1821
                if (IsLabelNotFound) {
42✔
1822
                        Error("[STRUCT] Forward reference", NULL, EARLY);
5✔
1823
                }
1824
        }
1825
        if (!SkipBlanks()) {
258✔
1826
                Error("[STRUCT] syntax error, unexpected", lp);
3✔
1827
        }
1828
        st = StructureTable.Add(validLabel.get(), offset);        // create structure and start parsing members
258✔
1829
        ListFile();
258✔
1830
        while (ReadLine()) {
1,503✔
1831
                lp = line;
1,500✔
1832
                SkipBlanks(lp);
1,500✔
1833
                if (*lp == '.') {
1,500✔
1834
                        ++lp;
33✔
1835
                }
1836
                if (cmphstr(lp, "ends")) {
1,500✔
1837
                        ++CompiledCurrentLine;
255✔
1838
                        if (st) st->deflab();                // create structure symbols from members and struct itself (size)
255✔
1839
                        lp = ReplaceDefine(lp);                // skip any empty substitutions and comments
254✔
1840
                        substitutedLine = line;                // override substituted listing for ENDS
254✔
1841
                        return;
254✔
1842
                }
1843
                if (st) ParseStructLine(st);
1,245✔
1844
                ListFile(true);
1,245✔
1845
        }
1846
        Error("[STRUCT] Unexpected end of structure");
3✔
1847
        st->deflab();
3✔
1848
}
269✔
1849

1850
static void dirFPOS() {
18✔
1851
        aint val;
1852
        int method = SEEK_SET;
18✔
1853
        SkipBlanks(lp);
18✔
1854
        if ((*lp == '+') || (*lp == '-')) {
18✔
1855
                method = SEEK_CUR;
6✔
1856
        }
1857
        if (!ParseExpressionNoSyntaxError(lp, val)) {
18✔
1858
                Error("[FPOS] Syntax error", lp, SUPPRESS);
3✔
1859
        } else if (pass == LASTPASS) {
15✔
1860
                SeekDest(val, method);
5✔
1861
        }
1862
}
18✔
1863

1864
// isWhile == false: DUP/REPT parsing
1865
// isWhile == true: WHILE parsing
1866
static void DupWhileImplementation(bool isWhile) {
4,499✔
1867
        aint val = 0;
4,499✔
1868
        CStringsList* condition = nullptr;
4,499✔
1869

1870
        if (!RepeatStack.empty()) {
4,499✔
1871
                SRepeatStack& dup = RepeatStack.top();
3,941✔
1872
                if (!dup.IsInWork) {
3,941✔
1873
                        SkipToEol(lp);                // Just skip the expression to the end of line, don't evaluate yet
264✔
1874
                        ++dup.Level;
264✔
1875
                        return;
276✔
1876
                }
1877
        }
1878

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

1927
        RepeatStack.emplace(val, condition, new CStringsList(indexVar ? indexVar : ""));
4,223✔
1928
        if (!SkipBlanks()) Error("[DUP] unexpected chars", lp, SUPPRESS);
4,223✔
1929
}
1930

1931
static void dirDUP() {
4,457✔
1932
        DupWhileImplementation(false);
4,457✔
1933
}
4,457✔
1934

1935
static void dirWHILE() {
42✔
1936
        DupWhileImplementation(true);
42✔
1937
}
42✔
1938

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

1967
static void dirEDUP() {
4,493✔
1968
        if (RepeatStack.empty() || RepeatStack.top().IsInWork) {
4,493✔
1969
                Error("[EDUP/ENDR/ENDW] End repeat without repeat");
9✔
1970
                return;
273✔
1971
        }
1972

1973
        SRepeatStack& dup = RepeatStack.top();
4,484✔
1974
        if (!dup.IsInWork && dup.Level) {
4,484✔
1975
                --dup.Level;
264✔
1976
                return;
264✔
1977
        }
1978
        dup.IsInWork = true;
4,220✔
1979
        // kill the "EDUP" inside DUP-list (+ works as "while (IsRunning && lijstp && lijstp->string)" terminator)
1980
        if (dup.Pointer->string) free(dup.Pointer->string);
4,220✔
1981
        dup.Pointer->string = NULL;
4,220✔
1982
        ++listmacro;
4,220✔
1983
        char* ml = STRDUP(line);        // copy the EDUP line for List purposes (after the DUP block emit)
4,220✔
1984
        if (ml == NULL) ErrorOOM();
4,220✔
1985

1986
        CStringsList* olijstp = lijstp;
4,220✔
1987
        ++lijst;
4,220✔
1988
        assert(!sourcePosStack.empty());
4,220✔
1989
        const TextFilePos oSourcePos = sourcePosStack.back();
4,220✔
1990
        aint currentRepeatIndex = 0;
4,220✔
1991
        while (IsRunning && dup.Lines && shouldRepeat(dup)) {
360,491✔
1992
                sourcePosStack.back() = dup.sourcePos;
356,271✔
1993
                lijstp = dup.Lines;
356,271✔
1994
                assert(lijstp);
356,271✔
1995
                if (*lijstp->string) {
356,271✔
1996
                        // if the DUP has index variable, the first "line" is the variable name, set it up to current index
1997
                        std::unique_ptr<char[]> indexVar(ValidateLabel(lijstp->string,  false));
147✔
1998
                        if (indexVar.get() &&
147✔
1999
                                !LabelTable.Insert(indexVar.get(), currentRepeatIndex, LABEL_IS_DEFL) &&
147✔
2000
                                0 == currentRepeatIndex &&
294✔
2001
                                1 == pass)
3✔
2002
                        {
2003
                                Error("Duplicate label", indexVar.get(), EARLY);
1✔
2004
                        }
2005
                        ++currentRepeatIndex;
147✔
2006
                }
147✔
2007
                lijstp = lijstp->next;        // skip first empty line / indexVar name
356,271✔
2008
                while (IsRunning && lijstp && lijstp->string) {        // the EDUP/REPT/ENDM line has string=NULL => ends loop
781,616✔
2009
                        if (lijstp->source.line) sourcePosStack.back() = lijstp->source;
425,345✔
2010
                        STRCPY(line, LINEMAX, lijstp->string);
425,345✔
2011
                        substitutedLine = line;                // reset substituted listing
425,345✔
2012
                        eolComment = NULL;                        // reset end of line comment
425,345✔
2013
                        lijstp = lijstp->next;
425,345✔
2014
                        ParseLineSafe();
425,345✔
2015
                        sourcePosStack.back().nextSegment();
425,345✔
2016
                }
2017
        }
2018
        sourcePosStack.back() = oSourcePos;
4,220✔
2019
        RepeatStack.pop();
4,220✔
2020
        lijstp = olijstp;
4,220✔
2021
        --lijst;
4,220✔
2022
        --listmacro;
4,220✔
2023
        STRCPY(line, LINEMAX,  ml);                // show EDUP line itself
4,220✔
2024
        free(ml);
4,220✔
2025
        ++CompiledCurrentLine;
4,220✔
2026
        substitutedLine = line;                        // override substituted list line for EDUP
4,220✔
2027
        ListFile();
4,220✔
2028
}
2029

2030
static void dirENDM() {
18✔
2031
        if (!RepeatStack.empty()) {
18✔
2032
                Warning("ENDM used as DUP/REPT block terminator, this is deprecated (and bugged when used inside macro), change to EDUP or ENDR");
3✔
2033
                dirEDUP();
3✔
2034
        } else {
2035
                Error("[ENDM] End macro without macro");
15✔
2036
        }
2037
}
18✔
2038

2039
static bool dirDEFARRAY_parseItems(CStringsList** nextPtr) {
204✔
2040
        char ml[LINEMAX];
2041
        do {
2042
                const char* const itemLp = lp;
1,197✔
2043
                char* n = ml;
1,197✔
2044
                if (!GetMacroArgumentValue(lp, n)) {
1,197✔
2045
                        Error("[DEFARRAY] Syntax error", itemLp, SUPPRESS);
18✔
2046
                        return false;
18✔
2047
                }
2048
                *nextPtr = new CStringsList(ml);
1,179✔
2049
                if ((*nextPtr)->string == NULL) ErrorOOM();
1,179✔
2050
                nextPtr = &((*nextPtr)->next);
1,179✔
2051
        } while (anyComma(lp));
1,179✔
2052
        return SkipBlanks();
186✔
2053
}
2054

2055
static void dirDEFARRAY_add(const char* id) {
21✔
2056
        DefineTable.Get(id);
21✔
2057
        if (NULL == DefineTable.DefArrayList) {
21✔
2058
                Error("[DEFARRAY+] unknown array <id>", id);
6✔
2059
                SkipToEol(lp);
6✔
2060
                return;
6✔
2061
        }
2062
        // array was already defined, seek to the last item in the list
2063
        while (DefineTable.DefArrayList->next) DefineTable.DefArrayList = DefineTable.DefArrayList->next;
84✔
2064
        dirDEFARRAY_parseItems(&DefineTable.DefArrayList->next);
15✔
2065
        return;
15✔
2066
}
2067

2068
static void dirDEFARRAY() {
261✔
2069
        bool plus = ('+' == *lp) ? ++lp, true : false;
261✔
2070
        const char* id = White() ? GetID(lp) : nullptr;
261✔
2071
        if (!id) {
261✔
2072
                Error("[DEFARRAY] Syntax error in <id>", lp);
18✔
2073
                SkipToEol(lp);
18✔
2074
                return;
18✔
2075
        }
2076
        if (!White() || SkipBlanks()) {        // enforce whitespace between ID and first item and detect empty ones
243✔
2077
                if (SkipBlanks()) Error("[DEFARRAY] must have at least one entry");
33✔
2078
                else Error("[DEFARRAY] missing space between <id> and first <item>", lp);
15✔
2079
                SkipToEol(lp);
33✔
2080
                return;
33✔
2081
        }
2082
        if (plus) {
210✔
2083
                dirDEFARRAY_add(id);
21✔
2084
        } else {
2085
                CStringsList* a = NULL;
189✔
2086
                if (!dirDEFARRAY_parseItems(&a) || NULL == a) {
189✔
2087
                        if (a) delete a;        // release already parsed items, if there was syntax error
15✔
2088
                        return;
15✔
2089
                }
2090
                DefineTable.Add(id, "", a);
174✔
2091
        }
2092
}
2093

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

2096
static void dirDEFDEVICE() {
53✔
2097
        //DEFDEVICE <deviceid>, <slot_size>, <page_count>[, <slot_0_initial_page>[, ...]]
2098
        const char* id = GetID(lp);
53✔
2099
        if (!id) {
53✔
2100
                Error(DEFDEVICE_SYNTAX_ERR, bp, SUPPRESS);
3✔
2101
                return;
45✔
2102
        }
2103

2104
        const bool is_defined = std::any_of(
50✔
2105
                DefDevices.begin(), DefDevices.end(),
2106
                [&](const CDeviceDef* el) { return 0 == strcasecmp(id, el->getID()); }
124✔
2107
        );
2108
        if (is_defined || 1 < pass) {
50✔
2109
                // same id defined twice during first pass?
2110
                if (pass <= 1) Error("[DEFDEVICE] device with such ID is already defined", id, EARLY);
35✔
2111
                // in later passes ignore the line, DEFDEVICE works only in first pass
2112
                SkipToEol(lp);
35✔
2113
                return;
35✔
2114
        }
2115

2116
        // add new definition if arguments are correct and this is first pass
2117
        aint args[2 + CDeviceDef::MAX_SLOT_N] = {};        // slot_size, page_count, initial pages, ...
15✔
2118
        bool optional[2 + CDeviceDef::MAX_SLOT_N] = {false, false};
15✔
2119
        aint &slot_size = args[0], &page_count = args[1], *initial_pages = args + 2;
15✔
2120
        for (size_t i = 2; i < CDeviceDef::MAX_SLOT_N; ++i) {
3,825✔
2121
                args[i] = -1;
3,810✔
2122
                optional[i] = true;
3,810✔
2123
        }
2124
        if (!anyComma(lp) || !getIntArguments<2 + CDeviceDef::MAX_SLOT_N>(lp, args, optional)) {
15✔
2125
                Error(DEFDEVICE_SYNTAX_ERR, bp, EARLY);
4✔
2126
                return;
4✔
2127
        }
2128
        if (slot_size < 256 || 0x10000 < slot_size || page_count <= 0) {
11✔
2129
                Error("[DEFDEVICE] valid slot_size: 256..64ki, page_count: 1 or more", bp, EARLY);
3✔
2130
                return;
3✔
2131
        }
2132
        DefDevices.push_back(new CDeviceDef(id, slot_size, page_count));
8✔
2133

2134
        // init "initialPages array by going 0, 1, 2, ..., page_count-1, page_count-1, ... or parsed explicit values
2135
        CDeviceDef & dev = *DefDevices.back();
8✔
2136
        int previous_page = -1;
8✔
2137
        for (int32_t i = 0; i < dev.SlotsCount; ++i) {
298✔
2138
                if (0 <= initial_pages[i] && initial_pages[i] < dev.PagesCount) {
290✔
2139
                        previous_page = initial_pages[i];
19✔
2140
                } else {
2141
                        if (-1 != initial_pages[i]) ErrorInt("[DEFDEVICE] invalid initial page", initial_pages[i], EARLY);
271✔
2142
                        if (previous_page < dev.PagesCount - 1) ++previous_page;
271✔
2143
                }
2144
                dev.initialPages[i] = previous_page;
290✔
2145
        }
2146
}
2147

2148
static void dirDEVICE() {
899✔
2149
        // refresh source position of first DEVICE directive
2150
        if (1 == ++deviceDirectivesCount) {
899✔
2151
                assert(!sourcePosStack.empty());
488✔
2152
                globalDeviceSourcePos = sourcePosStack.back();
488✔
2153
        }
2154

2155
        char* id = GetID(lp);
899✔
2156
        if (id) {
899✔
2157
                aint ramtop = 0;
896✔
2158
                if (anyComma(lp)) {
896✔
2159
                        if (!ParseExpressionNoSyntaxError(lp, ramtop)) {
135✔
2160
                                Error("[DEVICE] Syntax error", bp); return;
30✔
2161
                        }
2162
                        if (ramtop < 0x5D00 || 0xFFFF < ramtop) {
129✔
2163
                                  ErrorInt("[DEVICE] valid range for RAMTOP is $5D00..$FFFF", ramtop); return;
24✔
2164
                        }
2165
                }
2166
                // if (1 == deviceDirectivesCount && Device) -> device was already set globally, skip SetDevice
2167
                if (1 < deviceDirectivesCount || !Devices) {
866✔
2168
                        if (!SetDevice(id, ramtop)) {
645✔
2169
                                Error("[DEVICE] Invalid parameter", id, IF_FIRST);
10✔
2170
                        }
2171
                }
2172
        } else {
2173
                Error("[DEVICE] Syntax error in <deviceid>", lp, SUPPRESS);
3✔
2174
        }
2175
}
2176

2177
static void dirSLDOPT() {
84✔
2178
        SkipBlanks(lp);
84✔
2179
        if (cmphstr(lp, "comment")) {
84✔
2180
                do {
2181
                        SldAddCommentKeyword(GetID(lp));
21✔
2182
                } while (!SkipBlanks(lp) && anyComma(lp));
21✔
2183
        } else if (cmphstr(lp, "swapon")) {
69✔
2184
                ++sldSwapSrcPos;
33✔
2185
        } else if (cmphstr(lp, "swapoff")) {
36✔
2186
                --sldSwapSrcPos;
30✔
2187
        } else {
2188
                Error("[SLDOPT] Syntax error in <type> (valid: COMMENT, SWAPON, SWAPOFF)", lp, SUPPRESS);
6✔
2189
        }
2190
}
84✔
2191

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

2297
        DirectivesTable.insertd(".device", dirDEVICE);
529✔
2298
        DirectivesTable.insertd(".defdevice", dirDEFDEVICE);
529✔
2299

2300
        DirectivesTable.insertd(".bplist", dirBPLIST);
529✔
2301
        DirectivesTable.insertd(".setbreakpoint", dirSETBREAKPOINT);
529✔
2302
        DirectivesTable.insertd(".setbp", dirSETBREAKPOINT);
529✔
2303

2304
        DirectivesTable.insertd(".relocate_start", Relocation::dirRELOCATE_START);
529✔
2305
        DirectivesTable.insertd(".relocate_end", Relocation::dirRELOCATE_END);
529✔
2306
        DirectivesTable.insertd(".relocate_table", Relocation::dirRELOCATE_TABLE);
529✔
2307

2308
        DirectivesTable.insertd(".sldopt", dirSLDOPT);
529✔
2309

2310
#ifdef USE_LUA
2311
        DirectivesTable.insertd(".lua", dirLUA);
529✔
2312
        DirectivesTable.insertd(".endlua", dirENDLUA);
529✔
2313
        DirectivesTable.insertd(".includelua", dirINCLUDELUA);
529✔
2314
#endif //USE_LUA
2315

2316
        DirectivesTable_dup.insertd(".dup", dirDUP);
529✔
2317
        DirectivesTable_dup.insertd(".edup", dirEDUP);
529✔
2318
        DirectivesTable_dup.insertd(".endm", dirENDM);
529✔
2319
        DirectivesTable_dup.insertd(".endr", dirEDUP);
529✔
2320
        DirectivesTable_dup.insertd(".endw", dirEDUP);
529✔
2321
        DirectivesTable_dup.insertd(".rept", dirDUP);
529✔
2322
        DirectivesTable_dup.insertd(".while", dirWHILE);
529✔
2323
}
529✔
2324

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