| 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 |
} |
|---|