Как писать интерпретаторы.

  Банников Н.А. www.stikriz.narod.ru Почта На главную страницу  

Алфавит встроенного калькулятора. 
Словарь языка встроенного калькулятора. 

Структура программы встроенного калькулятора. 

Проектирование калькулятора. 

Тексты – разбор полетов. 

Калькулятор. 

“Компилятор”. 

Вычислитель. 

Встроенные функции. 

Внешние функции. 

Переменные. 

Заключение. 

Рейтинг@Mail.ru

Исходные тексты 

Тексты не приглажены, а взяты как есть из проекта. Поэтому, возможно, Вам придется кое-что не нужное удалять от туда самостоятельно.

             Для выделения из программы баз данных бизнес логики в отдельный программный слой, который, конечно должен иметь возможность полностью настраиваться без перекомпиляции программы, можно использовать какой-нибудь интерпретатор. Тогда появится возможность просто менять программу для интерпретатора и тем самым менять бизнес логику программы. Давайте посмотрим, как можно сделать простейший интерпретатор. Для простоты синтаксиса, а значит и программной реализации, мы не будем придерживаться какого-то известного языка программирования. Назовем наш интерпретатор калькулятором – это нейтрально и непритязательно, так сказать без особых амбиций…

            Калькулятор представляет собой двухпроходный интерпретатор, способный выполнять математические, логические, и другие операции, вызывать встроенные или внешние функции. Встроенные функции заложены в калькулятор при его создании, а внешние – создаются программистом при создании приложения. Обычно внешние функции нужны для выполнения действий над данными, которые обрабатывает программа, поэтому приходится каким-то образом дать доступ калькулятора к этим данным, что и вызвало появление внешних функций.

В калькуляторе используется простой синтаксис без понятия блока программного кода и области видимости. Т.е. все переменные являются глобальными, так же нет блоков, наподобие beginend или { … }, которые присутствуют в языках высокого уровня Паскаль и 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;

Вот, собственно, все начальные условия, которые нам предстоит выполнить.

Проектирование калькулятора.

Калькулятор должен иметь как минимум следующие блоки:

  1. Компилятор, точнее что-то вроде анализатора текста ( парсер ), разбивающего текст на лексемы, и создающего нужные объекты для дальнейшего выполнения программы.
  2. Два стека – вычислительный и стек вызова подпрограмм. Я на всякий случай разделил их для простоты.
  3. Сам вычислитель, способный правильно выполнять программу, согласно правилам математики, вызывать функции и т.д.
  4. Контейнер переменных.
  5. Контейнер встроенных функций.
  6. Контейнер внешних функций, способный перед выполнением программы регистрировать внешние функции.
  7. Ну, и собственно, компонент – контейнер для всех этих объектов, который может согласовать их работу, который можно поставить на форму.

Тексты – разбор полетов.

Калькулятор.

  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