merge
[tamarin-stm.git] / extensions / selftest.as
blobe20383be8bbee317003b6c825ea7e29ec1efc6ab
1 /* -*- mode: java; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
2 /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is [Open Source Virtual Machine.].
18 * The Initial Developer of the Original Code is
19 * Adobe System Incorporated.
20 * Portions created by the Initial Developer are Copyright (C) 2008
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Adobe AS3 Team
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 * Generate selftest C++ code from spec files.
43 * Usage
45 * selftest.abc filename ...
47 * Overview
49 * The input to this program is some set of file names. These
50 * should have a .st extension and conform to the grammar below.
52 * This program processes the input files and creates the
53 * corresponding .cpp files (same path and file name, just different
54 * extension). These .cpp files should be included in the project.
56 * Additionally, the program produces a single file "SelftestInit.cpp"
57 * in the program's working directory. This will contain a single
58 * function, "SelftestRunner::createGeneratedSelftestClasses()", which
59 * is called on AVM startup. This file too should be included in the
60 * project.
62 * Grammar
64 * file ::= component category ifdef* prefix? (decls | methods | prologue | epilogue | test)*
65 * component ::= SOL "%%component" ident EOL
66 * category ::= SOL "%%category" ident EOL
67 * ifdef ::= SOL ( "%%ifdef" | "%%ifndef" ) ident EOL
68 * prefix ::= SOL "%%prefix" EOL text
69 * decls ::= SOL "%%decls" EOL text
70 * methods ::= SOL "%%methods" EOL text
71 * prologue ::= SOL "%%prologue" EOL text
72 * epilogue ::= SOL "%%epilogue" EOL text
73 * test ::= SOL ( "%%test" | %%explicit ) ident EOL (text | verify)*
74 * verify ::= SOL "%%verify" expr EOL
75 * expr ::= text but not newline
76 * text ::= arbitrary text not containing "%%" at SOL
77 * EOL ::= newline
78 * SOL ::= beginning of line, possibly with leading spaces
80 * Comment lines are C++ style, "//" possibly preceded by blanks, to the end of line.
81 * Blank lines are legal everywhere.
83 * Semantic constraints
85 * There can be an arbitrary number of tests, but at least one.
86 * There can be at most one prologue or epilogue.
87 * There can be an arbitrary number of decls and methods, they are just cat'ed together.
88 * There must be at least one verify in a test.
90 * (The requirements for at least one test and at least one verify
91 * are fascistic, but may help catch errors. Experience will tell.)
93 * Semantics
95 * A selftest file defines one or more test cases for a component
96 * (like "avmplus" or "player") and a category (like "peephole" or
97 * "jit"). Additionally, each test is named. The test harness lets
98 * the user define the components, categories, and tests to run.
100 * Each generated file is compiled conditionally on the existence of
101 * VMCFG_SELFTEST and also each of the names declared in %%ifdef
102 * statements.
104 * Code is generated into a "namespace avmplus" block.
106 * The %%prefix text, if any, is inserted verbatim before generated
107 * classes or methods; it is useful for "use namespace", class
108 * definitions, and so on.
110 * Apart from the prefix, the code that is generated comprises a
111 * class and its methods. Text in %%decls blocks is inserted
112 * verbatim into the class definition; text in %%methods blocks is
113 * inserted verbatim into the top level of the file following the
114 * class definition. These sections allow the test case to define
115 * and use auxiliary data and methods.
117 * The code in a %%prologue section will be run once, before any
118 * test in the file is run. The code in an %%epilogue section will
119 * also be run once, after all tests in the file have been run.
120 * They are typically used to initialize instance data (declared by
121 * %%decls).
123 * Each %%test or %%explicit is enclosed in an anonymous method of
124 * the generated class. They have the same meaning but if
125 * %%explicit is used then the test must be selected explicitly by
126 * component, category, and test. Each %%verify that appears in the
127 * test will test its condition, and if it does not hold that test
128 * function will be aborted and the error recorded. Subsequent
129 * %%verify statements in that test will not be executed; however,
130 * subsequent tests in the same selftest spec will be executed.
132 * Comment lines preceding directives are copied verbatim to the output.
135 package selftest
137 import avmplus.*;
139 class Selftest {
140 var comments = [];
141 var component = null;
142 var category = null;
143 var ifdefs = [];
144 var ifndefs = [];
145 var prefix = [];
146 var decls = [];
147 var methods = [];
148 var prologue = null;
149 var epilogue = null;
150 var tests = [];
151 // generated by the formatter
152 var ifdef_text = null;
153 var ifndef_text = null;
154 var classname = null;
157 function parse(lines, filename) {
158 var st = new Selftest;
159 var i=0;
160 var l=lines.length;
161 var res;
162 var state = -1;
163 while (i < l) {
164 var line = lines[i++];
165 if (res = /^\s*%%component\s+([a-zA-Z_][a-zA-Z0-9_]+)\s*$/.exec(line)) {
166 if (state > 0)
167 throw "Too late to define component";
168 state = 0;
169 if (st.component != null)
170 throw "Component already defined";
171 st.component = res[1];
173 else if (res = /^\s*%%category\s+([a-zA-Z_][a-zA-Z0-9_]+)\s*$/.exec(line)) {
174 if (state > 0)
175 throw "Too late to define category";
176 state = 0;
177 if (st.category != null)
178 throw "Category already defined";
179 st.category = res[1];
181 else if (res = /^\s*%%ifdef\s+([a-zA-Z_][a-zA-Z0-9_]+)\s*$/.exec(line)) {
182 if (state > 1)
183 throw "Too late to define ifdef";
184 state = 1;
185 st.ifdefs.push(res[1]);
187 else if (res = /^\s*%%ifndef\s+([a-zA-Z_][a-zA-Z0-9_]+)\s*$/.exec(line)) {
188 if (state > 1)
189 throw "Too late to define ifndef";
190 state = 1;
191 st.ifndefs.push(res[1]);
193 else if (line.match(/^\s*%%prefix/)) {
194 if (state > 2)
195 throw "Too late to define prefix";
196 state = 2;
197 st.prefix = text();
198 state = 3;
200 else if (line.match(/^\s*%%decls/)) {
201 state = 3;
202 pushMultiple(st.decls, text());
204 else if (line.match(/^\s*%%methods/)) {
205 state = 3;
206 pushMultiple(st.methods, text());
208 else if (line.match(/^\s*%%prologue/)) {
209 state = 3;
210 if (st.prologue != null)
211 throw "Prologue already defined";
212 st.prologue = text();
214 else if (line.match(/^\s*%%epilogue/)) {
215 state = 3;
216 if (st.epilogue != null)
217 throw "Epilogue already defined";
218 st.epilogue = text();
220 else if (res = /^\s*(%%test|%%explicit)\s+([a-zA-Z_][a-zA-Z0-9_]+)\s*$/.exec(line)) {
221 state = 3;
222 var loc = i;
223 var t = [];
224 var vs = 0;
225 var name = res[2];
226 var explicit = res[1] == "%%explicit";
227 while (i < l) {
228 pushMultiple(t, text());
229 if (i == l)
230 break;
231 line = lines[i];
232 if ((res = /^\s*%%verify\s+(.*)$/.exec(line)) == null)
233 break;
234 vs++;
235 i++;
236 // The line number is the line number of the following line, not of the #line itself
237 t.push("#line " + i + " \"" + filename + "\"");
238 t.push("verifyPass(" + res[1] + ", \"" + quote(res[1]) + "\", __FILE__, __LINE__);");
240 if (vs == 0)
241 throw "No %%verify statements for test on line " + loc;
242 st.tests.push([name, explicit, t]);
244 else if (line.match(/^\s*\/\//)) {
245 if (state < 0)
246 st.comments.push(line);
248 else if (line.match(/^\s*$/)) {
249 if (state < 0)
250 st.comments.push(line);
252 else {
253 throw "Bogus line " + (i+1) + ": " + line;
257 if (st.category == null)
258 throw "Category missing";
259 if (st.component == null)
260 throw "Component missing";
261 if (st.tests.length == 0)
262 throw "No tests";
264 return st;
266 // note, this uses i, l, and lines and updates i
267 function text() {
268 var res;
269 var t = [];
270 while (i < l) {
271 line = lines[i];
272 if (line.match(/^\s*%%/))
273 break;
274 i++;
275 t.push(line);
277 return t;
280 function quote(s) {
281 return s.replace(/\"/g, "\\\"");
285 function formatSelftest(input, st) {
286 var s = [];
287 var classname = "ST_" + st.component + "_" + st.category;
288 st.classname = classname;
289 s.push("// Generated from " + input);
290 pushMultiple(s, st.comments);
291 s.push("#include \"avmshell.h\"");
292 st.ifdef_text = null;
293 if (st.ifdefs.length > 0)
294 st.ifdef_text = "#if defined " + st.ifdefs.join(" && defined ");
295 if (st.ifndefs.length > 0)
296 st.ifndef_text = "#if !defined " + st.ifndefs.join(" && !defined ");
297 s.push("#ifdef VMCFG_SELFTEST");
298 if (st.ifdef_text != null)
299 s.push(st.ifdef_text);
300 if (st.ifndef_text != null)
301 s.push(st.ifndef_text);
302 s.push("namespace avmplus {");
304 for ( var i=0 ; i < st.prefix.length ; i++ )
305 s.push(st.prefix[i]);
307 s.push("class " + classname + " : public Selftest {");
308 s.push("public:");
309 s.push(classname + "(AvmCore* core);");
310 s.push("virtual void run(int n);");
311 if (st.prologue)
312 s.push("virtual void prologue();");
313 if (st.epilogue)
314 s.push("virtual void epilogue();");
315 s.push("private:");
316 s.push("static const char* ST_names[];");
317 s.push("static const bool ST_explicits[];");
318 for ( var i=0 ; i < st.tests.length ; i++ )
319 s.push("void test" + i + "();");
320 pushMultiple(s, st.decls);
321 s.push("};");
323 s.push(classname + "::" + classname + "(AvmCore* core)");
324 s.push(" : Selftest(core, \"" + st.component + "\", \"" + st.category + "\", " + classname + "::ST_names," + classname + "::ST_explicits)");
325 s.push("{}");
327 s.push("const char* " + classname + "::ST_names[] = {" +
328 st.tests.map(function (t) { return '"' + t[0] + '"' }).join(",") + ", NULL };");
330 s.push("const bool " + classname + "::ST_explicits[] = {" +
331 st.tests.map(function (t) { return t[1] }).join(",") + ", false };");
333 s.push("void " + classname + "::run(int n) {");
334 s.push("switch(n) {");
335 for ( var i=0 ; i < st.tests.length ; i++ )
336 s.push("case " + i + ": test" + i + "(); return;");
337 s.push("}");
338 s.push("}");
340 if (st.prologue) {
341 s.push("void " + classname + "::prologue() {");
342 pushMultiple(s, st.prologue);
343 s.push("}");
346 if (st.epilogue) {
347 s.push("void " + classname + "::epilogue() {");
348 pushMultiple(s, st.epilogue);
349 s.push("}");
352 if (st.methods) {
353 pushMultiple(s, st.methods);
356 for ( var i=0 ; i < st.tests.length ; i++ ) {
357 s.push("void " + classname + "::test" + i + "() {");
358 pushMultiple(s, st.tests[i][2]);
359 s.push("}");
362 s.push("void create_" + st.component + "_" + st.category + "(AvmCore* core) { new " + classname + "(core); }");
364 s.push("}");
365 if (st.ifndef_text != null)
366 s.push("#endif");
367 if (st.ifdef_text != null)
368 s.push("#endif");
369 s.push("#endif");
371 return s.join("\n") + "\n";
374 function pushMultiple(stk, xs) {
375 for ( var i=0 ; i < xs.length ; i++ )
376 stk.push(xs[i]);
377 return stk;
380 // VS2008 requires the extern declarations to be at the top level for
381 // the namespace tagging to work out correctly.
382 function formatGeneratedInitializer() {
383 var s = [];
384 s.push("/* ***** BEGIN LICENSE BLOCK *****");
385 s.push(" * Version: MPL 1.1/GPL 2.0/LGPL 2.1");
386 s.push(" *");
387 s.push(" * The contents of this file are subject to the Mozilla Public License Version");
388 s.push(" * 1.1 (the \"License\"); you may not use this file except in compliance with");
389 s.push(" * the License. You may obtain a copy of the License at");
390 s.push(" * http://www.mozilla.org/MPL/");
391 s.push(" *");
392 s.push(" * Software distributed under the License is distributed on an \"AS IS\" basis,");
393 s.push(" * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License");
394 s.push(" * for the specific language governing rights and limitations under the");
395 s.push(" * License.");
396 s.push(" *");
397 s.push(" * The Original Code is [Open Source Virtual Machine.].");
398 s.push(" *");
399 s.push(" * The Initial Developer of the Original Code is");
400 s.push(" * Adobe System Incorporated.");
401 s.push(" * Portions created by the Initial Developer are Copyright (C) 2008");
402 s.push(" * the Initial Developer. All Rights Reserved.");
403 s.push(" *");
404 s.push(" * Contributor(s):");
405 s.push(" * Adobe AS3 Team");
406 s.push(" *");
407 s.push(" * Alternatively, the contents of this file may be used under the terms of");
408 s.push(" * either the GNU General Public License Version 2 or later (the \"GPL\"), or");
409 s.push(" * the GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"),");
410 s.push(" * in which case the provisions of the GPL or the LGPL are applicable instead");
411 s.push(" * of those above. If you wish to allow use of your version of this file only");
412 s.push(" * under the terms of either the GPL or the LGPL, and not to allow others to");
413 s.push(" * use your version of this file under the terms of the MPL, indicate your");
414 s.push(" * decision by deleting the provisions above and replace them with the notice");
415 s.push(" * and other provisions required by the GPL or the LGPL. If you do not delete");
416 s.push(" * the provisions above, a recipient may use your version of this file under");
417 s.push(" * the terms of any one of the MPL, the GPL or the LGPL.");
418 s.push(" *");
419 s.push(" * ***** END LICENSE BLOCK ***** */");
420 s.push("// Initialization code for generated selftest code");
421 s.push("#include \"avmshell.h\"");
422 s.push("namespace avmplus {");
423 s.push("#ifdef VMCFG_SELFTEST");
424 for ( var i=0 ; i < selftests.length ; i++ ) {
425 var st = selftests[i];
426 if (st.ifdef_text != null)
427 s.push(st.ifdef_text);
428 if (st.ifndef_text != null)
429 s.push(st.ifndef_text);
430 s.push("extern void create_" + st.component + "_" + st.category + "(AvmCore* core);");
431 if (st.ifndef_text != null)
432 s.push("#endif");
433 if (st.ifdef_text != null)
434 s.push("#endif");
436 s.push("void SelftestRunner::createGeneratedSelftestClasses() {");
437 for ( var i=0 ; i < selftests.length ; i++ ) {
438 var st = selftests[i];
439 if (st.ifdef_text != null)
440 s.push(st.ifdef_text);
441 if (st.ifndef_text != null)
442 s.push(st.ifndef_text);
443 s.push("create_" + st.component + "_" + st.category + "(core);");
444 if (st.ifndef_text != null)
445 s.push("#endif");
446 if (st.ifdef_text != null)
447 s.push("#endif");
449 s.push("}");
450 s.push("#endif // VMCFG_SELFTEST");
451 s.push("}");
452 return s.join("\n") + "\n";
455 var selftests = [];
457 function process(input, output) {
458 var st = parse(File.read(input).split("\n"), input);
459 selftests.push(st);
460 File.write(output, formatSelftest(input, st));
463 for ( var i=0 ; i < System.argv.length ; i++ ) {
464 if (!System.argv[i].match(/\.st$/))
465 print("WARNING: ignoring non-selftes file " + System.argv[i]);
466 process(System.argv[i], System.argv[i].replace(/\.st$/, ".cpp"));
468 File.write("SelftestInit.cpp", formatGeneratedInitializer());