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

/src/tink/markup/formats/Tags.hx

http://github.com/cambiata/cx
Haxe | 278 lines | 255 code | 16 blank | 7 comment | 76 complexity | 3dc93aa2673d0275cd2ab7f038d4c654 MD5 | raw file
  1. package tink.markup.formats;
  2. import haxe.macro.Context;
  3. import haxe.macro.Expr;
  4. import haxe.macro.Format;
  5. using tink.macro.tools.MacroTools;
  6. using StringTools;
  7. using tink.core.types.Outcome;
  8. using Lambda;
  9. private enum Kind {
  10. Prop;
  11. Child;
  12. Conflict;
  13. None;
  14. }
  15. private typedef Plugin = {
  16. function init(pos:Position):Null<Expr>;
  17. function finalize(pos:Position):Null<Expr>;
  18. function defaultTag(pos:Position):Expr;
  19. function postprocess(e:Expr):Expr;
  20. function setProp(attr:String, value:Expr, pos:Position):Expr;
  21. function addString(s:String, pos:Position):Expr;
  22. function addChild(e:Expr, ?t:Type):Expr;
  23. function buildNode(nodeName:Expr, props:Array<Expr>, children:Array<Expr>, pos:Position, yield:Expr->Expr):Expr;
  24. }
  25. class Tags {
  26. var plugin:Plugin;
  27. public function new(plugin) {
  28. this.plugin = plugin;
  29. }
  30. public function init(pos:Position):Null<Expr> return plugin.init(pos);
  31. public function finalize(pos:Position):Null<Expr> return plugin.finalize(pos);
  32. public function postprocess(e:Expr):Expr return plugin.postprocess(e);
  33. function getTag(src:Expr) {
  34. return
  35. switch (src.getIdent()) {
  36. case Success(s):
  37. if (s.startsWith('$'))
  38. s.substr(1).toExpr(src.pos).asSuccess();
  39. else
  40. src.asFailure();
  41. default:
  42. switch (src.getString()) {
  43. case Success(_):
  44. interpolate(src).fold(function (e1, e2) return macro $e1 + $e2, macro '').asSuccess();
  45. default:
  46. src.asFailure();
  47. }
  48. }
  49. }
  50. function annotadedName(atom:Expr, yield:Expr->Dynamic) {
  51. return
  52. switch (atom.expr) {
  53. case EArrayDecl(values):
  54. if (values.length != 1)
  55. atom.reject();
  56. getAnnotations(values[0], yield);
  57. plugin.defaultTag(atom.pos).asSuccess();
  58. case EArray(e1, e2):
  59. getAnnotations(e2, yield);
  60. getTag(e1);
  61. default:
  62. getTag(atom);
  63. }
  64. }
  65. function getAnnotations(src:Expr, yield:Expr->Dynamic) {
  66. var cls = new List();
  67. while (src != null)
  68. switch (src.expr) {
  69. case EField(e, f):
  70. cls.push(f.replace('_', '-'));
  71. src = e;
  72. default:
  73. switch (src.getIdent()) {
  74. case Success(s):
  75. //TODO: can call to plugin here directly
  76. s = s.replace('_', '-');
  77. if (s.charAt(0) == '$') {
  78. var value = s.substr(1).toExpr(src.pos);
  79. yield((macro id = $value).finalize(src.pos));
  80. }
  81. else
  82. cls.push(s);
  83. if (cls.length > 0) {
  84. var value = cls.join(' ').toExpr(src.pos);
  85. yield((macro 'class' = $value).finalize(src.pos));
  86. }
  87. src = null;
  88. default:
  89. src.reject();
  90. }
  91. }
  92. }
  93. function getKind(e:Expr):Kind {
  94. return
  95. if (e == null) None;
  96. else if (OpAssign.get(e).isSuccess()) Prop;
  97. else
  98. switch (e.expr) {
  99. case EParenthesis(e), EUntyped(e):
  100. getKind(e);
  101. case EIf(_, cons, alt), ETernary(_, cons, alt):
  102. unify(getKind(cons), getKind(alt));
  103. case EFor(_, expr):
  104. getKind(expr);
  105. case EWhile(_, body, _):
  106. getKind(body);
  107. case ESwitch(_, cases, edef):
  108. var ret = getKind(edef);
  109. for (c in cases)
  110. ret = unify(ret, getKind(c.expr));
  111. ret;
  112. default: Child;
  113. }
  114. }
  115. function unify(k1:Kind, k2:Kind):Kind {
  116. return
  117. if (k1 == None) k2;
  118. else if (k2 == None) k1;
  119. else if (k1 == k2) k1;
  120. else Conflict;
  121. }
  122. function build(of:Expr, params:Array<Expr>, src:Expr, yield:Expr->Expr):Expr {
  123. var props = [];
  124. return
  125. switch (annotadedName(of, props.push)) {
  126. case Success(nodeName):
  127. var children = [];
  128. for (p in params)
  129. switch (getKind(p)) {
  130. case Prop: props.push(p);
  131. case Child: children = children.concat(interpolate(p));
  132. case None: p.reject('expression seems not to yield an attribute or a child');
  133. case Conflict: p.reject('you can only either set an attribute or a child');
  134. }
  135. plugin.buildNode(nodeName, props, children, src.pos, yield);
  136. default:
  137. plugin.addChild(src);
  138. }
  139. }
  140. public function transform(e:Expr, yield:Expr->Expr):Expr {
  141. return
  142. //switch (e.typeof()) {
  143. //case Success(_):
  144. //switch (e.getString()) {
  145. //case Success(s): plugin.addString(s, e.pos);
  146. //default: plugin.addChild(e);
  147. //}
  148. switch (e.getString()) {
  149. case Success(s): plugin.addString(s, e.pos);
  150. case Failure(_):
  151. switch (e.getBinop()) {
  152. case Success(op):
  153. switch (op.op) {
  154. case OpAssign:
  155. var ret = [];
  156. switch (op.e1.getName()) {
  157. case Success(name):
  158. ret.push(plugin.setProp(name, op.e2, op.pos));
  159. default:
  160. switch (op.e1.expr) {
  161. case EArrayDecl(exprs):
  162. for (e in exprs) {
  163. var name = e.getName().sure();
  164. ret.push(plugin.setProp(name, op.e2.field(name, e.pos), e.pos));
  165. }
  166. default:
  167. op.e1.reject();
  168. }
  169. }
  170. if (ret.length == 1) ret[0];
  171. else
  172. ret.toBlock(op.pos);
  173. case OpLt:
  174. build(op.e1, [op.e2], e, yield);
  175. default:
  176. build(e, [], e, yield);
  177. }
  178. default:
  179. switch (e.expr) {
  180. case ECall(target, params):
  181. build(target, params, e, yield);
  182. default:
  183. build(e, [], e, yield);
  184. }
  185. }
  186. }
  187. }
  188. static function interpolate(e:Expr):Array<Expr> {
  189. return
  190. switch (e.getString()) {
  191. case Success(str):
  192. var pos = Context.getPosInfos(e.pos);
  193. var min = pos.min;
  194. pos.min++;
  195. function make(size) {
  196. pos.max = pos.min + size;
  197. var p = Context.makePosition(pos);
  198. pos.min += size;
  199. return p;
  200. }
  201. var ret = [];
  202. var add = ret.push,
  203. i = 0,
  204. start = 0,
  205. max = str.length;
  206. while( i < max ) {
  207. if( StringTools.fastCodeAt(str,i++) != '$'.code )
  208. continue;
  209. var len = i - start - 1;
  210. if( len > 0 )
  211. add({ expr : EConst(CString(str.substr(start,len))), pos : make(len) });
  212. pos.min++;
  213. start = i;
  214. var c = StringTools.fastCodeAt(str, i);
  215. if( c == '{'.code ) {
  216. var count = 1;
  217. i++;
  218. while( i < max ) {
  219. var c = StringTools.fastCodeAt(str,i++);
  220. if( c == "}".code ) {
  221. if( --count == 0 ) break;
  222. } else if( c == "{".code )
  223. count++;
  224. }
  225. if( count > 0 )
  226. Context.error("Closing brace not found",make(1));
  227. pos.min++;
  228. start++;
  229. var len = i - start - 1;
  230. var expr = str.substr(start, len);
  231. add(Context.parseInlineString(expr, make(len)));
  232. pos.min++;
  233. start++;
  234. } else if( (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || c == '_'.code ) {
  235. i++;
  236. while( true ) {
  237. var c = StringTools.fastCodeAt(str, i);
  238. if( (c >= 'a'.code && c <= 'z'.code) || (c >= 'A'.code && c <= 'Z'.code) || (c >= '0'.code && c <= '9'.code) || c == '_'.code )
  239. i++;
  240. else
  241. break;
  242. }
  243. var len = i - start;
  244. var ident = str.substr(start, len);
  245. add( { expr : EConst(CIdent(ident)), pos : make(len) } );
  246. } else if( c == '$'.code ) {
  247. start = i++;
  248. continue;
  249. } else {
  250. start = i - 1;
  251. continue;
  252. }
  253. start = i;
  254. }
  255. var len = i - start;
  256. if( len > 0 )
  257. add( { expr : EConst(CString(str.substr(start, len))), pos : make(len) } );
  258. ret;
  259. default: [e];
  260. }
  261. }
  262. }