GCC 및 Clang 파서는 실제로 손으로 작성됩니까?


90

GCC와 LLVM-연타 사용하고있는 것으로 보인다 필기 재귀 하강 파서를 , 그리고 하지 생성, 들소 플렉스를 기반으로, 아래에서 위로 구문 분석 기계.

여기 누군가가 이것이 사실인지 확인해 주시겠습니까? 그렇다면 왜 주류 컴파일러 프레임 워크는 손으로 쓴 파서를 사용합니까?

업데이트 : 여기에이 주제에 대한 흥미로운 블로그


27
거의 모든 주류 컴파일러는 손으로 쓴 파서를 사용하고 있습니다. 그게 무슨 문제입니까?
SK-logic

2
성능이 필요한 경우 수동으로 수행해야합니다 (반).
Gene Bushuyev 2011 년

15
그리고뿐만 아니라 성능 - 더 나은 오류 메시지, 복구 할 수있는 능력 등
SK-논리

MS VisualStudio는 어떻습니까? 오픈 소스는 아니지만 MS의 누군가가 손으로 쓴 재귀 하강 파서를 사용하고 있는지 확인할 수 있습니까?
OrenIshShalom

3
@GeneBushuyev, GCC의 위키에서 : "...하지만 타이밍이 1.5 %의 속도 향상을 보여 주었다 , 주요 이점은 향후 개선의 촉진된다 ..."이 속도 향상은 ... 오히려 한계 것
OrenIshShalom

답변:


78

예:

  • GCC는 한때 yacc (bison) 파서를 사용했지만 3.x 시리즈의 어느 시점에서 손으로 쓴 재귀 하강 파서로 대체되었습니다. http://gcc.gnu.org/wiki/New_C_Parser 를 참조하십시오. 관련 패치 제출에 대한 링크.

  • Clang은 또한 손으로 작성한 재귀 하강 파서를 사용합니다 . http://clang.llvm.org/features.html 끝 부분에있는 "C, Objective C, C ++ 및 Objective C ++ 용 단일 통합 파서"섹션을 참조 하십시오 .


3
ObjC, C 및 C ++에 LL (k) 문법이 있다는 의미입니까?
Lindemann 2012

47
아니오 : 세 가지 중 가장 단순한 C조차도 문법이 모호합니다. 예를 들어, foo * bar;곱셈 식 (결과가 사용되지 않음) 또는 bar포인터 -to- 유형 의 변수 선언으로 구문 분석 할 수 있습니다 foo. 어느 것이 올바른지 여부는 typedeffor foo가 당시 범위 내에 있는지 여부에 따라 달라지며 , 이는 어떤 양의 미리 보기로도 결정할 수 없습니다. 그러나 그것은 재귀 하강 파서가 그것을 처리하기 위해 추악한 추가 기계가 필요하다는 것을 의미합니다.
Matthew Slattery 2012

9
C ++ 11, C 및 Objective C에는 GLR 파서가 처리 할 수있는 문맥 자유 문법이 있다는 경험적 증거를 통해 확인할 수 있습니다.
Ira Baxter

2
컨텍스트 민감성과 관련 하여이 답변 은 두 가지 모두 주장하지 않습니다. 이러한 언어를 구문 분석하는 것이 Turing-complete 일 가능성이 높습니다.
Ioannis Filippidis 2014

106

C는 구문 분석하기 어렵고 C ++는 본질적으로 불가능하다는 민간 이론이 있습니다.

사실이 아닙니다.

사실은 C와 C ++는 구문 분석 기계를 해킹하고 기호 테이블 데이터를 엉키지 않고 LALR (1) 구문 분석기를 사용하여 구문 분석하기가 매우 어렵다는 것입니다. 실제로 GCC는 YACC와 이와 같은 추가 해커를 사용하여 구문 분석하는 데 사용되었으며, 그렇습니다. 이제 GCC는 손으로 쓴 파서를 사용하지만 여전히 기호 테이블 해커를 사용합니다. Clang 사람들은 자동화 된 파서 생성기를 사용하지 않았습니다. AFAIK Clang 파서는 항상 손으로 코딩 된 재귀 하강입니다.

사실은 C와 C ++가 자동으로 생성 된 강력한 파서 (예 : GLR 파서 ) 로 비교적 쉽게 파싱 할 수 있으며 해킹이 필요하지 않다는 것입니다. 엘사 C ++ 파서는이 하나의 예이다. 우리의 C ++ 프런트 엔드 는 또 다른 것입니다 (모든 "컴파일러"프런트 엔드와 마찬가지로 GLR은 매우 훌륭한 구문 분석 기술입니다).

