В некоторых случаях бывает удобно скомпилировать определенные части программы во время работы приложения. Например, в мире явы так происходит компиляция веб сервером .jsp страниц в сервлеты. Другими возможными применениями подобной техники являются разные языки шаблонов, регулярные выражения и т.д.
В эрланге транслятор встроен в библиотеку времени исполнения, и позволяет полностью управлять всеми стадиями трансляции.
Как это сделать
К примеру, создадим модуль, содержащий одну функцию. Пусть это будет — сложение двух чисел.
Итак. Формируем строку, содержащую текст модуля.
Далее нам необходимо сделать лексический анализ,
В результате в Lexems получаем список лексем, что-то вроде
Синтаксический анализ
На этом этапе Parsed будет содержать внутреннее представление нашей программы. Между прочим, в читаемом виде.
Так, что в некоторых простых случаях может быть разумно строить сразу такое атрибутное дерево без предварительного лексического и синтаксического анализа.
Осталась мелочь. Преобразовать атрибутное дерево в бинарный код виртуальной машины,
И загрузить этот код из строки в виртуальную машину.
Проверяем,
Все работает! Мы сформировали в памяти модуль, странслировали его, загрузили, и теперь он ничем не отличается от других модулей эрланга. Его можно вызывать откуда угодно, менять (спасибо эрлангу за эту возможность), и, между прочим, он у нас был скомпилирован с опцией native — то есть в код процессора, с помощью hipe.
В эрланге транслятор встроен в библиотеку времени исполнения, и позволяет полностью управлять всеми стадиями трансляции.
Как это сделать
К примеру, создадим модуль, содержащий одну функцию. Пусть это будет — сложение двух чисел.
Итак. Формируем строку, содержащую текст модуля.
Src = [ "-module(testmodule).",
"-export([add_two_numbers/2]).",
"-compile(native)." ,
"add_two_numbers(X,Y) -> X+Y."].
Далее нам необходимо сделать лексический анализ,
Lexems = lists:map(fun(X) -> {ok,Scanned,_} = erl_scan:string(X), Scanned end, Src).
В результате в Lexems получаем список лексем, что-то вроде
[[{'-',1},
{atom,1,module},
{'(',1},
{atom,1,testmodule},
{')',1},
{dot,1}],
[{'-',1},
{atom,1,export},
{'(',1},
{'[',1},
{atom,1,add_two_numbers},
... И т.д.
Синтаксический анализ
Parsed = lists:map(fun(X) -> {ok,P} = erl_parse:parse_form(X), P end, Lexems).
На этом этапе Parsed будет содержать внутреннее представление нашей программы. Между прочим, в читаемом виде.
[{attribute,1,module,testmodule},
{attribute,1,export,[{add_two_numbers,2}]},
{attribute,1,compile,native},
{function,1,add_two_numbers,2,
[{clause,1,
[{var,1,'X'},{var,1,'Y'}],
[],
[{op,1,'+',{var,1,'X'},{var,1,'Y'}}]}]}]
Так, что в некоторых простых случаях может быть разумно строить сразу такое атрибутное дерево без предварительного лексического и синтаксического анализа.
Осталась мелочь. Преобразовать атрибутное дерево в бинарный код виртуальной машины,
{ok,_,B} = compile:forms(Parsed).
И загрузить этот код из строки в виртуальную машину.
code:load_binary(testmodule,"nofile",B).
Проверяем,
19> testmodule:add_two_numbers(5,8).
13
Все работает! Мы сформировали в памяти модуль, странслировали его, загрузили, и теперь он ничем не отличается от других модулей эрланга. Его можно вызывать откуда угодно, менять (спасибо эрлангу за эту возможность), и, между прочим, он у нас был скомпилирован с опцией native — то есть в код процессора, с помощью hipe.