Post

⛓️ Java Design-Pattern XXβ…£ - Interpreter

⛓️ Java Design-Pattern XXβ…£ - Interpreter

πŸ“— γ€ŽJAVA μ–Έμ–΄λ‘œ λ°°μš°λŠ” λ””μžμΈ νŒ¨ν„΄ : μ‰½κ²Œ λ°°μš°λŠ” GoF의 23κ°€μ§€ λ””μžμΈ νŒ¨ν„΄γ€λ₯Ό 읽고 μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.

Interpreter νŒ¨ν„΄μ΄λž€?

  • ν”„λ‘œκ·Έλž¨μ΄ ν•΄κ²°ν•˜λ €λŠ” 문제λ₯Ό κ°„λ‹¨ν•œ λ―Έλ‹ˆ μ–Έμ–΄λ‘œ κ΅¬ν˜„ν•˜λŠ” νŒ¨ν„΄μ΄λ‹€.
  • 즉, ꡬ체적인 문제λ₯Ό λ―Έλ‹ˆ μ–Έμ–΄λ‘œ μž‘μ„±λœ λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μœΌλ‘œ λ§Œλ“œλŠ” 것이닀.
  • λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ€ κ·Έ μžμ²΄λ‘œλŠ” λ™μž‘ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, Java μ–Έμ–΄λ‘œ ν†΅μ—­ν•˜λŠ” 역할을 ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ λ§Œλ“€μ–΄ λ‘”λ‹€.
  • 톡역 ν”„λ‘œκ·Έλž¨μ€ λ―Έλ‹ˆ μ–Έμ–΄λ₯Ό μ΄ν•΄ν•˜κ³  ν•΄μ„ν•˜μ—¬ ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•œλ‹€.
  • 이 톡역 ν”„λ‘œκ·Έλž¨ 자체λ₯Ό 인터프리터라고 λΆ€λ₯Έλ‹€.
  • ν•΄κ²°ν•΄μ•Ό ν•  λ¬Έμ œκ°€ 변경됐을 λ•ŒλŠ” Java μ–Έμ–΄λ‘œ μž‘μ„±λœ ν”„λ‘œκ·Έλž¨μ΄ μ•„λ‹Œ λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨ μͺ½ μ½”λ“œλ₯Ό λ³€κ²½ν•œλ‹€.

λ―Έλ‹ˆ μ–Έμ–΄

λ―Έλ‹ˆ μ–Έμ–΄μ˜ λͺ…λ Ή

  • λ―Έλ‹ˆ μ–Έμ–΄λ₯Ό μ„€λͺ…ν•˜κΈ° μœ„ν•΄ λ¬΄μ„ μœΌλ‘œ μžλ™μ°¨λ₯Ό μ‘°μ’…ν•˜λŠ” μ–Έμ–΄λ₯Ό μƒκ°ν•΄λ³΄μž.
  • μžλ™μ°¨ μ‘°μ’…μ΄λΌμ§€λ§Œ ν•  수 μžˆλŠ” 일은 λ‹€μŒ μ„Έ κ°€μ§€ 뿐이닀.
  1. go: μ•žμœΌλ‘œ 1λ―Έν„° μ΄λ™ν•œλ‹€.
  2. right: 였λ₯Έμͺ½μœΌλ‘œ λˆλ‹€.
  3. left: μ™Όμͺ½μœΌλ‘œ λˆλ‹€.
  • μ΄κ²ƒλ§ŒμœΌλ‘œλŠ” μ‹œμ‹œν•˜λ‹ˆ 반볡 λͺ…령도 μΆ”κ°€ν•˜μž.
  • repeat: λ°˜λ³΅ν•œλ‹€.

λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ˜ 예

1
program go end
  • μœ„μ™€ 같이 ν”„λ‘œκ·Έλž¨μ˜ μ‹œμž‘κ³Ό 끝을 μ•Œ 수 μžˆλ„λ‘ programκ³Ό end λ‹¨μ–΄λ‘œ λͺ…령을 감싼닀.
1
program go right right go end
  • 상기 μ½”λ“œλŠ” μ•žμœΌλ‘œ κ°”λ‹€κ°€ λ‹€μ‹œ λ’€λ‘œ λŒμ•„μ„œ μ˜€λŠ” λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ΄λ‹€.