우리의 C ++ 프론트 엔드는 GCC만큼 빠르지 않고 확실히 Elsa보다 느립니다. 다른 더 긴급한 문제가 있기 때문에 신중하게 튜닝하는 데 약간의 에너지를 쏟았습니다 (수백만 줄의 C ++ 코드에 사용 되었음에도 불구하고). Elsa는 더 일반적이기 때문에 GCC보다 느릴 가능성이 높습니다. 요즘 프로세서 속도를 감안할 때 이러한 차이는 실제로별로 중요하지 않을 수 있습니다.

그러나 오늘날 널리 배포되는 "실제 컴파일러"는 10 년 또는 20 년 전 또는 그 이상의 컴파일러에 뿌리를두고 있습니다. 비 효율성은 훨씬 더 중요했고 아무도 GLR 파서에 대해 들어 본 적이 없었기 때문에 사람들은 자신이 할 줄 아는 일을했습니다. Clang은 확실히 더 최근에 나온 것이지만 민속 정리는 오랫동안 "설득력"을 유지합니다.

더 이상 그렇게 할 필요가 없습니다. 컴파일러 유지 관리 기능이 향상되어 GLR 및 기타 파서를 프런트 엔드로 매우 합리적으로 사용할 수 있습니다.

무엇 이며 사실, 당신의 다정한 이웃 컴파일러의 동작에 알맞은 문법을 얻는 것은 어려운 것입니다. 거의 모든 C ++ 컴파일러가 (대부분의) 원래 표준을 구현하지만 MS 컴파일러의 DLL 사양 등과 같은 다크 코너 확장도 많이있는 경향이 있습니다. 강력한 구문 분석 엔진을 사용하는 경우 구문 분석기 생성기의 한계에 맞추기 위해 문법을 구부리는 대신 현실과 일치하는 최종 문법입니다.

2012 년 11 월 편집 :이 답변을 작성한 이후로 ANSI, GNU 및 MS 변형 방언을 포함한 전체 C ++ 11을 처리하도록 C ++ 프런트 엔드를 개선했습니다. 많은 추가 사항이 있었지만 파싱 엔진을 변경할 필요가 없습니다. 방금 문법 규칙을 수정했습니다. 우리 의미 분석을 변경해야했습니다. C ++ 11은 의미 상 매우 복잡하며이 작업은 파서를 실행하기위한 노력을 엄청나게합니다.

2015 년 2 월 편집 : ... 이제 전체 C ++ 14를 처리합니다. ( 간단한 코드 비트의 GLR 구문 분석 및 C ++의 악명 높은 "가장 성가신 구문 분석"에 대해서는 C ++ 코드에서 사람이 읽을 수있는 AST 가져 오기를 참조하십시오 .)

2017 년 4 월 편집 : 이제 (초안) C ++ 17을 처리합니다.


6
포스트 스크립트 : 벤더가 실제로하는 일과 일치하는 문법을 얻는 것이 더 어렵 듯이, C ++ 11 매뉴얼에 대한 다른 벤더의 해석과 일치하도록 이름과 유형 확인을 얻는 것은 훨씬 더 어렵습니다. 당신이 그들을 찾을 수 있다면 다르게. 우리는 2013 년 8 월 현재 C ++ 11의 경우 거의 지나쳤지만 C 형식으로 훨씬 더 큰 (경험적으로, 더 혼란스러운) 표준을 만드는 데 지옥처럼 보이는 C ++위원회에서 약간 절망적입니다. ++ 1 년.
Ira Baxter

5
정말 알고 싶습니다 : 그 foo * bar;모호함을 어떻게 처리 합니까?
Martin

14
@Martin : 우리의 파서는 가지 방식으로 파싱하여 자식이 대체 파싱 인 특별한 "모호한 노드"를 포함하는 트리를 생성합니다. 아이들은 아이들을 최대한 나누기 때문에 나무 대신 DAG로 끝납니다. 구문 분석이 완료된 선언 된 모든 식별자의 유형을 계산하는 DAG (모르는 경우 "트리를 걸어보고 물건을 처리"하는 멋진 이름)를 통해 AGE (attribute grammar evaluator)를 실행합니다. ...
Ira Baxter

