<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://wiki.riguz.com/index.php?action=history&amp;feed=atom&amp;title=Blog%3A%E4%BD%BF%E7%94%A8_Antlr_%E8%A7%A3%E6%9E%90%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6</id>
	<title>Blog:使用 Antlr 解析配置文件 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.riguz.com/index.php?action=history&amp;feed=atom&amp;title=Blog%3A%E4%BD%BF%E7%94%A8_Antlr_%E8%A7%A3%E6%9E%90%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6"/>
	<link rel="alternate" type="text/html" href="https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8_Antlr_%E8%A7%A3%E6%9E%90%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6&amp;action=history"/>
	<updated>2026-06-02T21:42:59Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.42.3</generator>
	<entry>
		<id>https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8_Antlr_%E8%A7%A3%E6%9E%90%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6&amp;diff=2562&amp;oldid=prev</id>
		<title>imported&gt;Riguz：​在纠结了一阵子 yml,ini,xml甚至 lua 等等 配置文件的格式后，还是决定使用antlr实现了一种我自定义的格式的解析。</title>
		<link rel="alternate" type="text/html" href="https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8_Antlr_%E8%A7%A3%E6%9E%90%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6&amp;diff=2562&amp;oldid=prev"/>
		<updated>2018-05-09T00:00:00Z</updated>

		<summary type="html">&lt;p&gt;在纠结了一阵子 yml,ini,xml甚至 lua 等等 配置文件的格式后，还是决定使用antlr实现了一种我自定义的格式的解析。&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;在纠结了一阵子 yml,ini,xml甚至 lua 等等 配置文件的格式后，还是决定使用antlr实现了一种我自定义的格式的解析。&lt;br /&gt;