1
2
program go right go right go right go right end
program repeat 4 go right end end
  • 상기 μ½”λ“œλŠ” μ •μ‚¬κ°ν˜•μ„ 그리고 λŒμ•„μ˜€λŠ” λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ΄λ‹€.
1
program repeat 4 repeat 3 go right go left end right end end
  • 상기 μ½”λ“œλŠ” λ§ˆλ¦„λͺ¨λ₯Ό 그리고 λŒμ•„μ˜€λŠ” λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ΄λ‹€.

λ―Έλ‹ˆ μ–Έμ–΄μ˜ 문법

1
2
3
4
5
<program> ::= program <command List>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left
  • BNC ν‘œκΈ°λ²•μ„ μ‚¬μš©ν–ˆλ‹€.
  • Backus-Naur Form λ˜λŠ” Backus Normal Form의 μ€„μž„λ§λ‘œ μ–Έμ–΄μ˜ 문법을 ν‘œκΈ°ν•  λ•Œ 많이 μ‚¬μš©λœλ‹€.
1
<program> ::= program <command list>
  • ν”„λ‘œκ·Έλž¨μ΄λΌλŠ” <program>을 μ •μ˜ν•œλ‹€.
  • ν‚€μ›Œλ“œ λ’€λ‘œ λͺ…λ Ή λͺ©λ‘ <command list>κ°€ 이어진 κ²ƒμœΌλ‘œ μ •μ˜ν•œλ‹€.
  • ::=λŠ” μ’Œλ³€μ΄ μ •μ˜λ˜λŠ” λŒ€μƒμ΄κ³ , μš°λ³€μ΄ μ •μ˜λ˜λŠ” λ‚΄μš©μ΄λ‹€.
1
<command list> ::= <command>* end
  • μ—¬κΈ°μ„œλŠ” λͺ…λ Ή λͺ©λ‘ <command list>λ₯Ό μ •μ˜ν•œλ‹€.
  • <command list>λŠ” <command>κ°€ 0개 이상 반볡된 ν›„ end 단어가 μ˜€λŠ” κ²ƒμœΌλ‘œ μ •μ˜ν•œλ‹€.
  • *λŠ” μ§μ „μ˜ λͺ…λ Ήμ–΄κ°€ 0회 이상 λ°˜λ³΅λ¨μ„ μ˜λ―Έν•œλ‹€.
1
<command> ::= <repeat command> | <primitive command>
  • μ΄λ²ˆμ—λŠ” λͺ…λ Ή <command>λ₯Ό μ •μ˜ν•œλ‹€.
  • <command>λŠ” <repeat command> λ˜λŠ” <primitive command> 쀑 ν•˜λ‚˜λ‘œ μ •μ˜λœλ‹€.
1
<repeat command> ::= repeat <number> <command list>
  • λ‹€μŒμœΌλ‘œ 반볡 λͺ…λ Ή <repeat command>λ₯Ό μ •μ˜ν•œλ‹€.
  • repeatμ΄λΌλŠ” λͺ…λ Ήμ–΄ 뒀에 반볡 횟수 <number>κ°€ 였고 <command list>κ°€ λ’€λ”°λ₯΄λŠ” κ²ƒμœΌλ‘œ μ •μ˜ν•œλ‹€.
  • λͺ…λ Ή λͺ©λ‘ <command list>λŠ” 이미 μœ„μ—μ„œ μ •μ˜λ˜μ—ˆλ‹€.
  • <command list>의 μ •μ˜μ—λŠ” <command>κ°€ μ‚¬μš©λ˜κ³ , <command>의 μ •μ˜μ—μ„œλŠ” <repeat command>κ°€ μ‚¬μš©λ˜κ³ , <repeat command> μ •μ˜μ—μ„œλŠ” <command list>κ°€ μ‚¬μš©λ˜λŠ” μ΄λŸ¬ν•œ ν˜•νƒœλ₯Ό μž¬κ·€μ μΈ μ •μ˜λΌκ³  ν•œλ‹€.
1
<primitive command> ::= go | right | left
  • κΈ°λ³Έ λͺ…λ Ή <primitive command>을 μ •μ˜ν•œλ‹€.

