렉서가 파서에 반환하는 토큰의 데이터 유형은 무엇입니까?


21

제목에서 말했듯이, 어휘 분석기는 어떤 데이터 타입을 파서에 반환 / 주어야합니까? Wikipedia 의 어휘 분석 기사를 읽을 때 다음과 같이 진술했습니다.

컴퓨터 과학에서 어휘 분석은 일련의 문자 (예 : 컴퓨터 프로그램 또는 웹 페이지)를 일련의 토큰 ( 식별 된 "의미"가있는 문자열 )으로 변환하는 프로세스입니다 .

그러나 위의 진술과 완전히 모순되어 다른 사이트에서 질문 한 다른 질문 ( 호기심이 많은 경우 코드 검토 )에 답변했을 때 응답하는 사람은 다음과 같이 말했습니다.

어휘 분석기는 보통 문자열을 읽고 이것을 lexemes의 스트림으로 변환합니다. 벡스는 숫자 스트림 일 필요가 있습니다 .

그리고 그는이 시각을주었습니다 :

nl_output => 256
output    => 257
<string>  => 258

이 기사에서 그는 Flex이미 존재하는 어휘 분석기를 언급 했으며, '규칙'을 작성하는 것은 손으로 어휘 분석기를 작성하는 것보다 간단 할 것이라고 말했다. 그는 나에게이 예를 제시했다.

Space              [ \r\n\t]
QuotedString       "[^"]*"
%%
nl_output          {return 256;}
output             {return 257;}
{QuotedString}     {return 258;}
{Space}            {/* Ignore */}
.                  {error("Unmatched character");}
%%

통찰력을 높이고 자세한 정보를 얻으려면 Flex 에 대한 Wikipedia 기사를 읽으십시오 . Flex 기사에서는 다음과 같은 방식으로 토큰을 사용하여 일련의 구문 규칙을 정의 할 수 있음을 보여주었습니다.

digit         [0-9]
letter        [a-zA-Z]

%%
"+"                  { return PLUS;       }
"-"                  { return MINUS;      }
"*"                  { return TIMES;      }
"/"                  { return SLASH;      }
"("                  { return LPAREN;     }
")"                  { return RPAREN;     }
";"                  { return SEMICOLON;  }
","                  { return COMMA;      }
"."                  { return PERIOD;     }
":="                 { return BECOMES;    }
"="                  { return EQL;        }
"<>"                 { return NEQ;        }
"<"                  { return LSS;        }
">"                  { return GTR;        }
"<="                 { return LEQ;        }
">="                 { return GEQ;        }
"begin"              { return BEGINSYM;   }
"call"               { return CALLSYM;    }
"const"              { return CONSTSYM;   }
"do"                 { return DOSYM;      }
"end"                { return ENDSYM;     }
"if"                 { return IFSYM;      }
"odd"                { return ODDSYM;     }
"procedure"          { return PROCSYM;    }
"then"               { return THENSYM;    }
"var"                { return VARSYM;     }
"while"              { return WHILESYM;   }

Flex lexer가 키워드 / 토큰 문자열을 반환하는 것 같습니다. 그러나 특정 숫자와 동일한 상수를 반환 할 수 있습니다.

렉서가 숫자를 반환한다면 어떻게 문자열 리터럴을 읽을 수 있습니까? 단일 키워드의 경우 숫자를 반환하는 것이 좋습니다. 그러나 문자열을 어떻게 처리 하시겠습니까? 어휘 분석기는 문자열을 이진수로 변환 할 필요가없고 파서는 숫자를 다시 문자열로 변환 할 것이다. 렉서가 문자열을 반환하는 것이 훨씬 논리적이고 쉬운 것처럼 보이며 파서가 숫자 문자열 리터럴을 실제 숫자로 변환하게합니다.

아니면 렉서가 두 가지를 모두 반환 할 수 있습니까? 나는 C ++로 간단한 렉서를 작성하려고 노력했지만 함수에 대해 하나의 반환 유형 만 가질 수 있습니다 . 따라서 저에게 질문을하도록 이끌었습니다.

내 질문을 단락으로 요약하려면 : 어휘 분석기를 작성할 때 하나의 데이터 유형 (문자열 또는 숫자) 만 반환 할 수 있다고 가정 하면 더 논리적 인 선택입니까?


