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

z00m128 / sjasmplus / 1470

27 Jan 2025 01:40AM UTC coverage: 96.394% (-0.01%) from 96.404%
1470

Pull #255

cirrus-ci

ped7g
sjio: extended --fullpath - inner implementation

Unify filename string used for output (errors, listing, sld, ...) and
make it depend on the --fullpath option.

Add three levels internally: 1) base filename (default), 2) relative to
launch dir, 3) absolute full path

The only exception is opening some.lua file where the c-string path to
open is processed by lua itself and also preserved for source-positon
debug info, which becomes visible in error messages - here the
relative-to-launch-dir normalization is enforced (to make error strings
reasonable).

The `--fullpath` option itself is not modified in this commit yet, still
works as before (allowing only options 1) and 2))
Pull Request #255: more --fullpath options

33 of 37 new or added lines in 7 files covered. (89.19%)

2 existing lines in 1 file now uncovered.

9544 of 9901 relevant lines covered (96.39%)

171031.41 hits per line

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

98.43
/sjasm/directives.cpp
1
/*
2

3
  SjASMPlus Z80 Cross Compiler
4

5
  This is modified sources of SjASM by Aprisobal - aprisobal@tut.by
6

7
  Copyright (c) 2006 Sjoerd Mastijn
8

9
  This software is provided 'as-is', without any express or implied warranty.
10
  In no event will the authors be held liable for any damages arising from the
11
  use of this software.
12

13
  Permission is granted to anyone to use this software for any purpose,
14
  including commercial applications, and to alter it and redistribute it freely,
15
  subject to the following restrictions:
16

17
  1. The origin of this software must not be misrepresented; you must not claim
18
         that you wrote the original software. If you use this software in a product,
19
         an acknowledgment in the product documentation would be appreciated but is
20
         not required.
21

22
  2. Altered source versions must be plainly marked as such, and must not be
23
         misrepresented as being the original software.
24

25
  3. This notice may not be removed or altered from any source distribution.
26

27
*/
28

29
// direct.cpp
30

31
#include "sjdefs.h"
32

33
CFunctionTable DirectivesTable;
34
CFunctionTable DirectivesTable_dup;
35

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

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

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

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

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

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

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

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

109

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

585
static void dirINCHOB() {
30✔
586
        unsigned char len[2];
587
        int offset = 0,length = -1;
30✔
588
        fullpath_ref_t fnaam = GetInputFile(lp);
30✔
589
        if (anyComma(lp)) {
30✔
590
                aint val;
591
                if (!anyComma(lp)) {
24✔
592
                        if (!ParseExpression(lp, val)) {
18✔
593
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
594
                        }
595
                        if (val < 0) {
18✔
596
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
597
                        }
598
                        offset += val;
18✔
599
                } else --lp;                // there was second comma right after, reread it
6✔
600
                if (anyComma(lp)) {
24✔
601
                        if (!ParseExpression(lp, val)) {
18✔
602
                                Error("[INCHOB] Syntax error", bp, IF_FIRST); return;
×
603
                        }
604
                        if (val < 0) {
18✔
605
                                Error("[INCHOB] Negative values are not allowed", bp); return;
×
606
                        }
607
                        length = val;
18✔
608
                }
609
        }
610

611
        FILE* ff;
612
        if (!FOPEN_ISOK(ff, fnaam.full, "rb")) {
30✔
NEW
613
                Error("[INCHOB] Error opening file", fnaam.str.c_str(), FATAL);
×
614
        }
615
        if (fseek(ff, 0x0b, 0) || 2 != fread(len, 1, 2, ff)) {
30✔
NEW
616
                Error("[INCHOB] Hobeta file has wrong format", fnaam.str.c_str(), FATAL);
×
617
        }
618
        fclose(ff);
30✔
619
        if (length == -1) {
30✔
620
                // calculate remaining length of the file (from the specified offset)
621
                length = len[0] + (len[1] << 8) - offset;
12✔
622
        }
623
        offset += 17;                // adjust offset (skip HOB header)
30✔
624
        BinIncFile(fnaam, offset, length);
30✔
625
}
626

