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

z00m128 / sjasmplus / 1476

02 Feb 2025 10:37PM UTC coverage: 96.426% (-0.002%) from 96.428%
1476

push

cirrus-ci

ped7g
.cleanup TODO/comments

LGTM is no more and I haven't seen any output from the github built-in
code quality check, so let's see if removing the obsolete comment does
produce any change.

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

20 existing lines in 2 files now uncovered.

9550 of 9904 relevant lines covered (96.43%)

171073.45 hits per line

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

99.78
/sjasm/sjasm.cpp
1
/*
2

3
  SjASMPlus Z80 Cross Compiler
4

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

7
  Copyright (c) 2006 Sjoerd Mastijn
8

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

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

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

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

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

27
*/
28

29
// sjasm.cpp
30

31
#include "sjdefs.h"
32
#include <cstdlib>
33
#include <chrono>
34
#include <ctime>
35

36
static void PrintHelpMain() {
4✔
37
        // Please keep help lines at most 79 characters long (cursor at column 88 after last char)
38
        //     |<-- ...8901234567890123456789012345678901234567890123456789012... 80 chars -->|
39
        _COUT "Based on code of SjASM by Sjoerd Mastijn (http://www.xl2s.tk)" _ENDL;
4✔
40
        _COUT "Copyright 2004-2024 by Aprisobal and all other participants" _ENDL;
4✔
41
        //_COUT "Patches by Antipod / boo_boo / PulkoMandy and others" _ENDL;
42
        //_COUT "Tidy up by Tygrys / UB880D / Cizo / mborik / z00m" _ENDL;
43
        _COUT "\nUsage:\nsjasmplus [options] sourcefile(s)" _ENDL;
4✔
44
        _COUT "\nOption flags as follows:" _ENDL;
4✔
45
        _COUT "  -h or --help[=warnings]  Help information (you see it)" _ENDL;
4✔
46
        _COUT "  --zxnext[=cspect]        Enable ZX Spectrum Next Z80 extensions (Z80N)" _ENDL;
4✔
47
        _COUT "  --i8080                  Limit valid instructions to i8080 only (+ no fakes)" _ENDL;
4✔
48
        _COUT "  --lr35902                Sharp LR35902 CPU instructions mode (+ no fakes)" _ENDL;
4✔
49
        _COUT "  --outprefix=<path>       Prefix for save/output/.. filenames in directives" _ENDL;
4✔
50
        _COUT "  -i <path> or -I <path> or --inc= <path> (--inc only to empty the list)" _ENDL;
4✔
51
        _COUT "                           Include path (later defined have higher priority)" _ENDL;
4✔
52
        _COUT "  --lst[=<filename>]       Save listing to <filename> (<source>.lst is default)" _ENDL;
4✔
53
        _COUT "  --lstlab[=sort]          Append [sorted] symbol table to listing" _ENDL;
4✔
54
        _COUT "  --sym=<filename>         Save symbol table to <filename>" _ENDL;
4✔
55
        _COUT "  --exp=<filename>         Save exports to <filename> (see EXPORT pseudo-op)" _ENDL;
4✔
56
        //_COUT "  --autoreloc              Switch to autorelocation mode. See more in docs." _ENDL;
57
        _COUT "  --raw=<filename>         Machine code saved also to <filename> (- is STDOUT)" _ENDL;
4✔
58
        _COUT "  --sld[=<filename>]       Save Source Level Debugging data to <filename>" _ENDL;
4✔
59
        _COUT " Note: use OUTPUT, LUA/ENDLUA and other pseudo-ops to control output" _ENDL;
4✔
60
        _COUT " Logging:" _ENDL;
4✔
61
        _COUT "  --nologo                 Do not show startup message" _ENDL;
4✔
62
        _COUT "  --msg=[all|war|err|none|lst|lstlab]" _ENDL;
4✔
63
        _COUT "                           Stderr messages verbosity (\"all\" is default)" _ENDL;
4✔
64
        _COUT "  --fullpath[=on|rel|off]  Input file paths: full | CWD relative | name only" _ENDL;
4✔
65
        _COUT "  --color=[on|off|auto]    Enable or disable ANSI coloring of warnings/errors" _ENDL;
4✔
66
        _COUT " Other:" _ENDL;
4✔
67
        _COUT "  -D<NAME>[=<value>] or --define <NAME>[=<value>]" _ENDL;
4✔
68
        _COUT "                           Define <NAME> as <value>" _ENDL;
4✔
69
        _COUT "  -                        Reads STDIN as source (even in between regular files)" _ENDL;
4✔
70
        _COUT "  --longptr                No device: program counter $ can go beyond 0x10000" _ENDL;
4✔
71
        _COUT "  --reversepop             Enable reverse POP order (as in base SjASM version)" _ENDL;
4✔
72
        _COUT "  --dirbol                 Enable directives from the beginning of line" _ENDL;
4✔
73
        _COUT "  --dos866                 Encode from Windows codepage to DOS 866 (Cyrillic)" _ENDL;
4✔
74
        _COUT "  --syntax=<...>           Adjust parsing syntax, check docs for details." _ENDL;
4✔
75
}
4✔
76

