Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
Siêu Lập Trình với AST trong Erlang
Siêu lập trình (metaprogramming) là thuật ngữ để chỉ việc viết code này để sinh ra code nọ. Theo bài về Leex và Yecc, ngôn ngữ cấp cao thường theo qui trình: (1) mã nguồn, cấu trúc dữ liệu văn bản --> (2) tokens, cấu trúc dữ liệu mảng --> (3) cấu trúc dữ liệu cây thô, có thể chưa đúng cú pháp theo qui định của ngôn ngữ --> (4) AST, cấu trúc dữ liệu cây tuân theo đúng cú pháp của ngôn ngữ --> (5) CST, cấu trúc dữ liệu cây cuối cùng, có thể chạy trên máy ảo (ví dụ tập tin bytecode .class) hoặc máy thật (ví dụ tập tin .exe). Khi viết chương trình gì đó, thường ta viết code có dạng (1), rồi dùng chương trình biên dịch để tự động biến đổi thành dạng (5), chẳng cần quan tâm đến sự tồn tại của các dạng trung gian. Bài viết này giới thiệu chiêu thức viết code này ở dạng (1) để sinh ra code nọ ở dạng (4) trong Erlang. Dĩ nhiên ý tưởng hoàn toàn áp dụng được cho các ngôn ngữ cấp cao khác như Java và Ruby.
Chiêu thức này tuy khó hơn chiêu siêu bình dân là nối chuỗi, hoặc cao hơn chút là dùng XML và XSLT để viết code ở dạng (1) để sinh ra code ở dạng (1), nhưng bù lại lợi hại hơn nhiều:
- AST nằm trong bộ nhớ, đã theo đúng cú pháp ngôn ngữ, nên chỉ mất thời gian nháy mắt để biên dịch tiếp thành CST chạy được ngay.
- Có thể biến đổi AST thành mã nguồn bình thường để đọc.
Ví dụ
Giả sử ta muốn viết module i18n có hàm t như sau:
- Nhét vào key "hello" thì ra value "sao giờ này mới tới"
- Nhét vào key "bye" thì ra value "về nha"
- Nhét vào key nào khác thì ra chính key đó
Nếu viết code ở dạng (1) thì chỉ mất 3, 4 dòng. Nhưng cách này không scale nếu thêm vào yếu tố động:
- Muốn thay đổi tên module, ví dụ lúc thì en, lúc thì ja, lúc thì vi
- Key và value lấy từ nguồn động nào đó, ví dụ tập tin .po bất kì
po_to_module.erl
-module(po_to_module).
-compile(export_all).
generate_module(Module, Function, KVs) ->
ModuleAst = erl_syntax:attribute(
erl_syntax:atom(module),
[erl_syntax:atom(Module)]
),
ExportAst = erl_syntax:attribute(
erl_syntax:atom(export),
[
erl_syntax:list([
erl_syntax:arity_qualifier(
erl_syntax:atom(Function),
erl_syntax:integer(1))
])
]
),
FunctionAst = generate_function(Function, KVs),
Forms = [erl_syntax:revert(Ast) || Ast <- [
ModuleAst, ExportAst, FunctionAst
]],
{ok, Module, ByteCode} = compile:forms(Forms, []),
code:load_binary(Module, "not_important.erl", ByteCode),
Forms.
generate_function(Function, KVs) ->
Clauses = [generate_clause(K, V) || {K, V} <- KVs],
X = erl_syntax:variable("X"),
AnyClause = erl_syntax:clause([X], none, [X]),
erl_syntax:function(erl_syntax:atom(Function), Clauses ++ [AnyClause]).
generate_clause(K, V) ->
erl_syntax:clause([erl_syntax:string(K)], none, [erl_syntax:string(V)]).
test() ->
Forms = generate_module(vi, t, [
{"hello", "sao giờ này mới tới"},
{"bye", "về nha"}
]),
io:format("hello in Vietnamese: ~s~n", [vi:t("hello")]),
io:format("bye in Vietnamese: ~s~n", [vi:t("bye")]),
Source = to_source(Forms),
io:format("Source:~n~s~n", [Source]).
to_source(Forms) ->
io:format("Forms:~n~p~n", [Forms]),
FormList = erl_syntax:form_list(Forms),
io:format("FormList:~n~p~n", [FormList]),
erl_prettypr:format(FormList).
Kết quả chạy
hello in Vietnamese: sao giờ này mới tới
bye in Vietnamese: về nha
Forms:
[{attribute, 0, module, vi},
{attribute, 0, export, [{t, 1}]},
{function, 0, t, 1,
[{clause, 0,
[{string, 0, "hello"}],
[],
[{string, 0,
[115, 97, 111, 32, 103, 105, 225, 187, 157, 32, 110, 195,
160, 121, 32, 109, 225, 187, 155, 105, 32, 116, 225, 187,
155, 105]}]},
{clause, 0,
[{string, 0, "bye"}],
[],
[{string, 0, [118, 225, 187, 129, 32, 110, 104, 97]}]},
{clause, 0, [{var, 0, 'X'}], [], [{var, 0, 'X'}]}]}]
FormList:
{tree, form_list,
{attr, 0, [], none},
[{attribute, 0, module, vi},
{attribute, 0, export, [{t, 1}]},
{function, 0, t, 1,
[{clause, 0,
[{string, 0, "hello"}],
[],
[{string, 0,
[115, 97, 111, 32, 103, 105, 225, 187, 157, 32, 110,
195, 160, 121, 32, 109, 225, 187, 155, 105, 32, 116,
225, 187, 155, 105]}]},
{clause, 0,
[{string, 0, "bye"}],
[],
[{string, 0, [118, 225, 187, 129, 32, 110, 104, 97]}]},
{clause, 0, [{var, 0, 'X'}], [], [{var, 0, 'X'}]}]}]}
Source:
-module(vi).
-export([t/1]).
t("hello") -> "sao giờ này mới tới";
t("bye") -> "về nha";
t(X) -> X.
Như vậy, mấu chốt vấn đề là tham khảo module erl_syntax để mày mò tạo cây AST cho tương ứng với cú pháp Erlang.
Tham Khảo







