CHAPTER 7

Semantic Analysis — Type System과 Type Checking

Douglas Thain, Introduction to Compilers and Language Design (2nd ed., 2023) · pp.99–118

📌단원 개요

AST 구성을 마쳤으니, 이제 프로그램의 구조가 아니라 실제 의미(semantics)를 분석한다. 이 단계가 semantic analysis이며 핵심은 type checking이다.

💡 이 장의 큰 그림 — 3단계
  • ① name resolution — expression 안의 각 identifier가 어떤 정의(local / parameter / global)를 가리키는지 결정한다. 각 정의를 symbol table에 등록한다.
  • ② type checking — name resolution이 끝나면 모든 정보가 모이므로, standard conversion rule에 따라 복합 expression의 type을 계산하고 검증한다.
  • ③ 그 외 correctness 검사 — array 범위, bad pointer traversal, control flow 등도 semantic analysis에 포함된다. 언어 설계에 따라 compile time 혹은 runtime에 검출된다.
💡 왜 type checking이 중요한가
  • type system은 programmer가 verifiable assertion을 하게 해주고, 그것을 compiler가 자동으로 검사한다.
  • 덕분에 error를 runtime이 아니라 compile-time에 검출할 수 있다.
  • 언어마다 접근이 다르다: C는 weak type system(잘못하면 심각한 error 가능), Ada는 strong type system(대신 compile 자체가 까다로움).
AST name resolution symbol table type checking 검증된 의미

7.1Overview of Type Systems

대부분의 언어는 모든 value(literal, constant, variable)에 type을 부여한다. type은 그 data를 어떻게 해석할지를 나타낸다 — integer, floating point, boolean, string, pointer 등.

이런 atomic type들은 enumeration, structure, variant type 같은 higher-order type으로 결합되어 복잡한 제약을 표현할 수 있다.

type system의 3가지 목적

목적설명
Correctness부적절한 동작을 시도하면 warning/error를 발생. 예: pointer variable에 integer 할당은 거의 항상 error. runtime error를 compile time에 미리 걸러냄.
Performancetype 정보를 이용해 가장 효율적인 구현을 찾음. 예: 변수가 constant임을 알면 register에 한 번 load해 여러 번 재사용.
Expressivenesstype system으로 추론 가능한 사실은 생략 가능 → 더 간결. 예: B-Minor의 print는 integer/string/boolean을 명시할 필요 없이 type을 inference해 알맞게 출력.

type system 분류의 3축

safe ↔ unsafe static ↔ dynamic explicit ↔ implicit
💡 safe vs unsafe
  • unsafe: 프로그램의 기본 구조를 위반하는 wildly undefined behavior를 작성 가능. 예: C에서 임의의 pointer로 메모리 아무 word나 수정 가능. OS·driver 같은 low-level 코드엔 필요하지만 일반 코드엔 문제.
  • safe: 어떤 입력에도 항상 well-defined 방식으로 실행되어 언어의 abstraction을 보존. array 경계, pointer 사용, type 할당을 강제. Perl, Python, Java 같은 대부분의 interpreted language가 safe.

unsafe — C (배열 경계 침범, 결과 미정)

/* This is C code */
int i;
int a[10];
for(i=0;i<100;i++) a[i] = i;

safe — C# (runtime에 bounds checking → exception)

/* This is C-sharp code */
a = new int[10];
for(int i=0;i<100;i++) a[i] = i;
// IndexOutOfRangeException 발생
💡 static vs dynamic
  • static: 모든 typechecking을 compile-time에 수행. 실행 시 type 정보를 유지할 필요가 없어 가장 high performance code를 생성. 단, 일부 편리한 idiom은 불가능.
  • static typing은 integer vs floating point 구분에 자주 쓰임. (a+b)가 integer면 X86에서 ADDL, floating point면 FADD 명령으로 번역됨 → a, b의 type을 먼저 알아야 +의 의미를 결정.
  • dynamic: type 정보를 runtime에 data와 함께 메모리에 저장. 실행 중 각 연산의 안전성을 operand의 type 비교로 검사하고, 호환되지 않으면 runtime type error로 중단. 변수의 type을 명시적으로 검사 가능(예: Java의 instanceof).