77
namespace Options {
78
        const STerminalColorSequences tcols_ansi = {
79
                /*end*/                "\033[m",
80
                /*display*/        "\033[36m",                // Cyan
81
                /*warning*/        "\033[33m",                // Yellow
82
                /*error*/        "\033[31m",                // Red
83
                /*bold*/        "\033[1m"                // bold
84
        };
85

86
        const STerminalColorSequences tcols_none = {
87
                /*end*/                "",
88
                /*display*/        "",
89
                /*warning*/        "",
90
                /*error*/        "",
91
                /*bold*/        ""
92
        };
93

94
        const STerminalColorSequences* tcols = &tcols_none;
95

96
        void SetTerminalColors(bool enabled) {
1,019✔
97
                tcols = enabled ? &tcols_ansi : &tcols_none;
1,019✔
98
        }
1,019✔
99

100
        std::filesystem::path OutPrefix {""};
101
        std::filesystem::path SymbolListFName {""};
102
        std::filesystem::path ListingFName {""};
103
        std::filesystem::path ExportFName {""};
104
        std::filesystem::path DestinationFName {""};
105
        std::filesystem::path RAWFName {""};
106
        std::filesystem::path UnrealLabelListFName {""};
107
        std::filesystem::path CSpectMapFName {""};
108
        int CSpectMapPageSize = 0x4000;
109
        std::filesystem::path SourceLevelDebugFName {""};
110
        bool IsDefaultSldName = false;
111

112
        EOutputVerbosity OutputVerbosity = OV_ALL;
113
        bool IsLabelTableInListing = false;
114
        bool IsDefaultListingName = false;
115
        EFileVerbosity FileVerbosity = FNAME_BASE;
116
        bool AddLabelListing = false;
117
        bool HideLogo = false;
118
        bool ShowHelp = false;
119
        bool ShowHelpWarnings = false;
120
        bool ShowVersion = false;
121
        bool NoDestinationFile = true;                // no *.out file by default
122
        SSyntax syx, systemSyntax;
123
        bool IsI8080 = false;
124
        bool IsLR35902 = false;
125
        bool IsLongPtr = false;
126
        bool SortSymbols = false;
127
        bool IsBigEndian = false;
128
        bool EmitVirtualLabels = false;
129

130
        // Include directories list is initialized with "." (aka LaunchDirectory aka CWD) directory
131
        std::vector<std::filesystem::path> IncludeDirsList{"."};
132
                // ^ Not empty string, because that fails include path existence check
133
                // ^ Not empty list b/c legacy: launch dir is implicit include path (unless reset by `--inc`)
134

135
        CDefineTable CmdDefineTable;                // is initialized by constructor
136

137
        static const char* fakes_disabled_txt_error = "Fake instructions are not enabled";
138
        static const char* fakes_in_i8080_txt_error = "Fake instructions are not implemented in i8080 mode";
139
        static const char* fakes_in_lr35902_txt_error = "Fake instructions are not implemented in Sharp LR35902 mode";
140

141
        // returns true if fakes are completely disabled, false when they are enabled
142
        // showMessage=true: will also display error/warning (use when fake ins. is emitted)
143
        // showMessage=false: can be used to silently check if fake instructions are even possible
144
        bool noFakes(bool showMessage) {
3,999✔
145
                bool fakesDisabled = Options::IsI8080 || Options::IsLR35902 || (!syx.FakeEnabled);
3,999✔
146
                if (!showMessage) return fakesDisabled;
3,999✔
147
                if (fakesDisabled) {
2,322✔
148
                        const char* errorTxt = fakes_disabled_txt_error;
315✔
149
                        if (Options::IsI8080) errorTxt = fakes_in_i8080_txt_error;
315✔
150
                        if (Options::IsLR35902) errorTxt = fakes_in_lr35902_txt_error;
315✔
151
                        Error(errorTxt, bp, SUPPRESS);
315✔
152
                        return true;
315✔
153
                }
154
                if (syx.FakeWarning) {
2,007✔
155
                        WarningById(W_FAKE, bp);
687✔
156
                }
157
                return false;
2,007✔
158
        }
159

160
        std::stack<SSyntax> SSyntax::syxStack;
161

162
        void SSyntax::resetCurrentSyntax() {
109✔
163
                new (&syx) SSyntax();        // restore defaults in current syntax
109✔
164
        }
109✔
165

166
        void SSyntax::pushCurrentSyntax() {
78✔
167
                syxStack.push(syx);                // store current syntax options into stack
78✔
168
        }
78✔
169

170
        bool SSyntax::popSyntax() {
81✔
171
                if (syxStack.empty()) return false;        // no syntax stored in stack
81✔
172
                syx = syxStack.top();        // copy the syntax values from stack
75✔
173
                syxStack.pop();
75✔
174
                return true;
75✔
175
        }
176

177
        void SSyntax::restoreSystemSyntax() {
1,526✔
178
                while (!syxStack.empty()) syxStack.pop();        // empty the syntax stack first
1,528✔
179
                syx = systemSyntax;                // reset to original system syntax
1,526✔
180
        }
1,526✔
181

182
} // eof namespace Options
183

184
static void PrintHelp(bool forceMainHelp) {
5✔
185
        if (forceMainHelp || Options::ShowHelp) PrintHelpMain();
5✔
186
        if (Options::ShowHelpWarnings) PrintHelpWarnings();
5✔
187
}
5✔
188

189
CDevice *Devices = nullptr;
190
CDevice *Device = nullptr;
191
CDevicePage *Page = nullptr;
192
char* DeviceID = nullptr;
193
TextFilePos globalDeviceSourcePos;
194
aint deviceDirectivesCount = 0;
195
static char* globalDeviceID = nullptr;
196
static aint globalDeviceZxRamTop = 0;
197

198
// extend
199
fullpath_p_t fileNameFull = nullptr;
200
char* lp, line[LINEMAX], temp[LINEMAX], * bp;
201
char sline[LINEMAX2], sline2[LINEMAX2], * substitutedLine, * eolComment, ModuleName[LINEMAX];
202

203
SSource::SSource(SSource && src) {        // move constructor, "pick" the stdin pointer
1,013✔
204
        memcpy(fname, src.fname, MAX_PATH);
1,013✔
205
        stdin_log = src.stdin_log;
1,013✔
206
        src.fname[0] = 0;
1,013✔
207
        src.stdin_log = nullptr;
1,013✔
208
}
1,013✔
209

210
SSource::SSource(const char* newfname) : stdin_log(nullptr) {
783✔
211
        STRNCPY(fname, MAX_PATH, newfname, MAX_PATH-1);
783✔
212
        fname[MAX_PATH-1] = 0;
783✔
213
}
783✔
214

