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

z00m128 / sjasmplus / 28301674672

27 Jun 2026 08:36PM UTC coverage: 97.815% (+0.003%) from 97.812%
28301674672

push

github

ped7g
operators: add `pair` and `u16`, close #346

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

1 existing line in 1 file now uncovered.

10161 of 10388 relevant lines covered (97.81%)

173980.43 hits per line

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

99.36
/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,671,075✔
38
        int res = 0;
1,671,075✔
39
        if (SkipBlanks(p)) {
1,671,075✔
40
                return 0;
1,941✔
41
        }
42
        if (*p == '(') {                // already did SkipBlanks, so `need(p, '(')` would do it second time
1,669,134✔
43
                ++p;
22,150✔
44
                res = ParseExpressionEntry(p, nval);
22,150✔
45
                if (!need(p, ')')) {
22,150✔
46
                        Error("')' expected");
15✔
47
                        return 0;
15✔
48
                }
49
        } else if (DeviceID && *p == '{') {                // read WORD/BYTE from virtual device memory
1,646,984✔
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,626,031✔
72
                return 1;
195✔
73
        } else if (isdigit((byte)*p) || (*p == '#' && isalnum((byte)*(p + 1))) || (*p == '$' && isalnum((byte)*(p + 1))) || *p == '%') {
1,625,836✔
74
                return GetConstant(p, nval);
867,391✔
75
        } else if (isLabelStart(p)) {
758,445✔
76
                return GetLabelValue(p, nval);
749,485✔
77
        } else if (*p == '?' && isLabelStart(p+1)) {
8,960✔
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 ('$' == p[0] && '$' == p[1] && '$' == p[2] && '$' == p[3] && isLabelStart(p + 4)) {
8,951✔
85
                p += 4;                                                                // $$$$label variant returns physical page
69✔
86
                return GetLabelPhPage(p, nval);
69✔
87
        } else if ('$' == p[0] && '$' == p[1] && '$' == p[2] && isLabelStart(p + 3)) {
8,882✔
88
                p += 3;                                                                // $$$label variant returns physical value
72✔
89
                return GetLabelPhValue(p, nval);
72✔
90
        } else if (DISP_NONE != PseudoORG && '$' == p[0] && '$' == p[1] && '$' == p[2]) {
8,810✔
91
                if ('$' == p[3]) {                // "$$$$" operator to get physical memory page inside DISP block
24✔
92
                        p += 4;
12✔
93
                        nval = DeviceID ? Page->Number : LABEL_PAGE_UNDEFINED;
12✔
94
                        return 1;
12✔
95
                }
96
                // "$$$" operator to get physical address inside DISP block
97
                p += 3;
12✔
98
                nval = adrdisp;                // this is never affected by relocation
12✔
99
                return 1;
12✔
100
        } else if (DeviceID && *p == '$' && *(p + 1) == '$') {
8,786✔
101
                p += 2;
403✔
102
                if (isLabelStart(p)) return GetLabelPage(p, nval);
403✔
103
                if (DISP_NONE != PseudoORG && LABEL_PAGE_UNDEFINED != dispPageNum) {
235✔
104
                        // enforce explicit request of fake DISP page
105
                        nval = dispPageNum;
3✔
106
                } else {
107
                        // current page
108
                        nval = Page->Number;
232✔
109
                }
110
                return 1;
235✔
111
        } else if (*p == '$') {
8,383✔
112
                ++p;
3,011✔
113
                nval = CurAddress;
3,011✔
114
                if (Relocation::type && Relocation::areLabelsOffset && DISP_INSIDE_RELOCATE != PseudoORG) {
3,011✔
115
                        nval += Relocation::alternative_offset;
48✔
116
                }
117
                return 1;
3,011✔
118
        } else if (!(res = GetCharConst(p, nval))) {
5,372✔
119
                if (synerr) Error("Syntax error", p, IF_FIRST);
1,648✔
120
                return 0;
1,648✔
121
        }
122
        return res;
25,859✔
123
}
124

125
static int ParseExpUnair(char*& p, aint& nval) {
1,706,439✔
126
        SkipBlanks(p);
1,706,439✔
127
        char* oldP = p;
1,706,439✔
128
        if (cmphstr(p, "norel", true)) {
1,706,439✔
129
                // switch off alternative relocation evaluation for the following part of expression
130
                const bool oldAreLabelsOffset = Relocation::areLabelsOffset;
114✔
131
                Relocation::areLabelsOffset = false;
114✔
132
                int norelParseRes = ParseExpPrim(p, nval);                        // higher priority than other unary
114✔
133
                Relocation::areLabelsOffset = oldAreLabelsOffset;        // restore alternative evaluation
114✔
134
                if (norelParseRes) return 1;
114✔
135
                // "norel" operator didn't parse successfully, try to ignore it (will treat it as label)
136
                p = oldP;
6✔
137
        }
138
        if (cmphstr(p, "exist", true)) {
1,706,331✔
139
                int existEval = 0, hasParentheses = need(p, '(');
180✔
140
                if (hasParentheses || isLabelStart(p)) {
180✔
141
                        existEval = LabelExist(p, nval);
171✔
142
                }
143
                if (existEval && hasParentheses) {
180✔
144
                        existEval = need(p, ')');                                                // check closing parenthesis
21✔
145
                }
146
                if (existEval) return 1;
180✔
147
                p = oldP;
24✔
148
        }
149
        if (cmphstr(p, "sizeof", true)) {
1,706,175✔
150
                int sizeofEval = 0, hasParentheses = need(p, '(');
207✔
151
                if (hasParentheses || isLabelStart(p)) {                        // may tag label as +sizeof even when closing parenthesis
207✔
152
                        sizeofEval = GetLabelSize(p, nval);                                // is missing: OK, it's just extra trait causing extra tracking
207✔
153
                }
154
                if (hasParentheses) {
207✔
155
                        sizeofEval &= need(p, ')');
207✔
156
                }
157
                if (sizeofEval) return 1;        // valid label name (and valid optional parentheses)
207✔
158
                p = oldP;                                        // invalid label name or parentheses, try to ignore "sizeof"
6✔
159
        }
160
        if (cmphstr(p, "pair", true)) {
1,705,974✔
161
                aint hi = 0, lo = 0;
24✔
162
                if (need(p, '(') && ParseExpressionEntry(p, hi) && need(p, ',') && ParseExpressionEntry(p, lo) && need(p, ')')) {
24✔
163
                        check8(hi);
12✔
164
                        check8(lo);
12✔
165
                        nval = ((hi & 0xFF) << 8) | (lo & 0xFF);
12✔
166
                        return 1;
12✔
167
                } else {
168
                        p = oldP;
12✔
169
                        return 0;
12✔
170
                }
171
        }
172
        aint right;
173
        int oper;
174
        if ((oper = need(p, "! ~ + - ")) || \
1,705,950✔
175
                (oper = needa(p, "not", '!', "low", 'l', "high", 'h', true)) || \
3,376,968✔
176
                (oper = needa(p, "abs", 'a', "u16", 'u', nullptr, 0, true)) ) {
1,671,018✔
177
                switch (oper) {
34,989✔
178
                case '!':
208✔
179
                        if (!ParseExpUnair(p, right)) return 0;
208✔
180
                        nval = -!right;
150✔
181
                        break;
150✔
182
                case '~':
18✔
183
                        if (!ParseExpUnair(p, right)) return 0;
18✔
184
                        nval = ~right;
9✔
185
                        break;
9✔
186
                case '+':
10,842✔
187
                        if (!ParseExpUnair(p, right)) return 0;
10,842✔
188
                        nval = right;
10,821✔
189
                        break;
10,821✔
190
                case '-':
1,746✔
191
                        if (!ParseExpUnair(p, right)) return 0;
1,746✔
192
                        nval = ~right + 1;
1,743✔
193
                        break;
1,743✔
194
                case 'l':
15,857✔
195
                        if (!ParseExpUnair(p, right)) return 0;
15,857✔
196
                        nval = right & 255;
15,854✔
197
                        break;
15,854✔
198
                case 'h':
6,261✔
199
                        if (!ParseExpUnair(p, right)) return 0;
6,261✔
200
                        nval = (right >> 8) & 255;
6,258✔
201
                        break;
6,258✔
202
                case 'a':
48✔
203
                        if (!ParseExpUnair(p, right)) return 0;
48✔
204
                        nval = abs(right);
48✔
205
                        break;
48✔
206
                case 'u':
9✔
207
                        if (!ParseExpUnair(p, right)) return 0;
9✔
208
                        nval = right & 0xFFFF;
6✔
209
                        break;
6✔
UNCOV
210
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
211
                }
212
                return 1;
34,889✔
213
        } else {
214
                return ParseExpPrim(p, nval);
1,670,961✔
215
        }
216
}
217