dynamic 예 — Java instanceof

/* This is Java code */
public void sit( Furniture f ) {
   if (f instanceof Chair) {
       System.out.println("Sit up straight!");
   } else if ( f instanceof Couch ) {
       System.out.println("You may slouch.");
   } else {
       System.out.println("You may sit normally.");
   }
}
💡 explicit vs implicit
  • explicit: programmer가 변수 등의 type을 직접 명시. 노력은 더 들지만 예기치 못한 error 가능성을 줄임. 예: C에서 int x = 32.5;는 precision loss로 error/warning 가능. 같은 표현이지만 의미가 다른 pointer 간 할당(int *i; float *f = i;)도 error/warning.
  • implicit: compiler가 가능한 범위에서 type을 inference. 더 간결하지만 의도치 않은 동작 가능. 예: C++11의 auto x = 32.5;32.5double이므로 xdouble로 추론.
/* This is C code */
int x = 32.5;          // precision loss
/* This is C code */
int *i;
float *f = i;          // pointer 타입 불일치
/* This is C++11 code */
auto x = 32.5;         // x 는 double 로 추론
cout << x << endl;
⭐ 시험 포인트 — 분류 3축 정확히 구분
  • safe/unsafe = 언어 구조 위반(undefined behavior) 가능 여부.
  • static/dynamic = typecheck를 compile-time에 하는가 runtime에 하는가.
  • explicit/implicit = type을 programmer가 쓰는가 compiler가 inference하는가.
  • 이 셋은 독립적인 축이다. (예: C = unsafe·static·explicit)

7.2Designing a Type System

type system을 기술하려면 atomic type, compound type, 그리고 type 간 assigning·converting rule을 설명해야 한다.

💡 atomic types
  • 개별 변수를 기술하는 단순 type. 보통 assembly에서 single register에 저장됨: integer, floating point, boolean 등.
  • 각 atomic type마다 지원 range를 명확히 정의해야 함. 예: integer는 signed/unsigned, 8/16/32/64 bit; floating point는 32/40/64 bit; character는 ASCII/Unicode.

user-defined types

atomic type으로 구현하되 range를 제한해 새 의미를 부여한다.

Ada — range 제한이 가능 (강함)

-- This is Ada code
type Day   is range 1..31;
type Month is range 1..12;

Day와 Month를 서로 할당하거나, Month에 13을 주는 것을 방지.

C — typedef (약함)

/* This is C code */
typedef int Month;
typedef int Day;

Month m = 10;
Day d = m;   /* 둘 다 int 라 허용됨 */

typedef는 새 이름만 줄 뿐 range 제한·할당 방지를 못 함.

enumerations

변수가 가질 수 있는 finite set의 symbolic value를 선언하는 user-defined type. 내부적으로는 integer지만 가독성을 높이고 불법 값 할당을 막는다. (C는 enumeration 선언은 되지만 integer와 섞어 쓰는 것을 막지 못함.)

/* This is Rust code */
enum Fuzzy { True, False, Uncertain };

compound types

structure (record) type — Go

/* This is Go code */
type coordinates struct {
    latitude  float64
    longitude float64
}

여러 값을 하나의 큰 단위로 묶음.

union type — C

/* This is C code */
union number {
    int i;
    float f;
};

union number n;
n.i = 10;
n.f = 3.14;

n.in.f가 같은 메모리를 차지 → 한쪽에 쓰고 다른 쪽을 읽으면 garbage. device driver 등에 가끔 유용.

💡 variant type
  • 여러 variant를 명시적으로 기술하며 각각 다른 field를 가짐. union과 비슷하지만 unsafe access를 방지한다.
  • 선택된 type에 해당하는 field만 사용 가능 (예: ADDleft/right, NAME이면 name).
/* This is Rust code */
enum Expression {
    ADD{ left: Expression, right: Expression },
    MULTIPLY{ left: Expression, right: Expression },
    INTEGER{ value: i32 },
    NAME{ name: string }
}

unlike type을 함께 쓸 때의 4가지 선택지

integer i를 floating point f에 할당하거나, 함수 인자로 다른 type을 넘길 때 언어가 할 수 있는 일:

