如何使用 ANTLR4 突出显示 QScintilla?
How to highlight QScintilla using ANTLR4?
我正在努力学习 ANTLR4,但我的第一个实验已经遇到了一些问题。
这里的目标是学习如何使用 ANTLR 来语法高亮 QScintilla 组件。为了练习一下,我决定学习如何正确突出显示 *.ini
个文件。
首先,为了 运行 mcve,您需要:
- 下载 antlr4 并确保其正常工作,阅读主站点上的说明
- 安装python antlr 运行时,只需执行:
pip install antlr4-python3-runtime
生成ini.g4
的lexer/parser:
grammar ini;
start : section (option)*;
section : '[' STRING ']';
option : STRING '=' STRING;
COMMENT : ';' ~[\r\n]*;
STRING : [a-zA-Z0-9]+;
WS : [ \t\n\r]+;
来自 运行宁 antlr ini.g4 -Dlanguage=Python3 -o ini
最后保存main.py
:
import textwrap
from PyQt5.Qt import *
from PyQt5.Qsci import QsciScintilla, QsciLexerCustom
from antlr4 import *
from ini.iniLexer import iniLexer
from ini.iniParser import iniParser
class QsciIniLexer(QsciLexerCustom):
def __init__(self, parent=None):
super().__init__(parent=parent)
lst = [
{'bold': False, 'foreground': '#f92472', 'italic': False}, # 0 - deeppink
{'bold': False, 'foreground': '#e7db74', 'italic': False}, # 1 - khaki (yellowish)
{'bold': False, 'foreground': '#74705d', 'italic': False}, # 2 - dimgray
{'bold': False, 'foreground': '#f8f8f2', 'italic': False}, # 3 - whitesmoke
]
style = {
"T__0": lst[3],
"T__1": lst[3],
"T__2": lst[3],
"COMMENT": lst[2],
"STRING": lst[0],
"WS": lst[3],
}
for token in iniLexer.ruleNames:
token_style = style[token]
foreground = token_style.get("foreground", None)
background = token_style.get("background", None)
bold = token_style.get("bold", None)
italic = token_style.get("italic", None)
underline = token_style.get("underline", None)
index = getattr(iniLexer, token)
if foreground:
self.setColor(QColor(foreground), index)
if background:
self.setPaper(QColor(background), index)
def defaultPaper(self, style):
return QColor("#272822")
def language(self):
return self.lexer.grammarFileName
def styleText(self, start, end):
view = self.editor()
code = view.text()
lexer = iniLexer(InputStream(code))
stream = CommonTokenStream(lexer)
parser = iniParser(stream)
tree = parser.start()
print('parsing'.center(80, '-'))
print(tree.toStringTree(recog=parser))
lexer.reset()
self.startStyling(0)
print('lexing'.center(80, '-'))
while True:
t = lexer.nextToken()
print(lexer.ruleNames[t.type-1], repr(t.text))
if t.type != -1:
len_value = len(t.text)
self.setStyling(len_value, t.type)
else:
break
def description(self, style_nr):
return str(style_nr)
if __name__ == '__main__':
app = QApplication([])
v = QsciScintilla()
lexer = QsciIniLexer(v)
v.setLexer(lexer)
v.setText(textwrap.dedent("""\
; Comment outside
[section s1]
; Comment inside
a = 1
b = 2
[section s2]
c = 3 ; Comment right side
d = e
"""))
v.show()
app.exec_()
和运行它,如果一切顺利你应该得到这个结果:
这是我的问题:
- 如您所见,演示的结果离可用还很远,您肯定不希望那样,这确实令人不安。相反,您希望获得与所有 IDE 类似的行为。不幸的是,我不知道如何实现这一点,您将如何修改提供这种行为的代码段?
- 现在我正在尝试模仿与以下快照类似的突出显示:
你可以在那个截图上看到变量赋值的突出显示是不同的(variable=deeppink and values=yellowish)但我不知道如何实现,我试过使用这个稍微修改过的语法:
grammar ini;
start : section (option)*;
section : '[' STRING ']';
option : VARIABLE '=' VALUE;
COMMENT : ';' ~[\r\n]*;
VARIABLE : [a-zA-Z0-9]+;
VALUE : [a-zA-Z0-9]+;
WS : [ \t\n\r]+;
然后将样式更改为:
style = {
"T__0": lst[3],
"T__1": lst[3],
"T__2": lst[3],
"COMMENT": lst[2],
"VARIABLE": lst[0],
"VALUE": lst[1],
"WS": lst[3],
}
但是如果您查看词法分析输出,您会发现 VARIABLE
和 VALUES
之间没有区别,因为在 ANTLR 语法中顺序优先。所以我的问题是,您将如何修改 grammar/snippet 以实现这种视觉效果?
Sctintilla 中的语法高亮是由专用高亮器 类 完成的,它们是词法分析器。解析器不太适合此类工作,因为语法高亮功能必须有效,即使输入包含错误。解析器是一种验证输入正确性的工具 - 2 个完全不同的任务。
所以我建议您停止考虑为此使用 ANTLR4,只需使用现有的 Lex 之一 类 并为您想要突出显示的语言创建一个新的。
问题在于词法分析器需要对上下文敏感:=
左侧的所有内容都需要是变量,而其右侧是值。您可以使用 ANTLR 的 lexical modes 来完成此操作。您首先将连续的 non-spaces 分类为变量,当遇到 =
时,您进入 value-mode。当在 value-mode 内时,只要遇到换行符就会退出此模式。
请注意,词法模式仅适用于词法分析器语法,不适用于您现在拥有的组合语法。此外,对于语法突出显示,您可能只需要词法分析器。
这是一个如何工作的快速演示(将其粘贴在名为 IniLexer.g4
的文件中):
lexer grammar IniLexer;
SECTION
: '[' ~[\]]+ ']'
;
COMMENT
: ';' ~[\r\n]*
;
ASSIGN
: '=' -> pushMode(VALUE_MODE)
;
KEY
: ~[ \t\r\n]+
;
SPACES
: [ \t\r\n]+ -> skip
;
UNRECOGNIZED
: .
;
mode VALUE_MODE;
VALUE_MODE_SPACES
: [ \t]+ -> skip
;
VALUE
: ~[ \t\r\n]+
;
VALUE_MODE_COMMENT
: ';' ~[\r\n]* -> type(COMMENT)
;
VALUE_MODE_NL
: [\r\n]+ -> skip, popMode
;
如果您现在 运行 以下脚本:
source = """
; Comment outside
[section s1]
; Comment inside
a = 1
b = 2
[section s2]
c = 3 ; Comment right side
d = e
"""
lexer = IniLexer(InputStream(source))
stream = CommonTokenStream(lexer)
stream.fill()
for token in stream.tokens[:-1]:
print("{0:<25} '{1}'".format(IniLexer.symbolicNames[token.type], token.text))
您将看到以下输出:
COMMENT '; Comment outside'
SECTION '[section s1]'
COMMENT '; Comment inside'
KEY 'a'
ASSIGN '='
VALUE '1'
KEY 'b'
ASSIGN '='
VALUE '2'
SECTION '[section s2]'
KEY 'c'
ASSIGN '='
VALUE '3'
COMMENT '; Comment right side'
KEY 'd'
ASSIGN '='
VALUE 'e'
附带的解析器语法可能如下所示:
parser grammar IniParser;
options {
tokenVocab=IniLexer;
}
sections
: section* EOF
;
section
: COMMENT
| SECTION section_atom*
;
section_atom
: COMMENT
| KEY ASSIGN VALUE
;
这将在以下解析树中解析您的示例输入:
我已经用 C++ 实现了类似的东西。
https://github.com/tora-tool/tora/blob/master/src/editor/tosqltext.cpp
Sub-classed QScintilla class 并根据 ANTLR 生成的源实施了自定义词法分析器。
你甚至可以使用 ANTLR 解析器(我没有使用它),QScitilla 允许你有多个分析器(具有不同的权重),因此你可以定期对文本进行一些语义检查。在 QScintilla 中不能轻易完成的是将 token 与一些额外的数据相关联。
我正在努力学习 ANTLR4,但我的第一个实验已经遇到了一些问题。
这里的目标是学习如何使用 ANTLR 来语法高亮 QScintilla 组件。为了练习一下,我决定学习如何正确突出显示 *.ini
个文件。
首先,为了 运行 mcve,您需要:
- 下载 antlr4 并确保其正常工作,阅读主站点上的说明
- 安装python antlr 运行时,只需执行:
pip install antlr4-python3-runtime
生成
ini.g4
的lexer/parser:grammar ini; start : section (option)*; section : '[' STRING ']'; option : STRING '=' STRING; COMMENT : ';' ~[\r\n]*; STRING : [a-zA-Z0-9]+; WS : [ \t\n\r]+;
来自 运行宁 antlr ini.g4 -Dlanguage=Python3 -o ini
最后保存
main.py
:import textwrap from PyQt5.Qt import * from PyQt5.Qsci import QsciScintilla, QsciLexerCustom from antlr4 import * from ini.iniLexer import iniLexer from ini.iniParser import iniParser class QsciIniLexer(QsciLexerCustom): def __init__(self, parent=None): super().__init__(parent=parent) lst = [ {'bold': False, 'foreground': '#f92472', 'italic': False}, # 0 - deeppink {'bold': False, 'foreground': '#e7db74', 'italic': False}, # 1 - khaki (yellowish) {'bold': False, 'foreground': '#74705d', 'italic': False}, # 2 - dimgray {'bold': False, 'foreground': '#f8f8f2', 'italic': False}, # 3 - whitesmoke ] style = { "T__0": lst[3], "T__1": lst[3], "T__2": lst[3], "COMMENT": lst[2], "STRING": lst[0], "WS": lst[3], } for token in iniLexer.ruleNames: token_style = style[token] foreground = token_style.get("foreground", None) background = token_style.get("background", None) bold = token_style.get("bold", None) italic = token_style.get("italic", None) underline = token_style.get("underline", None) index = getattr(iniLexer, token) if foreground: self.setColor(QColor(foreground), index) if background: self.setPaper(QColor(background), index) def defaultPaper(self, style): return QColor("#272822") def language(self): return self.lexer.grammarFileName def styleText(self, start, end): view = self.editor() code = view.text() lexer = iniLexer(InputStream(code)) stream = CommonTokenStream(lexer) parser = iniParser(stream) tree = parser.start() print('parsing'.center(80, '-')) print(tree.toStringTree(recog=parser)) lexer.reset() self.startStyling(0) print('lexing'.center(80, '-')) while True: t = lexer.nextToken() print(lexer.ruleNames[t.type-1], repr(t.text)) if t.type != -1: len_value = len(t.text) self.setStyling(len_value, t.type) else: break def description(self, style_nr): return str(style_nr) if __name__ == '__main__': app = QApplication([]) v = QsciScintilla() lexer = QsciIniLexer(v) v.setLexer(lexer) v.setText(textwrap.dedent("""\ ; Comment outside [section s1] ; Comment inside a = 1 b = 2 [section s2] c = 3 ; Comment right side d = e """)) v.show() app.exec_()
和运行它,如果一切顺利你应该得到这个结果:
这是我的问题:
- 如您所见,演示的结果离可用还很远,您肯定不希望那样,这确实令人不安。相反,您希望获得与所有 IDE 类似的行为。不幸的是,我不知道如何实现这一点,您将如何修改提供这种行为的代码段?
- 现在我正在尝试模仿与以下快照类似的突出显示:
你可以在那个截图上看到变量赋值的突出显示是不同的(variable=deeppink and values=yellowish)但我不知道如何实现,我试过使用这个稍微修改过的语法:
grammar ini;
start : section (option)*;
section : '[' STRING ']';
option : VARIABLE '=' VALUE;
COMMENT : ';' ~[\r\n]*;
VARIABLE : [a-zA-Z0-9]+;
VALUE : [a-zA-Z0-9]+;
WS : [ \t\n\r]+;
然后将样式更改为:
style = {
"T__0": lst[3],
"T__1": lst[3],
"T__2": lst[3],
"COMMENT": lst[2],
"VARIABLE": lst[0],
"VALUE": lst[1],
"WS": lst[3],
}
但是如果您查看词法分析输出,您会发现 VARIABLE
和 VALUES
之间没有区别,因为在 ANTLR 语法中顺序优先。所以我的问题是,您将如何修改 grammar/snippet 以实现这种视觉效果?
Sctintilla 中的语法高亮是由专用高亮器 类 完成的,它们是词法分析器。解析器不太适合此类工作,因为语法高亮功能必须有效,即使输入包含错误。解析器是一种验证输入正确性的工具 - 2 个完全不同的任务。
所以我建议您停止考虑为此使用 ANTLR4,只需使用现有的 Lex 之一 类 并为您想要突出显示的语言创建一个新的。
问题在于词法分析器需要对上下文敏感:=
左侧的所有内容都需要是变量,而其右侧是值。您可以使用 ANTLR 的 lexical modes 来完成此操作。您首先将连续的 non-spaces 分类为变量,当遇到 =
时,您进入 value-mode。当在 value-mode 内时,只要遇到换行符就会退出此模式。
请注意,词法模式仅适用于词法分析器语法,不适用于您现在拥有的组合语法。此外,对于语法突出显示,您可能只需要词法分析器。
这是一个如何工作的快速演示(将其粘贴在名为 IniLexer.g4
的文件中):
lexer grammar IniLexer;
SECTION
: '[' ~[\]]+ ']'
;
COMMENT
: ';' ~[\r\n]*
;
ASSIGN
: '=' -> pushMode(VALUE_MODE)
;
KEY
: ~[ \t\r\n]+
;
SPACES
: [ \t\r\n]+ -> skip
;
UNRECOGNIZED
: .
;
mode VALUE_MODE;
VALUE_MODE_SPACES
: [ \t]+ -> skip
;
VALUE
: ~[ \t\r\n]+
;
VALUE_MODE_COMMENT
: ';' ~[\r\n]* -> type(COMMENT)
;
VALUE_MODE_NL
: [\r\n]+ -> skip, popMode
;
如果您现在 运行 以下脚本:
source = """
; Comment outside
[section s1]
; Comment inside
a = 1
b = 2
[section s2]
c = 3 ; Comment right side
d = e
"""
lexer = IniLexer(InputStream(source))
stream = CommonTokenStream(lexer)
stream.fill()
for token in stream.tokens[:-1]:
print("{0:<25} '{1}'".format(IniLexer.symbolicNames[token.type], token.text))
您将看到以下输出:
COMMENT '; Comment outside'
SECTION '[section s1]'
COMMENT '; Comment inside'
KEY 'a'
ASSIGN '='
VALUE '1'
KEY 'b'
ASSIGN '='
VALUE '2'
SECTION '[section s2]'
KEY 'c'
ASSIGN '='
VALUE '3'
COMMENT '; Comment right side'
KEY 'd'
ASSIGN '='
VALUE 'e'
附带的解析器语法可能如下所示:
parser grammar IniParser;
options {
tokenVocab=IniLexer;
}
sections
: section* EOF
;
section
: COMMENT
| SECTION section_atom*
;
section_atom
: COMMENT
| KEY ASSIGN VALUE
;
这将在以下解析树中解析您的示例输入:
我已经用 C++ 实现了类似的东西。
https://github.com/tora-tool/tora/blob/master/src/editor/tosqltext.cpp
Sub-classed QScintilla class 并根据 ANTLR 生成的源实施了自定义词法分析器。
你甚至可以使用 ANTLR 解析器(我没有使用它),QScitilla 允许你有多个分析器(具有不同的权重),因此你可以定期对文本进行一些语义检查。在 QScintilla 中不能轻易完成的是将 token 与一些额外的数据相关联。