root/trunk/dxmp_lib.d

Revision 5, 13.3 kB (checked in by rubikitch, 5 years ago)

initial release

Line 
1 private import std.conv;
2 private import std.process;
3 private import std.file;
4 private import std.stdio;
5 private import std.string;
6 private import std.regexp;
7 private import std.math2;
8
9 struct XMPOptions {
10     char[] interpreter;
11     bool output_stdout;
12     char[] dump;
13     char[] filename;
14 }
15
16 class XMPFilter {
17     const char[] VERSION = "0.1.0";
18
19     const char[] MARKER = "!XMP87678_555_333333!";
20     static RegExp XMP_RE;
21     const char[] VAR = "_xmp_99999_555_333333";
22     static RegExp RUNTIME_ERROR_RE;
23
24     struct RuntimeData {
25         char[][][][int] results;
26         char[][][int] exceptions;
27     }
28
29     // INITIALIZE_OPTS
30
31     static this() {
32         XMP_RE = RegExp("^" ~ MARKER ~ `\[([0-9]+)\] (=>|~>) (.*)`);
33         RUNTIME_ERROR_RE = RegExp(`^Error: (.+) \S+\(([0-9]+)\)`); 
34     }
35
36     static char[] run(char[] code, XMPOptions opts) {
37         auto obj = new XMPFilter(opts);
38         return obj.annotate(code);
39     }
40
41     char[] interpreter;
42     bool output_stdout;
43     char[] dump;
44     char[] filename;
45
46     this(XMPOptions opts) {
47         interpreter = opts.interpreter;
48         output_stdout = opts.output_stdout;
49         dump = opts.dump;
50         filename = opts.filename;
51     }
52
53     this() {}
54
55     static char[] add_markers(char[] code, int min_codeline_size = 50) {
56         int[] lengthes;
57         int maxlen;
58         foreach(line; code.splitlines()) {
59             lengthes ~= line.length;
60         }
61         maxlen = lengthes.max();
62
63         lengthes.length = 2;
64         lengthes[0] = min_codeline_size;
65         lengthes[1] = maxlen + 2;
66         maxlen = lengthes.max();
67
68         char[] ret = "";
69         foreach(line; code.splitlines()) {
70             line = line.sub(r" // (=>|!>).*", "", "gm").sub(r" \t*$", "");
71             ret ~= (line ~ " ".repeat(maxlen - line.length) ~ " // =>" ~ newline);
72         }
73         return ret;
74     }
75
76     unittest {
77         assert("a;         // =>\n" == add_markers("a;", 10),
78                format("\n<%s> expected but was\n<%s>.\n", "a;         // =>\n", add_markers("a;", 10)));
79         assert("a;         // =>\n" == add_markers("a; // =>\n", 10),
80                format("\n<%s> expected but was\n<%s>.\n", "a;         // =>\n", add_markers("a; // =>\n", 10)));
81     }
82
83
84     static char[] add_import(char[] code) {
85         char[] ret = "import std.cstream;";
86         return ret ~ code.sub(`^#!.+`, "");
87     }
88  
89     unittest {
90         assert("import std.cstream;void main() {}\n" == add_import("void main() {}\n"),
91                format("\n<%s> expected but was\n<%s>.\n", "import std.cstream;void main() {}\n", add_import("void main() {}\n")));
92         assert("import std.cstream;\nvoid main() {}\n" == add_import("#!/usr/bin/env rdmd\nvoid main() {}\n"),
93                format("\n<%s> expected but was\n<%s>.\n", "import std.cstream;\nvoid main() {}\n", add_import("#!/usr/bin/env rdmd\nvoid main() {}\n")));
94     }
95
96     char[] annotate(char[] code) {
97         auto idx = 0;
98         char[] newcode = add_import(code).
99             sub(r"^(.*) // =>.*",
100                 (RegExp l){ return prepare_line(l.match(1), ++idx);},
101                 "gm");
102         if (dump) write(dump, newcode);
103
104         char[][] exec_result = execute_tmpfile(newcode, interpreter);
105         char[] stdout = exec_result[0], stderr = exec_result[1];
106
107         RuntimeData runtime_data = extract_data(stderr);
108
109         set_internal_info();
110
111         idx = 0;
112         char[] annotated = code.sub(r"^(.*) // =>.*",
113                                     (RegExp re){
114                                         auto l = re.match(0);
115                                         auto expr = re.match(1);
116      
117                                         if (RegExp(r"^\s*//").test(l))
118                                             return l;
119                                         else
120                                             return annotated_line(l, expr, runtime_data, ++idx);
121                                     }, "gm").sub(r" // !>.*", "", "gm").sub(r"// (>>|~>).*$", "", "gm");
122
123         auto ret = final_decoration(annotated, (stdout ~ stderr).splitlines()).join("");
124         if (output_stdout && stdout != "") {
125             char[] retplus;
126             foreach(line; stdout.splitlines()) {
127                 if (!( (compile_error_regexp.test(line)) ||
128                        (RUNTIME_ERROR_RE.test(line)))) {
129                     retplus ~= "// >> " ~ line ~ newline;
130                 }
131             }
132             ret ~= retplus;
133         }
134         return ret;
135     }
136    
137     static char[] cast_void(char[] expression) {
138         if (std.string.find(expression, "cast(void)") >= 0) {
139             return expression;
140         } else {
141             RegExp re;
142             char[] lspaces, rspaces;
143             re = expression.search(r"^\s*");
144             lspaces = re ? re.match(0) : "";
145             re = expression.search(r"\s+$");
146             rspaces = re ? re.match(0) : "";
147             return format("%scast(void)(%s);%s", lspaces, expression.strip().chomp(";"), rspaces);
148         }
149     }
150
151     unittest {
152         assert("cast(void)(1+1);" == cast_void("1+1;"),
153                format("\n<%s> expected but was\n<%s>.\n", "cast(void)(1+1);", cast_void("1+1;")));
154         assert("cast(void)(1+1)" == cast_void("cast(void)(1+1)"),
155                format("\n<%s> expected but was\n<%s>.\n", "cast(void)(1+1)", cast_void("cast(void)(1+1)")));
156     }
157
158     static char[] annotated_line(char[] line, char[] expression, RuntimeData runtime_data, int idx) {
159         char[][] elems;
160
161         try {
162             foreach(x; runtime_data.results[idx]) {
163                 elems ~= x[1];
164             }
165             return cast_void(expression) ~ " // => " ~ elems.join(", ");
166         } catch  {
167             return cast(char[])"";
168         }
169     }
170
171     unittest {
172         RuntimeData runtime_data;
173         runtime_data.results[0] = [["klass", "1"]];
174         runtime_data.results[1] = [["klass", "2"], ["klass", "4"], ["klass", "6"]];
175
176         char[] expected = "cast(void)(a); // => 1";
177         char[] actual = annotated_line("ignore", "a", runtime_data, 0);
178         assert(expected == actual,
179                format("\n<%s> expected but was\n<%s>.\n", expected, actual));
180
181         expected = "cast(void)(a); // => 2, 4, 6";
182         actual = annotated_line("ignore", "cast(void)(a);", runtime_data, 1);
183         assert(expected == actual,
184                format("\n<%s> expected but was\n<%s>.\n", expected, actual));
185
186         assert("" == annotated_line("", "", runtime_data, 999),
187                format("\n<%s> expected but was\n<%s>.\n", "", annotated_line("", "", runtime_data, 999)));
188
189     }
190
191     static char[] prepare_line_annotation(char[] expr, int idx) {
192         expr = expr.sub(r"cast\(void\)", "").sub(r";\s*$", "");
193         return format(`derr.writefln("%s[%d] => %%s %%s", "klass", (%s));`, MARKER, idx, expr);
194     }
195     alias prepare_line_annotation prepare_line;
196
197     unittest {
198         assert("derr.writefln(\"" ~ MARKER ~ "[0] => %s %s\", \"klass\", (  1+1));" == prepare_line_annotation("  1+1;", 0),
199                format("\n<%s> expected but was\n<%s>.\n", "derr.writefln(\"" ~ MARKER ~ "[0] => %s %s\", \"klass\", (  1+1));", prepare_line_annotation("  1+1;", 0)));
200
201         auto expected = "derr.writefln(\"" ~ MARKER ~ "[1] => %s %s\", \"klass\", (  (1+1)));";
202         auto actual = prepare_line_annotation("  cast(void)(1+1);", 1);
203         assert(expected == actual,
204                format("\n<%s> expected but was\n<%s>.\n", expected, actual));
205     }
206
207     const char[] DXMP_TMPFILE_FMT = "dxmp_tmpfile_999_%d.d";
208
209     static char[][] execute_tmpfile(char[] code, char[] interpreter) {
210         enum { stdin=1, stdout=2, stderr=3 };
211         char[][] fname;
212         int i;
213         fname.length = 4;
214         for (i=stdin; i<=stderr; i++) {
215             fname[i] = format(DXMP_TMPFILE_FMT, i);
216         }
217         write(fname[stdin], code.chomp() ~ newline);
218         system(format("%s %s > %s 2> %s", interpreter, fname[stdin], fname[stdout], fname[stderr]));
219
220         char[][] ret = [ cast(char[])read(fname[stdout]), cast(char[])read(fname[stderr]) ];
221         for (i=stdin; i<=stderr; i++)
222             std.file.remove(fname[i]);
223         return ret;
224     }
225    
226  
227     unittest {
228         assert(["hoge\n", ""] == execute_tmpfile("hoge", "cat"),
229                format("\n<%s> expected but was\n<%s>.\n", ["hoge\n", ""], execute_tmpfile("hoge", "cat")));
230         assert(["", "0\n"] == execute_tmpfile("hoge", "ruby -e '$stderr.puts 0'"),
231                format("\n<%s> expected but was\n<%s>.\n", ["", "0\n"], execute_tmpfile("hoge", "ruby -e '$stderr.puts 0'")));
232
233         assert(0 == format(DXMP_TMPFILE_FMT,1).exists(),
234                format("\n<%s> expected but was\n<%s>.\n", 0, format(DXMP_TMPFILE_FMT,1).exists()));
235         assert(0 == format(DXMP_TMPFILE_FMT,2).exists(),
236                format("\n<%s> expected but was\n<%s>.\n", 0, format(DXMP_TMPFILE_FMT,2).exists()));
237         assert(0 == format(DXMP_TMPFILE_FMT,3).exists(),
238                format("\n<%s> expected but was\n<%s>.\n", 0, format(DXMP_TMPFILE_FMT,3).exists()));
239     }
240
241     static RuntimeData extract_data(char[] output){
242         char[][][][int] results;
243         char[][][int] exceptions;
244         foreach(line; output.splitlines()) {
245             auto md = XMP_RE;
246             if (XMP_RE.test(line)) {
247                 int result_id = toInt(md.match(1));
248                 char[] op = md.match(2);
249                 char[] result = md.match(3);
250                 switch(op) {
251                 case "=>":
252                     RegExp md2 = RegExp(r"(\S+)\s+(.*)");
253                     md2.find(result);
254                     char[] klass = md2.match(1);
255                     char[] value = md2.match(2);
256                     results[result_id] ~= [klass, value];
257                     break;
258                 case "~>":
259                     exceptions[result_id] ~= result;
260                     break;
261                 default:
262                     assert(0);
263                 }
264             }
265         }
266
267         RuntimeData ret;
268         ret.results = results;
269         ret.exceptions = exceptions;
270         return ret;
271     }
272
273     unittest {
274         char[] output = format(r"%s[0] => klass 2
275 %s[1] => klass 3
276 %s[2] ~> an error", MARKER, MARKER, MARKER);
277         RuntimeData runtime_data = extract_data(output);
278         auto results = runtime_data.results;
279         auto exceptions = runtime_data.exceptions;
280         assert( [["klass", "2"]] == results[0] ,
281                 format("\n<%s> expected but was\n<%s>.\n", [["klass", "2"]], results[0]));
282         assert( [["klass", "3"]] == results[1] ,
283                 format("\n<%s> expected but was\n<%s>.\n", [["klass", "3"]], results[1]));
284         assert( "an error" == exceptions[2][0] ,
285                 format("\n<%s> expected but was\n<%s>.\n", "an error", exceptions[2][0]));
286     }
287  
288     char[][] final_decoration(char[] code, char[][] output) {
289         char[][int] runtime_errors;
290         foreach(x; output) {
291             if (RUNTIME_ERROR_RE.test(x)) {
292                 runtime_errors[toInt(RUNTIME_ERROR_RE.match(2))] = RUNTIME_ERROR_RE.match(1);
293             }
294         }
295         int idx = 1;
296         char[][] ret;
297         foreach(line; code.splitlines()) {
298             if (idx in runtime_errors) {
299                 auto w = runtime_errors[idx];
300                 ret ~= line ~ " // !> " ~ w ~ newline;
301             } else {
302                 ret ~= line ~ newline;
303             }
304             idx++;
305         }
306
307         foreach(inout x; output) {
308             x = x.chomp() ~ newline;
309             if (RUNTIME_ERROR_RE.test(x))
310                 x = "";
311         }
312    
313         if (compile_error_regexp.test(output.join(""))) {
314             char[] exception_lines = output.join("").sub(input_d, filename, "gm");
315             foreach(line; exception_lines.splitlines())
316                 ret ~= "// ~> " ~ line.chomp() ~ newline;
317         }
318         return ret;
319     }
320  
321     char[] input_d;
322     RegExp compile_error_regexp;
323     void set_internal_info() {
324         input_d = format(DXMP_TMPFILE_FMT, 1);
325         auto error = input_d ~ r"\([0-9]+\)";
326         compile_error_regexp = RegExp(format("^(%s|warning -).*", error));
327     }
328
329     unittest {
330         char[] code = r"void main(){
331 assert(0)
332 }
333 ";
334         auto x = new XMPFilter();
335         x.filename = "temp.d";
336         x.set_internal_info();
337
338         {
339             char[] expected = "assert(0) // !> AssertError Failure\n";
340             char[] actual = x.final_decoration(code, ["Error: AssertError Failure temp(2) \n"])[1];
341             assert(expected == actual,
342                    format("\n<%s> expected but was\n<%s>.\n", expected, actual));
343         }
344
345         {
346             char[][] output =
347                 [ x.input_d ~ "(40): Error: undefined identifier x\n",
348                   x.input_d ~ "(40): Error: function expected before (), not x of type int\n",
349                   "rdmd: Couldn't compile or execute dxmp_testcases.d.\n"];
350             char[][] expected =
351                 ["code\n",
352                  "// ~> temp.d(40): Error: undefined identifier x\n",
353                  "// ~> temp.d(40): Error: function expected before (), not x of type int\n",
354                  "// ~> rdmd: Couldn't compile or execute dxmp_testcases.d.\n"];
355             char[][] actual = x.final_decoration("code\n", output);
356             assert(expected == actual,
357                    format("\n<%s> expected but was\n<%s>.\n", expected, actual));
358         }
359
360         {
361             char[][] output = [ cast(char[])(MARKER ~ "[2] => klass 20") ];
362             assert(null == x.final_decoration("", output),
363                    format("\n<%s> expected but was\n<%s>.\n", null, x.final_decoration("", output)));
364
365         }
366    
367     }
368 }
Note: See TracBrowser for help on using the browser.