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

z00m128 / sjasmplus / 1580

03 Apr 2026 10:56PM UTC coverage: 97.715% (-0.002%) from 97.717%
1580

push

cirrus-ci

ped7g
wip

93 of 95 new or added lines in 6 files covered. (97.89%)

2 existing lines in 1 file now uncovered.

10094 of 10330 relevant lines covered (97.72%)

170816.48 hits per line

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

99.21
/sjasm/parser.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) 2005 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
// parser.cpp
30

31
#include "sjdefs.h"
32

33
static bool synerr = true;        // flag whether ParseExpression should report syntax error with Error()
34

35
static int ParseExpressionEntry(char*& p, aint& nval);
36

37
static int ParseExpPrim(char*& p, aint& nval) {
1,667,687✔
38
        int res = 0;
1,667,687✔
39
        if (SkipBlanks(p)) {
1,667,687✔
40
                return 0;
1,932✔
41
        }
42
        if (*p == '(') {
1,665,755✔
43
                ++p;
22,132✔
44
                res = ParseExpressionEntry(p, nval);
22,132✔
45
                if (!need(p, ')')) {
22,132✔
46
                        Error("')' expected");
15✔
47
                        return 0;
15✔
48
                }
49
        } else if (DeviceID && *p == '{') {                // read WORD/BYTE from virtual device memory
1,643,623✔
50
                char* const readMemP = p;
20,953✔
51
                const int byteOnly = cmphstr(++p, "b");
20,953✔
52
                // switch off alternative relocation evaluation for the address value
53
                const bool oldAreLabelsOffset = Relocation::areLabelsOffset;
20,953✔
54
                Relocation::areLabelsOffset = false;
20,953✔
55
                int addressParseRes = ParseExpressionEntry(p, nval);
20,953✔
56
                Relocation::areLabelsOffset = oldAreLabelsOffset;        // restore alternative evaluation
20,953✔
57
                if (!addressParseRes) return 0;        // some syntax error inside the address expression
20,953✔
58
                if (!need(p, '}')) {
20,944✔
59
                        Error("'}' expected", readMemP, SUPPRESS);
6✔
60
                        return 0;
6✔
61
                }
62
                if (nval < 0 || (0xFFFE + byteOnly) < nval) {
20,938✔
63
                        Error("Address in {..} must fetch bytes from 0x0000..0xFFFF range", readMemP);
18✔
64
                        nval = 0;
18✔
65
                        return 1;                                                // and return zero value as result (avoid "syntax error")
18✔
66
                }
67
                res = int(MemGetByte(nval));
20,920✔
68
                if (!byteOnly) res += int(MemGetByte(nval + 1)) << 8;
20,920✔
69
                nval = res;
20,920✔
70
                return 1;
20,920✔
71
        } else if (isdigit((byte)*p) && GetTemporaryLabelValue(p, nval, true)) {        // temporary label with underscore suffix
1,622,670✔
72
                return 1;
192✔
73
        } else if (isdigit((byte)*p) || (*p == '#' && isalnum((byte)*(p + 1))) || (*p == '$' && isalnum((byte)*(p + 1))) || *p == '%') {
1,622,478✔
74
                return GetConstant(p, nval);
866,655✔
75
        } else if (isLabelStart(p)) {
755,823✔
76
                return GetLabelValue(p, nval);
748,360✔
77
        } else if (*p == '?' && isLabelStart(p+1)) {
7,463✔
78
                // this is undocumented "?<symbol>" operator, seems as workaround for labels like "not"
79
                // This is deprecated and will be removed in v2.x of sjasmplus
80
                // (where keywords will be reserved and such label would be invalid any way)
81
                Warning("?<symbol> operator is deprecated and will be removed in v2.x", p);
9✔
82
                ++p;
9✔
83
                return GetLabelValue(p, nval);
9✔
84
        } else if (DISP_NONE != PseudoORG && '$' == p[0] && '$' == p[1] && '$' == p[2]) {
7,454✔
85
                if ('$' == p[3]) {                // "$$$$" operator to get physical memory page inside DISP block
24✔
86
                        p += 4;
12✔
87
                        nval = DeviceID ? Page->Number : LABEL_PAGE_UNDEFINED;
12✔
88
                        return 1;
12✔
89
                }
90
                // "$$$" operator to get physical address inside DISP block
91
                p += 3;
12✔
92
                nval = adrdisp;                // this is never affected by relocation
12✔
93
                return 1;
12✔
94
        } else if (DeviceID && *p == '$' && *(p + 1) == '$') {
7,430✔
95
                p += 2;
349✔
96
                if (isLabelStart(p)) return GetLabelPage(p, nval);
349✔
97
                if (DISP_NONE != PseudoORG && LABEL_PAGE_UNDEFINED != dispPageNum) {
235✔
98
                        // enforce explicit request of fake DISP page
99
                        nval = dispPageNum;
3✔
100
                } else {
101
                        // current page
102
                        nval = Page->Number;
232✔
103
                }
104
                return 1;
235✔
105
        } else if (*p == '$') {
7,081✔
106
                ++p;
1,709✔
107
                nval = CurAddress;
1,709✔
108
                if (Relocation::type && Relocation::areLabelsOffset && DISP_INSIDE_RELOCATE != PseudoORG) {
1,709✔
109
                        nval += Relocation::alternative_offset;
48✔
110
                }
111
                return 1;
1,709✔
112
        } else if (!(res = GetCharConst(p, nval))) {
5,372✔
113
                if (synerr) Error("Syntax error", p, IF_FIRST);
1,648✔
114
                return 0;
1,648✔
115
        }
116
        return res;
25,841✔
117
}
118

119
static int ParseExpUnair(char*& p, aint& nval) {
1,702,923✔
120
        SkipBlanks(p);
1,702,923✔
121
        char* oldP = p;
1,702,923✔
122
        if (cmphstr(p, "norel", true)) {
1,702,923✔
123
                // switch off alternative relocation evaluation for the following part of expression
124
                const bool oldAreLabelsOffset = Relocation::areLabelsOffset;
114✔
125
                Relocation::areLabelsOffset = false;
114✔
126
                int norelParseRes = ParseExpPrim(p, nval);                        // higher priority than other unary
114✔
127
                Relocation::areLabelsOffset = oldAreLabelsOffset;        // restore alternative evaluation
114✔
128
                if (norelParseRes) return 1;
114✔
129
                // "norel" operator didn't parse successfully, try to ignore it (will treat it as label)
130
                p = oldP;
6✔
131
        }
132
        if (cmphstr(p, "exist", true)) {
1,702,815✔
133
                int existEval = 0, hasParentheses = need(p, '(');
180✔
134
                if (hasParentheses || isLabelStart(p)) {
180✔
135
                        existEval = LabelExist(p, nval);
171✔
136
                }
137
                if (existEval && hasParentheses) {
180✔
138
                        existEval = need(p, ')');                                                // check closing parenthesis
21✔
139
                }
140
                if (existEval) return 1;
180✔
141
                p = oldP;
24✔
142
        }
143
        if (cmphstr(p, "sizeof", true)) {
1,702,659✔
144
                int sizeofEval = 0, hasParentheses = need(p, '(');
126✔
145
                if (hasParentheses || isLabelStart(p)) {
126✔
146
                        sizeofEval = GetLabelSize(p, nval);
126✔
147
                }
148
                if (hasParentheses) {
126✔
149
                        sizeofEval &= need(p, ')');
126✔
150
                }
151
                if (sizeofEval) return 1;        // valid label name (and valid optional parentheses)
126✔
NEW
152
                p = oldP;                                        // invalid label name or parentheses, try to ignore "sizeof"
×
153
        }
154
        aint right;
155
        int oper;
156
        if ((oper = need(p, "! ~ + - ")) || \
1,702,533✔
157
                (oper = needa(p, "not", '!', "low", 'l', "high", 'h', true)) || \
3,370,154✔
158
                (oper = needa(p, "abs", 'a', nullptr, 0, nullptr, 0, true)) ) {
1,667,621✔
159
                switch (oper) {
34,960✔
160
                case '!':
208✔
161
                        if (!ParseExpUnair(p, right)) return 0;
208✔
162
                        nval = -!right;
150✔
163
                        break;
150✔
164
                case '~':
18✔
165
                        if (!ParseExpUnair(p, right)) return 0;
18✔
166
                        nval = ~right;
9✔
167
                        break;
9✔
168
                case '+':
10,842✔
169
                        if (!ParseExpUnair(p, right)) return 0;
10,842✔
170
                        nval = right;
10,821✔
171
                        break;
10,821✔
172
                case '-':
1,726✔
173
                        if (!ParseExpUnair(p, right)) return 0;
1,726✔
174
                        nval = ~right + 1;
1,723✔
175
                        break;
1,723✔
176
                case 'l':
15,857✔
177
                        if (!ParseExpUnair(p, right)) return 0;
15,857✔
178
                        nval = right & 255;
15,854✔
179
                        break;
15,854✔
180
                case 'h':
6,261✔
181
                        if (!ParseExpUnair(p, right)) return 0;
6,261✔
182
                        nval = (right >> 8) & 255;
6,258✔
183
                        break;
6,258✔
184
                case 'a':
48✔
185
                        if (!ParseExpUnair(p, right)) return 0;
48✔
186
                        nval = abs(right);
48✔
187
                        break;
48✔
188
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
189
                }
190
                return 1;
34,863✔
191
        } else {
192
                return ParseExpPrim(p, nval);
1,667,573✔
193
        }
194
}
195

