parsing - ANTLR 在词法分析/解析中包含元数据的最佳方式(自定义对象、注释类型)

标签 parsing metadata antlr3 lexical-analysis

我计划在解析过程中包含文本元数据(如粗体字体大小等)以实现更好的识别。

例如,我有一个给定的结构,其中一个词在它自己的行上 word/r/n 粗体 大小为 24px,是某篇文章的标题。为了得到更好的识别结果,我想把字符和元数据都考虑进去。就 ANTRL 而言,我不确定如何才能做到最好。我想做类似的事情:

  1. 将原始文本的每个字符包装到一个带有元数据字段的自定义对象中,并将其传递给 ANTLR。
  2. 预处理文本并在特定位置插入元数据的注释,这将被语法分析器考虑。

我真的很喜欢选择选项1.,但我不确定我需要从 ANTLR 的哪一部分继承等。我必须从 ANTLRInputStream-Object 开始吗? ,以便为子类 Lexer 获取适当的流,为子类 Parser 获取自定义 Tokens 等等。有没有更优雅的方法,特别是在使用 {} 中的操作解析时查询 token 阻止?

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

编辑:

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

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

凡标题entryOneentryTwo最初是 24 像素的字体大小,内容是 12 像素的字体大小(作为示例性给定值)。一个字符一个字符地创建一个自定义对象的新实例,将字符封装为 Stringfont-size

我用字体大小的字段为每个字符初始化各自的对象,例如 entryOne 的第一个字母像 MyChar aTitelChar = new MyChar("e", 24); 对于内容,喜欢第二行Here is some content one.我像这样创建 MyChar 的实例:

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

文本的所有字符都包裹在下面的实例中 MyChar -Class 并添加到 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;
    }
}

我希望我的语法与上述两个条目(或更多以这种方式格式化)相匹配,这两个条目依次包含一个标题和一个以句号结尾的内容。该语法可能如下所示:

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';

现在,例如,我想做的是通过检查包含title-tokentitel 之前-规则返回。如果输入符合语法但实际上是某种错误(原始元数据编码文件以符合 title 规则的内容开头,但实际上是内容)语法的作者可以解决这个问题,如果他知道标题的原始 font-size 是 24 并检查它。如果其中一个 tokens 不等于 font-size 24,则抛出异常/不返回/做 smthg。合适的。

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

最佳答案

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

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.

title 是匹配标题元数据(24 号字体)后跟一个或多个换行符的

content 是匹配内容元数据(12 号字体)后跟一个或多个换行符的。如上所述,为了简化,我删除了一段时间的检查。

是不包括换行符的字符序列。

A 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 对应于输入中的一个字符(包括换行符,尽管您可以根据需要删除该限制——如果我还没有,我会删除它这个答案写了 ;) )。

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

关于parsing - ANTLR 在词法分析/解析中包含元数据的最佳方式(自定义对象、注释类型),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13534218/

相关文章:

c# - 我如何初始化 DataSpace.SSpace 和 DataSpace.CSpace?

python - 如何从sqlalchemy中的方言特定类型获取通用数据类型?

java - ANTLR 3解析问题

antlr - 如何从这棵 AST 树中删除无用的节点?

MySQL:检查一个值是否包含在一个范围区间内

json - 从 MS Access 解析 VBA 中的 JSON (US BLS),更新

带有空分隔符的 Java 扫描器

c++ - 从源代码项目中提取所有功能的工具

file - Rust:我想获取目录中最后修改的文件

pattern-matching - Antlr 树模式匹配与重写规则