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


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.