196
static int ParseExpMul(char*& p, aint& nval) {
1,664,081✔
197
        aint left, right;
198
        int oper;
199
        if (!ParseExpUnair(p, left)) return 0;
1,664,081✔
200
        while ((oper = need(p, "* / % ")) || (oper = needa(p, "mod", '%'))) {
1,664,097✔
201
                if (!ParseExpUnair(p, right)) return 0;
3,882✔
202
                switch (oper) {
3,870✔
203
                case '*':
1,215✔
204
                        left *= right; break;
1,215✔
205
                case '/':
237✔
206
                        left = right ? left / right : 0;
237✔
207
                        if (!right) Error("Division by zero");
237✔
208
                        break;
237✔
209
                case '%':
2,418✔
210
                        left = right ? left % right : 0;
2,418✔
211
                        if (!right) Error("Division by zero");
2,418✔
212
                        break;
2,418✔
213
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
214
                }
215
        }
216
        nval = left;
1,660,215✔
217
        return 1;
1,660,215✔
218
}
219

220
static int ParseExpAdd(char*& p, aint& nval) {
1,335,497✔
221
        aint left, right;
222
        int oper;
223
        if (!ParseExpMul(p, left)) return 0;
1,335,497✔
224
        while ((oper = need(p, "+ - "))) {
1,660,215✔
225
                if (!ParseExpMul(p, right)) return 0;
328,584✔
226
                if ('-' == oper) right = -right;
328,568✔
227
                left += right;
328,568✔
228
        }
229
        nval = left;
1,331,631✔
230
        return 1;
1,331,631✔
231
}
232

233
static int ParseExpShift(char*& p, aint& nval) {
1,315,939✔
234
        aint left, right;
235
        uint32_t l;
236
        int oper;
237
        if (!ParseExpAdd(p, left)) return 0;
1,315,939✔
238
        while ((oper = need(p, "<<>>")) || (oper = needa(p, "shl", '<' + '<', "shr", '>' + '>'))) {
1,331,631✔
239
                if (oper == '>' + '>' && *p == '>') {
19,558✔
240
                        ++p;
15✔
241
                        oper += '>';
15✔
242
                }
243
                if (!ParseExpAdd(p, right)) return 0;
19,558✔
244
                switch (oper) {
19,543✔
245
                case '<'+'<':
972✔
246
                        left <<= right; break;
972✔
247
                case '>'+'>':
18,559✔
248
                        left >>= right; break;
18,559✔
249
                case '>'+'>'+'>':
12✔
250
                        l = left; l >>= right; left = l; break;
12✔
251
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
252
                }
253
        }
254
        nval = left;
1,312,073✔
255
        return 1;
1,312,073✔
256
}
257

258
static int ParseExpMinMax(char*& p, aint& nval) {
1,315,906✔
259
        aint left, right;
260
        int oper;
261
        if (!ParseExpShift(p, left)) return 0;
1,315,906✔
262
        while ((oper = need(p, "<?>?"))) {
1,312,073✔
263
                if (!ParseExpShift(p, right)) return 0;
33✔
264
                switch (oper) {
27✔
265
                case '<'+'?':
9✔
266
                        left = left < right ? left : right; break;
9✔
267
                case '>'+'?':
18✔
268
                        left = left > right ? left : right; break;
18✔
269
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
270
                }
271
        }
272
        nval = left;
1,312,040✔
273
        return 1;
1,312,040✔
274
}
275

276
static int ParseExpCmp(char*& p, aint& nval) {
1,014,843✔
277
        aint left, right;
278
        int oper;
279
        if (!ParseExpMinMax(p, left)) return 0;
1,014,843✔
280
        while ((oper = need(p, "<=>=< > "))) {
1,312,040✔
281
                if (!ParseExpMinMax(p, right)) return 0;
301,063✔
282
                switch (oper) {
301,051✔
283
                case '<':
747✔
284
                        left = -(left < right); break;
747✔
285
                case '>':
33✔
286
                        left = -(left > right); break;
33✔
287
                case '<'+'=':
300,220✔
288
                        left = -(left <= right); break;
300,220✔
289
                case '>'+'=':
51✔
290
                        left = -(left >= right); break;
51✔
291
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
292
                }
293
        }
294
        nval = left;
1,010,977✔
295
        return 1;
1,010,977✔
296
}
297

298
static int ParseExpEqu(char*& p, aint& nval) {
1,010,672✔
299
        aint left, right;
300
        int oper;
301
        if (!ParseExpCmp(p, left)) return 0;
1,010,672✔
302
        while ((oper = need(p, "=_==!="))) {
1,010,977✔
303
                if (!ParseExpCmp(p, right)) return 0;
4,171✔
304
                left = (('!'+'=') == oper) ? -(left != right) : -(left == right);
4,162✔
305
        }
306
        nval = left;
1,006,806✔
307
        return 1;
1,006,806✔
308
}
309

310
static int ParseExpBitAnd(char*& p, aint& nval) {
991,017✔
311
        aint left, right;
312
        if (!ParseExpEqu(p, left)) return 0;
991,017✔
313
        while (need(p, "&_") || needa(p, "and", '&')) {
1,006,806✔
314
                if (!ParseExpEqu(p, right)) return 0;
19,655✔
315
                left &= right;
19,649✔
316
        }
317
        nval = left;
987,151✔
318
        return 1;
987,151✔
319
}
320

321
static int ParseExpBitXor(char*& p, aint& nval) {
990,872✔
322
        aint left, right;
323
        if (!ParseExpBitAnd(p, left)) return 0;
990,872✔
324
        while (need(p, "^ ") || needa(p, "xor", '^')) {
987,151✔
325
                if (!ParseExpBitAnd(p, right)) return 0;
145✔
326
                left ^= right;
139✔
327
        }
328
        nval = left;
987,006✔
329
        return 1;
987,006✔
330
}
331

332
static int ParseExpBitOr(char*& p, aint& nval) {
990,735✔
333
        aint left, right;
334
        if (!ParseExpBitXor(p, left)) return 0;
990,735✔
335
        while (need(p, "|_") || needa(p, "or", '|')) {
987,006✔
336
                if (!ParseExpBitXor(p, right)) return 0;
137✔
337
                left |= right;
131✔
338
        }
339
        nval = left;
986,869✔
340
        return 1;
986,869✔
341
}
342

343
static int ParseExpLogAnd(char*& p, aint& nval) {
989,660✔
344
        aint left, right;
345
        if (!ParseExpBitOr(p, left)) return 0;
989,660✔
346
        while (need(p, "&&")) {
986,869✔
347
                if (!ParseExpBitOr(p, right)) return 0;
1,075✔
348
                left = -(left && right);
1,069✔
349
        }
350
        nval = left;
985,794✔
351
        return 1;
985,794✔
352
}
353

354
static int ParseExpLogOr(char*& p, aint& nval) {
989,627✔
355
        aint left, right;
356
        if (!ParseExpLogAnd(p, left)) return 0;
989,627✔
357
        while (need(p, "||")) {
985,794✔
358
                if (!ParseExpLogAnd(p, right)) return 0;
33✔
359
                left = -(left || right);
30✔
360
        }
361
        nval = left;
985,761✔
362
        return 1;
985,761✔
363
}
364

