그것은 큰 주제이지만, "책을 읽고, 아이를 읽어라"라는 말로 당신을 털어 놓는 대신 머리를 감싸는 데 도움이되는 포인터를 기꺼이 줄 것이다.
대부분의 컴파일러 및 / 또는 인터프리터는 다음과 같이 작동합니다.
토큰 화 : 코드 텍스트를 스캔하여 토큰 목록으로 나눕니다.
공백으로 문자열을 분리 할 수 없기 때문에이 단계는 까다로울 수 있습니다 if (bar) foo += "a string";
. WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR 등 8 개의 토큰 목록 임을 인식해야 합니다. 보시다시피 공백으로 소스 코드를 나누는 것만으로는 작동하지 않으므로 각 문자를 시퀀스로 읽어야하므로 영숫자 문자가 발생하면 영숫자가 아닌 문자를 입력 할 때까지 문자를 계속 읽습니다. 읽기만하면 나중에 더 분류 할 단어입니다. 토크 나이저가 지 여부와 상관없이 토큰 가 얼마나 세밀한 지 스스로 결정할 수 있습니다."a string"
나중에 STRING_LITERAL이라는 하나의 토큰으로 거나 나중에 더 구문 분석할지 여부를 결정할 수 있습니다."a string"
OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE 등 무엇이든 코딩 할 때 스스로 결정해야하는 많은 선택 중 하나입니다.
Lex : 이제 토큰 목록이 생겼습니다. 첫 번째 패스 동안 각 문자열의 컨텍스트를 파악하는 데 너무 많은 노력을 기울이지 않기 때문에 WORD와 같이 모호한 분류로 일부 토큰에 태그를 지정했을 수 있습니다. 이제 소스 토큰 목록을 다시 읽고 모호한 토큰을 언어별로 키워드를 기반으로보다 구체적인 토큰 유형으로 다시 분류하십시오. 따라서 "if"와 같은 WORD가 있고 "if"가 symbol IF라는 특수 키워드 목록에 있으므로 해당 토큰의 기호 유형을 WORD에서 IF로 변경하고 특수 키워드 목록에없는 WORD를 변경하십시오. foo와 같은은 IDENTIFIER입니다.
구문 분석 : 이제 if (bar) foo += "a string";
다음과 같은 어휘 토큰 목록을 설정했습니다. IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. 이 단계는 토큰 시퀀스를 명령문으로 인식합니다. 이것은 파싱입니다. 다음과 같은 문법을 사용하면됩니다 :
진술 : = ASIGN_EXPRESSION | IF_STATEMENT
IF_STATEMENT : = IF, PAREN_EXPRESSION, 진술
ASIGN_EXPRESSION : = IDENTIFIER, ASIGN_OP, VALUE
PAREN_EXPRESSSION : = OPEN_PAREN, VALUE, CLOSE_PAREN
VALUE : = 아이디 | STRING_LITERAL | PAREN_EXPRESSION
ASIGN_OP : = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT
"|"를 사용하는 프로덕션 용어 사이의 용어는 "이들 중 하나와 일치"를 의미하며, 용어 사이에 쉼표가 있으면 "이 순서의 용어와 일치"를 의미합니다.
이것을 어떻게 사용합니까? 첫 번째 토큰부터 시작하여 일련의 토큰을 이러한 프로덕션과 일치 시키십시오. 먼저 토큰 목록을 STATEMENT와 일치 시키려고하면 STATEMENT의 규칙을 읽고 "STATEMENT는 ASIGN_EXPRESSION 또는 IF_STATEMENT"이므로 ASIGN_EXPRESSION과 먼저 일치 시키려고하므로 ASIGN_EXPRESSION에 대한 문법 규칙을 찾습니다. "ASIGN_EXPRESSION은 IDENTIFIER 다음에 ASIGN_OP 뒤에 VALUE가 있습니다. 따라서 IDENTIFIER에 대한 문법 규칙을 검색하고 IDENTIFIER에 대한 문법 규칙이 없으므로 IDENTIFIER가"터미널 "임을 의미하므로 더 이상 필요하지 않습니다. 토큰과 직접 일치 시키려고 구문 분석하지만 첫 번째 소스 토큰은 IF이고 IF는 IDENTIFIER와 동일하지 않으므로 일치하지 못했습니다. 지금 무엇? STATEMENT 규칙으로 돌아가 다음 용어 IF_STATEMENT와 일치 시키십시오. IF_STATEMENT 조회, IF로 시작, IF 조회, IF는 터미널, 첫 번째 토큰과 터미널 비교, 토큰 일치, 멋진 토큰 계속, 다음 기간은 PAREN_EXPRESSION, 조회 PAREN_EXPRESSION, 터미널이 아닙니다. 첫 번째 용어는 무엇입니까? PAREN_EXPRESSION은 OPEN_PAREN (으)로 시작하고 OPEN_PAREN (검색) OPEN_PAREN (단말기)입니다. OPEN_PAREN (을)를 다음 토큰에 일치 시키면 일치합니다.
이 단계에 접근하는 가장 쉬운 방법은 parse ()라는 함수를 사용하여 일치시키려는 소스 코드 토큰과 일치시키려는 문법 용어를 전달하는 것입니다. 문법 용어가 터미널이 아닌 경우 되풀이됩니다. parse ()를 다시 호출하여 동일한 문법 토큰과이 문법 규칙의 첫 번째 용어를 전달합니다. 이것이 "재귀 하강 파서 (recursive descent parser)"라고 불리는 이유이다. parse () 함수는 소스 토큰을 읽을 때 현재 위치를 반환 (또는 수정)하며, 일치하는 순서로 마지막 토큰을 다시 전달하며 다음 호출을 계속한다 거기에서 구문 분석하십시오.
parse ()가 ASIGN_EXPRESSION과 같은 프로덕션과 일치 할 때마다 해당 코드 조각을 나타내는 구조를 만듭니다. 이 구조에는 원본 소스 토큰에 대한 참조가 포함됩니다. 이러한 구조의 목록을 작성하기 시작합니다. 이 전체 구조를 AST (Abstract Syntax Tree)라고합니다.
컴파일 및 / 또는 실행 : 문법의 특정 프로덕션에 대해 AST 구조가 제공되면 해당 AST 청크를 컴파일하거나 실행하는 핸들러 함수를 작성했습니다.
ASIGN_ADD 유형의 AST 부분을 살펴 보겠습니다. 따라서 인터프리터로서 ASIGN_ADD_execute () 함수가 있습니다. 이 함수는에 대한 구문 분석 트리에 해당하는 AST의 일부로 전달되므로이 foo += "a string"
함수는 해당 구조를보고 구조의 첫 번째 용어는 IDENTIFIER이고 두 번째 용어는 VALUE이어야한다는 것을 알고 있으므로 ASIGN_ADD_execute () 메모리에 평가 된 값을 나타내는 객체를 반환하는 VALUE_eval () 함수에 VALUE 용어를 전달한 다음 ASIGN_ADD_execute ()는 변수 테이블에서 "foo"를 조회하고 eval_value ()에 의해 반환 된 모든 것에 대한 참조를 저장합니다. 기능.
통역사입니다. 컴파일러는 대신 핸들러 함수가 AST를 실행하는 대신 AST를 바이트 코드 또는 기계 코드로 변환하도록합니다.
Flex 및 Bison과 같은 도구를 사용하여 1 단계에서 3 단계, 그리고 4 단계를 더 쉽게 수행 할 수 있습니다. (일명 Lex와 Yacc) 통역사를 처음부터 작성하는 것은 아마도 프로그래머가 달성 할 수있는 가장 강력한 운동 일 것입니다. 다른 모든 프로그래밍 문제는 이것을 정상화 한 후에 사소한 것처럼 보입니다.
저의 조언은 작은 문법으로 작은 언어로 시작하고, 몇 가지 간단한 문장을 파싱하고 실행 해 본 다음 거기서 자라나는 것입니다.
이것들을 읽고 행운을 빕니다!
http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c
http://en.wikipedia.org/wiki/Recursive_descent_parser
lex
,yacc
하고bison
.