627
static void dirINCTRD() {
78✔
628
        aint val, offset = 0, length = INT_MAX;
78✔
629
        fullpath_ref_t trdfile = GetInputFile(lp);
78✔
630
        std::string filename {""};
78✔
631
        if (anyComma(lp) && !anyComma(lp)) filename = GetDelimitedString(lp);
78✔
632
        if (filename.empty()) {
78✔
633
                // file-in-disk syntax error
634
                Error("[INCTRD] Syntax error", bp, IF_FIRST);
9✔
635
                SkipToEol(lp);
9✔
636
                return;
9✔
637
        }
638
        if (anyComma(lp)) {
69✔
639
                if (!anyComma(lp)) {
51✔
640
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
48✔
641
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
642
                                SkipToEol(lp);
6✔
643
                                return;
6✔
644
                        }
645
                        if (val < 0) {
42✔
646
                                ErrorInt("[INCTRD] Negative offset value is not allowed", val);
3✔
647
                                SkipToEol(lp);
3✔
648
                                return;
3✔
649
                        }
650
                        offset = val;
39✔
651
                } else --lp;                // there was second comma right after, reread it
3✔
652
                if (anyComma(lp)) {
42✔
653
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
33✔
654
                                Error("[INCTRD] Syntax error", bp, IF_FIRST);
6✔
655
                                SkipToEol(lp);
6✔
656
                                return;
6✔
657
                        }
658
                        if (val < 0) {
27✔
659
                                ErrorInt("[INCTRD] Negative length value is not allowed", val);
3✔
660
                                SkipToEol(lp);
3✔
661
                                return;
3✔
662
                        }
663
                        length = val;
24✔
664
                }
665
        }
666
        if (TRD_PrepareIncFile(trdfile, filename.c_str(), offset, length)) {
51✔
667
                BinIncFile(trdfile, offset, length);
36✔
668
        }
669
}
78✔
670

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

674
        if (!DeviceID) {
30✔
675
                Error("SAVESNA only allowed in real device emulation mode (See DEVICE)", nullptr, SUPPRESS);
2✔
676
                return;
2✔
677
        } else if (!IsZXSpectrumDevice(DeviceID)) {
28✔
678
                Error("[SAVESNA] Device must be ZXSPECTRUM48 or ZXSPECTRUM128.", nullptr, SUPPRESS);
1✔
679
                return;
1✔
680
        }
681

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

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

703
static void dirEMPTYTAP() {
33✔
704
        if (pass != LASTPASS) {
33✔
705
                SkipParam(lp);
22✔
706
                return;
23✔
707
        }
708
        const std::filesystem::path fnaam = GetOutputFileName(lp);
11✔
709
        if (!fnaam.has_filename()) {
11✔
710
                Error("[EMPTYTAP] Syntax error", bp, IF_FIRST); return;
1✔
711
        }
712
        TAP_SaveEmpty(fnaam);
10✔
713
}
11✔
714

