1 module argon.compiler;
2 
3 import std.array;
4 import std.format;
5 import std.typecons;
6 import std.conv;
7 
8 import argon.ast;
9 import argon.parser;
10 
11 template renderArgonTemplate(string file, Args...)
12 {
13 	auto renderArgonTemplate(Writer)(auto ref Writer writer)
14 	{
15 		static if (__traits(compiles, writer.bodyWriter))
16 		{
17 			auto output = writer.bodyWriter;
18 		}
19 		else
20 		{
21 			auto output = writer;
22 		}
23 		
24 		enum mainTemplate = compileArgonTemplate!file();
25 		enum includeTemplates = compileArgonIncludeTemplates!(mainTemplate[1])() ~ mainTemplate;
26 
27 		void delegate()[string] file_table;
28 
29 		auto get_namespace(string namespace)()
30 		{
31 			static foreach(i, Arg; Args)
32 				static if (__traits(compiles, Arg == namespace) && Arg == namespace)
33 					return Args[i+1];
34 		}
35 
36 		auto register_file(string fileName)(void delegate() func)
37 		{
38 			file_table[fileName] = func;
39 		}
40 
41 		auto do_file(string fileName)()
42 		{
43 			file_table[fileName]();
44 		}
45 
46 		static foreach(includeTemplate; includeTemplates)
47 		{
48 			mixin(includeTemplate[0]);
49 		}
50 
51 		do_file!file();
52 	}
53 }
54 
55 private alias ArgonTemplate = Tuple!(string, string[]);
56 
57 private ArgonTemplate compileArgonTemplate(string file)()
58 {
59 	auto compiler = new ArgonCompiler(file, import(file));
60 	return tuple(compiler.source, compiler.includeFiles);
61 }
62 
63 private ArgonTemplate[] compileArgonIncludeTemplates(string[] files)()
64 {
65 	ArgonTemplate[] templates;
66 	static foreach(i, file; files)
67 	{
68 		mixin(format(q{
69 			enum template_%s = compileArgonTemplate!file();
70 			templates ~= template_%s ~ compileArgonIncludeTemplates!(template_%s[1])();
71 			}, i, i, i));
72 	}
73 	return templates;
74 }
75 
76 final class ArgonCompiler : ASTVisitor
77 {
78 	string file;
79 	string[] includeFiles;
80 	ASTTemplateNode templateAST;
81 
82 	private Appender!string output;
83 	private size_t[string] listElementDict;
84 	private size_t lastUniqueId;
85 
86 	this(string file, string source)
87 	{
88 		this.file = file;
89 		templateAST = parseArgonTemplate(file, source);
90 		templateAST.accept(this);
91 	}
92 
93 	void visit(ASTTemplateNode node)
94 	{
95 		auto functionId = node.file.hashOf();
96 		output.formattedWrite("void template_%s()\n{\n", functionId);
97 		foreach(child; node.children)
98 		{
99 			child.accept(this);
100 		}
101 		output.formattedWrite("}\nregister_file!(`%s`)(&template_%s);\n\n", this.file, functionId);
102 	}
103 
104 	void visit(ASTHTMLNode node)
105 	{
106 		output.formattedWrite("output.write(`%s`);\n", node.html.replace("`", "` ~ '`' ~ `"));
107 	}
108 
109 	void visit(ASTIncludeNode node)
110 	{
111 		includeFiles ~= node.file;
112 		output.formattedWrite("do_file!(`%s`)();\n", node.file);
113 	}
114 
115 	void visit(ASTElementNode node)
116 	{
117 		output.put("output.write(text(");
118 		node.identifier.accept(this);
119 		output.put("));\n");
120 	}
121 
122 	void visit(ASTListNode node)
123 	{
124 		auto idString = format("%s:%-(%s.%)", node.identifier.namespace, node.identifier.callChain);
125 		listElementDict[idString] = uniqueId;
126 		output.formattedWrite("foreach(list_%s, element_%s; ", listElementDict[idString], listElementDict[idString]);
127 		node.identifier.accept(this);
128 		output.put(")\n{\n");
129 		foreach(child; node.children)
130 		{
131 			child.accept(this);
132 		}
133 		output.put("}\n");
134 	}
135 
136 	void visit(ASTIdentifierNode node)
137 	{
138 		Appender!string identifier;
139 
140 		identifier.formattedWrite("get_namespace!(`%s`)()", node.namespace);
141 		foreach(i, call; node.callChain)
142 		{
143 			if (call == "[$]")
144 			{
145 				auto idString = format("%s:%-(%s.%)", node.namespace, node.callChain[0..i]);
146 				identifier.formattedWrite("[list_%s]", listElementDict[idString]);
147 			}
148 			else if (call == "[#]")
149 			{
150 				auto idString = format("%s:%-(%s.%)", node.namespace, node.callChain[0..i]);
151 				identifier = Appender!string.init;
152 				identifier.formattedWrite("list_%s", listElementDict[idString]);
153 			}
154 			else
155 			{
156 				identifier.formattedWrite(".%s", call);
157 			}
158 		}
159 		output.put(identifier.data);
160 	}
161 
162 	@property string source()
163 	{
164 		return output.data;
165 	}
166 
167 	@property size_t uniqueId()
168 	{
169 		return lastUniqueId++;
170 	}
171 }