#!/usr/bin/awk -f
# "common-union-type.awk"
# David Fang, 2004
#	$Id: common-union-type.awk,v 1.6 2006/07/26 22:46:30 fang Exp $
# TODO: factor most of the code out into skeleton code

# CO-DEPENDENT ON:
# parser/yacc-union-type.awk OR parser/bison-union-type.awk

# the goal of this script is to produce:
# a C function that takes as arguments:
#	a union object
#	a pair of states (enumerated) representing a transition
#		between states of the parser
# and returns a pointer to the correct member
#	as a pointer to a super-class of all members
# to use this function, make an external decaration reference to
# extern <base-type>* yy_union_resolve(const YYSTYPE&, const short, const short);

# To accomplish this, we need to know what state the parser was in
# when a particular symbol was shifted onto the stack.  
# What we have available when the parser encounters a syntax error
# are the entire state stack and value stack.  
# Walking the state stack from bottom to top tells us the precise
# state stransitions.  
# Where do we look up union-member type given the state transition?
# It's not in y.tab.c because it has no need to go figure out what
# the type IS, only that types match where they are expected.  
# We leverage the "human-readable" output of yacc, namely the -v option
# which produces "y.output".  
# y.output conveniently contains the information about what symbols caused
# transitions from one state to the next.  
# We just have to translate them into a form that can be used by C.  
# more on this later...

# input variables:
# yaccfile = the yacc input file, preferably preprocessed with the command:
#	suggetsed creation of intermediate file:
#	grep -v "#include" <yacc-file.y> | cpp -P |
#		sed -e "/^%start/,$$$$d" -e "/%{/,/%}/d"
# enumfile = "y.tab.h" for symbol enumerations
# include (optional) = space-delimited list of headers to include
# namespace (optional) = space-delimited list of namespaces to use
# token_enum (optional) = the start enumeration, i.e. the first token enum

# criteria for the union type for yylval:
# all members of the union must be pointers to classes with a common
# parent class.  

BEGIN {
	go_union = 0;
	member_count = 0;
	state_count = 0;

	if (yaccfile == "") {
		print "\"yaccfile\" must be defined as an argument!";
		exit 1;
	} 

	print "/*";
	print "This file was automatically generated by:";
	printf("awk -f " main_program_name() " -v yaccfile=" yaccfile " ");
	if (include != "") printf("-v include=\"" include "\" ");
	if (namespace != "") printf("-v namespace=\"" namespace "\" ");
	if (token_enum != "") printf("-v token_enum=\"" token_enum "\" ");
	print "<processed yacc-file>";
	print "*/";
	print "";

	# if token_enum is not supplied, take a wild guess.
	handle_default_token_enum();

	# need namespace first, before including headers
	# but namespaces need to be opened and declared before used!
	nns = split(namespace, ns_array);
	for (i=1; i<= nns; i++) {
		# note: delete <array> is a gawk extension, and not POSIX
		# delete spaces;		# clear first
		for (s in spaces) {
			delete spaces[s];
		}
		depth = split(ns_array[i], spaces, "::");
		# just open and close (nested)
		for (j=1; j <= depth; j++)
			print "namespace " spaces[j] " {";
		for (j=1; j <= depth; j++)
			print "}";
		print "using namespace " ns_array[i] ";";
	}
	print "";

	nincs = split(include, inc_array);
	for (i=1; i<= nincs; i++) {
		print "#include \"" inc_array[i] "\"";
	}
	print "";

#	print "#include \"y.tab.h\"";	# for YYSTYPE
#		but is not always named y.tab.h! add to the "include" variable
	print "#include <cassert>";
	print "#include <iostream>";
	print "";
	print "using std::ostream;";
	print "";

	process_union(yaccfile);
	register_end_symbols();
	process_symbol_types(yaccfile);

	print "#ifndef NULL";
	print "#define NULL\t0";
	print "#endif";
	print "";

	print "/* basic linked-list node with state key and union member type enumeration */";
	print "typedef struct _yy_state_map_link_ yy_state_map_link;";
	print "struct _yy_state_map_link_ {";

# no need for const-ness if the links are all static global constants
# also eliminates need for explicit constructor in gcc-3.2, 
# can use struct assignment, costs less compile-time memory than
# using constructors.  
#	print "\tconst int state;\t\t/* state number to match */";
#	print "\tconst int type_enum;\t\t/* enumerated type */";
	print "\tint state;\t\t/* state number to match */";
	print "\tint type_enum;\t\t/* enumerated type */";
	print "\tconst yy_state_map_link* next;";

#	gcc-3.2 requires constructor-style with non-static consts
#	print "\t_yy_state_map_link_(const int s, const int e, const yy_state_map_link* n) :";
#	print "\t\tstate(s), type_enum(e), next(n) { }";

	print "};";
	print "";
}

