Syntax Analysis Part 3

COMP321 Compiler โ€” Syntax Analysis Part 3

Kyungpook National University | Hwisoo So | Spring 2026


๐Ÿ“‹ 1ํŽ˜์ด์ง€ โ€” ์ œ๋ชฉ

Syntax Analysis โ€“ 3

๊ตฌ๋ฌธ ๋ถ„์„ โ€“ 3

์ด๋ฒˆ ๊ฐ•์˜๋Š” Syntax Analysis (๊ตฌ๋ฌธ ๋ถ„์„)์˜ ์„ธ ๋ฒˆ์งธ ํŒŒํŠธ๋‹ค.
์ฆ‰ ์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ์—์„œ Lexical Analysis(ํ† ํฐํ™”) ๋‹ค์Œ ๋‹จ๊ณ„, ๋ฌธ์žฅ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๋Š” ๋‹จ๊ณ„๋ฅผ ๋‹ค๋ฃจ๋Š” ๋‚ด์šฉ์ด๋‹ค.

  • Hwisoo So โ†’ ์†Œํœ˜์ˆ˜ (๊ฐ•์˜ ๋‹ด๋‹น ๊ต์ˆ˜)
  • Kyungpook National University โ†’ ๊ฒฝ๋ถ๋Œ€ํ•™๊ต
  • COMP321 Compiler Spring 2026 โ†’ ์ปดํŒŒ์ผ๋Ÿฌ ๊ณผ๋ชฉ, 2026๋…„ ๋ด„ํ•™๊ธฐ

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ๊ฐ€์šด๋ฐ: โ€œSyntax Analysis โ€“ 3โ€
  • ์•„๋ž˜: ๊ต์ˆ˜ ์ด๋ฆ„ + ํ•™๊ต ๋กœ๊ณ 
  • ์˜ค๋ฅธ์ชฝ ์œ„: ๊ณผ๋ชฉ ์ •๋ณด

์ด ํŽ˜์ด์ง€๋Š” ์™„์ „ํ•œ ํ‘œ์ง€(slide cover)๋‹ค. ๋‚ด์šฉ ์—†์Œ, ๋‹จ์ˆœํžˆ ๊ฐ•์˜ ์‹œ์ž‘ ์•Œ๋ฆผ ์—ญํ• .


๐Ÿ“‹ 2ํŽ˜์ด์ง€ โ€” Outlook (์ „์ฒด ๊ฐœ์š”)

Syntax Analysis COMP321@KNU

Outlook

์ „์ฒด ๊ฐœ์š” / ์ง„ํ–‰ ๋‚ด์šฉ

ํ•ญ๋ชฉ ์ƒํƒœ
Syntax and Semantics of Programming Languages โœ” ์™„๋ฃŒ
Specifying the Syntax of a programming language โœ” ์™„๋ฃŒ
CFGs, BNF and EBNF โœ” ์™„๋ฃŒ โ€” CFG, BNF, EBNF (๋ฌธ๋ฒ• ์ •์˜ ํ˜•์‹๋“ค)
Grammar transformations โœ” ์™„๋ฃŒ โ€” Left factoring, left recursion ์ œ๊ฑฐ ๋“ฑ
Top-down parsing (LL) โœ” ์™„๋ฃŒ
Recursive descent (LL) parser construction โ† ์ง€๊ธˆ ์—ฌ๊ธฐ
LL Grammars ์˜ˆ์ •
AST Construction ์˜ˆ์ •
Parse trees vs. ASTs ์˜ˆ์ •
Chomskyโ€™s Hierarchy ์˜ˆ์ •

์ง€๊ธˆ ์œ„์น˜

๐Ÿ‘‰ Parsing ์ค‘์—์„œ๋„ Recursive Descent Parser ๋ถ€๋ถ„ ์‹œ์ž‘


๐Ÿ“‹ 3ํŽ˜์ด์ง€ โ€” Recursive Descent Parsing

Recursive Descent Parsing

์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์‹ฑ

Recursive descent parsing is a straightforward LL (top-down) parsing method.

์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์‹ฑ์€ ๋‹จ์ˆœํ•œ LL(ํ•˜ํ–ฅ์‹) ํŒŒ์‹ฑ ๋ฐฉ๋ฒ•์ด๋‹ค.

  • LL = Left to right + Leftmost derivation
  • top-down ๋ฐฉ์‹
  • ๊ตฌํ˜„ ์‰ฌ์›€ / ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ตฌ์กฐ๋กœ ๋งŒ๋“ ๋‹ค

We will now look at how to develop a recursive descent parser from an EBNF specification.

์ด์ œ EBNF ๋ช…์„ธ๋กœ๋ถ€ํ„ฐ ์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์„œ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณธ๋‹ค.

ํ•ต์‹ฌ: ๋ฌธ๋ฒ• โ†’ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜

Key idea: the parse tree structure corresponds to the callerโ€“callee relationship of parsing procedures that call each other at runtime.

ํ•ต์‹ฌ ์•„์ด๋””์–ด: ํŒŒ์ŠคํŠธ๋ฆฌ ๊ตฌ์กฐ๋Š” ์‹คํ–‰ ์‹œ ์„œ๋กœ ํ˜ธ์ถœํ•˜๋Š” ํŒŒ์‹ฑ ํ•จ์ˆ˜๋“ค์˜ ํ˜ธ์ถœ ๊ด€๊ณ„์™€ ๋Œ€์‘๋œ๋‹ค.

โœ” ํ•ต์‹ฌ ๊ฐœ๋…: ํŒŒ์ŠคํŠธ๋ฆฌ ๊ตฌ์กฐ = ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ตฌ์กฐ

Sentence
 โ”œโ”€โ”€ Subject
 โ”œโ”€โ”€ Verb
 โ””โ”€โ”€ Object
