2019-12-13 04:50:43 +00:00
|
|
|
|
|
|
|
#include <string_view>
|
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <cerrno>
|
|
|
|
#include <cstdint>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cctype>
|
|
|
|
|
|
|
|
/* old version of stdlib have this stuff in utility */
|
|
|
|
#if __has_include(<charconv>)
|
|
|
|
#define HAVE_CHARCONV
|
|
|
|
#include <charconv>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <err.h>
|
|
|
|
#include <sysexits.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "link.h"
|
|
|
|
|
|
|
|
static void usage(int ex) {
|
|
|
|
|
|
|
|
fputs(
|
|
|
|
"merlin-link [options] infile...\n"
|
|
|
|
"\noptions:\n"
|
|
|
|
"-C inhibit SUPER compression\n"
|
|
|
|
"-D symbol=value define symbol\n"
|
|
|
|
"-X inhibit expressload segment\n"
|
|
|
|
"-o outfile specify output file (default gs.out)\n"
|
|
|
|
"-v be verbose\n"
|
|
|
|
"\n",
|
|
|
|
stderr);
|
|
|
|
|
|
|
|
exit(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* older std libraries lack charconv and std::from_chars */
|
|
|
|
static bool parse_number(const char *begin, const char *end, uint32_t &value, int base = 10) {
|
|
|
|
|
|
|
|
#if defined(HAVE_CHARCONV)
|
|
|
|
auto r = std::from_chars(begin, end, value, base);
|
|
|
|
if (r.ec != std::errc() || r.ptr != end) return false;
|
|
|
|
#else
|
|
|
|
auto xerrno = errno;
|
|
|
|
errno = 0;
|
|
|
|
char *ptr = nullptr;
|
|
|
|
value = std::strtoul(begin, &ptr, base);
|
|
|
|
std::swap(errno, xerrno);
|
|
|
|
if (xerrno || ptr != end) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_define(std::string str) {
|
|
|
|
/* -D key[=value]
|
|
|
|
value = 0x, $, % or base 10 */
|
|
|
|
|
|
|
|
uint32_t value = 0;
|
|
|
|
|
|
|
|
auto ix = str.find('=');
|
|
|
|
if (ix == 0) usage(EX_USAGE);
|
|
|
|
if (ix == str.npos) {
|
|
|
|
value = 1;
|
|
|
|
} else {
|
|
|
|
|
|
|
|
int base = 10;
|
|
|
|
auto pos = ++ix;
|
|
|
|
|
|
|
|
char c = str[pos]; /* returns 0 if == size */
|
|
|
|
|
|
|
|
switch(c) {
|
|
|
|
case '%':
|
|
|
|
base = 2; ++pos; break;
|
|
|
|
case '$':
|
|
|
|
base = 16; ++pos; break;
|
|
|
|
case '0':
|
|
|
|
c = str[pos+1];
|
|
|
|
if (c == 'x' || c == 'X') {
|
|
|
|
base = 16; pos += 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!parse_number(str.data() + pos, str.data() + str.length(), value, base))
|
|
|
|
usage(EX_USAGE);
|
|
|
|
|
|
|
|
str.resize(ix-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-14 04:49:10 +00:00
|
|
|
define(str, value, LBL_D);
|
2019-12-13 04:50:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* .ends_with() is c++20 */
|
|
|
|
static bool is_S(std::string_view sv) {
|
|
|
|
size_t s = sv.size();
|
|
|
|
// && is a sequence point.
|
|
|
|
return s >= 2 && std::toupper(sv[--s]) == 'S' && sv[--s] == '.';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool verbose = false;
|
|
|
|
std::string save_file = "gs.out";
|
|
|
|
bool express = true;
|
|
|
|
bool compress = true;
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
|
|
|
|
int c;
|
|
|
|
bool script = false;
|
|
|
|
|
|
|
|
while ((c = getopt(argc, argv, "o:D:XCSv")) != -1) {
|
|
|
|
switch(c) {
|
|
|
|
case 'o':
|
|
|
|
save_file = optarg;
|
|
|
|
break;
|
|
|
|
case 'X': express = false; break;
|
|
|
|
case 'C': compress = false; break;
|
|
|
|
case 'D': add_define(optarg); break;
|
2019-12-13 05:16:53 +00:00
|
|
|
case 'v': verbose = true; break;
|
|
|
|
case 'S': script = true; break;
|
2019-12-13 04:50:43 +00:00
|
|
|
case ':':
|
|
|
|
case '?':
|
|
|
|
default:
|
|
|
|
usage(EX_USAGE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
argv += optind;
|
|
|
|
argc -= optind;
|
|
|
|
|
2019-12-13 05:16:53 +00:00
|
|
|
if (!script && !argc) usage(EX_USAGE);
|
2019-12-13 04:50:43 +00:00
|
|
|
if (script && argc > 1) usage(EX_USAGE);
|
|
|
|
if (argc == 1 && is_S(*argv)) script = true;
|
|
|
|
|
2019-12-13 05:01:28 +00:00
|
|
|
if (script) process_script(argc ? *argv : nullptr);
|
2019-12-13 04:50:43 +00:00
|
|
|
else process_files(argc, argv);
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|