#toy.g
#externs.g

/*
 * statement.d: parsing and code generation of statements.
 */

uint ELIF_MAX = 10;

/*
 * parseIfStatement - parsing and code generation for the 'if' statement.
 */

proc parseIfStatement()void:
    [ELIF_MAX + 1] uint offsets;
    register uint offsetCount, lastBranch;
    register TokenKind_t tk;
    register ResultType_t typ;
    bool first;

    first := true;
    offsetCount := 0;
    while
	nextToken();
	if not first then
	    if offsetCount = ELIF_MAX + 1 then
		abort("too many 'elif's in 'if'");
	    fi;
	    offsets[offsetCount] := branchForward(false);
	    offsetCount := offsetCount + 1;
	    patchBranch(lastBranch);
	fi;
	first := false;
	typ := parseExpression();
	if typ = rt_void then
	    errorHere("condition for 'if' must be expression");
	else
	    if typ ~= rt_bool and typ ~= rt_error then
		errorHere("condition for 'if' must be bool");
	    fi;
	    lastBranch := branchForward(true);
	fi;
	if getSimpleToken() = tk_then then
	    nextToken();
	else
	    errorHere("expecting 'then' in 'if'");
	fi;
	if parseStatementList() ~= rt_void then
	    errorHere("'if' expressions not allowed");
	fi;
	tk := getSimpleToken();
	if tk ~= tk_elif and tk ~= tk_else and tk ~= tk_fi then
	    errorHere("expecting 'elif', 'else' or 'fi' in 'if'");
	fi;
	tk = tk_elif
    do
    od;
    if tk = tk_else then
	nextToken();
	if offsetCount = ELIF_MAX + 1 then
	    abort("too many 'elif's in 'if'");
	fi;
	offsets[offsetCount] := branchForward(false);
	offsetCount := offsetCount + 1;
	patchBranch(lastBranch);
	if parseStatementList() ~= rt_void then
	    errorHere("'if' expressions not allowed");
	fi;
	tk := getSimpleToken();
    else
	patchBranch(lastBranch);
    fi;
    while offsetCount ~= 0 do
	offsetCount := offsetCount - 1;
	patchBranch(offsets[offsetCount]);
    od;
    if tk = tk_fi then
	nextToken();
    else
	errorHere("expecting 'fi' to end 'if'");
    fi;
corp;

/*
 * parseWhileStatement - parsing and code generation for the 'while' statement.
 */

proc parseWhileStatement()void:
    register uint loopPos, branchPos;
    register ResultType_t typ;

    nextToken();
    loopPos := getPos();
    typ := parseStatementList();
    if typ = rt_void then
	errorHere("condition for 'while' must be expression");
    else
	if typ ~= rt_bool and typ ~= rt_error then
	    errorHere("condition for 'while' must be bool");
	fi;
	branchPos := branchForward(true);
    fi;
    if getSimpleToken() = tk_do then
	nextToken();
    else
	errorHere("expecting 'do' in 'while'");
    fi;
    if parseStatementList() ~= rt_void then
	errorHere("body of 'while' must not be an expression");
    fi;
    branchTo(loopPos);
    patchBranch(branchPos);
    if getSimpleToken() = tk_od then
	nextToken();
    else
	errorHere("expecting 'od' to end 'while'");
    fi;
corp;

/*
 * parseReadlnStatement - handle the 'readln' statement.
 */

proc parseReadlnStatement()void:
    *SymbolEntry_t se;
    register TokenKind_t tk;

    nextToken();
    if getSimpleToken() = tk_leftParen then
	nextToken();
    else
	errorHere("expecting '(' in 'readln' statement");
    fi;
    callSpecial(rc_readLine);
    while getToken(&se, nil, nil) = tk_id do
	checkUndef(se);
	checkAssign(se);
	if se*.se_type ~= rt_int and se*.se_type ~= rt_error then
	    errorHere("only int variables can be used in 'readln'");
	fi;
	callSpecial(rc_readInt);
	popVariable(se);
	nextToken();
	tk := getSimpleToken();
	if tk = tk_comma then
	    nextToken();
	elif tk ~= tk_rightParen then
	    errorHere("expecting ',' between variables in 'readln'");
	    findClose();
	fi;
    od;
    if getSimpleToken() = tk_rightParen then
	nextToken();
    else
	errorHere("expecting ')' to end 'readln' statement");
    fi;
corp;

/*
 * parseWriteStatement - handle the 'write' statement.
 */

proc parseWriteStatement()void:
    *char buffer;
    ulong number;
    register TokenKind_t tk;
    ResultType_t typ;

    nextToken();
    if getSimpleToken() = tk_leftParen then
	nextToken();
    else
	errorHere("expecting '(' in 'write' statement");
    fi;
    while
	tk := getToken(nil, &buffer, &number);
	tk ~= tk_eof and tk ~= tk_rightParen and
	    (tk = tk_id or tk = tk_number or not isStatement())
    do
	if tk = tk_string then
	    emitString(buffer, number);
	    callSpecial(rc_printString);
	    nextToken();
	else
	    typ := parseExpression();
	    if typ = rt_void then
		errorHere("expecting string or expression in 'write'");
	    else
		if typ ~= rt_int and typ ~= rt_error then
		    errorHere("only int values can be used with 'write'");
		fi;
		callSpecial(rc_printInt);
	    fi;
	fi;
	tk := getSimpleToken();
	if tk = tk_comma then
	    nextToken();
	elif tk ~= tk_rightParen then
	    errorHere("expecting ',' between expressions in 'write'");
	    findClose();
	fi;
    od;
    if getSimpleToken() = tk_rightParen then
	nextToken();
    else
	errorHere("expecting ')' to end 'write' statement");
    fi;
corp;

/*
 * parseAssignmentStatement - handle ':='; variable already given.
 */

proc parseAssignmentStatement(register *SymbolEntry_t se)void:
    register ResultType_t typ;

    checkAssign(se);
    nextToken();
    typ := parseExpression();
    if typ = rt_void then
	errorHere("assignment right-hand-side must be expression");
    else
	if typ ~= se*.se_type and typ ~= rt_error and se*.se_type ~= rt_error
	then
	    errorHere("assignment incompatibility");
	fi;
	popVariable(se);
    fi;
corp;

/*
 * parseStatement - handle a single statement or expression.
 */

proc parseStatement()ResultType_t:
    register TokenKind_t tk;
    ResultType_t typ;

    typ := rt_void;
    tk := getSimpleToken();
    if tk = tk_if then
	parseIfStatement();
    elif tk = tk_while then
	parseWhileStatement();
    elif tk = tk_readln then
	parseReadlnStatement();
    elif tk = tk_write then
	parseWriteStatement();
    else
	typ := parseExpression();
    fi;
    typ
corp;

/*
 * parseStatementList - handle a ';'-separated list of statements, which
 *	can end in an expression.
 */

proc parseStatementList()ResultType_t:
    register TokenKind_t tk;
    register ResultType_t typ;

    typ := rt_void;
    while
	tk := getSimpleToken();
	tk ~= tk_eof and tk ~= tk_int and tk ~= tk_proc and tk ~= tk_corp and
	    tk ~= tk_elif and tk ~= tk_else and tk ~= tk_fi and
	    tk ~= tk_od and tk ~= tk_do
    do
	if tk = tk_semicolon then
	    if typ ~= rt_void and typ ~= rt_error then
		errorHere("can't use expression as a statement");
	    fi;
	    nextToken();
	    typ := rt_void;
	else
	    typ := parseStatement();
	fi;
    od;
    typ
corp;
