컨텍스트가없는 임의의 문법, 주로 짧은 스 니펫 구문 분석


20

사용자 정의 도메인 특정 언어를 구문 분석하고 싶습니다. 이 언어는 일반적으로 수학 표기법에 가깝습니다 (자연어를 구문 분석하지 않습니다). 사용자는 다음과 같이 BNF 표기법으로 DSL을 정의합니다.

expr ::= LiteralInteger
       | ( expr )
       | expr + expr
       | expr * expr

like like 입력은 1 + ( 2 * 3 )승인되어야하고, like like 입력 1 +은 올바르지 않은 것으로 거부되어야하며, like like 입력 1 + 2 * 3은 모호한 것으로 거부되어야합니다.

여기서 가장 어려운 점은 사용자에게 친숙한 방식으로 모호한 문법에 대처하는 것입니다. 문법을 모호하지 않게 제한하는 것은 선택 사항이 아닙니다. 이것이 언어의 방식입니다. 아이디어는 모호성을 피할 필요가 없을 때 괄호를 생략하는 것을 선호합니다. 표현이 모호하지 않은 한 구문 분석해야하며, 그렇지 않은 경우 거부해야합니다.

내 파서는 컨텍스트가없는 문법, 모호한 문법에서 작동해야하며 모호하지 않은 모든 입력을 수용해야합니다. 허용되는 모든 입력에 대해 구문 분석 트리가 필요합니다. 유효하지 않거나 모호한 입력의 경우 이상적으로는 좋은 오류 메시지를 원하지만 시작하려면 먼저 얻을 수있는 것을 취할 것입니다.

일반적으로 비교적 짧은 입력에서 파서를 호출하고 때로는 더 긴 입력을 사용합니다. 따라서 점진적으로 빠른 알고리즘이 최선의 선택이 아닐 수 있습니다. 20 자 미만의 기호, 20 자에서 50 자 사이의 19 %, 1 %의 드문 입력 사이에 약 80 %의 입력 분포를 최적화하고 싶습니다. 유효하지 않은 입력 속도는 큰 문제가되지 않습니다. 또한, 나는 1000에서 100000 개의 입력마다 DSL의 수정을 기대합니다. 몇 분이 아니라 몇 초 동안 내 문법을 전처리 할 수 ​​있습니다.

일반적인 입력 크기를 고려할 때 어떤 파싱 알고리즘을 조사해야합니까? 오류보고가 내 선택에 영향을 주어야합니까, 모호하지 않은 입력을 구문 분석하는 데 집중해야하고 오류 피드백을 제공하기 위해 완전히 분리 된 느린 파서를 실행해야합니까?

(필요한 프로젝트 (이전의 프로젝트)에서 CYK를 사용했습니다 . 이는 구현하기가 어렵지 않고 입력 크기에 적절하게 작동했지만 매우 멋진 오류는 발생하지 않았습니다.)


특히 좋은 오류 보고서는 달성하기 어려운 것 같습니다. 모호한 문법의 경우 허용되는 입력으로 이어지는 하나 이상의 로컬 변경이있을 수 있습니다.
Raphael

나는 방금 아래에 대답했다. 이미 잘 받아 들여진 오래된 질문의 수정에 대답하는 것은 약간 어색합니다. 분명히 비슷한 방식으로 대답하지는 않지만 사용자는 동일한 질문에 대답하는 것처럼 두 가지 답변을 모두 읽습니다.
babou

사용자가 글을 쓰는 경우 실제로 모호한 입력에 대한 오류 메시지가 표시됩니까 x+y+z?
babou

@babou 질문을 변경하지 않았으며 의견에 요청한 설명 만 추가했습니다 (이제 삭제됨). 여기에 주어진 작은 문법에 대해에 대한 연관성을 지정하지 않았 +으므로 x+y+z실제로 모호합니다.
Gilles 'SO- 악마 그만해'

글쎄, 괄호 안에 있더라도 마지막 문장입니다. 당신은 말할 것 같습니다 : 나는 마침내 CYK와 함께했지만 더 이상 어떤 이유로 적합하지 않습니다. 그리고 나는 정확한 이유가있을 수 있습니다 ... 당신은 무엇을 궁금해 이제 더 답변을 제공 할 경우, 문제의 종류와 사용하는 솔루션으로 가장 경험이있는 사람, 그래서 하나는 더 많은 정보를 기대하는 것이다.
babou