터미널 μ΅μŠ€ν”„λ ˆμ…˜κ³Ό 논터미널 μ΅μŠ€ν”„λ ˆμ…˜

  • go, left, right 처럼 더 μ „κ°œλ˜μ§€ μ•ŠλŠ” ν‘œν˜„μ„ 터미널 μ΅μŠ€ν”„λ ˆμ…˜μ΄λΌκ³  ν•œλ‹€.
  • λ²„μŠ€λ‚˜ μ—΄μ°¨μ˜ 쒅착역을 터미널이라고 ν•˜λŠ” 것과 λΉ„μŠ·ν•˜λ‹€.
  • λ˜ν•œ programμ΄λ‚˜ repeat λͺ…λ Ήμ–΄μ²˜λŸΌ λ‹€μ‹œ 더 μ „κ°œλ˜λŠ” ν‘œν˜„μ„ 논터미널 μ΅μŠ€ν”„λ ˆμ…˜μ΄λΌκ³  ν•œλ‹€.

예제 ν”„λ‘œκ·Έλž¨

  • λ‹€μŒμ€ λ―Έλ‹ˆ μ–Έμ–΄λ₯Ό ꡬ문 ν•΄μ„ν•˜λŠ” 예제 ν”„λ‘œκ·Έλž¨μ΄λ‹€.

  • λ‹¨μˆœν•œ λ¬Έμžμ—΄μΈ λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ„ λΆ„ν•΄ν•˜μ—¬ 각 뢀뢄이 μ–΄λ–€ ꡬ쑰둜 λ˜μ–΄ μžˆλŠ”μ§€λ₯Ό ν•΄μ„ν•˜λŠ” 것이 ꡬ문 해석이닀.
  • κ°€λ Ή λ‹€μŒ λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μ΄ μ£Όμ–΄μ‘Œλ‹€κ³  ν•˜μž.
1
program repeat 4 go right end end

  • μœ„ 이미지와 같은 ꡬ문 트리 ꡬ쑰λ₯Ό λ©”λͺ¨λ¦¬ 상에 λ§Œλ“€μ–΄ λ‚΄λŠ” μ²˜λ¦¬κ°€ ꡬ문 해석이닀.

Node 클래슀

1
2
3
public abstract class Node {
	public abstract void parse(Context context) throws ParseException;
}
  • ꡬ문 트리의 각 λ…Έλ“œλ₯Ό κ΅¬μ„±ν•˜λŠ” μ΅œμƒμœ„ ν΄λž˜μŠ€μ΄λ‹€.
  • ν•΄λ‹Ή ν΄λž˜μŠ€μ—λŠ” 좔상 λ©”μ„œλ“œ parse()만 μ„ μ–Έλ˜μ–΄ μžˆλ‹€.
  • μ΄λŠ” ꡬ문 해석 처리λ₯Ό μœ„ν•œ λ©”μ„œλ“œμ΄λ‹€.
  • 인수둜 μ „λ‹¬λ˜λŠ” ContextλŠ” 상황을 λ‚˜νƒ€λ‚΄λŠ”λ°, 이후에 λ“±μž₯ν•œλ‹€.
  • 이 λ©”μ„œλ“œμ—λŠ” ꡬ문 해석을 ν•˜λ‹€κ°€ 였λ₯˜κ°€ 났을 λ•Œ ParseException μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.

ProgramNode 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* <program> ::= program <command list> */
public class ProgramNode extends Node {
	private Node commandListNode;
	
	@Override
	public void parse(Context context) throws ParseException {
		context.skipToken("program");
		commandListNode = new CommandListNode();
		commandListNode.parse(context);
	}
	