โ†’ ์‹ค์ œ ์ฝ”๋“œ:
parseSentence()
  -> parseSubject()
  -> parseVerb()
  -> parseObject()

์ฆ‰ ํŠธ๋ฆฌ = ํ•จ์ˆ˜ ํ˜ธ์ถœ ํŠธ๋ฆฌ


๐Ÿ“‹ 4ํŽ˜์ด์ง€ โ€” Recursive Descent Parser Construction Example

Recursive Descent Parser Construction Example

์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์„œ ๊ตฌ์„ฑ ์˜ˆ์ œ

๋ฌธ๋ฒ• ์ •์˜

Sentence ::= Subject Verb Object .
Subject  ::= I | a Noun | the Noun
Object   ::= me | a Noun | the Noun
Noun     ::= cat | mat | rat
Verb     ::= like | is | see | sees
๊ทœ์น™ ์˜๋ฏธ
Sentence ์ฃผ์–ด + ๋™์‚ฌ + ๋ชฉ์ ์–ด + โ€œ.โ€
Subject I / a+๋ช…์‚ฌ / the+๋ช…์‚ฌ
Object me / a+๋ช…์‚ฌ / the+๋ช…์‚ฌ
Noun cat / mat / rat
Verb like / is / see / sees

Define a procedure parseN for each non-terminal N

๊ฐ non-terminal N๋งˆ๋‹ค parseN ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•œ๋‹ค.

parseSentence()
parseSubject()
parseObject()
parseNoun()
parseVerb()

โœ” ํ•ต์‹ฌ ๊ทœ์น™: ๋ฌธ๋ฒ• โ†’ ํ•จ์ˆ˜ 1:1 ๋งคํ•‘

๋ฌธ๋ฒ• ํ•จ์ˆ˜
Sentence parseSentence
Subject parseSubject
Object parseObject
Noun parseNoun
Verb parseVerb

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ ์ƒ๋‹จ: ๋ฌธ๋ฒ•
  • ํ•˜๋‹จ: ํ•จ์ˆ˜ ๋ฆฌ์ŠคํŠธ
  • ์˜๋ฏธ: ๋ฌธ๋ฒ•์„ ์ฝ”๋“œ๋กœ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธด๋‹ค

๐Ÿ“‹ 5ํŽ˜์ด์ง€ โ€” Recursive Descent Parser Construction Example (ํด๋ž˜์Šค ๊ตฌ์กฐ)

Recursive Descent Parser Construction Example

ํŒŒ์„œ ํด๋ž˜์Šค ๋ผˆ๋Œ€

class MicroEnglishParser:

    def __init__(self):
        self.currentToken = None  # ํ˜„์žฌ ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๋ณ€์ˆ˜

        # Auxiliary methods will go here (๋ณด์กฐ ํ•จ์ˆ˜๋“ค์ด ์—ฌ๊ธฐ์— ๋“ค์–ด๊ฐ„๋‹ค)

        # Parsing methods will go here (ํŒŒ์‹ฑ ํ•จ์ˆ˜๋“ค์ด ์—ฌ๊ธฐ์— ๋“ค์–ด๊ฐ„๋‹ค)

โœ” parser๋Š” ํ•ญ์ƒ ํ˜„์žฌ ํ† ํฐ์„ ํ•˜๋‚˜ ๋ณด๊ณ  ํŒ๋‹จํ•œ๋‹ค.

์ „์ฒด ๊ตฌ์กฐ:

Parser ํด๋ž˜์Šค
 โ”œโ”€โ”€ currentToken
 โ”œโ”€โ”€ auxiliary methods (accept ๋“ฑ)
 โ””โ”€โ”€ parsing methods (parseX)

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ ๋ฐ•์Šค ์•ˆ: ํด๋ž˜์Šค ๊ตฌ์กฐ
  • ์ฃผ์„์œผ๋กœ ์˜์—ญ ๋‚˜๋ˆ”
  • ์˜๋ฏธ: ์‹ค์ œ ๊ตฌํ˜„ ๋ผˆ๋Œ€

๐Ÿ”ฅ 1~5ํŽ˜์ด์ง€ ํ•ต์‹ฌ ์ •๋ฆฌ

  1. Recursive Descent Parser ์ •์˜ โ€” LL(top-down) ๋ฐฉ์‹ / ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ธฐ๋ฐ˜ ํŒŒ์‹ฑ
  2. ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฐœ๋… โ†’ ํŒŒ์ŠคํŠธ๋ฆฌ = ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ตฌ์กฐ
  3. ๊ตฌํ˜„ ์›๋ฆฌ โ€” Non-terminal โ†’ ํ•จ์ˆ˜ / Terminal โ†’ accept()
  4. ์ฝ”๋“œ ๊ตฌ์กฐ
    Parser
     โ”œโ”€โ”€ currentToken
     โ”œโ”€โ”€ accept()
     โ””โ”€โ”€ parseX()
    

๐Ÿ“‹ 6ํŽ˜์ด์ง€ โ€” Recursive Descent Parser Construction: Auxiliary Methods

Recursive Descent Parser Construction: Auxiliary Methods

์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์„œ ๊ตฌ์„ฑ: ๋ณด์กฐ ๋ฉ”์„œ๋“œ

class MicroEnglishParser:

    def __init__(self, scanner):
        self.scanner = scanner          # scanner๋ฅผ ์ €์žฅํ•œ๋‹ค
        self.currentToken = None        # ํ˜„์žฌ ํ† ํฐ ์ดˆ๊ธฐํ™”

    def accept(self, expectedToken):
        if self.currentToken == expectedToken:
            # get next token from scanner
            self.currentToken = self.scanner.scan()  # ๋‹ค์Œ ํ† ํฐ์œผ๋กœ ์ด๋™
        else:
            report a syntax error  # ๋ฌธ๋ฒ• ์˜ค๋ฅ˜ ๋ฐœ์ƒ