답변:


19

아마도 귀하의 요구에 이상적인 알고리즘은 Generalized LL parsing 또는 GLL입니다. 이것은 매우 새로운 알고리즘입니다 (이 논문은 2010 년에 출판되었습니다). 어떤 방식으로, GSS (Graph Structured Stack)로 보강되고 LL (1) lookahead를 사용하는 Earley 알고리즘입니다.

이 알고리즘은 문법이 LL (1)이 아닌 경우 문법을 거부하지 않는다는 점을 제외하면 일반 LL (1)과 매우 유사합니다. 가능한 모든 LL (1) 구문 분석 만 시도합니다. 구문 분석의 모든 지점에 대해 유 방향 그래프를 사용합니다. 즉, 이전에 처리 된 구문 분석 상태가 발생하면이 두 정점을 단순히 병합합니다. LL과 달리 왼쪽 재귀 문법에도 적합합니다. 내부 작업에 대한 자세한 내용은 종이를 읽으십시오 (라벨 수프에는 인내가 필요하지만 읽기 쉬운 종이입니다).

이 알고리즘에는 다른 일반적인 구문 분석 알고리즘 (필자가 알고있는)에 비해 사용자의 요구와 관련하여 여러 가지 분명한 장점이 있습니다. 첫째, 구현이 매우 쉽습니다. Earley 만 구현하기가 더 쉽다고 생각합니다. 둘째, 성능이 매우 우수합니다. 실제로는 LL (1) 인 문법에서 LL (1)만큼 빠릅니다. 셋째, 구문 분석을 복구하는 것은 매우 쉽고, 하나 이상의 구문 분석이 있는지 확인하는 것도 가능합니다.

GLL의 주요 장점은 LL (1)을 기반으로하기 때문에 문법을 설계 할 때뿐만 아니라 입력을 구문 분석 할 때 구현할 때 이해하고 디버그하기가 매우 쉽다는 것입니다. 또한 오류 처리가 더 쉬워집니다. 구문 분석이 가능한 위치와 해당 구문이 어떻게 진행되었는지 정확하게 알 수 있습니다. 오류 지점에서 구문 분석이 가능한 마지막 3 지점을 쉽게 구문 분석 할 수 있습니다. 대신 오류에서 복구를 시도하고 가장 멀리있는 구문 분석이 해당 구문 분석에 대해 '완료'로 표시 한 프로덕션을 표시하고 그 이후 구문 분석이 계속 될 수 있는지 확인하십시오 (예 : 누군가 괄호를 잊어 버림). 예를 들어 가장 멀리있는 5 개의 파싱을 위해 그렇게 할 수도 있습니다.

이 알고리즘의 유일한 단점은 새로운 알고리즘이라는 것입니다. 즉, 잘 구현 된 구현을 쉽게 사용할 수 없습니다. 이것은 당신에게 문제가되지 않을 수 있습니다-나는 알고리즘을 직접 구현했으며 매우 쉽습니다.


새로운 것을 배우게되어 반갑습니다. 몇 년 전에 언젠가 부활하고 싶은 프로젝트에서 이것을 필요로 할 때 CYK를 사용했습니다. GLL은 모호한 입력을 어떻게 처리합니까? 이 기사는 이것을 논의하지 않는 것 같지만, 나는 단지 그것을 감추었습니다.
Gilles 'SO- 악의를 그만두십시오

@Gilles : 그래프 구조화 된 스택을 빌드하고 GLR의 작동 방식과 유사하게 모든 (잠재적으로 지수가 많은) 구문 분석이이 그래프에 간결하게 표시됩니다. 내가 올바르게 기억한다면 cstheory.stackexchange.com/questions/7374/…에 언급 된 논문 이 이것을 다루고 있습니다.
Alex ten Brink