215
SSource::SSource(int) {
6✔
216
        fname[0] = 0;
6✔
217
        stdin_log = new stdin_log_t();
6✔
218
        stdin_log->reserve(50*1024);
6✔
219
}
6✔
220

221
SSource::~SSource() {
1,802✔
222
        if (stdin_log) delete stdin_log;
1,802✔
223
}
1,802✔
224

225
std::vector<SSource> sourceFiles;
226

227
int ConvertEncoding = ENCWIN;
228

229
EDispMode PseudoORG = DISP_NONE;
230
bool IsLabelNotFound = false, IsSubstituting = false;
231
int pass = 0, ErrorCount = 0, WarningCount = 0, IncludeLevel = -1;
232
int IsRunning = 0, donotlist = 0, listmacro = 0;
233
int adrdisp = 0, dispPageNum = LABEL_PAGE_UNDEFINED, StartAddress = -1;
234
byte* MemoryPointer=NULL;
235
int macronummer = 0, lijst = 0, reglenwidth = 0;
236
source_positions_t sourcePosStack;
237
source_positions_t smartSmcLines;
238
source_positions_t::size_type smartSmcIndex;
239
uint32_t maxlin = 0;
240
aint CurAddress = 0, CompiledCurrentLine = 0, LastParsedLabelLine = 0, PredefinedCounter = 0;
241
aint destlen = 0, size = -1L, comlin = 0;
242

243
char* vorlabp=NULL, * macrolabp=NULL, * LastParsedLabel=NULL;
244
std::stack<SRepeatStack> RepeatStack;
245
CStringsList* lijstp = NULL;
246
CLabelTable LabelTable;
247
CTemporaryLabelTable TemporaryLabelTable;
248
CDefineTable DefineTable;
249
CMacroDefineTable MacroDefineTable;
250
CMacroTable MacroTable;
251
CStructureTable StructureTable;
252

253
// reserve keywords in labels table, to detect when user is defining label colliding with keyword
254
static void ReserveLabelKeywords() {
514✔
255
        for (const char* keyword : {
6,682✔
256
                "abs", "and", "exist", "high", "low", "mod", "norel", "not", "or", "shl", "shr", "xor"
257
        }) {
7,196✔
258
                LabelTable.Insert(keyword, -65536, LABEL_IS_UNDEFINED|LABEL_IS_KEYWORD);
6,168✔
259
        }
260
}
514✔
261

262
void InitPass() {
1,526✔
263
        assert(sourcePosStack.empty());                                // there's no source position [left] in the stack
1,526✔
264
        Relocation::InitPass();
1,526✔
265
        Options::SSyntax::restoreSystemSyntax();        // release all stored syntax variants and reset to initial
1,526✔
266
        uint32_t maxpow10 = 1;
1,526✔
267
        reglenwidth = 0;
1,526✔
268
        do {
269
                ++reglenwidth;
2,486✔
270
                maxpow10 *= 10;
2,486✔
271
                if (maxpow10 < 10) ExitASM(1);        // 32b overflow
2,486✔
272
        } while (maxpow10 <= maxlin);
2,486✔
273
        *ModuleName = 0;
1,526✔
274
        SetLastParsedLabel(nullptr);
1,526✔
275
        if (vorlabp) free(vorlabp);
1,526✔
276
        vorlabp = STRDUP("_");
1,526✔
277
        macrolabp = NULL;
1,526✔
278
        listmacro = 0;
1,526✔
279
        CurAddress = 0;
1,526✔
280
        CompiledCurrentLine = 0;
1,526✔
281
        smartSmcIndex = 0;
1,526✔
282
        PseudoORG = DISP_NONE; adrdisp = 0; dispPageNum = LABEL_PAGE_UNDEFINED;
1,526✔
283
        ListAddress = 0; macronummer = 0; lijst = 0; comlin = 0;
1,526✔
284
        lijstp = NULL;
1,526✔
285
        DidEmitByte();                                // reset the emitted flag
1,526✔
286
        StructureTable.ReInit();
1,526✔
287
        MacroTable.ReInit();
1,526✔
288
        MacroDefineTable.ReInit();
1,526✔
289
        DefineTable = Options::CmdDefineTable;
1,526✔
290
        TemporaryLabelTable.InitPass();
1,526✔
291

292
        // reset "device" stuff + detect "global device" directive
293
        if (globalDeviceID) {                // globalDeviceID detector has to trigger before every pass
1,526✔
294
                free(globalDeviceID);
107✔
295
                globalDeviceID = nullptr;
107✔
296
        }
297
        if (1 < pass && 1 == deviceDirectivesCount && Devices) {        // only single DEVICE used
1,526✔
298
                globalDeviceID = STRDUP(Devices->ID);                // make it global for next pass
215✔
299
                globalDeviceZxRamTop = Devices->ZxRamTop;
215✔
300
        }
301
        if (Devices) delete Devices;
1,526✔
302
        Devices = Device = nullptr;
1,526✔
303
        DeviceID = nullptr;
1,526✔
304
        Page = nullptr;
1,526✔
305
        deviceDirectivesCount = 0;
1,526✔
306
        // resurrect "global" device here
307
        if (globalDeviceID) {
1,526✔
308
                sourcePosStack.push_back(globalDeviceSourcePos);
215✔
309
                if (!SetDevice(globalDeviceID, globalDeviceZxRamTop)) {                // manually tested (remove "!")
215✔
UNCOV
310
                        Error("Failed to re-initialize global device", globalDeviceID, FATAL);
×
311
                }
312
                sourcePosStack.pop_back();
215✔
313
        }
314

315
        // predefined defines - (deprecated) classic sjasmplus v1.x (till v1.15.1)
316
        DefineTable.Replace("_SJASMPLUS", "1");
1,526✔
317
        DefineTable.Replace("_RELEASE", "0");
1,526✔
318
        DefineTable.Replace("_VERSION", "__VERSION__");
1,526✔
319
        DefineTable.Replace("_ERRORS", "__ERRORS__");
1,526✔
320
        DefineTable.Replace("_WARNINGS", "__WARNINGS__");
1,526✔
321
        // predefined defines - sjasmplus v2.x-like (since v1.16.0)
322
        // __DATE__ and __TIME__ are defined just once in main(...) (stored in Options::CmdDefineTable)
323
        DefineTable.Replace("__SJASMPLUS__", VERSION_NUM);                // modified from _SJASMPLUS
1,526✔
324
        DefineTable.Replace("__VERSION__", "\"" VERSION "\"");        // migrated from _VERSION
1,526✔
325
        DefineTable.Replace("__ERRORS__", ErrorCount);                        // migrated from _ERRORS (can be already > 0 from earlier pass)
1,526✔
326
        DefineTable.Replace("__WARNINGS__", WarningCount);                // migrated from _WARNINGS (can be already > 0 from earlier pass)
1,526✔
327
        DefineTable.Replace("__PASS__", pass);                                        // current pass of assembler
1,526✔
328
        DefineTable.Replace("__INCLUDE_LEVEL__", "-1");                        // include nesting
1,526✔
329
        DefineTable.Replace("__BASE_FILE__", "<none>");                        // the include-level 0 file
1,526✔
330
        DefineTable.Replace("__FILE__", "<none>");                                // current file
1,526✔
331
        DefineTable.Replace("__LINE__", "<dynamic value>");                // current line in current file
1,526✔
332
        DefineTable.Replace("__COUNTER__", "<dynamic value>");        // gcc-like, incremented upon every use
1,526✔
333
        PredefinedCounter = 0;
1,526✔
334

335
        // open Export file (defined by --exp or by EXPORT directive), even when no EXPORT is in source
336
        if (LASTPASS == pass) OpenExpFile();                                        // will not do anything if filename is empty
1,526✔
337
}
1,526✔
338

