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타입 연산에서 정수 변환으로 정밀도 문제 해결
- 비지터 패턴은 객체 구조(트리 등)를 변경하지 않고, 각 타입별로 다양한 연산(동작)을 분리해서 구현할 수 있게 해주는 디자인 패턴입니다.
- 즉, 데이터 구조(예: AST)는 그대로 두고, 그 위에서 동작하는 로직(예: 해석, 출력 등)을 별도의 클래스로 분리할 수 있습니다.
- 새로운 연산을 추가할 때 데이터 구조를 수정하지 않고, 비지터(Visitor)만 추가하면 되므로 확장성이 좋습니다.
- Expr.java와 Stmt.java에서 각각
Visitor인터페이스를 정의하고, 각 표현식/문장 타입(이항, 단항, 그룹, 변수, 블록 등) 내부에accept(Visitor)메서드를 구현합니다. - Interpreter.java와 AstPrinter.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 메서드에 주석을 달아 역할을 명확히 설명할 수 있음
cd C:\javajavac com/craftinginterpreters/lox/*.java
-> 이렇게 하면 영어만 인식해서 한국어 쓸려고 intellij에서 컴파일을 먼저 하고 그 다음에 실행하였다.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
변수 a = 10;
만약 (a>10) {
출력 "많음";
} 아니면 만약 (a==10) {
출력 "정답";
} 아니면 {
출력 "적음";
}
변수 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형태로 정수 변환