365
static int ParseExpressionEntry(char*& p, aint& nval) {
989,627✔
366
        if (ParseExpLogOr(p, nval)) return 1;
989,627✔
367
        nval = 0;
3,866✔
368
        return 0;
3,866✔
369
}
370

371
int ParseExpression(char*& p, aint& nval) {
931,459✔
372
        // if relocation is active, do the full evaluation in syntax-error-OFF mode with alternative
373
        // label values, and remember the result (to compare it with regular evaluation afterward)
374
        aint relocationVal = 0;
931,459✔
375
        int relocationRes = 0;
931,459✔
376
        if (Relocation::type) {
931,459✔
377
                char* altP = p;
14,978✔
378
                bool osynerr = synerr;
14,978✔
379
                synerr = false;
14,978✔
380
                Relocation::areLabelsOffset = true;
14,978✔
381
                relocationRes = ParseExpressionEntry(altP, relocationVal);
14,978✔
382
                Relocation::areLabelsOffset = false;
14,978✔
383
                synerr = osynerr;
14,978✔
384
        }
385
        // if relocation is off, or the alternative run did finish already, do regular evaluation
386
        int res = ParseExpressionEntry(p, nval);
931,459✔
387
        // set the Relocation::isResultAffected if the two alternative results are different
388
        if (res && relocationRes) {
931,459✔
389
                const bool isAffected = (relocationVal != nval);
14,621✔
390
                Relocation::isResultAffected |= isAffected;
14,621✔
391
                Relocation::deltaType =
14,621✔
392
                        (Relocation::alternative_offset == (relocationVal - nval)) ? Relocation::REGULAR :
26,096✔
393
                        ((Relocation::HIGH == Relocation::type) && ((Relocation::alternative_offset >> 8) == (relocationVal - nval))) ? Relocation::HIGH :
11,475✔
394
                        Relocation::OFF;
395
        }
396
        return res;
931,459✔
397
}
398

399
int ParseExpressionNoSyntaxError(char*& lp, aint& val) {
836,707✔
400
        bool osynerr = synerr;
836,707✔
401
        synerr = false;
836,707✔
402
        int ret_val = ParseExpression(lp, val);
836,707✔
403
        synerr = osynerr;
836,707✔
404
        return ret_val;
836,707✔
405
}
406

407
static int ParseExpressionInSubstitution(char *& lp, aint& val) {
15,632✔
408
        assert(!IsSubstituting);
15,632✔
409
        IsSubstituting = true;
15,632✔
410
        int ret_val = ParseExpressionNoSyntaxError(lp, val);
15,632✔
411
        IsSubstituting = false;
15,632✔
412
        return ret_val;
15,632✔
413
}
414

415
// returns 0 on syntax error, 1 on expression which is not enclosed in parentheses
416
// 2 when whole expression is in [] or () (--syntax=b/B affects when "2" is reported)
417
int ParseExpressionMemAccess(char*& p, aint& nval) {
13,567✔
418
        const EBracketType bt = OpenBracket(p);
13,567✔
419
        // if round parenthesis starts the expression, calculate pointer where it ends (and move "p" back on "(")
420
        char* const expectedEndBracket = (BT_ROUND == bt) ? ParenthesesEnd(--p) : nullptr;
13,567✔
421
        if (!ParseExpression(p, nval)) return 0;        // evaluate expression
13,567✔
422
        if (BT_NONE == bt) return 1;                                // no parentheses are always "value"
13,534✔
423
        if (BT_ROUND == bt) return (expectedEndBracket == p) ? 2 : 1;        // round parentheses are "memory" when end is as expected
3,846✔
424
        if (CloseBracket(p)) return 2;                                // square brackets must be closed properly, then it is "memory"
300✔
425
        return 0;        // curly brackets are not detect by OpenBracket, but if they would, it would work same as square here
18✔
426
}
427

428
void ParseAlignArguments(char* & src, aint & alignment, aint & fill) {
462✔
429
        SkipBlanks(src);
462✔
430
        const char * const oldSrc = src;
462✔
431
        fill = -1;
462✔
432
        if (!ParseExpression(src, alignment)) {
462✔
433
                alignment = -1;
42✔
434
                return;
42✔
435
        }
436
        if (Relocation::type) {
420✔
437
                WarningById(W_RELOCATABLE_ALIGN);
24✔
438
        }
439
        // check if alignment value is power of two (0..15-th power only)
440
        if (alignment < 1 || (1<<15) < alignment || (alignment & (alignment-1))) {
420✔
441
                Error("[ALIGN] Illegal align", oldSrc, SUPPRESS);
15✔
442
                alignment = 0;
15✔
443
                return;
15✔
444
        }
445
        if (!comma(src)) return;
405✔
446
        if (!ParseExpressionEntry(lp, fill)) {
105✔
447
                Error("[ALIGN] fill-byte expected after comma", bp, IF_FIRST);
3✔
448
                fill = -1;
3✔
449
        } else if (fill < 0 || 255 < fill) {
102✔
450
                Error("[ALIGN] Illegal align fill-byte", oldSrc, SUPPRESS);
6✔
451
                fill = -1;
6✔
452
        }
453
}
454

455
static constexpr const char GLUE_TAG = 26;                // "substitute" in utf8, kinda fits the purpose?
456
static constexpr const char GLUE_CHAR = '_';
457

