ANTLR在lexing / parsing中包含元数据的最佳方法(自定义对象,一种注释)

时间:2012-11-23 18:31:06

标签: parsing metadata antlr lexical-analysis

我计划在解析过程中包含文本元数据(如粗体 font-size 等)以获得更好的识别效果。

例如,我有一个给定的结构,其中一行单词word/r/n 粗体且大小 24px ,是某些人的标题文章。为了获得更好的识别结果,我想要考虑角色以及元数据。就ANTRL而言,我不确定如何做到最好。我想做点什么:

  1. 将原始文本的每个字符包装到带有元数据字段的自定义对象中,并将其传递给ANTLR。
  2. 预处理文本并在特定位置插入语法所考虑的元数据注释。
  3. 我真的很想选择 1。但是我不确定ANTLR中哪个部分需要子类化等等。我必须从ANTLRInputStream-Object开始,以便为子类 Lexer 获取适当的流以获取子类 Parser 等的自定义标记。是否有更优雅的方式,尤其是在查询时使用{}块中的操作解析时的令牌?

    如果有人有一些提示和/或经验,那就太棒了!

    编辑:

    这是一个更具体的简单示例:我有一个文件,其中包含我正手解析的元数据编码。包含换行符的实际文本如下所示:

    entryOne
    Here is some content one.
    entryTwo
    Here is some content two.
    

    标题entryOneentryTwo最初 font-size 为24px且内容为 font-size 为12px(作为示范给定值)。 Char by char我创建了一个自定义对象的新实例,将该字符封装为 String font-size

    我使用font-size字段初始化每个字符的相应对象,例如entryOne的第一个字母 MyChar aTitelChar = new MyChar("e", 24); 对于内容,如第二行Here is some content one.,我创建了MyChar的实例,如:

    MyChar aContentChar= new MyChar("H", 12);

    文本的所有字符都包含在以下MyChar - 的实例中,并添加到List<MyChar>以便为ANTLR生成新输入。

    下面是字符的Java类:

    public class MyChar {
        private int fontSizePx;
        private String text;
    
        public MyChar(String text, int fontSizePx) {
            this.text = text;
            this.fontSizePx = fontSizePx;
        }
    
        public int getFontSizePx() {
            return fontSizePx;
        }
    
        public String getText() {
            return text;
        }
    }
    

    我希望我的语法匹配上面两个条目(或者更多格式化的方式),这些条目又包含标题和以fullstop终止的内容。这个语法看起来像这样:

    rule: entry+ NEWLINE
    ;
    entry:
    title
    content
    ;   
    title: 
    letters NEWLINE
    ;
    content:
    (letters)+ '.' NEWLINE
    ;
    letters:
    LETTERS 
    ;
    LETTERS:
    ('a'..'z' | 'A'..'Z')+
    ;
    WS:
    (' ' | '\t' | 'f' ) + {$channel = HIDDEN;};
    NEWLINE:'\r'? '\n';
    

    现在,例如,我想要做的是通过检查包含该字母的所有字母的 font-size 来查明它是否真的是条目的标题 titel之前的 title-token - 规则返回。如果输入符合语法但实际上是某种错误(原始的元数据编码文件以符合title规则的内容开始,但实际上是内容),语法的作者可以对其进行排序如果他知道标题的原始 font-size 是24并检查这个。如果其中一个字母令牌不等于 font-size 24则抛出异常/不返回/执行smthg。合适的。

    我正在思考的是在哪里插入List<MyChar>以提供此功能(在ANTLR的上下文中解析时查询各种元数据)。我正在尝试使用ANTLR的类,但由于我是ANTLR的新手,我想可能有些有经验的用户可以指出我正确的方向,比如哪里会有一个很好的自定义对象插入点?我应该从实现CharStream开始并覆盖一些方法吗?可能有一些ANTLR提供的东西,我还没有找到?

1 个答案:

答案 0 :(得分:2)

这是实现我认为你想要的一种方法,使用解析器来管理匹配的元数据输入。请注意,我将空白显着,因为它是内容的一部分,不能被跳过。我还将句点作为内容的一部分来简化示例,而不是将它们用作标记。

<强> SysEx.g

grammar SysEx;

@header {
    import java.util.List;
}

