D语言编译期生成和编译期执行技术
借助D语言新的mixin表达式,可以完成一些代码生成功能,比如:template attr_accessor(T, char[] name){ mixin(" private T _" ~ name ~ "; public " ~ name ~ "(){ return _" ~ name ~ "; } public " ~ name ~ "(T v){ _" ~ name " = v; } ");}class Foo{ mixin attr_accessor!(int, "bar");}void main(){ auto foo = new Foo; foo.bar = 3; writefln(foo.bar);}
下面的大段代码演示了另一个功能,编译期字符串解析:
import std.metastrings;template drop_white(char[] s){static if (s.length && (s == ' ' || s == '\t' || s == '\r' || s == '\n'))const char[] drop_white = s;elseconst char[] drop_white = s;}template drop_comment(char[] s){static if (s.length >= 2 && (s == '/' && s == '*'))const char[] drop_comment = scan_comment_end!(s);elseconst char[] drop_comment = s;}template scan_comment_end(char[] s){static if (s.length >= 2){static if (s == '*' && s == '/')const char[] scan_comment_end = s;elseconst char[] scan_comment_end = scan_comment_end!(s);}else{pragma(msg, "Failed to scan comment end");static assert(false);}}template Token(char[] t, char[] v, char[] r){const char[] type = t;const char[] value = v;const char[] remain = r;static if (t != "eof")alias next_token!(r) next;}template is_digit(char c){const bool is_digit = c >= '0' && c <= '9';}template is_letter(char c){const bool is_letter = c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';}template get_token(char[] s){static if (s.length == 0 || s == '\0'){const char[] type = "eof";const char[] value = "";const char[] remain = "";}else static if (s == '_' || is_letter!(s)){alias parse_id!(s) token;const char[] type = "id";const char[] value = token.first;const char[] remain = token.second;}else static if (s == '"'){alias parse_string!(s) token;const char[] type = "string";const char[] value = token.first;const char[] remain = token.second;}else static if (is_digit!(s)){alias parse_number!(s) token;const char[] type = "number";const char[] value = token.first;const char[] remain = token.second;}else static if (s == '/'){alias parse_regexp!(s) token;const char[] type = "regexp";const char[] value = token.first;const char[] remain = token.second;}else{pragma(msg, "Can't parse token from: " ~ s);static assert(false);}debug pragma(msg, "Get token: " ~ value);}template parse_id(char[] s){static if (s.length && (s == '_' || is_letter!(s) || is_digit!(s))){alias parse_id!(s) id;const first = s ~ id.first;alias id.second second;}else{const char[] first = "";alias s second;}}template parse_string(char[] s){static if (s == '"')alias scan_string_end!(s) parse_string;else{pragma(msg, "Failed to parse string");static assert(false);}}template parse_number(char[] s){static if (s.length && is_digit!(s)){alias parse_number!(s) number;const char[] first = s ~ number.first;const char[] second = number.second;}else{const char[] first = "";const char[] second = s;}}template parse_regexp(char[] s){static if (s.length && s == '/')alias scan_regexp_end!(s) parse_regexp;else{pragma(msg, "Failed to parse regexp");static assert(false);}}template scan_regexp_end(char[] s){static if (s.length){static if (s == '/'){alias pair!("", s) scan_regexp_end;}else static if (s.length >= 2 && s == '\\'){alias scan_regexp_end!(s) r;const char[] first = escape!(s) ~ r.first;const char[] second = r.second;}else{alias scan_regexp_end!(s) r;const char[] first = s ~ r.first;const char[] second = r.second;}}else{pragma(msg, "Failed to parse regexp: " ~ s);static assert(false);}}template scan_string_end(char[] s){static if (s.length){static if (s == '"')alias pair!("", s) scan_string_end;else static if (s.length >= 2 && s == '\\'){alias scan_string_end!(s) s;alias pair!(escape!(s) ~ r.first, r.second) scan_string_end;}else{alias scan_string_end!(s) string;const char[] first = s ~ string.first;const char[] second = string.second;}}else{pragma(msg, "Failed to parse string");static assert(false);}}template escape(char c){static if (c == 'n')const char[] escape = "\n";else static if (c == 't')const char[] escape = "\t";else static if (c == 'r')const char[] escape = "\r";else static if (c == '\\')const char[] escape = "\\";else{pragma(msg, "Failed to escape char '" ~ std.metastrings.ToString!(c) ~ "'");static assert(false);}}template next_token(char[] s){static if (s.length == 0){const char[] type = "eof";const char[] value = "";const char[] remain = "";}else{const char[] comment_and_white_dropped = drop_comment!(drop_white!(s));static if (s.length != comment_and_white_dropped.length){alias next_token!(comment_and_white_dropped) t;}else{alias get_token!(s) t;}alias t.type type;const char[] value = t.value;const char[] remain = t.remain;}static if (type != "eof")alias next_token!(remain) next;}template pair(char[] F, char[] S){alias F first;alias S second;}import std.stdio;template output_token(alias T){static if (T.type != "eof"){pragma(msg, T.type ~ ": " ~ T.value);alias output_token!(T.next) output_token;}}template output(char[] token_parser, char[] source){mixin("alias " ~ token_parser ~ "!(source) parser;");alias output_token!(parser) output;}mixin output!("next_token", "a bc /a\\\\34/ \"bbc\" s 32 33 abv");
只需要编译它,就可以在编译期把字符串"a bc /a\\\\34/ \"bbc\" s 32 33 abv"解析为标记。
上面的代码全部使用模板来完成,实现极其复杂,而且无法在动态代码中重用,所以D 1.006加入了编译期执行方法,让这类代码可以在编译期和执行期都可以使用,不过目前为止,仅有小部分代码可以这样,但这会是它的大方向。
结合编译期生成和编译期执行这2项技术,可以完成一些复杂的编译器功能,一个理想的工作方式如下:
alias BNF!(import("ebnf.bnf")) EBNF;alias EBNF!(import("sql92.ebnf")) SQL;// 插入更多生成代码,或者是利用mixin生成。User[] users = SQL!("select * from users where status = ?", 1);alias EBNF!(import("idl.ebnf")) IDL;mixin IDL!("interface X{} interface Y: X{}");
大概是这样的工作方式,在编译期就可以检查出SQL和IDL字符串的语法。
页:
[1]