458
static bool ReplaceDefineInternal(char* lp, char* const nl) {
728,525✔
459
        int definegereplaced = 0,dr;
728,525✔
460
        char* rp = nl,* nid;
728,525✔
461
        const char* ver;
462
        bool isDefDir = false;        // to remember if one of DEFINE-related directives was used
728,525✔
463
        bool afterNonAlphaNum, afterNonAlphaNumNext = true, glueDetected = false;
728,525✔
464
        char defarrayCountTxt[16] = { 0 };
728,525✔
465
        while (*lp && ((rp - nl) < LINEMAX)) {
7,767,154✔
466
                glueDetected |= (GLUE_TAG == *lp);                // there is already some glue in source line
7,116,955✔
467
                const char c1 = lp[0], c2 = lp[1];
7,116,955✔
468
                afterNonAlphaNum = afterNonAlphaNumNext;
7,116,955✔
469
                afterNonAlphaNumNext = !isalnum((byte)c1);
7,116,955✔
470
                if (c1 == '/' && c2 == '*') {        // block-comment local beginning (++block_nesting)
7,116,955✔
471
                        lp += 2;
318✔
472
                        ++comlin;
318✔
473
                        continue;
5,705,780✔
474
                }
475
                if (comlin) {
7,116,637✔
476
                        if (c1 == '*' && c2 == '/') {
6,681✔
477
                                lp += 2;
312✔
478
                                // insert space into line, if the block ending may have affected parsing of line
479
                                if (1 == comlin) {
312✔
480
                                        *rp++ = ' ';                // ^^ otherwise this line is completely commented out
288✔
481
                                }
482
                                --comlin;        // decrement block comment counter
312✔
483
                        } else {
484
                                ++lp;                // just skip all characters inside comment block
6,369✔
485
                        }
486
                        continue;
6,681✔
487
                }
488
                // for following code (0 == comlin) (unless it has its own parse loop)
489

490
                // single line comments -> finish
491
                if (c1 == ';' || (c1 == '/' && c2 == '/')) {
7,109,956✔
492
                        // set empty eol line comment, if the source of data is still the original "line" buffer
493
                        if (!eolComment && line <= lp && lp < line+LINEMAX) eolComment = lp;
78,326✔
494
                        break;
78,326✔
495
                }
496

497
                // strings parsing
498
                if (afterNonAlphaNum && (c1 == '"' || c1 == '\'')) {
7,031,630✔
499
                        *rp++ = *lp++;                                // copy the string delimiter (" or ')
37,524✔
500
                        // apostrophe inside apostrophes ('') will parse as end + start of another string
501
                        // which sort of "accidentally" leads to correct final results
502
                        while (*lp && c1 != *lp) {        // until end of current string is reached (or line ends)
164,771✔
503
                                // inside double quotes the backslash should escape (anything after it)
504
                                if ('"' == c1 && '\\' == *lp && lp[1]) *rp++ = *lp++;        // copy escaping backslash extra
127,247✔
505
                                *rp++ = *lp++;                        // copy string character
127,247✔
506
                        }
507
                        if (*lp) *rp++ = *lp++;                // copy the ending string delimiter (" or ')
37,524✔
508
                        continue;
37,524✔
509
                }
510

511
                if (!isLabelStart(lp, false)) {
6,994,106✔
512
                        *rp++ = *lp++;
5,661,257✔
513
                        continue;
5,661,257✔
514
                }
515

516
                // update "is define-related directive" for remainder of the line
517
                char* kp = lp;
1,332,849✔
518
                isDefDir |= afterNonAlphaNum && (cmphstr(kp, "define+") || cmphstr(kp, "define")
2,646,769✔
519
                        || cmphstr(kp, "undefine") || cmphstr(kp, "defarray+") || cmphstr(kp, "defarray")
1,313,920✔
520
                        || cmphstr(kp, "ifdef") || cmphstr(kp, "ifndef"));
1,313,542✔
521
                // if DEFINE-related directive was used, only macro-arguments are substituted
522
                // in the remaining part of the line, the define-based substitution is inhibited till EOL
523

524
                // The following loop is recursive-like macro/define substitution, the `*lp` here points
525
                // at alphabet/underscore char, marking start of "id" string, and it will be parsed by
526
                // sub-id parts, delimited by underscores, each combination of consecutive sub-ids may
527
                // be substituted by some macro argument or define.
528

529
                //TODO - maybe consider the substitution search to go downward, from longest term to shortest subterm
530
                ResetGrowSubId();
1,332,849✔
531
                char* nextSubIdLp = lp, * wholeIdLp = lp;
1,332,849✔
532
                do { //while(islabchar(*lp));
533
                        nid = GrowSubId(lp);                // grow the current sub-id part by part, checking each combination for substitution
2,091,791✔
534
                        // defines/macro arguments can substitute in the middle of ID only if they don't start with underscore
535
                        const bool canSubstituteInside = '_' != nid[0] || nextSubIdLp == wholeIdLp;
2,091,791✔
536
                        if (macrolabp && canSubstituteInside && (ver = MacroDefineTable.getverv(nid))) {
2,091,791✔
537
                                dr = 2;                        // macro argument substitution is possible
4,710✔
538
                        } else if (!isDefDir && canSubstituteInside && (ver = DefineTable.Get(nid))) {
2,087,081✔
539
                                dr = 1;                        // DEFINE substitution is possible
32,355✔
540
                                //handle DEFARRAY case
541
                                if (DefineTable.DefArrayList) {
32,355✔
542
                                        ver = nid;        // in case of some error, just copy the array id "as is"
15,650✔
543
                                        CStringsList* a = DefineTable.DefArrayList;
15,650✔
544
                                        while (White(*lp)) GrowSubIdByExtraChar(lp);
15,650✔
545
                                        aint val;
546
                                        if ('[' != *lp) Error("[ARRAY] Expression error", nextSubIdLp, SUPPRESS);
15,650✔
547
                                        if ('[' == *lp && '#' == lp[1] && ']' == lp[2]) {        // calculate size of defarray
15,650✔
548
                                                lp += 3;
15✔
549
                                                val = 0;
15✔
550
                                                while (a) {
87✔
551
                                                        ++val;
72✔
552
                                                        a = a->next;
72✔
553
                                                }
554
                                                SPRINTF1(defarrayCountTxt, 16, "%d", val);
15✔
555
                                                ver = defarrayCountTxt;
15✔
556
                                        } else {
557
                                                char* expLp = lp + ('[' == *lp);        // the '[' will become part of subId in didParseBrackets
15,635✔
558
                                                IsLabelNotFound = false;
15,635✔
559
                                                bool didParseBrackets = '[' == *lp && GrowSubIdByExtraChar(lp) && ParseExpressionInSubstitution(lp, val) && ']' == *lp;
15,635✔
560
                                                if (didParseBrackets && !IsLabelNotFound) {
15,635✔
561
                                                        // expression was successfully parsed and all values were known
562
                                                        ++lp;
15,419✔
563
                                                        while (0 < val && a) {
59,027✔
564
                                                                a = a->next;
43,608✔
565
                                                                --val;
43,608✔
566
                                                        }
567
                                                        if (val < 0 || NULL == a) {
15,419✔
568
                                                                *defarrayCountTxt = 0;                // substitute with empty string
15✔
569
                                                                ver = defarrayCountTxt;
15✔
570
                                                                Error("[ARRAY] index not in 0..<Size-1> range", nextSubIdLp, SUPPRESS);
15✔
571
                                                        } else {
572
                                                                ver = a->string;        // substitute with array value
15,404✔
573
                                                        }
574
                                                } else {        // no substitution of array possible at this time (index eval / syntax error)
575
                                                        lp = expLp;                                // restore lp in case expression parser went ahead a lot
216✔
576
                                                        dr = -1;// write into output, but don't count as replacement
216✔
577
                                                }
578
                                        }
579
                                }
580
                        } else {
581
                                dr = 0;                        // no possible substitution found
2,054,726✔
582
                                ver = nid;
2,054,726✔
583
                        }
584
                        // check if no substitution was found, and there's no more chars to extend SubId
585
                        if (0 == dr && !islabchar(*lp)) {
2,091,791✔
586
                                lp = nextSubIdLp;                // was fully extended, no match, "eat" first subId
1,470,750✔
587
                                ResetGrowSubId();
1,470,750✔
588
                                ver = GrowSubId(lp);        // find the first SubId again, for the copy
1,470,750✔
589
                                dr = -1;                                // write into output, but don't count as replacement
1,470,750✔
590
                        }
591
                        if (0 < dr) {
2,091,791✔
592
                                definegereplaced = 1;        // above zero => count as replacement
36,849✔
593
                                // look ahead in "to" array to convert any whitespace enclosed `_` to glue tag
594
                                char* gluep = rp;                // first skip whitespace ahead of substitution
36,849✔
595
                                while ((nl < gluep--) && (GLUE_TAG != gluep[0]) && (White(gluep[0]))) /* empty */;
143,107✔
596
                                // now check if there was whitespace, is glue char '_' and further whitespace ahead of it
597
                                if ((nl < gluep) && (gluep + 1 < rp) && (GLUE_CHAR == gluep[0]) && White(gluep[-1])) {
36,849✔
598
                                        gluep[0] = GLUE_TAG;// convert it to glue tag for later
33✔
599
                                        // not important to set glueDetected because substitution happened
600
                                }
601
                                // look after in "from" array to convert any whitespace enclosed `_` to glue tag
602
                                for (gluep = lp; GLUE_TAG != gluep[0] && White(gluep[0]); ) ++gluep;
45,238✔
603
                                if ((lp < gluep) && (GLUE_CHAR == gluep[0]) && White(gluep[1])) {
36,849✔
604
                                        gluep[0] = GLUE_TAG;// convert it to glue tag for later
48✔
605
                                }
606
                        }
607
                        if (0 != dr) {                                // any non-zero dr => write to the output
2,091,791✔
608
                                while (*ver && ((rp - nl) < LINEMAX)) *rp++ = *ver++;                // replace the string into target buffer
5,772,983✔
609
                                // reset subId parser to catch second+ subId in current Id
610
                                ResetGrowSubId();
1,507,815✔
611
                                nextSubIdLp = lp;
1,507,815✔
612
                        }
613
                        // continue with extending the subId, if there's still something to parse
614
                } while(islabchar(*lp));
2,091,791✔
615
        } // while(*lp)
616
        // add line terminator to the output buffer
617
        *rp++ = 0;
728,525✔
618
        if (LINEMAX <= (rp - nl)) {
728,525✔
619
                Error("line too long after macro expansion", nl, SUPPRESS);
6✔
620
        }
621
        // if no substitution happened, but there are glue bytes, glue it all together now
622
        if (0 == definegereplaced && glueDetected) {
728,525✔
623
                // now glue the pieces together around each glue
624
                int dropChars = 0;
57✔
625
                for (rp = nl; *rp; ++rp) {
879✔
626
                        if (GLUE_TAG == *rp) {
822✔
627
                                char* leftp = rp, * rightp = rp;
81✔
628
                                leftp = rp;
81✔
629
                                while (nl < leftp && White(leftp[-1])) --leftp;        // eat all whitespace to left
171✔
630
                                while (White(rightp[0])) ++rightp;                                // eat all whitespace to right
312✔
631
                                // leftp points at left-most whitespace char, rightp points at first non-whitespace or \0
632
                                if ((nl == leftp) || (0 == rightp[0])) rp[0] = GLUE_CHAR;        // nothing to glue with, restore
81✔
633
                                else {
634
                                        dropChars -= (rightp - leftp);                                // discard this whitespace + glue block
66✔
635
                                        rp = rightp;
66✔
636
                                }
637
                        }
638
                        if (dropChars < 0) rp[dropChars] = rp[0];        // move other chars over removed areas
822✔
639
                }
640
                rp[dropChars] = 0;                // make sure there's zero terminator also for glued result
57✔
641
                return true;                        // report modification
57✔
642
        }
643
        // check if whole line is just blanks, then return just empty one
644
        rp = nl;
728,468✔
645
        if (SkipBlanks(rp)) *nl = 0;
728,468✔
646
        substitutedLine = nl;                // set global pointer to the latest substituted version
728,468✔
647
        return definegereplaced;
728,468✔
648
}
649

