#drinc:util.g
#toy.g
#externs.g

/*
 * decl.d: declaration and definition of the various symbols.
 */

/*
 * declareVariables - declare a set of variables of the given kind.
 */

proc declareVariables(SymbolKind_t kind)void:
    *SymbolEntry_t se;
    register TokenKind_t tk;
    ResultType_t typ;

    while
	tk := getSimpleToken();
	tk = tk_int or tk = tk_bool
    do
	typ := if tk = tk_int then rt_int else rt_bool fi;
	nextToken();
	while
	    tk := getToken(&se, nil, nil);
	    tk = tk_id
	do
	    if se*.se_kind ~= sk_empty then
		errorHereThree("symbol '", se*.se_name, "' already defined");
	    else
		se*.se_kind := kind;
		se*.se_type := typ;
		allocateVariable(se);
	    fi;
	    nextToken();
	    tk := getSimpleToken();
	    if tk = tk_comma then
		nextToken();
	    elif tk ~= tk_semicolon then
		errorHere("expecting comma between variable names");
		while
		    skipToken();
		    tk := getSimpleToken();
		    tk ~= tk_id and tk ~= tk_semicolon and tk ~= tk_comma and
			tk ~= tk_eof and tk ~= tk_int and tk ~= tk_bool
		do
		od;
		if tk = tk_comma then
		    nextToken();
		fi;
	    fi;
	od;
	if tk = tk_semicolon then
	    nextToken();
	else
	    errorHere("expecting semicolon after variable list");
	    if kind = sk_globalVariable then
		while
		    skipToken();
		    tk := getSimpleToken();
		    tk ~= tk_semicolon and tk ~= tk_proc and tk ~= tk_eof and
			tk ~= tk_int and tk ~= tk_bool
		do
		od;
	    else
		while
		    skipToken();
		    tk := getSimpleToken();
		    tk ~= tk_semicolon and tk ~= tk_corp and tk ~= tk_id and
			tk ~= tk_if and tk ~= tk_while and tk ~= tk_readln and
			tk ~= tk_write and tk ~= tk_int and tk ~= tk_bool
		do
		od;
	    fi;
	    if tk = tk_semicolon then
		nextToken();
	    fi;
	fi;
    od;
corp;

/*
 * defineProc - the outer part of defining a proc.
 */

proc defineProc()void:
    SymbolEntry_t dummyEntry;
    *SymbolEntry_t procSe, se;
    register TokenKind_t tk;
    register uint parCount;
    ResultType_t functionResult, bodyResult, typ;

    nextToken();
    tk := getToken(&procSe, nil, nil);
    if tk = tk_id then
	if procSe*.se_kind ~= sk_empty then
	    errorHereThree("symbol '", procSe*.se_name, "' already defined");
	    procSe := &dummyEntry;
	    dummyEntry.se_name := " ";
	else
	    /* put in non-empty kind, to prevent conflict with parameters */
	    procSe*.se_kind := sk_undefined;
	fi;
	nextToken();
    else
	errorHere("expecting identifier as proc name");
	procSe := &dummyEntry;
	dummyEntry.se_name := " ";
	if tk ~= tk_leftParen then
	    skipToken();
	fi;
    fi;
    tk := getSimpleToken();
    if tk = tk_leftParen then
	nextToken();
    else
	errorHere("expecting '(' to start parameter list");
    fi;
    procStartParameters();
    parCount := 0;
    while
	tk := getSimpleToken();
	tk = tk_int or tk = tk_bool
    do
	typ := if tk = tk_int then rt_int else rt_bool fi;
	if typ = rt_bool then
	    errorHere("proc parameters can only be 'int'");
	fi;
	nextToken();
	while
	    tk := getToken(&se, nil, nil);
	    tk = tk_id
	do
	    if se*.se_kind ~= sk_empty then
		errorHereThree("symbol '", se*.se_name, "' already defined");
	    else
		se*.se_kind := sk_parameter;
		se*.se_type := typ;
		allocateVariable(se);
	    fi;
	    parCount := parCount + 1;
	    nextToken();
	    tk := getSimpleToken();
	    if tk = tk_comma then
		nextToken();
	    elif tk ~= tk_semicolon and tk ~= tk_rightParen then
		errorHere("expecting comma between parameter names");
		while
		    skipToken();
		    tk := getSimpleToken();
		    tk ~= tk_id and tk ~= tk_semicolon and tk ~= tk_comma and
			tk ~= tk_eof and tk ~= tk_int and tk ~= tk_bool and
			tk ~= tk_rightParen and tk ~= tk_colon
		do
		od;
		if tk = tk_comma then
		    nextToken();
		fi;
	    fi;
	od;
	if tk = tk_semicolon then
	    nextToken();
	elif tk ~= tk_rightParen then
	    errorHere("expecting semicolon after parameter list");
	    while
		skipToken();
		tk := getSimpleToken();
		tk ~= tk_semicolon and tk ~= tk_eof and tk ~= tk_int and
		    tk ~= tk_bool and tk ~= tk_rightParen and tk ~= tk_colon
	    do
	    od;
	    if tk = tk_semicolon then
		nextToken();
	    fi;
	fi;
    od;
    procEndParameters();
    if tk = tk_rightParen then
	nextToken();
	tk := getSimpleToken();
    else
	errorHere("expecting ')' after parameter list");
    fi;
    if parCount ~= 0 and CharsEqual(procSe*.se_name, "main") then
	errorHere("'main' cannot have any parameters");
    fi;
    procSe*.se_kind := sk_procedure;
    procSe*.se_parCount := parCount;
    if tk = tk_int or tk = tk_bool then
	functionResult := if tk = tk_int then rt_int else rt_bool fi;
	if CharsEqual(procSe*.se_name, "main") then
	    errorHere("'main cannot have a result");
	fi;
	nextToken();
    elif tk = tk_void then
	functionResult := rt_void;
	nextToken();
    else
	functionResult := rt_error;
	errorHere("expecting result type for proc");
    fi;
    procSe*.se_type := functionResult;
    tk := getSimpleToken();
    if tk = tk_colon then
	nextToken();
	tk := getSimpleToken();
    else
	errorHere("expecting ':' after proc header");
    fi;
    declareVariables(sk_localVariable);
    procEndLocals();
    codeInit(procSe);
    while
	if tk = tk_corp or tk = tk_eof then
	    bodyResult := rt_void;
	else
	    bodyResult := parseStatementList();
	    tk := getSimpleToken();
	fi;
	tk ~= tk_corp and tk ~= tk_eof
    do
	errorHere("expecting 'corp' to end proc");
	findStatement();
    od;
    if functionResult = rt_int or functionResult = rt_bool then
	if bodyResult = rt_void then
	    errorHere("expecting result expression at end of function");
	else
	    if functionResult ~= bodyResult then
		errorHere("type mismatch for proc result");
	    fi;
	    procResult();
	fi;
    elif functionResult = rt_void and
	(bodyResult = rt_int or bodyResult = rt_bool)
    then
	errorHere("unwanted result expression at end of procedure");
    fi;
    codeTerm(procSe);
    symbolPurge();
    if tk = tk_corp then
	nextToken();
    else
	errorHere("missing 'corp' on last proc");
    fi;
    if getSimpleToken() = tk_semicolon then
	nextToken();
    fi;
corp;
