8 비트 임베디드 시스템에서 사용할 수있는 flex / bison에 대한 대안이 있습니까?


83

avr-gcc 툴체인을 사용하여 C의 AVR 마이크로 컨트롤러에 대한 연습으로 언어와 같은 간단한 BASIC에 대한 작은 인터프리터를 작성하고 있습니다. 그러나 어휘 분석기와 파서를 작성하는 데 도움이 될 수있는 오픈 소스 도구가 있는지 궁금합니다.

내 Linux 상자에서 실행되도록 작성하려면 flex / bison을 사용할 수 있습니다. 이제 8 비트 플랫폼으로 제한 했으므로 모든 작업을 수작업으로해야합니까?


1
사용하려는 특정 칩이 있습니까? ROM / RAM이 얼마나 있습니까?
Steve S

@mre의 링크로 업데이트하십시오. embed.com이 URL을 삭제했습니다. ( embedded.com/design/prototyping-and-development/4024523/… )
pgvoorhees

커널이 번쩍로 보인다에만 스택 laguages (규정 및 공동), 2킬로바이트 RAM에 기회가
제이 섹 Cz에

답변:


59

ATmega328p를 대상으로하는 간단한 명령 언어에 대한 파서를 구현했습니다 . 이 칩에는 32k ROM과 2k RAM 만 있습니다. RAM은 확실히 더 중요한 제한 사항입니다. 아직 특정 칩에 묶여 있지 않은 경우 가능한 한 많은 RAM이있는 것을 선택하십시오. 이것은 당신의 삶을 훨씬 더 쉽게 만들 것입니다.

처음에는 flex / bison 사용을 고려했습니다. 나는 두 가지 주요 이유로이 옵션에 반대하기로 결정했습니다.

  • 기본적으로 Flex & Bison은 사용할 수 없거나 avr-libc에서 동일하게 작동하지 않는 일부 표준 라이브러리 기능 (특히 I / O 용)에 의존합니다. 지원되는 해결 방법이 있다고 확신하지만 이것은 고려해야 할 추가 노력입니다.
  • AVR에는 하버드 아키텍처가 있습니다. C는이를 설명하도록 설계되지 않았기 때문에 상수 변수도 기본적으로 RAM에로드됩니다 . 플래시EEPROM에 데이터를 저장하고 액세스하려면 특수 매크로 / 기능을 사용해야합니다 . Flex & Bison은 상대적으로 큰 룩업 테이블을 생성 하며 이는 RAM을 매우 빠르게 소모합니다. 내가 착각하지 않는 한 (가능한 경우) 특수 플래시 및 EEPROM 인터페이스를 이용하려면 출력 소스를 편집해야합니다.

Flex & Bison을 거부 한 후 다른 생성기 도구를 찾았습니다. 제가 고려한 몇 가지 사항은 다음과 같습니다.

Wikipedia의 비교를 살펴볼 수도 있습니다 .

궁극적으로 나는 어휘 분석기와 파서를 직접 코딩했습니다.

파싱을 위해 재귀 하강 파서를 사용했습니다. 내 생각 아이라 박스터는 이미이 주제를 다루는의 적절한 일을하고 있으며, 튜토리얼의 많은 온라인이 있습니다.

내 어휘 분석기를 위해 모든 터미널에 대한 정규식을 작성하고 동등한 상태 머신을 다이어그램으로 작성하고 상태 goto간 점프를 위해 's를 사용하여 하나의 거대한 함수로 구현했습니다 . 이것은 지루했지만 결과는 훌륭했습니다. 여담으로, goto상태 머신을 구현하기위한 훌륭한 도구입니다 - 당신의 모든 상태가 바로 옆에 관련 코드에 대한 명확한 라벨을 가질 수 있습니다, 거기에 어떤 함수 호출 또는 상태 변수의 오버 헤드가 없으며, 그것에 대해 최대한 빨리 얻을 수 있습니다. C는 실제로 정적 상태 머신을 구축하기위한 더 나은 구조를 가지고 있지 않습니다.

