2311 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			2311 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/tcc -run -lm
 | |
| //
 | |
| // ZyklonB scripting plugin, using a custom stack-based language
 | |
| //
 | |
| // Copyright 2014 Přemysl Janouch.  All rights reserved.
 | |
| // See the file LICENSE for licensing information.
 | |
| //
 | |
| // Just compile this file as usual (sans #!) if you don't feel like using TCC.
 | |
| // It is a very basic and portable C99 application.  It's not supposed to be
 | |
| // very sophisticated, for it'd get extremely big.
 | |
| //
 | |
| // The main influences of the language were Factor and Joy, stripped of all
 | |
| // even barely complex stuff.  In its current state, it's only really useful as
 | |
| // a calculator but it's got great potential for extending.
 | |
| //
 | |
| // If you don't like something, just change it; this is just an experiment.
 | |
| //
 | |
| // NOTE: it is relatively easy to abuse.  Be careful.
 | |
| //
 | |
| 
 | |
| #define _XOPEN_SOURCE 500
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <errno.h>
 | |
| #include <stdarg.h>
 | |
| #include <assert.h>
 | |
| #include <time.h>
 | |
| #include <stdbool.h>
 | |
| #include <strings.h>
 | |
| #include <math.h>
 | |
| 
 | |
| #define ADDRESS_SPACE_LIMIT (100 * 1024 * 1024)
 | |
| #include <sys/resource.h>
 | |
| 
 | |
| #if defined __GNUC__
 | |
| #define ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y)))
 | |
| #else // ! __GNUC__
 | |
| #define ATTRIBUTE_PRINTF(x, y)
 | |
| #endif // ! __GNUC__
 | |
| 
 | |
| #define N_ELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
 | |
| 
 | |
| // --- Utilities ---------------------------------------------------------------
 | |
| 
 | |
| static char *strdup_printf (const char *format, ...) ATTRIBUTE_PRINTF (1, 2);
 | |
| 
 | |
| static char *
 | |
| strdup_vprintf (const char *format, va_list ap)
 | |
| {
 | |
| 	va_list aq;
 | |
| 	va_copy (aq, ap);
 | |
| 	int size = vsnprintf (NULL, 0, format, aq);
 | |
| 	va_end (aq);
 | |
| 	if (size < 0)
 | |
| 		return NULL;
 | |
| 
 | |
| 	char buf[size + 1];
 | |
| 	size = vsnprintf (buf, sizeof buf, format, ap);
 | |
| 	if (size < 0)
 | |
| 		return NULL;
 | |
| 
 | |
| 	return strdup (buf);
 | |
| }
 | |
| 
 | |
| static char *
 | |
| strdup_printf (const char *format, ...)
 | |
