diff options
Diffstat (limited to 'cpp/tangle')
-rw-r--r-- | cpp/tangle/000test.cc | 26 | ||||
-rw-r--r-- | cpp/tangle/001trace.cc | 338 | ||||
-rw-r--r-- | cpp/tangle/001trace.test.cc | 164 | ||||
-rw-r--r-- | cpp/tangle/002main.cc | 61 | ||||
-rw-r--r-- | cpp/tangle/030tangle.cc | 355 | ||||
-rw-r--r-- | cpp/tangle/030tangle.test.cc | 251 | ||||
-rw-r--r-- | cpp/tangle/boot.cc | 63 | ||||
-rw-r--r-- | cpp/tangle/makefile | 23 |
8 files changed, 1281 insertions, 0 deletions
diff --git a/cpp/tangle/000test.cc b/cpp/tangle/000test.cc new file mode 100644 index 00000000..2754b254 --- /dev/null +++ b/cpp/tangle/000test.cc @@ -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; \ + } diff --git a/cpp/tangle/001trace.cc b/cpp/tangle/001trace.cc new file mode 100644 index 00000000..a99951e0 --- /dev/null +++ b/cpp/tangle/001trace.cc @@ -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(); +} diff --git a/cpp/tangle/001trace.test.cc b/cpp/tangle/001trace.test.cc new file mode 100644 index 00000000..e0db457c --- /dev/null +++ b/cpp/tangle/001trace.test.cc @@ -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"); +} diff --git a/cpp/tangle/002main.cc b/cpp/tangle/002main.cc new file mode 100644 index 00000000..851811c8 --- /dev/null +++ b/cpp/tangle/002main.cc @@ -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; +} diff --git a/cpp/tangle/030tangle.cc b/cpp/tangle/030tangle.cc new file mode 100644 index 00000000..2dda8667 --- /dev/null +++ b/cpp/tangle/030tangle.cc @@ -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(" "+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; +} diff --git a/cpp/tangle/030tangle.test.cc b/cpp/tangle/030tangle.test.cc new file mode 100644 index 00000000..36ce2d1f --- /dev/null +++ b/cpp/tangle/030tangle.test.cc @@ -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(), " 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(), " 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(), " 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(), " 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(), " 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(), " run(\"abc def\\n\");"); lines.pop_front(); + CHECK_EQ(lines.front(), " CLEAR_TRACE;"); lines.pop_front(); + CHECK_EQ(lines.front(), " 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(), " 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(), " 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"); +} diff --git a/cpp/tangle/boot.cc b/cpp/tangle/boot.cc new file mode 100644 index 00000000..89f943a8 --- /dev/null +++ b/cpp/tangle/boot.cc @@ -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" diff --git a/cpp/tangle/makefile b/cpp/tangle/makefile new file mode 100644 index 00000000..3d938c09 --- /dev/null +++ b/cpp/tangle/makefile @@ -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 |