650
char* ReplaceDefine(char* src) {
701,125✔
651
        char* to = sline;
701,125✔
652
        for (int maxIter = 31; maxIter--;) {
728,528✔
653
                if (!ReplaceDefineInternal(src, to)) return to;        // no more replacements
728,525✔
654
                // Some define were replaced, now ping-pong sline <-> sline2 buffers
655
                src = to;
27,403✔
656
                to = (sline == to) ? sline2 : sline;
27,403✔
657
        }
658
        Error("Unable to finish substitutions, line after last iteration", src, SUPPRESS);
3✔
659
        return src;
3✔
660
}
661

662
void SetLastParsedLabel(const char* label) {
26,369✔
663
        if (LastParsedLabel) free(LastParsedLabel);
26,369✔
664
        if (nullptr != label) {
26,369✔
665
                LastParsedLabel = STRDUP(label);
24,206✔
666
                if (nullptr == LastParsedLabel) ErrorOOM();
24,206✔
667
                LastParsedLabelLine = CompiledCurrentLine;
24,206✔
668
        } else {
669
                LastParsedLabel = nullptr;
2,163✔
670
                LastParsedLabelLine = 0;
2,163✔
671
        }
672
}
26,369✔
673

674
void ParseLabel() {
591,333✔
675
        if (White()) return;
592,513✔
676
        if (':' == *lp) {                                                        // detect possible SIZEOF boundary tag
365,281✔
677
                bool isLocal = ('.' == lp[1]);
48✔
678
                if (':' == lp[1 + isLocal]) {                        // tag detected, process it
48✔
679
                        LabelTable.SizeBoundary(isLocal ? CLabelTable::BOUNDARY_LOCAL : CLabelTable::BOUNDARY_MAIN);
45✔
680
                        lp += (2 + isLocal);
45✔
681
                        return;
45✔
682
                }
683
        }
684
        if (Options::syx.IsPseudoOpBOF && ParseDirective(true)) {
365,236✔
685
                if (!SkipBlanks()) Error("Unexpected", lp);
207✔
686
                return;
207✔
687
        }
688
        char temp[LINEMAX], * tp = temp, * ttp;
365,029✔
689
        aint val, equPageNum = LABEL_PAGE_UNDEFINED, smcOffset = 0;
365,029✔
690
        // copy the label name into `temp` array
691
        while (*lp && !White() && *lp != ':' && *lp != '=' && *lp != '+') {
1,633,465✔
692
                *tp = *lp; ++tp; ++lp;
1,268,436✔
693
        }
694
        *tp = 0;
365,029✔
695
        // handle the special SMC_offset syntax "<label>+<single_digit>"
696
        if ('+' == lp[0] && isdigit(byte(lp[1])) && !isalnum(byte(lp[2]))) {
365,029✔
697
                smcOffset = lp[1] - '0';
72✔
698
                lp += 2;
72✔
699
        }
700
        // handle the special SMC_offset syntax "<label>+*" to target significant immediate of the instruction
701
        if ('+' == lp[0] && '*' == lp[1] && !isalnum(byte(lp[2]))) {
365,029✔
702
                assert(!sourcePosStack.empty());
410✔
703
                smcOffset = 1;                                        // heuristic value 1 for first pass or when something fails
410✔
704
                lp += 2;
410✔
705
                if (1 == pass) {
410✔
706
                        // put the current source position into smart-smc (if first pass, or missing record)
707
                        smartSmcLines.push_back(sourcePosStack.back());
136✔
708
                        smartSmcLines.back().colBegin = ~0U;                // mark as unresolved
136✔
709
                } else {
710
                        if ((smartSmcLines.size() <= smartSmcIndex)
274✔
711
                                || smartSmcLines.at(smartSmcIndex) != sourcePosStack.back()) {
274✔
712
                                Error("mismatch of smart-SMC positions between passes");
10✔
713
                        } else {
714
                                auto & smartSmcLine = smartSmcLines.at(smartSmcIndex);
264✔
715
                                if (~0U == smartSmcLine.colBegin) {
264✔
716
                                        Error("unresolved smart-SMC symbol (no significant target)");
100✔
717
                                } else {
718
                                        smcOffset = smartSmcLine.colBegin;        // use the smart value from previous pass
164✔
719
                                        smartSmcLine.colBegin = ~0U;                // mark as unsolved again
164✔
720
                                }
721
                        }
722
                }
723
                ++smartSmcIndex;
410✔
724
        }
725
        if (*lp == ':') ++lp;        // eat the optional colon after label
365,029✔
726
        tp = temp;
365,029✔
727
        SkipBlanks();
365,029✔
728
        IsLabelNotFound = false;
365,029✔
729
        if (isdigit((byte)*tp)) {
365,029✔
730
                if (smcOffset) {
364✔
731
                        Error("Temporary label can't use SMC-offset");
12✔
732
                        return;
12✔
733
                }
734
                ttp = tp;
352✔
735
                while (*ttp && isdigit((byte)*ttp)) ++ttp;
839✔
736
                if (*ttp) {
352✔
737
                        Error("Invalid temporary label (not a number)", temp);
3✔
738
                        return;
3✔
739
                }
740
                if (NeedEQU() || NeedDEFL()) {
349✔
741
                        Error("Number labels are allowed as address labels only, not for DEFL/=/EQU", temp, SUPPRESS);
27✔
742
                        return;
27✔
743
                }
744
                val = atoi(tp);
322✔
745
                if (!TemporaryLabelTable.InsertRefresh(val)) {
322✔
746
                        Error("Temporary labels flow differs in last pass (missing/new temporary label since previous pass)");
115✔
747
                }
748
        } else {
749
                if (isMacroNext()) {
364,665✔
750
                        if (smcOffset) Error("Macro name can't use SMC-offset");
261✔
751
                        else SetLastParsedLabel(tp);        // store raw label into "last parsed" without adding module/etc
255✔
752
                        return;                                        // and don't add it to labels table at all
886✔
753
                }
754
                bool IsDEFL = NeedDEFL(), IsEQU = NeedEQU();
364,404✔
755
                if (IsDEFL || IsEQU) {
364,404✔
756
                        Relocation::isResultAffected = false;
344,339✔
757
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
344,339✔
758
                                Error("Expression error", lp);
9✔
759
                                val = 0;
9✔
760
                        }
761
                        if (IsLabelNotFound && IsDEFL) Error("Forward reference", NULL, EARLY);
344,339✔
762
                        // check for explicit page defined by EQU
763
                        if (IsEQU && comma(lp)) {
344,339✔
764
                                if (!ParseExpressionNoSyntaxError(lp, equPageNum)) {
21✔
765
                                        Error("Expression error", lp);
6✔
766
                                        equPageNum = LABEL_PAGE_UNDEFINED;
6✔
767
                                }
768
                        }
769

770
                        // after EQU/DEFL expressions there must be <EOL>, anything else is syntax error
771
                        // this was added in v1.17.1 after realizing the line `label=$+1 and 7`
772
                        // does evaluate whole "$+1 and 7" as expression, not as instruction `and`
773
                        // (same problem exists with and/or/xor and if you have macro named after operator)
774
                        if (!SkipBlanks(lp)) {
344,378✔
775
                                Error("Unexpected", lp);
39✔
776
                                SkipToEol(lp);
39✔
777
                        }
778

779
                } else {
780
                        int gl = 0;
20,065✔
781
                        char* p = lp,* n;
20,065✔
782
                        SkipBlanks(p);
20,065✔
783
                        if (*p == '@') {
20,065✔
784
                                ++p; gl = 1;
24✔
785
                        }
786
                        if ((n = GetID(p)) && StructureTable.Emit(n, tp, p, gl)) {
20,065✔
787
                                if (smcOffset) Error("Structure instance can't use SMC-offset");
576✔
788
                                lp = p;
576✔
789
                                // this was instancing STRUCT, make it also define "main" label for future "local" ones
790
                                tp = ValidateLabel(tp, true);
576✔
791
                                if (tp) delete[] tp;
576✔
792
                                return;
576✔
793
                        }
794
                        val = CurAddress;
19,489✔
795
                }
796
                val += smcOffset;
363,828✔
797
                ttp = tp;
363,828✔
798
                bool isLocal;
799
                if (!(tp = ValidateLabel(isLocal, tp, true))) {
363,828✔
800
                        return;
42✔
801
                }
802
                // Copy label name to last parsed label variable
803
                if (!IsDEFL) SetLastParsedLabel(tp);
363,786✔
804
                unsigned traits = (isLocal ? LABEL_IS_LOCAL : 0) | (IsEQU ? LABEL_IS_EQU : 0) | (IsDEFL ? LABEL_IS_DEFL : 0) | (smcOffset ? LABEL_IS_SMC : 0);
363,786✔
805
                if (pass == LASTPASS) {
363,786✔
806
                        SLabelTableEntry* label = LabelTable.Find(tp, true);
121,299✔
807
                        if (nullptr == label && IsDEFL) {        // DEFL labels can be defined as late as needed (including pass3)
121,299✔
808
                                if (LabelTable.Insert(tp, val, traits)) label = LabelTable.Find(tp, true);
3✔
809
                        }
810
                        if (nullptr == label) {                // should have been already defined before last pass
121,299✔
811
                                Error("Label not found", tp);
7✔
812
                                delete[] tp;
7✔
813
                                return;
7✔
814
                        }
815
                        if (IsDEFL) {                //re-set DEFL value
121,292✔
816
                                LabelTable.Insert(tp, val, traits);
113,311✔
817
                        } else if (IsSldExportActive()) {
7,981✔
818
                                // SLD (Source Level Debugging) tracing-data logging
819
                                WriteToSldFile(IsEQU ? -1 : label->page, val, IsEQU ? 'D' : 'F', tp);        //version 0
171✔
820
                                WriteToSldFile(IsEQU ? -1 : label->page, val, 'L', ExportLabelToSld(ttp, label));        //version 1
171✔
821
                        }
822

823
                        if (val != label->value) {
121,292✔
824
                                char* buf = new char[LINEMAX];
23✔
825

826
                                SPRINTF2(buf, LINEMAX, "previous value %u not equal %u", label->value, val);
23✔
827
                                Warning("Label has different value in pass 3", buf);
23✔
828
                                LabelTable.Update(tp, val);
23✔
829

830
                                delete[] buf;
23✔
831
                        } else {
832
                                label->updatePass = pass;        // just "touch" it here in third pass
121,269✔
833
                        }
834
                } else if (pass == 2 && !LabelTable.Insert(tp, val, traits, equPageNum)) {
242,487✔
835
                        if (!LabelTable.Update(tp, val)) assert(false); // unreachable, update will always work after insert failed
7✔
836
                } else if (pass == 1 && !LabelTable.Insert(tp, val, traits, equPageNum)) {
242,480✔
837
                        Error("Duplicate label", tp, EARLY);
6✔
838
                }
839
                // sizeof() implementation, must search for inserted label one more time (to do it in every pass)
840
                SLabelTableEntry* label = LabelTable.Find(tp, true);
363,779✔
841
                assert(label);
363,779✔
842
                // soft main/local boundary for current nesting level
843
                LabelTable.SizeBoundary(isLocal ? CLabelTable::BOUNDARY_LOCAL : CLabelTable::BOUNDARY_MAIN);
363,779✔
844
                // add new label to size tracking if it has SIZEOF trait
845
                if (LABEL_HAS_SIZE & label->traits) {        // this label wants to know size, add it to sizeof stack
363,779✔
846
                        LabelTable.TrackForSize(label);
66✔
847
                        //FIXME all - labels parsing with LABEL_HAS_SIZE traits:
848
                        // - there could be multiple labels "open" looking for boundary to calculate their size
849
                        // - so I guess I need stack with those labels and next label defined or :: :.: operators will pop + set the relevant ones
850
                        // - based on include level, macro nesting level and local vs main label
851
                        // - ORG, MODULE, ENDMODULE inside include/macro works as boundary even for upper levels (with errors reported)
852
                        // - ORG, EOF, MODULE, ENDMODULE, end-of-macro, ...TODO?...: works as silent boundary for their own level
853
                        // - it's LIFO stack, any nesting/locality must finish sooner or at same time as the current content of stack!!!
854
                        // - FIXME ^^^ verify the idea -> seems sound so far
855

856
                        // --- other steps elsewhere:
857

858
                        // - on all boundaries with non-empty stack - close all relevant nesting index items (main / local variant)
859
                        // - on hard boundaries (org, module, endmodule, ? anything else?) with non-empty stack - close ALL items (with warnings for wrong nesting index)
860
                        // - on nesting in/out update nesting index (nesting out is also boundary, never hard one)
861

862
                        // so this boils down to three main topics:
863
                        // - nesting index tracking (includes, modules, macro emits, anything else? watch out for dup/dot repeater... :/ )
864
                        // - boundary implementation, 3 levels: (local / main / hard) (where to place it? `::` tag has to access it too, must see nesting index and stack)
865
                        // - hunting down everything what is boundary and calling correct level of it
866
                        //   - all: org, module, endmodule
867
                        //   - local: new local label, `:.:` tag
868
                        //   - main: everything else: new main label, `::` tag, nesting-out, EOF (== nesting out?)
869
                        //           abuse vorlab/namespace updates to not miss something obvious, but it's more than that
870

871
                        //// FIXME WIP state as far as I remember:
872
                        // - [ ] polish design like what about STRUCT labels, DEFL, EQU, ... (some notes in test file)
873
                        // - [ ] extend test (based on tracked notes, but also whatever else pops up meanwhile)
874
                        // - [ ] add SLD, check for +sizeof
875
                        // - [ ] UB/ASAN check
876
                        // - [ ] docs :sweat:
877
                        // - [ ] open MR to get AI review
878
                        // ??? this seems all, it's almost implemented now??? Check also original issue
879

880
                }
881

882
// TODO v2.x: this is too complicated in current version: Unreal/Cspect already expect
883
// EQU/DEFL to be current page or "ROM" = not a big deal as they did change in v1.x course already.
884
// But also struct labels are set as EQU ones, so this has to split, and many other details.
885
// (will also need more than LABEL_PAGE_UNDEFINED value to deal with more states)
886
//                 if (IsEQU && comma(lp)) {        // Device extension: "<label> EQU <address>,<page number>"
887
//                         if (!DeviceID) {
888
//                                 Error("EQU can set page to label only in device mode", line);
889
//                                 SkipToEol(lp);
890
//                         } else if (!ParseExpression(lp, oval)) {        // try to read page number into "oval"
891
//                                 Error("Expression error", lp);
892
//                                 oval = -1;
893
//                         } else if (oval < 0 || Device->PagesCount <= oval) {
894
//                                 ErrorInt("Invalid page number", oval);
895
//                                 oval = -1;
896
//                         } else {
897
//                                 if (val < 0 || 0xFFFF < val) Warning("The EQU address is outside of 16bit range", line);
898
//                                 CLabelTableEntry* equLabel = LabelTable.Find(tp, true);        // must be already defined + found
899
//                                 equLabel->page = oval;                        // set it's page number
900
//                         }
901
//                 }
902

903
                delete[] tp;
363,779✔
904
        }