	@Override
	public String toString() {
		return "[program " + commandListNode + "]";
	}
}
  • <program>을 λ‚˜νƒ€λ‚΄λŠ” programNode ν΄λž˜μŠ€λ‹€.
  • 이 ν΄λž˜μŠ€μ—λŠ” Nodeν˜• commandListNode ν•„λ“œκ°€ μžˆλ‹€.
  • 이 ν•„λ“œλŠ” μžμ‹ μ˜ 뒀에 μ΄μ–΄μ§€λŠ” <command list에 λŒ€μ‘ν•˜λŠ” λ…Έλ“œλ₯Ό μ €μž₯ν•˜κΈ° μœ„ν•¨μ΄λ‹€.
  • ProgramNode의 parse() λ©”μ„œλ“œμ˜ 첫 λΌμΈμ—μ„œλŠ” "program"μ΄λΌλŠ” 단어λ₯Ό κ±΄λ„ˆ λ›΄λ‹€.
  • ꡬ문 해석을 ν•  λ•Œ 처리 λ‹¨μœ„λ₯Ό 토큰이라고 ν•œλ‹€.
  • μ’€ 더 μžμ„Ένžˆ λ§ν•˜μžλ©΄, μ–΄νœ˜ 뢄석은 λ¬Έμžλ‘œλΆ€ν„° 토큰을 λ§Œλ“€κ³ , ꡬ문 해석은 ν† ν°μœΌλ‘œλΆ€ν„° ꡬ문 트리λ₯Ό λ§Œλ“ λ‹€.
  • BNFλ₯Ό 보면 κ·Έ λ’€λ‘œ <command list>κ°€ 이어진닀.
  • <command list>에 λŒ€μ‘ν•˜λŠ” CommandListNode μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , κ·Έ μΈμŠ€ν„΄μŠ€μ˜ parse() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  • <command list>κ°€ μ–΄λ– ν•œ λ‚΄μš©μœΌλ‘œ λ˜μ–΄ μžˆλŠ”μ§€λŠ” ProgramNode λ©”μ„œλ“œμ—λŠ” κΈ°μˆ λ˜μ–΄ μžˆμ§€ μ•Šλ‹€.
  • ProgramNode에 κΈ°μˆ ν•˜λŠ” 것은 μ–΄λ””κΉŒμ§€λ‚˜ λ‹€μŒκ³Ό 같이 BNFμ—μ„œ λ³΄μ΄λŠ” λ²”μœ„μ— ν•œμ •λœλ‹€.
1
<program> ::= program <command list>

CommandListNode 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.ArrayList;
import java.util.List;

/* <command list> ::= <command>* end */
public class CommandListNode extends Node {
	private List<Node> list = new ArrayList<>();
	
	@Override
	public void parse(Context context) throws ParseException {
		while (true) {
			if (context.currentToken() == null) {
				throw new ParseException("Error: Missing 'end'");
			} else if (context.currentToken().equals("end")) {
				context.skipToken("end");
				break;
			} else {
				Node commandNode = new CommandNode();
				commandNode.parse(context);
				list.add(commandNode);
			}
		}
	}
	
	@Override
	public String toString() {
		return list.toString();
	}
}
  • <command>κ°€ 0회 이상 λ°˜λ³΅ν•˜λŠ” ν˜•νƒœλ₯Ό κ°–κ³ μž java.util.List<Node>ν˜• ν•„λ“œλ₯Ό κ°€μ§€κ³  μžˆλ‹€.
  • parse() λ©”μ„œλ“œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.
  1. ν˜„μž¬ μ£Όλͺ©ν•˜κ³  μžˆλŠ” 토큰 context.curruntToken() 값이 null이면 λ”λŠ” 남은 토큰이 μ—†λ‹€λŠ” 말이닀. 이 경우 parse()λŠ” end λͺ…λ Ήμ–΄κ°€ μ—†λ‹€λŠ” λ©”μ‹œμ§€λ₯Ό λΆ™μ—¬ μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.
  2. ν˜„μž¬ μ£Όλͺ©ν•˜κ³  μžˆλŠ” 토큰이 endλ©΄ λ°˜λ³΅λ¬Έμ„ νƒˆμΆœν•œλ‹€.
  3. ν˜„μž¬ μ£Όλͺ©ν•˜κ³  μžˆλŠ” 토큰이 endκ°€ μ•„λ‹ˆλ©΄ 그것은 <command>λΌλŠ” 의미둜, CommandNode μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ parse() ν•œλ‹€. 이후 μΈμŠ€ν„΄μŠ€λ₯Ό list ν•„λ“œμ— μΆ”κ°€ν•œλ‹€.
  • μ—¬κΈ°μ„œλ„ BNF둜 기술된 λ²”μœ„μ—μ„œλ§Œ μ²˜λ¦¬ν•˜λŠ” 것을 μ•Œ 수 μžˆλ‹€.
  • μ΄λ ‡κ²Œ ν•˜λ©΄ ν”„λ‘œκ·Έλž¨μ— μ‹€μˆ˜κ°€ 쀄어든닀.
  • 무심코 β€˜μ΄λ ‡κ²Œ ν•˜λ©΄ 속도가 더 빨라지지 μ•Šμ„κΉŒ?β€™λΌλŠ” μœ ν˜Ήμ— μ‚¬λ‘œμž‘ν˜€ 더 μžμ„Έν•œ κ΅¬μ‘°κΉŒμ§€ μ½λŠ” 처리λ₯Ό ν•œλ‹€λ©΄ μƒκ°ν•˜μ§€ λͺ»ν•œ 버그λ₯Ό λ§Œλ“€μ–΄ λ‚Ό 수 μžˆλ‹€.
  • interpreter νŒ¨ν„΄μ€ μ›λž˜ λ―Έλ‹ˆ μ–Έμ–΄λΌλŠ” 간접적인 처리 방법을 μ΄μš©ν•˜λ―€λ‘œ μž”μž¬μ£Όλ‘œ νš¨μœ¨μ„ κΎ€ν•˜λŠ” 것은 κ·Έλ‹€μ§€ ν˜„λͺ…ν•œ 방법이 μ•„λ‹ˆλ‹€.

