Parser란?
Parser는 인터프리터나 컴파일러의 일부다.
프로그램(코드)을 분석해서 이게 뭔지 파악하고, 그 구체적인 문법(Concrete Syntax)을 추상 문법(Abstract Syntax) 으로 변환해 주는 역할을 한다.
구체적 문법(Concrete Syntax)이 뭔지 다시 간단하게 정리를 해보자면
예를 들어, x + 1 같은 코드가 구체적 문법이다.
추상 문법(Abstract Syntax)는 x + 1이라는 코드를 Add(Var("x"), Num(1)) 같은 구조로 변환하는 게 추상 문법으로 바꾸는 과정이다. 이 과정을 파싱이라고 한다.
그래서 파서를 만들려면, 언어의 구체적 문법(Concrete Syntax)에 대한 명확한 명세(specification)가 필요하다.
바로 BNF를 이용하여 명세하는 것이다.
다음과 같은 구조로 작동한다고 보면 된다.

예를 들어 두 가지의 더하기 문법 정의가 있다고 해보자
{+ 4 2} 와 (4 + 2)
이 서로 다른 두 구체적 문법을 파싱하면 동일한 트리 구조(Abstarct Syntax Tree)를 가진 추상 문법으로 바뀐다..
다음 구조를 간단하게 코드로 설명해보겠다
trait Expr
case class Num(n: Int) extends Expr
case class Add(l: Expr, r: Expr) extends Expr
case class Sub(l: Expr, r: Expr) extends Expr
def interp(expr: Expr): Int = expr match {
case Num(n) => n
case Add(l, r) => interp(l) + interp(r)
case Sub(l, r) => interp(l) - interp(r)
}
@main def run(): Unit = {
val codeInConcreteSyntax = "{+ 3 {- 8 2}}"
val abstracSytaxOfCode = parse(codeInConcreteSyntax)
println(interp(abstracSytaxOfCode))
}
{+ 3 {- 8 2}}를 구체적 문법으로 입력받고, 파서가 그걸 추상 문법으로 변환한 후, 인터프리터가 그걸 계산해서 결과를 내는 구조다.
파싱된 결과는 Add(Num(3), Sub(Num(8, 2)))일 것이다.
interp 함수는 우리가 추상 구문 트리로 변환한 표현식을 받아서 실제 계산을 해준다.
최종 출력 결과는 9가 될 것이다.
파서가 없으면 컴퓨터는 님이 쓴 코드를 해석할 수 없다.
'parse 함수'를 구현하지 않는 건, 마치 책을 읽지도 않고 내용을 이해했다고 주장하는 것과 비슷하다고 할 수 있다.
파서를 구현하지 않으면 님이 만든 언어는 단순히 '문자들의 나열'에 불과하게 된다.
프로그램은 복잡한 계산이나 명령을 이해하지 못하고 단순히 글자 하나하나를 처리하는 수준에 머물러 버린다.