Skip to content

Latest commit

 

History

History
400 lines (329 loc) · 15.7 KB

File metadata and controls

400 lines (329 loc) · 15.7 KB

Lox 인터프리터 실행 가이드

프로젝트 전체 구조 및 파일별 역할

java/
└── com/
    └── craftinginterpreters/
        ├── lox/
        │   ├── AstPrinter.java      # 추상 구문 트리(Expr)를 사람이 읽기 쉽게 출력하는 클래스
        │   ├── Environment.java     # 변수 스코프(환경)를 구현, 중첩 환경 지원
        │   ├── Expr.java            # 표현식(Expr) 추상 구문 트리 및 비지터 패턴 정의, 배열/인덱싱/메서드 등 지원
        │   ├── Interpreter.java     # AST(Stmt, Expr)를 실행하는 인터프리터, 스코프 관리, 배열/append/길이 등 지원
        │   ├── Lox.java             # 메인 클래스, REPL 및 파일 실행 진입점
        │   ├── Parser.java          # 파서(구문 분석기), 토큰 리스트를 AST로 변환, 배열 리터럴/인덱싱/메서드 호출 지원
        │   ├── Resolver.java        # 변수/함수 이름의 유효 범위(스코프) 추적 및 바인딩, 중복 선언/미정의 변수 체크
        │   ├── RuntimeError.java    # 런타임 에러 처리 클래스
        │   ├── Scanner.java         # 소스코드를 토큰 리스트로 변환하는 스캐너(어휘 분석기), 한글 키워드 지원
        │   ├── Stmt.java            # 문장(Stmt) 추상 구문 트리 및 비지터 패턴 정의
        │   ├── Token.java           # 토큰 객체, 타입/이름/리터럴/라인 정보 포함
        │   └── TokenType.java       # 토큰 타입 열거형(키워드, 연산자, 리터럴 등)
        └── tool/
            └── GenerateAst.java     # AST 클래스(Expr, Stmt) 자동 생성 도구

주요 파일/클래스별 설명 (주석 포함)

  • Lox.java

    • 프로그램의 진입점. REPL(대화형) 또는 파일 실행을 지원.
    • run()에서 Scanner → Parser → Resolver → Interpreter 순으로 실행.
    • 에러 처리 및 메시지 출력 담당.
  • Scanner.java

    • 소스코드를 문자 단위로 읽어 토큰(Token) 리스트로 변환.
    • 한글/영문 키워드, 연산자, 식별자, 숫자, 문자열 등 다양한 토큰을 인식.
    • 내부적으로 scanToken(), number(), identifier() 등 메서드로 세분화.
    • 한글 키워드 완벽 지원: 변수, 함수, 반환, 출력, 조건반복, 만약, 아니면 등
  • Token.java / TokenType.java

    • Token: 토큰의 타입, 원본 문자열, 리터럴 값, 라인 정보를 저장.
    • TokenType: 모든 토큰 종류(키워드, 연산자, 리터럴 등)를 열거형으로 정의.
  • Parser.java

    • 토큰 리스트를 받아 AST(Stmt, Expr)로 변환.
    • Lox의 문법을 재귀 하강 파싱 방식으로 구현.
    • 변수 선언, 블록, 표현식, print문, 배열 리터럴([1,2,3]), 배열 인덱싱(a[0]), 메서드 호출(a.append(1), a.붙이기(1)) 등 다양한 구문을 처리.
  • Expr.java / Stmt.java

    • Expr: 표현식(이항, 단항, 그룹, 변수, 리터럴, 할당, 배열, 인덱싱, 메서드 등) 추상 클래스와 내부 클래스들.
    • Stmt: 문장(블록, 변수 선언, print, 표현식, break/continue 등) 추상 클래스와 내부 클래스들.
    • **비지터 패턴(Visitor Pattern)**을 통해 Interpreter, AstPrinter, Resolver 등에서 타입별 처리 가능.
  • Interpreter.java

    • AST(Stmt, Expr)를 실제로 실행(해석)하는 클래스.
    • 변수 스코프(블록) 처리를 위해 Environment를 사용.
    • 배열(ArrayList) 지원: 배열 리터럴, 인덱싱, 요소 할당, append/붙이기, length/길이 등
    • 각 visit 메서드에 주석으로 역할 설명:
      • visitBlockStmt: 블록 내부 문장들을 새로운 환경(Environment)에서 실행, 블록 종료 시 이전 환경으로 복구
      • visitArrayExpr: 배열 리터럴 평가, 자바 ArrayList로 변환
      • visitGetExpr: 배열 인덱싱, 메서드/프로퍼티 접근 처리
      • visitCallExpr: 함수/메서드 호출, append/붙이기 등 지원
      • visitSetExpr: 배열 요소 할당
      • visitVarStmt: 변수 선언 및 초기화
      • visitPrintStmt: print문 실행 및 출력
      • visitBinaryExpr: 이항 연산자 평가 등
  • Resolver.java

    • 변수, 함수 등 이름(식별자)의 유효 범위(스코프)를 추적하고, 올바른 바인딩을 찾아주는 역할
    • 중복 선언, 미정의 변수 사용 등 오류를 미리 잡아줌
    • 함수/블록/지역변수 스코프 관리, resolveLocal로 바인딩 위치 추적
  • Environment.java

    • 변수 이름과 값을 저장하는 맵(Map)과, 바깥(Environment) 참조(enclosing)를 가짐.
    • 변수 정의/조회/할당 시, 현재 환경에 없으면 바깥 환경을 따라가며 찾음(스코프 체인).
  • AstPrinter.java

    • Expr(추상 구문 트리)을 사람이 읽기 쉬운 문자열로 변환.
    • 각 Expr 타입별로 visit 메서드 오버라이드.
  • RuntimeError.java

    • 런타임 에러 발생 시 예외 객체로 사용.
  • tool/GenerateAst.java

    • Expr, Stmt 등 AST 클래스/비지터 인터페이스를 자동 생성하는 도구.