218
static int ParseExpMul(char*& p, aint& nval) {
1,667,565✔
219
        aint left, right;
220
        int oper;
221
        if (!ParseExpUnair(p, left)) return 0;
1,667,565✔
222
        while ((oper = need(p, "* / % ")) || (oper = needa(p, "mod", '%'))) {
1,667,563✔
223
                if (!ParseExpUnair(p, right)) return 0;
3,885✔
224
                switch (oper) {
3,873✔
225
                case '*':
1,218✔
226
                        left *= right; break;
1,218✔
227
                case '/':
237✔
228
                        left = right ? left / right : 0;
237✔
229
                        if (!right) Error("Division by zero");
237✔
230
                        break;
237✔
231
                case '%':
2,418✔
232
                        left = right ? left % right : 0;
2,418✔
233
                        if (!right) Error("Division by zero");
2,418✔
234
                        break;
2,418✔
235
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
236
                }
237
        }
238
        nval = left;
1,663,678✔
239
        return 1;
1,663,678✔
240
}
241

242
static int ParseExpAdd(char*& p, aint& nval) {
1,338,936✔
243
        aint left, right;
244
        int oper;
245
        if (!ParseExpMul(p, left)) return 0;
1,338,936✔
246
        while ((oper = need(p, "+ - "))) {
1,663,678✔
247
                if (!ParseExpMul(p, right)) return 0;
328,629✔
248
                if ('-' == oper) right = -right;
328,613✔
249
                left += right;
328,613✔
250
        }
251
        nval = left;
1,335,049✔
252
        return 1;
1,335,049✔
253
}
254

255
static int ParseExpShift(char*& p, aint& nval) {
1,319,378✔
256
        aint left, right;
257
        uint32_t l;
258
        int oper;
259
        if (!ParseExpAdd(p, left)) return 0;
1,319,378✔
260
        while ((oper = need(p, "<<>>")) || (oper = needa(p, "shl", '<' + '<', "shr", '>' + '>'))) {
1,335,049✔
261
                if (oper == '>' + '>' && *p == '>') {
19,558✔
262
                        ++p;
15✔
263
                        oper += '>';
15✔
264
                }
265
                if (!ParseExpAdd(p, right)) return 0;
19,558✔
266
                switch (oper) {
19,543✔
267
                case '<'+'<':
972✔
268
                        left <<= right; break;
972✔
269
                case '>'+'>':
18,559✔
270
                        left >>= right; break;
18,559✔
271
                case '>'+'>'+'>':
12✔
272
                        l = left; l >>= right; left = l; break;
12✔
273
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
274
                }
275
        }
276
        nval = left;
1,315,491✔
277
        return 1;
1,315,491✔
278
}
279

280
static int ParseExpMinMax(char*& p, aint& nval) {
1,319,345✔
281
        aint left, right;
282
        int oper;
283
        if (!ParseExpShift(p, left)) return 0;
1,319,345✔
284
        while ((oper = need(p, "<?>?"))) {
1,315,491✔
285
                if (!ParseExpShift(p, right)) return 0;
33✔
286
                switch (oper) {
27✔
287
                case '<'+'?':
9✔
288
                        left = left < right ? left : right; break;
9✔
289
                case '>'+'?':
18✔
290
                        left = left > right ? left : right; break;
18✔
291
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
292
                }
293
        }
294
        nval = left;
1,315,458✔
295
        return 1;
1,315,458✔
296
}
297

298
static int ParseExpCmp(char*& p, aint& nval) {
1,018,282✔
299
        aint left, right;
300
        int oper;
301
        if (!ParseExpMinMax(p, left)) return 0;
1,018,282✔
302
        while ((oper = need(p, "<=>=< > "))) {
1,315,458✔
303
                if (!ParseExpMinMax(p, right)) return 0;
301,063✔
304
                switch (oper) {
301,051✔
305
                case '<':
747✔
306
                        left = -(left < right); break;
747✔
307
                case '>':
33✔
308
                        left = -(left > right); break;
33✔
309
                case '<'+'=':
300,220✔
310
                        left = -(left <= right); break;
300,220✔
311
                case '>'+'=':
51✔
312
                        left = -(left >= right); break;
51✔
313
                default: Error("internal error", nullptr, FATAL); break;        // unreachable
×
314
                }
315
        }
316
        nval = left;
1,014,395✔
317
        return 1;
1,014,395✔
318
}
319

320
static int ParseExpEqu(char*& p, aint& nval) {
1,011,453✔
321
        aint left, right;
322
        int oper;
323
        if (!ParseExpCmp(p, left)) return 0;
1,011,453✔
324
        while ((oper = need(p, "=_==!="))) {
1,014,395✔
325
                if (!ParseExpCmp(p, right)) return 0;
6,829✔
326
                left = (('!'+'=') == oper) ? -(left != right) : -(left == right);
6,820✔
327
        }
328
        nval = left;
1,007,566✔
329
        return 1;
1,007,566✔
330
}
331

