ANTLR에서 "단편"은 무엇을 의미합니까?


99

ANTLR에서 조각 은 무엇을 의미합니까?

두 가지 규칙을 모두 보았습니다.

fragment DIGIT : '0'..'9';

DIGIT : '0'..'9';

차이점은 무엇입니까?

답변:


110

프래그먼트는 인라인 함수와 다소 비슷합니다. 문법을 더 읽기 쉽고 유지하기 쉽게 만듭니다.

조각은 토큰으로 계산되지 않으며 문법을 단순화하는 역할 만합니다.

치다:

NUMBER: DIGITS | OCTAL_DIGITS | HEX_DIGITS;
fragment DIGITS: '1'..'9' '0'..'9'*;
fragment OCTAL_DIGITS: '0' '0'..'7'+;
fragment HEX_DIGITS: '0x' ('0'..'9' | 'a'..'f' | 'A'..'F')+;

이 예에서 NUMBER를 일치 시키면 "1234", "0xab12"또는 "0777"과 일치하는지 여부에 관계없이 항상 NUMBER가 렉서에 반환됩니다.

항목 3 참조


43
fragmentANTLR에서 의미 하는 바에 대해 맞습니다 . 그러나 여러분이 제공하는 예는 형편없는 것입니다. 어휘 분석기가 NUMBER16 진수, 10 진수 또는 8 진수가 될 수 있는 토큰 을 생성하는 것을 원하지 않습니다 . 즉 NUMBER, 프로덕션 (파서 규칙)에서 토큰 을 검사해야합니다 . 당신은 더 나은 렉서 생산 할 수있는 INT, OCT그리고 HEX토큰을 프로덕션 규칙을 만듭니다 number : INT | OCT | HEX;. 이러한 예에서 a DIGIT는 토큰 INTHEX.
바트 Kiers

10
"가난함"이 약간 거칠게 들릴 수 있지만 더 나은 단어를 찾을 수 없습니다 ... 죄송합니다! :)
바트 Kiers

1
당신은 가혹하게 들리지 않았습니다. .. 당신은 옳고 똑 바릅니다!
asyncwait 2014

2
중요한 것은 프래그먼트는 다른 렉서 토큰을 정의하기 위해 다른 렉서 규칙에서만 사용된다는 것입니다. 조각은 문법 (파서) 규칙에서 사용하기위한 것이 아닙니다.
djb

1
@BartKiers : 더 나은 답변을 포함하여 새로운 답변을 만들 수 있습니까?
David Newcomb

18

Definitive Antlr4 참고서에 따르면 다음과 같습니다.

fragment 접두사가 붙은 규칙은 다른 렉서 규칙에서만 호출 할 수 있습니다. 그들은 그 자체로 토큰이 아닙니다.

실제로 그들은 문법의 가독성을 향상시킬 것입니다.

이 예를보십시오 :