생각할 것 : 렉서는 파서의 전문 화일뿐입니다. 가장 큰 차이점은 일반 문법은 어휘 분석에 일반적으로 충분하지만 대부분의 프로그래밍 언어에는 (대부분) 문맥없는 문법이 있다는 것입니다. 따라서 렉서를 재귀 하강 파서로 구현하거나 파서 생성기를 사용하여 렉서를 작성하는 것을 막을 수는 없습니다. 일반적으로 더 전문화 된 도구를 사용하는 것만 큼 편리하지 않습니다.


225

파서를 코딩하는 쉬운 방법을 원하거나 공간이 부족한 경우 재귀 하강 파서를 직접 코딩해야합니다. 이들은 본질적으로 LL (1) 파서입니다. 이것은 Basic처럼 "단순"한 언어에 특히 효과적입니다. (나는 70 년대에이 중 몇 가지를했다!). 좋은 소식은 라이브러리 코드가 포함되어 있지 않다는 것입니다. 당신이 쓰는 그대로.

이미 문법이 있다면 코딩하기가 매우 쉽습니다. 먼저 왼쪽 재귀 규칙을 제거해야합니다 (예 : X = XY). 이것은 일반적으로 매우 쉽기 때문에 연습으로 남겨 둡니다. (목록 형성 규칙에 대해이 작업을 수행 할 필요는 없습니다. 아래 설명 참조).

다음 형식의 BNF 규칙이있는 경우 :

 X = A B C ;

"해당 구문 구조를 보았다"라는 부울을 반환하는 규칙 (X, A, B, C)의 각 항목에 대한 서브 루틴을 만듭니다. X의 경우 코드 :