332
static int ParseExpBitAnd(char*& p, aint& nval) {
991,795✔
333
        aint left, right;
334
        if (!ParseExpEqu(p, left)) return 0;
991,795✔
335
        while (need(p, "&_") || needa(p, "and", '&')) {
1,007,566✔
336
                if (!ParseExpEqu(p, right)) return 0;
19,658✔
337
                left &= right;
19,652✔
338
        }
339
        nval = left;
987,908✔
340
        return 1;
987,908✔
341
}
342

343
static int ParseExpBitXor(char*& p, aint& nval) {
991,650✔
344
        aint left, right;
345
        if (!ParseExpBitAnd(p, left)) return 0;
991,650✔
346
        while (need(p, "^ ") || needa(p, "xor", '^')) {
987,908✔
347
                if (!ParseExpBitAnd(p, right)) return 0;
145✔
348
                left ^= right;
139✔
349
        }
350
        nval = left;
987,763✔
351
        return 1;
987,763✔
352
}
353

354
static int ParseExpBitOr(char*& p, aint& nval) {
991,513✔
355
        aint left, right;
356
        if (!ParseExpBitXor(p, left)) return 0;
991,513✔
357
        while (need(p, "|_") || needa(p, "or", '|')) {
987,763✔
358
                if (!ParseExpBitXor(p, right)) return 0;
137✔
359
                left |= right;
131✔
360
        }
361
        nval = left;
987,626✔
362
        return 1;
987,626✔
363
}
364

365
static int ParseExpLogAnd(char*& p, aint& nval) {
990,246✔
366
        aint left, right;
367
        if (!ParseExpBitOr(p, left)) return 0;
990,246✔
368
        while (need(p, "&&")) {
987,626✔
369
                if (!ParseExpBitOr(p, right)) return 0;
1,267✔
370
                left = -(left && right);
1,261✔
371
        }
372
        nval = left;
986,359✔
373
        return 1;
986,359✔
374
}
375

376
static int ParseExpLogOr(char*& p, aint& nval) {
990,213✔
377
        aint left, right;
378
        if (!ParseExpLogAnd(p, left)) return 0;
990,213✔
379
        while (need(p, "||")) {
986,359✔
380
                if (!ParseExpLogAnd(p, right)) return 0;
33✔
381
                left = -(left || right);
30✔
382
        }
383
        nval = left;
986,326✔
384
        return 1;
986,326✔
385
}
386

387
static int ParseExpressionEntry(char*& p, aint& nval) {
990,213✔
388
        if (ParseExpLogOr(p, nval)) return 1;
990,213✔
389
        nval = 0;
3,887✔
390
        return 0;
3,887✔
391
}
392

393
int ParseExpression(char*& p, aint& nval) {
931,994✔
394
        // if relocation is active, do the full evaluation in syntax-error-OFF mode with alternative
395
        // label values, and remember the result (to compare it with regular evaluation afterward)
396
        aint relocationVal = 0;
931,994✔
397
        int relocationRes = 0;
931,994✔
398
        if (Relocation::type) {
931,994✔
399
                char* altP = p;
14,978✔
400
                bool osynerr = synerr;
14,978✔
401
                synerr = false;
14,978✔
402
                Relocation::areLabelsOffset = true;
14,978✔
403
                relocationRes = ParseExpressionEntry(altP, relocationVal);
14,978✔
404
                Relocation::areLabelsOffset = false;
14,978✔
405
                synerr = osynerr;
14,978✔
406
        }
407
        // if relocation is off, or the alternative run did finish already, do regular evaluation
408
        int res = ParseExpressionEntry(p, nval);
931,994✔
409
        // set the Relocation::isResultAffected if the two alternative results are different
410
        if (res && relocationRes) {
931,994✔
411
                const bool isAffected = (relocationVal != nval);
14,621✔
412
                Relocation::isResultAffected |= isAffected;
14,621✔
413
                Relocation::deltaType =
14,621✔
414
                        (Relocation::alternative_offset == (relocationVal - nval)) ? Relocation::REGULAR :
26,096✔
415
                        ((Relocation::HIGH == Relocation::type) && ((Relocation::alternative_offset >> 8) == (relocationVal - nval))) ? Relocation::HIGH :
11,475✔
416
                        Relocation::OFF;
417
        }
418
        return res;
931,994✔
419
}
420

421
int ParseExpressionNoSyntaxError(char*& lp, aint& val) {
837,161✔
422
        bool osynerr = synerr;
837,161✔
423
        synerr = false;
837,161✔
424
        int ret_val = ParseExpression(lp, val);
837,161✔
425
        synerr = osynerr;
837,161✔
426
        return ret_val;
837,161✔
427
}
428

429
static int ParseExpressionInSubstitution(char *& lp, aint& val) {
15,632✔
430
        assert(!IsSubstituting);
15,632✔
431
        IsSubstituting = true;
15,632✔
432
        int ret_val = ParseExpressionNoSyntaxError(lp, val);
15,632✔
433
        IsSubstituting = false;
15,632✔
434
        return ret_val;
15,632✔
435
}
436

437
// returns 0 on syntax error, 1 on expression which is not enclosed in parentheses
438
// 2 when whole expression is in [] or () (--syntax=b/B affects when "2" is reported)
439
int ParseExpressionMemAccess(char*& p, aint& nval) {
13,591✔
440
        const EBracketType bt = OpenBracket(p);
13,591✔
441
        // if round parenthesis starts the expression, calculate pointer where it ends (and move "p" back on "(")
442
        char* const expectedEndBracket = (BT_ROUND == bt) ? ParenthesesEnd(--p) : nullptr;
13,591✔
443
        if (!ParseExpression(p, nval)) return 0;        // evaluate expression
13,591✔
444
        if (BT_NONE == bt) return 1;                                // no parentheses are always "value"
13,558✔
445
        if (BT_ROUND == bt) return (expectedEndBracket == p) ? 2 : 1;        // round parentheses are "memory" when end is as expected
3,846✔
446
        if (CloseBracket(p)) return 2;                                // square brackets must be closed properly, then it is "memory"
300✔
447
        return 0;        // curly brackets are not detect by OpenBracket, but if they would, it would work same as square here
18✔
448
}
449

450
void ParseAlignArguments(char* & src, aint & alignment, aint & fill) {
462✔
451
        SkipBlanks(src);
462✔
452
        const char * const oldSrc = src;
462✔
453
        fill = -1;
462✔
454
        if (!ParseExpression(src, alignment)) {
462✔
455
                alignment = -1;
42✔
456
                return;
42✔
457
        }
458
        if (Relocation::type) {
420✔
459
                WarningById(W_RELOCATABLE_ALIGN);
24✔
460
        }
461
        // check if alignment value is power of two (0..15-th power only)
462
        if (alignment < 1 || (1<<15) < alignment || (alignment & (alignment-1))) {
420✔
463
                Error("[ALIGN] Illegal align", oldSrc, SUPPRESS);
15✔
464
                alignment = 0;
15✔
465
                return;
15✔
466
        }
467
        if (!comma(src)) return;
405✔
468
        if (!ParseExpressionEntry(lp, fill)) {
105✔
469
                Error("[ALIGN] fill-byte expected after comma", bp, IF_FIRST);
3✔
470
                fill = -1;
3✔
471
        } else if (fill < 0 || 255 < fill) {
102✔
472
                Error("[ALIGN] Illegal align fill-byte", oldSrc, SUPPRESS);
6✔
473
                fill = -1;
6✔
474
        }
475
}
476

