PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/util/metastrings_expand.d

http://github.com/9rnsr/dio
D | 386 lines | 317 code | 28 blank | 41 comment | 52 complexity | 0754387af715238527a142411441b209 MD5 | raw file
  1. module util.metastrings_expand;
  2. import std.algorithm : startsWith;
  3. import std.ascii : isalpha = isAlpha, isalnum = isAlphaNum;
  4. import std.traits : isCallable, isSomeString;
  5. import std.typetuple : TypeTuple;
  6. import std.conv;// : text;
  7. /**
  8. Expand expression in string literal, with mixin expression.
  9. Expression in ${ ... } is implicitly converted to string (requires importing std.conv.to)
  10. If expreesion is single variable, you can omit side braces.
  11. --------------------
  12. enum int a = 10;
  13. enum string op = "+";
  14. static assert(mixin(expand!q{ ${a*2} $op 2 }) == q{ 20 + 2 });
  15. // a*2 is caluclated in this scope, and converted to string.
  16. --------------------
  17. Other example, it is easy making parameterized code-blocks.
  18. --------------------
  19. template DefFunc(string name)
  20. {
  21. // generates specified name function.
  22. mixin(
  23. mixin(expand!q{
  24. int ${name}(int a){ return a; }
  25. })
  26. );
  27. }
  28. --------------------
  29. */
  30. template expand(string s)
  31. {
  32. enum expand = "text(" ~ expandSplit!s ~ ")";
  33. }
  34. template expandSplit(string s)
  35. {
  36. enum expandSplit = "TypeTuple!(" ~ splitVars(s) ~ ")";
  37. }
  38. string splitVars(string code)
  39. {
  40. auto s = Slice(Kind.CODESTR, code);
  41. s.parseCode();
  42. return "`" ~ s.buffer ~ "`";
  43. }
  44. template toStringNow(alias V)
  45. {
  46. static if (isCompileTimeValue!(V))
  47. {
  48. static if (__traits(compiles, { auto v = V; }))
  49. {
  50. // pragma(msg, "toStringNow, Alias : ", V);
  51. alias V toStringNow;
  52. }
  53. else
  54. {
  55. // pragma(msg, "toStringNow, Template : ", V);
  56. enum toStringNow = V.stringof;
  57. }
  58. }
  59. else
  60. {
  61. alias V toStringNow; // run-time value
  62. }
  63. }
  64. template toStringNow(T)
  65. {
  66. enum toStringNow = T.stringof;
  67. }
  68. string text(T...)(T args) @trusted
  69. {
  70. return std.conv.text(args);
  71. }
  72. private @trusted
  73. {
  74. template Identity(alias V)
  75. {
  76. alias V Identity;
  77. }
  78. template isCompileTimeValue(alias V)
  79. {
  80. static if (is(typeof(V)))
  81. {
  82. // pragma(msg, "isCompileTimeValue : alias is(typeof(V))");
  83. enum isCompileTimeValue = true;
  84. }
  85. else static if (is(V))
  86. {
  87. // pragma(msg, "isCompileTimeValue : type'");
  88. enum isCompileTimeValue = true;
  89. }
  90. else
  91. {
  92. enum isCompileTimeValue = __traits(compiles, {
  93. alias Identity!(runtime_eval(V)) A;
  94. });
  95. // pragma(msg, "isCompileTimeValue : compiles : ", isCompileTimeValue);
  96. }
  97. }
  98. template isCompileTimeValue(V)
  99. {
  100. // pragma(msg, "isCompileTimeValue : type");
  101. enum isCompileTimeValue = true;
  102. }
  103. enum Kind
  104. {
  105. METACODE,
  106. CODESTR,
  107. STR_IN_METACODE,
  108. ALT_IN_METACODE,
  109. RAW_IN_METACODE,
  110. QUO_IN_METACODE,
  111. }
  112. string match(Pred)(string s, Pred pred)
  113. {
  114. static if (isCallable!Pred)
  115. {
  116. size_t eaten = 0;
  117. while (eaten < s.length && pred(s[eaten]))
  118. ++eaten;
  119. if (eaten)
  120. return s[0..eaten];
  121. return null;
  122. }
  123. else static if (isSomeString!Pred)
  124. {
  125. if (startsWith(s, pred))
  126. return s[0 .. pred.length];
  127. return null;
  128. }
  129. }
  130. /+
  131. // match and eat
  132. string munch(Pred)(ref string s, Pred pred)
  133. {
  134. auto r = chomp(s, pred);
  135. if (r.length)
  136. s = s[r.length .. $];
  137. return r;
  138. }+/
  139. struct Slice
  140. {
  141. Kind current;
  142. string buffer;
  143. size_t eaten;
  144. this(Kind c, string h, string t=null){
  145. current = c;
  146. if (t is null)
  147. {
  148. buffer = h;
  149. eaten = 0;
  150. }
  151. else
  152. {
  153. buffer = h ~ t;
  154. eaten = h.length;
  155. }
  156. }
  157. bool chomp(string s)
  158. {
  159. auto res = startsWith(tail, s);
  160. if (res)
  161. eaten += s.length;
  162. return res;
  163. }
  164. void chomp(size_t n)
  165. {
  166. if (eaten + n <= buffer.length)
  167. eaten += n;
  168. }
  169. @property bool exist() {return eaten < buffer.length;}
  170. @property string head() {return buffer[0..eaten];}
  171. @property string tail() {return buffer[eaten..$];}
  172. bool parseEsc()
  173. {
  174. if (chomp(`\`))
  175. {
  176. if (chomp("x"))
  177. chomp(2);
  178. else
  179. chomp(1);
  180. return true;
  181. }
  182. else
  183. return false;
  184. }
  185. bool parseStr()
  186. {
  187. if (chomp(`"`))
  188. {
  189. auto save_head = head; // workaround for ctfe
  190. auto s = Slice(
  191. (current == Kind.METACODE ? Kind.STR_IN_METACODE : current),
  192. tail);
  193. while (s.exist && !s.chomp(`"`))
  194. {
  195. if (s.parseVar()) continue;
  196. if (s.parseEsc()) continue;
  197. s.chomp(1);
  198. }
  199. this = Slice(
  200. current,
  201. (current == Kind.METACODE
  202. ? save_head[0..$-1] ~ `(text("` ~ s.head[0..$-1] ~ `"))`
  203. : save_head[0..$] ~ s.head[0..$]),
  204. s.tail);
  205. return true;
  206. }
  207. else
  208. return false;
  209. }
  210. bool parseAlt()
  211. {
  212. if (chomp("`"))
  213. {
  214. auto save_head = head; // workaround for ctfe
  215. auto s = Slice(
  216. (current == Kind.METACODE ? Kind.ALT_IN_METACODE : current),
  217. tail);
  218. while (s.exist && !s.chomp("`"))
  219. {
  220. if (s.parseVar()) continue;
  221. s.chomp(1);
  222. }
  223. this = Slice(
  224. current,
  225. (current == Kind.METACODE
  226. ? save_head[0..$-1] ~ "(text(`" ~ s.head[0..$-1] ~ "`))"
  227. : save_head[0..$-1] ~ "` ~ \"`\" ~ `" ~ s.head[0..$-1] ~ "` ~ \"`\" ~ `"),
  228. s.tail);
  229. return true;
  230. }
  231. else
  232. return false;
  233. }
  234. bool parseRaw()
  235. {
  236. if (chomp(`r"`))
  237. {
  238. auto save_head = head; // workaround for ctfe
  239. auto s = Slice(
  240. (current == Kind.METACODE ? Kind.RAW_IN_METACODE : current),
  241. tail);
  242. while (s.exist && !s.chomp(`"`))
  243. {
  244. if (s.parseVar()) continue;
  245. s.chomp(1);
  246. }
  247. this = Slice(
  248. current,
  249. (current == Kind.METACODE
  250. ? save_head[0..$-2] ~ `(text(r"` ~ s.head[0..$-1] ~ `"))`
  251. : save_head[0..$] ~ s.head[0..$]),
  252. s.tail);
  253. return true;
  254. }
  255. else
  256. return false;
  257. }
  258. bool parseQuo()
  259. {
  260. if (chomp(`q{`))
  261. {
  262. auto save_head = head; // workaround for ctfe
  263. auto s = Slice(
  264. (current == Kind.METACODE ? Kind.QUO_IN_METACODE : current),
  265. tail);
  266. if (s.parseCode!`}`())
  267. {
  268. this = Slice(
  269. current,
  270. (current == Kind.METACODE
  271. ? save_head[0..$-2] ~ `(text(q{` ~ s.head[0..$-1] ~ `}))`
  272. : save_head[] ~ s.head),
  273. s.tail);
  274. }
  275. return true;
  276. }
  277. else
  278. return false;
  279. }
  280. bool parseBlk()
  281. {
  282. if (chomp(`{`))
  283. return parseCode!`}`();
  284. else
  285. return false;
  286. }
  287. private void checkVarNested()
  288. {
  289. if (current == Kind.METACODE)
  290. {
  291. if (__ctfe)
  292. assert(0, "Invalid var in raw-code.");
  293. else
  294. throw new Exception("Invalid var in raw-code.");
  295. }
  296. }
  297. private string encloseVar(string exp)
  298. {
  299. string open, close;
  300. switch(current)
  301. {
  302. case Kind.CODESTR : open = "`" , close = "`"; break;
  303. case Kind.STR_IN_METACODE: open = `"` , close = `"`; break;
  304. case Kind.ALT_IN_METACODE: open = "`" , close = "`"; break;
  305. case Kind.RAW_IN_METACODE: open = `r"`, close = `"`; break;
  306. case Kind.QUO_IN_METACODE: open = `q{`, close = `}`; break;
  307. default:assert(0);
  308. }
  309. // return close ~ " ~ .std.conv.to!string(toStringNow!("~exp~")) ~ " ~ open;
  310. return close ~ ", toStringNow!("~exp~"), " ~ open;
  311. }
  312. bool parseVar()
  313. {
  314. if (auto r = match(tail, `$`))
  315. {
  316. auto t = tail[1..$];
  317. static bool isIdtHead(dchar c) { return c=='_' || isalpha(c); }
  318. static bool isIdtTail(dchar c) { return c=='_' || isalnum(c); }
  319. if (match(t, `{`))
  320. {
  321. checkVarNested();
  322. auto s = Slice(Kind.METACODE, t[1..$]);
  323. s.parseCode!`}`();
  324. this = Slice(current, head ~ encloseVar(s.head[0..$-1]), s.tail);
  325. return true;
  326. }
  327. else if (auto r2 = match(t[0..1], &isIdtHead))
  328. {
  329. checkVarNested();
  330. auto id = t[0 .. 1 + match(t[1..$], &isIdtTail).length];
  331. this = Slice(current, head ~ encloseVar(id), t[id.length .. $]);
  332. return true;
  333. }
  334. return false;
  335. }
  336. return false;
  337. }
  338. bool parseCode(string end=null)()
  339. {
  340. enum endCheck = end ? "!chomp(end)" : "true";
  341. while (exist && mixin(endCheck))
  342. {
  343. if (parseStr()) continue;
  344. if (parseAlt()) continue;
  345. if (parseRaw()) continue;
  346. if (parseQuo()) continue;
  347. if (parseBlk()) continue;
  348. if (parseVar()) continue;
  349. chomp(1);
  350. }
  351. return true;
  352. }
  353. }
  354. }
  355. //import expand_utest;