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

  • Tutorial
Привет, хабр!

UPD: третья часть цикла.

Это вторая статья цикла про 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--;
    }
}


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

Результат


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

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




Видно некоторые баги, например в слове «dword» подсвечивается вхождение «or» как ключевое. Как подсказал товарищ TheHorse — нужно проверять, что вхождение ключевых слов в тексте, разделено сепараторами. Но к концу написания статьи я так устал, что решил не фиксить этот баг, а оставить его фикс на читателей:)

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

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

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 19

    +1
    Спасибо за Вашу серию постов! Я вот сейчас пишу небольшой вспомогательный инструмент для себя на Qt, но использую там QSyntaxHighlighter, думаю изменить на QScintilla, т.к. по возможностям покруче будет, просто не знал про него в Qt до Ваших постов. Со Scintilla я знаком по wxPython, там есть виджет stc.StyledTextCtrl, как раз основанный Scintilla очень очень и удобный, уже успел заценить все его плюшки прямо из коробки :)
      +1
      Судя по последней картинке, как-то не очень правильно ключевые слова ищутся. Нужно проверять, что вхождение ключевых слов в тексте, разделено сепараторами.

      В слове dword, or подсвечиваться не должен.

      В остальном все хорошо, спасибо.
        +1
        Простите за пунктуацию, у меня тоже проблемы с сепараторами(.
          +2
          Я написал про этот баг под картинкой:
          >> Видно некоторые баги, например в слове «dword» подсвечивается вхождение «or» как ключевое
            +1
            Приношу свои извинения, не заметил. Рекомендую все-же указать алгоритм избавления от этой баги.
          +1
          >«В коробке» его нету — не беда, напишем сами!

          Для тех лексеров, поддержки которых нет «в коробке» QScintilla, но есть «в коробке» оригинальной Scintilla, всё гораздо проще — нужно лишь написать небольшую обёртку. Ну или большую — в зависимости от языка :)
          Несколько примеров можно посмотреть тут: github.com/Mezomish/juffed/tree/master/src/app/qsci/lexers
            0
            Спасибо. Но я показывал лишь пример реализации.
              0
              Я ни на секунду не осправивал полезность вашего примера :) Я просто показал ещё одну опцию для тех, кому понадобится реализовать свой собственный лексер.

              Если необходимый синтаксис поддерживается оригинальной Scintilla — можно использовать оба способа, если же не поддерживается — то тут только ваш способ, без вариантов.
            0
            Кстати, хочу попросить совета. Есть необходимость реализовать WYSIWYG-редактор текста с плюшками (подсветка синтаксиса, замена последовательностей символов на картинки (что-то вроде графической мнемоники), при необходимости использование кастомных растровых шрифтов и т.п.). Подойдёт ли для этих целей Scintilla или QScintilla? Насколько я знаю, стандартные Qt'шные контролы в этом плане весьма и весьма тормознутые.
              0
              Вполне подойдет. Но зависит непосредственно от проекта. Может быть такой проект, что scintilla вообще не подходит, а может быть что только она и годится.
                0
                А она разве поддерживает «замена последовательностей символов на картинки»?
                  0
                  Что вы имеете ввиду? Насколько мне известно, можно использовать scintilla как угодно. А вот qscintilla надо кастомить немного. Надо посмотреть. Если QsciScintilla от QTextEdit, то возможно в теории.
                    0
                    Насколько я понял требования, необходима возможность вставлять картинки прямо в текст, аналогично тому, как вставляются смайлики в окне чата.
                      0
                      Конечно это можно сделать. Ведь так или иначе, qscintilla наследует QTextEdit. А он поддерживает html.
                        0
                        Так она же, видимо, переопределяет часть функционала QTextEdit, используя Scintill'у. А HTML мне не подходит, ведь цель — не вывести текст с графикой, а позволять редактировать его в WYSIWYG-режиме. В этом случае HTML будет сторонней примесью.
                          0
                          >Конечно это можно сделать. Ведь так или иначе, qscintilla наследует QTextEdit.

                          М-м-м?

                          class QSCINTILLA_EXPORT QsciScintilla : public QsciScintillaBase
                          {
                              Q_OBJECT
                              ...
                          


                          class QSCINTILLA_EXPORT QsciScintillaBase : public QAbstractScrollArea
                          {
                              Q_OBJECT
                              ...
                          
                            0
                            Упс. Я был о ней большего мнения. Это… это ужасно, господа.
                              0
                              Ну, знаете, это смотря с какой стороны посмотреть. QTextEdit с QSyntaxHighlighter-ом начинает захлёбываться на файлах даже в пару мегабайт, в то время как QScintilla жуёт без проблем и в 10 раз бОльшие файлы.
                              Ну, по крайней мере оно так было в те стародавние времена, когда JuffEd ещё был основан на QTextEdit, может сейчас и поправили уже.
                          0
                          Да, именно так. Удачная аналогия. При этом в сам текст должны вставляться соответствующие картинкам последовательности символов. В случае смайликов это были бы их текстовые представления — ":)", ";)" и т.п.

                Only users with full accounts can post comments. Log in, please.