905
}
906

907
int ParseMacro() {
246,024✔
908
        int gl = 0, r = 0;
246,024✔
909
        char* p = lp, *n;
246,024✔
910
        SkipBlanks(p);
246,024✔
911
        if (*p == '@') {
246,024✔
912
                gl = 1; ++p;
45✔
913
        }
914
        if (!(n = GetID(p))) {
246,024✔
915
                return 0;
86✔
916
        }
917

918
        if (!gl) r = MacroTable.Emit(n, p);                // global '@' operator inhibits macros
245,938✔
919
        if (r == 2) return 1;        // successfully emitted
245,937✔
920
        if (r == 1) {                        // error reported
244,416✔
921
                lp = p;
42✔
922
                return 0;
42✔
923
        }
924

925
        // not a macro, see if it's structure
926
        if (StructureTable.Emit(n, nullptr, p, gl)) {
244,374✔
927
                lp = p;
174✔
928
                return 1;
174✔
929
        }
930

931
        return 0;
244,200✔
932
}
933

934
void ParseInstruction() {
244,328✔
935
        if ('@' == *lp) ++lp;                // skip single '@', if it was used to inhibit macro expansion
244,328✔
936
        if (ParseDirective()) return;
244,328✔
937
        Z80::GetOpCode();
120,279✔
938
}
939

