Commit Diff


commit - be123ad0c5b63cea56aa028394f614e337b6c957
commit + c734c0e9abc0f228021c1ca2665da71645da46eb
blob - b63dbac99cfb77281563f7ca0ccb2559bc9f8cbe
blob + 4a0917b89f790a51eb33db5ac8a3f46ee81b4dd5
--- .gitignore
+++ .gitignore
@@ -26,6 +26,7 @@ kamirepl
 *.o
 kamid.conf
 parse.c
+np.c
 
 *.crt
 *.pem
blob - 962ff719f60e999c511d513a294599c24f9befa5
blob + 10533b85630a966bcbf049f0375f7d351ea40ac9
--- Makefile.am
+++ Makefile.am
@@ -1,4 +1,5 @@
-bin_PROGRAMS =		kamictl kamid kamirepl
+# TODO: 9ps should be only built for tests
+bin_PROGRAMS =		kamictl kamid kamirepl ninepscript
 
 EXTRA_kamid_SOURCES =	compat/ohash.h compat/imsg.h compat/queue.h compat/tree.h
 
@@ -39,6 +40,14 @@ kamirepl_SOURCES =	kamid.h		\
 			utils.c		\
 			utils.h
 
+ninepscript_SOURCES =	log.c		\
+			log.h		\
+			np.y		\
+			script.c	\
+			script.h	\
+			utils.c		\
+			utils.h
+
 LDADD =			$(LIBOBJS)
 
 BUILT_SOURCES =		compile_flags.txt