최근 추가/확장된 주요 기능

  • 배열 리터럴/인덱싱/메서드: [1,2,3], a[0], a.append(4), a.붙이기(5), a.length, a.길이 등 완벽 지원
  • 한글 키워드/메서드/속성: 변수, 함수, 반환, 출력, 조건반복, 만약, 아니면, 길이, 붙이기 등
  • break/continue: 반복문 제어 지원
  • 스코프/이름 바인딩: Resolver로 중복 선언, 미정의 변수 등 오류 사전 방지
  • 자료구조 구현: 스택, 큐, 병합 정렬 등 다양한 알고리즘 구현
  • 부동소수점 정밀도 처리: double 타입 연산에서 정수 변환으로 정밀도 문제 해결

비지터 패턴(Visitor Pattern) 설명 및 본 프로젝트에서의 활용

비지터 패턴이란?

  • 비지터 패턴은 객체 구조(트리 등)를 변경하지 않고, 각 타입별로 다양한 연산(동작)을 분리해서 구현할 수 있게 해주는 디자인 패턴입니다.
  • 즉, 데이터 구조(예: AST)는 그대로 두고, 그 위에서 동작하는 로직(예: 해석, 출력 등)을 별도의 클래스로 분리할 수 있습니다.
  • 새로운 연산을 추가할 때 데이터 구조를 수정하지 않고, 비지터(Visitor)만 추가하면 되므로 확장성이 좋습니다.

Lox 인터프리터에서의 비지터 패턴 적용

  • Expr.javaStmt.java에서 각각 Visitor 인터페이스를 정의하고, 각 표현식/문장 타입(이항, 단항, 그룹, 변수, 블록 등) 내부에 accept(Visitor) 메서드를 구현합니다.
  • Interpreter.javaAstPrinter.java 등은 이 Visitor 인터페이스를 구현하여, 각 타입별 동작(실행, 출력 등)을 visit 메서드로 분리합니다.
  • 예시:
    • Expr.Binary(이항 연산식)은 accept(Visitor)를 통해 Interpreter의 visitBinaryExpr 또는 AstPrinter의 visitBinaryExpr를 호출합니다.
    • Stmt.Print(print문)은 accept(Visitor)를 통해 Interpreter의 visitPrintStmt를 호출합니다.
  • 이렇게 하면 AST 구조(Expr, Stmt)는 변경하지 않고도, 다양한 동작(실행, 출력, 타입 검사 등)을 Visitor 클래스를 추가하여 확장할 수 있습니다.
  • 장점:
    • AST 구조와 동작(해석, 출력 등)이 분리되어 코드가 명확해짐
    • 새로운 동작(Visitor) 추가가 쉬움 (예: 타입 검사기, 최적화기 등)
    • 각 타입별 visit 메서드에 주석을 달아 역할을 명확히 설명할 수 있음

컴파일 및 실행 방법

1. 디렉토리 이동

cd C:\java

2. 컴파일