940
static const byte win2dos[] = //taken from HorrorWord %)))
941
{
942
        0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
943
        0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
944
        0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xF0, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
945
        0xDF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF1, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0x20,
946
        0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
947
        0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
948
        0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
949
        0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF
950
};
951

952
//#define DEBUG_COUT_PARSE_LINE
953

954
// returns 1 when already fully processed (part of DUP/etc)
955
int PrepareLine() {
678,082✔
956
        ListSilentOrExternalEmits();
678,082✔
957

958
        ++CompiledCurrentLine;
678,082✔
959
        if (!RepeatStack.empty()) {
678,082✔
960
                SRepeatStack& dup = RepeatStack.top();
450,684✔
961
                if (!dup.IsInWork) {
450,684✔
962
                        lp = line;
24,403✔
963
                        dup.Pointer->next = new CStringsList(lp);
24,403✔
964
                        dup.Pointer = dup.Pointer->next;
24,403✔
965
#ifdef DEBUG_COUT_PARSE_LINE
966
                        fprintf(stderr, ">%d %d %c%ld-%d [%s]\n", pass, CurSourcePos.line,
967
                                        (!RepeatStack.empty() && RepeatStack.top().IsInWork ? '!' : '.'),RepeatStack.size(),
968
                                        (!RepeatStack.empty() ? RepeatStack.top().Level : 0), line);
969
#endif
970
                        // check if there's some label at beginning of the line, skip it
971
                        if (islabchar(*lp)) {
24,403✔
972
                                // if directives are enabled at beginning of line, check if it is nested DUP/REPT/WHILE/EDUP...
973
                                if (Options::syx.IsPseudoOpBOF && ParseDirective_REPT()) return 1;
4,296✔
974
                                // skip label chars and trailing colon
975
                                while (islabchar(*lp)) ++lp;
22,800✔
976
                                if (':' == *lp) ++lp;
4,281✔
977
                        }
978
                        // catch any nested DUP/WHILE/REPT and EDUP directives
979
                        ParseDirective_REPT();
24,388✔
980
                        return 1;
24,388✔
981
                }
982
        }
983
#ifdef DEBUG_COUT_PARSE_LINE
984
        fprintf(stderr, "|%d %d %c%ld-%d [%s]\n", pass, CurSourcePos.line,
985
                        (!RepeatStack.empty() && RepeatStack.top().IsInWork ? '!' : '.'), RepeatStack.size(),
986
                        (!RepeatStack.empty() ? RepeatStack.top().Level : 0), line);
987
#endif
988
        lp = ReplaceDefine(line);
653,679✔
989

990
#ifdef DEBUG_COUT_PARSE_LINE
991
        fprintf(stderr,"rdOut [%s]->[%s] %d\n", line, lp, comlin);
992
#endif
993

994
        // update current address by memory wrapping, current page, etc... (before the label is defined)
995
        if (DeviceID)        Device->CheckPage(CDevice::CHECK_NO_EMIT);
653,679✔
996
        ListAddress = CurAddress;
653,679✔
997

998
        if (!ConvertEncoding) {
653,679✔
999
                byte* lp2 = (byte*) lp;
108✔
1000
                while (*lp2) {
1,518✔
1001
                        if (128 <= *lp2) {
1,410✔
1002
                                *lp2 = win2dos[(*lp2) - 128];
33✔
1003
                        }
1004
                        ++lp2;
1,410✔
1005
                }
1006
        }
1007
        return 0;
653,679✔
1008
}
1009

1010
bool PrepareNonBlankMultiLine(char*& p) {
5,532✔
1011
        // loop while the current line is blank-only (read further lines until EOF or non-blank char)
1012
        while (SkipBlanks(p)) {
5,877✔
1013
                // if inside macro system, but without any more macro-lines in buffer, act as if "EOF"
1014
                // (to not leak into reading actual file while the macro is executing)
1015
                if (listmacro && nullptr == lijstp) return false;
354✔
1016
                // list the current (old) line
1017
                ListFile();
345✔
1018
                // read the next line
1019
                if (!ReadLine()) return false;
345✔
1020
                PrepareLine();
345✔
1021
                p = lp;
345✔
1022
        }
1023
        return true;
5,523✔
1024
}
1025

1026
void ParseLine(bool parselabels) {
677,737✔
1027
        if (PrepareLine()) return;
677,737✔
1028

1029
        if (eolComment && IsSldExportActive()) SldTrackComments();
653,334✔
1030

1031
        if (!*lp) {
653,334✔
1032
                char *srcNonWhiteChar = line;
58,969✔
1033
                SkipBlanks(srcNonWhiteChar);
58,969✔
1034
                // check if only "end-line" comment remained, treat that one as "empty" line too
1035
                if (';' == srcNonWhiteChar[0] || ('/' == srcNonWhiteChar[0] && '/' == srcNonWhiteChar[1]))
58,969✔
1036
                        srcNonWhiteChar = lp;                        // force srcNonWhiteChar to point to 0
32,124✔
1037
                if (*srcNonWhiteChar || comlin) {        // non-empty source line turned into nothing
58,969✔
1038
                        ListFile(true);                                        // or empty source inside comment-block -> "skipped"
210✔
1039
                } else {
1040
                        ListFile();                                                // empty source line outside of block-comment -> "normal"
58,759✔
1041
                }
1042
                return;
58,969✔
1043
        }
1044

1045
        if (parselabels) ParseLabel();
594,365✔
1046
        if (!SkipBlanks()) ParseMacro();
594,365✔
1047
        if (!SkipBlanks()) ParseInstruction();
594,364✔
1048
        if (!SkipBlanks()) Error("Unexpected", lp);
594,358✔
1049
        ListFile();
594,358✔
1050
}
1051

1052
void ParseLineSafe(bool parselabels) {
457,309✔
1053
        char* tmp = NULL, * tmp2 = NULL;
457,309✔
1054
        char* rp = lp;
457,309✔
1055
        if (sline[0] > 0) {
457,309✔
1056
                tmp = STRDUP(sline);
434,683✔
1057
                if (tmp == NULL) ErrorOOM();
434,683✔
1058
        }
1059
        if (sline2[0] > 0) {
457,309✔
1060
                tmp2 = STRDUP(sline2);
90,697✔
1061
                if (tmp2 == NULL) ErrorOOM();
90,697✔
1062
        }
1063

1064
        ParseLine(parselabels);
457,309✔
1065

1066
        *sline = 0;
457,308✔
1067
        *sline2 = 0;
457,308✔
1068

1069
        if (tmp2 != NULL) {
457,308✔
1070
                STRCPY(sline2, LINEMAX2, tmp2);
90,697✔
1071
                free(tmp2);
90,697✔
1072
        }
1073
        if (tmp != NULL) {
457,308✔
1074
                STRCPY(sline, LINEMAX2, tmp);
434,682✔
1075
                free(tmp);
434,682✔
1076
        }
1077
        lp = rp;
457,308✔
1078
}
457,308✔
1079

