CS Engineering Gyan

Semantic Analysis

A program can be perfectly well formed and still be nonsense. Consider a statement like "total = "hello" + count;" where a text string is added directly to a number, or a line that uses a variable no one ever declared. Neither of these problems would trouble a lexical analyzer, since every character forms a valid token, and neither would trouble a parser, since the overall shape of the statement matches the grammar perfectly well. Catching mistakes like these requires a phase that actually understands what a program means, not just how it is structured, and that phase is semantic analysis.

Semantic analysis sits right after syntax analysis in the compilation pipeline, and it is where a compiler starts to behave less like a grammar checker and more like a careful reader who actually understands the rules of the language being used. It draws heavily on the annotated parse trees and attribute-based techniques introduced in the previous tutorial on syntax directed translation, using them to compute and check meaningful properties of a program, such as the types of expressions and the scope of variables.

In this tutorial, you will learn what semantic analysis actually checks, how type checking works and what type conversion means, how scope resolution allows a compiler to correctly track which declaration a given variable refers to, the role the symbol table plays throughout this phase, common categories of semantic errors, and how semantic analysis connects to the syntax directed translation techniques covered previously.


What is Semantic Analysis?

Semantic analysis is the compiler phase responsible for checking whether a syntactically valid program actually makes sense according to the rules of the language, going beyond grammar to examine meaning. It takes the parse tree or syntax tree produced by the previous phase and walks through it, applying a series of checks and computations, often using exactly the kind of synthesized and inherited attributes described in the syntax directed translation tutorial.

Unlike syntax analysis, which relies almost entirely on the context-free grammar of the language, semantic analysis frequently needs information that cannot be captured by a context-free grammar at all, such as whether a particular identifier has already been declared earlier in the program, or what specific data type that identifier was declared with. This is precisely why semantic analysis is treated as its own distinct phase, separate from parsing, even though both phases are concerned with validating the correctness of a program.


Categories of Checks Performed During Semantic Analysis

Semantic analysis covers a fairly broad set of responsibilities, and different compilers may organize these checks somewhat differently, but most of them fall into a few recognizable categories.

Category Description
Type Checking Verifying that operations are performed on compatible data types, and that values assigned to a variable match its declared type.
Scope Resolution Determining which declaration a given use of an identifier actually refers to, based on the nested block and function structure of the program.
Declaration Checking Confirming that every identifier used in the program has been properly declared before it is used, and that no identifier is declared more than once in a conflicting way within the same scope.
Function Call Verification Checking that a function is called with the correct number of arguments, and that each argument's type is compatible with the corresponding parameter.
Control Flow Checks Verifying certain structural rules related to meaning rather than grammar, such as ensuring a break statement only appears inside a loop or switch construct.

Type Checking

Type checking is one of the most prominent responsibilities of semantic analysis, and it involves verifying that every operation in a program is applied to operands of an appropriate and compatible type. A compiler performs type checking by computing the type of each expression as a synthesized attribute, working upward from individual literals and variables toward larger, more complex expressions, exactly following the attribute evaluation pattern introduced in the previous tutorial.

Example

Type Checking Rules (Simplified):

Expression -> Expression1 + Expression2
    {
      if Expression1.type == int and Expression2.type == int:
          Expression.type = int
      else if Expression1.type == float or Expression2.type == float:
          Expression.type = float
      else:
          report type error
    }

Using a rule like this, a compiler can automatically determine that adding two integers produces an integer, that adding an integer and a floating-point number produces a floating-point number, and that attempting to add a number to a text string, assuming the language does not explicitly support that operation, should be reported as a type error.


Type Conversion

Many programming languages allow certain type mismatches to be resolved automatically rather than treated as outright errors, through a process called type conversion, or more specifically, type coercion when it happens automatically without an explicit instruction from the programmer. Semantic analysis is responsible for identifying exactly where these conversions are needed and inserting the appropriate conversion operations into the program's internal representation.