@parser::members {
        private List<MyChar> metadata;
        private int curpos;

        private boolean isTitleInput(String input) {
            return isFontSizeInput(input, 24);
        }

        private boolean isContentInput(String input){
            return isFontSizeInput(input, 12);
        }

        private boolean isFontSizeInput(String input, int fontSize){
            List<MyChar> sublist = metadata.subList(curpos, curpos + input.length());

            System.out.println(String.format("Testing metadata for input=\%s, font-size=\%d", input, fontSize));

            int start = curpos;            
            //move our metadata pointer forward.
            skipInput(input);

            for (int i = 0, count = input.length(); i < count; ++i){
                MyChar chardata = sublist.get(i);
                char c = input.charAt(i);
                if (chardata.getText().charAt(0) != c){
                    //This character doesn't match the metadata (ERROR!)
                    System.out.println(String.format("Content mismatch at metadata position \%d: metadata=(\%s,\%d); input=\%c", start + i, chardata.getText(), chardata.getFontSizePx(), c));
                    return false;
                } else if (chardata.getFontSizePx() != fontSize){
                    //The font is wrong.
                    System.out.println(String.format("Format mismatch at metadata position \%d: metadata=(\%s,\%d); input=\%c", start + i, chardata.getText(), chardata.getFontSizePx(), c));
                    return false;
                }
            }

            //All characters check out.
            return true;
        }

        private void skipInput(String str){
            curpos += str.length();
            System.out.println("\t\tMoving metadata pointer ahead by " + str.length() + " to " + curpos);
        }
}

rule[List<MyChar> metadata]
    @init {
        this.metadata = metadata;
    }
    : entry+ EOF
    ;
entry
    : title content
    {System.out.println("Finished reading entry.");}
    ;   
title
    : line {isTitleInput($line.text)}? newline {System.out.println("Finished reading title " + $line.text);}
    ;
content
    : line {isContentInput($line.text)}? newline {System.out.println("Finished reading content " + $line.text);}
    ;
newline
    : (NEWLINE{skipInput($NEWLINE.text);})+
    ;
line returns [String text]
    @init { 
        StringBuilder builder = new StringBuilder();
    }
    @after {
        $text = builder.toString();
    }
    : (ANY{builder.append($ANY.text);})+ 
    ;

NEWLINE:'\r'? '\n';
ANY: .; //whitespace can't be skipped because it's content.

titleline,与标题元数据(大小为24字体)匹配,后跟一个或多个换行符。

contentline,匹配内容元数据(大小为12的字体),后跟一个或多个换行符。如上所述,为了简化,我删除了支票一段时间。

line是一系列不包含换行符的字符。

validating semantic predicate{...}?之后的line)用于验证该行是否与元数据匹配。

以下是我用来测试语法的代码(减去导入,为简洁起见):

<强> SysExGrammar.java

public class SysExGrammar {
    public static void main(String[] args) throws Exception {
        //Create some metadata that matches our input.
        List<MyChar> matchingMetadata = new ArrayList<MyChar>();
        appendMetadata(matchingMetadata, "entryOne\r\n", 24);
        appendMetadata(matchingMetadata, "Here is some content one.\r\n", 12);
        appendMetadata(matchingMetadata, "entryTwo\r\n", 24);
        appendMetadata(matchingMetadata, "Here is some content two.\r\n", 12);

        parseInput(matchingMetadata);

        System.out.println("Finished example #1");


        //Create some metadata that doesn't match our input (negative test).
        List<MyChar> mismatchingMetadata = new ArrayList<MyChar>();
        appendMetadata(mismatchingMetadata, "entryOne\r\n", 24);
        appendMetadata(mismatchingMetadata, "Here is some content one.\r\n", 12);
        appendMetadata(mismatchingMetadata, "entryTwo\r\n", 12); //content font size!
        appendMetadata(mismatchingMetadata, "Here is some content two.\r\n", 12);

        parseInput(mismatchingMetadata);

        System.out.println("Finished example #2");
    }

    private static void parseInput(List<MyChar> metadata) throws Exception {
        //Test setup
        InputStream resource = SysExGrammar.class.getResourceAsStream("SysExTest.txt");

        CharStream input = new ANTLRInputStream(resource);

        resource.close();

        SysExLexer lexer = new SysExLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        SysExParser parser = new SysExParser(tokens);
        parser.rule(metadata);

        System.out.println("Parsing encountered " + parser.getNumberOfSyntaxErrors() + " syntax errors");
    }

    private static void appendMetadata(List<MyChar> metadata, String string,
            int fontSize) {

        for (int i = 0, count = string.length(); i < count; ++i){
            metadata.add(new MyChar(string.charAt(i) + "", fontSize));
        }
    }
}

SysExTest.txt (请注意,这会使用Windows换行符(\r\n

entryOne
Here is some content one.
entryTwo
Here is some content two.

测试输出(已修剪;第二个示例有故意不匹配的元数据):

Parsing encountered 0 syntax errors
Finished example #1
Parsing encountered 2 syntax errors
Finished example #2

此解决方案要求每个MyChar对应输入中的一个字符(包括换行符,但如果您愿意,可以删除该限制 - 如果我还没有写出这个答案,我会删除它up;))。

如您所见,可以将元数据绑定到解析器,一切都按预期工作。我希望这会有所帮助。