1. concrete syntax / abstract syntax
프로그래밍 언어의 문법은 크게 concrete syntax와 abstract syntax로 구성되어 있음.
concrete syntax는 프로그램을 실제 문자열로 표현하는 방법을 다루는 거고, abstract syntax는 이 표현의 대상인 프로그램을 문법적 측면에서 정의함. abstract syntax에서는 프로그램을 문자열로 직접 다루지 않음.
예를 들어서 덧셈, 곱셈, 상수, 괄호가 있는 concrete syntax는 CFG를 사용해서 아래처럼 정의할 수 있음
E -> T | T '+' E
T -> U | U '*' T
U -> N | '(' E ')'
N -> '0' | '1' | '2' | ...
이 문법에서는 E(expression), T(term), U(unit) 을 구분하고 있지만 얘들은 concrete syntax 수준에만 있는 것들이고, abstract syntax에서는 이런 구분이 불필요함.
E -> N | E + E | E * E
N -> natural number
abstract syntax에서는 애초에 프로그램이 트리로 표현되기 때문에 괄호나 fixity에 따른 구분(E, T, U)이 없음. concrete syntax에서 그런 구분이 필요한 이유는 syntax tree를 문자열로 표현했을 때 ambiguity가 발생하지 않도록 세심한 처리가 필요하기 때문임. 예를 들어서 concrete syntax를
E -> N | E '+' E | E '*' E
이렇게 정의하면 "2 * 3 + 4" 를 (2 * 3) + 4 로 읽어야 하는지 2 * (3 + 4) 로 읽어야 하는지 알 수 없게 됨.
파서가 하는 일은 먼저 concrete syntax에 기반해서 문자열을 concrete syntax tree(parse tree랑 같은 뜻임)로 바꾸고, 그걸 다시 abstract syntax tree로 변환한다고 보면 됨.
https://ropas.snu.ac.kr/~dreameye/PL/slide/PL2.pdf 이 자료에도 concrete/abstract syntax에 대한 설명이 나오는데, 개인적으로 concrete syntax에 대한 설명은 약간 아쉬움. "프로그램을 읽는 방법"이라는 표현은 concrete syntax보다는 뒤에서 설명할 generative grammar와 parsing algorithm의 차이를 대비시키기에 더 어울린다고 생각함.
2. generative grammar & algorithm / analytic grammar
그럼 concrete syntax를 어떻게 정의할 수 있을까?
가장 흔하게 사용되는 방법은 Noam Chomsky가 만든 generative grammar인데, generative grammar는 말이 되는 문자열을 만드는 방법을 제시함으로서 문법을 정의함. 아까 예로 든 CFG(context free grammar)나 regular language, context sensitive language 등이 전부 generative grammar의 일종임.
generative grammar의 특징중 하나는 문법이 애매할 수 있다는 점임. 문자열을 만드는 방법만 제시하니까 A방법으로 만든 문자열하고 B방법으로 만든 문자열이 충돌할 수 있는거지. 자연어에서는 이런 ambiguity가 필수적이지만 프로그래밍 언어에서는 피해야 하기 때문에 아까 말했듯이 문법을 설계할 때 주의해야 함.
그리고 generative grammar는 문장을 만드는 법만 알려주고 읽는 법(파싱하는 법)은 안알려줌. 그래서 별도의 파싱 알고리즘이 필요하고 어떤 알고리즘으로 파싱될 수 있느냐에 따라서 LL(k)니 LR(k)니 LALR이니 하는 구분이 생김.
이렇게 문법 정의랑 알고리즘이 따로 노는게 불편해서 나온게 PEG 같은 analytic grammar임. analytic grammar는 문법을 정의할 때 애초에 파싱하는 방법을 고려해서 애매함, 비결정성이 없게 만듬
개인적으로는 analytic grammar 보다는 generative grammar가 더 맘에 듬. 일단 비결정성 자체가 굉장한 추상화인데 그걸 빼버리는 점이 별로. 단적으로 regex를 NFA로 표현하는건 쉽지만 DFA로 표현하는건 까다로움.
1번은 사람들이 parse tree랑 AST는 아는데 concrete/abstract syntax는 생각보다 잘 모르는것 같아서 얘기한거고
2번은 generative grammar와 parsing algorithm의 구분에 대해서 얘기하고 싶었는데 찾아보니 analytic grammar란 것도 있길래 추가해서 써봄
주딱 성능 확실하구만
머라는거임?
저도 못 모루겠음 ㅎㅎ 그냥 그런게있구나 하고 키워드만 줍줍함.
주딱 이정도면 현업이 프로그래머가 2군이고 1군이 수학자일듯
이 새낀 왜 자꾸 후빨함
어떤 새끼가 나를 앞빨하길래 재밌어보여서 나도 해봄
앞빨은 뭔 근첩 단어노? 후빨이 뭔지 모르노?
키스라도 했나보지
주딱 취향 확실하노
파싱보다는 타입쪽에 할 얘기가 더 많은데 힘들어서,,
레알 콤퓨타 싸이언티스트 ㄷㄷ - dc App
그래서 프로그래밍 언어의 구문을 어떻게 기술하면 좋은지 요약해 줄래...? 나중에 이런 분야도 공부하고 싶지만 지금은 다른 분야부터 배우고 있어서 아직 잘 이해가 안 가네.
음 요약은 어렵지만 언어 문법을 직접 만들거면 일단 CFG가 뭔지를 배우라고 추천하고 싶음. 그 다음 파소가 필요하면 CFG 파싱 알고리즘을 배우던 파서 라이브러리를 쓰던 하면 됨. (CFG가 제일 흔하게 쓰이는 문법임. 다른건 지나치게 단순하거나 복잡함)
고마워. 문맥 자유 문법[context-free grammar, CFG]은 언어학 관련 자료에서도 언급되던데 여기서도 중요한가 보네.
알았어
]주딱님 뉴비 질문 받아주시나요. 촘스키 계층 구조에서 여러 계층으로 언어가 나뉘어지는데, 그 중 보면 그 계층 중 하나 안에도 다시 진부분집합을 구해낼 수 있잖아요. 예를 들어, LL(1) 파서에 의해 인식되는 언어는 모두 CFG 이지만, 모든 CFG가 LL(1) 파서에 의해 인식되지 못하는 것 처럼요. 이걸 생각해보다보니 결국 각 언어와 문법의 범위를 규정하는 조건이 왜 하필 그렇게 되어 있는지 궁금해졌습니다. 그러니까, 왜 하필 계층이 4개인지, 왜 5개가 혹은 3개가 아닌지 묻고 싶습니다!
필연적이라고 말하기는 애매하지만, unrestricted grammar(
https://en.wikipedia.org/wiki/Unrestricted_grammar)를
정의하고 저기서 production rule의 형태를 제한하는 방법으로 얻어지는게 Chomsky hierarchy임. "LL(1) 파서로 인식되는 언어" 같은 개념은 production rule에 제약을 주는 형태로 얻어지는게 아니고 저런 파싱 알고리즘은 더 늦게 등장했으니까 들어갈 수 없었을거라고 생각함.
production rule에 제약을 줘서 얻어지는 다른 언어가 있으면 그런게 계층에 들어가도 이상하진 않을듯?
아, 그런 관점에서 접근해 만들어진거였군요. 대답 감사합니다. 덕분에 의문이 해소 되었습니다. 여담이지만 지금 보니 제가 질문에 CFL을 CFG라고 잘못 적은게 보여서 좀 부끄럽네요. 여하튼, 감사합니다!
컴공 학부만 나와도 다 알아듣는데 비전공 웹개발충들 갤러리에서 똥글만 처싸다가 여기와서 멘탈 털리는거 존나웃기네 ㅋㅋ
ㄹㅇ PL 3학년 수준 개념들인데 ㅋㅋ
근디 lalr같은거는 conflict체크 하자나 그거 있어도 문법 모호성은 계속 있는거냐
CFG는 모호성이 있을 수도 있고 없을 수도 있음. LALR 같은 파싱 알고리즘은 보통 애매하지 않은 CFG 중에서도 일부만 파싱함. 애매하면 conflict가 나지만, 애매하지 않은 문법이어도 conflict가 날 수 있고 그러면 LALR로 파싱 안되는거지.
근데 여기서 말하는 파서가 스캐너까지 포함해서 말하는거임? 보통 concrete는 스캐너가 처리하고 abstract로 바꾸는 거는 파서로 알고있었는데 진심 궁금해서 물어보는거
lexing으로 만들어낸 토큰 리스트를 AST로 바꾸는것도 concrete syntax가 관여하는 부분임
스캐너 자체가 하는일도 문자열을 다루니까 당연히 concrete syntax랑 관련 있고