477
static constexpr const char GLUE_TAG = 10;                // '\n' CR abused for glues, can't be part of source line
478
static constexpr const char GLUE_CHAR = '_';
479
static bool glueToProcess = false;
480

481
static bool ReplaceDefineInternal(char* lp, char* const nl) {
730,463✔
482
        int definegereplaced = 0,dr;
730,463✔
483
        char* rp = nl,* nid;
730,463✔
484
        const char* ver;
485
        bool isDefDir = false;        // to remember if one of DEFINE-related directives was used
730,463✔
486
        bool afterNonAlphaNum, afterNonAlphaNumNext = true;
730,463✔
487
        char defarrayCountTxt[16] = { 0 };
730,463✔
488
        while (*lp && ((rp - nl) < LINEMAX)) {
7,849,114✔
489
                const char c1 = lp[0], c2 = lp[1];
7,197,328✔
490
                afterNonAlphaNum = afterNonAlphaNumNext;
7,197,328✔
491
                afterNonAlphaNumNext = !isalnum((byte)c1);
7,197,328✔
492
                if (c1 == '/' && c2 == '*') {        // block-comment local beginning (++block_nesting)
7,197,328✔
493
                        lp += 2;
327✔
494
                        ++comlin;
327✔
495
                        continue;
5,770,442✔
496
                }
497
                if (comlin) {
7,197,001✔
498
                        if (c1 == '*' && c2 == '/') {
6,834✔
499
                                lp += 2;
321✔
500
                                // insert space into line, if the block ending may have affected parsing of line
501
                                if (1 == comlin) {
321✔
502
                                        *rp++ = ' ';                // ^^ otherwise this line is completely commented out
297✔
503
                                }
504
                                --comlin;        // decrement block comment counter
321✔
505
                        } else {
506
                                ++lp;                // just skip all characters inside comment block
6,513✔
507
                        }
508
                        continue;
6,834✔
509
                }
510
                // for following code (0 == comlin) (unless it has its own parse loop)
511

512
                // single line comments -> finish
513
                if (c1 == ';' || (c1 == '/' && c2 == '/')) {
7,190,167✔
514
                        // set empty eol line comment, if the source of data is still the original "line" buffer
515
                        if (!eolComment && line <= lp && lp < line+LINEMAX) eolComment = lp;
78,677✔
516
                        break;
78,677✔
517
                }
518

519
                // strings parsing
520
                if (afterNonAlphaNum && (c1 == '"' || c1 == '\'')) {
7,111,490✔
521
                        *rp++ = *lp++;                                // copy the string delimiter (" or ')
37,770✔
522
                        // apostrophe inside apostrophes ('') will parse as end + start of another string
523
                        // which sort of "accidentally" leads to correct final results
524
                        while (*lp && c1 != *lp) {        // until end of current string is reached (or line ends)
166,580✔
525
                                // inside double quotes the backslash should escape (anything after it)
526
                                if ('"' == c1 && '\\' == *lp && lp[1]) *rp++ = *lp++;        // copy escaping backslash extra
128,810✔
527
                                *rp++ = *lp++;                        // copy string character
128,810✔
528
                        }
529
                        if (*lp) *rp++ = *lp++;                // copy the ending string delimiter (" or ')
37,770✔
530
                        continue;
37,770✔
531
                }
532

533
                if (!isLabelStart(lp, false)) {
7,073,720✔
534
                        *rp++ = *lp++;
5,725,511✔
535
                        continue;
5,725,511✔
536
                }
537

538
                // update "is define-related directive" for remainder of the line
539
                char* kp = lp;
1,348,209✔
540
                isDefDir |= afterNonAlphaNum && (cmphstr(kp, "define+") || cmphstr(kp, "define")
2,677,399✔
541
                        || cmphstr(kp, "undefine") || cmphstr(kp, "defarray+") || cmphstr(kp, "defarray")
1,329,190✔
542
                        || cmphstr(kp, "ifdef") || cmphstr(kp, "ifndef"));
1,328,812✔
543
                // if DEFINE-related directive was used, only macro-arguments are substituted
544
                // in the remaining part of the line, the define-based substitution is inhibited till EOL
545

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

551
                //TODO - maybe consider the substitution search to go downward, from longest term to shortest subterm
552
                ResetGrowSubId();
1,348,209✔
553
                char* nextSubIdLp = lp, * wholeIdLp = lp;
1,348,209✔
554
                do { //while(islabchar(*lp));
555
                        nid = GrowSubId(lp);                // grow the current sub-id part by part, checking each combination for substitution
2,107,814✔
556
                        // defines/macro arguments can substitute in the middle of ID only if they don't start with underscore
557
                        const bool canSubstituteInside = '_' != nid[0] || nextSubIdLp == wholeIdLp;
2,107,814✔
558
                        if (macrolabp && canSubstituteInside && (ver = MacroDefineTable.getverv(nid))) {
2,107,814✔
559
                                dr = 2;                        // macro argument substitution is possible
17,667✔
560
                        } else if (!isDefDir && canSubstituteInside && (ver = DefineTable.Get(nid))) {
2,090,147✔
561
                                dr = 1;                        // DEFINE substitution is possible
32,406✔
562
                                //handle DEFARRAY case
563
                                if (DefineTable.DefArrayList) {
32,406✔
564
                                        ver = nid;        // in case of some error, just copy the array id "as is"
15,650✔
565
                                        CStringsList* a = DefineTable.DefArrayList;
15,650✔
566
                                        while (White(*lp)) GrowSubIdByExtraChar(lp);
15,650✔
567
                                        aint val;
568
                                        if ('[' != *lp) Error("[ARRAY] Expression error", nextSubIdLp, SUPPRESS);
15,650✔
569
                                        if ('[' == *lp && '#' == lp[1] && ']' == lp[2]) {        // calculate size of defarray
15,650✔
570
                                                lp += 3;
15✔
571
                                                val = 0;
15✔
572
                                                while (a) {
87✔
573
                                                        ++val;
72✔
574
                                                        a = a->next;
72✔
575
                                                }
576
                                                SPRINTF1(defarrayCountTxt, 16, "%d", val);
15✔
577
                                                ver = defarrayCountTxt;
15✔
578
                                        } else {
579
                                                char* expLp = lp + ('[' == *lp);        // the '[' will become part of subId in didParseBrackets
15,635✔
580
                                                IsLabelNotFound = false;
15,635✔
581
                                                bool didParseBrackets = '[' == *lp && GrowSubIdByExtraChar(lp) && ParseExpressionInSubstitution(lp, val) && ']' == *lp;
15,635✔
582
                                                if (didParseBrackets && !IsLabelNotFound) {
15,635✔
583
                                                        // expression was successfully parsed and all values were known
584
                                                        ++lp;
15,419✔
585
                                                        while (0 < val && a) {
59,027✔
586
                                                                a = a->next;
43,608✔
587
                                                                --val;
43,608✔
588
                                                        }
589
                                                        if (val < 0 || NULL == a) {
15,419✔
590
                                                                *defarrayCountTxt = 0;                // substitute with empty string
15✔
591
                                                                ver = defarrayCountTxt;
15✔
592
                                                                Error("[ARRAY] index not in 0..<Size-1> range", nextSubIdLp, SUPPRESS);
15✔
593
                                                        } else {
594
                                                                ver = a->string;        // substitute with array value
15,404✔
595
                                                        }
596
                                                } else {        // no substitution of array possible at this time (index eval / syntax error)
597
                                                        lp = expLp;                                // restore lp in case expression parser went ahead a lot
216✔
598
                                                        dr = -1;// write into output, but don't count as replacement
216✔
599
                                                }
600
                                        }
601
                                }
602
                        } else {
603
                                dr = 0;                        // no possible substitution found
2,057,741✔
604
                                ver = nid;
2,057,741✔
605
                        }
606
                        // check if no substitution was found, and there's no more chars to extend SubId
607
                        if (0 == dr && !islabchar(*lp)) {
2,107,814✔
608
                                lp = nextSubIdLp;                // was fully extended, no match, "eat" first subId
1,473,366✔
609
                                ResetGrowSubId();
1,473,366✔
610
                                ver = GrowSubId(lp);        // find the first SubId again, for the copy
1,473,366✔
611
                                dr = -1;                                // write into output, but don't count as replacement
1,473,366✔
612
                        }
613
                        if (0 < dr) {
2,107,814✔
614
                                definegereplaced = 1;        // above zero => count as replacement
49,857✔
615
                                // look ahead in "to" array to convert any whitespace enclosed `_` to glue tag
616
                                char* gluep = rp;                // first skip whitespace ahead of substitution
49,857✔
617
                                while ((nl < gluep--) && (GLUE_TAG != gluep[0]) && (White(gluep[0]))) /* empty */;
156,919✔
618
                                // now check if there was whitespace, is glue char '_' and further whitespace ahead of it
619
                                if ((nl < gluep) && (gluep + 1 < rp) && (GLUE_CHAR == gluep[0]) && White(gluep[-1])) {
49,857✔
620
                                        gluep[0] = GLUE_TAG;// convert it to glue tag for later
33✔
621
                                        glueToProcess = true;
33✔
622
                                }
623
                                // look after in "from" array to convert any whitespace enclosed `_` to glue tag
624
                                for (gluep = lp; GLUE_TAG != gluep[0] && White(gluep[0]); ) ++gluep;
58,351✔
625
                                if ((lp < gluep) && (GLUE_CHAR == gluep[0]) && White(gluep[1])) {
49,857✔
626
                                        gluep[0] = GLUE_TAG;// convert it to glue tag for later
84✔
627
                                        glueToProcess = true;
84✔
628
                                }
629
                        }
630
                        if (0 != dr) {                                // any non-zero dr => write to the output
2,107,814✔
631
                                while (*ver && ((rp - nl) < LINEMAX)) *rp++ = *ver++;                // replace the string into target buffer
5,914,106✔
632
                                // reset subId parser to catch second+ subId in current Id
633
                                ResetGrowSubId();
1,523,439✔
634
                                nextSubIdLp = lp;
1,523,439✔
635
                        }
636
                        // continue with extending the subId, if there's still something to parse
637
                } while(islabchar(*lp));
2,107,814✔
638
        } // while(*lp)
639
        // add line terminator to the output buffer
640
        *rp++ = 0;
730,463✔
641
        if (LINEMAX <= (rp - nl)) {
730,463✔
642
                Error("line too long after macro expansion", nl, SUPPRESS);
75✔
643
        }
644
        // if no substitution happened, but there are glue bytes, glue it all together now
645
        if (0 == definegereplaced && glueToProcess) {
730,463✔
646
                // now glue the pieces together around each glue
647
                int dropChars = 0;
69✔
648
                for (rp = nl; *rp; ++rp) {
1,362✔
649
                        if (GLUE_TAG == *rp) {
1,293✔
650
                                char* leftp = rp + dropChars, * rightp = rp;
117✔
651
                                while (nl < leftp && White(leftp[-1])) --leftp;        // eat all whitespace to left
324✔
652
                                while (White(rightp[0])) ++rightp;                                // eat all whitespace to right
747✔
653
                                // leftp points at left-most whitespace char, rightp points at first non-whitespace or \0
654
                                if ((nl == leftp) || (0 == rightp[0])) rp[0] = GLUE_CHAR;        // nothing to glue with, restore
117✔
655
                                else {
656
                                        dropChars = (leftp - rightp);                                // discard this whitespace + glue block
102✔
657
                                        rp = rightp;
102✔
658
                                }
659
                        }
660
                        if (dropChars < 0) rp[dropChars] = rp[0];        // move other chars over removed areas
1,293✔
661
                }
662
                rp[dropChars] = 0;                // make sure there's zero terminator also for glued result
69✔
663
                glueToProcess = false;
69✔
664
                return true;                        // report modification
69✔
665
        }
666
        // check if whole line is just blanks, then return just empty one
667
        rp = nl;
730,394✔
668
        if (!glueToProcess && SkipBlanks(rp)) *nl = 0;                // only when glues were processed as well
730,394✔
669
        substitutedLine = nl;                // set global pointer to the latest substituted version
730,394✔
670
        return definegereplaced;
730,394✔
671
}
672

