Как выделить QScintilla с помощью ANTLR4? - PullRequest
1 голос
/ 07 июня 2019

Я пытаюсь выучить ANTLR4 , и у меня уже есть некоторые проблемы с моим первым экспериментом.

Цель здесь - узнать, как использовать ANTLR для синтаксиса, выделите QScintillaсоставная часть.Чтобы немного попрактиковаться, я решил научиться правильно выделять файлы *.ini.

Для начала, для запуска mcve вам потребуется:

  • Скачайте antlr4 и убедитесь, что он работает, прочитайте инструкции на главном сайте
  • Установите Python Antlr Runtime, просто выполните: pip install antlr4-python3-runtime
  • Сгенерируйте лексер / парсериз ini.g4:

    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_()
    

и запустите его, если все прошло хорошо, вы должны получить такой результат:

showcase

Вот мои вопросы:

  • Как вы видите, результат демонстрации далек от того, чтобы его можно было использовать, вы определенно не хотите этого, это действительно беспокоит.Вместо этого вы хотели бы получить подобное поведение, чем все IDE там.К сожалению, я не знаю, как этого добиться, как бы вы изменили фрагмент кода, обеспечивающий такое поведение?
  • Сейчас я пытаюсь имитировать подсветку, аналогичную приведенному ниже снимку:

enter image description here

Вы можете видеть на этом скриншоте, выделение отличается при назначении переменных (переменная = deeppink и значения = желтоватый), но я не знаю, как этого добитьсяЯ попытался использовать эту слегка измененную грамматику:

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.Поэтому мой вопрос: как бы вы изменили грамматику / фрагмент для достижения такого визуального вида?

Ответы [ 3 ]

1 голос
/ 09 июня 2019

Проблема в том, что лексер должен быть контекстно-зависимым: все в левой части = должно быть переменной, а справа от нее - значением.Вы можете сделать это с помощью лексических режимов ANTLR .Вы начинаете с того, что классифицируете последовательные непробелы как переменные, а когда встречаете =, вы переходите в свой режим значений.Находясь внутри режима значений, вы выходите из этого режима всякий раз, когда сталкиваетесь с разрывом строки.

Обратите внимание, что лексические режимы работают только в грамматике лексера, а не в комбинированной грамматике, которая у вас есть сейчас.Кроме того, для подсветки синтаксиса вам, вероятно, нужен только лексер.

Вот краткая демонстрация того, как это может работать (вставьте его в файл с именем 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
 ;

который будет анализировать ваш пример ввода в следующем дереве разбора:

enter image description here

1 голос
/ 10 июня 2019

Я уже реализовал нечто подобное в C ++.

https://github.com/tora-tool/tora/blob/master/src/editor/tosqltext.cpp

Подклассифицированный класс QScintilla и реализованный пользовательский Lexer на основе сгенерированного источника ANTLR.

Вы можете даже использовать анализатор ANTLR (я не использовал его), QScitilla позволяет вам иметь более одного анализатора (с разным весом), поэтому вы можете периодически выполнять некоторую семантическую проверку текста. В QScintilla нелегко связать токен с некоторыми дополнительными данными.

0 голосов
/ 09 июня 2019

Подсветка синтаксиса в Sctintilla выполняется выделенными классами подсветки, которые являются лексерами. Синтаксический анализатор не очень подходит для такой работы, потому что функция подсветки синтаксиса должна работать, даже если ввод содержит ошибки. Парсер - это инструмент для проверки правильности ввода - 2 абсолютно разные задачи.

Поэтому я рекомендую вам перестать думать об использовании ANTLR4 для этого и просто взять один из существующих классов Lex и создать новый для языка, который вы хотите выделить.

...