@Gilles이 2010 구문 분석기는 문법에서 직접 프로그래밍해야하는 것으로 보입니다. 여러 언어가 있거나 언어를 자주 수정하는 경우에는 적절하지 않습니다. 선택한 전략 (LL, LR 또는 기타)에 따라 일반 파서의 문법에서 자동 생성하고 모든 파싱의 포리스트를 생성하는 기술은 약 40 년 동안 알려져 왔습니다. 그러나 구문 분석을 나타내는 그래프의 복잡성과 구성과 관련하여 숨겨진 문제가 있습니다. 구문 분석 수가 지수보다 무한 할 수 있습니다 : 무한. 오류 복구는보다 체계적이고 파서 독립적 인 기술을 사용할 수 있습니다.
babou

GLL은 ANTLR에있는 LL (*)과 어떤 관련이 있습니까?
Raphael

6

내 회사 (Semantic Designs)는 GLR 파서 를 사용 하여 DMS Software Reengineering Toolkit으로 도메인 특정 언어와 "고전"프로그래밍 언어를 모두 구문 분석 할 때 OP가 제안하는 바를 정확하게 수행했습니다. 이는 대규모 프로그램 재구성 / 역 엔지니어링 / 앞으로 코드 생성에 사용되는 소스-소스 프로그램 변환을 지원합니다 . 여기에는 구문 오류에 대한 실질적인 수리가 포함됩니다. GLR을 기초로 사용하고 다른 변경 사항 (의미 적 술어, 토큰 입력이 아닌 토큰 세트 입력 등)을 사용하여 약 40 개 언어에 대한 파서를 작성했습니다.

전체 언어 인스턴스를 구문 분석하는 기능만큼이나 GLR은 소스 간 재 작성 규칙을 구문 분석하는 데 매우 유용한 것으로 입증되었습니다 . 이들은 전체 프로그램보다 컨텍스트가 훨씬 적은 프로그램 조각이므로 일반적으로 더 모호합니다. 우리는 규칙을 파싱하는 동안 / 이후의 모호성을 해결하기 위해 특수 주석 (예 : 문구가 특정 문법 비 터미널에 해당한다고 주장)을 사용합니다. GLR 구문 분석 기계와 그 주변의 도구를 구성하여 언어에 대한 구문 분석기가 있으면 "무료"에 대한 규칙을 다시 작성하는 구문 분석기를 얻습니다. DMS 엔진에는 내장 된 재 작성 규칙 적용자가 있으며 이러한 규칙을 적용하여 원하는 코드 변경을 수행 할 수 있습니다.

아마도 가장 놀라운 결과는 컨텍스트가없는 문법을 기본으로 사용하여 모든 모호함에도 불구하고 C ++ 14 전체구문 분석 하는 능력 일 것 입니다. 모든 고전적인 C ++ 컴파일러 (GCC, Clang)는이 작업을 수행하고 손으로 쓴 파서를 사용하는 기능을 포기했습니다 (IMHO는 유지 관리를 훨씬 어렵게하지만 내 문제는 아닙니다). 우리는이 기계를 사용 하여 대규모 C ++ 시스템의 아키텍처를 크게 변경했습니다.

성능면에서 GLR 파서는 초당 수만 줄이 적당히 빠릅니다. 이것은 최신 기술보다 훨씬 낮지 만이를 최적화하려는 시도는 없었으며 일부 병목 현상은 문자 스트림 처리 (전체 유니 코드)에 있습니다. 이러한 파서를 작성하기 위해 LR (1) 파서 생성기와 매우 유사한 것을 사용하여 컨텍스트 프리 문법을 사전 처리합니다. 이것은 보통 C ++ 크기의 큰 문법으로 10 초 안에 최신 워크 스테이션에서 실행됩니다. 놀랍게도 현대 COBOL 및 C ++와 같은 매우 복잡한 언어의 경우 어휘 분석기 생성에는 약 1 분이 소요됩니다. 유니 코드를 통해 정의 된 일부 DFA는 매우 털이 있습니다. 방금 루비 (정규적인 정규 표현식에 대한 완전한 하위 문법 포함)를 손가락 연습으로 수행했습니다. DMS는 약 8 초 안에 어휘 분석기와 문법을 함께 처리 할 수 ​​있습니다.


