diff mbox series

[5/8] pdbg: Add command/flag parsing definitions

Message ID 20180620053409.14538-6-alistair@popple.id.au
State Accepted
Headers show
Series Rework option parsing | expand

Commit Message

Alistair Popple June 20, 2018, 5:34 a.m. UTC
Add code to support command argument and flag parsing. A future patch will
update existing code to use these parsers.

The idea behind this code is to allow easily calling C functions from the
command line without requiring shim/boilerplate functions to marshal arguments
and flags which can be repetitive and error prone.

In general a command consists of positional arguments and non-positional flags
which optionally take arguments as shown below:

cmd_name <arg1> <arg2> ... <argN> --flag1=<arg?> --flag2 ... --flagM

It supports default values for the last N postional arguments which allows
trailing arguments to be made optional. The above definition allows a function
with the following prototype to be called directly without any manual shim code:

cmd_name(arg1, arg2, ... argN, flags)

Where `flags` is a struct defined elsewhere containing fields which will match
with the flag specification.

Signed-off-by: Alistair Popple <alistair@popple.id.au>
---
 Makefile.am  |  13 ++++-
 src/optcmd.c | 110 +++++++++++++++++++++++++++++++++++
 src/optcmd.h | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 306 insertions(+), 2 deletions(-)
 create mode 100644 src/optcmd.c
 create mode 100644 src/optcmd.h
diff mbox series

Patch

diff --git a/Makefile.am b/Makefile.am
index f0274b1..b860e14 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,8 +27,17 @@  DT_headers = $(DT:.dts=.dt.h)
 BUILT_SOURCES = $(DT) $(DT_headers)
 
 pdbg_SOURCES = \
-	src/main.c src/cfam.c src/scom.c src/reg.c src/mem.c src/thread.c \
-	src/ring.c src/htm.c src/progress.c src/options_@ARCH@.c
+	src/main.c \
+	src/cfam.c \
+	src/scom.c \
+	src/reg.c \
+	src/mem.c \
+	src/thread.c \
+	src/ring.c \
+	src/htm.c \
+	src/progress.c \
+	src/optcmd.c \
+	src/options_@ARCH@.c
 
 pdbg_LDADD = $(DT_objects) libpdbg.la libfdt.la \
 	-L.libs -lrt
