From 01b2852b653a81c4d5e197a0c52659c7e0dcaf5f Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Wed, 18 Feb 2015 14:48:19 -0800 Subject: 782 - promote literate version to canonical C++ version --- cpp/000organization | 111 ++++++++ cpp/000test.cc | 28 -- cpp/001test | 79 ++++++ cpp/001trace.cc | 351 ------------------------- cpp/001trace.test.cc | 164 ------------ cpp/002main.cc | 378 --------------------------- cpp/002main.test.cc | 29 --- cpp/002trace | 478 ++++++++++++++++++++++++++++++++++ cpp/002trace.tests | 164 ++++++++++++ cpp/009includes | 48 ++++ cpp/010vm | 145 +++++++++++ cpp/011load | 150 +++++++++++ cpp/012run | 41 +++ cpp/boot.cc | 66 ----- cpp/build_and_test_until | 7 + cpp/literate/000organization | 111 -------- cpp/literate/001test | 79 ------ cpp/literate/002trace | 478 ---------------------------------- cpp/literate/002trace.tests | 164 ------------ cpp/literate/009includes | 48 ---- cpp/literate/010vm | 145 ----------- cpp/literate/011load | 150 ----------- cpp/literate/012run | 41 --- cpp/literate/build_and_test_until | 7 - cpp/literate/makefile | 26 -- cpp/literate/tangle/000test.cc | 26 -- cpp/literate/tangle/001trace.cc | 338 ------------------------ cpp/literate/tangle/001trace.test.cc | 164 ------------ cpp/literate/tangle/002main.cc | 61 ----- cpp/literate/tangle/030tangle.cc | 355 ------------------------- cpp/literate/tangle/030tangle.test.cc | 251 ------------------ cpp/literate/tangle/boot.cc | 63 ----- cpp/literate/tangle/makefile | 23 -- cpp/literate/vimrc.vim | 18 -- cpp/makefile | 37 +-- cpp/tangle/000test.cc | 26 ++ cpp/tangle/001trace.cc | 338 ++++++++++++++++++++++++ cpp/tangle/001trace.test.cc | 164 ++++++++++++ cpp/tangle/002main.cc | 61 +++++ cpp/tangle/030tangle.cc | 355 +++++++++++++++++++++++++ cpp/tangle/030tangle.test.cc | 251 ++++++++++++++++++ cpp/tangle/boot.cc | 63 +++++ cpp/tangle/makefile | 23 ++ cpp/vimrc.vim | 18 ++ 44 files changed, 2541 insertions(+), 3582 deletions(-) create mode 100644 cpp/000organization delete mode 100644 cpp/000test.cc create mode 100644 cpp/001test delete mode 100644 cpp/001trace.cc delete mode 100644 cpp/001trace.test.cc delete mode 100644 cpp/002main.cc delete mode 100644 cpp/002main.test.cc create mode 100644 cpp/002trace create mode 100644 cpp/002trace.tests create mode 100644 cpp/009includes create mode 100644 cpp/010vm create mode 100644 cpp/011load create mode 100644 cpp/012run delete mode 100644 cpp/boot.cc create mode 100755 cpp/build_and_test_until delete mode 100644 cpp/literate/000organization delete mode 100644 cpp/literate/001test delete mode 100644 cpp/literate/002trace delete mode 100644 cpp/literate/002trace.tests delete mode 100644 cpp/literate/009includes delete mode 100644 cpp/literate/010vm delete mode 100644 cpp/literate/011load delete mode 100644 cpp/literate/012run delete mode 100755 cpp/literate/build_and_test_until delete mode 100644 cpp/literate/makefile delete mode 100644 cpp/literate/tangle/000test.cc delete mode 100644 cpp/literate/tangle/001trace.cc delete mode 100644 cpp/literate/tangle/001trace.test.cc delete mode 100644 cpp/literate/tangle/002main.cc delete mode 100644 cpp/literate/tangle/030tangle.cc delete mode 100644 cpp/literate/tangle/030tangle.test.cc delete mode 100644 cpp/literate/tangle/boot.cc delete mode 100644 cpp/literate/tangle/makefile delete mode 100644 cpp/literate/vimrc.vim create mode 100644 cpp/tangle/000test.cc create mode 100644 cpp/tangle/001trace.cc create mode 100644 cpp/tangle/001trace.test.cc create mode 100644 cpp/tangle/002main.cc create mode 100644 cpp/tangle/030tangle.cc create mode 100644 cpp/tangle/030tangle.test.cc create mode 100644 cpp/tangle/boot.cc create mode 100644 cpp/tangle/makefile create mode 100644 cpp/vimrc.vim (limited to 'cpp') diff --git a/cpp/000organization b/cpp/000organization new file mode 100644 index 00000000..ba82dd52 --- /dev/null +++ b/cpp/000organization @@ -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. diff --git a/cpp/000test.cc b/cpp/000test.cc deleted file mode 100644 index 1a731804..00000000 --- a/cpp/000test.cc +++ /dev/null @@ -1,28 +0,0 @@ -typedef void (*test_fn)(void); - -const test_fn Tests[] = { - #include "test_list" // auto-generated; see makefile -}; - -bool Passed = true; - -long Num_failures = 0; - -#define TEST(name) void test_##name() { Trace_file = #name; - -#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/001test b/cpp/001test new file mode 100644 index 00000000..45ec591d --- /dev/null +++ b/cpp/001test @@ -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 +using std::istream; +using std::ostream; +using std::iostream; +using std::cin; +using std::cout; +using std::cerr; + +#include diff --git a/cpp/001trace.cc b/cpp/001trace.cc deleted file mode 100644 index 0de7c687..00000000 --- a/cpp/001trace.cc +++ /dev/null @@ -1,351 +0,0 @@ -bool Hide_warnings = false; - -struct trace_stream { - vector > > past_lines; // [(layer label, frame, line)] - unordered_map 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 >(curr_layer, pair(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 > >::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 << "
start
\n"; - for (vector > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) { - if (p->first != layer) continue; - dump << "
"; - dump << p->second.second; - dump << "
\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. -string Trace_file; -static string Trace_dir = ".traces/"; -struct lease_tracer { - lease_tracer() { Trace_stream = new trace_stream; } - ~lease_tracer() { -//? cerr << "write to file? " << Trace_file << "$\n"; //? 1 - if (!Trace_file.empty()) { -//? cerr << "writing\n"; //? 1 - ofstream fout((Trace_dir+Trace_file).c_str()); - fout << Trace_stream->readable_contents(""); - fout.close(); - } - delete Trace_stream, Trace_stream = NULL; Trace_file = ""; } -}; - -#define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; - -void trace_all(const string& label, const list& in) { - for (list::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 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 > >::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 expected_lines = split(expected, ""); -//? cout << "aa check2 " << layer << ": " << expected_lines.size() << '\n'; //? 1 - 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 layers = split(layer, ","); - for (vector > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { - if (!layer.empty() && !any_prefix_match(layers, p->first)) - continue; -//? cout << "comparing " << p->second.second << '\n'; //? 1 -//? cout << " with " << expected_lines[curr_expected_line] << '\n'; //? 1 - 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 layers = split(layer, ","); - for (vector > >::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 layers = split(layer, ","); - for (vector > >::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 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 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 layers = split(layer, ","); - for (vector > >::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 split(string s, string delim) { - vector 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& 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::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/001trace.test.cc b/cpp/001trace.test.cc deleted file mode 100644 index e0db457c..00000000 --- a/cpp/001trace.test.cc +++ /dev/null @@ -1,164 +0,0 @@ -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 result = split("", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], ""); -} - -void test_split_returns_entire_input_when_no_delim() { - vector result = split("abc", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], "abc"); -} - -void test_split_works() { - vector result = split("abc,def", ","); - CHECK_EQ(result.size(), 2); - CHECK_EQ(result[0], "abc"); - CHECK_EQ(result[1], "def"); -} - -void test_split_works2() { - vector 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 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/002main.cc b/cpp/002main.cc deleted file mode 100644 index c6b72104..00000000 --- a/cpp/002main.cc +++ /dev/null @@ -1,378 +0,0 @@ -//? enum recipe_number { -//? // arithmetic -//? add = 1, -//? subtract, -//? multiply, -//? divide, -//? divide_with_remainder, -//? -//? // boolean -//? conjunction, // 'and' is a keyword -//? disjunction, // 'or' is a keyword -//? negation, // 'not' is a keyword -//? -//? // comparison -//? equal, -//? not_equal, -//? less_than, -//? greater_than, -//? lesser_or_equal, -//? greater_or_equal, -//? -//? // control flow -//? jump, -//? jump_if, -//? jump_unless, -//? -//? // data management: scalars, arrays, and_records (structs) -//? copy, -//? get, -//? get_address, -//? index, -//? index_address, -//? allocate, -//? size, -//? length, -//? -//? // tagged_values require one primitive -//? save_type, -//? -//? // code points for characters -//? character_to_integer, -//? integer_to_character, -//? -//? // multiprocessing -//? fork, -//? fork_helper, -//? sleep, -//? assert, -//? assert_false, -//? -//? // cursor-based (text mode) interaction -//? cursor_mode, -//? retro_mode, -//? clear_host_screen, -//? clear_line_on_host, -//? cursor_on_host, -//? cursor_on_host_to_next_line, -//? cursor_up_on_host, -//? cursor_down_on_host, -//? cursor_right_on_host, -//? cursor_left_on_host, -//? print_character_to_host, -//? read_key_from_host, -//? -//? // debugging aides -//? _dump_memory, -//? _dump_trace, -//? _start_tracing, -//? _stop_tracing, -//? _dump_routine, -//? _dump_channel, -//? _quit, -//? _wait_for_key_from_host, -//? _print, -//? -//? // first-class continuations -//? current_continuation, -//? continue_from, -//? -//? // user-defined functions -//? next_input, -//? input, -//? prepare_reply, -//? reply, -//? -//? Max_primitive_recipe, -//? }; - -struct property { - vector values; -}; - -typedef int type_number; - -struct type_info { - int size; - bool is_address; - bool is_record; - bool is_array; - vector target; // only if is_address - vector > elements; // only if is_record or is_array - type_info() :size(0) {} -}; - -unordered_map Type_number; -unordered_map Type; -int Next_type_number = 1; - -unordered_map Memory; - -struct reagent { - string name; - vector types; - vector > properties; - reagent(string s) { - istringstream in(s); - name = slurp_until(in, ':'); - types.push_back(Type_number[slurp_until(in, '/')]); // todo: multiple types - } - string to_string() { - ostringstream out; - out << "{name: \"" << name << "\", type: " << types[0] << "}"; // todo: properties - return out.str(); - } -}; - -const int idle = 0; - -struct instruction { - bool is_label; - string label; // only if is_label - recipe_number operation; // only if !is_label - vector ingredients; // only if !is_label - vector products; // only if !is_label - instruction() :is_label(false), operation(idle) {} - void clear() { is_label=false; label.clear(); operation=idle; ingredients.clear(); products.clear(); } -}; - -struct recipe { - vector step; -}; - -typedef int recipe_number; -unordered_map Recipe_number; -unordered_map Recipe; -int Next_recipe_number = 1; - -void load(string filename) { -} - -void run(string function_name) { -} - -void setup_memory() { - Memory.clear(); -} - -void setup_types() { - Type.clear(); Type_number.clear(); - Type_number["literal"] = 0; - Next_type_number = 1; - int integer = Type_number["integer"] = Next_type_number++; - Type[integer].size = 1; -} - -void setup_recipes() { - Recipe.clear(); Recipe_number.clear(); - Recipe_number["idle"] = 0; - Next_recipe_number = 1; - Recipe_number["copy"] = Next_recipe_number++; -//? dbg << "AAA " << Recipe_number["copy"] << '\n'; //? 1 -} - -void compile(string form) { - istringstream in(form); - in >> std::noskipws; - - string _recipe = next_word(in); -//? cout << _recipe << '\n'; //? 1 - if (_recipe != "recipe") - raise << "top-level forms must be of the form 'recipe _name_ [ _instruction_ ... ]'\n"; - - string recipe_name = next_word(in); -//? cout << '^' << recipe_name << "$\n"; //? 1 - if (recipe_name.empty()) - raise << "empty recipe name in " << form << '\n'; - int r = Recipe_number[recipe_name] = Next_recipe_number++; - -//? string foo = next_word(in); //? 1 -//? cout << '^' << foo << "$ (" << foo.size() << ")\n"; //? 1 - if (next_word(in) != "[") - raise << "recipe body must begin with '['\n"; - - skip_newlines(in); - - instruction curr; - while (next_instruction(in, &curr)) { - Recipe[r].step.push_back(curr); - } -} - -bool next_instruction(istream& in, instruction* curr) { - curr->clear(); - if (in.eof()) return false; - skip_whitespace(in); if (in.eof()) return false; - skip_newlines(in); if (in.eof()) return false; - -//? vector ingredients, products; //? 1 - vector words; - while (in.peek() != '\n') { - skip_whitespace(in); if (in.eof()) return false; - string word = next_word(in); if (in.eof()) return false; - words.push_back(word); - skip_whitespace(in); if (in.eof()) return false; - } - skip_newlines(in); if (in.eof()) return false; -//? cout << "words: "; //? 1 -//? for (vector::iterator p = words.begin(); p != words.end(); ++p) { //? 1 -//? cout << *p << "; "; //? 1 -//? } //? 1 -//? cout << '\n'; //? 1 -//? return true; //? 1 - - if (words.size() == 1 && *(words[0].end()-1) == ':') { - curr->is_label = true; - words[0].erase(words[0].end()-1); - curr->label = words[0]; - trace("parse") << "label: " << curr->label; - return !in.eof(); - } - - vector::iterator p = words.begin(); - if (find(words.begin(), words.end(), "<-") != words.end()) { -//? cout << "instruction yields products\n"; //? 1 - for (; *p != "<-"; ++p) { - if (*p == ",") continue; -//? cout << "product: " << *p << '\n'; //? 1 -//? products.push_back(*p); //? 1 - curr->products.push_back(reagent(*p)); - } - ++p; // skip <- - } -//? return true; //? 1 - - curr->operation = Recipe_number[*p]; ++p; - - for (; p != words.end(); ++p) { - if (*p == ",") continue; -//? cout << "ingredient: " << *p << '\n'; //? 1 - curr->ingredients.push_back(reagent(*p)); - } - - trace("parse") << "instruction: " << curr->operation; - for (vector::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p) { - trace("parse") << " ingredient: " << p->to_string(); - } - for (vector::iterator p = curr->products.begin(); p != curr->products.end(); ++p) { - trace("parse") << " product: " << p->to_string(); - } - return !in.eof(); -} - -string next_word(istream& in) { - ostringstream out; -//? cout << "1: " << (int)in.peek() << '\n'; //? 1 - skip_whitespace(in); -//? cout << "2: " << (int)in.peek() << '\n'; //? 1 - slurp_word(in, out); -//? cout << "3: " << (int)in.peek() << '\n'; //? 1 -//? cout << out.str() << '\n'; //? 1 - return out.str(); -} - -void slurp_word(istream& in, ostream& out) { - char c; - if (in.peek() == ',') { - in >> c; - out << c; - return; - } - while (in >> c) { -//? cout << c << '\n'; //? 1 - if (isspace(c) || c == ',') { -//? cout << " space\n"; //? 1 - in.putback(c); - break; - } - out << c; - } -} - -void skip_whitespace(istream& in) { - while (isspace(in.peek()) && in.peek() != '\n') { -//? cout << "skip\n"; //? 1 - in.get(); - } -} - -void skip_newlines(istream& in) { - while (in.peek() == '\n') - in.get(); -} - -void skip_comma(istream& in) { - skip_whitespace(in); - if (in.peek() == ',') in.get(); - skip_whitespace(in); -} - -string slurp_until(istream& in, char delim) { - ostringstream out; - char c; - while (in >> c) { - if (c == delim) { - break; - } - out << c; - } - return out.str(); -} - - - -//// test harness - -void run_tests() { - for (unsigned long i=0; i < sizeof(Tests)/sizeof(Tests[0]); ++i) { - START_TRACING_UNTIL_END_OF_SCOPE; - setup(); - CLEAR_TRACE; - (*Tests[i])(); - verify(); - } - cerr << '\n'; - if (Num_failures > 0) - cerr << Num_failures << " failure" - << (Num_failures > 1 ? "s" : "") - << '\n'; -} - -void verify() { - if (!Passed) - ; - else - cerr << "."; -} - -void setup() { - setup_memory(); - setup_types(); - setup_recipes(); - Passed = true; -} - -string time_string() { - time_t t; - time(&t); - char buffer[10]; - if (!strftime(buffer, 10, "%H:%M:%S", localtime(&t))) - return ""; - return buffer; -} - -} // end namespace mu - -int main(int argc, const char* argv[]) { - if (argc == 2 && string(argv[1]) == "test") { - mu::run_tests(); - return 0; - } - - for (int i = 1; i < argc; ++i) { - mu::load(argv[i]); - } - mu::run("main"); -} - -namespace mu { diff --git a/cpp/002main.test.cc b/cpp/002main.test.cc deleted file mode 100644 index 72dbe41f..00000000 --- a/cpp/002main.test.cc +++ /dev/null @@ -1,29 +0,0 @@ -TEST(parse) - compile("recipe main [\n" - " 1:integer <- copy 23:literal\n" - "]\n"); - CHECK_TRACE_CONTENTS("parse", "instruction: 1 ingredient: {name: \"23\", type: 0} product: {name: \"1\", type: 1}"); -} - -TEST(parse_label) - compile("recipe main [\n" - " foo:\n" - "]\n"); - CHECK_TRACE_CONTENTS("parse", "label: foo"); - CHECK_TRACE_DOESNT_CONTAIN("parse", "instruction: 1"); -} - -TEST(parse2) - compile("recipe main [\n" - " 1:integer, 2:integer <- copy 23:literal\n" - "]\n"); - CHECK_TRACE_CONTENTS("parse", "instruction: 1 ingredient: {name: \"23\", type: 0} product: {name: \"1\", type: 1} product: {name: \"2\", type: 1}"); -} - -TEST(literal) - compile("recipe main [\n" - " 1:integer <- copy 23:literal\n" - "]\n"); - run("main"); - CHECK_EQ(Memory[1], 23); -} diff --git a/cpp/002trace b/cpp/002trace new file mode 100644 index 00000000..4d584cb1 --- /dev/null +++ b/cpp/002trace @@ -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 > > past_lines; // [(layer label, frame, line)] + unordered_map 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 >(curr_layer, pair(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 > >::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 << "
start
\n"; + for (vector > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) { + if (p->first != layer) continue; + dump << "
"; + dump << p->second.second; + dump << "
\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& in) { + for (list::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 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 > >::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 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 layers = split(layer, ","); + for (vector > >::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 layers = split(layer, ","); + for (vector > >::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 layers = split(layer, ","); + for (vector > >::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 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 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 layers = split(layer, ","); + for (vector > >::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 split(string s, string delim) { + vector 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& 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::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 + +#include +using std::string; +#define NOT_FOUND string::npos + +#include +using std::vector; +#include +using std::list; +#include +using std::pair; + +#include +using std::tr1::unordered_map; +#include +using std::tr1::unordered_set; +#include + +#include +using std::istream; +using std::ostream; +using std::iostream; +using std::cin; +using std::cout; +using std::cerr; + +#include +using std::stringstream; +using std::istringstream; +using std::ostringstream; + +#include +using std::ifstream; +using std::ofstream; + +#define unused __attribute__((unused)) diff --git a/cpp/002trace.tests b/cpp/002trace.tests new file mode 100644 index 00000000..00705346 --- /dev/null +++ b/cpp/002trace.tests @@ -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 result = split("", ","); + CHECK_EQ(result.size(), 1); + CHECK_EQ(result[0], ""); +} + +void test_split_returns_entire_input_when_no_delim() { + vector result = split("abc", ","); + CHECK_EQ(result.size(), 1); + CHECK_EQ(result[0], "abc"); +} + +void test_split_works() { + vector result = split("abc,def", ","); + CHECK_EQ(result.size(), 2); + CHECK_EQ(result[0], "abc"); + CHECK_EQ(result[1], "def"); +} + +void test_split_works2() { + vector 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 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/009includes b/cpp/009includes new file mode 100644 index 00000000..5c9a8d65 --- /dev/null +++ b/cpp/009includes @@ -0,0 +1,48 @@ +// Some common includes needed all over the place. +// More tightly-targeted includes show up in other files. + +:(before "End Includes") +#include +#include +#include +#include +#include +#include +#include +#include +using std::vector; +#include +using std::list; +#include +using std::stack; +#include +using std::pair; + +#include +using std::tr1::unordered_map; +#include +using std::tr1::unordered_set; +#include + +#include +using std::string; +#define NOT_FOUND string::npos // macro doesn't complain about redef + +#include +using std::istream; +using std::ostream; +using std::iostream; +using std::cin; +using std::cout; +using std::cerr; + +#include +using std::stringstream; +using std::istringstream; +using std::ostringstream; + +#include +using std::ifstream; +using std::ofstream; + +#define unused __attribute__((unused)) diff --git a/cpp/010vm b/cpp/010vm new file mode 100644 index 00000000..3b8a7c96 --- /dev/null +++ b/cpp/010vm @@ -0,0 +1,145 @@ +// A program is a book of 'recipes' (functions) + +:(after "Types") +typedef int recipe_number; +:(before "End Globals") +unordered_map Recipe_number; +unordered_map Recipe; +int Next_recipe_number = 1; + +:(before "End Types") +// Recipes are lists of instructions. To run a recipe, the computer runs its +// instructions. +struct recipe { + vector steps; +}; + +:(before "struct recipe") +// Each instruction is either of the form: +// product1, product2, product3, ... <- operation ingredient1, ingredient2, ingredient3, ... +// or just a single 'label' followed by a colon +// label: +// Labels don't do anything, they're just waypoints. +struct instruction { + bool is_label; + string label; // only if is_label + recipe_number operation; // only if !is_label + vector ingredients; // only if !is_label + vector products; // only if !is_label + instruction(); + void clear(); +}; + +:(before "struct instruction") +// Ingredients and products are a single species -- a reagent. Reagents refer +// either to numbers or to locations in memory along with 'type' tags telling +// us how to interpret them. They also can contain arbitrary other lists of +// properties besides types, but we're getting ahead of ourselves. +struct reagent { + string name; + vector types; + vector > properties; + reagent(string s); + string to_string(); +}; + +:(before "struct reagent") +struct property { + vector values; +}; + +:(before "End Globals") +// Locations refer to a common 'memory'. Each location can store a number. +unordered_map Memory; +:(before "End Setup") + Memory.clear(); + +:(after "Types") +// Types encode how the numbers stored in different parts of memory are +// interpreted. A location tagged as a 'character' type will interpret the +// number 97 as the letter 'a', while a different location of type 'integer' +// would not. +// +// Unlike most computers today, mu stores types in a single big table, shared +// by all the mu programs on the computer. This is useful in providing a +// seamless experience to help understand arbitrary mu programs. +typedef int type_number; +:(before "End Globals") +unordered_map Type_number; +unordered_map Type; +int Next_type_number = 1; +:(code) +void setup_types() { + Type.clear(); Type_number.clear(); + Type_number["literal"] = 0; + Next_type_number = 1; + int integer = Type_number["integer"] = Next_type_number++; + Type[integer].size = 1; +} +:(before "End Setup") + setup_types(); + +:(before "End Types") +// You can construct arbitrary new types. Types are either 'records', containing +// 'fields' of other types, 'array's of a single type repeated over and over, +// or 'addresses' pointing at a location elsewhere in memory. +struct type_info { + int size; + bool is_address; + bool is_record; + bool is_array; + vector target; // only if is_address + vector > elements; // only if is_record or is_array + type_info() :size(0) {} +}; + +:(code) +// It's all very well to construct recipes out of other recipes, but we need +// to know how to do *something* out of the box. For the following +// recipes there are only codes, no entries in the book, because mu just knows +// what to do for them. +void setup_recipes() { + Recipe.clear(); Recipe_number.clear(); + Recipe_number["idle"] = 0; + Next_recipe_number = 1; + Recipe_number["copy"] = Next_recipe_number++; +} +:(before "End Types") +const int idle = 0; // always the first entry in the recipe book +:(before "End Setup") + setup_recipes(); + + + +:(code) +// Helpers + instruction::instruction() :is_label(false), operation(idle) {} + void instruction::clear() { is_label=false; label.clear(); operation=idle; ingredients.clear(); products.clear(); } + + // Reagents have the form :::...///... + reagent::reagent(string s) { + istringstream in(s); + name = slurp_until(in, ':'); + types.push_back(Type_number[slurp_until(in, '/')]); // todo: multiple types + } + string reagent::to_string() { + ostringstream out; + out << "{name: \"" << name << "\", type: " << types[0] << "}"; // todo: properties + return out.str(); + } + +string slurp_until(istream& in, char delim) { + ostringstream out; + char c; + while (in >> c) { + if (c == delim) { + break; + } + out << c; + } + return out.str(); +} + + + +:(before "End Setup") diff --git a/cpp/011load b/cpp/011load new file mode 100644 index 00000000..f2855e5f --- /dev/null +++ b/cpp/011load @@ -0,0 +1,150 @@ +// It's often convenient to express recipes in a textual fashion. +:(scenarios add_recipe) +:(scenario first_recipe) +recipe main [ + 1:integer <- copy 23:literal +] ++parse: instruction: 1 ++parse: ingredient: {name: "23", type: 0} ++parse: product: {name: "1", type: 1} + +:(code) +int add_recipe(string form) { + istringstream in(form); + in >> std::noskipws; + + string _recipe = next_word(in); + if (_recipe != "recipe") + raise << "top-level forms must be of the form 'recipe _name_ [ _instruction_ ... ]'\n"; + + string recipe_name = next_word(in); + if (recipe_name.empty()) + raise << "empty recipe name in " << form << '\n'; + int r = Recipe_number[recipe_name] = Next_recipe_number++; + + if (next_word(in) != "[") + raise << "recipe body must begin with '['\n"; + + skip_newlines(in); + + instruction curr; + while (next_instruction(in, &curr)) { + Recipe[r].steps.push_back(curr); + } + return r; +} + +bool next_instruction(istream& in, instruction* curr) { + curr->clear(); + if (in.eof()) return false; + skip_whitespace(in); if (in.eof()) return false; + skip_newlines(in); if (in.eof()) return false; + + vector words; + while (in.peek() != '\n') { + skip_whitespace(in); if (in.eof()) return false; + string word = next_word(in); if (in.eof()) return false; + words.push_back(word); + skip_whitespace(in); if (in.eof()) return false; + } + skip_newlines(in); if (in.eof()) return false; + + if (words.size() == 1 && *(words[0].end()-1) == ':') { + curr->is_label = true; + words[0].erase(words[0].end()-1); + curr->label = words[0]; + trace("parse") << "label: " << curr->label; + return !in.eof(); + } + + vector::iterator p = words.begin(); + if (find(words.begin(), words.end(), "<-") != words.end()) { + for (; *p != "<-"; ++p) { + if (*p == ",") continue; + curr->products.push_back(reagent(*p)); + } + ++p; // skip <- + } + + curr->operation = Recipe_number[*p]; ++p; + + for (; p != words.end(); ++p) { + if (*p == ",") continue; + curr->ingredients.push_back(reagent(*p)); + } + + trace("parse") << "instruction: " << curr->operation; + for (vector::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p) { + trace("parse") << " ingredient: " << p->to_string(); + } + for (vector::iterator p = curr->products.begin(); p != curr->products.end(); ++p) { + trace("parse") << " product: " << p->to_string(); + } + return !in.eof(); +} + +string next_word(istream& in) { + ostringstream out; + skip_whitespace(in); + slurp_word(in, out); + return out.str(); +} + +void slurp_word(istream& in, ostream& out) { + char c; + if (in.peek() == ',') { + in >> c; + out << c; + return; + } + while (in >> c) { + if (isspace(c) || c == ',') { + in.putback(c); + break; + } + out << c; + } +} + +void skip_whitespace(istream& in) { + while (isspace(in.peek()) && in.peek() != '\n') { + in.get(); + } +} + +void skip_newlines(istream& in) { + while (in.peek() == '\n') + in.get(); +} + +void skip_comma(istream& in) { + skip_whitespace(in); + if (in.peek() == ',') in.get(); + skip_whitespace(in); +} + +:(scenario parse_label) +recipe main [ + foo: +] ++parse: label: foo +-parse: instruction: 1 + +:(scenario parse_multiple_products) +recipe main [ + 1:integer, 2:integer <- copy 23:literal +] ++parse: instruction: 1 ++parse: ingredient: {name: "23", type: 0} ++parse: product: {name: "1", type: 1} ++parse: product: {name: "2", type: 1} + +:(scenario parse_multiple_ingredients) +recipe main [ + 1:integer, 2:integer <- copy 23:literal, 4:integer +] ++parse: instruction: 1 ++parse: ingredient: {name: "23", type: 0} ++parse: ingredient: {name: "4", type: 1} ++parse: product: {name: "1", type: 1} ++parse: product: {name: "2", type: 1} diff --git a/cpp/012run b/cpp/012run new file mode 100644 index 00000000..f7b5469a --- /dev/null +++ b/cpp/012run @@ -0,0 +1,41 @@ +:(scenarios run) +:(scenario copy_literal) +recipe main [ + 1:integer <- copy 23:literal +] ++run: instruction 0 ++run: ingredient 23 ++mem: storing in location 1 + +:(code) +void run(string form) { + run(add_recipe(form)); +} + +void run(recipe_number r) { + vector& instructions(Recipe[r].steps); + int n = 0; + vector::iterator p; + for (n = 0, p = instructions.begin(); p != instructions.end(); ++p, ++n) { + trace("run") << "instruction " << n; + switch (p->operation) { + case 1: { // copy + int arg = to_int(p->ingredients[0].name); + trace("run") << " ingredient " << arg; + int dest = to_int(p->products[0].name); + trace("mem") << " storing in location " << dest; + Memory[dest] = arg; + break; + } + default: + raise << "undefined operation " << p->operation; + } + } +} + +int to_int(string n) { + char* end = NULL; + int result = strtol(n.c_str(), &end, /*any base*/0); + assert(*end == '\0'); + return result; +} diff --git a/cpp/boot.cc b/cpp/boot.cc deleted file mode 100644 index 64a0c58e..00000000 --- a/cpp/boot.cc +++ /dev/null @@ -1,66 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -using std::vector; -#include -using std::list; -#include -using std::stack; -#include -using std::pair; - -#include -using std::tr1::unordered_map; -#include -using std::tr1::unordered_set; -#include - -#include -using std::string; -const size_t NOT_FOUND = string::npos; - -#include -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include -using std::stringstream; -using std::istringstream; -using std::ostringstream; - -#include -using std::ifstream; -using std::ofstream; - - - -// interpreter decls -namespace mu { - -#include "type_list" - -#include "function_list" - -// interpreter impl - -#include "file_list" - -// interpreter tests - -#include "test_file_list" - -} // namespace mu diff --git a/cpp/build_and_test_until b/cpp/build_and_test_until new file mode 100755 index 00000000..3661c93a --- /dev/null +++ b/cpp/build_and_test_until @@ -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 diff --git a/cpp/literate/000organization b/cpp/literate/000organization deleted file mode 100644 index ba82dd52..00000000 --- a/cpp/literate/000organization +++ /dev/null @@ -1,111 +0,0 @@ -// 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. diff --git a/cpp/literate/001test b/cpp/literate/001test deleted file mode 100644 index 45ec591d..00000000 --- a/cpp/literate/001test +++ /dev/null @@ -1,79 +0,0 @@ -// 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 -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include diff --git a/cpp/literate/002trace b/cpp/literate/002trace deleted file mode 100644 index 4d584cb1..00000000 --- a/cpp/literate/002trace +++ /dev/null @@ -1,478 +0,0 @@ -// 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 > > past_lines; // [(layer label, frame, line)] - unordered_map 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 >(curr_layer, pair(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 > >::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 << "
start
\n"; - for (vector > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) { - if (p->first != layer) continue; - dump << "
"; - dump << p->second.second; - dump << "
\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& in) { - for (list::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 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 > >::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 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 layers = split(layer, ","); - for (vector > >::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 layers = split(layer, ","); - for (vector > >::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 layers = split(layer, ","); - for (vector > >::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 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 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 layers = split(layer, ","); - for (vector > >::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 split(string s, string delim) { - vector 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& 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::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 - -#include -using std::string; -#define NOT_FOUND string::npos - -#include -using std::vector; -#include -using std::list; -#include -using std::pair; - -#include -using std::tr1::unordered_map; -#include -using std::tr1::unordered_set; -#include - -#include -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include -using std::stringstream; -using std::istringstream; -using std::ostringstream; - -#include -using std::ifstream; -using std::ofstream; - -#define unused __attribute__((unused)) diff --git a/cpp/literate/002trace.tests b/cpp/literate/002trace.tests deleted file mode 100644 index 00705346..00000000 --- a/cpp/literate/002trace.tests +++ /dev/null @@ -1,164 +0,0 @@ -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 result = split("", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], ""); -} - -void test_split_returns_entire_input_when_no_delim() { - vector result = split("abc", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], "abc"); -} - -void test_split_works() { - vector result = split("abc,def", ","); - CHECK_EQ(result.size(), 2); - CHECK_EQ(result[0], "abc"); - CHECK_EQ(result[1], "def"); -} - -void test_split_works2() { - vector 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 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/literate/009includes b/cpp/literate/009includes deleted file mode 100644 index 5c9a8d65..00000000 --- a/cpp/literate/009includes +++ /dev/null @@ -1,48 +0,0 @@ -// Some common includes needed all over the place. -// More tightly-targeted includes show up in other files. - -:(before "End Includes") -#include -#include -#include -#include -#include -#include -#include -#include -using std::vector; -#include -using std::list; -#include -using std::stack; -#include -using std::pair; - -#include -using std::tr1::unordered_map; -#include -using std::tr1::unordered_set; -#include - -#include -using std::string; -#define NOT_FOUND string::npos // macro doesn't complain about redef - -#include -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include -using std::stringstream; -using std::istringstream; -using std::ostringstream; - -#include -using std::ifstream; -using std::ofstream; - -#define unused __attribute__((unused)) diff --git a/cpp/literate/010vm b/cpp/literate/010vm deleted file mode 100644 index 3b8a7c96..00000000 --- a/cpp/literate/010vm +++ /dev/null @@ -1,145 +0,0 @@ -// A program is a book of 'recipes' (functions) - -:(after "Types") -typedef int recipe_number; -:(before "End Globals") -unordered_map Recipe_number; -unordered_map Recipe; -int Next_recipe_number = 1; - -:(before "End Types") -// Recipes are lists of instructions. To run a recipe, the computer runs its -// instructions. -struct recipe { - vector steps; -}; - -:(before "struct recipe") -// Each instruction is either of the form: -// product1, product2, product3, ... <- operation ingredient1, ingredient2, ingredient3, ... -// or just a single 'label' followed by a colon -// label: -// Labels don't do anything, they're just waypoints. -struct instruction { - bool is_label; - string label; // only if is_label - recipe_number operation; // only if !is_label - vector ingredients; // only if !is_label - vector products; // only if !is_label - instruction(); - void clear(); -}; - -:(before "struct instruction") -// Ingredients and products are a single species -- a reagent. Reagents refer -// either to numbers or to locations in memory along with 'type' tags telling -// us how to interpret them. They also can contain arbitrary other lists of -// properties besides types, but we're getting ahead of ourselves. -struct reagent { - string name; - vector types; - vector > properties; - reagent(string s); - string to_string(); -}; - -:(before "struct reagent") -struct property { - vector values; -}; - -:(before "End Globals") -// Locations refer to a common 'memory'. Each location can store a number. -unordered_map Memory; -:(before "End Setup") - Memory.clear(); - -:(after "Types") -// Types encode how the numbers stored in different parts of memory are -// interpreted. A location tagged as a 'character' type will interpret the -// number 97 as the letter 'a', while a different location of type 'integer' -// would not. -// -// Unlike most computers today, mu stores types in a single big table, shared -// by all the mu programs on the computer. This is useful in providing a -// seamless experience to help understand arbitrary mu programs. -typedef int type_number; -:(before "End Globals") -unordered_map Type_number; -unordered_map Type; -int Next_type_number = 1; -:(code) -void setup_types() { - Type.clear(); Type_number.clear(); - Type_number["literal"] = 0; - Next_type_number = 1; - int integer = Type_number["integer"] = Next_type_number++; - Type[integer].size = 1; -} -:(before "End Setup") - setup_types(); - -:(before "End Types") -// You can construct arbitrary new types. Types are either 'records', containing -// 'fields' of other types, 'array's of a single type repeated over and over, -// or 'addresses' pointing at a location elsewhere in memory. -struct type_info { - int size; - bool is_address; - bool is_record; - bool is_array; - vector target; // only if is_address - vector > elements; // only if is_record or is_array - type_info() :size(0) {} -}; - -:(code) -// It's all very well to construct recipes out of other recipes, but we need -// to know how to do *something* out of the box. For the following -// recipes there are only codes, no entries in the book, because mu just knows -// what to do for them. -void setup_recipes() { - Recipe.clear(); Recipe_number.clear(); - Recipe_number["idle"] = 0; - Next_recipe_number = 1; - Recipe_number["copy"] = Next_recipe_number++; -} -:(before "End Types") -const int idle = 0; // always the first entry in the recipe book -:(before "End Setup") - setup_recipes(); - - - -:(code) -// Helpers - instruction::instruction() :is_label(false), operation(idle) {} - void instruction::clear() { is_label=false; label.clear(); operation=idle; ingredients.clear(); products.clear(); } - - // Reagents have the form :::...///... - reagent::reagent(string s) { - istringstream in(s); - name = slurp_until(in, ':'); - types.push_back(Type_number[slurp_until(in, '/')]); // todo: multiple types - } - string reagent::to_string() { - ostringstream out; - out << "{name: \"" << name << "\", type: " << types[0] << "}"; // todo: properties - return out.str(); - } - -string slurp_until(istream& in, char delim) { - ostringstream out; - char c; - while (in >> c) { - if (c == delim) { - break; - } - out << c; - } - return out.str(); -} - - - -:(before "End Setup") diff --git a/cpp/literate/011load b/cpp/literate/011load deleted file mode 100644 index f2855e5f..00000000 --- a/cpp/literate/011load +++ /dev/null @@ -1,150 +0,0 @@ -// It's often convenient to express recipes in a textual fashion. -:(scenarios add_recipe) -:(scenario first_recipe) -recipe main [ - 1:integer <- copy 23:literal -] -+parse: instruction: 1 -+parse: ingredient: {name: "23", type: 0} -+parse: product: {name: "1", type: 1} - -:(code) -int add_recipe(string form) { - istringstream in(form); - in >> std::noskipws; - - string _recipe = next_word(in); - if (_recipe != "recipe") - raise << "top-level forms must be of the form 'recipe _name_ [ _instruction_ ... ]'\n"; - - string recipe_name = next_word(in); - if (recipe_name.empty()) - raise << "empty recipe name in " << form << '\n'; - int r = Recipe_number[recipe_name] = Next_recipe_number++; - - if (next_word(in) != "[") - raise << "recipe body must begin with '['\n"; - - skip_newlines(in); - - instruction curr; - while (next_instruction(in, &curr)) { - Recipe[r].steps.push_back(curr); - } - return r; -} - -bool next_instruction(istream& in, instruction* curr) { - curr->clear(); - if (in.eof()) return false; - skip_whitespace(in); if (in.eof()) return false; - skip_newlines(in); if (in.eof()) return false; - - vector words; - while (in.peek() != '\n') { - skip_whitespace(in); if (in.eof()) return false; - string word = next_word(in); if (in.eof()) return false; - words.push_back(word); - skip_whitespace(in); if (in.eof()) return false; - } - skip_newlines(in); if (in.eof()) return false; - - if (words.size() == 1 && *(words[0].end()-1) == ':') { - curr->is_label = true; - words[0].erase(words[0].end()-1); - curr->label = words[0]; - trace("parse") << "label: " << curr->label; - return !in.eof(); - } - - vector::iterator p = words.begin(); - if (find(words.begin(), words.end(), "<-") != words.end()) { - for (; *p != "<-"; ++p) { - if (*p == ",") continue; - curr->products.push_back(reagent(*p)); - } - ++p; // skip <- - } - - curr->operation = Recipe_number[*p]; ++p; - - for (; p != words.end(); ++p) { - if (*p == ",") continue; - curr->ingredients.push_back(reagent(*p)); - } - - trace("parse") << "instruction: " << curr->operation; - for (vector::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p) { - trace("parse") << " ingredient: " << p->to_string(); - } - for (vector::iterator p = curr->products.begin(); p != curr->products.end(); ++p) { - trace("parse") << " product: " << p->to_string(); - } - return !in.eof(); -} - -string next_word(istream& in) { - ostringstream out; - skip_whitespace(in); - slurp_word(in, out); - return out.str(); -} - -void slurp_word(istream& in, ostream& out) { - char c; - if (in.peek() == ',') { - in >> c; - out << c; - return; - } - while (in >> c) { - if (isspace(c) || c == ',') { - in.putback(c); - break; - } - out << c; - } -} - -void skip_whitespace(istream& in) { - while (isspace(in.peek()) && in.peek() != '\n') { - in.get(); - } -} - -void skip_newlines(istream& in) { - while (in.peek() == '\n') - in.get(); -} - -void skip_comma(istream& in) { - skip_whitespace(in); - if (in.peek() == ',') in.get(); - skip_whitespace(in); -} - -:(scenario parse_label) -recipe main [ - foo: -] -+parse: label: foo --parse: instruction: 1 - -:(scenario parse_multiple_products) -recipe main [ - 1:integer, 2:integer <- copy 23:literal -] -+parse: instruction: 1 -+parse: ingredient: {name: "23", type: 0} -+parse: product: {name: "1", type: 1} -+parse: product: {name: "2", type: 1} - -:(scenario parse_multiple_ingredients) -recipe main [ - 1:integer, 2:integer <- copy 23:literal, 4:integer -] -+parse: instruction: 1 -+parse: ingredient: {name: "23", type: 0} -+parse: ingredient: {name: "4", type: 1} -+parse: product: {name: "1", type: 1} -+parse: product: {name: "2", type: 1} diff --git a/cpp/literate/012run b/cpp/literate/012run deleted file mode 100644 index f7b5469a..00000000 --- a/cpp/literate/012run +++ /dev/null @@ -1,41 +0,0 @@ -:(scenarios run) -:(scenario copy_literal) -recipe main [ - 1:integer <- copy 23:literal -] -+run: instruction 0 -+run: ingredient 23 -+mem: storing in location 1 - -:(code) -void run(string form) { - run(add_recipe(form)); -} - -void run(recipe_number r) { - vector& instructions(Recipe[r].steps); - int n = 0; - vector::iterator p; - for (n = 0, p = instructions.begin(); p != instructions.end(); ++p, ++n) { - trace("run") << "instruction " << n; - switch (p->operation) { - case 1: { // copy - int arg = to_int(p->ingredients[0].name); - trace("run") << " ingredient " << arg; - int dest = to_int(p->products[0].name); - trace("mem") << " storing in location " << dest; - Memory[dest] = arg; - break; - } - default: - raise << "undefined operation " << p->operation; - } - } -} - -int to_int(string n) { - char* end = NULL; - int result = strtol(n.c_str(), &end, /*any base*/0); - assert(*end == '\0'); - return result; -} diff --git a/cpp/literate/build_and_test_until b/cpp/literate/build_and_test_until deleted file mode 100755 index 3661c93a..00000000 --- a/cpp/literate/build_and_test_until +++ /dev/null @@ -1,7 +0,0 @@ -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 diff --git a/cpp/literate/makefile b/cpp/literate/makefile deleted file mode 100644 index 97187383..00000000 --- a/cpp/literate/makefile +++ /dev/null @@ -1,26 +0,0 @@ -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 diff --git a/cpp/literate/tangle/000test.cc b/cpp/literate/tangle/000test.cc deleted file mode 100644 index 2754b254..00000000 --- a/cpp/literate/tangle/000test.cc +++ /dev/null @@ -1,26 +0,0 @@ -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/literate/tangle/001trace.cc b/cpp/literate/tangle/001trace.cc deleted file mode 100644 index a99951e0..00000000 --- a/cpp/literate/tangle/001trace.cc +++ /dev/null @@ -1,338 +0,0 @@ -bool Hide_warnings = false; - -struct trace_stream { - vector > > past_lines; // [(layer label, frame, line)] - unordered_map 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 >(curr_layer, pair(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 > >::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 << "
start
\n"; - for (vector > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) { - if (p->first != layer) continue; - dump << "
"; - dump << p->second.second; - dump << "
\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& in) { - for (list::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 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 > >::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 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 layers = split(layer, ","); - for (vector > >::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 layers = split(layer, ","); - for (vector > >::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 layers = split(layer, ","); - for (vector > >::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 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 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 layers = split(layer, ","); - for (vector > >::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 split(string s, string delim) { - vector 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& 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::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/literate/tangle/001trace.test.cc b/cpp/literate/tangle/001trace.test.cc deleted file mode 100644 index e0db457c..00000000 --- a/cpp/literate/tangle/001trace.test.cc +++ /dev/null @@ -1,164 +0,0 @@ -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 result = split("", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], ""); -} - -void test_split_returns_entire_input_when_no_delim() { - vector result = split("abc", ","); - CHECK_EQ(result.size(), 1); - CHECK_EQ(result[0], "abc"); -} - -void test_split_works() { - vector result = split("abc,def", ","); - CHECK_EQ(result.size(), 2); - CHECK_EQ(result[0], "abc"); - CHECK_EQ(result[1], "def"); -} - -void test_split_works2() { - vector 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 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/literate/tangle/002main.cc b/cpp/literate/tangle/002main.cc deleted file mode 100644 index 851811c8..00000000 --- a/cpp/literate/tangle/002main.cc +++ /dev/null @@ -1,61 +0,0 @@ -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/literate/tangle/030tangle.cc b/cpp/literate/tangle/030tangle.cc deleted file mode 100644 index 2dda8667..00000000 --- a/cpp/literate/tangle/030tangle.cc +++ /dev/null @@ -1,355 +0,0 @@ -#include - -int tangle_files_in_cwd() { - list result; - vector files = sorted_files(".", /*no extension*/ ""); - for (vector::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::iterator p = result.begin(); p != result.end(); ++p) - cout << *p << '\n'; - return 0; -} - -void tangle(istream& in, list& 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& out) { - list 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 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::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::iterator next = balancing_curly(target); - list 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::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& l, list::iterator exemplar) { - string curr_indent = indent(*exemplar); - for (list::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::iterator balancing_curly(list::iterator orig) { - list::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& lines, list& 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& lines, list& result) { - result.push_back(" "+Toplevel+"(\""+input_lines(lines)+"\");"); -} - -void emit_result_checking_session(list& lines, list& 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& 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& 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::iterator find_substr(list& in, const string& pat) { - for (list::iterator p = in.begin(); p != in.end(); ++p) - if (p->find(pat) != NOT_FOUND) - return p; - return in.end(); -} - -list::iterator find_trim(list& in, const string& pat) { - for (list::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& lines, const string& pat) { - for (list::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& lines) { - for (list::const_iterator p = lines.begin(); p != lines.end(); ++p) - if (!is_input(*p)) return true; - return false; -} - -#include -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 - -vector sorted_files(const char* dirname, const char* ext) { - vector 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/literate/tangle/030tangle.test.cc b/cpp/literate/tangle/030tangle.test.cc deleted file mode 100644 index 36ce2d1f..00000000 --- a/cpp/literate/tangle/030tangle.test.cc +++ /dev/null @@ -1,251 +0,0 @@ -void test_tangle() { - istringstream in("a\nb\nc\n:(before b)\nd\n"); - list dummy; - tangle(in, dummy); - CHECK_TRACE_CONTENTS("tangle", "adbc"); -} - -void test_tangle2() { - istringstream in("a\nb\nc\n:(after b)\nd\n"); - list 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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/literate/tangle/boot.cc b/cpp/literate/tangle/boot.cc deleted file mode 100644 index 89f943a8..00000000 --- a/cpp/literate/tangle/boot.cc +++ /dev/null @@ -1,63 +0,0 @@ -// 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 -#include -#include -#include -#include -#include -#include -using std::vector; -#include -using std::list; -#include -using std::stack; -#include -using std::pair; - -#include -using std::tr1::unordered_map; -#include -using std::tr1::unordered_set; -#include - -#include -using std::string; -const size_t NOT_FOUND = string::npos; - -#include -using std::istream; -using std::ostream; -using std::iostream; -using std::cin; -using std::cout; -using std::cerr; - -#include -using std::stringstream; -using std::istringstream; -using std::ostringstream; - -#include -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/literate/tangle/makefile b/cpp/literate/tangle/makefile deleted file mode 100644 index 3d938c09..00000000 --- a/cpp/literate/tangle/makefile +++ /dev/null @@ -1,23 +0,0 @@ -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 diff --git a/cpp/literate/vimrc.vim b/cpp/literate/vimrc.vim deleted file mode 100644 index 65fd4575..00000000 --- a/cpp/literate/vimrc.vim +++ /dev/null @@ -1,18 +0,0 @@ -" 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() diff --git a/cpp/makefile b/cpp/makefile index 0dcdbce5..97187383 100644 --- a/cpp/makefile +++ b/cpp/makefile @@ -1,25 +1,26 @@ -mu: makefile type_list function_list file_list test_file_list test_list - g++ -O3 -Wall -Wextra -fno-strict-aliasing boot.cc -o mu +mu: makefile tangle/tangle mu.cc + g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc -o mu + -@./mu test -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 +# 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 -function_list: boot.cc [0-9]*.cc - @# assumes function decl has space before '{' - @grep -h "^[^ #].*) {" [0-9]*.cc |perl -pwe 's/ {.*/;/' > function_list - @grep -h "^[[:space:]]*TEST(" [0-9]*.cc |perl -pwe 's/^\s*TEST\((.*)\)$$/void test_$$1();/' >> function_list +tangle/tangle: + cd tangle && make -file_list: boot.cc [0-9]*.cc - @ls [0-9]*.cc |grep -v "\.test\.cc$$" |perl -pwe 's/.*/#include "$$&"/' > file_list +# auto-generated files; by convention they end in '_list'. +.PHONY: autogenerated_lists +autogenerated_lists: mu.cc function_list test_list -test_file_list: [0-9]*.test.cc - @ls [0-9]*.test.cc |perl -pwe 's/.*/#include "$$&"/' > test_file_list +function_list: mu.cc + @grep -h "^[^ #].*) {" mu.cc |perl -pwe 's/ {.*/;/' > function_list -test_list: [0-9]*.cc - @grep -h "^[[:space:]]*void test_" [0-9]*.cc |perl -pwe 's/^\s*void (.*)\(\) {$$/$$1,/' > test_list - @grep -h "^[[:space:]]*TEST(" [0-9]*.cc |perl -pwe 's/^\s*TEST\((.*)\)$$/test_$$1,/' >> test_list +test_list: mu.cc + @grep -h "^[[:space:]]*void test_" mu.cc |perl -pwe 's/^\s*void (.*)\(\) {.*/$$1,/' > test_list clean: - rm -rf mu* *_list + cd tangle && make clean + rm -rf mu.cc mu *_list 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 > > past_lines; // [(layer label, frame, line)] + unordered_map 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 >(curr_layer, pair(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 > >::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 << "
start
\n"; + for (vector > >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) { + if (p->first != layer) continue; + dump << "
"; + dump << p->second.second; + dump << "
\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& in) { + for (list::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 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 > >::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 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 layers = split(layer, ","); + for (vector > >::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 layers = split(layer, ","); + for (vector > >::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 layers = split(layer, ","); + for (vector > >::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 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 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 layers = split(layer, ","); + for (vector > >::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 split(string s, string delim) { + vector 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& 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::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 result = split("", ","); + CHECK_EQ(result.size(), 1); + CHECK_EQ(result[0], ""); +} + +void test_split_returns_entire_input_when_no_delim() { + vector result = split("abc", ","); + CHECK_EQ(result.size(), 1); + CHECK_EQ(result[0], "abc"); +} + +void test_split_works() { + vector result = split("abc,def", ","); + CHECK_EQ(result.size(), 2); + CHECK_EQ(result[0], "abc"); + CHECK_EQ(result[1], "def"); +} + +void test_split_works2() { + vector 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 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 + +int tangle_files_in_cwd() { + list result; + vector files = sorted_files(".", /*no extension*/ ""); + for (vector::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::iterator p = result.begin(); p != result.end(); ++p) + cout << *p << '\n'; + return 0; +} + +void tangle(istream& in, list& 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& out) { + list 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 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::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::iterator next = balancing_curly(target); + list 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::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& l, list::iterator exemplar) { + string curr_indent = indent(*exemplar); + for (list::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::iterator balancing_curly(list::iterator orig) { + list::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& lines, list& 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& lines, list& result) { + result.push_back(" "+Toplevel+"(\""+input_lines(lines)+"\");"); +} + +void emit_result_checking_session(list& lines, list& 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& 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& 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::iterator find_substr(list& in, const string& pat) { + for (list::iterator p = in.begin(); p != in.end(); ++p) + if (p->find(pat) != NOT_FOUND) + return p; + return in.end(); +} + +list::iterator find_trim(list& in, const string& pat) { + for (list::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& lines, const string& pat) { + for (list::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& lines) { + for (list::const_iterator p = lines.begin(); p != lines.end(); ++p) + if (!is_input(*p)) return true; + return false; +} + +#include +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 + +vector sorted_files(const char* dirname, const char* ext) { + vector 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 dummy; + tangle(in, dummy); + CHECK_TRACE_CONTENTS("tangle", "adbc"); +} + +void test_tangle2() { + istringstream in("a\nb\nc\n:(after b)\nd\n"); + list 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 +#include +#include +#include +#include +#include +#include +using std::vector; +#include +using std::list; +#include +using std::stack; +#include +using std::pair; + +#include +using std::tr1::unordered_map; +#include +using std::tr1::unordered_set; +#include + +#include +using std::string; +const size_t NOT_FOUND = string::npos; + +#include +using std::istream; +using std::ostream; +using std::iostream; +using std::cin; +using std::cout; +using std::cerr; + +#include +using std::stringstream; +using std::istringstream; +using std::ostringstream; + +#include +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 diff --git a/cpp/vimrc.vim b/cpp/vimrc.vim new file mode 100644 index 00000000..65fd4575 --- /dev/null +++ b/cpp/vimrc.vim @@ -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() -- cgit 1.4.1-2-gfad0