어휘 분석기는 당신이 리턴 한 것을 리턴합니다. 디자인에서 숫자를 요구하면 숫자를 반환합니다. 분명히 문자열 리터럴을 나타내는 것은 그 이상을 요구할 것입니다. 참조 는 구문 분석 번호 및 문자열에 렉서의 작업인가? 문자열 리터럴은 일반적으로 "언어 요소"로 간주되지 않습니다.
— Robert Harvey

@RobertHarvey 문자열 리터럴을 이진수로 변환 하시겠습니까?.
— Christian Dean

알다시피, 어휘 분석기의 목적은 언어 요소 (키워드, 연산자 등)를 가져 와서 토큰으로 만드는 것입니다. 따라서 인용 된 문자열은 언어 요소가 아니기 때문에 어휘 분석기에 관심이 없습니다. 나는 렉서를 직접 작성하지는 않았지만 인용 된 문자열이 변경되지 않고 그대로 통과한다고 상상할 것이다 (따옴표 포함).
— Robert Harvey

그래서 당신의 말은 렉서가 문자열 리터럴을 읽거나 신경 쓰지 않는다는 것입니다. 파서는이 문자열 리터럴을 찾아야합니까? 이것은 매우 혼란 스럽다.
— Christian Dean

이 기사를 읽는 데 몇 분이 걸릴 수 있습니다. en.wikipedia.org/wiki/Lexical_analysis
— Robert Harvey

답변:


10

일반적으로 lexing 및 parsing을 통해 언어를 처리하는 경우 다음과 같은 어휘 토큰에 대한 정의가 있습니다.

NUMBER ::= [0-9]+
ID     ::= [a-Z]+, except for keywords
IF     ::= 'if'
LPAREN ::= '('
RPAREN ::= ')'
COMMA  ::= ','
LBRACE ::= '{'
RBRACE ::= '}'
SEMICOLON ::= ';'
...

파서에 대한 문법이 있습니다.

STATEMENT ::= IF LPAREN EXPR RPAREN STATEMENT
            | LBRACE STATEMENT BRACE
            | EXPR SEMICOLON
EXPR      ::= ID
            | NUMBER
            | ID LPAREN EXPRS RPAREN
...

렉서는 입력 스트림을 가져와 토큰 스트림을 생성합니다. 파서가 토큰 스트림을 소비하여 구문 분석 트리를 생성합니다. 경우 에 따라 토큰 유형 만 알고 있으면 충분하지만 (예 : LPAREN, RBRACE, FOR) 경우에 따라 토큰과 관련된 실제 값 이 필요합니다 . 예를 들어, ID 토큰을 발견하면 나중에 참조하려는 식별자를 파악하려고 할 때 ID를 구성하는 실제 문자가 필요합니다.

따라서 일반적으로 다음과 같은 것이 있습니다.

enum TokenType {
  NUMBER, ID, IF, LPAREN, RPAREN, ...;
}

class Token {
  TokenType type;
  String value;
}

어휘 분석기가 토큰을 반환 할 때, 어떤 유형 (구문 분석에 필요한지)과 생성 된 문자 순서 (나중에 문자열 및 숫자 리터럴, 식별자, 기타.). 매우 간단한 집계 유형을 반환하므로 두 개의 값을 반환하는 것처럼 느껴질 수 있지만 실제로 두 부분이 모두 필요합니다. 결국 다음 프로그램을 다르게 취급하고 싶을 것입니다.

if (2 > 0) {
  print("2 > 0");
}
if (0 > 2) {
  print("0 > 2");
}

IF, LPAREN, NUMBER, GREATER_THAN, NUMBER, RPAREN, LBRACE, ID, LPAREN, STRING, RPAREN, SEMICOLON, RBRACE 와 같은 일련의 토큰 유형 이 생성 됩니다. 즉 , 그것들 도 동일 하게 구문 분석 합니다. 그러나 실제로 구문 분석 트리로 무언가를 할 때 첫 번째 숫자의 값은 '2'(또는 '0')이고 두 번째 숫자의 값은 '0'(또는 '2) ')이며 문자열 값은'2> 0 '(또는'0> 2 ')입니다.


나는 대부분의 당신이 말하는 무엇을 얻을 수 있지만, 어떻게 즉 String value채워지는 것? 문자열이나 숫자로 채워 집니까? 또한 String유형을 어떻게 정의 합니까?
— Christian Dean