javac com/craftinginterpreters/lox/*.java
-> 이렇게 하면 영어만 인식해서 한국어 쓸려고 intellij에서 컴파일을 먼저 하고 그 다음에 실행하였다.

3. 실행 방법

대화형 모드 (REPL)

java com.craftinginterpreters.lox.Lox
  • > 프롬프트가 나타나면 Lox 코드를 직접 입력
  • 각 줄을 입력하면 토큰으로 분석되어 출력
  • 종료하려면 Ctrl+C 또는 빈 줄에서 Enter

파일 실행

java com.craftinginterpreters.lox.Lox [파일명.lox]

사용 예시

대화형 모드에서 테스트

> print "Hello, World!";
> var x = 42;
> var name = "Lox";
> var result = x + 10;
> print result;
> 변수 한국어 = "한국어이다.";
> 출력 한국어;

토큰 분석 결과

입력한 코드는 다음과 같이 토큰으로 분석됩니다:

  • PRINT - 출력 키워드
  • STRING - 문자열 리터럴
  • VAR - 변수 선언 키워드
  • IDENTIFIER - 변수명
  • NUMBER - 숫자 리터럴
  • SEMICOLON - 세미콜론
  • ...

주의사항

  • Java 8 이상이 필요합니다
  • 컴파일 후 .class 파일이 생성됩니다
  • 대화형 모드에서는 각 줄이 즉시 토큰으로 분석됩니다
  • 부동소수점 연산 주의: Lox에서 모든 숫자는 double 타입이므로, 정수 연산 시 값 = 값 - 값 % 1 형태로 정수 변환이 필요할 수 있습니다

파일 구조 이해를 위한 핵심 용어 요약

  • 토큰(Token): 소스 코드를 의미 있는 최소 단위로 나눈 조각. (예: x, =, 3, +, 5)
  • 어휘 분석(Lexical Analysis): 소스 코드를 토큰으로 분리하는 과정. 담당: 스캐너(Scanner)
  • 구문 분석(Parsing): 토큰이 문법적으로 맞는지 확인하고 트리로 만드는 과정. 담당: 파서(Parser)
  • 표현식(Expression): 값을 만들어내는 코드 조각. (예: 3 + 5, x)
  • 문장(Statement): 동작을 수행하는 코드 단위. (예: x = 3 + 5, print(x))
  • 추상 구문 트리(AST): 코드의 구조를 트리 형태로 표현한 것. (예: 3 + 5의 AST)
  • 평가(Evaluation): AST나 표현식을 실제로 계산하여 값을 만드는 단계. 담당: 인터프리터(Interpreter)
  • 환경(Environment): 변수와 값이 저장되는 공간. (예: { x: 5, y: 7 })
  • 인터프리터(Interpreter): 코드를 한 줄씩 읽고 즉시 실행하는 프로그램.
  • 비지터 패턴(Visitor Pattern): AST(Expr, Stmt) 각 타입별 동작을 분리해 처리하는 디자인 패턴. (예: Interpreter, AstPrinter)
  • 스코프(Scope): 변수의 유효 범위. 블록({ ... })마다 새로운 환경(Environment)이 생성되어 변수 격리.
  • 런타임 에러(Runtime Error): 실행 중 발생하는 오류. (예: 0으로 나누기, 정의되지 않은 변수 접근)
  • REPL(Read-Eval-Print Loop): 한 줄씩 코드를 입력하고 바로 결과를 확인하는 대화형 실행 환경.
  • 자동 코드 생성 도구: AST(Expr, Stmt) 클래스와 비지터 인터페이스를 자동으로 만들어주는 도구. (예: tool/GenerateAst.java)

한국어 사용법 예시

아래는 인터프리터에서 지원하는 한국어 키워드와 연산자를 활용한 예시입니다.

변수 선언 및 출력

변수 숫자 = 10;
출력 숫자;

산술 연산 (더하기, 빼기, 곱하기, 나누기, 나머지)

변수 a = 7;
변수 b = 3;
출력 a + b;    // 10
출력 a - b;    // 4
출력 a * b;    // 21
출력 a / b;    // 2.333...
출력 a % b;    // 1

조건문 (if, else)

변수 a = 10;
만약 (a>10) {
    출력 "많음";
} 아니면 만약 (a==10) {
    출력 "정답";
} 아니면 {
    출력 "적음";
}

반복문 (while, for)

변수 i = 0;
조건반복 (i < 5) {
    출력 i;
    i = i + 1;
}

범위반복 (변수 j = 0; j < 3; j = j + 1) {
    출력 j;
}

논리 연산

변수 참값 = 참;
변수 거짓값 = 거짓;
출력 참값 그리고 거짓값; // false
출력 참값 또는 거짓값;   // true

함수 정의 및 호출

함수 더하기(값1, 값2) {
    반환 값1 + 값2;
}

변수 결과 = 더하기(3, 5);
출력 결과; // 8

입력 추가

변수 정수 = 숫자입력();
변수 문자열 = 문자열입력();
출력 정수;
출력 문자열;

배열 추가

변수 예시 = [1,2,3];
예시.붙이기(4);
출력 예시;
출력 예시.길이;

함수 이진탐색(배열, 찾는값) {
    변수 왼쪽 = 0;
    변수 오른쪽 = 배열.길이 - 1;
    조건반복 (왼쪽 <= 오른쪽) {
        변수 중간값 = (왼쪽 + 오른쪽) / 2;
        중간값 = (중간값 - 중간값 % 1); // 정수 내림
        만약 (배열[중간값] == 찾는값) {
            반환 중간값;
        } 아니면 만약 (배열[중간값] < 찾는값) {
            왼쪽 = 중간값 + 1;
        } 아니면 {
            오른쪽 = 중간값 - 1;
        }
    }
    반환 -1;
}

변수 a = [1, 3, 5, 7, 9, 11];
출력 이진탐색(a, 7);   // 3
출력 이진탐색(a, 2);   // -1

병합 정렬 구현

// 병합 정렬 알고리즘 (부동소수점 정밀도 처리 포함)
함수 병합(왼쪽, 오른쪽) {
    변수 결과 = [];
    변수 i = 0;
    변수 j = 0;
    
    조건반복 (i < 왼쪽.길이 and j < 오른쪽.길이) {
        만약 (왼쪽[i] <= 오른쪽[j]) {
            결과.붙이기(왼쪽[i]);
            i = i + 1;
        } else {
            결과.붙이기(오른쪽[j]);
            j = j + 1;
        }
    }
    
    조건반복 (i < 왼쪽.길이) {
        결과.붙이기(왼쪽[i]);
        i = i + 1;
    }
    
    조건반복 (j < 오른쪽.길이) {
        결과.붙이기(오른쪽[j]);
        j = j + 1;
    }
    
    반환 결과;
}

함수 병합정렬(배열) {
    만약 (배열.길이 <= 1) {
        변수 복사 = [];
        범위반복(변수 i = 0; i < 배열.길이; i = i + 1) {
            복사.붙이기(배열[i]);
        }
        반환 복사;
    }
    
    변수 중간 = 배열.길이 / 2;
    중간 = 중간 - 중간 % 1; // 부동소수점 오차 방지
    변수 왼쪽 = [];
    변수 오른쪽 = [];
    
    범위반복(변수 i = 0; i < 중간; i = i + 1) {
        왼쪽.붙이기(배열[i]);
    }
    
    범위반복(변수 i = 중간; i < 배열.길이; i = i + 1) {
        오른쪽.붙이기(배열[i]);
    }
    
    변수 정렬된왼쪽 = 병합정렬(왼쪽);
    변수 정렬된오른쪽 = 병합정렬(오른쪽);
    
    반환 병합(정렬된왼쪽, 정렬된오른쪽);
}

변수 배열 = [64, 34, 25, 12, 22, 11, 90];
변수 정렬된배열 = 병합정렬(배열);
출력 정렬된배열; // [11, 12, 22, 25, 34, 64, 90]

클래스 및 객체 지향 프로그래밍

클래스 포인트 {
    초기화(x, y) {
        자기자신.x = x;
        자기자신.y = y;
    }
    
    거리계산(다른점) {
        변수 dx = 자기자신.x - 다른점.x;
        변수 dy = 자기자신.y - 다른점.y;
        반환 (dx * dx + dy * dy) ^ 0.5;
    }
    
    출력() {
        출력 "(" + 자기자신.x + ", " + 자기자신.y + ")";
    }
}

변수 p1 = 포인트(3, 4);
변수 p2 = 포인트(6, 8);
p1.출력();           // (3, 4)
출력 p1.거리계산(p2); // 5.0

참고: this자기자신 키워드가 모두 동일하게 동작합니다.

주의 사항 + 고쳐야 할 것

  • 연산 속도가 끔찍하게 느리다. 1000만번 반복문 안 기준 1.56초가 걸린다. 10만번 출력은 1.18초(원래 6초였는데 이것도 줄인거다)로 매우 느리다.
  • continue를 쓰면 for문에서 증감연산자가 안먹힘. continue를 하면 해당 for문의 연산을 끝내는데 문제는 증감 연산자도 끝냅니다.
  • queue를 arraylist로 구현하다보니 넣고 빼는게 O(n)임. 시간 남으면 arraylist 말고 linkedlist도 만들듯?
  • 부동소수점 정밀도 문제: Lox에서 모든 숫자가 double 타입이므로, 정수 연산 시 부동소수점 오차가 발생할 수 있습니다. 해결책: 값 = 값 - 값 % 1 형태로 정수 변환