ucc compiler (semantic analysis)

Posted by ultrus on Sat, 29 Jan 2022 01:47:44 +0100

[statement: All rights reserved, welcome to reprint, please do not use it for commercial purposes. Contact email: feixiaoxing @163.com]

 

Code that conforms to syntax does not necessarily conform to semantics. This sentence sounds awkward. Let's take an example. Suppose we define a variable int a;, At this time, we can't define an int a. This is an example of a variable.

 

We can also give an example of a function. Suppose a function has no return value, but a return true statement appears during implementation. Is this also unreasonable.

 

Because the syntax tree itself has been created, at this time, it is only necessary to advance layer by layer according to the structure of the syntax tree. The basic processing method is similar to the post order traversal of binary tree.

 

1. Entrance to semantic parsing

void CheckTranslationUnit(AstTranslationUnit transUnit)

 

2. Every syntax parsing place has a semantic parsing file

declchk.c

https://github.com/sheisc/ucc162.3/blob/master/ucc/ucl/declchk.c

 

exprchk.c

https://github.com/sheisc/ucc162.3/blob/master/ucc/ucl/exprchk.c

 

stmtchk.c

https://github.com/sheisc/ucc162.3/blob/master/ucc/ucl/stmtchk.c

 

3,symbol.c

A key file for semantic parsing

https://github.com/sheisc/ucc162.3/blob/master/ucc/ucl/symbol.c

Whether variables exist, functions exist, and types are correct are all related to symbol C closely related.

symbol. addTag and addFunction in C will appear in different semantic check files.

 

4. Without losing generality, let's analyze stmtchk c

4.1 entry function

static AstStatement CheckStatement(AstStatement stmt)
{
	return (* StmtCheckers[stmt->kind - NK_ExpressionStatement])(stmt);
}

 

4.2 check case statement

/**
	case-statement:
					case  constant-expression :	statement
	 Constraints 
		A case or default label shall appear only in a switch statement.
	 Further constraints on such labels are discussed under the switch
	 statement.
	 Even the following code is legal:
	 	int a = 5;
		switch(a){
			printf("123.\n");	---------- these are dead-code.
			break;
		case 3:
			printf("3.\n");
			printf("this printf is not part of case-statment from syntactic view.\n");
		case 2:
			printf("2.\n");
		}
 */
static AstStatement CheckCaseStatement(AstStatement stmt)
{
	AstCaseStatement caseStmt = AsCase(stmt);
	AstSwitchStatement swtchStmt;
	// We have pushed the current switch statement in CheckSwitchStatement(...)
	swtchStmt = (AstSwitchStatement)TopStatement(CURRENTF->swtches);
	if (swtchStmt == NULL)
	{
		Error(&stmt->coord, "A case label shall appear in a switch statement.");
		return stmt;
	}

	caseStmt->expr = CheckConstantExpression(caseStmt->expr);
	if (caseStmt->expr == NULL)
	{
		Error(&stmt->coord, "The case value must be integer constant.");
		return stmt;
	}

	caseStmt->stmt = CheckStatement(caseStmt->stmt);
	caseStmt->expr = FoldCast(swtchStmt->expr->ty, caseStmt->expr);
	AddCase(swtchStmt, caseStmt);

	return stmt;
}

The most common problems in case statement are that there is no switch statement outside, and that value is not an integer.

 

4.3 check for statement

/**
	 iteration-statement:
			 while (  expression )	statement
			 do  statement while (	expression ) ;
 */
static AstStatement CheckLoopStatement(AstStatement stmt)
{
	AstLoopStatement loopStmt = AsLoop(stmt);

	PushStatement(CURRENTF->loops,    stmt);
	PushStatement(CURRENTF->breakable, stmt);
	// Adjust expr's type to Pointer(...)  when its type is FUNCTION/ARRAY
	loopStmt->expr = Adjust(CheckExpression(loopStmt->expr), 1);
	if (! IsScalarType(loopStmt->expr->ty))
	{
		Error(&stmt->coord, "The expression in do or while statement shall be scalar type.");
	}
	loopStmt->stmt = CheckStatement(loopStmt->stmt);
	
	PopStatement(CURRENTF->loops);
	PopStatement(CURRENTF->breakable);

	return stmt;
}

The main problem of circular statements is to judge whether expression is a scalar.

 

4.4 check break statement

// break;
static AstStatement CheckBreakStatement(AstStatement stmt)
{
	AstBreakStatement brkStmt = AsBreak(stmt);

	brkStmt->target = TopStatement(CURRENTF->breakable);
	if (brkStmt->target == NULL)
	{
		Error(&stmt->coord, "The break shall appear in a switch or loop");
	}

	return stmt;
}

The break statement is mainly used to judge whether it is in the loop.

 

4.5 check default statement

/**
	default-statement
			default :  statement
 */
static AstStatement CheckDefaultStatement(AstStatement stmt)
{
	AstDefaultStatement defStmt = AsDef(stmt);
	AstSwitchStatement swtchStmt;

	swtchStmt = (AstSwitchStatement)TopStatement(CURRENTF->swtches);
	if (swtchStmt == NULL)
	{
		Error(&stmt->coord, "A default label shall appear in a switch statement.");
		return stmt;
	}
	if (swtchStmt->defStmt != NULL)
	{
		Error(&stmt->coord, "There shall be only one default label in a switch statement.");
		return stmt;
	}

	defStmt->stmt = CheckStatement(defStmt->stmt);
	swtchStmt->defStmt = defStmt;

	return stmt;
}

The default statement mainly has two problems: one is to judge whether it is not in the switch statement, and the other is to judge whether there is only one default statement.

 

4.6 summary

The part of statement semantic analysis is similar, mainly to list the possible problems. Generally speaking, if there is no problem, it will be considered that the statement is no problem, and the following binary translation can be carried out.