673
char* ReplaceDefine(char* src) {
702,622✔
674
        glueToProcess = false;
702,622✔
675
        char* to = sline;
702,622✔
676
        for (int maxIter = 31; maxIter--;) {
730,478✔
677
                if (!ReplaceDefineInternal(src, to)) return to;        // no more replacements
730,463✔
678
                // Some define were replaced, now ping-pong sline <-> sline2 buffers
679
                src = to;
27,856✔
680
                to = (sline == to) ? sline2 : sline;
27,856✔
681
        }
682
        Error("Unable to finish substitutions, line after last iteration", src, SUPPRESS);
15✔
683
        return src;
15✔
684
}
685

686
void SetLastParsedLabel(const char* label) {
26,598✔
687
        if (LastParsedLabel) free(LastParsedLabel);
26,598✔
688
        if (nullptr != label) {
26,598✔
689
                LastParsedLabel = STRDUP(label);
24,419✔
690
                if (nullptr == LastParsedLabel) ErrorOOM();
24,419✔
691
                LastParsedLabelLine = CompiledCurrentLine;
24,419✔
692
        } else {
693
                LastParsedLabel = nullptr;
2,179✔
694
                LastParsedLabelLine = 0;
2,179✔
695
        }
696
}
26,598✔
697

698
void ParseLabel() {
592,329✔
699
        if (White()) return;
593,563✔
700
        if (':' == *lp) {                                                        // detect possible SIZEOF boundary tag
365,554✔
701
                bool isLocal = ('.' == lp[1]);
96✔
702
                if (':' == lp[1 + isLocal]) {                        // tag detected, process it
96✔
703
                        LabelTable.SizeBoundary(isLocal ? CLabelTable::BOUNDARY_LOCAL : CLabelTable::BOUNDARY_MAIN);
87✔
704
                        lp += (2 + isLocal);
87✔
705
                        return;
87✔
706
                }
707
        }
708
        if (Options::syx.IsPseudoOpBOF && ParseDirective(true)) {
365,467✔
709
                if (!SkipBlanks()) Error("Unexpected", lp);
207✔
710
                return;
207✔
711
        }
712
        char temp[LINEMAX], * tp = temp, * ttp;
365,260✔
713
        aint val, equPageNum = LABEL_PAGE_UNDEFINED, smcOffset = 0;
365,260✔
714
        // copy the label name into `temp` array
715
        while (*lp && !White() && *lp != ':' && *lp != '=' && *lp != '+') {
1,635,403✔
716
                *tp = *lp; ++tp; ++lp;
1,270,143✔
717
        }
718
        *tp = 0;
365,260✔
719
        // handle the special SMC_offset syntax "<label>+<single_digit>"
720
        if ('+' == lp[0] && isdigit(byte(lp[1])) && !isalnum(byte(lp[2]))) {
365,260✔
721
                smcOffset = lp[1] - '0';
72✔
722
                lp += 2;
72✔
723
        }
724
        // handle the special SMC_offset syntax "<label>+*" to target significant immediate of the instruction
725
        if ('+' == lp[0] && '*' == lp[1] && !isalnum(byte(lp[2]))) {
365,260✔
726
                assert(!sourcePosStack.empty());
413✔
727
                smcOffset = 1;                                        // heuristic value 1 for first pass or when something fails
413✔
728
                lp += 2;
413✔
729
                if (1 == pass) {
413✔
730
                        // put the current source position into smart-smc (if first pass, or missing record)
731
                        smartSmcLines.push_back(sourcePosStack.back());
137✔
732
                        smartSmcLines.back().colBegin = ~0U;                // mark as unresolved
137✔
733
                } else {
734
                        if ((smartSmcLines.size() <= smartSmcIndex)
276✔
735
                                || smartSmcLines.at(smartSmcIndex) != sourcePosStack.back()) {
276✔
736
                                Error("mismatch of smart-SMC positions between passes");
10✔
737
                        } else {
738
                                auto & smartSmcLine = smartSmcLines.at(smartSmcIndex);
266✔
739
                                if (~0U == smartSmcLine.colBegin) {
266✔
740
                                        Error("unresolved smart-SMC symbol (no significant target)");
100✔
741
                                } else {
742
                                        smcOffset = smartSmcLine.colBegin;        // use the smart value from previous pass
166✔
743
                                        smartSmcLine.colBegin = ~0U;                // mark as unsolved again
166✔
744
                                }
745
                        }
746
                }
747
                ++smartSmcIndex;
413✔
748
        }
749
        if (*lp == ':') ++lp;        // eat the optional colon after label
365,260✔
750
        tp = temp;
365,260✔
751
        SkipBlanks();
365,260✔
752
        IsLabelNotFound = false;
365,260✔
753
        if (isdigit((byte)*tp)) {
365,260✔
754
                if (smcOffset) {
367✔
755
                        Error("Temporary label can't use SMC-offset");
12✔
756
                        return;
12✔
757
                }
758
                ttp = tp;
355✔
759
                while (*ttp && isdigit((byte)*ttp)) ++ttp;
851✔
760
                if (*ttp) {
355✔
761
                        Error("Invalid temporary label (not a number)", temp);
3✔
762
                        return;
3✔
763
                }
764
                if (NeedEQU() || NeedDEFL()) {
352✔
765
                        Error("Number labels are allowed as address labels only, not for DEFL/=/EQU", temp, SUPPRESS);
27✔
766
                        return;
27✔
767
                }
768
                val = atoi(tp);
325✔
769
                if (!TemporaryLabelTable.InsertRefresh(val)) {
325✔
770
                        Error("Temporary labels flow differs in last pass (missing/new temporary label since previous pass)");
116✔
771
                }
772
        } else {
773
                if (isMacroNext()) {
364,893✔
774
                        if (smcOffset) Error("Macro name can't use SMC-offset");
261✔
775
                        else SetLastParsedLabel(tp);        // store raw label into "last parsed" without adding module/etc
255✔
776
                        return;                                        // and don't add it to labels table at all
898✔
777
                }
778
                bool IsDEFL = NeedDEFL(), IsEQU = NeedEQU();
364,632✔
779
                if (IsDEFL || IsEQU) {
364,632✔
780
                        Relocation::isResultAffected = false;
344,357✔
781
                        if (!ParseExpressionNoSyntaxError(lp, val)) {
344,357✔
782
                                Error("Expression error", lp);
9✔
783
                                val = 0;
9✔
784
                        }
785
                        if (IsLabelNotFound && IsDEFL) Error("Forward reference", NULL, EARLY);
344,357✔
786
                        // check for explicit page defined by EQU
787
                        if (IsEQU && comma(lp)) {
344,357✔
788
                                if (!ParseExpressionNoSyntaxError(lp, equPageNum)) {
30✔
789
                                        Error("Expression error", lp);
6✔
790
                                        equPageNum = LABEL_PAGE_UNDEFINED;
6✔
791
                                }
792
                        }
793

794
                        // after EQU/DEFL expressions there must be <EOL>, anything else is syntax error
795
                        // this was added in v1.17.1 after realizing the line `label=$+1 and 7`
796
                        // does evaluate whole "$+1 and 7" as expression, not as instruction `and`
797
                        // (same problem exists with and/or/xor and if you have macro named after operator)
798
                        if (!SkipBlanks(lp)) {
344,396✔
799
                                Error("Unexpected", lp);
39✔
800
                                SkipToEol(lp);
39✔
801
                        }
802

803
                } else {
804
                        int gl = 0;
20,275✔
805
                        char* p = lp,* n;
20,275✔
806
                        SkipBlanks(p);
20,275✔
807
                        if (*p == '@') {
20,275✔
808
                                ++p; gl = 1;
24✔
809
                        }
810
                        if ((n = GetID(p)) && StructureTable.Emit(n, tp, p, gl)) {
20,275✔
811
                                if (smcOffset) Error("Structure instance can't use SMC-offset");
582✔
812
                                lp = p;
582✔
813
                                // this was instancing STRUCT, make it also define "main" label for future "local" ones
814
                                tp = ValidateLabel(tp, true);
582✔
815
                                if (tp) delete[] tp;
582✔
816
                                return;
582✔
817
                        }
818
                        val = CurAddress;
19,693✔
819
                }
820
                val += smcOffset;
364,050✔
821
                ttp = tp;
364,050✔
822
                bool isLocal;
823
                if (!(tp = ValidateLabel(isLocal, tp, true))) {
364,050✔
824
                        return;
48✔
825
                }
826
                // Copy label name to last parsed label variable
827
                if (!IsDEFL) SetLastParsedLabel(tp);
364,002✔
828
                unsigned traits = (isLocal ? LABEL_IS_LOCAL : 0) | (IsEQU ? LABEL_IS_EQU : 0) | (IsDEFL ? LABEL_IS_DEFL : 0) | (smcOffset ? LABEL_IS_SMC : 0);
364,002✔
829
                if (pass == LASTPASS) {
364,002✔
830
                        SLabelTableEntry* label = LabelTable.Find(tp, true);
121,371✔
831
                        if (nullptr == label && IsDEFL) {        // DEFL labels can be defined as late as needed (including pass3)
121,371✔
832
                                if (LabelTable.Insert(tp, val, traits)) label = LabelTable.Find(tp, true);
3✔
833
                        }
834
                        if (nullptr == label) {                // should have been already defined before last pass
121,371✔
835
                                Error("Label not found", tp);
7✔
836
                                delete[] tp;
7✔
837
                                return;
7✔
838
                        }
839
                        if (IsDEFL) {                //re-set DEFL value
121,364✔
840
                                LabelTable.Insert(tp, val, traits);
113,312✔
841
                        } else if (IsSldExportActive()) {
8,052✔
842
                                // SLD (Source Level Debugging) tracing-data logging
843
                                WriteToSldFile(IsEQU ? equPageNum : label->page, val, IsEQU ? 'D' : 'F', tp);        //version 0
174✔
844
                                WriteToSldFile(IsEQU ? equPageNum : label->page, val, 'L', ExportLabelToSld(ttp, label));        //version 1
174✔
845
                        }
846

847
                        if (val != label->value) {
121,364✔
848
                                // unless special label like DEFL, the values from Pass 2 are intact, just label->updatePass is touched
849
                                // so in case the value mismatch happens, the CLabelTable::Update is used to fix values for Pass 3 (with warning)
850
                                static constexpr const int WARNING_HELP_BUFFER_MAX = 128;
851
                                char buf[WARNING_HELP_BUFFER_MAX];
852
                                SPRINTF2(buf, WARNING_HELP_BUFFER_MAX, "previous value %u not equal %u", label->value, val);
23✔
853
                                Warning("Label has different value in pass 3", buf);
23✔
854
                                LabelTable.Update(tp, val);
23✔
855
                        } else {
856
                                label->updatePass = pass;        // just "touch" it here in third pass
121,341✔
857
                        }
858
                } else if (pass == 2 && !LabelTable.Insert(tp, val, traits, equPageNum)) {
242,631✔
859
                        // label already exist, and did NOT need update, ie. duplicate label (could not find other code path to this)
860
                        if (!LabelTable.Update(tp, val)) assert(false); // unreachable, update will always work after insert failed
7✔
861
                } else if (pass == 1 && !LabelTable.Insert(tp, val, traits, equPageNum)) {
242,624✔
862
                        Error("Duplicate label", tp, EARLY);
6✔
863
                }
864
                // sizeof() implementation, must search for inserted label one more time (to do it in every pass)
865
                SLabelTableEntry* label = LabelTable.Find(tp, true);
363,995✔
866
                assert(label);
363,995✔
867
                // soft main/local boundary for current nesting level
868
                LabelTable.SizeBoundary(isLocal ? CLabelTable::BOUNDARY_LOCAL : CLabelTable::BOUNDARY_MAIN);
363,995✔
869
                // add new label to size tracking if it has SIZEOF trait
870
                if (LABEL_HAS_SIZE & label->traits) LabelTable.TrackForSize(label);
363,995✔
871

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

893
                delete[] tp;
363,995✔
894
        }
895
}
896