1080
void ParseStructLabel(CStructure* st) {
1,206✔
1081
        char* tp, temp[LINEMAX];
1082
        if (PreviousIsLabel) {
1,206✔
1083
                free(PreviousIsLabel);
1,008✔
1084
                PreviousIsLabel = nullptr;
1,008✔
1085
        }
1086
        if (White()) {
1,206✔
1087
                return;
159✔
1088
        }
1089
        tp = temp;
1,053✔
1090
        if (*lp == '.') {
1,053✔
1091
                ++lp;
30✔
1092
        }
1093
        while (*lp && islabchar(*lp)) {
5,730✔
1094
                *tp = *lp; ++tp; ++lp;
4,677✔
1095
        }
1096
        *tp = 0;
1,053✔
1097
        if (*lp == ':') {
1,053✔
1098
                ++lp;
81✔
1099
        }
1100
        tp = temp; SkipBlanks();
1,053✔
1101
        if (isdigit((byte)*tp)) {
1,053✔
1102
                Error("[STRUCT] Number labels not allowed within structs"); return;
6✔
1103
        }
1104
        PreviousIsLabel = STRDUP(tp);
1,047✔
1105
        if (PreviousIsLabel == NULL) ErrorOOM();
1,047✔
1106
        st->AddLabel(tp);
1,047✔
1107
}
1108

1109
void ParseStructMember(CStructure* st) {
1,188✔
1110
        aint val, len;
1111
        bp = lp;
1,188✔
1112
        Relocation::isResultAffected = false;
1,188✔
1113
        Relocation::EType deltaType = Relocation::OFF;
1,188✔
1114
        switch (GetStructMemberId(lp)) {
1,188✔
1115
        case SMEMBBLOCK:
93✔
1116
                if (!ParseExpression(lp, len)) {
93✔
1117
                        len = 1;
6✔
1118
                        Error("[STRUCT] Expression expected");
6✔
1119
                }
1120
                if (comma(lp)) {
93✔
1121
                        if (!ParseExpression(lp, val)) {
54✔
1122
                                val = 0;
6✔
1123
                                Error("[STRUCT] Expression expected");
6✔
1124
                        }
1125
                        check8(val);
54✔
1126
                        val &= 255;
54✔
1127
                } else {
1128
                        val = -1;
39✔
1129
                }
1130
                st->AddMember(new CStructureEntry2(st->noffset, len, val, deltaType, SMEMBBLOCK));
93✔
1131
                break;
93✔
1132
        case SMEMBBYTE:
429✔
1133
                if (!ParseExpression(lp, val)) val = 0;
429✔
1134
                check8(val);
429✔
1135
                deltaType = Relocation::isResultAffected ? Relocation::deltaType : Relocation::OFF;
429✔
1136
                st->AddMember(new CStructureEntry2(st->noffset, 1, val, deltaType, SMEMBBYTE));
429✔
1137
                Relocation::resolveRelocationAffected(INT_MAX, Relocation::HIGH);        // clear flags + warn when can't be relocated
429✔
1138
                break;
429✔
1139
        case SMEMBWORD:
321✔
1140
                if (!ParseExpression(lp, val)) val = 0;
321✔
1141
                check16(val);
321✔
1142
                deltaType = Relocation::isResultAffected ? Relocation::deltaType : Relocation::OFF;
321✔
1143
                st->AddMember(new CStructureEntry2(st->noffset, 2, val, deltaType, SMEMBWORD));
321✔
1144
                Relocation::resolveRelocationAffected(INT_MAX);        // clear flags + warn when can't be relocated
321✔
1145
                break;
321✔
1146
        case SMEMBD24:
21✔
1147
                if (!ParseExpression(lp, val)) val = 0;
21✔
1148
                check24(val);
21✔
1149
                st->AddMember(new CStructureEntry2(st->noffset, 3, val, deltaType, SMEMBD24));
21✔
1150
                break;
21✔
1151
        case SMEMBDWORD:
33✔
1152
                if (!ParseExpression(lp, val)) val = 0;
33✔
1153
                st->AddMember(new CStructureEntry2(st->noffset, 4, val, deltaType, SMEMBDWORD));
33✔
1154
                break;
33✔
1155
        case SMEMBTEXT:
93✔
1156
                {
1157
                        if (!ParseExpression(lp, len) || len < 1 || CStructureEntry2::TEXT_MAX_SIZE < len) {
93✔
1158
                                Error("[STRUCT] Expression for length of text expected (1..8192)");
12✔
1159
                                SkipToEol(lp);
12✔
1160
                                break;
12✔
1161
                        }
1162
                        byte* textData = new byte[len]();        // zero-initialized for stable binary results
1,341✔
1163
                        if (nullptr == textData) ErrorOOM();
81✔
1164
                        if (comma(lp)) {                // if comma then init data array explicitly
81✔
1165
                                GetStructText(lp, len, textData);
63✔
1166
                        } else if (SkipBlanks(lp)) {
18✔
1167
                                // if empty without comma, init with the zeroed values
1168
                        } else {
1169
                                Error("[STRUCT] Comma expected", lp, SUPPRESS);        // syntax error
3✔
1170
                        }
1171
                        st->AddMember(new CStructureEntry2(st->noffset, len, textData));
81✔
1172
                }
1173
                break;
81✔
1174
        case SMEMBALIGN:
27✔
1175
        {
1176
                aint val, fill;
1177
                ParseAlignArguments(lp, val, fill);
27✔
1178
                if (-1 == val) val = 4;
27✔
1179
                if (st->maxAlignment < val) st->maxAlignment = val;        // update structure "max alignment"
27✔
1180
                aint bytesToAdvance = (~st->noffset + 1) & (val - 1);
27✔
1181
                if (bytesToAdvance < 1) break;                // already aligned, nothing to do
27✔
1182
                // create alignment block
1183
                st->AddMember(new CStructureEntry2(st->noffset, bytesToAdvance, fill, deltaType, SMEMBBLOCK));
21✔
1184
                break;
21✔
1185
        }
1186
        default:
171✔
1187
                char* pp = lp,* n;
171✔
1188
                int gl = 0;
171✔
1189
                CStructure* s;
1190
                SkipBlanks(pp);
171✔
1191
                if (*pp == '@') {
171✔
1192
                        ++pp;
6✔
1193
                        gl = 1;
6✔
1194
                }
1195
                if ((n = GetID(pp)) && (s = StructureTable.zoek(n, gl))) {
171✔
1196
                        if (st == s) {
96✔
1197
                                Error("[STRUCT] Can't include itself", NULL);
12✔
1198
                                SkipToEol(pp);
12✔
1199
                                lp = pp;
12✔
1200
                                break;
12✔
1201
                        }
1202
                        if (s->maxAlignment && ((~st->noffset + 1) & (s->maxAlignment - 1))) {
84✔
1203
                                // Inserted structure did use ALIGN in definition and it is misaligned here
1204
                                char warnTxt[LINEMAX];
1205
                                SPRINTF3(warnTxt, LINEMAX,
3✔
1206
                                                 "Struct %s did use ALIGN %d in definition, but here it is misaligned by %d bytes",
1207
                                                 s->naam, s->maxAlignment, ((~st->noffset + 1) & (s->maxAlignment - 1)));
1208
                                Warning(warnTxt);
3✔
1209
                        }
1210
                        lp = pp;
84✔
1211
                        st->CopyLabels(s);
84✔
1212
                        st->CopyMembers(s, lp);
84✔
1213
                }
1214
                break;
159✔
1215
        }
1216
        Relocation::checkAndWarn();
1,188✔
1217
}
1,188✔
1218

1219
void ParseStructLine(CStructure* st) {
1,260✔
1220
        ++CompiledCurrentLine;
1,260✔
1221
        lp = ReplaceDefine(line);
1,260✔
1222
        if (!*lp) return;
1,260✔
1223
        ParseStructLabel(st);
1,206✔
1224
        if (SkipBlanks()) return;
1,206✔
1225
        ParseStructMember(st);
1,188✔
1226
        if (SkipBlanks()) return;
1,188✔
1227
        if (*lp) Error("[STRUCT] Unexpected", lp);
99✔
1228
}
1229

1230
//eof parser.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