โœ” ์ด์ œ parser๋Š” scanner์™€ ์—ฐ๊ฒฐ๋œ๋‹ค: input โ†’ scanner โ†’ parser

accept ํ•จ์ˆ˜์˜ ์—ญํ•  (ํ•ต์‹ฌ ์ค‘์š”)

๋™์ž‘ ์„ค๋ช…
ํ˜„์žฌ ํ† ํฐ ๊ฒ€์‚ฌ expectedToken๊ณผ ์ผ์น˜ ์—ฌ๋ถ€ ํ™•์ธ
๋งž์œผ๋ฉด ๋‹ค์Œ ํ† ํฐ์œผ๋กœ ์ด๋™
ํ‹€๋ฆฌ๋ฉด ์—๋Ÿฌ ๋ฐœ์ƒ
accept("if")
โ†’ "if"๊ฐ€ ์•„๋‹ˆ๋ฉด ๋ฐ”๋กœ ์—๋Ÿฌ

๐Ÿ‘‰ accept ํ•จ์ˆ˜ = parser์˜ ํ•ต์‹ฌ primitive

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: ํด๋ž˜์Šค + accept ํ•จ์ˆ˜ ์ฝ”๋“œ
  • ์˜๋ฏธ: parser ๋™์ž‘์˜ ์ตœ์†Œ ๋‹จ์œ„ ์ •์˜

๐Ÿ“‹ 7ํŽ˜์ด์ง€ โ€” Recursive Descent Parser Construction: Parsing Methods

Recursive Descent Parser Construction: Parsing Methods

ํŒŒ์‹ฑ ๋ฉ”์„œ๋“œ

Sentence ::= Subject Verb Object .

๋ฌธ์žฅ โ†’ ์ฃผ์–ด + ๋™์‚ฌ + ๋ชฉ์ ์–ด + โ€œ.โ€

def parseSentence(self):
    parseSubject()   # ์ฃผ์–ด ํŒŒ์‹ฑ
    parseVerb()      # ๋™์‚ฌ ํŒŒ์‹ฑ
    parseObject()    # ๋ชฉ์ ์–ด ํŒŒ์‹ฑ
    self.accept('.')  # "."์„ ๊ธฐ๋Œ€ํ•˜๊ณ  ์†Œ๋น„ํ•œ๋‹ค

โœ” ๋ฌธ๋ฒ• ๊ทธ๋Œ€๋กœ ์ฝ”๋“œํ™”๋จ

Sentence ::= A B C .
โ†’
parseA()
parseB()
parseC()
accept('.')

๐Ÿ‘‰ ์™„์ „ํžˆ ๊ธฐ๊ณ„์  ๋ณ€ํ™˜

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ๋ฌธ๋ฒ•
  • ์•„๋ž˜: ์ฝ”๋“œ
  • 1:1 ๋Œ€์‘ ๊ฐ•์กฐ

๐Ÿ“‹ 8ํŽ˜์ด์ง€ โ€” Recursive Descent Parser: Parsing Methods (Subject)

Recursive Descent Parser: Parsing Methods

parseSubject ๊ตฌํ˜„

Subject ::= I | a Noun | the Noun

์ฃผ์–ด๋Š”: I / a + ๋ช…์‚ฌ / the + ๋ช…์‚ฌ

def parseSubject(self):
    if self.currentToken == 'I':
        self.accept('I')
    elif self.currentToken == 'a':
        self.accept('a')
        parseNoun()
    elif self.currentToken == 'the':
        self.accept('the')
        parseNoun()
    else:
        report a syntax error
โœ” **์„ ํƒ ๊ตฌ์กฐ ( ) ์ฒ˜๋ฆฌ ๋ฐฉ์‹:**
A ::= X | Y | Z
โ†’
if token == X:
elif token == Y:
elif token == Z:
else error

๐Ÿ‘‰ FIRST set ๊ธฐ๋ฐ˜ ์„ ํƒ

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ๋ฌธ๋ฒ•
  • ์•„๋ž˜: if-elif ์ฝ”๋“œ
  • โ€œ์„ ํƒ๋ฌธ = ์กฐ๊ฑด๋ฌธโ€ ๋ณ€ํ™˜

๐Ÿ“‹ 9ํŽ˜์ด์ง€ โ€” Recursive Descent Parser: Parsing Methods (Noun)

Recursive Descent Parser: Parsing Methods

parseNoun ๊ตฌํ˜„

Noun ::= cat | mat | rat

๋ช…์‚ฌ๋Š”: cat / mat / rat

def parseNoun(self):
    if self.currentToken == 'cat':
        self.accept('cat')
    elif self.currentToken == 'mat':
        self.accept('mat')
    elif self.currentToken == 'rat':
        self.accept('rat')
    else:
        report a syntax error

โœ” ์™„์ „ ๋™์ผ ํŒจํ„ด: ์„ ํƒ๋ฌธ โ†’ if-elif


๐Ÿ“‹ 10ํŽ˜์ด์ง€ โ€” Recursive Descent Parser: Parsing Methods (Object, Verb โ€” ๋นˆ์นธ)

Recursive Descent Parser: Parsing Methods

parseObject / parseVerb (๋ฏธ์™„์„ฑ ์ƒํƒœ)

Object ::= me | a Noun | the Noun
Verb   ::= like | is | see | sees
def parseObject(self):
    ? (๋ฏธ์™„์„ฑ)

def parseVerb(self):
    ? (๋ฏธ์™„์„ฑ)

โœ” ์ด ํŽ˜์ด์ง€๋Š” ์ผ๋ถ€๋Ÿฌ ๋น„์›Œ๋‘  โ†’ ํ•™์ƒ์ด ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ฒŒ ํ•จ