1
@ Mr.Python 가장 간단한 경우에는 어휘 생성과 일치하는 문자열입니다. 따라서 foo (23, "bar")가 표시 되면 토큰 [ID, "foo"], [LPAREN, "("], [NUMBER, "23"], [COMMA, "," ], [STRING, ""23 ""], [RPAREN, ")"] . 정보를 보존하는 것이 중요 할 수 있습니다. 또는 다른 접근 방식을 취하고 값에 문자열 또는 숫자 등이 될 수있는 공용체 유형을 가질 수 있으며 보유한 토큰 유형 (예 : 토큰 유형이 NUMBER 인 경우)에 따라 올바른 값 유형을 선택할 수 있습니다. , value.num을 사용하고 STRING 인 경우 value.str을 사용하십시오.
— Joshua Taylor

@MrPython "또한 문자열 유형을 어떻게 정의합니까?" 나는 자바 같은 사고 방식으로 글을 쓰고있었습니다. C ++에서 작업하는 경우 C ++의 문자열 유형을 사용하거나 C에서 작업하는 경우 char *를 사용할 수 있습니다. 요점은 토큰과 관련이 있다는 것, 해당 값 또는 값을 생성하기 위해 해석 할 수있는 텍스트가 있습니다.
— Joshua Taylor

1
@ ollydbg23이 옵션은 비합리적인 옵션은 아니지만 시스템의 내부 일관성을 떨어 뜨립니다. 예를 들어, 파싱 한 마지막 도시의 문자열 값을 원한다면, null 값을 명시 적으로 확인한 다음 문자열에서 문자열을 찾아서 역 토큰을 찾아야합니다. 게다가, 그것은 렉서와 파서 사이의 더 긴밀한 결합이다; LPAREN이 다른 문자열이나 여러 문자열과 일치 할 수있는 경우 업데이트 할 코드가 더 있습니다.
— Joshua Taylor

2
@ ollydbg23 하나의 사례는 간단한 의사 축소 기입니다. 쉽게 할 수 있습니다 parse(inputStream).forEach(token -> print(token.string); print(' '))(즉, 토큰으로 문자열 값을 공백으로 구분하여 인쇄하십시오). 꽤 빠릅니다. 그리고 LPAREN이 "("에서 나올 수 있다고해도 메모리에 상수 문자열이 될 수 있으므로 토큰에 참조를 포함시키는 것이 null 참조를 포함하는 것보다 더 비쌀 수는 없습니다. 코드를 특별하게 만들지 않는 코드
— Joshua Taylor

6

제목에서 말했듯이, 어휘 분석기는 어떤 데이터 타입을 파서에 반환 / 주어야합니까?

분명히 "토큰". 이 스트림 반환해야합니다 그래서 렉서는 토큰의 스트림을 생성 토큰을 .

그는 이미 존재하는 어휘 분석기 인 Flex를 언급했으며 '규칙'을 작성하는 것은 손으로 어휘 분석기를 작성하는 것보다 간단 할 것이라고 말했다.

기계 생성 어휘 분석기는 빠르게 생성 할 수 있다는 이점이 있는데, 이는 어휘 문법이 많이 바뀌 었다고 생각할 때 특히 유용합니다. 구현 선택에 많은 유연성을 얻지 못하는 단점이 있습니다.

즉, 누가 "더 단순한"것을 신경 쓰나요? 어휘 분석기를 작성하는 것은 보통 어려운 일이 아닙니다!

렉서를 작성할 때 하나의 데이터 유형 (문자열 또는 숫자) 만 반환 할 수 있다고 가정하면 더 논리적 인 선택은 무엇입니까?

둘 다. 그것은 반환해야합니다 그래서 렉서는 일반적으로 그 반환 토큰은 "다음"작업이 토큰을 . 토큰은 문자열이나 숫자가 아닙니다. 토큰입니다.

필자가 마지막으로 작성한 어휘 분석기는 "완전한 충실도"어휘 분석기였다. 즉, 프로그램에서 모든 공백과 주석의 위치를 ​​추적하는 토큰과 토큰을 반환했다. 내 어휘 분석기에서 토큰은 다음과 같이 정의되었다 :

  • 최고의 퀴즈의 배열
  • 토큰 종류
  • 문자의 토큰 너비
  • 후행 퀴즈의 배열

퀴즈는 다음과 같이 정의되었습니다.

  • 사소한 종류-공백, 줄 바꿈, 주석 등
  • 문자의 퀴즈 너비

우리가 같은 것을 가지고 있다면

    foo + /* comment */
/* another comment */ bar;