function process_union(file, 
	# local variables
	member_type, member_id ) {
if (!got_union) {
	do { getline < file; } while(!match($0, "%union.*\\{"));
	# then we know we're in a union
	while (getline < file) {
	if (match($0, "[}]"))
		break;
	else if (NF >= 2) {
		member_type = $1;
		member_id = $2;
		gsub(";", "", member_id);
		# pointer handling, depends on here pointer is placed
		# only handles single pointers for now
		if (match(member_id,"[*]")) {
			gsub("[*]", "", member_id);
			member_type += "*";
		} else if (!match(member_type, "[*]")) {
			printf "union member must be a pointer!";
			exit 1;
		}
		type_of[member_id] = member_type;
#		print "union member " member_id " has type " member_type;
		enum_of[member_type] = member_count;
		member_id_array[member_count] = member_id;
		member_count++;
	}
	# else ignore line
	}	# end while
	got_union = 1;
}}

function process_symbol_types(file, 
	# local variables
	argc, i, member_id, member_type, symbol_id, first_symbol ) {
if (got_union) {
	while(getline < file) {
	argc = split($0, type_args);
	if (match($1,"%token") || match($1,"%type")) {
	# fix: need to handle un-typed tokens
	if (argc >= 2) {			# was 3
		# check if first item is a union member reference
		member_id = type_args[2];
		if (match(member_id, "<.*>")) {
			first_symbol = 3;
			gsub("[<>]","", member_id);	# strip angles
			member_type = type_of[member_id];  
#			print "found union member " member_id " \  
#				with type " member_type;
			if (member_type == "") {
				print "union member \"" member_id \
					"\" not found!";    
				exit 1;
			}
		} else {	# first item is a token
			member_type = "";
			first_symbol = 2;
		}

		for (i=first_symbol; i<= argc; i++) {
			symbol_id = type_args[i];
			symbol_type[symbol_id] = member_type;
#			print "symbol " symbol_id " returns member " \
#				member_id " with type " member_type;
			if (match($1, "type") && match(symbol_id, "'")) {
				# then token is a single character
				gsub("'", "", symbol_id);
#				print symbol_id " = " string_char_to_int(symbol_id);
				token_set[string_char_to_int(symbol_id)] = member_type;
			} else if (match($1, "token")) {
				# token is user-defined
#				print "#define " symbol_id " " token_enum;
				token_set[token_enum] = member_type;
				token_enum++;
			} # end if match "type"
		}
	} # end if argc
	} # end if match(...)
	# just read to end-of-file
} # end while
	# print out map of token_enum to type_enum
	printf("static int token_to_type_enum_map[" token_enum "] = {");
	for (i=0; i<token_enum; i++) {
		if (i % 10 == 0) printf("\n\t");	# for readability
		member_type = token_set[i];
		if (member_type == "") {
			printf("%3d, ", -1);	# should die
		} else {
			printf("%3d, ", enum_of[member_type]);
		}
	}
	print "};";
	print "";
} else {	# !got_union
	print "need to have completed process_union() before " \
		"invoking process_symbol_types().";
	exit 1;
}
}

# convert character to int
function string_char_to_int(char, 
	# local vars
	i, c) {
	for (i=0; i<256; i++) {
		c = sprintf("%c", i);
		if (char == c)
			return i;
	}
	return -1;
}