@Raphael : "대규모 변경"링크는 C ++ 아키텍처 리엔지니어링에 대한 몇 가지, DMS 엔진 자체 (기존이지만 기본에 대해 잘 설명되어 있음)에 대한 논문 및 디자인 캡처 및 재사용이라는 이국적인 주제는 DMS의 원래 동기였습니다 (아직도 달성하지 못했지만 DMS는 어쨌든 매우 유용했습니다).
Ira Baxter

1

모호한 문법에 따라 모호한 문장을 구문 분석 할 수있는 일반적인 문맥없는 구문 분석기가 많이 있습니다. 동적 프로그래밍 또는 차트 파서 등 다양한 이름으로 제공됩니다. 가장 잘 알려진 것 중 가장 간단한 것은 아마도 CYK 파서 일 것입니다. 다중 구문 분석을 처리해야하기 때문에 일반성이 필요하며, 모호성을 처리하는지 여부를 끝까지 알 수 없습니다.

당신이 말하는 것에서, 나는 CYK가 그렇게 나쁜 선택이 아니라고 생각합니다. 예측 성 (LL 또는 LR)을 추가하여 얻을 수있는 이점은 많지 않으며, 특히 LR의 경우에는 구별하기보다는 병합해야하는 계산을 구별하여 실제로 비용이 발생할 수 있습니다. 또한 생성 된 구문 분석 포리스트의 크기에 해당하는 비용이있을 수 있습니다 (모호성 오류에 영향을 줄 수 있음). 실제로 더 정교한 알고리즘의 적절성을 공식적으로 비교하는 방법을 모르겠지만 CYK가 우수한 계산 공유를 제공한다는 것을 알고 있습니다.

이제 모호한 문법에 대한 일반 CF 파서에 대한 많은 문헌이 모호하지 않은 입력 만 받아 들여야한다고 생각하지 않습니다. 기술 문서 나 프로그래밍 언어의 경우에도 다른 수단 (예 : ADA 표현의 모호성)으로 해결할 수있는 한 구문 적 모호성이 허용되기 때문에 아마도 어떤 것도 보지 못합니다.

나는 실제로 당신이 가진 것에 충실하기보다는 왜 알고리즘을 바꾸고 싶어하는지 궁금합니다. 어떤 변화가 가장 도움이 될 수 있는지 이해하는 데 도움이 될 수 있습니다. 속도 문제입니까, 구문 분석의 표현입니까, 아니면 오류 감지 및 복구입니까?

여러 구문 분석을 나타내는 가장 좋은 방법은 공유 포리스트를 사용하는 것입니다. 공유 포리스트는 입력 내용 만 생성하지만 DSL 문법과 정확히 동일한 구문 분석 트리를 사용하는 컨텍스트가없는 문법입니다. 따라서 이해하고 처리하기가 매우 쉽습니다. 자세한 내용 은 언어 사이트 에서 제공 한이 답변을 참조 하십시오. 구문 분석 포리스트를 얻는 데 관심이 없지만 구문 분석 포리스트를 올바르게 표현하면 모호한 문제가 무엇인지에 대한 더 나은 메시지를 제공하는 데 도움이 될 수 있습니다. 또한 원하는 경우 모호성이 중요하지 않은 경우 (연관성)를 결정하는 데 도움이 될 수 있습니다.

DSL 문법의 처리 시간 제약 조건에 대해서는 언급했지만 크기에 대해서는 힌트를주지 않습니다 (그렇지 않은 수치로 대답 할 수있는 것은 아닙니다).

이러한 일반적인 CF 알고리즘에 간단한 방법으로 일부 오류 처리를 통합 할 수 있습니다. 그러나 어떤 종류의 오류 처리가 더 긍정적이라고 생각해야하는지 이해해야합니다. 몇 가지 예가 있습니까?

나는 실제로 당신의 동기와 제약이 무엇인지 이해하지 못하기 때문에 좀 더 말하기가 쉽지 않습니다. 당신이 말한 것에 기초하여, 나는 CYK를 고수 할 것입니다 (그리고 나는 다른 알고리즘과 그 속성 중 일부를 알고 있습니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.