commit c734c0e9abc0f228021c1ca2665da71645da46eb from: Omar Polo date: Tue Aug 03 20:13:23 2021 UTC first draft of the ninepscript interpreter the ninepscript is meant as a way to test kamid. The idea is to execute these scripts that sends and receive 9p messages and ensure that the daemon works correctly in various situations. 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 + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * 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 +#include +#include +#include +#include +#include +#include + +#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 STRING SYMBOL +%token NUMBER + +%type cast cexpr check expr funcall +%type literal var varref + +%type 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 + * + * 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 +#include +#include +#include +#include +#include + +#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(""); + 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("")); + + 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 + * + * 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