본문 바로가기

컴파일러

Semantic Analysis - 1

semantic analysis 를 공부하기 전에 lexical 과 syntax analysis 를 배웠는데, lexical analysis 를 통해 캐릭터 스트림을 토큰 스트림으로 만들어 내고 이를 기반으로 syntax analysis 를 통해 문법을 체크했다.

 

위 과정을 거친 문제가 없는 프로그램들에 다른 추가적인 문제가 있을 수 있다. 예를 들어, 변수 사용에서 어떤 타입인지는 syntax analysis 에서 알 수 없고, 이미 정의된 것인지 여부도 알 수 없다.

 

이러한 추가적인 오류들을 체크하는 것이 semantic analysis 이다. 의미적으로 문제가 없는지 체크하는 것이다. 즉, context 를 본다는 것인데, context 는 지금 우리가 알고 있는 상황을 말한다. context 는 시스템적으로 memory 에 저장을 하는데, 보통 룩업 테이블 형태로 저장한다. 

 

 

* Categories of Semantic Analysis

> semantic 규칙의 예 )

   - 변수는 사용되기 전에 정의되어야 한다.

   - 변수는 여러번 정의되면 안된다.

   - 할당할 때, 변수와 수식은 같은 타입이어야 한다.

   - if 문은 bool 형태를 가져야 한다.

 

> 2 major categories : types / scopes 와 관련된 규칙

 

 

* Type Information / Checking

semantic analysis 의 메인 카테고리는 type 과 scope information 이다.

> Type information은 다양한 구조체에서 어떤 종류의 값들을 가질 수 있는지 설명해주는 것이다.

 

예를 들어 다음과 같이 값들의 정보를 알려준다고 보면 된다. 

- variables : int a; -> integer

- expressions : (a+1) == 2 -> boolean

- statements : a = 1.0; -> floating-point

- functions : int pow(int n, int m) -> int = int, int

 

> Type checking 은 실제로 정해진 타입의 룰을 따르는지 확인하는 것이다. 파이썬과 C를 비교했을 때 파이썬이 더 느린데, 런타임에서 한줄씩 번역해서 가기 때문에 그 중에서 타입 체킹때문에 느리다. 타입 체킹을 빠르게 하는 것은 중요한 부분이다.

 

 

* Scope Information

identifier 가 선언되고 프로그램의 어느 부분에서 사용될 수 있는지 지정하는 것이다. lexical scope 로 많이 지정되는데, 이는 코드를 보는 것이다. statement block, function body 등 프로그램에서 코드를 보고 region 을 정한다.

identifier 의 scope 는 선언된 lexical scope 를 지정해서 만든다.

 

 

* Variable Scope

global variable 의 경우에는 scope 가 현재 파일이고, external variable 의 경우에는 scope 가 전체 프로그램이 된다. 

 

 

* Semantic Rules for Scopes

> rule 1 : 각각의 identifier 는 각각의 scope 안에서만 사용된다.

> rule 2 : lexical scope 안에서는 같은 이름을 가진 같은 종류의 identifier 는 만들어지면 안된다.

 

이러한 규칙을 어떻게 체크할까? 컴파일러는 scope 문제를 symbol table 을 이용해 해결한다. symbol table 은 symbol 을 저장하는 lookup table 이고, identifier 들의 특성을 저장한 테이블이다. semantic analysis 를 위해서는 identifier 에 대한 정보를 저장해야 하고, symbol table 를 활용해 저장한다. symbol table 의 각 entry 가 identifier 의 이름과 종류, 타입, 특수정보 등을 저장한다. 

Symbol Table

 

 

* Scope Information

symbol table 에 어떻게 scope information 을 저장할 수 있을까?

> Idea

- 프로그램에는 scope 계층이 있기 때문에 symbol table 을 계층 구조로 만들어 보자.

- 각 scope 에 하나의 symbol table 을 가진다. 

- 각 symbol table 은 lexical scope 에서 선언된 symbol 들을 포함한다. 

=> 내가 있는 scope 에서 어떤 변수가 지정되고 사용할 수 있는지 알 수 있다. 

 

예를 보자.

 

Symbol Table 의 계층 구조

각각의 scope 별로 symbol table 을 가지고 있다. global scope 는 파일이므로 int x 와 function f, g 가 symbol table 에 들어간다. function f 의 scope 에는 파라미터인 m 과 변수 x, y 가 있고, 이 함수 안에는 두 개의 scope 가 존재한다. function g 도 마찬가지로 파라미터 n 과 변수 t 가 symbol table 에 들어간다.

 

