1 module buffer.compiler;
2 
3 import std.string;
4 import std.conv;
5 import std.array;
6 import std.typecons;
7 import std.algorithm.searching;
8 import std.uni;
9 import std.exception;
10 
11 template LoadBufferFile(const string fileName)
12 {
13     pragma(msg, "Compiling file: ", fileName, "...");
14     const char[] LoadBufferFile = compiler!(import(fileName));
15 }
16 
17 template LoadBufferScript(const string src)
18 {
19     pragma(msg, "Compiling script: ", extractScriptfragment(src), "...");
20     const char[] LoadBufferScript = compiler!src;
21 }
22 
23 private string compiler(const string source)()
24 {
25     Token[] tokens = lexer(source);
26     Sentence[] sentences = parser(tokens);
27 
28     Appender!string code;
29     code.put("import buffer;\r\n\r\n");
30 
31     foreach (sentence; sentences)
32     {
33         code.put("final static class " ~ sentence.name ~ " : Message\r\n");
34         code.put("{\r\n");
35 
36         foreach (field; sentence.fields)
37         {
38             code.put("\t" ~ field.get.type ~ " " ~ field.get.name ~ ";\r\n");
39         }
40 
41         code.put("\r\n");
42         code.put("\tubyte[] serialize(const string method = string.init)\r\n");
43         code.put("\t{\r\n");
44         code.put("\t\treturn super.serialize!(typeof(this))(this, method);\r\n");
45         code.put("\t}\r\n");
46         code.put("}\r\n");
47         code.put("\r\n");
48     }
49 
50     return code.data;
51 }
52 
53 /// lexer
54 
55 enum TokenType
56 {
57     Define            = 1,           // message
58     Keyword           = 2,           // type: int8...
59     Identifier        = 3,
60     SentenceEnd       = 110,         // ;
61     DelimiterOpen     = 120,         // {
62     DelimiterClose    = 121          // }
63 }
64 
65 private const string[] keywords = [
66     "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64",
67     "float32", "float64", "float128", "bool", "char", "string"
68 ];
69 
70 struct Token
71 {
72     TokenType type;
73     string name;
74 
75     this(const string name)
76     {
77         if (name == "message")
78         {
79             this.type = TokenType.Define;
80         }
81         else if (keywords.any!(x => x == name))
82         {
83             this.type = TokenType.Keyword;
84         }
85         else
86         {
87             this.type = TokenType.Identifier;
88         }
89 
90         this.name = name;
91     }
92 
93     this(const TokenType type, const string name)
94     {
95         this.type = type;
96         this.name = name;
97     }
98 }
99 
100 Token[] lexer(const string source)
101 {
102     /* State transition diagram:
103     0:  none      1: word      2: {      3: ;      4: }
104     -1: /        -2: //       -3: /*
105 
106     0   -> \s[ \f\n\r\t\v]      0
107         -> A..Za..z_            1
108         -> {                    2 -> add token -> 0
109         -> ;                    3 -> add token -> 0
110         -> }                    4 -> add token -> 0
111         -> /                    hang state, -1
112         -> other                Exception
113     1   -> \s[ \f\n\r\t\v]      1 -> add token -> 0
114         -> A..Za..z0..9_        1
115         -> {                    1 -> add token -> 2 -> add token -> 0
116         -> ;                    1 -> add token -> 3 -> add token -> 0
117         -> }                    1 -> add token -> 4 -> add token -> 0
118         -> /                    hang state, -1
119         -> other                Exception
120     2   ->                      0
121     3   ->                      0
122     4   ->                      0
123    -1   -> /                    -2
124         -> *                    -3
125         -> other                Exception
126    -2   -> \n                   restore state, hang = 0
127         -> other                skip
128    -3   -> /                    if last is * then restore state & hang = 0, else skip
129         -> other                skip
130     */
131 
132     Token[] tokens;
133     int state = 0;
134     int stateHang;
135     char last;
136     string token = string.init;
137 
138     const string src = (cast(string) source ~ "\r\n");
139     foreach (i, ch; src)
140     {
141         switch (state)
142         {
143             case 0:
144                 if (isWhite(ch))
145                 {
146                     continue;
147                 }
148                 else if (isIdentifierFirstChar(ch))
149                 {
150                     token = ch.to!string;
151                     state = 1;
152                 }
153                 else if (ch == '{')
154                 {
155                     tokens ~= Token(TokenType.DelimiterOpen, "{");
156                     state = 0;
157                 }
158                 else if (ch == ';')
159                 {
160                     tokens ~= Token(TokenType.SentenceEnd, ";");
161                     state = 0;
162                 }
163                 else if (ch == '}')
164                 {
165                     tokens ~= Token(TokenType.DelimiterClose, "}");
166                     state = 0;
167                 }
168                 else if (ch == '/')
169                 {
170                     stateHang = state;
171                     state = -1;
172                 }
173                 else
174                 {
175                     enforce(0, "Invalid character: " ~ ch.to!string);
176                 }
177                 break;
178             case 1:
179                 if (isWhite(ch))
180                 {
181                     tokens ~= Token(token);
182                     token = string.init;
183                     state = 0;
184                 }
185                 else if (isIdentifierChar(ch))
186                 {
187                     token ~= ch.to!string;
188                 }
189                 else if (ch == '{')
190                 {
191                     tokens ~= Token(token);
192                     tokens ~= Token(TokenType.DelimiterOpen, "{");
193                     token = string.init;
194                     state = 0;
195                 }
196                 else if (ch == ';')
197                 {
198                     tokens ~= Token(token);
199                     tokens ~= Token(TokenType.SentenceEnd, ";");
200                     token = string.init;
201                     state = 0;
202                 }
203                 else if (ch == '}')
204                 {
205                     tokens ~= Token(token);
206                     tokens ~= Token(TokenType.DelimiterClose, "}");
207                     token = string.init;
208                     state = 0;
209                 }
210                 else if (ch == '/')
211                 {
212                     stateHang = state;
213                     state = -1;
214                 }
215                 else
216                 {
217                     enforce(0, "Invalid character: " ~ ch.to!string);
218                 }
219                 break;
220             case -1:
221                 if (ch == '/')
222                 {
223                     state = -2;
224                 }
225                 else if (ch == '*')
226                 {
227                     state = -3;
228                 }
229                 else
230                 {
231                     enforce(0, "Invalid character: " ~ ch.to!string);
232                 }
233                 break;
234             case -2:
235                 if (ch == '\n')
236                 {
237                     state = stateHang;
238                     stateHang = 0;
239                 }
240                 break;
241             case -3:
242                 if ((ch == '/') && (last == '*'))
243                 {
244                     state = stateHang;
245                     stateHang = 0;
246                 }
247                 break;
248             default:
249                 break;
250         }
251 
252         last = ch;
253     }
254 
255     return tokens;
256 }
257 
258 private bool isIdentifierFirstChar(const char ch)
259 {
260     return isAlpha(ch) || ch == '_';
261 }
262 
263 private bool isIdentifierChar(const char ch)
264 {
265     return isAlphaNum(ch) || ch == '_';
266 }
267 
268 private string extractScriptfragment(const string source)
269 {
270     string ret = string.init;
271 
272     foreach (ch; source)
273     {
274         if (ret.length >= 50)
275             break;
276         if (!isWhite(ch))
277             ret ~= ch.to!string;
278         else if ((ret.length > 0) && (ret[$ - 1] != ' '))
279             ret ~= " ";
280     }
281 
282     return ret;
283 }
284 
285 /// parser
286 
287 struct Field
288 {
289     string type;
290     string name;
291 }
292 
293 struct Sentence
294 {
295     string name;
296     Nullable!Field[] fields;
297 }
298 
299 Sentence[] parser(const Token[] tokens)
300 {
301     Sentence[] sentences;
302     int pos;
303     while (pos < cast(int)tokens.length - 1)
304     {
305         if (tokens[pos].type != TokenType.Define)
306         {
307             enforce(0, "Syntax error at " ~ tokens[pos].name);
308         }
309 
310         sentences ~= parser_define(tokens, pos);
311     }
312 
313     return sentences;
314 }
315 
316 private Sentence parser_define(const Token[] tokens, ref int pos)
317 {
318     if ((cast(int)tokens.length - pos < 4) || (tokens[pos].type != TokenType.Define) || (tokens[pos + 1].type != TokenType.Identifier) || (tokens[pos + 2].type != TokenType.DelimiterOpen))
319     {
320         enforce(0, "Syntax error at " ~ tokens[pos].name);
321     }
322 
323     Sentence sentence;
324     sentence.name = tokens[pos + 1].name;
325     pos += 3;
326 
327     while (pos < tokens.length)
328     {
329         Nullable!Field field = parser_field(tokens, pos);
330 
331         if (field.isNull)
332             return sentence;
333 
334         sentence.fields ~= field;
335     }
336 
337     return sentence;
338 }
339 
340 private Nullable!Field parser_field(const Token[] tokens, ref int pos)
341 {
342     if ((cast(int)tokens.length - pos >= 1) && (tokens[pos].type == TokenType.DelimiterClose))
343     {
344         pos++;
345         return Nullable!Field();
346     }
347 
348     if ((cast(int)tokens.length - pos < 3) || (tokens[pos].type != TokenType.Keyword)
349             || (tokens[pos + 1].type != TokenType.Identifier)
350             || (tokens[pos + 2].type != TokenType.SentenceEnd))
351     {
352         enforce(0, "Syntax error at " ~ tokens[pos].name);
353     }
354 
355     Field field;
356     field.type = tokens[pos].name;
357     field.name = tokens[pos + 1].name;
358     pos += 3;
359 
360     return Nullable!Field(field);
361 }
362 
363 /*
364 import buffer;
365 
366 final static class Sample : Message
367 {
368     string name;
369     int32 age;
370     int16 sex;
371 
372     ubyte[] serialize(const string method = string.init)
373     {
374         return super.serialize!(typeof(this))(this, method);
375     }
376 }
377 */