์–ด๋–ป๊ฒŒ ์ฑ„์›Œ์•ผ ํ•˜๋Š”์ง€ (ํ•ต์‹ฌ)

parseObject:

if token == 'me':
elif token == 'a':
elif token == 'the':

parseVerb:

if token in ('like', 'is', 'see', 'sees'):

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ๋ฌธ๋ฒ•
  • ์•„๋ž˜: ๋นˆ ์ฝ”๋“œ
  • โ€œ์ด๊ฑธ ๋„ˆ๊ฐ€ ์ฑ„์›Œ๋ผโ€ ๊ตฌ์กฐ

๐Ÿ”ฅ 6~10ํŽ˜์ด์ง€ ํ•ต์‹ฌ ์š”์•ฝ

  1. accept ํ•จ์ˆ˜ โ€” ํ† ํฐ ๊ฒ€์‚ฌ + ๋‹ค์Œ ํ† ํฐ ์ด๋™ / ํ‹€๋ฆฌ๋ฉด ์—๋Ÿฌ

  2. ๋ณ€ํ™˜ ๊ทœ์น™ (ํ•ต์‹ฌ ์•”๊ธฐ)

๋ฌธ๋ฒ• ์ฝ”๋“œ
A ::= B C parseB(); parseC();
A ::= x accept(x)
A ::= X | Y if-elif
  1. ํ•ต์‹ฌ ๊ตฌ์กฐ โ†’ parser๋Š” ๊ฒฐ๊ตญ if / ํ•จ์ˆ˜ํ˜ธ์ถœ / accept ์ด 3๊ฐœ๋กœ ๋๋‚จ

๐Ÿ“‹ 11ํŽ˜์ด์ง€ โ€” Recursive Descent Parser: Parsing Methods (Object, Verb โ€” ์™„์„ฑ)

Recursive Descent Parser: Parsing Methods

parseObject / parseVerb ์™„์„ฑ๋ณธ

Object ::= me | a Noun | the Noun
Verb   ::= like | is | see | sees
def parseObject(self):
    if self.currentToken == 'me':
        self.accept('me')
    elif self.currentToken == 'a':
        self.accept('a')
        parseNoun()
    elif self.currentToken == 'the':
        self.accept('the')
        parseNoun()

def parseVerb(self):
    if self.currentToken in ('like', 'is', 'see', 'sees'):
        self.acceptIt()  # ํ˜„์žฌ ํ† ํฐ์„ ๊ทธ๋Œ€๋กœ ์†Œ๋น„ํ•œ๋‹ค

โœ” acceptIt() vs accept(token)

ํ•จ์ˆ˜ ์˜๋ฏธ
accept(x) ํŠน์ • ํ† ํฐ x ๊ธฐ๋Œ€
acceptIt() ๊ทธ๋ƒฅ ํ˜„์žฌ ํ† ํฐ ์†Œ๋น„

๐Ÿ‘‰ parseVerb๋Š”: โ€œ์–ด๋–ค ๋™์‚ฌ๋“  ์ƒ๊ด€์—†๊ณ , ๊ทธ๋ƒฅ ํ•˜๋‚˜ ์†Œ๋น„โ€

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: Object, Verb ๋ฌธ๋ฒ• + ์™„์„ฑ ์ฝ”๋“œ
  • ์ด์ „ ํŽ˜์ด์ง€(๋นˆ์นธ)๋ฅผ ์ฑ„์šด ์™„์„ฑ๋ณธ

๐Ÿ“‹ 12ํŽ˜์ด์ง€ โ€” Systematic Development of a RD Parser

Systematic Development of a RD Parser

์žฌ๊ท€ ํ•˜๊ฐ• ํŒŒ์„œ์˜ ์ฒด๊ณ„์ ์ธ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•

(1) Express grammar in EBNF

๋ฌธ๋ฒ•์„ EBNF๋กœ ํ‘œํ˜„ํ•œ๋‹ค.

(2) Grammar Transformations:

๋ฌธ๋ฒ• ๋ณ€ํ™˜ ์ˆ˜ํ–‰

  • Left factorization and left recursion elimination
  • ์ขŒ์ธก ์ธ์ˆ˜๋ถ„ํ•ด์™€ ์ขŒ์ธก ์žฌ๊ท€ ์ œ๊ฑฐ

(3) Create a parser class with

parser ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

  • private variable currentToken
  • methods to call the scanner: accept and acceptIt

(4) Convert EBNF into a RD parser

EBNF๋ฅผ RD parser๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

  • Implement private parsing methods โ€” ํŒŒ์‹ฑ ๋ฉ”์„œ๋“œ ๊ตฌํ˜„
  • add private parseN method for each nonterminal N โ€” ๊ฐ non-terminal๋งˆ๋‹ค parseN ํ•จ์ˆ˜ ์ถ”๊ฐ€
  • public parse method that:
    • gets the first token from the scanner (์ฒซ ํ† ํฐ์„ scanner์—์„œ ๊ฐ€์ ธ์˜ด)
    • calls parseS (S is the start symbol of the grammar)
    • ensure that all tokens are consumed upon return from parseS()

โœ” RD parser ๋งŒ๋“œ๋Š” ์ ˆ์ฐจ = ์‹œํ—˜ ํ•ต์‹ฌ

โ‘  ๋ฌธ๋ฒ• ์ž‘์„ฑ
โ‘ก ๋ฌธ๋ฒ• ๋ณ€ํ™˜
โ‘ข parser ๊ตฌ์กฐ ์ƒ์„ฑ
โ‘ฃ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜

๐Ÿ“‹ 13ํŽ˜์ด์ง€ โ€” MiniC: Systematic Development of an RD Parser (1 & 2)

MiniC: Systematic Development of an RD Parser (1 & 2)

Common prefixes require left-factorization:

