Siêu Lập Trình với AST trong Erlang
Erlang
14
White

Ngoc Dao viết ngày 21/03/2016

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

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Ngoc Dao

102 bài viết.
284 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
62 8
Làm thế nào để nâng cấp trang web mà không làm gián đoạn dịch vụ? Đây là câu hỏi phỏng vấn các công ty lớn thường hỏi khi bạn xin vào vị trí làm lậ...
Ngoc Dao viết hơn 2 năm trước
62 8
White
40 1
Bài viết này giải thích sự khác khác nhau giữa hai ngành khoa học máy tính (computer science) và kĩ thuật phần mềm (software engineering), hi vọng ...
Ngoc Dao viết hơn 2 năm trước
40 1
White
34 1
Nếu là team leader, giám đốc công ty hay tướng chỉ huy quân đội, vấn đề cơ bản bạn gặp phải là “hướng mọi người đi theo con đường bạn chỉ ra”. Thử...
Ngoc Dao viết hơn 2 năm trước
34 1
Bài viết liên quan
White
3 0
Chú thích: Bài này đăng lần đầu năm 2009 để chia sẻ kinh nghiệm, sau khi tác giả viết xong thư viện closed source để bán. Hiện tại năm 2016 đã có t...
Ngoc Dao viết hơn 2 năm trước
3 0
White
2 0
Trong xử lí song song, các đơn vị thực hiện nhiệm vụ xử lí thường gặp là: thread, process, core, CPU, node. Thường mỗi node ứng với một server vật ...
Ngoc Dao viết hơn 2 năm trước
2 0
White
3 0
Khi xây dựng hệ thống server phân tán chạy song song trên nhiều node, ta thường gặp bài toán có pattern sau: Có 3 node, mỗi node chạy một process ...
Ngoc Dao viết hơn 2 năm trước
3 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
102 bài viết.
284 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!