Привет!
Это 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--;
}
}
Готово. Теперь наш лексер умеет что-то делать. Можно компилировать. Да, можно. Да, определенно можно компилировать.
Результат
С ассемблером я не работал. Поэтому дико извиняюсь. Не знаю даже базовые принипы работы с ним. Но, я посчитал что он отлично подходит как пример к этой статье.
Вот что у нас получилось:
Видно некоторые баги, например в слове «dword» подсвечивается вхождение «or» как ключевое. Но это все поправимо.
А вот и наше творение: qscintilla-demo-2
Спасибо, и еще раз извиняюсь за выбор языка.
Автор: namespace