Type of Conversion Description
Widening Conversion Converting a value from a smaller or less precise type to a larger or more precise type, such as converting an integer to a floating-point number, which is usually safe and done automatically.
Narrowing Conversion Converting a value from a larger or more precise type to a smaller or less precise type, such as converting a floating-point number to an integer, which can lose information and is often required to be explicit.

Example

Source Code:
float total;
int quantity = 5;
total = quantity;

Semantic Analysis Result:
An implicit widening conversion is inserted, converting the integer value of quantity to a floating-point value before the assignment takes place.

This kind of automatic, safe conversion allows programmers to write natural, convenient code without manually converting every value themselves, while still ensuring that the underlying operations performed by the compiler always work on genuinely compatible types.


Scope Resolution

Programming languages generally allow the same identifier name to be reused in different parts of a program, as long as those uses occur in different scopes, such as separate functions or nested blocks. Scope resolution is the process semantic analysis uses to determine exactly which declaration a particular use of an identifier actually refers to, based on the nested structure of the program.

Example

Source Code:
int value = 10;

function display() {
    int value = 20;
    print(value);
}

print(value);

In this example, the identifier "value" is declared twice, once outside the function and once inside it. Semantic analysis must correctly determine that the print statement inside the function refers to the inner declaration, with a value of 20, while the print statement outside the function refers to the outer declaration, with a value of 10, based purely on the nested scoping rules of the language.

Compilers typically implement scope resolution using a structure often called a scope stack or a chain of nested symbol tables, where entering a new block pushes a fresh scope onto the stack, and leaving that block pops it back off, ensuring that identifiers declared inside a block are no longer visible once that block has ended.


The Role of the Symbol Table

The symbol table, introduced briefly in the tutorial on the phases of a compiler, becomes especially important during semantic analysis, since nearly every check performed in this phase depends on information stored there. For every identifier in the program, the symbol table typically records details such as its declared type, its scope, and sometimes additional information like whether it represents a variable, a function, or some other kind of named entity.

Example

Simplified Symbol Table Entries:

Name       Type      Scope
total      float     global
quantity   int       global
value      int       function display

When semantic analysis encounters a use of an identifier, it consults the symbol table, taking scope rules into account, to retrieve the correct declaration and its associated type information, which is then used to perform type checking, verify function calls, and support the various other checks described earlier in this tutorial.


Common Semantic Errors

Type of Semantic Error Example
Using a variable that has not been declared anywhere in an accessible scope. Referring to a variable named "result" that was never declared in the current function or any enclosing scope.
Assigning a value of an incompatible type to a variable without a valid conversion. Attempting to assign a text string directly to a variable declared as an integer.
Calling a function with the wrong number of arguments. Calling a function that expects two parameters while providing only one argument.
Declaring the same identifier more than once within the same scope in a conflicting way. Declaring two different variables with the same name inside the same function body.
Using a control statement in a context where it is not permitted by the rules of the language. Placing a break statement outside of any loop or switch construct.

Semantic Analysis vs Syntax Analysis

It is worth directly comparing semantic analysis to syntax analysis, since students sometimes struggle to draw a clean line between what belongs to each phase, especially since both are ultimately concerned with validating a program before it can proceed further through compilation.

Aspect Syntax Analysis Semantic Analysis
Primary Concern Whether tokens are arranged according to the grammar of the language. Whether a grammatically valid program actually makes sense in terms of types, scope, and meaning.
Underlying Tool Context-free grammars and parsing algorithms. Symbol tables, type systems, and attribute-based computation.
Typical Errors Detected Missing operators, unbalanced grouping symbols, or misplaced keywords. Undeclared variables, type mismatches, and incorrect function calls.
Output A parse tree representing the program's grammatical structure. An annotated syntax tree, verified for meaning and enriched with computed type information.

How Semantic Analysis Connects to Syntax Directed Translation