선택지설명
Disallow the assignment매우 엄격한 언어(B-Minor)는 error를 내고 compile을 막음. 정말 필요하면 IntToFloat 같은 built-in conversion function 호출을 요구.
Perform a bitwise copy저장 크기가 같으면 bit를 그대로 복사. 보통 나쁜 생각(의미 보장 없음). C의 서로 다른 pointer type 할당 등 일부에서만 발생.
Convert to an equivalent valuecompiler의 built-in conversion으로 값을 변환(integer↔floating point, signed↔unsigned). 단 safe하다는 뜻은 아님 — 정보 손실로 까다로운 bug 가능.
Interpret the value in a different way동등하진 않지만 유용한 다른 값으로 변환. 예: Perl에서 list를 scalar context로 복사하면 list 내용 대신 길이가 들어감.
@days = ("Monday", "Tuesday", "Wednesday", ... );
@a = @days; # copies the array to array a
$b = @days; # puts the length of the array into b
⭐ 시험 포인트
  • type system 기술의 3요소: atomic type · compound type · assign/convert rule.
  • union은 같은 메모리를 공유(unsafe), variant는 선택된 field만 노출(safe).
  • Ada는 range 제한이 되는 strong user-defined type, C의 typedef는 이름만 바꾸는 weak한 것.
  • implicit conversion이 일어난다고 해서 그것이 safe한 것은 아니다(정보 손실 가능).

7.3The B-Minor Type System

B-Minor의 type system은 safe, static, explicit이다. 그래서 기술이 간결하고 구현이 직관적이며 많은 error를 제거한다. 다만 엄격해서 검출해야 할 error가 많다.

atomic types

  • integer — 64 bit signed integer
  • booleantrue 또는 false
  • char — ASCII 값
  • string — ASCII, null terminated
  • void — 값을 반환하지 않는 function에만 사용

compound types

array [size] type

function type ( a: type, b: type, ... )
⭐ 시험 포인트 — B-Minor의 type rules (반드시 암기)
  • value는 같은 type의 variable에만 할당 가능.
  • function parameter는 같은 type의 value만 받음.
  • return statement의 type은 function return type과 일치해야 함.
  • 모든 binary operator는 좌·우변이 같은 type이어야 함.
  • equality operator != == : void·array·function을 제외한 모든 type에 적용, 항상 boolean 반환.
  • comparison operator < <= >= > : integer에만 적용, 항상 boolean 반환.
  • boolean operator ! && || : boolean에만 적용, 항상 boolean 반환.
  • arithmetic operator + - * / % ˆ ++ -- : integer에만 적용, 항상 integer 반환.

7.4The Symbol Table

symbol table은 프로그램의 모든 declared variable(및 function 같은 named item)에 대해 알아야 할 정보를 기록한다. 각 entry는 struct symbol이다.

Figure 7.1 — The Symbol Structure

struct symbol {
    symbol_t kind;
    struct type *type;
    char *name;
    int which;
};

typedef enum {
    SYMBOL_LOCAL,
    SYMBOL_PARAM,
    SYMBOL_GLOBAL
} symbol_t;

각 field의 의미

  • kind — local variable / global variable / function parameter 구분
  • type — 변수의 type을 가리키는 type structure 포인터
  • name — 이름
  • which — local variable·parameter의 ordinal position(순서)

factory function

struct symbol * symbol_create( symbol_t kind,
                               struct type *type,
                               char *name ) {
    struct symbol *s = malloc(sizeof(*s));
    s->kind = kind;
    s->type = type;
    s->name = name;
    return s;
}
💡 왜 단순한 map이 아닌가 — scope
  • 개념적으로 symbol table은 변수 이름 → symbol structure의 map.
  • 하지만 대부분의 언어는 같은 변수 이름을 서로 다른 scope에서 여러 번 쓸 수 있게 허용한다.
  • C 계열(B-Minor 포함)의 scope: global scope, function parameter·local variable의 scope, 그리고 curly brace마다 생기는 nested scope.

같은 이름 x가 세 scope에 등장하는 예

아래 프로그램은 x를 type·storage class를 달리하며 세 번 정의한다. 실행 시 10 hello false를 출력해야 한다.

