~lbnz/xr0

31703f78e04f4c183775f7bd40fd401fd64e1035 — Claude Betz 6 months ago c5f81fd
feat: implement topological sort

issue: https://github.com/xr0-org/xr0/issues/22
M Makefile => Makefile +1 -0
@@ 150,6 150,7 @@ $(UTIL_OBJ): $(UTIL_DIR)/util.c $(BUILD_DIR)
	@$(CC) $(CFLAGS) -o $@ -c $(UTIL_DIR)/util.c

$(AST_OBJ): $(AST_DIR)/ast.c \
	$(AST_DIR)/topological.h \
	$(AST_DIR)/expr/expr.h \
	$(AST_DIR)/type/type.h \
	$(AST_DIR)/literals.h \

M include/ast.h => include/ast.h +7 -0
@@ 1,6 1,7 @@
#ifndef XR0_AST_H
#define XR0_AST_H
#include <stdbool.h>
#include "util.h"

struct ast_expr;



@@ 543,4 544,10 @@ ast_append(struct ast *, struct ast_externdecl *);
void
ast_destroy(struct ast *);

struct string_arr *
ast_topological_order(char *fname, struct externals *ext);

struct ast_function *
ast_protostitch(struct ast_function *, struct externals *);

#endif

M include/util.h => include/util.h +37 -1
@@ 16,7 16,7 @@ struct map {
		char *key;
		const void *value;
	} *entry;
	size_t n;
	int n;
};

struct map *


@@ 67,4 67,40 @@ error_create(char *s);
struct error *
error_prepend(struct error *, char *msg);

/* XXX: string_arr: to be macro */

struct string_arr {
	int n;
	char **s;
};

struct string_arr *
string_arr_create();

void
string_arr_destroy();

char **
string_arr_s(struct string_arr *);

int
string_arr_n(struct string_arr *);

int
string_arr_append(struct string_arr *, char *);

char *
string_arr_deque(struct string_arr *);

struct string_arr *
string_arr_concat(struct string_arr *s1, struct string_arr *s2);

bool
string_arr_contains(struct string_arr *, char *s);

char *
string_arr_str(struct string_arr *);

struct externals;

#endif

M libx/ctype.h => libx/ctype.h +10 -0
@@ 0,0 1,10 @@
#ifndef CTYPE_H
#define CTYPE_H

axiom int
isalpha(int c);

axiom int
isspace(char *);

#endif

M libx/stdlib.h => libx/stdlib.h +4 -0
@@ 8,4 8,8 @@ malloc(int size) ~ [ .alloc result; ];
axiom void
free(void *ptr) ~ [ .dealloc ptr; ];

/* XXX: how to represent macros? */
axiom void
exit(int);

#endif

M libx/string.h => libx/string.h +3 -0
@@ 11,4 11,7 @@ strncpy(char *dest, char *src, size_t n);
axiom size_t
strlen(char *s);

axiom int
strncmp(char *s1, char *s2, size_t n);

#endif

M src/0v/ast/ast.c => src/0v/ast/ast.c +14 -0
@@ 8,6 8,7 @@
#include "util.h"

#include "expr/expr.c"
#include "topological.c"
#include "block.c"
#include "stmt/stmt.c"
#include "type/type.c"


@@ 201,3 202,16 @@ preresult_iscontradiction(struct preresult *r)
{
	return r->iscontradiction;
}

struct string_arr *
ast_topological_order(char *fname, struct externals *ext)
{
	return topological_order(fname, ext);
}

struct ast_function *
ast_protostitch(struct ast_function *f, struct externals *ext)
{
	return ast_function_protostitch(f, ext);
}


M src/0v/ast/expr/expr.c => src/0v/ast/expr/expr.c +52 -0
@@ 831,4 831,56 @@ binary_e2(struct ast_expr *e2, enum ast_binary_operator op)
	}
}

static struct string_arr *
ast_expr_call_getfuncs(struct ast_expr *expr);

struct string_arr *
ast_expr_getfuncs(struct ast_expr *expr)
{
	switch (expr->kind) {
	case EXPR_IDENTIFIER:
	case EXPR_CONSTANT:
	case EXPR_STRING_LITERAL:
	case EXPR_STRUCTMEMBER:
	case EXPR_ISDEALLOCAND:
	case EXPR_ARBARG:
		return string_arr_create();	
	case EXPR_CALL:
		return ast_expr_call_getfuncs(expr);
	case EXPR_BRACKETED:
	case EXPR_UNARY:
	case EXPR_INCDEC:
		return ast_expr_getfuncs(expr->root);
	case EXPR_ASSIGNMENT:
		return string_arr_concat(
			ast_expr_getfuncs(expr->root),
			ast_expr_getfuncs(expr->u.assignment_value)
		);
	case EXPR_BINARY:
		return string_arr_concat(
			ast_expr_getfuncs(expr->u.binary.e1),
			ast_expr_getfuncs(expr->u.binary.e2)
		);
	default:
		assert(false);
	}
}

static struct string_arr *
ast_expr_call_getfuncs(struct ast_expr *expr)
{
	struct string_arr *res = string_arr_create();
	struct ast_expr *root = expr->root;
	assert(root->kind == EXPR_IDENTIFIER);
	string_arr_append(res, dynamic_str(root->u.string));
	for (int i = 0; i < expr->u.call.n; i++) {
		res = string_arr_concat(
			res,
			ast_expr_getfuncs(expr->u.call.arg[i])
		);	
		/* XXX: leaks */
	}
	return res;
}

#include "verify.c"

M src/0v/ast/expr/expr.h => src/0v/ast/expr/expr.h +5 -0
@@ 1,6 1,8 @@
#ifndef XR0_AST_EXPR_H
#define XR0_AST_EXPR_H

#include "util.h"

