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、プロダクション(パーサールール)でトークンを検査する必要があることを意味します。あなたはより良いレクサーの農産物を聞かせてことができINTOCTおよびHEXトークンおよび生成規則を作成しますnumber : INT | OCT | HEX;。このような例では、DIGITは、トークンINTとによって使用されるフラグメントになる可能性がありHEXます。
Bart Kiers、2011年

10
「貧しい」は少し厳しいように聞こえるかもしれませんが、私はそれについてより良い言葉を見つけることができなかったことに注意してください...すみません!:)
Bart Kiers

1
あなたは厳しい音を鳴らしていませんでした..あなたは正直でした!
asyncwait 2014

2
重要なことに、フラグメントは、他のレクサートークンを定義する他のレクサールールでのみ使用することを目的としています。フラグメントは、文法(パーサー)ルールで使用するためのものではありません。
djb 2014

1
@BartKiers:より良い答えを含む新しい答えを作成できますか。
David Newcomb

18

決定的なAntlr4リファレンスブックによると:

プレフィックスがフラグメントのルールは、他のレクサールールからのみ呼び出すことができます。それ自体はトークンではありません。

実際には、文法の読みやすさが向上します。

この例を見てください:

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

STRINGは、ESCのようなフラグメントルールを使用するレクサーです。UnicodeはEscルールで使用され、HexはUnicodeフラグメントルールで使用されます。ESC、UNICODE、HEXルールは明示的に使用できません。


10

Definitive ANTLR 4リファレンス(ページ106):

プレフィックスがフラグメントのルールは、他のレクサールールからのみ呼び出すことができます。それ自体はトークンではありません。


抽象概念:

ケース1:(RULE1、RULE2、RULE3 エンティティまたはグループ情報が必要な場合)

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


ケース2:(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()


Case1と結果:

Alphabet.g4(ケース1)

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(ケース2)

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>)

あなたが見た「キャプチャグループ」「非キャプチャグループ」の部分は?




具体例2を見てみましょう。

目標: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_NUMBEROCTAL_NUMBERHEXADECIMAL_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
フラグメントがトークンを定義していないという理由だけでDIGITフラグメントを宣言INTすると問題が解決するのでINT、最初の字句ルールが作成されると私はただ主張しています。これは意味のある例ですが、(imo)fragmentキーワードの意味をすでに知っている人だけが同意するものです。フラグメントの正しい使い方を初めて理解しようとしている人にとっては、少し誤解を招くことがわかります。
BlackBrain

1
それで、これを学んでいたとき、私は上記のような例をたくさん見ましたが、なぜこれに追加のキーワードが必要になるのかよくわかりませんでした。これが「トークンそのもの」ではないことの実際の意味を理解できませんでした。現在、元の質問に対する適切な回答が何かはわかりません。受け入れられた回答に満足できない理由をコメントに追加します。
Vesal
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.