1 module metad.compiler;
2 
3 private import pegged.grammar;
4 
5 private import std.string;
6 private import std.typecons;
7 private import std.algorithm;
8 private import std.array;
9 private import std.traits;
10 
11 /++
12  + A compile-time compiler (meta-compiler).
13  +
14  + To use: (see the unittest for a running example)
15  +
16  + Create your pegged grammar.
17  +    enum _g = q{ GRAMMAR: ... };
18  +    mixin (grammar(_g));
19  +
20  + Create a template for your compiler, and mixin the default signature.
21  +   struct myCompiler(ParseTree T) {
22  +      mixin Compiler!(T,myCompiler);
23  +      // ....
24  +   }
25  +
26  + The "Compiler" mixin binds the "compileNode" function to a nested scope. We can override the compileNode function
27  + for specific matches in the parse tree. To do this, we use string mixins, that bind to
28  + the local scope.  The template "nodeOverride" takes a condition and a function, and creates a local override
29  + that binds the function to "compileNode" if the static condition matches against a tree node.  The "compilerOverride" template
30  + binds to a node name.
31  + This goes inside the parser template definition, and in this case we're overriding the behaviour
32  + for a nodes matching a rule named "GRAMMAR.Text".  The override template accepts a second string argument
33  + representing the code to be mixed in when the name matches. The code should return a string that will be output
34  + from the compiler, to be mixed in to the users program.  In this example, we concantenate the "matches" property.
35  + "T" is an alias to the current node.
36  +      // ...
37  +      mixin (compilerOverride!("GRAMMAR.Text","T.matches.join(\"\")"));
38  +      // ...
39  +
40  + Then, we invoke the compiler.
41  +    enum _d = q{ ... the input to your parser ... };
42  +    mixin Compiler!(myParser,GRAMMAR(_d));
43  +
44  + Or we can just pull out the text and not mix it in to our program right away
45  +    enum code = myParser!(GRAMMAR(_d)).compileNode();
46  +
47  + The compilerOverride template takes two strings:
48  + a string matching the name of the node to override,
49  + and a string representing the code to mixin in order to parse
50  + that particular node type.
51  +/
52 
53  template loadGrammar(string fname,string docRoot) {
54      mixin(grammar(import(fname)));
55      enum parser = mixin(docRoot);
56  }
57 
58  template loadGrammarAndData(string grammarFile,string docRoot,string dataFile) {
59      mixin loadGrammar!(grammmarFile,docRoot);
60      enum loadGrammarAndData = parser(import(dataFile));
61  }
62 
63  template loadAndCompile(string grammarFile,string docRoot,string dataFile,alias _Compiler) {
64      enum _data = loadGrammarAndData(grammarFile,docRoot,dataFile);
65      mixin Compiler!(_Compiler,_data);
66  }
67 
68  /++
69  UDA to indicate a method to process a compiler node.
70  ++/
71  struct MatchName {
72 	string name;
73  }
74  /++
75  UDA to indicate a method to process a compiler node.
76  ++/
77  struct MatchCond {
78 	string cond;
79  }
80 
81 /*struct Match(alias T) {
82 	string name() {
83 		return fullyQualifiedName!T;
84 	}
85 }*/
86 
87 template Compiler(ParseTree T,alias Parser) {
88 	/++
89 	A string-match function. If the name of a node matches N, then the an override of 'compileNode' is generated 
90 	returning the value F in the generated compiler.
91 	++/
92     template compilerOverride(string N,string F) {
93         enum compilerOverride = nodeOverride!(
94 											  "T.name == \""~N~"\"",
95 											  "return " ~ F);
96     }
97 
98 	/++
99 
100 	Add an match funtion to the compile tree. The condition is a compile-time expression which is 
101 	used in a static if statement: `static if(Cond)` in the declaration of the overriden function, in the 
102 	generated compiler. Func is the body of the required operation if the condition succeeds.
103 	The generated function is always called `compileNode`, with a static 'if' represented by the `Cond` parameter.
104 	Inside the function, the parse tree can be accessed as the variable `T`, which is the value passed as the first parameter to the 
105 	`Compile` template.
106 
107 	The return is a constant string that should be mixed in to the compiler generated in the client.
108 
109 	++/
110     template nodeOverride(string Cond,string Func) {
111         /*enum nodeOverride = q{
112             static if(__C__) {
113                 static auto compileNode() {
114                     __F__;
115                 }
116             }
117         }.replace("__C__",Cond).replace("__F__",Func);*/
118         enum nodeOverride = q{
119                 static auto compileNode()()
120 					if(__C__) {
121 						__F__;
122                 }
123         }.replace("__C__",Cond).replace("__F__",Func);
124 
125     }
126 
127     static auto compileNode()() {
128 		debug(MetaDCompile) {
129 			pragma(msg,"compileNode\n");
130 		}
131         return compileChildNodes().join;
132     }
133 
134     static string[] compileChildNodes() {
135         string[] result;
136         static foreach(x;T.children) {
137 			debug(MetaDCompile) {
138 				pragma(msg,"compileChildNodes:\n"~Parser!(x).compileNode());
139 			}
140             result~=Parser!(x).compileNode();
141         }
142         return result;
143     }
144 
145 	static string compileChild(int index)() {
146 		debug(MetaDCompile) {
147 			pragma(msg,"compileChild:\n"~Parser!(T.children[index]).compileNode());
148 		}
149 		return Parser!(T.children[index]).compileNode();
150 	}
151 
152 	/++
153 	Compile the tree to an inline expression.
154 	++/
155     mixin template compile() {
156 		debug(MetaDCompile) {
157 			pragma(msg,"compile:\n"~compileNode());
158 			pragma(msg,"data:\n"~T.toString);
159 		}
160         alias compile = mixin(compileNode());
161     }
162 	/++
163 	Compile the tree to a list of statements.
164 	++/
165 	mixin template compileStatements() {
166 		debug(MetaDCompile) {
167 			pragma(msg,"compileStatements:\n"~compileNode());
168 			pragma(msg,"data:\n"~T.toString);
169 		}
170         mixin(compileNode());
171 	}
172 
173 //	mixin template processTypeAnnotations(A) {
174 	static string processTypeAnnotations(A)() {
175 		import std.traits;
176 		string result="";
177 		static foreach (sym;getSymbolsByUDA!(A,MatchCond)) {
178 			debug(MetaDCompile) {
179 				pragma(msg,"MatchCond:"~fullyQualifiedName!sym);
180 			}
181 			static foreach (attr;getUDAs!(sym,MatchCond)) {
182 				debug(MetaDCompile) {
183 					pragma(msg,"+MatchCond: "~attr);
184 				}
185 				//mixin(nodeOverride!(attr.cond,fullyQualifiedName!sym));
186 				result ~= nodeOverride!(attr.cond,fullyQualifiedName!sym~"(T)");
187 			}
188 		}
189 		static foreach (sym;getSymbolsByUDA!(A,MatchName)) {
190 			debug(MetaDCompile) {
191 				pragma(msg,"MatchName:"~fullyQualifiedName!sym);
192 			}
193 			static foreach (attr;getUDAs!(sym,MatchName)) {
194 				debug(MetaDCompile) {
195 					pragma(msg,"+MatchName:"~attr.name);
196 				}
197 				//mixin(compilerOverride!(attr.name,fullyQualifiedName!sym));
198 				result ~= compilerOverride!(attr.name,fullyQualifiedName!sym~"(T)");
199 			}
200 		}
201 		/*static foreach (sym;getSymbolsByUDA!(A,Match)) {
202 			debug(MetaDCompile) {
203 				pragma(msg,"Match:"~fullyQualifiedName!sym);
204 			}
205 			static foreach (attr;getUDAs!(sym,Match)) {
206 				debug(MetaDCompile) {
207 					pragma(msg,"+Match:"~attr.name);
208 				}
209 				//mixin(compilerOverride!(attr.name,fullyQualifiedName!sym));
210 				result ~= compilerOverride!(attr.name,fullyQualifiedName!sym~"(T)");
211 			}
212 		}*/
213 		return result;
214 	}
215 
216 
217 }
218 
219 /*template Compiler(alias Parser,ParseTree data) {
220     alias _compiler = Parser!(data);
221     mixin _compiler.compile!(_compiler);
222 }*/
223 
224 void compileToModule(alias Compiler)(string moduleName,string filename,string optHeader="") {
225 	import std.stdio;
226 	auto f = File(filename ~ ".d","w");
227 	f.write("/++\nThis module was automatically generated.\n\n");
228 	f.write("\n\n+/\n");		
229 	
230 	f.writefln("module %s;",moduleName);
231 	
232 	if (optHeader.length > 0)
233         f.write(optHeader ~ "\n\n");
234 
235 	f.write(Compiler.compileNode);
236 }
237 
238 void compileExpressionToModule(alias Compiler)(string moduleName,string filename,string optHeader="") {
239 	import std.stdio;
240 	auto f = File(filename ~ ".d","w");
241 	f.write("/++\nThis module was automatically generated.\n\n");
242 	f.writeln(Compiler.compileNode);
243 	f.write("\n\n+/\n");		
244 
245 	f.writefln("module %s;",moduleName);
246 
247 	if (optHeader.length > 0)
248         f.write(optHeader ~ "\n\n");
249 
250 	f.write("enum __EXPR = ");
251 	f.write(Compiler.compileNode);
252 	f.write(";\n\n");
253 }
254 
255 unittest {
256     import std.array;
257     import std.typecons;
258     import std.string;
259     import std.algorithm;
260 
261     import pegged.grammar;
262 
263     // A grammar that expands {{Template}} into
264     // statements.
265     enum _g = q{
266 GRAMMAR(Template):
267     Doc <- Line+ :endOfInput
268     Line <- (Var / Text)
269     Var <- :LDelim ^Template :RDelim
270     LDelim <- "{{"
271     RDelim <- "}}"
272     Text <- ~((!LDelim) Char )*
273     Char <- .
274     };
275     mixin(grammar(_g));
276 
277     // Replace these identifiers in the input
278     enum __M = "MyStruct";
279     enum __T = "MyType";
280 
281     // some input data.
282     enum _d = q{
283         struct {{__T}} {
284             enum v = 'v';
285             struct {{__M}} {
286             };
287             static {{__M}} m;
288         };
289     };
290 
291     // Create a compiler from the parse tree.
292     struct myCompiler(ParseTree T,alias Parser=myCompiler) {
293         mixin Compiler!(T,Parser);
294         // Override two node types: GRAMMAR.Text and identifier.
295         // GRAMMAR.Text is just the matches array values concantenated.
296         mixin (compilerOverride!("GRAMMAR.Text","T.matches.join"));
297         // identifier returns a mixin of the matches value.
298         mixin (compilerOverride!("identifier","mixin(T.matches.join)"));
299 
300 		// For some reason the compiler isn't finding the default catch-all case. We have to mixin every node type.
301 		//mixin (nodeOverride!("true","return compileChildNodes().join;"));
302 		mixin (nodeOverride!("T.name != \"GRAMMAR.Text\" && T.name != \"identifier\"","return compileChildNodes().join;"));
303 /*		mixin (compilerOverride!("GRAMMAR.Doc","compileChildNodes.join;"));
304 		mixin (compilerOverride!("GRAMMAR.Line","compileChildNodes.join;"));
305 		mixin (compilerOverride!("GRAMMAR.Var","compileChildNodes.join;"));
306 		mixin (compilerOverride!("GRAMMAR","compileChildNodes.join;"));*/
307     }
308 
309     pragma(msg,"Compiling:\n"~_d);
310     pragma(msg,"Tree:\n" ~ GRAMMAR!identifier(_d).toString);
311 
312     enum compiled = myCompiler!(GRAMMAR!identifier(_d)).compileNode();
313     pragma(msg,"Compiled to:\n" ~ compiled);
314     mixin(compiled);
315 
316     static assert(mixin("MyType.v") == 'v');
317 
318 }
319 /*
320 unittest {
321     import std.array;
322     import std.typecons;
323     import std.string;
324     import std.algorithm;
325 
326     import pegged.grammar;
327 
328     // A grammar that expands {{Template}} into
329     // statements.
330     enum _g = q{
331 GRAMMAR(Template):
332     Doc <- Line+ :endOfInput
333     Line <- (Var / Text)
334     Var <- :LDelim ^Template :RDelim
335     LDelim <- "{{"
336     RDelim <- "}}"
337     Text <- ~((!LDelim) Char )*
338     Char <- .
339     };
340     mixin(grammar(_g));
341     // Replace these identifiers in the input
342     enum __M = 500;
343 
344     // some input data.
345     enum _d = q{
346 		{{__M}} + 10
347     };
348 
349 
350     // Create a compiler from the parse tree.
351     struct myCompiler(ParseTree T,alias Parser=myCompiler) {
352         mixin Compiler!(T,Parser);
353         // Override two node types: GRAMMAR.Text and identifier.
354         // GRAMMAR.Text is just the matches array values concantenated.
355         //mixin (compilerOverride!("GRAMMAR.Text","T.matches.join"));
356         // identifier returns a mixin of the matches value.
357         //mixin (compilerOverride!("identifier","mixin(T.matches.join)"));
358 
359 		@MatchName("GRAMMAR.Text")
360 		static auto joinMatches(ParseTree t) {
361 			return t.matches.join;
362 		}
363 		@MatchName("identifier")
364 		static auto joinMatchesMixin(ParseTree t) {
365 			//return mixin(T.matches.join);
366 			return ""~(t.matches.join);
367 		}
368 
369 		mixin (processTypeAnnotations!(myCompiler));
370 
371 		mixin (compilerOverride!("GRAMMAR.Doc","compileChildNodes.join;"));
372 		mixin (compilerOverride!("GRAMMAR.Line","compileChildNodes.join;"));
373 		mixin (compilerOverride!("GRAMMAR.Var","compileChildNodes.join;"));
374 		mixin (compilerOverride!("GRAMMAR","compileChildNodes.join;"));
375     }
376 
377     pragma(msg,"Compiling:\n"~_d);
378     pragma(msg,"Tree:\n" ~ GRAMMAR!identifier(_d).toString);
379 
380     enum compiled = myCompiler!(GRAMMAR!identifier(_d)).compileNode();
381     pragma(msg,"Compiled to:\n" ~ compiled);
382     auto v = mixin(compiled);
383     static assert(v == 510);
384 }
385 */
386 
387 /**
388 The TupleCompiler converts you input to a tuple at compile-time.
389 The default node parser simply adds the "matches" as an unnamed string[].
390 
391 The parser works in two phases, and generates code of the
392 form "Tuple!(types...)(values...)". This can be assigned
393 to an enum.
394 
395 As in the ordinary compiler, the parser/generator methods
396 should be overridden to parse nodes from your grammar.
397 
398 There are two overrides per-node in the tuple generator -
399 the type, and the value.  The compileType returns a
400 string; either single type, or a string of the
401 form 'type,"name"', which would add the type as a
402 named field in the tuple.
403 
404 The compileValue method returns a string - the
405 code to mixin when the value is computed.
406 
407 This version of the compilerOverride template takes three
408 string parameters:
409 
410 the name of the node type, the code to mixin to generate the
411 "type" portion of the Tuple! created to represent that node,
412 and a code string to mixin to generate the "value" part of
413 the tuple (ie the argument to the tuple constructor).
414 **/
415 template TupleCompiler(ParseTree T,alias Parser) {
416     mixin Compiler!(T,Parser);
417 
418     static string[] compileChildValues() {
419         string[] result;
420         static foreach(x;T.children) {
421             debug {
422               pragma(msg,"value: \n"~Parser!(x).compileValue);
423             }
424             result ~= Parser!(x).compileValue;
425         }
426         return result;
427     }
428     static string[] compileChildTypes() {
429         string[] result;
430         static foreach(x;T.children) {
431             debug {
432               pragma(msg,"type: \n"~x.name);
433             }
434             result ~= Parser!(x).compileType;
435             // we can't use the node names as field names
436             // because they could be non-unique.
437             /*static if (x.name.indexOf(".")!=-1) {
438                 result ~= '"' ~ x.name[x.name.indexOf(".")+1..$] ~ '"';
439             } else {
440               result ~= '"' ~ x.name ~ '"';
441             }*/
442         }
443         return result;
444     }
445 
446     template tupleTypes() {
447       enum tupleTypes =
448         "Tuple!("~compileChildTypes().join(",")~")";
449     }
450 
451     template tupleValues() {
452       enum tupleValues =
453         "("~compileChildValues().join(",")~")";
454       }
455 
456     static string compileNode() {
457         return tupleTypes!() ~ tupleValues!();
458         //return "Tuple!("~compileChildTypes().join(",")~")("~compileChildValues().join(",")~")";
459     }
460     static string compileType() {
461         return "string[]";
462     }
463     static string compileValue() {
464       debug {
465         pragma(msg,"compileValue: \n"~T.name);
466         pragma(msg,T.matches.map!(T=>"<"~T~">").join(","));
467       }
468       enum v = T.matches.map!(x=>"\""~x~"\"").join(",");
469       return "[" ~ v ~ "]";
470     }
471     template nodeOverride(string Cond,string TypeFunc,string ValueFunc) {
472         enum nodeOverride = q{
473             static if(__C__) {
474                 static string compileType() {
475                     return __F__;
476                 }
477                 static string compileValue() {
478                     return __G__;
479                 }
480             }
481         }.replace("__C__",Cond).replace("__F__",TypeFunc).replace("__G__",ValueFunc);
482     }
483 
484     template compilerOverride(string Name,string TypeF,string ValueF) {
485         enum compilerOverride = nodeOverride!("T.name == \""~Name~"\"",TypeF,ValueF);
486     }
487 }
488 
489 template TupleCompiler(alias Parser,alias data) {
490     alias _compiler = Parser!(data);
491     //enum TupleCompiler = _compiler.compileAlias!(_compiler);
492     debug {
493       pragma(msg,"Tuples: \n"~_compiler.compileNode);
494     }
495     mixin("enum TupleCompiler = " ~ _compiler.compileNode ~ ";");
496 }
497 
498 unittest {
499 
500     import pegged.grammar;
501 
502     enum _g = q{
503 GRAMMAR(Template):
504     Doc <- Line+ :endOfInput
505     Line <- (Var / Text)
506     Var <- :LDelim ^Template :RDelim
507     LDelim <- "{{"
508     RDelim <- "}}"
509     Text <- ~((!LDelim) Char )*
510     Char <- .
511     };
512     mixin(grammar(_g));
513 
514     enum __M = "MyStruct";
515     //enum __T = "\"MyType\""; // quotes causes error.
516     enum __T = "MyType";
517 
518     enum _d = q{
519         struct {{__T}} {
520             enum v = 'v';
521             struct {{__M}} {
522             };
523             static {{__M}} m;
524         };
525     };
526 
527     struct myTupleCompiler(ParseTree T,alias Parser=myTupleCompiler) {
528         mixin TupleCompiler!(T,Parser);
529         //mixin (compilerOverride!("GRAMMAR","compileChildNodes().join(\"\")"));
530 
531         /* mixin (compilerOverride!(
532           "identifier",
533           "\" ~ T.matches.join(\"\") ~ \",\"identifier\")")
534           ); */
535         /*mixin (compilerOverride!(
536           "GRAMMAR.Doc",
537           "Tuple!("
538             ~ "compileChildTypes.join(\",\")"
539             ~ ")",
540           "Tuple!("
541             ~ "compileChildTypes.join(\",\")"
542             ~ ")("
543             ~ "compileChildValues.join(\",\")"
544             ~")"
545            )
546         );
547         mixin (compilerOverride!(
548           "GRAMMAR.Line",
549           "Tuple!("
550             ~ "compileChildTypes.join(\",\")"
551             ~ ")",
552           "Tuple!("
553             ~ "compileChildTypes.join(\",\")"
554             ~ ")("
555             ~ "compileChildValues.join(\",\")"
556             ~")"
557            )
558         );*/
559         /*mixin (compilerOverride!(
560           "GRAMMAR.Var",
561           "compileChildTypes.join(\",\")",
562           "compileChildValues.join(\",\")"
563         ));*/
564         mixin (compilerOverride!(
565           "GRAMMAR.Var",
566           "tupleTypes!()",
567           //"tupleTypes!()(tupleValues!())"
568           //"tuple(tupleValues!())"
569           "compileNode"
570         ));
571         mixin (compilerOverride!(
572           "GRAMMAR.Text",
573           "\"string\"",
574           " '\"' ~ T.matches.join ~ '\"'"
575         ));
576         static foreach(i;["GRAMMAR.Line","GRAMMAR.Doc"]) {
577           mixin (compilerOverride!(
578             i,
579             "compileChildTypes.join(\",\")",
580             "compileChildValues.join(\",\")"
581           ));
582 
583         }
584         /* mixin (compilerOverride!(
585           "GRAMMAR.Line",
586           "compileChildTypes.join(\",\")",
587           "compileChildValues.join(\",\")"
588         ));
589         mixin (compilerOverride!(
590           "GRAMMAR.Doc",
591           "compileChildTypes.join(\",\")",
592           "compileChildValues.join(\",\")"
593         )); */
594         // the "identifier" token is replaced by
595         // a mixin of it's value.
596         mixin (compilerOverride!(
597           "identifier",
598           "\"string,\" ~ '\"' ~ T.matches[0] ~ '\"'",
599           "'\"' ~ mixin(T.matches[0]) ~ '\"'"
600         ));
601     }
602     enum compiledTuple = TupleCompiler!(myTupleCompiler,GRAMMAR!identifier(_d));
603     //mixin compiledTuple;
604     //static assert(mixin("MyType.v") == "v");
605     pragma(msg,"Tuple Compiler:");
606     pragma(msg,typeof(compiledTuple));
607     pragma(msg,compiledTuple);
608 }