x: integer = 10;

f: function void ( x: string ) =
{
    print x, "\n";
    {
        x: boolean = false;
        print x, "\n";
    }
}

main: function void () =
{
    print x, "\n";
    f("hello");
}
💡 핵심 자료구조 — stack of hash tables (Figure 7.2)
  • symbol table을 hash table들의 stack으로 구성한다. 각 hash table은 한 scope의 이름→symbol을 map.
  • 같은 x가 여러 scope에 충돌 없이 공존 가능.
  • scope에 진입하면 새 table을 push, scope를 떠나면 table을 pop.
Stack Top ┌──────────────────────────────┐ │ Inner Scope Table → x LOCAL(0) BOOLEAN ├──────────────────────────────┤ │ Function Scope Table → x PARAM(0) STRING ├──────────────────────────────┤ │ Global Scope Table → x GLOBAL INTEGER │ f GLOBAL FUNCTION │ main GLOBAL FUNCTION └──────────────────────────────┘ Figure 7.2: A Nested Symbol Table

Symbol Table API (Figure 7.3) — 6개 연산

void scope_enter();
void scope_exit();
int  scope_level();

void scope_bind( const char *name, struct symbol *sym );
struct symbol *scope_lookup( const char *name );
struct symbol *scope_lookup_current( const char *name );
연산의미
scope_enter()새 hash table을 stack top에 push (새 scope 시작)
scope_exit()최상단 hash table 제거
scope_level()현재 stack의 hash table 개수 반환 (global scope 여부 판단에 유용)
scope_bind(name,sym)최상단 table에 name → sym entry 추가
scope_lookup(name)stack을 top → bottom으로 탐색, name이 정확히 일치하는 첫 entry 반환. 없으면 null
scope_lookup_current(name)scope_lookup과 같되 최상단 table만 탐색. 현재 scope에 이미 정의됐는지 판단용
⭐ 시험 포인트
  • symbol table = stack of hash tables. scope 진입 시 push, 탈출 시 pop.
  • scope_lookup안쪽(top)부터 바깥(bottom)으로 탐색 → inner scope가 outer scope를 가린다(shadowing).
  • scope_lookup_current는 최상단만 봄 → 중복 선언(redeclaration) 검사에 사용.
  • symbol_t의 세 kind: SYMBOL_LOCAL, SYMBOL_PARAM, SYMBOL_GLOBAL.

7.5Name Resolution

symbol table이 준비되면, 변수 이름의 각 use를 그에 맞는 definition에 연결한다. 이 과정이 name resolution이다.

💡 구현 방식
  • AST의 각 구조에 대해 resolve method를 작성: decl_resolve(), stmt_resolve(), expr_resolve() 등.
  • 이들이 모여 AST 전체를 순회하며 변수의 declarationuse를 찾는다.
  • declared: symbol table에 등록하고 symbol structure를 AST에 연결.
  • used: symbol table에서 lookup하여 symbol structure를 AST에 연결.
  • 같은 scope에서 두 번 선언되거나 선언 없이 사용되면 적절한 error message를 출력.
  • 전체 AST에 대한 name resolution은 root node에서 decl_resolve()를 한 번 호출하면 된다(하위 함수들이 트리 전체를 순회).

선언 resolve (Figure 7.4)

decl은 변수 선언이므로 새 symbol을 만들어 현재 scope의 이름에 bind한다. expression 선언(d->value가 not null)이면 그 expression도 resolve하고, function 선언(d->code가 not null)이면 새 scope를 만들어 parameter와 code를 resolve한다.

void decl_resolve( struct decl *d )
{
    if(!d) return;

    symbol_t kind = scope_level() > 1 ?
                    SYMBOL_LOCAL : SYMBOL_GLOBAL;

    d->symbol = symbol_create(kind,d->type,d->name);

    expr_resolve(d->value);
    scope_bind(d->name,d->symbol);

    if(d->code) {
        scope_enter();
        param_list_resolve(d->type->params);
        stmt_resolve(d->code);
        scope_exit();
    }

    decl_resolve(d->next);
}

표현식 resolve (Figure 7.5)