END {

	print "static const char* null_what = \"(null) \";";
	print "";

	for (mid in type_of) {
		type_str = type_of[mid];
		gsub("[*]", "", type_str);
		gsub("::", "_", type_str);

	if (0) {
		# OBSOLETE
		# returns the desired union member
		print "static " type "* yy_union_get_" type_str \
			"(const YYSTYPE& u) {";
#		print "\tcerr << "\"" type_str "\";";
		print "\treturn u." mid ";";
		print "}";
	}

		# properly deletes the selected union member
		print "static void yy_union_delete_" type_str \
			"(const YYSTYPE& u) {";
		print "\tif (u." mid ")";
		print "\t\tdelete u." mid ";";	#" u." mid " = NULL;";
		print "}";

		# dumps a name associated with the member type
		# and location of token
		print "static ostream& yy_union_what_where_" type_str \
			"(const YYSTYPE& u, ostream& o) {";
		print "\treturn (u." mid ") ? u." mid "->what(o) << \" \" <<" \
			"where(*u." mid ") :";
		print "\t\to << null_what;";
		print "}";

		# add more functions as necessary
	}
	print "";

	print "static const yy_state_map_link* yysma[" state_count "] = {";
	for (i=0; i<state_count; i++) {
		if (shift_count[i])
			print "\t&yysml_" i "_" shift_count[i] -1 ", ";
		else print "\tNULL, ";
	}
	print " };";

if (0) {
	# function-table for returning base pointer type.
	# This may become obsolete.  
	print "static " type "* (*yy_union_get[" member_count "])(const YYSTYPE&) = {";
	for (i=0; i<member_count; i++) {
		type_str = type_of[member_id_array[i]];
		gsub("[*]", "", type_str);
		gsub("::", "_", type_str);
		print "\t&yy_union_get_" type_str ", ";
	}
	print "};";	# end of array of function pointers
}
	print "";

	# function-table for deleting pointer (like virtual destructor)
	print "static void (*yy_union_delete[" member_count "])(const YYSTYPE&) = {";
	for (i=0; i<member_count; i++) {
		type_str = type_of[member_id_array[i]];
		gsub("[*]", "", type_str);
		gsub("::", "_", type_str);
		print "\t&yy_union_delete_" type_str ", ";
	}
	print "};";	# end of array of function pointers
	print "";

	# This section implements an interface that's specific to this project.
	print "static ostream& (*yy_union_what_where[" member_count "])(const YYSTYPE&, ostream&) = {";
	for (i=0; i<member_count; i++) {
		type_str = type_of[member_id_array[i]];
		gsub("[*]", "", type_str);
		gsub("::", "_", type_str);
		print "\t&yy_union_what_where_" type_str ", ";
	}
	print "};";	# end of array of function pointers
	print "";

	# returns the appropriate index into the local function tables
	print "/**";
	print "\tUse this function to resolve the union member type.";
	print "\t\\param i the previous state of the transition.";
	print "\t\\param j the current state of the transition.";
	print "\t\\return wrapped pointer as the ultimate base type.";
	print " */";
	print "static int";
	print "yy_union_resolve_index(const short i, const short j) {";
	print "/* function body really starts here */";
	print "const yy_state_map_link* iter = yysma[i];";
	print "/* sequentially compare state keys */";
	print "while (iter) {";
	print "\tif(iter->state == j) break;";
	print "\titer = iter->next;";
	print "} /* end while */";
	print "if (iter && iter->state == j) {";
		print "\t/* then we've found a match, return appropriately wrapped pointer */";
		print "\tconst int k = iter->type_enum;";
		print "\tif (k >= 0) {";
			print "\t\tassert(k < " member_count ");";
		print "\t} // else k < 0, signaling an error.";
		print "\treturn k;";
	print "} else {";
		print "\treturn -1;\t// error, not found";
	print "}";
	print "}";
	print "";

	# returns the appropriate index into the local function tables
	print "static int";
	print "yy_union_lookup_index(const int c) {";
	print "\tif (c >= 0 && c < " token_enum ") {";
		print "\t\tconst int i = token_to_type_enum_map[c];";
		print "\t\tassert(i >= 0);";
		print "\t\tassert(i < " member_count ");";
		print "\t\treturn i;";
	print "\t} else {";	# else is garbage yychar value, just drop it
	print "\t\treturn -1;";
	print "\t}";
	print "}";
	print "";

	# public virtual virtual functions

	print "/**";
	print "\tDumps symbol information, as indexed by state transition.";
	print "\t\\param u the unionized symbol to dump.";
	print "\t\\param i the before state value.";
	print "\t\\param j the after state value.";
	print "\t\\param o the output stream.";
	print " */";
	print "ostream&";
	print "yy_union_resolve_dump(const YYSTYPE& u, const short i, const short j, ostream& o) {";
	print "\tconst int k = yy_union_resolve_index(i,j);";
	print "\tif (k >= 0)";
		print "\t\treturn (*yy_union_what_where[k])(u,o);";
	print "\telse\treturn o << \"(unknown)\";";
	print "}";
	print "";

	print "/**";
	print "\tDumps symbol information, as indexed by symbol number.";
	print "\t\\param u the unionized symbol to dump.";
	print "\t\\param c the symbol enumeration, as returned by yylex.";
	print "\t\\param o the output stream.";
	print " */";
	print "ostream&";
	print "yy_union_lookup_dump(const YYSTYPE& u, const int c, ostream& o) {";
	print "\treturn (*yy_union_what_where[yy_union_lookup_index(c)])(u,o);";
	print "}";
	print "";

	print "/**";
	print "\tDeallocates a union symbol as indexed state transition.";
	print "\t\\param u the unionized symbol to deallocate.";
	print "\t\\param i the before state value.";
	print "\t\\param j the after state value.";
	print " */";
	print "void";
	print "yy_union_resolve_delete(const YYSTYPE& u, const short i, const short j) {";
	print "\tconst int k = yy_union_resolve_index(i,j);";
	print "\tif (k >= 0)";
		print "\t\t(*yy_union_delete[k])(u);";
	print "\t// else cannot delete unknown type";
	print "}";
	print "";

	print "/**";
	print "\tDeallocates a union symbol as indexed by symbol number.";
	print "\t\\param u the unionized symbol to deallocate.";
	print "\t\\param c the symbol enumeration, as returned by yylex.";
	print " */";
	print "void";
	print "yy_union_lookup_delete(const YYSTYPE& u, const int c) {";
	print "\tconst int i = yy_union_lookup_index(c);";
	print "\tif (i >= 0) (*yy_union_delete[i])(u);";
	print "}";
	print "";

}

