`
白开水
  • 浏览: 6565 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

决战 ANTLR3

    博客分类:
  • JAVA
阅读更多
  前不久写完了一个工具软件Nova Studio用来开发自己编写的Java Web 框架Nova,其中有SQL编辑框,虽然通过Java正则表达式实现了现在流行的代码自动提示功能,但感觉过于硬编码,于是想通过语法分析的方法重新实现一下。
  网上搜了搜,找到了语法生成器antlr,去官方网站下载了antlr3,找了相应的例子和文章熟悉了了一下,开始实践Calculator小例子,中间出过不少错误,主要是antlr3和antlr2有些地方不太一样,总算最后都一一解决了。
  实践结果有点让人泄气,antlr3只能分析输入的文本是否语法正确,却不能告诉我后续应该跟随操作数还是运算符,另外即使语法正确时它也不能像antlr2那样给我一棵嵌套的语法树,它返回的是所有的叶节点数组,是一维的,这样我就没法通过遍历嵌套树实现不同语义不同的显示风格,如:SQL保留字黑体,表名红色等。
  难道几天研究实践的努力白费了,实在是心有不甘,研究了一下antlr3生成的语法分析类CalculatorParser,发现它在向后匹配时会调用这个方法:
 
   
match(input,OR,FOLLOW_OR_in_logic_expr111);

  FOLLOW_OR_in_logic_expr111这个follow参数正是'or' 逻辑或操作符后面可以跟随的各种标记(Token),灵机一动,如果重载这个方法把这个follow保存起来,那么捕获到语法错后就能知道正确的后续标记了。行动!
  CalculatorParser 是继承org.antlr.runtime.Parser类的,于是首先写一个自己的扩展类 MyAntlrParser:

    public class NovaAntlrParser extends MyAntlrParser
    {
        public BitSet follow = null;
        public int ttype; // 当前处理的token的数组下标
        
        public MyAntlrParser(TokenStream input)
        {
            super(input);
        }
        
        public void match(IntStream input, int ttype, BitSet follow)
        throws RecognitionException
        {
            this.ttype = ttype;
            this.follow = follow;

            super.match(input, ttype, follow);
        }		
    }

  CalculatorParser类改成继承 MyAntlrParser类,在测试类Main中捕获异常进行处理,代码如下:


    import java.io.IOException;
    import java.io.StringReader;

    import org.antlr.runtime.ANTLRReaderStream;
    import org.antlr.runtime.CommonTokenStream;
    import org.antlr.runtime.MismatchedSetException;
    import org.antlr.runtime.MismatchedTokenException;
    import org.antlr.runtime.RecognitionException;

    public class Main 
    {
        public static void main(String[] args) throws IOException
        {
            String content = "isprogrammer or ";
            ANTLRReaderStream input = new ANTLRReaderStream(new StringReader(content)); 
            CalculatorLexer lexer = new CalculatorLexer(input); 
            CommonTokenStream tokens = new CommonTokenStream(lexer); 
            CalculatorParser parser = new CalculatorParser(tokens); 

            // 可能的后续匹配标记名
            String[] expecting = null;

            try 
            { 
                CommonTree tree = parser.expr().getTree();
                // 处理tree
                ......
            }
            catch (RecognitionException e) 
            {
                expecting = expecting(parser, e);
            }         

        }

        public static ExpectInfo expecting(MyAntlrParser parser, RecognitionException e)
        {				
            if (e instanceof MismatchedTokenException) 
            { 
                String expecting =  parser.getTokenNames()[parser.ttype];
                return new String[]{expecting);
            } else if (e instanceof MismatchedSetException ||
                        e instanceof NoViableAltException) 
            { 
                String[] expecting = CollectionUtil.getElements(
                        parser.getTokenNames(), 
                        parser.follow.toArray());
                return toExpectInfo(e, expecting);
            } else
            {
                return getRule(e), null;
            }
        }

        private static String getRule(Exception e) 
        {
            return e.getStackTrace()[0].getMethodName();
        }

    }

  语法文件Calculator.g如下:

    grammar Calculator;
    options
    {
      backtrack=true;
      memoize=true;
      k = 10;
      output=AST;
      ASTLabelType=CommonTree;
    }

    expr : basic_expr | logic_expr ;
    basic_expr : operator WS? PLUS WS? operator ;
    logic_expr : operator ( WS OR WS operator ) + ;
    operator : (VAR | INT) ;

    PLUS  : '+' ;
    OR : 'or' ;
    INT   : ('0'..'9')+ ;
    VAR  : 'a'..'z' ('a'..'z'|'0'..'9'|'_'|'$'|'#')* ;
    WS : ( ' ' | '\t' | '\r' | '\n' )+ ;

  进行测试,发现expecting返回的基本上是我所希望的,第一步算成功了。但还有两个
问题。第一个,抛出异常时得不到tree。第二个,即使语法正确不抛异常得到了tree,但
这个tree是扁平的,只能得到PLUS, OR, INT, VAR, WS 这些标记名,不能知道是basic_expr
还是logic_expr。
  那么能不能再次祭起重载利器解决这个难题呢?再次分析CalculatorParser.java代码,发现它都是通过一个CommonTreeAdaptor对象来生成树、添加子树等操作,如果我把自己的Adaptor强制赋给它不就有希望在自己的Adaptor类中生成自己定义的语法树了吗?
  另外通过其它的例子发现可以在定义语法规则时定义对应的代码片断,Antlr3会把它插入到该规则对应的方法中(方法名和规则名相同),这样就有可能让规则方法调用我的代码向自己生成的语法树插入规则名。再次动手!
  首先写一个树结构类 NovaAntlrTree 保存我所希望的标记信息:

    import java.util.ArrayList;
    import java.util.List;

    import org.antlr.runtime.tree.Tree;

    public class NovaAntlrTree 
    {
        private NovaAntlrTree parent = null;
        
        private String rule = null;
        private String text = null;
        private int type;
        private int line;
        private int positionInLine;
        
        private List children = null;
        
        public NovaAntlrTree(Tree node, String rule)
        {
            this.rule = rule;
            
            text = node.getText();
            type = node.getType();
            line = node.getLine();
            positionInLine = node.getCharPositionInLine();
        }
        
        public NovaAntlrTree getChild(int i) 
        {
            if ( children==null || i>=children.size() ) 
            {
                return null;
            }
            return (NovaAntlrTree)children.get(i);
        }

        public int getChildCount()
        {
            if ( children==null ) 
            {
                return 0;
            }
            return children.size();
        }

        public void addChild(NovaAntlrTree t)
        {
            if ( t==null ) 
            {
                return; 
            }
            
            t.setParent(this);
            
            if ( children==null ) 
            {
                children = new ArrayList();
            }
            children.add(t);
        }

        public String getFinalRule(NovaAntlrParser parser) 
        {
            String realRule = rule;
            if (realRule == null && type > 0)
                realRule = parser.getTokenNames()[type];
            
            return realRule;
        }
        
        public NovaAntlrTree getStartTree()
        {
            if (line > 0)
                return this;
            
            return getChild(0).getStartTree();
        }
        
        public NovaAntlrTree getEndTree()
        {
            if (line > 0)
                return this;
            
            return getChild(getChildCount()-1).getEndTree();
        }
        
        public int getLine() {
            return line;
        }

        public int getPositionInLine() {
            return positionInLine;
        }

        public String getRule() {
            return rule;
        }

        public void setRule(String rule)
        {
            this.rule = rule;
        }

        public String getText() {
            return text;
        }

        public int getType() {
            return type;
        }

        public NovaAntlrTree getParent() {
            return parent;
        }

        public void setParent(NovaAntlrTree parent) {
            this.parent = parent;
        }

    }


  语法文件 Calculator.g略作修改:

  ......
  basic_expr : ( operator WS? PLUS WS? operator ) { setRule("basic_expr"); } ;
  logic_expr : ( operator ( WS OR WS operator ) + ) { setRule("logic_expr"); } ;
  operator : (VAR | INT)  { setRule("operator"); };
  ......


  现在来写自己的Adator类,它继承 CommonTreeAdaptor

    import java.util.HashSet;

    import org.antlr.runtime.Token;
    import org.antlr.runtime.tree.CommonTree;
    import org.antlr.runtime.tree.CommonTreeAdaptor;
    import org.antlr.runtime.tree.Tree;

    public class NovaTreeAdaptor extends CommonTreeAdaptor
    {
        private NovaAntlrTree root = null;
        private NovaAntlrTree current = null;

        private HashSet addedTokenIndices = new HashSet();
        
        public NovaTreeAdaptor()
        {
            super();
        }
        
        public void addChild(Object t, Object child)
        {
            super.addChild(t, child);
            
            CommonTree ct = (CommonTree) child;
            Token token = ct.getToken();
            if (token != null)
            {
                Integer index = new Integer(token.getTokenIndex());
                if (!addedTokenIndices.contains(index))
                {
                    current.addChild(createTree(child));
                    addedTokenIndices.add(index);
                }
            }
        }

        public Object nil() 
        {
            Object o = super.nil();
            
            NovaAntlrTree t = createTree(o);
            if (current != null)
                current.addChild(t);
            current = t;
            if (root == null)
                root = current;
            //System.out.println("rule:"+parser.getRule()+", nil object "+o);
            
            return o;
        }

        private NovaAntlrTree createTree(Object o) {
            NovaAntlrTree t = new NovaAntlrTree((Tree) o, null);
            return t;
        }

        public NovaAntlrTree getTree() {
            return root;
        }

        public void setRule(String rule)
        {
            if (current != null)
            {
                current.setRule(rule);
                current = current.getParent();
            }
            
        }
        
        public Object rulePostProcessing(Object root) 
        {
            return super.rulePostProcessing(root);
        }
    }


  下面我们来增强MyAntlrParser类,先重载三个异常处理方法,目的一是减少修改CalculatorParser.java
的工作量,毕竟语法文件可能经常会作修改,每次重新生成CalculatorParser.java文件后对它进行比较多的替换
动作总是烦人的,二是希望它能直接抛出异常,三是不想看到它烦人的异常信息输出:

	public void recover(IntStream input, RecognitionException re)
	{
	}
		
	public void reportError(RecognitionException e) 
	{		
	}
	
	public void recoverFromMismatchedToken(IntStream input,
			   RecognitionException e,
			   int ttype,
			   BitSet follow)
	throws RecognitionException
	{
		if ( input.LA(2)==ttype ) 
		{
			beginResync();
			input.consume(); 
			endResync();
			input.consume(); 
			return;
		}
		if ( !recoverFromMismatchedElement(input,e,follow) ) 
		{
			throw e;
		}
	}


  然后重载两个和Adaptor相关的方法:

	public TreeAdaptor getTreeAdaptor()
	{
		return null;
	}
	public void setTreeAdaptor(TreeAdaptor adaptor)
	{
    }

	public NovaTreeAdaptor getNovaTreeAdaptor()
	{
		return (NovaTreeAdaptor) getTreeAdaptor();
	}

    然后实现在语法文件中增加的代码中的setRule方法:

	public void setRule(String rule)
    {
		getNovaTreeAdaptor().setRule(rule);
	}


  最后通过命令行 java org.antlr.Tool Calculator.g重新生成Calculator.java文件,并作以下两个替换动作(以后每次修改语法后都要做这个动作即可):
  把父类Parser替换成MyAntlrParser
  把所有的reportError(RecognitionException e); 这一行替换为throw e;

  好了,来编写新的测试代码测试我们自己生成的语法树有没有达到预期效果:

    public static void main(String[] args) throws IOException
    {
        .....

        try 
        { 
            parser.setTreeAdaptor(new NovaTreeAdaptor(parser));
        	parser.expr();
        }
        catch (RecognitionException e) 
        {  
        } 
        iterate(parser.getNovaTreeAdaptor().getTree(), 0);
    }

	private static void iterate(NovaAntlrTree t, int depth)
	{
		if (t == null)
			return;
				
		System.out.print(createSpace(depth*4));
		System.out.println("rule:"+t.getRule()+"["+t.getText()+"]");
		depth++;

		for (int i=0; i<t.getChildCount(); i++)
		{
			iterate(t.getChild(i), depth);
		}

	}
    
    public static String createSpace(int count)
    {
    	StringBuffer sb = new StringBuffer(count);
    	for (int i=0; i<count; i++)
    		sb.append(' ');
    	return sb.toString();
    }


  运行结果如下:

  rule:null[null]
    rule:logic_expr[null]
      rule:operator[null]
        rule:null[isprogrammer]
      rule:null[ ]
      rule:null[or]
      rule:null[ ]
      rule:operator[null]
        rule:null[3]
      rule:null[ ]
      rule:null[or]
      rule:null[ ]
      rule:operator[null]
        rule:null[isantlr]


  测试成功后把类移植到我的SQL编辑器中,增加显示不同Style功能后,大功告成,这是效果图:


2
0
分享到:
评论
5 楼 山脚下的农民 2011-02-28  
不错,有意思
4 楼 healer_kx 2009-08-21  
一般ANTLR的Lexer和Parser,都是分开写的,你为什么喜欢写到一个文件里面呢?
3 楼 healer_kx 2009-08-21  
很好,很不错,支持。能问问题吗?
2 楼 jiaqing 2008-09-11  
高手,能看看你的语法文件吗?谢谢,让我也学点。我的QQ:664723267.
1 楼 y263542662 2008-05-26  
白开水.的破2

相关推荐

    Antlr3.Runtime_C#_

    在“Antlr3.Runtime_C#_”这个主题中,我们主要关注的是ANTLR针对C#目标的运行时库,即Antlr3.Runtime.dll。这个库是ANTLR 3版本的C#运行时组件,用于支持由ANTLR生成的C#解析器在.NET平台上运行。ANTLR 3.5是这个库...

    antlr3最终手册

    《antlr3最终手册》作为antlr3的学习资源,被誉为是最好的学习文档和详细说明手册,它不仅对antlr3进行了全面深入的解析,还通过实际应用案例和专家观点,为读者提供了宝贵的见解和指导。以下是对该手册核心知识点的...

    Antlr3.Runtime.dll

    Antlr3.Runtime.dll .net运行时库

    antlrcs, ANTLR 3 StringTemplate 3和 StringTemplate 4的C# 端口.zip

    antlrcs, ANTLR 3 StringTemplate 3和 StringTemplate 4的C# 端口 ANTLR 3 C# 目标 这里知识库包含 3个主要项目的C# 版本,其中有些项目具有多个生成构件:ANTLR 3Antlr3: ANTLR 3的代码生成器Antlr3.Runtime: ANTLR...

    antlr3学习以及简单的应用--使用sql语句查询集合中的对象

    ANTLR3(ANTLR Version 3)是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件。它广泛应用于构建语言、工具和框架,尤其是在处理结构化数据、语法分析和编译编译器领域。ANTLR 支持多种...

    antlr .net 2.0运行库 Antlr3.Runtime.dll

    发布的.net的antlr运行库存在不同步的问题,导致产生错误,vs2005或vs2008等无法编译. 我用最新的发布源码生产了该运行库,解决了错误。 ANTLR 是ANother Tool for Language Recognition 的缩写“又一个语言识别...

    antlr3_3.2.is.3.2-7ubuntu3_all.deb

    antlr3_3.2.is.3.2-7ubuntu3_all.deb

    antlr-2.7 (3个)

    3. 完善的错误处理:ANTLR在遇到语法错误时会提供清晰的错误消息,方便开发者调试。 4. 直译器生成:ANTLR不仅用于编译器的构建,还可以生成解释器,直接执行源代码而无需编译成中间代码。 5. 生成的目标代码可读性...

    antlr-2.7.5H3.jar

    antlr-2.7.5H3.antlr-2.7.5H3.antlr-2.7.5H3.jar

    antlr-2.7.7.jar和antlr-2.7.6.jar

    3. 稳定性:较新的版本可能更稳定,但有时候老版本在特定场景下表现更好。 4. 社区支持:新版本通常有更活跃的社区支持,遇到问题时可以获取更多帮助。 在实际开发中,如果项目已经使用了ANTLR,并且没有遇到重大...

    antlr-2.7.7.jar.zip

    3. **运行解析器**:在你的应用程序中,你可以使用生成的解析器类来读取和处理输入源码,ANTLR会自动进行词法分析和语法分析。 4. **处理抽象语法树**:ANTLR生成的AST可以方便地进行进一步的处理,如代码生成、验证...

    antlr-2.7.5H3.jar.zip

    3. **目标代码生成**:ANTLR将AST转换为用户指定的目标语言代码,可以是编译器、解释器或者其他需要处理结构化输入的应用。 4. **错误处理**:ANTLR提供强大的错误检测和恢复机制,可以在解析过程中发现并报告语法...

    ANTLR

    3. **语法规则定义**:详细解释ANTLR语法文件的结构,如如何定义关键字、标识符、运算符等词法单元,以及如何构建语法规则表达复杂的语言结构。 4. **构建解析器和词法分析器**:演示如何使用ANTLR工具将语法规则...

    antlr-2.7.7.jar

    3. **解析输入**:在运行时,词法分析器将输入分解为词法单元(如关键字、标识符、数字等),然后解析器使用这些词法单元来构建抽象语法树(AST)。 4. **处理AST**:解析器生成的AST可以用于执行、翻译或其他操作。...

    Antlr4 C++ 计算器

    3. **解析输入**:在运行时,词法分析器将输入的数学表达式分解为一个个单独的标记(tokens),然后这些标记由解析器处理,根据语法规则构建抽象语法树(AST)。 4. **执行计算**:解析器构建的AST可以被遍历执行,...

    The Definitive ANTLR 4 Reference.pdf_antlr_

    3. **LL(*)和预测LL(*)解析**:ANTLR 4使用预测LL(*)算法,这是一个自适应的左到右解析策略,能够处理许多复杂的上下文无关语法,包括左递归和右递归。 4. **树解析器**:ANTLR 4不仅生成词法分析器和语法解析器,...

    开源项目-antlr-antlr4.zip

    3. **目标语言生成**:ANTLR支持多种目标语言,包括Java、C#、Python、JavaScript等,以及新增的Go语言。这意味着生成的解析器和词法分析器可以无缝集成到相应的目标语言项目中。 4. **运行时库**:ANTLR还提供了一...

    ANTLR V3 语法解析工具

    3. `antlrworks-1.2.3-src.zip`:这个文件是ANTLRWORKS的源代码包,对于想要了解ANTLRWORKS内部实现或者进行二次开发的用户来说非常有用。通过解压这个文件,可以查看ANTLRWORKS的源代码并进行学习或修改。 使用...

Global site tag (gtag.js) - Google Analytics