๊ณตํ†ต ์ ‘๋‘์–ด๊ฐ€ ์žˆ์œผ๋ฉด left-factorization ํ•„์š”

๊ธฐ์กด ๋ฌธ๋ฒ•:

program ::= ( function-def | variable-def )*

ํ”„๋กœ๊ทธ๋žจ์€ ํ•จ์ˆ˜ ์ •์˜ ๋˜๋Š” ๋ณ€์ˆ˜ ์ •์˜ ๋ฐ˜๋ณต

โŒ ๋ฌธ์ œ:

typespec ID ...
typespec ID ...

๋‘˜ ๋‹ค ๊ฐ™์€ ์‹œ์ž‘ โ†’ LL parser์—์„œ ์„ ํƒ ๋ถˆ๊ฐ€

๋ณ€ํ™˜ ํ›„:

program ::= ( typespec ID (VarPart | FuncPart) )*

๊ณตํ†ต ๋ถ€๋ถ„(typespec ID)์„ ๋จผ์ € ๋นผ๊ณ  ๋ถ„๊ธฐ

โœ” left factoring:

A ::= X Y | X Z
โ†’
A ::= X (Y | Z)

๐Ÿ‘‰ LL parser์—์„œ ํ•„์ˆ˜

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ์›๋ž˜ ๋ฌธ๋ฒ•
  • ์•„๋ž˜: ๋ณ€ํ™˜๋œ ๋ฌธ๋ฒ•
  • ๊ณตํ†ต prefix ์ œ๊ฑฐ ๊ณผ์ • ์‹œ๊ฐํ™”

๐Ÿ“‹ 14ํŽ˜์ด์ง€ โ€” Left Recursion Elimination

Left recursion elimination

์ขŒ์ธก ์žฌ๊ท€ ์ œ๊ฑฐ

๊ธฐ์กด ๋ฌธ๋ฒ• (left recursion):

add-expr ::= mult-expr
           | add-expr "+" mult-expr
           | add-expr "-" mult-expr

๋ง์…ˆ ํ‘œํ˜„์‹ ์ •์˜

โŒ ๋ฌธ์ œ: left recursion โ†’ add-expr โ†’ add-expr ... โ†’ RD parser์—์„œ ๋ฌดํ•œ ์žฌ๊ท€ ๋ฐœ์ƒ

๋ณ€ํ™˜ ํ›„:

add-expr ::= mult-expr ( ("+" | "-") mult-expr )*

mult-expr ๋’ค์— +,- ๋ฐ˜๋ณต ๊ตฌ์กฐ

โœ” left recursion ์ œ๊ฑฐ ๊ณต์‹:

A โ†’ A ฮฑ | ฮฒ
โ†’
A โ†’ ฮฒ ฮฑ*

๐Ÿ‘‰ ์žฌ๊ท€ โ†’ while๋ฌธ์œผ๋กœ ๋ฐ”๋€œ

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ์žฌ๊ท€ ๊ตฌ์กฐ
  • ์•„๋ž˜: ๋ฐ˜๋ณต ๊ตฌ์กฐ

๐Ÿ“‹ 15ํŽ˜์ด์ง€ โ€” Developing a RD Parser for MiniC

Developing a RD Parser for MiniC

MiniC.py์˜ ์ปดํŒŒ์ผ๋Ÿฌ ์‹คํ–‰ ์ฝ”๋“œ

def compileProgram(self, sourceName):
    self.scanner = Scanner(source)           # scanner ์ƒ์„ฑ
    self.reporter = ErrorReporter()          # ์—๋Ÿฌ ๋ฆฌํฌํ„ฐ ์ƒ์„ฑ
    self.parser = Parser(scanner, reporter)  # parser ์ƒ์„ฑ
    self.parser.parse()                      # ํŒŒ์‹ฑ ์‹คํ–‰

    boolean successful = (self.reporter.numErrors == 0)  # ์—๋Ÿฌ 0์ด๋ฉด ์„ฑ๊ณต

    if successful:
        print("Compilation was successful.")
    else:
        print("Compilation was unsuccessful.")

โœ” ์ปดํŒŒ์ผ๋Ÿฌ ์ „์ฒด ํ๋ฆ„:

source
 โ†’ scanner
 โ†’ parser
 โ†’ errorReporter
 โ†’ ๊ฒฐ๊ณผ ์ถœ๋ ฅ

๐Ÿ‘‰ parser๋Š” ์ „์ฒด pipeline์˜ ์ผ๋ถ€


๐Ÿ”ฅ 11~15ํŽ˜์ด์ง€ ํ•ต์‹ฌ ์š”์•ฝ

  1. parseObject / parseVerb ์™„์„ฑ โ€” ์„ ํƒ๋ฌธ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ ์™„์„ฑ
  2. RD parser ๋งŒ๋“œ๋Š” ์ ˆ์ฐจ (์‹œํ—˜ ํ•ต์‹ฌ)
    • EBNF ์ž‘์„ฑ โ†’ left factoring โ†’ left recursion ์ œ๊ฑฐ โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜
  3. ๋ฌธ๋ฒ• ๋ณ€ํ™˜ ์ด์œ  โ€” LL parser์—์„œ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ
  4. ์ปดํŒŒ์ผ๋Ÿฌ ์ „์ฒด ๊ตฌ์กฐ โ€” scanner โ†’ parser โ†’ error ์ฒ˜๋ฆฌ

๐Ÿ“‹ 16ํŽ˜์ด์ง€ โ€” The Error Reporter (ErrorReport.py)

The Error Reporter (ErrorReport.py)

์—๋Ÿฌ ๋ฆฌํฌํ„ฐ