blob - /dev/null
blob + 54878d74254435a206ffdb0a0bee0066599691fd (mode 644)
--- /dev/null
+++ np.y
@@ -0,0 +1,614 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+
+#include "compat.h"
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "kamid.h"
+#include "utils.h"
+
+#include "script.h"
+
+TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+	TAILQ_ENTRY(file)	 entry;
+	FILE			*stream;
+	char			*name;
+	size_t	 		 ungetpos;
+	size_t			 ungetsize;
+	u_char			*ungetbuf;
+	int			 eof_reached;
+	int			 lineno;
+	int			 errors;
+} *file, *topfile;
+struct file	*pushfile(const char *);
+int		 popfile(void);
+int		 yyparse(void);
+int		 yylex(void);
+int		 yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+int		 kw_cmp(const void *, const void *);
+int		 lookup(char *);
+int		 igetc(void);
+int		 lgetc(int);
+void		 lungetc(int);
+int		 findeol(void);
+
+typedef struct {
+	union {
+		struct op	*op;
+		struct proc	*proc;
+		char		*str;
+		int64_t		 num;
+	} v;
+	int		 lineno;
+} YYSTYPE;
+
+static struct op	base_arg;
+
+%}
+
+/*
+ * for bison:
+ * %define parse.error verbose
+ */
+
+%token	ASSERT
+%token	CONST
+%token	DIR
+%token	ERROR
+%token	INCLUDE
+%token	PROC
+%token	REPEAT
+%token	STR
+%token	TESTING
+%token	U8 U16 U32
+
+%token	<v.str>		STRING SYMBOL
+%token	<v.num>		NUMBER
+
+%type	<v.op>		cast cexpr check expr funcall
+%type	<v.op>		literal var varref
+
+%type	<v.proc>	procname
+
+%%
+
+program : /* empty */
+	| program '\n'
+	| program include '\n'
+	| program const '\n'
+	| program proc '\n'
+	| program test '\n'
+	;
+
+optnl		: '\n' optnl		/* zero or more newlines */
+		| /*empty*/
+		;
+
+nl		: '\n' optnl		/* one or more newlines */
+		;
+
+include : INCLUDE STRING {
+		struct file	*nfile;
+
+		if ((nfile = pushfile($2)) == NULL) {
+			yyerror("failed to include file %s", $2);
+			free($2);
+			YYERROR;
+		}
+		free($2);
+
+		file = nfile;
+		lungetc('\n');
+	}
+	;
+
+const	: CONST SYMBOL '=' literal { global_set($2, $4); };
+
+var	: SYMBOL '=' expr	{ $$ = op_assign($1, $3); }	;
+varref	: SYMBOL		{ $$ = op_var($1); }		;
+literal	: STRING		{ $$ = op_lit_str($1); }
+	| NUMBER		{ $$ = op_lit_num($1); }	;
+
+/*
+ * `expr '=' '=' expr` is ambiguous.  furthermore, we're not
+ * interested in checking all the possibilities here.
+ */
+cexpr	: literal | varref | funcall ;
+check	: cexpr '=' '=' cexpr	{ $$ = op_cmp_eq($1, $4); }	;
+
+expr	: literal | funcall | varref | check | cast ;
+
+cast	: expr ':' U8	{ $$ = op_cast($1, V_U8); }
+	| expr ':' U16	{ $$ = op_cast($1, V_U16); }
+	| expr ':' U32	{ $$ = op_cast($1, V_U32); }
+	| expr ':' STR	{ $$ = op_cast($1, V_STR); }
+	;
+
+procname: SYMBOL {
+		if (($$ = proc_by_name($1)) == NULL) {
+			yyerror("unknown proc %s", $1);
+			free($1);
+			YYERROR;
+		}
+		free($1);
+	}
+	;
+
+funcall	: procname {
+		prepare_funcall(&base_arg);
+	} '(' args optcomma ')' {
+		$$ = op_funcall($1, &base_arg);
+	}
+	;
+
+optcomma: /* empty */ | ',' ;
+
+args	: /* empty */
+	| args ',' expr	{ push_arg($3); }
+	| expr		{ push_arg($1); }
+	;
+
+proc	: PROC SYMBOL {
+		prepare_proc($2);
+	} '(' args ')' {
+		proc_setup_body();
+	} '{' optnl block '}' {
+		proc_done();
+	}
+	;
+
+block	: /* empty */
+	| block var '\n'	{ block_push($2); }
+	| block funcall '\n'	{ block_push($2); }
+	| block assert '\n'
+	;
+
+assert	: ASSERT check		{ block_push(op_assert($2)); }
+	/* | ASSERT '(' optnl massert ')' */
+	;
+
+massert	: /* empty */
+	| massert ',' optnl check	{ block_push(op_assert($4)); }
+	| check				{ block_push(op_assert($1)); }
+	;
+
+test	: TESTING STRING DIR STRING {
+		prepare_test($2, $4);
+	} '{' optnl block '}' {
+		test_done();
+	}
+	;
+
+%%
+
+struct keywords {
+	const char	*k_name;
+	int		 k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+	va_list		 ap;
+	char		*msg;
+
+	file->errors++;
+	va_start(ap, fmt);
+	if (vasprintf(&msg, fmt, ap) == -1)
+		fatalx("yyerror vasprintf");
+	va_end(ap);
+	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+	free(msg);
+	return 0;
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+	return strcmp(k, ((const struct keywords *)e)->k_name);
+}
+
+int
+lookup(char *s)
+{
+	/* This has to be sorted always. */
+	static const struct keywords keywords[] = {
+		{"assert",	ASSERT},
+		{"const",	CONST},
+		{"dir",		DIR},
+		{"include",	INCLUDE},
+		{"proc",	PROC},
+		{"repeat",	REPEAT},
+		{"str",		STR},
+		{"testing",	TESTING},
+		{"u8",		U8},
+		{"u16",		U16},
+		{"u32",		U32},
+	};
+	const struct keywords	*p;
+
+	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+	    sizeof(keywords[0]), kw_cmp);
+
+	if (p)
+		return p->k_val;
+	else
+		return SYMBOL;
+}
+
+#define START_EXPAND	1
+#define DONE_EXPAND	2
+
+static int	expanding;
+
+int
+igetc(void)
+{
+	int	c;
+
+	while (1) {
+		if (file->ungetpos > 0)
+			c = file->ungetbuf[--file->ungetpos];
+		else
+			c = getc(file->stream);
+
+		if (c == START_EXPAND)
+			expanding = 1;
+		else if (c == DONE_EXPAND)
+			expanding = 0;
+		else
+			break;
+	}
+	return c;
+}
+
+int
+lgetc(int quotec)
+{
+	int		c, next;
+
+	if (quotec) {
+		if ((c = igetc()) == EOF) {
+			yyerror("reached end of file while parsing "
+			    "quoted string");
+			if (file == topfile || popfile() == EOF)
+				return EOF;
+			return quotec;
+		}
+		return c;
+	}
+
+	while ((c = igetc()) == '\\') {
+		next = igetc();
+		if (next != '\n') {
+			c = next;
+			break;
+		}
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+
+	if (c == EOF) {
+		/*
+		 * Fake EOL when hit EOF for the first time. This gets line
+		 * count right if last line in included file is syntactically
+		 * invalid and has no newline.
+		 */
+		if (file->eof_reached == 0) {
+			file->eof_reached = 1;
+			return '\n';
+		}
+		while (c == EOF) {
+			if (file == topfile || popfile() == EOF)
+				return EOF;
+			c = igetc();
+		}
+	}
+	return c;
+}
+
+void
+lungetc(int c)
+{
+	if (c == EOF)
+		return;
+
+	if (file->ungetpos >= file->ungetsize) {
+		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
+		if (p == NULL)
+			err(1, "lungetc");
+		file->ungetbuf = p;
+		file->ungetsize *= 2;
+	}
+	file->ungetbuf[file->ungetpos++] = c;
+}
+
+int
+findeol(void)
+{
+	int	c;
+
+	/* Skip to either EOF or the first real EOL. */
+	while (1) {
+		c = lgetc(0);
+		if (c == '\n') {
+			file->lineno++;
+			break;
+		}
+		if (c == EOF)
+			break;
+	}
+	return ERROR;
+}
+
+
+#if 0
+int my_yylex(void);
+
+int
+yylex(void)
+{
+	int x;
+
+	switch (x = my_yylex()) {
+	case ASSERT: puts("assert"); break;
+	case CONST: puts("const"); break;
+	case DIR: puts("dir"); break;
+	case ERROR: puts("error"); break;
+	case INCLUDE: puts("include"); break;
+	case PROC: puts("proc"); break;
+	case REPEAT: puts("repeat"); break;
+	case STR: puts(":str"); break;
+	case TESTING: puts("testing"); break;
+	case U8: puts(":u8"); break;
+	case U16: puts(":u16"); break;
+	case U32: puts(":u32"); break;
+
+	case STRING: printf("string \"%s\"\n", yylval.v.str); break;
+	case SYMBOL: printf("symbol %s\n", yylval.v.str); break;
+	case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break;
+
+	default:
+		printf("character ");
+		if (x == '\n')
+			printf("\\n");
+		else
+			printf("%c", x);
+		printf(" [0x%x]", x);
+		printf("\n");
+		break;
+	}
+
+	return x;
+}
+
+int
+my_yylex(void)
+#else
+int
+yylex(void)
+#endif
+{
+	unsigned char	 buf[8096];
+	unsigned char	*p;
+	int		 quotec, next, c;
+	int		 token;
+
+	p = buf;
+	while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f')
+		; /* nop */
+
+	yylval.lineno = file->lineno;
+	if (c == '#')
+		while ((c = lgetc(0)) != '\n' && c != EOF)
+			; /* nop */
+
+	switch (c) {
+	case '\'':
+	case '\"':
+		quotec = c;
+		while (1) {
+			if ((c = lgetc(quotec)) == EOF)
+				return 0;
+			if (c == '\n') {
+				file->lineno++;
+				continue;
+			} else if (c == '\\') {
+				if ((next = lgetc(quotec)) == EOF)
+					return 0;
+				if (next == quotec || next == ' ' ||
+				    next == '\t')
+					c = next;
+				else if (next == '\n') {
+					file->lineno++;
+					continue;
+				} else
+					lungetc(next);
+			} else if (c == quotec) {
+				*p = '\0';
+				break;
+			} else if (c == '\0') {
+				yyerror("syntax error");
+				return findeol();
+			}
+
+			if (p + 1 >= buf + sizeof(buf) - 1) {
+				yyerror("string too long");
+				return findeol();
+			}
+
+			*p++ = c;
+		}
+
+		yylval.v.str = xstrdup(buf);
+		return STRING;
+	}
+
+#define allowed_to_end_number(x) \
+	(isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \
+	    || x == '=')
+
+	if (c == '-' || isdigit(c)) {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && isdigit(c));
+		lungetc(c);
+		if (p == buf + 1 && buf[0] == '-')
+			goto nodigits;
+		if (c == EOF || allowed_to_end_number(c)) {
+			const char *errstr = NULL;
+
+			*p = '\0';
+			yylval.v.num = strtonum(buf, INT64_MIN, INT64_MAX,
+			    &errstr);
+			if (errstr) {
+				yyerror("\"%s\" invalid number: %s",
+				    buf, errstr);
+				return findeol();
+			}
+			return NUMBER;
+		} else {
+nodigits:
+			while (p > buf + 1)
+				lungetc(*--p);
+			c = *--p;
+			if (c == '-')
+				return c;
+		}
+	}
+
+#define allowed_in_symbol(x)				\
+	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+	    x != '{' && x != '}' && \
+	    x != '!' && x != '=' && \
+	    x != '#' && x != ','))
+
+	if (isalnum(c) || c == ':' || c == '_') {
+		do {
+			*p++ = c;
+			if ((size_t)(p-buf) >= sizeof(buf)) {
+				yyerror("string too long");
+				return findeol();
+			}
+		} while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c)));
+		lungetc(c);
+		*p = '\0';
+		if ((token = lookup(buf)) == SYMBOL)
+			yylval.v.str = xstrdup(buf);
+		return token;
+	}
+
+	if (c == '\n') {
+		yylval.lineno = file->lineno;
+		file->lineno++;
+	}
+	if (c == EOF)
+		return 0;
+	return c;
+}
+
+struct file *
+pushfile(const char *name)
+{
+	struct file	*nfile;
+
+	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+		log_warn("calloc");
+		return NULL;
+	}
+	if ((nfile->name = strdup(name)) == NULL) {
+		log_warn("strdup");
+		free(nfile);
+		return NULL;
+	}
+	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+		log_warn("%s", nfile->name);
+		free(nfile->name);
+		free(nfile);
+		return NULL;
+	}
+	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
+	nfile->ungetsize = 16;
+	nfile->ungetbuf = malloc(nfile->ungetsize);
+	if (nfile->ungetbuf == NULL) {
+		log_warn("malloc");
+		fclose(nfile->stream);
+		free(nfile->name);
+		free(nfile);
+		return NULL;
+	}
+	TAILQ_INSERT_TAIL(&files, nfile, entry);
+	return nfile;
+}
+
+int
+popfile(void)
+{
+	struct file	*prev;
+
+	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+		prev->errors += file->errors;
+
+	TAILQ_REMOVE(&files, file, entry);
+	fclose(file->stream);
+	free(file->name);
+	free(file->ungetbuf);
+	free(file);
+	file = prev;
+	return file ? 0 : EOF;
+}
+
+void
+loadfile(const char *path)
+{
+	int errors;
+
+	file = pushfile(path);
+	if (file == NULL)
+		err(1, "pushfile");
+	topfile = file;
+
+	yyparse();
+	errors = file->errors;
+	popfile();
+
+	if (errors)
+		errx(1, "can't load %s because of errors", path);
+}
blob - /dev/null
blob + 7d2891d503c4e27fb09e1586c812d32c97a85882 (mode 644)
--- /dev/null
+++ regress/simple.9ps
@@ -0,0 +1,10 @@
+proc test() {
+	dummy(30)
+}
+
+testing "dummy" dir "./root" {
+	# test()
+	assert 5 == 7
+	assert 7 == 9
+	assert 8 == 0
+}
blob - /dev/null
blob + a06453a45a642ff63f4cea7bdd9f73c2417edfb5 (mode 644)
--- /dev/null
+++ script.c
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "utils.h"
+#include "script.h"
+#include "log.h"
+
+#define DEBUG 0
+
+static struct procs procs = TAILQ_HEAD_INITIALIZER(procs);
+static struct tests tests = TAILQ_HEAD_INITIALIZER(tests);
+
+static struct proc	*curr_proc;
+static struct test	*curr_test;
+
+static struct op	*curr_block;
+
+static struct op	*curr_argv;
+static int		 curr_argc;
+
+#define STACK_HEIGHT 16
+static struct value	vstack[STACK_HEIGHT];
+static int		stackh;
+
+static struct value v_false = {.type = V_NUM, .v = {.num = 0}};
+static struct value v_true  = {.type = V_NUM, .v = {.num = 1}};
+
+void
+global_set(char *sym, struct op *op)
+{
+	assert(op->type == OP_LITERAL);
+}
+
+struct op *
+newop(int type)
+{
+	struct op *op;
+
+        op = xcalloc(1, sizeof(*op));
+	op->type = type;
+
+	return op;
+}
+
+void
+free_op(struct op *op)
+{
+	/* TODO: probably more... */
+	free(op);
+}
+
+struct op *
+op_assign(char *sym, struct op *expr)
+{
+	struct op *op;
+
+	op = newop(OP_ASSIGN);
+	op->v.assign.name = sym;
+	op->v.assign.expr = expr;
+
+	return op;
+}
+
+struct op *
+op_assert(struct op *expr)
+{
+	struct op *op;
+
+	op = newop(OP_ASSERT);
+	op->v.assert = expr;
+
+	return op;
+}
+
+struct op *
+op_var(char *sym)
+{
+	struct op *op;
+
+	op = newop(OP_VAR);
+	op->v.var = sym;
+
+	return op;
+}
+
+struct op *
+op_lit_str(char *str)
+{
+	struct op *op;
+
+	op = newop(OP_LITERAL);
+	op->v.literal.type = V_NUM;
+	op->v.literal.v.str = str;
+
+	return op;
+}
+
+struct op *
+op_lit_num(uint64_t n)
+{
+	struct op *op;
+
+	op = newop(OP_LITERAL);
+	op->v.literal.type = V_NUM;
+	op->v.literal.v.num = n;
+
+	return op;
+}
+
+struct op *
+op_cmp_eq(struct op *a, struct op *b)
+{
+	struct op *op;
+
+	op = newop(OP_CMP_EQ);
+	op->v.cmp_eq.a = a;
+	op->v.cmp_eq.b = b;
+
+	return op;
+}
+
+struct op *
+op_cast(struct op *expr, int totype)
+{
+	struct op *op;
+
+	op = newop(OP_CAST);
+	op->v.cast.expr = expr;
+	op->v.cast.totype = totype;
+
+	return op;
+}
+
+void
+pp_val(struct value *val)
+{
+	switch (val->type) {
+	case V_SYM:
+		printf("%s", val->v.str);
+		break;
+	case V_STR:
+		printf("\"%s\"", val->v.str);
+		break;
+	case V_NUM:
+	case V_U8:
+	case V_U16:
+	case V_U32:
+		printf("%"PRIu64, val->v.num);
+		break;
+	default:
+		printf("<unknown value>");
+		break;
+	}
+}
+
+int
+val_trueish(struct value *a)
+{
+	return a->type == V_NUM && a->v.num;
+}
+
+static inline int
+val_isnum(struct value *a)
+{
+	return a->type == V_NUM
+		|| a->type == V_U8
+		|| a->type == V_U16
+		|| a->type == V_U32;
+}
+
+int
+val_eq(struct value *a, struct value *b)
+{
+	if (val_isnum(a) && val_isnum(b))
+		return a->v.num == b->v.num;
+
+	if (a->type != b->type)
+		return 0;
+
+	switch (a->type) {
+	case V_STR:
+	case V_SYM:
+		return !strcmp(a->v.str, b->v.str);
+	}
+
+	return 0;
+}
+
+void
+pp_op(struct op *op)
+{
+	switch (op->type) {
+	case OP_ASSIGN:
+		printf("%s = ", op->v.assign.name);
+                pp_op(op->v.assign.expr);
+		break;
+	case OP_ASSERT:
+		printf("assert ");
+		pp_op(op->v.assert);
+		break;
+	case OP_FUNCALL:
+		printf("funcall()");
+		break;
+	case OP_LITERAL:
+                pp_val(&op->v.literal);
+		break;
+	case OP_VAR:
+		printf("%s", op->v.var);
+		break;
+	case OP_CAST:
+		pp_op(op->v.cast.expr);
+		printf(":");
+		switch (op->v.cast.totype) {
+		case V_U8: printf("u8"); break;
+		case V_U16: printf("u16"); break;
+		case V_U32: printf("u32"); break;
+		case V_STR: printf("str"); break;
+		default: printf("???"); break;
+		}
+		break;
+	case OP_CMP_EQ:
+		pp_op(op->v.cmp_eq.a);
+		printf(" == ");
+		pp_op(op->v.cmp_eq.b);
+		break;
+	default:
+		printf(" ???[%d] ", op->type);
+	}
+}
+
+void
+pp_block(struct op *op)
+{
+        while (op != NULL) {
+		printf("> ");
+		pp_op(op);
+		printf("\n");
+
+		op = op->next;
+	}
+}
+
+static inline void
+popv(struct value *v)
+{
+	if (stackh == 0)
+		errx(1, "can't pop the stack: underflow");
+	memcpy(v, &vstack[--stackh], sizeof(*v));
+
+#if DEBUG
+	printf("popping "); pp_val(v); printf("\n");
+#endif
+}
+
+static inline void
+pushv(struct value *v)
+{
+	if (stackh == STACK_HEIGHT)
+		errx(1, "can't push the stack: overflow");
+
+#if DEBUG
+	printf("pushing "); pp_val(v); printf("\n");
+#endif
+
+	memcpy(&vstack[stackh++], v, sizeof(*v));
+}
+
+static inline void
+pushbool(int n)
+{
+	pushv(n ? &v_true : &v_false);
+}
+
+int
+eval(struct op *op)
+{
+	struct value	 a, b;
+	struct proc	*proc;
+	struct op	*t;
+	int		 i, ret;
+
+#if DEBUG
+        pp_op(op);
+	printf("\n");
+#endif
+
+	switch (op->type) {
+	case OP_ASSIGN:
+		printf("TODO: assignment\n");
+		break;
+
+	case OP_ASSERT:
+		if ((ret = eval(op->v.assert)) != TEST_PASSED)
+			return ret;
+                popv(&a);
+                if (!val_trueish(&a)) {
+			printf("assertion failed: ");
+			pp_op(op->v.assert);
+			printf("\n");
+			return TEST_FAILED;
+		}
+		break;
+
+	case OP_FUNCALL:
+		/* TODO: arity check! */
+
+		for (i = 0; i < op->v.funcall.argc; ++i) {
+			t = &op->v.funcall.argv[i];
+			if ((ret = eval(t)) != TEST_PASSED)
+				return ret;
+		}
+
+                proc = op->v.funcall.proc;
+		if (proc->nativefn != NULL)
+			proc->nativefn(i);
+		else if ((ret = eval(proc->body)) != TEST_PASSED)
+				return ret;
+		break;
+
+	case OP_LITERAL:
+		pushv(&op->v.literal);
+		break;
+
+	case OP_VAR:
+		printf("TODO: load variable\n");
+		break;
+
+	case OP_CAST:
+		printf("TODO: cast value\n");
+		break;
+
+	case OP_CMP_EQ:
+		if ((ret = eval(op->v.cmp_eq.a)) != TEST_PASSED)
+			return ret;
+		if ((ret = eval(op->v.cmp_eq.b)) != TEST_PASSED)
+			return ret;
+
+		popv(&b);
+		popv(&a);
+		pushbool(val_eq(&a, &b));
+
+		break;
+
+	default:
+		abort();
+	}
+
+	if (op->next)
+		return eval(op->next);
+	return TEST_PASSED;
+}
+
+void
+prepare_funcall(struct op *base)
+{
+	if (curr_argv != NULL)
+		err(1, "can't funcall during funcall");
+
+	curr_argv = base;
+	curr_argc = 0;
+}
+
+void
+push_arg(struct op *op)
+{
+	curr_argv->next = op;
+	curr_argv = op;
+	curr_argc++;
+}
+
+struct op *
+op_funcall(struct proc *proc, struct op *base)
+{
+	struct op *op;
+
+	op = newop(OP_FUNCALL);
+	op->v.funcall.proc = proc;
+	op->v.funcall.argv = base->next;
+	op->v.funcall.argc = curr_argc;
+
+	curr_argv = NULL;
+
+	return op;
+}
+
+void
+add_builtin_proc(const char *name, int (*fn)(int))
+{
+	struct proc *proc;
+
+	proc = xcalloc(1, sizeof(*proc));
+	proc->name = xstrdup(name);
+	proc->nativefn = fn;
+
+	TAILQ_INSERT_HEAD(&procs, proc, entry);
+}
+
+void
+prepare_proc(char *name)
+{
+	if (curr_proc != NULL)
+		err(1, "can't recursively create a proc!");
+
+	curr_proc = xcalloc(1, sizeof(*curr_proc));
+	curr_proc->name = name;
+
+	curr_argv = &curr_proc->tmp_args;
+
+	curr_argv = NULL;
+	curr_argc = 0;
+}
+
+void
+proc_setup_body(void)
+{
+	struct op *next, *op = curr_proc->tmp_args.next;
+	int i;
+
+	i = 0;
+	while (op != NULL) {
+		if (op->type != OP_VAR)
+			errx(1, "invalid argument in proc definition: "
+			    "got type %d but want OP_VAR", op->type);
+		assert(i < curr_argc && curr_argc < MAXWELEM);
+		curr_proc->args[i] = xstrdup(op->v.var);
+		next = op->next;
+		free_op(op);
+	}
+
+	curr_proc->minargs = curr_argc;
+}
+
+void
+proc_done(void)
+{
+	TAILQ_INSERT_HEAD(&procs, curr_proc, entry);
+
+	curr_proc = NULL;
+}
+
+void
+block_push(struct op *op)
+{
+	if (curr_block == NULL) {
+		curr_proc->body = op;
+		curr_block = op;
+	} else {
+		curr_block->next = op;
+		curr_block = op;
+	}
+}
+
+struct proc *
+proc_by_name(const char *name)
+{
+	struct proc *p;
+
+	TAILQ_FOREACH(p, &procs, entry) {
+		if (!strcmp(p->name, name))
+			return p;
+	}
+
+	return NULL;
+}
+
+void
+prepare_test(char *name, char *dir)
+{
+	assert(curr_test == NULL);
+
+	prepare_proc(xstrdup("<test-internal>"));
+
+	curr_test = xcalloc(1, sizeof(*curr_test));
+	curr_test->name = name;
+	curr_test->dir = dir;
+	curr_test->proc = curr_proc;
+}
+
+void
+test_done(void)
+{
+	TAILQ_INSERT_HEAD(&tests, curr_test, entry);
+	curr_test = NULL;
+}
+
+static int
+builtin_dummy(int argc)
+{
+	printf("dummy! yay!\n");
+	return 0;
+}
+
+static int
+run_test(struct test *t)
+{
+#if DEBUG
+	puts("=====================");
+	pp_block(t->proc->body);
+	puts("=====================");
+#endif
+
+	return eval(t->proc->body);
+}
+
+int
+main(int argc, char **argv)
+{
+	struct test	*t;
+	int		 i, passed = 0, failed = 0, skipped = 0;
+
+	log_init(1, LOG_DAEMON);
+	log_setverbose(1);
+
+	add_builtin_proc("dummy", builtin_dummy);
+
+	for (i = 1; i < argc; ++i)
+		loadfile(argv[i]);
+
+	TAILQ_FOREACH(t, &tests, entry) {
+                switch (run_test(t)) {
+		case TEST_PASSED: passed++; break;
+		case TEST_FAILED: failed++; break;
+		case TEST_SKIPPED: skipped++; break;
+		}
+	}
+
+	printf("passed = %d\n", passed);
+	printf("failed = %d\n", failed);
+	printf("skipped = %d\n", skipped);
+
+	return failed != 0;
+}
blob - /dev/null
blob + ad3c7e7c91c090d190de2c8b416c57a58b6ff21c (mode 644)
--- /dev/null
+++ script.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef SCRIPT_H
+#define SCRIPT_H
+
+#include "kamid.h"
+
+enum {
+	/* literals */
+	V_SYM,
+	V_STR,
+	V_NUM,
+
+	/* foreign */
+	V_QID,
+
+	/* casted */
+	V_U8,
+	V_U16,
+	V_U32,
+};
+
+struct value {
+	int type;
+	union {
+		char		*str;
+		uint64_t	 num;
+		uint8_t		 qid[QIDSIZE];
+	} v;
+};
+
+enum {
+	OP_ASSIGN,
+	OP_ASSERT,
+	OP_FUNCALL,
+	OP_LITERAL,
+	OP_VAR,
+	OP_CAST,
+	OP_CMP_EQ,
+};
+
+struct proc;
+
+struct op {
+	struct op	*next;
+	int		 type;
+	union {
+		struct {
+			char		*name;
+			struct op	*expr;
+		} assign;
+		struct op		*assert;
+		struct {
+			struct proc	*proc;
+			struct op	*argv;
+			int		 argc;
+		} funcall;
+		struct value literal;
+		char *var;
+		struct {
+			struct op	*expr;
+			int		 totype;
+		} cast;
+		struct {
+			struct op	*a;
+			struct op	*b;
+		} cmp_eq;
+	} v;
+};
+
+TAILQ_HEAD(procs, proc);
+struct proc {
+	TAILQ_ENTRY(proc)	 entry;
+	char			*name;
+	int			 minargs;
+	int			 varargs;
+	char			*args[MAXWELEM];
+	struct op		 tmp_args;
+	struct op		*body;
+	int			(*nativefn)(int);
+};
+
+TAILQ_HEAD(tests, test);
+struct test {
+	TAILQ_ENTRY(test)	 entry;
+	char			*name;
+	char			*dir;
+	struct proc		*proc;
+};
+
+enum {
+	TEST_PASSED,
+	TEST_FAILED,
+	TEST_SKIPPED,
+};
+
+void		 global_set(char *, struct op *);
+
+struct op	*newop(int);
+void		 free_op(struct op *);
+struct op	*op_assign(char *, struct op *);
+struct op	*op_assert(struct op *);
+struct op	*op_var(char *);
+struct op	*op_lit_str(char *);
+struct op	*op_lit_num(uint64_t);
+struct op	*op_cmp_eq(struct op *, struct op *);
+struct op	*op_cast(struct op *, int);
+
+void		 pp_val(struct value *);
+int		 val_trueish(struct value *);
+int		 val_eq(struct value *, struct value *);
+void		 pp_op(struct op *);
+void		 pp_block(struct op *);
+int		 eval(struct op *);
+
+/* funcall */
+void		 prepare_funcall(struct op *);
+void		 push_arg(struct op *);
+struct op	*op_funcall(struct proc *, struct op *);
+
+/* proc */
+void		 add_builtin_proc(const char *name, int (*)(int));
+void		 prepare_proc(char *);
+/* push_arg works on procs too */
+void		 proc_setup_body(void);
+void		 proc_done(void);
+void		 block_push(struct op *);
+struct proc	*proc_by_name(const char *);
+
+/* testing */
+void		 prepare_test(char *, char *);
+void		 test_done(void);
+
+/* np.y */
+void		 loadfile(const char *);
+
+#endif