339
void FreeRAM() {
517✔
340
        if (Devices) {
517✔
341
                delete Devices;                Devices = nullptr;
159✔
342
        }
343
        if (globalDeviceID) {
517✔
344
                free(globalDeviceID);        globalDeviceID = nullptr;
108✔
345
        }
346
        for (CDeviceDef* deviceDef : DefDevices) delete deviceDef;
525✔
347
        DefDevices.clear();
517✔
348
        lijstp = NULL;                // do not delete this, should be released by owners of DUP/regular macros
517✔
349
        free(vorlabp);                vorlabp = NULL;
517✔
350
        LabelTable.RemoveAll();
517✔
351
        DefineTable.RemoveAll();
517✔
352
        SetLastParsedLabel(nullptr);
517✔
353
        if (PreviousIsLabel) {
517✔
354
                free(PreviousIsLabel);
34✔
355
                PreviousIsLabel = nullptr;
34✔
356
        }
357
}
517✔
358

359

360
void ExitASM(int p) {
12✔
361
        FreeRAM();
12✔
362
        if (pass == LASTPASS) {
12✔
363
                Close();
1✔
364
        }
365
        exit(p);
12✔
366
}
367

368
namespace Options {
369

370
        class COptionsParser {
371
        private:
372
                const char* arg;
373
                char opt[LINEMAX];
374
                char val[LINEMAX];
375

376
                // returns 1 when argument was processed (keyword detected, value copied into path var)
377
                int CheckAssignmentOption(const char* keyword, std::filesystem::path & path) {
10,873✔
378
                        if (strcmp(keyword, opt)) return 0;                // detect "keyword" (return 0 if not)
10,873✔
379
                        if (*val) {
344✔
380
                                path = val;
343✔
381
                        } else {
382
                                Error("no parameters found in", arg, ALL);
1✔
383
                        }
384
                        return 1;        // keyword detected, option was processed
344✔
385
                }
386

387
                static void splitByChar(const char* s, const int splitter,
3,258✔
388
                                                           char* v1, const size_t v1Size,
389
                                                           char* v2, const size_t v2Size) {
390
                        // only non-zero splitter character is supported
391
                        const char* spos = splitter ? STRCHR(s, splitter) : NULL;
3,258✔
392
                        if (NULL == spos) {
3,258✔
393
                                // splitter character not found, copy whole input string into v1, v2 = empty string
394
                                STRCPY(v1, v1Size, s);
1,206✔
395
                                v2[0] = 0;
1,206✔
396
                        } else {
397
                                // splitter found, copy string ahead splitter to v1, after it to v2
398
                                STRNCPY(v1, v1Size, s, spos - s);
2,052✔
399
                                v1[spos - s] = 0;
2,052✔
400
                                STRCPY(v2, v2Size, spos + 1);
2,052✔
401
                        }
402
                }
3,258✔
403

404
                void parseSyntaxValue() {
146✔
405
                        // Options::syx is expected to be already in default state before entering this
406
                        for (const auto & syntaxOption : val) {
443✔
407
                                switch (syntaxOption) {
443✔
408
                                case 0:   return;
146✔
409
                                // f F - instructions: fake warning, no fakes (default = fake enabled)
410
                                case 'f': syx.FakeEnabled = syx.FakeWarning = true; break;
297✔
411
                                case 'F': syx.FakeEnabled = false; break;
13✔
412
                                // a - multi-argument delimiter ",," (default is ",")
413
                                case 'a': syx.MultiArg = &doubleComma; break;
104✔
414
                                // b - single parentheses enforce mem access (default = relaxed syntax)
415
                                case 'b': syx.MemoryBrackets = 1; break;
73✔
416
                                // B - memory access brackets [] required (default = relaxed syntax)
417
                                case 'B': syx.MemoryBrackets = 2; break;
7✔
418
                                // l L - warn/error about labels using keywords (default = no message)
419
                                case 'l':
5✔
420
                                case 'L':
421
                                {
422
                                        const char this_option_is[2] = { syntaxOption, 0 };
5✔
423
                                        Error("Syntax option not implemented yet", this_option_is, PASS03);
5✔
424
                                        break;
5✔
425
                                }
426
                                // i - case insensitive instructions/directives (default = same case required)
427
                                case 'i': syx.CaseInsensitiveInstructions = true; break;
3✔
428
                                // w - warnings option: report warnings as errors
429
                                case 'w': syx.WarningsAsErrors = true; break;
38✔
430
                                // M - alias "m" and "M" for "(hl)" to cover 8080-like syntax: ADD A,M
431
                                case 'M': syx.Is_M_Memory = true; break;
3✔
432
                                // s - switch off sub-word substitution in DEFINEs (s like "Simple defines" or "Sub word")
433
                                case 's': syx.IsSubwordSubstitution = false; break;
3✔
434
                                // unrecognized option
435
                                default:
1✔
436
                                        const char this_option_is[2] = { syntaxOption, 0 };
1✔
437
                                        Error("Unrecognized syntax option", this_option_is, PASS03);
1✔
438
                                        break;
1✔
439
                                }
440
                        }
441
                }
442

443
        public:
444
                void GetOptions(const char* const * const argv, int& i, bool onlySyntaxOptions = false) {
1,055✔
445
                        while ((arg=argv[i]) && ('-' == arg[0])) {
4,934✔
446
                                bool doubleDash = false;
3,884✔
447
                                // copy "option" (up to '=' char) into `opt`, copy "value" (after '=') into `val`
448
                                if ('-' == arg[1]) {        // double-dash detected, value is expected after "="
3,884✔
449
                                        doubleDash = true;
3,216✔
450
                                        splitByChar(arg + 2, '=', opt, LINEMAX, val, LINEMAX);
3,216✔
451
                                } else {                                // single dash, parse value from second character onward
452
                                        opt[0] = arg[1];        // copy only single letter into `opt`
668✔
453
                                        opt[1] = 0;
668✔
454
                                        if (opt[0]) {                // if it was not empty, try to copy also `val`
668✔
455
                                                STRCPY(val, LINEMAX, arg + 2);
663✔
456
                                        }
457
                                }
458

459
                                // check for particular options and setup option value by it
460
                                // first check all syntax-only options which may be modified by OPT directive
461
                                if (!strcmp(opt, "zxnext")) {
3,884✔
462
                                        if (IsI8080) Error("Can't enable Next extensions while in i8080 mode", nullptr, FATAL);
45✔
463
                                        if (IsLR35902) Error("Can't enable Next extensions while in Sharp LR35902 mode", nullptr, FATAL);
44✔
464
                                        syx.IsNextEnabled = 1;
43✔
465
                                        if (!strcmp(val, "cspect")) syx.IsNextEnabled = 2;        // CSpect emulator extensions
43✔
466
                                } else if (!strcmp(opt, "reversepop")) {
3,839✔
467
                                        syx.IsReversePOP = true;
19✔
468
                                } else if (!strcmp(opt, "dirbol")) {
3,820✔
469
                                        syx.IsPseudoOpBOF = true;
24✔
470
                                } else if (!strcmp(opt, "nofakes")) {
3,796✔
471
                                        syx.FakeEnabled = false;
5✔
472
                                        // was deprecated, as it is provided by `--syntax=F` too, but now I decided to keep also this older option
473
                                        // (it's less cryptic than the --syntax letter soup, one duplicity of functionality will not end the world, right?)
474
                                } else if (!strcmp(opt, "syntax")) {
3,791✔
475
                                        parseSyntaxValue();
146✔
476
                                } else if (!doubleDash && 'W' == opt[0]) {
3,645✔
477
                                        CliWoption(val);
597✔
478
                                } else if (onlySyntaxOptions) {
3,048✔
479
                                        // rest of the options is available only when launching the sjasmplus
480
                                        return;
3✔
481
                                } else if (!strcmp(opt, "lr35902")) {
3,045✔
482
                                        IsLR35902 = true;
16✔
483
                                        // force (silently) other CPU modes OFF
484
                                        IsI8080 = false;
16✔
485
                                        syx.IsNextEnabled = 0;
16✔
486
                                } else if (!strcmp(opt, "i8080")) {
3,029✔
487
                                        IsI8080 = true;
15✔
488
                                        // force (silently) other CPU modes OFF
489
                                        IsLR35902 = false;
15✔
490
                                        syx.IsNextEnabled = 0;
15✔
491
                                } else if ((!doubleDash && 'h' == opt[0] && !val[0]) || (doubleDash && !strcmp(opt, "help"))) {
3,014✔
492
                                        ShowHelp |= !!strcmp("warnings", val);
4✔
493
                                        ShowHelpWarnings |= !strcmp("warnings", val);
4✔
494
                                } else if (doubleDash && !strcmp(opt, "version")) {
3,010✔
495
                                        ShowVersion = true;
2✔
496
                                } else if (!strcmp(opt, "lstlab")) {
3,008✔
497
                                        AddLabelListing = true;
493✔
498
                                        if (val[0]) SortSymbols = !strcmp("sort", val);
493✔
499
                                } else if (!strcmp(opt, "longptr")) {
2,515✔
500
                                        IsLongPtr = true;
3✔
501
                                } else if (!strcmp(opt, "msg")) {
2,512✔
502
                                        if (!strcmp("none", val)) {
526✔
503
                                                OutputVerbosity = OV_NONE;
444✔
504
                                                HideLogo = true;
444✔
505
                                        } else if (!strcmp("err", val)) {
82✔
506
                                                OutputVerbosity = OV_ERROR;
1✔
507
                                        } else if (!strcmp("war", val)) {
81✔
508
                                                OutputVerbosity = OV_WARNING;
27✔
509
                                        } else if (!strcmp("all", val)) {
54✔
510
                                                OutputVerbosity = OV_ALL;
2✔
511
                                        } else if (!strcmp("lst", val)) {
52✔
512
                                                OutputVerbosity = OV_LST;
2✔
513
                                                AddLabelListing = false;
2✔
514
                                                HideLogo = true;
2✔
515
                                        } else if (!strcmp("lstlab", val)) {
50✔
516
                                                OutputVerbosity = OV_LST;
47✔
517
                                                AddLabelListing = true;
47✔
518
                                                SortSymbols = true;
47✔
519
                                                HideLogo = true;
47✔
520
                                        } else {
521
                                                Error("unexpected parameter in", arg, ALL);
3✔
522
                                        }
523
                                } else if (!strcmp(opt, "lst") && !val[0]) {
1,986✔
524
                                        IsDefaultListingName = true;
4✔
525
                                } else if (!strcmp(opt, "sld") && !val[0]) {
1,982✔
526
                                        IsDefaultSldName = true;
4✔
527
                                } else if (
1,978✔
528
                                        CheckAssignmentOption("outprefix", OutPrefix) ||
1,978✔
529
                                        CheckAssignmentOption("sym", SymbolListFName) ||
1,977✔
530
                                        CheckAssignmentOption("lst", ListingFName) ||
1,971✔
531
                                        CheckAssignmentOption("exp", ExportFName) ||
1,657✔
532
                                        CheckAssignmentOption("sld", SourceLevelDebugFName) ||
5,593✔
533
                                        CheckAssignmentOption("raw", RAWFName) ) {
1,638✔
534
                                        // was proccessed inside CheckAssignmentOption function
535
                                } else if (!strcmp(opt, "fullpath")) {
1,634✔
536
                                        if (!strcmp("on", val)) {
515✔
537
                                                FileVerbosity = FNAME_ABSOLUTE;
1✔
538
                                        } else if (!val[0] || !strcmp("rel", val)) {
514✔
539
                                                FileVerbosity = FNAME_LAUNCH_REL;
512✔
540
                                        } else if (!strcmp("off", val)) {
2✔
541
                                                FileVerbosity = FNAME_BASE;
1✔
542
                                        } else {
543
                                                Error("invalid --fullpath setting (use: on|rel|off)", val, ALL);
1✔
544
                                        }
545
                                } else if (!strcmp(opt, "color")) {
1,119✔
546
                                        if (!strcmp("on", val)) {
500✔
547
                                                SetTerminalColors(true);
3✔
548
                                        } else if (!strcmp("off", val)) {
497✔
549
                                                SetTerminalColors(false);
490✔
550
                                        } else if (!strcmp("auto", val)) {
7✔
551
                                                // already heuristically detected, nothing to do
552
                                        } else {
553
                                                Error("invalid --color setting (use: on|off|auto)", val, ALL);
1✔
554
                                        }
555
                                } else if (!strcmp(opt, "nologo")) {
619✔
556
                                        HideLogo = 1;
519✔
557
                                } else if (!strcmp(opt, "dos866")) {
100✔
558
                                        ConvertEncoding = ENCDOS;
1✔
559
                                } else if ((doubleDash && !strcmp(opt, "inc")) ||
99✔
560
                                                        (!doubleDash && 'i' == opt[0]) ||
74✔
561
                                                        (!doubleDash && 'I' == opt[0])) {
69✔
562
                                        if (*val) {
49✔
563
                                                IncludeDirsList.emplace_back(val);
35✔
564
                                        } else {
565
                                                if (!doubleDash || '=' == arg[5]) {
14✔
566
                                                        if (argv[i+1] && '-' != argv[i+1][0]) {                // include path provided as next argument (after space, like gcc)
8✔
567
                                                                IncludeDirsList.emplace_back(argv[++i]);
5✔
568
                                                        } else {
569
                                                                Error("no include path found for", arg, ALL);
3✔
570
                                                        }
571
                                                } else {        // individual `--inc` without "= path" will RESET include dirs
572
                                                        IncludeDirsList.clear();
6✔
573
                                                }
574
                                        }
575
                                } else if (!strcmp(opt, "define")) {        // for --define name=value the next argv is used (if available)
50✔
576
                                        const char* defarg = argv[i+1] ? argv[++i] : nullptr;
4✔
577
                                        char defN[LINEMAX] {}, defV[LINEMAX] {};
4✔
578
                                        if (defarg) splitByChar(defarg, '=', defN, LINEMAX, defV, LINEMAX);
4✔
579
                                        if (*defN) CmdDefineTable.Add(defN, defV, nullptr);
4✔
580
                                        else Error("missing define value", defarg, ALL);
1✔
581
                                } else if (!doubleDash && 'D' == opt[0]) {
46✔
582
                                        char defN[LINEMAX], defV[LINEMAX];
583
                                        if (*val) {                // for -Dname=value the `val` contains "name=value" string
40✔
584
                                                splitByChar(val, '=', defN, LINEMAX, defV, LINEMAX);
39✔
585
                                                CmdDefineTable.Add(defN, defV, NULL);
39✔
586
                                        } else {
587
                                                Error("no parameters found in", arg, ALL);
1✔
588
                                        }
589
                                } else if (!doubleDash && 0 == opt[0]) {
46✔
590
                                        // only single "-" was on command line = source STDIN
591
                                        sourceFiles.push_back(SSource(1));                // special constructor for stdin input
5✔
592
                                } else {
593
                                        Error("unrecognized option", arg, ALL);
1✔
594
                                }
595

596
                                ++i;                                        // next CLI argument
3,879✔
597
                        } // end of while ((arg=argv[i]) && ('-' == arg[0]))
598
                }
599

600
                void checkIncludePaths(const std::vector<std::filesystem::path> & includes) {
517✔
601
                        for (auto it = includes.crbegin(); it != includes.crend(); ++it) {
1,068✔
602
                                if (std::filesystem::is_directory(*it)) continue;
551✔
603
                                const char* errtxt = '~' == it->string()[0] ? "include path starts with ~ (check docs)" : "include path not found";
7✔
604
                                Error(errtxt, it->string().c_str(), ALL);
7✔
605
                        }
606
                        return;
517✔
607
                }
608
        };
609

610
        int parseSyntaxOptions(int n, char** options) {
363✔
611
                if (n <= 0) return 0;
363✔
612
                int i = 0;
259✔
613
                Options::COptionsParser optParser;
614
                optParser.GetOptions(options, i, true);
259✔
615
                return i;
257✔
616
        }
617
}
618

