#!/usr/bin/nickle

typedef struct {
	string	feature;
	string	type;
	string	c_name;
	string[*]	lisp_names;
} builtin_t;

string[string] type_map = {
	"lambda" => "LAMBDA",
	"nlambda" => "NLAMBDA",
	"macro" => "MACRO",
	"f_lambda" => "F_LAMBDA",
	"atom" => "atom",
	"feature" => "feature",
};

string[*]
make_lisp(string[*] tokens)
{
	string[...] lisp = {};

	if (dim(tokens) < 4)
		return (string[1]) { tokens[dim(tokens) - 1] };
	return (string[dim(tokens)-3]) { [i] = tokens[i+3] };
}

builtin_t
read_builtin(file f) {
	string	line = File::fgets(f);
	string[*]	tokens = String::wordsplit(line, " \t");

	return (builtin_t) {
		.feature = dim(tokens) > 0 ? tokens[0] : "#",
		.type = dim(tokens) > 1 ? type_map[tokens[1]] : "#",
		.c_name = dim(tokens) > 2 ? tokens[2] : "#",
		.lisp_names = make_lisp(tokens),
	};
}

builtin_t[*]
read_builtins(file f) {
	builtin_t[...] builtins = {};

	while (!File::end(f)) {
		builtin_t	b = read_builtin(f);

		if (b.type[0] != '#')
			builtins[dim(builtins)] = b;
	}
	return builtins;
}

void
dump_ifdef(builtin_t builtin)
{
	if (builtin.feature != "all")
		printf("#ifdef AO_SCHEME_FEATURE_%s\n", builtin.feature);
}

void
dump_endif(builtin_t builtin)
{
	if (builtin.feature != "all")
		printf("#endif /* AO_SCHEME_FEATURE_%s */\n", builtin.feature);
}

bool is_atom(builtin_t b) = b.type == "atom";

bool is_func(builtin_t b) = b.type != "atom" && b.type != "feature";

bool is_feature(builtin_t b) = b.type == "feature";

void
dump_ids(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_ID\n");
	printf("#undef AO_SCHEME_BUILTIN_ID\n");
	printf("enum ao_scheme_builtin_id {\n");
	for (int i = 0; i < dim(builtins); i++)
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			printf("\tbuiltin_%s,\n", builtins[i].c_name);
			dump_endif(builtins[i]);
		}
	printf("\t_builtin_last\n");
	printf("};\n");
	printf("#endif /* AO_SCHEME_BUILTIN_ID */\n");
}

void
dump_casename(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_CASENAME\n");
	printf("#undef AO_SCHEME_BUILTIN_CASENAME\n");
	printf("static char *ao_scheme_builtin_name(enum ao_scheme_builtin_id b) {\n");
	printf("\tswitch(b) {\n");
	for (int i = 0; i < dim(builtins); i++)
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			printf("\tcase builtin_%s: return ao_scheme_poly_atom(_atom(\"%s\"))->name;\n",
			       builtins[i].c_name, builtins[i].lisp_names[0]);
			dump_endif(builtins[i]);
		}
	printf("\tdefault: return (char *) \"???\";\n");
	printf("\t}\n");
	printf("}\n");
	printf("#endif /* AO_SCHEME_BUILTIN_CASENAME */\n");
}

void
cify_lisp(string l) {
	for (int j = 0; j < String::length(l); j++) {
		int c= l[j];
		if (Ctype::isalnum(c) || c == '_')
			printf("%c", c);
		else
			printf("%02x", c);
	}
}

void
dump_arrayname(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_ARRAYNAME\n");
	printf("#undef AO_SCHEME_BUILTIN_ARRAYNAME\n");
	printf("static const ao_poly builtin_names[] = {\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			printf("\t[builtin_%s] = _ao_scheme_atom_",
			       builtins[i].c_name);
			cify_lisp(builtins[i].lisp_names[0]);
			printf(",\n");
			dump_endif(builtins[i]);
		}
	}
	printf("};\n");
	printf("#endif /* AO_SCHEME_BUILTIN_ARRAYNAME */\n");
}