struct ast_expr {
	enum ast_expr_kind {
		EXPR_IDENTIFIER		= 1 << 0,


@@ 70,4 72,7 @@ ast_expr_binary_create(struct ast_expr *e1, enum ast_binary_operator,
enum ast_binary_operator
ast_expr_binary_op(struct ast_expr *expr);

struct string_arr *
ast_expr_getfuncs(struct ast_expr *);

#endif

M src/0v/ast/function/function.c => src/0v/ast/function/function.c +74 -7
@@ 5,6 5,7 @@

#include "ast.h"
#include "function.h"
#include "stmt/stmt.h"
#include "intern.h"
#include "object.h"
#include "props.h"


@@ 169,22 170,17 @@ ast_function_params(struct ast_function *f)
struct error *
paths_verify(struct ast_function_arr *paths, struct externals *);

struct ast_function *
proto_stitch(struct ast_function *f, struct externals *);

struct error *
ast_function_verify(struct ast_function *f, struct externals *ext)
{
	struct ast_function *proto = proto_stitch(f, ext);

	struct ast_function_arr *paths = paths_fromfunction(proto);
	struct ast_function_arr *paths = paths_fromfunction(f);
	struct error *err = paths_verify(paths, ext);
	ast_function_arr_destroy(paths);
	return err;
}

struct ast_function *
proto_stitch(struct ast_function *f, struct externals *ext)
ast_function_protostitch(struct ast_function *f, struct externals *ext)
{
	struct ast_function *proto = externals_getfunc(ext, f->name);



@@ 359,5 355,76 @@ ast_function_absexec(struct ast_function *f, struct state *state)
	return result_value_create(object_as_value(obj));
}

static void
recurse_buildgraph(struct map *g, struct map *dedup, char *fname, struct externals *ext);

struct map *
ast_function_buildgraph(char *fname, struct externals *ext)
{
	struct map *dedup = map_create(),
		   *g = map_create();
	
	recurse_buildgraph(g, dedup, fname, ext);

	return g;
}

static void
recurse_buildgraph(struct map *g, struct map *dedup, char *fname, struct externals *ext)
{
	struct map *local_dedup = map_create();

	if (map_get(dedup, fname) != NULL) {
		return;
	}
	map_set(dedup, fname, (void *) true);
	struct ast_function *f = externals_getfunc(ext, fname);
	if (!f) {
		/* TODO: pass up an error */
		fprintf(stderr, "function `%s' is not declared\n", fname);	
		exit(EXIT_FAILURE);
	}
	assert(f);

	if (f->isaxiom) {
		return;
	} 

	/* XXX: look in abstracts */
	/* XXX: handle prototypes */
	assert(f->body);
	struct ast_block *body = f->body;
	int nstmts = ast_block_nstmts(body);
	struct ast_stmt **stmt = ast_block_stmts(body);

	assert(stmt);
	struct string_arr *val = string_arr_create();
	for (int i = 0; i < nstmts; i++) {
		struct string_arr *farr = ast_stmt_getfuncs(stmt[i]);		
		if (!farr) {
			continue;
		}

		char **func = string_arr_s(farr); 
		for (int j = 0; j < string_arr_n(farr); j++) {
			/* avoid duplicates */
			if (map_get(local_dedup, func[j]) != NULL) {
				continue;
			}
				
			struct ast_function *f = externals_getfunc(ext, func[j]);
			if (!f->isaxiom) {
				string_arr_append(val, func[j]);	
			}
			map_set(local_dedup, func[j], (void *) true);

			/* recursively build for other funcs */
			recurse_buildgraph(g, dedup, func[j], ext);
		}
	}

	map_set(g, dynamic_str(fname), val);
}

#include "arr.c"
#include "paths.c"

M src/0v/ast/function/function.h => src/0v/ast/function/function.h +8 -1
@@ 26,8 26,15 @@ ast_function_arr_len(struct ast_function_arr *);
struct ast_function **
ast_function_arr_func(struct ast_function_arr *);


struct ast_function_arr *
paths_fromfunction(struct ast_function *f);

struct externals;

struct map *
ast_function_buildgraph(char *fname, struct externals *ext);

struct ast_function *
ast_function_protostitch(struct ast_function *f, struct externals *ext);

#endif

M src/0v/ast/stmt/stmt.c => src/0v/ast/stmt/stmt.c +105 -0
@@ 6,6 6,7 @@
#include "lex.h"
#include "util.h"
#include "stmt.h"
#include "../expr/expr.h"

struct ast_stmt {
	enum ast_stmt_kind kind;


@@ 668,4 669,108 @@ ast_stmt_as_expr(struct ast_stmt *stmt)
	return stmt->u.expr;
}

static struct string_arr *
ast_stmt_expr_getfuncs(struct ast_stmt *stmt);

static struct string_arr *
ast_stmt_selection_getfuncs(struct ast_stmt *stmt);

static struct string_arr *
ast_stmt_iteration_getfuncs(struct ast_stmt *stmt);

static struct string_arr *
ast_stmt_compound_getfuncs(struct ast_stmt *stmt);

struct string_arr *
ast_stmt_getfuncs(struct ast_stmt *stmt)
{
	switch (stmt->kind) {
	case STMT_NOP:
		return string_arr_create();
	case STMT_LABELLED:
		return ast_stmt_getfuncs(stmt->u.labelled.stmt);
	case STMT_COMPOUND:
	case STMT_COMPOUND_V:
		return ast_stmt_compound_getfuncs(stmt);
	case STMT_EXPR:
		return ast_stmt_expr_getfuncs(stmt);
	case STMT_SELECTION:
		return ast_stmt_selection_getfuncs(stmt);
	case STMT_ITERATION:
	case STMT_ITERATION_E:
		return ast_stmt_iteration_getfuncs(stmt);
	case STMT_JUMP:
		return ast_expr_getfuncs(stmt->u.jump.rv);
	case STMT_ALLOCATION:
		return ast_expr_getfuncs(stmt->u.alloc.arg);
	default:
		assert(false);
	}
}

static struct string_arr *
ast_stmt_expr_getfuncs(struct ast_stmt *stmt)
{
	return ast_expr_getfuncs(stmt->u.expr);
}

static struct string_arr *
ast_stmt_selection_getfuncs(struct ast_stmt *stmt)
{
	struct ast_expr *cond = stmt->u.selection.cond;
	struct ast_stmt *body = stmt->u.selection.body,
			*nest = stmt->u.selection.nest;
	struct string_arr *cond_arr = ast_expr_getfuncs(cond),
			  *body_arr = ast_stmt_getfuncs(body),
			  *nest_arr = nest ? ast_stmt_getfuncs(nest) : string_arr_create();
	
	return string_arr_concat(
		string_arr_create(),
		string_arr_concat(
			cond_arr,
			string_arr_concat(
				body_arr, nest_arr
			)
		)
	);
}

static struct string_arr *
ast_stmt_iteration_getfuncs(struct ast_stmt *stmt)
{
	struct ast_stmt *init = stmt->u.iteration.init,
			*cond = stmt->u.iteration.cond,
			*body = stmt->u.iteration.body;
	struct ast_expr *iter = stmt->u.iteration.iter;
	/* XXX: inlucde loop abstracts potentially */
	struct string_arr *init_arr = ast_stmt_getfuncs(init),
			  *cond_arr = ast_stmt_getfuncs(cond),
			  *body_arr = ast_stmt_getfuncs(body),
			  *iter_arr = ast_expr_getfuncs(iter);
	
	return string_arr_concat(
		string_arr_create(),
		string_arr_concat(
			string_arr_concat(init_arr, cond_arr),
			string_arr_concat(body_arr, iter_arr)
		)
	);
}

static struct string_arr *
ast_stmt_compound_getfuncs(struct ast_stmt *stmt)
{
	struct string_arr *res = string_arr_create();
	struct ast_block *b = stmt->u.compound;
	struct ast_stmt **stmts = ast_block_stmts(b);
	for (int i = 0; i < ast_block_nstmts(b); i++) {
		res = string_arr_concat(
			res,
			ast_stmt_getfuncs(stmts[i])
		);
		/* XXX: leaks */
	}
	return res;
}

#include "verify.c"

M src/0v/ast/stmt/stmt.h => src/0v/ast/stmt/stmt.h +5 -0
@@ 1,6 1,8 @@
#ifndef XR0_AST_STMT_H
#define XR0_AST_STMT_H

#include "util.h"

enum ast_stmt_kind {
	STMT_NOP		= 1 << 0,
	STMT_LABELLED		= 1 << 1,


@@ 23,4 25,7 @@ ast_stmt_ispre(struct ast_stmt *stmt);
bool
ast_stmt_isassume(struct ast_stmt *stmt);

struct string_arr *
ast_stmt_getfuncs(struct ast_stmt *stmt);

#endif

A src/0v/ast/topological.c => src/0v/ast/topological.c +120 -0
@@ 0,0 1,120 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "ast.h"
#include "ext.h"
#include "util.h"
#include "stmt/stmt.h"
#include "function/function.h"

/*
 * map<node, string_arr> g maps node to adjacent neighbouring nodes
 */

static struct map *
calculate_indegrees(struct map *g);

static struct string_arr *
build_indegree_zero(struct map *indegrees);

struct string_arr *
topological_order(char *fname, struct externals *ext)
{
	struct string_arr *order = string_arr_create();

	struct map *g = ast_function_buildgraph(fname, ext); 
	struct map *indegrees = calculate_indegrees(g);
	struct string_arr *indegree_zero = build_indegree_zero(indegrees);
	/* while there are nodes of indegree zero */
	while (indegree_zero->n > 0) {
		/* add one node with indegree zero to ordered */
		char *curr = string_arr_deque(indegree_zero);
		string_arr_append(order, curr);

		for (int i = 0; i < g->n; i++) {
			struct entry e = g->entry[i];
			struct string_arr *v = (struct string_arr *) map_get(g, e.key);
			if (string_arr_contains(v, curr)) {
				/* decrement indegree */
				int *count = (int *) map_get(indegrees, e.key);
				*count = *count - 1;
				if (*count == 0) {
					string_arr_append(indegree_zero, dynamic_str(e.key));
				}
			}
		}
	}

	/* no more nodes with incoming edges */
	if (order->n != indegrees->n) {
		/* TODO: pass up error */
		fprintf(stderr, "cycle detected in graph");		
		exit(EXIT_FAILURE);
	}

	return order;
}

static int *
dynamic_int(int i);

static struct map *
calculate_indegrees(struct map *g)
{
	struct map *indegrees = map_create();
	for (int i = 0; i < g->n; i++) {
		struct entry e = g->entry[i];
		struct string_arr *deps = (struct string_arr *) map_get(g, e.key);

		/* init all nodes and their dependencies to 0 */
		if (map_get(indegrees, e.key) != NULL) {
			continue;
		}
		map_set(indegrees, dynamic_str(e.key), dynamic_int(0));
		for (int j = 0; j < deps->n; j++) {
			char *dep_key = deps->s[j]; 
			if (map_get(indegrees, dep_key) != NULL) {
				continue;
			}		
			map_set(indegrees, dynamic_str(dep_key), dynamic_int(0));
		}
	}

	for (int i = 0; i < indegrees->n; i++) {
		struct entry e = indegrees->entry[i];
		struct string_arr *n_arr = map_get(g, e.key);
		if (!n_arr) {
			continue;
		}
		for (int j = 0; j < n_arr->n; j++) {
			int *count = (int *) map_get(indegrees, e.key);
			*count = *count + 1; /* XXX */
		}
	}
	return indegrees;
}

static int *
dynamic_int(int i)
{
	int *val = malloc(sizeof(int));
	*val = i;
	return val;
}

static struct string_arr *
build_indegree_zero(struct map *indegrees)
{
	struct string_arr *indegree_zero = string_arr_create();
	for (int i = 0; i < indegrees->n; i++) {
		struct entry e = indegrees->entry[i];
		int *val = (int *) map_get(indegrees, e.key);
		if (*val == 0) {
			string_arr_append(
				indegree_zero,
				dynamic_str(e.key)
			);
		}
	}
	return indegree_zero;
}

A src/0v/ast/topological.h => src/0v/ast/topological.h +9 -0
@@ 0,0 1,9 @@
#ifndef XR0_TOPOLOGICAL_H
#define XR0_TOPOLOGICAL_H

struct externals;

struct string_arr *
topological_order(char *fname, struct externals *);

#endif

M src/0v/main.c => src/0v/main.c +52 -71
@@ 21,38 21,26 @@
int
yyparse();

struct string_arr;

struct string_arr *
string_arr_create();

void
string_arr_destroy(struct string_arr *);

void
string_arr_append(struct string_arr *, char *);

char **
string_arr_s(struct string_arr *);

int
string_arr_n(struct string_arr *);

struct config {
	char *infile;
	char *outfile;
	struct string_arr *includedirs;
	bool verbose;

	char *sortfunc;
	bool sort;
};

struct config
parse_config(int argc, char *argv[])
{
	bool verbose = false;
	bool sort = false;
	struct string_arr *includedirs = string_arr_create();
	char *outfile = OUTPUT_PATH;
	char *sortfunc = NULL;
	int opt;
	while ((opt = getopt(argc, argv, "vo:I:")) != -1) {
	while ((opt = getopt(argc, argv, "vos:I:")) != -1) {
		switch (opt) {
		case 'I':
			string_arr_append(includedirs, dynamic_str(optarg));


@@ 63,6 51,10 @@ parse_config(int argc, char *argv[])
		case 'v':
			verbose = true;
			break;
		case 's':
			sortfunc = optarg;
			sort = true;
			break;
		default:
			fprintf(stderr, "Usage: %s [-o output] input_file\n", argv[0]);
			exit(EXIT_FAILURE);


@@ 77,49 69,11 @@ parse_config(int argc, char *argv[])
		.outfile	= outfile,
		.includedirs	= includedirs,
		.verbose	= verbose,
		.sort		= sort,
		.sortfunc	= sortfunc,
	};
}

struct string_arr {
	int n;
	char **s;
};

struct string_arr *
string_arr_create()
{
	return calloc(1, sizeof(struct string_arr));
}

void
string_arr_destroy(struct string_arr *arr)
{
	for (int i = 0; i < arr->n; i++) {
		free(arr->s[i]);
	}
	free(arr);
}

void
string_arr_append(struct string_arr *arr, char *s)
{
	arr->s = realloc(arr->s, sizeof(char *) * ++arr->n);
	arr->s[arr->n-1] = s;
}

char **
string_arr_s(struct string_arr *arr)
{
	return arr->s;
}

int
string_arr_n(struct string_arr *arr)
{
	return arr->n;
}


char *
genincludes(struct string_arr *includedirs)
{


@@ 179,15 133,10 @@ static bool
verifyproto(struct ast_function *f, int n, struct ast_externdecl **decl);

void
pass1(struct ast *root, struct externals *ext)
pass0(struct ast *root, struct externals *ext)
{
	struct error *err;
	/* TODO:
	 * - enforce syntax rules
	 * - check that sfuncs have no bodies
	 * - unify declarations and definitions so that each function appears
	 *   once in the array passed to verify
	 * - check that chains do not have contradictory operators
	 */
	for (int i = 0; i < root->n; i++) {
		struct ast_externdecl *decl = root->decl[i];


@@ 207,18 156,33 @@ pass1(struct ast *root, struct externals *ext)
			ast_externdecl_install(decl, ext);
			continue;
		}
		if (!externals_getfunc(ext, ast_function_name(f))) {
			ast_externdecl_install(decl, ext);
		struct ast_function *stitched = ast_protostitch(f, ext);
		ast_externdecl_install(
			ast_functiondecl_create(ast_function_copy(stitched)),
			ext
		);
	}
}

void
pass1(struct ast *root, struct externals *ext)
{
	struct error *err;
	for (int i = 0; i < root->n; i++) {
		struct ast_externdecl *decl = root->decl[i];
		if (!ast_externdecl_isfunction(decl)) {
			continue;
		}
		struct ast_function *f = ast_externdecl_as_function(decl);
		if (ast_function_isaxiom(f) || ast_function_isproto(f)) {
			continue;
		}
		
		/* XXX: ensure that verified functions always have an abstract */
		assert(ast_function_abstract(f));

		if ((err = ast_function_verify(f, ext))) {
			fprintf(stderr, "%s", err->msg);
			exit(EXIT_FAILURE);
		}
		printf("%s\n", ast_function_name(f));
	}
}



@@ 249,7 213,7 @@ verifyproto(struct ast_function *proto, int n, struct ast_externdecl **decl)
	}
	if (count == 1) {
		if (proto_defisvalid(proto, def)) {
			return true;	
			return true;
		}
		fprintf(
			stderr,


@@ 295,7 259,24 @@ main(int argc, char *argv[])

	/* TODO: move table from lexer to pass1 */
	struct externals *ext = externals_create();
	pass1(root, ext);

	/* setup externals */
	pass0(root, ext);

	/* if -s param specified output topological eval order */
	if (c.sort) {
		/* TODO: pass up error conditions */
		if (!c.sortfunc) {
			fprintf(stderr, "supply function to `-s' flag to evaluate dependencies for");
			exit(EXIT_FAILURE);
		}
		struct string_arr *order = ast_topological_order(c.sortfunc, ext);
		/* TODO: our tests run 2>&1 > /dev/null */
		fprintf(stderr, "%s\n", string_arr_str(order));
	} else { 
		/* TODO: verify in topological order */
		pass1(root, ext);
	}

	externals_destroy(ext);
	ast_destroy(root);

M src/util/util.c => src/util/util.c +97 -0
@@ 4,6 4,8 @@
#include <string.h>
#include <assert.h>
#include "util.h"
#include "ast.h"
#include "ext.h"

char *
dynamic_str(const char *s)


@@ 200,6 202,101 @@ error_prepend(struct error* e, char *prefix)
	return error_create(new_msg);
}

/* string_arr */

struct string_arr *
string_arr_create()
{
	struct string_arr *arr = calloc(1, sizeof(struct string_arr));
	assert(arr);
	return arr;
}

void
string_arr_destroy(struct string_arr *arr)
{
	for (int i = 0; i < arr->n; i++) {
		free(arr->s[i]);
	}
	free(arr->s);
	free(arr);
}

char **
string_arr_s(struct string_arr *arr)
{
	return arr->s;
}

int
string_arr_n(struct string_arr *arr)
{
	return arr->n;
}

/* string_arr_append: Append struct node to array and return index (address). */
int
string_arr_append(struct string_arr *arr, char *s)
{
	arr->s = realloc(arr->s, sizeof(struct string_arr) * ++arr->n);
	assert(arr->s);
	int loc = arr->n-1;
	arr->s[loc] = s;
	return loc;
}

struct string_arr *
string_arr_copy(struct string_arr *old)
{
	struct string_arr *new = string_arr_create();
	for (int i = 0; i < old->n; i++) {
		string_arr_append(new, dynamic_str(old->s[i]));
	}
	return new;
}

struct string_arr *
string_arr_concat(struct string_arr *s1, struct string_arr *s2)
{
	assert(s1 && s2);
	struct string_arr *new = string_arr_copy(s1);		
	for (int i = 0; i < s2->n; i++) {
		string_arr_append(new, s2->s[i]);
	}
	return new;
}

char *
string_arr_deque(struct string_arr *arr)
{
	char *ret = dynamic_str(arr->s[0]);
	for (int i = 0; i < arr->n-1; i++) {
		arr->s[i] = arr->s[i+1];
	}
	arr->s = realloc(arr->s, sizeof(char *) * --arr->n);
	return ret;
}

bool
string_arr_contains(struct string_arr *arr, char *s)
{
	for (int i = 0; i < arr->n; i++) {
		if (strcmp(s, arr->s[i]) == 0) {
			return true;
		}
	}
	return false;
}

char *
string_arr_str(struct string_arr *string_arr)
{
	struct strbuilder *b = strbuilder_create();
	char **s = string_arr->s;
	int n = string_arr->n;
	for (int i = 0; i < n; i++) {
		char *str = s[i];
		strbuilder_printf(b, "%s%s", str, (i + 1 < n) ? ", " : "");
	}
	return strbuilder_build(b);
}

A tests/3-topological/000-valid-sort-empty.x => tests/3-topological/000-valid-sort-empty.x +9 -0
@@ 0,0 1,9 @@
#include <stdlib.h>
#include <stdio.h>

int
main()
{
	return NULL;
}


A tests/3-topological/000-valid-sort-empty.x.EXPECTED => tests/3-topological/000-valid-sort-empty.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
main

R tests/3-program/000-matrix.x => tests/3-topological/001-valid-sort-matrix.x +6 -6
@@ 17,7 17,11 @@ matrix_create(int rows, int cols) ~ [
	for (i = 0; i < result->rows; i++) {
		.alloc result->data[i];	
	}
]{
];

struct matrix *
matrix_create(int rows, int cols)
{
	int i;
	struct matrix *m;



@@ 89,11 93,7 @@ matrix_add(struct matrix *m1, struct matrix *m2) ~ [
void
matrix_print(struct matrix *m) ~ [
	pre: m = matrix_create($, $);
];

void
matrix_print(struct matrix *m)
{
] {
	int i; int j; int digit;

	for (i = 0; i < m->rows; i++) {

A tests/3-topological/001-valid-sort-matrix.x.EXPECTED => tests/3-topological/001-valid-sort-matrix.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
matrix_create, matrix_print, matrix_destroy, matrix_add, main

A tests/3-topological/002-valid-sort-parse.x => tests/3-topological/002-valid-sort-parse.x +745 -0
@@ 0,0 1,745 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define false 0
#define true 1

char *
read_file(char *path) ~ [ .alloc result; ];

struct lexer;

struct lexer *
parse(char *input);

void
lexer_destroy(struct lexer *) ~ [
	pre: {
		l = lexer_create(
			$, $,
			$, malloc(1),
			$, malloc(1)
		);
	}

	.dealloc l->pattern;
	.dealloc l->token;
	.dealloc l;
];

void
lexer_print(struct lexer *) ~ [
	pre: {
		l = lexer_create(
			$, $,
			$, malloc(1),
			$, malloc(1)
		);
	}
];

int
main()
{
	char *file;
	struct lexer *l;

	file = read_file("tests/3-program/100-lex/gen.l");

	l = parse(file);
	lexer_print(l);
	lexer_destroy(l);

	free(file);
}

char *
read_file(char *path)
{
	FILE *f;
	char *str;
	int fsize; /* XXX: should be long */

	f = fopen(path, "rb");
	fseek(f, 0, SEEK_END);
	fsize = ftell(f);
	fseek(f, 0, SEEK_SET);  /* same as rewind(f); */
	str = malloc(fsize + 1);
	fread(str, fsize, 1, f);
	fclose(f);
	str[fsize] = '\0';
	return str;
}

struct pattern {
	char *name; char *pattern;
};

void
pattern_print(struct pattern *p) ~ [
	pre: p = pattern_create("", "");
];

struct token {
	int isliteral;
	char *name; char *action;
};

void
token_print(struct token *t) ~ [
	pre: t = token_create(0, "", "");
];

struct lexer {
	char *pre; char *post;
	int npat; struct pattern *pattern;
	int ntok; struct token *token;
};

struct lexer *
lexer_create(char *pre, char *post, int npat, struct pattern *pattern,
		int ntok, struct token *token) ~ [
	.alloc result; 
	result->pre = pre;
	result->post = post;
	result->pattern = pattern;
	result->token = token;
]{
	struct lexer *l;

	l = malloc(sizeof(struct lexer));
	l->pre = pre;
	l->post = post;
	l->pattern = pattern;
	l->npat = npat;
	l->token = token;
	l->ntok = ntok;

	return l;
}

void
lexer_destroy(struct lexer *l)
{
	free(l->pattern);
	free(l->token);
	free(l);
}

void
lexer_print(struct lexer *l)
{
	int i;

	puts("\tpre:");
	puts(l->pre);

	puts("\tpost:");
	puts(l->post);

	puts("\tpatterns:");
	for (i = 0; i < l->npat; i++) {
		putchar('\t');
		putchar('\t');
		pattern_print(&l->pattern[i]);
		putchar('\n');
	}
	puts("\ttokens:");
	for (i = 0; i < l->ntok; i++) {
		putchar('\t');
		putchar('\t');
		token_print(&l->token[i]);
		putchar('\n');
	}
}

/* skipws: skip whitespace */
char *
skipws(char *s);

struct defsresult {
	char *pre;
	struct pattern *pattern;
	int npat;
	char *pos;
};

int
beginsdefs(char *s);

int
count_patterns(char *pos);

struct defsresult
parse_defs(char *input) ~ [
	if (beginsdefs(skipws(input))) {
		.alloc result.pre;
	}
	if (count_patterns(input)) {
		.alloc result.pattern;
	}
];

int
beginsdefs(char *s)
{
	return strncmp(s, "%{", 2) == 0;
}

struct rulesresult {
	struct token *token;
	int ntok;
	char *pos;
};

struct rulesresult
parse_rules(char *pos) ~ [
	result.token = $; /* TODO: put in else block */
	if (count_tokens(pos)) {
		.alloc result.token;
	}
	result.pos = $;
];

char *
parse_toeof(char *input) ~ [ .alloc result; ];

struct lexer *
parse(char *pos)
{
	struct defsresult def;
	struct rulesresult res;
	char *post;

	def = parse_defs(pos);
	pos = def.pos;
	if (strncmp(pos, "%%", 2) != 0) {
		/* TODO: fprintf breaks */
		/*fprintf(stderr, "invalid transition to rules: '%.*s'\n", 10, pos);*/
		exit(1);
	}
	pos = skipws(pos + 2); /* %% */
	res = parse_rules(pos);
	pos = res.pos;
	post = "";
	if (strncmp(pos, "%%", 2) == 0) {
		pos += 2;
		post = parse_toeof(pos);
	}
	return lexer_create(def.pre, post, def.npat, def.pattern, res.ntok,
		res.token);
}

char *
substr(char *s, int n) ~ [ .alloc result; ]
{
	int len;
	char *ss;

	len = n + 1;
	ss = malloc(sizeof(char) * len);
	strncpy(ss, s, len);
	ss[len-1] = '\0';
	return ss;
}

char *
skipws(char *s)
{
	for (; isspace(*s); s++) {}
	return s;
}

char *
skiplinespace(char *s)
{
	for (; *s == ' ' || *s == '\t'; s++) {}
	return s;
}

char *
parse_id(char *input) ~ [ .alloc result; ];

char *
skipoptions(char *pos)
{
	char *keyword;
	char *id;

	keyword = "%option";
	if (strncmp(pos, keyword, strlen(keyword)) != 0) {
		return pos;
	}
	pos += strlen(keyword);
	pos = skiplinespace(pos);
	id = parse_id(pos);
	pos += strlen(id);
	free(id);
	return pos;
}

char *
parse_id(char *input)
{
	char *s;

	if (!isalpha(*input)) {
		/* TODO: print to stderr */
		puts("id must begin with letter");
		exit(1);
	}
	s = input + 1;
	/* XXX: '0' is a placeholder to allow this to parse */
	for (; isalpha(*s) || isdigit(*s) || *s == '_' ; 0) {
		s++;
	}
	return substr(input, s - input);
}

char *
parse_tonewline(char *input) ~ [ .alloc result; ]
{
	char *s;
	s = input; /* must be here because not seen with skip loop hack */
	for (; *s != '\n'; 0) {
		s++;
	}
	return substr(input, s - input);
}

struct stringresult {
	char *s;
	char *pos;
};

struct stringresult
parse_defsraw(char *input) ~ [
	result.s = $;
	if (beginsdefs(input)) {
		.alloc result.s;
	}
	result.pos = $;
];

struct patternet {
	struct pattern *pattern;
	int npat;
	char *pos;
};

struct patternet
parse_defsproper(char *input) ~ [
	result.pattern = $; /* TODO: put in else block */
	if (count_patterns(input)) {
		.alloc result.pattern;
	}
	result.npat = $;
	result.pos = $;
];

struct defsresult
parse_defs(char *input)
{
	struct stringresult raw;
	struct patternet set;
	struct defsresult res;

	input = skipws(input);
	if (*input == '\0') {
		puts("EOF in defs");
		exit(1);
	}
	raw = parse_defsraw(input);
	input = raw.pos;
	input = skipws(input);
	input = skipoptions(input);
	input = skipws(input);
	set = parse_defsproper(input);

	res.pre = raw.s;
	res.pattern = set.pattern;
	res.npat = set.npat;
	res.pos = set.pos;
	return res;
}

struct stringresult
parse_defsraw(char *input)
{
	char *pos;
	struct stringresult res;

	if (!beginsdefs(input)) {
		res.s = "";
		res.pos = input;
		return res;
	}
	input += 2;
	pos = input;
	for (; strncmp(pos, "%}", 2) != 0; pos++) {}
	res.s = substr(input, pos - input);
	res.pos = pos + 2;
	return res;
}

struct pattern *
pattern_create(char *name, char *pattern) ~ [
	.alloc result;
	result->name = name;
	result->pattern = pattern;
]{
	struct pattern *p;

	p = malloc(sizeof(struct pattern));
	p->name = name;
	p->pattern = pattern;

	return p;
}

void
pattern_print(struct pattern *p)
{
	puts("pattern:");
	puts(p->name);
	puts(p->pattern);
	puts("end pattern");
}


struct patternresult {
	struct pattern *p;
	char *pos;
};

struct patternpos {
	struct pattern *p;
	char *pos;
};

struct patternpos
parse_defs_n(char *pos, int npat) ~ [
	result.p = $; /* TODO: put in else block */
	if (npat) {
		.alloc result.p;
	}
	result.pos = $;
];

struct patternet
parse_defsproper(char *input)
{
	struct patternet res;
	struct patternpos defs_n;

	res.npat = count_patterns(input);
	defs_n = parse_defs_n(input, res.npat);
	res.pattern = defs_n.p;
	res.pos = defs_n.pos;
	return res;
}

struct patternresult
parse_pattern(char *pos) ~ [
	result.p = pattern_create(malloc(1), malloc(1));
];

int
count_patterns(char *pos)
{
	int n;
	struct patternresult parsed;

	n = 0;
	for (; strncmp(pos, "%%", 2) != 0; n++) {
		parsed = parse_pattern(pos);
		pos = skipws(parsed.pos);
		/* TODO: clean up r.p */
	}

	return n;
}

struct patternresult
parse_pattern(char *pos)
{
	char *name; char *pattern;
	struct patternresult res;

	name = parse_id(pos);
	pos = pos + strlen(name);
	pos = skiplinespace(pos);
	pattern = parse_tonewline(pos);
	pos += strlen(pattern);

	res.p = pattern_create(name, pattern);
	res.pos = pos;
	return res;
}

struct patternpos
parse_defs_n(char *pos, int npat)
{
	int i;
	struct pattern *p;
	struct patternresult parsed;
	struct patternpos res;

	p = NULL;
	if (npat) {
		p = malloc(sizeof(struct pattern) * npat);
		for (i = 0; i < npat; i++) {
			parsed = parse_pattern(pos);
			p[i] = *parsed.p;
			pos = skipws(parsed.pos);
		}
	}

	res.p = p;
	res.pos = pos;
	return res;
}

struct token *
token_create(int isliteral, char *name, char *action) ~ [
	.alloc result;
	result->isliteral = isliteral;
	result->name = name;
	result->action = action;
]{
	struct token *tk;

	tk = malloc(sizeof(struct token));
	tk->isliteral = isliteral;
	tk->name = name;
	tk->action = action;

	return tk;
}

void
token_print(struct token *t)
{
	puts("token:");
	puts(t->name);
	puts(t->action);
	puts("end token");
}

int
count_tokens(char *pos);

struct tokenpos {
	struct token *t;
	char *pos;
};

struct tokenpos
parse_rules_n(char *pos, int ntok) ~ [
	result.t = $; /* TODO: put in else block */
	if (ntok) {
		.alloc result.t;
	}
	result.pos = $;
];

struct rulesresult
parse_rules(char *pos)
{
	struct rulesresult res;
	struct tokenpos rules_n;

	res.ntok = count_tokens(pos);
	rules_n = parse_rules_n(pos, res.ntok);
	res.token = rules_n.t;
	res.pos = rules_n.pos;
	return res;
}

struct tokenresult {
	struct token *tk;
	char *pos;
};

struct tokenresult
parse_token(char *pos) ~ [
	result.tk = token_create($, malloc($), malloc($));
	result.pos = $;
];

int
count_tokens(char *pos)
{
	int n;
	struct tokenresult r;

	n = 0;
	for (; *pos != '\0' && strncmp(pos, "%%", 2) != 0 ; n++) {
		r = parse_token(pos);
		pos = skipws(r.pos);
		/* TODO: clean up r.tk */
	}

	return n;
}

struct tokenpos
parse_rules_n(char *pos, int ntok)
{
	int i;
	struct token *t;
	struct tokenresult parsed;
	struct tokenpos res;

	t = NULL;
	if (ntok) {
		t = malloc(sizeof(struct token) * ntok);
		for (i = 0; i < ntok; i++) {
			parsed = parse_token(pos);
			t[i] = *parsed.tk;
			pos = skipws(parsed.pos);
		}
	}

	res.t = t;
	res.pos = pos;
	return res;
}

struct tknameresult {
	int isliteral;
	char *name;
	char *pos;
};

struct tknameresult
parse_name(char *pos) ~ [
	.alloc result.name;
	result.isliteral = $;
	result.pos = $;
];

struct stringresult
parse_action(char *input) ~ [
	.alloc result.s;
	result.pos = $;
];

struct tokenresult
parse_token(char *pos)
{
	struct tknameresult nameres;
	struct stringresult actionres;
	struct tokenresult res;

	nameres = parse_name(pos);
	actionres = parse_action(skiplinespace(nameres.pos));

	res.tk = token_create(nameres.isliteral, nameres.name, actionres.s);
	res.pos = actionres.pos;
	return res;
}

struct tknameresult
parse_token_id(char *pos) ~ [ .alloc result.name; ];

struct tknameresult
parse_token_literal(char *input) ~ [ .alloc result.name; ];

struct tknameresult
parse_token_pattern(char *pos) ~ [ .alloc result.name; ];

struct tknameresult
parse_name(char *pos)
{
	/* TODO: make into switch */
	if (*pos == '{') {
		return parse_token_id(pos);
	}
	if (*pos == '"') {
		return parse_token_literal(pos);
	}
	return parse_token_pattern(pos);
}

struct tknameresult
parse_token_id(char *pos)
{
	char *id;
	struct tknameresult res;

	id = parse_id(++pos); /* skip '{' */
	pos += strlen(id);
	if (*pos != '}') {
		puts("token id must end in '}'");
		exit(1);
	}

	res.isliteral = false;
	res.name = id;
	res.pos = pos + 1; /* '}' */
	return res;
}

struct tknameresult
parse_token_literal(char *input)
{
	char *pos;
	struct tknameresult res;

	input++; /* skip '"' */
	pos = input;
	for (pos++; *pos != '"'; pos++) {}

	res.isliteral = true;
	res.name = substr(input, pos - input);
	res.pos = pos + 1;
	return res;
}

struct tknameresult
parse_token_pattern(char *pos)
{
	char *id;
	struct tknameresult res;

	id = parse_id(pos);

	res.isliteral = true;
	res.name = id;
	res.pos = pos + strlen(id);
	return res;
}

struct stringresult
parse_action(char *input)
{
	char *pos;
	struct stringresult res;

	if (*input != '{') {
		puts("action must begin with '{'");
		exit(1);
	}
	input++; /* skip '{' */
	pos = input;
	for (; *pos != '}'; pos++) {}

	res.s = substr(input, pos - input);
	res.pos = pos + 1;
	return res;
}

char *
parse_toeof(char *input)
{
	char *s;

	s = input; /* must be here because not seen with skip loop hack */
	for (; *s != '\0'; 0) {
		s++;
	}
	return substr(input, s - input);
}

A tests/3-topological/002-valid-sort-parse.x.EXPECTED => tests/3-topological/002-valid-sort-parse.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
read_file, skipws, beginsdefs, substr, skiplinespace, pattern_create, token_create, lexer_create, pattern_print, token_print, lexer_destroy, parse_defsraw, parse_id, parse_tonewline, parse_token_literal, parse_action, parse_toeof, lexer_print, skipoptions, parse_token_id, parse_token_pattern, parse_pattern, parse_name, count_patterns, parse_defs_n, parse_token, parse_defsproper, count_tokens, parse_rules_n, parse_defs, parse_rules, parse, main

A tests/3-topological/010-FAIL-cycle.x => tests/3-topological/010-FAIL-cycle.x +28 -0
@@ 0,0 1,28 @@
#include <stdlib.h>
#include <stdio.h>

void
main();

void *
func2()
{
	main();
	return NULL;
}

void *
func3();

void *
main()
{
	func3();
	return NULL;
}

void *
func3()
{
	return func2();
}

A tests/3-topological/010-FAIL-cycle.x.EXPECTED => tests/3-topological/010-FAIL-cycle.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
cycle detected in graph

A tests/3-topological/020-FAIL-non-existant-funcname.x => tests/3-topological/020-FAIL-non-existant-funcname.x +8 -0
@@ 0,0 1,8 @@
#include <stdlib.h>
#include <stdio.h>

void *
main2()
{
	return NULL;
}

A tests/3-topological/020-FAIL-non-existant-funcname.x.EXPECTED => tests/3-topological/020-FAIL-non-existant-funcname.x.EXPECTED +1 -0
@@ 0,0 1,1 @@
function `main' is not declared

R tests/3-program/.vimrc => tests/5-program/.vimrc +0 -0
A tests/5-program/000-matrix.x => tests/5-program/000-matrix.x +149 -0
@@ 0,0 1,149 @@
#include <stdlib.h>
#include <stdio.h>

struct matrix {
	int rows; int cols;
	int **data;
};

struct matrix *
matrix_create(int rows, int cold) ~ [
	int i;

	.alloc result;
	.alloc result->data;
	result->rows = rows;
	result->cols = cols;
	for (i = 0; i < result->rows; i++) {
		.alloc result->data[i];	
	}
];

struct matrix *
matrix_create(int rows, int cols)
{
	int i;
	struct matrix *m;

	m = malloc(sizeof(struct matrix));

	m->rows = rows;
	m->cols = cols;

	m->data = malloc(sizeof(int *) * rows);
	for (i = 0; i < rows; i++) ~ [ .alloc m->data[i]; ] {
		m->data[i] = malloc(sizeof(int) * cols);
	}

	return m;
}

void
matrix_destroy(struct matrix *m) ~ [
	int i;

	pre: m = matrix_create($, $);

	for (i = 0; i < m->rows; i++) {
		.dealloc m->data[i];
	}
	.dealloc m->data;
	.dealloc m;
]{
	int i;

	for (i = 0; i < m->rows; i++) ~ [ .dealloc m->data[i]; ] {
		free(m->data[i]);
	}
	free(m->data);
	free(m);
}

struct matrix *
matrix_add(struct matrix *m1, struct matrix *m2) ~ [
	int i;

	pre: {
		m1 = matrix_create($, $);
		m2 = matrix_create($, $);
	}

	.alloc result;
	.alloc result->data;
	result->rows = m1->rows;
	result->cols = m1->cols;
	for (i = 0; i < result->rows; i++) {
		.alloc result->data[i];	
	}
]{
	int i; int j;
	struct matrix *res;

	/*assert(m1->rows == m2->rows && m1->cols == m2->cols);*/

	res = matrix_create(m1->rows, m1->cols);
	for (i = 0; i < res->rows; i++) {
		for (j = 0; j < m1->cols; j++) {
			res->data[i][j] = m1->data[i][j] + m2->data[i][j];
		}
	}
	return res;
}

void
matrix_print(struct matrix *m) ~ [
	pre: m = matrix_create($, $);
] {
	int i; int j; int digit;

	for (i = 0; i < m->rows; i++) {
		for (j = 0; j < m->cols; j++) {
			digit = m->data[i][j] + '0';
			putchar(digit);
		}
		puts("\n");
	}
	puts("\n");
}

int
main()
{
	struct matrix *m1;
	struct matrix *m2;
	struct matrix *sum;

	puts("matrix program:\n");
	m1 = matrix_create(2, 2);
	~ [ @m1; ]

	m1->data[0][0] = 1;
	m1->data[0][1] = 2;
	m1->data[1][0] = 3;
	m1->data[1][1] = 4;
	puts("m1:\n");
	matrix_print(m1);

	m2 = matrix_create(2, 2);
	~ [ @m1; @m2; ]

	m2->data[0][0] = 1;
	m2->data[0][1] = 1;
	m2->data[1][0] = 1;
	m2->data[1][1] = 1;
	puts("m2:\n");
	matrix_print(m2);

	sum = matrix_add(m1, m2);
	~ [ @m1; @m2; @sum; ]

	puts("sum:\n");
	matrix_print(sum);

	matrix_destroy(sum);
	~ [ @m1; @m2; !@sum; ]
	matrix_destroy(m2);
	~ [ @m1; !@m2; !@sum; ]
	matrix_destroy(m1);
	~ [ !@m1; !@m2; !@sum; ]
}

R tests/3-program/100-lex/gen.l => tests/5-program/100-lex/gen.l +0 -0
R tests/3-program/100-lex/parse.x => tests/5-program/100-lex/parse.x +15 -14
@@ 121,19 121,8 @@ lexer_create(char *pre, char *post, int npat, struct pattern *pattern,
}

void
lexer_destroy(struct lexer *l) ~ [
	pre: {
		l = lexer_create(
			$, $,
			$, malloc(1),
			$, malloc(1)
		);
	}

	.dealloc l->pattern;
	.dealloc l->token;
	.dealloc l;
]{
lexer_destroy(struct lexer *l)
{
	free(l->pattern);
	free(l->token);
	free(l);


@@ 371,7 360,19 @@ struct lexer *
parse(char *input);

void
lexer_destroy(struct lexer *);
lexer_destroy(struct lexer *) ~ [
	pre: {
		l = lexer_create(
			$, $,
			$, malloc(1),
			$, malloc(1)
		);
	}

	.dealloc l->pattern;
	.dealloc l->token;
	.dealloc l;
];

void
lexer_print(struct lexer *);

M tests/run => tests/run +25 -20
@@ 7,6 7,8 @@ npass=0

expected_suffix="EXPECTED"
fail_prefix="FAIL"
topological_folder="topological"
main_func="main"

XR0=$(pwd)/bin/0v
libx=$(pwd)/libx


@@ 17,34 19,37 @@ length=$(ls */*.x | awk '{ print length, $0 }' | sort -n -s | cut -d' ' -f2- |
for f in */*.x
do
	ntests=$((ntests+1))
	folder=$(dirname "$f")

	printf "%-${length}s ..." "$f"
	output=$($XR0 -I $libx $f 2>&1 >/dev/null)
	if [[ "$f" != *"${fail_prefix}"* && $output -eq 0 ]]
	if [ "$folder" == *"$topological_folder"* ]
	then
		npass=$((npass+1))
		printf 'PASS\n'
		output=$($XR0 -s main -I $libx $f 2>&1 >/dev/null)
	else
		# negative tests

		#echo "expected filename: ${expected_file}\n"
		#echo "output: ${output} expected_output: ${expected_output}\n"
		output=$($XR0 -I $libx $f 2>&1 >/dev/null)
	fi

		# check if fail prefix present and output matches expected error
		if [[ "$f" == *"${fail_prefix}"* ]]
	# cases where we have an expected output to compare with
	if [[ "$f" == *"${fail_prefix}"* || "$folder" == *"$topological_folder"* ]]
	then
		expected_file="${f}.${expected_suffix}"
		expected_output=$(cat $expected_file)
		
		# check if output matches expected_output
		if [[ $output == $expected_output ]]
		then
			expected_file="${f}.${expected_suffix}"
			expected_output=$(cat $expected_file)
			
			if [[ $output == $expected_output ]]
			then
				npass=$((npass+1))
				printf 'PASS\n'
			else
				printf 'FAIL\n'
			fi
			npass=$((npass+1))
			printf 'PASS\n'
		else
			printf 'FAIL\n'
		fi
	else
		# silence is golden
		if [[ $output -eq 0 ]] 
		then
			npass=$((npass+1))
			printf 'PASS\n'
		fi
	fi
done