Вообще-то в начале статьи полагается введение. О том, зачем всё это надо. Я таких вещей писать не умею. Скажу только, что для меня главной была способность 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-версия, то просто открываем оглавление).
Читаем первую главу. Там написано примерно следующее:
scheme_basic_env
.Естественно, файл “scheme.h” для Delphi бесполезен. Мы должны создать свой модуль, который описывал бы функции, импортируемые из dll. Немного подумав, поймём, что достаточно импортировать 3 функции:
scheme_basic_env
(инициализирует среду выполнения);scheme_eval_string
(вычисляет выражение, заданное в виде строки текста (PChar));scheme_display_to_string
(«печатает» объект PScheme_Object в строку PChar).Кроме того, добавим пару своих функций для удобства.
Итак, библиотека “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 пункта программы – импортировали функции и инициализировали среду выполнения. Осталось воспользоваться результатами наших трудов.
Только прежде, чем экспериментировать, проверьте:
mzlibrary = 'libmzsch352_000.dll'
и gclibrary = 'libmzgc352_000.dll'
названиям библиотек из того дистрибутива, который Вы скачали. Если не соответствуют, исправьте константы.Давайте напишем простенький интерпрететор Scheme. Создадим новый проект, кинем на форму два компонента TMemo и одну кнопку и добавим «mzscheme» в uses. Обработчик для нажатия кнопки:
procedure TForm1.Button1Click(Sender: TObject); begin Memo2.Text := SchemeExec(Memo1.Lines.Text); end;
Всё! Теперь в первом Memo набираем текст команды, нажимаем на кнопку и получаем во втором Memo результат. Например, результат выполнения команды (* 33 23) равен 759.
Естественно, если ввести неправильную команду, возникнет исключительная ситуация и наш «интерпретатор» автоматически закроется. Но это демонстрационный пример использования DrScheme в Delphi, а на практике мы будем генерировать команды автоматически и поэтому должны избежать таких проблем.
Снова создаём новый проект, кидаем на форму один компонент 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” из рекурсивной в итеративную, как осуществлять символьные вычисления и многое другое.