void
dump_funcs(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_FUNCS\n");
	printf("#undef AO_SCHEME_BUILTIN_FUNCS\n");
	printf("const ao_scheme_func_t ao_scheme_builtins[] = {\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			printf("\t[builtin_%s] = ao_scheme_do_%s,\n",
			       builtins[i].c_name,
			       builtins[i].c_name);
			dump_endif(builtins[i]);
		}
	}
	printf("};\n");
	printf("#endif /* AO_SCHEME_BUILTIN_FUNCS */\n");
}

void
dump_decls(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_DECLS\n");
	printf("#undef AO_SCHEME_BUILTIN_DECLS\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			printf("ao_poly\n");
			printf("ao_scheme_do_%s(struct ao_scheme_cons *cons);\n",
			       builtins[i].c_name);
			dump_endif(builtins[i]);
		}
	}
	printf("#endif /* AO_SCHEME_BUILTIN_DECLS */\n");
}

void
dump_consts(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_CONSTS\n");
	printf("#undef AO_SCHEME_BUILTIN_CONSTS\n");
	printf("struct builtin_func funcs[] = {\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (is_func(builtins[i])) {
			dump_ifdef(builtins[i]);
			for (int j = 0; j < dim(builtins[i].lisp_names); j++) {
				printf ("\t{ .feature = \"%s\", .name = \"%s\", .args = AO_SCHEME_FUNC_%s, .func = builtin_%s },\n",
					builtins[i].feature,
					builtins[i].lisp_names[j],
					builtins[i].type,
					builtins[i].c_name);
			}
			dump_endif(builtins[i]);
		}
	}
	printf("};\n");
	printf("#endif /* AO_SCHEME_BUILTIN_CONSTS */\n");
}

void
dump_atoms(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_ATOMS\n");
	printf("#undef AO_SCHEME_BUILTIN_ATOMS\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (!is_feature(builtins[i])) {
			for (int j = 0; j < dim(builtins[i].lisp_names); j++) {
				printf("#define _ao_scheme_atom_");
				cify_lisp(builtins[i].lisp_names[j]);
				printf(" _atom(\"%s\")\n", builtins[i].lisp_names[j]);
			}
		}
	}
	printf("#endif /* AO_SCHEME_BUILTIN_ATOMS */\n");
}

void
dump_atom_names(builtin_t[*] builtins) {
	printf("#ifdef AO_SCHEME_BUILTIN_ATOM_NAMES\n");
	printf("#undef AO_SCHEME_BUILTIN_ATOM_NAMES\n");
	printf("static struct builtin_atom atoms[] = {\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (is_atom(builtins[i])) {
			for (int j = 0; j < dim(builtins[i].lisp_names); j++) {
				printf("\t{ .feature = \"%s\", .name = \"%s\" },\n",
				       builtins[i].feature,
				       builtins[i].lisp_names[j]);
			}
		}
	}
	printf("};\n");
	printf("#endif /* AO_SCHEME_BUILTIN_ATOM_NAMES */\n");
}

bool
has_feature(string[*] features, string feature)
{
	for (int i = 0; i < dim(features); i++)
		if (features[i] == feature)
			return true;
	return false;
}

void
dump_features(builtin_t[*] builtins) {
	string[...] features = {};
	printf("#ifdef AO_SCHEME_BUILTIN_FEATURES\n");
	for (int i = 0; i < dim(builtins); i++) {
		if (builtins[i].feature != "all") {
			string feature = builtins[i].feature;
			if (!has_feature(features, feature)) {
				features[dim(features)] = feature;
				printf("#define AO_SCHEME_FEATURE_%s\n", feature);
			}
		}
	}
	printf("#endif /* AO_SCHEME_BUILTIN_FEATURES */\n");
}

void main() {
	if (dim(argv) < 2) {
		File::fprintf(stderr, "usage: %s <file>\n", argv[0]);
		exit(1);
	}
	twixt(file f = File::open(argv[1], "r"); File::close(f)) {
		builtin_t[*]	builtins = read_builtins(f);

		printf("/* %d builtins */\n", dim(builtins));
		dump_ids(builtins);
		dump_casename(builtins);
		dump_arrayname(builtins);
		dump_funcs(builtins);
		dump_decls(builtins);
		dump_consts(builtins);
		dump_atoms(builtins);
		dump_atom_names(builtins);
		dump_features(builtins);
	}
}

main();