CommandNode 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* <command> ::= <repeat command> | <primitive command> */
public class CommandNode extends Node {
	private Node node;
	
	@Override
	public void parse(Context context) throws ParseException {
		if (context.currentToken().equals("repeat")) {
			node = new RepeatCommandNode();
			node.parse(context);
		} else {
			node = new PrimitiveCommandNode();
			node.parse(context);
		}
	}
	
	@Override
	public String toString() {
		return node.toString();
	}
}
  • Nodeν˜• ν•„λ“œ nodeλŠ” <repeat command>에 λŒ€μ‘ν•˜λŠ” RepeatCommandNode λ˜λŠ” <primitive command>에 λŒ€μ‘ν•˜λŠ” PrimitiveCommandNode 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ €μž₯ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.

RepeatCommandNode 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* <repeat command> ::= repeat <number> <command list> */
public class RepeatCommandNode extends Node {
	private int number;
	private Node commandListNode;
	
	@Override
	public void parse(Context context) throws ParseException {
		context.skipToken("repeat");
		number = context.currentNumber();
		context.nextToken();
		commandListNode = new CommandListNode();
		commandListNode.parse(context);
	}
	
	@Override
	public String toString() {
		return "[repeat " + number + " " + commandListNode + "]";
	}
}
  • <number> 뢀뢄은 number ν•„λ“œ, <command list> 뢀뢄은 commandListNode ν•„λ“œμ— μ €μž₯λœλ‹€.
  • parse() λ©”μ„œλ“œλŠ” λ‹€μŒκ³Ό 같이 μž¬κ·€μ„±μ„ λˆλ‹€.
  1. RepeatCommandNode의 parse() λ©”μ„œλ“œ μ•ˆμ—μ„œλŠ” CommandListNode의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ parse() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  2. CommandListNode의 parse() λ©”μ„œλ“œ μ•ˆμ—μ„œλŠ” CommandNode의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ parse() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  3. CommandNode의 parse() λ©”μ„œλ“œ μ•ˆμ—μ„œλŠ” RepeatCommandNode의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ parse() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.
  • μœ„μ˜ μž¬κ·€ μ²˜λ¦¬λŠ” μ–Έμ  κ°€ PrimitiveCommandNodeλ₯Ό λ§Œλ‚˜κΈ° μ „κΉŒμ§€ λ°˜λ³΅λœλ‹€.
  • PrimitiveCommandNodeκ°€ λ°”λ‘œ 터미널 μ΅μŠ€ν”„λ ˆμ…˜μ΄λ‹€.
  • μž¬κ·€μ μΈ 취급에 μ΅μˆ™ν•˜μ§€ μ•Šλ‹€λ©΄, μ™ μ§€ λ¬΄ν•œ 루프가 될 것 κ°™μ§€λ§Œ, 그것은 착각이닀.
  • 터미널 μ΅μŠ€ν”„λ ˆμ…˜μ— μ˜μ›νžˆ λ„λ‹¬ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ κ·Έ μ •μ˜λŠ” 잘λͺ»λœ 것이닀.