Semantic analysis is, in many practical compiler implementations, simply a specific and very important application of the syntax directed translation techniques covered in the previous tutorial. Type checking rules are typically written as synthesized attributes, computing the type of larger expressions from the types of their smaller sub-expressions. Scope-related checks often rely on inherited attributes, passing scope information downward through the parse tree so that each identifier can be checked against the correct enclosing declarations.

This close relationship is precisely why syntax directed translation was introduced before semantic analysis in this tutorial series. Rather than being a completely separate set of techniques, semantic analysis largely reuses the same attribute-based framework, applying it to the specific and practically essential goal of verifying that a program's meaning is correct before compilation proceeds any further.


Common Mistakes Beginners Make

Mistake Correct Understanding
Assuming a program that passes syntax analysis is already guaranteed to run correctly. Syntax analysis only checks grammatical structure. Semantic analysis is still required to catch type mismatches, undeclared variables, and other meaning-related errors.
Believing type conversion always happens automatically and safely, regardless of direction. Widening conversions are generally safe and automatic, but narrowing conversions can lose information and are often required to be written explicitly by the programmer.
Treating scope resolution as a simple matter of matching identifier names without considering nesting. Scope resolution must account for the nested structure of blocks and functions, since the same identifier name can refer to entirely different declarations depending on where it is used.
Overlooking the symbol table as a passive record, rather than an actively consulted resource. The symbol table is actively consulted throughout semantic analysis, providing the type and scope information needed to perform nearly every check in this phase.

Frequently Asked Interview Questions

  1. What is semantic analysis in compiler design?
    Semantic analysis is the compiler phase that checks whether a syntactically valid program is actually meaningful, verifying rules related to types, scope, and correct usage of identifiers and functions.
  2. What is the difference between type checking and type conversion?
    Type checking verifies whether operations are performed on compatible types, while type conversion automatically or explicitly transforms a value from one type to another when a compatible conversion is possible.
  3. What is the difference between a widening conversion and a narrowing conversion?
    A widening conversion transforms a value into a larger or more precise type and is generally safe, while a narrowing conversion transforms a value into a smaller or less precise type and can lose information.
  4. What is scope resolution, and why is it important?
    Scope resolution determines which declaration a particular use of an identifier refers to, based on the nested block and function structure of the program, which is essential since the same name can be reused in different scopes.
  5. What role does the symbol table play during semantic analysis?
    The symbol table stores information about every identifier, including its type and scope, and is consulted throughout semantic analysis to perform type checking, scope resolution, and function call verification.
  6. What are some common examples of semantic errors?
    Common semantic errors include using an undeclared variable, assigning an incompatible type without a valid conversion, calling a function with the wrong number of arguments, and declaring conflicting identifiers within the same scope.
  7. How does semantic analysis relate to syntax directed translation?
    Semantic analysis is largely implemented using the same synthesized and inherited attribute techniques introduced in syntax directed translation, applying them specifically to compute and verify type and scope information.

Summary

Semantic analysis is where a compiler moves beyond checking grammatical structure and starts verifying that a program actually makes sense, catching errors such as type mismatches, undeclared variables, and incorrect function calls that syntax analysis alone cannot detect. Using the symbol table to track type and scope information, and relying heavily on the synthesized and inherited attribute techniques from syntax directed translation, this phase computes and checks meaningful properties throughout the parse tree, inserting type conversions where appropriate and reporting semantic errors where a program's meaning breaks down.

In this tutorial, you learned what semantic analysis checks and why it is necessary, how type checking and type conversion work with concrete examples, how scope resolution correctly distinguishes between identically named identifiers in different scopes, the central role played by the symbol table, common categories of semantic errors, and how this entire phase connects back to the attribute-based framework of syntax directed translation. With this foundation in place, you are ready to explore intermediate code generation, where a verified and annotated program is transformed into a simplified, machine-independent representation.


← Previous: Syntax Directed Translation Next: Intermediate Code Generation →

Home Visit Our YouTube Channel