QScintilla: пишем свой лексер

в 12:09, , рубрики: linux, qscintilla, qt, Qt Software, Ubuntu, Программирование, метки: , , ,

Привет!

Это 2я статья цикла про QScintilla. Первая здесь. Для начала хочу сказать огромное спасибо всем, кто вывел меня из кармоямы! А теперь можно начать. Что мы сегодня будем делать? Мы напишем лексер для Assembler'а! «В коробке» его нету — не беда, напишем сами! Процесс довольно длительный, поэтому я буду немного меньше расписывать и комментировать. Тем более я не знаю язык ассемблера, так что лексер будет до ужаса примитивный и будет разрисовывать только комманды и комментарии.

Как сказал Гагарин — «Поехали!»

На данном этапе я полагаю что вы уже прочитали первую статью (смотри выше) и знаете как создать редактор и его кастомить. Продолжаем с того же проекта, который мы редактировали прошлый раз и с тем же набором инструментов (тут и поймались те, кто не читали первую статью).

Задача и идеи

Написать рассцветку синтаксиса языка Assembler'а. Но лексера (схемы рассцветки) в QScintilla по умолчанию нету. Ничего, напишем. Для этого есть класс QsciLexerCustom (по-секрету: у него есть виртуальные методы).

Заготовка

Давайте заготовим тесто наш лексер. Заготовка выглядит так:

class QsciLexerASM : public QsciLexerCustom
{
    Q_OBJECT
public:
    explicit QsciLexerASM(QObject *parent = 0);
    ~QsciLexerASM();

    //! Разбор текста на стили (самая важная функция)
    void styleText(int start, int end);
    //! Наши функции рассцветки (вызывается из styleText())
    void paintKeywords(const QString &source, int start);
    void paintComments(const QString &source, int start);
    //! Название языка (в нашем случае ASM
    const char * language() const;
    //! Цвета для стилей
    QColor defaultColor(int style) const;
    //! Описание стиля
    QString description(int style) const;

    //! Список стилей
    enum {
        Default = 0,
        Comment = 1,
        Keyword = 2
    };
private:
    QsciLexerASM(const QsciLexerASM &);
    QsciLexerASM &operator=(const QsciLexerASM &);
    QStringList keywordsList;
    
};

Некоторые перечисленные тут функции нужны не нам, а QScintilla. Но если они кому-то нужны, значит реализуем их в mainwindow.h:

QString QsciLexerASM::description(int style) const
{
    switch(style) {
        case Default:
            return "Default";
        case Comment:
            return "Comment";
        case Keyword:
            return "Keyword";
    }
    return QString(style);
}

const char * QsciLexerASM::language()
{
    return "ASM";
}

Я думаю, тут все понятно. Пойдем дальше. Теперь надо реализовать дефолтный цвет раскраски для всех стилей:

QColor QsciLexerASM::defaultColor(int style) const
{
    switch(style) {
        case Comment:
            return Qt::darkGreen;
        case Keyword:
            return Qt::blue;
    }
    return Qt::black;
}

А вот только теперь мы будем раскрашивать наш код. Для этого нам надо знать какие есть в ассемблере ключевые слова. В википедии я нашел немного. Для этого зададим наш keywordsList в конструкторе:

QsciLexerASM::QsciLexerASM(QObject *parent) :
    QsciLexerCustom(parent)
{
    keywordsList << "mov" << "add"  << "sub" << "imul" <<
                    "or"  << "and"  << "xor" << "shr"  <<
                    "jmp" << "loop" << "ret" << "int";
}

Продолжим. Теперь надо быть очень аккуратным — мы пришли к месту где мы будем раскрашивать синтаксис! Я приведу листинг кода функции styleText(), а потом его коротко докомментирую:

void QsciLexerASM::styleText(int start, int end)
{
    if(!editor())
        return;

    // получим кусок сорца который нам надо разукрасить
    char * data = new char[end - start + 1];
    // обращение к Scintilla
    editor()->SendScintilla(QsciScintilla::SCI_GETTEXTRANGE, start, end, data);
    QString source(data);
    delete [] data;
    if(source.isEmpty())
        return;

    // Начнем разрисовывать!
    paintKeywords(source, start);
    paintComments(source, start);
}

Один момент. Две последние строчки метода. Функции paintKeyword() и paintComments() занимаются расветкой ключевых слов и комментариев соответственно. Мы вызываем разукраску команд, а только потом комментариев. Почему? Догадайтесь.

Теперь все хорошо. Почти все методы реализованы. Осталось только реализовать paintKeyword() и paintComments():

void QsciLexerASM::paintKeywords(const QString &source, int start)
{
    foreach(QString word, keywordsList) { // перебираем ключевые слова
        if(source.contains(word)) {
            int p = source.count(word); // считаем вхождения
            int index = 0; // начнем считать индексы c 0
            while(p != 0) {
                int begin = source.indexOf(word, index); // считаем индекс вхождения
                index = begin+1; // задаем точку отсчета для следущей итерации

                startStyling(start + begin); // начнем стилизировать с индекса вхождения
                setStyling(word.length(), Keyword); // для длины word.length задаем стиль Keyword
                startStyling(start + begin); // заканчиваем стилизацию

                p--;
            }
        }
    }
}

void QsciLexerASM::paintComments(const QString &source, int start)
{
    int p = source.count(";"); // посчитаем вхождения знака комментария
    if(p == 0)
        return;
    int index = 0; // начнем считать индексы ";" с 0
    while(p != 0) {
        int begin = source.indexOf(";", index); // считаем индекс вхождения
        int length=0; // длина комментария
        index = begin+1; // задаем точку отсчета для следущей итерации

        for(int k = begin; source[k] != 'n'; k++) // ведь source необязательно одна строка
            length++;

        startStyling(start + begin); // начнем стилизировать с индекса вхождения
        setStyling(length, Comment); // для длины length задаем стиль Comment
        startStyling(start + begin); // заканчиваем стилизацию

        p--;
    }
}

Готово. Теперь наш лексер умеет что-то делать. Можно компилировать. Да, можно. Да, определенно можно компилировать.

Результат

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

Вот что у нас получилось:

QScintilla: пишем свой лексер

Видно некоторые баги, например в слове «dword» подсвечивается вхождение «or» как ключевое. Но это все поправимо.

А вот и наше творение: qscintilla-demo-2

Спасибо, и еще раз извиняюсь за выбор языка.

Автор: namespace


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js