class ErrorReporter:

    def __init__(self):
        self.numErrors = 0  # ์—๋Ÿฌ ๊ฐœ์ˆ˜๋ฅผ 0์œผ๋กœ ์ดˆ๊ธฐํ™”

    def reportError(self, m, tok, pos):
        print("ERROR: ", end="")
        for c in m:
            if c == "%":
                print(tok, end="")  # % ์ž๋ฆฌ์— ํ† ํฐ ์‚ฝ์ž…
            else:
                print(c, end="")
        print(" " + str(pos.StartCol) + ".." + str(pos.EndCol)
              + ", line" + str(pos.StartLine) + ".")  # ์—๋Ÿฌ ์œ„์น˜ ์ถœ๋ ฅ
        self.numErrors += 1  # ์—๋Ÿฌ ๊ฐœ์ˆ˜ ์ฆ๊ฐ€

์˜ˆ์‹œ

reportError("Type specifier instead of % expected", ...)
โ†’ ์ถœ๋ ฅ: ERROR: Type specifier instead of IF expected 9..10, line 22.

โœ” โ€%โ€์˜ ์—ญํ• : ๋ฉ”์‹œ์ง€ ํ…œํ”Œ๋ฆฟ์˜ % ์ž๋ฆฌ์— ํ† ํฐ ์‚ฝ์ž…

๐Ÿ‘‰ ์ถœ๋ ฅ ํ˜•์‹: ๋ฉ”์‹œ์ง€ ํ…œํ”Œ๋ฆฟ + ํ† ํฐ + ์œ„์น˜


๐Ÿ“‹ 17ํŽ˜์ด์ง€ โ€” MiniC Parser: Reporting Errors and Throwing Exceptions

MiniC Parser: Reporting errors and throwing exceptions

์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์˜ˆ์™ธ ๋ฐœ์ƒ

def syntaxError(self, messageTemplate, tokenQuoted):
    pos = self.currentToken.GetSourcePos()  # ํ˜„์žฌ ํ† ํฐ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ
    self.errorReporter.reportError(messageTemplate, tokQuoted, pos)  # ์—๋Ÿฌ ์ถœ๋ ฅ
    raise SyntaxError()  # ์˜ˆ์™ธ ๋ฐœ์ƒ

class SyntaxError(Exception):
    def __init__(self, message=""):
        super().__init__(message)  # ๋ถ€๋ชจ ํด๋ž˜์Šค(Exception) ์ดˆ๊ธฐํ™”

์˜ˆ์‹œ

syntaxError("Type specifier instead of % expected", ...)
โ†’ ERROR: Type specifier instead of IF expected ...

โœ” ํ๋ฆ„:

syntaxError()
 โ†’ reportError()
 โ†’ raise exception

๐Ÿ‘‰ exception์„ ์“ฐ๋Š” ์ด์œ : ํŒŒ์‹ฑ ์ค‘๋‹จ + ์ƒ์œ„๋กœ ์—๋Ÿฌ ์ „๋‹ฌ


๐Ÿ“‹ 18ํŽ˜์ด์ง€ โ€” Exception Handling in the MiniC Parser

Exception Handling in the MiniC Parser

MiniC ํŒŒ์„œ์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

def parse(self):
    self.currentToken = self.scanner.scan()  # ์ฒซ ํ† ํฐ ์ฝ๊ธฐ
    try:
        self.parseProgram()  # ํ”„๋กœ๊ทธ๋žจ ํŒŒ์‹ฑ
        if currentToken.kind != Token.EOF:
            self.syntaxError("% not expected after end of program", ...)
            # ํ”„๋กœ๊ทธ๋žจ ๋ ๋’ค์— ์ด์ƒํ•œ ํ† ํฐ โ†’ ์—๋Ÿฌ
    except SyntaxError as s:
        return None  # ์‹คํŒจ ์‹œ None ๋ฐ˜ํ™˜

โœ” ์ „์ฒด ๊ตฌ์กฐ:

try:
    parseProgram()
except:
    ์—๋Ÿฌ ์ฒ˜๋ฆฌ

๐Ÿ‘‰ ์˜๋ฏธ: ์—๋Ÿฌ ๋ฐœ์ƒ โ†’ exception โ†’ ์—ฌ๊ธฐ์„œ catch

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: try-except ๊ตฌ์กฐ ๊ฐ•์กฐ
  • parser์˜ โ€œ์ตœ์ƒ์œ„ ์•ˆ์ „์žฅ์น˜โ€

๐Ÿ“‹ 19ํŽ˜์ด์ง€ โ€” Algorithm to Convert EBNF into a RD Parser

Algorithm to convert EBNF into a RD parser

EBNF โ†’ RD parser ๋ณ€ํ™˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜

def parseN(self) { parse X() }

N ::= X โ†’ parseN์€ parseX ํ˜ธ์ถœ

The conversion โ€ฆ is so โ€œmechanicalโ€ that it can easily be automated!

์ด ๋ณ€ํ™˜์€ ๋งค์šฐ ๊ธฐ๊ณ„์ ์ด๋ผ ์ž๋™ํ™” ๊ฐ€๋Šฅํ•˜๋‹ค.

We can describe the algorithm by a set of mechanical rewrite rules.
๊ธฐ๊ณ„์  ๋ณ€ํ™˜ ๊ทœ์น™์œผ๋กœ ์„ค๋ช… ๊ฐ€๋Šฅํ•˜๋‹ค.

โœ” ์ง„์งœ ์ค‘์š”: ๋ฌธ๋ฒ• โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜์€ ๊ทœ์น™ ๊ธฐ๋ฐ˜

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: ๋ฌธ๋ฒ• โ†’ ํ•จ์ˆ˜ ๋ณ€ํ™˜ ํ™”์‚ดํ‘œ

๐Ÿ“‹ 20ํŽ˜์ด์ง€ โ€” Algorithm to Convert EBNF into a RD Parser (๊ทœ์น™)

Algorithm to convert EBNF into a RD parser

