tokenizer.c 9.39 KB
Newer Older
unknown's avatar
unknown committed
1
/*	$NetBSD: tokenizer.c,v 1.14 2003/12/05 13:37:48 lukem Exp $	*/
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

/*-
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Christos Zoulas of Cornell University.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
unknown's avatar
unknown committed
18
 * 3. Neither the name of the University nor the names of its contributors
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

unknown's avatar
unknown committed
35
#include <config.h>
36 37 38 39 40 41

/*
 * tokenize.c: Bourne shell like tokenizer
 */
#include <string.h>
#include <stdlib.h>
unknown's avatar
unknown committed
42
#include "histedit.h"
43 44 45 46 47 48 49 50 51 52 53 54 55

typedef enum {
	Q_none, Q_single, Q_double, Q_one, Q_doubleone
} quote_t;

#define	IFS		"\t \n"

#define	TOK_KEEP	1
#define	TOK_EAT		2

#define	WINCR		20
#define	AINCR		10

unknown's avatar
unknown committed
56
#define	tok_strdup(a)		strdup(a)
57 58 59 60 61 62 63 64
#define	tok_malloc(a)		malloc(a)
#define	tok_free(a)		free(a)
#define	tok_realloc(a, b)	realloc(a, b)


struct tokenizer {
	char	*ifs;		/* In field separator			 */
	int	 argc, amax;	/* Current and maximum number of args	 */
unknown's avatar
unknown committed
65
	char   **argv;		/* Argument list			 */
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	char	*wptr, *wmax;	/* Space and limit on the word buffer	 */
	char	*wstart;	/* Beginning of next word		 */
	char	*wspace;	/* Space of word buffer			 */
	quote_t	 quote;		/* Quoting state			 */
	int	 flags;		/* flags;				 */
};


private void tok_finish(Tokenizer *);


/* tok_finish():
 *	Finish a word in the tokenizer.
 */
private void
tok_finish(Tokenizer *tok)
{

	*tok->wptr = '\0';
	if ((tok->flags & TOK_KEEP) || tok->wptr != tok->wstart) {
		tok->argv[tok->argc++] = tok->wstart;
		tok->argv[tok->argc] = NULL;
		tok->wstart = ++tok->wptr;
	}
	tok->flags &= ~TOK_KEEP;
}


/* tok_init():
 *	Initialize the tokenizer
 */
public Tokenizer *
tok_init(const char *ifs)
{
	Tokenizer *tok = (Tokenizer *) tok_malloc(sizeof(Tokenizer));

unknown's avatar
unknown committed
102 103
	if (tok == NULL)
		return NULL;
unknown's avatar
unknown committed
104
	tok->ifs = tok_strdup(ifs ? ifs : IFS);
unknown's avatar
unknown committed
105 106 107 108
	if (tok->ifs == NULL) {
		tok_free((ptr_t)tok);
		return NULL;
	}
109 110
	tok->argc = 0;
	tok->amax = AINCR;
unknown's avatar
unknown committed
111 112 113 114 115 116
	tok->argv = (char **) tok_malloc(sizeof(char *) * tok->amax);
	if (tok->argv == NULL) {
		tok_free((ptr_t)tok->ifs);
		tok_free((ptr_t)tok);
		return NULL;
	}
117 118
	tok->argv[0] = NULL;
	tok->wspace = (char *) tok_malloc(WINCR);
unknown's avatar
unknown committed
119 120 121 122 123 124
	if (tok->wspace == NULL) {
		tok_free((ptr_t)tok->argv);
		tok_free((ptr_t)tok->ifs);
		tok_free((ptr_t)tok);
		return NULL;
	}
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
	tok->wmax = tok->wspace + WINCR;
	tok->wstart = tok->wspace;
	tok->wptr = tok->wspace;
	tok->flags = 0;
	tok->quote = Q_none;

	return (tok);
}


/* tok_reset():
 *	Reset the tokenizer
 */