EXPR_NAME이면 symbol table에서 lookup해 연결하고, 그 외엔 left·right 자식을 재귀적으로 resolve한다.

void expr_resolve( struct expr *e )
{
    if(!e) return;

    if( e->kind==EXPR_NAME ) {
        e->symbol = scope_lookup(e->name);
    } else {
        expr_resolve( e->left );
        expr_resolve( e->right );
    }
}
💡 나머지 resolve 함수들
  • stmt_resolve() — 각 sub-component에 알맞은 resolve를 호출. STMT_BLOCK의 경우 새 scope를 enter/leave 한다.
  • param_list_resolve() — function의 각 parameter를 새 변수 선언으로 등록 → function의 code에서 그 정의들을 쓸 수 있게 함.
⭐ 시험 포인트
  • scope_level() > 1local, 아니면 global로 kind 결정.
  • declaration은 bind, use(EXPR_NAME)는 lookup.
  • function 선언 처리: scope_enter → parameter resolve → code resolve → scope_exit.
  • name resolution 자체는 type을 검사하지 않는다 — 이름과 정의를 연결할 뿐. type 검사는 다음 단계.

7.6Implementing Type Checking

expression을 검사하기 전에 type structure를 다루는 helper 함수가 필요하다: equality·copy·delete.

type helper 함수들 (pseudo-code)

type_equals

boolean type_equals(
    struct type *a, struct type *b )
{
  if( a->kind == b->kind ) {
    if( a and b are atomic types ){
       Return true
    } else if ( both are array ) {
       Return true if subtype is
              recursively equal
    } else if ( both are function ) {
       Return true if both subtype
       and params are recursively equal
    }
  } else {
    Return false
  }
}

type_copy / type_delete

struct type * type_copy( struct type *t )
{
    Return a duplicate copy of t,
    making sure to duplicate subtype
    and params recursively.
}

void type_delete( struct type *t )
{
    Free all the elements of t
    recursively.
}
💡 expr_typecheck의 핵심 규약
  • expression의 올바른 type을 계산해 반환한다.
  • non-null expr에 호출되면 항상 newly-allocated type structure를 반환한다(코드 단순화).
  • type 조합이 invalid해도 error를 출력하되 valid type을 반환한다 → compiler가 계속 진행하며 가능한 한 많은 error를 찾게 함.
  • 방식: expression tree의 recursive post-order traversal.
    • leaf: node의 kind가 곧 type (integer literal → integer, string literal → string). 변수 이름이면 symbol pointer를 따라가 symbol structure의 type을 copy해 반환.
    • interior node: left·right subtree의 type을 비교해 7.3의 rule과 호환되는지 확인. 안 되면 error 출력 후 global error counter 증가. 어느 쪽이든 operator에 맞는 type을 반환하고, 더 이상 필요 없는 left·right type은 delete.

기본 구조

struct type * expr_typecheck( struct expr *e )
{
    if(!e) return 0;

    struct type *lt = expr_typecheck(e->left);
    struct type *rt = expr_typecheck(e->right);

    struct type *result;

    switch(e->kind) {
        case EXPR_INTEGER_LITERAL:
            result = type_create(TYPE_INTEGER,0,0);
            break;
        case EXPR_STRING_LITERAL:
            result = type_create(TYPE_STRING,0,0);
            break;

        /* more cases here */

    }

    type_delete(lt);
    type_delete(rt);

    return result;
}

operator별 case 예시

arithmetic — integer만, integer 반환

case EXPR_ADD:
    if( lt->kind!=TYPE_INTEGER ||
        rt->kind!=TYPE_INTEGER ) {
        /* display an error */
    }
    result = type_create(TYPE_INTEGER,0,0);
    break;

equality — 양쪽 동일 type, boolean 반환

case EXPR_EQ:
case EXPR_NE:
    if(!type_equals(lt,rt)) {
        /* display an error */
    }
    if(lt->kind==TYPE_VOID ||
       lt->kind==TYPE_ARRAY ||
       lt->kind==TYPE_FUNCTION) {
        /* display an error */
    }
    result = type_create(TYPE_BOOLEAN,0,0);
    break;

array dereference a[i] — a는 array, i는 integer, subtype 반환

