projects/pwm

changeset 27:722a45b4028b

Add define command for defining macros

Macros are parsed when they are defined with the D command and can
subsequently be used as arguments for other commands.
Handle out of memory errors directly in tok.c.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Mon Sep 25 21:21:25 2017 +0200 (2017-09-25)
parents 5bdea77d0c1d
children e3db02d7f1f4
files Makefile cmd.c macro.c macro.h pwm.1.xml pwm.c pwm.h tok.c tok.h
line diff
     1.1 --- a/Makefile	Thu Sep 21 09:45:59 2017 +0200
     1.2 +++ b/Makefile	Mon Sep 25 21:21:25 2017 +0200
     1.3 @@ -162,6 +162,7 @@
     1.4  
     1.5  PWM_OBJS =		cmd.o \
     1.6  			io.o \
     1.7 +			macro.o \
     1.8  			pager.o \
     1.9  			proc.o \
    1.10  			pw.o \
     2.1 --- a/cmd.c	Thu Sep 21 09:45:59 2017 +0200
     2.2 +++ b/cmd.c	Mon Sep 25 21:21:25 2017 +0200
     2.3 @@ -37,10 +37,12 @@
     2.4  
     2.5  #include "cmd.h"
     2.6  #include "io.h"
     2.7 +#include "macro.h"
     2.8  #include "pager.h"
     2.9  #include "proc.h"
    2.10  #include "pw.h"
    2.11  #include "pwfile.h"
    2.12 +#include "tok.h"
    2.13  #include "util.h"
    2.14  
    2.15  #define	TIME_FORMAT	"%Y-%m-%dT%TZ"
    2.16 @@ -82,6 +84,7 @@
    2.17  	CHARCLASS_GRAPH
    2.18  };
    2.19  
    2.20 +static enum cmd_return	cmd_define(struct pwm_ctx *, int, char *[]);
    2.21  static enum cmd_return	cmd_status(struct pwm_ctx *, int, char *[]);
    2.22  static enum cmd_return	cmd_info(struct pwm_ctx *, int, char *[]);
    2.23  static enum cmd_return	cmd_list(struct pwm_ctx *, int, char *[]);
    2.24 @@ -152,6 +155,7 @@
    2.25  };
    2.26  
    2.27  static struct cmd cmds[] = {
    2.28 +    { "D", "define", "define name=value", "Define a macro", cmd_define },
    2.29      { "t", "status", "status", "Redisplay an error message of the previous "
    2.30      "command and unsaved changes", cmd_status },
    2.31      { "i", "info", "info", "Show metadata information about the current file",
    2.32 @@ -217,6 +221,66 @@
    2.33  }
    2.34  
    2.35  static enum cmd_return
    2.36 +cmd_define(struct pwm_ctx *ctx, int argc, char *argv[])
    2.37 +{
    2.38 +	int		retval = CMD_ERR;
    2.39 +	const char	*value;
    2.40 +	char		*name = NULL;
    2.41 +	size_t		tokenc = 0;
    2.42 +	union tok	**tokenv = NULL;
    2.43 +	struct macro_entry *macro_entry;
    2.44 +
    2.45 +	if (argc != 2) {
    2.46 +		return (CMD_USAGE);
    2.47 +	}
    2.48 +
    2.49 +	/* split into name and value */
    2.50 +	value = strchr(argv[1], '=');
    2.51 +	if (value == NULL) {
    2.52 +		pwm_err(ctx, "bad macro definition \"%s\"", argv[1]);
    2.53 +		goto out;
    2.54 +	}
    2.55 +	xasprintf(&name, "%.*s", value - argv[1], argv[1]);
    2.56 +	value++;
    2.57 +
    2.58 +	/* tokenize macro value */
    2.59 +	switch (tok_tokenize(value, &tokenc, &tokenv)) {
    2.60 +	case TOK_ERR_UNTERMINATED_QUOTE:
    2.61 +		pwm_err(ctx, "unterminated quote in macro");
    2.62 +		goto out;
    2.63 +	case TOK_ERR_TRAILING_BACKSLASH:
    2.64 +		pwm_err(ctx, "trailing backslash in macro");
    2.65 +		goto out;
    2.66 +	case TOK_ERR_INVALID_MACRO_NAME:
    2.67 +		pwm_err(ctx, "invalid macro name referenced in macro");
    2.68 +		goto out;
    2.69 +	}
    2.70 +
    2.71 +	/* parse macro definition */
    2.72 +	switch (macro_parse(name, tokenc, tokenv, ctx->macro_head,
    2.73 +	    &macro_entry)) {
    2.74 +	case MACRO_ERR_INVALID_NAME:
    2.75 +		pwm_err(ctx, "invalid macro name \"%s\"", name);
    2.76 +		goto out;
    2.77 +	case MACRO_ERR_UNDEFINED_MACRO:
    2.78 +		pwm_err(ctx, "macro definition references undefined macro");
    2.79 +		goto out;
    2.80 +	case MACRO_ERR_RECURSIVE:
    2.81 +		pwm_err(ctx, "macro definition must not be recursive");
    2.82 +		goto out;
    2.83 +	}
    2.84 +
    2.85 +	macro_add(ctx->macro_head, macro_entry);
    2.86 +	retval = CMD_OK;
    2.87 +
    2.88 +out:
    2.89 +	tok_free(tokenv);
    2.90 +	free(name);
    2.91 +
    2.92 +	return (retval);
    2.93 +}
    2.94 +
    2.95 +static enum cmd_return
    2.96  cmd_status(struct pwm_ctx *ctx, int argc, char *argv[])
    2.97  {
    2.98  	if (argc != 1) {
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/macro.c	Mon Sep 25 21:21:25 2017 +0200
     3.3 @@ -0,0 +1,202 @@
     3.4 +/*
     3.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     3.6 + *
     3.7 + * Permission is hereby granted, free of charge, to any person obtaining
     3.8 + * a copy of this software and associated documentation files (the
     3.9 + * "Software"), to deal in the Software without restriction, including
    3.10 + * without limitation the rights to use, copy, modify, merge, publish,
    3.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    3.12 + * permit persons to whom the Software is furnished to do so, subject to
    3.13 + * the following conditions:
    3.14 + *
    3.15 + * The above copyright notice and this permission notice shall be included
    3.16 + * in all copies or substantial portions of the Software.
    3.17 + *
    3.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    3.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    3.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    3.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    3.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    3.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    3.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    3.25 + */
    3.26 +
    3.27 +#include "compat.h"
    3.28 +
    3.29 +#include <ctype.h>
    3.30 +#ifdef	HAVE_QUEUE_H
    3.31 +#include <sys/queue.h>
    3.32 +#endif /* HAVE_QUEUE_H */
    3.33 +#include <stdlib.h>
    3.34 +#include <string.h>
    3.35 +
    3.36 +#include "macro.h"
    3.37 +#include "tok.h"
    3.38 +#include "util.h"
    3.39 +
    3.40 +TAILQ_HEAD(macro_head, macro_entry);
    3.41 +
    3.42 +struct macro_entry {
    3.43 +	TAILQ_ENTRY(macro_entry) entry;
    3.44 +	char		*name;
    3.45 +	int		argc;
    3.46 +	char		**argv;
    3.47 +};
    3.48 +
    3.49 +static struct macro_entry *
    3.50 +match_macro(struct macro_head *head, const char *name)
    3.51 +{
    3.52 +	struct macro_entry *entry;
    3.53 +
    3.54 +	TAILQ_FOREACH(entry, head, entry) {
    3.55 +		if (strcmp(entry->name, name) == 0) {
    3.56 +			return (entry);
    3.57 +		}
    3.58 +	}
    3.59 +
    3.60 +	return (NULL);
    3.61 +}
    3.62 +
    3.63 +void
    3.64 +macro_init(struct macro_head **headp)
    3.65 +{
    3.66 +	struct macro_head *head;
    3.67 +
    3.68 +	head = xmalloc(sizeof (struct macro_head));
    3.69 +	TAILQ_INIT(head);
    3.70 +	*headp = head;
    3.71 +}
    3.72 +
    3.73 +static void
    3.74 +free_macro_entry(struct macro_entry *entry)
    3.75 +{
    3.76 +	int	i;
    3.77 +
    3.78 +	if (entry == NULL) {
    3.79 +		return;
    3.80 +	}
    3.81 +
    3.82 +	free(entry->name);
    3.83 +	for (i = 0; i < entry->argc; i++) {
    3.84 +		free(entry->argv[i]);
    3.85 +	}
    3.86 +	free(entry->argv);
    3.87 +	free(entry);
    3.88 +}
    3.89 +
    3.90 +void
    3.91 +macro_destroy(struct macro_head *head)
    3.92 +{
    3.93 +	struct macro_entry *entry;
    3.94 +	struct macro_entry *tmp_entry;
    3.95 +
    3.96 +	if (head == NULL) {
    3.97 +		return;
    3.98 +	}
    3.99 +
   3.100 +	TAILQ_FOREACH_SAFE(entry, head, entry, tmp_entry) {
   3.101 +		TAILQ_REMOVE(head, entry, entry);
   3.102 +		free_macro_entry(entry);
   3.103 +	}
   3.104 +	free(head);
   3.105 +}
   3.106 +
   3.107 +enum macro_err
   3.108 +macro_parse(const char *name, size_t tokenc, union tok **tokenv,
   3.109 +    struct macro_head *head, struct macro_entry **macro_entryp)
   3.110 +{
   3.111 +	const char	*p = name;
   3.112 +	int		argc;
   3.113 +	char		**argv;
   3.114 +	struct macro_entry *entry;
   3.115 +
   3.116 +	/* validate macro name */
   3.117 +	for (p = name; *p != '\0'; p++) {
   3.118 +		/*
   3.119 +		 * macro names must contain only ASCII alphanumeric characters
   3.120 +		 * and underscores
   3.121 +		 */
   3.122 +		if (!isascii(*p) || (!isalnum(*p) && (*p != '_'))) {
   3.123 +			return (MACRO_ERR_INVALID_NAME);
   3.124 +		}
   3.125 +	}
   3.126 +
   3.127 +	/* expand macros */
   3.128 +	if (macro_expand_macros(head, tokenc, tokenv, &argc, &argv) != 0) {
   3.129 +		return (MACRO_ERR_UNDEFINED_MACRO);
   3.130 +	}
   3.131 +
   3.132 +	/* create macro */
   3.133 +	entry = xmalloc(sizeof (struct macro_entry));
   3.134 +	entry->name = xstrdup(name);
   3.135 +	entry->argc = argc;
   3.136 +	entry->argv = argv;
   3.137 +	*macro_entryp = entry;
   3.138 +
   3.139 +	return (MACRO_ERR_OK);
   3.140 +}
   3.141 +
   3.142 +void
   3.143 +macro_add(struct macro_head *head, struct macro_entry *entry)
   3.144 +{
   3.145 +	struct macro_entry *old_entry;
   3.146 +
   3.147 +	/* remove existing macro with the same name */
   3.148 +	old_entry = match_macro(head, entry->name);
   3.149 +	if (old_entry != NULL) {
   3.150 +		TAILQ_REMOVE(head, old_entry, entry);
   3.151 +		free_macro_entry(old_entry);
   3.152 +	}
   3.153 +
   3.154 +	TAILQ_INSERT_TAIL(head, entry, entry);
   3.155 +}
   3.156 +
   3.157 +int
   3.158 +macro_expand_macros(struct macro_head *head, size_t tokenc, union tok **tokenv,
   3.159 +    int *argcp, char ***argvp)
   3.160 +{
   3.161 +	int		retval = -1;
   3.162 +	char		**argv = NULL;
   3.163 +	int		argc = 0;
   3.164 +	size_t		argv_size = tokenc + 1;
   3.165 +	size_t		i;
   3.166 +	struct macro_entry *entry;
   3.167 +	int		j;
   3.168 +
   3.169 +	argv = xmalloc(argv_size * sizeof (char *));
   3.170 +	argv[0] = NULL;
   3.171 +
   3.172 +	for (i = 0; i < tokenc; i++) {
   3.173 +		switch (tokenv[i]->any.type) {
   3.174 +		case TOK_ARG:
   3.175 +			argv[argc++] = xstrdup(tokenv[i]->arg.value);
   3.176 +			argv[argc] = NULL;
   3.177 +			break;
   3.178 +		case TOK_MACRO:
   3.179 +			entry = match_macro(head, tokenv[i]->macro.name);
   3.180 +			if (entry == NULL) {
   3.181 +				goto out;
   3.182 +			}
   3.183 +			argv_size += entry->argc;
   3.184 +			argv = xrealloc(argv, argv_size * sizeof (char *));
   3.185 +			for (j = 0; j < entry->argc; j++) {
   3.186 +				argv[argc++] = xstrdup(entry->argv[j]);
   3.187 +				argv[argc] = NULL;
   3.188 +			}
   3.189 +			break;
   3.190 +		}
   3.191 +	}
   3.192 +	*argvp = argv;
   3.193 +	*argcp = argc;
   3.194 +	retval = 0;
   3.195 +
   3.196 +out:
   3.197 +	if (retval != 0) {
   3.198 +		for (j = 0; j < argc; j++) {
   3.199 +			free(argv[j]);
   3.200 +		}
   3.201 +		free(argv);
   3.202 +	}
   3.203 +
   3.204 +	return (retval);
   3.205 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/macro.h	Mon Sep 25 21:21:25 2017 +0200
     4.3 @@ -0,0 +1,47 @@
     4.4 +/*
     4.5 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     4.6 + *
     4.7 + * Permission is hereby granted, free of charge, to any person obtaining
     4.8 + * a copy of this software and associated documentation files (the
     4.9 + * "Software"), to deal in the Software without restriction, including
    4.10 + * without limitation the rights to use, copy, modify, merge, publish,
    4.11 + * distribute, sublicense, and/or sell copies of the Software, and to
    4.12 + * permit persons to whom the Software is furnished to do so, subject to
    4.13 + * the following conditions:
    4.14 + *
    4.15 + * The above copyright notice and this permission notice shall be included
    4.16 + * in all copies or substantial portions of the Software.
    4.17 + *
    4.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    4.19 + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    4.20 + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    4.21 + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    4.22 + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    4.23 + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    4.24 + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    4.25 + */
    4.26 +
    4.27 +#ifndef	MACRO_H
    4.28 +#define	MACRO_H
    4.29 +
    4.30 +#include "tok.h"
    4.31 +
    4.32 +enum macro_err {
    4.33 +	MACRO_ERR_OK,
    4.34 +	MACRO_ERR_INVALID_NAME = -1,
    4.35 +	MACRO_ERR_UNDEFINED_MACRO = -2,
    4.36 +	MACRO_ERR_RECURSIVE = -3
    4.37 +};
    4.38 +
    4.39 +struct macro_head;
    4.40 +struct macro_entry;
    4.41 +
    4.42 +void		macro_init(struct macro_head **);
    4.43 +void		macro_destroy(struct macro_head *);
    4.44 +enum macro_err	macro_parse(const char *, size_t, union tok **,
    4.45 +    struct macro_head *, struct macro_entry **);
    4.46 +void		macro_add(struct macro_head *, struct macro_entry *);
    4.47 +int		macro_expand_macros(struct macro_head *, size_t, union tok **,
    4.48 +    int *, char ***);
    4.49 +
    4.50 +#endif /* !MACRO_H */
     5.1 --- a/pwm.1.xml	Thu Sep 21 09:45:59 2017 +0200
     5.2 +++ b/pwm.1.xml	Mon Sep 25 21:21:25 2017 +0200
     5.3 @@ -34,7 +34,7 @@
     5.4        <email>guido+pwm@berhoerster.name</email>
     5.5        <personblurb/>
     5.6      </author>
     5.7 -    <date>17 September, 2017</date>
     5.8 +    <date>25 September, 2017</date>
     5.9    </info>
    5.10    <refmeta>
    5.11      <refentrytitle>pwm</refentrytitle>
    5.12 @@ -482,6 +482,34 @@
    5.13            </listitem>
    5.14          </varlistentry>
    5.15          <varlistentry>
    5.16 +          <term>Define macro</term>
    5.17 +          <listitem>
    5.18 +            <cmdsynopsis>
    5.19 +              <command>define</command>
    5.20 +              <arg choice="plain">
    5.21 +                <replaceable>name</replaceable>=<replaceable>value</replaceable>
    5.22 +              </arg>
    5.23 +            </cmdsynopsis>
    5.24 +            <cmdsynopsis>
    5.25 +              <command>D</command>
    5.26 +              <arg choice="plain">
    5.27 +                <replaceable>name</replaceable>=<replaceable>value</replaceable>
    5.28 +              </arg>
    5.29 +              <sbr/>
    5.30 +            </cmdsynopsis>
    5.31 +            <para>Define the macro <replaceable>name</replaceable> to expand to
    5.32 +            <replaceable>value</replaceable>.  A Macro is a shorthand term for
    5.33 +            a command and/or command arguments.  Macros are tokenized when they
    5.34 +            are defined, thus if a macro contains other macros, these other
    5.35 +            macros are expanded once at the time the macro is defined and not
    5.36 +            each time a macro is expanded later.  A macro can be used as like a
    5.37 +            command or as part of the command's argument by prefixing the macro
    5.38 +            name by a &quot;$&quot;. It is expanded by substituting the
    5.39 +            previously tokenized contents before the command is
    5.40 +            evaluated.</para>
    5.41 +          </listitem>
    5.42 +        </varlistentry>
    5.43 +        <varlistentry>
    5.44            <term>Display help text</term>
    5.45            <listitem>
    5.46              <cmdsynopsis>
     6.1 --- a/pwm.c	Thu Sep 21 09:45:59 2017 +0200
     6.2 +++ b/pwm.c	Mon Sep 25 21:21:25 2017 +0200
     6.3 @@ -42,6 +42,7 @@
     6.4  #include "pwm.h"
     6.5  #include "cmd.h"
     6.6  #include "io.h"
     6.7 +#include "macro.h"
     6.8  #include "pwfile.h"
     6.9  #include "tok.h"
    6.10  #include "util.h"
    6.11 @@ -108,6 +109,8 @@
    6.12  	GetLine		*gl = NULL;
    6.13  	char		buf[PWM_LINE_MAX + 1];
    6.14  	int		io_retval;
    6.15 +	size_t		tokenc = 0;
    6.16 +	union tok	**tokenv = NULL;
    6.17  	int		argc = 0;
    6.18  	char		**argv = NULL;
    6.19  	struct cmd	*cmd;
    6.20 @@ -159,9 +162,7 @@
    6.21  		}
    6.22  
    6.23  		/* tokenize line */
    6.24 -		switch (tok_tokenize(buf, &argc, &argv)) {
    6.25 -		case TOK_ERR_SYSTEM_ERROR:
    6.26 -			err(1, "tok_tokenize");
    6.27 +		switch (tok_tokenize(buf, &tokenc, &tokenv)) {
    6.28  		case TOK_ERR_UNTERMINATED_QUOTE:
    6.29  			fprintf(stderr, "unterminated quote\n");
    6.30  			if (!ctx->is_interactive) {
    6.31 @@ -174,6 +175,22 @@
    6.32  				goto out;
    6.33  			}
    6.34  			goto next;
    6.35 +		case TOK_ERR_INVALID_MACRO_NAME:
    6.36 +			fprintf(stderr, "invalid macro name\n");
    6.37 +			if (!ctx->is_interactive) {
    6.38 +				goto out;
    6.39 +			}
    6.40 +			goto next;
    6.41 +		}
    6.42 +
    6.43 +		/* expand macros */
    6.44 +		if (macro_expand_macros(ctx->macro_head, tokenc, tokenv, &argc,
    6.45 +		    &argv) != 0) {
    6.46 +			fprintf(stderr, "undefined macro\n");
    6.47 +			if (!ctx->is_interactive) {
    6.48 +				goto out;
    6.49 +			}
    6.50 +			goto next;
    6.51  		}
    6.52  
    6.53  		if (argc == 0) {
    6.54 @@ -216,6 +233,9 @@
    6.55  		free(argv);
    6.56  		argc = 0;
    6.57  		argv = NULL;
    6.58 +		tok_free(tokenv);
    6.59 +		tokenc = 0;
    6.60 +		tokenv = NULL;
    6.61  	}
    6.62  
    6.63  quit:
    6.64 @@ -226,6 +246,7 @@
    6.65  		free(argv[i]);
    6.66  	}
    6.67  	free(argv);
    6.68 +	tok_free(tokenv);
    6.69  	del_GetLine(gl);
    6.70  
    6.71  	return (retval);
    6.72 @@ -397,6 +418,7 @@
    6.73  	}
    6.74  
    6.75  	pwfile_init(&ctx);
    6.76 +	macro_init(&ctx.macro_head);
    6.77  
    6.78  	fp = fopen(ctx.filename, "r");
    6.79  	if ((fp == NULL) && (errno != ENOENT)) {
    6.80 @@ -424,6 +446,7 @@
    6.81  	status = (run_input_loop(&ctx) != 0);
    6.82  
    6.83  out:
    6.84 +	macro_destroy(ctx.macro_head);
    6.85  	pwfile_destroy(&ctx);
    6.86  	if (fp != NULL) {
    6.87  		fclose(fp);
     7.1 --- a/pwm.h	Thu Sep 21 09:45:59 2017 +0200
     7.2 +++ b/pwm.h	Mon Sep 25 21:21:25 2017 +0200
     7.3 @@ -55,6 +55,7 @@
     7.4  	int		unsaved_changes;
     7.5  	unsigned int	next_id;
     7.6  	char		password[PWS3_MAX_PASSWORD_LEN + 1];
     7.7 +	struct macro_head *macro_head;
     7.8  };
     7.9  
    7.10  void	pwm_err(struct pwm_ctx *, char *, ...);
     8.1 --- a/tok.c	Thu Sep 21 09:45:59 2017 +0200
     8.2 +++ b/tok.c	Mon Sep 25 21:21:25 2017 +0200
     8.3 @@ -1,5 +1,5 @@
     8.4  /*
     8.5 - * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
     8.6 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     8.7   *
     8.8   * Permission is hereby granted, free of charge, to any person obtaining
     8.9   * a copy of this software and associated documentation files (the
    8.10 @@ -23,22 +23,24 @@
    8.11  
    8.12  #include "compat.h"
    8.13  
    8.14 -#include <errno.h>
    8.15 +#include <ctype.h>
    8.16  #include <stdio.h>
    8.17  #include <stdlib.h>
    8.18  #include <string.h>
    8.19  
    8.20  #include "tok.h"
    8.21 +#include "util.h"
    8.22  
    8.23  enum tok_states {
    8.24  	STATE_INITIAL,
    8.25  	STATE_IN_WORD,
    8.26  	STATE_IN_QUOTE,
    8.27  	STATE_IN_WORD_ESCAPE,
    8.28 -	STATE_IN_QUOTE_ESCAPE
    8.29 +	STATE_IN_QUOTE_ESCAPE,
    8.30 +	STATE_IN_MACRO
    8.31  };
    8.32  
    8.33 -static inline int
    8.34 +static inline void
    8.35  strbuf_appendc(char **bufp, size_t *buf_sizep, int c)
    8.36  {
    8.37  	char	*buf = *bufp;
    8.38 @@ -50,10 +52,7 @@
    8.39  	/* allocate buffer if *bufp is NULL and *buf_sizep is 0 */
    8.40  	if (buf_size < len + (c >= 0) + 1) {
    8.41  		buf_size = (buf_size * 2 > BUFSIZ) ? buf_size * 2 : BUFSIZ;
    8.42 -		buf = realloc(buf, buf_size);
    8.43 -		if (buf == NULL) {
    8.44 -			return (-1);
    8.45 -		}
    8.46 +		buf = xrealloc(buf, buf_size);
    8.47  	}
    8.48  
    8.49  	/* append character to string buffer or reset buffer if c is -1 */
    8.50 @@ -64,35 +63,48 @@
    8.51  
    8.52  	*bufp = buf;
    8.53  	*buf_sizep = buf_size;
    8.54 +}
    8.55  
    8.56 -	return (0);
    8.57 +void
    8.58 +tok_free(union tok **tokenv)
    8.59 +{
    8.60 +	size_t	i;
    8.61 +
    8.62 +	if (tokenv == NULL) {
    8.63 +		return;
    8.64 +	}
    8.65 +
    8.66 +	for (i = 0; tokenv[i] != NULL; i++) {
    8.67 +		switch (tokenv[i]->any.type) {
    8.68 +		case TOK_MACRO:
    8.69 +			free(tokenv[i]->macro.name);
    8.70 +			break;
    8.71 +		case TOK_ARG:
    8.72 +			free(tokenv[i]->arg.value);
    8.73 +			break;
    8.74 +		}
    8.75 +		free(tokenv[i]);
    8.76 +	}
    8.77 +	free(tokenv);
    8.78  }
    8.79  
    8.80  enum tok_err
    8.81 -tok_tokenize(const char *s, int *tokencp, char ***tokenvp)
    8.82 +tok_tokenize(const char *s, size_t *tokencp, union tok ***tokenvp)
    8.83  {
    8.84 -	int		retval = TOK_ERR_SYSTEM_ERROR;
    8.85 -	int		saved_errno = 0;
    8.86 -	char		**tokenv;
    8.87 +	int		retval = TOK_ERR_OK;
    8.88 +	union tok	**tokenv;
    8.89  	size_t		tokenc = 0;
    8.90  	const char	*p = s;
    8.91  	enum tok_states	state = STATE_INITIAL;
    8.92  	char		quote;
    8.93  	char		*buf = NULL;
    8.94  	size_t		buf_size = 0;
    8.95 -	char		*token;
    8.96 -	size_t		i;
    8.97 +	char		*value;
    8.98 +	char		*name;
    8.99  
   8.100 -	/*
   8.101 -	 * allocate maximum number of tokens including the terminating NULL
   8.102 -	 * pointer: ceil(length / 2) + 1
   8.103 -	 */
   8.104 -	tokenv = malloc(((strlen(s) + 2 - 1) / 2 + 1) * sizeof (char *));
   8.105 -	if (tokenv == NULL) {
   8.106 -		saved_errno = errno;
   8.107 -		goto out;
   8.108 -	}
   8.109 -	tokenv[0] = NULL;
   8.110 +	/* allocate maximum number of tokens: ceil(length / 2) */
   8.111 +	tokenv = xmalloc((((strlen(s) + 2 - 1) / 2) + 1) *
   8.112 +	    sizeof (union tok *));
   8.113  
   8.114  	for (;;) {
   8.115  		switch (state) {
   8.116 @@ -108,34 +120,27 @@
   8.117  				/* start quoted part of token */
   8.118  				state = STATE_IN_QUOTE;
   8.119  				quote = *p;
   8.120 -				if (strbuf_appendc(&buf, &buf_size, -1) != 0) {
   8.121 -					saved_errno = errno;
   8.122 -					goto out;
   8.123 -				}
   8.124 +				strbuf_appendc(&buf, &buf_size, -1);
   8.125  				break;
   8.126  			case '\\':
   8.127  				/* start token with a backslash escape */
   8.128  				state = STATE_IN_WORD_ESCAPE;
   8.129 -				if (strbuf_appendc(&buf, &buf_size, -1) != 0) {
   8.130 -					saved_errno = errno;
   8.131 -					goto out;
   8.132 -				}
   8.133 +				strbuf_appendc(&buf, &buf_size, -1);
   8.134 +				break;
   8.135 +			case '$':
   8.136 +				/* start macro token */
   8.137 +				state = STATE_IN_MACRO;
   8.138 +				strbuf_appendc(&buf, &buf_size, -1);
   8.139  				break;
   8.140  			case '\0':
   8.141  				/* end of input */
   8.142 -				retval = 0;
   8.143 +				retval = TOK_ERR_OK;
   8.144  				goto out;
   8.145  			default:
   8.146  				/* start token with a word */
   8.147  				state = STATE_IN_WORD;
   8.148 -				if (strbuf_appendc(&buf, &buf_size, -1) != 0) {
   8.149 -					saved_errno = errno;
   8.150 -					goto out;
   8.151 -				}
   8.152 -				if (strbuf_appendc(&buf, &buf_size, *p) != 0) {
   8.153 -					saved_errno = errno;
   8.154 -					goto out;
   8.155 -				}
   8.156 +				strbuf_appendc(&buf, &buf_size, -1);
   8.157 +				strbuf_appendc(&buf, &buf_size, *p);
   8.158  			}
   8.159  			break;
   8.160  		case STATE_IN_WORD:
   8.161 @@ -145,15 +150,14 @@
   8.162  			case '\n':	/* FALLTHROUGH */
   8.163  			case '\0':
   8.164  				/* end of token */
   8.165 -				token = strdup(buf);
   8.166 -				if (token == NULL) {
   8.167 -					saved_errno = errno;
   8.168 -					goto out;
   8.169 -				}
   8.170 -				tokenv[tokenc++] = token;
   8.171 -				tokenv[tokenc] = NULL;
   8.172 +				value = xstrdup(buf);
   8.173 +				tokenv[tokenc] = xmalloc(sizeof (union tok));
   8.174 +				tokenv[tokenc]->arg.type = TOK_ARG;
   8.175 +				tokenv[tokenc]->arg.value = value;
   8.176 +				tokenc++;
   8.177 +
   8.178  				if (*p == '\0') {
   8.179 -					retval = 0;
   8.180 +					retval = TOK_ERR_OK;
   8.181  					goto out;
   8.182  				}
   8.183  				state = STATE_INITIAL;
   8.184 @@ -170,10 +174,7 @@
   8.185  				break;
   8.186  			default:
   8.187  				/* regular character */
   8.188 -				if (strbuf_appendc(&buf, &buf_size, *p) != 0) {
   8.189 -					saved_errno = errno;
   8.190 -					goto out;
   8.191 -				}
   8.192 +				strbuf_appendc(&buf, &buf_size, *p);
   8.193  			}
   8.194  			break;
   8.195  		case STATE_IN_QUOTE:
   8.196 @@ -185,11 +186,7 @@
   8.197  					state = STATE_IN_WORD;
   8.198  				} else {
   8.199  					/* quote quote character */
   8.200 -					if (strbuf_appendc(&buf, &buf_size,
   8.201 -					    *p) != 0) {
   8.202 -						saved_errno = errno;
   8.203 -						goto out;
   8.204 -					}
   8.205 +					strbuf_appendc(&buf, &buf_size, *p);
   8.206  				}
   8.207  				break;
   8.208  			case '\\':
   8.209 @@ -202,10 +199,7 @@
   8.210  				goto out;
   8.211  			default:
   8.212  				/* regular character */
   8.213 -				if (strbuf_appendc(&buf, &buf_size, *p) != 0) {
   8.214 -					saved_errno = errno;
   8.215 -					goto out;
   8.216 -				}
   8.217 +				strbuf_appendc(&buf, &buf_size, *p);
   8.218  			}
   8.219  			break;
   8.220  		case STATE_IN_WORD_ESCAPE:	/* FALLTHROUGH */
   8.221 @@ -218,9 +212,38 @@
   8.222  			/* escaped character */
   8.223  			state = (state == STATE_IN_WORD_ESCAPE) ?
   8.224  			    STATE_IN_WORD : STATE_IN_QUOTE;
   8.225 -			if (strbuf_appendc(&buf, &buf_size, *p) != 0) {
   8.226 -				saved_errno = errno;
   8.227 -				goto out;
   8.228 +			strbuf_appendc(&buf, &buf_size, *p);
   8.229 +			break;
   8.230 +		case STATE_IN_MACRO:
   8.231 +			switch (*p) {
   8.232 +			case ' ':	/* FALLTHROUGH */
   8.233 +			case '\t':	/* FALLTHROUGH */
   8.234 +			case '\n':	/* FALLTHROUGH */
   8.235 +			case '\0':
   8.236 +				/* end of token */
   8.237 +				name = xstrdup(buf);
   8.238 +				tokenv[tokenc] = xmalloc(sizeof (union tok));
   8.239 +				tokenv[tokenc]->macro.type = TOK_MACRO;
   8.240 +				tokenv[tokenc]->macro.name = name;
   8.241 +				tokenc++;
   8.242 +
   8.243 +				if (*p == '\0') {
   8.244 +					retval = TOK_ERR_OK;
   8.245 +					goto out;
   8.246 +				}
   8.247 +				state = STATE_INITIAL;
   8.248 +				break;
   8.249 +			default:
   8.250 +				/*
   8.251 +				 * macro names must only contain alphanumeric
   8.252 +				 * characters and underscores
   8.253 +				 */
   8.254 +				if (!isascii(*p) || (!isalnum(*p) &&
   8.255 +				    (*p != '_'))) {
   8.256 +					retval = TOK_ERR_INVALID_MACRO_NAME;
   8.257 +					goto out;
   8.258 +				}
   8.259 +				strbuf_appendc(&buf, &buf_size, *p);
   8.260  			}
   8.261  			break;
   8.262  		}
   8.263 @@ -229,18 +252,14 @@
   8.264  
   8.265  out:
   8.266  	if (retval < 0) {
   8.267 -		for (i = 0; i < tokenc; i++) {
   8.268 -			free(tokenv[i]);
   8.269 -		}
   8.270 -		free(tokenv);
   8.271 +		tok_free(tokenv);
   8.272  	} else {
   8.273 +		tokenv[tokenc] = NULL;
   8.274  		*tokencp = tokenc;
   8.275 -		*tokenvp = realloc(tokenv, (tokenc + 1) * sizeof (char *));
   8.276 +		*tokenvp = xrealloc(tokenv, (tokenc + 1) *
   8.277 +		    sizeof (union tok *));
   8.278  	}
   8.279  	free(buf);
   8.280 -	if (retval < 0) {
   8.281 -		errno = saved_errno;
   8.282 -	}
   8.283  
   8.284  	return (retval);
   8.285  }
     9.1 --- a/tok.h	Thu Sep 21 09:45:59 2017 +0200
     9.2 +++ b/tok.h	Mon Sep 25 21:21:25 2017 +0200
     9.3 @@ -1,5 +1,5 @@
     9.4  /*
     9.5 - * Copyright (C) 2016 Guido Berhoerster <guido+pwm@berhoerster.name>
     9.6 + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
     9.7   *
     9.8   * Permission is hereby granted, free of charge, to any person obtaining
     9.9   * a copy of this software and associated documentation files (the
    9.10 @@ -25,11 +25,38 @@
    9.11  #define	TOK_H
    9.12  
    9.13  enum tok_err {
    9.14 -	TOK_ERR_SYSTEM_ERROR = -1,
    9.15 -	TOK_ERR_UNTERMINATED_QUOTE = -2,
    9.16 -	TOK_ERR_TRAILING_BACKSLASH = -3
    9.17 +	TOK_ERR_OK,
    9.18 +	TOK_ERR_UNTERMINATED_QUOTE = -1,
    9.19 +	TOK_ERR_TRAILING_BACKSLASH = -2,
    9.20 +	TOK_ERR_INVALID_MACRO_NAME = -3
    9.21  };
    9.22  
    9.23 -enum tok_err	tok_tokenize(const char *, int *, char ***);
    9.24 +enum tok_type {
    9.25 +	TOK_ARG,
    9.26 +	TOK_MACRO
    9.27 +};
    9.28 +
    9.29 +struct tok_any {
    9.30 +	enum tok_type	type;
    9.31 +};
    9.32 +
    9.33 +struct tok_arg {
    9.34 +	enum tok_type	type;
    9.35 +	char		*value;
    9.36 +};
    9.37 +
    9.38 +struct tok_macro {
    9.39 +	enum tok_type	type;
    9.40 +	char		*name;
    9.41 +};
    9.42 +
    9.43 +union tok {
    9.44 +	struct tok_any	any;
    9.45 +	struct tok_arg	arg;
    9.46 +	struct tok_macro macro;
    9.47 +};
    9.48 +
    9.49 +void		tok_free(union tok **);
    9.50 +enum tok_err	tok_tokenize(const char *, size_t *, union tok ***);
    9.51  
    9.52  #endif /* !TOK_H */