使用 Antlr 将 org 文件翻译为 html
Table of Contents
本文会涉及到以下内容:
- 背景
- Antlr 在 Python 下的使用
- Antlr 处理文本的过程
- g4 文件说明
背景
这个项目目的是练习 Antlr 在 Python 下的使用。 其中 org 文件的格式如下:
#+TITLE: hello world
#+CSS: a.css
* header1[id=myHeader]
** hello world[id=myHeader]
this is a test
and this is another test
another test
*** hello kitty[id=myHeader]
what can i do for you
and hahhaaa
其中 TITLE 标记表示标题,其中 CSS 是自定义的,表示要引用的 css 文件地址,并非 Org mode 支持的功能。同时在一级、二级可以自定义 css 的 id,来灵活的定义格式。
下面来看如何将其转换为 html 文件。
Antlr 在 Python 下使用
Antlr 可以将 g4 文件转化为很多语言,生成 lexer, parser 文件,其中支持的目标语言包括 Java、Python、C++、Go 等。
在 Python 下使用,非常简单:
安装 Python 运行时环境
pip install antlr4-python3-runtime
指定语言目标
antlr4 -Dlanguage=Python3 MyGrammar.g4
Antlr 处理文本的过程
语言的处理过程分为两个独立的阶段:
- Lexing: 将文本转化为 tokens 符号。
- Parsing: 从 tokens 中构建语法树。
首先来看 Lexing 过程:
- 大写符号表示的表示 lexer 规则。
- lexer 首先找到一个匹配最好的规则来匹配当前的输入。
- 最好的规则是能够匹配长度最长的一个。
- tokens 产生过程有如下的可能:
- 如果只有一个规则匹配输入,会将匹配的输入 push 到 token stream 中。
- 如果有多个规则匹配输入,最好的匹配是第一个遇到的 lexer 规则。
例如:
FILEPATH: ('A'..'Z'|'a'..'z'|'0'..'9'|':'|'\\'|'/'|' '|'-'|'_'|'.')+ ;
TITLE: ('A'..'Z'|'a'..'z'|' ')+ ;
如果匹配一个 TITLE,它会被认定为 FILEPATH, 而不是 TITLE,所以当我们使用 TITLE 的时候它肯定是找不到。也就是在定义 Lexer 规则的时候,我们要尽量不要让它们有交叉,否则可能就会出现类似 mismatched input xxx' expecting xxx 这样的错误。
如何写 g4 文件
首先看下为实现该应用定义的 demo.g4 文件。
grammar demo;
org: line+;
line: '#+TITLE:' one params? NEWLINE # title
|'#+CSS: ' path # css
| HEADER1 WS? one params? NEWLINE # header1
| HEADER2 WS? one params? NEWLINE # header2
| HEADER3 WS? one params? NEWLINE # header3
| content+ # con
| NEWLINE # newline
;
path: (ID | '/' | '.')+ # p ;
content: (WS* ID)+ NEWLINE # con2;
params : '[' exprlist ']' # Para;
exprlist: ID '=' ID # expr ;
one: (WS* ID)+;
NEWLINE : '\r'? '\n' ;
HEADER1: '*' ;
HEADER2: '**' ;
HEADER3: '***' ;
WS : [ \t]+ ;
ID : [0-9a-zA-Z]+;
语法定义以 grammer 开头,后面的名称和文件名对应。大写的表示 Lexer 规则, 小写的为 Parser 规则。需要注意很多的规则都是可复用的,也就是在大部分情况下,我们都可以在别的语言中找到一些通用的模式。例如这个Grammars written for ANTLR v4。
在该应用中,我们用到了 visitor 模式,在这种模式下,需要对定义的 g4 文件的 parser 规则加上标签。方式也比较简单,只需要在行的后面加上 # <label>就可以了。需要注意:
- 行结尾的;号要在标签的后面。
- 标签的大小写不敏感。
如何执行
首先,使用 vistor 模式来生成词法和语法文件:
antlr4 -visitor -no-listener c.g
同时定义处理文件,文件内容如下:
from demoVisitor import demoVisitor
HEAD_CSS = '''
<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">
'''
class Org2Html(demoVisitor):
def __init__(self):
self.html = '<html>'
def visitCss(self, ctx):
path = ctx.path().getText()
self.html = self.html + "<head>" + HEAD_CSS.format(path)
def visitTitle(self, ctx):
title = ctx.one().getText()
if ctx.params():
a, b = self.visit(ctx.params())
else:
a = 'id'
b = 'xxx'
title = "<title {}=\"{}\">{}</title></head>".format(a, b, title)
self.html = self.html + title + '\n'
print(self.html)
def visitPara(self, ctx):
# need return by all level
return self.visit(ctx.exprlist())
def visitExpr(self, ctx):
a = ctx.ID(0).getText()
b = ctx.ID(1).getText()
print(a, b)
return a, b
在该文件中,如果想要访问某个 parser 规则,只需要 visit 后跟上对应的标签即可。对于嵌套的例如 Para Expr,如果想在在最上层获得结果,每个都返回,如 VisitPara 函数。
同时获取某个节点的 value 使用 getText 即可。对于其他的使用可以阅读 The Definitive ANTLR 4 Reference 2。
最后可以执行的项目地址在:org2html
参考
- Python target
- The Definitive ANTLR 4 Reference, book