case EXPR_DEREF:
    if(lt->kind==TYPE_ARRAY) {
        if(rt->kind!=TYPE_INTEGER) {
             /* error: index not an integer */
        }
        result = type_copy(lt->subtype);
    } else {
        /* error: not an array */
        /* but we need to return a valid type */
        result = type_copy(lt);
    }
    break;

declaration / statement typecheck

대부분의 작업은 expr_typecheck에서 일어나지만, declaration·statement 등 다른 AST 요소도 typecheck해야 한다. 이들은 AST를 순회하며 expression type을 계산해 declaration이나 제약과 비교한다.

decl_typecheck

void decl_typecheck( struct decl *d )
{
    if(d->value) {
        struct type *t;
        t = expr_typecheck(d->value);
        if(!type_equals(t,d->symbol->type)) {
            /* display an error */
        }
    }
    if(d->code) {
        stmt_typecheck(d->code);
    }
}

선언이 initializer와 일치하는지 확인하고, function이면 body를 typecheck.

stmt_typecheck

void stmt_typecheck( struct stmt *s )
{
    struct type *t;
    switch(s->kind) {
        case STMT_EXPR:
            t = expr_typecheck(s->expr);
            type_delete(t);
            break;
        case STMT_IF_THEN:
            t = expr_typecheck(s->expr);
            if(t->kind!=TYPE_BOOLEAN) {
                /* display an error */
            }
            type_delete(t);
            stmt_typecheck(s->body);
            stmt_typecheck(s->else_body);
            break;
        /* more cases here */
    }
}

if-then의 control expression은 boolean이어야 함.

⭐ 시험 포인트
  • expr_typecheckpost-order traversal: 자식 type을 먼저 계산 후 부모에서 결합.
  • error가 있어도 valid type을 반환해 여러 error를 한 번에 검출(error counter 증가).
  • leaf의 type은 node kind로 결정, name은 symbol의 type을 copy.
  • 다 쓴 lt·rttype_delete로 해제(메모리 관리).
  • ==/!=void·array·function 제외 + 양쪽 type 동일 → boolean.
  • if-then control expression은 boolean type이어야 함.

7.7Error Messages

compiler는 형편없는 error message로 악명 높다. 하지만 지금까지 만든 구조 덕분에, 어떤 type이 발견됐고 무엇이 문제인지 정확히 설명하는 message를 쉽게 출력할 수 있다.

type 문제가 가득한 B-Minor 코드

s: string  = "hello";
b: boolean = false;
i: integer = s + (b<5);
⚠️ 흔한(나쁜) message
error: type compatibility in expression
💡 더 나은 message
error: cannot compare a boolean (b)
       to an integer (5)
error: cannot add a boolean (b<5)
       to a string (s)

문제 발견 시 관련된 expression과 type을 정성껏 출력하면 된다.

printf("error: cannot add a ");
type_print(lt);
printf(" (");
expr_print(e->left);
printf(") to a ");
type_print(rt);
printf(" (");
expr_print(e->right);
printf(")\n");
⭐ 시험 포인트
  • 좋은 error message = 발견된 type과 문제의 expression을 함께 보여줌(type_print + expr_print).
  • 앞서 만든 AST·type 구조 덕분에 상세한 message 출력이 어렵지 않다.

7.8Exercises (연습문제)

  1. 기존 hash table 구현을 출발점으로 symbol.c·scope.c의 symbol·scope 함수를 구현하라.
  2. stmt_resolve()·param_list_resolve() 등 필요한 코드를 작성해 name resolution을 완성하라.
  3. decl_resolve()·expr_resolve()를 수정해 같은 이름 중복 선언 또는 선언 없는 사용 시 error를 표시하라.
  4. expr_typecheck를 완성해 모든 종류의 expression의 type을 검사·반환하라.
  5. stmt_typecheck를 완성해 각 statement 종류별 제약을 강제하라.
  6. %T(type), %E(expression) 같은 기호를 지원하는 myprintf를 작성해 error message 출력을 쉽게 하라. stdarg.h의 variadic function을 참고하라.
myprintf(
    "error: cannot add a %T (%E) to a %T (%E)\n",
    lt,e->left,rt,e->right
);