토큰 종류 네 토큰으로 렉스 것이라고 Identifier, Plus, Identifier, Semicolon, 및 폭 3, 1, 3 (1) 제 1 식별자 퀴즈 이루어지는 선두 갖는다 Whitespace(4)의 폭 및 퀴즈 후행 Whitespace(1)의 폭을 Plus선두에 퀴즈가 없다 및 하나의 공백, 주석 및 개행 문자로 구성된 후미 퀴즈. 최종 식별자에는 주석과 공백 등이 있습니다.

이 구성표를 사용하면 파일의 모든 문자가 어휘 분석기의 출력에서 ​​설명됩니다. 이는 구문 색상 표시와 같은 것들에 대한 편리한 속성입니다.

물론, 퀴즈가 필요하지 않으면 종류와 너비의 두 가지 토큰을 만들 수 있습니다.

토큰과 퀴즈에는 소스 코드에서 절대 위치가 아닌 너비 만 포함되어 있습니다. 고의적입니다. 이러한 계획에는 장점이 있습니다.

  • 메모리 및 유선 형식으로 컴팩트 함
  • 편집시 다시 적용 할 수 있습니다. 이는 어휘 분석기가 IDE 내에서 실행중인 경우에 유용합니다. 즉, 토큰에서 편집을 감지하면 편집하기 전에 렉서를 몇 개의 토큰으로 백업하고 이전 토큰 스트림과 동기화 될 때까지 렉싱을 다시 시작하십시오. 문자를 입력하면 해당 문자 이후의 모든 토큰 위치 가 변경되지만 일반적으로 하나 또는 두 개의 토큰 만 너비가 변경되므로 해당 상태를 모두 재사용 할 수 있습니다.
  • 모든 토큰의 정확한 문자 오프셋은 토큰 스트림을 반복하고 현재 오프셋을 추적하여 쉽게 도출 할 수 있습니다. 정확한 문자 오프셋이 있으면 필요한 경우 텍스트를 쉽게 추출 할 수 있습니다.

이러한 시나리오에 신경 쓰지 않으면 토큰은 종류와 너비가 아니라 종류와 오프셋으로 표현 될 수 있습니다.

그러나 여기서 중요한 점은 프로그래밍은 유용한 추상화를 만드는 기술입니다 . 토큰을 조작하고 있으므로 토큰에 대해 유용한 추상화를 한 다음 구현 세부 사항을 선택하십시오.


3

일반적으로 토큰 (또는 사용하기 쉬운 열거 형 값)을 나타내는 숫자와 선택적 값 (문자열 또는 일반적 / 템플릿 값)을 가진 작은 구조를 반환합니다. 다른 방법은 추가 데이터를 전달해야하는 요소에 대해 파생 된 형식을 반환하는 것입니다. 둘 다 약간 불쾌하지만 실제 문제에 대한 충분한 해결책입니다.


약간 불쾌 하다는 것은 무엇을 의미 합니까? 문자열 값을 얻는 비효율적 인 방법입니까?
— Christian Dean

@ Mr.Python-코드에서 사용하기 전에 많은 검사를 수행하므로 비효율적이지만 코드를 좀 더 복잡하고 취약하게 만듭니다.
— Telastyn

C ++에서 렉서를 디자인 할 때 비슷한 질문이 있습니다. Token *또는 a 또는 단순히 a Token또는 클래스 TokenPtr의 공유 포인터 인 a 를 반환 할 수 Token있습니다. 그러나 일부 lexer가 TokenType 만 반환하고 문자열 또는 숫자 값을 다른 전역 또는 정적 변수에 저장하는 것을 볼 수 있습니다. 또 다른 질문은 위치 정보를 저장하는 방법입니다. TokenType, String 및 Location 필드가있는 Token 구조체가 필요합니까? 감사.
— ollydbg23

@ ollydbg23-이 중 하나라도 작동 할 수 있습니다. 구조체를 사용합니다. 비 학습 언어의 경우 어쨌든 파서 생성기를 사용하게됩니다.
— Telastyn

@Telastyn 답장을 보내 주셔서 감사합니다. 토큰 구조는 다음과 같을 수 있습니다 struct Token {TokenType id; std::string lexeme; int line; int column;}. 과 같은 Lexer의 공개 함수의 PeekToken()경우 함수는 Token *또는을 반환 할 수 TokenPtr있습니다. 함수가 TokenType을 반환하면 파서가 토큰에 대한 다른 정보를 얻으려고 어떻게 시도합니까? 따라서 데이터 유형과 같은 포인터가 그러한 함수에서 반환하는 것이 좋습니다. 내 아이디어에 대한 의견이 있으십니까? 감사합니다
— ollydbg23
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.