897
int ParseMacro() {
246,771✔
898
        int gl = 0, r = 0;
246,771✔
899
        char* p = lp, *n;
246,771✔
900
        SkipBlanks(p);
246,771✔
901
        if (*p == '@') {
246,771✔
902
                gl = 1; ++p;
45✔
903
        }
904
        if (!(n = GetID(p))) {
246,771✔
905
                return 0;
86✔
906
        }
907

908
        if (!gl) r = MacroTable.Emit(n, p);                // global '@' operator inhibits macros
246,685✔
909
        if (r == 2) return 1;        // successfully emitted
246,684✔
910
        if (r == 1) {                        // error reported
245,103✔
911
                lp = p;
69✔
912
                return 0;
69✔
913
        }
914

915
        // not a macro, see if it's structure
916
        if (StructureTable.Emit(n, nullptr, p, gl)) {
245,034✔
917
                lp = p;
174✔
918
                return 1;
174✔
919
        }
920

921
        return 0;
244,860✔
922
}
923

924
void ParseInstruction() {
245,009✔
925
        if ('@' == *lp) ++lp;                // skip single '@', if it was used to inhibit macro expansion
245,009✔
926
        if (ParseDirective()) return;
245,009✔
927
        Z80::GetOpCode();
120,375✔
928
}
929

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