这个格式是这个样子的:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
// Here is some comment&lt;br /&gt;
shared {&lt;br /&gt;
    string _baseUrl = &amp;quot;http://localhost:8080&amp;quot;;&lt;br /&gt;
    string domain   = &amp;quot;riguz.com&amp;quot;;&lt;br /&gt;
    bool ssl        = false;&lt;br /&gt;
    int version     = 19;&lt;br /&gt;
    int subVersion  = 25;&lt;br /&gt;
    float number  = 19.25;&lt;br /&gt;
&lt;br /&gt;
    string urls         = [&amp;quot;http://localhost:8080&amp;quot;, &amp;quot;http://riguz.com:8080&amp;quot;];&lt;br /&gt;
    string domains      = [&amp;quot;riguz.com&amp;quot;, &amp;quot;dr.riguz.com&amp;quot;];&lt;br /&gt;
    bool sslArray       = [true, false];&lt;br /&gt;
    int versionArray    = [19, 25];&lt;br /&gt;
    float numberArray   = [18.01, 19.25, 20.23];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
scope dev_db {&lt;br /&gt;
    string url = ${domain} .. &amp;quot;:3306/mysql&amp;quot;;&lt;br /&gt;
    string user = &amp;quot;lihaifeng&amp;quot;;&lt;br /&gt;
    int connections = 10;&lt;br /&gt;
    string password = &amp;quot;iikjouioqueyjkajkqq==&amp;quot;;&lt;br /&gt;
    string domains = ${domains};&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
其实是一个k-v形式的文本文件，支持的基本类型有：字符串、布尔值、整数、小数、数组。定义的方法类似于Java或者C语言，&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
string _baseUrl = &amp;quot;http://localhost:8080&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
前面会限定数据类型。如果要定义数组，则用&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
bool sslArray       = [true, false];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
这种形式。&lt;br /&gt;
&lt;br /&gt;
然后使用scope区分不同的配置块。因为可能有些相同的配置会重名，这样我们利用不同的scope去区分就好了。考虑到有些配置中需要共同的变量的使用，所以定义了一个shared的scope，这个是写死的scope，其他scope中只能引用shared scope中的变量。&lt;br /&gt;
&lt;br /&gt;
字符串连接使用```..```操作符。这样可以组装字符串。详细的实现可以在[https://github.com/soleverlee/forks/tree/master/config/src/main forks的子项目config]中找到。&lt;br /&gt;
&lt;br /&gt;
另外还实现了一个类似Play! Framework的路由定义文件的解析，长这个样子的:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
controllers admin{&lt;br /&gt;
package com.riguz.forks.demo.controller&lt;br /&gt;
UserController&lt;br /&gt;
FileController&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
controllers {&lt;br /&gt;
package com.riguz.forks.demo.admin&lt;br /&gt;
UserController-&amp;gt;AdminUserController&lt;br /&gt;
PostController&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
filters {&lt;br /&gt;
package com.riguz.forks.demo.filters&lt;br /&gt;
AuthorizationFilter&lt;br /&gt;
NocsrfFilter&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
routes admin {&lt;br /&gt;
+AuthorizationFilter&lt;br /&gt;
get  /users                 UserController.getUsers()&lt;br /&gt;
get  /users/:id             UserController.getUser(id: Long)&lt;br /&gt;
post /users                 UserController.createUser()&lt;br /&gt;
get  /users/:id/files/*name FileUserController.getFile(id: Long, name: String)&lt;br /&gt;
}&lt;br /&gt;
routes guest {&lt;br /&gt;
+NocsrfFilter&lt;br /&gt;
get /posts      PostUserController.getPosts()&lt;br /&gt;
get /posts/:id  PostUserController.getPost(id: String)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
routes guest {&lt;br /&gt;
+NocsrfFilter&lt;br /&gt;
get /posts      PostUserController.getPosts()&lt;br /&gt;
get /posts/:id  PostUserController.getPost(id: String)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这个文件的解析也在上面的git中可以找到实现。通过Antlr可以很方便的把类似这样的文件解析出来，你甚至可以实现自己的领域语言。在实现过程中，遇到过一些问题，来说下问题吧。&lt;br /&gt;
&lt;br /&gt;
首先是Antlr提供了Listener和Visitor两种方式，起初使用Listener来实现但是感觉比较麻烦，而使用Visitor则可以直接通过返回值来取得AST解析结果。我们解析一个文件的时候，是自顶向下的，一个个的去解析的，比如我们的配置文件的antlr语法定义如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
script&lt;br /&gt;
    : shared? scope*&lt;br /&gt;
      EOF&lt;br /&gt;
    ;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
其中shared又是这样的&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
shared&lt;br /&gt;
    : SHARED LBRACE (property SEMI)* RBRACE SEMI&lt;br /&gt;
    ;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
也就是说 ```shared { k=v...} ;```这样的形式，然后又开始到了property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
property&lt;br /&gt;
    : type NAME ASSIGN expression        #basicProperty&lt;br /&gt;
    | type NAME ASSIGN LBRACK&lt;br /&gt;
        expression? (COMMA expression)*&lt;br /&gt;
      RBRACK                             #arrayProperty&lt;br /&gt;
    ;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这样层层往下来看。然后解析的时候也是一样，我们首先有一个顶层的解析器：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
public class ScriptVisitor extends CfParserBaseVisitor&amp;lt;Map&amp;lt;String, ScriptVisitor.Scope&amp;gt;&amp;gt; {&lt;br /&gt;
    private static final Logger logger = LoggerFactory.getLogger(ScriptVisitor.class);&lt;br /&gt;
&lt;br /&gt;
    @Override&lt;br /&gt;
    public Map&amp;lt;String, Scope&amp;gt; visitScript(CfParser.ScriptContext ctx) {&lt;br /&gt;
        ...&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这个Visitor负责解析语法文件中定义的script块，然后解析里面的scope：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
ScopeVisitor scopeVisitor = new ScopeVisitor(context);&lt;br /&gt;
        ctx.scope().forEach(scopeContext -&amp;gt; {&lt;br /&gt;
            logger.debug(&amp;quot;Visit scope:{}&amp;quot;, scopeContext.getText());&lt;br /&gt;
            Scope scope = scopeContext.accept(scopeVisitor);&lt;br /&gt;
            scopes.put(scope.name, scope);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这样又实现一个ScopeVisitor去解析scope就好了。详细的实现就不多贴代码了。&lt;br /&gt;
&lt;br /&gt;
另外一个问题是，对于错误的处理，我们在哪一步做？比如```bool s = &amp;quot;123&amp;quot;;```这是错误的，我们其实可以在定义grammar的时候就避免这种错误来，但写起来会麻烦一些。目前的实现是在Visitor中去对逻辑进行判断的，前面只做语法检查就可以了。&lt;br /&gt;
&lt;br /&gt;
参考:&lt;br /&gt;
&lt;br /&gt;
* http://jakubdziworski.github.io/java/2016/04/01/antlr_visitor_vs_listener.html&lt;/div&gt;</summary>
		<author><name>imported&gt;Riguz</name></author>
	</entry>
</feed>