12
... 애매 모호한 자식은 모두 유형이 일치 할 수 없습니다. 현명하게 타이핑 할 수없는 애매한 아이를 발견 한 AGE는 단순히 그것을 삭제합니다. 남은 것은 잘 유형의 아이들입니다. 따라서 우리는 "foo bar "의 구문 분석을 결정했습니다 . 맞다. 이 트릭은 C ++ 11의 실제 방언을 위해 빌드 한 실제 문법에서 발견되는 모든 종류의 미친 모호성에 대해 작동하며 * 이름에 대한 의미 분석과 구문 분석을 완전히 분리합니다. 이 깔끔한 분리는 수행 할 엔지니어링 작업이 훨씬 적다는 것을 의미합니다 (디버그 할 엉킴 없음). 자세한 내용은 stackoverflow.com/a/1004737/120163 을 참조하십시오 .
Ira Baxter

3
@TimCas : 사실, 저는 너무 복잡해서 제대로 이해하기 어려운 언어 구문 (및 의미론)을 디자인하는 어리석은 어리 석음에 당신과 함께 있습니다. 저는 언어 디자인위원회가 구문을 디자인하여 더 간단한 구문 분석 기술이 작동하고 언어 의미를 명시 적으로 정의하고 일부 의미 분석 도구로 확인하기를 바랍니다. 아아, 세상은 그렇지 않은 것 같습니다. 그래서 저는 당신이 할 수있는 한 당신이 만들어야하는 것을 만들고, 어색함에도 불구하고 삶을 살아 간다는 견해를 가지고 있습니다.
Ira Baxter

31

Clang의 파서는 다른 오픈 소스 및 상용 C 및 C ++ 프런트 엔드와 마찬가지로 손으로 작성한 재귀 하강 파서입니다.