STRING : '"' (ESC | ~["\\])* '"' ;
fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;

STRING은 ESC와 같은 조각 규칙을 사용하는 렉서로, Esc 규칙에서는 유니 코드를 사용하고 유니 코드 조각 규칙에서는 Hex를 사용합니다. ESC 및 UNICODE 및 HEX 규칙은 명시 적으로 사용할 수 없습니다.


10

결정적인 ANTLR 4 참조 (페이지 106) :

fragment 접두사가 붙은 규칙은 다른 렉서 규칙에서만 호출 할 수 있습니다. 그들은 그 자체로 토큰이 아닙니다.


추상 개념 :

Case1 : (RULE1, RULE2, RULE3 엔티티 또는 그룹 정보가 필요한 경우)

rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;


Case2 : (RULE1, RULE2, RULE3을 신경 쓰지 않으면 RULE0에만 집중합니다.)

RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;
// RULE0 is a terminal node. 
// You can't name it 'rule0', or you will get syntax errors:
// 'A-C' came as a complete surprise to me while matching alternative
// 'DEF' came as a complete surprise to me while matching alternative


Case3 : (Case2와 동일하므로 Case2보다 읽기 쉽습니다. )

RULE0 : RULE1 | RULE2 | RULE3 ;
fragment RULE1 : [A-C]+ ;
fragment RULE2 : [DEF]+ ;
fragment RULE3 : ('G'|'H'|'I')+ ;
// You can't name it 'rule0', or you will get warnings:
// warning(125): implicit definition of token RULE1 in parser
// warning(125): implicit definition of token RULE2 in parser
// warning(125): implicit definition of token RULE3 in parser
// and failed to capture rule0 content (?)


Case1과 Case2 / 3의 차이점은 무엇입니까?

  1. 어휘 분석기 규칙은 동일합니다.
  2. Case1의 각 RULE1 / 2 / 3은 Regex : (X)와 유사한 캡처 그룹입니다.
  3. Case3의 각 RULE1 / 2 / 3은 Regex :( ?: X)와 유사한 비 캡처 그룹입니다. 여기에 이미지 설명 입력



구체적인 예를 보겠습니다.

목표 : 식별 [ABC]+, [DEF]+, [GHI]+토큰

input.txt

ABBCCCDDDDEEEEE ABCDE
FFGGHHIIJJKK FGHIJK
ABCDEFGHIJKL


Main.py

import sys
from antlr4 import *
from AlphabetLexer import AlphabetLexer
from AlphabetParser import AlphabetParser
from AlphabetListener import AlphabetListener

class MyListener(AlphabetListener):
    # Exit a parse tree produced by AlphabetParser#content.
    def exitContent(self, ctx:AlphabetParser.ContentContext):
        pass

    # (For Case1 Only) enable it when testing Case1
    # Exit a parse tree produced by AlphabetParser#rule0.
    def exitRule0(self, ctx:AlphabetParser.Rule0Context):
        print(ctx.getText())
# end-of-class

def main():
    file_name = sys.argv[1]
    input = FileStream(file_name)
    lexer = AlphabetLexer(input)
    stream = CommonTokenStream(lexer)
    parser = AlphabetParser(stream)
    tree = parser.content()
    print(tree.toStringTree(recog=parser))

    listener = MyListener()
    walker = ParseTreeWalker()
    walker.walk(listener, tree)
# end-of-def

main()


사례 1 및 결과 :

Alphabet.g4 (Case1)

grammar Alphabet;

content : (rule0|ANYCHAR)* EOF;

rule0 : RULE1 | RULE2 | RULE3 ;
RULE1 : [A-C]+ ;
RULE2 : [DEF]+ ;
RULE3 : ('G'|'H'|'I')+ ;

ANYCHAR : . -> skip;

결과:

# Input data (for reference)
# ABBCCCDDDDEEEEE ABCDE
# FFGGHHIIJJKK FGHIJK
# ABCDEFGHIJKL

$ python3 Main.py input.txt 
(content (rule0 ABBCCC) (rule0 DDDDEEEEE) (rule0 ABC) (rule0 DE) (rule0 FF) (rule0 GGHHII) (rule0 F) (rule0 GHI) (rule0 ABC) (rule0 DEF) (rule0 GHI) <EOF>)
ABBCCC
DDDDEEEEE
ABC
DE
FF
GGHHII
F
GHI
ABC
DEF
GHI


Case2 / 3 및 결과 :

Alphabet.g4 (Case2)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

RULE0 : [A-C]+ | [DEF]+ | ('G'|'H'|'I')+ ;

ANYCHAR : . -> skip;

Alphabet.g4 (Case3)

grammar Alphabet;

content : (RULE0|ANYCHAR)* EOF;

RULE0 : RULE1 | RULE2 | RULE3 ;
fragment RULE1 : [A-C]+ ;
fragment RULE2 : [DEF]+ ;
fragment RULE3 : ('G'|'H'|'I')+ ;

ANYCHAR : . -> skip;

결과:

# Input data (for reference)
# ABBCCCDDDDEEEEE ABCDE
# FFGGHHIIJJKK FGHIJK
# ABCDEFGHIJKL

$ python3 Main.py input.txt 
(content ABBCCC DDDDEEEEE ABC DE FF GGHHII F GHI ABC DEF GHI <EOF>)

"그룹 캡처""그룹 캡처하지 않음" 부분을 보았습니까 ?




구체적인 example2를 봅시다.

목표 : 8 진수 / 10 진수 / 16 진수 식별

input.txt

0
123
 1~9999
 001~077
0xFF, 0x01, 0xabc123


Number.g4

grammar Number;

content
    : (number|ANY_CHAR)* EOF
    ;

number
    : DECIMAL_NUMBER
    | OCTAL_NUMBER
    | HEXADECIMAL_NUMBER
    ;

DECIMAL_NUMBER
    : [1-9][0-9]*
    | '0'
    ;

OCTAL_NUMBER
    : '0' '0'..'9'+
    ;

HEXADECIMAL_NUMBER
    : '0x'[0-9A-Fa-f]+
    ;

ANY_CHAR
    : .
    ;


Main.py

import sys
from antlr4 import *
from NumberLexer import NumberLexer
from NumberParser import NumberParser
from NumberListener import NumberListener

class Listener(NumberListener):
    # Exit a parse tree produced by NumberParser#Number.
    def exitNumber(self, ctx:NumberParser.NumberContext):
        print('%8s, dec: %-8s, oct: %-8s, hex: %-8s' % (ctx.getText(),
            ctx.DECIMAL_NUMBER(), ctx.OCTAL_NUMBER(), ctx.HEXADECIMAL_NUMBER()))
    # end-of-def
# end-of-class

def main():
    input = FileStream(sys.argv[1])
    lexer = NumberLexer(input)
    stream = CommonTokenStream(lexer)
    parser = NumberParser(stream)
    tree = parser.content()
    print(tree.toStringTree(recog=parser))

    listener = Listener()
    walker = ParseTreeWalker()
    walker.walk(listener, tree)
# end-of-def

main()


결과:

# Input data (for reference)
# 0
# 123
#  1~9999
#  001~077
# 0xFF, 0x01, 0xabc123

$ python3 Main.py input.txt 
(content (number 0) \n (number 123) \n   (number 1) ~ (number 9999) \n   (number 001) ~ (number 077) \n (number 0xFF) ,   (number 0x01) ,   (number 0xabc123) \n <EOF>)
       0, dec: 0       , oct: None    , hex: None    
     123, dec: 123     , oct: None    , hex: None    
       1, dec: 1       , oct: None    , hex: None    
    9999, dec: 9999    , oct: None    , hex: None    
     001, dec: None    , oct: 001     , hex: None    
     077, dec: None    , oct: 077     , hex: None    
    0xFF, dec: None    , oct: None    , hex: 0xFF    
    0x01, dec: None    , oct: None    , hex: 0x01    
0xabc123, dec: None    , oct: None    , hex: 0xabc123

당신이에 수정 '조각'을 추가하는 경우 DECIMAL_NUMBER, OCTAL_NUMBER, HEXADECIMAL_NUMBER(그들은 더 이상 토큰이 아니기 때문에), 당신은 수 엔티티를 캡처 할 수 없습니다. 결과는 다음과 같습니다.

$ python3 Main.py input.txt 
(content 0 \n 1 2 3 \n   1 ~ 9 9 9 9 \n   0 0 1 ~ 0 7 7 \n 0 x F F ,   0 x 0 1 ,   0 x a b c 1 2 3 \n <EOF>)

8

블로그 게시물 에는 fragment상당한 차이를 만드는 매우 명확한 예가 있습니다.

grammar number;  

number: INT;  
DIGIT : '0'..'9';  
INT   :  DIGIT+;

문법은 '42'는 인식하지만 '7'은 인식하지 않습니다. 숫자를 조각으로 만들거나 INT 뒤에 DIGIT를 이동하여 수정할 수 있습니다.


1
여기서 문제는 키워드가 없다는 것이 아니라 fragment렉서 규칙의 순서입니다.
BlackBrain

나는 "수정"이라는 단어를 사용했지만 문제를 고치는 것이 핵심이 아닙니다. 여기에이 예제를 추가했습니다. 왜냐하면 이것은 키워드 조각을 사용할 때 실제로 변경 되는 사항 에 대한 가장 유용하고 간단한 예제이기 때문 입니다.
Vesal

2
조각이 토큰을 정의하지 않아 첫 번째 어휘 규칙 을 만들기 때문에 조각으로 선언 하면 문제 DIGITINT해결 된다고 주장하고 INT있습니다. 나는 이것이 의미있는 예라는 것에 동의하지만 fragment키워드가 의미하는 바를 이미 알고있는 사람만을위한 것입니다 . 처음으로 조각의 올바른 사용을 알아 내려는 사람에게는 다소 오해의 소지가 있습니다.
BlackBrain

1
그래서 제가 이것을 배우고있을 때 위와 같은 많은 예제를 보았지만 왜 추가 키워드가 필요한지 알지 못했습니다. 나는 이것이 "자체의 토큰"이 아니라는 것이 실제로 무엇을 의미하는지 이해하지 못했습니다. 이제 원래 질문에 대한 좋은 답이 무엇인지 실제로 잘 모르겠습니다. 내가 받아 들인 답변에 만족하지 않는 이유 위에 의견을 추가하겠습니다.
Vesal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.