775 - starting to reorg C++ mu to use layers
This commit is contained in:
parent
6042828bde
commit
5153091647
|
@ -0,0 +1,111 @@
|
|||
// You guessed right: the '000' prefix means you should start reading here.
|
||||
//
|
||||
// This project is setup to load all files with a numeric prefix. Just create
|
||||
// a new file and start hacking.
|
||||
//
|
||||
// The first few files (00*) are independent of what this program does, an
|
||||
// experimental skeleton that will hopefully make it both easier for others to
|
||||
// understand and more malleable, easier to rewrite and remould into radically
|
||||
// different shapes without breaking in subtle corner cases. The premise is
|
||||
// that understandability and rewrite-friendliness are related in a virtuous
|
||||
// cycle. Doing one well makes it easier to do the other.
|
||||
//
|
||||
// Lower down, this file contains a legal, bare-bones C++ program. It doesn't
|
||||
// do anything yet; subsequent files will add behaviors by inserting lines
|
||||
// into it with directives like:
|
||||
// :(after "more events")
|
||||
// This will insert the following lines after a line in the program containing
|
||||
// the words "more events".
|
||||
//
|
||||
// Directives free up the programmer to order code for others to read rather
|
||||
// than as forced by the computer or compiler. Each individual feature can be
|
||||
// organized in a self-contained 'layer' that adds code to many different data
|
||||
// structures and functions all over the program. The right decomposition into
|
||||
// layers will let each layer make sense in isolation.
|
||||
//
|
||||
// "If I look at any small part of it, I can see what is going on -- I don't
|
||||
// need to refer to other parts to understand what something is doing.
|
||||
//
|
||||
// If I look at any large part in overview, I can see what is going on -- I
|
||||
// don't need to know all the details to get it.
|
||||
//
|
||||
// Every level of detail is as locally coherent and as well thought-out as
|
||||
// any other level."
|
||||
//
|
||||
// -- Richard Gabriel, "The Quality Without A Name"
|
||||
// (http://dreamsongs.com/Files/PatternsOfSoftware.pdf, page 42)
|
||||
//
|
||||
// Directives are powerful; they permit inserting or modifying any point in
|
||||
// the program. Using them tastefully requires mapping out specific lines as
|
||||
// waypoints for future layers to hook into. Often such waypoints will be in
|
||||
// comments, capitalized to hint that other layers rely on their presence.
|
||||
//
|
||||
// A single waypoint might have many different code fragments hooking into
|
||||
// it from all over the codebase. Use 'before' directives to insert
|
||||
// code at a location in order, top to bottom, and 'after' directives to
|
||||
// insert code in reverse order. By convention waypoints intended for insertion
|
||||
// before begin with 'End'. Notice below how the layers line up above the "End
|
||||
// Foo" waypoint.
|
||||
//
|
||||
// File 001 File 002 File 003
|
||||
// ============ =================== ===================
|
||||
// // Foo
|
||||
// ------------
|
||||
// <---- :(before "End Foo")
|
||||
// ....
|
||||
// ...
|
||||
// ------------
|
||||
// <---------------------------- :(before "End Foo")
|
||||
// ....
|
||||
// ...
|
||||
// // End Foo
|
||||
// ============
|
||||
//
|
||||
// Here's part of a layer in color: http://i.imgur.com/0eONnyX.png. Directives
|
||||
// are shaded dark. Notice the references to waypoints lower down in this
|
||||
// file.
|
||||
//
|
||||
// Layers do more than just shuffle code around. Past the initial skeleton of
|
||||
// this program (currently 00*-02*), it ought to be possible to stop loading
|
||||
// after any file/layer, build and run the program, and pass all tests for
|
||||
// loaded features. (Relevant is http://youtube.com/watch?v=c8N72t7aScY, a
|
||||
// scene from "2001: A Space Odyssey".)
|
||||
//
|
||||
// This 'subsetting guarantee' ensures that this directory contains a
|
||||
// cleaned-up narrative of the evolution of this codebase. Organizing
|
||||
// autobiographically allows a newcomer to rapidly orient himself, reading the
|
||||
// first few files to understand a simple gestalt of a program's core purpose
|
||||
// and features, and later gradually working his way through other features as
|
||||
// the need arises. Each step should be as simple as possible (but no simpler).
|
||||
//
|
||||
// Programmers shouldn't need to understand everything about a program to hack
|
||||
// on it. But they shouldn't be prevented from a thorough understanding of
|
||||
// each aspect either. The goal of layers is to reward curiosity.
|
||||
|
||||
// Includes
|
||||
// End Includes
|
||||
|
||||
// Types
|
||||
// End Types
|
||||
|
||||
// prototypes are auto-generated; define your functions in any order
|
||||
#include "function_list" // by convention, files ending with '_list' are auto-generated
|
||||
|
||||
// Globals
|
||||
// End Globals
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc > 1) {
|
||||
// Commandline Options
|
||||
}
|
||||
|
||||
setup();
|
||||
return 0; // End Main
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// End Setup
|
||||
}
|
||||
|
||||
// Without directives or with the :(code) directive, lines get added at the
|
||||
// end.
|
|
@ -0,0 +1,79 @@
|
|||
// A simple test harness. To create new tests define functions starting with
|
||||
// 'test_'. To run all tests so defined, run:
|
||||
// $ wart test
|
||||
//
|
||||
// So far it seems tasteful for layers to never ever reach back to modify
|
||||
// previously-defined tests. Every test is a contract once written, and should
|
||||
// pass as-is if it is included, regardless of how much later layers change
|
||||
// the program. Avoid writing 'temporary' tests that only work with some
|
||||
// subsets of the program.
|
||||
|
||||
:(before "End Types")
|
||||
typedef void (*test_fn)(void);
|
||||
|
||||
:(before "End Globals")
|
||||
const test_fn Tests[] = {
|
||||
#include "test_list" // auto-generated; see makefile
|
||||
};
|
||||
|
||||
bool Passed = true;
|
||||
|
||||
long Num_failures = 0;
|
||||
|
||||
#define CHECK(X) \
|
||||
if (!(X)) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define CHECK_EQ(X, Y) \
|
||||
if ((X) != (Y)) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \
|
||||
cerr << " got " << (X) << '\n'; /* BEWARE: multiple eval */ \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
:(after "Commandline Options")
|
||||
if (is_equal(argv[1], "test")) {
|
||||
run_tests();
|
||||
return 0;
|
||||
}
|
||||
|
||||
:(code)
|
||||
void run_tests() {
|
||||
time_t t; time(&t);
|
||||
cerr << "C tests: " << ctime(&t);
|
||||
for (unsigned long i=0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) {
|
||||
setup();
|
||||
// End Test Setup
|
||||
(*Tests[i])();
|
||||
if (Passed) cerr << ".";
|
||||
// Test Teardown
|
||||
// End Test Teardown
|
||||
}
|
||||
|
||||
cerr << '\n';
|
||||
if (Num_failures > 0)
|
||||
cerr << Num_failures << " failure"
|
||||
<< (Num_failures > 1 ? "s" : "")
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
bool is_equal(char* s, const char* lit) {
|
||||
return strncmp(s, lit, strlen(lit)) == 0;
|
||||
}
|
||||
|
||||
:(before "End Includes")
|
||||
#include<iostream>
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::iostream;
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
|
||||
#include<cstring>
|
|
@ -0,0 +1,478 @@
|
|||
// The goal of this skeleton is to make programs more easy to understand and
|
||||
// more malleable, easy to rewrite in radical ways without accidentally
|
||||
// breaking some corner case. Tests further both goals. They help
|
||||
// understandability by letting one make small changes and get feedback. What
|
||||
// if I wrote this line like so? What if I removed this function call, is it
|
||||
// really necessary? Just try it, see if the tests pass. Want to explore
|
||||
// rewriting this bit in this way? Tests put many refactorings on a firmer
|
||||
// footing.
|
||||
//
|
||||
// But the usual way we write tests seems incomplete. Refactorings tend to
|
||||
// work in the small, but don't help with changes to function boundaries. If
|
||||
// you want to extract a new function you have to manually test-drive it to
|
||||
// create tests for it. If you want to inline a function its tests are no
|
||||
// longer valid. In both cases you end up having to reorganize code as well as
|
||||
// tests, an error-prone activity.
|
||||
//
|
||||
// This file tries to fix this problem by supporting domain-driven testing
|
||||
// rather than coverage-driven testing. The goal isn't to test all possible
|
||||
// paths in the code any longer, but to focus on the domain of inputs the
|
||||
// program should work on. All tests invoke the program in a single way: by
|
||||
// calling run() with different inputs. The program operates on the input and
|
||||
// logs _facts_ it deduces to a trace:
|
||||
// trace("label") << "fact 1: " << val;
|
||||
//
|
||||
// The tests check for facts:
|
||||
// :(scenario foo)
|
||||
// 34 # call run() with this input
|
||||
// +label: fact 1: 34 # trace should have logged this at the end
|
||||
// -label: fact 1: 35 # trace should never contain such a line
|
||||
//
|
||||
// Since we never call anything but the run() function directly, we never have
|
||||
// to rewrite the tests when we reorganize the internals of the program. We
|
||||
// just have to make sure our rewrite deduces the same facts about the domain,
|
||||
// and that's something we're going to have to do anyway.
|
||||
//
|
||||
// To avoid the combinatorial explosion of integration tests, we organize the
|
||||
// program into different layers, and each fact is logged to the trace with a
|
||||
// specific label. Individual tests can focus on specific labels. In essence,
|
||||
// validating the facts logged with a specific label is identical to calling
|
||||
// some internal subsystem.
|
||||
//
|
||||
// Traces interact salubriously with layers. Thanks to our ordering
|
||||
// directives, each layer can contain its own tests. They may rely on other
|
||||
// layers, but when a test fails its usually due to breakage in the same
|
||||
// layer. When multiple tests fail, it's usually useful to debug the very
|
||||
// first test to fail. This is in contrast with the traditional approach,
|
||||
// where changes can cause breakages in faraway subsystems, and picking the
|
||||
// right test to debug can be an important skill to pick up.
|
||||
//
|
||||
// A final wrinkle is for recursive functions; it's often useful to segment
|
||||
// calls of different depth in the trace:
|
||||
// +eval/1: => 34 # the topmost call to eval should have logged this line
|
||||
// (look at new_trace_frame below)
|
||||
//
|
||||
// To build robust tests, trace facts about your domain rather than details of
|
||||
// how you computed them.
|
||||
//
|
||||
// More details: http://akkartik.name/blog/tracing-tests
|
||||
//
|
||||
// ---
|
||||
//
|
||||
// Between layers and domain-driven testing, programming starts to look like a
|
||||
// fundamentally different activity. Instead of a) superficial, b) local rules
|
||||
// on c) code [like http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet],
|
||||
// we allow programmers to engage with the a) deep, b) global structure of the
|
||||
// c) domain. If you can systematically track discontinuities in the domain
|
||||
// you don't care if the code used gotos as long as it passed the tests. If
|
||||
// tests become more robust to run it becomes easier to try out radically
|
||||
// different implementations for the same program. If code is super-easy to
|
||||
// rewrite, it becomes less important what indentation style it uses, or that
|
||||
// the objects are appropriately encapsulated, or that the functions are
|
||||
// referentially transparent.
|
||||
//
|
||||
// Instead of plumbing, programming becomes building and gradually refining a
|
||||
// map of the environment the program must operate under. Whether a program is
|
||||
// 'correct' at a given point in time is a red herring; what matters is
|
||||
// avoiding regression by monotonically nailing down the more 'eventful' parts
|
||||
// of the terrain. It helps readers new and old and rewards curiosity to
|
||||
// organize large programs in self-similar hiearchies of example scenarios
|
||||
// colocated with the code that makes them work.
|
||||
//
|
||||
// "Programming properly should be regarded as an activity by which
|
||||
// programmers form a mental model, rather than as production of a program."
|
||||
// -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22)
|
||||
|
||||
:(before "int main")
|
||||
// End Tracing // hack to ensure most code in this layer comes before anything else
|
||||
|
||||
:(before "End Tracing")
|
||||
bool Hide_warnings = false;
|
||||
:(before "End Setup")
|
||||
Hide_warnings = false;
|
||||
|
||||
:(before "End Tracing")
|
||||
struct trace_stream {
|
||||
vector<pair<string, pair<int, string> > > past_lines; // [(layer label, frame, line)]
|
||||
unordered_map<string, int> frame;
|
||||
// accumulator for current line
|
||||
ostringstream* curr_stream;
|
||||
string curr_layer;
|
||||
string dump_layer;
|
||||
trace_stream() :curr_stream(NULL) {}
|
||||
~trace_stream() { if (curr_stream) delete curr_stream; }
|
||||
|
||||
ostringstream& stream(string layer) {
|
||||
newline();
|
||||
curr_stream = new ostringstream;
|
||||
curr_layer = layer;
|
||||
return *curr_stream;
|
||||
}
|
||||
|
||||
// be sure to call this before messing with curr_stream or curr_layer or frame
|
||||
void newline() {
|
||||
if (!curr_stream) return;
|
||||
past_lines.push_back(pair<string, pair<int, string> >(curr_layer, pair<int, string>(frame[curr_layer], curr_stream->str())));
|
||||
if (curr_layer == dump_layer || curr_layer == "dump" ||
|
||||
(!Hide_warnings && curr_layer == "warn"))
|
||||
cerr << frame[curr_layer] << ": " << with_newline(curr_stream->str());
|
||||
delete curr_stream;
|
||||
curr_stream = NULL;
|
||||
}
|
||||
|
||||
// Useful for debugging.
|
||||
string readable_contents(string layer) { // missing layer = everything, frame, hierarchical layers
|
||||
newline();
|
||||
ostringstream output;
|
||||
string real_layer, frame;
|
||||
parse_layer_and_frame(layer, &real_layer, &frame);
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p)
|
||||
if (layer.empty() || prefix_match(real_layer, p->first))
|
||||
output << p->first << "/" << p->second.first << ": " << with_newline(p->second.second);
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// Useful for a newcomer to visualize the program at work.
|
||||
void dump_browseable_contents(string layer) {
|
||||
ofstream dump("dump");
|
||||
dump << "<div class='frame' frame_index='1'>start</div>\n";
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) {
|
||||
if (p->first != layer) continue;
|
||||
dump << "<div class='frame";
|
||||
if (p->second.first > 1) dump << " hidden";
|
||||
dump << "' frame_index='" << p->second.first << "'>";
|
||||
dump << p->second.second;
|
||||
dump << "</div>\n";
|
||||
}
|
||||
dump.close();
|
||||
}
|
||||
|
||||
string with_newline(string s) {
|
||||
if (s[s.size()-1] != '\n') return s+'\n';
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
trace_stream* Trace_stream = NULL;
|
||||
|
||||
// Top-level helper. IMPORTANT: can't nest.
|
||||
#define trace(layer) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(layer)
|
||||
// Warnings should go straight to cerr by default since calls to trace() have
|
||||
// some unfriendly constraints (they delay printing, they can't nest)
|
||||
#define RAISE ((!Trace_stream || !Hide_warnings) ? cerr /*do print*/ : Trace_stream->stream("warn")) << __FILE__ << ":" << __LINE__ << " "
|
||||
|
||||
// A separate helper for debugging. We should only trace domain-specific
|
||||
// facts. For everything else use log.
|
||||
#define xlog if (false) log
|
||||
// To turn on logging replace 'xlog' with 'log'.
|
||||
#define log cerr
|
||||
|
||||
:(before "End Types")
|
||||
// RAISE << die exits after printing -- unless Hide_warnings is set.
|
||||
struct die {};
|
||||
:(before "End Tracing")
|
||||
ostream& operator<<(ostream& os, unused die) {
|
||||
if (Hide_warnings) return os;
|
||||
os << "dying";
|
||||
if (Trace_stream) Trace_stream->newline();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream;
|
||||
|
||||
#define DUMP(layer) cerr << Trace_stream->readable_contents(layer)
|
||||
|
||||
// Trace_stream is a resource, lease_tracer uses RAII to manage it.
|
||||
struct lease_tracer {
|
||||
lease_tracer() { Trace_stream = new trace_stream; }
|
||||
~lease_tracer() { delete Trace_stream, Trace_stream = NULL; }
|
||||
};
|
||||
|
||||
#define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer;
|
||||
:(before "End Test Setup")
|
||||
START_TRACING_UNTIL_END_OF_SCOPE
|
||||
|
||||
:(before "End Tracing")
|
||||
void trace_all(const string& label, const list<string>& in) {
|
||||
for (list<string>::const_iterator p = in.begin(); p != in.end(); ++p)
|
||||
trace(label) << *p;
|
||||
}
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { // missing layer == anywhere, frame, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, "");
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
string layer, frame, contents;
|
||||
parse_layer_frame_contents(expected_lines[curr_expected_line], &layer, &frame, &contents);
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !prefix_match(layer, p->first))
|
||||
continue;
|
||||
|
||||
if (!frame.empty() && strtol(frame.c_str(), NULL, 0) != p->second.first)
|
||||
continue;
|
||||
|
||||
if (contents != p->second.second)
|
||||
continue;
|
||||
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
parse_layer_frame_contents(expected_lines[curr_expected_line], &layer, &frame, &contents);
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void parse_layer_frame_contents(const string& orig, string* layer, string* frame, string* contents) {
|
||||
string layer_and_frame;
|
||||
parse_contents(orig, ": ", &layer_and_frame, contents);
|
||||
parse_layer_and_frame(layer_and_frame, layer, frame);
|
||||
}
|
||||
|
||||
void parse_contents(const string& s, const string& delim, string* prefix, string* contents) {
|
||||
string::size_type pos = s.find(delim);
|
||||
if (pos == NOT_FOUND) {
|
||||
*prefix = "";
|
||||
*contents = s;
|
||||
}
|
||||
else {
|
||||
*prefix = s.substr(0, pos);
|
||||
*contents = s.substr(pos+delim.size());
|
||||
}
|
||||
}
|
||||
|
||||
void parse_layer_and_frame(const string& orig, string* layer, string* frame) {
|
||||
size_t last_slash = orig.rfind('/');
|
||||
if (last_slash == NOT_FOUND
|
||||
|| orig.find_last_not_of("0123456789") != last_slash) {
|
||||
*layer = orig;
|
||||
*frame = "";
|
||||
}
|
||||
else {
|
||||
*layer = orig.substr(0, last_slash);
|
||||
*frame = orig.substr(last_slash+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string layer, string expected) { // empty layer == everything, multiple layers, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, "");
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !any_prefix_match(layers, p->first))
|
||||
continue;
|
||||
if (p->second.second != expected_lines[curr_expected_line])
|
||||
continue;
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << expected_lines[curr_expected_line] << "] in trace:\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
int trace_count(string layer) {
|
||||
return trace_count(layer, "");
|
||||
}
|
||||
|
||||
int trace_count(string layer, string line) {
|
||||
Trace_stream->newline();
|
||||
long result = 0;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (any_prefix_match(layers, p->first))
|
||||
if (line == "" || p->second.second == line)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int trace_count(string layer, int frame, string line) {
|
||||
Trace_stream->newline();
|
||||
long result = 0;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (any_prefix_match(layers, p->first) && p->second.first == frame)
|
||||
if (line == "" || p->second.second == line)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_WARNS() CHECK(trace_count("warn") > 0)
|
||||
#define CHECK_TRACE_DOESNT_WARN() \
|
||||
if (trace_count("warn") > 0) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected warnings\n"; \
|
||||
DUMP("warn"); \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string layer, string line) {
|
||||
return trace_count(layer, line) == 0;
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string expected) {
|
||||
vector<string> tmp = split(expected, ": ");
|
||||
return trace_doesnt_contain(tmp[0], tmp[1]);
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string layer, int frame, string line) {
|
||||
return trace_count(layer, frame, line) == 0;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__))
|
||||
|
||||
|
||||
|
||||
// manage layer counts in Trace_stream using RAII
|
||||
struct lease_trace_frame {
|
||||
string layer;
|
||||
lease_trace_frame(string l) :layer(l) {
|
||||
if (!Trace_stream) return;
|
||||
Trace_stream->newline();
|
||||
++Trace_stream->frame[layer];
|
||||
}
|
||||
~lease_trace_frame() {
|
||||
if (!Trace_stream) return;
|
||||
Trace_stream->newline();
|
||||
--Trace_stream->frame[layer];
|
||||
}
|
||||
};
|
||||
#define new_trace_frame(layer) lease_trace_frame leased_frame(layer);
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string layer, int frame, string expected) { // multiple layers, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, ""); // hack: doesn't handle newlines in embedded in lines
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !any_prefix_match(layers, p->first))
|
||||
continue;
|
||||
if (p->second.first != frame)
|
||||
continue;
|
||||
if (p->second.second != expected_lines[curr_expected_line])
|
||||
continue;
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << expected_lines[curr_expected_line] << "] in trace/" << frame << ":\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_TOP(layer, expected) CHECK_TRACE_CONTENTS(layer, 1, expected)
|
||||
|
||||
|
||||
|
||||
vector<string> split(string s, string delim) {
|
||||
vector<string> result;
|
||||
string::size_type begin=0, end=s.find(delim);
|
||||
while (true) {
|
||||
if (end == NOT_FOUND) {
|
||||
result.push_back(string(s, begin, NOT_FOUND));
|
||||
break;
|
||||
}
|
||||
result.push_back(string(s, begin, end-begin));
|
||||
begin = end+delim.size();
|
||||
end = s.find(delim, begin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool any_prefix_match(const vector<string>& pats, const string& needle) {
|
||||
if (pats.empty()) return false;
|
||||
if (*pats[0].rbegin() != '/')
|
||||
// prefix match not requested
|
||||
return find(pats.begin(), pats.end(), needle) != pats.end();
|
||||
// first pat ends in a '/'; assume all pats do.
|
||||
for (vector<string>::const_iterator p = pats.begin(); p != pats.end(); ++p)
|
||||
if (headmatch(needle, *p)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool prefix_match(const string& pat, const string& needle) {
|
||||
if (*pat.rbegin() != '/')
|
||||
// prefix match not requested
|
||||
return pat == needle;
|
||||
return headmatch(needle, pat);
|
||||
}
|
||||
|
||||
bool headmatch(const string& s, const string& pat) {
|
||||
if (pat.size() > s.size()) return false;
|
||||
return std::mismatch(pat.begin(), pat.end(), s.begin()).first == pat.end();
|
||||
}
|
||||
|
||||
:(before "End Includes")
|
||||
#include<cstdlib>
|
||||
|
||||
#include<string>
|
||||
using std::string;
|
||||
#define NOT_FOUND string::npos
|
||||
|
||||
#include<vector>
|
||||
using std::vector;
|
||||
#include<list>
|
||||
using std::list;
|
||||
#include<utility>
|
||||
using std::pair;
|
||||
|
||||
#include<tr1/unordered_map>
|
||||
using std::tr1::unordered_map;
|
||||
#include<tr1/unordered_set>
|
||||
using std::tr1::unordered_set;
|
||||
#include<algorithm>
|
||||
|
||||
#include<iostream>
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::iostream;
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
|
||||
#include<sstream>
|
||||
using std::stringstream;
|
||||
using std::istringstream;
|
||||
using std::ostringstream;
|
||||
|
||||
#include<fstream>
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
|
||||
#define unused __attribute__((unused))
|
|
@ -0,0 +1,164 @@
|
|||
void test_trace_check_compares() {
|
||||
CHECK_TRACE_CONTENTS("test layer", "");
|
||||
trace("test layer") << "foo";
|
||||
CHECK_TRACE_CONTENTS("test layer", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_filters_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_ignores_other_lines() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_always_finds_empty_lines() {
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "");
|
||||
}
|
||||
|
||||
void test_trace_check_treats_empty_layers_as_wildcards() {
|
||||
trace("test layer 1") << "foo";
|
||||
CHECK_TRACE_CONTENTS("", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_multiple_lines_at_once() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
CHECK_TRACE_CONTENTS("", "foobar");
|
||||
}
|
||||
|
||||
void test_trace_check_always_finds_empty_lines2() {
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "");
|
||||
}
|
||||
|
||||
void test_trace_orders_across_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("", "foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_orders_across_layers2() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_checks_ordering_spanning_multiple_layers() {
|
||||
trace("layer1") << "foo";
|
||||
trace("layer2") << "bar";
|
||||
trace("layer1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("layer1: foolayer2: barlayer1: qux");
|
||||
}
|
||||
|
||||
void test_trace_segments_within_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
new_trace_frame("test layer 1");
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "fooqux");
|
||||
CHECK_TRACE_CONTENTS("test layer 1", 0, "foo");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("test layer 1", 1, "foo");
|
||||
}
|
||||
|
||||
void test_trace_checks_ordering_across_layers_and_frames() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
new_trace_frame("test layer 1");
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1/0: footest layer 2: bartest layer 1: qux");
|
||||
CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1/1: qux");
|
||||
}
|
||||
|
||||
void trace_test_fn(int n) {
|
||||
if (n == 0) return;
|
||||
new_trace_frame("foo");
|
||||
trace("foo") << "before: " << n;
|
||||
trace_test_fn(n-1);
|
||||
trace("foo") << "after: " << n;
|
||||
}
|
||||
|
||||
void test_trace_keeps_level_together() {
|
||||
CHECK_TRACE_CONTENTS("foo", "");
|
||||
trace_test_fn(4);
|
||||
CHECK_TRACE_CONTENTS("foo", 2, "before: 3after: 3");
|
||||
}
|
||||
|
||||
void test_trace_supports_multiple_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1,test layer 2", "foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_supports_hierarchical_layers() {
|
||||
trace("test layer/a") << "foo";
|
||||
trace("different layer/c") << "foo 2";
|
||||
trace("test layer/b") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer/", "foobar");
|
||||
}
|
||||
|
||||
void test_trace_supports_count() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "foo";
|
||||
CHECK_EQ(trace_count("test layer 1", "foo"), 2);
|
||||
}
|
||||
|
||||
void test_trace_supports_count2() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "bar";
|
||||
CHECK_EQ(trace_count("test layer 1"), 2);
|
||||
}
|
||||
|
||||
// pending: DUMP tests
|
||||
// pending: readable_contents() adds newline if necessary.
|
||||
// pending: RAISE also prints to stderr.
|
||||
// pending: RAISE doesn't print to stderr if Hide_warnings is set.
|
||||
// pending: RAISE doesn't have to be saved if Hide_warnings is set, just printed.
|
||||
// pending: RAISE prints to stderr if Trace_stream is NULL.
|
||||
// pending: RAISE prints to stderr if Trace_stream is NULL even if Hide_warnings is set.
|
||||
// pending: RAISE << ... die() doesn't die if Hide_warnings is set.
|
||||
|
||||
|
||||
|
||||
// can't check trace because trace methods call 'split'
|
||||
|
||||
void test_split_returns_at_least_one_elem() {
|
||||
vector<string> result = split("", ",");
|
||||
CHECK_EQ(result.size(), 1);
|
||||
CHECK_EQ(result[0], "");
|
||||
}
|
||||
|
||||
void test_split_returns_entire_input_when_no_delim() {
|
||||
vector<string> result = split("abc", ",");
|
||||
CHECK_EQ(result.size(), 1);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
}
|
||||
|
||||
void test_split_works() {
|
||||
vector<string> result = split("abc,def", ",");
|
||||
CHECK_EQ(result.size(), 2);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
}
|
||||
|
||||
void test_split_works2() {
|
||||
vector<string> result = split("abc,def,ghi", ",");
|
||||
CHECK_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
CHECK_EQ(result[2], "ghi");
|
||||
}
|
||||
|
||||
void test_split_handles_multichar_delim() {
|
||||
vector<string> result = split("abc,,def,,ghi", ",,");
|
||||
CHECK_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
CHECK_EQ(result[2], "ghi");
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Some common includes needed all over the place.
|
||||
// More tightly-targeted includes show up in other files.
|
||||
|
||||
:(before "End Includes")
|
||||
#include<cstdio>
|
||||
#include<cstring>
|
||||
#include<cstdlib>
|
||||
#include<errno.h>
|
||||
#include<time.h>
|
||||
#include<math.h>
|
||||
#include<vector>
|
||||
using std::vector;
|
||||
#include<list>
|
||||
using std::list;
|
||||
#include<stack>
|
||||
using std::stack;
|
||||
#include<utility>
|
||||
using std::pair;
|
||||
|
||||
#include<tr1/unordered_map>
|
||||
using std::tr1::unordered_map;
|
||||
#include<tr1/unordered_set>
|
||||
using std::tr1::unordered_set;
|
||||
#include<algorithm>
|
||||
|
||||
#include<string>
|
||||
using std::string;
|
||||
#define NOT_FOUND string::npos // macro doesn't complain about redef
|
||||
|
||||
#include<iostream>
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::iostream;
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
|
||||
#include<sstream>
|
||||
using std::stringstream;
|
||||
using std::istringstream;
|
||||
using std::ostringstream;
|
||||
|
||||
#include<fstream>
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
|
||||
#define unused __attribute__((unused))
|
|
@ -0,0 +1,7 @@
|
|||
set -e
|
||||
set -v
|
||||
make tangle/tangle
|
||||
./tangle/tangle --until $* > mu.cc
|
||||
make autogenerated_lists
|
||||
g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc -o mu
|
||||
./mu test
|
|
@ -0,0 +1,26 @@
|
|||
mu: makefile tangle/tangle mu.cc
|
||||
g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc -o mu
|
||||
-@./mu test
|
||||
|
||||
# To see what the program looks like after all layers have been applied, read
|
||||
# mu.cc
|
||||
mu.cc: 0*
|
||||
./tangle/tangle --until 999 > mu.cc
|
||||
@make autogenerated_lists >/dev/null
|
||||
|
||||
tangle/tangle:
|
||||
cd tangle && make
|
||||
|
||||
# auto-generated files; by convention they end in '_list'.
|
||||
.PHONY: autogenerated_lists
|
||||
autogenerated_lists: mu.cc function_list test_list
|
||||
|
||||
function_list: mu.cc
|
||||
@grep -h "^[^ #].*) {" mu.cc |perl -pwe 's/ {.*/;/' > function_list
|
||||
|
||||
test_list: mu.cc
|
||||
@grep -h "^[[:space:]]*void test_" mu.cc |perl -pwe 's/^\s*void (.*)\(\) {.*/$$1,/' > test_list
|
||||
|
||||
clean:
|
||||
cd tangle && make clean
|
||||
rm -rf mu.cc mu *_list
|
|
@ -0,0 +1,26 @@
|
|||
typedef void (*test_fn)(void);
|
||||
|
||||
const test_fn Tests[] = {
|
||||
#include "test_list" // auto-generated; see makefile
|
||||
};
|
||||
|
||||
bool Passed = true;
|
||||
|
||||
long Num_failures = 0;
|
||||
|
||||
#define CHECK(X) \
|
||||
if (!(X)) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define CHECK_EQ(X, Y) \
|
||||
if ((X) != (Y)) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \
|
||||
cerr << " got " << (X) << '\n'; /* BEWARE: multiple eval */ \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
bool Hide_warnings = false;
|
||||
|
||||
struct trace_stream {
|
||||
vector<pair<string, pair<int, string> > > past_lines; // [(layer label, frame, line)]
|
||||
unordered_map<string, int> frame;
|
||||
// accumulator for current line
|
||||
ostringstream* curr_stream;
|
||||
string curr_layer;
|
||||
string dump_layer;
|
||||
trace_stream() :curr_stream(NULL) {}
|
||||
~trace_stream() { if (curr_stream) delete curr_stream; }
|
||||
|
||||
ostringstream& stream(string layer) {
|
||||
newline();
|
||||
curr_stream = new ostringstream;
|
||||
curr_layer = layer;
|
||||
return *curr_stream;
|
||||
}
|
||||
|
||||
// be sure to call this before messing with curr_stream or curr_layer or frame
|
||||
void newline() {
|
||||
if (!curr_stream) return;
|
||||
past_lines.push_back(pair<string, pair<int, string> >(curr_layer, pair<int, string>(frame[curr_layer], curr_stream->str())));
|
||||
if (curr_layer == "dump")
|
||||
cerr << with_newline(curr_stream->str());
|
||||
else if ((!dump_layer.empty() && prefix_match(dump_layer, curr_layer))
|
||||
|| (!Hide_warnings && curr_layer == "warn"))
|
||||
cerr << curr_layer << "/" << frame[curr_layer] << ": " << with_newline(curr_stream->str());
|
||||
delete curr_stream;
|
||||
curr_stream = NULL;
|
||||
}
|
||||
|
||||
string readable_contents(string layer) { // missing layer = everything, frame, hierarchical layers
|
||||
newline();
|
||||
ostringstream output;
|
||||
string real_layer, frame;
|
||||
parse_layer_and_frame(layer, &real_layer, &frame);
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p)
|
||||
if (layer.empty() || prefix_match(real_layer, p->first))
|
||||
output << p->first << "/" << p->second.first << ": " << with_newline(p->second.second);
|
||||
return output.str();
|
||||
}
|
||||
|
||||
void dump_browseable_contents(string layer) {
|
||||
ofstream dump("dump");
|
||||
dump << "<div class='frame' frame_index='1'>start</div>\n";
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) {
|
||||
if (p->first != layer) continue;
|
||||
dump << "<div class='frame";
|
||||
if (p->second.first > 1) dump << " hidden";
|
||||
dump << "' frame_index='" << p->second.first << "'>";
|
||||
dump << p->second.second;
|
||||
dump << "</div>\n";
|
||||
}
|
||||
dump.close();
|
||||
}
|
||||
|
||||
string with_newline(string s) {
|
||||
if (s[s.size()-1] != '\n') return s+'\n';
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
trace_stream* Trace_stream = NULL;
|
||||
|
||||
// Top-level helper. IMPORTANT: can't nest.
|
||||
#define trace(layer) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(layer)
|
||||
// Warnings should go straight to cerr by default since calls to trace() have
|
||||
// some unfriendly constraints (they delay printing, they can't nest)
|
||||
#define RAISE ((!Trace_stream || !Hide_warnings) ? cerr /*do print*/ : Trace_stream->stream("warn")) << __FILE__ << ":" << __LINE__ << " "
|
||||
// Just debug logging without any test support.
|
||||
#define dbg cerr << __FUNCTION__ << '(' << __FILE__ << ':' << __LINE__ << ") "
|
||||
|
||||
// RAISE << die exits after printing -- unless Hide_warnings is set.
|
||||
struct die {};
|
||||
ostream& operator<<(ostream& os, unused die) {
|
||||
if (Hide_warnings) return os;
|
||||
os << "dying";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream;
|
||||
|
||||
#define DUMP(layer) cerr << Trace_stream->readable_contents(layer)
|
||||
|
||||
// Trace_stream is a resource, lease_tracer uses RAII to manage it.
|
||||
struct lease_tracer {
|
||||
lease_tracer() { Trace_stream = new trace_stream; }
|
||||
~lease_tracer() { delete Trace_stream, Trace_stream = NULL; }
|
||||
};
|
||||
|
||||
#define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer;
|
||||
|
||||
void trace_all(const string& label, const list<string>& in) {
|
||||
for (list<string>::const_iterator p = in.begin(); p != in.end(); ++p)
|
||||
trace(label) << *p;
|
||||
}
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { // missing layer == anywhere, frame, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, "");
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
string layer, frame, contents;
|
||||
parse_layer_frame_contents(expected_lines[curr_expected_line], &layer, &frame, &contents);
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !prefix_match(layer, p->first))
|
||||
continue;
|
||||
|
||||
if (!frame.empty() && strtol(frame.c_str(), NULL, 0) != p->second.first)
|
||||
continue;
|
||||
|
||||
if (contents != p->second.second)
|
||||
continue;
|
||||
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
parse_layer_frame_contents(expected_lines[curr_expected_line], &layer, &frame, &contents);
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void parse_layer_frame_contents(const string& orig, string* layer, string* frame, string* contents) {
|
||||
string layer_and_frame;
|
||||
parse_contents(orig, ": ", &layer_and_frame, contents);
|
||||
parse_layer_and_frame(layer_and_frame, layer, frame);
|
||||
}
|
||||
|
||||
void parse_contents(const string& s, const string& delim, string* prefix, string* contents) {
|
||||
string::size_type pos = s.find(delim);
|
||||
if (pos == NOT_FOUND) {
|
||||
*prefix = "";
|
||||
*contents = s;
|
||||
}
|
||||
else {
|
||||
*prefix = s.substr(0, pos);
|
||||
*contents = s.substr(pos+delim.size());
|
||||
}
|
||||
}
|
||||
|
||||
void parse_layer_and_frame(const string& orig, string* layer, string* frame) {
|
||||
size_t last_slash = orig.rfind('/');
|
||||
if (last_slash == NOT_FOUND
|
||||
|| last_slash == orig.size()-1 // trailing slash indicates hierarchical layer
|
||||
|| orig.find_last_not_of("0123456789") != last_slash) {
|
||||
*layer = orig;
|
||||
*frame = "";
|
||||
}
|
||||
else {
|
||||
*layer = orig.substr(0, last_slash);
|
||||
*frame = orig.substr(last_slash+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string layer, string expected) { // empty layer == everything, multiple layers, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, "");
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !any_prefix_match(layers, p->first))
|
||||
continue;
|
||||
if (p->second.second != expected_lines[curr_expected_line])
|
||||
continue;
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << expected_lines[curr_expected_line] << "] in trace:\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
int trace_count(string layer) {
|
||||
return trace_count(layer, "");
|
||||
}
|
||||
|
||||
int trace_count(string layer, string line) {
|
||||
Trace_stream->newline();
|
||||
long result = 0;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (any_prefix_match(layers, p->first))
|
||||
if (line == "" || p->second.second == line)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int trace_count(string layer, int frame, string line) {
|
||||
Trace_stream->newline();
|
||||
long result = 0;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (any_prefix_match(layers, p->first) && p->second.first == frame)
|
||||
if (line == "" || p->second.second == line)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_WARNS() CHECK(trace_count("warn") > 0)
|
||||
#define CHECK_TRACE_DOESNT_WARN() \
|
||||
if (trace_count("warn") > 0) { \
|
||||
++Num_failures; \
|
||||
cerr << "\nF " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected warnings\n"; \
|
||||
DUMP("warn"); \
|
||||
Passed = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string layer, string line) {
|
||||
return trace_count(layer, line) == 0;
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string expected) {
|
||||
vector<string> tmp = split(expected, ": ");
|
||||
return trace_doesnt_contain(tmp[0], tmp[1]);
|
||||
}
|
||||
|
||||
bool trace_doesnt_contain(string layer, int frame, string line) {
|
||||
return trace_count(layer, frame, line) == 0;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__))
|
||||
|
||||
|
||||
|
||||
// manage layer counts in Trace_stream using RAII
|
||||
struct lease_trace_frame {
|
||||
string layer;
|
||||
lease_trace_frame(string l) :layer(l) {
|
||||
if (!Trace_stream) return;
|
||||
Trace_stream->newline();
|
||||
++Trace_stream->frame[layer];
|
||||
}
|
||||
~lease_trace_frame() {
|
||||
if (!Trace_stream) return;
|
||||
Trace_stream->newline();
|
||||
--Trace_stream->frame[layer];
|
||||
}
|
||||
};
|
||||
#define new_trace_frame(layer) lease_trace_frame leased_frame(layer);
|
||||
|
||||
bool check_trace_contents(string FUNCTION, string FILE, int LINE, string layer, int frame, string expected) { // multiple layers, hierarchical layers
|
||||
vector<string> expected_lines = split(expected, ""); // hack: doesn't handle newlines in embedded in lines
|
||||
size_t curr_expected_line = 0;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
Trace_stream->newline();
|
||||
ostringstream output;
|
||||
vector<string> layers = split(layer, ",");
|
||||
for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
|
||||
if (!layer.empty() && !any_prefix_match(layers, p->first))
|
||||
continue;
|
||||
if (p->second.first != frame)
|
||||
continue;
|
||||
if (p->second.second != expected_lines[curr_expected_line])
|
||||
continue;
|
||||
++curr_expected_line;
|
||||
while (curr_expected_line < expected_lines.size() && expected_lines[curr_expected_line].empty())
|
||||
++curr_expected_line;
|
||||
if (curr_expected_line == expected_lines.size()) return true;
|
||||
}
|
||||
|
||||
++Num_failures;
|
||||
cerr << "\nF " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << expected_lines[curr_expected_line] << "] in trace/" << frame << ":\n";
|
||||
DUMP(layer);
|
||||
Passed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CHECK_TRACE_TOP(layer, expected) CHECK_TRACE_CONTENTS(layer, 1, expected)
|
||||
|
||||
|
||||
|
||||
vector<string> split(string s, string delim) {
|
||||
vector<string> result;
|
||||
string::size_type begin=0, end=s.find(delim);
|
||||
while (true) {
|
||||
if (end == NOT_FOUND) {
|
||||
result.push_back(string(s, begin, NOT_FOUND));
|
||||
break;
|
||||
}
|
||||
result.push_back(string(s, begin, end-begin));
|
||||
begin = end+delim.size();
|
||||
end = s.find(delim, begin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool any_prefix_match(const vector<string>& pats, const string& needle) {
|
||||
if (pats.empty()) return false;
|
||||
if (*pats[0].rbegin() != '/')
|
||||
// prefix match not requested
|
||||
return find(pats.begin(), pats.end(), needle) != pats.end();
|
||||
// first pat ends in a '/'; assume all pats do.
|
||||
for (vector<string>::const_iterator p = pats.begin(); p != pats.end(); ++p)
|
||||
if (headmatch(needle, *p)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool prefix_match(const string& pat, const string& needle) {
|
||||
if (*pat.rbegin() != '/')
|
||||
// prefix match not requested
|
||||
return pat == needle;
|
||||
return headmatch(needle, pat);
|
||||
}
|
||||
|
||||
bool headmatch(const string& s, const string& pat) {
|
||||
if (pat.size() > s.size()) return false;
|
||||
return std::mismatch(pat.begin(), pat.end(), s.begin()).first == pat.end();
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
void test_trace_check_compares() {
|
||||
CHECK_TRACE_CONTENTS("test layer", "");
|
||||
trace("test layer") << "foo";
|
||||
CHECK_TRACE_CONTENTS("test layer", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_filters_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_ignores_other_lines() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_always_finds_empty_lines() {
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "");
|
||||
}
|
||||
|
||||
void test_trace_check_treats_empty_layers_as_wildcards() {
|
||||
trace("test layer 1") << "foo";
|
||||
CHECK_TRACE_CONTENTS("", "foo");
|
||||
}
|
||||
|
||||
void test_trace_check_multiple_lines_at_once() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
CHECK_TRACE_CONTENTS("", "foobar");
|
||||
}
|
||||
|
||||
void test_trace_check_always_finds_empty_lines2() {
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "");
|
||||
}
|
||||
|
||||
void test_trace_orders_across_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("", "foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_orders_across_layers2() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_checks_ordering_spanning_multiple_layers() {
|
||||
trace("layer1") << "foo";
|
||||
trace("layer2") << "bar";
|
||||
trace("layer1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("layer1: foolayer2: barlayer1: qux");
|
||||
}
|
||||
|
||||
void test_trace_segments_within_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
new_trace_frame("test layer 1");
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1", "fooqux");
|
||||
CHECK_TRACE_CONTENTS("test layer 1", 0, "foo");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("test layer 1", 1, "foo");
|
||||
}
|
||||
|
||||
void test_trace_checks_ordering_across_layers_and_frames() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
new_trace_frame("test layer 1");
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1/0: footest layer 2: bartest layer 1: qux");
|
||||
CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1/1: qux");
|
||||
}
|
||||
|
||||
void trace_test_fn(int n) {
|
||||
if (n == 0) return;
|
||||
new_trace_frame("foo");
|
||||
trace("foo") << "before: " << n;
|
||||
trace_test_fn(n-1);
|
||||
trace("foo") << "after: " << n;
|
||||
}
|
||||
|
||||
void test_trace_keeps_level_together() {
|
||||
CHECK_TRACE_CONTENTS("foo", "");
|
||||
trace_test_fn(4);
|
||||
CHECK_TRACE_CONTENTS("foo", 2, "before: 3after: 3");
|
||||
}
|
||||
|
||||
void test_trace_supports_multiple_layers() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 2") << "bar";
|
||||
trace("test layer 1") << "qux";
|
||||
CHECK_TRACE_CONTENTS("test layer 1,test layer 2", "foobarqux");
|
||||
}
|
||||
|
||||
void test_trace_supports_hierarchical_layers() {
|
||||
trace("test layer/a") << "foo";
|
||||
trace("different layer/c") << "foo 2";
|
||||
trace("test layer/b") << "bar";
|
||||
CHECK_TRACE_CONTENTS("test layer/", "foobar");
|
||||
}
|
||||
|
||||
void test_trace_supports_count() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "foo";
|
||||
CHECK_EQ(trace_count("test layer 1", "foo"), 2);
|
||||
}
|
||||
|
||||
void test_trace_supports_count2() {
|
||||
trace("test layer 1") << "foo";
|
||||
trace("test layer 1") << "bar";
|
||||
CHECK_EQ(trace_count("test layer 1"), 2);
|
||||
}
|
||||
|
||||
// pending: DUMP tests
|
||||
// pending: readable_contents() adds newline if necessary.
|
||||
// pending: RAISE also prints to stderr.
|
||||
// pending: RAISE doesn't print to stderr if Hide_warnings is set.
|
||||
// pending: RAISE doesn't have to be saved if Hide_warnings is set, just printed.
|
||||
// pending: RAISE prints to stderr if Trace_stream is NULL.
|
||||
// pending: RAISE prints to stderr if Trace_stream is NULL even if Hide_warnings is set.
|
||||
// pending: RAISE << ... die() doesn't die if Hide_warnings is set.
|
||||
|
||||
|
||||
|
||||
// can't check trace because trace methods call 'split'
|
||||
|
||||
void test_split_returns_at_least_one_elem() {
|
||||
vector<string> result = split("", ",");
|
||||
CHECK_EQ(result.size(), 1);
|
||||
CHECK_EQ(result[0], "");
|
||||
}
|
||||
|
||||
void test_split_returns_entire_input_when_no_delim() {
|
||||
vector<string> result = split("abc", ",");
|
||||
CHECK_EQ(result.size(), 1);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
}
|
||||
|
||||
void test_split_works() {
|
||||
vector<string> result = split("abc,def", ",");
|
||||
CHECK_EQ(result.size(), 2);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
}
|
||||
|
||||
void test_split_works2() {
|
||||
vector<string> result = split("abc,def,ghi", ",");
|
||||
CHECK_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
CHECK_EQ(result[2], "ghi");
|
||||
}
|
||||
|
||||
void test_split_handles_multichar_delim() {
|
||||
vector<string> result = split("abc,,def,,ghi", ",,");
|
||||
CHECK_EQ(result.size(), 3);
|
||||
CHECK_EQ(result[0], "abc");
|
||||
CHECK_EQ(result[1], "def");
|
||||
CHECK_EQ(result[2], "ghi");
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
string Last_file = "";
|
||||
int main(int argc, const char* argv[]) {
|
||||
Last_file = flag_value("--until", argc, argv);
|
||||
if (flag("test", argc, argv))
|
||||
return run_tests();
|
||||
return tangle_files_in_cwd();
|
||||
}
|
||||
|
||||
bool eof(istream& in) {
|
||||
in.peek();
|
||||
return in.eof();
|
||||
}
|
||||
|
||||
bool flag(const string& flag, int argc, const char* argv[]) {
|
||||
for (int i = 1; i < argc; ++i)
|
||||
if (string(argv[i]) == flag)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
string flag_value(const string& flag, int argc, const char* argv[]) {
|
||||
for (int i = 1; i < argc-1; ++i)
|
||||
if (string(argv[i]) == flag)
|
||||
return argv[i+1];
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
//// test harness
|
||||
|
||||
int run_tests() {
|
||||
time_t t; time(&t);
|
||||
cerr << "C tests: " << ctime(&t);
|
||||
for (unsigned long i=0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) {
|
||||
START_TRACING_UNTIL_END_OF_SCOPE;
|
||||
setup();
|
||||
(*Tests[i])();
|
||||
verify();
|
||||
}
|
||||
|
||||
cerr << '\n';
|
||||
if (Num_failures > 0)
|
||||
cerr << Num_failures << " failure"
|
||||
<< (Num_failures > 1 ? "s" : "")
|
||||
<< '\n';
|
||||
return Num_failures;
|
||||
}
|
||||
|
||||
void verify() {
|
||||
Hide_warnings = false;
|
||||
if (!Passed)
|
||||
;
|
||||
else
|
||||
cerr << ".";
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Hide_warnings = false;
|
||||
Passed = true;
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
#include<sys/param.h>
|
||||
|
||||
int tangle_files_in_cwd() {
|
||||
list<string> result;
|
||||
vector<char*> files = sorted_files(".", /*no extension*/ "");
|
||||
for (vector<char*>::iterator p = files.begin(); p != files.end(); ++p) {
|
||||
if ((*p)[0] < '0' || (*p)[0] > '9') continue;
|
||||
if (!Last_file.empty() && *p > Last_file) break;
|
||||
ifstream in(*p);
|
||||
tangle(in, result);
|
||||
}
|
||||
for (list<string>::iterator p = result.begin(); p != result.end(); ++p)
|
||||
cout << *p << '\n';
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tangle(istream& in, list<string>& out) {
|
||||
string curr_line;
|
||||
while (!in.eof()) {
|
||||
getline(in, curr_line);
|
||||
if (starts_with(curr_line, ":("))
|
||||
process_next_hunk(in, trim(curr_line), out);
|
||||
else
|
||||
out.push_back(curr_line);
|
||||
}
|
||||
trace_all("tangle", out);
|
||||
}
|
||||
|
||||
string Toplevel = "run";
|
||||
|
||||
void process_next_hunk(istream& in, const string& directive, list<string>& out) {
|
||||
list<string> hunk;
|
||||
string curr_line;
|
||||
while (!in.eof()) {
|
||||
std::streampos old = in.tellg();
|
||||
getline(in, curr_line);
|
||||
if (starts_with(curr_line, ":(")) {
|
||||
in.seekg(old);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
hunk.push_back(curr_line);
|
||||
}
|
||||
}
|
||||
|
||||
istringstream directive_stream(directive.substr(2)); // length of ":("
|
||||
string cmd = next_tangle_token(directive_stream);
|
||||
|
||||
if (cmd == "code") {
|
||||
out.insert(out.end(), hunk.begin(), hunk.end());
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "scenarios") {
|
||||
Toplevel = next_tangle_token(directive_stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "scenario") {
|
||||
list<string> result;
|
||||
string name = next_tangle_token(directive_stream);
|
||||
emit_test(name, hunk, result);
|
||||
out.insert(out.end(), result.begin(), result.end());
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd == "before" || cmd == "after" || cmd == "replace" || cmd == "replace{}" || cmd == "delete" || cmd == "delete{}") {
|
||||
string pat = next_tangle_token(directive_stream);
|
||||
if (pat == "") {
|
||||
RAISE << "No target for " << cmd << " directive.\n" << die();
|
||||
return;
|
||||
}
|
||||
list<string>::iterator target = find_substr(out, pat);
|
||||
if (target == out.end()) {
|
||||
RAISE << "Couldn't find target " << pat << '\n' << die();
|
||||
return;
|
||||
}
|
||||
|
||||
indent_all(hunk, target);
|
||||
|
||||
if (cmd == "before") {
|
||||
out.splice(target, hunk);
|
||||
}
|
||||
else if (cmd == "after") {
|
||||
++target;
|
||||
out.splice(target, hunk);
|
||||
}
|
||||
else if (cmd == "replace" || cmd == "delete") {
|
||||
out.splice(target, hunk);
|
||||
out.erase(target);
|
||||
}
|
||||
else if (cmd == "replace{}" || cmd == "delete{}") {
|
||||
if (find_trim(hunk, ":OLD_CONTENTS") == hunk.end()) {
|
||||
out.splice(target, hunk);
|
||||
out.erase(target, balancing_curly(target));
|
||||
}
|
||||
else {
|
||||
list<string>::iterator next = balancing_curly(target);
|
||||
list<string> old_version;
|
||||
old_version.splice(old_version.begin(), out, target, next);
|
||||
old_version.pop_back(); old_version.pop_front(); // contents only please, not surrounding curlies
|
||||
|
||||
list<string>::iterator new_pos = find_trim(hunk, ":OLD_CONTENTS");
|
||||
indent_all(old_version, new_pos);
|
||||
hunk.splice(new_pos, old_version);
|
||||
hunk.erase(new_pos);
|
||||
out.splice(next, hunk);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RAISE << "unknown directive " << cmd << '\n';
|
||||
}
|
||||
|
||||
// indent all lines in l like indentation at exemplar
|
||||
void indent_all(list<string>& l, list<string>::iterator exemplar) {
|
||||
string curr_indent = indent(*exemplar);
|
||||
for (list<string>::iterator p = l.begin(); p != l.end(); ++p)
|
||||
if (!p->empty())
|
||||
p->insert(p->begin(), curr_indent.begin(), curr_indent.end());
|
||||
}
|
||||
|
||||
string next_tangle_token(istream& in) {
|
||||
in >> std::noskipws;
|
||||
ostringstream out;
|
||||
skip_whitespace(in);
|
||||
if (in.peek() == '"')
|
||||
slurp_tangle_string(in, out);
|
||||
else
|
||||
slurp_word(in, out);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void slurp_tangle_string(istream& in, ostream& out) {
|
||||
in.get();
|
||||
char c;
|
||||
while (in >> c) {
|
||||
if (c == '\\') // only works for double-quotes
|
||||
continue;
|
||||
if (c == '"')
|
||||
break;
|
||||
out << c;
|
||||
}
|
||||
}
|
||||
|
||||
void slurp_word(istream& in, ostream& out) {
|
||||
char c;
|
||||
while (in >> c) {
|
||||
if (isspace(c) || c == ')') {
|
||||
in.putback(c);
|
||||
break;
|
||||
}
|
||||
out << c;
|
||||
}
|
||||
}
|
||||
|
||||
void skip_whitespace(istream& in) {
|
||||
while (isspace(in.peek()))
|
||||
in.get();
|
||||
}
|
||||
|
||||
list<string>::iterator balancing_curly(list<string>::iterator orig) {
|
||||
list<string>::iterator curr = orig;
|
||||
long open_curlies = 0;
|
||||
do {
|
||||
for (string::iterator p = curr->begin(); p != curr->end(); ++p) {
|
||||
if (*p == '{') ++open_curlies;
|
||||
if (*p == '}') --open_curlies;
|
||||
}
|
||||
++curr;
|
||||
// no guard so far against unbalanced curly
|
||||
} while (open_curlies != 0);
|
||||
return curr;
|
||||
}
|
||||
|
||||
// A scenario is one or more sessions separated by calls to CLEAR_TRACE ('===')
|
||||
// A session is one or more lines of input
|
||||
// followed by a return value ('=>')
|
||||
// followed by one or more lines expected in trace in order ('+')
|
||||
// followed by one or more lines trace shouldn't include ('-')
|
||||
// Remember to update is_input below if you add to this format.
|
||||
void emit_test(const string& name, list<string>& lines, list<string>& result) {
|
||||
result.push_back("void test_"+name+"() {");
|
||||
while (any_non_input_line(lines)) {
|
||||
if (!any_line_starts_with(lines, "=>"))
|
||||
emit_session(lines, result); // simpler version; no need to check result
|
||||
else
|
||||
emit_result_checking_session(lines, result);
|
||||
if (!lines.empty() && lines.front()[0] == '+')
|
||||
result.push_back(" CHECK_TRACE_CONTENTS(\""+expected_in_trace(lines)+"\");");
|
||||
while (!lines.empty() && lines.front()[0] == '-') {
|
||||
result.push_back(" CHECK_TRACE_DOESNT_CONTAIN(\""+expected_not_in_trace(lines.front())+"\");");
|
||||
lines.pop_front();
|
||||
}
|
||||
if (!lines.empty() && lines.front() == "===") {
|
||||
result.push_back(" CLEAR_TRACE;");
|
||||
lines.pop_front();
|
||||
}
|
||||
}
|
||||
result.push_back("}");
|
||||
|
||||
while (!lines.empty() &&
|
||||
(trim(lines.front()).empty() || starts_with(lines.front(), "//")))
|
||||
lines.pop_front();
|
||||
if (!lines.empty()) {
|
||||
cerr << lines.size() << " unprocessed lines in scenario.\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void emit_session(list<string>& lines, list<string>& result) {
|
||||
result.push_back(" rmref("+Toplevel+"(\""+input_lines(lines)+"\"));");
|
||||
}
|
||||
|
||||
void emit_result_checking_session(list<string>& lines, list<string>& result) {
|
||||
result.push_back("{");
|
||||
result.push_back(" ostringstream os;");
|
||||
result.push_back(" TEMP(tmp, "+Toplevel+"(\""+input_lines(lines)+"\"));");
|
||||
result.push_back(" os << tmp;");
|
||||
if (!lines.empty() && starts_with(lines.front(), "=>")) {
|
||||
size_t pos = lines.front().find("=>")+2; // length of '=>'
|
||||
result.push_back(" CHECK_EQ(os.str(), \""+trim(string(lines.front(), pos))+"\");");
|
||||
lines.pop_front();
|
||||
}
|
||||
result.push_back("}");
|
||||
}
|
||||
|
||||
bool is_input(const string& line) {
|
||||
return line != "===" && line[0] != '+' && line[0] != '-' && !starts_with(line, "=>");
|
||||
}
|
||||
|
||||
string input_lines(list<string>& hunk) {
|
||||
string result;
|
||||
while (!hunk.empty() && is_input(hunk.front())) {
|
||||
result += hunk.front()+""; // temporary delimiter; replace with escaped newline after escaping other backslashes
|
||||
hunk.pop_front();
|
||||
}
|
||||
return escape(result);
|
||||
}
|
||||
|
||||
string expected_in_trace(list<string>& hunk) {
|
||||
string result;
|
||||
while (!hunk.empty() && hunk.front()[0] == '+') {
|
||||
hunk.front().erase(0, 1);
|
||||
result += hunk.front()+"";
|
||||
hunk.pop_front();
|
||||
}
|
||||
return escape(result);
|
||||
}
|
||||
|
||||
string expected_not_in_trace(const string& line) {
|
||||
return escape(line.substr(1));
|
||||
}
|
||||
|
||||
list<string>::iterator find_substr(list<string>& in, const string& pat) {
|
||||
for (list<string>::iterator p = in.begin(); p != in.end(); ++p)
|
||||
if (p->find(pat) != NOT_FOUND)
|
||||
return p;
|
||||
return in.end();
|
||||
}
|
||||
|
||||
list<string>::iterator find_trim(list<string>& in, const string& pat) {
|
||||
for (list<string>::iterator p = in.begin(); p != in.end(); ++p)
|
||||
if (trim(*p) == pat)
|
||||
return p;
|
||||
return in.end();
|
||||
}
|
||||
|
||||
string escape(string s) {
|
||||
s = replace_all(s, "\\", "\\\\");
|
||||
s = replace_all(s, "\"", "\\\"");
|
||||
s = replace_all(s, "", "\\n");
|
||||
return s;
|
||||
}
|
||||
|
||||
string replace_all(string s, const string& a, const string& b) {
|
||||
for (size_t pos = s.find(a); pos != NOT_FOUND; pos = s.find(a, pos+b.size()))
|
||||
s = s.replace(pos, a.size(), b);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool any_line_starts_with(const list<string>& lines, const string& pat) {
|
||||
for (list<string>::const_iterator p = lines.begin(); p != lines.end(); ++p)
|
||||
if (starts_with(*p, pat)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_non_input_line(const list<string>& lines) {
|
||||
for (list<string>::const_iterator p = lines.begin(); p != lines.end(); ++p)
|
||||
if (!is_input(*p)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#include <locale>
|
||||
using std::isspace; // unicode-aware
|
||||
|
||||
// does s start with pat, after skipping whitespace?
|
||||
// pat can't start with whitespace
|
||||
bool starts_with(const string& s, const string& pat) {
|
||||
for (size_t pos = 0; pos < s.size(); ++pos)
|
||||
if (!isspace(s[pos]))
|
||||
return s.compare(pos, pat.size(), pat) == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
string indent(const string& s) {
|
||||
for (size_t pos = 0; pos < s.size(); ++pos)
|
||||
if (!isspace(s[pos]))
|
||||
return s.substr(0, pos);
|
||||
return "";
|
||||
}
|
||||
|
||||
string strip_indent(const string& s, size_t n) {
|
||||
if (s.empty()) return "";
|
||||
string::const_iterator curr = s.begin();
|
||||
while (curr != s.end() && n > 0 && isspace(*curr)) {
|
||||
++curr;
|
||||
--n;
|
||||
}
|
||||
return string(curr, s.end());
|
||||
}
|
||||
|
||||
string trim(const string& s) {
|
||||
string::const_iterator first = s.begin();
|
||||
while (first != s.end() && isspace(*first))
|
||||
++first;
|
||||
if (first == s.end()) return "";
|
||||
|
||||
string::const_iterator last = --s.end();
|
||||
while (last != s.begin() && isspace(*last))
|
||||
--last;
|
||||
++last;
|
||||
return string(first, last);
|
||||
}
|
||||
|
||||
#include<dirent.h>
|
||||
|
||||
vector<char*> sorted_files(const char* dirname, const char* ext) {
|
||||
vector<char*> result;
|
||||
dirent** files;
|
||||
int num_files = scandir(dirname, &files, NULL, alphasort);
|
||||
for (int i = 0; i < num_files; ++i) {
|
||||
unsigned long n = strlen(files[i]->d_name), extn = strlen(ext);
|
||||
if (n < extn) continue;
|
||||
if (strncmp(&files[i]->d_name[n-extn], ext, extn)) continue;
|
||||
if (!isdigit(files[i]->d_name[0])) continue;
|
||||
char* s = new char[n+1];
|
||||
strncpy(s, files[i]->d_name, n+1);
|
||||
result.push_back(s);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
void test_tangle() {
|
||||
istringstream in("a\nb\nc\n:(before b)\nd\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "adbc");
|
||||
}
|
||||
|
||||
void test_tangle2() {
|
||||
istringstream in("a\nb\nc\n:(after b)\nd\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "abdc");
|
||||
}
|
||||
|
||||
void test_tangle_at_end() {
|
||||
istringstream in("a\nb\nc\n:(after c)\nd\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "abcd");
|
||||
}
|
||||
|
||||
void test_tangle_indents_hunks_correctly() {
|
||||
istringstream in("a\n b\nc\n:(after b)\nd\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "a b dc");
|
||||
}
|
||||
|
||||
void test_tangle_warns_on_missing_target() {
|
||||
Hide_warnings = true;
|
||||
istringstream in(":(before)\nabc def\n");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_TRACE_WARNS();
|
||||
}
|
||||
|
||||
void test_tangle_warns_on_unknown_target() {
|
||||
Hide_warnings = true;
|
||||
istringstream in(":(before \"foo\")\nabc def\n");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_TRACE_WARNS();
|
||||
}
|
||||
|
||||
void test_tangle_delete_range_of_lines() {
|
||||
istringstream in("a\nb {\nc\n}\n:(delete{} \"b\")\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "a");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "b");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "c");
|
||||
}
|
||||
|
||||
void test_tangle_replace() {
|
||||
istringstream in("a\nb\nc\n:(replace b)\nd\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "adc");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "b");
|
||||
}
|
||||
|
||||
void test_tangle_replace_range_of_lines() {
|
||||
istringstream in("a\nb {\nc\n}\n:(replace{} \"b\")\nd\ne\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "ade");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "b {");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "c");
|
||||
}
|
||||
|
||||
void test_tangle_replace_tracks_old_lines() {
|
||||
istringstream in("a\nb {\nc\n}\n:(replace{} \"b\")\nd\n:OLD_CONTENTS\ne\n");
|
||||
list<string> dummy;
|
||||
tangle(in, dummy);
|
||||
CHECK_TRACE_CONTENTS("tangle", "adce");
|
||||
CHECK_TRACE_DOESNT_CONTAIN("tangle", "b {");
|
||||
}
|
||||
|
||||
// todo: include line numbers in tangle errors
|
||||
|
||||
|
||||
|
||||
void test_tangle_supports_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n+layer1: pqr\n+layer2: xyz");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc def\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqrlayer2: xyz\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_supports_configurable_toplevel() {
|
||||
istringstream in(":(scenarios foo)\n:(scenario does_bar)\nabc def\n+layer1: pqr");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(foo(\"abc def\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
|
||||
istringstream cleanup(":(scenarios run)\n");
|
||||
tangle(cleanup, lines);
|
||||
}
|
||||
|
||||
void test_tangle_supports_strings_in_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc \"def\"\n+layer1: pqr\n+layer2: \"xyz\"");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc \\\"def\\\"\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqrlayer2: \\\"xyz\\\"\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_supports_strings_in_scenarios2() {
|
||||
istringstream in(":(scenario does_bar)\nabc \"\"\n+layer1: pqr\n+layer2: \"\"");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc \\\"\\\"\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqrlayer2: \\\"\\\"\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_supports_multiline_input_in_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n efg\n+layer1: pqr\n+layer2: \"\"");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc def\\n efg\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqrlayer2: \\\"\\\"\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_supports_reset_in_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n===\nefg\n+layer1: pqr\n+layer2: \"\"");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc def\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CLEAR_TRACE;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"efg\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqrlayer2: \\\"\\\"\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_can_check_for_absence_at_end_of_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n efg\n+layer1: pqr\n-layer1: xyz");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc def\\n efg\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_DOESNT_CONTAIN(\"layer1: xyz\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_can_check_for_absence_at_end_of_scenarios2() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n efg\n-layer1: pqr\n-layer1: xyz");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " rmref(run(\"abc def\\n efg\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_DOESNT_CONTAIN(\"layer1: pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_DOESNT_CONTAIN(\"layer1: xyz\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_can_check_return_values_of_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc def\n=> pqr");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "{"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " ostringstream os;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " TEMP(tmp, run(\"abc def\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " os << tmp;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_EQ(os.str(), \"pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
void test_tangle_can_check_return_values_of_multiple_scenarios() {
|
||||
istringstream in(":(scenario does_bar)\nabc\n=> pqr\n+layer1: pqr\ndef\n=> xyz\n");
|
||||
list<string> lines;
|
||||
tangle(in, lines);
|
||||
CHECK_EQ(lines.front(), "void test_does_bar() {"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "{"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " ostringstream os;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " TEMP(tmp, run(\"abc\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " os << tmp;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_EQ(os.str(), \"pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_TRACE_CONTENTS(\"layer1: pqr\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "{"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " ostringstream os;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " TEMP(tmp, run(\"def\\n\"));"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " os << tmp;"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), " CHECK_EQ(os.str(), \"xyz\");"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK_EQ(lines.front(), "}"); lines.pop_front();
|
||||
CHECK(lines.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void test_trim() {
|
||||
CHECK_EQ(trim(""), "");
|
||||
CHECK_EQ(trim(" "), "");
|
||||
CHECK_EQ(trim(" "), "");
|
||||
CHECK_EQ(trim("a"), "a");
|
||||
CHECK_EQ(trim(" a"), "a");
|
||||
CHECK_EQ(trim(" a"), "a");
|
||||
CHECK_EQ(trim(" ab"), "ab");
|
||||
CHECK_EQ(trim("a "), "a");
|
||||
CHECK_EQ(trim("a "), "a");
|
||||
CHECK_EQ(trim("ab "), "ab");
|
||||
CHECK_EQ(trim(" a "), "a");
|
||||
CHECK_EQ(trim(" a "), "a");
|
||||
CHECK_EQ(trim(" ab "), "ab");
|
||||
}
|
||||
|
||||
void test_strip_indent() {
|
||||
CHECK_EQ(strip_indent("", 0), "");
|
||||
CHECK_EQ(strip_indent("", 1), "");
|
||||
CHECK_EQ(strip_indent("", 3), "");
|
||||
CHECK_EQ(strip_indent(" ", 0), " ");
|
||||
CHECK_EQ(strip_indent(" a", 0), " a");
|
||||
CHECK_EQ(strip_indent(" ", 1), "");
|
||||
CHECK_EQ(strip_indent(" a", 1), "a");
|
||||
CHECK_EQ(strip_indent(" ", 2), "");
|
||||
CHECK_EQ(strip_indent(" a", 2), "a");
|
||||
CHECK_EQ(strip_indent(" ", 0), " ");
|
||||
CHECK_EQ(strip_indent(" a", 0), " a");
|
||||
CHECK_EQ(strip_indent(" ", 1), " ");
|
||||
CHECK_EQ(strip_indent(" a", 1), " a");
|
||||
CHECK_EQ(strip_indent(" ", 2), "");
|
||||
CHECK_EQ(strip_indent(" a", 2), "a");
|
||||
CHECK_EQ(strip_indent(" ", 3), "");
|
||||
CHECK_EQ(strip_indent(" a", 3), "a");
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// C++ style:
|
||||
// no pointers except cell*
|
||||
// use long as the default integer type; it's always large enough to hold pointers
|
||||
|
||||
#define unused __attribute__((unused))
|
||||
|
||||
#include<cstdio>
|
||||
#include<cstring>
|
||||
#include<cstdlib>
|
||||
#include<errno.h>
|
||||
#include<time.h>
|
||||
#include<math.h>
|
||||
#include<vector>
|
||||
using std::vector;
|
||||
#include<list>
|
||||
using std::list;
|
||||
#include<stack>
|
||||
using std::stack;
|
||||
#include<utility>
|
||||
using std::pair;
|
||||
|
||||
#include<tr1/unordered_map>
|
||||
using std::tr1::unordered_map;
|
||||
#include<tr1/unordered_set>
|
||||
using std::tr1::unordered_set;
|
||||
#include<algorithm>
|
||||
|
||||
#include<string>
|
||||
using std::string;
|
||||
const size_t NOT_FOUND = string::npos;
|
||||
|
||||
#include<iostream>
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::iostream;
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
|
||||
#include<sstream>
|
||||
using std::stringstream;
|
||||
using std::istringstream;
|
||||
using std::ostringstream;
|
||||
|
||||
#include<fstream>
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
|
||||
|
||||
|
||||
// interpreter decls
|
||||
|
||||
#include "type_list"
|
||||
|
||||
#include "function_list"
|
||||
|
||||
// interpreter impl
|
||||
|
||||
#include "file_list"
|
||||
|
||||
// interpreter tests
|
||||
|
||||
#include "test_file_list"
|
|
@ -0,0 +1,23 @@
|
|||
tangle: makefile type_list function_list file_list test_file_list test_list
|
||||
g++ -O3 -Wall -Wextra -fno-strict-aliasing boot.cc -o tangle
|
||||
|
||||
type_list: boot.cc [0-9]*.cc
|
||||
@# assumes struct decl has space before '{'
|
||||
@grep -h "^struct .* {" [0-9]*.cc |perl -pwe 's/(struct *[^ ]*).*/$$1;/' > type_list
|
||||
@grep -h typedef [0-9]*.cc >> type_list
|
||||
|
||||
function_list: boot.cc [0-9]*.cc
|
||||
@# assumes function decl has space before '{'
|
||||
@grep -h "^[^ #].*) {" [0-9]*.cc |perl -pwe 's/ {.*/;/' > function_list
|
||||
|
||||
file_list: boot.cc [0-9]*.cc
|
||||
@ls [0-9]*.cc |grep -v "\.test\.cc$$" |perl -pwe 's/.*/#include "$$&"/' > file_list
|
||||
|
||||
test_file_list: [0-9]*.test.cc
|
||||
@ls [0-9]*.test.cc |perl -pwe 's/.*/#include "$$&"/' > test_file_list
|
||||
|
||||
test_list: [0-9]*.cc
|
||||
@grep -h "^[[:space:]]*void test_" [0-9]*.cc |perl -pwe 's/^\s*void (.*)\(\) {$$/$$1,/' > test_list
|
||||
|
||||
clean:
|
||||
rm -rf tangle *_list
|
|
@ -0,0 +1,18 @@
|
|||
" Highlighting wart's literate directives in C++ sources.
|
||||
function! HighlightTangledFile()
|
||||
if &ft == ""
|
||||
set ft=cpp
|
||||
endif
|
||||
syntax region wartTangle start=+:(+ skip=+".*"+ end=+)+
|
||||
highlight link wartTangle Delimiter
|
||||
syntax region wartTrace start="^+" end="$"
|
||||
highlight wartTrace ctermfg=darkgreen
|
||||
syntax region wartTraceAbsent start="^-" end="$"
|
||||
highlight wartTraceAbsent ctermfg=darkred
|
||||
syntax region wartTraceResult start="^=>" end="$"
|
||||
highlight wartTraceResult ctermfg=darkgreen cterm=bold
|
||||
syntax region wartComment start="# " end="$"
|
||||
highlight link wartComment Comment
|
||||
endfunction
|
||||
call HighlightTangledFile()
|
||||
autocmd BufReadPost,BufNewFile 0* call HighlightTangledFile()
|
Loading…
Reference in New Issue