| {
 | |
| 	va_list ap;
 | |
| 	va_start (ap, format);
 | |
| 	char *result = strdup_vprintf (format, ap);
 | |
| 	va_end (ap);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| // --- Generic buffer ----------------------------------------------------------
 | |
| 
 | |
| struct buffer
 | |
| {
 | |
| 	char *s;                            ///< Buffer data
 | |
| 	size_t alloc;                       ///< Number of bytes allocated
 | |
| 	size_t len;                         ///< Number of bytes used
 | |
| 	bool memory_failure;                ///< Memory allocation failed
 | |
| };
 | |
| 
 | |
| #define BUFFER_INITIALIZER { NULL, 0, 0, false }
 | |
| 
 | |
| static bool
 | |
| buffer_append (struct buffer *self, const void *s, size_t n)
 | |
| {
 | |
| 	if (self->memory_failure)
 | |
| 		return false;
 | |
| 
 | |
| 	if (!self->s)
 | |
| 		self->s = malloc (self->alloc = 8);
 | |
| 	while (self->len + n > self->alloc)
 | |
| 		self->s = realloc (self->s, self->alloc <<= 1);
 | |
| 
 | |
| 	if (!self->s)
 | |
| 	{
 | |
| 		self->memory_failure = true;
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	memcpy (self->s + self->len, s, n);
 | |
| 	self->len += n;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| inline static bool
 | |
| buffer_append_c (struct buffer *self, char c)
 | |
| {
 | |
| 	return buffer_append (self, &c, 1);
 | |
| }
 | |
| 
 | |
| // --- Data types --------------------------------------------------------------
 | |
| 
 | |
| enum item_type
 | |
| {
 | |
| 	ITEM_STRING,
 | |
| 	ITEM_WORD,
 | |
| 	ITEM_INTEGER,
 | |
| 	ITEM_FLOAT,
 | |
| 	ITEM_LIST
 | |
| };
 | |
| 
 | |
| struct item
 | |
| {
 | |
| #define ITEM_HEADER                                                            \
 | |
| 	enum item_type type;                /**< The type of this object        */ \
 | |
| 	struct item *next;                  /**< Next item on the list/stack    */
 | |
| 
 | |
| 	ITEM_HEADER
 | |
| };
 | |
| 
 | |
| struct item_string
 | |
| {
 | |
| 	ITEM_HEADER
 | |
| 	size_t len;                         ///< Length of the string (sans '\0')
 | |
| 	char value[];                       ///< The null-terminated string value
 | |
| };
 | |
| 
 | |
| #define get_string(item)                                                       \
 | |
| 	(assert ((item)->type == ITEM_STRING),                                     \
 | |
| 	 ((struct item_string *)(item))->value)
 | |
| 
 | |
| /// It looks like a string but it doesn't quack like a string
 | |
| #define item_word item_string
 | |
| 
 | |
| #define get_word(item)                                                         \
 | |
| 	(assert ((item)->type == ITEM_WORD),                                       \
 | |
| 	 ((struct item_word *)(item))->value)
 | |
| 
 | |
| struct item_integer
 | |
| {
 | |
| 	ITEM_HEADER
 | |
| 	long long value;                    ///< The integer value
 | |
| };
 | |
| 
 | |
| #define get_integer(item)                                                      \
 | |
| 	(assert ((item)->type == ITEM_INTEGER),                                    \
 | |
| 	 ((struct item_integer *)(item))->value)
 | |
| 
 | |
| struct item_float
 | |
| {
 | |
| 	ITEM_HEADER
 | |
| 	long double value;                  ///< The floating point value
 | |
| };
 | |
| 
 | |
| #define get_float(item)                                                        \
 | |
| 	(assert ((item)->type == ITEM_FLOAT),                                      \
 | |
| 	 ((struct item_float *)(item))->value)
 | |
| 
 | |
| struct item_list
 | |
| {
 | |
| 	ITEM_HEADER
 | |
| 	struct item *head;                  ///< The head of the list
 | |
| };
 | |
| 
 | |
| #define get_list(item)                                                         \
 | |
| 	(assert ((item)->type == ITEM_LIST),                                       \
 | |
| 	 ((struct item_list *)(item))->head)
 | |
| 
 | |
| #define set_list(item, head_)                                                  \
 | |
| 	(assert ((item)->type == ITEM_LIST),                                       \
 | |
| 	 item_free_list (((struct item_list *)(item))->head),                      \
 | |
| 	 ((struct item_list *)(item))->head = (head_))
 | |
| 
 | |
| const char *
 | |
| item_type_to_str (enum item_type type)
 | |
| {
 | |
| 	switch (type)
 | |
| 	{
 | |
| 	case ITEM_STRING:   return "string";
 | |
| 	case ITEM_WORD:     return "word";
 | |
| 	case ITEM_INTEGER:  return "integer";
 | |
| 	case ITEM_FLOAT:    return "float";
 | |
| 	case ITEM_LIST:     return "list";
 | |
| 	}
 | |
| 	abort ();
 | |
| }
 | |
| 
 | |
| // --- Item management ---------------------------------------------------------
 | |
| 
 | |
| static void item_free_list (struct item *);
 | |
| static struct item *new_clone_list (const struct item *);
 | |
| 
 | |
| static void
 | |
| item_free (struct item *item)
 | |
| {
 | |
| 	if (item->type == ITEM_LIST)
 | |
| 		item_free_list (get_list (item));
 | |
| 	free (item);
 | |
| }
 | |
| 
 | |
| static void
 | |
| item_free_list (struct item *item)
 | |
| {
 | |
| 	while (item)
 | |
| 	{
 | |
| 		struct item *link = item;
 | |
| 		item = item->next;
 | |
| 		item_free (link);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_clone (const struct item *item)
 | |
| {
 | |
| 	size_t size;
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_STRING:
 | |
| 	case ITEM_WORD:
 | |
| 	{
 | |
| 		const struct item_string *x = (const struct item_string *) item;
 | |
| 		size = sizeof *x + x->len + 1;
 | |
| 		break;
 | |
| 	}
 | |
| 	case ITEM_INTEGER:  size = sizeof (struct item_integer);  break;
 | |
| 	case ITEM_FLOAT:    size = sizeof (struct item_float);    break;
 | |
| 	case ITEM_LIST:     size = sizeof (struct item_list);     break;
 | |
| 	}
 | |
| 
 | |
| 	struct item *clone = malloc (size);
 | |
| 	if (!clone)
 | |
| 		return NULL;
 | |
| 
 | |
| 	memcpy (clone, item, size);
 | |
| 	if (item->type == ITEM_LIST)
 | |
| 	{
 | |
| 		struct item_list *x = (struct item_list *) clone;
 | |
| 		if (x->head && !(x->head = new_clone_list (x->head)))
 | |
| 		{
 | |
| 			free (clone);
 | |
| 			return NULL;
 | |
| 		}
 | |
| 	}
 | |
| 	clone->next = NULL;
 | |
| 	return clone;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_clone_list (const struct item *item)
 | |
| {
 | |
| 	struct item *head = NULL, *clone;
 | |
| 	for (struct item **out = &head; item; item = item->next)
 | |
| 	{
 | |
| 		if (!(clone = *out = new_clone (item)))
 | |
| 		{
 | |
| 			item_free_list (head);
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		clone->next = NULL;
 | |
| 		out = &clone->next;
 | |
| 	}
 | |
| 	return head;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_string (const char *s, ssize_t len)
 | |
| {
 | |
| 	if (len < 0)
 | |
| 		len = strlen (s);
 | |
| 
 | |
| 	struct item_string *item = calloc (1, sizeof *item + len + 1);
 | |
| 	if (!item)
 | |
| 		return NULL;
 | |
| 
 | |
| 	item->type = ITEM_STRING;
 | |
| 	item->len = len;
 | |
| 	memcpy (item->value, s, len);
 | |
| 	item->value[len] = '\0';
 | |
| 	return (struct item *) item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_word (const char *s, ssize_t len)
 | |
| {
 | |
| 	struct item *item = new_string (s, len);
 | |
| 	if (!item)
 | |
| 		return NULL;
 | |
| 
 | |
| 	item->type = ITEM_WORD;
 | |
| 	return item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_integer (long long value)
 | |
| {
 | |
| 	struct item_integer *item = calloc (1, sizeof *item);
 | |
| 	if (!item)
 | |
| 		return NULL;
 | |
| 
 | |
| 	item->type = ITEM_INTEGER;
 | |
| 	item->value = value;
 | |
| 	return (struct item *) item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_float (long double value)
 | |
| {
 | |
| 	struct item_float *item = calloc (1, sizeof *item);
 | |
| 	if (!item)
 | |
| 		return NULL;
 | |
| 
 | |
| 	item->type = ITEM_FLOAT;
 | |
| 	item->value = value;
 | |
| 	return (struct item *) item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| new_list (struct item *head)
 | |
| {
 | |
| 	struct item_list *item = calloc (1, sizeof *item);
 | |
| 	if (!item)
 | |
| 		return NULL;
 | |
| 
 | |
| 	item->type = ITEM_LIST;
 | |
| 	item->head = head;
 | |
| 	return (struct item *) item;
 | |
| }
 | |
| 
 | |
| // --- Parsing -----------------------------------------------------------------
 | |
| 
 | |
| #define PARSE_ERROR_TABLE(XX)                                                  \
 | |
| 	XX( OK,                  NULL                                  )           \
 | |
| 	XX( EOF,                 "unexpected end of input"             )           \
 | |
| 	XX( INVALID_HEXA_ESCAPE, "invalid hexadecimal escape sequence" )           \
 | |
| 	XX( INVALID_ESCAPE,      "unrecognized escape sequence"        )           \
 | |
| 	XX( MEMORY,              "memory allocation failure"           )           \
 | |
| 	XX( FLOAT_RANGE,         "floating point value out of range"   )           \
 | |
| 	XX( INTEGER_RANGE,       "integer out of range"                )           \
 | |
| 	XX( INVALID_INPUT,       "invalid input"                       )           \
 | |
| 	XX( UNEXPECTED_INPUT,    "unexpected input"                    )
 | |
| 
 | |
| enum tokenizer_error
 | |
| {
 | |
| #define XX(x, y) PARSE_ERROR_ ## x,
 | |
| 	PARSE_ERROR_TABLE (XX)
 | |
| #undef XX
 | |
| 	PARSE_ERROR_COUNT
 | |
| };
 | |
| 
 | |
| struct tokenizer
 | |
| {
 | |
| 	const char *cursor;
 | |
| 	enum tokenizer_error error;
 | |
| };
 | |
| 
 | |
| static bool
 | |
| decode_hexa_escape (struct tokenizer *self, struct buffer *buf)
 | |
| {
 | |
| 	int i;
 | |
| 	char c, code = 0;
 | |
| 
 | |
| 	for (i = 0; i < 2; i++)
 | |
| 	{
 | |
| 		c = tolower (*self->cursor);
 | |
| 		if (c >= '0' && c <= '9')
 | |
| 			code = (code << 4) | (c - '0');
 | |
| 		else if (c >= 'a' && c <= 'f')
 | |
| 			code = (code << 4) | (c - 'a' + 10);
 | |
| 		else
 | |
| 			break;
 | |
| 
 | |
| 		self->cursor++;
 | |
| 	}
 | |
| 
 | |
| 	if (!i)
 | |
| 		return false;
 | |
| 
 | |
| 	buffer_append_c (buf, code);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| decode_octal_escape (struct tokenizer *self, struct buffer *buf)
 | |
| {
 | |
| 	int i;
 | |
| 	char c, code = 0;
 | |
| 
 | |
| 	for (i = 0; i < 3; i++)
 | |
| 	{
 | |
| 		c = *self->cursor;
 | |
| 		if (c < '0' || c > '7')
 | |
| 			break;
 | |
| 
 | |
| 		code = (code << 3) | (c - '0');
 | |
| 		self->cursor++;
 | |
| 	}
 | |
| 
 | |
| 	if (!i)
 | |
| 		return false;
 | |
| 
 | |
| 	buffer_append_c (buf, code);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| decode_escape_sequence (struct tokenizer *self, struct buffer *buf)
 | |
| {
 | |
| 	// Support some basic escape sequences from the C language
 | |
| 	char c;
 | |
| 	switch ((c = *self->cursor))
 | |
| 	{
 | |
| 	case '\0':
 | |
| 		self->error = PARSE_ERROR_EOF;
 | |
| 		return false;
 | |
| 	case 'x':
 | |
| 	case 'X':
 | |
| 		self->cursor++;
 | |
| 		if (decode_hexa_escape (self, buf))
 | |
| 			return true;
 | |
| 
 | |
| 		self->error = PARSE_ERROR_INVALID_HEXA_ESCAPE;
 | |
| 		return false;
 | |
| 	default:
 | |
| 		if (decode_octal_escape (self, buf))
 | |
| 			return true;
 | |
| 
 | |
| 		self->cursor++;
 | |
| 		const char *from = "abfnrtv\"\\", *to = "\a\b\f\n\r\t\v\"\\", *x;
 | |
| 		if ((x = strchr (from, c)))
 | |
| 		{
 | |
| 			buffer_append_c (buf, to[x - from]);
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		self->error = PARSE_ERROR_INVALID_ESCAPE;
 | |
| 		return false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| parse_string (struct tokenizer *self)
 | |
| {
 | |
| 	struct buffer buf = BUFFER_INITIALIZER;
 | |
| 	struct item *item = NULL;
 | |
| 	char c;
 | |
| 
 | |
| 	while (true)
 | |
| 	switch ((c = *self->cursor++))
 | |
| 	{
 | |
| 	case '\0':
 | |
| 		self->cursor--;
 | |
| 		self->error = PARSE_ERROR_EOF;
 | |
| 		goto end;
 | |
| 	case '"':
 | |
| 		if (buf.memory_failure
 | |
| 		 || !(item = new_string (buf.s, buf.len)))
 | |
| 			self->error = PARSE_ERROR_MEMORY;
 | |
| 		goto end;
 | |
| 	case '\\':
 | |
| 		if (decode_escape_sequence (self, &buf))
 | |
| 			break;
 | |
| 		goto end;
 | |
| 	default:
 | |
| 		buffer_append_c (&buf, c);
 | |
| 	}
 | |
| 
 | |
| end:
 | |
| 	free (buf.s);
 | |
| 	return item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| try_parse_number (struct tokenizer *self)
 | |
| {
 | |
| 	// These two standard library functions can digest a lot of various inputs,
 | |
| 	// including NaN and +/- infinity.  That may get a bit confusing.
 | |
| 	char *float_end;
 | |
| 	errno = 0;
 | |
| 	long double float_value = strtold (self->cursor, &float_end);
 | |
| 	int float_errno = errno;
 | |
| 
 | |
| 	char *int_end;
 | |
| 	errno = 0;
 | |
| 	long long int_value = strtoll (self->cursor, &int_end, 10);
 | |
| 	int int_errno = errno;
 | |
| 
 | |
| 	// If they both fail, then this is most probably not a number.
 | |
| 	if (float_end == int_end && float_end == self->cursor)
 | |
| 		return NULL;
 | |
| 
 | |
| 	// Only use the floating point result if it parses more characters:
 | |
| 	struct item *item;
 | |
| 	if (float_end > int_end)
 | |
| 	{
 | |
| 		if (float_errno == ERANGE)
 | |
| 		{
 | |
| 			self->error = PARSE_ERROR_FLOAT_RANGE;
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		self->cursor = float_end;
 | |
| 		if (!(item = new_float (float_value)))
 | |
| 			self->error = PARSE_ERROR_MEMORY;
 | |
| 		return item;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		if (int_errno == ERANGE)
 | |
| 		{
 | |
| 			self->error = PARSE_ERROR_INTEGER_RANGE;
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		self->cursor = int_end;
 | |
| 		if (!(item = new_integer (int_value)))
 | |
| 			self->error = PARSE_ERROR_MEMORY;
 | |
| 		return item;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| parse_word (struct tokenizer *self)
 | |
| {
 | |
| 	struct buffer buf = BUFFER_INITIALIZER;
 | |
| 	struct item *item = NULL;
 | |
| 	char c;
 | |
| 
 | |
| 	// Here we accept almost anything that doesn't break the grammar
 | |
| 	while (!strchr (" []\"", (c = *self->cursor++)) && (unsigned char) c > ' ')
 | |
| 		buffer_append_c (&buf, c);
 | |
| 	self->cursor--;
 | |
| 
 | |
| 	if (buf.memory_failure)
 | |
| 		self->error = PARSE_ERROR_MEMORY;
 | |
| 	else if (!buf.len)
 | |
| 		self->error = PARSE_ERROR_INVALID_INPUT;
 | |
| 	else if (!(item = new_word (buf.s, buf.len)))
 | |
| 		self->error = PARSE_ERROR_MEMORY;
 | |
| 
 | |
| 	free (buf.s);
 | |
| 	return item;
 | |
| }
 | |
| 
 | |
| static struct item *parse_item_list (struct tokenizer *);
 | |
| 
 | |
| static struct item *
 | |
| parse_list (struct tokenizer *self)
 | |
| {
 | |
| 	struct item *list = parse_item_list (self);
 | |
| 	if (self->error)
 | |
| 	{
 | |
| 		assert (list == NULL);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	if (!*self->cursor)
 | |
| 	{
 | |
| 		self->error = PARSE_ERROR_EOF;
 | |
| 		item_free_list (list);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	assert (*self->cursor == ']');
 | |
| 	self->cursor++;
 | |
| 	return new_list (list);
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| parse_item (struct tokenizer *self)
 | |
| {
 | |
| 	char c;
 | |
| 	switch ((c = *self->cursor++))
 | |
| 	{
 | |
| 	case '[':  return parse_list (self);
 | |
| 	case '"':  return parse_string (self);
 | |
| 	default:;
 | |
| 	}
 | |
| 
 | |
| 	self->cursor--;
 | |
| 	struct item *item = try_parse_number (self);
 | |
| 	if (!item && !self->error)
 | |
| 		item = parse_word (self);
 | |
| 	return item;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| parse_item_list (struct tokenizer *self)
 | |
| {
 | |
| 	struct item *head = NULL;
 | |
| 	struct item **tail = &head;
 | |
| 
 | |
| 	char c;
 | |
| 	bool expected = true;
 | |
| 	while ((c = *self->cursor) && c != ']')
 | |
| 	{
 | |
| 		if (isspace (c))
 | |
| 		{
 | |
| 			self->cursor++;
 | |
| 			expected = true;
 | |
| 			continue;
 | |
| 		}
 | |
| 		else if (!expected)
 | |
| 		{
 | |
| 			self->error = PARSE_ERROR_UNEXPECTED_INPUT;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 
 | |
| 		if (!(*tail = parse_item (self)))
 | |
| 			goto fail;
 | |
| 		tail = &(*tail)->next;
 | |
| 		expected = false;
 | |
| 	}
 | |
| 	return head;
 | |
| 
 | |
| fail:
 | |
| 	item_free_list (head);
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| parse (const char *s, const char **error)
 | |
| {
 | |
| 	struct tokenizer self = { .cursor = s, .error = PARSE_ERROR_OK };
 | |
| 	struct item *list = parse_item_list (&self);
 | |
| 	if (!self.error && *self.cursor != '\0')
 | |
| 	{
 | |
| 		self.error = PARSE_ERROR_UNEXPECTED_INPUT;
 | |
| 		item_free_list (list);
 | |
| 		list = NULL;
 | |
| 	}
 | |
| 
 | |
| #define XX(x, y) y,
 | |
| 	static const char *strings[PARSE_ERROR_COUNT] =
 | |
| 		{ PARSE_ERROR_TABLE (XX) };
 | |
| #undef XX
 | |
| 
 | |
| 	static char error_buf[128];
 | |
| 	if (self.error && error)
 | |
| 	{
 | |
| 		snprintf (error_buf, sizeof error_buf, "at character %d: %s",
 | |
| 			(int) (self.cursor - s) + 1, strings[self.error]);
 | |
| 		*error = error_buf;
 | |
| 	}
 | |
| 	return list;
 | |
| }
 | |
| 
 | |
| // --- Runtime -----------------------------------------------------------------
 | |
| 
 | |
| // TODO: try to think of a _simple_ way to do preemptive multitasking
 | |
| 
 | |
| struct context
 | |
| {
 | |
| 	struct item *stack;                 ///< The current top of the stack
 | |
| 	size_t stack_size;                  ///< Number of items on the stack
 | |
| 
 | |
| 	size_t reduction_count;             ///< # of function calls so far
 | |
| 	size_t reduction_limit;             ///< The hard limit on function calls
 | |
| 
 | |
| 	char *error;                        ///< Error information
 | |
| 	bool error_is_fatal;                ///< Whether the error can be catched
 | |
| 	bool memory_failure;                ///< Memory allocation failure
 | |
| 
 | |
| 	void *user_data;                    ///< User data
 | |
| };
 | |
| 
 | |
| /// Internal handler for a function
 | |
| typedef bool (*handler_fn) (struct context *);
 | |
| 
 | |
| struct fn
 | |
| {
 | |
| 	struct fn *next;                    ///< The next link in the chain
 | |
| 
 | |
| 	handler_fn handler;                 ///< Internal C handler, or NULL
 | |
| 	struct item *script;                ///< Alternatively runtime code
 | |
| 	char name[];                        ///< The name of the function
 | |
| };
 | |
| 
 | |
| struct fn *g_functions;                 ///< Maps words to functions
 | |
| 
 | |
| static void
 | |
| context_init (struct context *ctx)
 | |
| {
 | |
| 	ctx->stack = NULL;
 | |
| 	ctx->stack_size = 0;
 | |
| 
 | |
| 	ctx->reduction_count = 0;
 | |
| 	ctx->reduction_limit = 2000;
 | |
| 
 | |
| 	ctx->error = NULL;
 | |
| 	ctx->error_is_fatal = false;
 | |
| 	ctx->memory_failure = false;
 | |
| 
 | |
| 	ctx->user_data = NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| context_free (struct context *ctx)
 | |
| {
 | |
| 	item_free_list (ctx->stack);
 | |
| 	ctx->stack = NULL;
 | |
| 
 | |
| 	free (ctx->error);
 | |
| 	ctx->error = NULL;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| set_error (struct context *ctx, const char *format, ...)
 | |
| {
 | |
| 	free (ctx->error);
 | |
| 
 | |
| 	va_list ap;
 | |
| 	va_start (ap, format);
 | |
| 	ctx->error = strdup_vprintf (format, ap);
 | |
| 	va_end (ap);
 | |
| 
 | |
| 	if (!ctx->error)
 | |
| 		ctx->memory_failure = true;
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| push (struct context *ctx, struct item *item)
 | |
| {
 | |
| 	// The `item' is typically a result from new_<type>(), thus when it is null,
 | |
| 	// that function must have failed.  This is a shortcut for convenience.
 | |
| 	if (!item)
 | |
| 	{
 | |
| 		ctx->memory_failure = true;
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	assert (item->next == NULL);
 | |
| 	item->next = ctx->stack;
 | |
| 	ctx->stack = item;
 | |
| 	ctx->stack_size++;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| bump_reductions (struct context *ctx)
 | |
| {
 | |
| 	if (++ctx->reduction_count >= ctx->reduction_limit)
 | |
| 	{
 | |
| 		ctx->error_is_fatal = true;
 | |
| 		return set_error (ctx, "reduction limit reached");
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool execute (struct context *, struct item *);
 | |
| 
 | |
| static bool
 | |
| call_function (struct context *ctx, const char *name)
 | |
| {
 | |
| 	struct fn *iter;
 | |
| 	for (iter = g_functions; iter; iter = iter->next)
 | |
| 		if (!strcmp (name, iter->name))
 | |
| 			goto found;
 | |
| 	return set_error (ctx, "unknown function: %s", name);
 | |
| 
 | |
| found:
 | |
| 	if (!bump_reductions (ctx))
 | |
| 		return false;
 | |
| 
 | |
| 	if (iter->handler
 | |
| 		? iter->handler (ctx)
 | |
| 		: execute (ctx, iter->script))
 | |
| 		return true;
 | |
| 
 | |
| 	// In this case, `error' is NULL
 | |
| 	if (ctx->memory_failure)
 | |
| 		return false;
 | |
| 
 | |
| 	// This creates some form of a stack trace
 | |
| 	char *tmp = ctx->error;
 | |
| 	ctx->error = NULL;
 | |
| 	set_error (ctx, "%s -> %s", name, tmp);
 | |
| 	free (tmp);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_function (struct fn *fn)
 | |
| {
 | |
| 	item_free_list (fn->script);
 | |
| 	free (fn);
 | |
| }
 | |
| 
 | |
| static void
 | |
| unregister_function (const char *name)
 | |
| {
 | |
| 	for (struct fn **iter = &g_functions; *iter; iter = &(*iter)->next)
 | |
| 		if (!strcmp ((*iter)->name, name))
 | |
| 		{
 | |
| 			struct fn *tmp = *iter;
 | |
| 			*iter = tmp->next;
 | |
| 			free_function (tmp);
 | |
| 			break;
 | |
| 		}
 | |
| }
 | |
| 
 | |
| static struct fn *
 | |
| prepend_new_fn (const char *name)
 | |
| {
 | |
| 	struct fn *fn = calloc (1, sizeof *fn + strlen (name) + 1);
 | |
| 	if (!fn)
 | |
| 		return NULL;
 | |
| 
 | |
| 	strcpy (fn->name, name);
 | |
| 	fn->next = g_functions;
 | |
| 	return g_functions = fn;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| register_handler (const char *name, handler_fn handler)
 | |
| {
 | |
| 	unregister_function (name);
 | |
| 	struct fn *fn = prepend_new_fn (name);
 | |
| 	if (!fn)
 | |
| 		return false;
 | |
| 	fn->handler = handler;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| register_script (const char *name, struct item *script)
 | |
| {
 | |
| 	unregister_function (name);
 | |
| 	struct fn *fn = prepend_new_fn (name);
 | |
| 	if (!fn)
 | |
| 		return false;
 | |
| 	fn->script = script;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| execute (struct context *ctx, struct item *script)
 | |
| {
 | |
| 	for (; script; script = script->next)
 | |
| 	{
 | |
| 		if (script->type != ITEM_WORD)
 | |
| 		{
 | |
| 			if (!bump_reductions (ctx)
 | |
| 			 || !push (ctx, new_clone (script)))
 | |
| 				return false;
 | |
| 		}
 | |
| 		else if (!call_function (ctx, get_word (script)))
 | |
| 			return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| // --- Runtime library ---------------------------------------------------------
 | |
| 
 | |
| #define defn(name) static bool name (struct context *ctx)
 | |
| 
 | |
| #define check_stack(n)                                                         \
 | |
| 	if (ctx->stack_size < n) {                                                 \
 | |
| 		set_error (ctx, "stack underflow");                                    \
 | |
| 		return 0;                                                              \
 | |
| 	}
 | |
| 
 | |
| inline static bool
 | |
| check_stack_safe (struct context *ctx, size_t n)
 | |
| {
 | |
| 	check_stack (n);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| check_type (struct context *ctx, const void *item_, enum item_type type)
 | |
| {
 | |
| 	const struct item *item = item_;
 | |
| 	if (item->type == type)
 | |
| 		return true;
 | |
| 
 | |
| 	return set_error (ctx, "invalid type: expected `%s', got `%s'",
 | |
| 		item_type_to_str (type), item_type_to_str (item->type));
 | |
| }
 | |
| 
 | |
| static struct item *
 | |
| pop (struct context *ctx)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *top = ctx->stack;
 | |
| 	ctx->stack = top->next;
 | |
| 	top->next = NULL;
 | |
| 	ctx->stack_size--;
 | |
| 	return top;
 | |
| }
 | |
| 
 | |
| // - - Types - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| #define defn_is_type(name, item_type)                                          \
 | |
| 	defn (fn_is_##name) {                                                      \
 | |
| 		check_stack (1);                                                       \
 | |
| 		struct item *top = pop (ctx);                                          \
 | |
| 		push (ctx, new_integer (top->type == (item_type)));                    \
 | |
| 		item_free (top);                                                       \
 | |
| 		return true;                                                           \
 | |
| 	}
 | |
| 
 | |
| defn_is_type (string,  ITEM_STRING)
 | |
| defn_is_type (word,    ITEM_WORD)
 | |
| defn_is_type (integer, ITEM_INTEGER)
 | |
| defn_is_type (float,   ITEM_FLOAT)
 | |
| defn_is_type (list,    ITEM_LIST)
 | |
| 
 | |
| defn (fn_to_string)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	char *value;
 | |
| 
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_WORD:
 | |
| 		item->type = ITEM_STRING;
 | |
| 	case ITEM_STRING:
 | |
| 		return push (ctx, item);
 | |
| 
 | |
| 	case ITEM_FLOAT:
 | |
| 		value = strdup_printf ("%Lf", get_float (item));
 | |
| 		break;
 | |
| 	case ITEM_INTEGER:
 | |
| 		value = strdup_printf ("%lld", get_integer (item));
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		set_error (ctx, "cannot convert `%s' to `%s'",
 | |
| 			item_type_to_str (item->type), item_type_to_str (ITEM_STRING));
 | |
| 		item_free (item);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	item_free (item);
 | |
| 	if (!value)
 | |
| 	{
 | |
| 		ctx->memory_failure = true;
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	item = new_string (value, -1);
 | |
| 	free (value);
 | |
| 	return push (ctx, item);
 | |
| }
 | |
| 
 | |
| defn (fn_to_integer)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	long long value;
 | |
| 
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_INTEGER:
 | |
| 		return push (ctx, item);
 | |
| 	case ITEM_FLOAT:
 | |
| 		value = get_float (item);
 | |
| 		break;
 | |
| 
 | |
| 	case ITEM_STRING:
 | |
| 	{
 | |
| 		char *end;
 | |
| 		const char *s = get_string (item);
 | |
| 		value = strtoll (s, &end, 10);
 | |
| 		if (end != s && *s == '\0')
 | |
| 			break;
 | |
| 
 | |
| 		item_free (item);
 | |
| 		return set_error (ctx, "integer conversion error");
 | |
| 	}
 | |
| 
 | |
| 	default:
 | |
| 		set_error (ctx, "cannot convert `%s' to `%s'",
 | |
| 			item_type_to_str (item->type), item_type_to_str (ITEM_INTEGER));
 | |
| 		item_free (item);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	item_free (item);
 | |
| 	return push (ctx, new_integer (value));
 | |
| }
 | |
| 
 | |
| defn (fn_to_float)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	long double value;
 | |
| 
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_FLOAT:
 | |
| 		return push (ctx, item);
 | |
| 	case ITEM_INTEGER:
 | |
| 		value = get_integer (item);
 | |
| 		break;
 | |
| 
 | |
| 	case ITEM_STRING:
 | |
| 	{
 | |
| 		char *end;
 | |
| 		const char *s = get_string (item);
 | |
| 		value = strtold (s, &end);
 | |
| 		if (end != s && *s == '\0')
 | |
| 			break;
 | |
| 
 | |
| 		item_free (item);
 | |
| 		return set_error (ctx, "float conversion error");
 | |
| 	}
 | |
| 
 | |
| 	default:
 | |
| 		set_error (ctx, "cannot convert `%s' to `%s'",
 | |
| 			item_type_to_str (item->type), item_type_to_str (ITEM_FLOAT));
 | |
| 		item_free (item);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	item_free (item);
 | |
| 	return push (ctx, new_float (value));
 | |
| }
 | |
| 
 | |
| // - - Miscellaneous - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| defn (fn_length)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	bool success = true;
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_STRING:
 | |
| 		success = push (ctx, new_integer (((struct item_string *) item)->len));
 | |
| 		break;
 | |
| 	case ITEM_LIST:
 | |
| 	{
 | |
| 		long long length = 0;
 | |
| 		struct item *iter;
 | |
| 		for (iter = get_list (item); iter; iter = iter->next)
 | |
| 			length++;
 | |
| 		success = push (ctx, new_integer (length));
 | |
| 		break;
 | |
| 	}
 | |
| 	default:
 | |
| 		success = set_error (ctx, "invalid type");
 | |
| 	}
 | |
| 	item_free (item);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| // - - Stack operations  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| defn (fn_dup)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	return push (ctx, new_clone (ctx->stack));
 | |
| }
 | |
| 
 | |
| defn (fn_drop)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	item_free (pop (ctx));
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| defn (fn_swap)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *second = pop (ctx), *first = pop (ctx);
 | |
| 	return push (ctx, second) && push (ctx, first);
 | |
| }
 | |
| 
 | |
| defn (fn_call)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *script = pop (ctx);
 | |
| 	bool success = check_type (ctx, script, ITEM_LIST)
 | |
| 		&& execute (ctx, get_list (script));
 | |
| 	item_free (script);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| defn (fn_dip)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *script = pop (ctx);
 | |
| 	struct item *item   = pop (ctx);
 | |
| 	bool success = check_type (ctx, script, ITEM_LIST)
 | |
| 		&& execute (ctx, get_list (script));
 | |
| 	item_free (script);
 | |
| 	if (!success)
 | |
| 	{
 | |
| 		item_free (item);
 | |
| 		return false;
 | |
| 	}
 | |
| 	return push (ctx, item);
 | |
| }
 | |
| 
 | |
| defn (fn_unit)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	return push (ctx, new_list (item));
 | |
| }
 | |
| 
 | |
| defn (fn_cons)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	if (!check_type (ctx, list, ITEM_LIST))
 | |
| 	{
 | |
| 		item_free (list);
 | |
| 		item_free (item);
 | |
| 		return false;
 | |
| 	}
 | |
| 	item->next = get_list (list);
 | |
| 	((struct item_list *) list)->head = item;
 | |
| 	return push (ctx, list);
 | |
| }
 | |
| 
 | |
| defn (fn_cat)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *scnd = pop (ctx);
 | |
| 	struct item *frst = pop (ctx);
 | |
| 	if (!check_type (ctx, frst, ITEM_LIST)
 | |
| 	 || !check_type (ctx, scnd, ITEM_LIST))
 | |
| 	{
 | |
| 		item_free (frst);
 | |
| 		item_free (scnd);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	// XXX: we shouldn't have to do this in O(n)
 | |
| 	struct item **tail = &((struct item_list *) frst)->head;
 | |
| 	while (*tail)
 | |
| 		tail = &(*tail)->next;
 | |
| 	*tail = get_list (scnd);
 | |
| 
 | |
| 	((struct item_list *) scnd)->head = NULL;
 | |
| 	item_free (scnd);
 | |
| 	return push (ctx, frst);
 | |
| }
 | |
| 
 | |
| defn (fn_uncons)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	if (!check_type (ctx, list, ITEM_LIST))
 | |
| 		goto fail;
 | |
| 	struct item *first = get_list (list);
 | |
| 	if (!first)
 | |
| 	{
 | |
| 		set_error (ctx, "list is empty");
 | |
| 		goto fail;
 | |
| 	}
 | |
| 	((struct item_list *) list)->head = first->next;
 | |
| 	first->next = NULL;
 | |
| 	return push (ctx, first) && push (ctx, list);
 | |
| fail:
 | |
| 	item_free (list);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| // - - Logical - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| static bool
 | |
| to_boolean (struct context *ctx, struct item *item, bool *ok)
 | |
| {
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 	case ITEM_STRING:
 | |
| 		return *get_string (item) != '\0';
 | |
| 	case ITEM_INTEGER:
 | |
| 		return get_integer (item) != 0;
 | |
| 	case ITEM_FLOAT:
 | |
| 		return get_float   (item) != 0.;
 | |
| 	default:
 | |
| 		return (*ok = set_error (ctx, "cannot convert `%s' to boolean",
 | |
| 			item_type_to_str (item->type)));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| defn (fn_not)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	bool ok = true;
 | |
| 	bool result = !to_boolean (ctx, item, &ok);
 | |
| 	item_free (item);
 | |
| 	return ok && push (ctx, new_integer (result));
 | |
| }
 | |
| 
 | |
| defn (fn_and)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	bool ok = true;
 | |
| 	bool result = to_boolean (ctx, op1, &ok) && to_boolean (ctx, op2, &ok);
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok && push (ctx, new_integer (result));
 | |
| }
 | |
| 
 | |
| defn (fn_or)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	bool ok = true;
 | |
| 	bool result = to_boolean (ctx, op1, &ok)
 | |
| 		|| !ok || to_boolean (ctx, op2, &ok);
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok && push (ctx, new_integer (result));
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| defn (fn_if)
 | |
| {
 | |
| 	check_stack (3);
 | |
| 	struct item *else_ = pop (ctx);
 | |
| 	struct item *then_ = pop (ctx);
 | |
| 	struct item *cond_ = pop (ctx);
 | |
| 
 | |
| 	bool ok = true;
 | |
| 	bool condition = to_boolean (ctx, cond_, &ok);
 | |
| 	item_free (cond_);
 | |
| 
 | |
| 	bool success = false;
 | |
| 	if (ok
 | |
| 	 && check_type (ctx, then_, ITEM_LIST)
 | |
| 	 && check_type (ctx, else_, ITEM_LIST))
 | |
| 		success = execute (ctx, condition
 | |
| 			? get_list (then_)
 | |
| 			: get_list (else_));
 | |
| 
 | |
| 	item_free (then_);
 | |
| 	item_free (else_);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| defn (fn_try)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *catch   = pop (ctx);
 | |
| 	struct item *try     = pop (ctx);
 | |
| 	bool success = false;
 | |
| 	if (!check_type (ctx, try,     ITEM_LIST)
 | |
| 	 || !check_type (ctx, catch,   ITEM_LIST))
 | |
| 		goto fail;
 | |
| 
 | |
| 	if (!execute (ctx, get_list (try)))
 | |
| 	{
 | |
| 		if (ctx->memory_failure || ctx->error_is_fatal)
 | |
| 			goto fail;
 | |
| 
 | |
| 		success = push (ctx, new_string (ctx->error, -1));
 | |
| 		free (ctx->error);
 | |
| 		ctx->error = NULL;
 | |
| 
 | |
| 		if (success)
 | |
| 			success = execute (ctx, get_list (catch));
 | |
| 	}
 | |
| 
 | |
| fail:
 | |
| 	item_free (try);
 | |
| 	item_free (catch);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| defn (fn_map)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *fn   = pop (ctx);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	if (!check_type (ctx, fn,   ITEM_LIST)
 | |
| 	 || !check_type (ctx, list, ITEM_LIST))
 | |
| 	{
 | |
| 		item_free (fn);
 | |
| 		item_free (list);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	bool success = false;
 | |
| 	struct item *result = NULL, **tail = &result;
 | |
| 	for (struct item *iter = get_list (list); iter; iter = iter->next)
 | |
| 	{
 | |
| 		if (!push (ctx, new_clone (iter))
 | |
| 		 || !execute (ctx, get_list (fn))
 | |
| 		 || !check_stack_safe (ctx, 1))
 | |
| 			goto fail;
 | |
| 
 | |
| 		struct item *item = pop (ctx);
 | |
| 		*tail = item;
 | |
| 		tail = &item->next;
 | |
| 	}
 | |
| 	success = true;
 | |
| 
 | |
| fail:
 | |
| 	set_list (list, result);
 | |
| 	item_free (fn);
 | |
| 	if (!success)
 | |
| 	{
 | |
| 		item_free (list);
 | |
| 		return false;
 | |
| 	}
 | |
| 	return push (ctx, list);
 | |
| }
 | |
| 
 | |
| defn (fn_filter)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *fn   = pop (ctx);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	if (!check_type (ctx, fn,   ITEM_LIST)
 | |
| 	 || !check_type (ctx, list, ITEM_LIST))
 | |
| 	{
 | |
| 		item_free (fn);
 | |
| 		item_free (list);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	bool success = false;
 | |
| 	bool ok = true;
 | |
| 	struct item *result = NULL, **tail = &result;
 | |
| 	for (struct item *iter = get_list (list); iter; iter = iter->next)
 | |
| 	{
 | |
| 		if (!push (ctx, new_clone (iter))
 | |
| 		 || !execute (ctx, get_list (fn))
 | |
| 		 || !check_stack_safe (ctx, 1))
 | |
| 			goto fail;
 | |
| 
 | |
| 		struct item *item = pop (ctx);
 | |
| 		bool survived = to_boolean (ctx, item, &ok);
 | |
| 		item_free (item);
 | |
| 		if (!ok)
 | |
| 			goto fail;
 | |
| 		if (!survived)
 | |
| 			continue;
 | |
| 
 | |
| 		if (!(item = new_clone (iter)))
 | |
| 			goto fail;
 | |
| 		*tail = item;
 | |
| 		tail = &item->next;
 | |
| 	}
 | |
| 	success = true;
 | |
| 
 | |
| fail:
 | |
| 	set_list (list, result);
 | |
| 	item_free (fn);
 | |
| 	if (!success)
 | |
| 	{
 | |
| 		item_free (list);
 | |
| 		return false;
 | |
| 	}
 | |
| 	return push (ctx, list);
 | |
| }
 | |
| 
 | |
| defn (fn_fold)
 | |
| {
 | |
| 	check_stack (3);
 | |
| 	struct item *op   = pop (ctx);
 | |
| 	struct item *null = pop (ctx);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	bool success = false;
 | |
| 	if (!check_type (ctx, op,   ITEM_LIST)
 | |
| 	 || !check_type (ctx, list, ITEM_LIST))
 | |
| 	{
 | |
| 		item_free (null);
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	push (ctx, null);
 | |
| 	for (struct item *iter = get_list (list); iter; iter = iter->next)
 | |
| 		if (!push (ctx, new_clone (iter))
 | |
| 		 || !execute (ctx, get_list (op)))
 | |
| 			goto fail;
 | |
| 	success = true;
 | |
| 
 | |
| fail:
 | |
| 	item_free (op);
 | |
| 	item_free (list);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| defn (fn_each)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op   = pop (ctx);
 | |
| 	struct item *list = pop (ctx);
 | |
| 	bool success = false;
 | |
| 	if (!check_type (ctx, op,   ITEM_LIST)
 | |
| 	 || !check_type (ctx, list, ITEM_LIST))
 | |
| 		goto fail;
 | |
| 
 | |
| 	for (struct item *iter = get_list (list); iter; iter = iter->next)
 | |
| 		if (!push (ctx, new_clone (iter))
 | |
| 		 || !execute (ctx, get_list (op)))
 | |
| 			goto fail;
 | |
| 	success = true;
 | |
| 
 | |
| fail:
 | |
| 	item_free (op);
 | |
| 	item_free (list);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| // - - Arithmetic  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| // XXX: why not a `struct item_string *` argument?
 | |
| static bool
 | |
| push_repeated_string (struct context *ctx, struct item *op1, struct item *op2)
 | |
| {
 | |
| 	struct item_string  *string = (struct item_string  *) op1;
 | |
| 	struct item_integer *repeat = (struct item_integer *) op2;
 | |
| 	assert (string->type == ITEM_STRING);
 | |
| 	assert (repeat->type == ITEM_INTEGER);
 | |
| 
 | |
| 	if (repeat->value < 0)
 | |
| 		return set_error (ctx, "cannot multiply a string by a negative value");
 | |
| 
 | |
| 	char *buf = NULL;
 | |
| 	size_t len = string->len * repeat->value;
 | |
| 	if (len < string->len && repeat->value != 0)
 | |
| 		goto allocation_fail;
 | |
| 
 | |
| 	buf = malloc (len);
 | |
| 	if (!buf)
 | |
| 		goto allocation_fail;
 | |
| 
 | |
| 	for (size_t i = 0; i < len; i += string->len)
 | |
| 		memcpy (buf + i, string->value, string->len);
 | |
| 	struct item *item = new_string (buf, len);
 | |
| 	free (buf);
 | |
| 	return push (ctx, item);
 | |
| 
 | |
| allocation_fail:
 | |
| 	ctx->memory_failure = true;
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| defn (fn_times)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) * get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_integer (op1) * get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) * get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) * get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_STRING)
 | |
| 		ok = push_repeated_string (ctx, op2, op1);
 | |
| 	else if (op1->type == ITEM_STRING  && op2->type == ITEM_INTEGER)
 | |
| 		ok = push_repeated_string (ctx, op1, op2);
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot multiply `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| defn (fn_pow)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		// TODO: implement this properly, outputting an integer
 | |
| 		ok = push (ctx, new_float (powl (get_integer (op1), get_integer (op2))));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (powl (get_integer (op1), get_float   (op2))));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (powl (get_float   (op1), get_float   (op2))));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float (powl (get_float   (op1), get_integer (op2))));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot exponentiate `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| defn (fn_div)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 	{
 | |
| 		if (get_integer (op2) == 0)
 | |
| 			ok = set_error (ctx, "division by zero");
 | |
| 		else
 | |
| 			ok = push (ctx, new_integer (get_integer (op1) / get_integer (op2)));
 | |
| 	}
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (get_integer (op1) / get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (get_float   (op1) / get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float (get_float   (op1) / get_integer (op2)));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot divide `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| defn (fn_mod)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 	{
 | |
| 		if (get_integer (op2) == 0)
 | |
| 			ok = set_error (ctx, "division by zero");
 | |
| 		else
 | |
| 			ok = push (ctx, new_integer (get_integer (op1) % get_integer (op2)));
 | |
| 	}
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (fmodl (get_integer (op1), get_float   (op2))));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float (fmodl (get_float   (op1), get_float   (op2))));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float (fmodl (get_float   (op1), get_integer (op2))));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot divide `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| push_concatenated_string (struct context *ctx,
 | |
| 	struct item *op1, struct item *op2)
 | |
| {
 | |
| 	struct item_string *s1 = (struct item_string *) op1;
 | |
| 	struct item_string *s2 = (struct item_string *) op2;
 | |
| 	assert (s1->type == ITEM_STRING);
 | |
| 	assert (s2->type == ITEM_STRING);
 | |
| 
 | |
| 	char *buf = NULL;
 | |
| 	size_t len = s1->len + s2->len;
 | |
| 	if (len < s1->len || len < s2->len)
 | |
| 		goto allocation_fail;
 | |
| 
 | |
| 	buf = malloc (len);
 | |
| 	if (!buf)
 | |
| 		goto allocation_fail;
 | |
| 
 | |
| 	memcpy (buf,           s1->value, s1->len);
 | |
| 	memcpy (buf + s1->len, s2->value, s2->len);
 | |
| 	struct item *item = new_string (buf, len);
 | |
| 	free (buf);
 | |
| 	return push (ctx, item);
 | |
| 
 | |
| allocation_fail:
 | |
| 	ctx->memory_failure = true;
 | |
| 	return false;
 | |
| 
 | |
| }
 | |
| 
 | |
| defn (fn_plus)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) + get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_integer (op1) + get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) + get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) + get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_STRING  && op2->type == ITEM_STRING)
 | |
| 		ok = push_concatenated_string (ctx, op1, op2);
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot add `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| defn (fn_minus)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) - get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_integer (op1) - get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) - get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_float   (get_float   (op1) - get_integer (op2)));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot subtract `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| // - - Comparison  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| static int
 | |
| compare_strings (struct item_string *s1, struct item_string *s2)
 | |
| {
 | |
| 	// XXX: not entirely correct wrt. null bytes
 | |
| 	size_t len = (s1->len < s2->len ? s1->len : s2->len) + 1;
 | |
| 	return memcmp (s1->value, s2->value, len);
 | |
| }
 | |
| 
 | |
| static bool compare_lists (struct item *, struct item *);
 | |
| 
 | |
| static bool
 | |
| compare_list_items (struct item *op1, struct item *op2)
 | |
| {
 | |
| 	if (op1->type != op2->type)
 | |
| 		return false;
 | |
| 
 | |
| 	switch (op1->type)
 | |
| 	{
 | |
| 	case ITEM_STRING:
 | |
| 	case ITEM_WORD:
 | |
| 		return !compare_strings ((struct item_string *) op1,
 | |
| 			(struct item_string *) op2);
 | |
| 	case ITEM_FLOAT:
 | |
| 		return get_float (op1) == get_float (op2);
 | |
| 	case ITEM_INTEGER:
 | |
| 		return get_integer (op1) == get_integer (op2);
 | |
| 	case ITEM_LIST:
 | |
| 		return compare_lists (get_list (op1), get_list (op2));
 | |
| 	}
 | |
| 	abort ();
 | |
| }
 | |
| 
 | |
| static bool
 | |
| compare_lists (struct item *op1, struct item *op2)
 | |
| {
 | |
| 	while (op1 && op2)
 | |
| 	{
 | |
| 		if (!compare_list_items (op1, op2))
 | |
| 			return false;
 | |
| 
 | |
| 		op1 = op1->next;
 | |
| 		op2 = op2->next;
 | |
| 	}
 | |
| 	return !op1 && !op2;
 | |
| }
 | |
| 
 | |
| defn (fn_eq)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) == get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) == get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_integer (get_float   (op1) == get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_float   (op1) == get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_LIST    && op2->type == ITEM_LIST)
 | |
| 		ok = push (ctx, new_integer (compare_lists
 | |
| 			(get_list (op1), get_list (op2))));
 | |
| 	else if (op1->type == ITEM_STRING  && op2->type == ITEM_STRING)
 | |
| 		ok = push (ctx, new_integer (compare_strings
 | |
| 			((struct item_string *)(op1), (struct item_string *)(op2)) == 0));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot compare `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| defn (fn_lt)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *op2 = pop (ctx);
 | |
| 	struct item *op1 = pop (ctx);
 | |
| 
 | |
| 	bool ok;
 | |
| 	if      (op1->type == ITEM_INTEGER && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) < get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_INTEGER && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_integer (get_integer (op1) < get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_FLOAT)
 | |
| 		ok = push (ctx, new_integer (get_float   (op1) < get_float   (op2)));
 | |
| 	else if (op1->type == ITEM_FLOAT   && op2->type == ITEM_INTEGER)
 | |
| 		ok = push (ctx, new_integer (get_float   (op1) < get_integer (op2)));
 | |
| 	else if (op1->type == ITEM_STRING  && op2->type == ITEM_STRING)
 | |
| 		ok = push (ctx, new_integer (compare_strings
 | |
| 			((struct item_string *)(op1), (struct item_string *)(op2)) < 0));
 | |
| 	else
 | |
| 		ok = set_error (ctx, "cannot compare `%s' and `%s'",
 | |
| 			item_type_to_str (op1->type), item_type_to_str (op2->type));
 | |
| 
 | |
| 	item_free (op1);
 | |
| 	item_free (op2);
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| // - - Utilities - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| defn (fn_rand)
 | |
| {
 | |
| 	return push (ctx, new_float ((long double) rand ()
 | |
| 		/ ((long double) RAND_MAX + 1)));
 | |
| }
 | |
| 
 | |
| defn (fn_time)
 | |
| {
 | |
| 	return push (ctx, new_integer (time (NULL)));
 | |
| }
 | |
| 
 | |
| // XXX: this is a bit too constrained; combines strftime() with gmtime()
 | |
| defn (fn_strftime)
 | |
| {
 | |
| 	check_stack (2);
 | |
| 	struct item *format = pop (ctx);
 | |
| 	struct item *time_  = pop (ctx);
 | |
| 	bool success = false;
 | |
| 	if (!check_type (ctx, time_,  ITEM_INTEGER)
 | |
| 	 || !check_type (ctx, format, ITEM_STRING))
 | |
| 		goto fail;
 | |
| 
 | |
| 	if (get_integer (time_) < 0)
 | |
| 	{
 | |
| 		set_error (ctx, "invalid time value");
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	char buf[128];
 | |
| 	time_t time__ = get_integer (time_);
 | |
| 	struct tm tm;
 | |
| 	gmtime_r (&time__, &tm);
 | |
| 	buf[strftime (buf, sizeof buf, get_string (format), &tm)] = '\0';
 | |
| 	success = push (ctx, new_string (buf, -1));
 | |
| 
 | |
| fail:
 | |
| 	item_free (time_);
 | |
| 	item_free (format);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| static void item_list_to_str (const struct item *, struct buffer *);
 | |
| 
 | |
| static void
 | |
| string_to_str (const struct item_string *string, struct buffer *buf)
 | |
| {
 | |
| 	buffer_append_c (buf, '"');
 | |
| 	for (size_t i = 0; i < string->len; i++)
 | |
| 	{
 | |
| 		char c = string->value[i];
 | |
| 		if      (c == '\n')  buffer_append (buf, "\\n", 2);
 | |
| 		else if (c == '\r')  buffer_append (buf, "\\r", 2);
 | |
| 		else if (c == '\t')  buffer_append (buf, "\\t", 2);
 | |
| 		else if (!isprint (c))
 | |
| 		{
 | |
| 			char tmp[8];
 | |
| 			snprintf (tmp, sizeof tmp, "\\x%02x", (unsigned char) c);
 | |
| 			buffer_append (buf, tmp, strlen (tmp));
 | |
| 		}
 | |
| 		else if (c == '\\')  buffer_append (buf, "\\\\", 2);
 | |
| 		else if (c == '"')   buffer_append (buf, "\\\"", 2);
 | |
| 		else                 buffer_append_c (buf, c);
 | |
| 	}
 | |
| 	buffer_append_c (buf, '"');
 | |
| }
 | |
| 
 | |
| static void
 | |
| item_to_str (const struct item *item, struct buffer *buf)
 | |
| {
 | |
| 	switch (item->type)
 | |
| 	{
 | |
| 		char *x;
 | |
| 	case ITEM_STRING:
 | |
| 		string_to_str ((struct item_string *) item, buf);
 | |
| 		break;
 | |
| 	case ITEM_WORD:
 | |
| 	{
 | |
| 		struct item_word *word = (struct item_word *) item;
 | |
| 		buffer_append (buf, word->value, word->len);
 | |
| 		break;
 | |
| 	}
 | |
| 	case ITEM_INTEGER:
 | |
| 		if (!(x = strdup_printf ("%lld", get_integer (item))))
 | |
| 			goto alloc_failure;
 | |
| 		buffer_append (buf, x, strlen (x));
 | |
| 		free (x);
 | |
| 		break;
 | |
| 	case ITEM_FLOAT:
 | |
| 		if (!(x = strdup_printf ("%Lf", get_float (item))))
 | |
| 			goto alloc_failure;
 | |
| 		buffer_append (buf, x, strlen (x));
 | |
| 		free (x);
 | |
| 		break;
 | |
| 	case ITEM_LIST:
 | |
| 		buffer_append_c (buf, '[');
 | |
| 		item_list_to_str (get_list (item), buf);
 | |
| 		buffer_append_c (buf, ']');
 | |
| 		break;
 | |
| 	}
 | |
| 	return;
 | |
| 
 | |
| alloc_failure:
 | |
| 	// This is a bit hackish but it simplifies stuff
 | |
| 	buf->memory_failure = true;
 | |
| 	free (buf->s);
 | |
| 	buf->s = NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| item_list_to_str (const struct item *script, struct buffer *buf)
 | |
| {
 | |
| 	if (!script)
 | |
| 		return;
 | |
| 
 | |
| 	item_to_str (script, buf);
 | |
| 	while ((script = script->next))
 | |
| 	{
 | |
| 		buffer_append_c (buf, ' ');
 | |
| 		item_to_str (script, buf);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // --- IRC protocol ------------------------------------------------------------
 | |
| 
 | |
| struct message
 | |
| {
 | |
| 	char *prefix;                       ///< Message prefix
 | |
| 	char *command;                      ///< IRC command
 | |
| 	char *params[16];                   ///< Command parameters (0-terminated)
 | |
| 	size_t n_params;                    ///< Number of parameters present
 | |
| };
 | |
| 
 | |
| inline static char *
 | |
| cut_word (char **s)
 | |
| {
 | |
| 	char *start = *s, *end = *s + strcspn (*s, " ");
 | |
| 	*s = end + strspn (end, " ");
 | |
| 	*end = '\0';
 | |
| 	return start;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| parse_message (char *s, struct message *msg)
 | |
| {
 | |
| 	memset (msg, 0, sizeof *msg);
 | |
| 
 | |
| 	// Ignore IRC 3.2 message tags, if present
 | |
| 	if (*s == '@')
 | |
| 	{
 | |
| 		s += strcspn (s, " ");
 | |
| 		s += strspn (s, " ");
 | |
| 	}
 | |
| 
 | |
| 	// Prefix
 | |
| 	if (*s == ':')
 | |
| 		msg->prefix = cut_word (&s) + 1;
 | |
| 
 | |
| 	// Command
 | |
| 	if (!*(msg->command = cut_word (&s)))
 | |
| 		return false;
 | |
| 
 | |
| 	// Parameters
 | |
| 	while (*s)
 | |
| 	{
 | |
| 		size_t n = msg->n_params++;
 | |
| 		if (msg->n_params >= N_ELEMENTS (msg->params))
 | |
| 			return false;
 | |
| 		if (*s == ':')
 | |
| 		{
 | |
| 			msg->params[n] = ++s;
 | |
| 			break;
 | |
| 		}
 | |
| 		msg->params[n] = cut_word (&s);
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static struct message *
 | |
| read_message (void)
 | |
| {
 | |
| 	static bool discard = false;
 | |
| 	static char buf[1025];
 | |
| 	static struct message msg;
 | |
| 
 | |
| 	bool discard_this;
 | |
| 	do
 | |
| 	{
 | |
| 		if (!fgets (buf, sizeof buf, stdin))
 | |
| 			return NULL;
 | |
| 		size_t len = strlen (buf);
 | |
| 
 | |
| 		// Just to be on the safe side, if the line overflows our buffer,
 | |
| 		// ignore everything up until the next line.
 | |
| 		discard_this = discard;
 | |
| 		if (len >= 2 && !strcmp (buf + len - 2, "\r\n"))
 | |
| 		{
 | |
| 			buf[len -= 2] = '\0';
 | |
| 			discard = false;
 | |
| 		}
 | |
| 		else
 | |
| 			discard = true;
 | |
| 	}
 | |
| 	// Invalid messages are silently ignored
 | |
| 	while (discard_this || !parse_message (buf, &msg));
 | |
| 	return &msg;
 | |
| }
 | |
| 
 | |
| // --- Interfacing with the bot ------------------------------------------------
 | |
| 
 | |
| #define BOT_PRINT "ZYKLONB print :script: "
 | |
| 
 | |
| static const char *
 | |
| get_config (const char *key)
 | |
| {
 | |
| 	printf ("ZYKLONB get_config :%s\r\n", key);
 | |
| 	struct message *msg = read_message ();
 | |
| 	if (!msg || msg->n_params <= 0)
 | |
| 		exit (EXIT_FAILURE);
 | |
| 	return msg->params[0];
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| // TODO: implement more functions; try to avoid writing them in C
 | |
| 
 | |
| static bool
 | |
| init_runtime_library_scripts (void)
 | |
| {
 | |
| 	bool ok = true;
 | |
| 
 | |
| 	// It's much cheaper (and more fun) to define functions in terms of other
 | |
| 	// ones.  The "unit tests" serve a secondary purpose of showing the usage.
 | |
| 	struct script
 | |
| 	{
 | |
| 		const char *name;               ///< Name of the function
 | |
| 		const char *definition;         ///< The defining script
 | |
| 		const char *unit_test;          ///< Trivial unit test, must return 1
 | |
| 	}
 | |
| 	scripts[] =
 | |
| 	{
 | |
| 		{ "nip",     "swap drop",                    "1 2 nip 2 ="            },
 | |
| 		{ "over",    "[dup] dip swap",               "1 2 over nip nip 1 ="   },
 | |
| 		{ "swons",   "swap cons",                    "[2] 1 swons [1 2] ="    },
 | |
| 		{ "first",   "uncons drop",                  "[1 2 3] first 1 ="      },
 | |
| 		{ "rest",    "uncons swap drop",             "[1 2 3] rest [2 3] ="   },
 | |
| 		{ "reverse", "[] swap [swap cons] each",     "[1 2] reverse [2 1] ="  },
 | |
| 		{ "curry",   "cons",                         "1 2 [+] curry call 3 =" },
 | |
| 
 | |
| 		{ "xor",     "not swap not + 1 =",           "1 1 xor 0 ="            },
 | |
| 		{ "min",     "over over < [drop] [nip] if",  "1 2 min 1 ="            },
 | |
| 		{ "max",     "over over > [drop] [nip] if",  "1 2 max 2 ="            },
 | |
| 
 | |
| 		{ "all?",    "[and] cat 1 swap fold",        "[3 4 5] [> 3] all? 0 =" },
 | |
| 		{ "any?",    "[or] cat 0 swap fold",         "[3 4 5] [> 3] any? 1 =" },
 | |
| 
 | |
| 		{ ">",       "swap <",                       "1 2 > 0 ="              },
 | |
| 		{ "!=",      "= not",                        "1 2 != 1 ="             },
 | |
| 		{ "<=",      "> not",                        "1 2 <= 1 ="             },
 | |
| 		{ ">=",      "< not",                        "1 2 >= 0 ="             },
 | |
| 
 | |
| 		// XXX: this is a bit crazy and does not work with an empty list
 | |
| 		{ "join",   "[uncons] dip swap [[dup] dip swap [+ +] dip] each drop",
 | |
| 		  "[1 2 3] [>string] map \" -> \" join \"1 -> 2 -> 3\" ="             },
 | |
| 	};
 | |
| 
 | |
| 	for (size_t i = 0; i < N_ELEMENTS (scripts); i++)
 | |
| 	{
 | |
| 		const char *error = NULL;
 | |
| 		struct item *script = parse (scripts[i].definition, &error);
 | |
| 		if (error)
 | |
| 		{
 | |
| 			printf (BOT_PRINT "error parsing internal script `%s': %s\r\n",
 | |
| 				scripts[i].definition, error);
 | |
| 			ok = false;
 | |
| 		}
 | |
| 		else
 | |
| 			ok &= register_script (scripts[i].name, script);
 | |
| 	}
 | |
| 
 | |
| 	struct context ctx;
 | |
| 	for (size_t i = 0; i < N_ELEMENTS (scripts); i++)
 | |
| 	{
 | |
| 		const char *error = NULL;
 | |
| 		struct item *script = parse (scripts[i].unit_test, &error);
 | |
| 		if (error)
 | |
| 		{
 | |
| 			printf (BOT_PRINT "error parsing unit test for `%s': %s\r\n",
 | |
| 				scripts[i].name, error);
 | |
| 			ok = false;
 | |
| 			continue;
 | |
| 		}
 | |
| 		context_init (&ctx);
 | |
| 		execute (&ctx, script);
 | |
| 		item_free_list (script);
 | |
| 
 | |
| 		const char *failure = NULL;
 | |
| 		if (ctx.memory_failure)
 | |
| 			failure = "memory allocation failure";
 | |
| 		else if (ctx.error)
 | |
| 			failure = ctx.error;
 | |
| 		else if (ctx.stack_size != 1)
 | |
| 			failure = "too many results on the stack";
 | |
| 		else if (ctx.stack->type != ITEM_INTEGER)
 | |
| 			failure = "result is not an integer";
 | |
| 		else if (get_integer (ctx.stack) != 1)
 | |
| 			failure = "wrong test result";
 | |
| 		if (failure)
 | |
| 		{
 | |
| 			printf (BOT_PRINT "error executing unit test for `%s': %s\r\n",
 | |
| 				scripts[i].name, failure);
 | |
| 			ok = false;
 | |
| 		}
 | |
| 		context_free (&ctx);
 | |
| 	}
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| init_runtime_library (void)
 | |
| {
 | |
| 	bool ok = true;
 | |
| 
 | |
| 	// Type detection
 | |
| 	ok &= register_handler ("string?",  fn_is_string);
 | |
| 	ok &= register_handler ("word?",    fn_is_word);
 | |
| 	ok &= register_handler ("integer?", fn_is_integer);
 | |
| 	ok &= register_handler ("float?",   fn_is_float);
 | |
| 	ok &= register_handler ("list?",    fn_is_list);
 | |
| 
 | |
| 	// Type conversion
 | |
| 	ok &= register_handler (">string",  fn_to_string);
 | |
| 	ok &= register_handler (">integer", fn_to_integer);
 | |
| 	ok &= register_handler (">float",   fn_to_float);
 | |
| 
 | |
| 	// Miscellaneous
 | |
| 	ok &= register_handler ("length",   fn_length);
 | |
| 
 | |
| 	// Basic stack manipulation
 | |
| 	ok &= register_handler ("dup",      fn_dup);
 | |
| 	ok &= register_handler ("drop",     fn_drop);
 | |
| 	ok &= register_handler ("swap",     fn_swap);
 | |
| 
 | |
| 	// Calling stuff
 | |
| 	ok &= register_handler ("call",     fn_call);
 | |
| 	ok &= register_handler ("dip",      fn_dip);
 | |
| 
 | |
| 	// Control flow
 | |
| 	ok &= register_handler ("if",       fn_if);
 | |
| 	ok &= register_handler ("try",      fn_try);
 | |
| 
 | |
| 	// List processing
 | |
| 	ok &= register_handler ("map",      fn_map);
 | |
| 	ok &= register_handler ("filter",   fn_filter);
 | |
| 	ok &= register_handler ("fold",     fn_fold);
 | |
| 	ok &= register_handler ("each",     fn_each);
 | |
| 
 | |
| 	// List manipulation
 | |
| 	ok &= register_handler ("unit",     fn_unit);
 | |
| 	ok &= register_handler ("cons",     fn_cons);
 | |
| 	ok &= register_handler ("cat",      fn_cat);
 | |
| 	ok &= register_handler ("uncons",   fn_uncons);
 | |
| 
 | |
| 	// Arithmetic operations
 | |
| 	ok &= register_handler ("+",        fn_plus);
 | |
| 	ok &= register_handler ("-",        fn_minus);
 | |
| 	ok &= register_handler ("*",        fn_times);
 | |
| 	ok &= register_handler ("^",        fn_pow);
 | |
| 	ok &= register_handler ("/",        fn_div);
 | |
| 	ok &= register_handler ("%",        fn_mod);
 | |
| 
 | |
| 	// Comparison
 | |
| 	ok &= register_handler ("=",        fn_eq);
 | |
| 	ok &= register_handler ("<",        fn_lt);
 | |
| 
 | |
| 	// Logical operations
 | |
| 	ok &= register_handler ("not",      fn_not);
 | |
| 	ok &= register_handler ("and",      fn_and);
 | |
| 	ok &= register_handler ("or",       fn_or);
 | |
| 
 | |
| 	// Utilities
 | |
| 	ok &= register_handler ("rand",     fn_rand);
 | |
| 	ok &= register_handler ("time",     fn_time);
 | |
| 	ok &= register_handler ("strftime", fn_strftime);
 | |
| 
 | |
| 	ok &= init_runtime_library_scripts ();
 | |
| 	return ok;
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_runtime_library (void)
 | |
| {
 | |
| 	struct fn *next, *iter;
 | |
| 	for (iter = g_functions; iter; iter = next)
 | |
| 	{
 | |
| 		next = iter->next;
 | |
| 		free_function (iter);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // --- Function database -------------------------------------------------------
 | |
| 
 | |
| // TODO: a global variable storing the various procedures (db)
 | |
| // XXX: defining procedures would ideally need some kind of an ACL
 | |
| 
 | |
| static void
 | |
| read_db (void)
 | |
| {
 | |
| 	// TODO
 | |
| }
 | |
| 
 | |
| static void
 | |
| write_db (void)
 | |
| {
 | |
| 	// TODO
 | |
| }
 | |
| 
 | |
| // --- Main --------------------------------------------------------------------
 | |
| 
 | |
| static char *g_prefix;
 | |
| 
 | |
| struct user_info
 | |
| {
 | |
| 	char *ctx;                          ///< Context: channel or user
 | |
| 	char *ctx_quote;                    ///< Reply quotation
 | |
| };
 | |
| 
 | |
| defn (fn_dot)
 | |
| {
 | |
| 	check_stack (1);
 | |
| 	struct item *item = pop (ctx);
 | |
| 	struct user_info *info = ctx->user_data;
 | |
| 
 | |
| 	struct buffer buf = BUFFER_INITIALIZER;
 | |
| 	item_to_str (item, &buf);
 | |
| 	item_free (item);
 | |
| 	buffer_append_c (&buf, '\0');
 | |
| 	if (buf.memory_failure)
 | |
| 	{
 | |
| 		ctx->memory_failure = true;
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	if (buf.len > 255)
 | |
| 		buf.s[255] = '\0';
 | |
| 
 | |
| 	printf ("PRIVMSG %s :%s%s\r\n", info->ctx, info->ctx_quote, buf.s);
 | |
| 	free (buf.s);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static void
 | |
| process_message (struct message *msg)
 | |
| {
 | |
| 	if (!msg->prefix
 | |
| 	 || strcasecmp (msg->command, "PRIVMSG")
 | |
| 	 || msg->n_params < 2)
 | |
| 		return;
 | |
| 	char *line = msg->params[1];
 | |
| 
 | |
| 	// Filter out only our commands
 | |
| 	size_t prefix_len = strlen (g_prefix);
 | |
| 	if (strncmp (line, g_prefix, prefix_len))
 | |
| 		return;
 | |
| 	line += prefix_len;
 | |
| 
 | |
| 	char *command = cut_word (&line);
 | |
| 	if (strcasecmp (command, "script"))
 | |
| 		return;
 | |
| 
 | |
| 	// Retrieve information on how to respond back
 | |
| 	char *msg_ctx = msg->prefix, *x;
 | |
| 	if ((x = strchr (msg_ctx, '!')))
 | |
| 		*x = '\0';
 | |
| 
 | |
| 	char *msg_ctx_quote;
 | |
| 	if (strchr ("#+&!", *msg->params[0]))
 | |
| 	{
 | |
| 		msg_ctx_quote = strdup_printf ("%s: ", msg_ctx);
 | |
| 		msg_ctx = msg->params[0];
 | |
| 	}
 | |
| 	else
 | |
| 		msg_ctx_quote = strdup ("");
 | |
| 
 | |
| 	if (!msg_ctx_quote)
 | |
| 	{
 | |
| 		printf (BOT_PRINT "%s\r\n", "memory allocation failure");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	struct user_info info;
 | |
| 	info.ctx = msg_ctx;
 | |
| 	info.ctx_quote = msg_ctx_quote;
 | |
| 
 | |
| 	// Finally parse and execute the macro
 | |
| 	const char *error = NULL;
 | |
| 	struct item *script = parse (line, &error);
 | |
| 	if (error)
 | |
| 	{
 | |
| 		printf ("PRIVMSG %s :%s%s: %s\r\n",
 | |
| 			msg_ctx, msg_ctx_quote, "parse error", error);
 | |
| 		goto end;
 | |
| 	}
 | |
| 
 | |
| 	struct context ctx;
 | |
| 	context_init (&ctx);
 | |
| 	ctx.user_data = &info;
 | |
| 	execute (&ctx, script);
 | |
| 	item_free_list (script);
 | |
| 
 | |
| 	const char *failure = NULL;
 | |
| 	if (ctx.memory_failure)
 | |
| 		failure = "memory allocation failure";
 | |
| 	else if (ctx.error)
 | |
| 		failure = ctx.error;
 | |
| 	if (failure)
 | |
| 		printf ("PRIVMSG %s :%s%s: %s\r\n",
 | |
| 			msg_ctx, msg_ctx_quote, "runtime error", failure);
 | |
| 	context_free (&ctx);
 | |
| end:
 | |
| 	free (msg_ctx_quote);
 | |
| }
 | |
| 
 | |
| int
 | |
| main (int argc, char *argv[])
 | |
| {
 | |
| 	freopen (NULL, "rb", stdin);   setvbuf (stdin,  NULL, _IOLBF, BUFSIZ);
 | |
| 	freopen (NULL, "wb", stdout);  setvbuf (stdout, NULL, _IOLBF, BUFSIZ);
 | |
| 
 | |
| 	struct rlimit limit =
 | |
| 	{
 | |
| 		.rlim_cur = ADDRESS_SPACE_LIMIT,
 | |
| 		.rlim_max = ADDRESS_SPACE_LIMIT
 | |
| 	};
 | |
| 
 | |
| 	// Lower the memory limits to something sensible to prevent abuse
 | |
| 	(void) setrlimit (RLIMIT_AS, &limit);
 | |
| 
 | |
| 	read_db ();
 | |
| 	if (!init_runtime_library ()
 | |
| 	 || !register_handler (".", fn_dot))
 | |
| 		printf (BOT_PRINT "%s\r\n", "runtime library initialization failed");
 | |
| 
 | |
| 	g_prefix = strdup (get_config ("prefix"));
 | |
| 	printf ("ZYKLONB register\r\n");
 | |
| 	struct message *msg;
 | |
| 	while ((msg = read_message ()))
 | |
| 		process_message (msg);
 | |
| 
 | |
| 	free_runtime_library ();
 | |
| 	free (g_prefix);
 | |
| 	return 0;
 | |
| }
 | |
| 
 |