๋ณ€ํ™˜ ๊ทœ์น™ ์ƒ์„ธ

๋ฌธ๋ฒ• ์š”์†Œ ๋ณ€ํ™˜ ์ฝ”๋“œ ์„ค๋ช…
terminal t self.accept(t) t๋ฅผ ๊ธฐ๋Œ€ํ•˜๊ณ  ์†Œ๋น„
non-terminal N self.parseN() parseN ํ˜ธ์ถœ
ฮต (๋นˆ ๋ฌธ์ž์—ด) # do nothing ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
X Y (์ˆœ์„œ) self.parseX(); self.parseY() X ํŒŒ์‹ฑ ํ›„ Y ํŒŒ์‹ฑ

โœ” ๋ณ€ํ™˜ ๊ทœ์น™ ์ •๋ฆฌ (์‹œํ—˜ ํ•ต์‹ฌ):

terminal t   โ†’ accept(t)
non-terminal N โ†’ parseN()
ฮต             โ†’ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ
X Y           โ†’ ์ˆœ์„œ๋Œ€๋กœ ํ˜ธ์ถœ

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: ๊ฐ ๊ทœ์น™ โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜
  • โ€œ์™„์ „ ๊ธฐ๊ณ„์  ๋ณ€ํ™˜ ๊ทœ์น™โ€

๐Ÿ”ฅ 16~20ํŽ˜์ด์ง€ ํ•ต์‹ฌ ์š”์•ฝ

  1. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ โ€” reportError โ†’ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ / syntaxError โ†’ exception ๋ฐœ์ƒ
  2. parser ์ „์ฒด ํ๋ฆ„ โ€” scan โ†’ parse โ†’ error handling
  3. EBNF โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜ ๊ทœ์น™ (ํ•ต์‹ฌ ์•”๊ธฐ)
    • t โ†’ accept(t)
    • N โ†’ parseN()
    • ฮต โ†’ nothing
    • XY โ†’ ์ˆœ์ฐจ ํ˜ธ์ถœ

๐Ÿ‘‰ ์—ฌ๊ธฐ๊นŒ์ง€ ์ดํ•ดํ•˜๋ฉด RD parser ๊ตฌํ˜„ 80% ๋๋‚œ ์ƒํƒœ๋‹ค.


๐Ÿ“‹ 21ํŽ˜์ด์ง€ โ€” Algorithm to Convert EBNF into a RD Parser (์„ ํƒ/๋ฐ˜๋ณต)

Algorithm to convert EBNF into a RD parser

์„ ํƒ ( | ) ๊ณผ ๋ฐ˜๋ณต ( * ) ๋ณ€ํ™˜

parsing X | Y

match self.currentToken.kind:
    cases in FIRST[X]:
        parse X
    cases in FIRST[Y]:
        parse Y
    case _:
        report syntax error

FIRST[X]: X๋กœ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” terminal๋“ค์˜ ์ง‘ํ•ฉ
FIRST [X] denotes the set of terminal symbols that start sentences derived from X.

parsing X*

while self.currentToken.kind is in FIRST[X]:
    parse X()

โœ” ํ•ต์‹ฌ ๋ณ€ํ™˜ ๊ทœ์น™ (๋งค์šฐ ์ค‘์š”):

1. ์„ ํƒ ( | )

X | Y
โ†’
if token โˆˆ FIRST[X]:
    parseX()
elif token โˆˆ FIRST[Y]:
    parseY()
else:
    error

2. ๋ฐ˜๋ณต ( * )

X*
โ†’
while token โˆˆ FIRST[X]:
    parseX()

๐Ÿ‘‰ ์ด ๋‘ ๊ฐœ๊ฐ€ RD parser ํ•ต์‹ฌ์ด๋‹ค.

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ์„ ํƒ ๊ตฌ์กฐ
  • ์•„๋ž˜: ๋ฐ˜๋ณต ๊ตฌ์กฐ
  • FIRST ์ง‘ํ•ฉ ๊ธฐ๋ฐ˜ ๋ถ„๊ธฐ

๐Ÿ“‹ 22ํŽ˜์ด์ง€ โ€” Example: parseExpr

Example: parseExpr

Expr ::= AndExpr ( "||" AndExpr )*
Expr๋Š” AndExpr ๋’ค์— (โ€œ ย  โ€ AndExpr)๊ฐ€ 0๋ฒˆ ์ด์ƒ ๋ฐ˜๋ณต
def parseExpr(self):
    self.parseAndExpr()                           # ๋จผ์ € AndExpr ํŒŒ์‹ฑ
    while self.currentToken.kind == Token.OR:     # ํ˜„์žฌ ํ† ํฐ์ด OR(||)์ด๋ฉด ๋ฐ˜๋ณต
        self.acceptIt()                           # "||" ์†Œ๋น„
        self.parseAndExpr()                       # ๋‹ค์Œ AndExpr ํŒŒ์‹ฑ

โœ” 21ํŽ˜์ด์ง€ ๊ทœ์น™ ์ ์šฉ:

( X )*
โ†’
while condition:
    X

๐Ÿ‘‰ ์˜๋ฏธ:

a || b || c
โ†’
parseAndExpr()
while '||':
    parseAndExpr()

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์œ„: ๋ฌธ๋ฒ•
  • ์•„๋ž˜: ์ฝ”๋“œ
  • ๋ฐ˜๋ณต ๊ตฌ์กฐ โ†’ while๋ฌธ

๐Ÿ“‹ 23ํŽ˜์ด์ง€ โ€” Example: parseProgram

Example: parseProgram

program ::= ( (VOID|INT|BOOL|FLOAT) ID ( FunPart | VarPart ) )*

ํ”„๋กœ๊ทธ๋žจ์€: ํƒ€์ž… + ID + (ํ•จ์ˆ˜ or ๋ณ€์ˆ˜), ์ด ๊ตฌ์กฐ๊ฐ€ ๋ฐ˜๋ณต๋จ

