summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2024-04-30 01:38:42 +0300
committerOxore <oxore@protonmail.com>2024-05-19 18:21:40 +0300
commit71b89bc9ceb59f2603cf4b0635849269597a4823 (patch)
tree7b53520d3f1306f71c2083cf8b6d9dbb749249da
parent47ffe952bcfc31a78c16be8620109955fdc17f2f (diff)
Impl --stop and --gdb options
-rw-r--r--CMakeLists.txt22
-rw-r--r--chardev.cpp50
-rw-r--r--chardev.hpp32
-rw-r--r--emulator.cpp246
-rw-r--r--optparse/UNLICENSE24
-rw-r--r--optparse/optparse.h403
6 files changed, 700 insertions, 77 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 487b6fd..6ba5328 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS True)
set(emulator_sources
bus.cpp
+ chardev.cpp
emulator.cpp
gdbremote_parser.cpp
graphics.cpp
@@ -40,6 +41,7 @@ add_library(musashi_m68k OBJECT ${musashi_m68k_sources})
target_include_directories(musashi_m68k PRIVATE
musashi-m68k
${CMAKE_CURRENT_BINARY_DIR}
+ .
)
# TODO make SDL2 optional for headless mode
@@ -76,15 +78,14 @@ target_compile_options(emulator PRIVATE
$<$<CONFIG:DEBUG>:${common_debug_flags}>
)
-target_link_options(emulator PRIVATE
- $<$<CONFIG:DEBUG>:${common_debug_flags}>
- )
+target_link_options(emulator PRIVATE $<$<CONFIG:DEBUG>:${common_debug_flags}>)
target_link_libraries(emulator musashi_m68k)
# TODO make SDL2 optional for headless mode
-target_include_directories(emulator PRIVATE ${SDL2_INCLUDE_DIRS})
+target_include_directories(emulator PRIVATE ${SDL2_INCLUDE_DIRS} .)
# TODO make SDL2 optional for headless mode
target_link_libraries(emulator ${SDL2_LIBRARIES})
target_compile_definitions(emulator PRIVATE
+ $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>
DEBUG_TRACE_INSTRUCTIONS=0
DEBUG_TRACE_GDB_REMOTE=0
DEBUG_TRACE_VDP_ACCESS=1
@@ -92,13 +93,6 @@ target_compile_definitions(emulator PRIVATE
HAS_GRAPHICS=1
)
-
-## Target for GDB Remote Debugging protocol implementation testing
-#add_executable(gdbremote
-# gdbremote.cpp
-# gdbremote_parser.cpp
-# )
-
-include_directories(
- .
- )
+# Target for GDB Remote Debugging protocol implementation testing
+#add_executable(gdbremote gdbremote.cpp gdbremote_parser.cpp)
+#target_include_directories(gdbremote PRIVATE .)
diff --git a/chardev.cpp b/chardev.cpp
new file mode 100644
index 0000000..1d587b9
--- /dev/null
+++ b/chardev.cpp
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: Unlicense
+ */
+
+#include "chardev.hpp"
+
+#include <cstring>
+#include <cstdlib>
+
+static bool startsWith(const char* str, const char* pattern)
+{
+ const size_t pattern_len = strlen(pattern);
+ const size_t str_len = strlen(str);
+ if (str_len < pattern_len) {
+ return false;
+ }
+ return 0 == memcmp(str, pattern, pattern_len);
+}
+
+static CharDev parseTcp(const char* path)
+{
+ // We need to make sure there is at least one more ':' delimiter in the
+ // string
+ const char* rest = strchr(path + (sizeof "tcp:") - 1, ':');
+ if (rest == nullptr) {
+ return CharDev::Invalid(path);
+ }
+ const char* port_str;
+ // Find last ':' before string ends, i.e. when strchr returns NULL
+ do {
+ port_str = rest + 1;
+ rest = strchr(port_str, ':');
+ } while (rest);
+ const unsigned long port = strtoul(port_str, nullptr, 0);
+ if (port <= UINT16_MAX) {
+ path += (sizeof "tcp:") - 1;
+ return CharDev::Tcp(path, port_str - 1 - path, port);
+ }
+ return CharDev::Invalid(path);
+}
+
+CharDev CharDev::Parse(const char* path)
+{
+ if (0 == strcmp(path, "pty")) {
+ return CharDev::Pty();
+ }
+ if (startsWith(path, "tcp:")) {
+ return parseTcp(path);
+ }
+ return CharDev::Invalid(path);
+}
diff --git a/chardev.hpp b/chardev.hpp
new file mode 100644
index 0000000..0a0d9df
--- /dev/null
+++ b/chardev.hpp
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: Unlicense
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstddef>
+
+enum class CharDevType: uint8_t {
+ kUndefined = 0,
+ kPty, ///< Creates new PTY
+ kTcp, ///< Binds and listens to specified address and port
+};
+
+struct CharDev {
+ CharDevType type{};
+ const char* path{};
+ int fd{-1};
+ size_t path_len{};
+ uint16_t port{};
+ static CharDev Invalid(const char* path = nullptr)
+ {
+ return CharDev{CharDevType::kUndefined, path};
+ }
+ static CharDev Pty(void) { return CharDev{CharDevType::kPty}; }
+ static CharDev Tcp(const char* host, size_t path_len, uint16_t port)
+ {
+ return CharDev{CharDevType::kTcp, host, -1, path_len, port};
+ }
+ static CharDev Parse(const char* path);
+};
+
diff --git a/emulator.cpp b/emulator.cpp
index 72d5184..e3ac6bb 100644
--- a/emulator.cpp
+++ b/emulator.cpp
@@ -1,6 +1,19 @@
/* SPDX-License-Identifier: Unlicense
*/
+#define OPTPARSE_IMPLEMENTATION
+#define OPTPARSE_API static
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include "optparse/optparse.h"
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+#include "chardev.hpp"
#include "bus.hpp"
#include "graphics.hpp"
#include "vdp.hpp"
@@ -22,9 +35,9 @@
#include <ctime>
#include <fcntl.h>
#include <sys/socket.h>
-#include <memory>
#include <unistd.h>
#include <signal.h>
+#include <netdb.h>
#if !defined(DEBUG_TRACE_INSTRUCTIONS)
# define DEBUG_TRACE_INSTRUCTIONS 0
@@ -85,15 +98,6 @@ static int set_socket_reuseaddr(const int socket_fd)
return setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, len);
}
-static inline struct sockaddr_in sockaddr_in_any_ip_with_port(const uint16_t port)
-{
- struct sockaddr_in server{};
- server.sin_family = AF_INET;
- server.sin_addr.s_addr = INADDR_ANY;
- server.sin_port = htons(port);
- return server;
-}
-
static inline int SetNonblock(const int fd)
{
const int flags = fcntl(fd, F_GETFL);
@@ -107,36 +111,67 @@ static inline int SetNonblock(const int fd)
return 0;
}
-static int setup_socket(const uint16_t port)
+static int setup_socket(
+ const char* host, uint16_t port, char* addrstr, size_t addrstr_size)
{
- // This creates the socket - or quits
- const int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (socket_fd == -1) {
- perror("Could not create socket");
+ struct addrinfo hints{}, * result{};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ char port_str[6]{};
+ snprintf(port_str, sizeof(port_str), "%u", port);
+ const int gai_ret = getaddrinfo(host, port_str, &hints, &result);
+ if (gai_ret != 0) {
+ fprintf(stderr, "getaddrinfo(%s): %s\n", host, gai_strerror(gai_ret));
return -1;
}
- // Set O_NONBLOCK for socket
- const int ret = SetNonblock(socket_fd);
- if (ret) {
- fprintf(stderr, "fcntl(socket_fd, F_SETFL O_NONBLOCK): %s\n", strerror(ret));
- close(socket_fd);
- return -1;
- }
- // Make TCP port reusable in case of kill or crash.
- // Deliberate explanation: https://stackoverflow.com/a/3233022
- if (set_socket_reuseaddr(socket_fd) == -1) {
- perror("setsockopt failed");
+ int socket_fd = -1;
+ for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
+ socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (socket_fd == -1)
+ continue;
+ // Set O_NONBLOCK for the socket
+ const int ret = SetNonblock(socket_fd);
+ if (ret) {
+ fprintf(stderr, "fcntl(socket_fd, F_SETFL O_NONBLOCK): %s\n", strerror(ret));
+ close(socket_fd);
+ socket_fd = -1;
+ break;
+ }
+ // Make TCP port reusable in case of kill or crash.
+ // Deliberate explanation: https://stackoverflow.com/a/3233022
+ if (set_socket_reuseaddr(socket_fd) == -1) {
+ perror("setsockopt failed");
+ close(socket_fd);
+ socket_fd = -1;
+ break;
+ }
+ if (0 == bind(socket_fd, rp->ai_addr, rp->ai_addrlen)) {
+ void* addr_ptr;
+ if (rp->ai_addr->sa_family == AF_INET) {
+ addr_ptr = reinterpret_cast<void*>(
+ &((struct sockaddr_in*)rp->ai_addr)->sin_addr);
+ } else if (rp->ai_addr->sa_family == AF_INET6) {
+ addr_ptr = reinterpret_cast<void*>(
+ &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr);
+ } else {
+ assert(false);
+ }
+ inet_ntop(rp->ai_addr->sa_family, addr_ptr, addrstr, addrstr_size);
+ addrstr[addrstr_size - 1] = '\0';
+ break;
+ }
+ fprintf(stderr, "Binding to %s:%u failed\n", addrstr, port);
close(socket_fd);
- return -1;
+ socket_fd = -1;
}
- puts("Socket created");
- const struct sockaddr_in server = sockaddr_in_any_ip_with_port(port);
- if (bind(socket_fd, (struct sockaddr *)&server, sizeof(server)) == -1) {
- perror("Bind failed");
- close(socket_fd);
+ freeaddrinfo(result);
+ if (socket_fd == -1) {
+ fprintf(stderr, "Could not bind or create socket: "
+ "getaddrinfo did not give any suitable entry\n");
return -1;
}
- printf("Binding to 0.0.0.0:%u done\n", port);
+ fprintf(stderr, "Binding to %s:%u done\n", addrstr, port);
return socket_fd;
}
@@ -451,10 +486,28 @@ static void RunSingleVideoCycle(M68KDebuggingControl& m68k_debug, Graphics& grap
g_cycles_counter += m68k_execute(400000);
}
-int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics)
+static int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics, CharDev &gdb_chardev)
{
- const int port = 3333;
- const int socket_fd = setup_socket(port);
+ if (gdb_chardev.type == CharDevType::kUndefined) {
+ while (!quit) {
+ RunSingleVideoCycle(m68k_debug, graphics);
+ }
+ return EXIT_SUCCESS;
+ }
+ constexpr size_t addrstr_size = 160;
+ char addrstr[addrstr_size]{};
+ const int port = gdb_chardev.port ? gdb_chardev.port : 3333;
+ int socket_fd;
+ if (gdb_chardev.path_len) {
+ char *host = new (std::nothrow) char[gdb_chardev.path_len + 1];
+ assert(host);
+ memcpy(host, gdb_chardev.path, gdb_chardev.path_len);
+ host[gdb_chardev.path_len] = '\0';
+ socket_fd = setup_socket(host, port, addrstr, addrstr_size);
+ delete [] host;
+ } else {
+ socket_fd = setup_socket("0.0.0.0", port, addrstr, addrstr_size);
+ }
if (socket_fd == -1)
return EXIT_FAILURE;
// Mark socket as listener
@@ -463,16 +516,24 @@ int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics)
close(socket_fd);
return EXIT_FAILURE;
}
- printf("Listening TCP 0.0.0.0:%u\n", port);
+ printf("Listening TCP %s:%u\n", addrstr, port);
while (!quit) {
struct sockaddr client_address{};
socklen_t address_len{};
const int conn_fd = accept(socket_fd, &client_address, &address_len);
- if (conn_fd == -1 ) {
+ if (conn_fd == -1) {
+ gdb_chardev.fd = conn_fd;
if (errno == EWOULDBLOCK) {
- // TODO turn off O_NONBLOCK and poll instead of sleep, only use
- // nonlock when freerunning
- clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr);
+ if (m68k_debug.IsRunning()) {
+ RunSingleVideoCycle(m68k_debug, graphics);
+ if (m68k_debug.HasBreakpoint()) {
+ m68k_debug.SetRunning(false);
+ puts("ResetPendingBreakpoint");
+ m68k_debug.ResetPendingBreakpoint();
+ }
+ } else {
+ clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr);
+ }
continue;
}
perror("Accept failed");
@@ -512,57 +573,116 @@ int emulator(M68KDebuggingControl& m68k_debug, Graphics& graphics)
if (send(conn_fd, &response[0], response.length(), 0) == -1)
perror("Send failed (response)");
}
+ } else {
+ clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr);
}
- // TODO turn off O_NONBLOCK and poll instead of sleep, only use
- // nonlock when freerunning
- clock_nanosleep(CLOCK_MONOTONIC, 0, &kOneMillisecond, nullptr);
}
close(conn_fd);
+ gdb_chardev.fd = -1;
g_no_ack_mode = false; // TODO move to GDB::ExchangeContext
}
close(socket_fd);
return 0;
}
-void sigint_handler(int sig)
+static void sigint_handler(int sig)
{
printf("Signal %d received, exiting\n",sig);
quit = true;
}
-int main(int argc, char* argv[])
+static void PrintUsage(FILE *s, const char *argv0)
{
- if (argc != 2)
- {
- printf("Usage: %s <program file>\n", argv[0]);
- exit(-1);
- }
-
- FILE* const fhandle = fopen(argv[1], "rb");
+ // Please, keep all lines in 80 columns range when printed.
+ fprintf(s,
+ "Usage: %s [options] <program-file-name>\n"
+ "Options:\n"
+ " -h, --help Show this message.\n"
+ " -g, --gdb[=DEV] Accept GDB connection on DEV (default: 'tcp::3333').\n"
+ " -S, --stop Freeze CPU at startup (use 'c' in GDB to start\n"
+ " execution).\n"
+ , argv0);
+}
- if (fhandle == NULL)
+int main(int, char* argv[])
+{
+ struct optparse_long longopts[] = {
+ {"help", 'h', OPTPARSE_NONE},
+ {"gdb", 'g', OPTPARSE_OPTIONAL},
+ {"stop", 'S', OPTPARSE_NONE},
+ {},
+ };
+ struct CharDev gdb_chardev{};
+ const char *program_file_name = nullptr;
+ bool stop = false;
+ struct optparse options;
+ optparse_init(&options, argv);
+ // Parse opts
+ int option;
+ while ((option = optparse_long(&options, longopts, nullptr)) != -1) {
+ switch (option) {
+ case 'h':
+ PrintUsage(stdout, argv[0]);
+ return EXIT_SUCCESS;
+ break;
+ case 'g':
+ {
+ const char *path = options.optarg ? options.optarg : "tcp::3333";
+ gdb_chardev = CharDev::Parse(path);
+ if (gdb_chardev.type == CharDevType::kPty) {
+ fprintf(stderr, "main: PTY chardev currently is not supported\n");
+ return EXIT_FAILURE;
+ } else if (gdb_chardev.type == CharDevType::kUndefined) {
+ fprintf(stderr, "invalid chardev specified: \"%s\"\n", path);
+ return EXIT_FAILURE;
+ }
+ }
+ break;
+ case 'S':
+ stop = true;
+ printf("stop=%s\n", stop ? "true" : "false");
+ break;
+ case '?':
+ fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg);
+ return EXIT_FAILURE;
+ }
+ }
+ // Parse input file name
+ char *arg;
+ while ((arg = optparse_arg(&options))) {
+ if (program_file_name == nullptr) {
+ program_file_name = arg;
+ } else {
+ fprintf(stderr, "error: too many free arguments provided\n");
+ return EXIT_FAILURE;
+ }
+ }
+ if (program_file_name == nullptr) {
+ fprintf(stderr, "main: Error: no program file name specified, see usage below.\n");
+ PrintUsage(stderr, argv[0]);
+ return EXIT_FAILURE;
+ }
+ FILE* const fhandle = fopen(program_file_name, "rb");
+ if (fhandle == nullptr) {
exit_error("Unable to open %s", argv[1]);
-
+ }
const size_t fread_ret = fread(g_rom, 1, ROM_SIZE, fhandle);
- if (fread_ret <= 0)
+ if (fread_ret <= 0) {
exit_error("Error reading %s", argv[1]);
+ }
printf("Read into ROM %zu bytes\n", fread_ret);
-
struct sigaction sa;
sa.sa_handler = sigint_handler;
- sigaction(SIGINT, &sa, NULL);
-
+ sigaction(SIGINT, &sa, nullptr);
Graphics graphics{};
if (!graphics.IsOk()) {
return EXIT_FAILURE;
}
-
m68k_init();
m68k_set_cpu_type(M68K_CPU_TYPE_68000);
m68k_pulse_reset();
g_cycles_counter += m68k_execute(1); // Skip reset cycles
-
- emulator(g_m68k_debug, graphics);
-
+ g_m68k_debug.SetRunning(!stop);
+ emulator(g_m68k_debug, graphics, gdb_chardev);
return EXIT_SUCCESS;
}
diff --git a/optparse/UNLICENSE b/optparse/UNLICENSE
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/optparse/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/optparse/optparse.h b/optparse/optparse.h
new file mode 100644
index 0000000..8d6c0a9
--- /dev/null
+++ b/optparse/optparse.h
@@ -0,0 +1,403 @@
+/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * To get the implementation, define OPTPARSE_IMPLEMENTATION.
+ * Optionally define OPTPARSE_API to control the API's visibility
+ * and/or linkage (static, __attribute__, __declspec).
+ *
+ * The POSIX getopt() option parser has three fatal flaws. These flaws
+ * are solved by Optparse.
+ *
+ * 1) Parser state is stored entirely in global variables, some of
+ * which are static and inaccessible. This means only one thread can
+ * use getopt(). It also means it's not possible to recursively parse
+ * nested sub-arguments while in the middle of argument parsing.
+ * Optparse fixes this by storing all state on a local struct.
+ *
+ * 2) The POSIX standard provides no way to properly reset the parser.
+ * This means for portable code that getopt() is only good for one
+ * run, over one argv with one option string. It also means subcommand
+ * options cannot be processed with getopt(). Most implementations
+ * provide a method to reset the parser, but it's not portable.
+ * Optparse provides an optparse_arg() function for stepping over
+ * subcommands and continuing parsing of options with another option
+ * string. The Optparse struct itself can be passed around to
+ * subcommand handlers for additional subcommand option parsing. A
+ * full reset can be achieved by with an additional optparse_init().
+ *
+ * 3) Error messages are printed to stderr. This can be disabled with
+ * opterr, but the messages themselves are still inaccessible.
+ * Optparse solves this by writing an error message in its errmsg
+ * field. The downside to Optparse is that this error message will
+ * always be in English rather than the current locale.
+ *
+ * Optparse should be familiar with anyone accustomed to getopt(), and
+ * it could be a nearly drop-in replacement. The option string is the
+ * same and the fields have the same names as the getopt() global
+ * variables (optarg, optind, optopt).
+ *
+ * Optparse also supports GNU-style long options with optparse_long().
+ * The interface is slightly different and simpler than getopt_long().
+ *
+ * By default, argv is permuted as it is parsed, moving non-option
+ * arguments to the end. This can be disabled by setting the `permute`
+ * field to 0 after initialization.
+ */
+#ifndef OPTPARSE_H
+#define OPTPARSE_H
+
+#ifndef OPTPARSE_API
+# define OPTPARSE_API
+#endif
+
+struct optparse {
+ char **argv;
+ int permute;
+ int optind;
+ int optopt;
+ char *optarg;
+ char errmsg[64];
+ int subopt;
+};
+
+enum optparse_argtype {
+ OPTPARSE_NONE,
+ OPTPARSE_REQUIRED,
+ OPTPARSE_OPTIONAL
+};
+
+struct optparse_long {
+ const char *longname;
+ int shortname;
+ enum optparse_argtype argtype;
+};
+
+/**
+ * Initializes the parser state.
+ */
+OPTPARSE_API
+void optparse_init(struct optparse *options, char **argv);
+
+/**
+ * Read the next option in the argv array.
+ * @param optstring a getopt()-formatted option string.
+ * @return the next option character, -1 for done, or '?' for error
+ *
+ * Just like getopt(), a character followed by no colons means no
+ * argument. One colon means the option has a required argument. Two
+ * colons means the option takes an optional argument.
+ */
+OPTPARSE_API
+int optparse(struct optparse *options, const char *optstring);
+
+/**
+ * Handles GNU-style long options in addition to getopt() options.
+ * This works a lot like GNU's getopt_long(). The last option in
+ * longopts must be all zeros, marking the end of the array. The
+ * longindex argument may be NULL.
+ */
+OPTPARSE_API
+int optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex);
+
+/**
+ * Used for stepping over non-option arguments.
+ * @return the next non-option argument, or NULL for no more arguments
+ *
+ * Argument parsing can continue with optparse() after using this
+ * function. That would be used to parse the options for the
+ * subcommand returned by optparse_arg(). This function allows you to
+ * ignore the value of optind.
+ */
+OPTPARSE_API
+char *optparse_arg(struct optparse *options);
+
+/* Implementation */
+#ifdef OPTPARSE_IMPLEMENTATION
+
+#define OPTPARSE_MSG_INVALID "invalid option"
+#define OPTPARSE_MSG_MISSING "option requires an argument"
+#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
+
+static int
+optparse_error(struct optparse *options, const char *msg, const char *data)
+{
+ unsigned p = 0;
+ const char *sep = " -- '";
+ while (*msg)
+ options->errmsg[p++] = *msg++;
+ while (*sep)
+ options->errmsg[p++] = *sep++;
+ while (p < sizeof(options->errmsg) - 2 && *data)
+ options->errmsg[p++] = *data++;
+ options->errmsg[p++] = '\'';
+ options->errmsg[p++] = '\0';
+ return '?';
+}
+
+OPTPARSE_API
+void
+optparse_init(struct optparse *options, char **argv)
+{
+ options->argv = argv;
+ options->permute = 1;
+ options->optind = argv[0] != 0;
+ options->subopt = 0;
+ options->optarg = 0;
+ options->errmsg[0] = '\0';
+}
+
+static int
+optparse_is_dashdash(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
+}
+
+static int
+optparse_is_shortopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
+}
+
+static int
+optparse_is_longopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
+}
+
+static void
+optparse_permute(struct optparse *options, int index)
+{
+ char *nonoption = options->argv[index];
+ int i;
+ for (i = index; i < options->optind - 1; i++)
+ options->argv[i] = options->argv[i + 1];
+ options->argv[options->optind - 1] = nonoption;
+}
+
+static int
+optparse_argtype(const char *optstring, char c)
+{
+ int count = OPTPARSE_NONE;
+ if (c == ':')
+ return -1;
+ for (; *optstring && c != *optstring; optstring++);
+ if (!*optstring)
+ return -1;
+ if (optstring[1] == ':')
+ count += optstring[2] == ':' ? 2 : 1;
+ return count;
+}
+
+OPTPARSE_API
+int
+optparse(struct optparse *options, const char *optstring)
+{
+ int type;
+ char *next;
+ char *option = options->argv[options->optind];
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ if (option == 0) {
+ return -1;
+ } else if (optparse_is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (!optparse_is_shortopt(option)) {
+ if (options->permute) {
+ int index = options->optind++;
+ int r = optparse(options, optstring);
+ optparse_permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+ option += options->subopt + 1;
+ options->optopt = option[0];
+ type = optparse_argtype(optstring, option[0]);
+ next = options->argv[options->optind + 1];
+ switch (type) {
+ case -1: {
+ char str[2] = {0, 0};
+ str[0] = option[0];
+ options->optind++;
+ return optparse_error(options, OPTPARSE_MSG_INVALID, str);
+ }
+ case OPTPARSE_NONE:
+ if (option[1]) {
+ options->subopt++;
+ } else {
+ options->subopt = 0;
+ options->optind++;
+ }
+ return option[0];
+ case OPTPARSE_REQUIRED:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1]) {
+ options->optarg = option + 1;
+ } else if (next != 0) {
+ options->optarg = next;
+ options->optind++;
+ } else {
+ char str[2] = {0, 0};
+ str[0] = option[0];
+ options->optarg = 0;
+ return optparse_error(options, OPTPARSE_MSG_MISSING, str);
+ }
+ return option[0];
+ case OPTPARSE_OPTIONAL:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1])
+ options->optarg = option + 1;
+ else
+ options->optarg = 0;
+ return option[0];
+ }
+ return 0;
+}
+
+OPTPARSE_API
+char *
+optparse_arg(struct optparse *options)
+{
+ char *option = options->argv[options->optind];
+ options->subopt = 0;
+ if (option != 0)
+ options->optind++;
+ return option;
+}
+
+static int
+optparse_longopts_end(const struct optparse_long *longopts, int i)
+{
+ return !longopts[i].longname && !longopts[i].shortname;
+}
+
+static void
+optparse_from_long(const struct optparse_long *longopts, char *optstring)
+{
+ char *p = optstring;
+ int i;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+ if (longopts[i].shortname && longopts[i].shortname < 127) {
+ int a;
+ *p++ = longopts[i].shortname;
+ for (a = 0; a < (int)longopts[i].argtype; a++)
+ *p++ = ':';
+ }
+ }
+ *p = '\0';
+}
+
+/* Unlike strcmp(), handles options containing "=". */
+static int
+optparse_longopts_match(const char *longname, const char *option)
+{
+ const char *a = option, *n = longname;
+ if (longname == 0)
+ return 0;
+ for (; *a && *n && *a != '='; a++, n++)
+ if (*a != *n)
+ return 0;
+ return *n == '\0' && (*a == '\0' || *a == '=');
+}
+
+/* Return the part after "=", or NULL. */
+static char *
+optparse_longopts_arg(char *option)
+{
+ for (; *option && *option != '='; option++);
+ if (*option == '=')
+ return option + 1;
+ else
+ return 0;
+}
+
+static int
+optparse_long_fallback(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ int result;
+ char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
+ optparse_from_long(longopts, optstring);
+ result = optparse(options, optstring);
+ if (longindex != 0) {
+ *longindex = -1;
+ if (result != -1) {
+ int i;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++)
+ if (longopts[i].shortname == options->optopt)
+ *longindex = i;
+ }
+ }
+ return result;
+}
+
+OPTPARSE_API
+int
+optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ int i;
+ char *option = options->argv[options->optind];
+ if (option == 0) {
+ return -1;
+ } else if (optparse_is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (optparse_is_shortopt(option)) {
+ return optparse_long_fallback(options, longopts, longindex);
+ } else if (!optparse_is_longopt(option)) {
+ if (options->permute) {
+ int index = options->optind++;
+ int r = optparse_long(options, longopts, longindex);
+ optparse_permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+
+ /* Parse as long option. */
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ option += 2; /* skip "--" */
+ options->optind++;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+ const char *name = longopts[i].longname;
+ if (optparse_longopts_match(name, option)) {
+ char *arg;
+ if (longindex)
+ *longindex = i;
+ options->optopt = longopts[i].shortname;
+ arg = optparse_longopts_arg(option);
+ if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
+ return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
+ } if (arg != 0) {
+ options->optarg = arg;
+ } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
+ options->optarg = options->argv[options->optind];
+ if (options->optarg == 0)
+ return optparse_error(options, OPTPARSE_MSG_MISSING, name);
+ else
+ options->optind++;
+ }
+ return options->optopt;
+ }
+ }
+ return optparse_error(options, OPTPARSE_MSG_INVALID, option);
+}
+
+#endif /* OPTPARSE_IMPLEMENTATION */
+#endif /* OPTPARSE_H */