PrimitiveCommandNode 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* <primitive command> ::= go | right | left */
public class PrimitiveCommandNode extends Node {
	private String name;
	
	@Override
	public void parse(Context context) throws ParseException {
		name = context.currentToken();
		if (name == null) {
			throw new ParseException("Error: Missing <primitive command>");
		} else if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
			throw new ParseException("Error: Unknown <primitive command>: '" + name + "'");
		}
		context.skipToken(name);
	}
	
	@Override
	public String toString() {
		return name;
	}
}
  • 이 클래슀의 parse()μ—μ„œλŠ” λ‹€λ₯Έ parse() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ§€ μ•ŠλŠ”λ‹€.

Context 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.*;

public class Context {
	private String[] tokens;
	private String lastToken;
	private int index;
	
	public Context(String text) {
		this.tokens = text.split("\\s+");
		this.index = 0;
		nextToken();
	}
	
	public String nextToken() {
		if (index < tokens.length) {
			lastToken = tokens[index++];
		} else {
			lastToken = null;
		}
		
		return lastToken;
	}
	
	public String currentToken() {
		return lastToken;
	}
	
	public void skipToken(String token) throws ParseException {
		if (currentToken() == null) {
			throw new ParseException("Error: '" + token + "' is expected, but no more token is found.");
		} else if (!token.equals(currentToken())) {
			throw new ParseException("Error: '" + token + "' is expected, but '" + currentToken() + "' is found.");
		}
		nextToken();
	}
	
	public int currentNumber() throws ParseException {
		if (currentToken() == null) {
			throw new ParseException("Error: No more token.");
		}
		
		int number = 0;
		
		try {
			number = Integer.parseInt(currentToken());
		} catch (NumberFormatException e) {
			throw new ParseException("Error: " + e);
		}
		
		return number;
	}
}
  • ꡬ문 해석을 μœ„ν•΄ ν•„μš”ν•œ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.
  1. nextToken(): λ‹€μŒ 토큰을 μ–»λŠ”λ‹€.
  2. currentToken(): ν˜„μž¬ 토큰을 μ–»λŠ”λ‹€.
  3. skipToken(): ν˜„μž¬ 토큰을 μ²΄ν¬ν•˜κ³ μ„œ, λ‹€μŒ 토큰을 μ–»λŠ”λ‹€.
  4. currentToken(): ν˜„μž¬ 토큰을 수치둜 μ–»λŠ”λ‹€.
  • μ£Όμ–΄μ§„ λ¬Έμžμ—΄λ‘œλΆ€ν„° 곡백 λ¬Έμžκ°€ ν•œ 개 이상 μ—°μ†λœ 것을 κ΅¬λΆ„ν•˜μ—¬ 토큰 배열을 μž‘μ„±ν•œλ‹€.
  • text.split("\\s+") 라인이 이에 ν•΄λ‹Ήν•œλ‹€.

ParseException 클래슀

1
2
3
4
5
public class ParseException extends Exception {
	public ParseException(String msg) {
		super(msg);
	}
}
  • λ‹¨μˆœνžˆ μ˜ˆμ™Έ 처리λ₯Ό μœ„ν•œ ν΄λž˜μŠ€μ΄λ‹€.