def parseProgram(self):
    while self.isTypeSpecifier(self.currentToken.kind):  # ํ˜„์žฌ ํ† ํฐ์ด ํƒ€์ž…์ด๋ฉด ๋ฐ˜๋ณต
        self.acceptIt()             # ํƒ€์ž… ์†Œ๋น„
        self.accept(Token.ID)       # ID ์†Œ๋น„
        if self.currentToken.kind == Token.LEFTPAREN:  # "("์ด๋ฉด
            self.parseFunPart()     # ํ•จ์ˆ˜ ํŒŒ์‹ฑ
        else:
            self.parseVarPart()     # ๋ณ€์ˆ˜ ํŒŒ์‹ฑ

isTypeSpecifier is true iff the current Token is either Token.VOID, Token.INT, Token.BOOL or Token.FLOAT.
โ†’ isTypeSpecifier๋Š” ํ† ํฐ์ด void/int/bool/float์ผ ๋•Œ true

โœ” ๊ตฌ์กฐ:

while ํƒ€์ž…:
    ํƒ€์ž…
    ID
    if '(':
        ํ•จ์ˆ˜
    else:
        ๋ณ€์ˆ˜
๐Ÿ‘‰ **์„ ํƒ ( )** ๊ณผ ๋ฐ˜๋ณต ( * ) ๋‘˜ ๋‹ค ํฌํ•จ๋œ ์™„์ „ํ•œ ์˜ˆ์ œ

๐Ÿ–ผ ๊ทธ๋ฆผ ์„ค๋ช…

  • ์Šฌ๋ผ์ด๋“œ: ๋ฌธ๋ฒ• โ†’ ์ฝ”๋“œ
  • ์‹ค์ œ ์ปดํŒŒ์ผ๋Ÿฌ ์ˆ˜์ค€ ์ฝ”๋“œ

๐Ÿ“‹ 24ํŽ˜์ด์ง€ โ€” Outlook (์ „์ฒด ์ •๋ฆฌ)

Outlook

์ „์ฒด ์ •๋ฆฌ

ํ•ญ๋ชฉ ์ƒํƒœ
Syntax and Semantics of Programming Languages โœ” ์™„๋ฃŒ
Specifying the Syntax โœ” ์™„๋ฃŒ
Top-down parsing (LL) โœ” ์™„๋ฃŒ
Recursive descent (LL) parser construction โœ” ์™„๋ฃŒ
LL Grammars ์˜ˆ์ •
AST Construction ์˜ˆ์ •
Parse trees vs. ASTs ์˜ˆ์ •
Chomskyโ€™s Hierarchy ์˜ˆ์ •

โœ” ์ง€๊ธˆ๊นŒ์ง€: RD parser ๊ตฌํ˜„ ์™„๋ฃŒ
โœ” ๋‹ค์Œ: AST / LL grammar


๐Ÿ”ฅ 21~24ํŽ˜์ด์ง€ ํ•ต์‹ฌ ์š”์•ฝ (์ง„์งœ ์ค‘์š”)

1. ์„ ํƒ๋ฌธ ( | )

if token โˆˆ FIRST[X]:
    parseX()
elif token โˆˆ FIRST[Y]:
    parseY()

2. ๋ฐ˜๋ณต๋ฌธ ( * )

while token โˆˆ FIRST[X]:
    parseX()

3. RD parser ํ•ต์‹ฌ ๊ตฌ์กฐ 4๊ฐœ

์š”์†Œ ์ฝ”๋“œ
terminal accept()
non-terminal parseN()
์„ ํƒ if / elif
๋ฐ˜๋ณต while

4. ์ „์ฒด ํ๋ฆ„

EBNF
 โ†’ ๋ฌธ๋ฒ• ๋ณ€ํ™˜
 โ†’ FIRST ๊ธฐ๋ฐ˜ ๋ถ„๊ธฐ
 โ†’ ํ•จ์ˆ˜ ์ฝ”๋“œ ์ƒ์„ฑ

๐Ÿ”ฅ ์ „์ฒด ๊ฐ•์˜ ํ•ต์‹ฌ ์š”์•ฝ (1~24ํŽ˜์ด์ง€)

RD parser ๋งŒ๋“œ๋Š” ์ ˆ์ฐจ

โ‘  EBNF ๋ฌธ๋ฒ• ์ž‘์„ฑ
โ‘ก Grammar Transformations (left factoring, left recursion ์ œ๊ฑฐ)
โ‘ข Parser ํด๋ž˜์Šค ์ƒ์„ฑ (currentToken, accept, acceptIt)
โ‘ฃ EBNF โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜

EBNF โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜ ๊ทœ์น™ ์™„์ „ ์ •๋ฆฌ

๋ฌธ๋ฒ• ์ฝ”๋“œ
terminal t accept(t)
non-terminal N parseN()
ฮต nothing
X Y parseX(); parseY();
X \| Y if FIRST[X]... elif FIRST[Y]...
X* while FIRST[X]: parseX()

ํ•ต์‹ฌ ํ•จ์ˆ˜ ์ •๋ฆฌ

ํ•จ์ˆ˜ ์—ญํ• 
accept(t) ํŠน์ • ํ† ํฐ t ๊ธฐ๋Œ€ํ•˜๊ณ  ์†Œ๋น„, ํ‹€๋ฆฌ๋ฉด ์—๋Ÿฌ
acceptIt() ํ˜„์žฌ ํ† ํฐ ๊ทธ๋ƒฅ ์†Œ๋น„
syntaxError() ์—๋Ÿฌ ์ถœ๋ ฅ + exception ๋ฐœ์ƒ
parseN() non-terminal N ํŒŒ์‹ฑ

Categories:

Updated:

Leave a comment