subroutine X()
     if ~(A()) return false;
     if ~(B()) { error(); return false; }
     if ~(C()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end X;

A, B, C도 마찬가지입니다.

토큰이 터미널 인 경우 터미널을 구성하는 문자열의 입력 스트림을 확인하는 코드를 작성하십시오. 예를 들어 숫자의 경우 입력 스트림에 숫자가 포함되어 있는지 확인하고 입력 스트림 커서를 숫자를지나 앞으로 이동합니다. 버퍼 스캔 포인터를 전진 시키거나 전진시키지 않음으로써 버퍼에서 파싱하는 경우 특히 쉽습니다 (BASIC의 경우 한 번에 한 줄씩 가져 오는 경향이 있음). 이 코드는 본질적으로 파서의 렉서 부분입니다.

BNF 규칙이 재귀 적이라면 ... 걱정하지 마십시오. 재귀 호출을 코딩하십시오. 이것은 다음과 같은 문법 규칙을 처리합니다.

T  =  '('  T  ')' ;

다음과 같이 코딩 할 수 있습니다.

subroutine T()
     if ~(left_paren()) return false;
     if ~(T()) { error(); return false; }
     if ~(right_paren()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end T;

대안이있는 BNF 규칙이있는 경우 :

 P = Q | R ;

그런 다음 대체 선택 사항으로 P를 코딩합니다.

subroutine P()
    if ~(Q())
        {if ~(R()) return false;
         return true;
        }
    return true;
end P;

때로는 목록 형성 규칙을 접하게됩니다. 이들은 재귀 적으로 남겨지는 경향이 있으며이 경우는 쉽게 처리됩니다. 기본 아이디어는 재귀보다는 반복을 사용하는 것이며, 이는 "명백한"방식으로이 작업을 수행하는 무한 재귀를 피하는 것입니다. 예:

L  =  A |  L A ;

반복을 사용하여 다음과 같이 코딩 할 수 있습니다.

subroutine L()
    if ~(A()) then return false;
    while (A()) do { /* loop */ }
    return true;
end L;

이런 식으로 하루나 이틀 만에 수백 개의 문법 규칙을 코딩 할 수 있습니다. 채워야 할 세부 사항이 더 있지만 여기의 기본 사항은 충분합니다.

공간 이 정말 부족한 경우 이러한 아이디어를 구현하는 가상 머신을 구축 할 수 있습니다. 그것이 제가 70 년대에 8K 16 비트 단어가 당신이 얻을 수 있었던 일이었을 때 한 일입니다.


이것을 수동으로 코딩하고 싶지 않다면 본질적으로 동일한 것을 생성하는 메타 컴파일러 ( Meta II )로 자동화 할 수 있습니다 . 이것들은 엄청난 기술적 재미이며 큰 문법의 경우에도 실제로 모든 작업을 수행합니다.

2014 년 8 월 :

"파서로 AST를 빌드하는 방법"에 대한 많은 요청을받습니다. 본질적 으로이 답변을 자세히 설명하는 이에 대한 자세한 내용은 다른 SO 답변 https://stackoverflow.com/a/25106688/120163을 참조하십시오.

2015 년 7 월 :

간단한 표현 평가기를 작성하려는 사람들이 많이 있습니다. 위의 "AST 빌더"링크가 제안하는 것과 동일한 종류의 작업을 수행하여이를 수행 할 수 있습니다. 트리 노드를 구축하는 대신 산술을 수행하십시오. 다음은 이런 식으로 수행 된 표현식 평가 기 입니다.


2
예, 간단한 언어에 대해 재귀 하강 파서를 직접 굴리는 것은 그리 어렵지 않습니다. 가능한 경우 테일 호출을 최적화하는 것을 잊지 마십시오. RAM이 2 킬로바이트 밖에 안되는 경우 스택 공간이 매우 중요합니다.
Steve S

2
모두 : 예, 테일 콜 최적화를 수행 할 수 있습니다. 파싱 ​​된 코드의 중첩이 정말로 깊어 질 것으로 예상하지 않는 한 이것은 중요하지 않습니다. BASIC 코드 라인의 경우 10 개 이상의 parathenses 깊이가있는 표현식을 찾기가 매우 어렵고 항상 깊이 제한 카운트를 입력하여 부팅 할 수 있습니다. 임베디드 시스템은 스택 공간이 적은 경향이 있으므로 적어도 여기서 선택하는 것에주의를 기울이십시오.
Ira Baxter

2
@Mark : 그리고 그것은 2012 년이 될 수도 있지만, 제가 참조하는 1965 년 기술 문서는 당시와 마찬가지로 지금도 좋고, 특히 당신이 그것을 모른다면 꽤 좋습니다.
Ira Baxter

2
@ 마크, 아, 알았어, 고마워! 날짜가 이상하게 고정 된 것 같습니다. 감사합니다, 타임로드.
Ira Baxter

2
빈 문자열을 어떻게 처리 할 수 ​​있습니까?
Dante

11

Linux에서 기본 gcc와 함께 flex / bison을 사용하여 포함 된 대상에 대한 AVR gcc와 교차 컴파일 할 코드를 생성 할 수 있습니다.


2

GCC는 다양한 플랫폼으로 크로스 컴파일 할 수 있지만 컴파일러를 실행중인 플랫폼에서 flex 및 bison을 실행합니다. 컴파일러가 빌드하는 C 코드를 뱉어냅니다. 결과 실행 파일이 실제로 얼마나 큰지 테스트하십시오. libfl.a타겟에 크로스 컴파일 해야하는 런타임 라이브러리 등이 있습니다.


나는 아직도 그 도서관의 크기를 조사해야하고 그것이 내가 처음에 질문 한 이유이다. 저는 특별히 작은 MCU를 대상으로하는 무언가를 원합니다.
Johan

-1

Boost :: Spirit을 사용해보십시오. C ++로 완전히 빠르고 깔끔한 파서를 만들 수있는 헤더 전용 라이브러리입니다. C ++의 오버로드 된 연산자는 특수 문법 파일 대신 사용됩니다.


답변의 문제는 8 비트 플랫폼 제약을 인식하지 못한다는 것입니다. 부스트와 그와 같은 작은 플랫폼을 동시에 지원하는 도구 체인을 얻기가 어려울 것입니다.
Waslap

-5

바퀴를 다시 발명하는 대신 LUA : www.lua.org를 살펴 보십시오 . 다른 소프트웨어에 내장되고 내장 시스템과 같은 소규모 시스템에서 사용되는 해석 언어입니다. 내장 된 절차 적 구문 구문 분석 트리, 제어 로직, 수학 및 변수 지원-수천 명의 다른 사람들이 이미 디버깅하고 사용한 것을 재발 명 할 필요가 없습니다. 그리고 확장 가능합니다. 즉, 자체 C 함수를 추가하여 문법에 추가 할 수 있습니다.


2
Lua가 작을 지 모르지만 여전히 너무 클 것이라고 확신합니다.
icktoofay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.