Main 클래슀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
	public static void main(String[] args) {
		try {
			for (String text: Files.readAllLines(Path.of("program.txt"))) {
				System.out.println("text = \"" + text + "\"");
				Node node = new ProgramNode();
				node.parse(new Context(text));
				System.out.println("node = " + node);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • Main은 program.txtλΌλŠ” νŒŒμΌμ„ 읽어 ν•œμ€„ν•œμ€„ λ―Έλ‹ˆ ν”„λ‘œκ·Έλž¨μœΌλ‘œ μƒκ°ν•˜κ³  ꡬ문을 λΆ„μ„ν•˜μ—¬ κ²°κ³Όλ₯Ό λ¬Έμžμ—΄λ‘œ ν‘œμ‹œν•œλ‹€.
  • program.txt 파일 λ‚΄μš©μ€ λ‹€μŒκ³Ό κ°™λ‹€.
program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end

Interpreter νŒ¨ν„΄μ˜ λ“±μž₯인물

Node 클래슀

  • ꡬ문 트리의 λ…Έλ“œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•˜λŠ” 좔상 ν‘œν˜„(AbstractExpression) 역을 λ§‘μ•˜λ‹€.

PrimitiveCommandNode 클래슀

  • BNF의 터미널 μ΅μŠ€ν”„λ ˆμ…˜μ— λŒ€μ‘ν•˜λŠ” 쒅단 ν‘œν˜„(TerminalExpression) 역을 λ§‘μ•˜λ‹€.

ProgramNode, CommandNode, RepeatCommandNode, CommandListNode 클래슀

  • BNF의 논터미널 μ΅μŠ€ν”„λ ˆμ…˜μ— λŒ€μ‘ν•˜λŠ” 비쒅단 ν‘œν˜„(NonterminalExpression) 역을 λ§‘μ•˜λ‹€.

Context 클래슀

  • 인터프리터가 ꡬ문 해석을 ν•˜κΈ° μœ„ν•œ 정보λ₯Ό μ œκ³΅ν•˜λŠ” λ¬Έλ§₯(Context) 역을 λ§‘μ•˜λ‹€.

Main 클래슀

  • ꡬ문 트리λ₯Ό μ‘°λ¦½ν•˜κΈ° μœ„ν•΄ TerminalExpression와 NonterminalExpressionλ₯Ό ν˜ΈμΆœν•˜λŠ” 의뒰자(Client) 역을 λ§‘μ•˜λ‹€.

μ±…μ—μ„œ μ œμ‹œν•˜λŠ” 힌트

κ·Έ 외에 μ–΄λ–€ λ―Έλ‹ˆ μ–Έμ–΄κ°€ μžˆμ„κΉŒ?

  • 이 μž₯μ—μ„œλŠ” λ¬΄μ„ μœΌλ‘œ μžλ™μ°¨λ₯Ό μ‘°μ’…ν•˜λŠ” λ―Έλ‹ˆ μ–Έμ–΄λ₯Ό ν•™μŠ΅ν–ˆλ‹€.
  • λͺ‡ κ°€μ§€ 예λ₯Ό λ“€μ–΄λ³΄μž.
  1. μ •κ·œ ν‘œν˜„: raining & (dogs | cats) * β†’ raining 뒀에 cat λ˜λŠ” dog이 0번 이상 반볡
  2. 검색 ꡬ문: site: example.com "검색어1" AND "검색어2" β†’ example.comμ—μ„œ 검색어1κ³Ό 검색어2에 μ™„μ „νžˆ μΌμΉ˜ν•˜λŠ” 검색을 ν‘œν˜„
  3. 일괄 처리 μ–Έμ–΄: 기본적인 λͺ…령이 λͺ‡ 개 μ€€λΉ„λ˜μ–΄ 있고, κ·Έ λͺ…령을 μˆœμ„œλŒ€λ‘œ μ‹€ν–‰ν•˜κ±°λ‚˜ λ°˜λ³΅ν•˜λŠ” 언어도 Interpreter νŒ¨ν„΄μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλ‹€. 이 μž₯의 무선 μ‘°μ’… μ œμ–΄λ„ 일괄 처리 μ–Έμ–΄μ˜ 일쒅이라고 ν•  수 μžˆλ‹€.

κ±΄λ„ˆλ›Έ 것인가 읽을 것인가?

  • 인터프리터λ₯Ό λ§Œλ“€ λ•Œ 자주 μΌμ–΄λ‚˜λŠ” 것이 토큰을 ν•œ 개 더 μ½κ±°λ‚˜ λͺ» μ½λŠ” 버그이닀.
  • 각 논터미널에 λŒ€μ‘ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ“Έ λ•ŒλŠ” 항상 이 λ©”μ„œλ“œμ— 왔을 λ•Œ μ–΄λ””κΉŒμ§€ 토큰을 μ½μ—ˆλŠ”μ§€, 이 λ©”μ„œλ“œμ—μ„œ λ‚˜μ˜¬ λ•Œ μ–΄λ””κΉŒμ§€ 토큰을 읽어야 ν•˜λŠ”μ§€ μ‹ κ²½ 써야 ν•œλ‹€.
This post is licensed under CC BY 4.0 by the author.