commit - be123ad0c5b63cea56aa028394f614e337b6c957
commit + c734c0e9abc0f228021c1ca2665da71645da46eb
blob - b63dbac99cfb77281563f7ca0ccb2559bc9f8cbe
blob + 4a0917b89f790a51eb33db5ac8a3f46ee81b4dd5
--- .gitignore
+++ .gitignore
*.o
kamid.conf
parse.c
+np.c
*.crt
*.pem
blob - 962ff719f60e999c511d513a294599c24f9befa5
blob + 10533b85630a966bcbf049f0375f7d351ea40ac9
--- Makefile.am
+++ Makefile.am
-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
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
+/*
+ * 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
+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
+/*
+ * 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
+/*
+ * 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