Тексты не приглажены, а взяты как есть из проекта. Поэтому, возможно, Вам придется кое-что не нужное удалять от туда самостоятельно.
Для выделения из программы баз данных бизнес логики в отдельный программный слой, который, конечно должен иметь возможность полностью настраиваться без перекомпиляции программы, можно использовать какой-нибудь интерпретатор. Тогда появится возможность просто менять программу для интерпретатора и тем самым менять бизнес логику программы. Давайте посмотрим, как можно сделать простейший интерпретатор. Для простоты синтаксиса, а значит и программной реализации, мы не будем придерживаться какого-то известного языка программирования. Назовем наш интерпретатор калькулятором – это нейтрально и непритязательно, так сказать без особых амбиций…
Калькулятор представляет собой двухпроходный интерпретатор, способный выполнять математические, логические, и другие операции, вызывать встроенные или внешние функции. Встроенные функции заложены в калькулятор при его создании, а внешние – создаются программистом при создании приложения. Обычно внешние функции нужны для выполнения действий над данными, которые обрабатывает программа, поэтому приходится каким-то образом дать доступ калькулятора к этим данным, что и вызвало появление внешних функций.
В калькуляторе используется простой синтаксис без понятия блока программного кода и области видимости. Т.е. все переменные являются глобальными, так же нет блоков, наподобие begin … end или { … }, которые присутствуют в языках высокого уровня Паскаль и C++. Несмотря на свои скромные языковые конструкции, калькулятор может выполнять все те же действия, что и вышеизложенные языки, за исключением определения типов пользователя.
Такой синтаксис не случаен. Дело в том, что область применения калькулятора – это обработка событий, вызванных действиями пользователя, и реакций на происходящие события в программе. Время реакции на эти события должно быть минимальным, поэтому процесс пре компиляции и выполнения должен проходить быстро, что требует приближения синтаксиса к оптимальному с точки зрения машины, а не человека. Но, т.к. в этих случаях обычно очень сложных программ писать не приходится, то с некоторой бедностью синтаксиса можно смирится.
Алфавит встроенного калькулятора. Алфавит встроенного калькулятора включает в себя следующие символы:
Все буквы латинского алфавита.
Цифры от 0 до 9
Специальные символы:
“ - обрамляет строковую константу
; - завершает оператор
: - начало имени метки
, - разделяет параметры, передаваемые функциям
+ - сложение
-
- вычитание
-
- умножение
/ - деление
( и ) - скобки в операторах или скобки вокруг параметров, передаваемых функции
< - меньше
- больше
= - равно
А так же составные символы:
= = - сравнение
>= - больше или равно
<= - меньше или равно
<> - не равно
Словарь языка встроенного калькулятора. Как в большинстве языков программирования, неделимые последовательности знаков алфавита образуют слова, отделяемые друг от друга разделителями, и несущими определенный смысл. Разделителями могут служить символ пробела, конца строки, конца оператора, т.е. точка с запятой, комментарии, другие специальные символы и их комбинации.
Все слова подразделяются на:
ключевые слова,
стандартные идентификаторы,
идентификаторы пользователя.
Ключевые слова являются составной частью языка, имеют фиксированное написание и однозначно определенный смысл:
End – конец программы.
Not – отрицание.
Or – объединение.
Стандартные идентификаторы служат для обозначения заранее определенных конструкций:
True - истина
False - ложь
Pi - число пи = 3.1415926535897932385
Имена встроенных и внешних функций.
Идентификаторы пользователя применяются для обозначения имен меток, переменных, констант, процедур, определенных пользователем. Все идентификаторы должны удовлетворять следующим требованиям:
Идентификаторы имен переменных и процедур, определенных пользователем не должны начинаться с цифр.
Идентификаторы не должны начинаться с символов !, :, _ , т.к. мы будем использовать эти символы в служебных целях.
Пре компилятор воспринимает символы в строчном и прописном регистре в именах идентификаторов как прописные.
Между двумя идентификаторами должен быть хотя бы один разделитель.
Структура программы встроенного калькулятора. Программа встроенного калькулятора состоит из операторов. Операторы могут быть как одиночными, так и составными. Программа обязательно должна заканчиваться словом end .
Строковые константы обрамляются двойными кавычками. Все операторы должны заканчиваться знаком точка с запятой. Метки начинаются двоеточием как в теле программы, так и в строковой константе передаваемой в качестве параметра функциям, например:
…
GoTo(“:Label1”);
…
:Label1;
Все переменные являются без типовыми, а точнее вариантными типами, поэтому Вы можете присваивать одной и той же переменной различные значения разных типов, но при выполнении операций, при неправильных типах значений переменных могут происходить исключительные ситуации. Например:
A = “2”;
B = 3;
C = A + B; // даст в результате 5.
A = “Вася”;
B = 3;
C = A + B; // даст в результате строку “Вася3”
C = A / B // вызовет исключительную ситуацию.
В программе Вы можете использовать комментарии. Комментарии начинаются с двойной наклонной черты - //. И заканчиваются концом строки, т.е. как короткие комментарии в С++.
Порядок выполнения операций естественный, принятый в математике, т.е. сначала операторы в скобках и функции, умножение и деление, сложение и вычитание, булевы операторы, например: 2+2*2 = = 6, а не 8. Вы можете использовать скобки, чтобы переопределить очередность выполнения операций, например: (2 + 2)*2 = = 8.
В качестве параметров функциям можно передавать другие функции и т.д., например: C = sin(cos(Z));
В числовых константах используется в качестве разделителя дробной и целой части только точка при любых настройках Вашей операционной системы. В качестве разделителя в датах – точка, а в качестве разделителей времени – двоеточие.
Все остальное, даже циклы и безусловные переходы мы организуем либо встроенными, либо внешними функциями. Если функция возвращает значение, которое Вам не нужно, Вы можете его игнорировать, т.е. записывать её в программе как процедуру без присваивания.
Вот как выглядит типичная программа:
Если ошибка, то просто уходим из программы
OnExcept(":Exit");
Получаем код текущей таблицы. Здесь IdMain и IdType – встроенные переменные
ThisTable = tTableId(IdMain, IdType);
Получаем код записи, которую будем искать в справочнике при его открытии
FindValue = tGetValByFieldNum(ThisTable, 74);
Получаем код другой таблицы – таблицы справочника
Categors = tTableId(5, 3);
Подготавливаем справочник к открытию
SetOpenDoc(5, 3, "Категории социального положения");
Открываем справочник. OpenReference вернет true, если пользователь нажмет Ok
if(OpenReference(5, 3, FindValue), ":SetVal", ":Exit");
:SetVal;
Устанавливаем код из справочника в текущую запись
tSetValByFieldNum(ThisTable, 74, tGetValByName(Categors, "ID_NUM"));
:Exit;
end;
Вот, собственно, все начальные условия, которые нам предстоит выполнить.
Проектирование калькулятора. Калькулятор должен иметь как минимум следующие блоки:
Компилятор, точнее что-то вроде анализатора текста ( парсер ), разбивающего текст на лексемы, и создающего нужные объекты для дальнейшего выполнения программы. Два стека – вычислительный и стек вызова подпрограмм. Я на всякий случай разделил их для простоты. Сам вычислитель, способный правильно выполнять программу, согласно правилам математики, вызывать функции и т.д. Контейнер переменных. Контейнер встроенных функций. Контейнер внешних функций, способный перед выполнением программы регистрировать внешние функции. Ну, и собственно, компонент – контейнер для всех этих объектов, который может согласовать их работу, который можно поставить на форму.
Тексты – разбор полетов.
Калькулятор. TunCalck = class(TComponent)
private
FCompiler: TunCompiler;
FStack: TunStack;
FSubStack: TunStack;
FVariables: TunVariables;
FFunctions: TunFunctions;
FStalker: TunStalker;
FExternals: TunExternals;
function GetCompProgramm: TStringList;
function GetProgramm: TStringList;
function GetVariable(const AName: string): Variant;
procedure SetVariable(const AName: string; const AVal: Variant);
function GetIsDebug: boolean;
procedure SetIsDebug(const Value: boolean);
protected
procedure DoCheckName(const VarName: string; var TypeName: TEnumToken;
var InternalName: string);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Compile;
function Run: Variant;
property Programm: TStringList read GetProgramm;
property CompilerProgramm: TStringList read GetCompProgramm;
property Variable[const AName: string]: Variant read GetVariable write SetVariable;
property Variables: TunVariables read FVariables;
property Functions: TunFunctions read FFunctions;
property Externals: TunExternals read FExternals;
published
end;
Fcompiler – это наш компилятор, упомянутый как пункт 1. Fstack и FsubStack – два стека для вычислений и подпрограмм, соответственно. Fvariables – контейнер с переменными. Ffunctions – контейнер с встроенными функциями. Fexternals – контейнер внешних функций. И, наконец, Fstalker – сам вычислитель.
Посмотрим, что собственно, происходит, когда нам нужно что-то посчитать. Например, нужно посчитать видимость пункта меню для пользователя:
…
Присваиваем калькулятору программу.
Calck.Programm.Assign( Work[I].WorckCheckPrg);
Устанавливаем значения неких переменных в программе
Calck.Variable[‘VISIBLE’]:=true;
Calck.Variable[‘ID_TYPE’]:=TypeDescriptor.ID;
Calck.Variable[‘ID_MAIN’]:=MainOwner;
Запускаем программу на выполнение.
Calck.Run;
Получаем результат, посчитанный калькулятором.
isOk:=Calck.Variable[‘VISIBLE’];
…
Здесь isOk – видимость пункта меню.
Это один из способов, но можно зарегистрировать внешние функции, и управлять поведением программы с их помощью. Этот вариант мы рассмотрим позже.
Вот метод Run:
function TunCalck.Run: Variant;
var AOldTimeSeparator: Char;
AOldDateSeparator: Char;
AOldDecimalSeparator: Char;
Begin
Если программа пуста, то просто выходим, иначе “компилируем”
if Trim(Programm.Text) <> '' then
FCompiler.Compile