942
//#define DEBUG_COUT_PARSE_LINE
943

944
// returns 1 when already fully processed (part of DUP/etc)
945
int PrepareLine() {
679,510✔
946
        ListSilentOrExternalEmits();
679,510✔
947

948
        ++CompiledCurrentLine;
679,510✔
949
        if (!RepeatStack.empty()) {
679,510✔
950
                SRepeatStack& dup = RepeatStack.top();
450,699✔
951
                if (!dup.IsInWork) {
450,699✔
952
                        lp = line;
24,409✔
953
                        dup.Pointer->next = new CStringsList(lp);
24,409✔
954
                        dup.Pointer = dup.Pointer->next;
24,409✔
955
#ifdef DEBUG_COUT_PARSE_LINE
956
                        fprintf(stderr, ">%d %d %c%ld-%d [%s]\n", pass, CurSourcePos.line,
957
                                        (!RepeatStack.empty() && RepeatStack.top().IsInWork ? '!' : '.'),RepeatStack.size(),
958
                                        (!RepeatStack.empty() ? RepeatStack.top().Level : 0), line);
959
#endif
960
                        // check if there's some label at beginning of the line, skip it
961
                        if (islabchar(*lp)) {
24,409✔
962
                                // if directives are enabled at beginning of line, check if it is nested DUP/REPT/WHILE/EDUP...
963
                                if (Options::syx.IsPseudoOpBOF && ParseDirective_REPT()) return 1;
4,296✔
964
                                // skip label chars and trailing colon
965
                                while (islabchar(*lp)) ++lp;
22,800✔
966
                                if (':' == *lp) ++lp;
4,281✔
967
                        }
968
                        // catch any nested DUP/WHILE/REPT and EDUP directives
969
                        ParseDirective_REPT();
24,394✔
970
                        return 1;
24,394✔
971
                }
972
        }
973
#ifdef DEBUG_COUT_PARSE_LINE
974
        fprintf(stderr, "|%d %d %c%ld-%d [%s]\n", pass, CurSourcePos.line,
975
                        (!RepeatStack.empty() && RepeatStack.top().IsInWork ? '!' : '.'), RepeatStack.size(),
976
                        (!RepeatStack.empty() ? RepeatStack.top().Level : 0), line);
977
#endif
978
        lp = ReplaceDefine(line);
655,101✔
979

980
#ifdef DEBUG_COUT_PARSE_LINE
981
        fprintf(stderr,"rdOut [%s]->[%s] %d\n", line, lp, comlin);
982
#endif
983

984
        // update current address by memory wrapping, current page, etc... (before the label is defined)
985
        if (DeviceID)        Device->CheckPage(CDevice::CHECK_NO_EMIT);
655,101✔
986
        ListAddress = CurAddress;
655,101✔
987

988
        if (!ConvertEncoding) {
655,101✔
989
                byte* lp2 = (byte*) lp;
108✔
990
                while (*lp2) {
1,518✔
991
                        if (128 <= *lp2) {
1,410✔
992
                                *lp2 = win2dos[(*lp2) - 128];
33✔
993
                        }
994
                        ++lp2;
1,410✔
995
                }
996
        }
997
        return 0;
655,101✔
998
}
999

