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 }