Игорь Русских
ruiv@uic.nnov.ru
Привет. Тут вот недавно вышел новый колорер, и решил я маленько его подкомментировать. Вообще, я давно заметил очень странное явление - колорер выходит сам. И меня тут заинтересовало, как это он так выходит, по своей воле. Наверное все-таки я сам его делаю, только не замечаю как. Необычная это программа вышла, вообще на программу-то не похожа.
Последняя на сегодня версия FreeCraze включает в себя помимо
привычного вам плагина еще и программу вьювер вместе с генератором цветных
HTML-листингов кодов. Но это не главное. Главное то, что почему-то в архиве
вместе со всем этим оказались все исходники колорера. Как всегда, что бы я
не делал, меня опять начали, мягко выражаясь, обругивать. То у тебя
документация левая и неправильная, непонятно в ней ничего, то сам колорер не
работает из-за его большой неправильности, то вот теперь вроде как
начинаются упреки о том, что исходники недостаточно ``лиричны''. Ну, то бишь
непонятно в них ничего. Кто-то сказал, что в хорошем проекте исходники должны
состоять из комментариев, слегка разбавленных инструкциями.
Ну что-ж, у меня поганый проект. Комментариев там нет вообще.
Ну и вот тут я попытаюсь кой-как это все хозяйство прокомментировать. Все-таки колорер вроде как библиотека - захотите вы его использовать у себя где-нить, а как, чем, какой стороной - не понятно. Ну, во-первых не надо думать, что колорер - это что-то сложное, сверх заумное и вообще из компьютерной магии. Не совсем так. На самом деле все можно просто понять, тем более, что для того чтобы использовать его как библиотеку в исходниках вовсе не обязательно штудировать их от корки до корки. Хотя, конечно, и это может быть полезно, так как вообще говоря колорер состоит из нескольких модулей, которые вообще-то и сами-по-себе представляют некоторую ценность.
В принципе вся либа колорера состоит из нескольких модулей,
которые могут использоваться как вместе, так и по-отдельности.
Плагин колорер - это всего лишь интерфейсная оболочка, такая же, как и
лежащий вместе с ним colorer.exe (их исходники находятся
соответственно в каталогах ( /src/colorer/ и
/src/colorlib/ ). Все разделяемые модули находятся в каталоге
/src/shared/.
Первый модуль и самый простой - это модуль парсинга SGML(HTML) подобного
текста ( /src/shared/sgml/ ). На самом деле на него можно не
обращать внимания, так как он используется колорером для внутренних целей
полностью автоматически. Единственно для чего он вам может понадобиться -
это для работы с расширенными параметрами в описаниях блоков
<type> скриптов HRC. Так, всякие дополнительные параметры
навроде цвета текста и фона по умолчанию, подсветки скобок и всей остальной
чуши - реализуются модулем плагина. Иными словами сама библиотека по
своим структурам никакие эти параметры не поддерживает. Если вам нужна
аналогичная функциональность, то использовать SGML-парсер вам придется.
Но, повторяю, там ничего сложного - единственный класс, который по методу
parse генерирует дерево, преобразуя параметры, названия тэгов,
и сохраняя линейный текст и комментарии. Как сгенерированное дерево использовать
вы можете понять из описания класса CSgmlEl и из исходников
плагина ( /src/colorer/pcolorer.cpp ).
методы и свойства класса CSgmlEl:
bool parse(char *src,int sz);parent(), next(), prev(), child()char *name; char *content; int contentsz;
Немногим более сложным является модуль регэкспов.
( /src/shared/regexp/ )
В принципе его вам вообще
можно не понимать (тем более многие люди испытывают какой-то животный страх
при одном слове ``регэксп'' и отмахиваются всеми шестью руками с саблями,
когда им начинаешь пытаться объяснить что ничего сложного нет, и основы
можно понять прямо сейчас). Но очень часто регулярные выражения можно
использовать как отдельную возможность по расширению и использовать
их в областях никак не связанных с расцветкой (к примеру поиск, замена).
На самом деле если вы уже знаете что это такое, то всунуть эти коды в вашу
программу не составит никакой сложности. У класса CRegExp фактически два
основных метода - SetExpr и Parse. Все остальное - это их вариации.
К слову сказать коды моих регэкспов полностью портируемы,
и работают даже под DOS :)
public-методы класса CRegExp:
bool SetExpr(char *Expr);bool SetNoMoves(bool Moves);bool Parse(char *Str,char *Sol, char *Eol, PMatches Mtch, int Moves = -1);bool Parse(char *Str, PMatches Mtch);bool SetBkTrace(char *Str, PMatches Trace);bool isok();Несколько файлов в других каталогах - это просто вспомогательные функции. Там же лежит plugins.hpp, дополнительные коды для работы с реестром, числами. Мелочь короче.
Ну вот добрались до самого главного. В каталоге
/src/shared/colorer/ лежат главные модули библиотеки по
парсингу и расцветке кода. Эти коды делятся на два модуля: первый (ccdata.cpp)
используется для загрузки и компиляции HRC-базы, второй (ccolorer.cpp) -
непосредственно для динамической расцветки текстов. Соответственно в разделяемом
файле classes.h (который вы тоже должны подключить в свой проект) описаны два
главных класса - CColorData и CColorer.
CColorData - класс, ответственный за загрузку и компиляцию HRC-скриптов. Хочу подчеркнуть, что текущая структура скриптов (имеется в виду дерево каталогов и организация некоторых соглашений по использованию цветов, определений, постановок) совсем не обязательны и не фиксированы. Иными словами, вы можете в вашем проекте взять из HRC-кодов только необходимые вам файлы, скомпоновать их в один большой (с расчетом на то, что пользователь не будет в вашей программе менять конфигурацию колорера) и подавать на парсинг именно этот файл. Замечу также, что значения цветов, используемых для раскраски вовсе не фиксированы. Смысл в том, что вы можете однозначно сопоставить каждой лексеме в расцвечиваемом коде определенный индекс цвета (ведь их может быть больше 16 - максимум 0xFFFF текста и столько же цветов фона - ограничения консоли на цвета не играют для библиотеки никакой роли), а уже в своей программе давать возможность пользователю сопоставлять каждой лексеме любой требуемый цвет. Надеюсь идея дошла. Теперь обзор основных методов:
CColorData(char *path, char *ef);CColorData(char *data, int len, char *ef);Заметьте, что вся база не загружается - грузится только базовый файл. Реальная компиляция схем из других файлов происходит при вызове метода loadtype. Из всего этого следует, что если вы все схемы и определения поместите в один файл (для компактности к примеру), или вообще будете вызывать второй вариант конструктора, когда никакого файла может и не существовать (данные к примеру могут храниться в ресурсах вашего проекта), все преимущество частичной загрузки баз сходит на нет - так как все схемы начнут компилироваться сразу из конструктора.
bool isok();unsigned int GetColor(char *name);PType enumtypes(PType next);PType seltype(char *fname, char *fstline, int no = 0);bool loadtype(PType type);Три последних типа используют открытую структуру
SType (*PType). Это связанный список структур, и в каждой
из них хранится информация о соответствующем типе, включая указатель на
CSgmlEl объект, который можно использовать для получения дополнительных
атрибутов. Я бы рекомендовал использовать ее как readonly :)
Этот класс является дополнением к классу CColorData, и если тот ничего не знает о нем, то CColorer полагает существование загруженного класса CColorData - из него он черпает информацию о непосредственной расцветке, коей сам и занимается. В отличие от класса CColorData вы можете создавать несколько представителей класса CColorer - к примеру для каждого окна в вашем редакторе. Это необходимо, так как этот класс варится в собственном соку и хранит много информации, зависимой от содержимого расцвечиваемого файла. Сначала маненький обзорчик по сервисам, потом скажу что к чему.
bool SetServices(char*(*gl)(PColorer, int, int*), void (*vr)(PColorer));bool SetColoringSrc(PType type);PLineHL SetMaxLines(int num);SLineHL - это структура, которая определяет один
элемент расцвеченного текста. Во время работы вы получаете указатель
на массив таких структур размером вами же заданным. Этот размер чаще
всего - количество одновременно видимых строк на экране - или можете
взять с запасом если это значение у вас меняется. Первый элемент
всегда пустой, и содержит внутреннюю информацию. Лучше его не трогать
- ровно как и всю структуру.
Собственно данные начинаются с элемента массива PLines[1].
Каждый элемент - строка, номер которой вы задали при вызове функции
парсинга текста. В каждом элементе содержится указатель next
на следующую такую структуру в строке. Замечу, что в самих элементах
массива информации не содержится - они просто скелет. Вся информация
о расцветке(откуда, докуда, и каким цветом) содержится в списках
динамических структур, связанных указателем next.
bool CompactLines();-h), это может обернуться проблемками.
Этот сервис после вызова гарантирует, что цветовые области будут упорядочены
без перекрытий.
bool ClearCache();void BreakParse();int QuickParse(int from, int num);int FullParse(int from, int fvis, int visnum, int allnum);Ну вот. Примерно значит так. Я думаю элементарный парсинг с использованием QuickParse объяснять не надо - там все линейно и без особых точек. Немного поподробнее надо поговорить о методе FullParse. Не знаю с чего начать объяснять, там любое место связано с другими еще необъясненными. Гмм... Ну первое что надо понять - это соотношения производительности. На примере реализации библиотеки в плагине вы можете видеть, что скорость практически на всех типах файлов приемлема. Ну то-есть жить можно. На некоторых конечно побыстрее, некоторые довольно тормозят - но в общем ничего. И это все на моем стареньком p166mmx. Думаю на любой другой телеге на порядок быстрее. Это зафиксировали. Теперь еще одна вещь - плагин использует наиболее медленный вариант расцветки кода. Конкретнее, при любом нажатии клавиши, даже клавиш курсора, происходит полный парсинг через FullParse текущего экрана. Все это связано с ограничениями ФАРовского внутреннего редактора, и вас не больно должно волновать. Единственно из всего этого хочу до вас донести идею, что при использовании библиотеки целенаправленно в своем проекте вы можете добиться еще больших скоростей. Как минимум вы будете избавлены от работы при каждом перемещении курсора... необходимо контролировать только действия по непосредственной модификации текста (ну или по его прокрутке :))
Теперь что конкретно значит кэширование при работе этот метода. Метод FullParse вы вызываете с двумя парами параметров - фактически внешними границами и внутренними (выраженными в строках). Внутренними границами чаще всего выступают строки, видимые на экране в текущий момент. Это то, на что реально заполняются цветовые структуры. Окаймляющие границы также полностью парсятся - но цветового заполнения не происходит - так как вы их не видите, и вам это просто не надо. После того, как вы первый раз вызвали этот метод, он у себя автоматически сохраняет всю структуру и вложенность схем, которые прошли парсинг. Если в следующий раз этот метод вызовется откуда-нибудь с середины текста, вместо того, чтобы начинать парсинг с нуля - с нулевой схемы (что фактически может быть неправильно - к примеру эта середина попадет внутрь комментария - и это простейший конфликт), функция обратится к кэшу и начнет парсинг с сохраненной в этой позиции схемы - а потом произойдет ``раскрутка'' схем назад - так как они могут быть многократно вложены. В итоге текст раскрасится с учетом блочных комментариев - хотя начинались они где-то раньше, и мы вообще-то не знали где. А расцветка в то же время корректная.
Короче можно сформулировать такое правило: Полученный код гарантированно будет расцвечен, если каждый раз при вызове FullParse происходит зацеп текста - то есть если в прошлый раз вы вызвали
// параметры распределяются следующим образом: // // from ------------+ // .. | // fvis ---------+ +- в этих пределах происходит // .. +--+ заполнение структур с цветовыми // fvis+visnum --+ +- областями // .. | // from+allnum -----+ // FullParse(from, fvis, visnum, allnum);То в следующий раз вы должны передать в параметре from значение, не превышающее предыдущие (from+allnum). В этом случае вы гарантированно не сможете пропустить никакой начинающийся блок, и как следствие получите корректную расцветку. Естественно, это правило не всегда удается выполнить - если юзер к примеру находясь в начале файла вдруг захочет проскочить сразу в конец его, то по этому правилу вам придется для корректности провести парсинг всего текста. На первой-второй тысяче строчек это еще не так критично - а вот потом время начинает неприятно увеличиваться.
В плагине колорер это решено довольно просто - по превышению некоего критического значения числа строк (задается параметром backparse), эти строки просто проскакивают. Как следствие возможны сбои в расцветке. Но! Для того чтобы увидеть такие сбои, надо специально потрудиться и создать специальный файл. Другими словами на обычных файлах этого не заметишь - практически всегда все будет корректно.
// fr - это параметр, задающий откуда начнется кэширование и расцветка
// CurList->endline - это его старое значение.
fr = (EditInfo.TopScreenLine > CurList->endline)?CurList->endline:EditInfo.TopScreenLine;
CurList->endline = EditInfo.TopScreenLine;
// тут есть дополнительная обработка на блочное выделение - нас это не волнует
// ...
// а вот тут глядится, превысила ли длина необходимого для парсинга текста
// некое критическое значение (CurList->typeinfo->backparse)
// если да - то число линий для парсинга уменьшается...
if (EditInfo.TopScreenLine - fr > CurList->typeinfo->backparse)
fr = EditInfo.TopScreenLine - CurList->typeinfo->backparse;
// А это вызывается сам парсер с соответствующими параметрами номеров строк
// Заметьте, что начало реального парсинга (fr) не совпадает с началом
// расцветки текста (EditInfo.TopScreenLine)
CurList->FullParse->FullParse(fr, EditInfo.TopScreenLine, EditInfo.WindowSizeY,
EditInfo.TopScreenLine - fr + EditInfo.WindowSizeY);
Я хочу подчеркнуть, что колорер оптимизирован и рассчитан на то, что
юзер - нормальный программер. То есть чаще всего он работает только в
определенной области файлов, резкие переходы совершает только может быть
на начало да на конец файла. Ну, это уже психология. И это правильно.
При таких предположениях колорер работает стабильно и сверх-быстро.
Некоторые сомнения вызывает только резкий переход на конец файла. Что
происходит в этом случае? Чего делать?
Во-первых вы должны себе ясно уяснить что для абсолютно корректного продолжения расцветки в любом случае вам необходимо провести парсинг всего текста. Если не очень можется вериться, попробуйте рассмотреть схему с одним блочным комментарием, в котором начало и конец представляются одной и той же комбинацией символов. И соответственно файл, в котором периодически эти комментарии повторяются. При изменении чего-то в начале(это что-то может включать в себя и символ начала комментария) и моментального перехода в конец для корректной расцветки вам остается только лишь парсить весь текст. И от этого не убежишь. Если текст большой - соответственно время увеличивается пропорционально.
И в этом случае, и вообще при случайном переходе по тексту(когда
пользователю к примеру взбрело подергать мышкой линейку прокрутки туда-сюда),
вы можете воспользоваться функцией QuickParse для раскраски только
текущего экрана. Естественно может оказаться, что полученная расцветка будет
неверна - но зато работать будет сверх-быстро. Дополнительно к этому вы
можете попытаться запустить в фоновом потоке функцию FullParse (кстати
этого не надо делать для одного и того же объекта - лучше создать два
объекта CColorer, ссылающихся на один тип данных). Если юзер успокоился, то
через пару мгновений у него на экране появится правильная расцветка вместо
показанной сначала некорректной. Если же попался буйный - то вы можете
использовать функцию BreakParse для прерывания обработки во втором потоке
и возобновлении ее опять с нужного места (так как гипотетически очень наглый
юзер может быстро прокручивать текст мышкой да еще и изменять его :-)).
Для этого-же предназначен таинственный второй метод vr в
функции SetServices. Библиотека вызывает его, когда процесс
расцветки закончился либо сам-по-себе либо когда его прервали функцией
BreakParse. Короче это синхронизационное сообщение. По идее эта функция
должна сообщить первому потоку (который отвечает за ввод пользователя), что
расцветка закончилась, можно вызывать парсинг заново (это она может сделать
посредством либо Event'ов, либо посредством любой другой синхронизации - не
мне вас учить).
Для оптимизации по скорости при вводе пользователем единственно что вам надо - это расцвечивать только текущий экран. Как уже было сказано это делается мгновенно. Определенный интерес может представлять оптимизация для линейного просмотра текста, то есть когда пользователь давит down и линейно глядит весь текст. Для того, чтобы каждый раз при изменении не парсить экран заново (в конце концов и это для некоторых типов становится медленным), вы можете запускать парсинг только при сдвиге всего текста. Далее, чтобы при каждом сдвиге не анализировать текст, вы можете поступить следующим образом: выделить массив
// num - количество строк на экране
cl->SetMaxLines( num * 2 );
// расцвечивать, только если добрались до строчки,
// не содержащейся в массиве цветовых областей.
if (topline+num > from + num*2){
from = topline;
cl->FullParse(from, topline, num*2, num*2);
};
Аналогично вы можете выделить массив в три раза больший экрана для кэширования предыдущего экрана и следующего... Увеличение времени парсинга экрана в два-три раза с лихвой окупается скоростью прокрутки текста. Вот такие вот каменья.
Напомни мне если я пел об этом раньше, я все равно не помню ни слова. Напомни мне если я пел об этом раньше - и я спою это снова, я не знаю ничего другого...
Есть два типа людей. Первые могут воспринимать колорер в стандартном дистрибутивном виде, вторые не могут. Соответственно первые (куда вхожу и я) могут воспринимать гамму цветов колорера, вторые не могут. Проблема вторых известна как синдром Рождественской Ёлки. Думаю, здесь имеют место глубокие инфантильные связи, первично обозначившиеся в домладенческом периоде. Причем физиологию уже не поменять. Я привык теперь (думаю как и многие из вас) к тому, что можно находить простейшие ошибки и опечатки уже на этапе набивки кода. И, вообще говоря, при написании HRC кодов я стремлюсь именно к этому. Основное правило гласит, что главным является не корректно расцвечиваемый правильный код, а ясно видимый и различимый неправильный код. Хотя, конечно, первое тоже важно. Ну и прежде чем хандрить по поводу рождественской гирлянды попробуйте различные варианты настройки цветов. Возможно, что даже какой-то из дистрибутивных вам понравится. Ну а если нет, то никто не мешает вам определить свою цветовую схему - на вкус и, главное, цвет товарищей нет...
Ну вот, думаю выложил все что смог, чего непонятно - спрашивайте.
Cail Lomecb
Sat 25 Mar 2000
Если б вы знали, как мне надоел скандал,