1000
bool PrepareNonBlankMultiLine(char*& p) {
5,550✔
1001
        // loop while the current line is blank-only (read further lines until EOF or non-blank char)
1002
        while (SkipBlanks(p)) {
5,895✔
1003
                // if inside macro system, but without any more macro-lines in buffer, act as if "EOF"
1004
                // (to not leak into reading actual file while the macro is executing)
1005
                if (listmacro && nullptr == lijstp) return false;
354✔
1006
                // list the current (old) line
1007
                ListFile();
345✔
1008
                // read the next line
1009
                if (!ReadLine()) return false;
345✔
1010
                PrepareLine();
345✔
1011
                p = lp;
345✔
1012
        }
1013
        return true;
5,541✔
1014
}
1015

1016
void ParseLine(bool parselabels) {
679,165✔
1017
        if (PrepareLine()) return;
679,165✔
1018

1019
        if (eolComment && IsSldExportActive()) SldTrackComments();
654,756✔
1020

1021
        if (!*lp) {
654,756✔
1022
                char *srcNonWhiteChar = line;
59,395✔
1023
                SkipBlanks(srcNonWhiteChar);
59,395✔
1024
                // check if only "end-line" comment remained, treat that one as "empty" line too
1025
                if (';' == srcNonWhiteChar[0] || ('/' == srcNonWhiteChar[0] && '/' == srcNonWhiteChar[1]))
59,395✔
1026
                        srcNonWhiteChar = lp;                        // force srcNonWhiteChar to point to 0
32,313✔
1027
                if (*srcNonWhiteChar || comlin) {        // non-empty source line turned into nothing
59,395✔
1028
                        ListFile(true);                                        // or empty source inside comment-block -> "skipped"
210✔
1029
                } else {
1030
                        ListFile();                                                // empty source line outside of block-comment -> "normal"
59,185✔
1031
                }
1032
                return;
59,395✔
1033
        }
1034

1035
        if (parselabels) ParseLabel();
595,361✔
1036
        if (!SkipBlanks()) ParseMacro();
595,361✔
1037
        if (!SkipBlanks()) ParseInstruction();
595,360✔
1038
        if (!SkipBlanks()) Error("Unexpected", lp);
595,354✔
1039
        ListFile();
595,354✔
1040
}
1041

1042
void ParseLineSafe(bool parselabels) {
457,447✔
1043
        char* tmp = NULL, * tmp2 = NULL;
457,447✔
1044
        char* rp = lp;
457,447✔
1045
        if (sline[0] > 0) {
457,447✔
1046
                tmp = STRDUP(sline);
434,797✔
1047
                if (tmp == NULL) ErrorOOM();
434,797✔
1048
        }
1049
        if (sline2[0] > 0) {
457,447✔
1050
                tmp2 = STRDUP(sline2);
90,579✔
1051
                if (tmp2 == NULL) ErrorOOM();
90,579✔
1052
        }
1053

1054
        ParseLine(parselabels);
457,447✔
1055

1056
        *sline = 0;
457,446✔
1057
        *sline2 = 0;
457,446✔
1058

1059
        if (tmp2 != NULL) {
457,446✔
1060
                STRCPY(sline2, LINEMAX2, tmp2);
90,579✔
1061
                free(tmp2);
90,579✔
1062
        }
1063
        if (tmp != NULL) {
457,446✔
1064
                STRCPY(sline, LINEMAX2, tmp);
434,796✔
1065
                free(tmp);
434,796✔
1066
        }
1067
        lp = rp;
457,446✔
1068
}
457,446✔
1069

1070
void ParseStructLabel(CStructure* st) {
1,221✔
1071
        char* tp, temp[LINEMAX];
1072
        if (PreviousIsLabel) {
1,221✔
1073
                free(PreviousIsLabel);
1,022✔
1074
                PreviousIsLabel = nullptr;
1,022✔
1075
        }
1076
        if (White()) {
1,221✔
1077
                return;
159✔
1078
        }
1079
        tp = temp;
1,068✔
1080
        if (*lp == '.') {
1,068✔
1081
                ++lp;
30✔
1082
        }
1083
        while (*lp && islabchar(*lp)) {
5,778✔
1084
                *tp = *lp; ++tp; ++lp;
4,710✔
1085
        }
1086
        *tp = 0;
1,068✔
1087
        if (*lp == ':') {
1,068✔
1088
                ++lp;
81✔
1089
        }
1090
        tp = temp; SkipBlanks();
1,068✔
1091
        if (isdigit((byte)*tp)) {
1,068✔
1092
                Error("[STRUCT] Number labels not allowed within structs"); return;
6✔
1093
        }
1094
        PreviousIsLabel = STRDUP(tp);
1,062✔
1095
        if (PreviousIsLabel == NULL) ErrorOOM();
1,062✔
1096
        st->AddLabel(tp);
1,062✔
1097
}
1098

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

1209
void ParseStructLine(CStructure* st) {
1,275✔
1210
        ++CompiledCurrentLine;
1,275✔
1211
        lp = ReplaceDefine(line);
1,275✔
1212
        if (!*lp) return;
1,275✔
1213
        ParseStructLabel(st);
1,221✔
1214
        if (SkipBlanks()) return;
1,221✔
1215
        ParseStructMember(st);
1,203✔
1216
        if (SkipBlanks()) return;
1,203✔
1217
        if (*lp) Error("[STRUCT] Unexpected", lp);
99✔
1218
}
1219

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