Использование интерпретатора DrScheme в проектах Delphi

Сергей Орлов

Подготовительные шаги

Вообще-то в начале статьи полагается введение. О том, зачем всё это надо. Я таких вещей писать не умею. Скажу только, что для меня главной была способность Scheme работать (в том числе делить) со сколь угодно большими числами без потери точности. Считается ещё, что очень важна способность к символьным вычислениям, но мне они пока были без надобности.

Для начала о том, что нам нужно. Во-первых, нужен дистрибутив DrScheme (12 Mb). В крайнем случае можно обойтись MZScheme (4,3 Mb, но почти нет документации) или набором DLL (1,7 Mb, но при использовании этого набора Вы не сможете отлаживать тексты на Scheme). Во-вторых, нужна книжечка «Inside PLT Scheme», к сожалению, на английском языке. Она существует в виде html, в виде PDF и в виде plt (последний вариант предпочтителен, поскольку там есть достаточно удобный поиск по всей книге, но для работы plt нужен большой дистрибутив DrScheme). В-третьих, материалы к статье (можно обойтись без них, но лучше скачайте, внутри архива есть и сама статья, так что сохранять её не надо).

Устанавливаем DrScheme, ставим “insidemz-doc.plt” (если Вы его скачали, то при установленной DrScheme достаточно сделать двойной щелчок по файлу), запускаем DrScheme и лезем в Help>HelpDesk>Software>Documentantion>Manuals>Inside PLT Scheme (если у Вас html-версия, то просто открываем оглавление).

Читаем первую главу. Там написано примерно следующее:

  1. Включите “scheme.h” в свой проект.
  2. Инициализируйте среду выполнения с помощью функции scheme_basic_env.
  3. Теперь можно запускать другие функции типа scheme_load, scheme_eval и т. д.

Естественно, файл “scheme.h” для Delphi бесполезен. Мы должны создать свой модуль, который описывал бы функции, импортируемые из dll. Немного подумав, поймём, что достаточно импортировать 3 функции:

Кроме того, добавим пару своих функций для удобства.

Итак, библиотека “mzscheme.pas”:

unit mzscheme;

interface

uses SysUtils;

//псевдонимы для имён библиотек. Проверьте названия ваших dll и внесите изменения 
//в случае необходимости
const mzlibrary = 'libmzsch352_000.dll';
      gclibrary = 'libmzgc352_000.dll';
type
  PMZChar = PChar;
  PScheme_Object = Pointer;
  PScheme_Env = Pointer;  
  PLong = ^Longint;

var Env: PScheme_Env;  //указатель на среду выполнения
    scheme_true: PScheme_Object;   //#t
    scheme_false: PScheme_Object;   //#f
    scheme_null: PScheme_Object;   //null

//импорт функций. Обратите внимание, что способ вызова -
//не общепринятый stdcall, а cdecl
function scheme_basic_env: PScheme_Env;  cdecl; external mzlibrary;
function scheme_eval_string(str: PChar; env: PScheme_Env): PScheme_Object;  cdecl; external mzlibrary;
function scheme_display_to_string(obj: PScheme_Object; len: PLong): PChar;  cdecl; external mzlibrary;

//функции, вводимые для удобства работы
function SchemeObjectAsString(obj: PScheme_Object): String;
function SchemeExec(Expr: String): String;

implementation

function SchemeObjectAsString;
var PC: PChar;
    L: Longint;
begin
  Result := '';
  PC := scheme_display_to_string(Obj, @L);
  SetString(Result, PC, L);
end;

function SchemeExec;
var Obj: PScheme_Object;
begin
  Obj := scheme_eval_string(PChar(Expr), Env);
  Result := SchemeObjectAsString(Obj);
end;

initialization
//инициализируем среду выполнения
  Env :=  scheme_basic_env;
//вычисляем #t, #f и null - они могут понадобиться для анализа булевых выражений
  scheme_true :=  scheme_eval_string('#t', Env);
  scheme_false :=  scheme_eval_string('#f', Env);
  scheme_null :=  scheme_eval_string('null', Env);
end.

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

Только прежде, чем экспериментировать, проверьте:

Пример 1: «интерпретатор Scheme»

Давайте напишем простенький интерпрететор Scheme. Создадим новый проект, кинем на форму два компонента TMemo и одну кнопку и добавим «mzscheme» в uses. Обработчик для нажатия кнопки:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo2.Text := SchemeExec(Memo1.Lines.Text);
end;

Всё! Теперь в первом Memo набираем текст команды, нажимаем на кнопку и получаем во втором Memo результат. Например, результат выполнения команды (* 33 23) равен 759.

Естественно, если ввести неправильную команду, возникнет исключительная ситуация и наш «интерпретатор» автоматически закроется. Но это демонстрационный пример использования DrScheme в Delphi, а на практике мы будем генерировать команды автоматически и поэтому должны избежать таких проблем.

Пример 2: вычисление факториала

Снова создаём новый проект, кидаем на форму один компонент TEdit, один компонент TMemo и одну кнопку. Добавляем «mzscheme» в uses. Пишем обработчик для нажатия кнопки (проверку на корретность введённого значения я вынес в отдельную процедуру):

function IsSimpleNumber(S: String): Boolean;
var i: Integer;
begin
  Result := True;
  for i:=1 to Length(S) do
    if not (S[i] in ['0'..'9']) then Result := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var Command, Number : String;
begin
  Number := Edit1.Text;
  Trim(Number);
  if IsSimpleNumber(Number) then
  begin
    Command := '(fac '+ Number + ')';
    Memo1.Text := SchemeExec(Command);
  end
  else
    ShowMessage('"' + Number + '" не является простым числом');  
end;

Ах да, нам надо ещё объяснить интерпретатору Scheme, что такое факториал. Для этого напишем (на языке Scheme) программу “fac.scm” и сохраним её в папке нашего проекта:

(define (fac n)
  (cond ((= n 0) 1)
        ((> n 0) (* n (fac (- n 1))))))

А теперь обработаем OnCreate для формы:

procedure TForm1.FormCreate(Sender: TObject);
begin
  SchemeExec('(load "fac.scm")');
end;

Всё, теперь это будет работать. Запускаем программу. Факториал числа 125 равен 188267717688892609974376770249160085759540364871492425887598231508353156331613598866882932889495923133646405445930057740630161919341380597818883457558547055524326375565007131770880000000000000000000000000000000

«А напоследок я скажу...»

Тем, кто загорелся идеей выучить язык Scheme, могу рекомендовать главу «Функциональное программирование» из книги А.Дехтяренко «Декларативное программирование». В частности, там вы узнаете, как превратить функцию fac нашей программы “fac.scm” из рекурсивной в итеративную, как осуществлять символьные вычисления и многое другое.