7.9Further Reading

문헌비고
H. Abelson, G. Sussman, J. Sussman, Structure and Interpretation of Computer ProgramsMIT Press, 1996
B. Pierce, Types and Programming LanguagesMIT Press, 2002
D. Friedman, D. Christiansen, The Little TyperMIT Press, 2018

핵심 정리 / 시험 포인트

🎯 반드시 외울 핵심 명제
  • semantic analysis = AST의 구조가 아닌 의미 분석. 핵심은 type checking.
  • 순서: name resolution → symbol table 구축 → type checking.
  • type system 3축: safe/unsafe · static/dynamic · explicit/implicit (서로 독립).
  • static = compile-time typecheck, dynamic = runtime에 type 정보 보관·검사.
  • type system 3목적: correctness · performance · expressiveness.
  • B-Minor = safe · static · explicit. integer는 64-bit signed.
  • union은 메모리 공유(unsafe), variant는 선택 field만 노출(safe).
  • symbol table = stack of hash tables. scope 진입 push, 탈출 pop.
  • scope_lookup=top→bottom 탐색(shadowing), scope_lookup_current=top만(중복 선언 검사).
  • name resolution: declaration은 bind, use는 lookup.
  • expr_typecheckpost-order, error 시에도 valid type 반환(여러 error 검출).
  • arithmetic·comparison·boolean operator는 operand type이 고정, 반환 type도 고정(7.3 rule).
💡 B-Minor operator 규칙 요약
operator입력 → 출력
+ - * / % ˆ ++ --integer → integer
< <= >= >integer → boolean
! && ||boolean → boolean
== !=동일 type(void/array/function 제외) → boolean
💡 symbol kind & scope
  • SYMBOL_LOCAL / SYMBOL_PARAM / SYMBOL_GLOBAL
  • scope_level() > 1 → local, 아니면 global
  • which = local·parameter의 ordinal position
❓ 예상 서술형/단답
  • Q. name resolution이란? → 변수 이름의 각 use를 그 definition에 연결하고 symbol table에 등록하는 과정.
  • Q. symbol table은 왜 단순 map이 아닌 stack of hash table인가? → 같은 이름을 다른 scope에서 재사용하도록 허용하기 위해.
  • Q. scope_lookupscope_lookup_current의 차이는? → 전자는 top→bottom 전체 탐색, 후자는 최상단 table만 → 중복 선언 검사용.
  • Q. expr_typecheck가 error 시에도 valid type을 반환하는 이유는? → compiler가 멈추지 않고 가능한 한 많은 error를 한 번에 찾기 위해.
  • Q. s + (b<5)의 type error를 설명하라 → boolean(b)과 integer(5) 비교 불가, boolean(b<5)과 string(s) 덧셈 불가.

📋한눈에 보기 — Cheat Sheet

키워드 / 항목의미
semantic analysis프로그램의 semantic을 검증. 핵심 = type checking
name resolution변수 use를 definition에 연결, symbol table에 등록
type checkingconversion rule로 expression type 계산·검증
safe / unsafe언어 구조 위반(undefined behavior) 가능 여부
static / dynamictypecheck를 compile-time / runtime에 수행
explicit / implicittype을 programmer가 명시 / compiler가 inference
atomic typeinteger, boolean, char, string, void …
compound typearray, function, structure, union, variant
union같은 메모리 공유(unsafe access 가능)
variant type선택된 type의 field만 노출(safe)
symbol tablestack of hash tables. scope마다 push/pop
struct symbolkind · type · name · which
symbol_tSYMBOL_LOCAL / SYMBOL_PARAM / SYMBOL_GLOBAL
scope_enter / exit새 scope table push / 최상단 pop
scope_bind최상단 table에 name → symbol 추가
scope_lookuptop→bottom 전체 탐색(shadowing)
scope_lookup_current최상단만 탐색(중복 선언 검사)
decl_resolve / expr_resolvedeclaration bind / use lookup
expr_typecheckpost-order로 type 계산, error에도 valid type 반환
type_equals / copy / deletetype 동등 비교 / 복제 / 해제(재귀)
B-Minor type systemsafe · static · explicit, integer=64-bit signed