public void
tok_reset(Tokenizer *tok)
{

	tok->argc = 0;
	tok->wstart = tok->wspace;
	tok->wptr = tok->wspace;
	tok->flags = 0;
	tok->quote = Q_none;
}


/* tok_end():
 *	Clean up
 */
public void
tok_end(Tokenizer *tok)
{

	tok_free((ptr_t) tok->ifs);
	tok_free((ptr_t) tok->wspace);
	tok_free((ptr_t) tok->argv);
	tok_free((ptr_t) tok);
}



/* tok_line():
unknown's avatar
unknown committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
 *	Bourne shell (sh(1)) like tokenizing
 *	Arguments:
 *		tok	current tokenizer state (setup with tok_init())
 *		line	line to parse
 *	Returns:
 *		-1	Internal error
 *		 3	Quoted return
 *		 2	Unmatched double quote
 *		 1	Unmatched single quote
 *		 0	Ok
 *	Modifies (if return value is 0):
 *		argc	number of arguments
 *		argv	argument array
 *		cursorc	if !NULL, argv element containing cursor
 *		cursorv	if !NULL, offset in argv[cursorc] of cursor
181 182
 */
public int
unknown's avatar
unknown committed
183 184
tok_line(Tokenizer *tok, const LineInfo *line,
    int *argc, const char ***argv, int *cursorc, int *cursoro)