diff --git a/src/optcmd.c b/src/optcmd.c
new file mode 100644
index 0000000..f6d2690
--- /dev/null
+++ b/src/optcmd.c
@@ -0,0 +1,110 @@ 
+/* Copyright 2016 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "optcmd.h"
+
+/* Parse a flag of the form "--long-flag=<blah>" or "-F <blah>" */
+static int optcmd_parse_flag(const char *argv, struct optcmd_flag *flags,
+			     int flag_count, void **flag_results)
+{
+	int i;
+	char *flag, *arg;
+
+	flag = strdup(argv);
+	arg = strchr(flag, '=');
+	if (arg) {
+		*arg = '\0';
+		arg++;
+	}
+
+	for (i = 0; i < flag_count; i++) {
+		if (!strcmp(flag, flags[i].name)) {
+			flag_results[i] = flags[i].arg(arg);
+			if (!flag_results[i]) {
+				printf("Unable to parse argument for %s\n", flag);
+				return 1;
+			}
+
+			return 0;
+		}
+	}
+
+	printf("Invalid flag %s specified\n", flag);
+	return 1;
+}
+
+/* Parse command arguments and flags */
+optcmd_cmd_t *optcmd_parse(struct optcmd_cmd *cmd, const char *argv[], int argc,
+			   void **arg_results[], void **flag_results[])
+{
+	int i, arg_count = 0, total_arg_count, total_flag_count;
+	struct optcmd_arg *args = cmd->args;
+	struct optcmd_flag *flags = cmd->flags;
+	void **tmp_arg_results, **tmp_flag_results;
+
+	/* Allocate space for parser results */
+	for (total_arg_count = 0; args[total_arg_count].parser && total_arg_count < OPTCMD_MAX_ARGS; total_arg_count++) {}
+	for (total_flag_count = 0; flags[total_flag_count].arg && total_flag_count < OPTCMD_MAX_FLAGS; total_flag_count++) {}
+	tmp_arg_results = malloc(total_arg_count*sizeof(void *));
+	assert(tmp_arg_results);
+	tmp_flag_results = calloc(1, total_flag_count*sizeof(void *));
+	assert(tmp_flag_results);
+
+	for (i = 0; i < argc; i++, argv++) {
+		if (!strncmp(*argv, "--", 2)) {
+			if (optcmd_parse_flag(*argv, flags, total_flag_count, tmp_flag_results))
+				/* Unable to parse flag */
+				return NULL;
+		} else {
+			if (arg_count >= total_arg_count) {
+				printf("Too many arguments passed to %s\n", cmd->cmd);
+				return NULL;
+			}
+
+			tmp_arg_results[arg_count] = args[arg_count].parser(*argv);
+			if (!tmp_arg_results[arg_count]) {
+				printf("Unable to parse argument %s\n", *argv);
+				return NULL;
+			}
+			arg_count++;
+		}
+	}
+
+	for (arg_count = arg_count; arg_count < total_arg_count; arg_count++) {
+		/* Process default positional arguments */
+		struct optcmd_arg *arg = &args[arg_count];
+
+		if (!arg->def) {
+			printf("Not enough arguments passed to %s\n", cmd->cmd);
+			return NULL;
+		}
+
+		tmp_arg_results[arg_count] = arg->parser(arg->def);
+		if (!tmp_arg_results[arg_count]) {
+			printf("Programming error - unable to parse default argument %s\n", arg->def);
+			return NULL;
+		}
+	}
+
+	*arg_results = tmp_arg_results;
+	*flag_results = tmp_flag_results;
+
+	return cmd->cmdp;
+}
diff --git a/src/optcmd.h b/src/optcmd.h
new file mode 100644
index 0000000..e5f688e
--- /dev/null
+++ b/src/optcmd.h
@@ -0,0 +1,185 @@ 
+/* Copyright 2016 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef __OPTCMD_H
+#define __OPTCMD_H
+
+#include "ccan/check_type/check_type.h"
+#include "ccan/build_assert/build_assert.h"
+#include "ccan/cppmagic/cppmagic.h"
+#include "config.h"
+
+#include "parsers.h"
+
+#ifndef OPTCMD_MAX_ARGS
+#define OPTCMD_MAX_ARGS 10
+#endif
+
+#ifndef OPTCMD_MAX_FLAGS
+#define OPTCMD_MAX_FLAGS 10
+#endif
+
+/*
+ * The idea of this module is to assist with easily calling C functions from the
+ * command line without requiring shim/boilerplate functions to marshal
+ * arguments and flags correctly which can be repetitive and error prone.
+ *
+ * In general a command consists of positional arguments and non-positional
+ * flags which optionally take arguments as shown below:
+ *
+ * `cmd_name <arg1> <arg2> ... <argN> --flag1=<arg?> --flag2 ... --flagM`
+ *
+ * It supports default values for the last N postional arguments which allows
+ * trailing arguments to be made optional. The above definition allows a
+ * function with the following prototype to be called directly:
+ *
+ * `cmd_name(arg1, arg2, ... argN, flags)`
+ *
+ * Where `flags` is a struct defined elsewhere containing fields which will
+ * match with the flags specified above.
+ *
+ * TODO:
+ *  - Add support for short flags (ie. `-f <arg>`)
+ *
+ *  - Add a function to free memory. The parsers allocate memory but it's never
+ *    freed. Not a big issue though as the program tends to execute one command
+ *    and exit.
+ */
+
+typedef void * (optcmd_parser_t)(const char *argv);
+typedef int (optcmd_cmd_t)(void *[], void *[]);
+
+/*
+ * The below data structures are used internally to specify commands along with
+ * any arguments and flags. They could be filled out without any of the macro
+ * magic further down, however internally the parser results are all cast to
+ * (void *) which could result in weird errors if a function expecting a
+ * argument of type char is called with an int.
+ *
+ * Using the macros should guard against these types of errors by ensuring type
+ * mismatch errors will result in compiler warnings or errors (although sadly
+ * these tend to be rather noisy).
+ */
+struct optcmd_flag {
+	const char *name;
+	optcmd_parser_t *arg;
+};
+
+struct optcmd_arg {
+	optcmd_parser_t *parser;
+	const char *def;
+};
+
+struct optcmd_cmd {
+	const char *cmd;
+	optcmd_cmd_t *cmdp;
+	struct optcmd_arg args[OPTCMD_MAX_ARGS];
+	struct optcmd_flag flags[OPTCMD_MAX_FLAGS];
+};
+
+/* Casts the given parser to a generic parser returning void * and taking a
+ * char * argument. */
+#define OPTCMD_PARSER_CAST(parser_) ((optcmd_parser_t *) parser_)
+
+/* Returns the return type definition of the given parser */
+#define OPTCMD_TYPEOF_PARSER(parser_, ...) typeof(*parser_(""))
+
+/* Cast a postional argument to the right type */
+#define OPTCMD_CAST_ARG(cnt_, parser_) *(OPTCMD_TYPEOF_PARSER parser_ *) args[cnt_]
+
+/* Returns a positional argument struct */
+#define _OPTCMD_ARG(parser_, default_) { .parser = OPTCMD_PARSER_CAST(parser_), .def = default_ }
+#define OPTCMD_ARG(pos_) _OPTCMD_ARG pos_
+
+/* Returns the type definition for a postional argument */
+#define _OPTCMD_ARG_DEF(parser_, ...) OPTCMD_TYPEOF_PARSER(parser_)
+#define OPTCMD_ARG_DEF(pos_) _OPTCMD_ARG_DEF pos_
+
+/* Associate a specific flag name with a parser */
+#define _OPTCMD_FLAG(flag_, field_, parser_, ...)		\
+	{ .name = flag_, .arg = OPTCMD_PARSER_CAST(parser_) }
+#define OPTCMD_FLAG(flag_) _OPTCMD_FLAG flag_
+
+/* Cast parser output to a specific flag field */
+#define _OPTCMD_CAST_FLAGS(flag_, field_, parser_, default_)	\
+	BUILD_ASSERT(!check_types_match(flag.field_, OPTCMD_TYPEOF_PARSER(parser_)));			\
+	flag.field_ = *flags ? *(OPTCMD_TYPEOF_PARSER(parser_) *) *flags : default_; \
+	flags++;
+#define OPTCMD_CAST_FLAGS(flags_) _OPTCMD_CAST_FLAGS flags_
+
+/*
+ * Defines a new command with arguments and flags.
+ *  @cmd_name - name of command used on the command line
+ *  @cmd_func - pointer to the function to call for this command
+ *  @pos - list of positional arguments in the form ((parser, <default value>, "help text"), ...)
+ *  @flag - name of flag struct to pass @cmd_func as the last parameter
+ *  @flags - list of flags in the form (("--flag-name", <struct field name>, parser, <default value>, "help text"), ...)
+ */
+#define OPTCMD_DEFINE_CMD_WITH_FLAGS(cmd_name_, cmd_func_, pos_, flag_, flags_)	\
+        int cmd_func_(CPPMAGIC_MAP(OPTCMD_ARG_DEF, CPPMAGIC_EVAL pos_), struct flag_); \
+	int __##cmd_func_(void *args[], void *flags[])				\
+	{								\
+		struct flag_ flag;					\
+		CPPMAGIC_JOIN(, CPPMAGIC_MAP(OPTCMD_CAST_FLAGS, CPPMAGIC_EVAL flags_)) \
+		return cmd_func_(CPPMAGIC_MAP_CNT(OPTCMD_CAST_ARG, CPPMAGIC_EVAL pos_), flag); \
+	}								\
+									\
+	struct optcmd_cmd optcmd_##cmd_name_ = {					\
+		.cmd = #cmd_name_,					\
+		.cmdp = __##cmd_func_,					\
+		.args = { CPPMAGIC_MAP(OPTCMD_ARG, CPPMAGIC_EVAL pos_), {NULL} }, \
+		.flags = { CPPMAGIC_MAP(OPTCMD_FLAG, CPPMAGIC_EVAL flags_), {NULL} }, \
+	}
+
+/*
+ * Defines a new command with arguments.
+ *  @cmd_name - name of command used on the command line
+ *  @cmd_func - pointer to the function to call for this command
+ *  @pos - list of positional arguments in the form ((parser, <default value>, "help text"), ...)
+ */
+#define OPTCMD_DEFINE_CMD_WITH_ARGS(cmd_name_, cmd_func_, pos_)			\
+        int cmd_func_(CPPMAGIC_MAP(OPTCMD_ARG_DEF, CPPMAGIC_EVAL pos_)); \
+	int __##cmd_func_(void *args[], void *flags[])			\
+	{								\
+		return cmd_func_(CPPMAGIC_MAP_CNT(OPTCMD_CAST_ARG, CPPMAGIC_EVAL pos_)); \
+	}								\
+									\
+	struct optcmd_cmd optcmd_##cmd_name_ = {					\
+		.cmd = #cmd_name_,					\
+		.cmdp = __##cmd_func_,					\
+		.args = { CPPMAGIC_MAP(OPTCMD_ARG, CPPMAGIC_EVAL pos_), {NULL} }, \
+	}
+
+/*
+ * Defines a new command taking no arguments or flags.
+ *  @cmd_name - name of command used on the command line
+ *  @cmd_func - pointer to the function to call for this command
+ */
+#define OPTCMD_DEFINE_CMD(cmd_name_, cmd_func_)		\
+        int cmd_func_(void);				\
+	int __##cmd_func_(void *args[], void *flags[])	\
+	{						\
+		return cmd_func_();			\
+	}						\
+							\
+	struct optcmd_cmd optcmd_##cmd_name_ = {	\
+		.cmd = #cmd_name_,			\
+		.cmdp = __##cmd_func_,			\
+	}
+
+optcmd_cmd_t *optcmd_parse(struct optcmd_cmd *cmd, const char *argv[], int argc,
+			   void **args[], void **flags[]);
+
+#endif