Pull to refresh

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

Reading time4 min
Views6.3K
Привет, хабр!

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

Спасибо, и еще раз извиняюсь за выбор языка.
Tags:
Hubs:
+16
Comments19

Articles