185 186
{
	const char *ptr;
unknown's avatar
unknown committed
187 188 189 190 191 192 193 194 195 196 197 198
	int cc, co;

	cc = co = -1;
	ptr = line->buffer;
	for (ptr = line->buffer; ;ptr++) {
		if (ptr >= line->lastchar)
			ptr = "";
		if (ptr == line->cursor) {
			cc = tok->argc;
			co = tok->wptr - tok->wstart;
		}
		switch (*ptr) {
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
		case '\'':
			tok->flags |= TOK_KEEP;
			tok->flags &= ~TOK_EAT;
			switch (tok->quote) {
			case Q_none:
				tok->quote = Q_single;	/* Enter single quote
							 * mode */
				break;

			case Q_single:	/* Exit single quote mode */
				tok->quote = Q_none;
				break;

			case Q_one:	/* Quote this ' */
				tok->quote = Q_none;
				*tok->wptr++ = *ptr;
				break;

			case Q_double:	/* Stay in double quote mode */
				*tok->wptr++ = *ptr;
				break;

			case Q_doubleone:	/* Quote this ' */
				tok->quote = Q_double;
				*tok->wptr++ = *ptr;
				break;

			default:
				return (-1);
			}
			break;

		case '"':
			tok->flags &= ~TOK_EAT;
			tok->flags |= TOK_KEEP;
			switch (tok->quote) {
			case Q_none:	/* Enter double quote mode */
				tok->quote = Q_double;
				break;

			case Q_double:	/* Exit double quote mode */
				tok->quote = Q_none;
				break;

			case Q_one:	/* Quote this " */
				tok->quote = Q_none;
				*tok->wptr++ = *ptr;
				break;

			case Q_single:	/* Stay in single quote mode */
				*tok->wptr++ = *ptr;
				break;

			case Q_doubleone:	/* Quote this " */
				tok->quote = Q_double;
				*tok->wptr++ = *ptr;
				break;

			default:
				return (-1);
			}
			break;

		case '\\':
			tok->flags |= TOK_KEEP;
			tok->flags &= ~TOK_EAT;
			switch (tok->quote) {
			case Q_none:	/* Quote next character */
				tok->quote = Q_one;
				break;

			case Q_double:	/* Quote next character */
				tok->quote = Q_doubleone;
				break;

			case Q_one:	/* Quote this, restore state */
				*tok->wptr++ = *ptr;
				tok->quote = Q_none;
				break;

			case Q_single:	/* Stay in single quote mode */
				*tok->wptr++ = *ptr;
				break;

			case Q_doubleone:	/* Quote this \ */
				tok->quote = Q_double;
				*tok->wptr++ = *ptr;
				break;

			default:
				return (-1);
			}
			break;

		case '\n':
			tok->flags &= ~TOK_EAT;
			switch (tok->quote) {
			case Q_none:
unknown's avatar
unknown committed
297
				goto tok_line_outok;
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

			case Q_single:
			case Q_double:
				*tok->wptr++ = *ptr;	/* Add the return */
				break;

			case Q_doubleone:   /* Back to double, eat the '\n' */
				tok->flags |= TOK_EAT;
				tok->quote = Q_double;
				break;

			case Q_one:	/* No quote, more eat the '\n' */
				tok->flags |= TOK_EAT;
				tok->quote = Q_none;
				break;

			default:
				return (0);
			}
			break;

		case '\0':
			switch (tok->quote) {
			case Q_none:
				/* Finish word and return */
				if (tok->flags & TOK_EAT) {
					tok->flags &= ~TOK_EAT;
					return (3);
				}
unknown's avatar
unknown committed
327
				goto tok_line_outok;
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389

			case Q_single:
				return (1);

			case Q_double:
				return (2);

			case Q_doubleone:
				tok->quote = Q_double;
				*tok->wptr++ = *ptr;
				break;

			case Q_one:
				tok->quote = Q_none;
				*tok->wptr++ = *ptr;
				break;

			default:
				return (-1);
			}
			break;

		default:
			tok->flags &= ~TOK_EAT;
			switch (tok->quote) {
			case Q_none:
				if (strchr(tok->ifs, *ptr) != NULL)
					tok_finish(tok);
				else
					*tok->wptr++ = *ptr;
				break;

			case Q_single:
			case Q_double:
				*tok->wptr++ = *ptr;
				break;


			case Q_doubleone:
				*tok->wptr++ = '\\';
				tok->quote = Q_double;
				*tok->wptr++ = *ptr;
				break;

			case Q_one:
				tok->quote = Q_none;
				*tok->wptr++ = *ptr;
				break;

			default:
				return (-1);

			}
			break;
		}

		if (tok->wptr >= tok->wmax - 4) {
			size_t size = tok->wmax - tok->wspace + WINCR;
			char *s = (char *) tok_realloc(tok->wspace, size);
			if (s == NULL)
				return (-1);

unknown's avatar
unknown committed
390
			if (s != tok->wspace) {
391
				int i;
unknown's avatar
unknown committed
392 393 394 395 396 397
				for (i = 0; i < tok->argc; i++) {
				    tok->argv[i] =
					(tok->argv[i] - tok->wspace) + s;
				}
				tok->wptr = (tok->wptr - tok->wspace) + s;
				tok->wstart = (tok->wstart - tok->wspace) + s;
398 399
				tok->wspace = s;
			}
unknown's avatar
unknown committed
400
			tok->wmax = s + size;
401 402
		}
		if (tok->argc >= tok->amax - 4) {
unknown's avatar
unknown committed
403
			char **p;
404
			tok->amax += AINCR;
unknown's avatar
unknown committed
405
			p = (char **) tok_realloc(tok->argv,
406 407 408 409 410 411
			    tok->amax * sizeof(char *));
			if (p == NULL)
				return (-1);
			tok->argv = p;
		}
	}
unknown's avatar
unknown committed
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
 tok_line_outok:
	if (cc == -1 && co == -1) {
		cc = tok->argc;
		co = tok->wptr - tok->wstart;
	}
	if (cursorc != NULL)
		*cursorc = cc;
	if (cursoro != NULL)
		*cursoro = co;
	tok_finish(tok);
	*argv = (const char **)tok->argv;
	*argc = tok->argc;
	return (0);
}

/* tok_str():
 *	Simpler version of tok_line, taking a NUL terminated line
 *	and splitting into words, ignoring cursor state.
 */
public int
tok_str(Tokenizer *tok, const char *line, int *argc, const char ***argv)
{
	LineInfo li;

	memset(&li, 0, sizeof(li));
	li.buffer = line;
	li.cursor = li.lastchar = strchr(line, '\0');
	return (tok_line(tok, &li, argc, argv, NULL, NULL));
440
}