Clang은 여러 가지 이유로 재귀 하강 파서를 사용합니다.

  • 성능 : 손으로 쓴 파서를 사용하면 빠른 파서를 작성하여 필요에 따라 핫 경로를 최적화 할 수 있으며 항상 해당 성능을 제어 할 수 있습니다. 빠른 구문 분석기를 사용하면 "실제"구문 분석기가 일반적으로 사용되지 않는 다른 개발 도구 (예 : IDE의 구문 강조 표시 및 코드 완성)에서 Clang을 사용할 수 있습니다.
  • 진단 및 오류 복구 : 손으로 작성한 재귀 하강 파서로 모든 권한을 가지고 있기 때문에 일반적인 문제를 감지하고 훌륭한 진단 및 오류 복구를 제공하는 특수 사례를 쉽게 추가 할 수 있습니다 (예 : http : //clang.llvm 참조) . .org / features.html # expressivediags ) 자동 생성 된 파서를 사용하면 생성기의 기능으로 제한됩니다.
  • 단순성 : 재귀 하강 파서는 작성, 이해 및 디버그가 쉽습니다. 파싱 ​​전문가가되거나 파서를 확장 / 개선하기위한 새로운 도구 (오픈 소스 프로젝트에 특히 중요 함)를 배울 필요는 없지만 여전히 훌륭한 결과를 얻을 수 있습니다.

전반적으로 C ++ 컴파일러의 경우 그다지 중요하지 않습니다. C ++의 구문 분석 부분은 사소하지 않지만 여전히 쉬운 부분 중 하나이므로 단순하게 유지하는 것이 좋습니다. 시맨틱 분석 (특히 이름 조회, 초기화, 과부하 해결 및 템플릿 인스턴스화)은 구문 분석보다 훨씬 더 복잡합니다. 증명을 원한다면 Clang의 "Sema"구성 요소 (의미 분석 용)와 "Parse"구성 요소 (파싱 용)의 코드 배포 및 커밋을 확인하십시오.


4
예, 의미 론적 분석은 훨씬 더 어렵습니다. C ++ 11 문법을 구성하는 약 4000 줄의 문법 규칙과 위의 "의미 분석"Doub 목록에 대한 약 180,000 줄의 속성 문법 코드와 100,000 줄의 지원 코드가 있습니다. 잘못된 발로 시작하면 충분히 어렵지만 구문 분석은 실제로 문제가 아닙니다.
Ira Baxter

1
손으로 쓴 파서가 오류보고 / 복구에 반드시 더 나은지 확신 할 수 없습니다 . 사람들은 실제로 자동 파서 생성기에 의해 생성 된 파서를 향상시키는 것보다 그러한 파서에 더 많은 에너지를 투입 한 것으로 보입니다. 주제에 대한 꽤 좋은 연구가있는 것 같습니다. 이 특정 논문은 정말 내 눈을 사로 잡았습니다. MG Burke, 1983, LR 및 LL 구문 오류 진단 및 복구를위한 실용적인 방법, PhD 논문, 뉴욕 대학교 컴퓨터 과학과, archive.org/details/practicalmethodf00burk
Ira Baxter

1
...이 사고 훈련을 계속하십시오 : 더 나은 진단을 위해 특별한 경우를 확인하기 위해 손으로 만든 파서를 수정 / 확장 / 사용자 정의하려는 경우 기계적으로 생성 된 파서의 더 나은 진단에 기꺼이 동일한 투자를 할 의향이 있어야합니다. 수동으로 인코딩 할 수있는 특수 구문 분석의 경우 기계적 검사도 코딩 할 수 있습니다. 식욕을 돋우는 정도까지는 게으르지 만 기계적으로 생성 된 파서 IMHO에 대한 기소는 아닙니다.
Ira Baxter

8

gcc의 파서는 손으로 작성되었습니다. . 나는 clang에 대해서도 동일하다고 생각합니다. 이는 아마도 몇 가지 이유 때문일 것입니다.

  • 성능 : 특정 작업에 대해 직접 최적화 한 것이 거의 항상 일반적인 솔루션보다 더 나은 성능을 발휘합니다. 추상화에는 일반적으로 성능 저하가 있습니다.
  • 타이밍 : 적어도 GCC의 경우 GCC는 많은 무료 개발자 도구보다 앞서 있습니다 (1987 년에 출시됨). 당시에는 무료 버전의 yacc 등이 없었습니다. FSF 사람들의 우선 순위가 될 것이라고 생각합니다.

이것은 아마도 "여기에서 발명되지 않음"증후군의 경우가 아니라 "우리가 필요로하는 것에 특별히 최적화 된 것이 없기 때문에 우리가 직접 작성했습니다"라는 문구에 더 가깝습니다.


15
1987 년에 무료 버전의 yacc가 없습니까? 나는 yacc가 70 년대에 Unix에서 처음 제공되었을 때 무료 버전이 있다고 생각합니다. 그리고 IIRC는 (다른 포스터 같은 것), GCC는 사용 YACC 기반 파서를 가지고. 변경 한 이유는 더 나은 오류보고를 얻기위한 것이라고 들었습니다.
Ira Baxter

7
손으로 쓴 파서에서 좋은 오류 메시지를 생성하는 것이 종종 더 쉽다는 점을 덧붙이고 싶습니다.
디트리히 엡

1
타이밍에 대한 귀하의 요점은 정확하지 않습니다. GCC는 YACC 기반 파서를 사용했지만 나중에 손으로 쓴 재귀 하강 파서로 대체되었습니다.
토미 앤더슨

7

이상한 대답!

C / C ++ 문법은 문맥이 자유롭지 않습니다. Foo * 막대 때문에 문맥에 민감합니다. 모호. Foo가 유형인지 아닌지 알기 위해 typedef 목록을 작성해야합니다.

Ira Baxter : 당신의 GLR에 대한 요점이 보이지 않습니다. 모호성을 포함하는 구문 분석 트리를 만드는 이유. 구문 분석은 모호성을 해결하고 구문 트리를 구축하는 것을 의미합니다. 두 번째 단계에서 이러한 모호성을 해결하므로 덜 추하지 않습니다. 나를 위해 그것은 훨씬 더 추합니다 ...

Yacc는 LR (1) 파서 생성기 (또는 LALR (1))이지만 상황에 맞게 쉽게 수정할 수 있습니다. 그리고 그 안에 못생긴 것은 없습니다. Yacc / Bison은 C 언어 구문 분석을 돕기 위해 만들어 졌으므로 아마도 C 구문 분석기를 생성하는 가장 못된 도구는 아닐 것입니다.

GCC 3.x까지 C 구문 분석기는 yacc / bison에 의해 생성되며 구문 분석 중에 typedefs 테이블이 작성됩니다. "in parse"typedefs 테이블 빌드를 사용하면 C 문법이 로컬 컨텍스트에서 자유 로워지고 더욱 "로컬 LR (1)"이됩니다.

이제 Gcc 4.x에서는 재귀 하강 파서입니다. Gcc 3.x에서와 똑같은 파서이며 여전히 LR (1)이며 동일한 문법 규칙을 가지고 있습니다. 차이점은 yacc 파서가 수작업으로 다시 작성되었으며 시프트 / 리 듀스가 이제 호출 스택에 숨겨져 있으며 gcc 3.x yacc 에서처럼 "state454 : if (nextsym == '(') goto state398"이 없다는 것입니다. parser를 사용하면 패치, 오류 처리 및 더 좋은 메시지를 인쇄하고 구문 분석 중에 다음 컴파일 단계를 수행하는 것이 더 쉽습니다. gcc noob에 대한 "읽기 쉬운"코드가 훨씬 적습니다.

왜 그들은 yacc에서 재귀 하강으로 전환 했습니까? C ++를 구문 분석하기 위해 yacc를 피해야하고 GCC가 다국어 컴파일러를 꿈꾸기 때문에 컴파일 할 수있는 다른 언어간에 최대 코드를 공유하기 때문입니다. 이것이 C ++와 C 파서가 같은 방식으로 작성된 이유입니다.

C ++는 C와 같이 "로컬"LR (1)이 아니고 LR (k)도 아니기 때문에 C보다 파싱하기가 더 어렵습니다. 봐가에 func<4 > 2>있는 즉, 4> 2 인스턴스화 템플릿 함수 func<4 > 2> 로 읽을 수있다 func<1>. 이것은 확실히 LR (1)이 아닙니다. 이제 func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>. 이것은 재귀 하강이 몇 가지 추가 함수 호출의 대가로 모호성을 쉽게 해결할 수있는 곳입니다 (parse_template_parameter는 모호한 파서 함수입니다. parse_template_parameter (17tokens)가 실패하면 parse_template_parameter (15tokens), parse_template_parameter (13tokens) ...까지 다시 시도하십시오. 효과가있다).

yacc / bison 재귀 하위 문법에 추가 할 수없는 이유를 모르겠습니다. 아마도 이것이 gcc / GNU 파서 개발의 다음 단계가 될까요?


9
"저에게는 훨씬 더 추합니다." 제가 말할 수있는 것은 GLR 및 지연 모호성 해결을 사용하는 프로덕션 품질 파서의 엔지니어링이 정말 작은 팀에서 실용적이라는 것입니다. 내가 본 다른 모든 솔루션은 LR, 재귀 하강과 함께 작동하는 데 필요한 백 플립과 해킹에 대해 공공 장소에서 수년간 치아를 갈고 닦는 것과 관련이 있습니다. 다른 멋진 파싱 기술을 많이 가정 할 수 있지만 내가 말할 수있는 한,이 시점에서 그것은 단지 더 많은 치아를 갈고 닦는 것입니다. 아이디어는 저렴합니다. 처형은 귀중합니다.
Ira Baxter


@Fizz : 복잡한 과학 프로그래밍 언어 인 Fortress 구문 분석에 대한 흥미로운 논문입니다. 그들은 몇 가지 주목할 점을 말했습니다 : a) 고전적인 파서 생성기 (LL (k), LALR (1))는 어려운 문법을 처리 할 수 ​​없습니다, b) GLR을 시도했고, 규모에 문제가 있었지만 개발자들은 경험이 없었기 때문에 그렇지 않았습니다. complete [그것은 GLR의 잘못이 아닙니다] 그리고 c) 그들은 역 추적 (트랜잭션) Packrat 파서를 사용했고 더 나은 오류 메시지를 생성하는 작업을 포함하여 많은 노력을 기울였습니다. "{| x || x ← mySet, 3 | x}"구문 분석의 예와 관련하여 GLR이 잘 수행하고 공백이 필요하지 않다고 생각합니다.
Ira Baxter

0

GCC와 LLVM-Clang은 기계 생성 Bison-Flex 기반 상향식 파싱이 아닌 손으로 쓴 재귀 하강 파서를 사용하는 것 같습니다.

특히 들소는 모호하게 파싱하고 나중에 두 번째 패스를 수행하지 않고는 문법을 다룰 수 없다고 생각합니다.

Haskell의 Happy가 C 구문의 특정 문제를 해결할 수있는 모나 딕 (즉, 상태 종속) 파서를 허용한다는 것을 알고 있지만 사용자가 제공하는 상태 모나드를 허용하는 C 파서 생성기가 없다는 것을 알고 있습니다.

이론적으로 오류 복구는 손으로 쓴 파서를 선호하지만 GCC / Clang에 대한 나의 경험은 오류 메시지가 특히 좋지 않다는 것입니다.

성능에 관해서는 일부 주장이 입증되지 않은 것 같습니다. 파서 생성기를 사용하여 큰 상태 머신을 생성하면 무언가가 발생해야하며 O(n)파싱이 많은 도구에서 병목 현상이 아닌지 의심합니다.


3
이 질문에는 이미 매우 높은 수준의 답변이 있습니다. 무엇을 추가하려고합니까?
tod
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.