619
// ==============================================================================================
620
// == UnitTest++ part, checking if unit tests are requested and does launch test-runner then   ==
621
// ==============================================================================================
622
#ifdef ADD_UNIT_TESTS
623

624
# include "UnitTest++/UnitTest++.h"
625

626
# define STOP_MAKE_BY_NON_ZERO_EXIT_CODE 0
627

628
//detect "--unittest" switch, prepare UnitTest++, run the test runner, collect results, exit
629
# define CHECK_UNIT_TESTS \
630
        { \
631
                if (2 == argc && !strcmp("--unittest", argv[1])) { \
632
                        _COUT "SjASMPlus \033[96mv" VERSION "\033[0m | \033[95mrunning unit tests:\033[0m" _ENDL _END \
633
                        int exitCode = STOP_MAKE_BY_NON_ZERO_EXIT_CODE + UnitTest::RunAllTests(); \
634
                        if (exitCode) _COUT "\033[91mNon-zero result from test runner!\033[0m" _ENDL _END \
635
                        else _COUT "\033[92mOK: 0 UnitTest++ tests failed.\033[0m" _ENDL _END \
636
                        exit(exitCode); \
637
                } \
638
        }
639
#else
640

641
# define CHECK_UNIT_TESTS { /* no unit tests in this build */ }
642