이렇게 symbol table 을 여러 계층으로 만들었기 때문에 이름 충돌 문제 (name collision) 를 해결할 수 있다. identifier 를 찾을 때, 현재 scope 의 symbol table 에서 먼저 찾고 없다면 계층 구조에서 위로 올라가서 찾는다. (없으면 에러)

 

>> 코드가 주어지고 계층 구조를 가진 symbol table 를 만들 수 있어야 한다.

 

 

* Symbol Table Operations

> symbol table 은 2개의 operation 으로 이루어져 있다.

   - table 을 만들 때, 새로운 identifier 를 추가해야 한다. (Insert)

   - 컴파일러가 프로그램을 보면서 table 에 있는지 찾아봐야 한다. (lookup function)

 

table 을 만드는 것은 lexical analysis 중에는 불가능하다. lexical analysis 는 토큰을 추출하는 것인데, 토큰이 identifier 가 되기 때문에 그때그때 symbol 을 알 수 없다.

 

symbol table 을 만들 때는  parsing 할 때 AST (Abstract Syntax Tree) 를 만들면서 insert 를 하고, 만들어진 후에 lookup 을 한다.

 

 

* Forward References

class A {
    int m() { return n(); }
    int n() { return 1; }
}

 

identifier 가 선언되기 전에 사용되는 경우를 말하는데, 문제가 되지 않으려면 AST 를 만들고 symbol table 을 만들어 나중에 타입 체킹을 한다. 즉, 컴파일러가 table 이 만들어지고 lookup 을 수행하도록 한다. type check 와 table build 는 동시에 할 수 없기 때문에 type check 를 나중에 한다.

 

 

* Back to Type Checking

타입은 프로그램이 실행되는 동안 사용 가능한 변수를 말한다. 

타입 에러는 프로그램 실행 동안 어떤 operation 이 잘못 동작하거나 특성이 바뀌는 경우이다.

 

> Type-Safety 를 어떻게 보장할 수 있을까?

타입을 바인딩(binding) 하고 타입을 체크한다.

타입 바인딩은 프로그램에서 어떤 구조들의 타입을 정의하는 것이다. 명시적으로 정의하거나 (int x), 암묵적으로 정의할 수 있다. (x = 1) Type consistency (safety) 는 타입이 맞게 잘 사용되고 있는지 체크한다.

타입 체킹은 어떤 프로그램에서 구조체가 타입 바인딩 정보에 따라서 올바르게 사용되고 있는지 체크하는 것이고, 타입 체킹 규칙에 의해 체크한다. 

 

 

* Type Checking 

semantic 체킹인데, 프로그램에서 type safety 를 체크하는 과정이다. 

예를 들어,

어떤 operator 가 있을 때, operand 가 올바른 타입으로 들어가야 한다.

function 은 올바른 개수와 타입의 argument 가 들어와야 한다.

return statement 는 return type 을 맞춰야 한다.

assignment 할 때, 왼쪽에 있는 타입과 호환가능한 타입이어야 한다.

 

 

* 4 Concepts Related to Types / Languages

1. static vs dynamic checking

   (타입 체킹이 컴파일타임 / 런타임 에 이루어짐)

   (빠르다. / 프로그램 짜기 쉽지만 성능저하가 있다.)

 

2. static vs dynamic typing

   (타입 define 이 컴파일타임 / 런타임 에 이루어짐)

 

3. strong vs weak typing

   (얼마나 정밀하게 타입을 체크할 것인지)

 

4. sound type systems

   (모든 타입 에러를 static 하게 잡을 수 있는 시스템)

   (컴파일타임에 타입적으로 문제가 없는 것을 보장해준다.)

 

위 개념에 따라 언어를 분류하면 다음과 같다.

 

 

 

 

그렇다면 왜 static checking 을 할까?

런타임에서의 체킹(dynamic) 은 프로그램이 느려지기 때문이다. 또한, static checking 은 프로그램이 실제 동작할 때 문제가 없다고 보장할 수 있는 것이다. 

'컴파일러' 카테고리의 다른 글

Control Flow - 1  (0) 2022.12.17
Intermediate Representation - 2 (stack frame)  (0) 2022.12.16
Intermediate Representation - 1  (0) 2022.12.15
Semantic Analysis - 2  (0) 2022.12.14