“ แฟรกเมนต์” ใน ANTLR หมายถึงอะไร


103

อะไรส่วนค่าเฉลี่ยใน ANTLR?

ฉันเห็นกฎทั้งสอง:

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

และ

DIGIT : '0'..'9';

อะไรคือความแตกต่าง?

คำตอบ:


115

แฟรกเมนต์ค่อนข้างคล้ายกับฟังก์ชันอินไลน์: ทำให้ไวยากรณ์อ่านง่ายขึ้นและดูแลรักษาง่ายขึ้น

ส่วนย่อยจะไม่ถูกนับเป็นโทเค็นมันทำหน้าที่เพียงเพื่อลดความซับซ้อนของไวยากรณ์เท่านั้น

พิจารณา:

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 จะส่งคืน NUMBER ไปยังตัวอักษรเสมอไม่ว่าจะตรงกับ "1234", "0xab12" หรือ "0777" ก็ตาม

ดูข้อ 3


43
คุณพูดถูกเกี่ยวกับความfragmentหมายใน ANTLR แต่ตัวอย่างที่คุณให้มานั้นเป็นตัวอย่างที่ไม่ดี: คุณไม่ต้องการให้ lexer สร้างNUMBERโทเค็นที่อาจเป็นเลขฐานสิบหกทศนิยมหรือฐานแปดก็ได้ นั่นหมายความว่าคุณต้องตรวจสอบNUMBERโทเค็นในการผลิต (กฎตัวแยกวิเคราะห์) คุณควรที่จะปล่อยให้ lexer ผลิตINT, OCTและราชสกุลและสร้างกฎการผลิต:HEX number : INT | OCT | HEX;ในตัวอย่างเช่น a DIGITอาจเป็นส่วนที่จะใช้โดยโทเค็นINTและHEX.
Bart Kiers

10
โปรดทราบว่า "น่าสงสาร" อาจฟังดูรุนแรงไปหน่อย แต่ฉันหาคำที่ดีกว่านี้ไม่ได้ ... ขออภัย! :)
Bart Kiers

1
คุณไม่ได้ฟังดูรุนแรง .. คุณพูดถูกและตรง!
asyncwait

2
ที่สำคัญแฟรกเมนต์มีไว้เพื่อใช้เฉพาะในกฎของ lexer อื่นเพื่อกำหนดโทเค็น lexer อื่น ๆ ส่วนย่อยไม่ได้หมายถึงการใช้ในกฎไวยากรณ์ (parser)
djb

1
@BartKiers: คุณช่วยสร้างคำตอบใหม่รวมถึงคำตอบที่ดีกว่าของคุณได้ไหม
David Newcomb

18

ตามหนังสืออ้างอิง Antlr4 ที่ชัดเจน:

กฎที่นำหน้าด้วยส่วนย่อยสามารถเรียกได้จากกฎของ lexer อื่น ๆ เท่านั้น พวกเขาไม่ใช่ราชสกุลในสิทธิของตนเอง

จริงๆแล้วพวกเขาจะช่วยปรับปรุงการอ่านไวยากรณ์ของคุณ

ดูตัวอย่างนี้:

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 อย่างชัดเจน


11

การอ้างอิง ANTLR 4 ขั้นสุดท้าย (หน้า 106):

กฎที่นำหน้าด้วยส่วนย่อยสามารถเรียกได้จากกฎของ lexer อื่น ๆ เท่านั้น พวกเขาไม่ใช่ราชสกุลในสิทธิของตนเอง


แนวคิดนามธรรม:

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. กฎ lexer เทียบเท่า
  2. RULE1 / 2/3 แต่ละข้อใน Case1 เป็นกลุ่มการจับภาพคล้ายกับ Regex: (X)
  3. RULE1 / 2/3 แต่ละอันใน Case3 เป็นกลุ่มที่ไม่จับภาพคล้ายกับ 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 (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>)

คุณเห็นส่วน"จับภาพกลุ่ม"และ"กลุ่มที่ไม่จับภาพ"หรือไม่




มาดูตัวอย่างที่เป็นรูปธรรม 2

เป้าหมาย: ระบุเลขฐานแปด / ฐานสิบ / ฐานสิบหก

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

ถ้าคุณเพิ่มปรับ 'ส่วน' to 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' คุณสามารถแก้ไขได้โดยทำให้ตัวเลขเป็นส่วนย่อย (หรือย้าย DIGIT หลัง INT)


1
ปัญหาที่นี่ไม่ใช่การไม่มีคีย์เวิร์ดfragmentแต่เป็นลำดับของกฎ lexer
Blackrain

ฉันใช้คำว่า "แก้ไข" แต่ประเด็นไม่ใช่เพื่อแก้ไขปัญหา ฉันเพิ่มตัวอย่างนี้ที่นี่เพราะสำหรับฉันนี่เป็นตัวอย่างที่ง่ายและเป็นประโยชน์ที่สุดของสิ่งที่เป็นจริงเปลี่ยนแปลงไปเมื่อใช้ส่วนคำหลัก
Vesal

2
ฉันแค่เถียงว่าการประกาศDIGITว่าเป็นส่วนหนึ่งของการINTแก้ปัญหาเพียงเพราะเศษเล็กเศษน้อยไม่ได้กำหนดโทเค็นจึงทำให้INTกฎคำศัพท์แรก ฉันเห็นด้วยกับคุณว่านี่เป็นตัวอย่างที่มีความหมาย แต่ (imo) เฉพาะสำหรับผู้ที่รู้แล้วว่าไฟล์fragmentคำหลักนั้นหมายถึงอะไร ฉันพบว่ามันค่อนข้างทำให้เข้าใจผิดสำหรับคนที่พยายามคิดหาการใช้ชิ้นส่วนที่ถูกต้องเป็นครั้งแรก
Blackrain

1
ดังนั้นเมื่อฉันเรียนรู้สิ่งนี้ฉันเห็นตัวอย่างมากมายเช่นตัวอย่างข้างต้น แต่ฉันไม่เข้าใจว่าทำไมจึงต้องมีคีย์เวิร์ดพิเศษสำหรับสิ่งนี้ ฉันไม่เข้าใจว่านี่คือ "ราชสกุลในสิทธิของตนเอง" หมายความว่าอย่างไรในทางปฏิบัติ ตอนนี้ฉันไม่แน่ใจจริงๆว่าอะไรจะตอบคำถามเดิมได้ดี ฉันจะเพิ่มความคิดเห็นด้านบนว่าทำไมฉันถึงไม่พอใจกับคำตอบที่ยอมรับ
Vesal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.