643
#endif
644

645
// == end of UnitTest++ part ====================================================================
646

647
#ifdef WIN32
648
int main(int argc, char* argv[]) {
649
#else
650
int main(int argc, char **argv) {
526✔
651
#endif
652
        Options::SetTerminalColors(autoColorsDetection());
526✔
653

654
        const char* logo = "SjASMPlus Z80 Cross-Assembler v" VERSION " (https://github.com/z00m128/sjasmplus)";
526✔
655

656
        sourcePosStack.reserve(32);
526✔
657
        smartSmcLines.reserve(64);
526✔
658
        sourceFiles.reserve(32);
526✔
659
        Options::IncludeDirsList.reserve(32);
526✔
660

661
        CHECK_UNIT_TESTS                // UnitTest++ extra handling in specially built executable
526✔
662

663
        const word little_endian_test[] = { 0x1234 };
525✔
664
        const byte le_test_byte = *reinterpret_cast<const byte*>(little_endian_test);
525✔
665
        Options::IsBigEndian = (0x12 == le_test_byte);
525✔
666

667
        // start counter
668
        long dwStart = GetTickCount();
525✔
669

670
        LaunchDirectory = std::filesystem::current_path();        // get CWD as launch directory
525✔
671
        Options::COptionsParser optParser;
672
        char* envFlags = std::getenv("SJASMPLUSOPTS");
525✔
673
        if (nullptr != envFlags) {
525✔
674
                // split environment arguments into "argc, argv" like variables (by white-space)
675
                char* parsedOptsArray[33] {};        // there must be one more nullptr in the array (32+1)
4✔
676
                int optI = 0, charI = 0;
4✔
677
                while (optI < 32 && !SkipBlanks(envFlags)) {
47✔
678
                        parsedOptsArray[optI++] = temp + charI;
43✔
679
                        while (*envFlags && !White(*envFlags) && charI < LINEMAX-1) temp[charI++] = *envFlags++;
330✔
680
                        temp[charI++] = 0;
43✔
681
                }
682
                if (!SkipBlanks(envFlags)) {
4✔
683
                        Error("SJASMPLUSOPTS environment variable contains too many options (max is 32)", nullptr, ALL);
1✔
684
                }
685
                // process environment variable ahead of command line options (in the same way)
686
                int i = 0;
4✔
687
                while (parsedOptsArray[i]) {
6✔
688
                        optParser.GetOptions(parsedOptsArray, i);
5✔
689
                        if (!parsedOptsArray[i]) break;
5✔
690
                        sourceFiles.push_back(SSource(parsedOptsArray[i++]));
2✔
691
                }
692
        }
693

694
        // setup __DATE__ and __TIME__ macros (setup them just once, not every pass!)
695
        auto now = std::chrono::system_clock::now();
525✔
696
        std::time_t now_c = std::chrono::system_clock::to_time_t(now);
525✔
697
        std::tm now_tm = *std::localtime(&now_c);
525✔
698
        char dateBuffer[32] = {}, timeBuffer[32] = {};
525✔
699
        SPRINTF3(dateBuffer, 30, "\"%04d-%02d-%02d\"", now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
525✔
700
        SPRINTF3(timeBuffer, 30, "\"%02d:%02d:%02d\"", now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
525✔
701
        Options::CmdDefineTable.Add("__DATE__", dateBuffer, nullptr);
525✔
702
        Options::CmdDefineTable.Add("__TIME__", timeBuffer, nullptr);
525✔
703

704
        int i = 1;
525✔
705
        if (argc > 1) {
525✔
706
                while (argv[i]) {
1,303✔
707
                        optParser.GetOptions(argv, i);
791✔
708
                        if (!argv[i]) break;
791✔
709
                        sourceFiles.push_back(SSource(argv[i++]));
779✔
710
                }
711
        }
712
        // warn about BE-host only when there's any CLI argument && after CLI options were parsed
713
        if (2 <= argc && Options::IsBigEndian) WarningById(W_BE_HOST, nullptr, W_EARLY);
525✔
714
        if (Options::IsDefaultListingName && Options::ListingFName.has_filename()) {
525✔
715
                Error("Using both  --lst  and  --lst=<filename>  is not possible.", NULL, FATAL);
1✔
716
        }
717
        if (OV_LST == Options::OutputVerbosity && (Options::IsDefaultListingName || Options::ListingFName.has_filename())) {
524✔
718
                Error("Using  --msg=lst[lab]  and other list options is not possible.", NULL, FATAL);
1✔
719
        }
720
        if (Options::IsDefaultSldName && Options::SourceLevelDebugFName.has_filename()) {
523✔
721
                Error("Using both  --sld  and  --sld=<filename>  is not possible.", NULL, FATAL);
1✔
722
        }
723
        Options::systemSyntax = Options::syx;                // create copy of initial system settings of syntax
522✔
724

725
        if (argc == 1 || Options::ShowHelp || Options::ShowHelpWarnings) {
522✔
726
                _COUT logo _ENDL;
5✔
727
                PrintHelp(argc == 1);
5✔
728
                exit(argc == 1);
5✔
729
        }
730

731
        if (!Options::HideLogo) {
517✔
732
                _CERR logo _ENDL;
3✔
733
        }
734

735
        if ((Options::FNAME_BASE == Options::FileVerbosity) &&
523✔
736
                (Options::IsDefaultSldName || Options::SourceLevelDebugFName.has_filename())) {
6✔
737
                Warning("missing  --fullpath  with  --sld  may produce incomplete file paths.", NULL, W_EARLY);
1✔
738
        }
739

740
        optParser.checkIncludePaths(Options::IncludeDirsList);
517✔
741

742
        if (Options::ShowVersion) {
517✔
743
                if (Options::HideLogo) {        // if "sjasmplus --version --nologo", emit only the raw VERSION
2✔
744
                        _CERR VERSION _ENDL;
1✔
745
                }
746
                // otherwise the full logo was already printed
747
                // now check if there were some sources to assemble, if NOT, exit with "OK"!
748
                if (0 == sourceFiles.size()) exit(0);
2✔
749
        }
750

751
        // exit with error if no input file were specified
752
        if (0 == sourceFiles.size()) {
515✔
753
                Error("no inputfile(s)", nullptr, ALL);
1✔
754
                exit(1);
1✔
755
        }
756

757
        // create default output name, if not specified
758
        ConstructDefaultFilename(Options::DestinationFName, "out");
514✔
759
        int base_encoding = ConvertEncoding;
514✔
760

761
        // init some vars
762
        InitCPU();
514✔
763

764
        // open lists (if not set to "default" file name, then the OpenFile will handle it)
765
        OpenList();
514✔
766

767
        ReserveLabelKeywords();
514✔
768

769
        do {
770
                ++pass;
1,526✔
771
                if (pass == LASTPASS) OpenSld();        //open source level debugging file (BEFORE InitPass)
1,526✔
772
                InitPass();
1,526✔
773
                if (pass == LASTPASS) OpenDest();
1,526✔
774

775
                static bool warn_stdin_default_lst = true;
776
                for (SSource & src : sourceFiles) {
3,847✔
777
                        if (src.stdin_log && warn_stdin_default_lst && 1 == pass && Options::IsDefaultListingName) {
2,330✔
778
                                Warning("use explicit --lst=<filename> for <stdin> source", nullptr, W_EARLY);
1✔
779
                                warn_stdin_default_lst = false;
1✔
780
                        }
781
                        IsRunning = 1;
2,330✔
782
                        ConvertEncoding = base_encoding;
2,330✔
783
                        // don't search include paths, but use GetInputFile to get "archived" fullpath_ref_t for c_str() lifetime
784
                        fullpath_ref_t src_fname = GetInputFile(delim_string_t(src.fname, DT_COUNT));
2,330✔
785
                        OpenFile(src_fname, src.stdin_log);
2,330✔
786
                }
787

788
                while (!RepeatStack.empty()) {
1,520✔
789
                        sourcePosStack.push_back(RepeatStack.top().sourcePos);        // mark DUP line with error
3✔
790
                        Error("[DUP/REPT] missing EDUP/ENDR to end repeat-block");
3✔
791
                        sourcePosStack.pop_back();
3✔
792
                        RepeatStack.pop();
3✔
793
                }
794

795
                if (DISP_NONE != PseudoORG) {
1,517✔
796
                        CurAddress = adrdisp;
3✔
797
                        PseudoORG = DISP_NONE;
3✔
798
                }
799

800
                if (Options::OutputVerbosity <= OV_ALL) {
1,517✔
801
                        if (pass != LASTPASS) {
9✔
802
                                _CERR "Pass " _CMDL pass _CMDL " complete (" _CMDL ErrorCount _CMDL " errors)" _ENDL;
6✔
803
                        } else {
804
                                _CERR "Pass 3 complete" _ENDL;
3✔
805
                        }
806
                }
807
        } while (pass < LASTPASS);
1,517✔
808

809
        pass = 9999; /* added for detect end of compiling */
505✔
810

811
        // dump label table into listing file, the explicit one (Options::IsDefaultListingName == false)
812
        if (Options::AddLabelListing) LabelTable.Dump();
505✔
813

814
        Close();
505✔
815

816
        if (Options::UnrealLabelListFName.has_filename()) {
505✔
817
                LabelTable.DumpForUnreal();
10✔
818
        }
819

820
        if (Options::CSpectMapFName.has_filename()) {
505✔
821
                LabelTable.DumpForCSpect();
12✔
822
        }
823

824
        if (Options::SymbolListFName.has_filename()) {
505✔
825
                LabelTable.DumpSymbols();
6✔
826
        }
827

828
        // sync files, release everything
829
        cout << flush;
505✔
830
        FreeRAM();
505✔
831
        lua_impl_close();
505✔
832

833
        if (Options::OutputVerbosity <= OV_ALL) {
505✔
834
                double dwCount;
835
                dwCount = GetTickCount() - dwStart;
3✔
836
                if (dwCount < 0) dwCount = 0;
3✔
837
                fprintf(stderr, "Errors: %d, warnings: %d, compiled: %d lines, work time: %.3f seconds\n",
3✔
838
                                ErrorCount, WarningCount, CompiledCurrentLine, dwCount / 1000);
839
        }
840

841
        return (ErrorCount != 0);
505✔
842
}
843
//eof sjasm.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