715
static void dirSAVETAP() {
192✔
716
        if (pass != LASTPASS) {
192✔
717
                SkipParam(lp);
128✔
718
                return;
158✔
719
        }
720

721
        bool exec = true, realtapeMode = false;
64✔
722
        int headerType = -1;
64✔
723
        aint val;
724
        int start = -1, length = -1, param2 = -1, param3 = -1;
64✔
725

726
        if (!DeviceID) {
64✔
727
                Error("SAVETAP only allowed in real device emulation mode (See DEVICE)");
1✔
728
                exec = false;
1✔
729
        }
730

731
        const std::filesystem::path fnaam = GetOutputFileName(lp);
64✔
732
        std::string fnaamh {""};
64✔
733
        if (anyComma(lp)) {
64✔
734
                if (!anyComma(lp)) {
62✔
735
                        char *tlp = lp;
61✔
736
                        char *id;
737

738
                        if ((id = GetID(lp)) && strlen(id) > 0) {
61✔
739
                                if (cmphstr(id, "basic")) {
59✔
740
                                        headerType = BASIC;
9✔
741
                                        realtapeMode = true;
9✔
742
                                } else if (cmphstr(id, "numbers")) {
50✔
743
                                        headerType = NUMBERS;
6✔
744
                                        realtapeMode = true;
6✔
745
                                } else if (cmphstr(id, "chars")) {
44✔
746
                                        headerType = CHARS;
2✔
747
                                        realtapeMode = true;
2✔
748
                                } else if (cmphstr(id, "code")) {
42✔
749
                                        headerType = CODE;
25✔
750
                                        realtapeMode = true;
25✔
751
                                } else if (cmphstr(id, "headless")) {
17✔
752
                                        headerType = HEADLESS;
16✔
753
                                        realtapeMode = true;
16✔
754
                                }
755
                        }
756

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

806
                                                        if (anyComma(lp) && !anyComma(lp) && ParseExpression(lp, val)) {
37✔
807
                                                                if (val < 0) {
36✔
808
                                                                        Error("[SAVETAP] Negative values are not allowed", bp, PASS3); return;
1✔
809
                                                                } else if (val > 0xFFFF) {
35✔
810
                                                                        Error("[SAVETAP] Values higher than FFFFh are not allowed", bp, PASS3); return;
1✔
811
                                                                }
812
                                                                length = val;
34✔
813

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

869
        if (exec) {
34✔
870
                int done = 0;
33✔
871

872
                if (realtapeMode) {
33✔
873
                        done = TAP_SaveBlock(fnaam, headerType, fnaamh.c_str(), start, length, param2, param3);
32✔
874
                } else {
875
                        if (!IsZXSpectrumDevice(DeviceID)) {
1✔
876
                                Error("[SAVETAP snapshot] Device is not of ZX Spectrum type.", Device->ID, SUPPRESS);
1✔
877
                        } else {
878
                                done = TAP_SaveSnapshot(fnaam, start);
×
879
                        }
880
                }
881

882
                if (!done) {
33✔
883
                        Error("[SAVETAP] Error writing file", bp, IF_FIRST);
1✔
884
                }
885
        }
886
}
94✔
887

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

925
        if (exec && !SaveBinary(fnaam, start, length)) {
50✔
926
                Error("[SAVEBIN] Error writing file (Disk full?)", bp, IF_FIRST);
×
927
        }
928
}
77✔
929

930
static void dirSAVEDEV() {
90✔
931
        bool exec = DeviceID && LASTPASS == pass;
90✔
932
        if (!exec && LASTPASS == pass) Error("SAVEDEV only allowed in real device emulation mode (See DEVICE)");
90✔
933

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

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

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

1028
static void dirSAVEHOB() {
15✔
1029
        if (!DeviceID || pass != LASTPASS) {
15✔
1030
                if (!DeviceID) Error("SAVEHOB only allowed in real device emulation mode (See DEVICE)");
11✔
1031
                SkipToEol(lp);
11✔
1032
                return;
14✔
1033
        }
1034
        aint val;
1035
        int start = -1,length = -1;
4✔
1036
        bool exec = true;
4✔
1037

1038
        const std::filesystem::path fnaam = GetOutputFileName(lp);
4✔
1039
        std::string fnaamh {""};
4✔
1040
        if (anyComma(lp)) {
4✔
1041
                if (!anyComma(lp)) {
3✔
1042
                        fnaamh = GetDelimitedString(lp);
2✔
1043
                        if (fnaamh.empty()) {
2✔
1044
                                Error("[SAVEHOB] Syntax error", bp, PASS3); return;
1✔
1045
                        }
1046
                } else {
1047
                          Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return;
1✔
1048
                }
1049
        } else {
1050
                Error("[SAVEHOB] Syntax error. No parameters", bp, PASS3); return; //is this ok?
1✔
1051
        }
1052

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

1084
static void dirEMPTYTRD() {
39✔
1085
        if (pass != LASTPASS) {
39✔
1086
                SkipToEol(lp);
26✔
1087
                return;
28✔
1088
        }
1089
        char diskLabel[9] = "        ";
13✔
1090

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

1110
static void dirSAVETRD() {
591✔
1111
        if (!DeviceID || pass != LASTPASS) {
591✔
1112
                if (!DeviceID) Error("SAVETRD only allowed in real device emulation mode (See DEVICE)");
395✔
1113
                SkipToEol(lp);
395✔
1114
                return;
402✔
1115
        }
1116

1117
        bool exec = true, replace = false, addplace = false;
196✔
1118
        aint val;
1119
        int start = -1, length = -1, autostart = -1, lengthMinusVars = -1;
196✔
1120

1121
        const std::filesystem::path fnaam = GetOutputFileName(lp);
196✔
1122

1123
        std::string fnaamh {""};
196✔
1124
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No parameters", bp, SUPPRESS);
196✔
1125
        if (!anyComma(lp)) {
196✔
1126
                if ((replace = ('|' == *lp))) SkipBlanks(++lp);        // detect "|" for "replace" feature
195✔
1127
                else if ((addplace = ('&' == *lp))) SkipBlanks(++lp); // detect "&" for "addplace" feature
188✔
1128
                fnaamh = GetDelimitedString(lp);
195✔
1129
        }
1130
        if (fnaamh.empty()) return Error("[SAVETRD] No filename", bp, SUPPRESS);
196✔
1131

1132
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No address", bp, SUPPRESS);
194✔
1133
        if (ParseExpressionNoSyntaxError(lp, val) && 0 <= val && val <= 0xFFFF) {
193✔
1134
                start = val;
192✔
1135
        }        // else start == -1 (syntax error or invalid value)
1136
        if (!anyComma(lp)) return Error("[SAVETRD] Syntax error. No length", bp, SUPPRESS);
193✔
1137
        ParseExpressionNoSyntaxError(lp, length);        // parse error/invalid value will be reported later
192✔
1138

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

1160
        if (exec) TRD_AddFile(fnaam, fnaamh.c_str(), start, length, autostart, replace, addplace, lengthMinusVars);
189✔
1161
}
203✔
1162

1163
static void dirENCODING() {
30✔
1164
        auto arg = GetDelimitedString(lp);
30✔
1165
        char* aP = arg.data();
30✔
1166
        if (cmphstr(aP, "dos")) {
30✔
1167
                ConvertEncoding = ENCDOS;
6✔
1168
        } else if (cmphstr(aP, "win")) {
24✔
1169
                ConvertEncoding = ENCWIN;
6✔
1170
        } else {
1171
                Error("[ENCODING] Invalid argument (valid values: \"dos\" and \"win\")", aP, IF_FIRST);
18✔
1172
        }
1173
}
30✔
1174

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

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

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

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

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

1319
/*void dirTEXTAREA() {
1320

1321
}*/
1322

1323
// error message templates for IF**some** directives
1324
constexpr static size_t dirIfErrorsN = 2, dirIfErrorsSZ = 48;
1325
const static char dirIfErrorsTxtSrc[dirIfErrorsN][dirIfErrorsSZ] = {
1326
        { "[%s] No ENDIF" },
1327
        { "[%s] one ELSE only expected" }
1328
};
1329

1330
// IF and IFN internal helper, to evaluate expression
1331
static bool dirIfIfn(aint & val) {
23,111✔
1332
        IsLabelNotFound = false;
23,111✔
1333
        if (!ParseExpression(lp, val)) {
23,111✔
1334
                Error("[IF/IFN] Syntax error", lp, IF_FIRST);
9✔
1335
                return false;
9✔
1336
        }
1337
        if (IsLabelNotFound) {
23,102✔
1338
                WarningById(W_FWD_REF, bp, W_EARLY);
13✔
1339
        }
1340
        return true;
23,102✔
1341
}
1342

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

1381
static void dirIF() {
20,669✔
1382
        aint val;
1383
        if (dirIfIfn(val)) dirIfInternal("IF", val);
20,669✔
1384
}
20,669✔
1385

1386
static void dirIFN() {
2,346✔
1387
        aint val;
1388
        if (dirIfIfn(val)) dirIfInternal("IFN", !val);
2,346✔
1389
}
2,346✔
1390

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

1413
static void dirIFUSED() {
81✔
1414
        char* id;
1415
        if (dirIfusedIfnused(id)) dirIfInternal("IFUSED", LabelTable.IsUsed(id));
81✔
1416
        if (id) free(id);
81✔
1417
}
81✔
1418

1419
static void dirIFNUSED() {
39✔
1420
        char* id;
1421
        if (dirIfusedIfnused(id)) dirIfInternal("IFNUSED", !LabelTable.IsUsed(id));
39✔
1422
        if (id) free(id);
39✔
1423
}
39✔
1424

1425
static void dirIFDEF() {
114✔
1426
        char* id;
1427
        if ((id = GetID(lp)) && *id) {
114✔
1428
                dirIfInternal("IFDEF", DefineTable.FindDuplicate(id));
111✔
1429
        } else {
1430
                Error("[IFDEF] Illegal identifier", bp);
3✔
1431
        }
1432
}
114✔
1433

1434
static void dirIFNDEF() {
915✔
1435
        char* id;
1436
        if ((id = GetID(lp)) && *id) {
915✔
1437
                dirIfInternal("IFNDEF", !DefineTable.FindDuplicate(id));
912✔
1438
        } else {
1439
                Error("[IFNDEF] Illegal identifier", bp);
3✔
1440
        }
1441
}
915✔
1442

1443
static void dirElseCheckLiveDup() {
12✔
1444
        if (RepeatStack.empty()) return;
12✔
1445
        if (!RepeatStack.top().IsInWork) return;
3✔
1446

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

1455
static void dirELSE() {
6✔
1456
        dirElseCheckLiveDup();
6✔
1457
        Error("ELSE without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
6✔
1458
}
6✔
1459

1460
static void dirELSEIF() {
3✔
1461
        dirElseCheckLiveDup();
3✔
1462
        Error("ELSEIF without IF/IFN");
3✔
1463
}
3✔
1464

1465
static void dirENDIF() {
3✔
1466
        dirElseCheckLiveDup();
3✔
1467
        Error("ENDIF without IF/IFN/IFUSED/IFNUSED/IFDEF/IFNDEF");
3✔
1468
}
3✔
1469

1470
/*void dirENDTEXTAREA() {
1471
  Error("ENDT without TEXTAREA",0);
1472
}*/
1473

1474
static void dirINCLUDE() {
471✔
1475
        fullpath_ref_t fnaam = GetInputFile(lp);
471✔
1476
        if (fnaam.full.has_filename()) {
471✔
1477
                ListFile();
465✔
1478
                IncludeFile(fnaam);
465✔
1479
                donotlist = 1;
465✔
1480
        } else {
1481
                Error("[INCLUDE] empty filename", bp);
6✔
1482
        }
1483
}
471✔
1484

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

1508
static void dirOUTEND()
54✔
1509
{
1510
        if (pass == LASTPASS) CloseDest();
54✔
1511
}
54✔
1512

1513
static void dirTAPOUT()
30✔
1514
{
1515
        aint val;
1516
        const std::filesystem::path fnaam = GetOutputFileName(lp);
30✔
1517
        int tape_flag = 255;
30✔
1518
        if (comma(lp))
30✔
1519
        {
1520
                if (!ParseExpression(lp, val))
12✔
1521
                {
1522
                        Error("[TAPOUT] Missing flagbyte value", bp, PASS3); return;
3✔
1523
                }
1524
                tape_flag = val;
9✔
1525
        }
1526
        if (pass == LASTPASS) OpenTapFile(fnaam, tape_flag);
27✔
1527
}
30✔
1528

1529
static void dirTAPEND()
24✔
1530
{
1531
        // if (!FP_tapout) {Error("TAPEND without TAPOUT", bp, PASS3); return;}
1532
        if (pass == LASTPASS) CloseTapFile();
24✔
1533
}
24✔
1534

1535
static void dirDEFINE() {
300✔
1536
        bool replaceEnabled = ('+' == *lp) ? ++lp, true : false;
300✔
1537
        char* id = GetID(lp);
300✔
1538
        if (nullptr == id) {
300✔
1539
                Error("[DEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1540
                return;
3✔
1541
        }
1542
        if (White(*lp)) ++lp;                // skip one whitespace (not considered part of value) (others are)
297✔
1543
        // but trim trailing spaces of value, if there's eol-comment
1544
        if (eolComment) {
297✔
1545
                char *rtrim = lp + strlen(lp);
6✔
1546
                while (lp < rtrim && ' ' == rtrim[-1]) --rtrim;
48✔
1547
                *rtrim = 0;
6✔
1548
        }
1549

1550
        if (replaceEnabled) {
297✔
1551
                DefineTable.Replace(id, lp);
9✔
1552
        } else {
1553
                DefineTable.Add(id, lp, nullptr);
288✔
1554
        }
1555
        SkipToEol(lp);
297✔
1556
        substitutedLine = line;                // override substituted listing for DEFINE
297✔
1557
}
1558

1559
static void dirUNDEFINE() {
105✔
1560
        char* id;
1561

1562
        if (!(id = GetID(lp)) && *lp != '*') {
105✔
1563
                Error("[UNDEFINE] Illegal <id>", lp, SUPPRESS);
3✔
1564
                return;
3✔
1565
        }
1566

1567
        if (*lp == '*') {
102✔
1568
                ++lp;
3✔
1569
                DefineTable.RemoveAll();
3✔
1570
        } else if (DefineTable.FindDuplicate(id)) {
99✔
1571
                DefineTable.Remove(id);
90✔
1572
        } else {
1573
                Warning("[UNDEFINE] Identifier not found", id);
9✔
1574
        }
1575
}
1576

1577
static void dirEXPORT() {
60✔
1578
        aint val;
1579
        char* n, * p;
1580

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

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

1688
        if (LASTPASS == pass && *e) {
87✔
1689
                _CERR "> " _CMDL Options::tcols->display _CMDL e _CMDL Options::tcols->end _ENDL;
27✔
1690
        }
1691
}
1692

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

1713
static void dirENDS() {
9✔
1714
        Error("[ENDS] End structure without structure");
9✔
1715
}
9✔
1716

1717
static void dirASSERT() {
2,240✔
1718
        char* p = lp;
2,240✔
1719
        aint val;
1720
        if (!ParseExpressionNoSyntaxError(lp, val)) {
2,240✔
1721
                Error("[ASSERT] Syntax error", p, SUPPRESS);
12✔
1722
                return;
12✔
1723
        }
1724
        if (pass == LASTPASS && !val) {
2,228✔
1725
                Error("[ASSERT] Assertion failed", p);
6✔
1726
        }
1727
        if (comma(lp)) SkipToEol(lp);
2,228✔
1728
}
1729

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

1758
static void dirSTRUCT() {
246✔
1759
        CStructure* st;
1760
        int global = 0;
246✔
1761
        aint offset = 0;
246✔
1762
        char* naam;
1763
        SkipBlanks();
246✔
1764
        if (*lp == '@') {
246✔
1765
                ++lp; global = 1;
15✔
1766
        }
1767

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

1807
static void dirFPOS() {
18✔
1808
        aint val;
1809
        int method = SEEK_SET;
18✔
1810
        SkipBlanks(lp);
18✔
1811
        if ((*lp == '+') || (*lp == '-')) {
18✔
1812
                method = SEEK_CUR;
6✔
1813
        }
1814
        if (!ParseExpressionNoSyntaxError(lp, val)) {
18✔
1815
                Error("[FPOS] Syntax error", lp, SUPPRESS);
3✔
1816
        } else if (pass == LASTPASS) {
15✔
1817
                SeekDest(val, method);
5✔
1818
        }
1819
}
18✔
1820

1821
// isWhile == false: DUP/REPT parsing
1822
// isWhile == true: WHILE parsing
1823
static void DupWhileImplementation(bool isWhile) {
4,496✔
1824
        aint val = 0;
4,496✔
1825
        CStringsList* condition = nullptr;
4,496✔
1826

1827
        if (!RepeatStack.empty()) {
4,496✔
1828
                SRepeatStack& dup = RepeatStack.top();
3,941✔
1829
                if (!dup.IsInWork) {
3,941✔
1830
                        SkipToEol(lp);                // Just skip the expression to the end of line, don't evaluate yet
264✔
1831
                        ++dup.Level;
264✔
1832
                        return;
276✔
1833
                }
1834
        }
1835

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

1884
        RepeatStack.emplace(val, condition, new CStringsList(indexVar ? indexVar : ""));
4,220✔
1885
        if (!SkipBlanks()) Error("[DUP] unexpected chars", lp, SUPPRESS);
4,220✔
1886
}
1887

1888
static void dirDUP() {
4,454✔
1889
        DupWhileImplementation(false);
4,454✔
1890
}
4,454✔
1891

1892
static void dirWHILE() {
42✔
1893
        DupWhileImplementation(true);
42✔
1894
}
42✔
1895

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

1924
static void dirEDUP() {
4,490✔
1925
        if (RepeatStack.empty() || RepeatStack.top().IsInWork) {
4,490✔
1926
                Error("[EDUP/ENDR/ENDW] End repeat without repeat");
9✔
1927
                return;
273✔
1928
        }
1929

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

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

1980
static void dirENDM() {
18✔
1981
        if (!RepeatStack.empty()) {
18✔
1982
                Warning("ENDM used as DUP/REPT block terminator, this is deprecated (and bugged when used inside macro), change to EDUP or ENDR");
3✔
1983
                dirEDUP();
3✔
1984
        } else {
1985
                Error("[ENDM] End macro without macro");
15✔
1986
        }
1987
}
18✔
1988

1989
static bool dirDEFARRAY_parseItems(CStringsList** nextPtr) {
204✔
1990
        char ml[LINEMAX];
1991
        do {
1992
                const char* const itemLp = lp;
1,197✔
1993
                char* n = ml;
1,197✔
1994
                if (!GetMacroArgumentValue(lp, n)) {
1,197✔
1995
                        Error("[DEFARRAY] Syntax error", itemLp, SUPPRESS);
18✔
1996
                        return false;
18✔
1997
                }
1998
                *nextPtr = new CStringsList(ml);
1,179✔
1999
                if ((*nextPtr)->string == NULL) ErrorOOM();
1,179✔
2000
                nextPtr = &((*nextPtr)->next);
1,179✔
2001
        } while (anyComma(lp));
1,179✔
2002
        return SkipBlanks();
186✔
2003
}
2004

2005
static void dirDEFARRAY_add(const char* id) {
21✔
2006
        DefineTable.Get(id);
21✔
2007
        if (NULL == DefineTable.DefArrayList) {
21✔
2008
                Error("[DEFARRAY+] unknown array <id>", id);
6✔
2009
                SkipToEol(lp);
6✔
2010
                return;
6✔
2011
        }
2012
        // array was already defined, seek to the last item in the list
2013
        while (DefineTable.DefArrayList->next) DefineTable.DefArrayList = DefineTable.DefArrayList->next;
84✔
2014
        dirDEFARRAY_parseItems(&DefineTable.DefArrayList->next);
15✔
2015
        return;
15✔
2016
}
2017

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

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

2046
static void dirDEFDEVICE() {
53✔
2047
        //DEFDEVICE <deviceid>, <slot_size>, <page_count>[, <slot_0_initial_page>[, ...]]
2048
        const char* id = GetID(lp);
53✔
2049
        if (!id) {
53✔
2050
                Error(DEFDEVICE_SYNTAX_ERR, bp, SUPPRESS);
3✔
2051
                return;
45✔
2052
        }
2053

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

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

2084
        // init "initialPages array by going 0, 1, 2, ..., page_count-1, page_count-1, ... or parsed explicit values
2085
        CDeviceDef & dev = *DefDevices.back();
8✔
2086
        int previous_page = -1;
8✔
2087
        for (int32_t i = 0; i < dev.SlotsCount; ++i) {
298✔
2088
                if (0 <= initial_pages[i] && initial_pages[i] < dev.PagesCount) {
290✔
2089
                        previous_page = initial_pages[i];
19✔
2090
                } else {
2091
                        if (-1 != initial_pages[i]) ErrorInt("[DEFDEVICE] invalid initial page", initial_pages[i], EARLY);
271✔
2092
                        if (previous_page < dev.PagesCount - 1) ++previous_page;
271✔
2093
                }
2094
                dev.initialPages[i] = previous_page;
290✔
2095
        }
2096
}
2097

2098
static void dirDEVICE() {
869✔
2099
        // refresh source position of first DEVICE directive
2100
        if (1 == ++deviceDirectivesCount) {
869✔
2101
                assert(!sourcePosStack.empty());
467✔
2102
                globalDeviceSourcePos = sourcePosStack.back();
467✔
2103
        }
2104

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

2127
static void dirSLDOPT() {
21✔
2128
        SkipBlanks(lp);
21✔
2129
        if (cmphstr(lp, "COMMENT")) {
21✔
2130
                do {
2131
                        SldAddCommentKeyword(GetID(lp));
21✔
2132
                } while (!SkipBlanks(lp) && anyComma(lp));
21✔
2133
        } else {
2134
                Error("[SLDOPT] Syntax error in <type> (valid is only COMMENT)", lp, SUPPRESS);
6✔
2135
        }
2136
}
21✔
2137

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

2241
        DirectivesTable.insertd(".device", dirDEVICE);
505✔
2242
        DirectivesTable.insertd(".defdevice", dirDEFDEVICE);
505✔
2243

2244
        DirectivesTable.insertd(".bplist", dirBPLIST);
505✔
2245
        DirectivesTable.insertd(".setbreakpoint", dirSETBREAKPOINT);
505✔
2246
        DirectivesTable.insertd(".setbp", dirSETBREAKPOINT);
505✔
2247

2248
        DirectivesTable.insertd(".relocate_start", Relocation::dirRELOCATE_START);
505✔
2249
        DirectivesTable.insertd(".relocate_end", Relocation::dirRELOCATE_END);
505✔
2250
        DirectivesTable.insertd(".relocate_table", Relocation::dirRELOCATE_TABLE);
505✔
2251

2252
        DirectivesTable.insertd(".sldopt", dirSLDOPT);
505✔
2253

2254
#ifdef USE_LUA
2255
        DirectivesTable.insertd(".lua", dirLUA);
505✔
2256
        DirectivesTable.insertd(".endlua", dirENDLUA);
505✔
2257
        DirectivesTable.insertd(".includelua", dirINCLUDELUA);
505✔
2258
#endif //USE_LUA
2259

2260
        DirectivesTable_dup.insertd(".dup", dirDUP);
505✔
2261
        DirectivesTable_dup.insertd(".edup", dirEDUP);
505✔
2262
        DirectivesTable_dup.insertd(".endm", dirENDM);
505✔
2263
        DirectivesTable_dup.insertd(".endr", dirEDUP);
505✔
2264
        DirectivesTable_dup.insertd(".endw", dirEDUP);
505✔
2265
        DirectivesTable_dup.insertd(".rept", dirDUP);
505✔
2266
        DirectivesTable_dup.insertd(".while", dirWHILE);
505✔
2267
}
505✔
2268

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